asp.net MVC: embedded object editing
-
05-07-2019 - |
Question
I'd like to embed an instance of object A inside object B. I have already an action and an editing view which renders a form for object B. I made it a strongly typed partial view accepting B.
I am dealing with the Create action now, so I do b = new B(); b.A = new A();
Now I'd render the form for B, and then call the partial view for A, passing it b.A.
But what I get back is either a FormCollection, or my new A object with the B field set to null. In the first case all is well, but what will I do if the form fails to falidate? Do I need to create the objects manually with the wrong data and pass them again with an invalid ModelState? Is the second option ever possible?
Or do I just need to avoid having the nested view? I thought, as an alternative, to create a special model object just to handle the form with all the values for both A and B, and then when this form would validate I'd populate manually the A and B objects and save them... is this the only solution?
Solution
You should be able to use A and B as you've described.
Suppose we have the following:
public class B {
public A A {get; set;}
public string X {get; set;}
public int Y {get;set;}
}
public class A {
public string Z {get; set;}
}
//then in your controller:
public ActionResult Edit () {
return View (
new B {
A = new A { Z = "AyyZee" } ,
X = "BeeEcks",
Y = 7
} );
}
So your model is an instance of B.
Your view and your nested partial view should produce HTML something like this:
<input type="text" name="A.Z" value="AyyZee" />
<input type="text" name="X" value="BeeEcks" />
<input type="text" name="Y" value="7" />
Now the default model binder should be able to hook this up:
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Edit (B input) {
// apply changes
//the binder should have populated input.A
}
Note that this only works if both A and B have a default constructor and are relatively simple classes. If you have something more complex you can use your own binder:
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Edit ( [ModelBinder( typeof( BBinder ) )] B input) {
//...
}
public class BBinder : IModelBinder
{
public object BindModel( ControllerContext controllerContext, ModelBindingContext bindingContext )
{
return
new B {
A = new A { Z = Request["A.Z"] } ,
X = Request["X"],
Y = int.Parse(Request["Y"])
};
}
}
OTHER TIPS
create your own custom model that incorporates A and B, then create a view from that model when you submit your form you will simply be able to update your custom model and update/add your individual models.
public class CustomViewModel
{
public ModelA myAModel {get;set;}
public ModelB mybModel {get;set;}
}
a view for that model will create a form that incorporates A and B and will enable you to then your posted formcollection can then be used to set values for each indivdual model and update/create then seperate.
My problems with this code were caused by two things, both in the model class:
- The fields must be properties, and not normal fields
- The constructor was missing that would initialize the inner objects
So the classes from the solution above should be:
public class B {
public A a {get; set;}
public string x {get; set;}
public int y {get;set;}
public B() {
a = new A();
}
}
public class A {
public string z {get; set;}
public A() {}
}