Building HelloMEF – Part V – Refactoring to ViewModel

In the last post we migrated over to the new DeploymentCatalog. In this post we’ll look at refactoring the code to incorporate the MVVM pattern. Code from the last post is available here

Why ViewModel?

There’s a ton of content out there in the blogosphere that discuss the virtues of separated presentation patterns including MVVM. I am not going to reiterate those here, but I will point you at Josh Smith’s excellent MVVM article here. I’ll also shamelessly plug my own post where I shared my own view on the essence of MVVM (which is based on views of others i respect). In short it’s about decoupling UI logic for better maintainability which includes making it easier for a graphic designer to style the UI.

So where are the maintainability issues in the current “app”? Currently the main place is within MainPage.xaml.cs where you see the following code:

public void OnImportsSatisfied()
{
    TopWidgets.Items.Clear();
    BottomWidgets.Items.Clear();

    foreach (var widget in Widgets)
    {
        if (widget.Metadata.Location == WidgetLocation.Top)
            TopWidgets.Items.Add(widget.Value);
        else if (widget.Metadata.Location == WidgetLocation.Bottom)
            BottomWidgets.Items.Add(widget.Value);
    }
}

The logic is simple yes, but it still has issues.

  • The main issue is that it is mixing UI business logic with the rendering. The logic which determines which item goes to Top to Bottom is a completely separate concern from how those items actually get displayed. Having the logic mixed in means  the code is very brittle where any slight change to the way the UI is rendered can break it. It also makes it difficult for a graphic designer from being able to change the look and feel of how the widgets are rendered.
  • The second issue is that I am imperatively adding elements to the UI rather than letting the rich DataBinding engine do the work it was designed for. This makes it difficult to re-skin the UI.
  • Third, in it’s current form, the logic is difficult to test, meaning if the logic breaks I am relying on QA / manual testing to pick it up.

So ViewModel will fix all of this?

I say with confidence Yes! Using ViewModel will allow us to refactor the logic into a nice reusable and testable place. It will also gives us the decoupling we need to leverage the Silverlight rendering engine to it’s fullest. Along the way you will also see how MEF can help us with regards to implementing MVVM within our applications. We’ll introduce 2 view models to help us get there. When we’re done, our application will look like this.

image

 

Pre-requisites.

 

As with the last post, you’ll need our MEF codeplex bits for following along. If you start where we left off in the last post you’ll be fine. You’ll also need a copy of Laurent Bugnion’s awesome “Glenn Block approved” :-) MVVM Light, which you can get here and which we’ll be using for it’s ICommand default implementation.

Let the refactoring begin!

First we’ll go create our ViewModel. We’ll start off with an empty class. Go add a new MainPageViewModel.cs class to the HelloMEF project. Leave it empty for now. Now let’s refactor MainPage to have MEF deliver it’s ViewModel. There’s a lot of religous debate in the community around View First vs ViewModel First construction of the UI. I am going to use View first, period, and no that’s not open for debate :-)

Refactoring MainPageView

What we’re going to do is rip out the logic in MainPageView, and replace it with importing it’s ViewModel and setting it to the DataContext. Replace MainPageView.xaml.cs the following:

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        CompositionInitializer.SatisfyImports(this);
        this.DataContext = ViewModel;
    }

    [Import]
    public MainPageViewModel ViewModel { get; set; }
}

I think you’ll agree this code is a lot cleaner. My UI is now completely decoupled from any UI “logic”.

But WAIT, there IS code in the code behind, and the ViewModel police are probably on their way to my house. My answer when they show up is “it’s simple wiring logic, so WHAT!” That being said you can certainly look at ways to remove to right that small amount of code, but I am not losing any sleep over it. :-)

Notice I am not using constructor injection rather I am importing my ViewModel through a property. The reason is because I am allowing XAML to create my view rather than having MEF create it for me. SatisfyImports is then called to tell MEF to inject the view. It removes one level of mental gymnastics which in particular manifests itself when you start dealing with nested Views and ViewModels. If you’ve dealt with manually assembling Composite UIs / using regions, you know what I mean. It also allows this view to be more designer friendly as I can configure a design time VM to show up. That is for another post.

Fleshing out MainPage’s ViewModel

Now we’re go and finish the empty MainPageViewModel we created earlier. We’re not just going to move the code from the view into the model as we’d just be moving the problem. Instead we’re going to get rid of the imperative code and allow the view to render itself on the model through binding. We’ll still need to leverage MEF’s metadata and ensure that the widgets go in the right “place” wherever that happens to be. This raises a question as to how as previously there was a single collection of widgets that we imported into MainPage before and which we manually walked. The solution I chose was to create collection properties for each set of widgets. That allows the view to render it any way it wishes and decouples from the ItemsControls that were used previously.

