问题
Currently I have
app.UseExceptionHandler("/Home/Error");
I want to make the path relative to the original path.
For example if
Tenant1/PageThatThrowsError then app.UseExceptionHandler("Tenant1/Home/Error");
but if
Tenant2/PageThatThrowsError then app.UseExceptionHandler("Tenant2/Home/Error");
I thought I would be able to do
app.UseExceptionHandler(
new ExceptionHandlerOptions
{
ExceptionHandler = async (ctx) =>
{
//logic that extracts tenant
ctx.Request.Path = new PathString(Invariant($"{tenant}/Home/Error"));
}
}
);
but this throws a 500
EDIT: All the current solutions that for example uses redirects loses the current error context and does not allow the controller to for example call HttpContext.Features.Get().
回答1:
We suppose that the application has required routes and endpoints of /Tenant1/Home/Error
and /Tenant2/Home/Error
. You can solve the issue using this code:
app.UseExceptionHandler(
new ExceptionHandlerOptions
{
ExceptionHandler = async (ctx) =>
{
string tenant = ctx.Request.Host.Value.Split('/')[0];
ctx.Response.Redirect($"/{tenant}/Home/Error");
},
}
);
Another equivalent solution is putting the following code on the startup.cs
:
app.UseExceptionHandler("$/{tenant}/Home/Error");
We suppose that tenant
comes from somewhere like appsettings. Then you can easily get exceptions on your desired endpoint by writing a simple route on your action:
[Route("/{TenantId}/Home/Error")]
public IActionResult Error(string TenantId)
{
string Id = TenantId;
// Here you can write your logic and decide what to do based on TenantId
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
or you can create two different actions:
[Route("/Tenant1/Home/Error")]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
[Route("/Tenant2/Home/Error")]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
Update:
If your tenants are dynamically added and can't be put in your appsettings.json
(what we've supposed in the above solutions) you can write a middle-ware to handle the Exceptions, here is how:
Add the middle-ware in your Startup.cs
in Configure
method:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
At the next line add a route for errors (exactly after the middle-ware):
app.UseMvc(routes =>
{
routes.MapRoute(
name: "errors",
template: "{tenant}/{controller=Home}/{action=Index}/");
});
Create a class for your middle-ware, and put these code on:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex,this.next);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex, RequestDelegate next)
{
string tenant = "tenant1";//write your logic something like this: context.Request.Path.Value.Split('/')[0];
context.Request.Path = new PathString($"/{tenant}/Home/Error");
context.Request.HttpContext.Features.Set<Exception>(ex);// add any object you want to the context
return next.Invoke(context);
}
}
Note that you can add anything you want to the context like this: context.Request.HttpContext.Features.Set<Exception>(ex);
.
And finally you should create an action with an appropriate routing to write your logic there:
[Route("/{TenantId}/Home/Error")]
public IActionResult Error(string TenantId)
{
string Id = TenantId;
var exception= HttpContext.Features.Get<Exception>();// you can get the object which was set on the middle-ware
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
Note that the object which was set on the middle-ware, now can be retrieved.
来源:https://stackoverflow.com/questions/60814674/how-to-handle-dynamic-error-pages-in-net-mvc-core