Click here to Skip to main content
12,510,189 members (52,244 online)
Click here to Skip to main content
Add your own
alternative version

Stats

14.5K views
546 downloads
16 bookmarked
Posted

Silverlight Cairngorm – Updates for Silverlight 4 and Visual Studio 2010

, 21 Dec 2009 CDDL
Rate this:
Please Sign up or sign in to vote.
Silverlight Cairngorm update for Silverlight 4 and Visual Studio 2010.

SilverlightCairngorm_src

Introduction

Since the initial release of Silverlight Cairngorm, it has been successfully used in several Silverlight 2, 3 projects and games developed in Visual Studio 2008 SP1 as an extra-light alternative to Prism. Now that both Silverlight 4 Beta and Visual Studio 2010 Beta 2 are available, and Silverlight 4 has a long list of new features, it's time for an update to accommodate some new features and feedbacks with the new development environment.

The theme for this update is completeness and ease of use while leveraging some new features available in Silverlight 4 Beta. The primary new functionality is the addition of ServiceLocator, the new CairngormDelegate and CairngormCommand abstract base classes that wire up the common tasks for interacting with Web Services, and the support for callbacks in CairngormEvent and CairngormCommand.

The companion demo project has all the sample code that tests all the new features.

Getting Started

There is no change in the sample project's functionality, it has the same visuals and functionalities as before. It simply allows the end user type in a term to search for matching pictures from Flickr; when the selected item changes in the search result list box, the corresponding picture shows up in the right pane.

What's really changed in this update is the sample project code that leverages the new features in Silverlight Cairngorm 4: registers Flickr services with ServiceLocator, uses the abstract CairngormDelegate class to locate the registered service, and also utilizes the new abstract CairngormCommand and CairngormEvent to call back the View's operations when the service response or an error comes back. The support for callback in CairngormEvent and CairngormCommand allows the concrete implementation of CairngormCommand to focus on the application logic and the operations on the application data model, no need to have any code or reference to the View, including message boxes.

The sample project is built with Visual Studio 2010 Beta 2, and Silverlight Tools for Visual Studio 2010 is also required to load the project. Optionally, it would help your evaluation work with the updated Silverlight Toolkit for Silverlight 4.

When the sample project loads into Visual Studio, all Silverlight Cairngorm 4 source code is located in the SilverlightCairngorm library project. Now, let's take a closer look at the new abstract class ServiceLocator under the Business folder.

ServiceLocator in Silverlight Cairngorm 4

The IServiceLocator interface and the ServiceLocator class were defined in the original Cairngorm framework, and widely used together with the service definition MXML in Flex RIA projects. But the implementation was left out in the initial release of Silverlight Cairngorm, mainly because Silverlight 2 and 3 lack of authentication support for WebClients. Now, with network authentication support in Silverlight 4, it's convenient to implement all methods defined in the IServiceLocator interface regarding service credentials and user credentials. Fig. 1 is the network authentication related method implementation in the ServiceLocator abstract class.

Fig. 1. Network authentication related methods in ServiceLocator:
/// <summary>
/// Set the credentials to use later.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
public void setCredentials(String username, String password)
{
    if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
        return;

    if (null == UserCredential)
        UserCredential = new NetworkCredential();

    UserCredential.UserName = username;
    UserCredential.Password = password;
}

/// <summary>
/// Set the remote credentials for all registered services.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
public void setRemoteCredentials(String username, String password)
{
    if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
    {
        //tell all the registered service to use browser network stack
        WebRequest.RegisterPrefix("http://", 
                  System.Net.Browser.WebRequestCreator.BrowserHttp);
        _netCredential = null;
    }
    else
    {
        if (null == _netCredential)
        {
            //tell all the registered service to use client network
            //stack in order to leverage network credential introduced in SL4
            WebRequest.RegisterPrefix("http://", 
                System.Net.Browser.WebRequestCreator.ClientHttp);
        }

        // NetworkCredential passing is available in ClientHttp networking stack in SL4
        _netCredential = new NetworkCredential(username, password);
    }

    foreach (KeyValuePair<string,> oneSvePair in _httpServices)
    {
        var oneSve = oneSvePair.Value;
        oneSve.Credentials = _netCredential;
        oneSve.UseDefaultCredentials = (null != _netCredential) ? false : true;
        // must be set to false if providing your own credentials
    }

}

