使用自定义标签帮助程序更新相关的Phone实体 [英] Updating related Phone entities with custom tag helper

查看:95
本文介绍了使用自定义标签帮助程序更新相关的Phone实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序当前位于的位置,每个 AppUser 可以(也可以不)具有3个电话号码( UserPhones ) 。

As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).

下面的Tag Helper很好用(谢谢@itminus)。

The following Tag Helper works great (Thanks @itminus).

来自Razor页面的呼叫代码:

Calling code from Razor Page:

<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 当前仅列出了一个相关的移动电话号码。因此, AppUser.UserPhones (计数= 1,即移动类型)。因此,上面的代码,原样,将仅呈现手机的输入。

Lets assume this AppUser only has one related Mobile phone number listed currently. So AppUser.UserPhones (count = 1 of type Mobile). So the code above, as-is, will only render an input for Mobile phone.

由于 types-to -edit 调用移动和其他,我希望将两个输入都呈现到屏幕上。并且,如果用户将电话号码添加到其他输入中,则该电话号码将被保存到Razor页面 OnPostAsync UserPhones 实体中c $ c>方法。如果用户未为其他输入提供数字,则不应创建类型为其他的相关 UserPhones 记录。

Since types-to-edit calls for both Mobile and Other, I want both inputs to be rendered to the screen. And IF the user adds a phone number to the Other input, then it would be saved to the related UserPhones entity on the Razor Pages OnPostAsync method. If the user does NOT provide a number for the "Other" input, then the related UserPhones record of type "Other" should NOT be created.

您能帮忙吗?

再次感谢!!!

推荐答案

TagHelper


当我的应用程序当前位于时,每个AppUser可以(或可以没有)有3个电话号码(UserPhones)。每种类型之一(手机,住宅,其他)。

As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).

如果我理解正确,AppUser可能有3个电话号码和

If I understand correctly, an AppUser might have 3 phone numbers and the count of each phone type for every user will be zero or one.

如果是这种情况,我们可以简单地使用PhoneType作为索引,换句话说,不需要使用自定义索引来遍历 Phones 属性,而 ProcessAsync()方法可以是:

If that's the case, we can simply use PhoneType as an index, in other words, there's no need to use a custom index to iterate through the Phones property, and the ProcessAsync() method could be :

    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 是为特定属性生成标签生成器的简单助手方法:

Here the GenerateFieldForProperty is a simply helper method to generate tag builder for particular property:

    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,实际的有效负载将类似于:

When posted to server, if someone doesn't input a phone number for the other PhoneType, the actual payload will be something like:

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 电话。

Since we use phone type as the index, we can conclude that the UserPhones[0] will be used as an Mobile phone and the UserPhones[2] will be treated as an Home phone.

页面处理程序或操作方法

服务器端的模型绑定器将为每个U创建一个空字符串serPhone。
要删除这些空的输入并防止过度发布攻击,我们可以使用Linq筛选UserPhone,以便我们可以在没有空Phone的情况下创建或更新UserPhone记录:

And the model binder on server side will create a empty string for each UserPhone. To remove those empty inputs and prevent overposting attack, we could use Linq to filter UserPhones so that we can create or update UserPhone records without empty Phones:

    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:

User1 who has Mobile phone and Home phone number:

要创建新手机号码的User2:

User2 who wants to create a new Mobile phone number :

这篇关于使用自定义标签帮助程序更新相关的Phone实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