Skip to content

Commit 493ed8b

Browse files
Add minimal option to webapi template (#36068) (#36241)
- Add "minimal" option to webapi project template - Factor Program.cs into multiple files and update template manifest to exclude/rename dependent on selected options - Updated controller and minimal versions to set endpoint/route name when EnableOpenAPI is true - Configure webapi template minimal option for VS display as "Use controllers"
1 parent 57595c4 commit 493ed8b

11 files changed

+380
-38
lines changed

src/ProjectTemplates/Shared/TemplatePackageInstaller.cs

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ private static async Task InstallTemplatePackages(ITestOutputHelper output)
111111

112112
await VerifyCannotFindTemplateAsync(output, "web");
113113
await VerifyCannotFindTemplateAsync(output, "webapp");
114+
await VerifyCannotFindTemplateAsync(output, "webapi");
114115
await VerifyCannotFindTemplateAsync(output, "mvc");
115116
await VerifyCannotFindTemplateAsync(output, "react");
116117
await VerifyCannotFindTemplateAsync(output, "reactredux");
@@ -125,6 +126,7 @@ private static async Task InstallTemplatePackages(ITestOutputHelper output)
125126

126127
await VerifyCanFindTemplate(output, "webapp");
127128
await VerifyCanFindTemplate(output, "web");
129+
await VerifyCanFindTemplate(output, "webapi");
128130
await VerifyCanFindTemplate(output, "react");
129131
}
130132

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

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
"UseLocalDB": {
55
"longName": "use-local-db"
66
},
7+
"UseMinimalAPIs": {
8+
"longName": "use-minimal-apis",
9+
"shortName": "minimal"
10+
},
711
"AADInstance": {
812
"longName": "aad-instance",
913
"shortName": ""

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

+9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
"invertBoolean": true,
4444
"isVisible": true,
4545
"defaultValue": true
46+
},
47+
{
48+
"id": "UseMinimalAPIs",
49+
"name": {
50+
"text": "Use controllers (uncheck to use minimal APIs)"
51+
},
52+
"invertBoolean": true,
53+
"isVisible": true,
54+
"defaultValue": true
4655
}
4756
],
4857
"disableHttpsSymbol": "NoHttps"

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

