在ASP.Net Core 2 Web API发生异常时捕获JSON请求

时间:2019-01-02 16:37:16

标签: c# asp.net-core asp.net-core-webapi asp.net-core-2.1 asp.net-core-middleware

我正在使用.Net Core 2.1创建ASP.Net Core Web API,并且需要创建用于全局异常处理的自定义中间件。我正在尝试做的是在应用程序中发生异常时捕获JSON请求。由于我使用的是自定义中间件,因此我希望在中间件中可以使用JSON请求。我怎样才能做到这一点?

我尝试通过遵循Marinko Spasojevic上的一篇文章来创建用于集中式异常处理的自定义中间件,并对其进行了少许修改以捕获json请求。似乎该请求已不可用,因为该异常发生在控制器操作内(不在中间件中)。这是我的代码:

这是我的错误日志模型

public class ErrorLog
{
    public DateTime LogDate { get; set; }
    public string URL { get; set; }
    public string Request { get; set; }
    public string Source { get; set; }
    public string Message { get; set; }
}

这是我的项目中使用的标准响应模型

public class BaseResponse<T> : IBaseResponse where T : class
{
    public bool Status { get; set; }
    public string Message { get; set; }
    public IEnumerable<T> Data { get; set; }
}

这是我的自定义异常中间件

public class GlobalException
{
    private readonly RequestDelegate _next;
    private readonly ICustomLogger _logger;

    public GlobalException(RequestDelegate next, ICustomLogger logger)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            ErrorLog log = new ErrorLog();
            log = await HandleLogError(httpContext, ex);

            _logger.LogError(log); // Custom build logger

            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        BaseResponse<object> response = new BaseResponse<object>();
        response.Status = false;
        response.Message = "There is an exception occured.";
        response.Data = new List<object>();

        await context.Response.WriteAsync(response.Serialize());
    }

    private static async Task<ErrorLog> HandleLogError(HttpContext context, Exception exception)
    {
        Stream body = context.Request.Body;

        context.Request.EnableRewind();

        byte[] buffer = new byte[Convert.ToInt32(context.Request.ContentLength)];

        await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);

        string requestText = Encoding.UTF8.GetString(buffer);

        context.Request.Body = body;

        ErrorLog log = new ErrorLog();
        UriBuilder builder = new UriBuilder();

        builder.Scheme = context.Request.Scheme;
        builder.Host = context.Request.Host.Host;
        builder.Path = context.Request.Path.ToString();
        builder.Query = context.Request.QueryString.ToString();

        log.LogDate = DateTime.Now;
        log.URL = builder.Uri.ToString();
        log.Request = requestText;
        log.Source = exception.Source;
        log.Message = exception.Message;

        return log;
    }
}

最后注册中间件

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
        app.UseMiddleware<GlobalException>();
        ...
    }

那么,任何人都可以给我启示吗?任何帮助将不胜感激,谢谢您。

2 个答案:

答案 0 :(得分:3)

  

似乎该请求已不可用,因为该异常发生在控制器操作内(而不是在中间件中)。

首先,如果您要获取的是错误信息,则可以通过ex.InnerException得到根错误。

  

我想做的是在应用程序中发生异常时捕获JSON请求。

此外,您可以在错误处理中间件中读取和记录请求和响应(在这种情况下,我假设它已序列化为json),如下所示。

public async Task InvokeAsync(HttpContext context)
{  
    var requestAsString = await FormatRequest(context.Request);

    var originalBodyStream = context.Response.Body;

    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;

        await _next(context);

        var responseString = await FormatResponse(context.Response);

        await responseBody.CopyToAsync(originalBodyStream);
    }
}

private async Task<string> FormatRequest(HttpRequest request)
{
    var body = request.Body;
    request.EnableRewind();

    var buffer = new byte[Convert.ToInt32(request.ContentLength)];
    await request.Body.ReadAsync(buffer, 0, buffer.Length);
    var bodyAsText = Encoding.UTF8.GetString(buffer);
    request.Body = body;

   return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}

private async Task<string> FormatResponse(HttpResponse response)
{
    response.Body.Seek(0, SeekOrigin.Begin);
    var text = await new StreamReader(response.Body).ReadToEndAsync(); 
    response.Body.Seek(0, SeekOrigin.Begin);

    return $"Response {text}";
}

顺便说一句,我做了一些小的改动以使其正确地适合您的问题,但是功劳已转到此gist页。希望这能解决您的问题。

答案 1 :(得分:0)

显然,导致请求无法检索的这部分代码

...
try
{
    await _next(httpContext); // this will change the httpContext contents
}
...

这是我的最终代码

public class GlobalException
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public GlobalException(RequestDelegate next, ILogger logger)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        HttpContext tempCtx = context; // had to contain the http context
        string request = await FormatRequest(context.Request);

        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            ErrorLog log = new ErrorLog();
            UriBuilder builder = new UriBuilder();

            builder.Scheme = tempCtx.Request.Scheme;
            builder.Host = tempCtx.Request.Host.Host;
            builder.Path = tempCtx.Request.Path.ToString();
            builder.Query = tempCtx.Request.QueryString.ToString();

            log.LogDate = DateTime.Now;
            log.URL = builder.Uri.ToString();
            log.Request = request;
            log.Source = ex.Source;
            log.Message = ex.Message;

            await _logger.LogError(log); // custom logger

            await HandleExceptionAsync(context);
        }
    }

    private async Task<string> FormatRequest(HttpRequest request)
    {
        request.EnableRewind();
        var body = request.Body;

        byte[] buffer = new byte[Convert.ToInt32(request.ContentLength)];

        await request.Body.ReadAsync(buffer, 0, buffer.Length);

        string requestBody = Encoding.UTF8.GetString(buffer);

        body.Seek(0, SeekOrigin.Begin);

        request.Body = body;

        return requestBody;
    }

    private async Task HandleExceptionAsync(HttpContext context)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        BaseResponse<object> response = new BaseResponse<object>();
        response.Status = false;
        response.Message = "There is an exception occured.";
        response.Data = new List<object>();

        await context.Response.WriteAsync(response.Serialize());
    }
}

非常感谢Hasan给我的启发。