Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing a Loosely Coupled Silverlight 3 Application

0.00/5 (No votes)
20 Jul 2009 1  
Create a line of business application using Silverlight 3.

Overview

“It was the best of times, it was the worst of times.” goes the opening line in Charles Dickens’ novel, A Tale of Two Cities (1859). The same can be said for Microsoft .NET developers in 2009. The economy may have hit rock bottom, but when it comes to the amount of new software development technologies being released or in the final stages of beta mode, the Microsoft .NET developer now (more than ever) has a plethora of technology options to choose from. It is the best of times for those who love to learn new technologies. One ever evolving new technology is Microsoft Silverlight. Silverlight is a programmable web browser plug-in that enables rich Internet content similar to Abode’s Flash technology. With the latest just released version of Silverlight, Microsoft has taken things a step further. With version 3 of Silverlight, developers can now start developing rich Internet line of business applications.

Sample Application

login.jpg

Figure 1 – Sample Login Screen

The sample application for this article will demonstrate a Silverlight 3.0 application using a loosely coupled architecture. A loosely coupled architecture means that individual components of an application can be developed and tested independently of each other and in parallel. What this means is that .NET developers, web graphic designers, and quality assurance automation engineers can work simultaneously on an application without having to wait on each other. All this leads to higher quality software and shorter development release cycles. The sample application in this article is a membership application. This article will walk you through a typical registration process that many social websites use.

Loosely Coupled Architecture

Without getting too deep into design pattern jargon, the loosely coupled architecture for this application will implement separate Silverlight classes called Controllers that will communicate with the Silverlight front-end GUI. Also know as the View, the Silverlight front end will contain both the XAML markup file and the code-behind class. The controllers will not make any direct references or calls to the views. The controllers and the views will communicate with each other through the implementation of data binding, event routing, and delegates. The advantage of this is that the controllers can be developed and tested independently of the GUI. This allows the GUI Designer to generate all the XAML for the application using a tool like Expression Blend, while at the same time, the .NET Software Engineer can work on the rest of the application using Visual Studio.

ag3.JPG

Figure 2 – A loosely coupled Silverlight architecture

The Shared Data Model Class

Application development usually starts with defining your data entities and designing a database. For this sample application, the main data entity is membership information. After building the database tables for this application, the following class is created that exposes the underling data model:

public class MembershipDataModel 
{
    /// <summary>
    /// Membership ID
    /// </summary>
    private long  _membershipID;
    public long MembershipID
    {
        get { return _membershipID; }
        set { _membershipID = value; 
            RaisePropertyChanged("MembershipID"); }
    }        
    /// <summary>
    /// Email Address
    /// </summary>
    private string _emailAddress;
    public string EmailAddress
    {
        get { return _emailAddress; }
        set { _emailAddress = value; 
            RaisePropertyChanged("EmailAddress"); }
    }
    /// <summary>
    /// Last Name
    /// </summary>
    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; 
            RaisePropertyChanged("LastName"); }
    }
    /// <summary>
    /// First Name
    /// </summary>
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; 
            RaisePropertyChanged("FirstName"); }
    }
    /// <summary>
    /// Overridable event
    /// </summary>
    /// <param name="propertyName"></param>
    public virtual void RaisePropertyChanged(string propertyName)
    {
    }
}
Figure 3 – Data Model

The Data Model class in figure 3 contains public properties that will be referenced by the sample application. This class will be used both in the Silverlight client application and in the server-side business and data classes. Classes referenced in Silverlight applications must be created as Silverlight class libraries. Silverlight uses a subset of the standard Microsoft .NET Framework, therefore it can only consume Silverlight classes. Fortunately, standard .NET classes can consume Silverlight classes, but with a few restrictions. Shared classes should not implement Silverlight-only based namespaces.

As you can see in figure 3, the Data Model class implements PropertyChangedEvent and raises the RaisePropertyChanged event when setting property values. Since the Data Model class will be shared, the event will not implement any functionality when referenced by server-side business and data tiers. When implemented in the Silverlight client, the event will be overridden with functionality that will enable data binding between the Silverlight XAML markup and the controller. The controller will simply inherit this class and implement the INotifyPropertyChanged interface which enables TwoWay data binding. TwoWay data binding enables two properties that are bound together to change each other.