Below is the code:

[Export]
public class MainPageViewModel : IPartImportsSatisfiedNotification, 
    INotifyPropertyChanged
{
    [ImportMany(AllowRecomposition = true)]
    public Lazy<UserControl, IWidgetMetadata>[] Widgets { get; set; }

    public IEnumerable<UserControl> TopWidgets { get; private set; }

    public IEnumerable<UserControl> BottomWidgets { get; private set; }

    public void OnImportsSatisfied()
    {
        TopWidgets = Widgets.Where(w => w.Metadata.Location == 
            WidgetLocation.Top).Select(i => i.Value);
        BottomWidgets = Widgets.Where(w => w.Metadata.Location == 
            WidgetLocation.Bottom).Select(i => i.Value);
        OnPropertyChanged("TopWidgets");
        OnPropertyChanged("BottomWidgets");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string property)
    {
        var handler = PropertyChanged;
        if (handler != null)
            PropertyChanged(this, new PropertyChangedEventArgs(
                property));

    }
}

MainPageViewModel implements IPartImportSatisfiedNotification as the view did previously. It also implements INotifyPropertyChanged in order to tell the view whenever it is recomposed. It exports itself in order to make itself available to be imported by the view. MainPageViewModel imports all the Widgets as did the view, but it also exposes two different properties TopWidgets and BottomWidgets. Whenever recompostion occurs, OnImportsSatisfied is called which updates these properties with their own filtered collections by using a LINQ query with the appropriate metadata filter. It then raises PropertyChanged in order to update the UI. This really shines through the power of metadata and metadata views in MEF!

Updating the bindings

The last thing we need to do to get the ViewModel wired is to update the view to bind the widget ItemControls to the appropriate properties. Replace the StackPanel in MainPage.xaml.

<StackPanel x:Name="LayoutRoot" Background="Black">
    <Border Background="Cyan">
        <ItemsControl x:Name="TopWidgets" 
            ItemsSource="{Binding TopWidgets}" Height="Auto" 
            FontSize="30">
        </ItemsControl>
    </Border>
    <Border Background="Yellow">
        <ItemsControl x:Name="BottomWidgets" 
            ItemsSource="{Binding BottomWidgets}" Height="Auto" 
            FontSize="30">
        </ItemsControl>
    </Border>
</StackPanel>

We’re almost there

Build and execute the app. You should see that everything works as before only now our new ViewModel is in place. You can now easily go and and reformat the UI without impacting the underlying ViewModel at all. We could stop here as we’ve already made a good leap in improving the maintenance of our app. We won’t though, there’s one more culprit we can refactor. Widget1.

What’s wrong with Widget1?

[ExportWidget(Location = WidgetLocation.Top)]
public partial class Widget1 : UserControl
{
    [Import]
    public IDeploymentCatalogService CatalogService { get; set; }

    public Widget1()
    {
        InitializeComponent();
        Button.Click += new RoutedEventHandler(Button_Click);
    }

    void Button_Click(object sender, RoutedEventArgs e)
    {
        CatalogService.AddXap("HelloMEF.Extensions.xap");
    }
}

Widget1 contains logic which calls the DeploymentCatalogService to download a XAP, it’s not horrible but it has some of the same problems as MainPage did.

  • It tightly couples the download logic to the UI. This means if you refactor the UI there is a decent chance it will break.
    • Downloading widgets is a completely non-UI concern which does not belong in the view.
    • It means a designer can’t change from using a button without modifying the code.
  • The logic is not reusable, you can’t reuse it across multiple views. This may or may not matter depending on the specific case.
  • As small as it is, it is hard to test. That means if someone breaks the code and it no longer works, the only way to pick it up is through QA/automated UI testing. We want to pick up as many bugs as we can up stream rather than waiting for them to be caught.

Refactoring Widget1 to use a ViewModel.

Widget1 can be refactored to use it’s own ViewModel. We can then refactor the logic out of the code behind and move it into the model leveraging Silverlight 4’s new commanding support, Laurent’s MVVM light library, and a little MEF to make it more maintainable.

We’ll do this again with the ViewFirst approach :-) This time we don’t need to use CompositionInitializer though as our ViewModel will get discovered by the catalog. Add a new Widget1ViewModel.cs file to the HelloMEF project. Second add a reference to GalaSoft.MVVM light (in your “ProgramFiles\Laurent Bugnion (Galasoft)\Mvvm Light Toolkit\Binaries\Silverlight” folder). Now paste in the following code to Widget1ViewModel.cs.

 

