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
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.
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
{
private long _membershipID;
public long MembershipID
{
get { return _membershipID; }
set { _membershipID = value;
RaisePropertyChanged("MembershipID"); }
}
private string _emailAddress;
public string EmailAddress
{
get { return _emailAddress; }
set { _emailAddress = value;
RaisePropertyChanged("EmailAddress"); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value;
RaisePropertyChanged("LastName"); }
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value;
RaisePropertyChanged("FirstName"); }
}
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
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;
public RegisterPage()
{
InitializeComponent();
InitializeView();
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);
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void InitializeView()
{
_registerController = new RegisterController();
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;
_registerController.WebServer =
App.Current.Resources["WebServer"].ToString();
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
LayoutRoot.DataContext = _registerController;
this.txtEmailAddress.IsTabStop = true;
this.txtEmailAddress.Focus();
}
public void GotoUpdateProfilePage(
MembershipDataModel membershipInformation)
{
UpdateProfilePage updateProfile =
new UpdateProfilePage(membershipInformation);
UIElement applicationRoot = Application.Current.RootVisual;
MainPage mainPage = (MainPage)applicationRoot;
if (mainPage == null) throw new NotImplementedException();
mainPage.NavigateToPage(updateProfile);
}
public void GotoLoginPage()
{
LoginPage loginPage = new LoginPage();
UIElement applicationRoot = Application.Current.RootVisual;
MainPage mainPage = (MainPage)applicationRoot;
if (mainPage == null) throw new NotImplementedException();
mainPage.NavigateToPage(loginPage);
}
void ShowDialog(string errorMessage)
{
DialogBox dlg = new DialogBox(errorMessage);
dlg.Title = "Registration Errors";
dlg.Closed += new EventHandler(OnErrorDialogClosed);
dlg.Show();
}
private void OnErrorDialogClosed(object sender, EventArgs e)
{
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);
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();
}
public event PropertyChangedEventHandler PropertyChanged;
public override void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public void GotoLoginPage(object sender, RoutedEventArgs e)
{
GotoLoginPageHandler();
}
public void CreateRegistration(object sender, RoutedEventArgs e)
{
_guiHelper.SetWaitCursor(sender);
CreateMemberRegistration();
}
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);
}
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());
}
public void OriginalPasswordChangedHandler
(Object sender, RoutedEventArgs args)
{
PasswordBox p = (PasswordBox)sender;
this.Password = p.Password.ToString();
}
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
.
public event PropertyChangedEventHandler PropertyChanged;
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.
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.
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();
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();
}
public void GotoUpdateProfilePage(MembershipDataModel membershipInformation)
{
_membershipInformation = membershipInformation;
_registerController.WCFCallCompleted = true;
}
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.
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.