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

使用 WCF RIA Services 类库的 Silverlight 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (11投票s)

2010年3月3日

CPOL

8分钟阅读

viewsIcon

63857

downloadIcon

1788

一个使用 WCF RIA Services 类库的 Silverlight 应用程序。演示了如何实现自定义的授权服务、利用本地化资源以及为 DAL 添加单元测试。

引言

最好通过创建原型来熟悉新技术,并采用基本的架构模式。对于复杂的应用程序,这涉及

  • 分层(分层)
  • 用户授权
  • 异常处理和审计
  • 资源本地化
  • 单元测试

等等。但对于初学者来说,这已经足够了。

文章中介绍的示例使用 WCF RIA Services 类库将业务逻辑组件分组到单独的项目中。事实证明,这项任务并非易事,更何况 WCF RIA Services 代码库中的大多数示例都不使用此库 - 无法借鉴!:)

这些示例是很好的起点,但我需要一些不同的代码。首先,我想使用自己的数据库进行用户授权。此外,我决定使用 LINQ to SQL 作为我的数据访问层 (DAL),并实现存储库模式,以提供无依赖的数据访问并简化单元测试。

最后,资源本地化本身并非一项艰巨的任务,但需要一些手工操作。

构建示例后,我们将看到以下内容

Business Application

先决条件

但在构建应用程序之前,我想列出我在项目中所使用的东西

  • Microsoft Visual Studio 2008 SP1
  • Microsoft Silverlight 3
  • Microsoft Silverlight 3 SDK
  • Microsoft Silverlight 3 Toolkit 2009 年 11 月
  • Microsoft Silverlight 3 Tools for Visual Studio 2008 SP1
  • WCF RIA Services Beta
  • SQL Server 2008 Express

项目结构

该解决方案包含以下项目

Project structure

BizApp.Web 托管此 Silverlight 应用程序的 Web 项目。这是我们的服务器项目。
BizApp Silverlight 客户端应用程序。
BizApp.Controls 辅助项目,包含 UI 控件。
BizApp.Services.Server WCF RIA Services 类库。服务器端项目。
BizApp.Services WCF RIA Services 类库。客户端项目。包含生成的代码;如果您单击“显示所有文件”图标,则可见。
BizApp.Services.Test 单元测试。

存储库和单元测试

存储库模式用于将数据访问方法封装在抽象层中。这使得创建实现 IRepository 接口的模拟对象成为可能,并在单元测试中使用它们,而不接触真实数据库。

我以 Testing Domain Services 示例和 Vijay 的文章 Unit Testing Business Logic in .NET RIA Services 为基础。唯一需要做的就是初始化存储库类,而不是硬编码。为此,我使用了 Microsoft Unity Application Block 1.2 实现的依赖注入。我认为这需要一些解释。

首先,我的服务(AuthenticationServiceUserRegistrationService)使用以下用 Dependency 特性装饰的属性来访问数据存储库

[Dependency]
public IRepository<UserAccount> UserAccountRepository { get; set; }

[Dependency]
public IRepository<UserRole> UserRoleRepository { get; set; }

但是,服务如何初始化这些属性?这发生在 DomainServiceFactory 类创建服务期间

public DomainService CreateDomainService(Type domainServiceType, 
                     DomainServiceContext context)
{
    var domainService = 
      DependencyInjectionProvider.UnityContainer.Resolve(
      domainServiceType) as DomainService;
    domainService.Initialize(context);

    return domainService;
}

您无需显式调用此方法。您只需要在 Global.asax.cs 文件中初始化我们的 DomainServiceFactory

protected void Application_Start(object sender, EventArgs e)
{
    // Create data context for all services
    AppDatabaseDataContext dataContext = new AppDatabaseDataContext();

    // Configure unity container
    DependencyInjectionProvider.Configure(
       (System.Data.Linq.DataContext)dataContext);

    // Set new domain service factory, creating domain services
    DomainService.Factory = new DomainServiceFactory();
}

回到 CreateDomainService() 方法,我想指出 UnityContainer 使用我们在 unity.config 文件中的指令来解析遇到的依赖项。特别是,IRepository<UserAccount> 接口被映射到 LinqToSqlRepository<UserAccount> 类。

但是,如果您查看该类,您会发现它可以使用一个类型为 DataContext 的参数的公共构造函数来实例化。诀窍在于 UnityContainer 已经知道这一点,并且将使用 AppDatabaseDataContext 来初始化 LinqToSqlRepository<UserAccount> 对象(请参阅 DependencyInjectionProvider.Configure() 方法)。因此,我们的存储库被配置为使用从数据库生成的 AppDatabaseDataContext

现在,我们可以测试我们的服务,用模拟对象替换存储库。打开包含单元测试的 BizApp.Services.Test 项目。每个测试都使用实现 IRepository<T> 接口的模拟存储库来初始化测试服务。有关更详细的描述,请参阅 Vijay 的博客

用户授权

此示例使用表单身份验证模式,即,用户输入其登录名和密码,然后程序尝试通过浏览 AppDatabase.mdf 数据库中的用户帐户列表来识别用户。身份验证逻辑在公开 IAuthentication<User> 接口的 AuthenticationService 类中实现。要启用身份验证,我们必须在 App.xaml 文件中指定从我们的身份验证域服务生成的域上下文

<Application.ApplicationLifetimeObjects>
    <app:WebContext>
        <app:WebContext.Authentication>
            <appsvc:FormsAuthentication 
                DomainContextType=
                  "BizApp.Services.Server.DomainServices.AuthenticationContext, 
                   BizApp.Services, Version= 1.0.0.0"/>
        </app:WebContext.Authentication>
    </app:WebContext>
</Application.ApplicationLifetimeObjects>

该服务的主要功能是 Login() 方法。而该方法的关键点在于语句

FormsAuthentication.SetAuthCookie(userName, isPersistent);

MSDN 指出,它会将表单身份验证票证添加到 cookie 集合或 URL 中,并且该票证会向浏览器下次发出的请求提供表单身份验证信息。换句话说,您无需费力识别用户 - WCF RIA Services 会为您完成。您需要的所有信息都存储在 ServiceContext.User.Identity 属性中。或者,您可以调用 GetUser() 方法来获取已通过身份验证的用户。

对于更喜欢探究的开发人员,我建议安装 FiddlerWCF Binary-encoded Message Inspector for Fiddler,并尝试捕获 Silverlight 应用程序和服务器之间的 HTTP 流量。但请注意一个缺点 - Fiddler 无法捕获本地流量(请参阅 故障排除)。要解决此问题,只需在浏览器中的 localhost 后面加上一个点

https://.:3121/BizApp.aspx

启动 Fiddler。运行应用程序,创建一个用户帐户,然后登录。在 Fiddler 中选择应用程序发出的身份验证请求。如果您查看服务器响应,您将看到类似以下的授权 cookie

Authorization Cookie

存储在 cookie 中的表单身份验证票证使用计算机的密钥进行加密和签名。因此,这些信息可以被认为是安全的。有关表单身份验证 cookie 的更详细信息,请参阅 此处

异常处理

迟早,您将不得不设计您的异常管理策略。WCF RIA Services 提供了一种在 DomainService.OnError 方法中处理服务器端发生的异常的方法。为了执行异常日志记录,我继承了我的服务,使其派生自 DomainServiceBase 类,并重写了 OnError 方法。这有助于在层边界捕获异常

protected override void OnError(DomainServiceErrorInfo errorInfo)
{
   if (errorInfo != null && errorInfo.Error != null)
   {
      EventLog eventLog = new EventLog("Application");
      eventLog.Source = "BizApp";
      eventLog.WriteEntry(errorInfo.Error.Message, EventLogEntryType.Error);
   }

   base.OnError(errorInfo);
}

本地化资源

如果您运行示例,您会注意到一个用于语言选择的下拉列表

Language selection

应用程序中有两个地方包含可以本地化的文本。首先,显示在标签、按钮、复选框等上的静态文本。其次 - 用于服务层数据注释的文本资源。

UI 上的本地化文本

为了本地化 UI 元素,我们将为选定的语言创建资源文件

Resources

此外,我们必须执行以下步骤

  • 将默认资源的“自定义工具”设置为 PublicResXFileCodeGenerator。在我们的例子中是 LocalizedStrings.resx
  • 将默认资源文件的“访问修饰符”设置为 Public
  • .csproj 文件中指定 SupportedCultures

Visual Studio 不提供为项目设置 SupportedCultures 的方法。因此,我们必须手动编辑 BizApp.csproj 文件。在编辑器中打开它,并添加一个带有您要使用的语言的标签

<SupportedCultures>de-de;es-es;fr-fr</SupportedCultures>

我创建了 ApplicationResources 类,其中包含 LocalizedStrings 属性。然后,我通过将其添加到 App.xaml 文件中的应用程序资源中,使其可供整个应用程序使用

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Assets/Styles.xaml"/>
            <ResourceDictionary>
                <res:ApplicationResources x:Key="LocalStrings" />
            </ResourceDictionary>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

现在,我们可以在 XAML 中使用我们的本地化字符串

<Button Content="{Binding Source={StaticResource LocalStrings}, 
                 Path=LocalizedStrings.LoginButton}"
    Click="Login_Click" Width="75" Height="23" />

要更改 UI 语言,我们必须更改当前线程中的区域性并重置资源(请参阅 AppMenu.xaml.cs

private void Language_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cbxLanguage != null)
    {
        ComboBoxItem item = cbxLanguage.SelectedItem as ComboBoxItem;
        Thread.CurrentThread.CurrentCulture = 
                             new CultureInfo(item.Content.ToString());
        Thread.CurrentThread.CurrentUICulture = 
                             new CultureInfo(item.Content.ToString());
        ((ApplicationResources)Application.Current.
          Resources["LocalStrings"]).LocalizedStrings = 
          new BizApp.Resources.LocalizedStrings();
    }
}

服务层上的本地化文本

System.ComponentModel.DataAnnotations 命名空间提供了一种便捷的方式来用元数据装饰我们的数据以进行验证(例如,请参阅 RegistrationData.cs

[Display(Name = "FullNameLabel", Description = "FullNameDescription", 
    ResourceType = typeof(RegistrationDataResources))]
[StringLength(100, ErrorMessageResourceName = "ValidationErrorBadFullNameLength", 
    ErrorMessageResourceType = typeof(ErrorResources))]
public string FullName { get; set; }

消息存储在资源文件中,可以进行本地化。创建资源文件不是问题 - 如果您使用 WCF RIA Services 类库,编译它是问题。BizApp.Services 项目包含一些代码,这些代码是从 BizApp.Services.Server 项目中定义的类生成的。但是其中一些可能包含对 BizApp.Services 项目中不存在的资源的引用。我将向您展示如何解决此问题。

首先,BizApp.Services.Server 项目中的资源应具有 Public 访问修饰符。然后,我们必须在 BizApp.Services 项目中创建一个名为 Server\Resources 的文件夹。请注意,文件夹结构必须与 BizApp.Services.Server 项目中的资源文件命名空间匹配!

下一步是将资源添加到 BizApp.Services。选择 Server\Resources 文件夹,然后调出 “添加现有项...” 对话框。选择 *Resources.resx*Resources.Designer.cs 文件,并将它们作为链接文件添加到 BizApp.Services 项目。保存项目并将其从解决方案中卸载。在编辑器中打开 BizApp.Services.csproj,并找到包含我们的 *Resources.Designer.cs 文件的部分

<Compile Include="..\BizApp.Services.Server\Resources\ErrorResources.Designer.cs">

添加 <AutoGen><DesignTime><DependentUpon> 子节。最终结果应如下所示

<Compile Include="..\BizApp.Services.Server\Resources\ErrorResources.Designer.cs">
   <AutoGen>True</AutoGen>
   <DesignTime>True</DesignTime>
   <DependentUpon>ErrorResources.resx</DependentUpon>
   <Link>Server\Resources\ErrorResources.Designer.cs</Link>
</Compile>

现在,您可以在 Visual Studio 中重新加载项目,构建它,然后运行它。

结论

我的文章不声称具有新颖性或完整性。它更多地收集了我构建此应用程序时找到的食谱或操作指南。下面,您将找到我的一些知识来源:)

参考文献

历史

  • 2010 年 3 月 3 日:初稿。
© . All rights reserved.