Registration Page

register.jpg

Figure 4 – The Registration Page

The first step to using this application is to register yourself as a member by clicking on the Register link of the login page in figure 1.

The Registration page in figure 4 will communicate with the Register Controller in figure 7 through data binding. The first step to enabling data binding is to set the Text property of each field in the RegisterPage.xaml file with the following syntax:

<TextBox Grid.Row="6" Grid.Column="1" x:Name="txtLastName" 
     HorizontalAlignment="Left" 
     Width="300" 
     Margin="2,5,2,2"
     Background="BlanchedAlmond" 
     Text="{Binding LastName, Mode=TwoWay}" />
Figure 5 – Register Page XAML Markup

In figure 5 above, the txtLastName TextBox will automatically populate the LastName property in the controller class through the Binding command when the user enters data in this field.

The next step is to wire up the Register Controller class in the code-behind file RegisterPage.xaml.cs, as shown in figure 6, as follows:

public partial class RegisterPage : UserControl
{
    public RegisterController _registerController;
    /// <summary>
    /// Register Page Constructor
    /// </summary>
    public RegisterPage()
    {
        
        InitializeComponent();
        ///
        ///  Initialize the Register Controller
        ///             
        InitializeView();
        ///
        /// route button click events and password change events 
        /// to the RegisterController
        /// 
        this.btnRegister.Click += new 
            RoutedEventHandler(_registerController.CreateRegistration);
        this.btnLogin.Click += new 
            RoutedEventHandler(_registerController.GotoLoginPage);
        this.txtPassword.PasswordChanged += 
            new RoutedEventHandler(
                _registerController.OriginalPasswordChangedHandler);
        this.txtConfirmPassword.PasswordChanged += new 
            RoutedEventHandler(
            _registerController.ConfirmationPasswordChangedHandler);
        ///
        /// create a handler for the page Loaded event
        /// 
        this.Loaded += new RoutedEventHandler(Page_Loaded);
    }
    /// <summary>
    /// Initialize View
    /// </summary>
    void InitializeView()
    {
        _registerController = new RegisterController();
        //
        // Set up Register Controller Delegates - The Register Controller
        // will not communicate directly with this code-behind file
        // so a delegate is needed to transfer control back to
        // this page
        // 
        RegisterController.ShowDialogDelegate showDialog =
           new RegisterController.ShowDialogDelegate(this.ShowDialog);
        RegisterController.GotoUpdateProfileDelegate gotoUpdateProfile =
           new RegisterController.GotoUpdateProfileDelegate(
               this.GotoUpdateProfilePage);
        RegisterController.GotoLoginPageDelegate gotoLoginPage =
             new RegisterController.GotoLoginPageDelegate(
                 this.GotoLoginPage);
        _registerController.GotoUpdateProfileHandler = gotoUpdateProfile;
        _registerController.GotoLoginPageHandler = gotoLoginPage;
        _registerController.ShowDialog = showDialog;
        //
        // Let the Register Controller know the address of the WCF Web Service
        // 
        _registerController.WebServer = 
            App.Current.Resources["WebServer"].ToString();
        
    }
    /// <summary>
    /// Page Loaded
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        //
        // Setting the DataContext binds the Register Controller
        // to the Xaml allowing for automatic data binding
        // to the properties of the Register Controller
        //
        LayoutRoot.DataContext = _registerController;
        this.txtEmailAddress.IsTabStop = true;
        this.txtEmailAddress.Focus();
    }
    /// <summary>
    /// Goto Update Profile
    /// </summary>
    public void GotoUpdateProfilePage(
        MembershipDataModel membershipInformation)
    {
        //
        // Upon successful Registration, the Register Controller 
        // will transfer control back to the Register View. The 
        // Register View will switch to the Update Profile View
        //
        UpdateProfilePage updateProfile = 
            new UpdateProfilePage(membershipInformation);
        //
        //  Create a reference to the Master Page of this 
        //  Silverlight application 
        //
        UIElement applicationRoot = Application.Current.RootVisual;
        MainPage mainPage = (MainPage)applicationRoot;
        if (mainPage == null) throw new NotImplementedException();
        //
        //  Load the Update Profile View
        //
        mainPage.NavigateToPage(updateProfile);
    }
    /// <summary>
    /// Goto Login Page
    /// </summary>
    public void GotoLoginPage()
    {
        //
        // Goto the Login View when the user presses the Login Button
        //
        LoginPage loginPage = new LoginPage();
        UIElement applicationRoot = Application.Current.RootVisual;
        MainPage mainPage = (MainPage)applicationRoot;
        if (mainPage == null) throw new NotImplementedException();
        mainPage.NavigateToPage(loginPage);
    }
    /// <summary>
    /// Show Dialog
    /// </summary>
    /// <param name="errorMessage"></param>
    void ShowDialog(string errorMessage)
    {
        //
        // The Register Controller received an error from the
        // WCF Service, display a DialogBox that displays
        // the error to the user
        
        DialogBox dlg = new DialogBox(errorMessage);
        dlg.Title = "Registration Errors";        
        dlg.Closed += new EventHandler(OnErrorDialogClosed);
        dlg.Show();

    }
    /// <summary>
    /// Closing Dialog
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnErrorDialogClosed(object sender, EventArgs e)
    {
        //
        // Close the DialogBox when the user presses the OK button
        //
        DialogBox dlg = (DialogBox)sender;
        bool? result = dlg.DialogResult;
    }
}
Figure 6 – RegisterPage.xaml.cs

