AppUser
identity model:
public virtual ICollection<UserPhones> UserPhones { get; set; }
Using Razor Pages, I call a partial view, like so:
@await Html.PartialAsync("_NameAndID", Model.AppUser)
PageModel:
[BindProperty]
public AppUser AppUser { get; set; }
public IActionResult OnGet()
{
AppUser = _userManager.Users
//.Include(x => x.UserAddresses) //OMITTED BC USING LAZY LOADING
.SingleOrDefaultAsync(x => x.UserName ==
_httpContext.HttpContext.User.Identity.Name).Result;
return Page();
}
Within _NameAndID.cshtml
, I explicitly reference a particular telephone from the UserPhones
entity. With:
<input type="hidden" asp-for="UserPhones
.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).UserPhoneId" />
//other properties removed for brevity
<div class="rvt-grid__item">
<label asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber">Mobile Phone</label>
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
<span asp-validation-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber"></span>
</div>
At runtime, the explicit mobile phone number is loaded properly. However when posting to public async Task<IActionResult> OnPostAsync()
the related AppUser.UserPhones
is null
. (The problem)
Can you help?
Thank you in advance!!!!
The Reason
The asp-for
does not work well for this scenario.
Considering your code in _NameAndID.cshtml
:
<input asp-for="UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber" autocomplete="tel" />
Note the LINQ extension method .SingleOrDefault(...)
here. The asp-for
here does not know how to get the name for UserPhones.SingleOrDefault(z => z.Type == EnumPhoneType.Mobile).PhoneNumber
, so it just render it as PhoneNumber
. As a result, the rendered html will be :
<input autocomplete="tel" type="text" id="PhoneNumber" name="PhoneNumber" value="">
Let's say someone inputs an value of 911
, when posted to server, the payload will be :
PhoneNumber=911
As your page model on server side is :
[BindProperty]
public AppUser AppUser{get;set;}
public IActionResult OnGet()
{
// ...
}
public IActionResult OnPostAsync()
{
return Page();
}
Note the AppUser.UserPhones
property is a collection. in other words, AppUser
expects a payload like :
UserPhones[0].UserPhoneId=1&UserPhones[0].PhoneNumber=911&UserPhones[1].UserPhoneId=2&UserPhones[1].PhoneNumber=119
However, what you send to the server is :
PhoneNumber=911
So the App.UserPhones
will always be null and the AppUser.PhoneNumber
property will be 911
.
How to Fix
Firstly, in order to bind the UserPhones
automatically, I change the type of App.UserPhones
to IList<UserPhones>
, so that we can use a index syntax
public class AppUser : IdentityUser{
// public virtual ICollection<UserPhones> UserPhones { get; set; }
public virtual IList<UserPhones> UserPhones { get; set; }
}
Secondly, don't use complex query in asp-for
, use simple index syntax instead. For example, if you would like to post some UserPhones
or post all UserPhones
, you can add an index for each field :
@for(var i=0;i <Model.UserPhones.Count(); i++) {
<div class="rvt-grid__item">
<label asp-for="@Model.UserPhones[i].UserPhoneId"></label>
<input asp-for="@Model.UserPhones[i].UserPhoneId"/>
<label asp-for="@Model.UserPhones[i].PhoneNumber"></label>
<input asp-for="@Model.UserPhones[i].PhoneNumber"/>
<span asp-validation-for="@Model.UserPhones[i].PhoneNumber"></span>
</div>
}
In this way, when someone submits the form, AppUser.UserPhones
will be the correctly set. Here's a screenshot of demo :
![](https://www.manongdao.com/static/images/pcload.jpg)