Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net WebApi deserializes UTC time string to local time

I have this url

http://example.com/api/record/getall?startdate=1994-11-05T17:15:30Z

and this webapi endpoint

[ActionName("GetAll")] public object GetAll(DateTime startDate) {      ... } 

The problem I face is that the startDate received the deserialized string as a local time, "11/5/1994 9:15:30 AM" instead of stay in UTC time which what I wanted "11/5/1994 5:15:30 PM".

I'm using VS2012 update2, latest Json.net nuget package. However, if I use json.net in a separate console app to test, the same string "1994-11-05T17:15:30Z" is able to deserialize correctly into "11/5/1994 5:15:30 PM".

Anyone know what is wrong here?

like image 256
Ray Avatar asked May 30 '13 00:05

Ray


2 Answers

Although you have already found a solution for your question, I thought I would take a shot at explaining why it did not work as you expected.

WebApi uses content type negotiation to determine what parser to use when reading data. That means it will look at the Content-Type header of the request to make the determination. If the Content-Type header is set to application/json then it will use Json.Net to parse to content and feed it to your method.

An HTTP GET request, such as the one you are making here, does not have a content type set. The "content" in this case is really just the query string from the URL. WebApi does not expect to find JSON data here, so it is not going to try to use a JSON parser to make sense of it. Even if it did, the string you are passing to your GetAll method isn't even valid JSON. (It would need to be quoted to be valid.)

Now, if you were to change your method to accept a POST request, and you set the content type header to application/json and passed the date as a JSON string in the body, then WebApi will use Json.Net to parse it, and it will work like you expect.

For example, say your method looked like this:

[HttpPost] public object GetAll([FromBody]DateTime startDate) {     try     {         return new         {             StartDate = startDate.ToString("yyyy-MM-dd HH:mm:ss"),             StartDateKind = startDate.Kind.ToString(),         };     }     catch (Exception ex)     {         return ex.Message;     } } 

And you made a request like this (note the POST):

POST http://localhost:57524/api/values/GetAll HTTP/1.1 Content-Type: application/json Content-Length: 22 Host: localhost:57524  "1994-11-05T17:15:30Z" 

The response would look like this:

HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; charset=utf-8 Expires: -1 Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Fri, 31 May 2013 01:25:48 GMT Content-Length: 57  {"StartDate":"1994-11-05 17:15:30","StartDateKind":"Utc"} 

As you can see, it does correctly recognize the date to be UTC in this scenario.

like image 118
Brian Rogers Avatar answered Sep 30 '22 10:09

Brian Rogers


If you want to modify the way Asp WebApi parses uri parameters of your GET requests, you can write custom IModelBinder like this:

public class UtcDateTimeModelBinder : IModelBinder {     public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)     {         var stringValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName)?.RawValue as string;         DateTime parsedDate;         if (!DateTime.TryParse(stringValue, null, DateTimeStyles.AdjustToUniversal, out parsedDate))         {             return false;         }          bindingContext.Model = parsedDate;         return true;     } } 

And then override default binder:

GlobalConfiguration.Configure(configuration => configuration.BindParameter(typeof(DateTime), new UtcDateTimeModelBinder());); 

Parameter Binding in ASP.NET Web API

like image 23
Atomosk Avatar answered Sep 30 '22 08:09

Atomosk