web-dev-qa-db-fra.com

Comment définir des en-têtes personnalisés lors de l'utilisation de IHttpActionResult?

Dans ASP.NET Web API 2, la variable IHttpActionResult offre une grande valeur pour simplifier le code du contrôleur et je suis réticent à cesser de l'utiliser, mais j'ai un problème. 

Je dois définir un ETag sur une réponse sortante et je ne trouve aucune propriété qui me donne accès aux en-têtes de la réponse. Pour le moment, j'utilise la méthode d'assistance Ok<T>(T content) à partir de ApiController, qui renvoie un objet OkNegotiatedContentResult<T> . Cela ne semble pas avoir quoi que ce soit qui puisse me permettre de modifier les en-têtes.

Est-ce que je manque quelque chose, ou n'y a-t-il vraiment aucun moyen de faire cela en utilisant les types d'actions IHttpActionResult? Je considérais un gestionnaire de messages, mais je devais ensuite trouver un moyen de passer l'ETag hors de l'action (les ETags sont générés différemment pour différentes actions. Il n'est donc pas nécessaire de créer un gestionnaire générique pour toutes les actions).

J'aimerais éviter de devoir utiliser le HttpResponseMessage brut, mais pour le moment, cela semble difficile.

38
ehdv

Pour votre scénario, vous devez créer une variable IHttpActionResult personnalisée. Voici un exemple où je dérive de OkNegotiatedContentResult<T> car il exécute Content-Negotiation et définit le code d'état Ok.

public class CustomOkResult<T> : OkNegotiatedContentResult<T>
{
    public CustomOkResult(T content, ApiController controller)
        : base(content, controller) { }

    public CustomOkResult(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters) 
        : base(content, contentNegotiator, request, formatters) { }

    public string ETagValue { get; set; }

    public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.ExecuteAsync(cancellationToken);

        response.Headers.ETag = new EntityTagHeaderValue(this.ETagValue);

        return response;
    }        
}

Manette

public class ValuesController : ApiController
{
    public IHttpActionResult Get()
    {
        return new CustomOkResult<string>(content: "Hello World!", controller: this)
            {
                    ETagValue = "You ETag value"
            };
    }
}

Notez que vous pouvez également dériver de NegotiatedContentResult<T>, auquel cas vous devrez fournir vous-même le StatusCode. J'espère que cela t'aides.

Vous pouvez trouver le code source OkNegotiatedContentResult<T> et NegotiatedContentResult<T>, qui, comme vous pouvez l’imaginer, sont simples.

33
Kiran Challa

Vous pouvez créer une HttpResponseMessage, ajouter des en-têtes si nécessaire, puis créer ResponseMessageResult à partir de celui-ci:

HttpResponseMessage response =new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add("MyHeader", "MyHeaderValue");
return ResponseMessage(response);
31
AlexACD

Voici ma mise en œuvre simple sans ActionFilterAttributes et est similaire à la réponse d'AlexACD. Ma solution utilise le ResponseMessageResult qui implémente l'interface IHttpActionResult.

HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
responseMessage.Headers.Add("Headername", "Value");
ResponseMessageResult response = new ResponseMessageResult(responseMessage);
return response;
9
Dave Thompson
public static class HttpExtentions
{
    public static IHttpActionResult AddHeader(this IHttpActionResult action,
        string headerName, IEnumerable<string> headerValues)
    {
        return new HeaderActionResult(action, headerName, headerValues);
    }

    public static IHttpActionResult AddHeader(this IHttpActionResult action,
        string headerName, string header)
    {
        return AddHeader(action, headerName, new[] {header});
    }

    private class HeaderActionResult : IHttpActionResult
    {
        private readonly IHttpActionResult action;

        private readonly Tuple<string, IEnumerable<string>> header;

        public HeaderActionResult(IHttpActionResult action, string headerName,
            IEnumerable<string> headerValues)
        {
            this.action = action;

            header = Tuple.Create(headerName, headerValues);
        }

        public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            var response = await action.ExecuteAsync(cancellationToken);

            response.Headers.Add(header.Item1, header.Item2);

            return response;
        }
    }
}
4
Andriy Tolstoy

