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().
To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware: Catches and logs unhandled exceptions. Re-executes the request in an alternate pipeline using the path indicated.
Use the UseExceptionHandler middleware in ASP.NET Core So, to implement the global exception handler, we can use the benefits of the ASP.NET Core build-in Middleware. A middleware is indicated as a software component inserted into the request processing pipeline which handles the requests and responses.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With