In figure 6 above, the Register Controller is created and bound to the XAML by setting Layroot.DataContext to the Register Controller in the Page_Loaded event.

The next item to wire up is the Register button through the Register button Click event.

this.btnRegister.Click += new RoutedEventHandler(_registerController.CreateRegistration);

When the user presses the Register button, the CreateRegistration method of the Register Controller will be executed.

The Password Box

The functionality of the PasswordBox control has changed in Silverlight 3. In version 2 of Silverlight, I could not directly bind the password fields in the XAML to properties in the Register Controller. Turns out that the PasswordBox in Silverlight 2 was encrypted and required a dependency property and a value converter before its value could be passed to the controller. All this sounded a little complicated, so I decided to simply wire up a PasswordChanged event of the PasswordBox and route the event to the Register Controller to populate the Password property in the Register Controller class as follows:

this.txtPassword.PasswordChanged += 
        new RoutedEventHandler(
            _registerController.OriginalPasswordChangedHandler);
/// <summary>
/// Password Changed Handler
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public void OriginalPasswordChangedHandler
    (Object sender, RoutedEventArgs args)
{
    PasswordBox p = (PasswordBox)sender;
    this.Password = p.Password.ToString();
}

In version 3 of Silverlight, this has been fixed. The encrypted PasswordBox control can now be bound directly to properties in a business object by binding the Password property of the PasswordBox as follows:

<PasswordBox Grid.Row="3" Grid.Column="1" x:Name="txtPassword"
     HorizontalAlignment="Left" 
     Width="200"
     Margin="2,5,2,2"                     
     Password="{Binding Password, Mode=TwoWay}"
     Background="BlanchedAlmond">
</PasswordBox>

Register Controller

At this point in time, the Register Controller is ready to process membership information entered by the user and call a WCF Web Service to validate the information. If all the information is valid, the WCF Service will proceed to register the user in the database.

