使用 OptGroup 组构建选择列表 [英] Constructing a Select List with OptGroup groups

查看:17
本文介绍了使用 OptGroup 组构建选择列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当前项目:

  • ASP.NET 4.5.2
  • MVC 5

我正在尝试使用模型中的 OptGroups 构建一个选择菜单,但我的问题是我似乎无法自己构建 OptGroups.

I am trying to build a select menu with OptGroups from the Model, but my problem is that I cannot seem to build the OptGroups themselves.

我的模型:

[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; }
}

每个城市都可以在一个区域内.我想要一个选择菜单来按地区对城市进行分组.据我所知,上面的代码应该可以完成这项工作,但我得到了一个下拉菜单,其中 所有城市 分组在名为 <代码>System.Web.Mvc.SelectListGroup

Each city can be in a region. I want a select menu to group the cities by region. By all that I can figure out, the code above is supposed to do the job, but instead I get a drop-down menu with all cities grouped under the OptGroup named System.Web.Mvc.SelectListGroup

上面代码中的关键是我首先遍历Region,并将它们放入一个Dictionary中,将RegionId设置为带回RegionName<的键/code>(它本身被格式化为 SelectListGroup).

The key thing in the above code is that I first iterate through the Regions, and put them into a Dictionary, with the RegionId set to be the key that brings back the RegionName (which itself is formatted as a SelectListGroup).

然后我遍历城市,并将与城市的RegionId相匹配的组分配给每个城市.

Then I iterate through the Cities, and assign to each city the group that matches the city’s RegionId.

我还没有在 Internet 上看到任何实际从数据库中提取内容的示例 -- 所有示例 100% 都使用硬编码的 SelectListGroupSelectListItem 值.

I have not seen any examples on the Internet that actually pull content from a database -- 100% of all examples use hard-coded SelectListGroup and SelectListItem values.

我的观点也是正确的,AFAIK:

My View is also correct, AFAIK:

