Возвращаемые типы WebApi 2

Я смотрю документацию по WebAPI 2, и я сильно разочарован тем, как результаты действий архивируются. Я действительно надеюсь, что есть лучший способ.

Итак, документация говорит, что я могу вернуть их:

**void**    Return empty 204 (No Content)

**HttpResponseMessage** Convert directly to an HTTP response message.

**IHttpActionResult**   Call ExecuteAsync to create an HttpResponseMessage, then convert to an HTTP response message.

**Other type**  Write the serialized return value into the response body; return 200 (OK).

Я не вижу чистого способа вернуть массив элементов с настраиваемым кодом статуса HTTP, настраиваемыми заголовками и с автосогласованным контентом.

Что бы я хотел увидеть, это что-то вроде

public HttpResult<Item> Post()
{
   var item = new Item();
   var result = new HttpResult<Item>(item, HttpStatusCode.Created);
   result.Headers.Add("header", "header value");

   return result;
}

Таким образом, я могу взглянуть на метод и сразу увидеть, что возвращается, и изменить код состояния и заголовки.

Самое близкое, что я нашел, это NegotiatedContentResult<T>, с странной сигнатурой (зачем нужен экземпляр контроллера?), но нет способа установить настраиваемые заголовки?

Есть ли лучший способ?

Ответ 1

Я не думаю, что дизайнеры web-api, предназначенные для методов контроллера, будут возиться с заголовками. Образец дизайна, похоже, должен использовать DelegatingHandler, ActionFilterAttribute и переопределяемый метод ExecuteAsync ApiController для обработки аутентификации и отклика.

Итак, может быть, нужна ваша логика для согласования содержимого сообщения?

Однако, если вам определенно нужно управлять заголовками из вашего метода контроллера, вы можете сделать небольшую настройку, чтобы заставить его работать. Для этого вы можете создать свой собственный DelegationHandler, который пересылает выбранные заголовки из заголовков ответов "Внутренний":

public class MessageHandlerBranding : DelegatingHandler {
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);
        //If we want to forward headers from inner content we can do this:
        if (response.Content != null && response.Content.Headers.Any())
        {
            foreach (var hdr in response.Content.Headers)
            {
                var keyUpr = hdr.Key.ToUpper(); //Response will not tolerate setting of some header values
                if ( keyUpr != "CONTENT-TYPE" && keyUpr != "CONTENT-LENGTH")
                {
                    string val = hdr.Value.Any() ? hdr.Value.FirstOrDefault() : "";
                    response.Headers.Add(hdr.Key, val);                       
                }
            }
        }
        //Add our branding header to each response
        response.Headers.Add("X-Powered-By", "My product");
        return response;
    }  
}

Затем вы регистрируете этот обработчик в своей конфигурации web-api, обычно это файл GlobalConfig.cs.

config.MessageHandlers.Add(new MessageHandlerBranding());

Вы также можете написать свой собственный пользовательский класс для объекта ответа следующим образом:

public class ApiQueryResult<T> : IHttpActionResult where T : class
{
    public ApiQueryResult(HttpRequestMessage request)
    {
        this.StatusCode = HttpStatusCode.OK; ;
        this.HeadersToAdd = new List<MyStringPair>();
        this.Request = request;
    }

    public HttpStatusCode StatusCode { get; set; }
    private List<MyStringPair> HeadersToAdd { get; set; }
    public T Content { get; set; }
    private HttpRequestMessage Request { get; set; }

    public void AddHeaders(string headerKey, string headerValue)
    {
        this.HeadersToAdd.Add(new MyStringPair(headerKey, headerValue));
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = this.Request.CreateResponse<T>(this.StatusCode, this.Content);
        foreach (var hdr in this.HeadersToAdd)
        {
            response.Content.Headers.Add(hdr.key, hdr.value); 
        }
        return Task.FromResult(response);
    }


    private class MyStringPair
    {
        public MyStringPair(string key, string value)
        {
            this.key = key;
            this.value = value;
        }
        public string key;
        public string value;
    }
}

И используйте его как в контроллере:

 [HttpGet]
    public ApiQueryResult<CustomersView> CustomersViewsRow(int id)
    {
        var ret = new ApiQueryResult<CustomersView>(this.Request);
        ret.Content = this.BLL.GetOneCustomer(id);
        ret.AddHeaders("myCustomHkey","myCustomValue");
        return ret;
    }

Ответ 2

Следующий код должен предоставить вам все, что вы хотите:

[ResponseType(typeof(Item))]
public IHttpActionResult Post()
{
    var item = new Item();
    HttpContext.Current.Response.AddHeader("Header-Name", "Header Value");
    return Content(HttpStatusCode.Created, item);
}

... если вам действительно нужно вернуть массив элементов...

[ResponseType(typeof(List<Item>))]
public IHttpActionResult Post()
{
    var items = new List<Item>();
    // Do something to fill items here...
    HttpContext.Current.Response.AddHeader("Item-Count", items.Count.ToString());
    return Content(HttpStatusCode.Created, items);
}