Asp.net MVC2 IModelBinder пытается свести меня с ума (и успешно)
Допустим, у меня есть
class FooClass { }
class BarClass
{
public FooClass Foo;
}
Этот BarClass является моделью, которую я передаю в ViewPage.
Я также передаю (через ViewData) IEnumerable<SelectListItem>
со всем Foo в нем, и тот, который соответствует bar.Foo
выбран (проверяется во время выполнения).
Я тогда звоню Html.DropDownList("Foo", foos);
Раскрывающийся список отображается хорошо, но он не выбирает нужный элемент, потому что элемент управления html имеет имя свойства и он путается с ViewData.Eval()
это работает внутренне. Кажется, это приемлемое поведение (я видел много ответов об этом в SO), поэтому я не спорю об этом и изменил вызов расширения:
Html.DropDownList("DDL_Foo", foos);
Правильное значение выбрано, и я счастлив. Поэтому я отправляю форму обратно.
К сожалению, в соответствующем действии моего контроллера член Foo является нулевым. Поэтому я добавляю FooModelBinder
который реализует IModelBinder
перехватить DDL_Foo формы и правильно инициализировать FooClass.
Но FooModelBinder.BindModel
НИКОГДА не увольняют и bar.Foo
нулевой. Если я снова изменю свое представление и переименую выпадающий список обратно в Foo, FooModelBinder срабатывает, как и ожидалось, и bar.Foo инициализируется, как и должно.
Итак, что я пропустил? И что более важно, как я должен делать это правильно. Я рассчитал тонны хаков и обходного пути для этого, но это не то, что я ищу. Я хочу знать, как сделать это правильно.
Спасибо!
[ПРАВКА] Спасибо за ваш отзыв, но я не думаю, что префикс является проблемой.
Что касается Binder, я добавил его, потому что иначе он не может быть правильно инициализирован. Обратите внимание, что реальный случай, над которым я работаю, гораздо сложнее, чем то, что представлено здесь. Это решение - самый маленький макет, который я мог сделать, чтобы воспроизвести проблему.
Вот это спросивший код (или скачайте полное решение):
КОНТРОЛЛЕР
[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);
}
ПОСМОТРЕТЬ
<%
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>
<%
}
%>
МОДЕЛЬ
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());
}
[РЕДАКТИРОВАТЬ] Существует открытый вопрос на codeplex, если вы хотите, чтобы он был решен, пожалуйста, проголосуйте за него (даже если он был открыт в течение почти года).
2 ответа
Мне удалось заставить все работать, сделав BarClassModelBinder, который делает всю работу. Вот код:
public class BarModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var bar = new BarClass();
// In real code, check for nulls, etc.
bar.AnotherValue = controllerContext.HttpContext.Request.Form["AnotherValue"];
bar.YetAnotherValue = controllerContext.HttpContext.Request.Form["YetAnotherValue"];
var guid = Guid.Empty;
if (Guid.TryParse(controllerContext.HttpContext.Request.Form["DDL_Foo"], out guid))
{
bar.Foo = new FooClass {Id = guid};
}
return bar;
}
}
Таким образом, единственное преимущество, которое я снова вижу в использовании FormCollection в контроллере, - это ясность кода. Единственное, что меня не устраивает, это то, что имя поля "скрыто" в ModelBinder, поэтому, если кто-то меняет представление, он должен быть очень осторожным с именем поля. Может быть, есть какой-то способ обойти эту проблему, может быть, с помощью атрибута. Но даже без этого это меньшее зло, поэтому я с этим согласен.
Вся проблема все еще выглядит как нежелательный побочный эффект от реализации DropDownListFor.
Просто потратил как полчаса, играя с этим. Я не пошел бы так далеко, чтобы беспокоиться о написании пользовательских моделей связующего. Я бы просто использовал модель представления не со всем FooClass
, но с Guid FooId
вместо. Вы не получите больше из выпадающего списка в любом случае. Тогда это будет работать:
<%: Html.DropDownListFor(m => m.FooId, foos) %>
Когда вы отправляете обратно, он будет правильно связываться FooId
имущество.
Если BarClass
является классом модели предметной области, модель представления может выглядеть следующим образом (obv):
public class BarViewModel
{
public Guid FooId { get; set; }
public string AnotherValue { get; set; }
public string YetAnotherValue { get; set; }
}