Как настроить пользовательские заголовки при использовании IHttpActionResult?
В ASP.NET Web API 2 IHttpActionResult
предлагает большую ценность в упрощении кода контроллера, и я не хочу его использовать, но я столкнулся с проблемой.
Мне нужно установить ETag исходящего ответа, и я не могу найти какое-либо свойство, которое дает мне доступ к заголовкам ответов. В настоящий момент я использую вспомогательный метод Ok<T>(T content)
из ApiController
, который возвращает объект OkNegotiatedContentResult<T>
. Это, похоже, не имеет ничего, что позволило бы мне изменить заголовки, хотя.
Я что-то упустил или нет способа сделать это при использовании типов запасов IHttpActionResult
? Я рассматривал обработчик сообщений, но тогда мне нужно было бы выяснить, как передать ETag из действия (ETags генерируются по-разному для разных действий, поэтому это не вопрос создания универсального обработчика для всех действий).
Я бы хотел избежать использования необработанного HttpResponseMessage, но на данный момент это выглядит трудным.
Ответы
Ответ 1
Для вашего сценария вам необходимо создать пользовательский IHttpActionResult
. Ниже приведен пример, когда я выхожу из OkNegotiatedContentResult<T>
, когда он запускает Content-Negotiation
и устанавливает код состояния 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;
}
}
Контроллер:
public class ValuesController : ApiController
{
public IHttpActionResult Get()
{
return new CustomOkResult<string>(content: "Hello World!", controller: this)
{
ETagValue = "You ETag value"
};
}
}
Обратите внимание, что вы также можете получить от NegotiatedContentResult<T>
, и в этом случае вам нужно будет предоставить StatusCode самостоятельно. Надеюсь, это поможет.
Вы можете найти исходный код OkNegotiatedContentResult<T>
и NegotiatedContentResult<T>
, которые, как вы можете себе представить, на самом деле просты.
Ответ 2
Вы можете создать HttpResponseMessage
, добавить заголовки по мере необходимости, а затем создать ResponseMessageResult
из него:
HttpResponseMessage response =new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add("MyHeader", "MyHeaderValue");
return ResponseMessage(response);
Ответ 3
Вот моя простая реализация без ActionFilterAttributes и похожа на ответ AlexACD. В моем решении используется ResponseMessageResult, который реализует интерфейс IHttpActionResult.
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
Это может быть достигнуто с помощью ActionFilterAttribute, который будет проверять ответ после функции контроллера, но до того, как он погаснет, вы можете установить атрибут метода контроллера для добавления этой информации, вот моя реализация ниже:
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
Вот решение, которое я использую в своем общем библиотечном коде Web API 2, который может легко поддерживать настройку любых заголовков или любых других свойств на HttpResponseMessage
, представленных в ExecuteAsync
, без привязки к какой-либо определенной производной NegotiatedContentResult
реализация:
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;
}
}
и пример использования:
new FlexibleNegotiatedContentResult<string>(HttpStatusCode.Created, "Entity created!", controller, response => response.Headers.Location = new Uri("https://myapp.com/api/entity/1"));
Ответ 7
очень старый вопрос Возможно, в то время были и другие ответы, но сегодня вы можете просто добавить эту строку, не меняя и не расширяя IHttpActionResult
. Это отлично добавляет заголовок в вашем ответе. Не забудьте также отформатировать в стандарт RFC 1123, как показано ниже. В противном случае, хотя Last-Modified отображается в заголовках, Клиент не может прочитать его с помощью HttpClient
.
System.Web.HttpContext.Current.Response.Headers.
Add(Microsoft.Net.Http.Headers.HeaderNames.LastModified, DBdateModified.Value.ToString("r"));