Updating related Phone entities with custom tag helper












1















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



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



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 }" />


Code:



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 { });
}
}


My Question:



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.



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.



Can you help?



Thanks again!!!!










share|improve this question



























    1















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



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



    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 }" />


    Code:



    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 { });
    }
    }


    My Question:



    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.



    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.



    Can you help?



    Thanks again!!!!










    share|improve this question

























      1












      1








      1








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



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



      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 }" />


      Code:



      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 { });
      }
      }


      My Question:



      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.



      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.



      Can you help?



      Thanks again!!!!










      share|improve this question














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



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



      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 }" />


      Code:



      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 { });
      }
      }


      My Question:



      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.



      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.



      Can you help?



      Thanks again!!!!







      entity-framework razor asp.net-core entity-framework-core tag-helpers






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 21 '18 at 17:25









      FrazeFraze

      458515




      458515
























          1 Answer
          1






          active

          oldest

          votes


















          1














          TagHelper




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




          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.



          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);
          }
          }
          }


          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;
          }


          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&....


          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.



          page handler or action method



          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


          Let's say you want to create phones :



          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();
          }


          Test Case :



          <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>


          User1 who has Mobile phone and Home phone number:



          enter image description here



          User2 who wants to create a new Mobile phone number :



          enter image description here






          share|improve this answer


























          • I truly can't thank you enough @itminus. All the best to you

            – Fraze
            Nov 26 '18 at 14:53













          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53417556%2fupdating-related-phone-entities-with-custom-tag-helper%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          TagHelper




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




          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.



          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);
          }
          }
          }


          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;
          }


          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&....


          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.



          page handler or action method



          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


          Let's say you want to create phones :



          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();
          }


          Test Case :



          <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>


          User1 who has Mobile phone and Home phone number:



          enter image description here



          User2 who wants to create a new Mobile phone number :



          enter image description here






          share|improve this answer


























          • I truly can't thank you enough @itminus. All the best to you

            – Fraze
            Nov 26 '18 at 14:53


















          1














          TagHelper




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




          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.



          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);
          }
          }
          }


          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;
          }


          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&....


          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.



          page handler or action method



          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


          Let's say you want to create phones :



          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();
          }


          Test Case :



          <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>


          User1 who has Mobile phone and Home phone number:



          enter image description here



          User2 who wants to create a new Mobile phone number :



          enter image description here






          share|improve this answer


























          • I truly can't thank you enough @itminus. All the best to you

            – Fraze
            Nov 26 '18 at 14:53
















          1












          1








          1







          TagHelper




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




          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.



          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);
          }
          }
          }


          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;
          }


          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&....


          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.



          page handler or action method



          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


          Let's say you want to create phones :



          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();
          }


          Test Case :



          <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>


          User1 who has Mobile phone and Home phone number:



          enter image description here



          User2 who wants to create a new Mobile phone number :



          enter image description here






          share|improve this answer















          TagHelper




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




          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.



          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);
          }
          }
          }


          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;
          }


          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&....


          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.



          page handler or action method



          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


          Let's say you want to create phones :



          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();
          }


          Test Case :



          <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>


          User1 who has Mobile phone and Home phone number:



          enter image description here



          User2 who wants to create a new Mobile phone number :



          enter image description here







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 22 '18 at 7:14

























          answered Nov 22 '18 at 6:45









          itminusitminus

          4,3031424




          4,3031424













          • I truly can't thank you enough @itminus. All the best to you

            – Fraze
            Nov 26 '18 at 14:53





















          • I truly can't thank you enough @itminus. All the best to you

            – Fraze
            Nov 26 '18 at 14:53



















          I truly can't thank you enough @itminus. All the best to you

          – Fraze
          Nov 26 '18 at 14:53







          I truly can't thank you enough @itminus. All the best to you

          – Fraze
          Nov 26 '18 at 14:53






















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53417556%2fupdating-related-phone-entities-with-custom-tag-helper%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          How to change which sound is reproduced for terminal bell?

          Title Spacing in Bjornstrup Chapter, Removing Chapter Number From Contents

          Can I use Tabulator js library in my java Spring + Thymeleaf project?