多平台开发

可移植类库:入门

Bill Kratochvil

下载代码示例

可移植类库 (PCL) 项目将生成可由 Windows 电话 7,Silverlight,Microsoft 引用的托管程序集。NET 框架和 Xbox 360 平台。这可以最大限度地重复使用您的代码,并减少所需的项目,特别是在多个目标应用程序共享相同的基本代码,如本文附带的演示应用程序的情况。我投资在编写本文中,Windows 电话 7 应用程序的时间和 WPF/Silverlight 的应用程序一起提供的免费。唯一的限制是 PCL 不能引用特定于平台的项目。 它只能引用其他 PCL 项目。表面上可能会显示受限制,特别是对于利用棱镜和依赖项注入 (DI) 的应用程序。但通过周密规划,您可以变通解决此限制,并创建高效的 PCL 项目有助于实施良好的开发实践。

才能 PCL 项目解决方案项目可能只能引用程序集,在同一个平台。Silverlight 项目引用其他 Silverlight 的程序集。NET 项目其他。NET 程序集等。我们无法有效地编写代码,累积难以管理项目 ; 数 当创建共享基本代码 (可跨所有平台的代码),我们就不得不创建用于每个平台的项目。

多目标密码管理器解决方案

密码管理器解决方案 (passwordmgr.codeplex.com) 是一个基本代码 (由 Silverlight 和 WPF 项目链接到的电话项目文件与 Windows 电话 7 项目承载) 与多目标应用程序。正如您所见,此小应用程序都有令人望而生畏的项目数。

为什么这么多项目?这是一个受欢迎的问题,我经常听到从开发人员为我构建客户端解决方案框架。典型的注意,"它们使解决方案混乱和难于理解,"这是一个很好的参数。

我的回答始终是:"能够清除应当区分所关注的问题和以最大限度地重复使用。每个项目应该只有一个目的 (并做到这一点很好地以松散耦合的方式)。作为一个案例中,请注意在 PasswordMgr 解决方案,并能够轻松地替换为不同的数据访问层 (DAL) 使用 DI 明显分离。不过,您将不得不拉的 SQLite 程序集和关联的项目,可以重复使用此代码,即使您不打算使用 SQLite (可能您将使用 SQL Server 为您 DAL)。

一旦您引用另一个项目,项目可以成为紧密结合在一起,迫使您将不仅,而且也是,任何依赖项拖动到其他项目。如果您仅有几个项目,它使越来越多地更难重用其他解决方案中的代码。

PCL 可以显著减少您需要管理,尤其是当您想要清楚问题使您能够方便地重新利用其他模块或解决方案中的项目的分隔的项目数。关键是要使您通过编程接口与松散耦合的项目。这将允许您使用 DI 框架如托管扩展框架 (MEF) 和集体,这使您可以轻松地配置的接口的实现。即,DAL 实现的接口可以是 SQL Server、 云或 SQLite 类。

一旦安装系统必备组件 (在 MSDN 文档中所述bit.ly/fxatk0),您必须在创建 PCL 的"添加新项目"下的新功能。

使用 DI 框架

当您尝试使用这些功能强大 DI 框架时,将快速出现的问题"如何使用 PCL 与这些框架如果我不能引用 DI 框架组件 — — 即的 [依赖性] 或 [导出] 属性?"例如,下面的代码 SecurityViewModel 的集体 [依赖性] 属性,它将解决 ISecurityViewModel 的实现:

namespace MsdnDemo.MvpVmViewModels
{
  public class SecurityViewModel : PresentationViewModelBase
  {
    [Dependency]
    public ISecurityViewModel UserInfo { get; set; }

    public bool IsAuthenticated
    {
      get { return UserInfo.IsAuthenticated; }
      set
      {
        UserInfo.IsAuthenticated = value;
        OnPropertyChanged("IsAuthenticated");

        if(value)
        {
          IsAdmin = UserInfo.IsInRole("Admin");
          IsGuest = UserInfo.IsInRole("Guest");
        }
      }
    }
  }
}

您只能引用其他 PCL 项目,因为它看起来可能不切实际使用 PCL DI 或其他共享的资源,必须将可重用的库中。 在实际情况下,此约束实际上有助于强制清除应当区分所关注的事项,使您的项目甚至可重用性,您可能需要在以后的项目将不会利用 DI 但无法受益于 PCL 项目。

我将演示 PCL 使用 DI 使用演示应用程序所涉及的原则。 它是一个简单应用程序有两个模块 (来宾和主) 所加载的按需使用基于角色的安全性。 通过连接到模块和登录的用户的角色决定可用的功能,这些模块和视图。 There are three users: Admin, Guest and Jane Doe (user). 业务规则是来宾帐户永远不可以访问主模块。

