0

I've been away from ASP.NET MVC for a while so forgotten some of the basics.

I have scoured SO for an answer, but none really seem to apply/work so this may seem like a duplicate question but it's really not, perhaps I just can't see the wood through the trees. I know I'm missing something obvious but cant remember what

I have a partial that I pass the model to that updates a property on the model (AddressDetails & ContactDetails).

Main page

<form asp-action="Create">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="Name" class="control-label"></label>
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name" class="text-danger"></span>
        </div
        @await Html.PartialAsync("../AddressDetails/Create.cshtml", Model)
        @await Html.PartialAsync("../ContactDetails/Create.cshtml", Model)
        <div class="form-group">
            <input type="submit" value="Create" class="btn btn-primary" />
        </div>
</form>

And partial page

@model CareHome.Models.CareHomes
<div class="form-group">
    <h4>AddressDetails</h4>
    <hr />
</div>

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="AddressDetails.NumberStreetName" class="control-label"></label>
    <input asp-for="AddressDetails.NumberStreetName" class="form-control" />
    <span asp-validation-for="AddressDetails.NumberStreetName" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="AddressDetails.Locality" class="control-label"></label>
    <input asp-for="AddressDetails.Locality" class="form-control" />
    <span asp-validation-for="AddressDetails.Locality" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="AddressDetails.Town" class="control-label"></label>
    <input asp-for="AddressDetails.Town" class="form-control" />
    <span asp-validation-for="AddressDetails.Town" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="AddressDetails.PostCode" class="control-label"></label>
    <input asp-for="AddressDetails.PostCode" class="form-control" />
    <span asp-validation-for="AddressDetails.PostCode" class="text-danger"></span>
</div>

This is working fine when I post data back to the controller

Working

However, I want to reuse the partial which means I want to replace

@model CareHome.Models.CareHomes

in the partial with the property class (see further below) that the model uses.

So when I change it to

main

<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div
            @await Html.PartialAsync("../AddressDetails/Create.cshtml", Model.AddressDetails)
            @await Html.PartialAsync("../ContactDetails/Create.cshtml", Model.ContactInfo)
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

note that im passing the property through to the partial now not the model

@model CareHome.Models.AddressDetails
<div class="form-group">
    <h4>AddressDetails</h4>
    <hr />
</div>

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="NumberStreetName" class="control-label"></label>
    <input asp-for="NumberStreetName" class="form-control" />
    <span asp-validation-for="NumberStreetName" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="Locality" class="control-label"></label>
    <input asp-for="Locality" class="form-control" />
    <span asp-validation-for="Locality" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="Town" class="control-label"></label>
    <input asp-for="Town" class="form-control" />
    <span asp-validation-for="Town" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="PostCode" class="control-label"></label>
    <input asp-for="PostCode" class="form-control" />
    <span asp-validation-for="PostCode" class="text-danger"></span>
</div>

iv now changed the partial to use

@model CareHome.Models.AddressDetails

but when I post this to the controller it comes back null

Not working

