Skip to content

Commit 8cb34cb

Browse files
committed
add swagger and versioning api #3
1 parent 3911e1e commit 8cb34cb

File tree

16 files changed

+226
-39
lines changed

16 files changed

+226
-39
lines changed

coolstore.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:Boolean x:Key="/Default/UserDictionary/Words/=Consts/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cqrs/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/UserDictionary/Words/=Criterias/@EntryIndexedValue">True</s:Boolean>
45
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dapr/@EntryIndexedValue">True</s:Boolean>
56
<s:Boolean x:Key="/Default/UserDictionary/Words/=dbcontext/@EntryIndexedValue">True</s:Boolean>

src/BuildingBlocks/N8T.Infrastructure/Extensions.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
using Microsoft.Extensions.Configuration;
88
using Microsoft.Extensions.DependencyInjection;
99
using Microsoft.Extensions.Hosting;
10+
using N8T.Core.Domain;
1011
using N8T.Infrastructure.Logging;
1112
using N8T.Infrastructure.Validator;
13+
using Newtonsoft.Json;
1214
using Serilog;
1315

1416
namespace N8T.Infrastructure
@@ -42,22 +44,18 @@ public static IServiceCollection AddCustomMediatR(this IServiceCollection servic
4244
}
4345

4446
[DebuggerStepThrough]
45-
public static IServiceCollection AddCustomMvc<TType>(this IServiceCollection services,
46-
bool withDapr = false,
47-
Action<IServiceCollection> doMoreActions = null)
47+
public static TResult SafeGetListQuery<TResult, TResponse>(this HttpContext httpContext, string query)
48+
where TResult : IListQuery<TResponse>, new()
4849
{
49-
var mvcBuilder = services.AddControllers();
50-
51-
if (withDapr)
50+
var queryModel = new TResult();
51+
if (!(string.IsNullOrEmpty(query) || query == "{}"))
5252
{
53-
mvcBuilder.AddDapr();
53+
queryModel = JsonConvert.DeserializeObject<TResult>(query);
5454
}
5555

56-
mvcBuilder.AddApplicationPart(typeof(TType).Assembly);
56+
httpContext?.Response.Headers.Add("x-query", JsonConvert.SerializeObject(queryModel));
5757

58-
doMoreActions?.Invoke(services);
59-
60-
return services;
58+
return queryModel;
6159
}
6260

6361
[DebuggerStepThrough]

src/BuildingBlocks/N8T.Infrastructure/N8T.Infrastructure.csproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
</PropertyGroup>
77

88
<ItemGroup>
9+
910
<PackageReference Include="Ardalis.ApiEndpoints" Version="3.0.0" />
1011
<PackageReference Include="Autofac" Version="6.1.0" />
1112
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" />
@@ -21,6 +22,7 @@
2122
<PackageReference Include="MediatR" Version="9.0.0" />
2223
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
2324
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.0" />
25+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
2426
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
2527
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
2628
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
@@ -38,17 +40,23 @@
3840
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
3941
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="6.8.0" />
4042
<PackageReference Include="Microsoft.Extensions.Configuration.KeyPerFile" Version="5.0.0" />
41-
<PackageReference Include="Polly" Version="7.2.1" />
4243
<PackageReference Include="Microsoft.Tye.Extensions.Configuration" Version="0.5.0-alpha.20555.1" />
44+
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
45+
<PackageReference Include="Polly" Version="7.2.1" />
4346
<PackageReference Include="Scrutor" Version="3.3.0" />
4447
<PackageReference Include="Serilog" Version="2.10.1-dev-01256" />
4548
<PackageReference Include="Serilog.AspNetCore" Version="3.4.1-dev-00188" />
4649
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.2-dev-10281" />
4750
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0-dev-00839" />
4851
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="8.5.0-alpha0003" />
4952
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0-dev-00905" />
53+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.7" />
5054
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
5155
<PackageReference Include="ValueTaskSupplement" Version="1.1.0" />
5256
</ItemGroup>
5357

