DarksideCookie

Come to the dark side...we have cookies!

Binding Views and ViewModels

[UPDATE] This post is somewhat interesting, but the idea doesn’t really work in the real world. More info HERE [/UPDATE]

I have tried to stay out of certain areas when I blog. Why? Well, simply because some things are still heavily debated, and some debates even seem a bit infected. Some topics seem to cause people to get somewhat rabid and I don’t feel like causing a big flame war of any kind. But still, I have decided to talk a bit about some of the options we have when binding our views to our viewmodels, or vice versa. I can’t even write that with out having to be careful…

Why? Well…once again it is the root of a lot of debate. Should we use view first, or viewmodel first. Basically, should the view create the viewmodel, or should the viewmodel create the view… Or should we perhaps have another object that creates both… As I said…there is still a lot of talk about this, but I still want to show some options. And I primarily want to show some options that work well with the tools we have, such as VS2010 and Blend 3.

Why is it important for us to build our viewmodels so that they work with the tools? Well, both Blend and VS have really helpful features that will cut the development time. This is a reason as good as any to get a view/viewmodel pattern that works with the tools.

There are already a bunch of ideas on how to do this, and also a couple of really sweet frameworks, such as MVVM Light, that have nice implementations for it. But I want to look at it without a framework for a couple of reasons. Mainly because I want to focus on how it can be done, and why I have chosen to do it the way I have…and yes…I will give you the source code to the solution that I have built. I really want some feedback about it though! I haven’t had time to try it out in a big project yet, but as soon as I find one I will.

But, before I get started, I want to show a couple of really simple solutions on how to get it working. Imagine the following Xaml

<UserControl ...>
<UserControl.DataContext>
<vms:MainViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot">
...
</Grid>
</UserControl>

This works fine. A new instance of MainViewModel will be created. But how do I get it to show data during design-time without adding a bunch of conditional statements in the VM? Not really a good way of doing it.

Ok…there is a solution to that…

<UserControl ...
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="vms:MainViewModelDesignTime"
mc:Ignorable="d">
<UserControl.DataContext>
<vms:MainViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot">
...
</Grid>
</UserControl>

Voila! By using d:DataContext I can define a different VM to use at design-time. But…doesn’t that mean a whole new set of code? A whole new set of code that needs to have exactly the same interface as the MainViewModel… Yep! So every time you add something to one of them to get the view to work, you need to add it to the other one as well…

I really don’t like that idea for a bunch of reasons. Mainly the maintenance of the whole thing… I really want to have the same VM during design-time and run-time. The VM should be fairly stupid anyway. It should preferably get all of its dependencies injected. So if I could inject one set of services at design-time and another at run-time I should be sweet. But isn’t that duplicate code again? Well, yes it is. But, we are probably going to create stubbed or mocked services for testing anyway… So it is a better solution according to me…

Ok…so how about I create a ViewModel locator? Well, a bunch of people have gone down that road, and I like it. By creating a class that is responsible for locating the VMs, I can just put an instance of that class in the application resources and reference it all over the application. Sweet! There is one little caveat though… I really want to have 2 different configurations. Hmm…ok…that can easily be solved by adding a sweet conditional looking at the DesignerProperties class. But I really want to be able to re-use the locator, so I don’t want to add the configuration inside that class. Ok…so how about configuring it in the application startup event in App.xaml.cs? Well…won’t work. The App.xaml will be parsed and handled at design-time, but the application will not be started and thus the code will not run. Hmm…ok…

So…what to do? Well, I started thinking about how I could fix it in a re-usable way. I wanted a ViewModel locator, that could have 2 different configurations and that would work in design-time. I also wanted/needed to be able to define it as much as possible in Xaml. The only thing I want to keep in code is the DI stuff, and that code had to be defined in a way that it would run at design-time. I also wanted to have my VMs defined in a way that makes full use of the tools available. Basically I wanted to be able to define bindings in a nice typed way with point and click. That is a lot of things…and a couple of sweet limitations…

