Проблема с Custom TagHelper (автозаполнение) при использовании внутри списка элементов (отображается с помощью EditorFor)

Мы написали пользовательский TagHelper для обработки автозаполнения в общем виде. Он имеет атрибут asp-for, который определяется как переменная ModelExpression.

Автозаполнение TagHelper записывает скрытое поле (поле Id), а также поле ввода для работы кода автозаполнения js. В конечном итоге он сохраняет значение идентификатора выбранных элементов в скрытом поле. Это автозаполнение очень хорошо работает для нескольких полей в форме.

Но когда автозаполнение TagHelper включено в список элементов с использованием EditorTemplate для отображения всех элементов одинаково (используя EditorFor в списке в модели), нам нужно установить имя на основе индекса Z в скрытом поле, чтобы оно возвращалось контроллеру в виде списка элементов. например z0__*Field*, Z1__*Field*, ...

  • Как нам получить префикс имени на основе индекса Z, который нужно прикрепить к началу всех имен полей?
  • Должны ли мы сделать это сами?
  • или мы извлекаем из ModelExpression как-то?
  • Стандартный ввод TagHelper обрабатывается правильно?
  • Является ли EditorFor/EditorTemplate правильным способом обработки списков редактируемых объектов в ASP.NET Core 1?

Автозаполнение TagHelper

public class AutocompleteTagHelper : TagHelper
{
    public ModelExpression AspFor { get; set; }

    public ModelExpression AspValue { get; set; }

    //public string AspFor { get; set; }

    public string Route { get; set; }
    public string RouteParameters { get; set; }
    public string TargetWrapper { get; set; }
    public string DisplayFormat { get; set; }
    public string ValueFormat { get; set; }

    public string ManageListCallback { get; set; }
    public string ListWrapper { get; set; }

    public string Placeholder { get; set; }

    private SkillDbContext _context;
    private readonly UserManager<ApplicationUser> _userManager;
    private IMemoryCache cache;
    public AutocompleteTagHelper(SkillDbContext Context, UserManager<ApplicationUser> userManager, IMemoryCache cache)
    {
        _context = Context;
        _userManager = userManager;
        this.cache = cache;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        var hiddenVal = "";
        var displayVal = "";
        //asp-for="LandingPointId" 
        //route="/Lookups/GetLandingPoint" 
        //route-parameter="SomeOtherId" 
        //target-wrapper="form" key="Id" 
        //label="{Name} ({Code})" 
        //output="{code}"
        //AspFor.

        //get parent model from AspFor

        object thisModel = null;

        //get value properties
        if (AspValue != null)
        {
            hiddenVal = ValueFormat;
            displayVal = DisplayFormat;

            thisModel = AspValue.Model;

        }
        else if (AspFor.Model != null && !AspFor.Model.Equals((object)0))
        {
            Object Id = AspFor.Model;
            string routeMethod = Route.Split('/').Last<string>();
        }

        if(thisModel != null)
        {
            PropertyInfo[] propertyInfo = thisModel.GetType().GetProperties();
            foreach (var info in propertyInfo)
            {
                var val = info.GetValue(thisModel);
                if (val != null)
                {
                    hiddenVal = hiddenVal.Replace(("{" + info.Name + "}"), val.ToString());
                    displayVal = displayVal.Replace(("{" + info.Name + "}"), val.ToString());
                }

            }
        }
        var isAcList = ManageListCallback != null && ListWrapper != null;

        string aspForName = AspFor.Name.Replace(".", "_");
        output.TagName = "input";    // replaces <email> with <a> tag
        inputId = inputName = aspForName;
        output.Attributes["id"] = aspForName;
        output.Attributes["name"] = aspForName;
        output.Attributes["type"] = "text";

        output.Attributes["route"] = Route;
        output.Attributes["route-parameters"] = RouteParameters;
        output.Attributes["target-wrapper"] = TargetWrapper;

        output.Attributes["placeholder"] = Placeholder;

        output.Attributes["value-format"] = ValueFormat;
        output.Attributes["display-format"] = DisplayFormat;
        output.Attributes["value"] = displayVal;

        output.Attributes["class"] = "autocomplete form-control" + (isAcList?" hasList":"");

        TagBuilder HiddenValue = new TagBuilder("input");
        HiddenValue.Attributes["name"] = inputName;
        HiddenValue.Attributes["id"] = inputId + "_hidden";
        HiddenValue.Attributes["type"] = "hidden";
        HiddenValue.Attributes["value"] = hiddenVal;

        output.PreElement.SetContent(HiddenValue);

        if (isAcList)
        {
            TagBuilder AddBtn = new TagBuilder("a");

            AddBtn.Attributes["id"] = AspFor.Name.Replace(".", "_") + "_submit";
            AddBtn.Attributes["class"] = "moana-autocomplete-list-manager disabled btn btn-primary";
            AddBtn.Attributes["listwrapper"] = ListWrapper;
            AddBtn.Attributes["href"] = ManageListCallback;

            AddBtn.InnerHtml.AppendHtml("Add");
            output.PostElement.SetContent(AddBtn);
        }

    }

Это модель

public class AddressEditorModel 
{

    public  int Id { get; set; }

    public string AddressLinkTo { get; set; }

    public int AddressLink { get; set; }

    public string AddressLine { get; set; }

    public int ContactTypeId { get; set; }

    public string Suburb { get; set; }

    public string City { get; set; }

    public string Postcode { get; set; }

    public int? CountryId { get; set; }

    public string ContactTypeName { get; set; }