public class RegisterController : MembershipDataModel, INotifyPropertyChanged
{
    GUIHelper _guiHelper;
    public delegate void GotoUpdateProfileDelegate
        (MembershipDataModel membershipInformation);
    public GotoUpdateProfileDelegate GotoUpdateProfileHandler;
    public delegate void GotoLoginPageDelegate();
    public GotoLoginPageDelegate GotoLoginPageHandler;
    public delegate void ShowDialogDelegate(string message);
    public ShowDialogDelegate ShowDialog;
    private string _webServer;
    public string WebServer
    {
        get { return _webServer; }
        set { _webServer = value; }
    }
    public RegisterController()
    {
        _guiHelper = new GUIHelper();
    }
    /// <summary>
    /// Property Changed Event Handler
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Raise Property Changed
    /// </summary>
    /// <param name="propertyName"></param>
    public override void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    /// <summary>
    /// Goto Login Page
    /// </summary>
    public void GotoLoginPage(object sender, RoutedEventArgs e)
    {
        GotoLoginPageHandler();
    }
    /// <summary>
    /// Create Registration
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void CreateRegistration(object sender, RoutedEventArgs e)
    {
        _guiHelper.SetWaitCursor(sender);
        CreateMemberRegistration();
    }

    /// <summary>
    /// Create Registration
    /// </summary>
    public void CreateMemberRegistration()
    {
        EndpointAddress endpoint = new 
            EndpointAddress("http://" + 
            this.WebServer + "/Ag3DemoWebServer/Ag3DemoWCFService.svc");
        BasicHttpBinding binding = new BasicHttpBinding();
        Ag3DemoControllers.WCFHelper wcfProxy = new 
            Ag3DemoControllers.WCFHelper(binding, endpoint);
        wcfProxy.CreateRegistrationCompleted +=
            new EventHandler
                <Ag3DemoWCFService.CreateRegistrationCompletedEventArgs>
                (CreateRegistration_Completed);
        MembershipDataModel membershipInformation = 
            new MembershipDataModel();
        membershipInformation.EmailAddress = this.EmailAddress;
        membershipInformation.Password = this.Password;
        membershipInformation.PasswordConfirmation = 
            this.PasswordConfirmation;
        membershipInformation.FirstName = this.FirstName;
        membershipInformation.LastName = this.LastName;
        wcfProxy.CreateRegistrationAsync(membershipInformation);
    }
    /// <summary>
    /// Create Registration Completed
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void CreateRegistration_Completed(object sender, 
        Ag3DemoWCFService.CreateRegistrationCompletedEventArgs e)
    {
        _guiHelper.SetDefaultCursor();
       
        if (e.Result == null)
        {
            ShowDialog("An error has occurred while trying to register.");
            return;
        }
        MembershipDataModel.MembershipValidationInformation 
            validation = e.Result.ValidationInformation;
        if (validation.MemberCreated == true)
        {
            MembershipDataModel membershipInformation = 
                (MembershipDataModel)e.Result;
            GotoUpdateProfileHandler(membershipInformation);
            return;
        }
        StringBuilder messageInformation = new StringBuilder();
        messageInformation.Append(
            "The following errors have occurred.\n\n");
   
        if (validation.ErrorMessage != null)
        {
            messageInformation.Append(validation.ErrorMessage.ToString());
        }
        ShowDialog(messageInformation.ToString());
    }
    /// <summary>
    /// Password Changed Handler
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public void OriginalPasswordChangedHandler
        (Object sender, RoutedEventArgs args)
    {
        PasswordBox p = (PasswordBox)sender;
        this.Password = p.Password.ToString();
    }
    /// <summary>
    /// Confirmation Password Changed Handler
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public void ConfirmationPasswordChangedHandler(
        Object sender, RoutedEventArgs args)
    {
        PasswordBox p = (PasswordBox)sender;
        this.PasswordConfirmation = p.Password.ToString();
    }
}
Figure 7 – RegisterController.cs

The Register Controller in figure 7 inherits the MembershipDataModel class that contains all the data properties and implements the INotifyPropertyChanged interface. The INotifyPropertyChanged interface is what needs to be implemented to enable TwoWay data binding between the Register page XAML markup and the Register Controller class. You may not need to implement TwoWay data binding all the time, but as a rule of thumb, I would generally include its implementation.

public class RegisterController : MembershipDataModel, INotifyPropertyChanged

