OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


Easily decouple your MVVM ViewModel from your Model using RX Extensions

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





Comments are closed.
Showing 9 Comments
Avatar  guest 7 years ago

wow thank you ^^

Avatar  Adrian Hara 7 years ago

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...

Avatar  Michael Washington 7 years ago

@Adrian Hara - When I debug, the web service is called only after the subscription, and since I am using an &quot;Anonymous Method&quot; to handle the return, I don't see how there could ever be a problem.&lt;br&gt;&lt;br&gt;However, I did create this thread: &lt;a href=&quot;http://social.msdn.microsoft.com/Forums/en-US/rx/thread/6c1de56f-89b5-4f1c-889b-daeaf25df1d3&quot; rel=&quot;nofollow&quot;&gt;social.msdn.microsoft.com/Forums/en-US/rx/thread/6c1de56f-89b5-4f1c-889b-daeaf25df1d3&lt;/a&gt;

Avatar  Adrian Hara 7 years ago

Ah i forgot, if i change the original code to this:&lt;br&gt;&lt;br&gt; var observable = WebSites.SearchWebsites(SearchString);&lt;br&gt; &lt;br&gt; observable.Subscribe(p =&gt;...&lt;br&gt;&lt;br&gt;...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?

Avatar  Adrian Hara 7 years ago

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:&lt;br&gt;&lt;br&gt; class Dummy&lt;br&gt; {&lt;br&gt; public event EventHandler SomeEvent;&lt;br&gt;&lt;br&gt; public void DoAsync()&lt;br&gt; {&lt;br&gt; Task.Factory.StartNew(() =&gt; { if (SomeEvent != null) SomeEvent(this, EventArgs.Empty); });&lt;br&gt; }&lt;br&gt; }&lt;br&gt;&lt;br&gt; class Program&lt;br&gt; {&lt;br&gt; static void Main(string<> args)&lt;br&gt; {&lt;br&gt; var d = new Dummy();&lt;br&gt;&lt;br&gt; var observable = Observable.FromEvent(d, &quot;SomeEvent&quot;);&lt;br&gt; d.DoAsync();&lt;br&gt; Thread.Sleep(1000);&lt;br&gt; observable.Subscribe(p =&gt; Console.WriteLine(&quot;event handler&quot;));&lt;br&gt;&lt;br&gt; Console.WriteLine(&quot;done&quot;);&lt;br&gt; Console.ReadLine();&lt;br&gt; }&lt;br&gt; }&lt;br&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;At least that's what I think happens...

Avatar  Michael Washington 7 years ago

@Adrian Hara - If you put a breakpoint inside the method that &quot;creates&quot; 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:&lt;br&gt;&lt;br&gt;1) Hey I want to subscribe to this &quot;Observable&quot;&lt;br&gt;2) Oh hey someone is subscribed, I will let them know if something happens&lt;br&gt;&lt;br&gt;(in this case the &quot;Event&quot; 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)&lt;br&gt;&lt;br&gt;3) Then inside the method that returns the Observable there is code that says &quot;SearchWebsitesAsync&quot; and that eventually raises the Event &lt;br&gt;&lt;br&gt;(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)&lt;br&gt;&lt;br&gt;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 &quot;potentially&quot; run.

Avatar  Adrian Hara 7 years ago

Hmm, I'm not sure I understand what you mean by &quot;the subscription will not fire until someone subscribes to it&quot;. first impression was that in the time between creating an IObservable from an event and actually subscribing to it any events fired are &quot;lots&quot;, i.e. the observable doesn't &quot;store&quot; the events so it can forward them to the observer &quot;later&quot;. That's why I thought your example wouldn't work.&lt;br&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;Am I on to something here or talking complete rubbish? :)

Avatar  Michael Washington 7 years ago

@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.

Avatar  Adrian Hara 7 years ago

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.