How I Dynamically Load a ViewModel from the Web

Tuesday, January 12, 2010 / Posted by Luke Puplett /

For my vuPlan client I wanted to avoid those 'update me' messages that Adobe products are good at annoying me with. My aim was to be able to tweak my application logic, perhaps fix bugs, and do it unobtrusively. I also wanted to maintain a typical execution context of an ordinary locally installed application. To be clear, I've not fully investigated the options open to me with ClickOnce, and recently I became a user of Seesmic Desktop which is the first time I've seen it used in the wild. But even if you have different design goals, here's how I do dynamic MVVM.

Objective

To download a compiled ViewModel class from the web at runtime and inject it into a local client application so that it hooks into the view just like a normal View-ViewModel does. Also to allow the last known good ViewModel to load when the web version is unavailable.

This article is not a complete code breakdown of the method, rather a 10,000ft overview with a few details. The concept is straight-forward enough for most OO programmers to get.

Overview

Dunno about you but I'm a visual learner and find graphics and diagrams more palatable than words alone. This schematic should compress most of what would otherwise be a long and arduous blog post into 1080x673 (if you look at the full size one I stuck on flickaarrrr).

Diagram of ViewModel class relationships

Each of the black-rimmed screen looking objects are ViewModel classes. The inheritence tree runs right to left and so rooted with ViewModel.cs.

This class is the base for all and every view model I use thoughtout my application and is very similar to the base VM Josh Smith defines in his excellent MSDN article on the Model-View-ViewModel pattern.

The only addition to Josh's base ViewModel worth mentioning is that I add a property (typed as object) which I used to store a reference to the Dispatcher for the UI control to which the ViewModel will be bound.

The coloured blocks define the code distribution in the sense of partitioning over assemblies. (A) is the actual WPF application executable/project and would reside on the client PC once deployed, (B) is the dynamically loaded assembly containing business logic and (C) is a reusable shared library.

The assemblies reference each other like so:

  • Assembly A references
    • The shared library C
  • Assembly B references
    • The shared library C
  • Assembly C references
    • Nothing other than the usual

The Shared Client .dll (C)

Two classes are defined in here. The abstract base ViewModel, as mentioned above, and an abstract base MainViewModel class. This latter class defines all the properties that the WPF view will bind to, but contains no other logic.

It also defines the ICommand properties into which behaviour and logic will be placed. This is an example of an ICommand to hook up to a Login button (in MainViewModel.cs):

public ICommand LoginCommand
{
    get
    {
        if (_loginCommand == null)
        {
            _loginCommand = new RelayCommand(param => this.Login(param), param => this.CanLogin(param));
            //_loginCommand = new RelayCommand(param => this.Login(param));
        }
        return _loginCommand;
    }
}
RelayCommand _loginCommand;
protected virtual void Login(object obj) { throw new NotImplementedException(); }
protected virtual bool CanLogin(object obj) { throw new NotImplementedException(); }

The RelayCommand comes straight from Josh Smith's article mentioned above. The key to it are the two virtual methods which will be overridden in the implementation assembly.

N.B. It may be possible to skip this class as I think WPF bindings are loosely coupled and not checked at build time. The XAML for my view simply contains lines like {Binding Path=Username} (for the username textbox) and so it stands to reason that any object instance could be set as the DataContext and binding/reflection will just work where it works and fail where it fails - although don't quote me on that.

I also define delegates for some actions such as closing the window or application which allows me to shift the implementation of that logic to where I know how I want it to work (in the application UI itself).

The Implementation Logic .dll (B)

I call this class MainImplViewModel and it contains an InitializeComponent method which in many ways is the main initializer for the application - remember that the UI assembly (A) is supposed to be 'dumb'.

For my app, it assigns some delegates and default property values. It also does things like checking if the internet is connected and altering property values which can affect whether various UI controls are active as well as loading stuff in from IsolatedStorage.

As mentioned above, I have a reference to the Dispatcher for the UI and so I also define utility methods for marshalling work onto that thread and my application also has a synchronisation process, so there's a private BackgroundWorker variable and event handlers there, too.

And of course, I override the methods that my RelayCommand objects rely on (in MainImplViewModel):

protected override bool CanLogin(object obj) { ... }
protected override void Login(object obj) { ... }

Within which is the actual implementation which can trigger UI changes by modifying the properties, starting my BackgroundWorker and all manner of stuff.

The Application UI XAML .exe

The job of the pared-down application code is to i) check we're online or that the local cache is available (Isolated Storage), ii) download the implementation assembly (B) from a URL and iii) load it.

