Thursday, May 17, 2012    
Blog  

OpenLight Blog

Easily decouple your MVVM ViewModel from your Model using RX Extensions

Apr 25

Written by:
4/25/2010 1:11 PM  RssIcon

image

With “Simplified MVVM” you can simply place your web service methods in your Model. The problem you run into, is how do you make an asynchronous web service call and fill a collection in your ViewModel? One method I have employed in the past is to pass an instance of the ViewModel to the Model, however, the problem this causes, is that you have now tightly coupled your ViewModel and your Model. It is also difficult to consume your Model from multiple ViewModels when you do it this way.

What you really want to do, is place your web service methods in your Model and simply call them from your ViewModel. RX Extensions allow you to do that.

First you want to download RX Extensions and install them from here: http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

image

Next, add references in your Silverlight project to:

  • System.CoreEx
  • System.Observable
  • System.Reactive

For the sample project, this is the web service method in the Model:

    #region SearchWebsites
    public static IObservable<IEvent<SearchWebsitesCompletedEventArgs>> 
        SearchWebsites(string SearchString)
    {
        // Set up web service call
        WebsiteServiceClient objWebsiteServiceClient =
            new WebsiteServiceClient();
        // Get the base address of the website that launched the Silverlight Application
        EndpointAddress MyEndpointAddress = new
            EndpointAddress(GetBaseAddress());
        // Set that address
        objWebsiteServiceClient.Endpoint.Address = MyEndpointAddress;
        // Set up a Rx Observable that can be consumed by the ViewModel
        IObservable<IEvent<SearchWebsitesCompletedEventArgs>> observable =
            Observable.FromEvent<SearchWebsitesCompletedEventArgs>(objWebsiteServiceClient, 
            "SearchWebsitesCompleted");
        objWebsiteServiceClient.SearchWebsitesAsync(SearchString);
        return observable;
    }
    #endregion
    // Utility
    #region GetBaseAddress
    private static string GetBaseAddress()
    {
        string strXapFile = @"/ClientBin/SimpleRxExample.xap";
        string strBaseWebAddress =
            App.Current.Host.Source.AbsoluteUri.Replace(strXapFile, "");
        return string.Format(@"{0}/{1}", strBaseWebAddress, @"WebsiteService.svc");
    }
    #endregion

Notice the web service method returns IObservable. This allows you to “subscribe” to the results from the ViewModel like this:

    #region SearchWebsites
    private void SearchWebsites(string SearchString)
    {
        // Clear the current Websites
        colWebsites.Clear();
        // Call the Model to get the collection of Websites
        WebSites.SearchWebsites(SearchString).Subscribe(p =>
        {
            if (p.EventArgs.Error == null)
            {
                // loop thru each item
                foreach (var Website in p.EventArgs.Result)
                {
                    // Add to the colWebsites collection
                    colWebsites.Add(Website);
                }
                // If we have any Websites, 
                // set the selected item value to the first one
                if (colWebsites.Count > 0)
                {
                    SetWebSite(colWebsites[0]);
                    SelectedWebsiteInListProperty = 0;
                }
                else
                {
                    // Set blank default values
                    SelectedWebsiteProperty = new Websites();
                    SelectedWebsiteInListProperty = -1;
                }
            }
        });
    }
    #endregion

You are now able to easily call the web service in the Model from multiple ViewModels. You can see the live example here:

http://silverlight.adefwebserver.com/simplerxexample/

You can download the full source code here:

http://silverlight.adefwebserver.com/simplerxexample/SimpleRxExample.zip

9 comment(s) so far...


Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

I'm probably missing something, but can't the async call return before the subscription is made? In this case you're basically subscribing too late and won't be notified about the result. I know it's very unlikely that it WILL return before the subscription method is called, but it's not impossible.

By Adrian Hara on   6/22/2010 4:04 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

@Adrian Hara - The subscription will not fire until someone subscribes to it. You can download the code and set a break point. You will notice nothing happens until the Search button is clicked.

By Michael Washington on   6/22/2010 4:07 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

