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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With