I'm new to .NET all together, please be patient with me if I have any silly mistakes.
I'm using ASP.NET MVC 3 with .NET 4.0
I want to have a "Create" view for a model that has a child Model. This view should include the child model's partial "Create" view, I'll use the following simple example for illustration purposes:
The Person model
class Person
{
    public string Name { get; set; }
    public Address { get; set; }
}
The Address model
class Address
{
    public string City { get; set; }
    public string Zip { get; set; }
    //A List for creating a <select/> item in the view
    //containing cities fetched from the database.
    //The initialization is done in the controller action returning
    //the related partial view.
    public IEnumerable<SelectListItem> CityDropDown { get; set; } )
}
The Controller Actions
    class MyController : Controller
    {
        public ViewResult Create()
        {
            var person = new Person();
            var address = new Address();
            // initialization of address.CityDropDown omitted
            person.Address = address;
            return View(MyViews.CreatePersonView, person);
        }
        [HttpPost]
        public ViewResult Create(Person person)
        {
            //persistance logic
        }
    }
The views hierarchy I want to have :

The solutions that I have tried in order to achieve this are the following :
@Html.Partial(..) or @{Html.RenderPartial(..)}
The Person view
@model Person
@using(Html.BeginForm()){
    @Html.EditorFor(m=>m.Name)
    @Html.Partial(MyViews.AddressPartialView, @Model.Address)
}
The Address partial view
@model Address
@Html.EditorFor(m=>m.Zip)
@Html.DropDownListFor(m=>m.City, @Model.CityDropDown)
When submitting the form, person.Address is null. After a bit of searching on Google, I found out that in order for the submit of the address field to work, the generated HTML markup must be the following (notice the Address_ prefix) :
<form...>
    <input type=text id="Name" />
    <input type=text id="Address_Zip" />
    <select id="Address_City">
        <!-- options... -->
    </select>
</form>
Needless to say, the generated HTML markup in my case isn't the same but instead it's the following (the Address_ prefix is missing) :
<form...>
    <input type=text id="Name" />
    <input type=text id="Zip" />
    <select id="City">
        <!-- options... -->
    </select>
</form>
I moved the Address partial view to the folder View/Shared/EditorTemplates assuring that it has the same name as the Address property in the Person model, i.e Address.cshtml.
The Person view
@model Person
@using(Html.BeginForm()){
    @Html.EditorFor(m=>m.Name)
    @Html.EditorFor(@Model.Address) //will automatically find the Address 
                             //partial view in the EditorTemplates folder
}
Using this approach the generated markup has in fact the proper prefix (i.e. Address_), but I get an Object reference not set to an instance exception for the Address.CityDropDown property which tells me that the pre-initialised Address object in the controller's action isn't passed to the partial view for some reason.
This approach works with no problems, but I don't want to use it as I don't want to have redundant code if I ever want to have a create view for address in another model.
What should I do in order to have a reusable partial create view that I can use accross my application?
You had the correct approach with EditorTemplates, but keep in mind you need to populate the CityDropDown. So, the view should be handed off something like:
Person model = new Person()
{
    Address = new Address
    {
        CityDropDown = new SelectListItem[]{
            new SelectListItem { Selected = true, Text = "Select one..." },
            new SelectListItem { Text = "Anywhere", Value = "Anywhere" },
            new SelectListItem { Text = "Somewhere", Value = "Somewhere" },
            new SelectListItem { Text = "Nowhere", Value = "Nowhere" }
        }
    }
};
Which would then make this view only consist of:
@Html.EditorForModel()
And then your EditorTemplates would pick up from there:
~/Views/shared/EditorTemplates/Address.cshtml (Note: this is based on type not property name)
@model MvcApplication.Models.Address
@Html.DropDownListFor(x => x.City, Model.CityDropDown)
@Html.EditorFor(x => x.Zip)
~/Views/Shared/EditorTemplates/Person.cshtml
@model MvcApplication.Models.Person
@using (Html.BeginForm())
{ 
    @Html.EditorFor(x => x.Name)
    @Html.EditorFor(x => x.Address)
    <input type="submit" value="Save" />
}
the three views then render something like:
<form action="/" method="post">
  <input class="text-box single-line" id="Name" name="Name" type="text" value="" />
  <select id="Address_City" name="Address.City">
    <option selected="selected">Select one...</option>
    <option value="Anywhere">Anywhere</option>
    <option value="Somewhere">Somewhere</option>
    <option value="Nowhere">Nowhere</option>
  </select>
  <input class="text-box single-line" id="Address_Zip" name="Address.Zip" type="text" value="" />
  <input type="submit" value="Save" />
Example project can be found here: https://github.com/bchristie/StackOverflow-Examples/tree/master/questions-19247958
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With