简体   繁体   中英

How to set custom headers when using IHttpActionResult?

In ASP.NET Web API 2, the IHttpActionResult offers a lot of value in simplifying controller code and I'm reluctant to stop using it, but I've hit a problem.

I need to set an ETag on an outgoing response, and I cannot find any property which gives me access to the response's headers. At the moment I'm using the Ok<T>(T content) helper method from the ApiController , which returns an OkNegotiatedContentResult<T> object. That doesn't seem to have anything exposed which would let me modify the headers though.

Am I missing something, or is there really no way to do this while using the stock IHttpActionResult types? I considered a message handler, but then I'd have to figure out how to pass the ETag out of the action (the ETags are generated differently for different actions, so it's not a matter of making a generic handler for all actions).

I'd like to avoid having to use the raw HttpResponseMessage, but at the moment that's looking difficult.

For your scenario, you would need to create a custom IHttpActionResult . Following is an example where I derive from OkNegotiatedContentResult<T> as it runs Content-Negotiation and sets the Ok status code.

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

Controller :

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

Note that you can also derive from NegotiatedContentResult<T> , in which case you would need to supply the StatusCode yourself. Hope this helps.

You can find the source code of OkNegotiatedContentResult<T> and NegotiatedContentResult<T> , which as you can imagine are simple actually.

You can create a HttpResponseMessage , add headers as needed and then create ResponseMessageResult from it:

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

Here is my simple implementation without ActionFilterAttributes and is similar to AlexACD's response. My solution uses the ResponseMessageResult which implements the IHttpActionResult interface.

HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
responseMessage.Headers.Add("Headername", "Value");
ResponseMessageResult response = new ResponseMessageResult(responseMessage);
return response;
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;
        }
    }
}

This can be achieved with an ActionFilterAttribute, which will examine the response after the controller function but before it goes out, then you can set the attribute on the controller method to add this information, here is my implementation below:

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

}

Here is a solution I use in my common Web API 2 library code that can easily support setting any headers--or any other properties on the HttpResponseMessage provided in ExecuteAsync --without being tied to any specific derived NegotiatedContentResult implementation:

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

and an example usage:

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

very old question. Probably other answers made sense that time but today you can simply add this line without changing or extending IHttpActionResult . It perfectly adds header in your response. Make sure also to format into RFC 1123 standard as below. Otherwise, although Last-Modified appears in the headers, Client is not able to read it using HttpClient .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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