如何配置 .NET Core 3 应用程序以使用 SPA 和 Swagger UI?

How do I configure a .NET Core 3 application to use a SPA and Swagger UI?

问题

当我在访问 localhost:5001/swagger 时尝试加载 SwaggerUI 时,我在 SwaggerUI 中收到一个错误,提醒我以下内容:

Fetch error undefined /swagger/v1/swagger.json

有趣的是,我的 .NET 应用程序在访问 SwaggerUI 时也会引发错误。

System.OverflowException: Value was either too large or too small for a Decimal.
   at System.Number.ThrowOverflowException(TypeCode type)
   at System.Decimal.DecCalc.VarDecFromR8(Double input, DecCalc& result)
   at System.Decimal.op_Explicit(Double value)
   at NJsonSchema.Generation.JsonSchemaGenerator.ApplyRangeAttribute(JsonSchema schema, IEnumerable`1 parentAttributes)
   at NJsonSchema.Generation.JsonSchemaGenerator.ApplyDataAnnotations(JsonSchema schema, JsonTypeDescription typeDescription)
   at NJsonSchema.Generation.JsonSchemaGenerator.<>c__DisplayClass37_1.<LoadPropertyOrField>b__0(JsonSchemaProperty propertySchema, JsonSchema typeS
chema)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable,
 JsonSchemaResolver schemaResolver, Action`2 transformation)
   at NSwag.Generation.OpenApiSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable, Js
onSchemaResolver schemaResolver, Action`2 transformation)
   at NJsonSchema.Generation.JsonSchemaGenerator.LoadPropertyOrField(JsonProperty jsonProperty, ContextualMemberInfo memberInfo, Type parentType, Js
onSchema parentSchema, JsonSchemaResolver schemaResolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateProperties(Type type, JsonSchema schema, JsonSchemaResolver schemaResolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateObject(JsonSchema schema, JsonTypeDescription typeDescription, JsonSchemaResolver schemaRes
olver)
   at NSwag.Generation.OpenApiSchemaGenerator.GenerateObject(JsonSchema schema, JsonTypeDescription typeDescription, JsonSchemaResolver schemaResolv
er)
   at NJsonSchema.Generation.JsonSchemaGenerator.Generate[TSchemaType](TSchemaType schema, ContextualType contextualType, JsonSchemaResolver schemaR
esolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.Generate[TSchemaType](ContextualType contextualType, JsonSchemaResolver schemaResolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable,
 JsonSchemaResolver schemaResolver, Action`2 transformation)
   at NSwag.Generation.OpenApiSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable, Js
onSchemaResolver schemaResolver, Action`2 transformation)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateArray[TSchemaType](TSchemaType schema, JsonTypeDescription typeDescription, JsonSchemaResol
ver schemaResolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.Generate[TSchemaType](TSchemaType schema, ContextualType contextualType, JsonSchemaResolver schemaR
esolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.Generate[TSchemaType](ContextualType contextualType, JsonSchemaResolver schemaResolver)
   at NJsonSchema.Generation.JsonSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable,
 JsonSchemaResolver schemaResolver, Action`2 transformation)
   at NSwag.Generation.OpenApiSchemaGenerator.GenerateWithReferenceAndNullability[TSchemaType](ContextualType contextualType, Boolean isNullable, Js
onSchemaResolver schemaResolver, Action`2 transformation)
   at NSwag.Generation.AspNetCore.Processors.OperationResponseProcessor.Process(OperationProcessorContext operationProcessorContext)
   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.RunOperationProcessors(OpenApiDocument document, ApiDescription apiDescription,
 Type controllerType, MethodInfo methodInfo, OpenApiOperationDescription operationDescription, List`1 allOperations, OpenApiDocumentGenerator swagge
rGenerator, OpenApiSchemaResolver schemaResolver)
   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerTyp
e, List`1 operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver)
   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateForControllers(OpenApiDocument document, IGrouping`2[] apiGroups, OpenA
piSchemaResolver schemaResolver)
   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(ApiDescriptionGroupCollection apiDescriptionGroups)
   at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(Object serviceProvider)
   at NSwag.AspNetCore.OpenApiDocumentProvider.GenerateAsync(String documentName)
   at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GenerateDocumentAsync(HttpContext context)
   at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GetDocumentAsync(HttpContext context)
   at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService event
s)
   at IdentityServer4.Hosting.MutualTlsTokenEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

我想要发生的事情

我希望能够为我的 SwaggerUI 提供服务,它记录了为我的 React.js 应用程序提供服务的 API。

我是如何解决这个问题的

我使用通常在 JetBrains Rider 或 Visual Studio 和 installed/configured NSwag Nuget 中找到的 'with React.js' 模板创建了一个新的 .NET Core 3。前端主要(如果不是全部)由 React.js 应用程序提供服务,而 .NET Web 应用程序仅用作 API.

Startup.cs

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.EntityFrameworkCore;
using FitnessApplication.Data;
using FitnessApplication.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FitnessApplication
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication()
                .AddIdentityServerJwt();

            services.AddControllersWithViews();
            services.AddRazorPages();

            services.AddControllers().AddJsonOptions(opt =>
            {
                opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
            });

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; });

            services.AddSwaggerDocument();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseIdentityServer();
            app.UseAuthorization();
            
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
            
            app.UseOpenApi();
            app.UseSwaggerUi3();
            
            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });
        }
    }
}

尝试解决问题

我首先尝试通过将对 app.UseOpenApi();app.UseSwaggerUi3(); 的方法调用放在对 app.UseSpa(); 的调用下方来解决这个问题,我认为这些方法在调用时存在某种逻辑错误正在被调用,但这只会让事情变得更糟,使我的 SwaggerUI 完全无法访问。

在阅读我的 .NET 应用程序在访问 localhost:5001/swagger 时引发的错误后,很明显我的模型或控制器中存在错误,导致无法生成 Swagger 规范。事实证明,当模型属性为双精度时,我试图在模型属性验证中使用浮点数。

UserStrengthMovement.cs

[Required]
[Range(0, float.MaxValue)]
[DefaultValue(0.0)]
public double WeightKilograms { get; set; }

上述代码块需要修改为:

[Required]
[Range(0, double.MaxValue)]
[DefaultValue(0.0)]
public double WeightKilograms { get; set; }