1

我仍然围绕着 MVC,我已经从单个模型应用程序转移到一个更复杂的模型应用程序(多模型)。

我以 Code First 方式设置了我的模型,以便 EF 可以为我创建我的数据库和表。

假设我有一个 Person 模型,它具有:

  • 姓名
  • 电话
  • 地址
  • 城市
  • 状态标识

然后我有一个状态模型:

  • 状态标识
  • 状态

我已经为这两个模型创建了我的控制器和视图。

在我的个人编辑视图中,我想显示当前在我的状态表中的所有状态的组合框。

在我看来,我只看到:

@model IEnumerable<myProject.Models.Person>

我看不到包含所有州名的方法,因此我可以填充组合框。

实现这一目标的最佳方法是什么?

4

2 回答 2

4

使用视图模型是正确的方法。然后,您可以在控制器上创建一个私有方法来SelectList每次构建。这种方法可以防止您不得不牺牲编译时安全性,如果您使用ViewBag.

所以首先,视图模型可能看起来像这样:

public class EditPersonViewModel
{
    public Person Person { get; set; }
    [Display(Name = "State")]
    public int StateId { get; set; }
    public SelectList States { get; set; }
}

StateId尽管我们可以通过对象访问该属性,但我在其中拥有该属性的原因Person是因为在这种情况下,Person是模型,并且在我看来,我认为在模型上放置数据注释是没有意义的. 除此之外,Display注释仅允许您控制在使用LabelFor为您的States SelectList.

在开始构建列表之前,让我们定义一个简单的类来保存状态:

public class State
{
    public int Id { get; set; }
    public string Name { get; set; }
}

接下来,我们需要能够构建SelectList. 为此,您可以向控制器添加私有方法:

private SelectList GetStates(object selectedValue = null)
{
    return new SelectList(this.States, StateListDataValueKey,
                   StateListDataTextKey, selectedValue);
}

this.States代表您在哪里获取/存储您的状态数据。所以它可能是这样的db.States.GetAll()

StateListDataValueKeyStateListDataTextKey分别表示要用作选定值和选定文本的字段的名称。所以在这种情况下,StateListDataValueKey应该是Id而且StateListDataTextKey应该是Name。我倾向于在我的控制器中定义这些:

public class HomeController : Controller
{
    // rest of controller

    // These two strings should correspond with
    // the properties in your State class
    private readonly string StateListDataValueKey = "Id";
    private readonly string StateListDataTextKey = "Name";
}

我们现在可以继续创建操作来显示一个人的数据,也可以编辑一个人:

public ActionResult Edit(int id)
{
    var model = new EditPersonViewModel();
    // Get a person by Id.
    model.Person = GetPerson(id);
    // Call the utility method to build the list.
    model.States = GetStates(model.Person.StateId);

    return View(model);
}

[HttpPost]
public ActionResult Edit(EditPersonViewModel model)
{
    if (ModelState.IsValid)
    {
        // Save the edits and then redirect.
        return RedirectToActionPermanent("Index");
    }

    // Build the SelectList again so we can repopulate the view.
    model.States = GetStates(model.Person.StateId);

    return View(model);
}

既然已经这样了,让我们看一下编辑视图的相关部分:

@model EditPersonViewModel

<div class="editor-label">
    @Html.LabelFor(model => model.StateId)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Person.StateId, Model.States)
    @Html.ValidationMessageFor(model => model.States)
</div>

调用LabelFor使用Display前面定义的数据注释来正确标记下拉列表。否则它将标有文本“StateId”,这对您的用户没有用处。

更新

我在控制器中指定的两个私有字段有点困扰我,因为这不是最好的做事方式。如果您喜欢它,我已经将这种方法留在那里,但这里有一个更好的实现:

private SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
    Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
    object selectedValue = null)
{
    var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
    var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;

    return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}

请注意我如何使该方法成为通用方法并更改了名称。现在,您可以使用它来构建任何SelectList,而不仅仅是一个状态。你可以像这样使用它:

model.States = BuildSelectList(this.States, m => m.Id, m => m.Name, model.Person.StateId);

您不再需要在控制器中定义StateListDataValueKeyStateListDataTextKey

于 2013-09-27T23:52:17.090 回答
1

你至少有两种方法可以做到这一点。最简单、“又快又脏”的方法是使用 ViewBag/ViewData/Session 并将状态列表存储在那里。一个(更好的 IMO)替代方法是创建一个 ViewModel,其中包含一个人、一组状态,以及可能选择了哪个状态。它看起来像这样:

public class PersonViewModel
{
    public Person Person { get; set; }

    public State SelectedState { get; set; }

    // this might also be IEnumerable<SelectListItem> depending on what you want to do.
    public IEnumerable<State> States { get; set; }
}

然后,在构造函数中,调用用数据填充 ViewModel 的方法。将该 ViewModel 绑定到您的 View,并以您认为合适的方式使用它。

于 2013-09-28T00:03:01.870 回答