    public string CountryCode { get; set; }
    public string CountryName { get; set; }
}

это cshtml

@model List<Skill.ViewModels.AddressEditorModel>

<div class="address-info-wrapper">
    @Html.EditorFor(m => m)
<div>

Это вызов метода контроллера

public async Task<IActionResult> UpdateAddressInfo(List<AddressEditorModel> addresses)

и, наконец, это EditorTemplate

@model Skill.ViewModels.AddressEditorModel

<input type="hidden" asp-for="Id" />
<input type="hidden" asp-for="ContactTypeId" />
<input type="hidden" asp-for="AddressLink" />
<input type="hidden" asp-for="AddressLinkTo" />
<input type="hidden" asp-for="CountryId" />

<label class="col-md-12 control-label" style="padding-bottom:20px;">@Model.ContactTypeName</label>
<div class="form-group">
    <label asp-for="AddressLine" class="col-md-2 control-label">Address</label>
    <div class="col-md-10">
        <input asp-for="AddressLine" class="form-control" style="resize:both" />
        <span asp-validation-for="AddressLine" class="text-danger" />
    </div>
</div>
<div class="form-group">
    <label asp-for="Suburb" class="col-md-2 control-label">Suburb</label>
    <div class="col-md-10">
        <input asp-for="Suburb" class="form-control" />
        <span asp-validation-for="Suburb" class="text-danger" />
    </div>
</div>
<div class="form-group">
    <label asp-for="City" class="col-md-2 control-label">City</label>
    <div class="col-md-10">
        <input asp-for="City" class="form-control" />
        <span asp-validation-for="City" class="text-danger" />
    </div>
</div>
<div class="form-group">
    <label class="col-md-2 control-label">Country</label>
    <div class="col-md-10">
        <autocomplete asp-for="CountryId" route="/Lookups/GetCountry" target-wrapper="form" display-format="{Name} ({Code})" value-format="{Id}"></autocomplete>
        <span asp-validation-for="CountryId" class="text-danger" />
    </div>
</div>
<div class="form-group">
    <label asp-for="Postcode" class="col-md-2 control-label">Post Code</label>
    <div class="col-md-10">
        <input asp-for="Postcode" class="form-control" />
        <span asp-validation-for="Postcode" class="text-danger" />
    </div>
</div>

Обратите внимание, что приведенный выше тег автозаполнения в EditorTemplate создает свои собственные внутренние теги (как часть помощника тегов).

Это часть страницы для первой информации об адресе (как показано в Firefox).

<input id="z0__Id" type="hidden" value="5" name="[0].Id" data-val-required="The Id field is required." data-val="true">
<input id="z0__ContactTypeId" type="hidden" value="1" name="[0].ContactTypeId" data-val-required="The ContactTypeId field is required." data-val="true">
<input id="z0__AddressLink" type="hidden" value="1" name="[0].AddressLink" data-val-required="The AddressLink field is required." data-val="true">
<input id="z0__AddressLinkTo" type="hidden" value="F" name="[0].AddressLinkTo">
<label class="col-md-12 control-label" style="padding-bottom:20px;">Work</label>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__AddressLine">Address</label>
<div class="col-md-10">
<input id="z0__AddressLine" class="form-control" type="text" value="4a Lansdowne Street" name="[0].AddressLine" style="resize:both">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].AddressLine"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Suburb">Suburb</label>
<div class="col-md-10">
<input id="z0__Suburb" class="form-control" type="text" value="Bayswater" name="[0].Suburb">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Suburb"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__City">City</label>
<div class="col-md-10">
<input id="z0__City" class="form-control" type="text" value="Auckland" name="[0].City">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].City"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Country</label>
<div class="col-md-10">
<input id="CountryId_hidden" type="hidden" value="1" name="CountryId">
<input id="CountryId" class="moana-autocomplete form-control ui-autocomplete-input" type="text" value="New Zealand (NZ)" display-format="{Name} ({Code})" value-format="{Id}" placeholder="" target-wrapper="form" route-parameters="" route="/Lookups/GetCountry" name="CountryId" autocomplete="off">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].CountryId"> </span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="z0__Postcode">Post Code</label>
<div class="col-md-10">
<input id="z0__Postcode" class="form-control" type="text" value="0604" name="[0].Postcode">
<span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="[0].Postcode"> </span>
</div>
</div>

Обратите внимание, что Html.EditorFor создает префиксы Zn__fieldname для входного атрибута имени, а также [n]. fieldname имя для входного атрибута id

Проблема заключается в том, как получить доступ к значению индекса или получить этот префикс для добавления к нашим сгенерированным входным данным из TagHelper, т. е. значения Zn__* или [n], который по сути является индексатором EditorFor, поскольку он генерирует повторяющиеся поля

Спасибо за любую помощь


person Grant Nilsson    schedule 03.03.2016    source источник
comment
Не могли бы вы предоставить пример кода для вашей модели, вспомогательного тега и шаблона редактора, чтобы лучше понять?   -  person Hossam Barakat    schedule 03.03.2016


Ответы (1)


Хорошо, я усердно работал над этим. В конце концов мне пришлось взглянуть на TagHelpers из основного исходного проекта Asp.Net.

Я обнаружил, что нам нужно получить доступ к ViewContext.ViewData.TemplateInfo; изнутри TagHelper.

Или, в качестве альтернативы, мы могли бы использовать объект IHtmlGenerator, вызывая GenerateTextBox, GenerateHidden и т. д. для создания TageHelpers по мере необходимости.

person Grant Nilsson    schedule 04.03.2016