3

假设我有

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

全球阿萨克斯

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes);

        ModelBinders.Binders.Add(typeof(FooClass), new FooModelBinder());
    }

[编辑] codeplex上有一个未解决的问题,如果你想解决它,请去投票(即使它已经开放了将近一年)。

4

2 回答 2

1

通过制作一个可以完成所有工作的 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;
    }
}

因此,我再次看到在 Controller 中使用 FormCollection 的唯一好处是代码清晰。我唯一不满意的是字段名称在 ModelBinder 中是“隐藏的”,所以如果有人更改视图,他必须非常小心字段名称。也许也有一些方法可以绕过这个问题,也许有一个属性。但即使没有这个,这是较小的邪恶,所以我会解决它。

整个问题看起来仍然像是 DropDownListFor 实现的不良副作用。

于 2010-07-31T13:45:34.263 回答
0

只花了半个小时玩这个。我不会费心编写自定义模型活页夹。我只会使用视图模型而不是整体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; }
}
于 2010-07-31T03:54:29.507 回答