ASP.Net Core MVC 全局异常处理程序

3

我正在开发一个使用Razor视图的ASP.Net Core MVC应用程序。该应用程序由许多表单组成,用户需要填写并提交。我有一个特殊的情况需要将引发应用程序中所有异常记录到日志中。我知道ASP.Net MVC Core带有全局异常处理程序中间件,我们可以在其中捕获应用程序中发生的所有异常并记录。但是同时我还必须向用户显示弹出窗口,提示在提交表单时保存数据出现了错误。如果成功则显示成功弹窗。如果我在控制器操作中放置try-catch块,我可以处理此问题,但我必须从操作本身记录相同的内容。是否有一种方法可以在一个地方处理所有异常,并向用户显示错误弹出窗口,而不是将用户重定向到另一个错误页面。


你有没有考虑使用过滤器?我不确定在MVC上如何使用过滤器,但我在Web API中经常使用它们。 - carlos chourio
通常,未处理和意外的异常应该是非常罕见的事件。所以我不会太担心它会将用户重定向到另一个页面。如果你充分测试你的应用程序,那么几乎没有人会看到它! - ADyson
3个回答

3

这是一个比较长的故事(我使用 jQuery 进行 API 调用)。 首先,我添加了如下的异常处理:

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

    private static Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        var code = HttpStatusCode.InternalServerError; // 500 if unexpected

        var result = new BaseResponseDTO<string>()
        {
            ErrorCode = (int)HttpStatusCode.InternalServerError,
            ErrorMessage = ex.Message,
            Succeed = false,
        };

        var jsonResult = JsonConvert.SerializeObject(result);
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)code;
        return context.Response.WriteAsync(jsonResult);
    }
}

然后注册它(在 app.UseMvc() 之前注册):

app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();

好的,接下来,请调用您的API。我通常会返回如下的DTO类:
public class BaseResponseDTO<T>
{
    public bool Succeed { get; set; }
    public string ErrorMessage { get; set; }
    public T Result { get; set; }
    public int? ErrorCode { get; set; }
}

现在来谈谈我的 Web API:有时会返回一个值,有时会抛出一个异常。

public BaseResponseDTO<string> TestApi()
{
    var r = new Random();
    var random = r.Next(0, 2);
    if (random == 0)
        throw new Exception("My Exception");
    else
        return new BaseResponseDTO<string>() { Succeed = true, Result = "Some result..." };
}

最终,通过jquery进行调用:
function callApi() {
    $.ajax({
        type: 'GET',
        url: 'https://localhost:5001/Home/TestApi',
        data: null,
        dataType: 'json',
        success: function (data) {
            if (data.succeed) {
                alert(data.result);
            }
            else {
                alert(data.errorMessage);
            }
        },
        error: function (error) {
            debugger;
            alert(error.responseJSON.ErrorMessage);
        }
    });
}

如果 Api 返回异常: enter image description here 如果 Api 返回结果: enter image description here

0

在默认的CORE应用程序中,Startup > Configure方法中的位:

app.UseExceptionHandler("/Home/Error");
将任何错误直接反弹到默认控制器/页面:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

可以使用哪些:

var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();

该程序具有exceptionmessageendpointpath属性,因此您可以将所有内容与用户可见的RequestId一起记录在日志中,以便轻松地在日志中查找错误:

