Simple ViewModel Locator for MVVM: The Patients Have Left the Asylum | John Papa

John Papa

Evangelist on the loose

Simple ViewModel Locator for MVVM: The Patients Have Left the Asylum

posted by John with 47 comments

I’ve been toying with some ideas for MVVM lately. Along the way I have been dragging some friends like Glenn Block and Ward Bell along for the ride. Now, normally its not so bad, but when I get an idea in my head to challenge everything I can be “interesting” to work with :) . These guys are great and I highly encourage you all to get your own personal Glenn and Ward bobble head dolls for your home.

But back to MVVM … I’ve been exploring the world of View first again. The idea is simple: the View is created, and it creates the ViewModel in the View’s XAML. Very simple to do when you create the ViewModel as a static resource in the View’s XAMl, however it gets a little more complicated when you want to see the ViewModel bind at design time in Cider or Blend. This is important … I wanted to have the View create itself first, then the ViewModel, and have design time data. One option is to use a ViewModel locator  (using the service locator pattern). This is a fine option and is used by MVVM Light Toolkit (a nice light MVVM framework). The thing I did not like was how much maintenance was involve din the ViewModel locator. So out came my “challenge everything I know” trait.

I started talking to Glenn Block about how to use MEF to make the ViewModel locator completely generic. I wanted to take it out of the picture and make it dead simple. So I enticed Glenn with a few encouraging remarks and we were off to a pair programming spree for a little over an hour. What we came up with is in this blog post, warts and all. The source can be retrieved from the bottom of the post.

Here are the steps (after you reference the DLL with the ViewModelLocator):

  1. Create the ViewModelLocator in App.xaml as a static resource
  2. Export your View Model using the custom attribute (this tells the locator about it using metadata)
  3. Set the DataContext of your View to the ViewModeLocator and bind to the specific ViewModel using an indexer

 

STEP 1: Create the ViewModelLocator in App.xaml as a Static Resource

So what I really wanted was to be able to easily crank up ViewModels. I’ll start with how to use the ViewModelLocator. It is in its own dll so there is no need to touch its code (unless you want to). So here is what I have to do once in my app. This code creates the ViewModelLocator object in the App.xaml.

<Application.Resources>

    <MVVMLib:ViewModelLocator x:Key="VMLocator"/>

    <MVVMLib:IndexerConverter x:Key="VMIndexerConverter" />

</Application.Resources>

Notice I create a converter too. This is only here because there appears to be a known bug in the Silverlight 4 RC when you bind to a string indexer. The bug is that it actually binds 6 times. The workaround is to use a converter. So once this bug is fixed, this converter is no longer needed.

STEP 2: Export Your View Model Using the Custom Attribute

Below I have a ViewModel (a rather boring one) where the only additional thing I do is Export it using a string as the key/identifier. This tells MEF which View Model it is.

[ExportViewModel("MainPageViewModel")]

public class MainPageViewModel : ViewModelBase 

{

    private string _companyName;

    public string CompanyName

    {

        get { return "Contoso, Inc."; }

        set

        {

            _companyName = value;

            FirePropertyChanged("CompanyName");

        }

    }

 

}

STEP 3: Set the DataContext of your View to the ViewModeLocator and Bind to the Specific ViewModel Using an Indexer

The final step is to set the DataContext of the View to the ViewModelLocator instance and tell it which View Model to use.

DataContext="{Binding 

    Source={StaticResource VMLocator}, 

    Converter={StaticResource VMIndexerConverter}, 

    ConverterParameter=MainPageViewModel}"

Again, this code is slightly different than I’d like because of the possible string indexer binding bug I mentioned earlier. If the binding worked once (not 6 times) then I could have just done this and skipped the converter. The code sample below shows how much simpler it would be to bind to the indexer directly without the converter. Much much nicer.

DataContext="{Binding 

    Source={StaticResource VMLocator}, 

    Path=Find[MainPageViewModel]}"

So that is it! You can still use Blend in a number of ways. One of which is to use the code like this if you prefer.

d:DataContext="{d:DesignInstance 

    Type=VMLocatorDemo:MainPageViewModelDesign, 

    IsDesignTimeCreatable=True}"

What if I Have Lots of Views and ViewModels?

Good question. This is where I really like it. You just repeat steps 2 and 3. Export the ViewModel, and bind to it from the View. Very very simple.

Anything Else I Need to Know?

If you want to create a new instance of a ViewModel each time, you can do that.You can also create 1 ViewModel singleton and reuse it each time. With the code we wrote you just call the Find indexer for creating instances each time or call the FindShared for the Singleton.

Where is the Code?

