Binding a custom property with model binders

This post was imported from FARMCode.org which has been discontinued. These posts now exist here as an archive. They may contain broken links and images.
In ASP.Net MVC Model Binders are great but what if you have a property on your model that is a ‘Simple’ type that you want to assign to a custom model binder? Take the following class for example:
public class MyModel { [Required] public string Name { get; set; } [Required] public DateTime StartDate { get; set; } [Required] public DateTime StartTime { get; set; } [CustomValidation(typeof(CreateJourneyModel), "OnDateValidate")] public DateTime FullStartDateTime { get; set; } //Custom validation logic which needs to validate the full date/time internal static ValidationResult OnDateValidate(DateTime dt, ValidationContext validationContext) { return dt < DateTime.Now.AddHours(1) ? new ValidationResult("Enter a date/time greater than 3 hours from now") : ValidationResult.Success; } }

The above model will allow us to have a different date and time field rendered for input on a form but requires the combined date/time to run custom validation against. You might already be thinking: Isn’t this the same problem that Scott Hanselman solved in this post? Well, sort of but not really. What Scott did was assign a custom model binder to all instances of DateTime or set specifically on an Action parameter and was also splitting one DateTime field into 2 on the front-end . I’m not really a fan of having a custom model binder for all instances of DateTime in my application , I don’t have a DateTime parameter in my Action to explicitly assign a model binder too and I also decided to use 2 DateTime properties to represent 2 different fields on my form.  The solution presented below could allow you to custom bind any single property of your model which you could then assign custom validation to.

First, we need to put the fields on your form so they get posted back to the server. Using the Html.EditorFor(x => …..) syntax is the way to go, however, for the field we want to custom bind using Html.HiddenFor(x => …. ) is better so the field isn’t actually displayed on the form. This will add this field name to the ValueProvider when it’s posted to the server.

Next, create the model binder. Since we cannot assign a model binder to a property of a model, we need to make the binder for the model containing the property. And, since we don’t want to do any custom binding for the rest of the properties, we’ll just inherit from DefaultModelBinder and let it do the work for us. The only thing we have to do is override GetPropertyValue , check for the property that we want to custom bind and bind it!

public class MyCustomModelBinder : DefaultModelBinder { protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { //check if it the 'FullStartDateTime' property if (propertyDescriptor.Name == "FullStartDateTime") { //try to get the date part var dateValue = bindingContext.ValueProvider.GetValue("StartDate"); DateTime date; if (!string.IsNullOrEmpty(dateValue.AttemptedValue) && DateTime.TryParseExact(dateValue.AttemptedValue, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) { //try to get the time part var timeValue = bindingContext.ValueProvider.GetValue("StartTime"); DateTime time; if (!string.IsNullOrEmpty(timeValue.AttemptedValue) && DateTime.TryParseExact(timeValue.AttemptedValue, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out time)) { //we've got both parts, join them together var fullDateTime = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second); return fullDateTime; } } //could not bind return null; } //let the default model binder do it's thing return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } }

Last, all we need to do is assign the custom model binder to the custom model which can easily be done by just attributing the model:

[ModelBinder(typeof(MyCustomModelBinder))] public class MyModel {...}

That's it! Now all validation is handled as per normal on the custom property.

Author

Administrator (1)

comments powered by Disqus