web-dev-qa-db-fra.com

Ajouter des en-têtes de réponse au middleware ASP.NET Core

Je souhaite ajouter un middleware de temps de traitement à mon ASP.NET Core WebApi comme ceci

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

Mais faire cela jette une exception disant

 System.InvalidOperationException: Headers are readonly, reponse has already started.

Comment puis-je ajouter des en-têtes à la réponse?

24
Gillardo

Qu'à cela ne tienne, le code est là

    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);
    }
37
Gillardo

Vous pouvez également ajouter un middleware directement dans la méthode de configuration Startup.cs.

        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();
5
Tee

En utilisant une surcharge de la méthode 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); 
}
4
Andriy Tolstoy

Les en-têtes de réponse ne peuvent pas être définis après quoi que ce soit ait été écrit dans le corps de la réponse .

Cependant, une solution est disponible en utilisant une méthode de rappel.

Microsoft.AspNetCore.Http.HttpResponse définit la méthode OnStarting, qui ajoute un délégué à appeler juste avant que les en-têtes de réponse soient envoyés au client. Vous pouvez considérer cette méthode comme une méthode de rappel qui sera appelée juste avant que l'écriture dans la réponse ne commence. 

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);
        }
    }
3
Abhinav Galodha

Dans votre exemple d'en-têtes déjà envoyés, lorsque l'exécution atteint l'instruction context.Response.Headers.Add (...).

Tu peux essayer:

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

Sur une note connexe, sans répondre à votre question en tant que telle, il existe maintenant une spécification Server-Timing, un en-tête standard fournissant des durées, entre autres métriques. Cela devrait vous permettre d'utiliser

Server-Timing: processingTime;dur=12ms

Vous pouvez trouver la spécification sur https://www.w3.org/TR/server-timing/

0
SerialSeb