Reputation: 5933
I have a base view model with an Id property of type object (so I can have it be an int or a Guid) like so:
public abstract class BaseViewModel
{
public virtual object Id { get; set; }
}
And the view models thus derive from this
public class UserViewModel : BaseViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
My HTML then is rendered as:
<input id="Id" name="Id" type="hidden" value="240" />
<input id="FirstName" name="FirstName" type="text" value="John" />
<input id="LastName " name="LastName " type="text" value="Smith" />
And when submitted to the MVC action:
[HttpPost]
public ActionResult EditUser(UserViewModel model)
{
...code omitted...
}
The values for the model properties are:
Id: string[0] = "240"
FirstName: string = "John"
LastName: string = "Smith"
My question is, why am I getting a one item string array as the value for Id, rather than just a string? And is there a way to change this behavior? It causes problems when I try to parse it into the expected type.
Upvotes: 3
Views: 2376
Reputation: 15404
The issue is with typing your id property as object
-- not sure how the default binding is supposed to work here, but since an object is potentially anything -- like a complex object with multiple properties itself -- perhaps it attempts to dump all of the properties it finds there into an array?
If the Id
is not always going to be an integer, I'd suggest typing this as string, since the model-binding mechanism should have no problem mapping virtually anything sent over HTTP as string, so:
public abstract class BaseViewModel
{
public virtual string Id { get; set; }
}
Upvotes: 1
Reputation: 5933
I ended up solving this with a custom model binder that handles the "Id" object property as a special case:
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
// apply the default model binding first to leverage the build in mapping logic
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
// since "Id" is a special property on BaseViewModel of type object,
// we need to figure out what it should be and parse it appropriately
if (propertyDescriptor.Name == "Id" && propertyDescriptor.PropertyType == typeof(object))
{
// get the value that the default binder applied
var defaultValue = propertyDescriptor.GetValue(bindingContext.Model);
// this should be a one element string array
if (defaultValue is string[])
{
var defaultArray = defaultValue as string[];
// extract the first element of the array (the actual value of "Id")
var propertyString = defaultArray[0];
object value = propertyString;
// try to convert the ID value to an integer (the most common scenario)
int intResult;
if (int.TryParse(propertyString, out intResult))
{
value = intResult;
}
else
{
// try to convert the ID value to an Guid
Guid guidResult;
if (Guid.TryParse(propertyString, out guidResult)) value = guidResult;
}
// set the model value
propertyDescriptor.SetValue(bindingContext.Model, value);
}
}
}
}
Upvotes: 2