58+
<ItemGroup>
59+
<ProjectReference Include="..\N8T.Core\N8T.Core.csproj" />
60+
</ItemGroup>
61+
5462
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Options;
5+
using Microsoft.OpenApi.Models;
6+
using Swashbuckle.AspNetCore.SwaggerGen;
7+
8+
namespace N8T.Infrastructure.Swagger
9+
{
10+
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
11+
{
12+
private readonly IApiVersionDescriptionProvider _provider;
13+
14+
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => _provider = provider;
15+
16+
public void Configure(SwaggerGenOptions options)
17+
{
18+
foreach (var description in _provider.ApiVersionDescriptions)
19+
{
20+
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
21+
}
22+
}
23+
24+
private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
25+
{
26+
var info = new OpenApiInfo()
27+
{
28+
Title = "APIs",
29+
Version = description.ApiVersion.ToString(),
30+
Description = "An application with Swagger, Swashbuckle, and API versioning.",
31+
Contact = new OpenApiContact() {Name = "Thang Chung", Email = "thangchung.onthenet@gmail.com"},
32+
License = new OpenApiLicense() {Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT")}
33+
};
34+
35+
if (description.IsDeprecated)
36+
{
37+
info.Description += " This API version has been deprecated.";
38+
}
39+
40+
return info;
41+
}
42+
}
43+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.IO;
2+
using System.Reflection;
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Options;
7+
using Microsoft.Extensions.PlatformAbstractions;
8+
using Swashbuckle.AspNetCore.SwaggerGen;
9+
10+
namespace N8T.Infrastructure.Swagger
11+
{
12+
public static class Extentions
13+
{
14+
public static IServiceCollection AddSwagger<TMarker>(this IServiceCollection services)
15+
{
16+
services.AddApiVersioning(
17+
options =>
18+
{
19+
options.ReportApiVersions = true;
20+
});
21+
services.AddVersionedApiExplorer(
22+
options =>
23+
{
24+
options.GroupNameFormat = "'v'VVV";
25+
26+
options.SubstituteApiVersionInUrl = true;
27+
});
28+
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
29+
services.AddSwaggerGen(
30+
options =>
31+
{
32+
options.OperationFilter<SwaggerDefaultValues>();
33+
34+
var xmlFile = XmlCommentsFilePath();
35+
if (File.Exists(xmlFile))
36+
{
37+
options.IncludeXmlComments(xmlFile);
38+
}
39+
});
40+
41+
return services;
42+
43+
static string XmlCommentsFilePath()
44+
{
45+
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
46+
var fileName = typeof(TMarker).GetTypeInfo().Assembly.GetName().Name + ".xml";
47+
return Path.Combine(basePath, fileName);
48+
}
49+
}
50+
51+
public static IApplicationBuilder UseSwagger(this IApplicationBuilder app, IApiVersionDescriptionProvider provider)
52+
{
53+
app.UseSwagger();
54+
app.UseSwaggerUI(
55+
options =>
56+
{
57+
foreach (var description in provider.ApiVersionDescriptions)
58+
{
59+
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
60+
description.GroupName.ToUpperInvariant());
61+
}
62+
});
63+
64+
return app;
65+
}
66+
}
67+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Linq;
2+
using System.Text.Json;
3+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
4+
using Microsoft.OpenApi.Models;
5+
using Swashbuckle.AspNetCore.SwaggerGen;
6+
7+
namespace N8T.Infrastructure.Swagger
8+
{
9+
public class SwaggerDefaultValues : IOperationFilter
10+
{
11+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
12+
{
13+
var apiDescription = context.ApiDescription;
14+
15+
operation.Deprecated |= apiDescription.IsDeprecated();
16+
17+
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077
18+
foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
19+
{
20+
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387
21+
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
22+
var response = operation.Responses[responseKey];
23+
24+
foreach (var contentType in response.Content.Keys)
25+
{
26+
if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType))
27+
{
28+
response.Content.Remove(contentType);
29+
}
30+
}
31+
}
32+
33+
if (operation.Parameters == null)
34+
{
35+
return;
36+
}
37+
38+
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
39+
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
40+
foreach (var parameter in operation.Parameters)
41+
{
42+
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
43+
44+
if (parameter.Description == null)
45+
{
46+
parameter.Description = description.ModelMetadata?.Description;
47+
}
48+
49+
if (parameter.Schema.Default == null && description.DefaultValue != null)
50+
{
51+
// REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
52+
var json = JsonSerializer.Serialize(description.DefaultValue, description.ModelMetadata.ModelType);
53+
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
54+
}
55+
56+
parameter.Required |= description.IsRequired;
57+
}
58+
}
59+
}
60+
}

