【问题标题】:Constructing a Select List with OptGroup groups使用 OptGroup 组构建选择列表
【发布时间】: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% 使用硬编码的 SelectListGroupSelectListItem 值。

我的观点也是正确的,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; }
}

注意:即使 CityIdGuid 并且 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 =&gt; x.Active == true), "CityId", "CityName", "Region.RegionName", null) 即可生成您的IEnumerable&lt;SelectListItem&gt;(但请使用控制器,而不是您的查看模型)
  • 如果我使用ViewBag 来引入下拉列表,客户端的 FluentValidation 将停止运行。因此,我必须从模型中填充所有下拉菜单,在页面加载View(model) 期间将模型附加到视图,然后通过视图将它们挂钩。如果我需要验证结果,我会尽可能避免使用ViewBag

标签: asp.net asp.net-mvc asp.net-mvc-5


【解决方案1】:

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

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

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

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

您也可以使用SelectListItemGroup 属性来执行此操作

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

【讨论】:

  • @Stephen 你是怎么做这个多选的
  • @DanHunex,您只需使用@Html.ListBoxFor(x =&gt; x.CityId, Model.CityList , new { @class = "form-control" }) 而不是DropDownListFor()
  • 这是旧的,但你说的话让我很困惑。您说即使 ViewModel 中的 SelectListItems 之类的东西也不能访问数据库。在加载 ViewModel 的每个地方都没有重复代码的情况下,如何将其移动到控制器?
  • @MichaelZiluck。通常,您将其重构为单独的方法/服务。例如,在this DotNetFiddle 中用于级联下拉列表,请注意ConfigureViewModel() 方法 - 这意味着您没有重复代码,因为该代码通常会在 Create 和 Edit 方法中调用,并且每个方法都调用 GET 方法,然后在 POST 中再次调用如果ModelState 无效并且您需要返回视图,则每个方法的方法
  • 这很好。在那之后,我实际上会移动一些东西。我为我正在做的事情设置了单元测试,但这实际上让我意识到有一个方面我没有完全测试过。谢谢!
【解决方案2】:

我使用了这个很棒的库:DropDownList Optgroup MVC 1.0.0

Nuget link

使用方法如下:

  1. 准备好你的清单:

    var ethnicityData = new List<GroupedSelectListItem> {
            new GroupedSelectListItem() { Value="British", Text ="British", GroupName = "White", GroupKey="1"},
            new GroupedSelectListItem() { Value="Irish", Text ="Irish", GroupName = "White", GroupKey="1"},
            new GroupedSelectListItem() { Value="White Other", Text ="White Other", GroupName = "White", GroupKey="1"},
            new GroupedSelectListItem() { Value="Other Ethin Group", Text ="Other Ethin Group", GroupName = "Other Ethnic Group", GroupKey="2"},
            new GroupedSelectListItem() { Value="Chinese", Text ="Chinese", GroupName = "Other Ethnic Group", GroupKey="2"},
            new GroupedSelectListItem() { Value="Other mixed background", Text ="Other mixed background", GroupName = "Mixed", GroupKey="4"},
            new GroupedSelectListItem() { Value="White and Asian", Text ="White and Asian", GroupName = "Mixed", GroupKey="4"},
            new GroupedSelectListItem() { Value="White and Black African", Text ="White and Black African", GroupName = "Mixed", GroupKey="4"},
            new GroupedSelectListItem() { Value="White and Black Caribbean", Text ="White and Black Caribbean", GroupName = "Mixed", GroupKey="4"},
            new GroupedSelectListItem() { Value="Other Black background", Text ="Other Black background", GroupName = "Black or Black British", GroupKey="5"},
            new GroupedSelectListItem() { Value="Caribbean", Text ="Caribbean", GroupName = "Black or Black British", GroupKey="5"},
            new GroupedSelectListItem() { Value="African", Text ="African", GroupName = "Black or Black British", GroupKey="5"},
            new GroupedSelectListItem() { Value="Bangladeshi", Text ="Bangladeshi", GroupName = "Asian or Asian British", GroupKey="6"},
            new GroupedSelectListItem() { Value="Other Asian background", Text ="Other Asian background", GroupName = "Asian or Asian British", GroupKey="6"},
            new GroupedSelectListItem() { Value="Indian", Text ="Indian", GroupName = "Asian or Asian British", GroupKey="6"},
            new GroupedSelectListItem() { Value="Pakistani", Text ="Pakistani", GroupName = "Asian or Asian British", GroupKey="6"},
            new GroupedSelectListItem() { Value="Not Stated", Text ="Not Stated", GroupName = "Not Stated", GroupKey="3"}
    

    };

  2. 像这样使用它:

    @Html.DropDownGroupListFor(model => model.Ethnicity,ethnicityData, "", new { @class= "form-control" })

【讨论】:

  • “上次发布时间:2014-01-27”--Yeeeeahhhh……谢谢,但不谢谢。一切都有错误,任何正在积极维护的东西都会定期更新;我对废弃软件不感兴趣。
  • 除非您明确指定,否则此包不会呈现不显眼的验证属性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-25
  • 1970-01-01
相关资源
最近更新 更多