如何让 asp-for input 标签助手生成驼峰命名?

How to make the asp-for input tag helper generate camelCase names?

如果我有这样的视图模型:

 public class MyModel{
      public DateTime? StartDate {get;set;}
 }

在一个视图中,输入标签与 asp-for 标签助手一起使用,如下所示:

<input asp-for="StartDate" />

由此生成的默认html是

 <input type="datetime" id="StartDate" name="StartDate" value="" />

但我希望它生成的是 html,看起来像这样:

 <input type="datetime" id="startDate" name="startDate" value="" />

如何让 asp-for input tag helper 生成像上面那样的驼峰命名法 不必让我的模型属性变成驼峰式?

最简单的方法就是写

<input asp-for="StartDate" name="startDate" />

或者您想让它在整个应用程序中以驼峰式大小写完全自动生成吗?

要做到这一点,您似乎必须在 Microsoft.AspNetCore.Mvc.TagHelpers.

中实现自己的 InputTagHelpers

这是生成名称的方法:

private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
{
    var format = Format;
    if (string.IsNullOrEmpty(format))
    {
        format = GetFormat(modelExplorer, inputTypeHint, inputType);
    }

    var htmlAttributes = new Dictionary<string, object>
    {
        { "type", inputType }
    };

    if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
    {
        htmlAttributes["multiple"] = "multiple";
    }

    return Generator.GenerateTextBox(
        ViewContext,
        modelExplorer,
        For.Name,
        value: modelExplorer.Model,
        format: format,
        htmlAttributes: htmlAttributes);
}

(以上代码来自https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs,Apache License,Version 2.0,Copyright .NET Foundation)

该行是"For.Name"。名称被发送到其他一些方法中,最后给出最终名称的方法是静态的class(Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider),所以我们无法真正轻松地插入。

在研究了@Bebben 发布的代码和随附的 link 之后,我继续深入研究 Asp.Net 核心源代码。我发现 Asp.Net Core 的设计者提供了一些可扩展点,可用于实现较低的驼峰式 idname 值。

为此,我们需要实现我们自己的 IHtmlGenerator,我们可以通过创建一个继承自 DefaultHtmlGenerator 的自定义 class 来实现。然后在 class 上,我们需要覆盖 GenerateTextBox 方法来修复外壳。或者我们可以覆盖 GenerateInput 方法来修复所有输入字段(不仅仅是输入文本字段)的 nameid 属性值的大小写,这是我选择做的。作为奖励,我还覆盖了 GenerateLabel 方法,因此标签的 for 属性也使用自定义大小写指定了一个值。

这是 class:

    using Microsoft.AspNetCore.Antiforgery;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Internal;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.AspNetCore.Mvc.Routing;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.Extensions.Options;
    using System.Collections.Generic;
    using System.Text.Encodings.Web;

    namespace App.Web {
        public class CustomHtmlGenerator : DefaultHtmlGenerator {

            public CustomHtmlGenerator(
                IAntiforgery antiforgery,
                IOptions<MvcViewOptions> optionsAccessor,
                IModelMetadataProvider metadataProvider,
                IUrlHelperFactory urlHelperFactory,
                HtmlEncoder htmlEncoder,
                ClientValidatorCache clientValidatorCache) : base
                                (antiforgery, optionsAccessor, metadataProvider, urlHelperFactory,
                                htmlEncoder, clientValidatorCache) {

               //Nothing to do

            }

            public CustomHtmlGenerator(
                IAntiforgery antiforgery,
                IOptions<MvcViewOptions> optionsAccessor,
                IModelMetadataProvider metadataProvider,
                IUrlHelperFactory urlHelperFactory,
                HtmlEncoder htmlEncoder,
                ClientValidatorCache clientValidatorCache,
                ValidationHtmlAttributeProvider validationAttributeProvider) : base
                                (antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder,
                                clientValidatorCache, validationAttributeProvider) {

                //Nothing to do

            }


            protected override TagBuilder GenerateInput(
                ViewContext viewContext,
                InputType inputType,
                ModelExplorer modelExplorer,
                string expression,
                object value,
                bool useViewData,
                bool isChecked,
                bool setId,
                bool isExplicitValue,
                string format,
                IDictionary<string, object> htmlAttributes) {

                expression = GetLowerCamelCase(expression);

                return base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData, 
                                        isChecked, setId, isExplicitValue, format, htmlAttributes);
            }


            public override TagBuilder GenerateLabel(
                ViewContext viewContext,
                ModelExplorer modelExplorer,
                string expression,
                string labelText,
                object htmlAttributes) {

                expression = GetLowerCamelCase(expression);

                return base.GenerateLabel(viewContext, modelExplorer, expression, labelText, htmlAttributes);
            }


            private string GetLowerCamelCase(string text) {

                if (!string.IsNullOrEmpty(text)) {
                    if (char.IsUpper(text[0])) {
                        return char.ToLower(text[0]) + text.Substring(1);
                    }
                }

                return text;
            }

        }
    }

现在我们有了 CustomHtmlGenerator class,我们需要在 IoC 容器中注册它来代替 DefaultHtmlGenerator。我们可以通过以下两行在 Startup.cs 的 ConfigureServices 方法中做到这一点:

  //Replace DefaultHtmlGenerator with CustomHtmlGenerator
  services.Remove<IHtmlGenerator, DefaultHtmlGenerator>();
  services.AddTransient<IHtmlGenerator, CustomHtmlGenerator>();

很酷。我们不仅解决了输入字段的 idname 大小写问题,而且通过实现我们自己的自定义 IHtmlGenerator 并注册它,我们打开了各种类型的大门html 可以做的定制。

我开始真正体会到围绕 IoC 构建的系统的强大功能,并且默认 class 使用虚拟方法。在这种方法下不费吹灰之力就可以实现的定制化水平真是太棒了。

更新
@Gup3rSuR4c 指出我的 services.Remove 调用必须是框架中未包含的扩展方法。我查了一下,没错。因此,这是该扩展方法的代码:

 public static class IServiceCollectionExtensions {

    public static void Remove<TServiceType, TImplementationType>(this IServiceCollection services) {

        var serviceDescriptor = services.First(s => s.ServiceType == typeof(TServiceType) &&
                                                    s.ImplementationType == typeof(TImplementationType));
        services.Remove(serviceDescriptor); 
    }

}