Вопрос по asp.net-web-api, httpresponse, http-status-codes, c# – Возврат кода состояния http из контроллера Web Api

193

Я пытаюсь вернуть код состояния 304, не модифицированный для метода GET, в контроллере веб-API.

Единственный способ, которым я преуспел, был чем-то вроде этого:

public class TryController : ApiController
{
    public User GetUser(int userId, DateTime lastModifiedAtClient)
    {
        var user = new DataEntities().Users.First(p => p.Id == userId);
        if (user.LastModified <= lastModifiedAtClient)
        {
             throw new HttpResponseException(HttpStatusCode.NotModified);
        }
        return user;
    }
}

Проблема здесь состоит в том, что это не исключение, оно просто не изменено, поэтому кэш клиента исправен. Я также хочу, чтобы возвращаемый тип был User (как показывают все примеры веб-API с GET), а не HttpResponseMessage или что-то в этом роде.

@Aliostad Я использую бета-версию ozba
Ты используешьbeta или жеnightly build? Aliostad
так что не так с возвращениемnew HttpResponseMessage(HttpStatusCode.NotModified) ? Это не работает? Aliostad
@Aliostad Я не могу вернуть HttpResponseMessage, когда тип возвращаемого значения - User, он не компилируется (очевидно). ozba

Ваш Ответ

10   ответов
2

и плюс сообщение, используйте:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
66

если хотите сохранить подпись действия как возвращающего пользователя:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

Если вы хотите вернуть что-то кроме200 тогда вы бросаетеHttpResponseException в вашем действии и передать вHttpResponseMessage Вы хотите отправить клиенту.

Это более элегантное решение (неполный ответ). Почему все предпочитают делать это нелегко?
@Geoiststackoverflow.com/questions/1282252/…, Бросать исключение дорого.
Да, если вы разрабатываете загруженный API, используйте исключение для передачи наиболее распространенного случаяNotModified действительно расточительно. Если все ваши API сделали это, то ваш сервер будет в основном преобразовывать ватты в исключения.
@nagytech, потому что вы не можете возвращать пользовательское сообщение об ошибке, если вы выдаваете ошибку (например, ответ 400) ... также создание исключений глупо для того, что вы ожидаете от кода. Дорого и будет зарегистрировано, когда вы не хотите, чтобы они были. Они на самом деле не являются исключениями.
1

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

public class HttpActionResult : IHttpActionResult
{
    public HttpActionResult(HttpRequestMessage request) : this(request, HttpStatusCode.OK)
    {
    }

    public HttpActionResult(HttpRequestMessage request, HttpStatusCode code) : this(request, code, null)
    {
    }

    public HttpActionResult(HttpRequestMessage request, HttpStatusCode code, object result)
    {
        Request = request;
        Code = code;
        Result = result;
    }

    public HttpRequestMessage Request { get; }
    public HttpStatusCode Code { get; }
    public object Result { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Request.CreateResponse(Code, Result));
    }
}

Затем вы можете добавить метод к вашему ApiController (или лучше к вашему базовому контроллеру) следующим образом:

protected IHttpActionResult CustomResult(HttpStatusCode code, object data) 
{
    // Request here is the property on the controller.
    return new HttpActionResult(Request, code, data);
}

Затем вы можете вернуть его так же, как любой из встроенных методов:

[HttpPost]
public IHttpActionResult Post(Model model)
{
    return model.Id == 1 ?
                Ok() :
                CustomResult(HttpStatusCode.NotAcceptable, new { 
                    data = model, 
                    error = "The ID needs to be 1." 
                });
}
2
public HttpResponseMessage Post(Article article)
{
    HttpResponseMessage response = Request.CreateResponse<Article>(HttpStatusCode.Created, article);

    string uriToTheCreatedItem = Url.Route(null, new { id = article.Id });
    response.Headers.Location = new Uri(Request.RequestUri, uriToTheCreatedItem);

    return response;
}
34

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Не можете указать сообщение?
Использование сообщения на самом деле является принятым ответом. Это просто немного терзее
226

Вот.

Таким образом, хитрость заключается в том, чтобы изменить подписьHttpResponseMessage и использоватьRequest.CreateResponse.

