Como lidar com páginas de erro dinâmicas no .net MVC Core?

8

Atualmente tenho

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

Eu quero fazer o caminho em relação ao caminho original.

Por exemplo, se

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

mas se

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

Eu pensei que seria capaz de fazer

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

mas isso gera um 500

EDIT: Todas as soluções atuais que, por exemplo, usam redirecionamentos, perdem o contexto de erro atual e não permitem que o controlador, por exemplo, chame HttpContext.Features.Get ().

Murdock
fonte
Não tenho certeza se isso ajudará docs.microsoft.com/en-us/aspnet/core/fundamentals/…
Patrick McVay em 23/03
Eu provavelmente lidaria com o redirecionamento (supondo que você tenha uma página de erro para cada inquilino) no método de erro chamado pelo manipulador de exceções padrão, ou você pode fazer como o link acima mostra e criar a resposta dinamicamente.
Patrick Mcvay 23/03
O manipulador de erros faz muitas coisas prontas, como passar o código de erro e assim por diante. Seria bom ainda ter isso e apenas poder definir o URL dinamicamente.
Murdock
seu aplicativo ouve Tenant1 / Home / Error e Tenant2 / Home / Error?
Alireza Mahmoudi 26/03
Sim. Mas o problema está encaminhando-o
Murdock

Respostas:

2

Supomos que o aplicativo tenha exigido rotas e pontos finais de /Tenant1/Home/Errore /Tenant2/Home/Error. Você pode resolver o 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");
        },
    }
);

Outra solução equivalente é colocar o seguinte código no startup.cs:

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

tenantSupomos que isso venha de algum lugar como appsettings. Em seguida, é possível obter facilmente exceções no terminal desejado, escrevendo uma rota simples em sua ação:

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

ou você pode criar duas ações 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 });
}

Atualizar:

Se seus inquilinos são adicionados dinamicamente e não podem ser colocados no seu appsettings.json(o que supomos nas soluções acima), você pode escrever um middleware para lidar com as exceções, eis como:

Adicione o middleware no seu método Startup.csin Configure:

app.UseMiddleware(typeof(ErrorHandlingMiddleware));

Na próxima linha, adicione uma rota para erros (exatamente após o middleware):

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

Crie uma classe para o seu middleware e coloque estes códigos:

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 que você pode adicionar qualquer coisa que você quer para o contexto como este: context.Request.HttpContext.Features.Set<Exception>(ex);.

E, finalmente, você deve criar uma ação com um roteamento apropriado para escrever sua lógica:

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

Observe que o objeto que foi definido no middleware agora pode ser recuperado.

Alireza Mahmoudi
fonte
HttpContext.Features.Get <IExceptionHandlerPathFeature> () costuma estar disponível dentro do controlador de erro. O uso do redirecionamento perde esse contexto e você não pode mais recuperá-lo.
Murdock
@ Murdock E a segunda solução? a segunda solução parece funcionar.
Alireza Mahmoudi 27/03
Meus inquilinos são dinâmicos, portanto, não posso adicionar a rota estática.
Murdock
A rota app.UseExceptionHandler($"/{tenant}/Home/Error");não é estática! Você pode colocar o que quiser em vez de {tenant}, e com o caminho, Route("/{TenantId}/Home/Error")]você pode ouvir todos eles.
Alireza Mahmoudi
Ok, mas como o .net sabe para qual inquilino encaminhar com app.UseExceptionHandler ($ "/ {tenant} / Home / Error"). A pergunta pergunta como deduzi-lo a partir do URL recebido que gerou o erro.
Murdock