繁体   English   中英

如何在渲染具有相同属性名称的多个输入元素时使用替代的自动生成标识符?

[英]How to use an alternative auto-generated identifier upon rendering multiple input elements with the same property name?

假设我有这样的视图 model

public class ExampleVM
{
    [Display(Name = "Foo")]
    public Nullable<decimal> FooInternal { get; set; }
}

我的视图看起来像这样(也是我在这篇文章中省略的表单标签)

@model ExampleVM
....
<input asp-for="FooInternal" class="form-control" type="number" />

这会生成一个以FooInternal作为 id 属性的渲染文本框。

在我的场景中,我还有一个带有另一个表单的模式对话框,另一个视图 model 共享一个同名的属性。 我知道asp-for taghelper 从指定的 id 手动呈现 id 属性或从属性名称推断 id。

在我的后端代码中,我希望能够在给定视图 model 上下文的情况下命名我的属性。 我不想重命名我的属性以使它们在全球范围内独一无二。

我尽量避免两件事:

  • 在视图/输入元素中手动指定 id。 我更愿意使用可以通过后端的另一个属性设置的自动生成的 id。
  • 鉴于我在帖子中使用带有[FromBody]的视图 model ,我不能像使用[FromRoute(Name="MyFoo")]那样完全重命名该属性。 我不想 map 手动输入的 id 回到我的财产。

基本上,我正在寻找这样的东西:

public class ExampleVM
{
    [Display(Name = "Foo")]
    [HtmlId(Name = "MyUniqueFooName")]
    public Nullable<decimal> FooInternal { get; set; }
}

其中 HtmlId 是一个属性,它与标签助手交互以进行渲染,也用于重新绑定视图 model 作为[HttpPost]方法的参数。

也许另一种方法也是有效的,因为在同一页面上的多个 forms 中避免多个输入元素(具有相同的标识符)对我来说似乎很常见。

根据您的描述,如果您想实现您的要求,您应该编写自定义模型绑定和自定义输入标签助手来实现您的要求。

由于 asp.net 核心模型绑定会根据回传的表单数据绑定数据,因此您应该首先编写自定义输入标签助手以渲染输入名称属性以使用 HtmlId 值。

然后您应该在您的项目中编写自定义 model 绑定,以根据 HtmlId 属性绑定 model。

关于如何重写自定义输入标签助手,您可以参考以下步骤:

注意:由于输入标签助手有多种类型“文件,单选,复选框和其他”,您应该根据源代码编写所有逻辑。

根据输入标签助手源码,可以发现标签助手会调用Generator.GenerateTextBox方法生成输入标签html内容。

Generator.GenerateTextBox 有五个参数,第三个参数表达式用于生成输入文本框的 for 属性。

Generator.GenerateTextBox(
                ViewContext,
                modelExplorer,
                For.Name,
                modelExplorer.Model,
                format,
                htmlAttributes);

如果要将 HtmlId 值显示为 for 属性的名称,则应创建自定义输入 taghelper。

您应该首先创建一个自定义属性:

