Skip to content
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PropertyGroup>
<VersionPrefix>7.0.0</VersionPrefix>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
<PreReleaseVersionIteration>1</PreReleaseVersionIteration>
<PreReleaseVersionIteration>2</PreReleaseVersionIteration>
<IncludeSourceRevisionInInformationalVersion>False</IncludeSourceRevisionInInformationalVersion>
<IsServicingBuild Condition="'$(PreReleaseVersionLabel)' == 'servicing'">true</IsServicingBuild>
<!--
Expand Down
48 changes: 35 additions & 13 deletions src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/MinimalApiGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model)
MSBuildLocator.RegisterDefaults();
}

if (!string.IsNullOrEmpty(modelTypeAndContextModel.DbContextFullName) && CalledFromCommandline)
{
EFValidationUtil.ValidateEFDependencies(ProjectContext.PackageDependencies, useSqlite: false);
}

var templateModel = new MinimalApiModel(modelTypeAndContextModel.ModelType, modelTypeAndContextModel.DbContextFullName, model.EndpintsClassName)
{
EndpointsName = model.EndpintsClassName,
Expand All @@ -96,10 +101,6 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model)
//endpoints file exists, use CodeAnalysis to add required clauses.
if (FileSystem.FileExists(endpointsFilePath))
{
if (CalledFromCommandline)
{
EFValidationUtil.ValidateEFDependencies(ProjectContext.PackageDependencies, useSqlite: false);
}
//get method block with the api endpoints.
string membersBlockText = await CodeGeneratorActionsService.ExecuteTemplate(GetTemplateName(model, existingEndpointsFile: true), TemplateFolders, templateModel);
var className = model.EndpintsClassName;
Expand All @@ -119,7 +120,7 @@ public async Task GenerateCode(MinimalApiGeneratorCommandLineModel model)
await CodeGeneratorActionsService.AddFileFromTemplateAsync(endpointsFilePath, GetTemplateName(model, existingEndpointsFile: false), TemplateFolders, templateModel);
Logger.LogMessage(string.Format(MessageStrings.AddedController, endpointsFilePath.Substring(AppInfo.ApplicationBasePath.Length)));
//add app.Map statement to Program.cs
await ModifyProgramCs(templateModel.MethodName, templateModel.EndpointsNamespace);
await ModifyProgramCs(templateModel);
}
}

Expand Down Expand Up @@ -153,7 +154,7 @@ internal async Task AddEndpointsMethod(string membersBlockText, string endpoints
//Get class syntax node to add members to the class
var docRoot = docEditor.OriginalRoot as CompilationUnitSyntax;
//create CodeFile just to add usings
var endpointsCodeFile = new CodeFile { Usings = new string[] { Constants.MicrosoftEntityFrameworkCorePackageName } };
var endpointsCodeFile = new CodeFile { Usings = new string[] { Constants.MicrosoftEntityFrameworkCorePackageName, templateModel.DbContextNamespace } };
var docBuilder = new DocumentBuilder(docEditor, endpointsCodeFile, ConsoleLogger);
var newRoot = docBuilder.AddUsings(new CodeChangeOptions());
var classNode = newRoot.DescendantNodes().FirstOrDefault(node => node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Identifier.ValueText.Contains(className));
Expand All @@ -176,7 +177,7 @@ internal async Task AddEndpointsMethod(string membersBlockText, string endpoints
//write to endpoints class path.
FileSystem.WriteAllText(endPointsDocument.FilePath, classFileTxt);
//add app.Map statement to Program.cs
await ModifyProgramCs(templateModel.MethodName, templateModel.EndpointsNamespace);
await ModifyProgramCs(templateModel);
}
}
}
Expand Down Expand Up @@ -230,32 +231,48 @@ internal static void ValidateModel(MinimalApiGeneratorCommandLineModel model)
}
}

internal async Task ModifyProgramCs(string mapMethodName, string endpointsNamespace)
internal async Task ModifyProgramCs(MinimalApiModel templateModel)
{
string endpointsNamespace = templateModel.EndpointsNamespace;
string mapMethodName = templateModel.MethodName;
var jsonText = GetMinimalApiCodeModifierConfig();
CodeModifierConfig minimalApiChangesConfig = JsonSerializer.Deserialize<CodeModifierConfig>(jsonText);
if (minimalApiChangesConfig != null)
{
//Getting Program.cs document
var programCsFile = minimalApiChangesConfig.Files.FirstOrDefault();
programCsFile.Usings = new string[] { endpointsNamespace };
var programType = ModelTypesLocator.GetType("<Program>$").FirstOrDefault() ?? ModelTypesLocator.GetType("Program").FirstOrDefault();
var project = Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.AssemblyName.Equals(ProjectContext.AssemblyName, StringComparison.OrdinalIgnoreCase));
var programDocument = GetUpdatedDocument(project, programType);
var docEditor = await DocumentEditor.CreateAsync(programDocument);

//Modifying Program.cs document
var docEditor = await DocumentEditor.CreateAsync(programDocument);
var docRoot = docEditor.OriginalRoot as CompilationUnitSyntax;
var docBuilder = new DocumentBuilder(docEditor, programCsFile, ConsoleLogger);
//adding usings
var newRoot = docBuilder.AddUsings(new CodeChangeOptions());
//add code snippets/changes.
if (programCsFile.Methods != null && programCsFile.Methods.Any())
{
var globalChanges = programCsFile.Methods.Where(x => x.Key.Equals("Global", StringComparison.OrdinalIgnoreCase)).First().Value;
foreach (var change in globalChanges.CodeChanges)
var addMethodMapping = programCsFile.Methods.Where(x => x.Key.Equals("Global", StringComparison.OrdinalIgnoreCase)).First().Value;
foreach (var change in addMethodMapping.CodeChanges)
{
change.Block = string.Format(change.Block, mapMethodName);
newRoot = DocumentBuilder.ApplyChangesToMethod(newRoot, new CodeSnippet[] {change}) as CompilationUnitSyntax;
}

if (templateModel.OpenAPI)
{
var builderVariable = ProjectModifierHelper.GetBuilderVariableIdentifierTransformation(newRoot.Members);
var openApiMethodChanges = programCsFile.Methods.Where(x => x.Key.Equals("OpenApi", StringComparison.OrdinalIgnoreCase)).First().Value;
if (builderVariable.HasValue)
{
(string oldValue, string newValue) = builderVariable.Value;
var filteredChanges = ProjectModifierHelper.UpdateVariables(openApiMethodChanges.CodeChanges, oldValue, newValue);
newRoot = DocumentBuilder.ApplyChangesToMethod(newRoot, filteredChanges) as CompilationUnitSyntax;
}
}
}
//replace root node with all the updates.
docEditor.ReplaceNode(docRoot, newRoot);
Expand All @@ -271,14 +288,19 @@ internal async Task ModifyProgramCs(string mapMethodName, string endpointsNamesp
//Need CodeAnalysis.Project for AddDocument method.
internal Document GetUpdatedDocument(Project project, ModelType type)
{
if (project!= null && type != null)
if (project != null && type != null)
{
string filePath = type.TypeSymbol?.Locations.FirstOrDefault()?.SourceTree?.FilePath;
string fileText = FileSystem.ReadAllText(filePath);
return project.AddDocument(filePath, fileText);
if (!string.IsNullOrEmpty(fileText))
{
return project.AddDocument(filePath, fileText);
}
}

return null;
}

private string GetMinimalApiCodeModifierConfig()
{
string jsonText = string.Empty;
Expand Down
31 changes: 31 additions & 0 deletions src/Scaffolding/VS.Web.CG.Mvc/Minimal Api/minimalApiChanges.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@
}
}
]
},
"OpenApi": {
"CodeChanges": [
{
"InsertAfter": "WebApplication.CreateBuilder.Services.AddControllers();",
"InsertBefore": [ "WebApplication.CreateBuilder.Services.AddSwaggerGen();", "var app = builder.Build();" ],
"Options": [ "OpenApi" ],
"Block": "WebApplication.CreateBuilder.Services.AddEndpointsApiExplorer()",
"CodeFormatting": {
"Newline": true
}
},
{
"InsertAfter": "WebApplication.CreateBuilder.Services.AddEndpointsApiExplorer();",
"InsertBefore": [ "var app = builder.Build();" ],
"Options": [ "OpenApi" ],
"Block": "WebApplication.CreateBuilder.Services.AddSwaggerGen()",
"CodeFormatting": {
"Newline": true
}
},
{
"Block": "if (app.Environment.IsDevelopment())\r\n{\r\n app.UseSwagger();\r\n app.UseSwaggerUI();\r\n}",
"Options": [ "OpenApi" ],
"InsertAfter": "var app = WebApplication.CreateBuilder.Build();",
"InsertBefore": [ "app.UseHttpsRedirection();" ],
"CodeFormatting": {
"Newline": true
}
}
]
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/Scaffolding/VS.Web.CG.Mvc/ModelMetadataUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ internal static async Task<ModelTypeAndContextModel> GetModelEFMetadataMinimalAs
ModelMetadata = null
};


if (!string.IsNullOrEmpty(commandLineModel.DataContextClass))
{
dataContext = ValidationUtil.ValidateType(commandLineModel.DataContextClass, "dataContext", modelTypesLocator, throwWhenNotFound: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class CodeChangeOptionStrings
public const string Skip = nameof(Skip);
public const string NonMinimalApp = nameof(NonMinimalApp);
public const string MinimalApp = nameof(MinimalApp);
public const string OpenApi = nameof(OpenApi);
}

public class CodeChangeOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public async Task ModifyProgramCsTests()
EndpointsNamespace = "MinimalApiTest",
MethodName = "MapCarEndpoints"
};
await minimalApiGenerator.ModifyProgramCs(minimalApiModel.MethodName, minimalApiModel.EndpointsNamespace);
await minimalApiGenerator.ModifyProgramCs(minimalApiModel);
string programCsText = fileSystem.ReadAllText("Program.cs");
Assert.Contains(minimalApiModel.MethodName, programCsText);
}
Expand Down