此应用程序的神奇在于它十分简单。 您将发现没有代码隐藏中的视图或通过用户界面要求污染的域对象。 ViewModels 包含特定于用户界面的状态和演示者管理与通过使用的业务逻辑和 DALs 他们关心视图/ViewModels。 由基础结构如按钮单击释放,开发人员可以专注于业务逻辑处理管道。 新的应用程序的开发人员将快速了解从何处开始查找代码总是启动与演示者。

紧耦合的组件

所示的只有紧密结合图 1。 这种耦合是应与使用棱镜和 DI 演示者视图模型 ViewModel (MVPVM) 模式。 使用此模式中,模块负责演示者,又实例化所需的视图和 ViewModel,布线之内根据需要进行实例化。 模块和它们的组件在彼此不了解。

Tightly Coupled Components

图 1紧密耦合的组件

理想情况下会创建单独的项目,每个模块 (ApplicationController、 主和访客),以便我可以再次使用这些模块 (该框架中) 在其他解决方案中。 (注: 因为 GuestPresenter 和 MainPresenter MainViewModel,我还必须将此 ViewModel 移动到无法访问的两个都共享项目。 您可以看到速度有多快可以积累项目的名称中分离问题和可重复使用性,尤其是当我被编码为多个平台。您还将看到因为我我演示保持简单,它提供了唯一的可重用性是复制和粘贴。 关键找到平衡点,和 PCL 帮助提供的。

图 2演示所有图层 (演示文稿、 业务和数据) 共享 PCL 资源。 因为安全是几乎所有应用程序的问题,我可以安全地集成 — 其实体 — — 在 UC0100,这将是我 PCL,可能是此演示应用程序只可重用组件的概念 (和有用的那种,这点)。

All Layers and Shared PCL Resources

图 2的所有图层和共享的 PCL 资源

它超出了范围,以涵盖所有方面的管理我的基础结构 ; 使用 PCL 但是,我会介绍以蓝色突出显示的使用案例图 3

PCL Use Cases

图 3PCL 使用案例

UC0100-020 基用例

为了提高企业应用程序中的可扩展性,最好有一致的标准的平台上的配线。 这将有助于新开发人员,以及有经验的开发人员可能不需要经常模块一段时间。 他们可以斜向提刀快速获取所需的任务完成。 将求职下代码所用最少的时间。 明确这一点后,创建用于处理棱镜 IModule 接口 ModuleBase 更具体地说,使用初始化方法。 此处的问题是 IModule 变为不可用,因为它位于棱镜 (非-PCL) 程序集。 我希望基站在有日志记录功能,因此 ILogger 接口必须提供一个实例之前调用初始化方法。 此 ModuleBase 现在可以作为合同的所有模块,在所有解决方案和项目,因为它实现 IModule 初始化方法时,所示图 4

图 4ModuleBase 类中的初始化方法

public class ModuleBase
{
  public ILogger Logger { get; set; }

  /// <summary>
  /// Called by Prism catalog manager.
Provides hook 
  /// to register types/views and initialize the view model.
/// </summary>
  public virtual void Initialize()
  {
    try
    {
      // Provide hooks for registrations
      RegisterTypes();
      RegisterViews();

      InitializeModule();
    }
    catch (Exception ex)
    {
      Logger.Log("ERROR in [{0}] {1}{2}", 
        GetType().Name, ex.Message, ex.StackTrace);
    }
  }

PCL,与 MsdnDemo.Phone 项目不会具有特定于平台的对引用我棱镜的程序集,以便 DI 代码可以驻留在 PresentationModuleBase 类中此项目。 它将所有模块的类的基类。 在实际应用中,此类,如其他 DI 基类,驻留在单独的可重复使用项目中。

当解决 (实例化) 由 DI 容器的模块,则它将按我 GwnBootstrapper 配置设置记录器。 设置记录器属性值后,它会将该实例传递到基记录器,有效地提供 ModuleBase ILogger 的初始化 (及其他) 的引用的虚方法 (请参阅图 5)。

图 5记录器属性设置

public class PresentationModuleBase : ModuleBase, IModule
{
  [Dependency]
  public override ILogger Logger {get;set;}

  [Dependency]
  public IUnityContainer Container { get; set; }

  [Dependency]
  public IRegionManager RegionManager { get; set; }

  [Dependency]
  public IEventAggregator EventAggregator { get; set; }

  [Dependency]
  public IRegionViewRegistry RegionViewRegistry { get; set; }

(注: Because constructor injection occurs before setter injection—in the Unity framework—the constructor of ModuleBase can’t have logging statements, because it will be null.)

从 PresentationModuleBase 派生的下面的 MainModule 代码是同一个代码/文件的所有三个平台 (Windows 电话 7、 Silverlight 和 WPF):

public class MainModule : PresentationModuleBase
{
  protected override void RegisterViews()
  {
    base.RegisterViews(); 

    // Instantiate the presenter, which in turn will instantiate
    // (resolve) the View and ViewModel
    var presenter = Container.Resolve<MainPresenter>();

    // Load this presenters view into the MainRegion
    RegionViewRegistry
      .RegisterViewWithRegion(MvpVm.MainRegion, () => presenter.View);

    // Activate the presenters view after module is loaded
    RaiseViewEvent(MvpVm.MainModule, presenter.View.GetType().Name,      
      ProcessType.ActivateView);
  }
}

UC0200-050 实体使用案例

使用普通旧 CLR 对象 (POCOs) 提供了最佳的可重用性。 即使 PCL 支持 INotifyPropertyChanged,我可能不总是使用这些实体与 XAML。 希望还与 ASP 一起使用它们。NET MVC 3 项目或实体框架。 因此 my UserEntity 和 SecurityEntity 对象,如中所示,也可以是企业级 POCO 类可在任何平台上的任何应用程序中方便地重用图 6

图 6的 UserEntity 和 SecurityEntity 对象

public class UserEntity 
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string PrimaryEmail { get; set; }
  public string Password { get; set; }
  public override string ToString()
  {
    return string.Format("{0} {1} ({2})", FirstName, LastName, Password);
  }
}

public class SecurityEntity : ISecurityViewModel
{
  private string _login;
  public int Id { get; set; }
  public bool IsAuthenticated { get; set; }
  public IEnumerable<string> Roles { get; set; }

  public string Login
  {
    get { return _login; }
    set 
    { 
      _login = value;
      Id = 0;
      IsAuthenticated = false;
      Roles = new List<string>();
    }
  }

  public bool IsInRole(string roleName)
  {
    if (Roles == null)
      return false;
     
    return Roles.FirstOrDefault(r => r == roleName) != null;
  }

  public bool IsInRole(string[] roles)
  {
    return roles.Any(role => IsInRole(role));
  }
}

如果 UserEntity 时间将在 ViewModel 中使用,它将不得不换行。 ViewModel 将引发通知,并在汽车发动机罩下下,数据将被传输到 POCO,MainViewModel 类中摘录中所示图 7

图 7到 POCO 的数据传输

public UserEntity SelectedUser
{
  get { return _selectedUser; }
  set
  {
    _selectedUser = value;

    OnPropertyChanged("SelectedUser");
    OnPropertyChanged("FirstName");
    OnPropertyChanged("LastName");
    OnPropertyChanged("Password");
    OnPropertyChanged("PrimaryEmail");
  }
}

public string FirstName
{
  get { return _selectedUser.FirstName; }
  set
  {
    _selectedUser.FirstName = value;
    OnPropertyChanged("FirstName");
  }
}

请注意仅显示名字属性中,但所有的 UserEntity 属性都有一个与之相当的包装。 如果更新 SelectedUser,将引发的所有 UserEntity 属性的属性更改通知,以便 XAML 将通知更新用户界面域如果适用)。

UC0100-060 事件使用案例

事件聚合函数的棱镜,提供一种松散的两个应用程序同时提供了极好的方法来分离组件之间进行通信。 这是它允许每个组件只是发布的订阅服务器的不知情的情况下。 同样,组件可以订阅事件和处理发布服务器的任何不知情的情况下的响应。

有些人认为事件聚合使代码难以执行,但使用正确的日志记录,它是实际相反。 例如,我可能有下拉列表允许用户切换用于财务价值的货币的另一个模块中。 我正处理的组件依赖于此设置用于计算的值为 ViewModel 属性中,并且我的组件和与下拉列表组件之间的逻辑层可能 (即使在一个独立模块)。 如果此值发生更改时,我的组件不会得到通知,会更容易地完成路径 (或多个路径) 使用其他方式使用事件聚合比所有潜在的逻辑跟踪在跟踪调试。 事件聚合,有一些要调试的只有两个点: 订阅服务器和发布。 如果我记录两个 (如同演示应用程序)、 缩小一个单点故障的问题。

由于棱镜 CompositePresentationEvent; 其依赖的驻留在 MsdnDemo.Phone 项目不得不棱镜事件 在这种情况下,它不能驻留在 PCL:

public class MessageEvent : CompositePresentationEvent<MessageEventArgs>
{
}

EventArgs,事件在其具有从属项,所提供很好地 PCL 因为我可以有一组通用的事件参数 — 跨多个企业应用程序将使用哪个 — 其中。 图 8显示了由以前的 MessageEvent MessageEventArgs。

图 8由 MessageEvent MessageEventArgs

public class MessageEventArgs : EventArgs
{
  public object Sender { get; set; }
  public Enum Type { get; set; }
  public string Message { get; set; }
  public bool IsError { get; set; }
  public bool IsInvalid { get; set; }
  public int StatusCode { get; set; }
  public int ErrorCode { get; set; }
  private Exception _exception;
  public Exception Exception
  {
    get { return _exception; }
    set
    {
      _exception = value;
      IsError = true;
    }
  }
}

UC0100-100 MvpVmBase 使用案例

代码重用的最大好处是可靠性。 很高,因为在这种 MsdnDemo.Phone 应用程序、 单元测试将检查的大多数代码的功能。 此操作,请结合字段中的时间,将导致稳定的、 可重用的代码。 尤其是当他们不得不重复使用,用于新的要求中,这将使您的工作组成员,提高了的速度和可靠性。 其他好处包括代码变得更易于使用、 新功能可以有全局的好处和绑定新的应用程序可以在几小时内发生。 您只需创建的视图、 ViewModels 和演示者 (应用适用接口),并且您新的模块可以启动并运行。

MsdnDemo.Phone 应用程序时,PresentationPresenterBase (演示者基类) 只需发送视图并将使用演示者的 ViewModel。 请注意这两个演示者如何共享中相同的 ViewModel图 9

MsdnDemo.Phone Presenters

图 9MsdnDemo.Phone 演示者

从 MvpVmPresenter,我的 PCL,将其作为一份合同对所有共享演示者和其 ViewModels 在我的企业应用程序中的类中派生出 PresentationPresenterBase。 它的代码所示图 10

图 10PresentationPresenterBase

public class MvpVmPresenter<TView,TViewModel> 
  : IMvpVmPresenter
    where TView: IView
    where TViewModel : IMvpVmViewModel
{

  public IShell Shell { get; set; }
  public TView View { get; set; }
  public TViewModel ViewModel { get; set; }

  /// <summary>
  /// Called when the view is activated (by Application controller)
  /// </summary>
  public virtual void ViewActivated(ViewEventArgs e){}
}

与前面提到的 PresentationModuleBase 类中的 ILogger,我不得不 ILogger 以类似的方式 (实例传递到基) 包装,以及解释器、 视图和 ViewModel,因为这些通过 DI 注入。

PresentationPresenterBase,如 PresentationModuleBase,将负责处理 DI 相关的所有服务,因为它有棱镜和集体特定于平台的程序集的引用。 随着流程的不断发展,更多的责任可以移动到其基类中 PCL。

请注意,在图 11DI 容器解析指定的 ViewModel (TViewModel) 时,它将在 ViewModel (行 99) 上设置 ButtonCommand 的绑定。 这就允许演示者,处理所有的按钮单击,通过在行中 56 ExecuteButtonCommandHandler图 9

The Presenter Base Class

图 11的演示者的基类

处理按钮单击,而不是 ViewModels,演示者是极具创意的"olden 天"结构设计师可以从演示文稿模型模式和应用程序模型模式发展为演示者视图模型的图案的众多优点之一。

ViewModels 可以被共享,因为是 MainViewModel,出现这种情况,每个演示者可以根据其需求的不同方式使用 MainViewModel。 我已迁移到 ViewModel 单击的按钮,就必须使用条件语句,这在时间会导致代码的膨胀速度并需要回归测试,因为新的代码可能影响未在开发周期中的模块的逻辑。 保持视图和 ViewModels 的业务逻辑使其最大限度重用。

总结

PCL 可以显著减少同时为企业级应用程序提供合同多目标应用程序所需的项目的数。 无法引用非 PCL 程序集的约束人士更好的编码方法,使项目具有明确的任务分离开来。 结合这些优点的 DI 与 PCL 将最大化重用测试过的分离的项目的能力与时间 (和可用单元测试),增加可扩展性、 可扩展性和您的团队完成新任务的速度。

Bill Kratochvil 独立的合同,是一种潜在顾客 £ º 和架构师为开发人员在医疗行业领先公司的机密项目工作的精英团队。 他自己的公司,全局 Webnet LLC,基于在德克萨斯州的 Amarillo。

衷心感谢以下技术专家对本文的审阅:Christina HeltonDavid Kean