ASP.NET MVC3 ValueProvider drops string input to a double property - asp.net-mvc-3

I'm attempting to validate the input of a text box which corresponds to a property of type double in my model. If the user inputs "foo" I want to know about it so I can display an error. However, the ValueProvider is dropping the value silently (no errors are added to the ModelState).
In a normal submission, I fill in "2" for the text box corresponding to myDouble and submit the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=2, among other correct inputs. bindingContext.ValueProvider.GetValue("myDouble") == 2, as expected. The bindingContext.ModelState.Count == 6 and bindingContext.ModelState["myDouble"].Errors.Count == 0. Everything is good and the model binds as expected.
Then I fill in "foo" for the text box corresponding to myDouble and submitted the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=foo, which is what I expected. However, bindingContext.ValueProvider.GetValue("myDouble") == null and bindingContext.ModelState.Count == 5 (The exact number isn't important, but it's one less than before). Looking at the ValueProvider, is as if myDouble was never submitted and the model binding occurs as if it wasn't. This makes it difficult to differentiate between a bad input and no input.
Is this the expected behavior of ValueProvider? Is there a way to get ValueProvider to report when conversion fails without implementing a custom ValueProvider? Thanks!

Part of the problem here is that your model has a type of double.
The problem is that double cannot be null, and as such will default to a value of 0, thus on submit.. if the ValueProvider returns null, the value of the field will still be 0 and validation will pass.
You should make the double nullable, by using double? and then add a Required attribute to the property. If the type is not required, then you can add a regular expression validator.

You can implement custom model binding logic using by implementing IModelBinder. This will put the data validation logic at the model binding level - thus being usable for any type of ValueProvider. In your situation, the model binder would determine that when myDouble = "foo" is not a double and add an exception to the ModelState errors showing the invalid value.
public class CustomDoubleBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
decimal tempDouble = 0m;
if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
{
if (double.TryParse(bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue, out tempDecimal))
{
bindingContext.ModelState[bindingContext.ModelName].Errors.Add("Error parsing double value: " + bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
}
}
return tempDouble;
}
}
Having created this custom model binder, you will then need to register it in the Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(double)] = new CustomDoubleBinder();
}

Related

Validate all strings

I want to prohibit the use of the | character for all strings submitted to my website but I don't want to have to apply a validator attribute to every string property because its unmanageable.
I could validate all strings in a model binder (I'm currently using one to trim all strings) but I don't think that would integrate with the standard validation framework. i.e. generating client side validation.
Any ideas how to do this?
I didn't get this working on the client however I can trap all dodgy strings in a custom model binder and make the form invalid with the following...
public class CustomStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue))
{
return null;
}
if (valueResult.AttemptedValue.Contains("|"))
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "The | character is prohibited.");
}
return valueResult.AttemptedValue.Trim();
}
}

MVC3 Child Action breaking my DateTime Editor Template