I tried a million variations on the binding

 // POST: CareHomes/Create
        // To protect from overposting attacks, enable the specific properties you want to bind to.
        // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        //Create([Bind("CareHomesId,Name,ContactName,ContactNumber")] CareHomes careHomes)
        public async Task<IActionResult> Create([Bind( "CareHomes,AddressDetails,ContactDetails")] CareHomes careHomes)
        {
            if (ModelState.IsValid)
            {
                _context.Add(careHomes);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            ViewData["AddressDetailsId"] = new SelectList(_context.AddressDetails, "AddressDetailsId", "NumberStreetName", careHomes.AddressDetailsId);
            ViewData["ContactDetailsId"] = new SelectList(_context.ContactDetails, "ContactDetailsId", "ContactName", careHomes.ContactDetailsId);
            return View(careHomes);
        }

but when I evaluate the ModelState I can see it's always missing. As the propertys of the model bind ok when i pass the model though why do they then not bind when i pass the property though

my classes are like so

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace CareHome.Models
{
    public class CareHomes
    {
        [Required]
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int CareHomesId { get; set; }

        [Required]
        [Column(TypeName = "VARCHAR(256)")]
        [StringLength(256, MinimumLength = 3)]
        public string Name { get; set; }

        public int? AddressDetailsId { get; set; }

        public AddressDetails AddressDetails { get; set; }

        public int? ContactDetailsId { get; set; }

        public ContactDetails ContactInfo { get; set; }

        public ICollection<Staff>? StaffMembers { get; set; }
    }
}

and one of the properties in question

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace CareHome.Models
{
    public class AddressDetails
    {
        [Required]
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int AddressDetailsId { get; set; }

        [Required]
        [Column(TypeName = "VARCHAR(256)")]
        [StringLength(256, MinimumLength = 3)]
        [Display(Name = "House No & Street Name")]
        public string NumberStreetName { get; set; }

        [Column(TypeName = "VARCHAR(256)")]
        [StringLength(256, MinimumLength = 3)]
        public string? Locality { get; set; }

        [Required]
        [Column(TypeName = "VARCHAR(256)")]
        [StringLength(256, MinimumLength = 3)]
        public string Town { get; set; }

        [Required]
        [Column(TypeName = "VARCHAR(16)")]
        [StringLength(16, MinimumLength = 4)]
        [RegularExpression(@"^(([A-Z]{1,2}\d[A-Z\d]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?\d[A-Z]{2}|BFPO ?\d{1,4}|(KY\d|MSR|VG|AI)[ -]?\d{4}|[A-Z]{2} ?\d{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$", ErrorMessage = "Please enter a valid UK post code in upper case")]
        public string PostCode { get; set; }

        public CareHomes? CareHomes { get; set; }
    }
}

I have tried adding bind animations like

[BindProperty]

to the property and adding hidden fields in the partual

@Html.HiddenFor(m => m.AddressDetailsId)
@Html.HiddenFor(m => m.AddressDetails)

As per some suggestions from some of the many SO searches I did, but no dice....so please...what am I missing?

I even tried @html.EditorFor but that seems to have the same problem


EDIT


Using @Jonesopolis suggestion I can see from the form being posted back when it uses the model:

?this.Request.Form.ToArray()
{System.Collections.Generic.KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>[11]}
    ...
    [4]: {[AddressDetails.CareHomes, {}]}
    [5]: {[AddressDetailsId, {}]}
    [6]: {[AddressDetails.NumberStreetName, {sad}]}
    [7]: {[AddressDetails.Locality, {sad}]}
    [8]: {[AddressDetails.Town, {wales}]}
    [9]: {[AddressDetails.PostCode, {CF83 8RD}]}

vs when i pass the property

?this.Request.Form.ToArray()
{System.Collections.Generic.KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>[11]}
    ...
    [4]: {[CareHomes, {}]}
    [5]: {[AddressDetailsId, {0}]}
    [6]: {[NumberStreetName, {test street}]}
    [7]: {[Locality, {}]}
    [8]: {[Town, {wales}]}
    [9]: {[PostCode, {CF83 8RD}]}

so clearly the "AddressDetails" is missing so MVC cant map the propery to the CareHomes class object on the binding because the property name is missing. So i know what the issue is not how to fix it though, How do I set the property name on the partual propertys so they map back to the parent object class. I though about a costom binder but not having much luck figuring that one out.

On a side note, intrestingly enough if in the partent model I do this :

@Html.EditorFor(m => m.AddressDetails.NumberStreetName)

then bind like so

 public async Task<IActionResult> Create([Bind(include: "CareHomes,AddressDetails")] CareHomes careHomes)

I can at least get the EditorFor to pull though on the parent

4
  • 2
    That's a heck of a novel you've written. I'd start by looking at the raw body of your request that was previously working, and compare it to the raw request body of your partial implementation that isn't working Commented Jul 22, 2022 at 15:17
  • thank you, that helped. I now know what the issue is...just not sure how to fix it (see edit above) ? Commented Jul 22, 2022 at 17:08
  • Is it possible to move away from the domain model to the view model for data binding at the view? Commented Jul 27, 2022 at 3:29
  • not sure what difference it would make, the domain class is a view model, i.e a class that you use in the view to bind to ? Commented Jul 27, 2022 at 21:15

1 Answer 1

0

Finally worked it out, seems model binding wasn't the issue, I just had to set the id and name properties on the form controls in the partial to match that of the object on the model action, e.g. id="AddressDetails_NumberStreetName" name="AddressDetails.NumberStreetName"

so adding

<div class="form-group">
    <label asp-for="NumberStreetName" class="control-label"></label>
    <input asp-for="NumberStreetName" id="AddressDetails_NumberStreetName" name="AddressDetails.NumberStreetName" class="form-control" />
    <span asp-validation-for="NumberStreetName" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="Locality" class="control-label"></label>
    <input asp-for="Locality" id="AddressDetails_Locality" name="AddressDetails.Locality" class="form-control" />
    <span asp-validation-for="Locality" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="Town" class="control-label"></label>
    <input asp-for="Town" id="AddressDetails_Town" name="AddressDetails.Town" class="form-control" />
    <span asp-validation-for="Town" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="Postcode" class="control-label"></label>
    <input asp-for="Postcode" id="AddressDetails_Postcode" name="AddressDetails.Postcode" class="form-control" />
    <span asp-validation-for="Postcode" class="text-danger"></span>
</div>

allows it to properly map to the contoller model

public async Task<IActionResult> Create([Bind(include: "CareHomes, Name,AddressDetails, ContactInfo")] CareHomes careHomes)

I worked it out when I put the partial mark up in the main form and looked at the HTML markup and compared it to when it was a partial. I hope this helps someone else someday

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.