¿Cómo manejar páginas de error dinámico en .net MVC Core?

8

Actualmente tengo

app.UseExceptionHandler("/Home/Error");

Quiero hacer la ruta relativa a la ruta original.

Por ejemplo si

Tenant1 / PageThatThrowsError luego app.UseExceptionHandler ("Tenant1 / Home / Error");

pero si

Tenant2 / PageThatThrowsError luego app.UseExceptionHandler ("Tenant2 / Home / Error");

Pensé que podría hacer

app.UseExceptionHandler(
    new ExceptionHandlerOptions
    {
        ExceptionHandler = async (ctx) =>
        {
            //logic that extracts tenant
            ctx.Request.Path = new PathString(Invariant($"{tenant}/Home/Error"));
        }
    }
);

pero esto arroja un 500

EDITAR: todas las soluciones actuales que, por ejemplo, utilizan redireccionamientos pierden el contexto de error actual y no permiten que el controlador llame, por ejemplo, a HttpContext.Features.Get ().

Murdock
fuente
No estoy seguro de si esto ayudará a docs.microsoft.com/en-us/aspnet/core/fundamentals/…
Patrick Mcvay
Probablemente solo manejaría la redirección (suponiendo que tenga una página de error para cada inquilino) en el método de error que es llamado por el controlador de excepción predeterminado, o puede hacer lo que se muestra en el enlace anterior y crear la respuesta dinámicamente.
Patrick Mcvay
El controlador de errores hace un montón de cosas fuera de la caja, como pasar el código de error, etc. Sería bueno tener eso y poder configurar la url dinámicamente.
Murdock el
¿Su aplicación escucha Tenant1 / Home / Error y Tenant2 / Home / Error?
Alireza Mahmoudi
Si. Pero el problema es enrutarlo
Murdock

Respuestas:

2

Suponemos que la aplicación ha requerido rutas y puntos finales de /Tenant1/Home/Errory /Tenant2/Home/Error. Puede resolver el problema usando este código:

app.UseExceptionHandler(
    new ExceptionHandlerOptions
    {
        ExceptionHandler = async (ctx) =>
        {
            string tenant = ctx.Request.Host.Value.Split('/')[0];
            ctx.Response.Redirect($"/{tenant}/Home/Error");
        },
    }
);

Otra solución equivalente es poner el siguiente código en startup.cs:

app.UseExceptionHandler("$/{tenant}/Home/Error");

Suponemos que tenantproviene de algún lugar, como las aplicaciones. Luego, puede obtener fácilmente excepciones en su punto final deseado escribiendo una ruta simple en su acción:

[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 });
}

o puedes crear dos acciones diferentes:

[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 });
}

Actualizar:

Si sus inquilinos se agregan dinámicamente y no se pueden poner en su appsettings.json(lo que supusimos en las soluciones anteriores), puede escribir un middleware para manejar las Excepciones, así es como:

Agregue el middleware en su método Startup.csin Configure:

app.UseMiddleware(typeof(ErrorHandlingMiddleware));

En la siguiente línea, agregue una ruta para errores (exactamente después del middleware):

app.UseMvc(routes =>
    {
       routes.MapRoute(
            name: "errors",
            template: "{tenant}/{controller=Home}/{action=Index}/");
    });

Cree una clase para su middleware y ponga este código en:

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);
    }
}

Tenga en cuenta que se puede añadir todo lo que desea el contexto de esta manera: context.Request.HttpContext.Features.Set<Exception>(ex);.

Y finalmente, debe crear una acción con una ruta adecuada para escribir su lógica allí:

[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 });
}

Tenga en cuenta que el objeto que se estableció en el middleware, ahora se puede recuperar.

Alireza Mahmoudi
fuente
HttpContext.Features.Get <IExceptionHandlerPathFeature> () solía estar disponible dentro del controlador de Error. El uso de la redirección pierde ese contexto y ya no puede recuperarlo.
Murdock
@Murdock ¿Qué pasa con la segunda solución? La segunda solución parece funcionar.
Alireza Mahmoudi
Mis inquilinos son dinámicos, por lo tanto, no puedo agregar estáticamente la ruta.
Murdock
¡La ruta app.UseExceptionHandler($"/{tenant}/Home/Error");no es estática! Puedes poner lo que quieras en lugar de {tenant}, y con la ruta Route("/{TenantId}/Home/Error")]puedes escucharlos a todos.
Alireza Mahmoudi
Ok, pero ¿cómo sabe .net a qué inquilino enrutar con app.UseExceptionHandler ($ "/ {inquilino} / Inicio / Error"). La pregunta pregunta cómo inferirlo de la url entrante que provocó el error.
Murdock