部分扩展为输入标签助手

Partial that expands into input tag helper

本书 Pro ASP.NET Core 3 在第 27 章的示例中有 the following code

<div class="m-2">
    <h5 class="bg-primary text-white text-center p-2">HTML Form</h5>
    <form asp-page="FormHandler" method="post" id="htmlform">
        <div class="form-group">
            <label>Name</label>
            <input class="form-control" asp-for="Product.Name" />
        </div>
        <div class="form-group">
            <label>Price</label>
            <input class="form-control" asp-for="Product.Price" />
        </div>
        <div class="form-group">
            <label>Category</label>
            <input class="form-control" asp-for="Product.Category.Name" />
        </div>
        <div class="form-group">
            <label>Supplier</label>
            <input class="form-control" asp-for="Product.Supplier.Name" />
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    <button form="htmlform" asp-page="FormHandler" class="btn btn-primary mt-2">
        Sumit (Outside Form)
    </button>
</div>

如您所见,<div class="form-group>...</div> 模式重复了四次。作为部分测试,我添加了文件 Pages\_FormGroupPartial.cshtml:

@model FormGroupParams

<div class="form-group">
    <label>@Model.LabelContent</label>
    <input class="form-control" asp-for="@Model.ForString" />
</div>

Pages\FormHandler.cshtml.cs 我添加了以下内容 class:

public class FormGroupParams
{        
    public string LabelContent;
    public string ForString;

    public FormGroupParams(string labelContent, string forString)
    {
        LabelContent = labelContent;
        ForString = forString;
    }
}

为了对此进行测试,我编辑了 Pages\FormHandler.cshtml 以在原始方法之后立即使用新的部分:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" asp-for="Product.Supplier.Name" />
</div>

<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", "Product.Supplier.Name")' />

在生成的页面中,我们看到部分生成的代码并不完全相同;请注意,该值现在是 Product.Supplier.Name 而不是预期的 Splash Dudes:

查看源码,我们看到原来的代码:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" asp-for="Product.Supplier.Name" />
</div>

扩展为:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" type="text" id="Product_Supplier_Name" name="Product.Supplier.Name" value="Splash Dudes" />
</div>

而我们的部分:

<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", "Product.Supplier.Name")' />

扩展为:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" type="text" id="ForString" name="ForString" value="Product.Supplier.Name" />
</div>

有没有什么好的方法可以实现这个部分,从而获得与原始代码相同的输出?


更新 2020-09-30

PeterG 在他的回答中提出以下建议:

<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", @Model.Product.Supplier.Name)' />

这扩展为以下内容:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" type="text" id="ForString" name="ForString" value="Splash Dudes" />
</div>

而目标如下:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" type="text" id="Product_Supplier_Name" name="Product.Supplier.Name" value="Splash Dudes" />
</div>

所以 value 现在是正确的。但是 idname 仍然关闭。

我认为问题是由于将“Product.Supplier.Name”作为字符串值传递给部分模型的 FormGroupParams 对象引起的。相反,您需要从模型中传递 Product.Supplier.Name 属性 的实际值。

可能是这样的:

<partial name="_FormGroupPartial" model='new FormGroupParams("Supplier", @Model.Product.Supplier.Name)' />

将变量绑定到输入标签助手并使其被识别为相应的模型字段将非常复杂。

这里会涉及到输入标签助手的源码,考虑不同类型的输入。

我的建议是直接在partial view的asp-for后面加上name和id属性覆盖原来的生成.

调用局部视图时只需要传一个额外的值参数即可:

代码如下:

Pages\FormHandler.cshtml:

  <div class="form-group">
    <label>Supplier</label>
    <input class="form-control" asp-for="Product.Supplier.Name" />
</div>

          <partial name="_FormGroupPartial" 
    model='new FormGroupParams("Supplier","Product.Supplier.Name", @Model.Product.Supplier.Name)' />

Pages\FormHandler.cshtml.cs:

 public class FormGroupParams
    {
        public string LabelContent;
        public string ForString;
        public string Value;
        public FormGroupParams(string labelContent, string forString, string value)
        {
            LabelContent = labelContent;
            ForString = forString;
            Value = value;
        }
    }

Pages_FormGroupPartial.cshtml:

@model FormGroupParams

<div class="form-group">
    <label>@Model.LabelContent</label>
    <input class="form-control" asp-for="@Model.Value" id="@Model.ForString.Replace(".","_")" name="@Model.ForString"  />
</div>

这是渲染后的 html :

这本书的作者 Adam Freeman 通过电子邮件向我提供了一个示例,演示了如何使用标签助手解决这个问题。

添加文件TagHelpers/FormGroupTagHelper.cs:

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

namespace WebApp.TagHelpers
{
    [HtmlTargetElement("form-group")]
    public class FormGroupTagHelper : TagHelper
    {
        public string Label { get; set; }
        public ModelExpression For { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "div";
            output.TagMode = TagMode.StartTagAndEndTag;
            output.Attributes.SetAttribute("class", "form-group");

            var label = new TagBuilder("label");
            label.InnerHtml.Append(Label ?? For.Name);
                        
            var input = new TagBuilder("input");
            input.AddCssClass("form-control");
            input.Attributes["type"] = "text";
            input.Attributes["id"] = For.Name.Replace(".", "_");
            input.Attributes["name"] = For.Name;
            input.Attributes["value"] = For.Model.ToString();
            
            output.Content.AppendHtml(label);
            output.Content.AppendHtml(input);
        }
    }
}

现在,在 Pages/FormHandler.cshtml 而不是这个:

<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" asp-for="Product.Supplier.Name" />
</div>

您可以使用以下内容:

<form-group label="Supplier" for="Product.Supplier.Name"/>

谢谢亚当!

这是一种使用模板函数而不是部分函数的方法。

以下代码:


<div class="form-group">
    <label>Name</label>
    <input class="form-control" asp-for="Product.Name" />
</div>
<div class="form-group">
    <label>Price</label>
    <input class="form-control" asp-for="Product.Price" />
</div>
<div class="form-group">
    <label>Category</label>
    <input class="form-control" asp-for="Product.Category.Name" />
</div>
<div class="form-group">
    <label>Supplier</label>
    <input class="form-control" asp-for="Product.Supplier.Name" />
</div>

可以替换为:

@{ 
    await Template("Name",     "Product.Name");
    await Template("Price",    "Product.Price");
    await Template("Category", "Product.Category.Name");
    await Template("Supplier", "Product.Supplier.Name");
}

在 Razor 页面的开头给出了以下模板函数:

@{ 
    async Task Template(string label, string str)
    {
        var expr = Model.CreateModelExpression(str);

        <div class="form-group">

            <label>@label</label>

            <input class="form-control" asp-for="@expr.Model" />

        </div>
    }
}

那里引用的CreateModelExpression方法在页面模型中定义:

public class FormHandlerModel : PageModel
{
    private ModelExpressionProvider _modelExpressionProvider { get; set; }

    private DataContext context;

    public FormHandlerModel(DataContext dbContext, ModelExpressionProvider modelExpressionProvider)
    {
        context = dbContext;
        _modelExpressionProvider = modelExpressionProvider;
    }

    public ModelExpression CreateModelExpression(string expr)
    {
        var dict = new ViewDataDictionary<FormHandlerModel>(ViewData);
        
        return _modelExpressionProvider.CreateModelExpression(dict, expr);
    }
    
    ...
}

请注意,ModelExpressionProvider 是通过依赖注入引入的。

github 上的上述 Razor 页面:link

以上github页面模型:link.

欢迎提出改进建议!