public IActionResult Error()
{
    var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();         
    var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

    StringBuilder sb = new StringBuilder();
    sb.Append($"RequestId={requestId};\r\n");           
    sb.Append($"Message={exceptionHandlerPathFeature.Error.Message};\r\n");
    sb.Append($"Endpoint={exceptionHandlerPathFeature.Endpoint};\r\n");
    sb.Append($"Path={exceptionHandlerPathFeature.Path};\r\n");
        
    _logger.LogError(sb.ToString());
   
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

你可以很容易地扩展它来检查Exception类型并进行重定向。


0

来源:https://www.strathweb.com/2018/07/centralized-exception-handling-and-request-validation-in-asp-net-core/

在 Asp.Net Core Web Api 3.1.5 中全局处理异常 我在 asp.net core Web Api 3.1.5 中实现了这些代码,对我有用。

ProblemDetail.cs

public class ProblemDetails
{
    public ProblemDetails();
    [JsonPropertyName("detail")]
    public string Detail { get; set; }
   
    [JsonExtensionData]
    public IDictionary<string, object> Extensions { get; }

    [JsonPropertyName("instance")]
    public string Instance { get; set; }

    [JsonPropertyName("status")]
    public int? Status { get; set; }
   
  
    [JsonPropertyName("title")]
    public string Title { get; set; }
  
    [JsonPropertyName("type")]
    public string Type { get; set; }
}

我的 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();
        //Data Base Configuration
        services.AddDbContext<Context>(option => option.UseSqlServer(Configuration.GetConnectionString("XYZ")));
 
        // In production, the React files will be served from this directory
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/build";
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        //else
        //{
        //    app.UseExceptionHandler("/Error");
        //    // 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.ConfigureExceptionHandler();//This The Main Method For Handel Exception

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseSpaStaticFiles();
      
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });

        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            {
                spa.UseReactDevelopmentServer(npmScript: "start");
            }
        });
    }
}

包含该方法的对象

public static class ExceptionMiddlewareExtensions
{
    public static void ConfigureExceptionHandler(this IApplicationBuilder app)
    {
        app.UseExceptionHandler(appError =>
        {
            appError.Run(async context =>
            {
                var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
                var exception = errorFeature.Error;

                // the IsTrusted() extension method doesn't exist and
                // you should implement your own as you may want to interpret it differently
                // i.e. based on the current principal

                var problemDetails = new ProblemDetails
                {
                    Instance = $"urn:myorganization:error:{Guid.NewGuid()}"
                };

                if (exception is BadHttpRequestException badHttpRequestException)
                {
                    problemDetails.Title = "Invalid request";
                    problemDetails.Status = (int)typeof(BadHttpRequestException).GetProperty("StatusCode",
                        BindingFlags.NonPublic | BindingFlags.Instance).GetValue(badHttpRequestException);
                    problemDetails.Detail = badHttpRequestException.Message;
                }
                else
                {
                    problemDetails.Title = "An unexpected error occurred!";
                    problemDetails.Status = 500;
                    problemDetails.Detail = exception.Demystify() .ToString();//Error 1
                }

                // log the exception etc..

                context.Response.StatusCode = problemDetails.Status.Value;
                context.Response.WriteJson(problemDetails, "application/problem+json");//(Error 2)
            });
        });
    }
}

错误1的解决方案

public static class ExceptionExtentions
{
    private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);

    private static void SetStackTracesString(this Exception exception, string value)
        => stackTraceString.SetValue(exception, value);

    /// <summary>
    /// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
    /// </summary>
    public static T Demystify<T>(this T exception) where T : Exception
    {
        try
        {
            var stackTrace = new EnhancedStackTrace(exception);

            if (stackTrace.FrameCount > 0)
            {
                exception.SetStackTracesString(stackTrace.ToString());
            }

            if (exception is AggregateException aggEx)
            {
                foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
                {
                    ex.Demystify();
                }
            }

            exception.InnerException?.Demystify();
        }
        catch
        {
            // Processing exceptions shouldn't throw exceptions; if it fails
        }

        return exception;
    }

    /// <summary>
    /// Gets demystified string representation of the <paramref name="exception"/>.
    /// </summary>
    /// <remarks>
    /// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
    /// issues if a system relies on the stack trace be in the specific form.
    /// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
    /// computes a demystified string representation and then restores the original state of the exception back.
    /// </remarks>
    [Pure]
    public static string ToStringDemystified(this Exception exception)
        => new StringBuilder().AppendDemystified(exception).ToString();
}

错误2的解决方案

public static class HttpExtensions
{
    private static readonly JsonSerializer Serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore };
    public static void WriteJson<T>(this HttpResponse response, T obj, string contentType = null)
    {
        response.ContentType = contentType ?? "application/json";
        using (var writer = new HttpResponseStreamWriter(response.Body, Encoding.UTF8))
        {
            using (var jsonWriter = new JsonTextWriter(writer))
            {
                jsonWriter.CloseOutput = false;
                jsonWriter.AutoCompleteOnClose = false;
                Serializer.Serialize(jsonWriter, obj);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接