You can grab the source for the ViewModelLocator that Glenn and I wrote tonight right here. I also included a sample demo so you can try it out. The intent is to see what is possible and how we can make the locator very easy and maintainable. We will be refactoring this and seeing if we can make it simpler or cleaner. We will also be looking into the string indexer options so we can get rid of the workaround with the converter.

But please take this code and leave your comments on it. We are eager to hear your thoughts!

tags: Silverlight

47 comments Hide comments

Anonymous on said:

I like it a lot, and it’s so simple, really. Wouldn’t it be possible to configure the shared/singleton setting as a property of the locator instead of exposing different methods… just thinking that if you want to change your approach, you have to change potentially in many places.

Anonymous on said:

So that’s why you guys put property indexers in :) I like it too, possible to use all the goodies MEF provides, including taking care of injecting params into constructor, etc.

Anonymous on said:

MEF is awesome and this is a definite improvement on the normal service locator.
How would you recommend using it in a multi xap modular application where the modules don’t have an app.xaml?

Anonymous on said:

Can I wire this up in Blend without dropping into code? If that is not possible is it possible to use a Behavior that I can drag and drop on the page to wire any of this up? I admit this is strait forward (once the ’6′ bug is fixed’).

Anonymous on said:

We’ve been using a convention-based approach for binding to the viewmodel. We don’t have ever have to set the data context explicitly. It’s pretty much what Rob did in his talk. Though we are handling design-time in the same way you illustrated.

Anonymous on said:

**
These guys are great and I highly encourage you all to get your own personal Glenn and Ward bobble head dolls for your home.
**
You’re not allowed to write that without providing links to where we can purchase them :)

Anonymous on said:

Great stuff. As my head wobbled I dreamt up a simpler alternative and emailed you about. It will compile so you know it works.

Anonymous on said:

I like this. The more we can keep our code loosely coupled, the less spaghetti code we create. It certainly is a step in the right direction. I think we can take this concept even further…
Honestly, what I’d like to see most is no decoration on the VM and no specific name call-out in the View.
Instead, I’d prefer to take a convention-based approach. Something where the View just sets an attached property like VMLocator:Locate.ViewModel="true".
Then, if your view is named MainPage, the attached behavior will search for MainPageVIewModel (based on the namve of the view), instantiate it, and attach the data context.
I might spike this out later this week. I am pretty sure it can be done.

Anonymous on said:

This is a very nice approach. I for one am a big fan of using indexers for binding dynamically from XAML (I’ll try to blog about my method for attaching VMs into regions of a shell using indexers and MEF at some point).
Another approach that might be of interest is the inclusion of support for binding to dynamic objects in WPF 4.0 (not sure of SL4?). This would mean that you could replace binding to indexers with binding to dynamic properties instead, making the XAML cleaner (no square brackets!), and also allowing property change notification to work. I’ve not tried this yet, but it’s something I’m going to look into in the future. ;)

Anonymous on said:

JP – This appears to change how the Screen Conductor you and one of your bobbleheads blogged about last year may be a bit less involved in screen creation. Great stuff!

Anonymous on said:

Brian Genisio, you can eliminate the attributes on the view model if you use the [InheritedExport] on the viewmodelbase, and it will import all the view models that derive from it.

Anonymous on said:

Rick Ratayczak: I’m new to MEF, so forgive any newbness. Applying the [InheritedExport] attribute to ViewModelBase (and removing the [ExportViewModel] attribute from the MainPageViewModel results in a ‘Sequence contains no matching element’ exception in the GetViewModel method in the ViewModelIndexer class. At least I was able to move the View and ViewModel into their own subdirectories :-) . Getting the ViewModelBase to export globally, and the the introduction of an anticipated fix to the Indexer workaround gives this approach 4 stars for single-XAP apps IMHO. I love Laurent’s work but absolutely hate the multiple viewmodel references in his ViewModelLocator approach. Oh, and thanks for a great post, John :-) !

Anonymous on said:

Hi guys. I am envious…but help me be like you! I am using VS 2010 RC, SL4 and EntityFramework, and i am looking to access records from my EntityModel
and show them in a datagrid and do some event actions on the datagrid too. LOB Application!
a simple Sample that uses Northwind Customers & Order tables on skydrive at: cid-5e33c7b2ca3aa2a9.skydrive.live.com/…/MVVMLightToolKi
It gives me some errors on load and i think i am missing how to use observable collections.

Anonymous on said:

Great job guys, the only problem I have found so far is if you attempt to Import/Export objects that are not ViewModels. When I do this, I get a Sequence contains no matching element error in the GetViewModel() method. Any ideas how to get around this?
Thanks,
Bob

Anonymous on said:

