Как заставить помощник тега ввода asp-for генерировать имена camelCase?

Если у меня есть такая модель представления:

 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 генерировать имена случаев верблюда, как указано выше, без необходимости делать мои свойства модели camelCase?


person RonC    schedule 11.04.2017    source источник


Ответы (2)


Изучив код, опубликованный @Bebben, и ссылку на него, я продолжил копаться в исходном коде Asp.Net Core. И я обнаружил, что разработчики Asp.Net Core предоставили некоторые точки расширяемости, которые можно использовать для достижения более низких значений camelCase id и name.

Для этого нам нужно реализовать наш собственный IHtmlGenerator, что мы можем сделать, создав собственный класс, унаследованный от DefaultHtmlGenerator. Затем в этом классе нам нужно переопределить метод GenerateTextBox, чтобы исправить корпус. Или, в качестве альтернативы, мы можем переопределить метод GenerateInput, чтобы исправить корпус значений атрибутов name и id для всех полей ввода (а не только текстовых полей ввода), что я и сделал. В качестве бонуса я также переопределяю метод GenerateLabel, поэтому атрибут for метки также указывает значение с использованием настраиваемого регистра.

Вот класс:

    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, нам нужно зарегистрировать его в контейнере IoC вместо класса DefaultHtmlGenerator. Мы можем сделать это в ConfigureServices методе Startup.cs с помощью следующих двух строк:

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

Довольно круто. И мы не только решили проблему id и name в полях ввода, но и реализовали наш собственный IHtmlGenerator и зарегистрировали его, мы открыли двери для всех видов настройки HTML, которые могут быть выполнены.

Я начинаю по-настоящему ценить мощь системы, построенной на IoC, и классы по умолчанию с виртуальными методами. Уровень настройки, доступный с небольшими усилиями при таком подходе, действительно впечатляет.

Обновление
@ 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); 
    }

}
person RonC    schedule 13.04.2017
comment
Ваш ответ привел меня к тому, что мне было нужно, а именно заменить чертово подчеркивание, которое создается для идентификаторов, точкой. Однако следует отметить, что services.Remove<TService, TImplementation>(), который вы используете, не является встроенным расширением и должен быть создан отдельно, или, по крайней мере, насколько мне известно, это так. - person Gup3rSuR4c; 20.10.2017
comment
Спасибо за ваш комментарий. Хорошая точка зрения. Я обновлю свой ответ кодом для этого метода services.Remove Extension. - person RonC; 21.10.2017
comment
Самолетом вспахиваешь поле ...? Разве это не слишком накладные расходы ...? - person Mladen B.; 27.08.2019
comment
@ MladenB. Должно быть почти никаких накладных расходов, только накладные расходы на манипуляции со строкой для строчной буквы одного ведущего символа, когда это необходимо. - person RonC; 27.08.2019

Самый простой способ сделать это - просто написать

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

Или вы хотите, чтобы он генерировался полностью автоматически в случае верблюда для всего приложения?

Для этого, похоже, вам нужно реализовать свои собственные InputTagHelpers в Microsoft.AspNetCore.Mvc.TagHelpers.

Вот метод, в котором генерируется имя:

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, версия 2.0, авторское право .NET Foundation)

Строка - «For.Name». Имя отправляется в некоторые другие методы, а тот, который в конце дает окончательное имя, находится в статическом классе (Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider), поэтому мы не можем легко подключиться к нему.

person Bebben    schedule 13.04.2017
comment
Ваш ответ вдохновил меня на более глубокое изучение исходного кода, и я обнаружил, что можно реализовать общесистемный подход. Впервые я по-настоящему начинаю ценить мощь сборки системы с использованием контейнера IoC. - person RonC; 13.04.2017
comment
@RonC Это отличные новости :-) - person Bebben; 18.05.2017