diff --git a/.editorconfig b/.editorconfig index d7ec0b83dec420209faacdf4f75508d92fba411a..8583569cfc7d1f6bd53c294282fb9090735eb64e 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 3c34f96f85e3a9cacb7a084e070f4281f297fe57..02abad32e7eb18e40f03f69d8f5022836d4ef1d5 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 e891e5f2df9789b29cd84d065c37e6119482c9f8..947fd0caa52b18cccf1709a260058fe8c961bd52 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 254598a9cfe2493e8536d3bfb2e2c2fdd9775fac..4b203fdd52b6770d9cdc1770d2da06373c0059f7 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 @@ -34,6 +34,21 @@ "wwwroot/**" ], "modifiers": [ + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, { "condition": "(!IndividualLocalAuth || UseLocalDB)", "exclude": [ @@ -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 0000000000000000000000000000000000000000..92eb45d80a910c5048bac031e5a0ff6f558f29c8 --- /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 93b2c5a3bc03932beb9f8251bd42b9240337302a..3d2007dc58684e2fa3d852ceeab7fdfe2c42f75e 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 9005a4d171a3732fedadfc12b6fdb143b1030222..2ed03203a45728b2a7fbd80cb1870e127516b32d 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 cd44728a48949ef2fcf12764ffe39ba1e4deb17e..9b261b76d232e27e69eb953cf566bab646255910 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 @@ -94,6 +94,24 @@ "Client/wwwroot/icon-512.png" ] }, + { + "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": [ @@ -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 0000000000000000000000000000000000000000..8b870e5dc9d3232df639a5d5bd40c0243fb470d0 --- /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 0000000000000000000000000000000000000000..31835439cd28607e4f5603d90431e94334cacb2d --- /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 d97858472b807514d1a7c5705d1fb5634f691673..6f4f4fb8936d4f1af7beba4d355a5b671dd011fd 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 751a95c5b2ff59107ebc97fb70824774418a0ba8..f30c4753e2a8b974c00c44ff8589b3f6c861834a 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 3b7edb411a86669637dbd82c1236960d63d6a33f..c85130f420f930026749f0a87cb15e63091bc360 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 0000000000000000000000000000000000000000..6a106499828e08eb2ad5d0f665757a0266ad22ad --- /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 684bc1e734f3bf56908c1d6ede0a49ade0519be5..ded3cbf35f232b0b77002c75e06b8c177b63aa23 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 2cb41909519c551548e0f86ccbf68a0db4e7738c..5c3a869512fdcb137cf2820c63800851be04151b 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 2fd14ec9251fe22d8b847b3eed7d646290bb7e61..fc4288d8738493e55d5e7cc3f7c0f03dd161fad7 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 0000000000000000000000000000000000000000..ec1af1a7e9c636fc71117ca7b4cb8686f926220f --- /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 6ce1fbe6eb27bd168daaec0ec5686f156a11286e..a00ae64f281e9524a4614b446377fc4fb090de45 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 f176b8df2a9c6b452f48c804b5c98741f063007d..0dc542e09b82b34dcf1033f2af59f064806e471e 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 d7226586d37dffd5bc7098bbdb0c41fa006a03bc..989dbb8ab86bd26ede7438da82696fe40f240682 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 0000000000000000000000000000000000000000..3a1a1d68cc066fe9140295b60d46e6e7de371386 --- /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 3c34f96f85e3a9cacb7a084e070f4281f297fe57..02abad32e7eb18e40f03f69d8f5022836d4ef1d5 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 12bb6ec5db17bba5f5b9d64cc90799469627e933..995a75bea9a06c1c95781a3f73ea7c2d289a4ed4 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 b67fe4f719ef4bc23bc3a8a3adbbb5d8b61c78c5..558a4e818d356ea307c9a917770b2be03f38e203 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 0000000000000000000000000000000000000000..5fda9092d06ac5a12394e824a953c6e35654cec5 --- /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 9b97a61820662f3e28b5b5f06229a614690cbce4..79217f16803f2aeecf881cf04945eae05479fd27 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 c8fdd64b6acd763a50dfd8d95c245db7af90231f..19355a4eecac6a8b6b924bf45845c6571fa287b1 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 ee1f9c886b38f98df927d323107596ae558f3912..5c9b53aedcd1519fb2fd57ceffa6293e8a94556d 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 @@ -37,6 +37,21 @@ "Properties/launchSettings.json" ] }, + { + "condition": "(!UseProgramMain)", + "exclude": [ + "Program.Main.cs" + ] + }, + { + "condition": "(UseProgramMain && !UseMinimalAPIs)", + "exclude": [ + "Program.cs" + ], + "rename": { + "Program.Main.cs": "Program.cs" + } + }, { "condition": "(UseMinimalAPIs)", "exclude": [ @@ -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 0000000000000000000000000000000000000000..a0c9ad67e817faff6c3ff1516284735d65d72813 --- /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 b1cf98e39bb87b5088daac22bba7d10a2dfa866b..36493a3a4ac71fe862da45ad709884241eb4c190 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 13a025d034fa03f52f8c8bc122b5841d22474a25..59f260a583df5b692057a862a61a2d8c02f12990 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 19358bf86a30e57244bf0bbb1ce6225fa371d5f2..d2789afed3ff7131dd80ac2e2850be01fc5fe878 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 0000000000000000000000000000000000000000..d69747f38d51aba1d6a3b7e618e2e014c3f6f10e --- /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 0000000000000000000000000000000000000000..93127bb08be03fbab39259e5eda5940c81d9ddfc --- /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 0000000000000000000000000000000000000000..3d9fdd64a70dbb4bd9e5ac93ba2597d437b23392 --- /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 0000000000000000000000000000000000000000..7c8755a8bba3a8e9ae38d2701851349bb908fc1d --- /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 0000000000000000000000000000000000000000..7453063baf21c4757850aed57b1f3bd7ec7cc262 --- /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 0000000000000000000000000000000000000000..4224cf985dd2873b91232b3483cfc795dab1a1e6 --- /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 0000000000000000000000000000000000000000..df61a5a117403cc0f168897b73d0fe2b242c7b56 --- /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 0000000000000000000000000000000000000000..076106d3e861714f1f96fec6baf0ff54d7a4d0e3 --- /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 0000000000000000000000000000000000000000..41f794b7eaaf9958c39fa2cb0f6946b7ea1cb170 --- /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 0000000000000000000000000000000000000000..9e0aa3d4607b24154a0ccaf42587a3911b5c666d --- /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 349174ad3d06207ac3eca1b98f07138ad63ea491..05cf0ef7128b244589f3826c92721e0cf94b8f95 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 432c85043bd7e2e9656718f80f9713f4442b985b..104f2304020d79a616ef1a11ea3ee3fd90f3e620 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 97a580dddd8b31584b719316b3c09a55f0781988..dae6148d213a85a640d4ff73b58921d60dce7f31 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -37,6 +37,9 @@ namespace Templates.Test [Fact] 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" }); diff --git a/src/ProjectTemplates/test/EmptyWebTemplateTest.cs b/src/ProjectTemplates/test/EmptyWebTemplateTest.cs index e90ccbf5b94c305f1d50743f21e20d219bb7ac99..7468a587b41ed93b8c87cb0328c0961749f850a5 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 585ac0bfb457b2f0a894953fd10d856fe147ef70..d26d0f8623a67c74648a360f299c523353dea6a0 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 47045d6972aa212f8fa0653a000e6dec0b2d81ae..a5d6bec3806603848d6eebf6486aeb0c0c4c44ba 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 1af0a68366ffc266a48504b1312268a08c49127d..3412233f5dd5ee56f1c576a07971771279f86019 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 ddc6e185320e5a254c479d5d126fe760b51f3b8d..f727abcc92c0e830e040c7e0cffa6e0c804f5ed7 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 80e67c18738ef989299851dccce831493a27445e..ba82b5073c0e9a89b9297814d4f033b8c344b4b7 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/spa-templates b/src/submodules/spa-templates index 3d16ec44cf4bc171531cefb0a81ae72b8bf0c478..08a4e743ccec97c24aec8b7a719655f08e827d8e 160000 --- a/src/submodules/spa-templates +++ b/src/submodules/spa-templates @@ -1 +1 @@ -Subproject commit 3d16ec44cf4bc171531cefb0a81ae72b8bf0c478 +Subproject commit 08a4e743ccec97c24aec8b7a719655f08e827d8e