257

I'm trying to return a status code of 304 not modified for a GET method in a web api controller.

The only way I succeeded was something like this:

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

The problem here is that it's not an exception, It's just not modified so the client cache is OK. I also want the return type to be a User (as all the web api examples shows with GET) not return HttpResponseMessage or something like this.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
ozba
  • 6,522
  • 4
  • 33
  • 40

14 Answers14

282

I did not know the answer so asked the ASP.NET team here.

So the trick is to change the signature to HttpResponseMessage and use 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);
}
mare
  • 13,033
  • 24
  • 102
  • 191
Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • 4
    It doesn't compile in ASP.NET MVC 4 beta release, as CreateResponse takes only status code as parameter. secondly I wanted a solution with no HttpResponseMessage as the return value as it's being deprecated: http://aspnetwebstack.codeplex.com/discussions/350492 – ozba May 19 '12 at 13:15
  • 1
    @ozba HttpResponseMessage is not being deprecated, **generic** HttpResponseMessage is. I have not used the generic one here. – Aliostad May 19 '12 at 14:25
  • 1
    I know. But beta is heavily changing. If you are investing anything you got to use newer stuff. But if you **insist** you can go back to using generic HttpResponseMessage. You just cannot have the cake and eat it at the same time. – Aliostad May 19 '12 at 18:19
  • is there a stable version after the Beta? relying on Beta in our production is one thing, but relying on nightly builds that are not stable is quite another thing... – ozba May 20 '12 at 11:53
  • Also do you know what are the major differences between the beta and the currently nightly build? – ozba May 20 '12 at 12:02
  • 6
    In case anyone needs it, to get the value from the controller method would be `GetUser(request, id, lastModified).TryGetContentValue(out user)`, where `user` (in the example case) is a `User` object. – Grinn Mar 19 '13 at 19:56
  • added ResponseType attribute to denote what the response content type is – mare Feb 13 '14 at 09:14
  • 5
    Is this still the preferred method in 2015? MVC 5? – crush Feb 24 '15 at 23:38
  • 1
    @crush API in 5 looks a bit different. http://codethug.com/2015/01/23/web-api-http-response-codes/ (The example is wrong however, it's `return NotFound(...)` not `NotFoundResult`. – nagytech Jul 17 '15 at 21:15
  • I came up with [another solution](http://stackoverflow.com/a/41881941/1195056) built from this solution that doesn't force you to change your method signature. – krillgar Jan 26 '17 at 20:02
  • 6
    The more modern version returns IHttpActionResult - not HttpResponseMessage (2017) – niico Feb 27 '17 at 09:38
  • 13
    To add to niico's suggestion, when the return type is `IHttpActionResult` and you want to return the User, you can just do `return Ok(user)`. If you need to return another status code (say, forbidden) you can just do `return this.StatusCode(HttpStatusCode.Forbidden)`. – Drew Apr 11 '17 at 20:30
  • Happy with the solution but instead of passing request as a parameter, I will go with Luke Puplett solution below using this.Request – NoloMokgosi Aug 04 '18 at 18:13
68

You can also do the following if you want to preserve the action signature as returning User:

public User GetUser(int userId, DateTime lastModifiedAtClient) 

If you want to return something other than 200 then you throw an HttpResponseException in your action and pass in the HttpResponseMessage you want to send to the client.

FreeMan
  • 5,660
  • 1
  • 27
  • 53
Henrik Frystyk Nielsen
  • 3,092
  • 1
  • 19
  • 17
  • 9
    This is a way more elegant solution (albiet incomplete answer). Why is everyone preferring to do it the hard way? – nagytech May 16 '13 at 00:30
  • 5
    @Geoist http://stackoverflow.com/questions/1282252/how-much-more-expensive-is-an-exception-than-a-return-value. Throwing exception is costly. – tia Jul 16 '13 at 11:49
  • 12
    Yeah, if you're designing a busy API, using an exception to communicate the most common case of `NotModified` is really wasteful. If all your APIs did this, then your server will be mostly converting watts to exceptions. – Luke Puplett Nov 28 '13 at 09:51
  • 2
    @nagytech because you can't return a custom error message if you throw an error (like a 400 response)... also throwing exceptions is silly for something that you expect the code to do. Expensive and will be logged when you don't neccesarily want them to be. They're not really exceptions. – Rocklan Mar 19 '15 at 06:02
44

Change the GetXxx API method to return HttpResponseMessage and then return a typed version for the full response and the untyped version for the NotModified response.

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

*The ClientHasStale data is my extension for checking ETag and IfModifiedSince headers.

The MVC framework should still serialize and return your object.

NOTE

I think the generic version is being removed in some future version of the Web API.

Luke Puplett
  • 42,091
  • 47
  • 181
  • 266
  • 4
    This was the exact answer I was looking for -- albeit as a Task> return type. Thanks! – xeb Jul 31 '12 at 09:44
  • 1
    @xeb - yes, that's totally worth calling out. More info on async here http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4 – Luke Puplett May 08 '13 at 14:18
44

In MVC 5, things got easier:

return new StatusCodeResult(HttpStatusCode.NotModified, this);
Jon Bates
  • 3,055
  • 2
  • 30
  • 48
25

For ASP.NET Web Api 2, this post from MS suggests to change the method's return type to IHttpActionResult. You can then return a built in IHttpActionResult implementation like Ok, BadRequest, etc (see here) or return your own implementation.

For your code, it could be done like:

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,778
  • 1
  • 28
  • 29
20

.net core 2.2 returning 304 status code. This is using an ApiController.

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

Optionally you can return an object with the response

    [HttpGet]
    public ActionResult<YOUROBJECT> Get()
    {
        return StatusCode(304, YOUROBJECT); 
    }
Ives.me
  • 2,359
  • 18
  • 24
19

I hate bumping old articles but this is the first result for this in google search and I had a heck of a time with this problem (even with the support of you guys). So here goes nothing...

Hopefully my solution will help those that also was confused.

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

and then just inherit from the BaseController

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

and then using it

[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);
}
Kenneth Garza
  • 1,886
  • 14
  • 12
6

Try this :

return new ContentResult() { 
    StatusCode = 404, 
    Content = "Not found" 
};
prison-mike
  • 451
  • 1
  • 5
  • 14
5

I don't like having to change my signature to use the HttpCreateResponse type, so I came up with a little bit of an extended solution to hide that.

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

You can then add a method to your ApiController (or better your base controller) like this:

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

Then you can return it just like any of the built in methods:

[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
  • 12,596
  • 6
  • 50
  • 86
  • Might want to use `HttpStatusCode.BadRequest` (400) in your example. `HttpStatusCode.NotAcceptable` (406) is used when content negotiation/accept headers specify something that is not accepted by the server. – prison-mike May 05 '21 at 23:19
4

If you need to return an IHttpActionResult and want to return the error code plus a message, use:

return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
Chris Halcrow
  • 28,994
  • 18
  • 176
  • 206
4

Another option:

return new NotModified();

public class NotModified : IHttpActionResult
{
    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.NotModified);
        return Task.FromResult(response);
    }
}
Bora Aydın
  • 439
  • 4
  • 6
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;
}
Jo Smo
  • 6,923
  • 9
  • 47
  • 67
2

An update to @Aliostads answer using the more moden IHttpActionResult introduced in Web API 2.

https://learn.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
  • 62,132
  • 37
  • 328
  • 418
0

I know there are several good answers here but this is what I needed so I figured I'd add this code in case anyone else needs to return whatever status code and response body they wanted in 4.7.x with webAPI.

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));
    }
}
JohnOpincar
  • 5,620
  • 3
  • 35
  • 38