src/Customer/CustomerService.Application/CustomerService.Application.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15-
<Folder Include="Endpoints\Commands\" />
16-
<Folder Include="Endpoints\Queries\" />
15+
<Folder Include="V1\Endpoints\Queries\" />
1716
</ItemGroup>
1817

1918
</Project>

src/Customer/CustomerService.Application/Properties/launchSettings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"CustomerService.Application": {
44
"commandName": "Project",
55
"launchBrowser": true,
6+
"launchUrl": "swagger",
67
"environmentVariables": {
78
"ASPNETCORE_ENVIRONMENT": "Development"
89
},

src/Customer/CustomerService.Application/Startup.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using Dapr.Client;
55
using Microsoft.AspNetCore.Builder;
66
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
78
using Microsoft.Extensions.Configuration;
89
using Microsoft.Extensions.DependencyInjection;
910
using Microsoft.Extensions.Hosting;
1011
using N8T.Core.Repository;
1112
using N8T.Infrastructure;
1213
using N8T.Infrastructure.Dapr;
1314
using N8T.Infrastructure.EfCore;
15+
using N8T.Infrastructure.Swagger;
1416
using N8T.Infrastructure.Tye;
1517
using RestEase;
1618
using RestEase.HttpClientFactory;
@@ -49,7 +51,8 @@ public void ConfigureServices(IServiceCollection services)
4951
})
5052
.AddCustomDaprClient()
5153
.AddControllers()
52-
.AddDapr();
54+
.AddDapr()
55+
.Services.AddSwagger<Startup>();
5356

5457
var settingAppUri = IsRunOnTye
5558
? $"http://{AppConsts.SettingAppName}:5005"
@@ -62,7 +65,7 @@ public void ConfigureServices(IServiceCollection services)
6265
}).AddHttpMessageHandler<InvocationHandler>();
6366
}
6467

65-
public void Configure(IApplicationBuilder app)
68+
public void Configure(IApplicationBuilder app, IApiVersionDescriptionProvider provider)
6669
{
6770
if (Env.IsDevelopment())
6871
{
@@ -77,6 +80,8 @@ public void Configure(IApplicationBuilder app)
7780
{
7881
endpoints.MapDefaultControllerRoute();
7982
});
83+
84+
app.UseSwagger(provider);
8085
}
8186
}
8287
}

src/Customer/CustomerService.Application/Endpoints/Commands/CreateCustomer.cs renamed to src/Customer/CustomerService.Application/V1/Endpoints/Commands/CreateCustomer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
using N8T.Core.Repository;
1313
using N8T.Infrastructure.Endpoint;
1414

15-
namespace CustomerService.Application.Endpoints.Commands
15+
namespace CustomerService.Application.V1.Endpoints.Commands
1616
{
17+
[ApiVersion( "1.0" )]
1718
public class CreateCustomer : BaseAsyncEndpoint.WithRequest<CreateCustomer.Command>.WithoutResponse
1819
{
1920
[HttpPost("/api/customers")]

0 commit comments

Comments
 (0)