/// <summary>
/// Logs the user out of all registered services.
/// </summary>
public void logout()
{
    setRemoteCredentials(null, null);
}

In addition to managing service credentials, another important responsibility of the ServiceLocator is to register all services to enable CairngormDelegate to locate the specified service instance by name. In Silverlight Cairngorm, we use a WebClient as a service reference. Fig. 2 shows all service registration and location related methods in the ServiceLocator.

Fig. 2. Service register and locate via WebClient in ServiceLocator
/// <summary>
/// WebClient service dictionary
/// </summary>
private Dictionary<string,> _httpServices = new Dictionary<string,>();

/// <summary>
/// register a service
/// </summary>
/// <param name="serviceName" /><param>
/// <param name="serviceClient" /></param>
public void addHTTPService(string serviceName, WebClient serviceClient)
{
    _httpServices.Add(serviceName, serviceClient);
}

/// <summary>
/// Un-register a service
/// </summary>
/// <param name="serviceName" /></param>
public void removeHTTPService(string serviceName)
{
    if (_httpServices.ContainsKey(serviceName))
        _httpServices.Remove(serviceName);
}

/// <summary>
/// Return the HTTPService for the given name.
/// </summary>
/// <param name="name" /></param>
/// <returns></returns>
public WebClient getHTTPService(String name)
{
    if (_httpServices.ContainsKey(name))
        return _httpServices[name];
    return null;
}

With the addition of ServiceLocator, we have completed all the major parts in the Cairngorm framework. Thanks to network authentication support in Silverlight 4 that makes this completeness a reality. Now that we have the completed ServiceLocator, how easy is it to use it in an application? Fig. 3 shows the entire class code in the sample project (SLCairngorm2010); you can tell how easy or difficult it is:

Fig.3  Application implements concrete ServiceLocator as a singleton:
namespace SLCairngorm2010.Business
{
    public class SilverPhotoService : ServiceLocator
    {
        public const string FLICKR_SEV_NAME = "FlickRService";
        
        private static readonly SilverPhotoService _instance = new SilverPhotoService();

        /// <summary>
        /// Return the single instance of business services
        /// </summary>
        /// <returns></returns>
        public static SilverPhotoService Instance { get { return _instance; } }

        /// <summary>
        /// Private constructor
        /// </summary>
        private SilverPhotoService()
        {
            //instantiate and register the HTTP service
            base.addHTTPService(FLICKR_SEV_NAME, new WebClient());
        }
    }
}

When the number of services grows, application just has more lines of code in the constructor to call base.addHTTPService, simply to register each service. With the new ServiceLocator available in the framework, how does CairngormDelegate locate the specified service and call it easily? Let's take a look at this.

Updated IResponder Interface and Enhanced Abstract CairngormDelegate

The IResponder interface is implemented by CairngormCommand and invoked by the CairngormDelegate instance to support a simplified programming model for asynchronous service calls. It's a simple interface with only two methods defined; the methods signature is updated in this version based on the fact that most Web Service payload formats are in text (XML, JSON, etc.) and some extended work I had to do in Flex based RIAs. Shown below is the new interface definition:

Fig. 4 IResponder interface
namespace SilverlightCairngorm.Command
{
    public interface IResponder
    {
        void onResult(string result);
        void onFault(Exception error);
    }
}

With the new interface definition, the abstract CairngormDelegate class can encapsulate common tasks that are needed by all concrete CairngormDelegate instances, like calling the right method on a WebClient, hooking up the right events, and implementing event handlers, etc. Again, based on the fact that most Web Service response content types are in text, both GET and POST for string based web operations are implemented in the abstract class. The application's concrete implementation doesn't need to worry about which method of the WebClient to call - as long as it only uses GET or POST strings back and forth. Fig. 5 has all the details.