+43
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,39 @@
3636
"exclude": [
3737
"Properties/launchSettings.json"
3838
]
39+
},
40+
{
41+
"condition": "(UseMinimalAPIs)",
42+
"exclude": [
43+
"Controllers/WeatherForecastController.cs",
44+
"Program.cs",
45+
"WeatherForecast.cs"
46+
]
47+
},
48+
{
49+
"condition": "(UseMinimalAPIs && (NoAuth || WindowsAuth))",
50+
"rename": {
51+
"Program.MinimalAPIs.WindowsOrNoAuth.cs": "Program.cs"
52+
},
53+
"exclude": [
54+
"Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs"
55+
]
56+
},
57+
{
58+
"condition": "(UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))",
59+
"rename": {
60+
"Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs": "Program.cs"
61+
},
62+
"exclude": [
63+
"Program.MinimalAPIs.WindowsOrNoAuth.cs"
64+
]
65+
},
66+
{
67+
"condition": "(UseControllers)",
68+
"exclude": [
69+
"Program.MinimalAPIs.WindowsOrNoAuth.cs",
70+
"Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs"
71+
]
3972
}
4073
]
4174
}
@@ -254,6 +287,12 @@
254287
"defaultValue": "false",
255288
"description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified."
256289
},
290+
"UseMinimalAPIs": {
291+
"type": "parameter",
292+
"datatype": "bool",
293+
"defaultValue": "false",
294+
"description": "Whether to use mininmal APIs instead of controllers."
295+
},
257296
"Framework": {
258297
"type": "parameter",
259298
"description": "The target framework for the project.",
@@ -321,6 +360,10 @@
321360
"EnableOpenAPI": {
322361
"type": "computed",
323362
"value": "(!DisableOpenAPI)"
363+
},
364+
"UseControllers": {
365+
"type": "computed",
366+
"value": "(!UseMinimalAPIs)"
324367
}
325368
},
326369
"primaryOutputs": [

src/ProjectTemplates/Web.ProjectTemplates/content/WebApi-CSharp/Controllers/WeatherForecastController.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,15 @@ public class WeatherForecastController : ControllerBase
4040
public WeatherForecastController(ILogger<WeatherForecastController> logger,
4141
IDownstreamWebApi downstreamWebApi)
4242
{
43-
_logger = logger;
43+
_logger = logger;
4444
_downstreamWebApi = downstreamWebApi;
4545
}
4646

47+
#if (EnableOpenAPI)
48+
[HttpGet(Name = "GetWeatherForecast")]
49+
#else
4750
[HttpGet]
51+
#endif
4852
public async Task<IEnumerable<WeatherForecast>> Get()
4953
{
5054
using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false);
@@ -74,11 +78,15 @@ public async Task<IEnumerable<WeatherForecast>> Get()
7478
public WeatherForecastController(ILogger<WeatherForecastController> logger,
7579
GraphServiceClient graphServiceClient)
7680
{
77-
_logger = logger;
81+
_logger = logger;
7882
_graphServiceClient = graphServiceClient;
7983
}
8084

85+
#if (EnableOpenAPI)
86+
[HttpGet(Name = "GetWeatherForecast")]
87+
#else
8188
[HttpGet]
89+
#endif
8290
public async Task<IEnumerable<WeatherForecast>> Get()
8391
{
8492
var user = await _graphServiceClient.Me.Request().GetAsync();
@@ -97,7 +105,11 @@ public WeatherForecastController(ILogger<WeatherForecastController> logger)
97105
_logger = logger;
98106
}
99107

108+
#if (EnableOpenAPI)
109+
[HttpGet(Name = "GetWeatherForecast")]
110+
#else
100111
[HttpGet]
112+
#endif
101113
public IEnumerable<WeatherForecast> Get()
102114
{
103115
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#if (GenerateApi)
2+
using System.Net.Http;
3+
#endif
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.AspNetCore.Authentication.JwtBearer;
6+
#if (GenerateGraph)
7+
using Graph = Microsoft.Graph;
8+
#endif
9+
using Microsoft.Identity.Web;
10+
using Microsoft.Identity.Web.Resource;
11+
12+
var builder = WebApplication.CreateBuilder(args);
13+
14+
// Add services to the container.
15+
#if (OrganizationalAuth)
16+
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
17+
#if (GenerateApiOrGraph)
18+
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
19+
.EnableTokenAcquisitionToCallDownstreamApi()
20+
#if (GenerateApi)
21+
.AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
22+
#endif
23+
#if (GenerateGraph)
24+
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
25+
#endif
26+
.AddInMemoryTokenCaches();
27+
#else
28+
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
29+
#endif
30+
#elif (IndividualB2CAuth)
31+
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
32+
#if (GenerateApi)
33+
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C"))
34+
.EnableTokenAcquisitionToCallDownstreamApi()
35+
.AddDownstreamWebApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
36+
.AddInMemoryTokenCaches();
37+
#else
38+
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAdB2C"));
39+
#endif
40+
#endif
41+
builder.Services.AddAuthorization();
42+
43+
#if (EnableOpenAPI)
44+
builder.Services.AddEndpointsApiExplorer();
45+
builder.Services.AddSwaggerGen();
46+
#endif
47+
48+
var app = builder.Build();
49+
50+
// Configure the HTTP request pipeline.
51+
#if (EnableOpenAPI)
52+
if (app.Environment.IsDevelopment())
53+
{
54+
app.UseSwagger();
55+
app.UseSwaggerUI();
56+
}
57+
#endif
58+
#if (RequiresHttps)
59+
60+
app.UseHttpsRedirection();
61+
#endif
62+
63+
app.UseAuthentication();
64+
app.UseAuthorization();
65+
66+
var scopeRequiredByApi = app.Configuration["AzureAd:Scopes"];
67+
var summaries = new[]
68+
{
69+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
70+
};
71+
72+
#if (GenerateApi)
73+
app.MapGet("/weatherforecast", (HttpContext httpContext, IDownstreamWebApi downstreamWebApi) =>
74+
{
75+
httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
76+
77+
using var response = await downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false);
78+
if (response.StatusCode == System.Net.HttpStatusCode.OK)
79+
{
80+
var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
81+
// Do something
82+
}
83+
else
84+
{
85+
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
86+
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}");
87+
}
88+
89+
var forecast = Enumerable.Range(1, 5).Select(index =>
90+
new WeatherForecast
91+
(
92+
DateTime.Now.AddDays(index),
93+
Random.Shared.Next(-20, 55),
94+
summaries[Random.Shared.Next(summaries.Length)]
95+
))
96+
.ToArray();
97+
98+
return forecast;
99+
})
100+
#elseif (GenerateGraph)
101+
app.MapGet("/weahterforecast", (HttpContext httpContext, GraphServiceClient graphServiceClient) =>
102+
{
103+
httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
104+
105+
var user = await _graphServiceClient.Me.Request().GetAsync();
106+
107+
var forecast = Enumerable.Range(1, 5).Select(index =>
108+
new WeatherForecast
109+
(
110+
DateTime.Now.AddDays(index),
111+
Random.Shared.Next(-20, 55),
112+
summaries[Random.Shared.Next(summaries.Length)]
113+
))
114+
.ToArray();
115+
116+
return forecast;
117+
})
118+
#else
119+
app.MapGet("/weatherforecast", (HttpContext httpContext) =>
120+
{
121+
httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
122+
123+
var forecast = Enumerable.Range(1, 5).Select(index =>
124+
new WeatherForecast
125+
(
126+
DateTime.Now.AddDays(index),
127+
Random.Shared.Next(-20, 55),
128+
summaries[Random.Shared.Next(summaries.Length)]
129+
))
130+
.ToArray();
131+
return forecast;
132+
#endif
133+
#if (EnableOpenAPI)
134+
})
135+
.WithName("GetWeatherForecast")
136+
.RequireAuthorization();
137+
#else
138+
})
139+
.RequireAuthorization();
140+
#endif
141+
142+
app.Run();
143+
144+
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
145+
{
146+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var builder = WebApplication.CreateBuilder(args);
2+
3+
// Add services to the container.
4+
#if (EnableOpenAPI)
5+
builder.Services.AddEndpointsApiExplorer();
6+
builder.Services.AddSwaggerGen();
7+
#endif
8+
9+
var app = builder.Build();
10+
11+
// Configure the HTTP request pipeline.
12+
#if (EnableOpenAPI)
13+
if (app.Environment.IsDevelopment())
14+
{
15+
app.UseSwagger();
16+
app.UseSwaggerUI();
17+
}
18+
#endif
19+
#if (RequiresHttps)
20+
21+
app.UseHttpsRedirection();
22+
#endif
23+
24+
var summaries = new[]
25+
{
26+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
27+
};
28+
29+
app.MapGet("/weatherforecast", () =>
30+
{
31+
var forecast = Enumerable.Range(1, 5).Select(index =>
32+
new WeatherForecast
33+
(
34+
DateTime.Now.AddDays(index),
35+
Random.Shared.Next(-20, 55),
36+
summaries[Random.Shared.Next(summaries.Length)]
37+
))
38+
.ToArray();
39+
return forecast;
40+
#if (EnableOpenAPI)
41+
})
42+
.WithName("GetWeatherForecast");
43+
#else
44+
});
45+
#endif
46+
47+
app.Run();
48+
49+
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
50+
{
51+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
52+
}

src/ProjectTemplates/scripts/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ angular/
55
blazorserver/
66
blazorwasm/
77
mvc/
8+
mvcorgauth/
89
razor/
910
react/
1011
reactredux/
1112
web/
1213
webapp/
1314
webapi/
15+
webapimin/
1416
worker/
1517
grpc/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env pwsh
2+
#requires -version 4
3+
4+
[CmdletBinding(PositionalBinding = $false)]
5+
param()
6+
7+
Set-StrictMode -Version 2
8+
$ErrorActionPreference = 'Stop'
9+
10+
. $PSScriptRoot\Test-Template.ps1
11+
12+
Test-Template "webapimin" "webapi -minimal" "Microsoft.DotNet.Web.ProjectTemplates.6.0.6.0.0-dev.nupkg" $false

0 commit comments

Comments
 (0)