Skip to content

Commit e6bc771

Browse files
Add support for the API Versioning, the API Explorer, and OpenAPI. Related to #1044 #1121
1 parent fba9885 commit e6bc771

34 files changed

+1293
-413
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ dotnet_diagnostic.SA1010.severity = none # Opening square brackets should be spa
120120
dotnet_diagnostic.ASP0022.severity = none # Route conflict detected between route handlers
121121
dotnet_diagnostic.ASP0023.severity = none # Route conflict detected between route handlers
122122

123+
# TEMP: temporary suppression for false positives
124+
# BUG: https://github.com/dotnet/sdk/issues/51681
125+
[*Extensions.cs]
126+
dotnet_diagnostic.CA1034.severity = none
127+
123128
# test settings
124129

125130
# Default severity for analyzer diagnostics with category 'Reliability'

asp.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
<Project Path="src/AspNetCore/WebApi/src/Asp.Versioning.Http/Asp.Versioning.Http.csproj" />
116116
<Project Path="src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Asp.Versioning.Mvc.ApiExplorer.csproj" />
117117
<Project Path="src/AspNetCore/WebApi/src/Asp.Versioning.Mvc/Asp.Versioning.Mvc.csproj" />
118+
<Project Path="src/AspNetCore/WebApi/src/Asp.Versioning.OpenApi/Asp.Versioning.OpenApi.csproj" />
118119
</Folder>
119120
<Folder Name="/src/AspNetCore/test/">
120121
<Project Path="src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Asp.Versioning.Mvc.Acceptance.Tests.csproj" />

examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs

Lines changed: 0 additions & 103 deletions
This file was deleted.

examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
5+
<AssemblyTitle>Example API</AssemblyTitle>
6+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
57
</PropertyGroup>
68

79
<ItemGroup>
8-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0-*" />
10+
<PackageReference Include="Scalar.AspNetCore" Version="2.12.*" />
11+
<PackageReference Include="Scalar.AspNetCore.Microsoft" Version="2.12.*" />
912
</ItemGroup>
1013

1114
<ItemGroup>
12-
<ProjectReference Include="..\..\..\..\src\AspNetCore\WebApi\src\Asp.Versioning.Mvc.ApiExplorer\Asp.Versioning.Mvc.ApiExplorer.csproj" />
15+
<ProjectReference Include="..\..\..\..\src\AspNetCore\WebApi\src\Asp.Versioning.OpenApi\Asp.Versioning.OpenApi.csproj" />
1316
</ItemGroup>
1417

1518
</Project>

examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
using ApiVersioning.Examples;
21
using Asp.Versioning;
3-
using Microsoft.Extensions.Options;
4-
using Swashbuckle.AspNetCore.SwaggerGen;
2+
using Scalar.AspNetCore;
3+
using System.Reflection;
54
using OrderV1 = ApiVersioning.Examples.Models.V1.Order;
65
using OrderV2 = ApiVersioning.Examples.Models.V2.Order;
76
using OrderV3 = ApiVersioning.Examples.Models.V3.Order;
87
using PersonV1 = ApiVersioning.Examples.Models.V1.Person;
98
using PersonV2 = ApiVersioning.Examples.Models.V2.Person;
109
using PersonV3 = ApiVersioning.Examples.Models.V3.Person;
1110

11+
[assembly: AssemblyDescription( "An example API" )]
12+
1213
var builder = WebApplication.CreateBuilder( args );
1314
var services = builder.Services;
1415

15-
// Add services to the container.
1616
services.AddProblemDetails();
1717
services.AddEndpointsApiExplorer();
1818
services.AddApiVersioning(
@@ -39,13 +39,11 @@
3939
// can also be used to control the format of the API version in route templates
4040
options.SubstituteApiVersionInUrl = true;
4141
} )
42+
.AddOpenApi( ( _, options ) => options.AddScalarTransformers() )
4243
// this enables binding ApiVersion as a endpoint callback parameter. if you don't use it, then
4344
// you should remove this configuration.
4445
.EnableApiVersionBinding();
45-
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
46-
services.AddSwaggerGen( options => options.OperationFilter<SwaggerDefaultValues>() );
4746

