在 ASP.NET Core 3 中如何处理从 400 到 500 的异常

Mos*_*afa 2 c# asp.net-core-mvc asp.net-core

我有一个 ASP.NET Core MVC 项目(Core 的版本是 3),我必须至少处理两个异常,例如 404 和 500,并且有一个 404 的视图,应该说“对不起,找不到页面”,否则错误 500 的另一个页面应该说“处理您的请求时发生错误”。这些页面必须具有 DefaultLayout。我的 Startup.cs 如下:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        services.AddDistributedMemoryCache();
        services.AddSession();
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        //services.AddScoped<Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration>();
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseStatusCodePages(async context =>
        {
            context.HttpContext.Response.ContentType = "text/plain";

            await context.HttpContext.Response.WriteAsync(
                "Status code page, status code: " +
                context.HttpContext.Response.StatusCode);
        });

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error/500");

            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.Use(async (ctx, next) =>
        {
            await next();

            if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
            {
                //Re-execute the request so the user gets the error page
                string originalPath = ctx.Request.Path.Value;
                ctx.Items["originalPath"] = originalPath;
                ctx.Request.Path = "/error/404";
                await next();
            }
        });

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();
        app.UseSession();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });

    }
}
Run Code Online (Sandbox Code Playgroud)

我做了一个错误控制器,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

namespace AgentRegister.Controllers
{
    [Route("error")]
    public class ErrorController : Controller
    {
        private readonly TelemetryClient _telemetryClient;

        public ErrorController(TelemetryClient telemetryClient)
        {
            _telemetryClient = telemetryClient;
        }
        [Route("500")]
        public IActionResult AppError()
        {
            var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            _telemetryClient.TrackException(exceptionHandlerPathFeature.Error);
            _telemetryClient.TrackEvent("Error.ServerError", new Dictionary<string, string>
            {
                ["originalPath"] = exceptionHandlerPathFeature.Path,
                ["error"] = exceptionHandlerPathFeature.Error.Message
            });
            return View();
        }


        [Route("404")]
        public IActionResult PageNotFound()
        {
            string originalPath = "unknown";
            if (HttpContext.Items.ContainsKey("originalPath"))
            {
                originalPath = HttpContext.Items["originalPath"] as string;
            }
            _telemetryClient.TrackEvent("Error.PageNotFound", new Dictionary<string, string>
            {
                ["originalPath"] = originalPath
            });
            return View();
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?任何帮助都会得到帮助!

Mic*_*ael 6

考虑通过将错误处理逻辑移动到专用的“中间件”类来简化您的控制器。

想象一个像这样的简单控制器,它所做的只是定义路由的目标页面,并包含一个示例异常来模拟 500 错误。它不关心具体的错误类型。(为简单起见,允许匿名访问。)

该项目称为TestError

using System;    
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace TestError.Controllers
{
    [AllowAnonymous]
    public class HomeController : Controller
    {
        public HomeController() { }

        [HttpGet]
        public ViewResult Home() => View("Home");

        [HttpGet]
        public ViewResult Bogus() => throw new Exception("Bogus error");

        [HttpGet]
        public ViewResult Error() => View("Error");

        [HttpGet]
        public ViewResult PageNotFound() => View("PageNotFound");
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs 中,就在路由定义的上方,有一个对错误处理程序的引用,如app.UseMiddleware<ErrorHandler>();

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TestError.Infrastructure;

namespace TestError
{
    public class Startup
    {
        //   Updated this class for ASP.NET Core 3
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddRazorPages();
        }

        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                 app.UseDeveloperExceptionPage();
            }
            app.UseStatusCodePages();
            app.UseStaticFiles();
            app.UseMiddleware<ErrorHandler>();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "Default", 
                                                pattern: "{controller=Home}/{action=Home}/{id?}");

                endpoints.MapControllerRoute(name: "Error",
                                                "error",
                                                new { controller = "Home", action = "Error" });

                endpoints.MapControllerRoute(name: "PageNotFound",
                                                "pagenotfound",
                                                new { controller = "Home", action = "PageNotFound" });
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs也需要一个引用,using TestError.Infrastructure;因为中间件类被创建为Infrastructure\ErrorHandler.cs

中间件类查看 HttpContext 管道context.Response.StatusCode并使用 switch 语句,根据需要调用自定义方法来响应每个错误,并且我为 404 错误添加了一个子句。

您可以根据需要为不同的错误代码添加更多子句,并附加自定义方法来处理特定情况,或者如果您想确保错误处理程序不会变得过于复杂和具体,则将它们构建在单独的类中。

一般代码异常作为 500 个错误单独处理,由 catch 块捕获。

using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace TestError.Infrastructure
{
    public class ErrorHandler
    {
        private readonly RequestDelegate _next;

        public ErrorHandler(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);

                //  Handle specific HTTP status codes
                switch (context.Response.StatusCode)
                {
                    case 404:
                    HandlePageNotFound(context);
                    break;

                    case 418:
                    //  Not implemented
                    break;

                    default:
                    break;
                }
            }
            catch (Exception e)
            {
                //  Handle uncaught global exceptions (treat as 500 error)
                HandleException(context, e);
            }
            finally
            {
            }
        }

        //  500
        private static void HandleException(HttpContext context, Exception e)
        {
            context.Response.Redirect("/Error");
        }

        //  404
        private static void HandlePageNotFound(HttpContext context)
        {
            //  Display an information page that displays the bad url using a cookie
            string pageNotFound = context.Request.Path.ToString().TrimStart('/');
            CookieOptions cookieOptions = new CookieOptions();
            cookieOptions.Expires = DateTime.Now.AddMilliseconds(10000);
            cookieOptions.IsEssential = true;
            context.Response.Cookies.Append("PageNotFound", pageNotFound, cookieOptions);
            context.Response.Redirect("/PageNotFound");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

HandleException方法创建一个重定向到“/错误”页面。

Error.cshtml与您期望的差不多

@{
    Layout = "~/Views/Shared/_Layout_Main.cshtml";
    ViewData["Title"] = "Error";
}
<div id="dataSection">
    <div class="TextLine Label">An error occurred while processing your request</div>
</div>
Run Code Online (Sandbox Code Playgroud)

HandlePageNotFound创建一个短期 cookie 来存储请求页面的地址,并重定向到“/PageNotFound”。

PageNotFound.cshtml引用 cookie 以显示有意义的错误

@{
    Layout = "_Layout_Main";
    ViewData["Title"] = "Page Not Found";
    string notFoundMessage = "Sorry, The page cannot be found";
    string pageNotFound = Context.Request.Cookies["PageNotFound"];
    if (pageNotFound != null){
        notFoundMessage += " : ";
    }
}
<div id="dataSection">
    <div class="TextLine Label">@notFoundMessage<b>@pageNotFound</b></div>
</div>
Run Code Online (Sandbox Code Playgroud)

示例用法,有一个主页如下(有一个布局页面)

@{
    ViewData["Title"] = "Home";
    Layout = "~/Views/Shared/_Layout_Main.cshtml";
}

<h1>Hello World</h1>
Run Code Online (Sandbox Code Playgroud)

示例 1,/Home/Home:查找Home.cshtml 在此处输入图片说明 示例 2,/Nopage:重定向到PageNotFound.cshtml 在此处输入图片说明 示例 3, /Home/Bogus:抛出异常并重定向到Error.cshtml 在此处输入图片说明

希望这有助于错误处理。对此有很多变化,如上所述,您可以添加更多 switch 子句,甚至包括 500 的特定案例。

更详细的示例还包括一些日志记录,为了简单起见,我将其排除在外。