{"action":"created","issue":{"url":"https://api.github.com/repos/nodatime/nodatime/issues/368","labels_url":"https://api.github.com/repos/nodatime/nodatime/issues/368/labels{/name}","comments_url":"https://api.github.com/repos/nodatime/nodatime/issues/368/comments","events_url":"https://api.github.com/repos/nodatime/nodatime/issues/368/events","html_url":"https://github.com/nodatime/nodatime/pull/368","id":87736437,"number":368,"title":"Add NodaTime.Serialization.ModelBinding","user":{"login":"zaccharles","id":5722738,"avatar_url":"https://avatars.githubusercontent.com/u/5722738?v=3","gravatar_id":"","url":"https://api.github.com/users/zaccharles","html_url":"https://github.com/zaccharles","followers_url":"https://api.github.com/users/zaccharles/followers","following_url":"https://api.github.com/users/zaccharles/following{/other_user}","gists_url":"https://api.github.com/users/zaccharles/gists{/gist_id}","starred_url":"https://api.github.com/users/zaccharles/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zaccharles/subscriptions","organizations_url":"https://api.github.com/users/zaccharles/orgs","repos_url":"https://api.github.com/users/zaccharles/repos","events_url":"https://api.github.com/users/zaccharles/events{/privacy}","received_events_url":"https://api.github.com/users/zaccharles/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-06-12T13:53:28Z","updated_at":"2015-06-12T18:34:19Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/nodatime/nodatime/pulls/368","html_url":"https://github.com/nodatime/nodatime/pull/368","diff_url":"https://github.com/nodatime/nodatime/pull/368.diff","patch_url":"https://github.com/nodatime/nodatime/pull/368.patch"},"body":"Keep in mind this will need some more work, such as tests, comments, and probably reverting some of the changes to the solution that VS2015RC made. I first did this for 2.0.0 so I was using 2015RC. This version is basically the same though; I just couldn't use `nameof()`.\r\n\r\n------------------\r\n\r\nThis is a proposal for a new package, `NodaTime.Serialization.ModelBinding` (maybe a different name?), which adds functionality so that Web API can bind URI parameters to Noda Time types.  \r\n\r\nTo do this in a simplistic way, you need to implement an `IModelBinder` for each Noda Time type, and attach it to said Noda Time type in some way. Since I imagine we don't want to pollute the `NodaTime` project with `System.Web.Http...` dependencies, this should be standalone. The normal way to do that is to add a `ModelBinderAttribute` to each action parameter. Usually this attribute would take the `Type` of the `IModelBinder` to use. However, I didn't want users to need to do this.  \r\n\r\nIn fact, I wanted using this package it to be very easy, like the Json.Net one. I got it to the point where you can just call `config.ConfigureForNodelTime(DateTimeZoneProviders.Tzdb);` on the `HttpConfiguration` in your startup code. I'm quite happy with this.  \r\n\r\nSo how do we avoid all this attribute nonsense. Well, this package works by replacing the `IActionValueBinder` service with a `NodaActionValueBinder`. In case this service has already been replaced by user code, or another package, `NodaActionValueBinder` wraps the previously registered `IActionValueBinder`, and passes calls down to it for anything unrelated to Noda Time types.  \r\n\r\nThe `IActionValueBinder` service is responsible for telling Web API which `IModelBinder` to use for a given parameter. At startup, it's given each parameter and asked that question. The `DefaultActionValueBinder` responds this by looking at the `ModelBinderAttribute`. The `NodaActionValueBinder` is slightly different. The first thing it does is identify whether the current parameter is a Noda Time type. If it is, it creates a new `NodaModelBinderAttribute` (which extends `ModelBinderAttribute`) and attaches it to the property. It then passes the request on to the `IActionValueBinder` it's wrapping. Now, the `IActionValueBinder` has a `ModelBinderAttribute` to look at, and we didn't need to manually add it to every parameter in code.\r\n\r\nSo why do we need `NodaModelBinderAttribute`? Well, by default, `ModelBinderAttribute` is responsible for instantiating the `IModelBinder`. We don't want this because we want to be able to pass the `IDateTimeZoneProvider` all the way down from the top.  \r\n`ModelBinderAttribute` first tries to resolve an instance of the `IModelBinder` from the configured dependency resolver. If that's not possible, it will try to instantiate a new one with `Activator.CreateInstance`. `NodaModelBinderAttribute`'s job is to temporarily replace the `IDependencyResolver` used to try and resolve the `IModelBinder`.  \r\n\r\nThis is where `NodaDependencyResolver` comes in. It's a simple wrapper for `IDependencyResolver`. This interface has `GetService(Type)` which is implemented so that if it's a Noda model binder being resolved, an instance with the correct `IDateTimeZoneProvider` will be returned. If it's not a Noda model binder, the call will be passed on to the wrapped `IDependencyResolver`.\r\n\r\nThe above could probably be avoided via the initial configuration extension method setting a static variable somewhere, and the model binders reading it as necessary. That way all model binders could have parameterless contructors and be instantiated with `Activator.CreateInstance`. It's simpler, but didn't seem as nice.\r\n\r\nLastly, the model binders are quite simple wrappers for the various `IPattern<T>` where available, and code ripped from the Json.Net package for other types like `Interval` and `DateTimeZone`. I'm not 100% sure I got the behavior correct for the `Interval`, especially the `IsoInterval`.\r\n\r\nAs I said at the top, this is a work in progress, though it does work currently to the extent of my manual testing. I didn't want to go too far in case it's rubbish and I'm crazy. All feedback welcome."},"comment":{"url":"https://api.github.com/repos/nodatime/nodatime/issues/comments/111583164","html_url":"https://github.com/nodatime/nodatime/pull/368#issuecomment-111583164","issue_url":"https://api.github.com/repos/nodatime/nodatime/issues/368","id":111583164,"user":{"login":"jskeet","id":17011,"avatar_url":"https://avatars.githubusercontent.com/u/17011?v=3","gravatar_id":"","url":"https://api.github.com/users/jskeet","html_url":"https://github.com/jskeet","followers_url":"https://api.github.com/users/jskeet/followers","following_url":"https://api.github.com/users/jskeet/following{/other_user}","gists_url":"https://api.github.com/users/jskeet/gists{/gist_id}","starred_url":"https://api.github.com/users/jskeet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jskeet/subscriptions","organizations_url":"https://api.github.com/users/jskeet/orgs","repos_url":"https://api.github.com/users/jskeet/repos","events_url":"https://api.github.com/users/jskeet/events{/privacy}","received_events_url":"https://api.github.com/users/jskeet/received_events","type":"User","site_admin":false},"created_at":"2015-06-12T18:34:19Z","updated_at":"2015-06-12T18:34:19Z","body":"Hi Zac - thanks very much for this. Will try to review it over the weekend, but it may take a bit longer than that."}}