Thursday, September 24, 2009

View-Model first data binding problem

I recently had a problem concerning data binding in silverlight and prism. When I used the view-first approach everything worked fine. When I used the view-model first approach nothing seemed to work.

public MyViewModel(IMyView view)
{
View = view;View.SetModel(this);
Use r = new User();
User.Username = "TestUser";
}


After long hours of head busting and searching for an answer, I found one. After creating a bare bones test app, I noticed a very enlightening error. But first let’s see how I got there. Well first all the typical PRISM stuff happens. Created shell, added the module to the module catalog programmatically, register the interfaces and implementations with the unity container, and then the interesting part:


rm.RegisterViewWithRegion(“TestRegion”,
() => Container.Resolve<IMyViewModel>().View);


The dependency injection container resolved the view model, which means it created an instance of the view model based on the interface you specified. But notice the method header below:


public MyViewModel(IMyView view) 


The dependency injection container (unity in this case, part of PRISM) detects in the method header that an instance of IMyView is needed and goes ahead and creates one, then injects it into the method as the view parameter. At this point, the data context for the view is not yet set, since the constructor does not take care of that (we created the SetModel method for that, which is in the view). So what did I do? I set the View property, and then set the data context of the view, and then I instantiate all my data objects. Frustratingly, this seemingly working code, does not work. Why…? Mind you we are taking the view-model first approach. On the flipside, the view first approach, things would look like this:


rm.RegisterViewWithRegion(“TestRegion”, typeof(IMyView));



public MyView(IMyViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}



Unity would resolve the IMyView interface, instantiating the view first, but then noticing the IMyViewModel in the method header would first instantiate the view model, and all it’s associated data objects, and second pass it as a parameter to the constructor of the view, and third, the data context is set in the constructor of the view. Well after realizing this, it’s not so difficult anymore, let’s compare the flow of code and see what the problem is.


View-Model first (broken)


  1. Resolve IMyViewModel
  2. Begin instantiation of MyViewModel
  3. Resolve IMyView from MyViewModel constructor.
  4. Instantiate MyView, then return.
  5. Set the MyView data context.
  6. Create data objects. 

View first (working)

  1. Resolve IMyView
  2. Begin instantiation of MyView.
  3. Resolve IMyViewModel from MyView constructor   
  4. Begin to instantiate MyViewModel
  5. Create data objects in MyViewModel, then return.
  6. Set the views data context, in the views constructor.


If you notice that the last to steps in the broken code are reversed. Then you guessed it! Calling the SetModel method after all the data objects we are binding to are created solves the problem.

No comments:

Post a Comment