Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle dynamic error pages in .net MVC Core?

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().

like image 712
Murdock Avatar asked Mar 23 '20 13:03

Murdock


People also ask

How do you handle errors in dot net core?

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.

How do you handle exceptions globally in .NET core?

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.


1 Answers

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.

like image 78
Alireza Mahmoudi Avatar answered Sep 29 '22 05:09

Alireza Mahmoudi