首页 > 解决方案 > 如何处理 .net MVC Core 中的动态错误页面?

问题描述

目前我有

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

我想让路径相对于原始路径。

例如,如果

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

但如果

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

我以为我能做到

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

但这会引发 500

编辑:例如使用重定向的所有当前解决方案都会丢失当前的错误上下文,并且不允许控制器例如调用 HttpContext.Features.Get()。

标签: c#asp.net-coreasp.net-core-mvcasp.net-core-middleware

解决方案


我们假设应用程序需要 和 的路由和/Tenant1/Home/Error端点/Tenant2/Home/Error。您可以使用以下代码解决问题:

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

另一个等效的解决方案是将以下代码放在startup.cs

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

我们假设这tenant来自 appsettings 之类的地方。然后,您可以通过在您的操作上编写一个简单的路由来轻松地在您想要的端点上获得异常:

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

或者您可以创建两个不同的操作:

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

更新:

如果您的租户是动态添加的并且不能放入您的appsettings.json(我们在上述解决方案中假设的),您可以编写一个中间件来处理异常,方法如下:

Startup.cs在您的inConfigure方法中添加中间件:

app.UseMiddleware(typeof(ErrorHandlingMiddleware));

在下一行添加错误路由(正好在中间件之后):

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

为您的中间件创建一个类,并将这些代码放在:

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

请注意,您可以像这样将任何您想要的内容添加到上下文中context.Request.HttpContext.Features.Set<Exception>(ex);

最后,您应该创建一个带有适当路由的操作,以便在那里编写您的逻辑:

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

请注意,现在可以检索在中间件上设置的对象。


推荐阅读