简体   繁体   中英

using UseExceptionHandler() with API handling and 404 handling in ASP.NET Core 2

I have the following situation:

  • ASP.NET Core 2 serving api requests on /api/*
  • ASP.NET Core 2 serving health requests on /health
  • ASP.NET Core 2 servering proxy requests on /svc/*
  • ASP.NET Core 2 serving SPA on /app OR / (if you go to example.com it will serve files from /app/ folder.

Now, if someone requests an entity which does not exist, an exception is thrown. This is handled by using the following in Startup.cs

app.UseExceptionHandler("/error");

The ErrorController handles these exceptions and return's stuff like NotFound() , BadRequest() , etc. In the end, they are all JSON responses.

Now, if you were to go to the /hshfdhfgh url, this now results in an empty 404 page because nothing matches. But what I would like to do is to be able to add some custom HTML views for error pages. Perhaps with Razor Pages or something.

I have looked this up and you need it is advised to use the UseExceptionHandler("/error") method so you can return some views. But this would conflict with my JSON responses!

The only thing I can come up with is something like:

if request does not start with /health, /svc/, /, /app or /api/
    app.UseExceptionHandler("/error/404");
else
    app.UseExceptionHandler("/error");

But this feels very hacky. Is there any other way?

And, what would be the best/easiest way to add razor support to my project? currently it has none.

You can use this as a start (it's netcore 3.1, but I don't think we had to make significant changes when migrating from 2). It feels hacky and it's pain to get the handler itself right, and to put it at the exactly right place in Startup (especially if you combine it with things like dealing properly with Unauthorized responses, razor views and static files for those razor views). But I didn't find a better way.

public static readonly PathString ApiPath = new PathString("/api");
public static readonly PathString StaticFilePath = new PathString("/site");
static bool IsApiRequestPredicate(HttpContext context) => context.Request.Path.StartsWithSegments(ApiPath, StringComparison.InvariantCulture);
static bool IsStaticFilePredicate(HttpContext context) => context.Request.Path.StartsWithSegments(StaticFilePath, StringComparison.InvariantCulture);

...

app.UseWhen(x => !IsApiRequestPredicate(x) && !IsStaticFilePredicate(x), builder =>
{
    builder.UseStatusCodePagesWithReExecute("/Error/StatusCodeViewReexecuteHandler/{0}");
    app.UseDeveloperExceptionPage();
});
app.UseWhen(x => IsApiRequestPredicate(x), builder =>
{
    builder.UseExceptionHandler("/Error/ExceptionApiReexecuteHandler");
    builder.UseStatusCodePagesWithReExecute("/Error/StatusCodeApiReexecuteHandler/{0}");
});

You could also use only one handler and decide based on OriginalPath in ErrorController action:

var errorFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();
if (errorFeature.OriginalPath.StartsWith("/api", StringComparison.InvariantCulture))
{
    return BadRequest(new { error = "entity not found" });
} else {
    return View("NotFound");
}
// exceptionFeature gives you access to the exception thrown

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM