Let's say I have
class FooClass { }
class BarClass
{
public FooClass Foo;
}
This BarClass is the Model I'm passing to the ViewPage.
I'm also passing (via ViewData) an IEnumerable<SelectListItem>
with all the Foo in it, and the one that match the bar.Foo
is selected (checked at runtime).
I'm then calling Html.DropDownList("Foo", foos);
The dropdownlist renders well, but it does not select the proper item because the html control has the name of the property and it messes with the ViewData.Eval()
that runs internaly. It seems to be an accepted behavior (saw many answers about that on SO), so I'm not arguing about that and change the call to the extension to :
Html.DropDownList("DDL_Foo", foos);
The proper value is selected and I'm happy. So I post the form back.
Sadly, in the appropriate Action of my controller, the Foo member is null. So I add a FooModelBinder
that implements IModelBinder
to intercept the DDL_Foo of the form and initialize the FooClass properly.
But FooModelBinder.BindModel
NEVER gets fired and bar.Foo
is null. If I change again my view and rename the dropdownlist back to Foo, the FooModelBinder fires as expected and bar.Foo is initialized as it should.
So, what did I missed ? And more important, how am I supposed to do that the proper way. I figured tons of hacks and workaround for it, but that's not what I'm looking for. I want to know how to do it right.
Thank you!
[EDIT] Thank you for your feedback, but I don't think the Prefix is the issue.
About the Binder, I added it because it can't be initialized properly otherwise. Please note that the real case I'm working on is way more complex that what is presented here. This solution is just the smallest mockup I could do to reproduce the issue.
Here it the the revelant code asked (or download the full solution) :
CONTROLLER
[HttpGet]
public ActionResult Index()
{
var dp = new DummyProvider();
var bar = dp.GetBar();
var foos = new List<SelectListItem>();
dp.GetAllFoos().ForEach(
f => foos.Add(new SelectListItem {Text = f.Name, Value = f.Id.ToString(), Selected = f.Id == bar.Foo.Id }));
ViewData["foos"] = foos;
return View(bar);
}
[HttpPost]
public ActionResult Index(BarClass bar)
{
var dp = new DummyProvider();
var foos = new List<SelectListItem>();
dp.GetAllFoos().ForEach(
f => foos.Add(new SelectListItem { Text = f.Name, Value = f.Id.ToString(), Selected = f.Id == bar.Foo.Id }));
ViewData["foos"] = foos;
ViewData["selectedItem"] = bar.Foo.Name;
return View(bar);
}
VIEW
<%
var foos = ViewData["foos"] as List<SelectListItem>;
using(Html.BeginForm())
{
%>
<p>
<h3>Enter Another Value</h3>
<%= Html.TextBox("AnotherValue", Model.AnotherValue) %>
</p>
<p>
<h3>Enter Yet Another Value</h3>
<%= Html.TextBox("YetAnotherValue", Model.YetAnotherValue) %>
</p>
<p>
<h3>Choose a foo</h3>
<%= Html.DropDownList("DDL_Foo", foos)%>
</p>
<button type="submit">Send back !</button>
<%
}
%>
MODEL
public class BarClass
{
public FooClass Foo { get; set; }
public string AnotherValue { get; set; }
public string YetAnotherValue { get; set; }
}
public class FooClass
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class FooClassCollection : List<FooClass> { }
public class FooModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var foo = new FooClass();
var guid = Guid.Empty;
if (Guid.TryParse(controllerContext.HttpContext.Request.Form["DDL_Foo"], out guid))
{
foo.Id = guid;
}
return foo;
}
}
public class DummyProvider
{
public FooClassCollection GetAllFoos()
{
return new FooClassCollection
{
new FooClass {Name = "Item 1", Id = new Guid("4a402abd-ab85-4065-94d6-d9fcc0f9b69e")},
new FooClass {Name = "Item 2", Id = new Guid("cf20bfd6-0918-4ffc-a6ec-c4cc4ed30e7f")},
new FooClass {Name = "Item 3", Id = new Guid("ad81b882-b93e-42b9-a42c-78376dd8f59d")},
new FooClass {Name = "Item 4", Id = new Guid("1511c15d-9ae4-4b18-9e10-e02588c21b27")},
new FooClass {Name = "Item 5", Id = new Guid("855e4a2f-fc5b-4117-a888-1dc3ebb990fc")},
};
}
public BarClass GetBar()
{
return new BarClass
{
AnotherValue = "Nice value",
YetAnotherValue = "This one is awesome",
Foo = new FooClass {Name = "Item 3", Id = new Guid("ad81b882-b93e-42b9-a42c-78376dd8f59d")}
};
}
}
GLOBAL.ASAX
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(FooClass), new FooModelBinder());
}
[EDIT] There is an open issue open on codeplex, if you want it resolved, go vote for it please (even if it had been open for almost a year now).