开发松散耦合的 Silverlight 3 应用程序






4.94/5 (19投票s)
使用 Silverlight 3 创建一个业务线应用程序。
概述
“这是一个最好的时代,这是一个最坏的时代。”这是查尔斯·狄更斯在他 1859 年的小说《双城记》开头的名句。同样的话也适用于 2009 年的 Microsoft .NET 开发者。经济可能已经跌至谷底,但就新软件开发技术发布的数量或处于 Beta 模式的最后阶段而言,Microsoft .NET 开发者现在(比以往任何时候都)拥有大量的技术选择。对于那些热爱学习新技术的人来说,这是最好的时代。Microsoft Silverlight 是一项不断发展的新技术。Silverlight 是一种可编程的 Web 浏览器插件,它能够提供丰富的 Internet 内容,类似于 Adobe 的 Flash 技术。随着最新版本的 Silverlight 的发布,Microsoft 已经更进一步。通过 Silverlight 3 版本,开发人员现在可以开始开发丰富的 Internet 业务线应用程序。
示例应用
本文的示例应用程序将演示一个使用松散耦合架构的 Silverlight 3.0 应用程序。松散耦合架构意味着应用程序的各个组件可以独立且并行地开发和测试。这意味着 .NET 开发者、Web 图形设计师和质量保证自动化工程师可以同时处理一个应用程序,而无需互相等待。所有这些都能带来更高质量的软件和更短的开发发布周期。本文的示例应用程序是一个会员应用程序。本文将引导您完成许多社交网站使用的典型注册过程。
松散耦合架构
在不深入探讨设计模式术语的情况下,此应用程序的松散耦合架构将实现独立的 Silverlight 类,称为控制器(Controllers),它们将与 Silverlight 前端 GUI 进行通信。Silverlight 前端也称为视图(View),将包含 XAML 标记文件和代码隐藏类。控制器不会直接引用或调用视图。控制器和视图将通过数据绑定、事件路由和委托的实现进行通信。这样做的好处是控制器可以独立于 GUI 进行开发和测试。这使得 GUI 设计师可以使用 Expression Blend 等工具生成应用程序的所有 XAML,同时,.NET 软件工程师可以使用 Visual Studio 处理应用程序的其余部分。
共享数据模型类
应用程序开发通常从定义数据实体和设计数据库开始。对于此示例应用程序,主要数据实体是会员信息。在为该应用程序构建数据库表后,将创建以下类,该类公开底层数据模型。
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)
{
}
}
图 3 中的数据模型类包含将在示例应用程序中引用的公共属性。此课堂将在 Silverlight 客户端应用程序和服务器端业务类及数据类中使用。在 Silverlight 应用程序中引用的类必须创建为 Silverlight 类库。Silverlight 使用 Microsoft .NET Framework 标准的子集,因此它只能使用 Silverlight 类。幸运的是,标准 .NET 类可以消耗 Silverlight 类,但有一些限制。共享类不应实现仅 Silverlight 的命名空间。
正如您在图 3 中看到的,数据模型类实现了 PropertyChangedEvent
,并在设置属性值时引发 RaisePropertyChanged
事件。由于数据模型类将被共享,因此当被服务器端业务和数据层引用时,该事件将不实现任何功能。在 Silverlight 客户端实现时,该事件将被覆盖,实现将启用 Silverlight XAML 标记和控制器之间的数据绑定。控制器将简单地继承此类并实现 INotifyPropertyChanged
接口,该接口启用双向数据绑定。双向数据绑定允许两个绑定的属性互相更改。
注册页面
使用此应用程序的第一步是单击图 1 中登录页面的“注册”链接来注册成为会员。
图 4 中的注册页面将通过数据绑定与图 7 中的注册控制器进行通信。启用数据绑定的第一步是使用以下语法在 RegisterPage.xaml 文件中设置每个字段的 Text
属性。
<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}" />
在上面的图 5 中,当用户在此字段中输入数据时,txtLastName
TextBox
将通过 Binding 命令自动填充控制器类中的 LastName
属性。
下一步是在代码隐藏文件 RegisterPage.xaml.cs 中连接注册控制器类,如图 6 所示,如下所示。
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;
}
}
在上面的图 6 中,创建了注册控制器,并通过在 Page_Loaded
事件中将 Layroot.DataContext
设置为注册控制器来将其绑定到 XAML。
下一个要连接的项目是通过注册按钮的 Click
事件连接注册按钮。
this.btnRegister.Click += new RoutedEventHandler(_registerController.CreateRegistration);
当用户按下注册按钮时,将执行注册控制器中的 CreateRegistration
方法。
密码框
Silverlight 3 中 PasswordBox
控件的功能已发生变化。在 Silverlight 2 中,我无法直接将 XAML 中的密码字段绑定到注册控制器中的属性。事实证明,Silverlight 2 中的 PasswordBox
是加密的,需要一个依赖项属性和一个值转换器才能将其值传递给控制器。所有这些听起来有点复杂,所以我决定简单地连接 PasswordBox
的 PasswordChanged
事件,并将事件路由到注册控制器,以填充注册控制器类中的 Password
属性,如下所示。
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();
}
在 Silverlight 3 版本中,这已得到修复。现在可以通过绑定 PasswordBox
的 Password
属性来直接将加密的 PasswordBox
控件绑定到业务对象中的属性,如下所示。
<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>
注册控制器
此时,注册控制器已准备好处理用户输入的会员信息,并调用 WCF Web 服务进行验证。如果所有信息都有效,WCF 服务将继续在数据库中注册用户。
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();
}
}
图 7 中的注册控制器继承了包含所有数据属性的 MembershipDataModel
类,并实现了 INotifyPropertyChanged
接口。INotifyPropertyChanged
接口是启用注册页面 XAML 标记和注册控制器类之间的双向数据绑定所必需的。您可能并不总是需要实现双向数据绑定,但经验法则通常是包含其实现。
public class RegisterController : MembershipDataModel, INotifyPropertyChanged
注册控制器还通过覆盖和实现 MembershipDataModel
中的 PropertyChanged
事件来实现双向绑定。
/// <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));
}
}
调用 WCF 服务
一旦开发人员在 Visual Studio 中手动创建了 WCF 服务引用,注册控制器类就可以通过创建代理对象来简单地调用 WCF 服务的 CreateRegistration
方法。在图 7 中,CreateMembershipInformation
方法异步调用 CreateRegistration
WCF 服务。这样做可以防止 WCF 服务执行时间过长时 Silverlight GUI 挂起或冻结。
在开发这段代码时,我遇到了一些关于从 Silverlight 调用 WCF 服务的注意事项:
- BasicHttpBinding - 当在 Visual Studio 中手动创建 WCF 服务引用时,我收到一个错误,提示 Silverlight 客户端只支持 BasicHttpBinding 协议。事实证明,我必须更改 Web 服务器项目中的 web.config 文件的默认设置,将其更改为 BasicHttpBinding。
- 安全访问策略 - Silverlight 中包含的安全策略系统旨在防止网络威胁和攻击。此外,策略系统还旨在让管理员更好地控制远程客户端可以连接到哪些资源。必须在托管 WCF 服务的 Web 域的根目录下放置一个 ClientAccessPolicy.xml 文件。例如,如果您的网站是 mywebsite.com,则该文件必须位于 http://mywebsite.com/clientaccesspolicy.xml。
- 通信错误 – 如果 WCF 服务在调用 Web 服务时引发了故障异常错误,浏览器将拦截此异常,Silverlight 运行时将无法捕获该异常。发生这种情况时,Silverlight 应用程序将直接崩溃。在花费过多时间研究此问题的解决方案之前,我决定创建一个 WCF 帮助类(如图 8 所示),该类继承自 reference.cs 文件中的 WCF 服务类。由于 reference.cs 是一个自动生成的类文件,当您创建 WCF 服务引用时,不应修改此文件。在我的 WCF 帮助类中,我实现了 Begin 和 End 服务方法,并提供了自己的
try
/catch
块来捕获通信错误。
/// <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;
}
}
图 8 中的 ValidationInformation
对象引用是 MembershipDataModel
类中的一个嵌套类。try
/catch
块捕获异常,将布尔值 HasErrors
设置为 true
,并将异常消息记录在 ErrorMessage
属性中。服务完成后,客户端可以检查 HasErrors
布尔值并采取适当的措施。
InitParams
将此值传递给 Silverlight。在进行 WCF 服务调用之前,注册控制器将设置并覆盖端点地址,如下所示。
EndpointAddress endpoint = new
EndpointAddress("http://" +
this.WebServer + "/Ag3DemoWebServer/Ag3DemoWCFService.svc");
BasicHttpBinding binding = new BasicHttpBinding();
Ag3DemoControllers.WCFHelper wcfProxy =
new Ag3DemoControllers.WCFHelper(binding, endpoint);
WCF 服务完成回调
当 WCF 服务完成后,将执行 CreateRegistration_Completed
回调方法。
/// <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());
}
在此方法中,将检查嵌套验证类中的 MemberCreated
属性,以确定成员是否已创建。当成员创建后,注册控制器需要通知 Silverlight 应用程序导航到并加载会员配置文件页面。如果发生错误,注册控制器将通知 Silverlight 应用程序显示一个带有错误消息的对话框。
委托(Delegates)
由于注册控制器是松散耦合的,并且不直接了解注册页面,因此控制器需要通过委托与注册页面通信,以在新操作(如切换到另一个页面或显示对话框)发生时进行通信。
_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;
委托充当中间人,提供控制器和视图(XAML 页面)之间所需的通信。基本上,委托允许一个类调用另一个类中的代码,而不必知道该代码的位置,甚至不知道它是否存在。
单元测试
如前所述,可以在开发注册页面及其底层 XAML 标记之前开发和测试注册控制器。为了测试 Create Registration 方法,我立即连接了我最喜欢的测试工具 MbUnit。不幸的是,令我沮丧的是,我无法编译和运行 MbUnit 来针对注册控制器,因为该控制器实现了嵌入在 Silverlight 系统命名空间中的 InotifyPropertyChanged
接口。MbUnit 本身似乎不支持对创建为 Silverlight 类的类的测试。MbUnit 需要标准的 .NET System
命名空间,这与 Silverlight System
命名空间冲突。
Microsoft Silverlight 单元测试框架
在研究可以测试 Silverlight 应用程序的单元测试框架时,我发现了一些开源测试框架,例如 SilverUnit 和 TestDriven.Net,它们可能都能胜任。在阅读 Microsoft 的 Scott Guthrie 的博客时,我偶然发现了 Microsoft Silverlight 单元测试框架。Microsoft 测试框架是一个简单、可扩展的单元测试解决方案,适用于丰富的 Silverlight 2 应用程序、控件和类库。虽然没有提到此框架是否适用于 Silverlight 3,但我还是决定尝试一下。Microsoft 的测试框架易于使用。首先,我只是从互联网上的某个地方下载了以下两个命名空间。
- Microsoft.Silverlight.Testing
- Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight
第二步是创建一个标准的 Silverlight 应用程序,引用这两个命名空间,并在应用程序启动时添加以下内容:
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = UnitTestSystem.CreateTestPage();
}
在此之后,您可以继续在 Silverlight 测试应用程序中创建您的单元测试类,添加您的测试,然后运行项目。
[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;
}
}
在上面的图 10 中,示例单元测试实际上非常复杂,因为 Create Registration 方法异步执行 WCF 服务。幸运的是,Microsoft 单元测试框架通过一系列 Enqueue
调用支持异步单元测试。单元测试将布尔值 WCFCallCompleted
设置为 false
,执行 CreateRegistration
方法,然后等待 CreateRegistration
返回并执行其回调委托函数之一。返回后,将 WCFCallCompleted
布尔值设置为 true
,从而释放 Enqueue
事件继续执行。然后执行 Assert
测试方法,以确定测试是否成功执行。
Microsoft Silverlight 单元测试框架远非完美,但它支持我为本次演示需要完成的任务。Silverlight 单元测试工具仍处于起步阶段,因此我期待 Silverlight 的这一领域在未来一年内随着 Silverlight 3 的市场推广而成熟。
结论
本文演示了 Silverlight 3 的强大功能及其强大的数据绑定到对象技术。数据绑定到对象为创建可测试的松散耦合、模块化面向对象代码打开了大门。这也为 Web 设计师、.NET 开发者和 QA 自动化工程师从项目开始就并行协作打开了大门。总的来说,Microsoft Silverlight 是一项令人兴奋但又充满挑战的新技术。在开始学习 Silverlight 时,您会遇到一个接一个的挑战。由于 Silverlight 框架的体积小、XAML 标记语言的性质以及 Silverlight 的部署模型,掌握 Silverlight 将会挑战您用其他方式来实现目标。