Once loaded, the types defined in the implementation assembly are scanned and the first class of type MainViewModel that is found is instantiated and assigned to a local variable within the application as well as the DataContext of the main window.

I have written a pretty complex background component loader/initializer class but the main thrust of what its doing is simple; download the file from a website using an ordinary http get, then load the assembly from the raw bytes with Assembly.Load(bytes).

After this is done, it fires an event which continues the ViewModel load process and is handled like so (in app.xaml.cs):

void Initializer_AfterComponentLoaded(object sender, TypeLoadEventArgs e)
{
    if (e.Instance.GetType() == typeof(ResourceDictionary))
    {
        Application.Current.Resources = (ResourceDictionary)e.Instance;

        this.MainWindow = (Window)LoadComponent(new Uri(@"MainWindow.xaml", UriKind.Relative));
        this.MainWindow.Show();
    }
    else if (e.Instance.GetType() == typeof(System.Reflection.Assembly))
    {                
        UI.MainViewModel mainVm;
        if (this.Initializer.AssemblyLoader.TryLoadType<UI.MainViewModel>(out mainVm))
        {
            this.MainWindowViewModel = mainVm;
            this.MainWindow.DataContext = mainVm; // DataContext set to view model.
            
            mainVm.ShutdownApplicationAction = new Action<int>(ShutdownVuplan);
            mainVm.ScrollIntoViewAction = new Action(delegate
            {
                //var listBox = ((Window1)this.MainWindow).MainListBox;
                //if (listBox.Items.Count > 0)
                //    listBox.ScrollIntoView(listBox.Items.GetItemAt(listBox.Items.Count - 1));
            });
            mainVm.Dispatcher = this.MainWindow.Dispatcher;
            mainVm.ParentControl = this.MainWindow;

            this.SessionEnding += new SessionEndingCancelEventHandler(App_SessionEnding);

            mainVm.SyncStatusMessages.Add(Client.UI.StringHelper.TryGetWinFxResourceString("Status_Message_Initializing"));
            mainVm.InitializeComponent(this.CloudStatus); // Warning: Go To Definition will jump to base impl.
        }
        else
        {
            if (this.Initializer.AssemblyLoader.TypeLoadException != null)
                throw this.Initializer.AssemblyLoader.TypeLoadException;
        }
    }
}

I like to leave all my code around so you can get an idea of the other stuff I do. For example, my loader also loads a resource dictionary from the web, based on location – this is a subject for a different blog post, but you can see that I roll my own localization this way. The method above checks what type of object was loaded and if its an Assembly (the implementation assembly) it hooks it up.

What’s most important here is that I set MainWindowViewModel to point to the view model and then set the DataContext of my main window. Further down, I call the all important InitializeComponent and so any state changes to the view model made during this method will be reflected in the view.

To load a type from the implementation assembly I use a generic method TryLoadType<T>

public bool TryLoadType<T>(out T instance) where T : class
{
    this.TypeLoadException = null;
    instance = null;
    try
    {
        if (this.Assembly == null)
            throw new InvalidOperationException(UI.StringHelper.TryGetResourceString("ErrorMessages", "ErrorVitalReferenceIsNull"));

        foreach (Type t in this.Assembly.GetTypes()) // Exceptions here probably due to assembly dependencies of wrong version (remote assembly not latest build).
        {
            if (typeof(T).IsAssignableFrom(t))
            {
                instance = (T)this.Assembly.CreateInstance(t.FullName);
                // this.OnLoaded(new TypeLoadEventArgs(instance));}
            }
        }
    }
    catch (Exception e)
    {
        // Could throw any of 6 exceptions.
        //
        this.TypeLoadException = e;
    }
    return (instance != null);
}

The important bit here is the foreach loop that looks for the type specified in T. MainImplViewModel is derived from MainViewModel so the calling code only need know of the MainViewModel type and it will load the class with all the implementation logic.

The caller (in this case, the method above this one) can also set properties on the view model because it knows of the base type – this is why I have a MainViewModel (recall that I mentioned that in theory it could be excluded).

Conclusion

I've not the time to simplify my example code above, I've just ripped out lumps of code from my projects, thus making it more complicated and excluding things that may have made it more readable, sorry, but I think the general idea can be grasped by most programmers au fait with MVVM.

Using this technique, I can keep my business logic in an implementation assembly, fix bugs in it, and then publish the .dll file to a web server and my clients will be updated invisibly.

Which reminds me: remember to add an XCOPY command to the post build actions for the implementation DLL which copies the assembly to your web server. Easily forgotton - you'll wonder why your breakpoints aren't being hit.

Labels: , ,

0 comments:

Post a Comment