using System.ComponentModel.Composition;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;

namespace HelloMEF
{
    [Export]
    public class Widget1ViewModel
    {
        public Widget1ViewModel()
        {
            Download = new RelayCommand(() => 
                CatalogService.AddXap("HelloMEF.Extensions.xap"));
        }

        [Import]
        public IDeploymentCatalogService CatalogService { get; set; }

        public ICommand Download { get; private set; }
    }
}

 

Widget1ViewModel is an export similar to MainPageViewModel. It imports IDeploymentCatalogService in order to allow downloading XAPs. It then exposes a Download command which is implemented using MVVM Lights’ (contributed by Josh Smith) RelayCommand to download the extensions.

Updating Widget1View

Once we have that in place we can clean up the Widget1 view. Paste the following into Widget1.xaml.cs.

[ExportWidget(Location=WidgetLocation.Top)]
public partial class Widget1 : UserControl
{
    public Widget1()
    {
        InitializeComponent();
    }

    [Import]
    public Widget1ViewModel ViewModel
    {
        get{ return (Widget1ViewModel) this.DataContext;}
        set { this.DataContext = value;}
    }
}

First I removed the previous UI logic. Then I added an import of the MainPageViewModel and set it to the DataContext. Constructor injection was also an option through usage of our ImportingConstructor attribute. I chose not to for consistency and in order to not increase the concept count. Feel free to use either approach though.

Now that we’ve updated the code behind we can update the XAML to bind to the new DownloadCommand. Replace the Grid element in Widget1.xaml with the following.

<Grid x:Name="LayoutRoot" Height="Auto">
    <Button x:Name="Button" Content="Hello MEF!" 
        Command="{Binding Download}" Width="300" Height="100">
    </Button>
</Grid>

Run the app!

Build and run the application, press the top button and you should see a screen that is quite familiar by now :-)

image

We’re done.

What did we gain?

You might be sitting there thinking wow that was fun, but what did it buy me? Here’s a quite summary:’

  • MainPage and Widget1 are completely decoupled from the UI logic.
  • MainPageViewModel and Widget1ViewModel are easily testable.

Both of which translate into less pain for us as our app evolves.

How did MEF help?

MEF enabled us to put the decoupled pieces back together. To be fair, we could have used other mechanisms. However MEF offered a nice solution in the box, which was convenient as our app was already using it :-) MEF also provided us CompositionInitializer which made it very easy to compose our UI parts without having to jump through a lot of hoops.

What’s next?

In this post I mentioned testing several times though notice I did not show ANY tests. That was deliberate :-) The reason is because I wanted you to see the value of ViewModel beyond the simple testability aspects, I wanted you to focus on how it improved our code whether we were testing it our not. In the next post we’ll look at how we can test it.

As always, code is attached.

