基于 Ria Services 和 Ria Services 类库的 Silverlight 业务应用程序骨架






4.50/5 (7投票s)
本文介绍了构建Silverlight业务应用程序的技术和注意事项。它描述了将应用程序分层,在N层应用程序中实现数据访问,实现自定义身份验证,以及向Ria Services应用程序添加https支持。
引言
真实的业务应用程序与互联网上能找到的示例完全不同。真实应用程序有许多层,因为所有架构师都会将应用程序分成多层,以降低复杂性并提高源代码的可读性。
任何Silverlight应用程序都是客户端应用程序。这意味着此类应用程序的编译代码在客户端层运行,并且必须能够访问服务器(中间)层上的数据。
从客户端层获取数据有多种方式:
- Web Services(Asp.Net web services, Wcf等)
- Ado.Net 数据服务
- .Net Remoting
- .Net Ria Services
最后一个是专门为富互联网应用程序(Ria)开发的,提供连接客户端和中间层的方法和框架。
.Net Ria Services 库增加了新的Visual Studio项目模板“Ria Services Class Library”,允许创建N层类库。
此外,.Net Ria Services 内置了使用安全连接来保护重要数据(如登录名、密码等)的能力。
在Ria Services基础上构建应用程序有一些未被描述的技巧,因为所述技术相当新,并且以预览版(2009年7月)提供。因此,我将揭示基于.Net Ria Services和Ria Services Class Library构建真实业务应用程序的秘密。
背景
在我看来,任何Silverlight业务应用程序都必须具有以下功能:
- 分离的层(中间层、客户端层)以及它们之间的数据传输;
- 访问数据层(数据库);
- 身份验证;
- 通过安全连接在各层之间传输数据。
.Net Ria Services 和 .Net Ria Services Class Library 允许实现上述所有功能。演示应用程序具有所有提及的功能,可以用作所有其他Silverlight业务应用程序的骨架。
技术要求
使用了以下软件:
- Windows XP SP3/IIS 5.1
- VS 2008 SP1
- .Net 3.5 SP1
- Microsoft Silverlight Projects 2008 版本 9.0.30730.126
- Silverlight 3.0.40818.0
- Silverlight Toolkit 2009年7月版
- Silverlight 3 开发包
- Silverlight 3 SDK。
- .NET RIA Services (2009年7月预览版)
- 来自IIS资源工具包的SelfSSL工具
- MS SQL Server 2005
- Web Development Helper 0.8.5.1 (IE 插件)
入门
本文主要关注Silverlight应用程序的后端而不是前端部分。因此,我们可以基于业务应用程序项目模板开始开发应用程序。该模板包含几个预定义的视图、登录和密码控件。此外,该模板包含几个服务(身份验证、注册),可以通过Ria services机制从客户端层调用。
注意:如果您已安装 .Net Ria Services 框架(详情请参阅下文),则可以使用业务应用程序项目模板。
解决方案结构的变化
最终,“业务应用程序项目”模板提供了一些可在实际业务应用程序中使用的源代码。但这对于实际应用程序来说是不够的。
首先,我必须向解决方案添加一种新型项目——.NET RIA Services 类库。这种方法允许在客户端层(silverlight应用程序)和中间层之间建立桥梁。
之后,我必须将服务从Web应用程序移动到RIA Services类库。这些服务将像现在一样对客户端层可用。
然后,我必须添加一个实体数据模型(数据库模型)以获取数据访问权限。客户端层和中间层之间的身份验证和流量加密也将得到支持。
因此,我的应用程序应该由四个项目组成:
BASample.Web
。这个项目是一个Web应用程序,它托管Silverlight应用程序。BASample.Data
。这个项目实现了对数据层的访问,并包含Silverlight应用程序使用的所有服务。BASample.Silverlight
。这个项目包含所有将显示给用户的控件和窗口。BASample.Data.Silverlight
。这个项目允许Silverlight应用程序访问中间层。
对已创建解决方案的更改。
通常,我会为每个创建的项目(通过项目属性窗口)更改根命名空间、程序集名称和项目名称。我将使用`BASample.Silverlight`作为命名空间、项目名称和程序集名称。
要更改 Silverlight 应用程序的命名空间,我应该同时更改 `*.cs` 文件(`namespace` 和 `using` 区域)和 `*.xaml` 文件中的命名空间。`*.xaml` 文件根元素的属性包含要更改的命名空间:
x:Class="<Namespace>.<className>"
xmlns:app="clr-namespace:<Namespace>"
*.xaml 文件根元素的示例(要更改的命名空间已高亮并加下划线)
<controls:ChildWindow
x:Class="BusinessApplication1.LoginWindow"
x:Name="childWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:dataform="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"
xmlns:app="clr-namespace:BusinessApplication1.Silverlight"
Width="400"
Title="Login"
Style="{StaticResource LogRegWindowStyle}">
我将Web应用程序的服务和其他*.cs文件的命名空间更改为`BASample.Web`。
重命名Web项目后,我应该更新Silverlight应用程序中的.Net Ria services链接。
图1. 重命名Web应用程序后如何更新Ria services链接。
此外,更新Ria services链接后,我从Silverlight项目(`Generated_Code`文件夹)中删除了自动生成的文件`BusinessApplication1.Web.g.cs`,并重新构建解决方案——自动生成的文件将以不同的名称`BASample.Web.g.cs`重新创建。成功构建意味着所有更改都已正确完成。
向解决方案添加.NET RIA Services类库。
我已经安装了Ria Services框架(你可以在此处找到),并且在Silverlight模板列表中看到了两个额外的项目模板。
图2. Ria Services Silverlight项目模板列表。
我选择“.NET RIA Services 类库”,输入项目名称`BASample.Data`,然后点击“确定”将其添加到解决方案。它将创建一个解决方案文件夹`BASample.Data`,并在其中添加两个额外的项目。
然后我更新了RIA Services类库的中间层和客户端层的程序集名称、根命名空间和项目名称(详见图3、4)
图3. 添加的.NET RIA Services类库中间层的属性。
图4. 添加的.NET RIA Services类库客户端层的属性。
我为客户端层库指定了正确的.Net Ria Services链接。正确的值是RIA Services类库中间层的名称(`BASample.Data`),因为它将包含数据实体模型。
完成这些更改后,解决方案可以成功构建。
下一步是将数据实体模型添加到解决方案。根据我的项目结构,我应该将其添加到`BASample.Data`项目下的`Model`文件夹中(选择文件夹 - 添加新项 - ADO.NET 实体数据模型)。
我选择现有数据库,取消勾选“在App.config中保存实体连接设置”(我稍后会解释原因),然后将连接字符串复制到缓冲区。在下一页中,我选择所有必需的表(在我的示例中只有一个表用于身份验证),输入模型命名空间`BASample.Data.Model`,然后点击`完成`。
注意:只有带主键的表才会被添加到模型中。
将模型添加到中间层后,我将更新`BASample.Data.Silverlight`应用程序中的.Net Ria Services链接(它应该设置为`BASample.Data`)。
提取 Ria 上下文类。
在将服务从web库移出之前,我应该将Ria上下文类从自动生成的类中提取到一个单独的类中,因为Ria上下文的实例在整个Silverlight应用程序中必须是唯一的,并且不应该放置在除`BASample.Silverlight`项目之外的任何地方(类似于web应用程序的HttpContext)。
我只需在`BASample.Silverlight`中添加一个新的`RiaContext`类,并将该类的源代码放在那里。
将服务从Web库移到单独的库中。
成功将.NET RIA Services类库添加到解决方案后,我将把服务函数从web应用程序移到`BASample.Data`中。
首先,我在`BASample.Data`中添加一个`DomainService`文件夹。这个文件夹将包含所有直接与数据模型一起工作的服务类。然后,我逐个将所有必需的服务添加到该文件夹中(选择文件夹“Domain Service” - 右键单击 - 添加新项 - Domain Service Class)。例如,表`AppUser`被映射到`AppUserService`服务类(我希望每个实体都有一个单独的类)。
图5. 此对话框允许设置添加域服务类的属性。
添加的类将代替`UserRegistrationService`(参见`BASample.Web`项目)。
下一步是添加身份验证服务(选择文件夹“Domain Service” - 右键单击 - 添加新项 - Authentication Domain Service)。
图6. 此对话框允许添加新的身份验证域服务类。
完成上述步骤后,我转到主 Silverlight 项目 `BASample.Silverlight`,并添加对 `BASample.Data.Silverlight` 项目的引用,因为它包含基于 `BASample.Data` 服务的自动生成类。
然后我开始用新的服务函数替换旧的服务函数调用。之后,我从`BASample.Silverlight`中删除到`BASample.Web`的.Net Ria Services链接,并且`BASample.Web`中的所有服务类都可以从项目中排除。此外,自动生成的类也可以从`BASample.Silverlight`项目中删除。
已知问题
第一个是在将身份验证服务从`BASample.Silverlight`项目移出后出现的。因此,我必须手动初始化Silverlight应用程序的身份验证上下文。有两种方法可以做到:通过app.xaml和通过app.xaml.cs。
`app.xaml`文件示例。身份验证上下文初始化已高亮显示。
<Application.ApplicationLifetimeObjects>
<app:RiaContext>
<app:RiaContext.Authentication>
<appsvc:FormsAuthentication DomainContextType="Pausoft.AuthSample.Data.DomainService.AuthenticationContext, Pausoft.AuthSample.Data.Client, Version= 1.0.0.0"/>
<!--<appsvc:WindowsAuthentication/>-->
</app:RiaContext.Authentication>
</app:RiaContext>
</Application.ApplicationLifetimeObjects>
`app.xaml.cs`文件示例。身份验证上下文初始化已高亮显示。
private void Application_Startup(object sender, StartupEventArgs e)
{
this.Resources.Add("RiaContext", RiaContext.Current);
((DomainAuthentication)RiaContext.Current.Authentication).DomainContext = new AuthenticationContext();
this.RootVisual = new MainPage();
}
第二个问题与数据库和连接字符串有关。“添加新的数据实体模型”对话框允许将连接字符串存储到`.config`文件中,我对此发出了警告并建议将连接字符串复制到缓冲区。这个问题的根源在于连接字符串需要在Web应用程序运行时可用。这意味着`BASample.Data.Silverlight`的`app.config`文件中的连接字符串不会被Web应用程序读取,并且会引发异常**“在配置中找不到指定的命名连接,或者不打算与EntityClient提供程序一起使用,或者无效。”**。
通过将连接字符串移动(添加)到`BASample.Web`项目的`web.config`文件,可以解决此问题。
`web.config`文件示例。连接字符串已高亮显示。
<connectionStrings>
<add name="AuthSampleEntities" connectionString="Past_Connection_String_Form_Clipboard_Here" providerName="System.Data.EntityClient" />
</connectionStrings>
在开发Silverlight业务应用程序时,您可能会遇到其他一些问题:
- Linq to Entities不支持所有Linq to Objects操作,因为它们无法转换为SQL;
- Web应用程序应该引用`BASample.Data`项目以访问服务操作。
- 如果数据库表包含`Identity`字段,则这些字段必须在数据模型中具有适当的属性。
<Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
- 如果您收到异常“Exception has been thrown by the target of an invocation”,请检查绑定参数:UI控件应该使用正确的实体字段名称进行绑定。
Ria应用程序的自定义身份验证。
为了实现自定义身份验证,我应该覆盖`AuthenticationService`类中的一些虚方法。首先,我应该覆盖初始化和销毁的方法,并添加对AppUserService的引用(我将用它来检查用户名/密码)。
private AppUserService appUserService = new AppUserService();
public override void Initialize(DomainServiceContext context)
{
appUserService.Initialize(context);
base.Initialize(context);
}
protected override void Dispose(bool disposing)
{
if (disposing)
this.appUserService.Dispose();
base.Dispose(disposing);
}
然后我应该覆盖实现用户验证的方法。
protected override bool ValidateUser(string userName, string password)
{
return appUserService.IsValidAppUserNamePassword(userName, password);
}
protected override UserIdentity GetAuthenticatedUser(IPrincipal principal)
{
UserIdentity user = CreateUser();
if (this.appUserService.DoesAppUserExist(principal.Identity.Name))
{
AppUser appUser = appUserService.LoadAppUserByName(principal.Identity.Name);
user.Name = appUser.Name;
user.AuthenticationType = principal.Identity.AuthenticationType;
}
return user;
}
因此,这些更改允许轻松实现任何业务应用程序的自定义身份验证。
Ria Services操作使用Https协议
Ria服务基于Rest协议,因此它不加密客户端层和中间层之间的数据传输。
尽管如此,Ria服务还是内置了一些选项来支持安全连接。例如,每个服务类的`EnableClientAccess`属性都有一个参数`RequiresSsl`。这个布尔属性表示该类是否只能通过https访问。此功能对于处理安全数据(例如身份验证服务、财务数据等)的服务非常重要。
注意:Visual Studio内置的Web服务器(Cassini)不支持https协议。因此,应该使用IIS。
使用`EnableClientAccess`保护`AuthenticationService`类所有操作的示例。
[EnableClientAccess(RequiresSsl = true)]
public class AuthenticationService : AuthenticationBase<UserIdentity>
{
...
}
注意:如果我使用https来托管Silverlight应用程序(`BASample.Web`),那么无论`EnableClientAccess`属性使用了什么参数,Silverlight客户端和中间层之间的通信都将使用相同的协议。
已知问题
- 应用程序的虚拟文件夹应启用集成 Windows 身份验证选项(在 IIS 中选择虚拟文件夹 - 右键单击 - 属性 - 目录安全选项卡 - 编辑匿名访问和身份验证控制 - 勾选集成 Windows 身份验证选项);
- IE6和IE7(对IE8一无所知)在URL基于安全协议时会引发异常(详见此处)。其他浏览器运行良好。
向 IIS 添加安全证书
为了利用安全连接的优势,我应该在IIS中安装一个安全证书。
创建和部署安全证书最简单的工具是SelfSSL(你可以在Internet Information Services (IIS) 6.0 Resource Kit Tools中找到它)。这个工具也适用于IIS 5.1。
我使用以下命令行来安装自签名证书:
selfssl.exe /T /V:730 /S:1
- /T - 将自签名证书添加到“受信任的证书”列表。
- /V:730 - 指定证书的有效期(天)。
- /S:1 - 指定站点的ID。Web站点的ID显示在IIS控制台 - Web站点列表中的`Identifier`列。
- /N:CN=domainname - 指定证书的通用名称。默认情况下使用计算机名称。对我来说没问题,我没有在命令行中指定它。
跨域场景
如上所述,`AuthenticationService`将仅使用`https`连接。但Silverlight应用程序是通过http协议加载的。这意味着应用程序从另一个域调用服务(使用http和https协议的两个应用程序虽然托管在同一个虚拟文件夹中,但属于不同的域),这是一种跨域场景。
默认情况下,不允许跨域场景,应用程序将无法工作。为避免此问题,我将创建`clientaccesspolicy.xml`。该文件包含有关哪些外部服务可以访问服务的说明。
`clientaccesspolicy.xml`文件示例。
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
上面的示例允许从任何外部域进行访问。
我将此文件放在网站的根目录(不是我应用程序的根目录!)。当Silverlight应用程序尝试通过安全通道访问服务时,将读取此文件,它将允许服务处理来自Silverlight应用程序的请求。
注意:如果我使用https来托管Silverlight应用程序(`BASample.Web`),则`clientaccesspolicy.xml`文件将不会被加载,因为客户端层和中间层在同一个域中工作。
注意:如果您计划允许非Silverlight客户端访问您的服务,您还必须使用`crossdomain.xml`文件。请参阅此处对该文件的描述。
演示应用程序
应用程序的源代码包含所有上述技术,并已准备好编译和部署。在运行之前,开发人员需要更改一些内容:
- 为Web应用程序创建虚拟文件夹;
- 部署数据库(数据库示例请参阅DB文件夹)并更新`web.config`中的连接字符串;
- 使用SelfSsl工具创建并部署安全证书;
此应用程序允许打开登录对话框,输入正确的凭据并根据数据库中的凭据进行身份验证。之后,用户可以注销。此外,应用程序允许在数据库中添加新成员,并使用添加的凭据登录。
图7. 应用程序通过`http`协议访问,但身份验证服务通过`https`访问。
图8. 应用程序通过`http`协议访问,但用户通过`https`协议成功认证。
图9. 用户注册对话框。
图10. 用户注册信息已成功添加到数据库(通过`http`协议),用户已通过`https`协议成功认证。
已知问题
- 如果Silverlight应用程序有任何更改,仅仅重新构建是不够的。应用程序应该在新浏览器窗口中重新启动;否则,更改将不会生效。
总结
本文面向从事业务应用程序开发的开发人员和架构师。它描述了:
- 如何使用.NET RIA Services类库将Silverlight解决方案分层;
- 如何建立对数据层(数据库)的访问;
- 如何实现自定义身份验证;
- 如何为Silverlight应用程序建立和使用https连接。
历史
- 版本1.0 (2009-11-07) - 初始发布。