Fig. 5 The abstract CairngormDelegate has implementations for string based web GET/POST operations:
namespace SilverlightCairngorm.Business
{
    public abstract class CairngormDelegate
    {
        protected IResponder responder;
        /// <summary>
        /// A Constructor, Recieving an IResponder instance, 
        /// used as "Callback" for the delegate's results -
        /// through its OnResult and OnFault methods.
        /// </summary>
        /// <param name="responder"></param>
        protected CairngormDelegate(IResponder responder)
        {
            this.responder = responder;
        }

        /// <summary>
        /// subclass needs to locate the httpSvc via
        /// ServiceLocator before calling any other method
        /// or, exception will throw 
        /// </summary>
        public WebClient httpSvc { get; set; }

        private DownloadStringCompletedEventHandler getAsyncHandler = null;
        private UploadStringCompletedEventHandler postAsyncHandler = null;

        protected void getAsync(string url, bool noCache = false)
        {
            string randomStr = noCache ? "?noCache=" + 
                    (new Random()).ToString() : "";
            string svcUrl = url + randomStr;

            if (null == getAsyncHandler)
                getAsyncHandler = new 
                  DownloadStringCompletedEventHandler(htpSvc_DownloadStringCompleted);

            //didn't used the httpSvc.DownloadStringCompleted += (s, e)=>(...)
            //lamda expression shortcut to enable the -= (remove event listener) 
            //in the result handler
            httpSvc.DownloadStringCompleted += getAsyncHandler;
            httpSvc.DownloadStringAsync(new Uri(svcUrl));
        }

        private void htpSvc_DownloadStringCompleted(object sender, 
                            DownloadStringCompletedEventArgs e)
        {
            httpSvc.DownloadStringCompleted -= getAsyncHandler;

            if (null != e.Error)
                responder.onFault(e.Error);
            else
                responder.onResult(e.Result);
        }

        protected void postAsync(string url, string payLoad)
        {
            if (null == postAsyncHandler)
                postAsyncHandler = new 
                  UploadStringCompletedEventHandler(httpSvc_UploadStringCompleted);

            httpSvc.UploadStringCompleted += postAsyncHandler;
            httpSvc.UploadStringAsync(new Uri(url), payLoad);
        }

        private void httpSvc_UploadStringCompleted(object sender, 
                             UploadStringCompletedEventArgs e)
        {
            httpSvc.UploadStringCompleted -= postAsyncHandler;

            if (null != e.Error)
                responder.onFault(e.Error);
            else
                //responder.onResult<string>(e.Result);
                responder.onResult(e.Result);
        }
    }
}

Now, let's get back to the question about how the Cairngorm delegate locates a service from the ServiceLocator, and furthermore, how easy it is to have a concrete CairngormDelegate class do that? When a concrete Cairngorm delegate derives from the abstract base (above), everything is very simple. Fig. 6 is the SearchPhoto delegate implemented in the sample application:

Fig. 6 The concrete CairngormDelegate derives from an abstract base class to simplify coding:
namespace SLCairngorm2010.Business
{
    public class SearchPhotoDelegate : CairngormDelegate
    {
        public SearchPhotoDelegate(IResponder responder)
            : base(responder)
        {
            base.httpSvc = SilverPhotoService.Instance.getHTTPService(
                                 SilverPhotoService.FLICKR_SEV_NAME);
        }

        public void SendRequest(string searchTerm)
        {
            string apiKey = "[Your FlickR API Key]";
            string url = String.Format("http://api.flickr.com/services/" + 
               "rest/?method=flickr.photos.search&api_key={1}&text={0}",
               searchTerm, apiKey);

            base.getAsync(url);
        }
    }
}

Within the sample project, the specified Flickr service is registered in SilverPhotoServiceLocator, and the actual WebClient instance is found by calling getHTTPService by passing in the service name. base.getAsync wires up the event handlers and IResponder methods with the specified WebClient instance, which then sends out the HTTP request to search. When the response comes back or an error occurs, the IResponder methods implemented in SearchPhotoCommand are invoked.