Hi,
I am new to MEF and conceptually I like this idea, however, I have a few questions. According to a blog post by Nicholas Blumhardt when using a PartCreator (ExportFactory) you need to clean up instances that it creates with Dispose method. What would you consider as the best way of doing this given that the goal is almost complete seperation of the view from the ViewModel? Would you have something on the Views unload event that disposes the ViewModel?

Anonymous on said:

Hi. Why doesnt it work with Navigation Template. Di i need to reference another thing!
Here is what i did.
#1.Created a Navigation Application.
#2. Added the MVVMLib.dll to the App project.
#3. Created a folder ViewModels and created HomeVM. Added the code like above cut with the required namespace.
#4 Reference the Locator and Indexer in the Application.Resources.
#4 instanciated the ViewModels namespace in Home.xaml and added the datacontext and it is in the demo.
#5. At this point, i can see my design time data. but when i run the application, i dont see my runtime data in the textbox.
Has anyone tried this on a NavApplication, does it work?
Earlier i also got a problem because i am using Glen’s MEFContentLoader to load and navigate my pages. So when i added the Export[HomeVM] to export the ViewModel. The result wasan exception indicatig that it wouldnt locate my "/Home" page. when i remove the export attribute off my page, it worked fine.
Someone please enlighten me on these issues.
thanks

Anonymous on said:

Hello, thanks for your great work!
I tried it with the new Silverlight 4 release (RC2) and the "6 times binding bug" is always here…
If you have a solution it cool be great because it’s really better to write directly:
DataContext="{Binding Source={StaticResource VMLocator}, Path=Find[MainPageViewModel]}"

Anonymous on said:

I love it. Thanks for being a smart nerd and geek and posting this. It came at the perfect time for me. :)

Anonymous on said:

Hi, thanks a lot for this nice way to bind view and viewmodel
I’d like to ask a general question about the view first approach. When you have a bigger App with a lot of screens. How would you change the screen (view) in a View-First-Approach? I had a look at the source of bubbleburst, but I guess with a high number of screens its not the right way to declare them all on the MainView (as with the GameOverView).
Unfortunately I don’t have a better idea.
Thanks for an answer
Mark

Anonymous on said:

What are the symptoms of the "6 times binding" bug? I have tried my xaml with both approaches and they both work, but I don’t know if the bug is still there and I’m not seeing it. I’m using VS2010 Premium and Silverlight 4 SDK both official releases.

RavindraGoud on said:

it s really working gud,Is it possible to import viewmodel,from code behind , i need to access viewmodel,but i tried using singelton,not working due to instances are different

help on said:

is it possible to get the viewmodel locator working in conjunction with mvvm light messenger?

Darrin on said:

This is always my issue with the locators. They never show an example where you need to pass in parameters to the constructor. At least in my experience I have not seen an example. Please let me know if you see one. Thanks.

Mark on said:

John,
I am having an issue in a Silverlight 5 project using this pattern for VM locator. What I see happening about 0% of the time when I instantiate the first ViewModel by loading a page is the in the ViewModelIndexer class the member ViewModelsLazy is null when it is accessed in GetViewModel(). It is as if the SatisfyImports() somehow failed. But it is totally inconsistent.

Nishit on said:

For caching VMs, its one thing to support name based VM loading via singleton vs. recreation, but what about caching for ‘MDI’ type apps with tabs. For example, a list of tasks that each have VM instances of the same TaskVM class that need caching as users switch between task views.

Naye on said:

Hi John,

I came across this article because I was looking for a way to have multiple view use the same ViewModel

How do I use the code below in a non-silverlight application.

ExportFactory
CompositionInitializer

Do not exist in WPF 4

Thanks

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;

namespace MVVMLib
{
public class ViewModelIndexer
{
public ViewModelIndexer()
{
CompositionInitializer.SatisfyImports(this);
IsShared = false;
}

[ImportMany("ViewModel", AllowRecomposition = true)]
public IEnumerable<Lazy> ViewModelsLazy { get; set; }

[ImportMany("ViewModel", AllowRecomposition = true)]
public IEnumerable<ExportFactory> ViewModelsFactories { get; set; }

private object GetViewModel(string viewModel)
{
//MessageBox.Show(“dude”);
//Debug.WriteLine(“just used me again”);

if (IsShared)
{
return ViewModelsLazy.Single(v => v.Metadata.Name.Equals(viewModel)).Value;
}

var context = ViewModelsFactories.Single(v => v.Metadata.Name.Equals(viewModel)).CreateExport();
return context.Value;
}

public bool IsShared { get; set; }

public object this[string viewModel]
{
get { return GetViewModel(viewModel); }
}

}
}

Leave a Reply


(required)


(required)



*