【发布时间】:2016-05-11 01:25:40
【问题描述】:
当前项目:
- ASP.NET 4.5.2
- MVC 5
我正在尝试使用模型中的OptGroups 构建一个选择菜单,但我的问题是我似乎无法自己构建OptGroups。
我的模特:
[DisplayName("City")]
public string CityId { get; set; }
private IList<SelectListItem> _CityName;
public IList<SelectListItem> CityName {
get {
List<SelectListItem> list = new List<SelectListItem>();
Dictionary<Guid, SelectListGroup> groups = new Dictionary<Guid, SelectListGroup>();
List<Region> region = db.Region.Where(x => x.Active == true).OrderBy(x => x.RegionName).ToList();
foreach(Region item in region) {
groups.Add(item.RegionId, new SelectListGroup() { Name = item.RegionName });
}
List<City> city = db.City.Where(x => x.Active == true).ToList();
foreach(City item in city) {
list.Add(new SelectListItem() { Text = item.CityName, Value = item.CityId.ToString(), Group = groups[item.RegionId] });
}
return list;
}
set { _CityName = value; }
}
每个城市都可以属于一个地区。我想要一个选择菜单按地区对城市进行分组。据我所知,上面的代码应该可以完成这项工作,但我得到了一个下拉菜单,其中 所有城市 分组在名为 @ 的 OptGroup 下987654326@
上面代码中的关键是我首先遍历区域,并将它们放入字典中,将 RegionId 设置为带回 RegionName 的键(它本身被格式化为选择列表组)。
然后我遍历城市,并为每个城市分配与城市的RegionId 匹配的组。
我在 Internet 上没有看到任何实际从数据库中提取内容的示例——所有示例中 100% 使用硬编码的 SelectListGroup 和 SelectListItem 值。
我的观点也是正确的,AFAIK:
@Html.DropDownListFor(x => x.CityId, new SelectList(Model.CityName, "Value", "Text", "Group", 1), "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
如您所见,组应该被引入SelectList,而DropDownList是是用OptGroups创建的,只是不是正确的。
我生成的下拉菜单如下所示:
« ‹ Select › »
System.Web.Mvc.SelectListGroup
City1
City2
...
LastCity
什么时候应该是这样的:
« ‹ Select › »
Region1
City2
City4
City5
Region2
City3
City1
City6
建议?
修改方案:我已经按照Stephen Muecke提供的the solution进行了一些修改。
MVC 的一般规则之一是您有一个比控制器更重的模型,并且该模型定义了您的业务逻辑。 Stephen 断言所有数据库访问都应该在控制器中完成。我都同意了。
我最大的“问题”之一是每次调用页面时都需要调用下拉菜单或任何其他预填充表单元素的创建。这意味着,对于创建或编辑页面,您不仅需要在 [HttpGet] 方法上调用它,而且还需要在模型被发送回视图的 [HttpPost] 方法中调用它,因为它没有正确验证。这意味着您必须将代码(传统上通过 ViewBags)添加到 每个方法,以预先填充下拉列表等元素。这称为代码重复,并不是一件好事。一定有更好的方法,多亏斯蒂芬的指导,我找到了。
将数据访问排除在模型之外的问题在于,您需要使用数据填充模型。避免代码重用和避免潜在错误的问题在于,您不应该将数据绑定到控制器中的元素。后一个动作是业务逻辑,理所当然地属于模型。在我的案例中,业务逻辑是我需要将用户输入限制为按地区分组的城市列表,管理员可以从下拉列表中选择该列表。因此,虽然我们可能在控制器中组装数据,但我们将其绑定到模型in模型中。我之前的错误是在模型中两者都做,这是完全不合适的。
通过将数据绑定到模型中的模型,我们避免了绑定两次——在控制器的 [HttpGet] 和 [HttpPost] 方法中的每一个中绑定一次。我们只需要在由两种方法处理的模型中绑定一次。如果我们有一个更通用的模型可以在 Create 和 Edit 函数之间共享,我们可以只在一个地方而不是四个地方进行这种绑定(但我没有这种程度的通用性,所以我不会把它作为一个例子)
所以一开始,我们实际上剥离了整个数据组装,并将其放入自己的类中:
public class SelectLists {
public static IEnumerable<SelectListItem> CityNameList() {
ApplicationDbContext db = new ApplicationDbContext();
List<City> items = db.City.Where(x => x.Active == true).OrderBy(x => x.Region.RegionName).ThenBy(x => x.CityName).ToList();
return new SelectList(items, "CityId", "CityName", "Region.RegionName", 1);
}
}
这存在于命名空间中,但在我们正在处理的部分的控制器之下。为了清楚起见,我把它放在文件的最后,就在命名空间关闭之前。
然后我们看一下这个页面的模型:
public string CityId { get; set; }
private IEnumerable<SelectListItem> _CityName;
public IEnumerable<SelectListItem> CityName {
get { return SelectLists.CityNameList(); }
set { _CityName = value; }
}
注意:即使 CityId 是 Guid 并且 DB 字段是 uniqueidentifier,我还是将这个值作为字符串引入视图,因为客户端验证会为 Guids 带来麻烦。如果 Value 被处理为字符串而不是 Guid,则在下拉菜单上进行客户端验证要容易得多。您只需将其转换回 Guid,然后再将其重新放入该表的主模型中。另外,CityName 不是 City 表中的实际字段 - 它纯粹作为下拉菜单本身的占位符存在,这就是它存在于 Create 页面的 CreateClientViewModel 中的原因。这样,我们可以在视图中创建一个DropDownListFor,将 CityId 显式绑定到下拉菜单,实际上首先允许客户端验证(Guid 只是一个额外的麻烦)。
关键是get {}。正如你所看到的,没有更多的执行数据库访问的代码,只有一个简单的SelectLists 以类为目标,以及方法CityNameList() 的调用。您甚至可以将变量传递给方法,因此您可以让相同的方法带回相同下拉菜单的不同变体。比如说,如果您希望一个页面上的一个下拉菜单(创建)将其选项按 OptGroups 分组,而另一个下拉菜单(编辑)则不包含任何选项分组。
实际模型最终比以前更简单:
@Html.DropDownListFor(x => x.CityId, Model.CityName, "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })
无需修改引入下拉列表数据的元素——只需通过Model.ElementName调用即可。
我希望这会有所帮助。
【问题讨论】:
-
City是否包含属性Region? -
是的,City 的底部有
public virtual Region Region { get; set; }。这种关系也在IdentityModels.cs中使用 FluentValidation 列出。 -
您在上面看到的模型完全是另一个模型,
CreateClientViewModel。它用于在管理部分创建客户端。我创建它是为了自动填充下拉菜单等而不需要ViewBag,这样我就可以实际应用 FluentValidation 进行客户端验证。 -
您只需要
new SelectList(db.City.Where(x => x.Active == true), "CityId", "CityName", "Region.RegionName", null)即可生成您的IEnumerable<SelectListItem>(但请使用控制器,而不是您的查看模型) -
如果我使用
ViewBag来引入下拉列表,客户端的 FluentValidation 将停止运行。因此,我必须从模型中填充所有下拉菜单,在页面加载View(model)期间将模型附加到视图,然后通过视图将它们挂钩。如果我需要验证结果,我会尽可能避免使用ViewBag。
标签: asp.net asp.net-mvc asp.net-mvc-5