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