如何让 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 的设计者提供了一些可扩展点,可用于实现较低的驼峰式 id
和 name
值。
为此,我们需要实现我们自己的 IHtmlGenerator
,我们可以通过创建一个继承自 DefaultHtmlGenerator
的自定义 class 来实现。然后在 class 上,我们需要覆盖 GenerateTextBox
方法来修复外壳。或者我们可以覆盖 GenerateInput
方法来修复所有输入字段(不仅仅是输入文本字段)的 name
和 id
属性值的大小写,这是我选择做的。作为奖励,我还覆盖了 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>();
很酷。我们不仅解决了输入字段的 id
和 name
大小写问题,而且通过实现我们自己的自定义 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);
}
}
如果我有这样的视图模型:
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 的设计者提供了一些可扩展点,可用于实现较低的驼峰式 id
和 name
值。
为此,我们需要实现我们自己的 IHtmlGenerator
,我们可以通过创建一个继承自 DefaultHtmlGenerator
的自定义 class 来实现。然后在 class 上,我们需要覆盖 GenerateTextBox
方法来修复外壳。或者我们可以覆盖 GenerateInput
方法来修复所有输入字段(不仅仅是输入文本字段)的 name
和 id
属性值的大小写,这是我选择做的。作为奖励,我还覆盖了 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>();
很酷。我们不仅解决了输入字段的 id
和 name
大小写问题,而且通过实现我们自己的自定义 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);
}
}