I have a generic DateTime editor template that should format all of my DateTime properties with the required format("dd/MM/yyyy"):
#Html.TextBox(string.Empty, Model.ToString("dd/MM/yyyy"), new { #class = "datepicker" })
but for some reason the format is not working for my child actions.
If I do #Html.EditorFor(x => x.MyDate) in MainPage.cshtml
I get the expected result of: "23/04/2012"
If I do #Html.EditorFor(x => x.MyDate) in ChildAction.cshtml
I get the unexpected result of: "2012-04-24"
I can confirm that the Editor Template is being used, because if I change it like so (notice the WTF string at the beginning):
WTF #Html.TextBox(string.Empty, Model.ToString("dd/MM/yyyy"), new { #class = "datepicker" })
Then if I do #Html.EditorFor(x => x.MyDate) in ChildAction.cshtml
I get: "WTF 2012-04-24"
Any ideas?
Update
In my quest to reproduce this error in the MVC sample app, I found that two other things are required:
public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
DateTime date;
if (DateTime.TryParseExact(value.AttemptedValue, "dd/MM/yyyy", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeLocal, out date))
{
return date;
}
return base.BindModel(controllerContext, bindingContext);
}
}
I'm assuming everyone knows how to wire that up. Also in the MainPage.cshtml you must pass the model in as the second parameter to RenderAction
#{Html.RenderAction("Child", Model);}
When passing the model as the second parameter, MVC uses the model binder to call the child action (why?). However, the format of the date is MM/dd/yyyy so it doesn't work with my model binder which assumes all my dates will be in dd/MM/yyyy format. That's why this isn't working.
But how do I fix it? The only place where the dates don't match my default format of dd/MM/yyyy is when rendering a child action.
The problem
MVC uses modelstate values if possible but the values are different in the model state of the parent action vs the child action:
In the parent action, the model state contains a string for the date submitted to the action (RawValue in the valueproviderresult) or a default value.
When the parent action passes the information to the child action
(renderaction) it passes a dateTime in the model state (RawValue in
the valueproviderresult).
So in the parent action, if no value is submitted, the value provided in Textbox(...) is correctly used. If a valid value is submitted by the client in the same format, it appears to be ok too since it takes the string in the model state which is identical to the one generated in the displaytemplate.
But in the child action it uses the modelstate which contains a datetime instead of the submitted string, MVC then convert that datetime to a string using invariantculture so it will show MM/dd/yyyy (I'm 3 years later so the value may have changed in the framework since you are having yyyy-MM-dd)
The solution
You need to replace the values in the model state with what you want or remove it for the affected values.
public static void Fix(ModelStateDictionary dic, string key)
{
ModelState modelState = dic[key];
if (!modelState.Errors.Any() && modelState.Value.RawValue != null
&& (modelState.Value.RawValue is DateTime || modelState.Value.RawValue is DateTime?))
{
var sValue = ((DateTime)modelState.Value.RawValue).ToString("dd/MM/yyyy");
var value = new ValueProviderResult(sValue, sValue, CultureInfo.InvariantCulture);
dic.SetModelValue(key, value);
//or
//dic.Remove(key);
}
}
and then call that method in your child action:
Fix(ModelState, nameof(viewModel.DateMax));

Get full name of Complex Type from ModelClientValidationRequiredIfRule method in custom ValidationAttribute

I am using the example at The Complete Guide To Validation In ASP.NET MVC 3 to create a RequiredIf validation attribute (it's about 1/3 down the page under the heading of "A more complex custom validator"). It all works fine with the exception of one scenario, and that is if I have the need to validate against a complex type. For example, I have the following model:
public class MemberDetailModel
{
public int MemberId { get; set; }
// Other model properties here
public MemberAddressModel HomeAddress { get; set; }
public MemberAddressModel WorkAddress { get; set; }
}
public class MemberAddressModel
{
public bool DontUse { get; set; }
// Other model properties here
[RequiredIf("DontUse", Comparison.IsEqualTo, false)]
public string StreetAddress1 { get; set; }
}
The problem is that when the attribute validation for the StreetAddress property is rendered, it get's decorated with the attribute of data-val-requiredif-other="DontUse". Unfortunately, since the address is a sub-type of the main model, it needs to be decorated with a name of HomeAddress_DontUse and not just DontUse.
Strangely enough, the validation works fine for server-side validation, but client-side unobtrusive validation fails with an JS error because JS can't find the object with a name of just "DontUse".
Therefore, I need to find a way to change the ModelClientValidationRequiredIfRule method to know that the property it is validating is a sub-type of a parent type, and if so, prepend the ParentType_ to the "otherProperty" field (e.g. otherProperty becomes HomeAddress_DontUse.
I have tried passing in typeof(MemberAddressModel) as a parameter of the attribute, but even when debugging the attribute creation, I can't seem to find any reference to the parent type of HomeAddress or WorkAddress from that type.
Based on the suggestion from The Flower Guy, I was able to come up with the following which seems to work. I simply modified the following in the customValidation.js file:
jQuery.validator.addMethod("requiredif", function (value, element, params) {
if ($(element).val() != '') return true;
var prefix = getModelPrefix(element.name); // NEW LINE
var $other = $('#' + prefix + params.other); // MODIFIED LINE
var otherVal = ($other.attr('type').toUpperCase() == "CHECKBOX") ? ($other.attr("checked") ? "true" : "false") : $other.val();
return params.comp == 'isequalto' ? (otherVal != params.value) : (otherVal == params.value);
});
I also added the following method to that file (within the JQuery block so as to be only privately accessible):
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1).replace(".","_");
}
Cannot do it exactly right now, but the problem is in the client javascript function:
jQuery.validator.addMethod("requiredif" ...
The js is not sophisticated enough to cope with complex view models where there may be a model prefix. If you take a look at Microsoft's jquery.validate.unobstrusive.js (in the Scripts folder over every MVC3 application), you will find some useful methods including getModelPrefix and appendModelPrefix. You can take a similar approach and change the requiredIf validation method - take a look at the equalto method in jquery.validate.unobstrusive.js for a helping hand.

MVC 3 Complicated validation of models

Current validation method for use in MVC 3 seems to be ValidationAttributes. I have a class validation that is very specific to that model and has interactions between a few properties.
Basically the model has a collection of other models and they are edited all in the same form. Let's call it ModelA and it has a collection of ModelB. One thing I might have to validate is that the sum of the some property of ModelB is less then a property of ModelA. The user has X number of points he can divide among some options.
ValidationAttributes are very generic and I'm not sure they are suited for this job.
I have no idea how IDateErrorInfo is supported in MVC 3 and whether it works straight out of the box.
One way would be to validate through a method but that means I can't do a clientside validation.
What is the proper way to do something like this? Are there any more options I have? Am I underestimating the power of ValidationAttribute?
IDateErrorInfo
IDateErrorInfo is supported by the MVC framework (a Microsoft tutorial can be found here). The default model binder will be reponsible for recreating model objects by binding the html form elements to the model. If the model binder detects that the model implements the interface then it will use the interface methods to validate each property in the model or to validate the model as a whole. See the tutorial for more information.
If you wanted to use client side validation using this method then (to quote Steve Sanderson) 'the most direct way to take advantage of additional validation rules is to manually generate the required attributes in the view':
<p>
#Html.TextBoxFor(m.ClientName, new { data_val = "true", data_val_email = "Enter a valid email address", data_val_required = "Please enter your name"})
#Html.ValidationMessageFor(m => m.ClientName)
</p>
This can then be used to trigger any client side validation that has been defined. See below for an example of how to define client side validation.
Explicit Validation
As you mentioned, you could explicity validate the model in the action. For example:
public ViewResult Register(MyModel theModel)
{
if (theModel.PropertyB < theModel.PropertyA)
ModelState.AddModelError("", "PropertyA must not be less then PropertyB");
if (ModelState.IsValid)
{
//save values
//go to next page
}
else
{
return View();
}
}
In the view you would then need to use #Html.ValidationSummary to display the error message as the above code would add a model level error and not a property level error.
To specify a property level error you can write:
ModelState.AddModelError("PropertyA", "PropertyA must not be less then PropertyB");
And then in the view use:
#Html.ValidationMessageFor(m => m.PropertyA);
to display the error message.
Again, any client side validation would need to be linked in by manually linking in the client side validation in the view by defining properties.
Custom Model Validation Attribute
If I understand the problem correctly, you are trying to validate a model which contains a single value and a collection where a property on the collection is to be summed.
For the example I will give, the view will present to the user a maximum value field and 5 value fields. The maximum value field will be a single value in the model where as the 5 value fields will be part of a collection. The validation will ensure that the sum of the value fields is not greater than the maximum value field. The validation will be defined as an attribute on the model which will also link in nicely to the javascript client side valdation.
The View:
#model MvcApplication1.Models.ValueModel
<h2>Person Ages</h2>
#using (#Html.BeginForm())
{
<p>Please enter the maximum total that will be allowed for all values</p>
#Html.EditorFor(m => m.MaximumTotalValueAllowed)
#Html.ValidationMessageFor(m => m.MaximumTotalValueAllowed)
int numberOfValues = 5;
<p>Please enter #numberOfValues different values.</p>
for (int i=0; i<numberOfValues; i++)
{
<p>#Html.EditorFor(m => m.Values[i])</p>
}
<input type="submit" value="submit"/>
}
I have not added any validation against the value fields as I do not want to overcomplicate the example.
The Model:
public class ValueModel
{
[Required(ErrorMessage="Please enter the maximum total value")]
[Numeric] //using DataAnnotationExtensions
[ValuesMustNotExceedTotal]
public string MaximumTotalValueAllowed { get; set; }
public List<string> Values { get; set; }
}
The Actions:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ValueModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
else
{
return RedirectToAction("complete"); //or whatever action you wish to define.
}
}
The Custom Attribute:
The [ValuesMustNotExceedTotal] attribute defined on the model can be defined by overriding the ValidationAttribute class:
public class ValuesMustNotExceedTotalAttribute : ValidationAttribute
{
private int maxTotalValueAllowed;
private int valueTotal;
public ValuesMustNotExceedTotalAttribute()
{
ErrorMessage = "The total of all values ({0}) is greater than the maximum value of {1}";
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, valueTotal, maxTotalValueAllowed);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo maxTotalValueAllowedInfo = validationContext.ObjectType.GetProperty("MaximumTotalValueAllowed");
PropertyInfo valuesInfo = validationContext.ObjectType.GetProperty("Values");
if (maxTotalValueAllowedInfo == null || valuesInfo == null)
{
return new ValidationResult("MaximumTotalValueAllowed or Values is undefined in the model.");
}
var maxTotalValueAllowedPropertyValue = maxTotalValueAllowedInfo.GetValue(validationContext.ObjectInstance, null);
var valuesPropertyValue = valuesInfo.GetValue(validationContext.ObjectInstance, null);
if (maxTotalValueAllowedPropertyValue != null && valuesPropertyValue != null)
{
bool maxTotalValueParsed = Int32.TryParse(maxTotalValueAllowedPropertyValue.ToString(), out maxTotalValueAllowed);
int dummyValue;
valueTotal = ((List<string>)valuesPropertyValue).Sum(x => Int32.TryParse(x, out dummyValue) ? Int32.Parse(x) : 0);
if (maxTotalValueParsed && valueTotal > maxTotalValueAllowed)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
//if the maximum value is not supplied or could not be parsed then we still return that the validation was successful.
//why? because this attribute is only responsible for validating that the total of the values is less than the maximum.
//we use a [Required] attribute on the model to ensure that the field is required and a [Numeric] attribute
//on the model to ensure that the fields are input as numeric (supplying appropriate error messages for each).
return null;
}
}
Adding Client Side Validation to the Custom Attribute:
To add client side validation to this attribute it would need to implement the IClientValidatable interface:
public class ValuesMustNotExceedTotalAttribute : ValidationAttribute, IClientValidatable
{
//...code as above...
//this will be called when creating the form html to set the correct property values for the form elements
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule {
ValidationType = "valuesmustnotexceedtotal", //the name of the client side javascript validation (must be lowercase)
ErrorMessage = "The total of all values is greater than the maximum value." //I have provided an alternative error message as i'm not sure how you would alter the {0} and {1} in javascript.
};
yield return rule;
//note: if you set the validation type above to "required" or "email" then it would use the default javascript routines (by those names) to validate client side rather than the one we define
}
}
If you were to run the application at this point and view the source html for the field defining the attribute you will see the following:
<input class="text-box single-line" data-val="true" data-val-number="The MaximumTotalValueAllowed field is not a valid number." data-val-required="Please enter the maximum total value" data-val-valuesmustnotexceedtotal="The total of all values is greater than the maximum value." id="MaximumTotalValueAllowed" name="MaximumTotalValueAllowed" type="text" value="" />
In particular notice the validation attribute of data-val-valuesmustnotexceedtotal. This is how our client side validation will link to the validation attribute.
Adding Client Side Validation:
To add client side validation you need to add the following similar library references in the tag of the view:
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
You need to also ensure that the client side validation is switched on in the web.config although I think this should be on by default:
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
All that is left is to define the client side validation in the view. Note that the validation added here is defined in the view but if it was defined in a library then the custom attribute (maybe not this one) could be added to other models for other views:
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add('valuesmustnotexceedtotal', [], function (options) {
options.rules['valuesmustnotexceedtotal'] = '';
options.messages['valuesmustnotexceedtotal'] = options.message;
});
//note: this will only be fired when the user leaves the maximum value field or when the user clicks the submit button.
//i'm not sure how you would trigger the validation to fire if the user leaves the value fields although i'm sure its possible.
jQuery.validator.addMethod('valuesmustnotexceedtotal', function (value, element, params) {
sumValues = 0;
//determine if any of the value fields are present and calculate the sum of the fields
for (i = 0; i <= 4; i++) {
fieldValue = parseInt($('#Values_' + i + '_').val());
if (!isNaN(fieldValue)) {
sumValues = sumValues + fieldValue;
valueFound = true;
}
}
maximumValue = parseInt(value);
//(if value has been supplied and is numeric) and (any of the fields are present and are numeric)
if (!isNaN(maximumValue) && valueFound) {
//perform validation
if (sumValues > maximumValue)
{
return false;
}
}
return true;
}, '');
</script>
And that should be it. I'm sure that there are improvements that can be made here and there and that if i've misunderstood the problem slightly that you should be able to tweak the validation for your needs. But I believe this validation seems to be the way that most developers code custom attributes including more complex client side validation.
Hope this helps. Let me know if you have any questions or suggestions regarding the above.
This is what you are looking for:
http://www.a2zdotnet.com/View.aspx?Id=182
I got some similar situation too, I need to compare the value of property A with property B, and I get it done by:
public sealed class PropertyAAttribute : ValidationAttribute
{
public string propertyBProperty { get; set; }
// Override the isValid function
public override bool IsValid(object value)
{
// Do your comparison here, eg:
return A >= B;
}
}
Then just use the custom validation attribute like this:
[PropertyA(propertyBProperty = "PropertyB")]
public string Property A {get; set;}
I also tried very hard and get this solution from others, Hope this help!
Your model class can implement the IValidatableObject interface.
This way you have access to all the properties of your model class and can perform all your custom validations.
You also have the IClientValidatable interface, for client side validations, but I'm not sure if by implementing it directly in the model class the client validations are picked by MVC since I only ever used this interface to specify client validations in custom validation attributes.

