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

使用自定义标签帮助程序更新相关电话实体

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

    当我的应用程序当前位于 AppUser 可能(或不可能)有3个电话号码( UserPhones )每种类型中的一种(移动、家庭、其他)。

    下面的标签助手非常有用(谢谢@itminus)。

    从Razor页面调用代码:

    <user-phones phones="@Model.UserPhones" 
                  asp-for="@Model.UserPhones" 
                  prop-name-to-edit="PhoneNumber"
                  types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile, 
                                   EnumPhoneType.Other }" />
    

    代码:

    public class UserPhonesTagHelper : TagHelper
    {
        private readonly IHtmlGenerator _htmlGenerator;
        private const string ForAttributeName = "asp-for";
    
        [HtmlAttributeName("expression-filter")]
        public Func<string, string> ExpressionFilter { get; set; } = e => e;
    
    
        public List<UserPhones> Phones { get; set; }
        public EnumPhoneType[] TypesToEdit { get; set; }
        public string PropNameToEdit { 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 = null; //DO NOT WANT AN OUTTER HTML ELEMENT
    
            for (int i = 0; i < Phones.Count(); i++)
            {
                var props = typeof(UserPhones).GetProperties();
                var pType = props.Single(z => z.Name == "Type");
                var pTypeVal = pType.GetValue(Phones[i]);
                EnumPhoneType eType = (EnumPhoneType) Enum.Parse(typeof(EnumPhoneType), pTypeVal.ToString());
    
                string lVal = null;
                switch (eType)
                {
                    case EnumPhoneType.Home:
                        lVal = "Home Phone";
                        break;
                    case EnumPhoneType.Mobile:
                        lVal = "Mobile Phone";
                        break;
                    case EnumPhoneType.Other:
                        lVal = "Other Phone";
                        break;
                    default:
                        break;
                }
    
                //LOOP ALL PROPERTIES
                foreach (var pi in props)
                {
                    var v = pi.GetValue(Phones[i]);
                    var expression = this.ExpressionFilter(For.Name + $"[{i}].{pi.Name}");
                    var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => v);
    
                    //IF REQUESTED TYPE AND PROPERTY SPECIFIED
                    if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && TypesToEdit.Contains(eType))
                    {
                        TagBuilder gridItem = new TagBuilder("div");
                        gridItem.Attributes.Add("class", "rvt-grid__item");
                        gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, lVal));
                        gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, v.ToString()));
                        output.Content.AppendHtml(gridItem);
                    }
                    else //ADD HIDDEN FIELD SO BOUND PROPERLY
                        output.Content.AppendHtml(BuildHidden(explorer, expression, v.ToString()));
                }
            }
        }
    
        private TagBuilder BuildTextBox(ModelExplorer explorer, string expression, string v)
        {
            return _htmlGenerator.GenerateTextBox(ViewContext, explorer, expression, v, null, new { @class = "form-control" });
        }
    
        public TagBuilder BuildHidden(ModelExplorer explorer, string expression, string v)
        {
            return _htmlGenerator.GenerateHidden(ViewContext, explorer, expression, v, false, new { });
        }
    
        public TagBuilder BuildLabel(ModelExplorer explorer, string expression, string v)
        {
            return _htmlGenerator.GenerateLabel(ViewContext, explorer, expression, v, new { });
        }
    }
    

    我的问题:

    假设这个 阿佩瑟 当前只列出了一个相关的手机号码。所以 AppUser.UserPhones (计数=移动式的1)。所以上面的代码, ,将仅呈现手机的输入。

    自从 types-to-edit 同时调用mobile和other,我希望两个输入都呈现到屏幕上。如果用户将一个电话号码添加到另一个输入中,那么它将保存到相关的 用户电话 Razor页面上的实体 OnPostAsync 方法。如果用户没有为“其他”输入提供数字,则 用户电话 不应创建“其他”类型的记录。

    你能帮忙吗?

    再次感谢!!!!!

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

    taghelper

    < Buff行情>

    由于我的应用程序当前位于,每个应用程序用户可能(或不可能)有3个电话号码(用户电话)。每种类型中的一种(移动、家庭、其他)。

    如果我理解正确,一个应用程序用户可能有3个电话号码,每个用户的每种电话类型的计数将为零或一。

    如果是这种情况,我们可以简单地使用phoneType作为索引,换句话说,不需要使用自定义索引来迭代 phones property,and the processAsync() method could be:。

    public override async task processasync(tagHelperContext context,tagHelperOutput output)
    {
    output.tagname=null;//不需要outter html元素
    
    var props=typeof(userphones.getproperties();
    
    //显示可编辑的手机标签
    foreach(此.typestoedit中的var pt){
    var phone=phones.singleOrDefault(p=>p.type==pt);
    VaR指数=(int)pt;
    foreach(props中的var pi)
    {
    //如果phone==null,则pv也应为null
    var pv=电话=空?空:pi.getValue(phone);
    var tag=generatefieldforproperty(pi.name,pv,index,pt);
    output.content.appendhtml(标记);
    }
    }
    //为手机生成隐藏的输入标签
    var phones=phones.where(p=>!this.typestoedit.contains((p.type));
    foreach(电话中的var p){
    var index=(int)p.type;
    foreach(props中的var pi){
    var pv=pi.getvalue(p);
    var tag=generatefieldforproperty(pi.name,pv,index,p.type);
    output.content.appendhtml(标记);
    }
    }
    }
    < /代码> 
    
    

    这里,GenerateFieldForProperty是为特定属性生成标记生成器的简单帮助方法:

    private tagbuilder generatefieldforproperty(string propname,object propvalue,int index,enumphonetype etype)
    {
    //当前用户电话是否可编辑(检查电话类型)
    var editable=typestoedit.contains(etype);
    var expression=this.expressionfilter(对于.name+$“[index]propname”);
    var explorer=for.modelexplorer.getExplorerForExpression(typeof(ilist<userphones>),o=>propValue);
    
    //如果指定了请求的类型和属性
    if(pi.name.normalizestring()==propnametoedit.normalizestring()可编辑)
    {
    tagbuilder griditem=new tagbuilder(“div”);
    grid item.attributes.add(“class”,“rvt-grid_uuu item”);
    var labeltext=this.getlabeltextbyphonetype(etype);
    griditem.innerhtml.appendhtml(buildLabel(explorer,expression,labelText));
    griditem.innerhtml.appendhtml(buildTextBox(explorer,expression,propValue?).toString());
    返回GridItem;
    }
    else//添加隐藏字段以便正确绑定
    返回buildHidden(explorer、expression、propValue)toSTRIN());
    }
    
    
    私有字符串GetLabelTextByPhoneType(EnumphoneType etype){
    字符串lval=空;
    开关(Eype)
    {
    案例EnumphoneType.Home:
    lval=“家庭电话”;
    断裂;
    EnumphoneType.手机:
    lval=“手机”;
    断裂;
    案例枚举类型。其他:
    lval=“其他电话”;
    断裂;
    违约:
    断裂;
    }
    返回LVAL;
    }
    < /代码> 
    
    

    当发布到服务器时,如果有人没有为otherphonetype输入电话号码,则实际有效负载将如下所示:

    appuser.userphones[0].userphoneid=……
    &appuser.userphones[2].userphoneid=&appuser.userphones[2].phonenumber=&appuser.userphones[2].type=&appuser.userphones[2].appuserid=&appuser.userphones[2].appuser=
    &appuser.userphones[1].userphoneid=..&appuser.userphones[1].phonenumber=119&….
    

    由于我们使用电话类型作为索引,因此我们可以得出结论,userphones[0]will be used as anmobile.phone and theuserphones[2]will be treated as anhome.phone.

    页面处理程序或操作方法

    服务器端的模型绑定器将为每个用户电话创建一个空字符串。 为了删除这些空输入并防止过度发布攻击,我们可以使用LINQ过滤用户电话,以便在不使用空电话的情况下创建或更新用户电话记录:

    var editables=new[]。{
    EnumphoneType.Mobile,
    EnumphoneType.其他,
    }(二)
    appuser.userphones=appuser.userphones
    。在哪里(p=>!string.isNullOrEmpty(p.PhoneNumber))//删除空输入
    .where(p=>editables.contains(p.type))//删除不可编辑的输入
    。
    //现在“userphones”将被清除以便以后使用
    //……根据需要创建或更新用户电话
    < /代码> 
    
    

    假设您要创建电话:

    public iactionresult onPostCreate()。{
    var editables=新建[]{
    EnumphoneType.Mobile,
    EnumphoneType.其他,
    };
    appuser.userphones=appuser.userphones
    。在哪里(p=>!string.isNullOrEmpty(p.PhoneNumber)
    。其中(p=>editables.contains(p.type))
    .select(p=>//为输入构造关系
    p.appuser=应用程序用户;
    p.appuser id=appuser.id;
    返回P;
    })
    。
    
    this._dbcontext.set<userphones>().addrange(appuser.userphones);
    this._dbContext.saveChanges();
    
    返回页();
    }
    < /代码> 
    
    

    测试用例:

    <form method=“post”>
    <DIV class=“row”>
    
    用户电话
    phones=“@model.appuser.userphones”
    asp for=“@model.appuser.userphones”
    prop name to edit=“电话号码”
    要编辑的类型=“New EnumphoneType[]EnumphoneType.Mobile,EnumphoneType.Other”
    gt;
    </用户电话>
    &L/DIV & GT;
    
    <button type=“submit”>提交</button>
    &表格/表格;
    < /代码> 
    
    

    拥有手机和家庭电话号码的用户1:。

    想要创建新手机号码的用户2:。

    每种类型(移动、家庭、其他)。

    如果我理解正确,一个appuser可能有3个电话号码,每个用户的每种电话类型的计数将为零或一。

    如果是这样,我们可以简单地使用phoneType作为索引,换句话说,不需要使用自定义索引来迭代 Phones 财产,以及 ProcessAsync() 方法可以是:

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
    
            var props = typeof(UserPhones).GetProperties();
    
            // display editable tags for phones
            foreach (var pt in this.TypesToEdit) {
                var phone = Phones.SingleOrDefault(p=>p.Type == pt);
                var index = (int) pt;
                foreach (var pi in props)
                {
                    // if phone==null , then the pv should be null too
                    var pv = phone==null? null: pi.GetValue(phone);
                    var tag = GenerateFieldForProperty(pi.Name, pv, index, pt);
                    output.Content.AppendHtml(tag);
                }
            }
            // generate hidden input tags for phones
            var phones= Phones.Where(p => !this.TypesToEdit.Contains((p.Type)));
            foreach (var p in phones) {
                var index = (int)p.Type;
                foreach (var pi in props) {
                    var pv = pi.GetValue(p);
                    var tag = GenerateFieldForProperty(pi.Name,pv,index,p.Type);
                    output.Content.AppendHtml(tag);
                }
            }
        }
    

    这里 GenerateFieldForProperty 是为特定属性生成标记生成器的简单帮助器方法:

        private TagBuilder GenerateFieldForProperty(string propName,object propValue,int index, EnumPhoneType eType )
        {
            // whether current UserPhone is editable (check the PhoneType)
            var editable = TypesToEdit.Contains(eType);
            var expression = this.ExpressionFilter(For.Name + $"[{index}].{propName}");
            var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => propValue);
    
            //IF REQUESTED TYPE AND PROPERTY SPECIFIED
            if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && editable)
            {
                TagBuilder gridItem = new TagBuilder("div");
                gridItem.Attributes.Add("class", "rvt-grid__item");
                var labelText = this.GetLabelTextByPhoneType(eType);
                gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, labelText));
                gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, propValue?.ToString()));
                return gridItem;
            }
            else //ADD HIDDEN FIELD SO BOUND PROPERLY
                return BuildHidden(explorer, expression, propValue?.ToString());
        }
    
    
        private string GetLabelTextByPhoneType(EnumPhoneType eType) {
            string lVal = null;
            switch (eType)
            {
                case EnumPhoneType.Home:
                    lVal = "Home Phone";
                    break;
                case EnumPhoneType.Mobile:
                    lVal = "Mobile Phone";
                    break;
                case EnumPhoneType.Other:
                    lVal = "Other Phone";
                    break;
                default:
                    break;
            }
            return lVal;
        }
    

    当发布到服务器时,如果有人没有为 other PhoneType,实际有效负载如下:

    AppUser.UserPhones[0].UserPhoneId=....&AppUser.UserPhones[0].PhoneNumber=911&....
    &AppUser.UserPhones[2].UserPhoneId=&AppUser.UserPhones[2].PhoneNumber=&AppUser.UserPhones[2].Type=&AppUser.UserPhones[2].AppUserId=&AppUser.UserPhones[2].AppUser=
    &AppUser.UserPhones[1].UserPhoneId=...&AppUser.UserPhones[1].PhoneNumber=119&....
    

    由于我们使用电话类型作为索引,因此我们可以得出以下结论: UserPhones[0] 将用作 Mobile 电话和 UserPhones[2] 将被视为 Home 电话。

    页处理程序或操作方法

    服务器端的模型绑定器将为每个用户电话创建一个空字符串。 为了删除这些空输入并防止过度发布攻击,我们可以使用LINQ过滤用户电话,以便在不使用空电话的情况下创建或更新用户电话记录:

        var editables = new[] {
            EnumPhoneType.Mobile,
            EnumPhoneType.Other,
        };
        AppUser.UserPhones = AppUser.UserPhones
            .Where(p => !string.IsNullOrEmpty(p.PhoneNumber))  // remove empty inputs
            .Where(p => editables.Contains(p.Type) )           // remove not editable inputs
            .ToList();
        // now the `UserPhones` will be clean for later use
        // ... create or update user phones as you like 
    

    假设您要创建电话:

    public IActionResult OnPostCreate() {
        var editables = new[] {
            EnumPhoneType.Mobile,
            EnumPhoneType.Other,
        };
        AppUser.UserPhones = AppUser.UserPhones
            .Where(p => !string.IsNullOrEmpty(p.PhoneNumber))
            .Where(p => editables.Contains(p.Type) )
            .Select(p => {                   // construct relationship for inputs
                p.AppUser = AppUser;
                p.AppUserId = AppUser.Id;
                return p;
            })
            .ToList();
    
        this._dbContext.Set<UserPhones>().AddRange(AppUser.UserPhones);
        this._dbContext.SaveChanges();
    
        return Page();
    }
    

    测试用例:

    <form method="post">
        <div class="row">
    
        <user-phones 
            phones="@Model.AppUser.UserPhones" 
            asp-for="@Model.AppUser.UserPhones" 
            prop-name-to-edit="PhoneNumber"
            types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile, EnumPhoneType.Other}"
            >
        </user-phones>
        </div>
    
        <button type="submit">submit</button>
    </form>
    

    拥有手机和家庭电话号码的用户1:

    enter image description here

    想要创建新手机号码的用户2:

    enter image description here

    推荐文章