在项目列表中使用自定义 TagHelper(自动完成)时出现问题(与 EditorFor 一起显示)
Issue with Custom TagHelper (Autocomplete) when used inside a List of Items (being displayed with EditorFor)
我们编写了自定义 TagHelper
以通用方式处理自动完成。它有一个 asp-for 属性,定义为 ModelExpression 变量。
自动完成 TagHelper
写出一个隐藏字段(Id 字段),以及一个供自动完成 js 代码处理的输入字段。它最终将所选项目的 Id 值保存到隐藏字段。此自动完成功能非常适用于表单上的多个字段。
但是当使用 EditorTemplate
将自动完成 TagHelper
合并到项目列表中以显示相同的所有项目时(在模型中的列表上使用 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>
请注意,上面编辑器模板中的自动完成标签 会生成自己的内部标签(作为标签助手的一部分)。
这是第一个地址信息页面的一部分(如 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 前缀到输入名称属性以及输入 id 属性的 [n].fieldname 名称
问题是如何访问索引值,或获取此前缀以附加到我们从 TagHelper 内部生成的输入,即 Zn__* 或 [n] 值,它本质上是 EditorFor 的索引器,因为它生成重复字段
感谢您的帮助
好的,我一直在努力解决这个问题。
我最终不得不查看 Asp.Net 核心源项目中的 TagHelpers。
我发现我们需要访问ViewContext.ViewData.TemplateInfo;
从 TagHelper 内部。
或者我们可以使用 IHtmlGenerator 对象 - 调用 GenerateTextBox、GenerateHidden 等来根据需要构建 TageHelpers
我们编写了自定义 TagHelper
以通用方式处理自动完成。它有一个 asp-for 属性,定义为 ModelExpression 变量。
自动完成 TagHelper
写出一个隐藏字段(Id 字段),以及一个供自动完成 js 代码处理的输入字段。它最终将所选项目的 Id 值保存到隐藏字段。此自动完成功能非常适用于表单上的多个字段。
但是当使用 EditorTemplate
将自动完成 TagHelper
合并到项目列表中以显示相同的所有项目时(在模型中的列表上使用 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>
请注意,上面编辑器模板中的自动完成标签 会生成自己的内部标签(作为标签助手的一部分)。
这是第一个地址信息页面的一部分(如 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 前缀到输入名称属性以及输入 id 属性的 [n].fieldname 名称
问题是如何访问索引值,或获取此前缀以附加到我们从 TagHelper 内部生成的输入,即 Zn__* 或 [n] 值,它本质上是 EditorFor 的索引器,因为它生成重复字段
感谢您的帮助
好的,我一直在努力解决这个问题。 我最终不得不查看 Asp.Net 核心源项目中的 TagHelpers。
我发现我们需要访问ViewContext.ViewData.TemplateInfo; 从 TagHelper 内部。
或者我们可以使用 IHtmlGenerator 对象 - 调用 GenerateTextBox、GenerateHidden 等来根据需要构建 TageHelpers