Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize a date query parameter of the form yyyy-MM-dd into a noda time LocalDate object using ASP.NET Web API

I'm investigating the use of NodaTime LocalDate to replace our existing use of of the BCL DateTime/DateTimeOffset classes. We have run into a number of timezone related issues with our code due to our misunderstanding of the arguably ambiguous behavior of DateTime.

To fully leverage NodaTime I want to be able to send and receive dates from our ASP.NET Web API 2 web services of the form YYYY-MM-DD. I have had success in properly serializing LocalDate to YYYY-MM-DD. However I am unable to deserialize a date query parameter to a LocalDate. The LocateDate is always 1970-01-01.

Here is my current prototype code (some code removed for clarity):

PeopleController.cs

[RoutePrefix("api")]
public class PeopleController : ApiController
{
    [Route("people")]
    public LocalDate GetPeopleByBirthday([FromUri]LocalDate birthday)
    {
        return birthday;
    }
}

Global.asax.cs

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // Web API configuration and services
        var formatters = GlobalConfiguration.Configuration.Formatters;
        var jsonFormatter = formatters.JsonFormatter;
        var settings = jsonFormatter.SerializerSettings;
        settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
        settings.Formatting = Formatting.Indented;
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        GlobalConfiguration.Configure(WebApiConfig.Register);
    }
}

I execute the web service via

http://localhost/api/people?birthday=1980-11-20

However, what is returned is January 1, 1970. Stepping into the code I confirm that birthday is set to 1970-01-01.

How can I configure the serialization such that the date specified in the URL as a query parameter (or path element) can be properly serialized into a NodaTime LocalDate?

like image 319
Ryan Taylor Avatar asked Jul 09 '14 20:07

Ryan Taylor


1 Answers

Thanks to this very helpful article from Microsoft, I was able to find the solution using a custom model binder.

Add this class to your project:

public class LocalDateModelBinder : IModelBinder
{
    private readonly LocalDatePattern _localDatePattern = LocalDatePattern.IsoPattern;

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof (LocalDate))
            return false;

        var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (val == null)
            return false;

        var rawValue = val.RawValue as string;

        var result = _localDatePattern.Parse(rawValue);
        if (result.Success)
            bindingContext.Model = result.Value;

        return result.Success;
    }
}

Then change your controller method to use it.

public LocalDate GetPeopleByBirthday(
    [ModelBinder(typeof(LocalDateModelBinder))] LocalDate birthday)

The article also mentions other ways to register model binders.

Note that since your method returns a LocalDate, you'll still need the Noda Time serialziation for Json.net, as that ends up getting used in the body for the return value.

like image 89
Matt Johnson-Pint Avatar answered Nov 14 '22 11:11

Matt Johnson-Pint