C# |无法将 'string' 转换为 'ModelExpression'

C# | Cannot convert 'string' to 'ModelExpression'

我想为表单域做一个局部视图,这样我就不用每次都写 ~10 行样板代码了。

我尝试了以下方法:

FieldViewModel.cs

using Microsoft.AspNetCore.Mvc.ViewFeatures;

public class FieldViewModel
{
    public FieldViewModel(ModelExpression Data, string Label = "")
    {
        this.Data = Data;
        this.Label = Label;
    }

    public ModelExpression Data { get; set; }

    public string Label { get; set; }
}

_Field.cshtml(我去掉了这个例子中的 html 代码)

// ...
<input asp-for="@Model.Data" />
// ...

然后我这样称呼它:

@await Html.PartialAsync("_Field", new FieldViewModel(Model.FirstName, "First Name"))

显然,这行不通,因为我们传递了 string 而它期望 ModelExpression。有什么办法可以工作吗?也许以某种方式手动构建 ModelExpression(不确定如何构建)然后传递它?

我已经尝试了一些东西并且到目前为止它有效,也许还有一些边缘情况你必须考虑,还有一些你应该扩展的 TagHelpers,但基本上它看起来不错。

首先我扩展了一些标签助手,用于标签输入和跨度。

using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

using System.Threading.Tasks;

在以下情况下,“asp-bind”是接受 ModelExpression 并将其重新分配给“asp-for”的属性的名称属性。

[HtmlTargetElement("label", Attributes = "asp-bind")]
public class FormGroupLabelTagHelper : LabelTagHelper
{
    public FormGroupLabelTagHelper(IHtmlGenerator generator)
        : base(generator) { }

    [HtmlAttributeName("asp-bind")]
    public ModelExpression AspBind
    {
        get => base.For;
        set => base.For = value.Model as ModelExpression;
    }

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        => base.ProcessAsync(context, output);
}

...

[HtmlTargetElement("input", Attributes = "asp-bind")]
public class FormGroupInputTagHelper : InputTagHelper
{
    public FormGroupInputTagHelper(IHtmlGenerator generator)
        : base(generator) { }

    [HtmlAttributeName("asp-bind")]
    public ModelExpression AspBind
    {
        get => base.For;
        set => base.For = value.Model as ModelExpression;
    }

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 
        => base.ProcessAsync(context, output);
}

...

[HtmlTargetElement("span", Attributes = "asp-bind")]
public class FormGroupErrorTagHelper : ValidationMessageTagHelper
{
    public FormGroupErrorTagHelper(IHtmlGenerator generator)
        : base(generator) { }

    [HtmlAttributeName("asp-bind")]
    public ModelExpression AspBind
    {
        get => base.For;
        set => base.For = value.Model as ModelExpression;
    }

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 
        => base.ProcessAsync(context, output);
}

然后我创建了一个以 ModelExpression 作为模型的局部视图。

@model ModelExpression

<div class="form-group">
    <label asp-bind="@this.Model" class="col-form-label-sm mb-0 pb-0"></label>
    <input asp-bind="@this.Model" class="form-control" />
    <span asp-bind="@this.Model" class="small text-danger"></span>
</div>

最后像下面这样使用

<form asp-controller="Home" asp-action="Index" method="post">
    <fieldset class="card card-body m-0 p-3">
        <legend class="w-auto px-1 py-0 m-0 small font-weight-bold ">With Tag Helpers</legend>
        <partial name="TestGroup" model="this.ModelExpressionProvider.CreateModelExpression(this.ViewData, x => x.Name)" />
        <partial name="TestGroup" model="this.ModelExpressionProvider.CreateModelExpression(this.ViewData, x => x.Age)" />
    </fieldset>
    <div class="d-flex mt-3">
        <input type="submit" class="btn btn-primary px-5" value="Sublit" />
    </div>
</form>

看起来创建的 ModelExpression 包裹在另一个 ModelExpression 中,其中 Model 是实际的 ModelExpression。我把它变成一个 ModelExpression 并将它交给 For 属性 到已经扩展的 TagHelpers.

如果你考虑走这条路,你就不再需要你的模型包装器了,你可以用下面的一些属性来装饰你的属性,而不是标签

using System.ComponentModel.DataAnnotations;

public class ExampleModel
{
    [Required]
    [Display(Name = "Fullname")]
    public string Name { get; set; }

    [Range(18, 100)]
    [Display(Name = "Age")]
    public int Age { get; set; }

    public ExampleModel InnerModel { get; set; }
}