如何在错误的 .NET Core API 路由上抛出 404?

How to throwing 404 on bad .NET Core API route?

我有一个 .NET Core 网络应用程序,它有一个 API。我已经根据 的回答定义了一个中间件 class,如下所示:

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate next;
    private readonly ILogger logger;

    public ErrorHandlingMiddleware(RequestDelegate next,
        ILoggerFactory loggerFactory)
    {
        this.next = next;
        logger = loggerFactory.CreateLogger<ErrorHandlingMiddleware>();
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            logger.LogError(0, ex, "An unhandled exception has occurred: " + ex.StackTrace);
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        var code = HttpStatusCode.InternalServerError;
        var message = exception.Message;
        if (exception is BadRequestException)
        {
            code = HttpStatusCode.BadRequest;
        }
        else if (exception is NotFoundException)
        {
            code = HttpStatusCode.NotFound;
        }
        else if (exception is NotAuthorizedException)
        {
            code = HttpStatusCode.Forbidden;
        }
        else if (exception is NotAuthenticatedException)
        {
            code = HttpStatusCode.Unauthorized;
        }
        else
        {
            message = "An unexpected error occurred.";
        }

        var result = JsonConvert.SerializeObject(new { error = message });
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)code;
        return context.Response.WriteAsync(result);
    }
}

错误处理仅在代码中抛出异常时处理。错误的路由不会抛出异常。问题是,如果我尝试访问一个不存在的 API 路由 - 即遵循 API 路由约定并以“/api/adfasdf”开头的路由 - API returns HTML(或者错误页面或者主页,我忘记了)。

我收到了一些在 await next(context); 执行后检查 context.Response.StatusCode 的建议,但它是 200

如何配置我的 Web 应用程序,使其能够识别错误的 API 路由和 returns 404?

更新 这是 where/when 我在启动时加载中间件 class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime, IOptions<OidcConfig> oidcConfigOptions)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    // Add Serilog to the logging pipeline
    loggerFactory.AddSerilog();

    app.UseMiddleware<ErrorHandlingMiddleware>();

    if (env.IsLocal())
    {
        app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
        {
            HotModuleReplacement = true
        });
    }

    var oidcConfig = oidcConfigOptions.Value;

    // Configure the app to use Jwt Bearer Authentication
    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        Authority = oidcConfig.GetAuthority(),
        Audience = oidcConfig.ResourceAppId,
        TokenValidationParameters = new TokenValidationParameters
        {
            RequireExpirationTime = true,
            RequireSignedTokens = true,
            ValidateAudience = true,
            ValidIssuer = oidcConfig.GetIssuer(),
            ValidateIssuer = true,
            ValidateActor = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true
        },
    });

    app.UseSiteIdClaimInjection();

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");

        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });

    appLifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}

引用 ASP.NET Core Middleware Fundamentals - Ordering

The order that middleware components are added in the Configure method defines the order in which they are invoked on requests, and the reverse order for the response. This ordering is critical for security, performance, and functionality.

The Configure method (shown below) adds the following middleware components:

  • Exception/error handling
  • Static file server
  • Authentication
  • MVC

C#

public void Configure(IApplicationBuilder app)
{
    app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
                                            // thrown in the following middleware.

    app.UseStaticFiles();                   // Return static files and end pipeline.

    app.UseIdentity();                     // Authenticate before you access
                                           // secure resources.

    app.UseMvcWithDefaultRoute();          // Add MVC to the request pipeline.
}

In the code above, UseExceptionHandler is the first middleware component added to the pipeline—therefore, it catches any exceptions that occur in later calls.

根据 OP 中提供的代码和引用的文档,我建议更早或首先将您的异常添加到管道中。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime) {
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
    loggerFactory.AddSerilog();

    app.UseMiddleware<ErrorHandlingMiddleware>(); // Call first to catch exceptions
                                                  // thrown in the following middleware.    
    if (env.IsLocal()) {
        app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true });
    }

    //Bunch of other stuff
}

更新基于评论。

我怀疑管道中的一个中间件导致了这个问题。尝试一个一个地删除它们并检查您是否有相同的行为,以缩小哪个是罪魁祸首。

为了后代,我在@Nkosi 帮助发现时获得 200 的原因与 Startup class 中的 MVC 路由定义有关。这是从 https://github.com/aspnet/JavaScriptServices.

自动输入的

解决方案是将我的路由配置更改为以下内容:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
    builder.UseMvc(routes =>
    {
        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });
});

与上述答案类似,我们在 AngularASP.NET MVC Core 项目中使用了它:

        public virtual void Configure(IHostingEnvironment environment, IApplicationBuilder app)
{

            // configurations...

            app.UseMvc(routes =>
            {
                routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });

            app.MapWhen(o => !o.Request.Path.Value.StartsWith("/api"), builder =>
            {
                builder.UseMvc(routes =>
                {
                    routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
                });
            });

}