在asp.net核心中间件上出现异常时得到空响应

时间:2019-02-26 05:50:08

标签: c# asp.net-core middleware

我正在尝试创建一个中间件,该中间件可以记录响应主体并全局管理异常,因此我成功了。我的问题是,我放入例外的自定义消息没有显示在响应中。

中间件代码01:

public async Task Invoke(HttpContext context)
{
    context.Request.EnableRewind();

    var originalBodyStream = context.Response.Body;
    using (var responseBody = new MemoryStream())
    {
        try
        {
            context.Response.Body = responseBody;
            await next(context);

            context.Response.Body.Seek(0, SeekOrigin.Begin);
            var response = await new StreamReader(context.Response.Body).ReadToEndAsync();
            context.Response.Body.Seek(0, SeekOrigin.Begin);

            // Process log
             var log = new LogMetadata();
             log.RequestMethod = context.Request.Method;
             log.RequestUri = context.Request.Path.ToString();
             log.ResponseStatusCode = context.Response.StatusCode;
             log.ResponseTimestamp = DateTime.Now;
             log.ResponseContentType = context.Response.ContentType;
             log.ResponseContent = response;
             // Keep Log to text file
             CustomLogger.WriteLog(log);

            await responseBody.CopyToAsync(originalBodyStream);
        }
        catch (Exception ex)
        {
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            var jsonObject = JsonConvert.SerializeObject(My Custom Model);
            await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
            return;
        }
    }
}

如果我这样编写中间件,则自定义异常可以正常工作,但无法记录响应正文。

中间件代码02:

 public async Task Invoke(HttpContext context)
  {
    context.Request.EnableRewind();

    try
    {
        await next(context);
    }
    catch (Exception ex)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        var jsonObject = JsonConvert.SerializeObject(My Custom Model);
        await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
        return;
    }
}

我的控制器操作:

    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        throw new Exception("Exception Message");
    }

现在,我想在中间件01上显示异常消息,但它不起作用,但可以在中间件02上工作。

所以我的观察是读取上下文响应时出现了问题。我的中间件01代码中缺少什么吗?

是否有更好的方法来实现我的目的,即记录响应正文以及全局管理异常?

2 个答案:

答案 0 :(得分:0)

有一篇精彩的文章详细介绍了您的问题-Using Middleware to trap Exceptions in Asp.Net Core

关于中间件,您需要记住以下内容:

如上所见,

中间件已在启动过程中添加到您的应用程序中。调用Use ...方法的顺序很重要!中间件将“瀑布”下来,直到全部执行完毕或停止执行为止。

传递给中间件的第一件事是请求委托。这是一个接受当前HttpContext对象并执行它的委托。您的中间件在创建时将其保存下来,并在Invoke()步骤中使用它。 Invoke()是完成工作的地方。作为中间件的一部分,您想要对请求/响应进行的操作都在这里完成。中间件的其他一些用法可能是基于标头授权请求或将标头注入请求或响应中

那么您要做的就是编写一个新的异常类型,并使用一个中间件处理程序来捕获您的异常:

新异常类型类:

public class HttpStatusCodeException : Exception
{
    public int StatusCode { get; set; }
    public string ContentType { get; set; } = @"text/plain";

    public HttpStatusCodeException(int statusCode)
    {
        this.StatusCode = statusCode;

    }
    public HttpStatusCodeException(int statusCode, string message) : base(message)

    {
        this.StatusCode = statusCode;
    }
    public HttpStatusCodeException(int statusCode, Exception inner) : this(statusCode, inner.ToString()) { }

    public HttpStatusCodeException(int statusCode, JObject errorObject) : this(statusCode, errorObject.ToString())

    {
        this.ContentType = @"application/json";
    }
}

和中间件处理程序:

public class HttpStatusCodeExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<HttpStatusCodeExceptionMiddleware> _logger;

    public HttpStatusCodeExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = loggerFactory?.CreateLogger<HttpStatusCodeExceptionMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (HttpStatusCodeException ex)
        {
            if (context.Response.HasStarted)
            {
                _logger.LogWarning("The response has already started, the http status code middleware will not be executed.");
                throw;
            }

            context.Response.Clear();
            context.Response.StatusCode = ex.StatusCode;
            context.Response.ContentType = ex.ContentType;

            await context.Response.WriteAsync(ex.Message);

            return;
        }
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class HttpStatusCodeExceptionMiddlewareExtensions
{
    public static IApplicationBuilder UseHttpStatusCodeExceptionMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<HttpStatusCodeExceptionMiddleware>();
    }
}

然后使用您的新中间件:

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseHttpStatusCodeExceptionMiddleware();
    }
    else
    {
        app.UseHttpStatusCodeExceptionMiddleware();
        app.UseExceptionHandler();
    }

    app.UseStaticFiles();
    app.UseMvc();
}

最终用途很简单:

throw new HttpStatusCodeException(StatusCodes.Status400BadRequest, @"You sent bad stuff");

答案 1 :(得分:0)

我认为您的意思是该代码没有将其响应发送给客户端。

this.customList = in.createTypedArrayList(CustomObject.CREATOR) // in the constructor which receives (Parcel in) as parameter

这样做的原因是 catch (Exception ex) { context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var jsonObject = JsonConvert.SerializeObject(My Custom Model); await context.Response.WriteAsync(jsonObject, Encoding.UTF8); return; } 并未写入原始主体流,而是写入了可寻找的内存流。因此,在写入后,您必须将其复制到原始流。因此,我认为代码应如下所示:

await context.Response.WriteAsync(jsonObject, Encoding.UTF8);