48-
// Configure the HTTP request pipeline.
4947
var app = builder.Build();
5048
var orders = app.NewVersionedApi( "Orders" );
5149
var people = app.NewVersionedApi( "People" );
@@ -56,6 +54,8 @@
5654
.HasApiVersion( 1.0 );
5755

5856
ordersV1.MapGet( "/{id:int}", ( int id ) => new OrderV1() { Id = id, Customer = "John Doe" } )
57+
.WithSummary( "Get Order" )
58+
.WithDescription( "Gets a single order." )
5959
.Produces<OrderV1>()
6060
.Produces( 404 );
6161

@@ -67,12 +67,16 @@
6767
var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" );
6868
return Results.Created( location, order );
6969
} )
70+
.WithSummary( "Place Order" )
71+
.WithDescription( "Places a new order." )
7072
.Accepts<OrderV1>( "application/json" )
7173
.Produces<OrderV1>( 201 )
7274
.Produces( 400 )
7375
.MapToApiVersion( 1.0 );
7476

7577
ordersV1.MapPatch( "/{id:int}", ( int id, OrderV1 order ) => Results.NoContent() )
78+
.WithSummary( "Update Order" )
79+
.WithDescription( "Updates an order." )
7680
.Accepts<OrderV1>( "application/json" )
7781
.Produces( 204 )
7882
.Produces( 400 )
@@ -90,10 +94,14 @@
9094
new(){ Id = 2, Customer = "Bob Smith" },
9195
new(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) },
9296
} )
97+
.WithSummary( "Get Orders" )
98+
.WithDescription( "Retrieves all orders." )
9399
.Produces<IEnumerable<OrderV2>>()
94100
.Produces( 404 );
95101

96102
ordersV2.MapGet( "/{id:int}", ( int id ) => new OrderV2() { Id = id, Customer = "John Doe" } )
103+
.WithSummary( "Get Order" )
104+
.WithDescription( "Gets a single order." )
97105
.Produces<OrderV2>()
98106
.Produces( 404 );
99107

@@ -105,12 +113,16 @@
105113
var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" );
106114
return Results.Created( location, order );
107115
} )
116+
.WithSummary( "Place Order" )
117+
.WithDescription( "Places a new order." )
108118
.Accepts<OrderV2>( "application/json" )
109119
.Produces<OrderV2>( 201 )
110120
.Produces( 400 );
111121

112122

113123
ordersV2.MapPatch( "/{id:int}", ( int id, OrderV2 order ) => Results.NoContent() )
124+
.WithSummary( "Update Order" )
125+
.WithDescription( "Updates an order." )
114126
.Accepts<OrderV2>( "application/json" )
115127
.Produces( 204 )
116128
.Produces( 400 )
@@ -127,9 +139,13 @@
127139
new(){ Id = 2, Customer = "Bob Smith" },
128140
new(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTimeOffset.UtcNow.AddDays( 7d ) },
129141
} )
142+
.WithSummary( "Get Orders" )
143+
.WithDescription( "Retrieves all orders." )
130144
.Produces<IEnumerable<OrderV3>>();
131145

132146
ordersV3.MapGet( "/{id:int}", ( int id ) => new OrderV3() { Id = id, Customer = "John Doe" } )
147+
.WithSummary( "Get Order" )
148+
.WithDescription( "Gets a single order." )
133149
.Produces<OrderV3>()
134150
.Produces( 404 );
135151

@@ -141,11 +157,15 @@
141157
var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/api/orders/{order.Id}" );
142158
return Results.Created( location, order );
143159
} )
160+
.WithSummary( "Place Order" )
161+
.WithDescription( "Places a new order." )
144162
.Accepts<OrderV3>( "application/json" )
145163
.Produces<OrderV3>( 201 )
146164
.Produces( 400 );
147165

148166
ordersV3.MapDelete( "/{id:int}", ( int id ) => Results.NoContent() )
167+
.WithSummary( "Cancel Order" )
168+
.WithDescription( "Cancels an order." )
149169
.Produces( 204 );
150170

