Asp.net MVC2 IModelBinder пытается свести меня с ума (и успешно) - PullRequest
3 голосов
/ 30 июля 2010

Допустим, у меня есть

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 инициализируется, как и должно.

Итак, что я пропустил? И что более важно, как я должен делать это правильным способом. Я рассчитал тонны хаков и обходного пути для этого, но это не то, что я ищу. Я хочу знать, как сделать это правильно.

Спасибо!

[EDIT] Спасибо за ваш отзыв, но я не думаю, что префикс является проблемой.

О Binder, я добавил его, потому что иначе он не может быть правильно инициализирован. Обратите внимание, что реальное дело, над которым я работаю, намного сложнее, чем то, что представлено здесь. Это решение - самый маленький макет, который я мог сделать, чтобы воспроизвести проблему.

Здесь задается соответствующий код (или скачайте полное решение ):

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());
    }

[РЕДАКТИРОВАТЬ] Существует открытый вопрос по codeplex , если вы хотите, чтобы он был решен, пожалуйста, проголосуйте за него (даже если он был открыт почти год) ).

Ответы [ 2 ]

1 голос
/ 31 июля 2010

Мне удалось заставить все работать, создав 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.

0 голосов
/ 31 июля 2010

Просто потратил как полчаса, играя с этим.Я не пошел бы так далеко, чтобы беспокоиться о написании пользовательских моделей связующего.Я бы просто использовал модель представления не с FooClass, а с Guid FooId.Вы не получите больше из выпадающего списка в любом случае.Тогда это будет работать:

<%: Html.DropDownListFor(m => m.FooId, foos) %>

Когда вы отправляете сообщение обратно, оно правильно связывает свойство FooId.

Если BarClass является классом модели домена, модель представления может выглядеть так):

public class BarViewModel
{
    public Guid FooId { get; set; }
    public string AnotherValue { get; set; }
    public string YetAnotherValue { get; set; }
}
...