And after that huge intro to the problem…let’s look at a way to fix it… I started by creating a new Silverlight Class Library project called ViewModelLocator. I started by adding a reference to Unity. As I mentioned before, I want to use DI and I like Unity. So it will be an integral part… Next, I created a new interface called IViewModelResolver. It is a very simple interface with a single method called ResolveViewModel

public interface IViewModelResolver
{
T ResolveViewModel<T>();
}

 

 

As you might have understood, any class implementing this interface will be responsible for resolving ViewModels based on type. I also added a class implementing this interface by using Unity. So I called it UnityViewModelResolver. It takes a UnityContainer as a parameter in the constructor and then uses it to resolve the types requested

public class UnityViewModelResolver : IViewModelResolver
{
IUnityContainer _container;

public UnityViewModelResolver(IUnityContainer container)
{
_container = container;
}

public T ResolveViewModel<T>()
{
return _container.Resolve<T>();
}
}

 

After I got this done, it was time to create the actual ViewModel locator. I made the class abstract. Why? Well, you will see a little later. But for now, all you need to know is that it will always be inherited.

It exposes a single instance property of type string, called DesignTimeViewModelResolverResourceName.

public abstract class ViewModelLocator
{
...

public string DesignTimeViewModelResolverResourceName
{
get;
set;
}
}

The most important part of the ViewModelLocator class however, is a protected, read-only property of type IViewModelResolver. It is backed by a member called _resolver. In the getter, it checks to see if _resolver has been set. If it hasn’t it needs to do so. But it does it in a slightly convoluted way. If the DesignTimeViewModelResolverResourceName has been set, it checks to see if the applications resource collection contains an item with that key. If it does, it casts that resource to an IViewModelResolver before assigning it to the _resolver variable. If it can’t find a resource, if just creates a new UnityViewModelResolver, passing in a new UnityContainer.

protected IViewModelResolver Resolver
{
get
{
if (_resolver == null)
{
if (!string.IsNullOrEmpty(DesignTimeViewModelResolverResourceName)
&& Application.Current.Resources[DesignTimeViewModelResolverResourceName] != null)
{
_resolver = Application.Current.Resources[DesignTimeViewModelResolverResourceName] as IViewModelResolver;
}
else
{
_resolver = new UnityViewModelResolver(new UnityContainer());
}
}
return _resolver;
}
}

But…is that it? No…I have one more method defined. It is called SetViewModelResolver. It takes a parameter of type IViewModelResolver, and stores it in the _resolver variable

public void SetViewModelResolver(IViewModelResolver vmResolver)
{
_resolver = vmResolver;
}

Ok…so what is so brilliant about this somewhat convoluted way of creating a ViewModelLocator? Well…first of all, I wouldn’t call it brilliant, but you will see what it does in a little while. But first I need to create some other stuff…

First, I need to create an assembly with the common stuff. In this case it will contain a single thing… So I create a new Silverlight Class Library called Common. In it, I create a single interface called IDataService. This will be the interface that defines the simple little service I will use to show of the power of the ViewModelLocator.

public interface IDataService
{
void GetData(Action<string> callback);
}

It has a single little method called GetData() and as with all my service interfaces it returns void. Instead, it returns its result using a callback of type Action<>. This makes it possible to create both asynchronous and synchronous implementations without making the call different. It also means that my VM can use the service without handling a bunch of events and stuff…

Next I I create a new Silverlight Class Library project called ViewModels. In that project, I create a single class called MainViewModel. It is probably the most stupid VM in a long time…but it will work for this demo… It takes an IDataService parameter in the constructor. It then uses that service to get some data that is placed in a property called WelcomeText. The VM also implements INotifyPropertyChanged

