关于asp.net核心:无干扰验证不适用于ViewComponents

Unobtrusive validation doesn't work with ViewComponents

我正在使用ASP.NET Core v3.1实现表单。

我在Razor页面上有一个下拉代码,如下所示:

1
2
3
4
5
6
7
    <label asp-for="MySelectedItem" class="m-b-none"></label>
    <help-text asp-for="MySelectedItem"></help-text>
   
        <select asp-for="MySelectedItem" asp-items="@Model.MyItems" class="form-control"></select>
        <partial name="_ValidationIcon" />
   
    <span asp-validation-for="MySelectedItem" class="validation-message"></span>

在模型类中,我包含了一个验证规则,以确保必须选择一个有效的选项。这样可以很好地呈现:

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DropdownViewComponent : ViewComponent
{
    public IEnumerable<SelectListItem> Items { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, IEnumerable<SelectListItem> items)
    {
        Items = items;
        SelectedItem = selectedItem;
        return View(this);
    }
}

/Dropdown/Default.cshtml

1
2
3
4
5
6
7
8
9
10
@model Web.ViewComponents.DropdownViewComponent


    <label asp-for="SelectedItem" class="m-b-none"></label>
    <help-text asp-for="SelectedItem"></help-text>
   
        <select asp-for="SelectedItem" asp-items="@Model.Items" class="form-control"></select>
        <partial name="_ValidationIcon" />
   
    <span asp-validation-for="SelectedItem" class="validation-message"></span>

用法:

1
<vc:dropdown selected-item="MySelectedItem" items="Model.MyItems"></vc:dropdown>

此代码正确呈现了下拉列表,但是所呈现的HTML中缺少验证属性。为什么?

enter

1
2
3
4
public class ParentModel {
  [Required]
  public IEnumerable<SelectListItem> MyItems { get; set; }
}

这样,问题是您的视图不再绑定到该模型或其MyItems属性。相反,它绑定到DropdownViewComponent模型上的Items属性,该属性上没有任何验证属性。这两个属性可能都指向相同的IEnumerable<SelectListItem>对象引用,但是它们的元数据完全不同。这样,ASP.NET Core会正确显示与DropdownViewComponent.Items属性关联的验证属性。

我认为这是对视图组件的不幸限制,但从概念上讲,这是很难理解的。有关更多信息,请参见我对先前提出的类似问题的回答:如何在ASP.NET Core中将ModelExpression绑定到ViewComponent

也就是说,鉴于您的特殊要求,您可以通过将包含目标属性的模型传递给视图组件来解决此问题,而不是传递目标属性本身的值,然后通过视图组件进行中继\\的视图模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DropdownViewComponent : ViewComponent
{
    public ParentModel ParentModel { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, ParentModel model)
    {
        ParentModel = model;
        SelectedItem = selectedItem;
        return View(this);
    }
}

Note: I would typically create a separate, lightweight view model for your view component, instead of passing your view component object down to its view. But I'm maintaining this structure for consistency with your original code.

然后您将能够绑定到视图组件视图中的原始属性,从而保留所有原始验证属性:

1
<select asp-for="SelectedItem" asp-items="@Model.ParentModel.MyItems"></select>

首先,这并不能为您带来太多收益,甚至可能根本无法解决您寻求视图组件的全部原因,因为它迫使您对单个模型进行操作。如果具有不同模型的多个视图要使用此视图组件,则会带来一些问题。但是,您可以通过引入抽象层来缓解这些问题。

有几种方法可以做到这一点,例如建立一个接口,但是我推荐的一种方法是开发一个专门的列表类,用于对IEnumerable<SelectListItem>

进行建模。

1
2
3
public class DropdownList: List<SelectListItem> {
  public virtual IEnumerable<SelectListItem> Items { get; } = this;
}

然后在DropdownViewComponent中处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DropdownViewComponent : ViewComponent
{
    public DropdownList DropdownList { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, DropdownList dropdownList)
    {
        DropdownList = dropdownList;
        SelectedItem = selectedItem;
        return View(this);
    }
}

最后,在视图组件的视图中按以下方式实现它:

1
<select asp-for="SelectedItem" asp-items="@Model.DropdownList.Items"></select>

这将允许您重写此类,以便根据需要添加属性。例如,

1
2
3
4
public class RequiredDropdownList : DropdownList {
  [Required]
  public override Items { get; } = this;
}

Note: If you have a need to use a variety of different collections on different view models, and were relying on IEnumberable to unify them, this approach won't work. In that case, creating something like an IDropdownList interface makes more sense. Regardless, the concept is virtually identical.