问题
I have a .NET Core web app which has an API. I've defined an Middleware class based on this answer like so:
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);
}
}
The error handling only handles when an exception is thrown in code. A bad route does not throw an exception. The problem is that if I try to access a non-existent API route - that is, one that follows the API route convention and starts with "/api/adfasdf" - the API returns HTML (or the error page or the home page, I forget).
I've received some suggestions to check the context.Response.StatusCode after await next(context); executes, but it's 200.
How can I configure my web app such that it recognizes a bad API route and returns a 404?
UPDATE Here is where/when I load the middleware in my Startup 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());
}
回答1:
Referencing 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,
UseExceptionHandleris the first middleware component added to the pipeline—therefore, it catches any exceptions that occur in later calls.
Based on code provided in OP and quoted documentation I would suggest adding your exception earlier or first to the pipeline.
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
}
UPDATE Based on comments.
I suspect that one of the middleware further down the pipeline is causing this issue. try removing them one by one and checking if you get the same behavior in order to narrow down which one is the culprit.
回答2:
For posterity, the reason I was getting a 200 as @Nkosi helped uncover had to do with an MVC route definition in the Startup class. This came in automatically from https://github.com/aspnet/JavaScriptServices.
The solution was to change my route config to the following:
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" });
});
});
回答3:
Similar to above answer, We are using this in our Angular and ASP.NET MVC Core project:
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" });
});
});
}
来源:https://stackoverflow.com/questions/44033517/how-to-throwing-404-on-bad-net-core-api-route