Voici une solution que j'utilise dans mon code de bibliothèque Web API 2 commun et qui peut facilement prendre en charge la définition d'en-têtes - ou de toute autre propriété de la variable HttpResponseMessage fournie dans ExecuteAsync--, sans être liée à une implémentation dérivée NegotiatedContentResult spécifique:

public class FlexibleNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    private readonly Action<HttpResponseMessage> _responseMessageDelegate;

    public FlexibleNegotiatedContentResult(HttpStatusCode statusCode, T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
        : base(statusCode, content, contentNegotiator, request, formatters)
    {
    }

    public FlexibleNegotiatedContentResult(HttpStatusCode statusCode, T content, ApiController controller, Action<HttpResponseMessage> responseMessageDelegate = null)
        : base(statusCode, content, controller)
    {
        _responseMessageDelegate = responseMessageDelegate;
    }

    public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage responseMessage = await base.ExecuteAsync(cancellationToken);

        if (_responseMessageDelegate != null)
        {
            _responseMessageDelegate(responseMessage);
        }

        return responseMessage;
    }
}

et un exemple d'utilisation:

new FlexibleNegotiatedContentResult<string>(HttpStatusCode.Created, "Entity created!", controller, response => response.Headers.Location = new Uri("https://myapp.com/api/entity/1"));
3
NathanAldenSr

Cela peut être réalisé avec un ActionFilterAttribute, qui examinera la réponse après la fonction du contrôleur, mais avant de l'éteindre, vous pouvez définir l'attribut sur la méthode du contrôleur pour ajouter ces informations. Voici mon implémentation ci-dessous:

public class EnableETag : ActionFilterAttribute
{

    /// <summary>
    /// NOTE: a real production situation, especially when it involves a web garden
    ///       or a web farm deployment, the tags must be retrieved from the database or some other place common to all servers.
    /// </summary>
    private static ConcurrentDictionary<string, EntityTagHeaderValue> etags = new ConcurrentDictionary<string, EntityTagHeaderValue>();

    public override void OnActionExecuting(HttpActionContext context)
    {
        var request = context.Request;
        if (request.Method == HttpMethod.Get)
        {
            var key = GetKey(request);
            ICollection<EntityTagHeaderValue> etagsFromClient = request.Headers.IfNoneMatch;
            if (etagsFromClient.Count > 0)
            {
                EntityTagHeaderValue etag = null;
                if (etags.TryGetValue(key, out etag) && etagsFromClient.Any(t => t.Tag == etag.Tag))
                {
                    context.Response = new HttpResponseMessage(HttpStatusCode.NotModified);
                    SetCacheControl(context.Response);
                }
            }
        }
    }
    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        var request = context.Request;
        var key = GetKey(request);
        EntityTagHeaderValue etag;
        if (!etags.TryGetValue(key, out etag) || request.Method == HttpMethod.Put ||
        request.Method == HttpMethod.Post)
        {
            etag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");
            etags.AddOrUpdate(key, etag, (k, val) => etag);
        }
        context.Response.Headers.ETag = etag;
        SetCacheControl(context.Response);
    }
    private string GetKey(HttpRequestMessage request)
    {
        return request.RequestUri.ToString();
    }

    /// <summary>
    /// Defines the time period to hold item in cache (currently 10 seconds)
    /// </summary>
    /// <param name="response"></param>
    private void SetCacheControl(HttpResponseMessage response)
    {
        response.Headers.CacheControl = new CacheControlHeaderValue()
        {
            MaxAge = TimeSpan.FromSeconds(10),
            MustRevalidate = true,
            Private = true
        };
    }
}

}

3
PlexDM

très vieille question. D'autres réponses ont probablement eu un sens cette fois-ci, mais aujourd'hui, vous pouvez simplement ajouter cette ligne sans changer ni étendre IHttpActionResult. Il ajoute parfaitement en-tête dans votre réponse. Assurez-vous également de formater dans RFC 1123 standard comme ci-dessous. Sinon, bien que Last-Modified apparaisse dans les en-têtes, le client n'est pas en mesure de le lire avec HttpClient.

    System.Web.HttpContext.Current.Response.Headers.
Add(Microsoft.Net.Http.Headers.HeaderNames.LastModified, DBdateModified.Value.ToString("r"));
0
batmaci