Skip to content

Commit 29e093c

Browse files
Backport UseProgramMain template option (#40945)
* Add option to project templates to use Program.Main instead of top-level statements (#40886) Fixes #40944 * Update spa templates submodule
1 parent 14468ef commit 29e093c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1365
-147
lines changed

.editorconfig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ dotnet_diagnostic.IDE0044.severity = warning
208208
dotnet_diagnostic.IDE0073.severity = warning
209209
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.
210210

211-
[**/{test,samples,perf}/**.{cs,vb}]
211+
[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,scripts}/**.cs}]
212212
# CA1018: Mark attributes with AttributeUsageAttribute
213213
dotnet_diagnostic.CA1018.severity = suggestion
214214
# CA1507: Use nameof to express symbol names
@@ -241,6 +241,10 @@ dotnet_diagnostic.CA1844.severity = suggestion
241241
dotnet_diagnostic.CA1845.severity = suggestion
242242
# CA1846: Prefer AsSpan over Substring
243243
dotnet_diagnostic.CA1846.severity = suggestion
244+
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
245+
dotnet_diagnostic.CA1847.severity = suggestion
246+
# CA2007: Consider calling ConfigureAwait on the awaited task
247+
dotnet_diagnostic.CA2007.severity = suggestion
244248
# CA2008: Do not create tasks without passing a TaskScheduler
245249
dotnet_diagnostic.CA2008.severity = suggestion
246250
# CA2012: Use ValueTask correctly

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
"CallsMicrosoftGraph": {
8686
"longName": "calls-graph",
8787
"shortName": ""
88+
},
89+
"UseProgramMain": {
90+
"longName": "use-program-main",
91+
"shortName": ""
8892
}
8993
},
9094
"usageExamples": [

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/ide.host.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
"useHttps": true
4444
}
4545
],
46+
"symbolInfo": [
47+
{
48+
"id": "UseProgramMain",
49+
"isVisible": true
50+
}
51+
],
4652
"disableHttpsSymbol": "NoHttps",
4753
"supportsDocker": true
4854
}

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@
3434
"wwwroot/**"
3535
],
3636
"modifiers": [
37+
{
38+
"condition": "(!UseProgramMain)",
39+
"exclude": [
40+
"Program.Main.cs"
41+
]
42+
},
43+
{
44+
"condition": "(UseProgramMain)",
45+
"exclude": [
46+
"Program.cs"
47+
],
48+
"rename": {
49+
"Program.Main.cs": "Program.cs"
50+
}
51+
},
3752
{
3853
"condition": "(!IndividualLocalAuth || UseLocalDB)",
3954
"exclude": [
@@ -490,6 +505,13 @@
490505
"datatype": "bool",
491506
"description": "If specified, skips the automatic restore of the project on create.",
492507
"defaultValue": "false"
508+
},
509+
"UseProgramMain": {
510+
"type": "parameter",
511+
"datatype": "bool",
512+
"defaultValue": "false",
513+
"displayName": "Do not use top-level statements",
514+
"description": "Whether to generate an explicit Program class and Main method instead of top-level statements."
493515
}
494516
},
495517
"primaryOutputs": [
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#if (OrganizationalAuth || IndividualB2CAuth)
2+
using Microsoft.AspNetCore.Authentication;
3+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
4+
using Microsoft.Identity.Web;
5+
using Microsoft.Identity.Web.UI;
6+
#endif
7+
#if (WindowsAuth)
8+
using Microsoft.AspNetCore.Authentication.Negotiate;
9+
#endif
10+
#if (OrganizationalAuth)
11+
#if (MultiOrgAuth)
12+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
13+
#endif
14+
using Microsoft.AspNetCore.Authorization;
15+
#endif
16+
using Microsoft.AspNetCore.Components;
17+
using Microsoft.AspNetCore.Components.Web;
18+
#if (IndividualLocalAuth)
19+
using Microsoft.AspNetCore.Components.Authorization;
20+
using Microsoft.AspNetCore.Identity;
21+
using Microsoft.AspNetCore.Identity.UI;
22+
#endif
23+
#if (OrganizationalAuth)
24+
using Microsoft.AspNetCore.Mvc.Authorization;
25+
#endif
26+
#if (IndividualLocalAuth)
27+
using Microsoft.EntityFrameworkCore;
28+
#endif
29+
#if (GenerateGraph)
30+
using Graph = Microsoft.Graph;
31+
#endif
32+
#if(MultiOrgAuth)
33+
using Microsoft.IdentityModel.Tokens;
34+
#endif
35+
#if (IndividualLocalAuth)
36+
using BlazorServerWeb_CSharp.Areas.Identity;
37+
#endif
38+
using BlazorServerWeb_CSharp.Data;
39+
40+
namespace Company.WebApplication1;
41+
42+
public class Program
43+
{
44+
public static void Main(string[] args)
45+
{
46+
var builder = WebApplication.CreateBuilder(args);
47+
48+
// Add services to the container.
49+
#if (IndividualLocalAuth)
50+
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
51+
builder.Services.AddDbContext<ApplicationDbContext>(options =>
52+
#if (UseLocalDB)
53+
options.UseSqlServer(connectionString));
54+
#else
55+
options.UseSqlite(connectionString));
56+
#endif
57+
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
58+
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
59+
.AddEntityFrameworkStores<ApplicationDbContext>();
60+
#elif (OrganizationalAuth)
61+
#if (GenerateApiOrGraph)
62+
var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ');
63+
64+
#endif
65+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
66+
#if (GenerateApiOrGraph)
67+
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
68+
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
69+
#if (GenerateApi)
70+
.AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
71+
#endif
72+
#if (GenerateGraph)
73+
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
74+
#endif
75+
.AddInMemoryTokenCaches();
76+
#else
77+
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
78+
#endif
79+
#elif (IndividualB2CAuth)
80+
#if (GenerateApi)
81+
var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ');
82+
83+
#endif
84+
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
85+
#if (GenerateApi)
86+
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C"))
87+
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
88+
.AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
89+
.AddInMemoryTokenCaches();
90+
#else
91+
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C"));
92+
#endif
93+
#endif
94+
#if (OrganizationalAuth || IndividualB2CAuth)
95+
builder.Services.AddControllersWithViews()
96+
.AddMicrosoftIdentityUI();
97+
98+
builder.Services.AddAuthorization(options =>
99+
{
100+
// By default, all incoming requests will be authorized according to the default policy
101+
options.FallbackPolicy = options.DefaultPolicy;
102+
});
103+
104+
#elif (WindowsAuth)
105+
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
106+
.AddNegotiate();
107+
108+
builder.Services.AddAuthorization(options =>
109+
{
110+
// By default, all incoming requests will be authorized according to the default policy.
111+
options.FallbackPolicy = options.DefaultPolicy;
112+
});
113+
114+
#endif
115+
builder.Services.AddRazorPages();
116+
#if (OrganizationalAuth || IndividualB2CAuth)
117+
builder.Services.AddServerSideBlazor()
118+
.AddMicrosoftIdentityConsentHandler();
119+
#else
120+
builder.Services.AddServerSideBlazor();
121+
#endif
122+
#if (IndividualLocalAuth)
123+
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
124+
#endif
125+
builder.Services.AddSingleton<WeatherForecastService>();
126+
127+
var app = builder.Build();
128+
129+
// Configure the HTTP request pipeline.
130+
#if (IndividualLocalAuth)
131+
if (app.Environment.IsDevelopment())
132+
{
133+
app.UseMigrationsEndPoint();
134+
}
135+
else
136+
#else
137+
if (!app.Environment.IsDevelopment())
138+
#endif
139+
{
140+
app.UseExceptionHandler("/Error");
141+
#if (RequiresHttps)
142+
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
143+
app.UseHsts();
144+
}
145+
146+
app.UseHttpsRedirection();
147+
#else
148+
}
149+
150+
#endif
151+
152+
app.UseStaticFiles();
153+
154+
app.UseRouting();
155+
156+
#if (OrganizationalAuth || IndividualAuth || WindowsAuth)
157+
app.UseAuthentication();
158+
app.UseAuthorization();
159+
160+
#endif
161+
#if (OrganizationalAuth || IndividualAuth)
162+
app.MapControllers();
163+
#endif
164+
app.MapBlazorHub();
165+
app.MapFallbackToPage("/_Host");
166+
167+
app.Run();
168+
}
169+
}

src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@
9595
"CallsMicrosoftGraph": {
9696
"longName": "calls-graph",
9797
"shortName": ""
98+
},
99+
"UseProgramMain": {
100+
"longName": "use-program-main",
101+
"shortName": ""
98102
}
99103
}
100104
}

src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/ide.host.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"text": "_Progressive Web Application"
4848
},
4949
"isVisible": "true"
50+
},
51+
{
52+
"id": "UseProgramMain",
53+
"isVisible": true
5054
}
5155
]
5256
}

src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@
9494
"Client/wwwroot/icon-512.png"
9595
]
9696
},
97+
{
98+
"condition": "(!UseProgramMain)",
99+
"exclude": [
100+
"Server/Program.Main.cs",
101+
"Client/Program.Main.cs"
102+
]
103+
},
104+
{
105+
"condition": "(UseProgramMain)",
106+
"exclude": [
107+
"Server/Program.cs",
108+
"Client/Program.cs"
109+
],
110+
"rename": {
111+
"Server/Program.Main.cs": "Server/Program.cs",
112+
"Client/Program.Main.cs": "Client/Program.cs"
113+
}
114+
},
97115
{
98116
"condition": "(!IndividualLocalAuth || UseLocalDB)",
99117
"exclude": [
@@ -591,6 +609,13 @@
591609
"GenerateApiOrGraph": {
592610
"type": "computed",
593611
"value": "(GenerateApi || GenerateGraph)"
612+
},
613+
"UseProgramMain": {
614+
"type": "parameter",
615+
"datatype": "bool",
616+
"defaultValue": "false",
617+
"displayName": "Do not use top-level statements",
618+
"description": "Whether to generate an explicit Program class and Main method instead of top-level statements."
594619
}
595620
},
596621
"tags": {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Microsoft.AspNetCore.Components.Web;
2+
#if (!NoAuth && Hosted)
3+
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
4+
#endif
5+
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
6+
#if (Hosted)
7+
using ComponentsWebAssembly_CSharp.Client;
8+
#else
9+
using ComponentsWebAssembly_CSharp;
10+
#endif
11+
12+
namespace Company.WebApplication1;
13+
14+
public class Program
15+
{
16+
public static async Task Main(string[] args)
17+
{
18+
var builder = WebAssemblyHostBuilder.CreateDefault(args);
19+
builder.RootComponents.Add<App>("#app");
20+
builder.RootComponents.Add<HeadOutlet>("head::after");
21+
22+
#if (!Hosted || NoAuth)
23+
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
24+
#else
25+
builder.Services.AddHttpClient("ComponentsWebAssembly_CSharp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
26+
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
27+
28+
// Supply HttpClient instances that include access tokens when making requests to the server project
29+
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ComponentsWebAssembly_CSharp.ServerAPI"));
30+
#endif
31+
#if(!NoAuth)
32+
33+
#endif
34+
#if (IndividualLocalAuth)
35+
#if (Hosted)
36+
builder.Services.AddApiAuthorization();
37+
#else
38+
builder.Services.AddOidcAuthentication(options =>
39+
{
40+
#if(MissingAuthority)
41+
// Configure your authentication provider options here.
42+
// For more information, see https://aka.ms/blazor-standalone-auth
43+
#endif
44+
builder.Configuration.Bind("Local", options.ProviderOptions);
45+
});
46+
#endif
47+
#endif
48+
#if (IndividualB2CAuth)
49+
builder.Services.AddMsalAuthentication(options =>
50+
{
51+
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
52+
#if (Hosted)
53+
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://qualified.domain.name/api.id.uri/api-scope");
54+
#endif
55+
});
56+
#endif
57+
#if(OrganizationalAuth)
58+
builder.Services.AddMsalAuthentication(options =>
59+
{
60+
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
61+
#if (Hosted)
62+
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/api-scope");
63+
#endif
64+
});
65+
#endif
66+
67+
await builder.Build().RunAsync();
68+
}
69+
}

0 commit comments

Comments
 (0)