public class MainViewModel : INotifyPropertyChanged
{
string _welcomeText;

public MainViewModel(IDataService dataService)
{
dataService.GetData((str) => { WelcomeText = str; });
}

public string WelcomeText
{
get { return _welcomeText; }
set
{
if (value == _welcomeText)
return;

_welcomeText = value;
OnPropertyChanged("WelcomeText");
}
}

protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

A very much simplified VM. But it will do the job for this demo…

Next, we obviously need a service that implements the IDataService interface. So, I create another class library project called Services. Yes…there are a LOT of projects in this solution…I know… I add a reference to the Common project and then add a single class called DataService to the Services project. The DataService class obviously needs to implement the IDataService interface. It does so in the most contrived way possible.

public class DataService : IDataService
{
public void GetData(Action<string> callback)
{
callback("Hello World!!!");
}
}

 

Ok…so FINALLY I can get started on the actual Silverlight application. So I add a new Silverlight Application project, connecting it to a new ASP.NET Web Application. I could probably skip that and just use an auto generated host page…but I did so anyway…

In this project, I add a reference to the Common, ViewModels, ViewModelLocator and Services projects. I then add a class called ViewModelLocator, which I have inherit the abstract ViewModelLocator class I created previously. I define a single property in it. It is of type MainViewModel and is called MainViewModel. In the getter, I use the Resolver property from the base class to resolve an instance of MainViewModel.

public class ViewModelLocator : DarksideCookie.MVVM.ViewModelLocator.ViewModelLocator
{
public MainViewModel MainViewModel
{
get
{
return Resolver.ResolveViewModel<MainViewModel>();
}
}
}

 

Now you see why the ViewModelLocator was abstract. I will always be subclassed to add properties for the different ViewModels in the application.

Next, it is time to start using it. So I open the App.xaml file, and add an instance to the Resources element, making it available throughout the entire application.

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DarksideCookie.MVVM.Demo.Client.App"
xmlns:Utils="clr-namespace:DarksideCookie.MVVM.Demo.Client.Utils">
<Application.Resources>
<Utils:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>
</Application>

 

I also need to define the IViewModelResolver to use during run-time. So I open the App.xaml.cs file and locate the Application_Startup. Right before the code creates a new MainPage, I get hold of the ViewModelLocator from the resources, and call its SetViewModelResolver() method. I pass in a nicely configured UnityViewModelResolver that is created by passing in a pre-configured UnityContainer

private void Application_Startup(object sender, StartupEventArgs e)
{
((ViewModelLocator.ViewModelLocator)this.Resources["ViewModelLocator"]).SetViewModelResolver(new UnityViewModelResolver(GetContainer()));
this.RootVisual = new MainPage();
}

private IUnityContainer GetContainer()
{
UnityContainer container = new UnityContainer();
container.RegisterType<IDataService, Services.DataService>();
return container;
}

That’s sort of it. Just need to start using it. So I open the MainPage.xaml file, and since I use VS2010 I get a sweet interactive designer area. I select the UserControl element by simply placing the caret in that element. Next I open the Properties window and locate the DataContext property. And this is when is starts getting cool, cause I can now use the Properties window to decide what VM to use…

PropWindow

PropWindow2

This results in a Xaml file that looks something like this

<UserControl ...
DataContext="{Binding MainViewModel, Source={StaticResource ViewModelLocator}}">
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>

Awesome! I can use the IDE to its fullest… But how about hooking up an element as well. So I drag out a new TextBlock to the design area. I then set the HorizontalAlignment and VerticalAlignment to Center using the Properties window. Next I pull up the Text property in the Properties window, and click the little square icon and select “Apply Data Binding”

PropWindow3

After I have done that, I get access to the same data binding selector as I got previously. It it will show me the following

PropWindow4

 

Cool! And the design area will show a TextBlock with the text “Hello World!!!” just as one would expect in this case. That is all nice if the service is that simple, but if it starts calling webservices and things, it stops working that nicely. And to be honest, I have promised you design-time data that is different from run-time. So let’s get that working as well…

First I create another…yes ANOTHER…class library project that I call ServicesDesignTime. In that project, I add references to the Common and ViewModels projects as well as to Unity. I then create a class called DataService, which of course implements IDataService.

public class DataService : IDataService
{
public void GetData(Action<string> callback)
{
callback("Hello Cider!!!");
}
}

 

And yes…Cider is a codename for the designer and not only a drink… Now that I have that design-time service, I need to make sure that that is the service that is used at design-time. I do that by creating a new class called DesignTimeViewModelResolver in the same project. The DesignTimeViewModelResolver class needs to implement IViewModelResolver. It does so using Unity. But it also adds the design-time data service to the UnityContainer during construction.

public class DesignTimeViewModelResolver : DarksideCookie.MVVM.ViewModelLocator.IViewModelResolver
{
IUnityContainer _container;

public DesignTimeViewModelResolver()
{
_container = new UnityContainer();
_container.RegisterType<IDataService, DataService>();
}

public T ResolveViewModel<T>()
{
return _container.Resolve<T>();
}
}

 

Now that I have this stubbed service and a IViewModelResolver to use at design-time, it is time to start using it. So I open up the Silverlight Application project and add a reference to the newly created design-time project. I then open the App.xaml file and add an instance of the DesignTimeViewModelResolver to the ResourceDictionary. I name the new resource, and set the same name in the DesignTimeViewModelResolverResourceName property of the ViewModelLocator resource

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DarksideCookie.MVVM.Demo.Client.App"
xmlns:Utils="clr-namespace:DarksideCookie.MVVM.Demo.Client.Utils"
xmlns:DesignTime="clr-namespace:DarksideCookie.MVVM.Demo.Services.DesignTime.Resolver;assembly=DarksideCookie.MVVM.Demo.Services.DesignTime">
<Application.Resources>
<Utils:ViewModelLocator x:Key="ViewModelLocator" DesignTimeViewModelResolverResourceName="DesignTimeViewModelResolver" />
<DesignTime:DesignTimeViewModelResolver x:Key="DesignTimeViewModelResolver" />
</Application.Resources>
</Application>

If I compile the solution and then open the MainPage.xaml file, the design area all of the sudden looks like this

DesignArea

But…if I run the application…it looks like this

Browser

I would call that mission accomplished. If I need more VMs, I just create them and add properties for them in the ViewModelLocator subclass. If I need services injected, I just need to add a constructor parameter of the right interface type. If I need more services, I just create them and add a stubbed version to the design-time service project. Just remember to register the service in the design-time ViewModelResolver.

Ok…that was quite a LONG post about something that simple. And it included a LOT of projects…I know. Luckily the code is available for download. The download is however for VS2010. The code will run in VS2008 without problem, but you will have to “downgrade” it for it to open in VS2008. But…to be honest…it makes less sense using this in VS2008 as it doesn’t have a sweet, interactive design area. It should however work nicely in Blend as well…

I would really appreciate feedback! I know I am trying to solve something that has been solved in a bunch of other ways already. But I was curios. Do you have a better way? Have I done something stupid? I appreciate all feedback, but I would appreciate if we left view first vs. vm first discussion for other people to have. Especially since the vm first pattern is hard to get working in the tools…

Anyhow…here it is. Download: DarksideCookie.MVVM.zip (97.28 kb)

Cheers!

Posted: Apr 26 2010, 12:13 by ZeroKoll | Comments (1) |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Manage post: :)

Comments (1) -

Geoff Hirst United Kingdom said:

Hi, what you had done really interested me as I am currently fighting with viewmodels and locators. Had a problem with your code, first up the viewmodellocator project was set as startup, and that totally blew a fuse on build talking about some switch or other needed setting and how it would have worked in a previous version of VS as it would have been sandboxed. So then I decided to try setting the client as the startup and that didn't work either, complains about file testpage.html not found.
thanks for your entry anyhow, going to re-read and see if I can pull anything from it.

# October 13 2011, 17:16

Pingbacks and trackbacks (1)+

Comments are closed