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