This entry was posted in HelloMEF, MEF, silverlight, SL, SL4, ViewModel. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • FB

    Hello Glen, very nice article series!

    I have an additional question regarding MEF modules error handling. I would like to know what approach should I take, from the SL host application perspective, regarding unhandled user exceptions that may rise from loaded MEF modules. If I consider 3rd party development of a modules for my application, this is an obvious problem. I tested a sample in which a module throws an exception after loading and it was caught in the SL host app as an Application_UnhandledException. A quick watch on the ApplicationUnhandledExceptionEventArgs shows no relevant information regarding its origin.

    T

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob

    One other thing, the reasn why the dependencies on MainPageViewModel were properties was to support recomposition. We don’t support recomposition on ctor params. There is a way to get there using a wrapper, but I didn’t want to go there.

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob

    After stewing on this more and chatting with Laurent. I realize that having the ViewModel exposing user controls rather than IWidget was not the best move. I also realize how I got there as I startted off having everything in th code-behind rather with no VM. Also I wanted to show that you did need to create a new abstraction as a contract if there was an existing one.

    In my follow up post I’ll certainly refactor to use an IWidget. It makes very good sense as it removes the unnecessary UI dependency on the VM, something which is bad bad bad.

    Thanks as always.
    Glenn

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob, after further investigation I realize why I did not go with constructor injection on MainPageViewModel. Constructor imports are not recomposable, and I am relying on recomposition.

    Now I did do a blog post a while ago on supporting recomposition through CI with a wrapper. (http://codebetter.com/blogs/glenn.block/archive/2009/03/21/recomposition-and-constructors.aspx)
    I guess I could consider using that approach.

  • http://andyonwpf.blogspot.com Andy Wilkinson

    I agree with your comments entirely Glenn. I don’t know either of any way to provide notifications of changes to indexer elements. In my case I’m not supporting recomposition (although it’s something I’ll definately do in the near future) so I’m taking the hit of invalidating the whole UI each time.

    I have a similar issue with binding to indexers elsewhere in my app where I bind to file metadata, and which metadata can be defined in the XAML…

    e.g. {Binding File.Metadata[FileSize]}

    To avoid the problem with change notifications I add an extra class between so that the result is,

    e.g. {Binding File.Metadata[FileSize].Value}

    In this case the value the indexer returns never changes, and I raise all changes on the ‘Value’ property of the this child object.

    In fact, this is exactly what you can do with ObservableCollection too…

    public ObservableCollection AllWidgets[string regionName]

    Here the ‘AllWidgets’ indexer creates and caches a new ObservableCollection each time a new regionName is used, and all changes are then done on this single ObservableCollection – problem solved! ;-)

    NB: Regarding ObservableCollections and syncronising changes, I’m having great success with using the Reactive Framework (Rx) to do this – my code here is quite buggy at the moment to be fair, but you can set up a binding with filters and transformations etc. at initialization, then the syncs are all performed automatically from then on!

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Davide

    Thanks! It’s in good company with your posts. On using CompositionInitializer at the app level there are many places where it makes sense not to do that. The downside of using it at the app-level is it pushes the concern up a notch. The View implementer may want to have it’s imports composed without worrying about the app having to do work.

    In particular this manifests itself in nested UI element scenarios. Imagine I am a custom button that gets a VM which uses a custom service. I am dropped somewhere deep in the XAML hieararchy within a custom user control. I don’t want the host to have to know that I have imports as that’s an implementation detail. This is really evident when we look at the third-party control using MEF scenario.

    In this particular case doing it at the App level is fine. I think the determiner is whether or not the thing needing to be composed is replacable or not. If it is then use the App to import it, if it is not, then use CI to compose it.

    The decision is yours.

  • http://www.davidezordan.net/blog Davide Zordan

    Great article Glenn,
    awesome approach for MVVM with MEF, I’ve used it recently and I find it elegant and extensible. I agree with you for the usage of CompositionInitializer at the application level :)

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob

    There’ s a grey area for sure :-) In general I don’t see controllers as bindable entities, rather they observe and coordinate. It’s arguable though.

    I’l ponder on this more though.

  • http://www.devlicio.us Rob

    Well, if it *is* a View Model, it’s a very funky View Model indeed ;) Especially considering that its primary role is to host and organize Views. In fact, it is a bit like a data-bindable region manager… Perhaps it’s not a SC, but I’m not sure that classifying it as a VM is the proper description. It certainly exhibits the MVVM mindset, but I’m still not sure I would call it a VM if I encountered it “in the wild.” Regardless, good post. Looking forward to the next installment.

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    You are right, using CI will improve testability signficantly. I started off with CI, but reverted back to props for consistency. You make very good arguments though for why that should move back to CI so I will do that. It’s looking like I definitely will do a follow up post with the flurry of suggestions. As a side not this is what happens when you DON’T (which I didn’t) write tests first :-)

    Thanks!
    Glenn

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    One refactoring I am considering after watching Jeremy Liknesses video is to not use CompositionInitializer on MainPage and instead use CI on the application level. Advantage of this is MainPage has an export and is discovered in the catalog. In it’s current form the MainPage itself is not pluggable, which is reasonable in some circumstances.

    Thoughts on whether I should do that in a follow up post?

    Glenn

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Josh

    ObservableCollection is one thing I did think about. We have used it combined with Recomposition in other samples.

    The reason I avoided it in this case relates to ordering. Often when folks use metadata they apply ordering schemes. If both properties were ObservableCollections I am not sure how I could address ordering changes when new things appear.

    Now granted I am not doing any sort of ordering in this example, so using ObservableCollection works fine :-)

    The other challenge with Observable collection relates to synchronizing the changes. Each time recomposition occurs, I would need to track manually exactly what changed so that I could update the collection (i.e. do the appropriate Add and Remove). I might attempt it for a follow up post though, or better yet…YOU can do it :-)

    Thanks
    Glenn

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob

    I respectfully disagree that MainPageViewModel is not a model. Having a ViewModel that references UI elements does not in itself violate any tenents fo ViewModel that I know of.

    It’s a ViewModel because the primary way the view communicates with it is through binding, nor is the ViewModel coupled to the view. It is exposing other view elements as a collection yes. However whether they implement IWidget or not is besides the point.

    Glenn

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rudi, @Bill thanks that means a lot.

    @Andy I like the dynamicity of that approach. One caveat on that is that when the underlying collection changes, the UI requeries all “regions”. AFAIK there is no way to throw a collection changed notification with an indexer value. In the current implementation although I am firing notifications for both collections, I don’t have to, I can be more fine grained or even use ObservableCollection as Josh mentioned.

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    @Rob

    You mean have the widgets implement IWidget? I didn’t do that deliberately in order to illustarte you “could” reuse existing types as contracts.

    However, I have no issue with using IWidget if that is what you mean. I’ll do a follow up mini-post with IWidget :-)

    On using the VM Yes I could have (and I did think of that), but then I need to have all my user controls have view models, or I need datatempltes / special plumbing. I wanted to keep it simple.

    Thanks
    Glenn

  • http://www.devlicio.us Rob

    Just to be clear, I have nothing against Supervising Controller. Probably every WPF/Silverlight project I have done has some mixture of Supervising Controller with MVVM. But, you should at least remove the UserControl reference and replace it with an interface. That would be the right way to handle that pattern.

    Of coarse, you could also replace the UserControl with a WidgetViewModel, then your MainPageViewModel would actually be a ViewModel, but now you would be doing Model-First ;)

  • http://www.devlicio.us Rob

    Glenn, a few thoughts on this…

    As you know, I favor a Model-First approach, but that is not my complaint here….and I don’t have any problem with the code-behind either.

    The problem is that your MainPageViewModel, IMHO, is *not* actually a View Model. It is a Supervising Controller. Your Widget1ViewModel *is* a ViewModel though. The difference? MainPageViewModel references Views. In ViewModel, there is no explicit View synchronization, data binding handles that. In Supervising Controller, there is a combination of explicit synchronization and data binding, which is what you have here. In fact, what you have is something that looks a lot more like a bad ScreenCollection/ScreenConductor implementation (no offense, you know I love ya). Your forthcoming unit tests will prove this to you because you will have to instantiate a UserControl in order to test the functionality of your MainPageViewModel.

    The second problem I have is also with the design of MainPageViewModel. If you take a normal approach to testing your VM by instantiating the SUT and inspecting the TopWidgets, you will have a NRE. If you instantiate, set the Widgets property, then inspect the TopWidgets, you will still have an NRE. You have to know to Instantiate, Populate Widgets, and manually call OnImportsSatisfied, in that order, before you can actually access the getter of TopWidgets. Why not just use constructor injection and avoid all that?

  • http://joshsmithonwpf.wordpress.com Josh Smith

    Well done! This series is great. Another interesting thing to show, in my opinion, is how recomposition could work in conjunction with an ObservableCollection.

  • hafasa

    Thank you for sharing Glenn. One question, I just need to know if it is just me or what. When I click the button the first time, great Widget3 gets loaded. However, if I click a second time the browser goes blank. I’m using Firefox 3.6.
    Thanks.

  • http://andyonwpf.blogspot.com AndyWilkinson

    Nice introduction to MEF/MVVM Glenn. One trick I like to use for inserting widgets into different regions is to bind to an indexer through XAML. So, rather than having,

    public IEnumerable TopWidgets
    public IEnumerable
    BottomWidgets

    [ExportWidget(Location=WidgetLocation.Top)]

    you can have what is effectively (NB: Not real code, you have to play about a bit with an extra class with an indexer to get the property how we want),

    public IEnumerable AllWidgets[string regionName]

    [ExportWidget(Location="Top")]

    Now you can bind to this in the MainPage.xaml as,

    ItemsSource=”{Binding AllWidgets[Top]}”

    The main advantage here is that the designer can easily add new regions into the XAML without any changes required to the MainPageViewModel code (in my case I actually use MEF to import a selection of views to apply, so a third-party can add a view with a new region that imports their widgets – all on top of the same view-model).

    Andy

  • Bill Campbell

    Wow – this Rocks! What an excellent example. I hope you know how much all your hard work helps the dev community. Regards!

  • http://www.rudigrobler.net Rudi Grobler

    WOW, Glenn…

    Very nice!

  • http://codebetter.com/members/gblock/default.aspx Glenn Block

    Thanks Sacha!

  • http://sachabarber.net sacha barber

    Nice post Glen, loving the MEF man