The Register Controller also implements TwoWay binding by overriding and implementing the PropertyChanged event in the MembershipDataModel.

/// <summary>
/// Property Changed Event Handler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise Property Changed
/// </summary>
/// <param name="propertyName"></param>
public override void RaisePropertyChanged(string propertyName)
{
    var handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Calling a WCF Service

Once a WCF service reference has been manually created by the developer in Visual Studio, the Register Controller class can simply call the CreateRegistration method of the WCF Service by creating a proxy object. In Figure 7, the CreateMembershipInformation method calls the CreateRegistration WCF Service asynchronously. Doing this will prevent the Silverlight GUI from hanging or freezing up in case the WCF Service takes a while to execute.

When developing this piece of code, I came across the following caveats when calling a WCF Service from Silverlight:

  • BasicHttpBinding - When manually creating a WCF service reference in Visual Studio, I received an error saying Silverlight clients only support the BasicHttpBinding protocol. Turns out I had to change the default setting in the web.config file in the web server project to BasicHttpBinding.
  • Security Access Policy - The security policy system incorporated in Silverlight is designed to prevent network threats and attacks. In addition, the policy system also aims to give administrators more control over which resources a remote client is allowed to connect to. A ClientAccessPolicy.xml file must reside in the root of the web domain where the WCF service is hosted. For example, if your website is mywebsite.com, then the file must be located at http://mywebsite.com/clientaccesspolicy.xml.
  • Communication Errors – If a WCF Service raises a fault exception error during its call to the Web Service, the browser will intercept this exception and the Silverlight runtime will not be able to catch the exception. The Silverlight application simply crashes when this occurs. Without spending too much time researching a solution to this problem, I decided to create a WCF Helper class as shown in figure 8 that inherits from the WCF Service class in the reference.cs file. Since reference.cs is an automatically generated class file, when you create the WCF service reference, this file should not be modified. In my WCF Helper class, I implement the Begin and End service methods and provide my own try/catch blocks to catch the communication errors.
  • /// <summary>
    /// End Create Registration
    /// </summary>
    /// <param name="result"></param>
    /// <returns></returns>
    public Ag3DemoDataModels.MembershipDataModel EndCreateRegistration(
    System.IAsyncResult result)
    {
        try
        {
            object[] _args = new object[0];
            Ag3DemoDataModels.MembershipDataModel _result =       
               ((Ag3DemoDataModels.MembershipDataModel)(
                  base.EndInvoke("CreateRegistration", _args, result)));
            return _result;
        }
        catch (Exception ex)
        {
            Ag3DemoDataModels.MembershipDataModel _result = new  
               Ag3DemoDataModels.MembershipDataModel();
            _result.ValidationInformation.HasErrors = true;
            _result.ValidationInformation.ErrorMessage = ex.Message.ToString();
    
           return _result;
        }
    }
    Figure 8 – WCFWrapper.cs - EndCreateRegistration

    The ValidationInformation object reference in figure 8 is a nested class within the MembershipDataModel class. The try/catch block catches the exception, sets the boolean HasErrors to true, and records the exception message in the ErrorMessage property. When the service completes, the client can check the HasErrors boolean value and take appropriate action.

  • WCF Endpoint Address – When a Silverlight client calls a WCF Service, the client gets the WCF web endpoint address or URL from a ServiceReferences.ClientConfig file that is embedded inside the deployed XAP file that contains the entire Silverlight application. This is an issue if you are staging and deploying your Silverlight application to different development, QA, pre-production, and production environments. Each environment will have different addresses. To overcome this, I set the web server address in the web.config for each Web Server and pass this value through Silverlight’s InitParams within the ASPX file that hosts the Silverlight application. Before making the WCF Service call, the Register Controller sets and overrides the endpoint address as follows:

    EndpointAddress endpoint = new 
                    EndpointAddress("http://" + 
                    this.WebServer + "/Ag3DemoWebServer/Ag3DemoWCFService.svc");
    BasicHttpBinding binding = new BasicHttpBinding();
    Ag3DemoControllers.WCFHelper wcfProxy = 
      new Ag3DemoControllers.WCFHelper(binding, endpoint);

WCF Service Completed Callback

When the WCF Service has completed, the CreateRegistration_Completed callback method is executed.

/// <summary>
/// Create Registration Completed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void CreateRegistration_Completed(object sender, 
    Ag3DemoWCFService.CreateRegistrationCompletedEventArgs e)
{
    _guiHelper.SetDefaultCursor();
   
    if (e.Result == null)
    {
        ShowDialog("An error has occurred while trying to register.");
        return;
    }
    MembershipDataModel.MembershipValidationInformation 
        validation = e.Result.ValidationInformation;
    if (validation.MemberCreated == true)
    {
        MembershipDataModel membershipInformation = 
            (MembershipDataModel)e.Result;
        GotoUpdateProfileHandler(membershipInformation);
        return;
    }
    StringBuilder messageInformation = new StringBuilder();
    messageInformation.Append(
        "The following errors have occurred.\n\n");
    if (validation.ErrorMessage != null)
    {
        messageInformation.Append(validation.ErrorMessage.ToString());
    }
    ShowDialog(messageInformation.ToString());
}
Figure 9 – RegisterController.cs - CreateRegistration_Completed Method

