Повернення коду статусу 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;
    }
}

Проблема тут полягає в тому, що це не виняток, він просто не модифікований, щоб кеш клієнта був у порядку. Я також хочу, щоб тип повернення був Користувачем (як показують усі приклади веб-api з GET), а не повертати HttpResponseMessage або щось подібне.


Ви використовуєте 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);
}

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) ... також викидання винятків є нерозумним для чогось, на що ви очікуєте, що код зробить. Дорогі і будуть реєструватися, коли ви не обов'язково хочете, щоб вони були. Вони насправді не є винятками.
Роклан

40

У MVC 5 все стало простіше:

return new StatusCodeResult(HttpStatusCode.NotModified, this);

3
Не можете вказати повідомлення?
розчавити

1
Використання повідомлення - це фактично прийнята відповідь. Це лише трохи коротше
Джон Бейтс

39

Змініть метод GetXxx API, щоб повернути 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 все ще повинна серіалізувати та повертати ваш об’єкт.

ПРИМІТКА

Я думаю, що загальна версія видаляється в майбутній версії веб-API.


4
Це була точна відповідь, яку я шукав - хоч як завдання Завдання <HttpResponseMessage <T>> тип повернення. Дякую!
xeb

1
@xeb - так, це взагалі варто зателефонувати. Більше інформації про async тут 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
Блискуча. Врятувало мене тону часу.
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); 
    }

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);
}

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." 
                });
}

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);
    }
}

0

Спробуйте це :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.