简体   繁体   English

在例外的asp.net核心中间件上获得空响应

[英]Getting empty response on asp.net core middleware on exception

I am trying to create a middleware that can log the response body as well as manage exception globally and I was succeeded about that. 我正在尝试创建一个中间件,可以记录响应主体以及全局管理异常,我成功了。 My problem is that the custom message that I put on exception it's not showing on the response. 我的问题是我提出的自定义消息异常它没有显示在响应上。

Middleware Code 01: 中间件代码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;
        }
    }
}

If I write my middleware like that, my custom exception is working fine but I unable to log my response body. 如果我像这样编写我的中间件,我的自定义异常工作正常但我无法记录我的响应正文。

Middleware Code 02: 中间件代码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;
    }
}

My Controller Action : 我的控制器动作:

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

Now i want to show my exception message with my middleware 01, but it does't work but its work on my middleware 02. 现在我想用我的中间件01显示我的异常消息,但它不起作用,但它在我的中间件02上工作。

So my observation is the problem is occurring for reading the context response. 所以我的观察是读取上下文响应时出现的问题。 Is there anything I have missed in my middleware 01 code? 我的中间件01代码中有什么我错过的吗?

Is there any better way to serve my purpose that log the response body as well as manage exception globally? 有没有更好的方法来实现我的目的,即记录响应主体以及全局管理异常?

I think what you are saying is that this code isn't sending it's response to the client. 我想你所说的是这段代码并没有将它的响应发送给客户端。

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

The reason for this is that await context.Response.WriteAsync(jsonObject, Encoding.UTF8); 原因是await context.Response.WriteAsync(jsonObject, Encoding.UTF8); isn't writing to the original body stream it's writing to the memory stream that is seekable. 不写入原始正文流,它写入可寻找的内存流。 So after you write to it you have to copy it to the original stream. 因此,在您写入它之后,您必须将其复制到原始流。 So I believe the code should look like this: 所以我相信代码应该是这样的:

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

        context.Response.Body.Seek(0, SeekOrigin.Begin);    //IMPORTANT!
        await responseBody.CopyToAsync(originalBodyStream); //IMPORTANT!
        return;
    }

There is a wonderful article explaining in detail your problem - Using Middleware to trap Exceptions in Asp.Net Core . 有一篇精彩的文章详细解释了您的问题 - 使用中间件来捕获Asp.Net Core中的异常

What you need to remember about middleware is the following: 您需要记住的有关中间件的内容如下:

Middleware is added to your app during Startup, as you saw above. 正如您在上面看到的那样,在启动期间,中间件会添加到您的应用中。 The order in which you call the Use... methods does matter! 调用Use ...方法的顺序很重要! Middleware is "waterfalled" down through until either all have been executed, or one stops execution. 中间件被“水封”直到任何一个都被执行,或者一个停止执行。

The first things passed to your middleware is a request delegate. 传递给中间件的第一件事是请求委托。 This is a delegate that takes the current HttpContext object and executes it. 这是一个委托,它接受当前的HttpContext对象并执行它。 Your middleware saves this off upon creation, and uses it in the Invoke() step. 您的中间件在创建时将其保存,并在Invoke()步骤中使用它。 Invoke() is where the work is done. Invoke()是完成工作的地方。 Whatever you want to do to the request/response as part of your middleware is done here. 无论您希望如何对请求/响应作为中间件的一部分,都可以在这里完成。 Some other usages for middleware might be to authorize a request based on a header or inject a header in to the request or response 中间件的一些其他用法可能是基于标头授权请求或将请求或响应注入标头

So what you do, you write a new exception type, and a middleware handler to trap your exception: 那么你做了什么,你编写了一个新的异常类型,以及一个中间件处理程序来捕获你的异常:

New Exception type class: 新的异常类型:

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

And the middlware handler: 和middlware处理程序:

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

Then use your new middleware: 然后使用您的新中间件:

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

The end use is simple: 最终用法很简单:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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