In this method, the MemberCreated property of the nested validation class is checked to determine if the member was created. When the member has been created, the Register Controller needs to tell the Silverlight application to navigate to and load the membership profile page. If an error occurs, the Register Controller will tell the Silverlight application to show a dialog box with an error message.

Delegates

Since the Register Controller is loosely coupled and has no direct knowledge of the Register page, the controller needs to communicate with the Register page through a delegate when a new action on the client needs to take place, like switching to another page or displaying a dialog box.

_registerController = new RegisterController();
//
// Set up Register Controller Delegates - The Register Controller
// will not communicate directly with this code-behind file
// so a delegate is needed to transfer control back to
// this page
// 
RegisterController.ShowDialogDelegate showDialog =
   new RegisterController.ShowDialogDelegate(this.ShowDialog);
RegisterController.GotoUpdateProfileDelegate gotoUpdateProfile =
   new RegisterController.GotoUpdateProfileDelegate(
       this.GotoUpdateProfilePage);
RegisterController.GotoLoginPageDelegate gotoLoginPage =
     new RegisterController.GotoLoginPageDelegate(
         this.GotoLoginPage);
_registerController.GotoUpdateProfileHandler = gotoUpdateProfile;
_registerController.GotoLoginPageHandler = gotoLoginPage;
_registerController.ShowDialog = showDialog;

The delegate acts as the middle man and provides the needed communication between the controllers and the views (XAML pages). Basically, delegates allow a class to invoke code in another class without necessarily knowing where that code is, or even if it exists at all.

Unite Testing

As stated before, the Register Controller can be developed and tested prior to the development of the Register page and its underlying XAML markup. To test the Create Registration method, I immediately wired up my favorite testing tool MbUnit. Unfortunately, to my dismay, I could not compile and run MbUnit against the Register Controller because the controller implements the InotifyPropertyChanged interface which is embedded in the Silverlight system namespace. MbUnit by itself does not seem to support the testing of classes created as Silverlight classes. MbUnit requires the standard .NET System namespace which conflicts with the Silverlight System namespace.

Microsoft Silverlight Unit Testing Framework

While researching Unit Testing frameworks that can test Silverlight applications, I found a couple of Open Source testing frameworks such as SilverUnit and TestDriven.Net that could probably do the job. While reading Microsoft’s Scott Guthrie’s blog, I came across the Microsoft Silverlight Unit Test Framework. The Microsoft testing framework is a simple, extensible unit testing solution for rich Silverlight 2 applications, controls, and class libraries. Without any mention of whether this framework works for Silverlight 3, I decided to try it anyways. Microsoft’s testing framework was easy to use. First, I just downloaded the following two namespace from somewhere on the Internet.

  • Microsoft.Silverlight.Testing
  • Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight

