6

I'm using Web API within ASP .NET MVC 4 RC, and I have a method that takes a complex object with nullable DateTime properties. I want the values of the input to be read from the query string, so I have something like this:

public class MyCriteria
{
    public int? ID { get; set; }
    public DateTime? Date { get; set; }
}

[HttpGet]
public IEnumerable<MyResult> Search([FromUri]MyCriteria criteria)
{
    // Do stuff here.
}

This works well if I pass a standard date format in the query string such as 01/15/2012:

http://mysite/Search?ID=1&Date=01/15/2012

However, I want to specify a custom format for the DateTime (maybe MMddyyyy)... for example:

http://mysite/Search?ID=1&Date=01152012

Edit:

I've tried to apply a custom model binder, but I haven't had any luck applying it to only DateTime objects. The ModelBinderProvider I've tried looks something like this:

public class DateTimeModelBinderProvider : ModelBinderProvider
{
    public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(DateTime) || bindingContext.ModelType == typeof(DateTime?))
        {
            return new DateTimeModelBinder();
        }
        return null;
    }
}

// In the Global.asax
GlobalConfiguration.Configuration.Services.Add(typeof(ModelBinderProvider), new DateTimeModelBinderProvider());

The new model binder provider is created, but GetBinder is only called once (for the complex model parameter, but not for each property within the model). This makes sense, but I would like to find a way to make it to use my DateTimeModelBinder for DateTime properties, while using the default binding for non-DateTime properties. Is there a way to override the default ModelBinder and specify how each property is bound?

Thanks!!!

j0k
  • 22,600
  • 28
  • 79
  • 90
Glen Hughes
  • 4,712
  • 2
  • 20
  • 25
  • http://stackoverflow.com/questions/528545/mvc-datetime-binding-with-incorrect-date-format - Same problem, may help you. – gdp Aug 09 '12 at 04:07
  • This post applies to controller actions in MVC web pages, but I haven't been able to find the equivalent for Web API. I've tried registering a custom model binder for Web API, and I think it would work if my method took in separate parameters. But, I want it to take a complex object as input, and I haven't found a way to override the binding of DateTime properties while using the default binding for the others. – Glen Hughes Aug 09 '12 at 05:15
  • as you already correctly mention, you can solve it by custom binder? what exact problem are you having with it? – Alexander Beletsky Aug 09 '12 at 07:01
  • I would use a dedicated view-model instead of the domain-model to send info to views and model-bind back again. Then I'd set MyCriteriaViewModel.Date to type string, and handle the conversion when I map this to the domain-model (MyCriteria.Date) in the controller or a mapping layer. – Faust Aug 09 '12 at 07:31
  • @alexanderb I've updated my question to provide more details... I haven't figured out how to register the model binder for only certain Types of properties. – Glen Hughes Aug 09 '12 at 16:38
  • @Faust This is an option (and I am using a dedicated view-model), but I would prefer a way of applying this type of conversion automatically so I don't have to do it within each controller method. – Glen Hughes Aug 09 '12 at 16:40
  • consider AutoMapper -- http://automapper.codeplex.com/ This is a very popular tool for configuration object-object mappings. You would define the mapping once at teh application level, then whenever you need to do execute a mapping, the syntax looks like: `Mapper.Map(viewModelInstance);` – Faust Aug 09 '12 at 16:55
  • you can try model binder attribute, here the example - http://stackoverflow.com/questions/10921726/how-to-register-a-custom-modelbinder-in-mvc4-rc-webapi – Alexander Beletsky Aug 09 '12 at 17:37
  • @alexanderb I've tried the `ModelBinderAttribute`, but it's not quite the right fit. It allows you to inject a `ValueProvider`, but the `ValueProvider` doesn't seem to have knowledge of the model to which the data is being bound. I need something that is injected into the actual binding process; that can interrogate each property of the model and determine how to convert the value to what the model expects. – Glen Hughes Aug 10 '12 at 00:47

1 Answers1

1

Consider setting your view-model's Date property to type string

Then either write a utility function to handle the mapping between the viewmodel type and the domain-model type:

public static MyCriteria MapMyCriteriaViewModelToDomain(MyCriteriaViewModel model){

    var date = Convert.ToDateTime(model.Date.Substring(0,2) + "/" model.Date.Substring(2,2) + "/" model.Date.Substring(4,2));

    return new MyCriteria
    {
        ID = model.ID,
        Date = date
    };

}

or use a tool like AutoMapper, like this:

in Global.asax

//if passed as MMDDYYYY:
Mapper.CreateMap<MyCriteriaViewModel, MyCriteria>().
    .ForMember(
          dest => dest.Date, 
          opt => opt.MapFrom(src => Convert.ToDateTime(src.Date.Substring(0,2) + "/" src.Date.Substring(2,2) + "/" src.Date.Substring(4,2)))
);

and in the controller:

public ActionResult MyAction(MyCriteriaViewModel model)
{
    var myCriteria = Mapper.Map<MyCriteriaViewModel, MyCriteria>(model);

    //  etc.
}

From this example it might not seem that AutoMapper is providing any added value. It's value comes when you are configuring several or many mappings with objects that generally have more properties than this example. CreateMap will automatically map properties with the same name and type, so it saves lots of typing and it's much DRYer.

Faust
  • 15,130
  • 9
  • 54
  • 111
  • 2
    I've used AutoMapper and I like what it offers, but I would still prefer to apply this mapping before the action is executed. It seems cleaner that way, IMHO. I will also need to define an interface that the API controller implements. The interface will be used to dynamically create clients for the API. I would want the interface to be more explicit in defining the expectations, rather than accepting strings. – Glen Hughes Aug 09 '12 at 18:08