How do I make the asp-for input tag helper generate camelCase names?
If I have a view model:
public class MyModel{
public DateTime? StartDate {get;set;}
}
And in the view, the input tag is used with the asp-for tag helper like this:
<input asp-for="StartDate" />
By default the html generated by this is
<input type="datetime" id="StartDate" name="StartDate" value="" />
But what I want to create is html that looks like this:
<input type="datetime" id="StartDate" name="StartDate" value="" />
How can I create an asp-for helper tag to type camel names like above without having to make my model properties camelCase?
source to share
After examining the code posted by @Bebben and the link provided with it, I kept digging more into the Asp.Net Core source code. And I found that the Asp.Net Core devs provided some extensibility points that could be used to achieve lower camelCase id
and name
.
To do this, we need to implement our own IHtmlGenerator
, which we can do by creating our own class that inherits from DefaultHtmlGenerator
. Then, in this class, we need to override the method GenerateTextBox
to fix the wrapper. Or, alternatively, we can override the method GenerateInput
to fix the wrapper for the attribute values name
and id
for all input fields (not just text input fields), which is what I did. As a bonus, I also override the method GenerateLabel
, so the label attribute for
also specifies the value using a custom body.
Here's the 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;
}
}
}
Now that we have our class CustomHtmlGenerator
, we need to register it with the IoC container instead DefaultHtmlGenerator
. We can do this in the method ConfigureServices
for Startup.cs via the following two lines:
//Replace DefaultHtmlGenerator with CustomHtmlGenerator
services.Remove<IHtmlGenerator, DefaultHtmlGenerator>();
services.AddTransient<IHtmlGenerator, CustomHtmlGenerator>();
Pretty cool. And not only did we solve the problem with the corpus id
and name
in the input fields, but by implementing our own custom one IHtmlGenerator
and getting it, we opened the door for all kinds of html customization that could be done.
I am starting to appreciate the power of a system built around IoC and default classes with virtual methods. The level of customization available with minimal effort with this approach is truly amazing.
Update
@ Gup3rSuR4c pointed out that my call services.Remove
should be an extension method that is not included in the framework. I checked and yes it is true. So, here is the code for this extension method:
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);
}
}
source to share
The easiest way to do this is to simply write
<input asp-for="StartDate" name="startDate" />
Or do you want it to be fully generated in the camel case, for the entire application?
To do this, you feel like you should implement your own InputTagHelpers in Microsoft.AspNetCore.Mvc.TagHelpers.
Here is the method in which the name is generated:
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);
}
(The above code is from https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs , Apache License, version 2.0, copyright .NET Foundation)
The string "For.Name". The name is sent to some other methods, and the one that gives the final name at the end is in a static class (Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider), so we can't hook anything up easily.
source to share