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

时间:2018-11-21 17:25:03

标签: entity-framework razor asp.net-core entity-framework-core tag-helpers

在我的应用程序当前位于的位置,每个AppUser可以(也可以不)具有3个电话号码(UserPhones)。每种类型之一(手机,家庭,其他)。

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

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

你能帮忙吗?

再次感谢!!!

1 个答案:

答案 0 :(得分:1)

TagHelper

  

在我当前的应用程序位于时,每个AppUser可能(也可能没有)具有3个电话号码(UserPhones)。每种类型之一(手机,家庭,其他)。

如果我理解正确,那么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电话。

页面处理程序或操作方法

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

    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