Добавление заголовков ответов к промежуточному программному обеспечению ASP.NET Core
Я хочу добавить промежуточное программное обеспечение времени обработки в мой ASP.NET Core WebApi вот так
public class ProcessingTimeMiddleware
{
private readonly RequestDelegate _next;
public ProcessingTimeMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
await _next(context);
context.Response.Headers.Add("X-Processing-Time-Milliseconds", new[] { watch.ElapsedMilliseconds.ToString() });
}
}
Но при этом возникает исключение, говорящее
System.InvalidOperationException: Headers are readonly, reponse has already started.
Как я могу добавить заголовки к ответу?
6 ответов
Неважно, код здесь
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
//To add Headers AFTER everything you need to do this
context.Response.OnStarting(state => {
var httpContext = (HttpContext)state;
httpContext.Response.Headers.Add("X-Response-Time-Milliseconds", new[] { watch.ElapsedMilliseconds.ToString() });
return Task.FromResult(0);
}, context);
await _next(context);
}
Заголовки ответа не могут быть установлены после того, как что-либо было записано в тело ответа. Как только вы передаете запрос следующему промежуточному программному обеспечению, и он записывает в ответ, промежуточное программное обеспечение не может снова установить заголовочные элементы ответа.
Однако существует решение, доступное с использованием метода обратного вызова.
Microsoft.AspNetCore.Http.HttpResponse
определяет OnStarting
Метод, который добавляет делегат, который будет вызван непосредственно перед отправкой заголовков ответа клиенту. Вы можете думать об этом методе как о методе обратного вызова, который будет вызван непосредственно перед началом записи в ответ.
public class ResponseTimeMiddleware
{
private const string RESPONSE_HEADER_RESPONSE_TIME = "X-Response-Time-ms";
private readonly RequestDelegate _next;
public ResponseTimeMiddleware(RequestDelegate next)
{
_next = next;
}
public Task InvokeAsync(HttpContext context)
{
var watch = new Stopwatch();
watch.Start();
context.Response.OnStarting(() =>
{
watch.Stop();
var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
context.Response.Headers[RESPONSE_HEADER_RESPONSE_TIME] = responseTimeForCompleteRequest.ToString();
return Task.CompletedTask;
});
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
В качестве альтернативы вы также можете добавить промежуточное ПО непосредственно в метод Startup.cs Configure.
app.Use(
next =>
{
return async context =>
{
var stopWatch = new Stopwatch();
stopWatch.Start();
context.Response.OnStarting(
() =>
{
stopWatch.Stop();
context.Response.Headers.Add("X-ResponseTime-Ms", stopWatch.ElapsedMilliseconds.ToString());
return Task.CompletedTask;
});
await next(context);
};
});
app.UseMvc();
На связанной ноте, не отвечая на ваш вопрос как таковой, теперь есть Server-Timing
спецификация, стандартный заголовок для обеспечения продолжительности, среди других метрик. Это должно позволить вам использовать
Server-Timing: processingTime;dur=12ms
Вы можете найти спецификацию на https://www.w3.org/TR/server-timing/
Используя перегрузку метода OnStarting:
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
context.Response.OnStarting(() =>
{
watch.Stop();
context
.Response
.Headers
.Add("X-Processing-Time-Milliseconds",
new[] { watch.ElapsedMilliseconds.ToString() });
return Task.CompletedTask;
});
watch.Start();
await _next(context);
}
В вашем примере заголовки уже отправлены, когда выполнение достигает оператора context.Response.Headers.Add(...).
Ты можешь попробовать:
public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
context.Response.OnSendingHeaders(x =>
{
watch.Stop();
context.Response.Headers.Add("X-Processing-Time-Milliseconds", new[] { watch.ElapsedMilliseconds.ToString() });
}, null);
watch.Start();
await _next(context);
watch.Stop();
}