ASP.NET Core 6 - 没有 Swagger 输出
ASP.NET Core 6 - no Swagger output
在 .NET 6 中,我摆弄了新的启动 class 不知何故我没有得到 swagger json 输出(所以 swagger ui 失败了)。可能某处有错误,但据我了解应该是这样。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddJsonFile("Settings/LogSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/HealthCheckSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/AcmeSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/DbConnectionSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/ApiSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddIniFile("appsettings.ini", optional: true, reloadOnChange: true);
builder.Configuration.AddXmlFile("apssettings.xml", optional: true, reloadOnChange: true);
builder.Configuration.AddIniFile("Settings/appsettings.ini", optional: true, reloadOnChange: true);
builder.Configuration.AddXmlFile("Settings/appsettings.xml", optional: true, reloadOnChange: true);
LoggerProviderCollection Providers = new();
var outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{Method}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}";
Serilog.Formatting.Display.MessageTemplateTextFormatter tf = new(outputTemplate, CultureInfo.InvariantCulture);
ColumnOptions columnOptions = new();
columnOptions.Store.Remove(StandardColumn.MessageTemplate);
columnOptions.Store.Remove(StandardColumn.Properties);
columnOptions.Store.Add(StandardColumn.LogEvent);
columnOptions.DisableTriggers = true;
columnOptions.ClusteredColumnstoreIndex = false;
columnOptions.Store.Remove(StandardColumn.Id);
columnOptions.TimeStamp.ConvertToUtc = true;
columnOptions.AdditionalColumns = new Collection<SqlColumn>
{
new SqlColumn { ColumnName = "RequestPath", PropertyName = "RequestPath", DataType = SqlDbType.NVarChar, DataLength = 1000 },
new SqlColumn { ColumnName = "SourceContext", PropertyName = "SourceContext", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ActionName", PropertyName = "ActionName", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ThreadId", PropertyName = "ThreadId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "RequestId", PropertyName = "RequestId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ActionId", PropertyName = "ActionId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "MachineName", PropertyName = "MachineName", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "MemoryUsage", PropertyName = "MemoryUsage", DataType = SqlDbType.NVarChar, DataLength = 16 }
};
Serilog.Core.Logger logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.WriteTo.Providers(Providers)
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
.WithDefaultDestructurers()
.WithDestructurers(new[] { new DbUpdateExceptionDestructurer() }))
.WriteTo.Map("Tag", builder.Configuration["Serilog:Sql:DefaultTable"], (name, wt) => wt.MSSqlServer(
connectionString: builder.Configuration.GetConnectionString(builder.Configuration["Serilog:Sql:ConnectionStringName"]),
sinkOptions: new Serilog.Sinks.MSSqlServer.MSSqlServerSinkOptions
{
TableName = builder.Configuration["Serilog:Sql:Table"] + name,
SchemaName = builder.Configuration["Serilog:Sql:Schema"],
BatchPeriod = new TimeSpan(0, 0, 5),
AutoCreateSqlTable = true,
BatchPostingLimit = 50,
EagerlyEmitFirstEvent = true
},
columnOptions: columnOptions
))
.Enrich.WithAssemblyName()
.Enrich.WithAssemblyVersion()
.CreateLogger();
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
builder.Logging.AddSerilog(logger);
builder.Services.AddLogging();
builder.WebHost.UseSerilog();
builder.Services.AddResponseCompression();
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes =
ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
builder.Services.AddHttpContextAccessor();
Acme.Sys.Health.RegisterHealthChecks.Register(builder.Services, builder.Configuration);
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
hubOptions.KeepAliveInterval = System.TimeSpan.FromMinutes(1);
});
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));
builder.Services.AddDistributedRateLimiting<AsyncKeyLockProcessingStrategy>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
builder
.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.WithExposedHeaders("*")
);
});
builder.Services.AddEndpointsApiExplorer();
Uri contactUrl = new("http://www.Acme.nl");
var xfile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var filePath = Path.Combine(AppContext.BaseDirectory, xfile);
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "7.44.00",
Title = "Acme7",
Description = "REST Service Acme 7",
TermsOfService = contactUrl,
Contact = new OpenApiContact()
{
Name = "Acme Support",
Email = "support@Acme.nl",
Url = contactUrl
}
});
options.SwaggerDoc("Acme6", new OpenApiInfo
{
Version = "3.999.99",
Title = "Acme3",
Description = "REST Service Acme 3",
TermsOfService = contactUrl,
Contact = new OpenApiContact()
{
Name = "Acme Support 3",
Email = "support@Acme.nl",
Url = contactUrl
}
});
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
options.DocumentFilter<TagOrderByNameDocumentFilter>();
options.ExampleFilters();
options.OperationFilter<AddResponseHeadersFilter>(); // [SwaggerResponseHeader]
options.EnableAnnotations();
options.CustomSchemaIds(x => x.FullName);
options.SchemaFilter<AttributeSchemaFilter>();
options.IncludeXmlComments(filePath);
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
var versions = methodInfo.DeclaringType
.GetCustomAttributes(true)
.OfType<ApiExplorerSettingsAttribute>();
return (docName == "v1" && string.IsNullOrEmpty(apiDesc.GroupName)) || versions.Any(v => string.Equals(v.GroupName, docName, StringComparison.CurrentCultureIgnoreCase));
});
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "OAuth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());
builder.WebHost.UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"));
Acme.Identity.Authentication.Settings _Acme_identity_authentication_settings = new();
builder.Configuration.GetSection(Acme.Identity.Authentication.Settings.SettingsPath).Bind(_Acme_identity_authentication_settings);
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _Acme_identity_authentication_settings.Issuer,
ValidAudience = _Acme_identity_authentication_settings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_Acme_identity_authentication_settings.AccessTokenKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs", System.StringComparison.OrdinalIgnoreCase))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("User", configurePolicy: policy =>
{
policy.RequireAssertion(context => context.User.IsInRole("USER"));
});
options.AddPolicy("Superman", policy =>
{
policy.RequireAssertion(context =>
context.User.IsInRole("SUPERMAN") &&
context.User.HasClaim(c => (c.Type == "IsSuperMan")));
});
options.AddPolicy("SuperUser", policy =>
{
policy.RequireAssertion(context =>
context.User.IsInRole("SUPERUSER"));
});
});
// ------------------------------------------------------------------------------------------------
// add services: Acme specific services
// ------------------------------------------------------------------------------------------------
AcmeCompany.Common.RegisterServices.Register(builder.Services);
builder.Services.Configure<AcmeCompany.Common.Domain.DomainLookupServiceOptions>
(builder.Configuration.GetSection(nameof(AcmeCompany.Common.Domain.DomainLookupServiceOptions)));
builder.Services.Configure<AcmeCompany.Common.Dapper.DapperOptions>(d =>
d.ConnectionString = builder.Configuration.GetConnectionString("Acme7"));
Acme.Sys.Dapper.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Db.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Org.Customer.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Realtime.RegisterServices.Register(builder.Services);
Acme.Sys.License.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Identity.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Job.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Localization.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Org.Employee.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Configuration.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Email.RegisterServices.Register(builder.Services, builder.Configuration);
// ------------------------------------------------------------------------------------------------
// add services: Automapper
// ------------------------------------------------------------------------------------------------
builder.Services.AddAutoMapper(typeof(Program));
var mapperConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new Acme.Sys.Job.MappingProfile());
cfg.AddProfile(new Acme.Org.Customer.MappingProfile());
cfg.AddProfile(new Acme.Sys.Localization.MappingProfile());
cfg.AddProfile(new Acme.Org.Country.MappingProfile());
cfg.AddProfile(new Acme.Identity.MappingProfile());
cfg.AddProfile(new Acme.Identity.Account.MappingProfile());
cfg.AddProfile(new Acme.Configuration.MappingProfile());
});
mapperConfig.AssertConfigurationIsValid();
mapperConfig.CompileMappings();
IMapper mapper = mapperConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
// ------------------------------------------------------------------------------------------------
// add services: Controllers
// ------------------------------------------------------------------------------------------------
builder.Services.AddControllers(mvcoptions => { })
.ConfigureApiBehaviorOptions(apibehaviour =>
{
apibehaviour.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = null;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
builder.Services.AddControllers(options => options.Filters.Add<CacheModificationDateActionFilter>());
builder.Services.AddControllers(options => options.Filters.Add<ValidateModelActionFilter>());
builder.Services.Configure<ApiBehaviorOptions>(opts => opts.SuppressModelStateInvalidFilter = true);
WebApplication app = builder.Build();
app.UseSerilogRequestLogging(options =>
//options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/api/error/error-local-development");
app.UseHsts();
}
else
{
app.UseExceptionHandler("/api/error/error");
}
app.UseRouting();
app.UseCors();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(url: "/swagger/v1/swagger.json", name: "Acme 7 Service");
c.SwaggerEndpoint(url: "/swagger/Acme3/swagger.json", name: "Acme 3");
});
app.UseSerilogRequestLogging(options =>
//options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
});
app.UseIpRateLimiting();
if (app.Configuration.GetValue<bool>("Acme:Https") == true)
{
app.UseHttpsRedirection();
}
app.UseResponseCompression();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
var supportedCultures = new[]
{
new CultureInfo("en"),
new CultureInfo("nl")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
{
var currentCulture = "en";
if (context.Request.HttpContext.User.Identity.IsAuthenticated)
{
var _applicationUserService = context.Request.HttpContext.RequestServices.GetService<IApplicationUserService>();
var applicationUser = await _applicationUserService.GetApplicationUserAsync(context.Request.HttpContext.User);
currentCulture = applicationUser.LanguageId;
}
else
{
// var arguments = context.ActionArguments;
// foreach (var argument in arguments)
// { if argument.name = "credentials" { get the value of the object LanguageId if not null convert to string } }
}
var requestCulture = new ProviderCultureResult(currentCulture);
return await Task.FromResult(requestCulture);
}));
app.UseRequestLocalization(options);
/*
app.Use(async (context, next) =>
{
Endpoint endpoint = context.GetEndpoint();
var rgx = "/(api|hubs)/.*";
if (System.Text.RegularExpressions.Regex.IsMatch(context.Request.Path.Value, rgx) && endpoint == null)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
await context.Response.WriteAsync("Endpoint is not defined");
}
else
{
// throws: object reference not set to an instance of an object: source HealthChecks .UI
await next();
}
});*/
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ResultHub>("/hubs/resultHub");
Acme.Sys.Health.HealthCheckEndPoints.RegisterHealthCheckEndPoints(app.Configuration, endpoints);
});
app.UseSpa(spa => spa.Options.SourcePath = "wwwroot");
app.Run();
您的代码缺少对 UseSwagger
的调用,它负责生成您缺少的 JSON 输出。你可以在 UseSwaggerUI
之前调用它,像这样:
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(url: "/swagger/v1/swagger.json", name: "Acme 7 Service");
c.SwaggerEndpoint(url: "/swagger/Acme3/swagger.json", name: "Acme 3");
});
有关 Swagger 中间件设置的详细信息,请参阅 Add and configure Swagger middleware。
在 .NET 6 中,我摆弄了新的启动 class 不知何故我没有得到 swagger json 输出(所以 swagger ui 失败了)。可能某处有错误,但据我了解应该是这样。
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddJsonFile("Settings/LogSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/HealthCheckSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/AcmeSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/DbConnectionSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("Settings/ApiSettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddIniFile("appsettings.ini", optional: true, reloadOnChange: true);
builder.Configuration.AddXmlFile("apssettings.xml", optional: true, reloadOnChange: true);
builder.Configuration.AddIniFile("Settings/appsettings.ini", optional: true, reloadOnChange: true);
builder.Configuration.AddXmlFile("Settings/appsettings.xml", optional: true, reloadOnChange: true);
LoggerProviderCollection Providers = new();
var outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{Method}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}";
Serilog.Formatting.Display.MessageTemplateTextFormatter tf = new(outputTemplate, CultureInfo.InvariantCulture);
ColumnOptions columnOptions = new();
columnOptions.Store.Remove(StandardColumn.MessageTemplate);
columnOptions.Store.Remove(StandardColumn.Properties);
columnOptions.Store.Add(StandardColumn.LogEvent);
columnOptions.DisableTriggers = true;
columnOptions.ClusteredColumnstoreIndex = false;
columnOptions.Store.Remove(StandardColumn.Id);
columnOptions.TimeStamp.ConvertToUtc = true;
columnOptions.AdditionalColumns = new Collection<SqlColumn>
{
new SqlColumn { ColumnName = "RequestPath", PropertyName = "RequestPath", DataType = SqlDbType.NVarChar, DataLength = 1000 },
new SqlColumn { ColumnName = "SourceContext", PropertyName = "SourceContext", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ActionName", PropertyName = "ActionName", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ThreadId", PropertyName = "ThreadId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "RequestId", PropertyName = "RequestId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "ActionId", PropertyName = "ActionId", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "MachineName", PropertyName = "MachineName", DataType = SqlDbType.NVarChar, DataLength = 255 },
new SqlColumn { ColumnName = "MemoryUsage", PropertyName = "MemoryUsage", DataType = SqlDbType.NVarChar, DataLength = 16 }
};
Serilog.Core.Logger logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.WriteTo.Providers(Providers)
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
.WithDefaultDestructurers()
.WithDestructurers(new[] { new DbUpdateExceptionDestructurer() }))
.WriteTo.Map("Tag", builder.Configuration["Serilog:Sql:DefaultTable"], (name, wt) => wt.MSSqlServer(
connectionString: builder.Configuration.GetConnectionString(builder.Configuration["Serilog:Sql:ConnectionStringName"]),
sinkOptions: new Serilog.Sinks.MSSqlServer.MSSqlServerSinkOptions
{
TableName = builder.Configuration["Serilog:Sql:Table"] + name,
SchemaName = builder.Configuration["Serilog:Sql:Schema"],
BatchPeriod = new TimeSpan(0, 0, 5),
AutoCreateSqlTable = true,
BatchPostingLimit = 50,
EagerlyEmitFirstEvent = true
},
columnOptions: columnOptions
))
.Enrich.WithAssemblyName()
.Enrich.WithAssemblyVersion()
.CreateLogger();
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
builder.Logging.AddSerilog(logger);
builder.Services.AddLogging();
builder.WebHost.UseSerilog();
builder.Services.AddResponseCompression();
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes =
ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
builder.Services.AddHttpContextAccessor();
Acme.Sys.Health.RegisterHealthChecks.Register(builder.Services, builder.Configuration);
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
hubOptions.KeepAliveInterval = System.TimeSpan.FromMinutes(1);
});
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
builder.Services.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));
builder.Services.AddDistributedRateLimiting<AsyncKeyLockProcessingStrategy>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
builder
.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.WithExposedHeaders("*")
);
});
builder.Services.AddEndpointsApiExplorer();
Uri contactUrl = new("http://www.Acme.nl");
var xfile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var filePath = Path.Combine(AppContext.BaseDirectory, xfile);
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "7.44.00",
Title = "Acme7",
Description = "REST Service Acme 7",
TermsOfService = contactUrl,
Contact = new OpenApiContact()
{
Name = "Acme Support",
Email = "support@Acme.nl",
Url = contactUrl
}
});
options.SwaggerDoc("Acme6", new OpenApiInfo
{
Version = "3.999.99",
Title = "Acme3",
Description = "REST Service Acme 3",
TermsOfService = contactUrl,
Contact = new OpenApiContact()
{
Name = "Acme Support 3",
Email = "support@Acme.nl",
Url = contactUrl
}
});
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
options.DocumentFilter<TagOrderByNameDocumentFilter>();
options.ExampleFilters();
options.OperationFilter<AddResponseHeadersFilter>(); // [SwaggerResponseHeader]
options.EnableAnnotations();
options.CustomSchemaIds(x => x.FullName);
options.SchemaFilter<AttributeSchemaFilter>();
options.IncludeXmlComments(filePath);
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
var versions = methodInfo.DeclaringType
.GetCustomAttributes(true)
.OfType<ApiExplorerSettingsAttribute>();
return (docName == "v1" && string.IsNullOrEmpty(apiDesc.GroupName)) || versions.Any(v => string.Equals(v.GroupName, docName, StringComparison.CurrentCultureIgnoreCase));
});
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "OAuth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());
builder.WebHost.UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"));
Acme.Identity.Authentication.Settings _Acme_identity_authentication_settings = new();
builder.Configuration.GetSection(Acme.Identity.Authentication.Settings.SettingsPath).Bind(_Acme_identity_authentication_settings);
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _Acme_identity_authentication_settings.Issuer,
ValidAudience = _Acme_identity_authentication_settings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_Acme_identity_authentication_settings.AccessTokenKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs", System.StringComparison.OrdinalIgnoreCase))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("User", configurePolicy: policy =>
{
policy.RequireAssertion(context => context.User.IsInRole("USER"));
});
options.AddPolicy("Superman", policy =>
{
policy.RequireAssertion(context =>
context.User.IsInRole("SUPERMAN") &&
context.User.HasClaim(c => (c.Type == "IsSuperMan")));
});
options.AddPolicy("SuperUser", policy =>
{
policy.RequireAssertion(context =>
context.User.IsInRole("SUPERUSER"));
});
});
// ------------------------------------------------------------------------------------------------
// add services: Acme specific services
// ------------------------------------------------------------------------------------------------
AcmeCompany.Common.RegisterServices.Register(builder.Services);
builder.Services.Configure<AcmeCompany.Common.Domain.DomainLookupServiceOptions>
(builder.Configuration.GetSection(nameof(AcmeCompany.Common.Domain.DomainLookupServiceOptions)));
builder.Services.Configure<AcmeCompany.Common.Dapper.DapperOptions>(d =>
d.ConnectionString = builder.Configuration.GetConnectionString("Acme7"));
Acme.Sys.Dapper.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Db.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Org.Customer.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Realtime.RegisterServices.Register(builder.Services);
Acme.Sys.License.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Identity.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Job.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Localization.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Org.Employee.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Configuration.RegisterServices.Register(builder.Services, builder.Configuration);
Acme.Sys.Email.RegisterServices.Register(builder.Services, builder.Configuration);
// ------------------------------------------------------------------------------------------------
// add services: Automapper
// ------------------------------------------------------------------------------------------------
builder.Services.AddAutoMapper(typeof(Program));
var mapperConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new Acme.Sys.Job.MappingProfile());
cfg.AddProfile(new Acme.Org.Customer.MappingProfile());
cfg.AddProfile(new Acme.Sys.Localization.MappingProfile());
cfg.AddProfile(new Acme.Org.Country.MappingProfile());
cfg.AddProfile(new Acme.Identity.MappingProfile());
cfg.AddProfile(new Acme.Identity.Account.MappingProfile());
cfg.AddProfile(new Acme.Configuration.MappingProfile());
});
mapperConfig.AssertConfigurationIsValid();
mapperConfig.CompileMappings();
IMapper mapper = mapperConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
// ------------------------------------------------------------------------------------------------
// add services: Controllers
// ------------------------------------------------------------------------------------------------
builder.Services.AddControllers(mvcoptions => { })
.ConfigureApiBehaviorOptions(apibehaviour =>
{
apibehaviour.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.ReferenceHandler = null;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
builder.Services.AddControllers(options => options.Filters.Add<CacheModificationDateActionFilter>());
builder.Services.AddControllers(options => options.Filters.Add<ValidateModelActionFilter>());
builder.Services.Configure<ApiBehaviorOptions>(opts => opts.SuppressModelStateInvalidFilter = true);
WebApplication app = builder.Build();
app.UseSerilogRequestLogging(options =>
//options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/api/error/error-local-development");
app.UseHsts();
}
else
{
app.UseExceptionHandler("/api/error/error");
}
app.UseRouting();
app.UseCors();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(url: "/swagger/v1/swagger.json", name: "Acme 7 Service");
c.SwaggerEndpoint(url: "/swagger/Acme3/swagger.json", name: "Acme 3");
});
app.UseSerilogRequestLogging(options =>
//options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
});
app.UseIpRateLimiting();
if (app.Configuration.GetValue<bool>("Acme:Https") == true)
{
app.UseHttpsRedirection();
}
app.UseResponseCompression();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
var supportedCultures = new[]
{
new CultureInfo("en"),
new CultureInfo("nl")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
{
var currentCulture = "en";
if (context.Request.HttpContext.User.Identity.IsAuthenticated)
{
var _applicationUserService = context.Request.HttpContext.RequestServices.GetService<IApplicationUserService>();
var applicationUser = await _applicationUserService.GetApplicationUserAsync(context.Request.HttpContext.User);
currentCulture = applicationUser.LanguageId;
}
else
{
// var arguments = context.ActionArguments;
// foreach (var argument in arguments)
// { if argument.name = "credentials" { get the value of the object LanguageId if not null convert to string } }
}
var requestCulture = new ProviderCultureResult(currentCulture);
return await Task.FromResult(requestCulture);
}));
app.UseRequestLocalization(options);
/*
app.Use(async (context, next) =>
{
Endpoint endpoint = context.GetEndpoint();
var rgx = "/(api|hubs)/.*";
if (System.Text.RegularExpressions.Regex.IsMatch(context.Request.Path.Value, rgx) && endpoint == null)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
await context.Response.WriteAsync("Endpoint is not defined");
}
else
{
// throws: object reference not set to an instance of an object: source HealthChecks .UI
await next();
}
});*/
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ResultHub>("/hubs/resultHub");
Acme.Sys.Health.HealthCheckEndPoints.RegisterHealthCheckEndPoints(app.Configuration, endpoints);
});
app.UseSpa(spa => spa.Options.SourcePath = "wwwroot");
app.Run();
您的代码缺少对 UseSwagger
的调用,它负责生成您缺少的 JSON 输出。你可以在 UseSwaggerUI
之前调用它,像这样:
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(url: "/swagger/v1/swagger.json", name: "Acme 7 Service");
c.SwaggerEndpoint(url: "/swagger/Acme3/swagger.json", name: "Acme 3");
});
有关 Swagger 中间件设置的详细信息,请参阅 Add and configure Swagger middleware。