Возврат кода состояния http из контроллера Web Api

219

Я пытаюсь вернуть код состояния 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 или что-то вроде этого.

ozba
источник
Вы используете betaили ночные сборки ?
Алиостад
@Aliostad Я использую бета
ozba
так что не так с возвращением new HttpResponseMessage(HttpStatusCode.NotModified)? Это не работает?
Алиостад
@Aliostad Я не могу вернуть HttpResponseMessage, когда возвращаемый тип - Пользователь, он не компилируется (очевидно).
Озба

Ответы:

251

Я не знал ответа, поэтому спросил команду ASP.NET здесь .

Таким образом, хитрость заключается в том, чтобы изменить подпись 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);
}
Aliostad
источник
3
Он не компилируется в бета-версии ASP.NET MVC 4, поскольку CreateResponse принимает только код состояния в качестве параметра. во-вторых, я хотел получить решение без HttpResponseMessage в качестве возвращаемого значения, поскольку оно устарело: aspnetwebstack.codeplex.com/discussions/350492
ozba
5
В случае, если это кому-то нужно, получить значение из метода контроллера можно было бы GetUser(request, id, lastModified).TryGetContentValue(out user), где user(в данном примере) это Userобъект.
Гринн
4
Это все еще предпочтительный метод в 2015 году? MVC 5?
раздавить
4
Более современная версия возвращает IHttpActionResult - не HttpResponseMessage (2017)
niico
8
Чтобы добавить к предложению niico, когда тип возвращаемого значения IHttpActionResultи вы хотите вернуть пользователя, вы можете просто сделать return Ok(user). Если вам нужно вернуть другой код состояния (скажем, запрещено), вы можете просто сделать return this.StatusCode(HttpStatusCode.Forbidden).
Дрю
68

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

public User GetUser(int userId, DateTime lastModifiedAtClient) 

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

Хенрик Фристик Нильсен
источник
9
Это более элегантное решение (неполный ответ). Почему все предпочитают делать это трудным путем?
nagytech
4
@Geoist stackoverflow.com/questions/1282252/… . Бросать исключение дорого.
TIA
10
Да, если вы разрабатываете загруженный API, использование исключения для передачи наиболее распространенного случая NotModifiedдействительно расточительно. Если все ваши API сделали это, то ваш сервер будет в основном преобразовывать ватты в исключения.
Люк Пуплетт
2
@nagytech, потому что вы не можете вернуть пользовательское сообщение об ошибке, если вы выдаваете ошибку (например, ответ 400) ... также создание исключений глупо для того, что вы ожидаете от кода. Дорого и будет зарегистрировано, когда вы не хотите, чтобы они были. Они на самом деле не исключения.
Rocklan
40

В MVC 5 все стало проще:

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

Измените метод API GetXxx, чтобы он возвращал 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 все еще должна сериализовать и вернуть ваш объект.

НОТА

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

Люк Пуплетт
источник
4
Это был точный ответ, который я искал - хотя и возвращаемый тип Task <HttpResponseMessage <T >>. Спасибо!
xeb
1
@ xeb - да, это стоит того Больше информации об асинхронности здесь asp.net/mvc/tutorials/mvc-4/…
Люк Пуплетт
14

Я ненавижу наталкиваться на старые статьи, но это первый результат для этого в поиске 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);
}
Кеннет Гарза
источник
1
Brilliant. Сэкономила мне кучу времени.
gls123
10

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

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

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

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
Ives.me
источник
7

Для ASP.NET Web Api 2 этот пост от MS предлагает изменить тип возвращаемого значения метода на IHttpActionResult. Затем вы можете вернуть встроенную IHttpActionResultреализацию, например Ok, BadRequestи т. Д. ( См. Здесь ) или вернуть собственную реализацию.

Для вашего кода это можно сделать так:

public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
{
    var user = new DataEntities().Users.First(p => p.Id == userId);
    if (user.LastModified <= lastModifiedAtClient)
    {
        return StatusCode(HttpStatusCode.NotModified);
    }
    return Ok(user);
}
datchung
источник
3

Другой вариант:

return new NotModified();

public class NotModified : IHttpActionResult
{
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    }
}
Бора Айдын
источник
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;
}
Джо Смо
источник
2

Если вам нужно вернуть IHttpActionResult и вы хотите вернуть код ошибки плюс сообщение, используйте:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
Крис Хэлкроу
источник
2

Мне не нравится менять свою подпись для использования типа 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." 
                });
}
krillgar
источник
0

Обновление @Aliostads отвечает с использованием большего количества модов, IHttpActionResultпредставленных в Web API 2.

https://docs.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/action-results#ihttpactionresult

public class TryController : ApiController
{
    public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
    {
        var user = new DataEntities().Users.First(p => p.Id == userId);
        if (user.LastModified <= lastModifiedAtClient)
        {
            return StatusCode(HttpStatusCode.NotModified);
            // If you would like to return a Http Status code with any object instead:
            // return Content(HttpStatusCode.InternalServerError, "My Message");
        }
        return Ok(user);
    }
}
Ogglas
источник
0

Попробуй это :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
don_mega
источник