The code in the abstract CairngormDelegate really makes the concrete Cairngorm delegate easy to use, and allows us to just focus on the specifics of the targeted service call; no need to worry about those common plumbing works. When other methods are needed, it can be easily extended in a similar way as we did for DownloadString and UpLoadString.

Following the similar approach, a new abstract base class for CairngormCommand is also added to the framework.

New Abstract Base Class for CairnogormCommand

The Command in the Cairngorm framework is really about separation and encapsulation of code that calls services, and when manipulating the data model, it instantiates the right cairngorm delegate calling to the service, and also implements the ICommand and IResponder interfaces. Shown below is the abstract CairngormCommand class:

Fig. 7. Abstract class for CairngormCommand:
namespace SilverlightCairngorm.Command
{
    public abstract class CairngormCommand : ICommand, IResponder
    {
        protected CairngormEvent cgEvt = null;
        #region ICommand Members

        virtual public void execute(CairngormEvent cairngormEvent)
        {
            //derived class call base first before call their own
            cgEvt = cairngormEvent;
        }

        #endregion

        #region IResponder Members

        virtual public void onResult(string result)
        {
            //derived class call their own logic first before call this
            //only needs to call base when callBack is needed
            if (null != cgEvt)
                cgEvt.responderCallBack(true);
        }

        virtual public void onFault(Exception error)
        {
            //derived class call their own logic first before call this
            //only needs to call base when callBack is needed
            if (null != cgEvt)
                cgEvt.onFaultCallBack(error);
        }

        #endregion
    }
}

As a matter of fact, there is not much "common tasks" performed in this base class; it just creates a cache for the corresponding CairngormEvent and calls methods on the cached reference. The real purpose of this abstract class is to wire up the corresponding .NET delegate as a callback that is referenced in the cairngorm event. The derived classes can take advantage of it to support call back to the View from the Command, and therefore the Cairngorm Command code can be totally View ignorant.

View Callback Support in Cairngorm Event and Command

As per the design of the Cairngorm framework, the View handles all the visuals, and user gestures (mouse clicks, key strokes, etc.) usually result in raising an application defined Cairngorm Event. The Cairngorm Controller handles the Event by instantiating the corresponding registered Cairngorm Command, which then invokes the Execute method through the ICommand interface. The Cairngorm Command then uses the right Cairngorm delegate to invoke the service asynchronously. When the service response comes back or an error occurs, the Cairngorm delegate calls back to the Command via the IResponder interface. There is no callback from the Command to the View when the View is updated based on different service responses or model state changes; this was intended to be handled by data binding.

With almost every RIA I've been working with, I found out that after the Command changes the Model, the data binding engine can handle most View changes automatically, but in certain cases, that's not efficient - we have the need for a View callback from the Command to perform some operations that are beyond normal data binding. For example, when submitting form data in a popup, the popup may have some visual indicator that shows the application is communicating with the server. When the server response is OK, we'd like to stop the visual indicator and close up the popup, and View's navigation probably follows up. To avoid mixing the View's code in the Command (to close the popup in the Command), we'd like to have all visual related code stay in the View, and have the Command callback the View via a .NET delegate to perform the intended operation for the View. This need is to support callbacks to the View from the Command via Events.

This callback support in Cairngorm Command not only decouples the Command from the View, and satisfies the "Separation of Concerns" principle in the application architecture, but also enables unit test for the Command, and makes test-driven development possible in the application structure.

We've already seen the callback wire up in the abstract CairngormCommand class in Fig. 7. The updated CairngormEvent class that supports callback is shown in Fig. 8.

Fig. 8. Updated CairngormEvent with callback support:
namespace SilverlightCairngorm.Control
{
    /// <summary>
    /// A parent Class for all Cairngorm events.
    /// </summary>
    public class CairngormEvent
    {
        /// <summary>
        /// Constructor of a cairngorm event
        /// </summary>
        /// <param name="typeName"></param>
        public CairngormEvent( String typeName)
        {
            this.Name = typeName;
        }

