Обновление связанных сущностей Phone с помощью специального помощника по тегам - PullRequest
0 голосов
/ 21 ноября 2018

Поскольку мое приложение в настоящее время сидит, каждый AppUser может (или не может) иметь 3 телефонных номера (UserPhones).Один из каждого типа (Мобильный, Домашний, Другой).

Следующий Tag Helper прекрасно работает (Спасибо @itminus).

Телефонный код от 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 (count = 1 типа Mobile).Таким образом, код выше, как есть , будет отображать только вход для мобильного телефона.

Поскольку types-to-edit вызывает как для мобильного, так и для другого, я хочу, чтобы оба входа отображались наэкран.И если пользователь добавляет телефонный номер к другому входу, он будет сохранен в связанной сущности UserPhones в методе Razor Pages OnPostAsync.Если пользователь НЕ предоставляет номер для ввода «Другое», то НЕ ДОЛЖНО создаваться связанная запись UserPhones типа «Другое».

Вы можете помочь?

Еще раз спасибо!!!!

1 Ответ

0 голосов
/ 22 ноября 2018

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 телефон.

обработчик страницы или метод действия

А связыватель модели на стороне сервера создаст пустую строку длякаждый пользовательский телефон.Чтобы удалить эти пустые входные данные и предотвратить атаку с использованием избыточной почты, мы можем использовать 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

...