The second step was to create a standard Silverlight application and reference these two namespaces and add the following in the application startup:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = UnitTestSystem.CreateTestPage();
}

After this, you can proceed to create your Unit Test classes within the Silverlight testing application, add your tests, and run the project.

[TestClass]
public class UnitTests : SilverlightTest 
{
    private string _generatedTestEmailAddress;
    RegisterController _registerController;
    MembershipDataModel _membershipInformation;
    [TestMethod]
    [Asynchronous]
    public void Register001_CreateRegistration()
    {
        RegisterController.GotoUpdateProfileDelegate gotoUpdateProfileDelegate =
            new RegisterController.GotoUpdateProfileDelegate(this.GotoUpdateProfilePage);
        RegisterController.ShowDialogDelegate showDialog =
            new RegisterController.ShowDialogDelegate(this.ShowDialog);
        _membershipInformation = new MembershipDataModel();
        
        _registerController = new RegisterController();
        _generatedTestEmailAddress = DateTime.Today.Year.ToString() + 
            DateTime.Today.Month.ToString() + DateTime.Today.Day.ToString() + 
            Environment.TickCount.ToString() + "@mywebsite.com";            
        _registerController.GotoUpdateProfileHandler = gotoUpdateProfileDelegate;
        _registerController.ShowDialog = showDialog;
        _registerController.WCFCallCompleted = false;
        _registerController.EmailAddress = _generatedTestEmailAddress;
        _registerController.Password = "mypassword";
        _registerController.PasswordConfirmation = "mypassword";
        _registerController.FirstName = "William";
        _registerController.LastName = "Gates";
        _registerController.WebServer = "localhost";
        _registerController.CreateRegistration(null, null);
         
          
        EnqueueConditional(() =>
        {
            return _registerController.WCFCallCompleted == true;
        });
        EnqueueCallback(() => Assert.IsTrue
           (_membershipInformation.ValidationInformation.MemberCreated));
        EnqueueCallback(() => Assert.AreEqual
          (_membershipInformation.LastName.ToLower(),"gates"));
        EnqueueTestComplete();
    }        
    /// <summary>
    /// Goto Update Profile
    /// </summary>
    public void GotoUpdateProfilePage(MembershipDataModel membershipInformation)
    {
        _membershipInformation = membershipInformation;
        _registerController.WCFCallCompleted = true;
    }
    /// <summary>
    /// Show Dialog
    /// </summary>
    /// <param name="errorMessage"></param>
    void ShowDialog(string errorMessage)
    {
        _registerController.WCFCallCompleted = true;
    }
}
Figure 10 – Ag3DemoUnitTests.cs

In figure 10 above, the sample Unit Test is actually very complicated because the Create Registration method executes a WCF Service asynchronously. Fortunately, the Microsoft unit testing framework supports asynchronous unit testing through a series of Enqueue calls. The unit test sets the boolean WCFCallCompleted to false, executes the CreateRegistration method, and then waits until the CreateRegistration returns and executes one of its callback delegate functions. Once returned, the WCFCallCompleted boolean is set to true, releasing the Enqueue events to continue on. Assert test methods are then executed to determine if the test executed successfully.

test5.jpg

The Microsoft Silverlight Unit Testing framework is far from perfect, but it supported what I needed to accomplish for this demonstration. Silverlight unit testing tools are still in their infancy, so I look forward to this area of Silverlight development to mature over the next year as Silverlight 3 makes its way into the market.

Conclusion

This article demonstrated the power of Silverlight 3 and its powerful data binding to objects technology. Data binding to objects opens the door for creating loosely coupled, modular object oriented code that is testable. This also opens the door for Web Designers, .NET developers, and QA automation engineers to work together in parallel from the start of a project. Overall, Microsoft Silverlight is an exciting but prickly new technology. As you begin to learn Silverlight, you will stumble across one challenge after the next. Due to the small footprint of the Silverlight framework, the nature of the XAML markup language, and Silverlight’s deployment model, mastering Silverlight will challenge you to think of alternate ways of accomplishing your goals.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here