Reputation: 89
I'm a newbie learning MVC and your help would be much appreciated. I have two models, Firms and Address. I'm trying to have a simple form where someone can add Firm details.
When the Firm Form is loaded, an Editor template containing Address model is Rendered. The problem I'm having is a straight forward one. I've even seen solutions to it on stack overflow but in my case it wont work (strange). When I'm passing new Innuendo.Models.AddressModel()
to the Address Template, during Postback the ModelState.IsValid
is false and Model.Address
is still set to null. What am I doing wrong?
Firm View
@model Innuendo.Models.FirmModel
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>FirmModel</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div>
@Html.Partial("~/Views/shared/EditorTemplates/_Address.cshtml", new Innuendo.Models.AddressModel())
</div>
<div class="editor-label">
@Html.LabelFor(model => model.LogoPath)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.LogoPath)
@Html.ValidationMessageFor(model => model.LogoPath)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Editor Template _Address
@model Innuendo.Models.AddressModel
<div class="editor-label">
@Html.LabelFor(model => model.HouseName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.HouseName)
@Html.ValidationMessageFor(model => model.HouseName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Street)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Street)
@Html.ValidationMessageFor(model => model.Street)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Town)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Town)
@Html.ValidationMessageFor(model => model.Town)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.County)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.County)
@Html.ValidationMessageFor(model => model.County)
</div>
Firm Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(FirmModel firmmodel)
{
if (ModelState.IsValid)
{
firmmodel.FirmId = Guid.NewGuid();
db.Firms.Add(firmmodel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(firmmodel);
}
Firms Model
[Table("Firms")]
public class FirmModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid FirmId { get; set; }
[Display(Name = "Firm Name")]
[Required]
[StringLength(250)]
public string Name { get; set; }
[Required]
public virtual AddressModel Address { get; set; }
[StringLength(250)]
public string LogoPath { get; set; }
}
Address Model
[Table("Addresses")]
public class AddressModel
{
[Key]
[HiddenInput(DisplayValue = false)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid AddressId { get; set; }
[Required]
[Display(Name = "House Name/Number")]
[StringLength(250)]
public string HouseName { get; set; }
[StringLength(250)]
[Required]
public string Street { get; set; }
[StringLength(250)]
[Required]
public string Town { get; set; }
[StringLength(250)]
[Required]
public string County { get; set; }
}
Upvotes: 0
Views: 1074
Reputation: 14741
You are using editor template as a partial view therefor MVC generates wrong ids for input
s and model binder fails to bind address's properties. To workaround first rename your _Address.cshtml
to Address.cshtml
to match with type name. Second replace
@Html.Partial("~/Views/shared/EditorTemplates/_Address.cshtml", new Innuendo.Models.AddressModel())
with
@Html.EditorFor(model=>model.Address)
Or if your view name dose not match with type name. You could explicitly set template name also:
@Html.EditorFor(model=>model.Address,"_Address")
As you can see you don't need to write full path of your templates or view since MVC knows where to find them.
And if AddressId
generates model state error, and you don't want use a view model without AddressId
you could write a custom model binder to inject new GUID while binding.
public class AddressBinder:DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(Guid))
((Address)bindingContext.Model).ID = Guid.NewGuid();
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
And register your model binder in Global.asax.cs
protected void Application_Start()
{
// other codes
ModelBinders.Binders.Add(typeof(Address), new AddressBinder());
}
Upvotes: 1