[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class HtmlId : Attribute
{
    public string _Id;
    public HtmlId(string Id) {

        _Id = Id;
    }

    public string Id
    {
        get { return _Id; }
    }
}

然后你可以使用var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault(); 在输入标签助手的 GenerateTextBox 方法中获取 htmlid。

详细信息,您可以参考下面的自定义输入标签助手代码:

using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace SecurityRelatedIssue
{
    [HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
    public class CustomInputTagHelper: InputTagHelper
    {
        private const string ForAttributeName = "asp-for";
        private const string FormatAttributeName = "asp-format";
        public override int Order => -10000;
        public CustomInputTagHelper(IHtmlGenerator generator)
       : base(generator)
        {
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            // Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying
            // from a TagBuilder.
            if (InputTypeName != null)
            {
                output.CopyHtmlAttribute("type", context);
            }

            if (Name != null)
            {
                output.CopyHtmlAttribute(nameof(Name), context);
            }

            if (Value != null)
            {
                output.CopyHtmlAttribute(nameof(Value), context);
            }

            // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
            // IHtmlGenerator will enforce name requirements.
            var metadata = For.Metadata;
            var modelExplorer = For.ModelExplorer;
            if (metadata == null)
            {
                throw new InvalidOperationException();
            }

            string inputType;
            string inputTypeHint;
            if (string.IsNullOrEmpty(InputTypeName))
            {
                // Note GetInputType never returns null.
                inputType = GetInputType(modelExplorer, out inputTypeHint);
            }
            else
            {
                inputType = InputTypeName.ToLowerInvariant();
                inputTypeHint = null;
            }

            // inputType may be more specific than default the generator chooses below.
            if (!output.Attributes.ContainsName("type"))
            {
                output.Attributes.SetAttribute("type", inputType);
            }

            // Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
            IDictionary<string, object> htmlAttributes = null;
            if (string.IsNullOrEmpty(For.Name) &&
                string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
                !string.IsNullOrEmpty(Name))
            {
                htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
                {
                    { "name", Name },
                };
            }

            TagBuilder tagBuilder;
            switch (inputType)
            {
                //case "hidden":
                //    tagBuilder = GenerateHidden(modelExplorer, htmlAttributes);
                //    break;

                //case "checkbox":
                //    tagBuilder = GenerateCheckBox(modelExplorer, output, htmlAttributes);
                //    break;

                //case "password":
                //    tagBuilder = Generator.GeneratePassword(
                //        ViewContext,
                //        modelExplorer,
                //        For.Name,
                //        value: null,
                //        htmlAttributes: htmlAttributes);
                //    break;

                //case "radio":
                //    tagBuilder = GenerateRadio(modelExplorer, htmlAttributes);
                //    break;

                default:
                    tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType, htmlAttributes);
                    break;
            }

            if (tagBuilder != null)
            {
                // This TagBuilder contains the one <input/> element of interest.
                output.MergeAttributes(tagBuilder);
                if (tagBuilder.HasInnerHtml)
                {
                    // Since this is not the "checkbox" special-case, no guarantee that output is a self-closing
                    // element. A later tag helper targeting this element may change output.TagMode.
                    output.Content.AppendHtml(tagBuilder.InnerHtml);
                }
            }
        }


        private TagBuilder GenerateTextBox(
     ModelExplorer modelExplorer,
     string inputTypeHint,
     string inputType,
     IDictionary<string, object> htmlAttributes)
        {
            var format = Format;
            if (string.IsNullOrEmpty(format))
            {
                if (!modelExplorer.Metadata.HasNonDefaultEditFormat &&
                    string.Equals("week", inputType, StringComparison.OrdinalIgnoreCase) &&
                    (modelExplorer.Model is DateTime || modelExplorer.Model is DateTimeOffset))
                {
                   // modelExplorer = modelExplorer.GetExplorerForModel(FormatWeekHelper.GetFormattedWeek(modelExplorer));
                }
                else
                {
                    //format = GetFormat(modelExplorer, inputTypeHint, inputType);
                }
            }

            if (htmlAttributes == null)
            {
                htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            }

            htmlAttributes["type"] = inputType;
            if (string.Equals(inputType, "file"))
            {
                htmlAttributes["multiple"] = "multiple";
            }

            var re = ((Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata)For.ModelExplorer.Metadata).Attributes.PropertyAttributes.Where(x => x.GetType() == typeof(HtmlId)).FirstOrDefault();

 
            return Generator.GenerateTextBox(
                ViewContext,
                modelExplorer,
                ((HtmlId)re).Id,
                modelExplorer.Model,
                format,
                htmlAttributes);
        }

    }
}

在 _ViewImports.cshtml 中引入这个 taghelper

@addTagHelper *,[yournamespace]

Model 示例:

    [Display(Name = "Foo")]
    [HtmlId("test")]
    public string str { get; set; }

结果:

在此处输入图像描述

然后,您可以为 model 编写自定义 model 绑定,以根据 htmlid 绑定数据。 关于如何使用自定义 model 绑定,可以参考这篇文章

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM