I am trying to make a binder for an abstract class. The binder decides which implementation of the class to use.
public abstract class Pet
{
public string name { get; set; }
public string species { get; set; }
abstract public string talk { get; }
}
public class Dog : Pet
{
override public string talk { get { return "Bark!"; } }
}
public class Cat : Pet
{
override public string talk { get { return "Miaow."; } }
}
public class Livestock : Pet
{
override public string talk { get { return "Mooo. Mooo. Fear me."; } }
}
So I have a controller which takes a Pet, the binder decides (depending on the species string) if it is a Dog, Cat or Livestock.
public class PetBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var values = (ValueProviderCollection)bindingContext.ValueProvider;
var name = (string)values.GetValue("name").ConvertTo(typeof(string));
var species = (string)values.GetValue("species").ConvertTo(typeof(string));
if (species == "dog")
{
return new Dog { name = name, species = "dog" };
}
else if (species == "cat")
{
return new Cat { name = name, species = "cat" };
}
else
{
return new Livestock { name = name, species = species };
}
}
}
public class HomeController : Controller
{
public JsonResult WorksFine(Pet pet)
{
return Json(pet);
}
public JsonResult DoesntWork(List<Pet> pets)
{
return Json(pets);
}
}
This works well, but as soon as the pet is in another structure (like List<Pet>
or another object), I get a NullReferenceException (on the line var name = (string)values.GetValue("name").ConvertTo(typeof(string));
in the PetBinder). What am I doing wrong?
I added a Person class to test. It also gave me a NullReferenceException.
public class Person
{
public string name { get; set; }
public Pet pet { get; set; }
}
public class HomeController : Controller
{
public JsonResult PersonAction(Person p)
{
return Json(p);
}
}
ccurrens said the reason var name = (string)values.GetValue("name").ConvertTo(typeof(string));
returned null was because it couldn't get the values from a list.
I see they are named [n].name
and [n].species
when in a List<Pet>
, but when in the Person
object they are named pet.name
and pet.species
and when they are in a single Pet
, they are just named name
and species
.
Solution
To get the parameter names with the right prefix ([n]
or pet
or anything else) for GetValue
, I used the following code:
bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
string prefix = ((hasPrefix)&&(bindingContext.ModelName!=""开发者_JS百科)) ? bindingContext.ModelName + "." : "";
If anyone is interested, I ended up inheriting from DefaultModelBinder
using something similar to this answer. Here is the full code I used:
public class DefaultPetBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,ModelBindingContext bindingContext,Type modelType)
{
//https://stackoverflow.com/questions/5460081/asp-net-mvc-3-defaultmodelbinder-inheritance-problem
bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
string prefix = ((hasPrefix)&&(bindingContext.ModelName!="")) ? bindingContext.ModelName + "." : "";
// get the parameter species
ValueProviderResult result;
result = bindingContext.ValueProvider.GetValue(prefix+"species");
if (result.AttemptedValue.Equals("cat"))
return base.CreateModel(controllerContext,bindingContext,typeof(Cat));
else if (result.AttemptedValue.Equals("dog"))
return base.CreateModel(controllerContext,bindingContext,typeof(Dog));
return base.CreateModel(controllerContext, bindingContext, typeof(Livestock)); // livestock
}
}
In the line you're getting your error, values
could be null or what GetValue("name")
returns could be null.
I'm assuming when you're calling the List<Pet>
method, ValueProvider is returning the entire List
instead of each individual Pet
, so it can't get the value "name"
since it doesn't exist in the List
class.
I can't be more sure without seeing more code.
精彩评论