代码之家  ›  专栏  ›  技术社区  ›  Fraze

复杂自定义标记帮助器

  •  1
  • Fraze  · 技术社区  · 6 年前

    基本上,我在扩展先前回答的问题( Updating related entities )所以它是一个自定义标记助手。

    我想向自定义标签助手发送一个与用户相关的电话列表,并为每个电话生成一个文本框。

    所以,假设我有以下语法:

    <user-phones phones="@Model.UserPhones" />
    

    下面是自定义标记助手的开始部分:

    public class UserPhonesTagHelper : TagHelper
    {
        private readonly IHtmlGenerator _htmlGenerator;
        private const string ForAttributeName = "asp-for";
    
    
        public List<UserPhones> Phones { get; set; }
    
        [ViewContext]
        public ViewContext ViewContext { set; get; }
    
        [HtmlAttributeName(ForAttributeName)]
        public ModelExpression For { get; set; }
    
        public UserPhonesTagHelper(IHtmlGenerator htmlGenerator)
        {
            _htmlGenerator = htmlGenerator;
        }
    
        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "div";
            output.TagMode = TagMode.StartTagAndEndTag;
            //output.Attributes.Add("class", "form-group");
    
            StringBuilder sbRtn = new StringBuilder();
            for (int i = 0; i < Phones.Count(); i++)
            {
                //NEED HELP HERE
            }
    
            output.Content.SetHtmlContent(sbRtn.ToString());
        }
    }
    

    for 循环,如何生成与迭代中的当前“userphone”实体相关的文本框和隐藏输入?当父Razor页面也被发布时,我需要这个保持绑定。

    我的思想是一种方法,这样会有所帮助。但是,我不知道如何通过 ModelExpression 对于 循环到方法

    private void WriteInput(TextWriter writer)
        {
            var tagBuilder = _htmlGenerator.GenerateTextBox(
              ViewContext,
              For.ModelExplorer,
              For.Name,
              value: null,
              format: null,
              htmlAttributes: new { @class = "form-control" });
    
            tagBuilder.WriteTo(writer, htmlEncoder);
        }
    

    再次感谢你的帮助…还在学习ASP.NET核心。

    1 回复  |  直到 6 年前
        1
  •  1
  •   itminus    6 年前

    你就快到了。

    设计

    这里的困难在于我们需要为未知属性构造一个表达式。假设您想在更高级别中使用用户电话ASP for=“”/>

    M0型 @ { var m1=getm1bymagic(m0); } <user phones asp for=“@m1.m2….mx.user phones”> </用户电话> < /代码>

    在标记助手中,我们可以假定每个属性的默认名称为 userphones[<index>]。<property name> 。但并非总是这样,用户可能希望将其更改为 m0.m2….mx.userphones[<index>]。<property name> 。但是,不可能知道编译时有多少个级别。

    因此,我们需要一个属性 expressionfilter 来将默认表达式转换为目标表达式:

    public class userPhoneStaghelper:tagHelper
    {
    
    [htmlattributename(“表达式筛选器”)]
    public func<string,string>expressionfilter get;set;=e=>e;
    
    /…
    }
    

    expressionfilterhere is a simple delegate to convert expression string.

    显示代码

    我只需复制您的大部分代码并做一点更改:

    public class userPhoneStaghelper:tagHelper
    {
    专用只读IHTML发电机;
    private const string forattributename=“asp for”;
    
    
    公共IList<用户电话>电话获取;设置;
    
    [视图上下文]
    公共视图上下文视图上下文设置;获取;
    
    [htmlattributename(forattributename)]
    get;set;的公共模型表达式
    
    公用用户电话Staghelper(IHTMlGenerator HTMLGenerator)
    {
    _ htmlgenerator=htmlgenerator;
    }
    
    [htmlattributename(“表达式筛选器”)]
    public func<string,string>expressionfilter get;set;=e=>e;
    
    //为某些属性生成标签和输入的帮助器方法
    私有标记生成器GenerateSimpleInputForField(int index,propertyinfo pi)
    {
    var instance=phones[index];//单个用户电话的当前实例
    var name=pi.name;//属性名:例如“phoneNumberID”
    var v=pi.getvalue(实例);
    
    var div=新的标记生成器(“div”);
    div.addcssclass(“表单组”);
    
    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(标签);
    
    var input=_htmlgenerator.generateTextBox(viewContext、explorer、expression、v、null、new@class=“form control”);
    div.innerhtml.appendhtml(输入);
    返回div;
    }
    
    公共重写异步任务处理异步(tagHelperContext上下文,tagHelperOutput输出)
    {
    output.tagname=“div”;
    output.tagmode=tagmode.starttagandendtag;
    
    var type=typeof(用户电话);
    propertyinfo phoneid=type.getproperty(“userphoneid”);
    propertyinfo phonenumber=type.getproperty(“phonenumber”);
    
    对于(int i=0;i<phones.count();i++){
    var div1=this.generatesimpleinputforfield(i,phoneID);
    var div2=this.GenerateSimpleInputForField(i,电话号码);
    
    output.content.appendhtml(div1);
    output.content.appendhtml(div2);
    }
    }
    }
    
    1. 上面的processAsync()仅显示userPhoneID的标签和输入。andphoneNumberfield.如果要自动显示所有属性,只需将方法更改为:

      public override async task processasync(tagHelperContext context,tagHelperOutput output)
      {
      output.tagname=“div”;
      output.tagmode=tagmode.starttagandendtag;
      
      对于(int i=0;i<phones.count();i++)
      {
      var pis=typeof(userphones.getproperties();
      foreach(pis中的var pi)
      {
      var div=this.GenerateSimpleInputForField(i,pi);
      输出.content.appendhtml(div);
      }
      }
      }
      
    2. 某些字段的默认表达式字符串由以下项生成:

      get_the_name_by('asp-for')+'[<index>]'+'<property name>'
      < /代码> 
      
      

      例如:appuser.userphones[i]。<property name>

      当然,它不适用于所有情况,我们可以自定义自己的expression filterto convert the expression as we like:。

      在视图文件中使用用户电话: //自定义我们自己的表达式筛选器: @ { var regex=new system.text.regularExpressions.regex(@“..”); func<string,string>表达式filter=e=>。{ var m=正则匹配(e); /… 返回m.groups[“expression”].value; }; } <user phones phones=“@model.appuser.user phones” asp for=“@model.appuser.userphones” expression filter=“expression filter”> </用户电话>
      1. 测试用例

        <div class=“row”>
        @等待html.partialasync(“\u nameandid”,model.appuser)
        &L/DIV & GT;
        
        <form method=“post”>
        <DIV class=“row”>
        <user phones phones=“@model.appuser.user phones”asp for=“@model.appuser.user phones”expression filter=“e=>e.substring(8)”></user phones>
        &L/DIV & GT;
        
        <button type=“submit”>提交</button>
        &表格/表格;
        < /代码> 
        
        

        第一部分由局部视图生成,第二部分由用户电话生成

        当你想使用<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;
        
            // ...
        }
        

        这个表达式过滤器下面是一个转换表达式字符串的简单委托。

        给我看看密码

        我只需复制您的大部分代码并做一点更改:

        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);
                }
            }
        }
        
        1. 这个ProcessAsync()上面只显示标签和输入UserPhoneIdPhoneNumber字段。如果要自动显示所有属性,只需将方法更改为:

          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);
                  }
              }
          }
          
        2. 某些字段的默认表达式字符串由以下内容生成:

          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:

        enter image description here

    推荐文章