views:

179

answers:

3

Hi, 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?

+1  A: 

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"])
            };
    }
}
Keith
I can't get this to work. Maybe I just made a simple mistake somewhere. Will try again on monday, maybe I'll have a fresher view :D Thanks a lot for suggestion.
Palantir
There's a lot more detail on binders here: http://weblogs.asp.net/scottgu/archive/2008/10/16/asp-net-mvc-beta-released.aspx
Keith
+1  A: 

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.

minus4
A: 

My problems with this code were caused by two things, both in the model class:

  1. The fields must be properties, and not normal fields
  2. 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() {}
 }
Palantir