[ResponseType(typeof(User))]
public HttpResponseMessage GetUser(HttpRequestMessage request, int userId, DateTime lastModifiedAtClient)
{
    var user = new DataEntities().Users.First(p => p.Id == userId);
    if (user.LastModified <= lastModifiedAtClient)
    {
         return new HttpResponseMessage(HttpStatusCode.NotModified);
    }
    return request.CreateResponse(HttpStatusCode.OK, user);
}
Это все еще предпочтительный метод в 2015 году? MVC 5?
Добавить к предложению niico, когда тип возвращаемого значенияIHttpActionResult и вы хотите вернуть пользователя, вы можете просто сделатьreturn Ok(user), Если вам нужно вернуть другой код состояния (скажем, запрещено), вы можете просто сделатьreturn this.StatusCode(HttpStatusCode.Forbidden).
Более современная версия возвращает IHttpActionResult, а не HttpResponseMessage (2017)
Он не компилируется в бета-версии ASP.NET MVC 4, поскольку CreateResponse принимает в качестве параметра только код состояния. во-вторых, я хотел получить решение без HttpResponseMessage в качестве возвращаемого значения, поскольку оно устарело:aspnetwebstack.codeplex.com/discussions/350492 ozba
В случае, если это кому-то нужно, получить значение из метода контроллера будетGetUser(request, id, lastModified).TryGetContentValue(out user), гдеuser (в данном случае) являетсяUser объект.
1

return new NotModified();
public class NotModified : IHttpActionResult
{
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    }
}
38

чтобы он возвращал HttpResponseMessage, а затем возвращайте типизированную версию для полного ответа и нетипизированную версию для ответа NotModified.

    public HttpResponseMessage GetComputingDevice(string id)
    {
        ComputingDevice computingDevice =
            _db.Devices.OfType<ComputingDevice>()
                .SingleOrDefault(c => c.AssetId == id);

        if (computingDevice == null)
        {
            return this.Request.CreateResponse(HttpStatusCode.NotFound);
        }

        if (this.Request.ClientHasStaleData(computingDevice.ModifiedDate))
        {
            return this.Request.CreateResponse<ComputingDevice>(
                HttpStatusCode.OK, computingDevice);
        }
        else
        {
            return this.Request.CreateResponse(HttpStatusCode.NotModified);
        }
    }

* Данные ClientHasStale - это мое расширение для проверки заголовков ETag и IfModifiedSince.

Среда MVC все еще должна сериализовать и вернуть ваш объект.

NOTE

Я думаю, что общая версия удаляется в какой-то будущей версии Web API.

@ xeb - да, это абсолютно стоит назвать. Больше информации об асинхронности здесьasp.net/mvc/tutorials/mvc-4/…
Это был точный ответ, который я искал - хотя и как Задача & lt; HttpResponseMessage & lt; T & gt; & gt; тип возврата. Спасибо!
3

..2 возвращает код состояния 304. Это использует ApiController.

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304);
    }

При желании вы можете вернуть объект с ответом

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
10

но это первый результат для этого в поиске Google, и у меня было много времени с этой проблемой (даже при поддержке вас, ребята). Так что здесь ничего не идет ...

Надеюсь, мое решение поможет тем, кто также был сбит с толку.

namespace MyApplication.WebAPI.Controllers
{
    public class BaseController : ApiController
    {
        public T SendResponse<T>(T response, HttpStatusCode statusCode = HttpStatusCode.OK)
        {
            if (statusCode != HttpStatusCode.OK)
            {
                // leave it up to microsoft to make this way more complicated than it needs to be
                // seriously i used to be able to just set the status and leave it at that but nooo... now 
                // i need to throw an exception 
                var badResponse =
                    new HttpResponseMessage(statusCode)
                    {
                        Content =  new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json")
                    };

                throw new HttpResponseException(badResponse);
            }
            return response;
        }
    }
}

а потом просто наследовать от BaseController

[RoutePrefix("api/devicemanagement")]
public class DeviceManagementController : BaseController
{...

а затем использовать его

[HttpGet]
[Route("device/search/{property}/{value}")]
public SearchForDeviceResponse SearchForDevice(string property, string value)
{
    //todo: limit search property here?
    var response = new SearchForDeviceResponse();

    var results = _deviceManagementBusiness.SearchForDevices(property, value);

    response.Success = true;
    response.Data = results;

    var statusCode = results == null || !results.Any() ? HttpStatusCode.NoContent : HttpStatusCode.OK;

    return SendResponse(response, statusCode);
}
Brilliant. Сэкономил мне кучу времени.

Похожие вопросы