65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (7投票s)

2009年11月7日

CPOL

13分钟阅读

viewsIcon

83775

downloadIcon

1493

本文介绍了构建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链接。

How to update ria services link after renaming web application.


此外,更新Ria services链接后,我从Silverlight项目(`Generated_Code`文件夹)中删除了自动生成的文件`BusinessApplication1.Web.g.cs`,并重新构建解决方案——自动生成的文件将以不同的名称`BASample.Web.g.cs`重新创建。成功构建意味着所有更改都已正确完成。

向解决方案添加.NET RIA Services类库。

我已经安装了Ria Services框架(你可以在此处找到),并且在Silverlight模板列表中看到了两个额外的项目模板。

图2. Ria Services Silverlight项目模板列表。

List of Silverlight project templates


我选择“.NET RIA Services 类库”,输入项目名称`BASample.Data`,然后点击“确定”将其添加到解决方案。它将创建一个解决方案文件夹`BASample.Data`,并在其中添加两个额外的项目。

然后我更新了RIA Services类库的中间层和客户端层的程序集名称、根命名空间和项目名称(详见图3、4)

图3. 添加的.NET RIA Services类库中间层的属性。

Properties of the middle tier of added .NET RIA Services Class Library


图4. 添加的.NET RIA Services类库客户端层的属性。

Properties of the client tier of added .NET RIA Services Class Library


我为客户端层库指定了正确的.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. 此对话框允许设置添加域服务类的属性。

This dialog allows to set properties for adding domain service classes


添加的类将代替`UserRegistrationService`(参见`BASample.Web`项目)。

下一步是添加身份验证服务(选择文件夹“Domain Service” - 右键单击 - 添加新项 - Authentication Domain Service)。

图6. 此对话框允许添加新的身份验证域服务类。

This dialog allows to add new Authentication domain service class


完成上述步骤后,我转到主 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`访问。

Loading application via <code>http</code> protocol


图8. 应用程序通过`http`协议访问,但用户通过`https`协议成功认证。

Authentication via <code>https</code> protocol


图9. 用户注册对话框。

User registration


图10. 用户注册信息已成功添加到数据库(通过`http`协议),用户已通过`https`协议成功认证。

User added to DB and authenticated


已知问题

  • 如果Silverlight应用程序有任何更改,仅仅重新构建是不够的。应用程序应该在新浏览器窗口中重新启动;否则,更改将不会生效。

总结

本文面向从事业务应用程序开发的开发人员和架构师。它描述了:

  • 如何使用.NET RIA Services类库将Silverlight解决方案分层;
  • 如何建立对数据层(数据库)的访问;
  • 如何实现自定义身份验证;
  • 如何为Silverlight应用程序建立和使用https连接。

历史

  • 版本1.0 (2009-11-07) - 初始发布。
© . All rights reserved.