        /// <summary>
        /// every event must have a unique name - multiple, similar, events 
        /// may be grouped in one class - as long as the "Name" property
        /// changes from one event to the other.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// The data property can be used to hold information to be passed with the event
        /// in cases where the developer does not want to extend the CairngormEvent class.
        /// However, it is recommended that specific classes are created for each type
        /// of event to be dispatched.
        /// </summary>
        public object Data { get; set; }

        /// <summary>
        /// Callback delegate usually used when gets response
        /// while comand needs to callback to View
        /// Optional field
        /// </summary>
        public Action<bool> responderCallBack = delegate {};

        /// <summary>
        /// Callback delegate usually used when command needs
        /// to callback to View's method to handle exception
        /// Optional field
        /// </summary>
        public Action<exception> onFaultCallBack = delegate {};

        /// <summary>
        /// Helper funtion to raise event without requiring
        /// client code to call CairngormEventDispatcher.getInstance()
        /// </summary>
        public void dispatch()
        {
            CairngormEventDispatcher.Instance.dispatchEvent(this);
        }
    }
}

Two callback .NET delegates are defined; if they are not defined in the concrete Cairngorm Event, the Command would invoke the default empty delegate to avoid the null-checking before invoking.

To use it, the application View's code just defines the callback function with the correct signature, then sets the callback .NET delegate to the corresponding callback property before dispatching it; please see Fig. 9 for an example.

Fig. 9. Example of CairngormEvent callback support in PhotoSearch.xaml.cs:
namespace SLCairngorm2010.View
{
    public partial class PhotoSearch : UserControl
    {
        protected ProgressDialog _progressDialog;
        
        public PhotoSearch()
        {
            InitializeComponent();
        }

        private void searchBtn_Click(object sender, RoutedEventArgs e)
        {
            SilverPhotoModel model = SilverPhotoModel.Instance;
            if (!String.IsNullOrEmpty(model.SearchTerm))
            {
                onSearchingStart();
                
                CairngormEvent cgEvent = new 
                  CairngormEvent(SilverPhotoController.SC_EVENT_SEARCH_PHOTO);
                cgEvent.responderCallBack = onSearchCompleted;
                cgEvent.onFaultCallBack = onSearchError;
                cgEvent.dispatch();
            }
        }

        protected virtual void onSearchingStart()
        {
            _progressDialog = new ProgressDialog("Searching Flickr...");
            _progressDialog.Show();
        }

        private void onSearchCompleted(bool result)
        {
            if (null != _progressDialog)
                _progressDialog.Close();
            _progressDialog = null;
        }

        private void onSearchError(Exception Error)
        {
            if (null == Error)
                return; //no error

            if (String.IsNullOrEmpty(Error.Message) || 
                      String.IsNullOrWhiteSpace(Error.Message))
                return; //nothing to show

            ChildWindow w = 
              new ChildWindow() { Title = "Communication to Server Failed" };
            w.Content = new TextBlock() { Text = "Error:" + Error.Message };
            w.Show();
        }

    }
}

Wrapping Up

This update for Silverlight Cairngorm completes adding an easy-to-use ServiceLocator to the framework, and improved the IResponder interface and the abstract base class CairngormDelegate that makes calling a string as a content type service significantly easier. Additionally, the abstract CairngormCommand class with the extended CairngormEvent provides support for View callback via a .NET delegate from the Command. All these changes and expansions were developed and tested with Silverlight 4 Beta in Visual Studio 2010 Beta 2. Wish this update would help make your next Silverlight 4 project more fun and easy to work with.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

Share

About the Author

Modesty Zhang
Technical Lead
United States United States
https://github.com/modesty

https://www.linkedin.com/in/modesty-zhang-9a43771

https://twitter.com/modestyqz

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160929.1 | Last Updated 21 Dec 2009
Article Copyright 2009 by Modesty Zhang
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid