Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling DateTime parameter formatting in WebAPI 2

So I have a WebAPI 2 controller written in C# that takes among other things a query parameter of type DateTime. This is an API that returns all values from the data store based on a date filter. Something like, let's say:

public MyThing GetThing([FromUri]DateTime startTime)
{
 // filter and return some results
}

I am running into 2 problems:

  1. For some reason despite passing in a ISO 8601 UTC formatted (with a Z) date, WebAPI is de-serializing it as a local DateTime, instead of Utc. This is obviously undesirable. I am not sure how to modify the pipeline to have it correctly understand UTC-0 DateTimes.
  2. I am returning a link back to the resource as part of the response body, in which I use the UrlHelper objects (obtained from the parent ApiController abstract class) Link() method to generate an href. I am passing a collection of query parameters I want added to the route. For whatever reason passing the DateTime formats it in a non-ISO8601 format. I can't find where this is controlled. I don't want to explicitly ToString() it as that's not enforceable universally.

In short, I want to figure out how to make sure that

  1. DateTimes that are passed in via FromUri query params are properly understood as ISO8601, including appropriate time zone offsets
  2. UrlHelper.Link() generates ISO8601-compliant DateTimes in the output URI string in a universally enforceable statically-typed way.

WebAPI 2 does provide wonderful hooks for formatting JSON, which I do make use of, so simply returning a DateTime in a JSON body formats it as desired using the ISO8601 format, and as well it is correctly understood in a [FromBody] JSON body. I can't find ways for pulling strings around URI handling though, and I would really like to!

like image 206
Zoinks Avatar asked Sep 21 '16 23:09

Zoinks


4 Answers

You can use modelbinder to transforming incoming data to your model.

GetThings([ModelBinder(typeof(UtcDateTimeModelBinder)), FromUri] DateTime dt){//do somthing}


public class UtcDateTimeModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {

        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        if (bindingContext.ModelMetadata.ModelType == typeof(DateTime))
        {
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            var str = valueProviderResult.AttemptedValue;
            return DateTime.Parse(str).ToUniversalTime();
        }

        return null;
    }

In that way you can set it as default modelbilder of DateTime.

ModelBinders.Binders.Add(typeof(DateTime), new UtcDateTimeModelBinder());
like image 57
reza taroosheh Avatar answered Oct 17 '22 17:10

reza taroosheh


Why not using DateTimeOffset instead of DateTime if you want to keep UTC offset? Here are some code working with JSON serialization:

Api Controller:

public class ValuesController : ApiController
{
    public object Get(DateTimeOffset dt)
    {
        return new {
            Input = dt,
            Local = dt.LocalDateTime,
            Utc = dt.UtcDateTime
        };
    }
}

Razor View sample code (assuming you have the default api route created in a MVC + Web API Visual studio template )

<a href="@Url.RouteUrl("DefaultApi",new {httproute = "",controller = "values",dt = DateTimeOffset.UtcNow})">Utc Now</a>

Rendered as:

<a href="/api/values?dt=06%2F26%2F2018%2009%3A37%3A24%20%2B00%3A00">Utc Now</a>

And you can call your API with datetime offset:

2018-06-26T08:25:48Z: http://localhost:1353/api/values?dt=2018-06-26T08:25:48Z
{"Input":"2018-06-26T08:25:48+00:00","Local":"2018-06-26T10:25:48+02:00","Utc":"2018-06-26T08:25:48Z"}

2018-06-26T08:25:48+01:00: http://localhost:1353/api/values?dt=2018-06-26T08%3A25%3A48%2B01%3A00 (note that : and + must be url encoded)
{"Input":"2018-06-26T08:25:48+01:00","Local":"2018-06-26T09:25:48+02:00","Utc":"2018-06-26T07:25:48Z"}
like image 33
asidis Avatar answered Oct 17 '22 17:10

asidis


1.

You should check the timezone of your parameter 'startTime' (which should be the timezone of your server/computer).

The DateTime provided by Web API is correct, it just depends on YOUR timezone.

2.

Create a Json DateTime serializer in order to generate ISO8601 formatted date.

like image 1
sheep Avatar answered Oct 17 '22 17:10

sheep


The query string parameter value you are sending is UTC time. So, the same gets translated to a time based on your local clock and if you call ToUniversalTime(), it gets converted back to UTC.

So, what exactly is the question? If the question is why is this happening if sent in as query string but not when posted in request body, the answer to that question is that ASP.NET Web API binds the URI path, query string, etc using model binding and the body using parameter binding. For latter, it uses a media formatter. If you send JSON, the JSON media formatter is used and it is based on JSON.NET.

Since you have specified DateTimeZoneHandling.Utc, it uses that setting and you get the date time kind you want. BTW, if you change this setting to DateTimeZoneHandling.Local, then you will see the same behavior as model binding.

Meaning in order to get the desired formatting you want, all you need to do is call the ToUniversalTime() method.

like image 2
Barr J Avatar answered Oct 17 '22 19:10

Barr J