Hmm, I'm not sure I understand what you mean by "the subscription will not fire until someone subscribes to it". first impression was that in the time between creating an IObservable from an event and actually subscribing to it any events fired are "lots", i.e. the observable doesn't "store" the events so it can forward them to the observer "later". That's why I thought your example wouldn't work.

Now I took the code and it actually works, but I suspect this has something to do with the fact how Silverlight actually makes the webservice call, i.e. even though the name of the method is XXAsync, which might lead you to believe that it fires it on a thread right away, I think it's actually using a message loop to for the invoke, which means that it will only actually be invoked after the current stack frame is gone, which means the .Subscribe() call will already have been made, hence it will work.

Am I on to something here or talking complete rubbish? :)

By Adrian Hara on   6/23/2010 12:36 PM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

@Adrian Hara - If you put a breakpoint inside the method that "creates" the observable (not the one subscribing to it), you will see that it only gets fired when it is called by the code that subscribes to it. So it's like:

1) Hey I want to subscribe to this "Observable"
2) Oh hey someone is subscribed, I will let them know if something happens

(in this case the "Event" is when the web service method COMPLETES, We could have used another event such as when the web service starts or when it simply has a record to pass to us)

3) Then inside the method that returns the Observable there is code that says "SearchWebsitesAsync" and that eventually raises the Event

(this is the confusing part because this COULD have been raised outside the method and some would say that is the proper place for it so you would not have to re-subscibe each time. I argue that it is a layer of abstraction that is hard to follow and could be MORE code)

But, #2 and #3 did not happen until the code that subscribed to it was actually called. Until then, the Observable code was only code that could "potentially" run.

By Michael Washington on   6/23/2010 12:46 PM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

Again I'm not sure I follow, but take this example: if inside your SearchWebSites method, instead of creating an Observable from the webservice client you create it for a random class that has a DoXAsync() method which it runs on another thread and which raises a XAsyncCompleted event when it's done, then it's possible that the code in the Subscribe() lambda will never get called. Example below:

class Dummy
{
public event EventHandler SomeEvent;

public void DoAsync()
{
Task.Factory.StartNew(() => { if (SomeEvent != null) SomeEvent(this, EventArgs.Empty); });
}
}

class Program
{
static void Main(string[] args)
{
var d = new Dummy();

var observable = Observable.FromEvent(d, "SomeEvent");
d.DoAsync();
Thread.Sleep(1000);
observable.Subscribe(p => Console.WriteLine("event handler"));

Console.WriteLine("done");
Console.ReadLine();
}
}

However, in the SL case, since the webservice call is actually done using the message pump, the call won't happen immediately, which gives your .Subscribe() call a chance to run.

At least that's what I think happens...

By Adrian Hara on   6/24/2010 4:18 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

Ah i forgot, if i change the original code to this:

var observable = WebSites.SearchWebsites(SearchString);

observable.Subscribe(p =>...

...then the code which creates the observable runs BEFORE the call to Subscribe(). At least for me it does, maybe I don't have the correct version of the RX framework and this behavior changed in the meantime?

By Adrian Hara on   6/24/2010 4:18 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

@Adrian Hara - When I debug, the web service is called only after the subscription, and since I am using an "Anonymous Method" to handle the return, I don't see how there could ever be a problem.

However, I did create this thread: social.msdn.microsoft.com/Forums/en-US/rx/thread/6c1de56f-89b5-4f1c-889b-daeaf25df1d3

By Michael Washington on   6/24/2010 4:58 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

Right, the webservice is called only after because of how the queuing mechanism using the Dispatcher works in Silverlight. However, should this mechanism change in the future (which is unlikely :p), I guess subtle bugs could appear...

By Adrian Hara on   6/25/2010 4:03 AM
Gravatar

Re: Easily decouple your MVVM ViewModel from your Model using RX Extensions

wow thank you ^^

By guest on   8/4/2010 4:09 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
  
Copyright 2009 by OpenLightGroup.net   |  Privacy Statement  |  Terms Of Use