可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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.
回答2:
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);
回答3:
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;
回答4:
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;
}
}
}
回答5:
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
};
}
}
}
回答6:
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"));
回答7:
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"));