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 WebClient
s. 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:
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;
}
public void setRemoteCredentials(String username, String password)
{
if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
{
WebRequest.RegisterPrefix("http://",
System.Net.Browser.WebRequestCreator.BrowserHttp);
_netCredential = null;
}
else
{
if (null == _netCredential)
{
WebRequest.RegisterPrefix("http://",
System.Net.Browser.WebRequestCreator.ClientHttp);
}
_netCredential = new NetworkCredential(username, password);
}
foreach (KeyValuePair<string,> oneSvePair in _httpServices)
{
var oneSve = oneSvePair.Value;
oneSve.Credentials = _netCredential;
oneSve.UseDefaultCredentials = (null != _netCredential) ? false : true;
}
}
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
private Dictionary<string,> _httpServices = new Dictionary<string,>();
public void addHTTPService(string serviceName, WebClient serviceClient)
{
_httpServices.Add(serviceName, serviceClient);
}
public void removeHTTPService(string serviceName)
{
if (_httpServices.ContainsKey(serviceName))
_httpServices.Remove(serviceName);
}
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();
public static SilverPhotoService Instance { get { return _instance; } }
private SilverPhotoService()
{
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;
protected CairngormDelegate(IResponder responder)
{
this.responder = responder;
}
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);
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(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)
{
cgEvt = cairngormEvent;
}
#endregion
#region IResponder Members
virtual public void onResult(string result)
{
if (null != cgEvt)
cgEvt.responderCallBack(true);
}
virtual public void onFault(Exception error)
{
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
{
public class CairngormEvent
{
public CairngormEvent( String typeName)
{
this.Name = typeName;
}
public string Name { get; set; }
public object Data { get; set; }
public Action<bool> responderCallBack = delegate {};
public Action<exception> onFaultCallBack = delegate {};
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;
if (String.IsNullOrEmpty(Error.Message) ||
String.IsNullOrWhiteSpace(Error.Message))
return;
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.