diff options
author | Damian Edwards <damian@damianedwards.com> | 2022-03-28 21:13:57 +0300 |
---|---|---|
committer | Damian Edwards <damian@damianedwards.com> | 2022-03-30 02:55:43 +0300 |
commit | 73abf36fc06bcbe90960d20d63f97906adae22af (patch) | |
tree | dae9927cdf73ffa5e53a6115e5b45cab090dfbab | |
parent | 7eb15474ba995903ee9c8be0eb2b7a75b8813719 (diff) |
Add option to project templates to use Program.Main instead of top-level statements (#40886)damianedwards/backport-UseProgramMain
Fixes #40944
54 files changed, 1364 insertions, 146 deletions
diff --git a/.editorconfig b/.editorconfig index d7ec0b83de..8583569cfc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -208,7 +208,7 @@ dotnet_diagnostic.IDE0044.severity = warning dotnet_diagnostic.IDE0073.severity = warning file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. -[**/{test,samples,perf}/**.{cs,vb}] +[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,scripts}/**.cs}] # CA1018: Mark attributes with AttributeUsageAttribute dotnet_diagnostic.CA1018.severity = suggestion # CA1507: Use nameof to express symbol names @@ -241,6 +241,10 @@ dotnet_diagnostic.CA1844.severity = suggestion dotnet_diagnostic.CA1845.severity = suggestion # CA1846: Prefer AsSpan over Substring dotnet_diagnostic.CA1846.severity = suggestion +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = suggestion +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = suggestion # CA2008: Do not create tasks without passing a TaskScheduler dotnet_diagnostic.CA2008.severity = suggestion # CA2012: Use ValueTask correctly diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json index 3c34f96f85..02abad32e7 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json index e891e5f2df..947fd0caa5 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json @@ -43,6 +43,12 @@ "useHttps": true } ], + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ], "disableHttpsSymbol": "NoHttps", "supportsDocker": true } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json index 254598a9cf..4b203fdd52 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json @@ -35,6 +35,21 @@ ], "modifiers": [ { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, + { "condition": "(!IndividualLocalAuth || UseLocalDB)", "exclude": [ "app.db" @@ -490,6 +505,13 @@ "datatype": "bool", "description": "If specified, skips the automatic restore of the project on create.", "defaultValue": "false" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs new file mode 100644 index 0000000000..92eb45d80a --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Program.Main.cs @@ -0,0 +1,169 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (OrganizationalAuth) +#if (MultiOrgAuth) +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +#endif +using Microsoft.AspNetCore.Authorization; +#endif +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if(MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (IndividualLocalAuth) +using BlazorServerWeb_CSharp.Areas.Identity; +#endif +using BlazorServerWeb_CSharp.Data; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext<ApplicationDbContext>(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores<ApplicationDbContext>(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddControllersWithViews() + .AddMicrosoftIdentityUI(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy + options.FallbackPolicy = options.DefaultPolicy; + }); + + #elif (WindowsAuth) + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + + #endif + builder.Services.AddRazorPages(); + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddServerSideBlazor() + .AddMicrosoftIdentityConsentHandler(); + #else + builder.Services.AddServerSideBlazor(); + #endif + #if (IndividualLocalAuth) + builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>(); + #endif + builder.Services.AddSingleton<WeatherForecastService>(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + + #endif + + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + app.UseAuthorization(); + + #endif + #if (OrganizationalAuth || IndividualAuth) + app.MapControllers(); + #endif + app.MapBlazorHub(); + app.MapFallbackToPage("/_Host"); + + app.Run(); + } +}
\ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json index 93b2c5a3bc..3d2007dc58 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json @@ -95,6 +95,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } } } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json index 9005a4d171..2ed03203a4 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json @@ -47,6 +47,10 @@ "text": "_Progressive Web Application" }, "isVisible": "true" + }, + { + "id": "UseProgramMain", + "isVisible": true } ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json index cd44728a48..9b261b76d2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json @@ -95,6 +95,24 @@ ] }, { + "condition": "(!UseProgramMain)", + "exclude": [ + "Server/Program.Main.cs", + "Client/Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Server/Program.cs", + "Client/Program.cs" + ], + "rename": { + "Server/Program.Main.cs": "Server/Program.cs", + "Client/Program.Main.cs": "Client/Program.cs" + } + }, + { "condition": "(!IndividualLocalAuth || UseLocalDB)", "exclude": [ "Server/app.db" @@ -591,6 +609,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "tags": { diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs new file mode 100644 index 0000000000..8b870e5dc9 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.Main.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Components.Web; +#if (!NoAuth && Hosted) +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +#endif +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +#if (Hosted) +using ComponentsWebAssembly_CSharp.Client; +#else +using ComponentsWebAssembly_CSharp; +#endif + +namespace Company.WebApplication1; + +public class Program +{ + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add<App>("#app"); + builder.RootComponents.Add<HeadOutlet>("head::after"); + + #if (!Hosted || NoAuth) + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + #else + builder.Services.AddHttpClient("ComponentsWebAssembly_CSharp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); + + // Supply HttpClient instances that include access tokens when making requests to the server project + builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ComponentsWebAssembly_CSharp.ServerAPI")); + #endif + #if(!NoAuth) + + #endif + #if (IndividualLocalAuth) + #if (Hosted) + builder.Services.AddApiAuthorization(); + #else + builder.Services.AddOidcAuthentication(options => + { + #if(MissingAuthority) + // Configure your authentication provider options here. + // For more information, see https://aka.ms/blazor-standalone-auth + #endif + builder.Configuration.Bind("Local", options.ProviderOptions); + }); + #endif + #endif + #if (IndividualB2CAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication); + #if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("https://qualified.domain.name/api.id.uri/api-scope"); + #endif + }); + #endif + #if(OrganizationalAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); + #if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/api-scope"); + #endif + }); + #endif + + await builder.Build().RunAsync(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs new file mode 100644 index 0000000000..31835439cd --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.Main.cs @@ -0,0 +1,125 @@ +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth) +using Microsoft.AspNetCore.Authentication; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication.JwtBearer; +#endif +using Microsoft.AspNetCore.ResponseCompression; +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +#endif +#if (IndividualLocalAuth) +using ComponentsWebAssembly_CSharp.Server.Data; +using ComponentsWebAssembly_CSharp.Server.Models; +#endif + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext<ApplicationDbContext>(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores<ApplicationDbContext>(); + + builder.Services.AddIdentityServer() + .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(); + + builder.Services.AddAuthentication() + .AddIdentityServerJwt(); + #endif + #if (OrganizationalAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + + builder.Services.AddControllersWithViews(); + builder.Services.AddRazorPages(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + #if (IndividualLocalAuth) + app.UseMigrationsEndPoint(); + #endif + app.UseWebAssemblyDebugging(); + } + else + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + #endif + } + + #if (RequiresHttps) + app.UseHttpsRedirection(); + + #endif + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + + #if (IndividualLocalAuth) + app.UseIdentityServer(); + #endif + #if (OrganizationalAuth || IndividualAuth) + app.UseAuthentication(); + #endif + #if (!NoAuth) + app.UseAuthorization(); + + #endif + + app.MapRazorPages(); + app.MapControllers(); + app.MapFallbackToFile("index.html"); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json index d97858472b..6f4f4fb893 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json @@ -27,6 +27,10 @@ "NoHttps": { "longName": "no-https", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json index 751a95c5b2..f30c4753e2 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/ide.host.json @@ -20,5 +20,11 @@ "useHttps": true } ], + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json index 3b7edb411a..c85130f420 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json @@ -29,6 +29,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -156,6 +171,13 @@ "datatype": "bool", "defaultValue": "false", "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth." + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs new file mode 100644 index 0000000000..6a10649982 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.Main.cs @@ -0,0 +1,14 @@ +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + var app = builder.Build(); + + app.MapGet("/", () => "Hello World!"); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json index 684bc1e734..ded3cbf35f 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/dotnetcli.host.json @@ -14,6 +14,10 @@ "ExcludeLaunchSettings": { "longName": "exclude-launch-settings", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json index 2cb4190951..5c3a869512 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/ide.host.json @@ -2,5 +2,11 @@ "$schema": "http://json.schemastore.org/vs-2017.3.host", "order": 500, "icon": "ide/gRPC.png", - "supportsDocker": true + "supportsDocker": true, + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json index 2fd14ec925..fc4288d873 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/.template.config/template.json @@ -26,6 +26,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -102,6 +117,13 @@ "fallbackVariableName": "kestrelHttpsPortGenerated" }, "replaces": "5001" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs new file mode 100644 index 0000000000..ec1af1a7e9 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/GrpcService-CSharp/Program.Main.cs @@ -0,0 +1,25 @@ +using GrpcService_CSharp.Services; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Additional configuration is required to successfully run gRPC on macOS. + // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 + + // Add services to the container. + builder.Services.AddGrpc(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + app.MapGrpcService<GreeterService>(); + app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + + app.Run(); + } +}
\ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json index 6ce1fbe6eb..a00ae64f28 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json index f176b8df2a..0dc542e09b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/ide.host.json @@ -45,7 +45,10 @@ } ], "symbolInfo": [ - + { + "id": "UseProgramMain", + "isVisible": true + } ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json index d7226586d3..989dbb8ab8 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json @@ -94,6 +94,21 @@ "exclude": [ "Data/SqlServer/**" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -406,6 +421,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs new file mode 100644 index 0000000000..3a1a1d68cc --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.Main.cs @@ -0,0 +1,155 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Identity; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (IndividualLocalAuth) +using Company.WebApplication1.Data; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth || MultiOrgAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext<ApplicationDbContext>(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores<ApplicationDbContext>(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth) + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #elif (IndividualB2CAuth) + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #elif (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages(); + #else + builder.Services.AddRazorPages(); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + #endif + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapRazorPages(); + #if (IndividualB2CAuth || OrganizationalAuth) + app.MapControllers(); + #endif + + app.Run(); + } +}
\ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json index 3c34f96f85..02abad32e7 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "CallsMicrosoftGraph": { "longName": "calls-graph", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json index 12bb6ec5db..995a75bea9 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/ide.host.json @@ -45,7 +45,10 @@ } ], "symbolInfo": [ - + { + "id": "UseProgramMain", + "isVisible": true + } ], "disableHttpsSymbol": "NoHttps" } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json index b67fe4f719..558a4e818d 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json @@ -90,6 +90,21 @@ "exclude": [ "Data/SqlServer/**" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -402,6 +417,13 @@ "GenerateApiOrGraph": { "type": "computed", "value": "(GenerateApi || GenerateGraph)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs new file mode 100644 index 0000000000..5fda9092d0 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-CSharp/Program.Main.cs @@ -0,0 +1,159 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Identity; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Mvc.Authorization; +#endif +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +using Microsoft.Identity.Web.UI; +#endif +#if (MultiOrgAuth) +using Microsoft.IdentityModel.Tokens; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (IndividualLocalAuth) +using Company.WebApplication1.Data; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth || MultiOrgAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (IndividualLocalAuth) + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext<ApplicationDbContext>(options => + #if (UseLocalDB) + options.UseSqlServer(connectionString)); + #else + options.UseSqlite(connectionString)); + #endif + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores<ApplicationDbContext>(); + #elif (OrganizationalAuth) + #if (GenerateApiOrGraph) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + #if (GenerateApi) + var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' '); + + #endif + builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + #if (OrganizationalAuth) + + builder.Services.AddControllersWithViews(options => + { + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + options.Filters.Add(new AuthorizeFilter(policy)); + }); + #else + builder.Services.AddControllersWithViews(); + #endif + #if (OrganizationalAuth || IndividualB2CAuth) + builder.Services.AddRazorPages() + .AddMicrosoftIdentityUI(); + #endif + #if (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + builder.Services.AddRazorPages(); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (IndividualLocalAuth) + if (app.Environment.IsDevelopment()) + { + app.UseMigrationsEndPoint(); + } + else + #else + if (!app.Environment.IsDevelopment()) + #endif + { + app.UseExceptionHandler("/Home/Error"); + #if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + #else + } + #endif + app.UseStaticFiles(); + + app.UseRouting(); + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + #if (OrganizationalAuth || IndividualAuth) + app.MapRazorPages(); + #endif + + app.Run(); + } +}
\ No newline at end of file diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json index 9b97a61820..79217f1680 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json @@ -85,6 +85,10 @@ "DisableOpenAPI": { "longName": "no-openapi", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json index c8fdd64b6a..19355a4eec 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/ide.host.json @@ -55,6 +55,10 @@ "invertBoolean": true, "isVisible": true, "defaultValue": true + }, + { + "id": "UseProgramMain", + "isVisible": true } ], "disableHttpsSymbol": "NoHttps" diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json index ee1f9c886b..5c9b53aedc 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json @@ -38,6 +38,21 @@ ] }, { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain && !UseMinimalAPIs)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, + { "condition": "(UseMinimalAPIs)", "exclude": [ "Controllers/WeatherForecastController.cs", @@ -364,6 +379,13 @@ "UseControllers": { "type": "computed", "value": "(!UseMinimalAPIs)" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs new file mode 100644 index 0000000000..a0c9ad67e8 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Program.Main.cs @@ -0,0 +1,95 @@ +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +#endif +#if (WindowsAuth) +using Microsoft.AspNetCore.Authentication.Negotiate; +#endif +#if (GenerateGraph) +using Graph = Microsoft.Graph; +#endif +#if (OrganizationalAuth || IndividualB2CAuth) +using Microsoft.Identity.Web; +#endif +#if (OrganizationalAuth || IndividualB2CAuth || GenerateGraph || WindowsAuth) + +#endif +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + #if (OrganizationalAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() + #if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + #endif + #if (GenerateGraph) + .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) + #endif + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); + #endif + #elif (IndividualB2CAuth) + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + #if (GenerateApi) + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); + #else + .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C")); + #endif + #endif + + builder.Services.AddControllers(); + #if (EnableOpenAPI) + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + #endif + #if (WindowsAuth) + + builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) + .AddNegotiate(); + + builder.Services.AddAuthorization(options => + { + // By default, all incoming requests will be authorized according to the default policy. + options.FallbackPolicy = options.DefaultPolicy; + }); + #endif + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + #if (EnableOpenAPI) + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + #endif + #if (RequiresHttps) + + app.UseHttpsRedirection(); + #endif + + #if (OrganizationalAuth || IndividualAuth || WindowsAuth) + app.UseAuthentication(); + #endif + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json index b1cf98e39b..36493a3a4a 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/dotnetcli.host.json @@ -11,6 +11,10 @@ "ExcludeLaunchSettings": { "longName": "exclude-launch-settings", "shortName": "" + }, + "UseProgramMain": { + "longName": "use-program-main", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json index 13a025d034..59f260a583 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/ide.host.json @@ -3,4 +3,10 @@ "order": 300, "icon": "ide/Worker.png", "supportsDocker": true, + "symbolInfo": [ + { + "id": "UseProgramMain", + "isVisible": true + } + ] } diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json index 19358bf86a..d2789afed3 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/.template.config/template.json @@ -30,6 +30,21 @@ "exclude": [ "Properties/launchSettings.json" ] + }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } } ] } @@ -67,6 +82,13 @@ "datatype": "bool", "description": "If specified, skips the automatic restore of the project on create.", "defaultValue": "false" + }, + "UseProgramMain": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Do not use top-level statements", + "description": "Whether to generate an explicit Program class and Main method instead of top-level statements." } }, "primaryOutputs": [ diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs new file mode 100644 index 0000000000..d69747f38d --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Worker-CSharp/Program.Main.cs @@ -0,0 +1,18 @@ +using Company.Application1; + +namespace Company.WebApplication1; + +public class Program +{ + public static void Main(string[] args) + { + IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddHostedService<Worker>(); + }) + .Build(); + + host.Run(); + } +}
\ No newline at end of file diff --git a/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 new file mode 100644 index 0000000000..93127bb08b --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-AngularProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "angular" "angular --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 new file mode 100644 index 0000000000..3d9fdd64a7 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-BlazorProgramMain-Locally.ps1 @@ -0,0 +1,13 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +# This script packages, installs and creates a template to help with rapid iteration in the templating area. +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "blazorserver" "blazorserver --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 new file mode 100644 index 0000000000..7c8755a8bb --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-BlazorWasmProgramMain-Locally.ps1 @@ -0,0 +1,13 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +# This script packages, installs and creates a template to help with rapid iteration in the templating area. +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "blazorwasm" "blazorwasm --use-program-main --hosted --auth Individual" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 new file mode 100644 index 0000000000..7453063baf --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-EmptyWebProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "web" "web --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 new file mode 100644 index 0000000000..4224cf985d --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-RazorProgramMain-Locally.ps1 @@ -0,0 +1,9 @@ +#!/usr/bin/env powershell +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "webapp" "webapp -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 new file mode 100644 index 0000000000..df61a5a117 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-ReactProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "react" "react --use-program-main" "Microsoft.DotNet.Web.Spa.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 new file mode 100644 index 0000000000..076106d3e8 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-StarterwebProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "mvc" "mvc -au Individual --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 new file mode 100644 index 0000000000..41f794b7ea --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-WebApiProgamMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "webapi" "webapi --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 new file mode 100644 index 0000000000..9e0aa3d460 --- /dev/null +++ b/src/ProjectTemplates/scripts/Run-WorkerProgramMain-Locally.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +#requires -version 4 + +[CmdletBinding(PositionalBinding = $false)] +param() + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +. $PSScriptRoot\Test-Template.ps1 + +Test-Template "worker" "worker --use-program-main" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Test-Template.ps1 b/src/ProjectTemplates/scripts/Test-Template.ps1 index 349174ad3d..05cf0ef712 100644 --- a/src/ProjectTemplates/scripts/Test-Template.ps1 +++ b/src/ProjectTemplates/scripts/Test-Template.ps1 @@ -64,7 +64,9 @@ function Test-Template($templateName, $templateArgs, $templateNupkg, $isBlazorWa if ($isBlazorWasmHosted) { Push-Location Server } - dotnet.exe ef migrations add mvc + if ($templateArgs -match '-au') { + dotnet.exe ef migrations add mvc + } dotnet.exe publish --configuration Release Set-Location .\bin\Release\net6.0\publish if ($isBlazorWasm -eq $false) { diff --git a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs index 432c85043b..104f230402 100644 --- a/src/ProjectTemplates/test/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorServerTemplateTest.cs @@ -26,18 +26,26 @@ namespace Templates.Test [Fact] public Task BlazorServerTemplateWorks_NoAuth() => CreateBuildPublishAsync("blazorservernoauth"); + [Fact] + public Task BlazorServerTemplateWorks_ProgamMainNoAuth() => CreateBuildPublishAsync("blazorservernoauth", args: new [] { "--use-program-main" }); + [Theory] - [InlineData(true)] - [InlineData(false)] + [InlineData(true, null)] + [InlineData(true, new string[] { "--use-program-main" })] + [InlineData(false, null)] + [InlineData(false, new string[] { "--use-program-main" })] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] - public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "")); + public Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB, string[] args) => CreateBuildPublishAsync("blazorserverindividual" + (useLocalDB ? "uld" : "", args: args)); [Theory] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main", "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index 97a580dddd..dae6148d21 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -38,6 +38,9 @@ namespace Templates.Test public Task BlazorWasmHostedTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--hosted" }, serverProject: true); [Fact] + public Task BlazorWasmHostedTemplateWithProgamMainCanCreateBuildPublish() => CreateBuildPublishAsync("blazorhosted", args: new[] { "--use-program-main", "--hosted" }, serverProject: true); + + [Fact] public Task BlazorWasmStandalonePwaTemplateCanCreateBuildPublish() => CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { "--pwa" }); [Fact] diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index e90ccbf5b9..7468a587b4 100644 --- a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs +++ b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs @@ -38,17 +38,24 @@ namespace Templates.Test await EmtpyTemplateCore(languageOverride: null); } + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task EmptyWebTemplateProgramMainCSharp() + { + await EmtpyTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + } + [Fact] public async Task EmptyWebTemplateFSharp() { await EmtpyTemplateCore("F#"); } - private async Task EmtpyTemplateCore(string languageOverride) + private async Task EmtpyTemplateCore(string languageOverride, string[] args = null) { var project = await ProjectFactory.GetOrCreateProject("empty" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); - var createResult = await project.RunDotNetNewAsync("web", language: languageOverride); + var createResult = await project.RunDotNetNewAsync("web", args: args, language: languageOverride); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 diff --git a/src/ProjectTemplates/test/GrpcTemplateTest.cs b/src/ProjectTemplates/test/GrpcTemplateTest.cs index 585ac0bfb4..d26d0f8623 100644 --- a/src/ProjectTemplates/test/GrpcTemplateTest.cs +++ b/src/ProjectTemplates/test/GrpcTemplateTest.cs @@ -34,14 +34,17 @@ namespace Templates.Test } } - [ConditionalFact] + [ConditionalTheory] [SkipOnHelix("Not supported queues", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [SkipOnAlpine("https://github.com/grpc/grpc/issues/18338")] - public async Task GrpcTemplate() + [InlineData(true)] + [InlineData(false)] + public async Task GrpcTemplate(bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("grpc", Output); - var createResult = await project.RunDotNetNewAsync("grpc"); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("grpc", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(); diff --git a/src/ProjectTemplates/test/MvcTemplateTest.cs b/src/ProjectTemplates/test/MvcTemplateTest.cs index 47045d6972..a5d6bec380 100644 --- a/src/ProjectTemplates/test/MvcTemplateTest.cs +++ b/src/ProjectTemplates/test/MvcTemplateTest.cs @@ -43,11 +43,15 @@ namespace Templates.Test [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] public async Task MvcTemplate_NoAuthCSharp() => await MvcTemplateCore(languageOverride: null); - private async Task MvcTemplateCore(string languageOverride) + [ConditionalFact] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task MvcTemplate_ProgramMainNoAuthCSharp() => await MvcTemplateCore(languageOverride: null, new [] { "--use-program-main" }); + + private async Task MvcTemplateCore(string languageOverride, string[] args = null) { var project = await ProjectFactory.GetOrCreateProject("mvcnoauth" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output); - var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride); + var createResult = await project.RunDotNetNewAsync("mvc", language: languageOverride, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectExtension = languageOverride == "F#" ? "fsproj" : "csproj"; @@ -75,10 +79,10 @@ namespace Templates.Test Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); IEnumerable<string> menuLinks = new List<string> { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyFullUrl - }; + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyFullUrl + }; var footerLinks = new string[] { PageUrls.PrivacyFullUrl }; @@ -116,14 +120,17 @@ namespace Templates.Test } [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] + [InlineData(true, false)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task MvcTemplate_IndividualAuth(bool useLocalDB) + public async Task MvcTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output); - var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectFileContents = project.ReadFile($"{project.ProjectName}.csproj"); @@ -148,72 +155,72 @@ namespace Templates.Test // Note: if any links are updated here, RazorPagesTemplateTest.cs should be updated as well var pages = new List<Page> { - new Page - { - Url = PageUrls.ForgotPassword, - Links = new string [] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.HomeUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.DocsUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.PrivacyFullUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.PrivacyUrl - } - }, - new Page - { - Url = PageUrls.LoginUrl, - Links = new string[] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.ForgotPassword, - PageUrls.RegisterUrl, - PageUrls.ResendEmailConfirmation, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl } - }, - new Page - { - Url = PageUrls.RegisterUrl, - Links = new string [] { - PageUrls.HomeUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl + new Page + { + Url = PageUrls.ForgotPassword, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.HomeUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.DocsUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.PrivacyFullUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.PrivacyUrl + } + }, + new Page + { + Url = PageUrls.LoginUrl, + Links = new string[] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.ForgotPassword, + PageUrls.RegisterUrl, + PageUrls.ResendEmailConfirmation, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl } + }, + new Page + { + Url = PageUrls.RegisterUrl, + Links = new string [] { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyUrl, + PageUrls.RegisterUrl, + PageUrls.LoginUrl, + PageUrls.ExternalArticle, + PageUrls.PrivacyUrl + } } - } - }; + }; using (var aspNetProcess = project.StartBuiltProjectAsync()) { @@ -234,67 +241,44 @@ namespace Templates.Test } } - [ConditionalFact(Skip = "https://github.com/dotnet/aspnetcore/issues/25103")] - [SkipOnHelix("cert failure", Queues = "All.OSX")] + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] // Running these requires the rid-specific runtime pack to be available which is not consistent in all our platform builds. + [SkipOnHelix("cert failure", Queues = "All.OSX;" + HelixConstants.Windows10Arm64)] public async Task MvcTemplate_SingleFileExe() { // This test verifies publishing an MVC app as a single file exe works. We'll limit testing // this to a few operating systems to make our lives easier. - string runtimeIdentifer; - if (OperatingSystem.IsWindows()) - { - runtimeIdentifer = "win-x64"; - } - else if (OperatingSystem.IsLinux()) - { - runtimeIdentifer = "linux-x64"; - } - else - { - return; - } - + var runtimeIdentifer = "win-x64"; var project = await ProjectFactory.GetOrCreateProject("mvcsinglefileexe", Output); project.RuntimeIdentifier = runtimeIdentifer; - var createResult = await project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: true); + var createResult = await project.RunDotNetNewAsync("mvc"); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(additionalArgs: $"/p:PublishSingleFile=true -r {runtimeIdentifer}", noRestore: false); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - var pages = new[] + var menuLinks = new[] + { + PageUrls.HomeUrl, + PageUrls.HomeUrl, + PageUrls.PrivacyFullUrl + }; + + var footerLinks = new[] { PageUrls.PrivacyFullUrl }; + + var pages = new List<Page> { new Page { - // Verify a view from the app works Url = PageUrls.HomeUrl, - Links = new [] - { - PageUrls.HomeUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.DocsUrl, - PageUrls.PrivacyUrl - } + Links = menuLinks.Append(PageUrls.DocsUrl).Concat(footerLinks), }, new Page { - // Verify a view from a RCL (in this case IdentityUI) works - Url = PageUrls.RegisterUrl, - Links = new [] - { - PageUrls.HomeUrl, - PageUrls.RegisterUrl, - PageUrls.LoginUrl, - PageUrls.HomeUrl, - PageUrls.PrivacyUrl, - PageUrls.ExternalArticle, - PageUrls.PrivacyUrl - } - }, + Url = PageUrls.PrivacyFullUrl, + Links = menuLinks.Concat(footerLinks), + } }; using var aspNetProcess = project.StartPublishedProjectAsync(usePublishedAppHost: true); diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 1af0a68366..3412233f5d 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -34,13 +34,16 @@ namespace Templates.Test } } - [ConditionalFact] + [ConditionalTheory] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_NoAuth() + [InlineData(true)] + [InlineData(false)] + public async Task RazorPagesTemplate_NoAuth(bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output); - var createResult = await project.RunDotNetNewAsync("razor"); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("razor", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("razor", project, createResult)); var projectFileContents = ReadFile(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); @@ -104,14 +107,17 @@ namespace Templates.Test } [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) + public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB, bool useProgramMain) { var project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output); - var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB); + var args = useProgramMain ? new [] { "--use-program-main" } : null; + var createResult = await project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var projectFileContents = ReadFile(project.TemplateOutputDir, $"{project.ProjectName}.csproj"); @@ -226,12 +232,15 @@ namespace Templates.Test [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); [ConditionalTheory] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task RazorPagesTemplate_IdentityWeb_BuildsAndPublishes_WithSingleOrg(string auth, string[] args) => BuildAndPublishRazorPagesTemplate(auth: auth, args: args); private async Task<Project> BuildAndPublishRazorPagesTemplate(string auth, string[] args) diff --git a/src/ProjectTemplates/test/WebApiTemplateTest.cs b/src/ProjectTemplates/test/WebApiTemplateTest.cs index ddc6e18532..f727abcc92 100644 --- a/src/ProjectTemplates/test/WebApiTemplateTest.cs +++ b/src/ProjectTemplates/test/WebApiTemplateTest.cs @@ -35,10 +35,15 @@ namespace Templates.Test [ConditionalTheory] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/28090", Queues = HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] [InlineData("IndividualB2C", null)] + [InlineData("IndividualB2C", new string[] { "--use-program-main" })] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("IndividualB2C", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] + [InlineData("SingleOrg", new string[] { "--use-program-main" })] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] + [InlineData("SingleOrg", new string[] { "--use-program-main --calls-graph" })] public Task WebApiTemplateCSharp_IdentityWeb_BuildsAndPublishes(string auth, string[] args) => PublishAndBuildWebApiTemplate(languageOverride: null, auth: auth, args: args); [Fact] @@ -50,11 +55,18 @@ namespace Templates.Test [ConditionalFact] [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] - public async Task WebApiTemplateCSharp_WithoutOpenAPI() + public Task WebApiTemplateProgramMainCSharp() => WebApiTemplateCore(languageOverride: null, args: new [] { "--use-program-main" }); + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + [SkipOnHelix("Cert failure, https://github.com/dotnet/aspnetcore/issues/28090", Queues = "All.OSX;" + HelixConstants.Windows10Arm64 + HelixConstants.DebianArm64)] + public async Task WebApiTemplateCSharp_WithoutOpenAPI(bool useProgramMain) { var project = await FactoryFixture.GetOrCreateProject("webapinoopenapi", Output); - var createResult = await project.RunDotNetNewAsync("webapi", args: new[] { "--no-openapi" }); + var args = useProgramMain ? new[] { "--use-program-main --no-openapi" } : new[] { "--no-openapi" }; + var createResult = await project.RunDotNetNewAsync("webapi", args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var buildResult = await project.RunDotNetBuildAsync(); @@ -68,7 +80,7 @@ namespace Templates.Test await aspNetProcess.AssertNotFound("swagger"); } - private async Task<Project> PublishAndBuildWebApiTemplate(string languageOverride, string auth, string[] args) + private async Task<Project> PublishAndBuildWebApiTemplate(string languageOverride, string auth, string[] args = null) { var project = await FactoryFixture.GetOrCreateProject("webapi" + (languageOverride == "F#" ? "fsharp" : "csharp") + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); @@ -94,9 +106,9 @@ namespace Templates.Test return project; } - private async Task WebApiTemplateCore(string languageOverride) + private async Task WebApiTemplateCore(string languageOverride, string[] args = null) { - var project = await PublishAndBuildWebApiTemplate(languageOverride, null, null); + var project = await PublishAndBuildWebApiTemplate(languageOverride, null, args); // Avoid the F# compiler. See https://github.com/dotnet/aspnetcore/issues/14022 if (languageOverride != null) diff --git a/src/ProjectTemplates/test/WorkerTemplateTest.cs b/src/ProjectTemplates/test/WorkerTemplateTest.cs index 80e67c1873..ba82b5073c 100644 --- a/src/ProjectTemplates/test/WorkerTemplateTest.cs +++ b/src/ProjectTemplates/test/WorkerTemplateTest.cs @@ -32,16 +32,17 @@ namespace Templates.Test [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux, SkipReason = "https://github.com/dotnet/sdk/issues/12831")] - [InlineData("C#")] - [InlineData("F#")] + [InlineData("C#", null)] + [InlineData("C#", new string[] { "--use-program-main" })] + [InlineData("F#", null)] [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/25404")] - public async Task WorkerTemplateAsync(string language) + public async Task WorkerTemplateAsync(string language, string[] args) { var project = await ProjectFactory.GetOrCreateProject( $"worker-{ language.ToLowerInvariant()[0] }sharp", Output); - var createResult = await project.RunDotNetNewAsync("worker", language: language); + var createResult = await project.RunDotNetNewAsync("worker", language: language, args: args); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); var publishResult = await project.RunDotNetPublishAsync(); diff --git a/src/submodules/googletest b/src/submodules/googletest -Subproject c9461a9b55ba954df0489bab6420eb297bed846 +Subproject af29db7ec28d6df1c7f0f745186884091e602e0 diff --git a/src/submodules/spa-templates b/src/submodules/spa-templates -Subproject 3d16ec44cf4bc171531cefb0a81ae72b8bf0c47 +Subproject 35fe7145d402a9519591e99edf20d6d3c7d091b |