MVC Model Binding a Complex Type to a Simple Type and Vice Versa

Here's a scenario:
I have an autocomplete plugin (custom) that keeps a hidden field of JSON objects (using a specific struct).
I've created an Html helper that helps me easily bind to a specific custom model (basically, it has a JSON property that is for two-way binding and a property that lets me deserialize the JSON into the appropriate struct):
public class AutoCompleteModel {
public string JSON { get; set; }
public IEnumerable<Person> People {
get {
return new JavaScriptSerializer().Deserialize<Person>(this.JSON);
}
set {
this.JSON = new JavaScriptSerializer().Serialize(value);
}
}
}
This works great and I can model bind using the default binder #Html.Autocomplete(viewModel => viewModel.AutoCompleteModelTest). The HTML helper generates HTML like:
<input type="text" id="AutoCompleteModelTest_ac" name="AutoCompleteModelTest_ac" value="" />
<input type="hidden" id="AutoCompleteModelTest_JSON" name="AutoCompleteModelTest.JSON" value="{JSON}" />
The problem is this is not the best way for consumers. They have to manually set the People property to an array of Person structs. In my data layer, my domain objects probably will not be storing the full struct, only the person's ID (a corporate ID). The autocomplete will take care of looking up the person itself if only given an ID.
The best scenario will be to call it like this:
#Html.Autocomplete(domainObject => domainObject.PersonID) or
#Html.Autocomplete(domainObject => domainObject.ListOfPersonIDs
I would like it to work against the string property AND against the custom AutoCompleteModel. The autocompleter only updates a single hidden field, and that field name is passed back on postback (the value looks like: [{ "Id":"12345", "FullName":"A Name"},{ "Id":"12347", "FullName":"Another Name" }]).
The problem is, of course, that those domain object properties only have an ID or array of IDs, not a full Person struct (so cannot be directly serialized into JSON). In the HTML helper, I can transform those property values into a struct, but I don't know how to transform it back into a simple type on POST. The solution I need would transform an ID into a new Person struct on page load, serializing it into the hidden field. On POST, it would deserialize the generated JSON back into a simple array of IDs.
Is a custom model binder the solution I need? How can I tell it to work both with a custom model AND simple types (because I don't want it applied to EVERY string property, just need it to deal with the values given by the HTML helper).
I figured it out, it's possible!
To clarify, I needed to: transform a string or string array (of IDs) into a JSON structure for my hidden field value, then on post back, deserialize the JSON in the hidden field and transform the struct back into a simple string or string array (of IDs) for my domain object's property.
Step 1: Create a HTML helper
I had done this already, but only for accepting my custom AutoCompleteModel type. I needed one for a string and an Enumerable of string type.
All I did was generate my Person struct(s) from the value of the property and serialize them into JSON for the hidden field the Autocompleter uses (this is an example of the string helper, I also have a nearly identical one for IEnumerable<string>):
public static MvcHtmlString AutoComplete<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, string>> idProp)
where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
string id = idProp.Compile().Invoke(model);
string propertyName = idProp.GetPropertyName();
Person[] people = new Person[] {
new Person() { ID = id }
};
// Don't name the textbox the same name as the property,
// otherwise the value will be whatever the textbox is,
// if you care.
MvcHtmlString textBox = htmlHelper.TextBox(propertyName + "_ac", string.Empty);
// For me, the JSON is the value I want to postback
MvcHtmlString hidden = htmlHelper.Hidden(propertyName, new JavaScriptSerializer().Serialize(people));
return MvcHtmlString.Create(
"<span class=\"AutoComplete\">" +
textBox.ToHtmlString() +
hidden.ToHtmlString() +
"</span>");
}
Usage: #Html.AutoComplete(model => model.ID)
Step 2: Create a custom model binder
The crux of my issue was that I needed this binder to only apply to certain properties, and they were strings or string arrays.
I was inspired by this article because it used Generics. I decided, hey, we can just ask people what property they want to apply the binder for.
public class AutoCompleteBinder<T> : DefaultModelBinder
where T : class
{
private IEnumerable<string> PropertyNames { get; set; }
public AutoCompleteBinder(params Expression<Func<T, object>>[] idProperties)
{
this.PropertyNames = idProperties.Select(x => x.GetPropertyName());
}
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (submittedValue != null && this.PropertyNames.Contains(propertyDescriptor.Name))
{
string json = submittedValue.AttemptedValue;
Person[] people = new JavaScriptSerializer().Deserialize<Person[]>(json);
if (people != null && people.Any())
{
string[] IDs = people.Where(x => !string.IsNullOrEmpty(x.ID)).Select(x => x.ID).ToArray();
bool isArray = bindingContext.ModelType != typeof(string) &&
(bindingContext.ModelType == typeof(string[]) ||
bindingContext.ModelType.HasInterface<IEnumerable>());
if (IDs.Count() == 1 && !isArray)
return IDs.First(); // return string
else if (IDs.Count() > 0 && isArray)
return IDs.ToArray(); // return string[]
else
return null;
}
else
{
return null;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
GetPropertyName() (translate LINQ expression into a string, i.e. m => m.ID = ID) and HasInterface() are just two utility methods I have.
Step 3: Register
Register the binder on your domain objects and their properties in Application_Start:
ModelBinders.Binders.Add(typeof(Employee), new AutoCompleteBinder<Employee>(e => e.ID, e => e.TeamIDs));
It's only a little bit annoying to have to register the binder for specific properties, but it's not the end of the world and provides a nice, smooth experience working with my autocompleter.
Any comments are welcome.

Resources