从 Web Api 控制器返回 http 状态码

Posted

技术标签:

【中文标题】从 Web Api 控制器返回 http 状态码【英文标题】:Returning http status code from Web Api controller 【发布时间】:2022-02-09 19:46:00 【问题描述】:

我正在尝试返回 304 的状态代码,未针对 Web api 控制器中的 GET 方法进行修改。

我成功的唯一方法是这样的:

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;
    

这里的问题是它不是异常,它只是没有修改所以客户端缓存是可以的。 我还希望返回类型是用户(正如所有 web api 示例都使用 GET 显示的那样),而不是返回 HttpResponseMessage 或类似的东西。

【问题讨论】:

您使用的是beta 还是nightly build @Aliostad 我正在使用测试版 那么返回 new HttpResponseMessage(HttpStatusCode.NotModified) 有什么问题?不行吗? @Aliostad 当返回类型为 User 时,我无法返回 HttpResponseMessage,它没有编译(显然)。 【参考方案1】:

我不知道答案所以问 ASP.NET 团队here。

所以诀窍是将签名更改为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);

【讨论】:

它不能在 ASP.NET MVC 4 beta 版本中编译,因为 CreateResponse 只接受状态码作为参数。其次,我想要一个没有 HttpResponseMessage 作为返回值的解决方案,因为它已被弃用:aspnetwebstack.codeplex.com/discussions/350492 如果有人需要它,从控制器方法获取值是GetUser(request, id, lastModified).TryGetContentValue(out user),其中user(在示例中)是一个User对象。 这仍然是 2015 年的首选方法吗? MVC 5? 更现代的版本返回 IHttpActionResult - 而不是 HttpResponseMessage (2017) 补充 niico 的建议,当返回类型为IHttpActionResult 并且您想要返回用户时,您可以直接使用return Ok(user)。如果您需要返回另一个状态码(例如,禁止),您可以执行 return this.StatusCode(HttpStatusCode.Forbidden)【参考方案2】:

如果您想将操作签名保留为返回用户,您还可以执行以下操作:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

如果您想返回 200 以外的其他内容,则在您的操作中抛出 HttpResponseException 并传入您要发送给客户端的 HttpResponseMessage

【讨论】:

这是一种更优雅的解决方案(尽管答案不完整)。为什么每个人都喜欢硬着头皮做? @Geoist ***.com/questions/1282252/…。抛出异常代价高昂。 是的,如果您正在设计一个繁忙的 API,使用异常来传达最常见的 NotModified 情况真的很浪费。如果您的所有 API 都这样做,那么您的服务器将主要将瓦特转换为异常。 @nagytech 因为如果您抛出错误(如 400 响应),则无法返回自定义错误消息......对于您期望代码执行的操作来说,抛出异常也是愚蠢的。昂贵,并且在您不需要它们时会被记录下来。他们并不是真正的例外。【参考方案3】:

更改 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 框架仍应序列化并返回您的对象。

注意

我认为通用版本将在 Web API 的某些未来版本中被删除。

【讨论】:

这正是我一直在寻找的答案——尽管它是一个 Task> 返回类型。谢谢! @xeb - 是的,这完全值得一提。更多关于异步的信息在这里asp.net/mvc/tutorials/mvc-4/…【参考方案4】:

在 MVC 5 中,事情变得更容易了:

return new StatusCodeResult(HttpStatusCode.NotModified, this);

【讨论】:

无法指定消息? 使用消息实际上是公认的答案。这只是一个小简洁【参考方案5】:

我讨厌撞旧文章,但这是在 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);

【讨论】:

太棒了。为我节省了大量时间。【参考方案6】:

对于 ASP.NET Web Api 2,this post from MS 建议将方法的返回类型更改为 IHttpActionResult。然后,您可以返回内置的 IHttpActionResult 实现,如 OkBadRequest 等 (see here) 或返回您自己的实现。

对于您的代码,可以这样做:

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

【讨论】:

【参考方案7】:

.net core 2.2 返回 304 状态码。这是使用 ApiController。

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

您可以选择返回一个带有响应的对象

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

【讨论】:

【参考方案8】:

我不喜欢更改我的签名来使用 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." 
                );

【讨论】:

可能希望在您的示例中使用HttpStatusCode.BadRequest (400)。 HttpStatusCode.NotAcceptable (406) 在内容协商/接受标头指定服务器不接受的内容时使用。【参考方案9】:

另一种选择:

return new NotModified();

public class NotModified : IHttpActionResult

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    

【讨论】:

【参考方案10】:

试试这个:

return new ContentResult()  
    StatusCode = 404, 
    Content = "Not found" 
;

【讨论】:

【参考方案11】:

如果你需要返回一个 IHttpActionResult 并且想返回错误码和一条消息,使用:

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

【讨论】:

【参考方案12】:
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;

【讨论】:

【参考方案13】:

使用 Web API 2 中引入的更现代的 IHttpActionResult 更新 @Aliostads 答案。

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

【讨论】:

【参考方案14】:

我知道这里有几个很好的答案,但这是我需要的,所以我想我会添加此代码,以防其他人需要使用 webAPI 在 4.7.x 中返回他们想要的任何状态代码和响应正文。

public class DuplicateResponseResult<TResponse> : IHttpActionResult

    private TResponse _response;
    private HttpStatusCode _statusCode;
    private HttpRequestMessage _httpRequestMessage;
    public DuplicateResponseResult(HttpRequestMessage httpRequestMessage, TResponse response, HttpStatusCode statusCode)
    
        _httpRequestMessage = httpRequestMessage;
        _response = response;
        _statusCode = statusCode;
    

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    
        var response = new HttpResponseMessage(_statusCode);
        return Task.FromResult(_httpRequestMessage.CreateResponse(_statusCode, _response));
    

【讨论】:

以上是关于从 Web Api 控制器返回 http 状态码的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 json 从 web.api 获取返回值到 mvc 控制器?

从 WEB API 控制器以异步方法返回 Void

如何使用 Amplify 框架从 AWS 中的 API 获取 http 状态和详细响应?

ASP.NET 4.5 Web API 2.0,JWT 消息处理程序将状态 0 返回到 Angular 7 HTTP 拦截器

从 ASP.NET Web API 中的控制器返回二进制文件

Web API 方法的返回类型格式器过滤器