我正在尝试创建一个中间件,可以记录响应主体以及全局管理异常,我成功了。我的问题是我提出的自定义消息异常它没有显示在响应上。
中间件代码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代码中有什么我错过的吗?
有没有更好的方法来实现我的目的,即记录响应主体以及全局管理异常?
我想你所说的是这段代码并没有将它的响应发送给客户端。
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);
没有写入它正在写入可寻找的内存流的原始正文流。因此,在您写入它之后,您必须将其复制到原始流。所以我相信代码应该是这样的:
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;
}
有一篇精彩的文章详细解释了你的问题 - 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");