Ты почти у цели.
Дизайн
Сложность заключается в том, что нам нужно построить выражение для неизвестных свойств.Скажем, когда вы хотите использовать <user-phones asp-for=""/>
на гораздо более высоком уровне, учитывая следующий код:
@model M0
@{
var M1 = GetM1ByMagic(M0);
}
<user-phones asp-for="@M1.M2....Mx.UserPhones">
</user-phones>
Внутри помощника тега мы можем принять имя по умолчанию каждого свойства равным UserPhones[<index>].<property-name>
.Но это не всегда так, пользователи могут захотеть изменить его на M0.M2....Mx.UserPhones[<index>].<property-name>
.Однако невозможно знать, сколько уровней будет во время компиляции.
Таким образом, нам необходим атрибут ExpressionFilter
для преобразования выражения по умолчанию в целевое выражение:
public class UserPhonesTagHelper : TagHelper
{
[HtmlAttributeName("expression-filter")]
public Func<string, string> ExpressionFilter { get; set; } = e => e;
// ...
}
Здесь ExpressionFilter
представляет собой простой делегат для преобразования строки выражения.
Покажите мне код
Я просто скопирую большую часть вашего кода и внесу небольшое изменение:
public class UserPhonesTagHelper : TagHelper
{
private readonly IHtmlGenerator _htmlGenerator;
private const string ForAttributeName = "asp-for";
public IList<UserPhones> Phones { get; set; }
[ViewContext]
public ViewContext ViewContext { set; get; }
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public UserPhonesTagHelper(IHtmlGenerator htmlGenerator)
{
_htmlGenerator = htmlGenerator;
}
[HtmlAttributeName("expression-filter")]
public Func<string, string> ExpressionFilter { get; set; } = e => e;
// a helper method that generate a label and input for some property
private TagBuilder GenerateSimpleInputForField( int index ,PropertyInfo pi)
{
var instance = Phones[index];// current instance of a single UserPhone
var name = pi.Name; // property name : e.g. "PhoneNumberId"
var v = pi.GetValue(instance);
var div = new TagBuilder("div");
div.AddCssClass("form-group");
var expression = this.ExpressionFilter(For.Name + $"[{index}].{name}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o =>v);
var label = _htmlGenerator.GenerateLabel( ViewContext, explorer, expression, name, new { } );
div.InnerHtml.AppendHtml(label);
var input = _htmlGenerator.GenerateTextBox( ViewContext, explorer, expression, v, null, new { @class = "form-control" } );
div.InnerHtml.AppendHtml(input);
return div;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
var type = typeof(UserPhones);
PropertyInfo phoneId= type.GetProperty("UserPhoneId");
PropertyInfo phoneNumber= type.GetProperty("PhoneNumber");
for (int i = 0; i< Phones.Count();i++) {
var div1 = this.GenerateSimpleInputForField(i,phoneId);
var div2 = this.GenerateSimpleInputForField(i,phoneNumber);
output.Content.AppendHtml(div1);
output.Content.AppendHtml(div2);
}
}
}
ProcessAsync()
выше показывает только метку и ввод для полей UserPhoneId
и PhoneNumber
.Если вы хотите, чтобы все свойства отображались автоматически, вы можете просто изменить метод на:
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
for (int i = 0; i < Phones.Count(); i++)
{
var pis = typeof(UserPhones).GetProperties();
foreach (var pi in pis)
{
var div = this.GenerateSimpleInputForField(i, pi);
output.Content.AppendHtml(div);
}
}
}
строка выражения по умолчанию для некоторого поля генерируется:
get_the_name_by('asp-for') +'[<index>]'+'<property-name>'
Например: AppUser.UserPhones[i].<property-name>
Конечно, это не будет применяться во всех случаях, мы можем настроить наш собственный expression-filter
для преобразования выражения, как нам нравится:
// use <user-phones> in view file :
// custom our own expression filter :
@{
var regex= new System.Text.RegularExpressions.Regex(@"...");
Func<string, string> expressionFilter = e => {
var m = regex.Match(e);
// ...
return m.Groups["expression"].Value;
};
}
<user-phones phones="@Model.AppUser.UserPhones"
asp-for="@Model.AppUser.UserPhones"
expression-filter="expressionFilter">
</user-phones>
Контрольный пример
<div class="row">
@await Html.PartialAsync("_NameAndID", Model.AppUser)
</div>
<form method="post">
<div class="row">
<user-phones phones="@Model.AppUser.UserPhones" asp-for="@Model.AppUser.UserPhones" expression-filter="e => e.Substring(8)"></user-phones>
</div>
<button type="submit">submit</button>
</form>
Первая часть генерируется частичным представлением, а вторая часть генерируется user-phones
: