简体   繁体   中英

Global Exception Handler ASP.Net Core MVC

I am developing a ASP.Net Core MVC application with Razor view. The application consists of many forms where user should fill and submit. I have a particular situation in which to log all exception that raises in the application to log. I know ASP.Net MVC Core comes with a global exception handler middleware where we can catch all exception happens in the application and log the same there. But at the same time I have to show a popup to the user that an error happened while saving the data on submitting the forms. If its success then show a success popup. If I put a try-catch block in Controller Action I can handle this, but I have to log the same from the action itself. Is there any way in which I can handle all exception in one place and show error popup to user instead of redirecting the user to another error page.

It's a long story (I used jquery for API call). First of all, I add an exception handling like this:

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

And then register it(It must be registered before app.UseMvc()):

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

Ok, After that, call your API. I always return DTO class like this:

public class BaseResponseDTO<T>
{
    public bool Succeed { get; set; }
    public string ErrorMessage { get; set; }
    public T Result { get; set; }
    public int? ErrorCode { get; set; }
}

And now my web API: Sometimes it returns a value and sometimes throws an exception.

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..." };
}

In the end, Call it by 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);
        }
    });
}

If Api returns an exception:

在此处输入图像描述

If Api returns a result:

在此处输入图像描述

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

Globally Handel Exception In Asp.Net Core Web Api 3.1.5 I implement those code in asp.net core Web Api 3.1.5 it's Working For Me

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

My Startup.cs class is

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

And The Method Contain In

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

Solution For Error 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();
}

Solution For Error 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);
            }
        }
    }
}

The bit that comes in a default CORE app Startup > Configure method:

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

Bounces any error directly to the default controller / page:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

Which can use:

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

Which has exception , message , endpoint and path properties, so you can then log everything alongside that user-visible RequestId for easy bug hunting in logs:

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

You could easily extend this to check the Exception type and redirect.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM