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?

+3


source to share


2 answers


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); 
    }

}

      

+4


source


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.

+2


source







All Articles