@Html.DropDownListFor(x => x.CityId, new SelectList(Model.CityName, "Value", "Text", "Group", 1), "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })

如您所见,应该将 Group 带入 SelectList,而 DropDownList 正在使用 创建OptGroups,只是不是正确的.

As you can see, the Group is supposed to be brought into the SelectList, and the DropDownList is being created with OptGroups, just not the correct ones.

我生成的下拉菜单如下所示:

My resulting drop-down menu looks something like this:

« ‹ Select › »
System.Web.Mvc.SelectListGroup
  City1
  City2
  ...
  LastCity

什么时候应该是这样:

« ‹ Select › »
Region1
  City2
  City4
  City5
Region2
  City3
  City1
  City6

建议?

修改后的解决方案:我遵循了解决方案://stackoverflow.com/users/3559349/stephen-muecke">Stephen Muecke,但稍作修改.

Modified solution: I have followed the solution provided by Stephen Muecke, but have modified it slightly.

MVC 的一般规则之一是您拥有一个比控制器更重的模型,并且该模型定义了您的业务逻辑.Stephen 断言所有数据库访问都应该在控制器中完成.我已经同意两者.

One of the general rules of MVC is that you have a model that is heavier than the controller, and that the model defines your business logic. Stephen asserts that all database access should be done in the controller. I have come to agree with both.

我最大的问题"之一是每次调用页面时都需要调用任何创建的下拉菜单或任何其他预填充的表单元素.这意味着,对于创建或编辑页面,您不仅需要在 [HttpGet] 方法上调用它,还需要在将模型发送回视图的 [HttpPost] 方法中调用它,因为它没有正确验证.这意味着您必须向每个方法添加代码(传统上通过 ViewBags),只是为了预先填充下拉列表等元素.这称为代码重复,并不是一件好事.一定有更好的方法,多亏了斯蒂芬的指导,我找到了一个.

One of my biggest "issues" is that any creation of a drop-down menu or any other pre-populated form element needs to be called every time the page is called. That means, for either a creation or edit page, you need to call it not only on the [HttpGet] method, but also in the [HttpPost] method where the model is sent back to the view because it did not properly validate. This means you have to add code (traditionally via ViewBags) to each Method, just to pre-populate elements like drop-down lists. This is called code duplication, and is not a Good Thing. There has to be a better way, and thanks to Stephen’s guidance, I have found one.

将数据访问保留在模型之外的问题是您需要用数据填充模型.避免代码重用和避免潜在错误的问题在于,您不应该将数据绑定到控制器中的元素.后一个动作是业务逻辑,理所当然地属于模型.在我的案例中,业务逻辑是我需要将用户输入限制为按地区分组的城市列表,管理员可以从下拉列表中进行选择.因此,虽然我们可能组装控制器中的数据,但我们绑定到模型中的模型.我之前的错误是在模型中两者都做了,这是完全不合适的.

The problem with keeping data access out of the Model is that you need to populate your model with the data. The problem with avoiding code reuse and avoiding potential errors is that you should not do the job of binding data to elements in the controller. This latter action is business logic, and rightfully belongs in the model. The business logic in my case is that I need to limit user input to a list of cities, grouped by region, that the administrator can select from a drop-down. So while we might assemble the data in the controller, we bind it to the model in the model. My mistake before was doing both in the model, which was entirely inappropriate.

通过将数据绑定到模型中的模型,我们避免了必须将其绑定两次 - 在控制器的 [HttpGet] 和 [HttpPost] 方法中的每一个中都绑定一次.我们只需要在由这两种方法处理的模型中绑定它一次.如果我们有一个可以在 Create 和 Edit 函数之间共享的更通用的模型,我们可以只在一个点而不是四个点进行这种绑定(但我没有这种程度的通用性,所以我不会把它作为一个例子)

By binding the data to the model in the model, we avoid having to bind it twice - once in each of the [HttpGet] and [HttpPost] methods of the controller. We only have to bind it once, in the model that is handled by both methods. And if we have a more generic model that can be shared between Create and Edit functions, we can do this binding in only one spot instead of four (but I don’t have this degree of genericness, so I won’t give that as an example)

所以一开始,我们实际上剥离了整个数据集,并将其粘贴在自己的类中:

So to start out, we actually peel off the entire data-assembly, and stick it inside its own class:

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

它存在于命名空间中,但位于我们正在处理的部分的控制器之下.为了清楚起见,我把它放在文件的最后,就在命名空间关闭之前.

This exists within the namespace, but beneath the controller of the section we are dealing with. For clarity’s sake, I stuck it at the very end of the file, just before the closing of the namespace.

然后我们看一下这个页面的模型:

Then we look at the model for this page:

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 显式绑定到下拉菜单,实际上首先允许客户端验证(Guids 只是一个额外的麻烦).

Note: Even though the CityId is a Guid and the DB field is a uniqueidentifier, I am bringing this value in as a string through the view because client-side validation sucks donkey balls for Guids. It’s far easier to do client-side validation on a drop-down menu if the Value is handled as a string instead of a Guid. You just convert it back into a Guid before you plunk it back into the master model for that table. Plus, CityName is not an actual field in the City table - it exists purely as a placeholder for the drop-down menu itself, which is why it exists in the CreateClientViewModel for the Create page. That way, in the view we can create a DropDownListFor that explicitly binds the CityId to the drop-down menu, actually allowing client-side validation in the first place (Guids are just an added headache).

关键是 get {}.如您所见,没有更多的数据库访问代码,只有一个简单的 SelectLists 以类为目标,并调用方法 CityNameList().您甚至可以将变量传递给该方法,因此您可以让相同的方法带回相同下拉菜单的不同变体.假设您希望页面上的一个下拉菜单 (Create) 的选项按 OptGroups 分组,而另一个下拉菜单 (Edit) 不包含任何选项分组.

The key thing is the get {}. As you can see, no more copious code which does DB access, just a simple SelectLists which targets the class, and a calling of the method CityNameList(). You can even pass variables on to the method, so you can have the same method bring back different variations of the same drop-down menu. Say, if you wanted one drop-down on one page (Create) to have its options grouped by OptGroups, and another drop-down (Edit) to not have any groupings of options.

实际模型最终比以前更简单:

The actual model ends up being even simpler than before:

@Html.DropDownListFor(x => x.CityId, Model.CityName, "« ‹ Select › »", htmlAttributes: new { @class = "form-control" })

无需修改引入下拉列表数据的元素——只需通过Model.ElementName调用即可.

No need to modify the element that brings in the drop-down list’s data -- you just call it via Model.ElementName.

我希望这会有所帮助.

推荐答案

首先,您的视图模型不应包含用于填充其属性的数据库访问代码.那是控制器的责任,您已经使您的代码无法进行单元测试.首先将模型更改为

Firstly, you view model should not contain database access code to populate its properties. That is the responsibility of the controller and you have made your code impossible to unit test. Start by changing the model to

public class CreateClientViewModel
{
    [DisplayName("City")]
    public string CityId { get; set; }
    public IList<SelectListItem> CityList { get; set; }
    ....
}

然后在控制器中,您可以使用接受 groupNameSelectList 重载之一来生成集合

Then in the controller, you can use one of the overloads of SelectList that accepts a groupName to generate the collection

var cities = var cities = db.City.Include(x => x.Region).Where(x => x.Active == true)
    .OrderBy(x => x.Region.RegionName).ThenBy(x => x.CityName);

var model = new CreateClientViewModel()
{
    CityList = new SelectList(cities, "CityId", "CityName", "Region.RegionName", null, null)
};
return View(model);

在视图中

@Html.DropDownListFor(x => x.CityId, Model.CityList , "« ‹ Select › »", new { @class = "form-control" })

或者,您也可以使用 SelectListItem

var model = new CreateClientViewModel()
{
    CityList = new List<SelectListItem> // or initialize this in the constructor
};
var cities = var cities = db.City.Include(x => x.Region).Where(x => x.Active == true).GroupBy(x => x.Region.RegionName);
foreach(var regionGroup in cities)
{
    var optionGroup = new SelectListGroup() { Name = regionGroup.Key };
    foreach(var city in regionGroup)
    {
        model.CityList.Add(new SelectListItem() { Value = city.CityId.ToString(), Text = city.CityName, Group = optionGroup });
    }
}
return View(model);

这篇关于使用 OptGroup 组构建选择列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