151171
// 1.0
@@ -160,6 +180,8 @@
160180
FirstName = "John",
161181
LastName = "Doe",
162182
} )
183+
.WithSummary( "Get Person" )
184+
.WithDescription( "Gets a single person." )
163185
.Produces<PersonV1>()
164186
.Produces( 404 );
165187

@@ -192,6 +214,8 @@
192214
Email = "jane.doe@somewhere.com",
193215
},
194216
} )
217+
.WithSummary( "Get People" )
218+
.WithDescription( "Gets all people." )
195219
.Produces<IEnumerable<PersonV2>>();
196220

197221
peopleV2.MapGet( "/{id:int}", ( int id ) =>
@@ -202,6 +226,8 @@
202226
LastName = "Doe",
203227
Email = "john.doe@somewhere.com",
204228
} )
229+
.WithSummary( "Get Person" )
230+
.WithDescription( "Gets a single person." )
205231
.Produces<PersonV2>()
206232
.Produces( 404 );
207233

@@ -237,6 +263,8 @@
237263
Phone = "555-789-3456",
238264
},
239265
} )
266+
.WithSummary( "Get People" )
267+
.WithDescription( "Gets all people." )
240268
.Produces<IEnumerable<PersonV3>>();
241269

242270
peopleV3.MapGet( "/{id:int}", ( int id ) =>
@@ -248,6 +276,8 @@
248276
Email = "john.doe@somewhere.com",
249277
Phone = "555-987-1234",
250278
} )
279+
.WithSummary( "Get Person" )
280+
.WithDescription( "Gets a single person." )
251281
.Produces<PersonV3>()
252282
.Produces( 404 );
253283

@@ -259,25 +289,28 @@
259289
var location = new Uri( $"{scheme}{Uri.SchemeDelimiter}{host}/v{version}/api/people/{person.Id}" );
260290
return Results.Created( location, person );
261291
} )
292+
.WithSummary( "Add Person" )
293+
.WithDescription( "Adds a new person." )
262294
.Accepts<PersonV3>( "application/json" )
263295
.Produces<PersonV3>( 201 )
264296
.Produces( 400 );
265297

266-
app.UseSwagger();
267298
if ( app.Environment.IsDevelopment() )
268299
{
269-
app.UseSwaggerUI(
300+
app.IncludeVersionedEndpoints().MapOpenApi().WithDocumentPerVersion();
301+
app.MapScalarApiReference(
270302
options =>
271303
{
272304
var descriptions = app.DescribeApiVersions();
273305

274-
// build a swagger endpoint for each discovered API version
275-
foreach ( var description in descriptions )
306+
for ( var i = 0; i < descriptions.Count; i++ )
276307
{
277-
var url = $"/swagger/{description.GroupName}/swagger.json";
278-
var name = description.GroupName.ToUpperInvariant();
279-
options.SwaggerEndpoint( url, name );
308+
var description = descriptions[i];
309+
var isDefault = i == descriptions.Count - 1;
310+
311+
options.AddDocument( description.GroupName, description.GroupName, isDefault: isDefault );
280312
}
281313
} );
282314
}
315+
283316
app.Run();

examples/AspNetCore/WebApi/MinimalOpenApiExample/Properties/launchSettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"commandName": "Project",
1313
"dotnetRunMessages": true,
1414
"launchBrowser": true,
15-
"launchUrl": "swagger",
15+
"launchUrl": "scalar",
1616
"applicationUrl": "https://localhost:5145;http://localhost:5144",
1717
"environmentVariables": {
1818
"ASPNETCORE_ENVIRONMENT": "Development"
@@ -21,7 +21,7 @@
2121
"IIS Express": {
2222
"commandName": "IISExpress",
2323
"launchBrowser": true,
24-
"launchUrl": "swagger",
24+
"launchUrl": "scalar",
2525
"environmentVariables": {
2626
"ASPNETCORE_ENVIRONMENT": "Development"
2727
}

0 commit comments

Comments
 (0)