Windows Phone

创建 Windows Phone 8 Company Hub 应用程序

Tony Champion

下载代码示例

之前的 Windows Phone 8 发布,公司有只有几个选项用于将企业应用程序部署到雇员的设备。 他们可能释放到 Windows Phone 存储的应用程序,需要用户身份验证,允许的应用程序,同时确保他们的用法要成功部署。 当然,app 将会可用存储区中并可供公众下载。 另一种方法是利用移动设备管理 (MDM) 解决方案来直接管理的设备。 Microsoft 现在提供了两个 MDMs:Windows Intune 和系统中心 2012年配置管理器。 虽然 MDMs 仍有许多大型企业的首选的方法,但他们不可能与公司的部署目标对齐。

Windows Phone 8 带来一种新的非托管部署企业应用程序的选项,允许设备安装的应用程序直接从电子邮件附件或从 URL 下载 app。 这种部署企业应用程序的直接方法打开企业应用程序开发和部署到更多的公司。

此非托管方法的缺点是,安装和 app 管理的用户不是一共直观。 当谈到移动设备时,用户期望,以便能够安装的应用程序,按下按钮。 无需手动安装应用程序,从电子邮件或 Web 站点违背正常的应用程序和经验,可以在用户和 IT 人员的培训问题导致的不确定性。 幸运的是,有一个解决方案 — — 公司集线器 app。

Windows Phone 8 包括一些增补到 SDK 支持这个新的概念。 公司枢纽 app 给企业介绍和交付给用户进行安装的应用程序的机制。 它使用户熟悉的经验,发现和安装公司的应用程序。

公司中心应用程序的要求

您可以向您的员工发布公司集线器 app 之前,有几个需要采取行政步骤。 第一步是关于 Windows Phone 开发中心公司帐户注册。 注册过程是类似的常规发展帐户,但经过一些额外的帐户验证的公司帐户。

创建帐户后,您必须购买企业移动代码签名证书从赛门铁克公司。 证书采购流程需要有效的赛门铁克 ID 从 Dev 中心,所以这才能完成后的公司帐户已经建立和验证。 一旦证书是购买并安装在一台机器上,您需要导出的 PFX 格式包含私钥的证书。 生成一个应用程序注册令牌 (AET) 和签署任何由本公司开发的应用程序将使用此证书。 在开发计算机上安装证书,时,重要的是要遵循的步骤概述了在 bit.ly/1287H8j。 否则,你就会用不会正确验证部署过程不完整的 PFX 文件。 跳过这些步骤是许多开发人员常见头痛。

AET 必须安装在设备上之前,设备可以安装任何应用程序的公司开发的。 通过安装 AET,设备先前建立的公司帐户中登记。 Windows Phone SDK 8.0 包括工具,AETGenerator,可以用于创建 AET 从导出的证书。 生成器创建 AET 的三种不同的形式:AET MDM Windows Intune 或系统中心 2012年配置管理器 (.aet) 和一种 XML 格式,可以直接通过电子邮件或互联网浏览器 (.aetx) 在设备上安装与使用 Base64 编码的版本的 XML 格式 (.xml),其中包含原始版本。

当决定要使用分发 AET 和您的公司应用程序的方法,有一点是很重要考虑。 当设备安装 AET 时,有效期直至其到期日期,其中,默认情况下是一年。 一旦 AET 过期后,设备将无法运行任何签署并由公司分配的应用程序 — — 包括集线器的应用程序 — — 直到安装了新的、 有效的 AET。 这将创建两个项目,您需要添加到您的部署的战略规划。

首先是如何处理的 AET 失效。 如果您使用的 MDM 来管理您的设备,可以直接从 MDM.到设备发布更新的 AET 这将有助于在设备上的原始 AET 的失效的影响降到最低。 如果非托管进程通过安装 AET — — 电子邮件或 Internet Explorer — — 新 AET 将需要由用户手动安装。 因为用户不能运行任何应用程序从公司,包括集线器 app,一旦 AET 过期,它是一个好的做法,来创建和分发新 AET 原始到期之前。 这将阻止用户失去对公司的应用程序的访问。

要考虑的第二项是清除 AET,以及任何公司在设备上安装的应用程序。 在当前带来您自己的设备 (BYOD) 世界中,这可以的主要考虑因素。 使用 MDM 给的公司的设备安装其应用程序的完全控制。 可以直接从 MDM.远程删除应用程序和澳大利亚东部标准时间 但是,在非托管的部署中,这不可能。 一旦 AET 添加到设备中,它是有效的和其到期之前不能删除。 同样,SDK 不会提供从设备通过代码删除应用程序的方法。 解决这一问题的最佳做法要求用户对公司的所有应用程序进行身份验证。 这使您可以防止用户帐户启动的应用程序。 虽然这不是相同,不能删除该应用程序,它不会给你的方法来管理您的应用程序的安全,一旦部署到设备。

您的初始步骤完成之后,你准备好要开始创建的应用程序可以部署到您公司的员工而无需通过商店去。 你会找到更完整的看看创建公司帐户、 获得企业移动代码签名证书和生成在 AET bit.ly/SBN6Tf

为发展作准备

公司枢纽 app,需要更多比大多数 Windows Phone 应用程序的设置。 为一件事,是要有至少几个应用程序可用于安装和使用应用程序中的测试用例作为相当方便。 虽然他们自己的应用程序可以是空白的 Windows Phone 应用程序,它们都必须有一个共同点:发布者 id。

如果您已经创建了一个 Windows Phone 应用程序,你可能熟悉 Windows Phone 应用程序清单文件,它在默认情况下是位于解决方案的属性文件夹中的 WMAppManifest.xml 文件。 此文件包含 Windows Phone 和信息存储需要了解您的应用程序。 打开 WMAppManifest.xml 文件从 Visual Studio 内的启动设计器,使它易于维护的文件。 设计器包含四个选项卡,其中之一就是包装选项卡。

包装选项卡提供了应用程序、 版本控制信息和支持的语言的开发人员信息。 这篇文章的目的,为版本、 产品 ID 和发布者 ID 是最重要的项目,在此选项卡上。 默认情况下,Windows Phone 项目模板生成新的 GUID 的产品 ID 和发布者 ID 在创建项目时。 工作时与 app 等公司的枢纽,app 将有只到其他应用程序具有相同的发布者 ID,它的知名度。 此发布者 ID 通常设置为发布服务器 GUID 是分配给您的开发人员帐户中开发中心。 Dev 中心帐户摘要页面上可以找到发行者的 GUID。

这篇文章的可下载文件包含七个解决方案。 第六个被命名为 CDSAPP [1-6]。 这些应用程序的每个从 Windows Phone 应用程序项目模板创建的是仅有的主页面上的应用程序标题和发布者 ID 修改。 如果你要使用这些应用程序为您的测试,它是重要的为您公司的集线器应用程序使用相同的发布者 ID,或要更改你的 app Id。

版本和产品 ID 是两部分的重要信息要知道何时创建公司的枢纽解决方案。 版本允许您确定当应用程序需要在设备上,升级和产品 ID 用于标识应用程序。

接下来要考虑是您的测试方法。 一般情况下,你可以在 Windows Phone 模拟器上测试大部分。 它做的很好,并允许您测试的事情,你需要在开发过程中测试的大多数。 测试公司集线器 app 的难处是您的应用程序需要有在仿真程序来测试与安装的附加程序。 然而,每次启动新实例的仿真程序,它从操作系统的干净版本开始。 这意味着什么你以前安装的测试不再存在。

有两种方法可以解决此问题。 第一是离开运行模拟器,安装一些测试的应用程序,,然后做贵公司的发展枢纽 app 仿真程序仍在运行。 当然,如果出于任何原因重新启动仿真程序获取你要重新安装这些应用程序开始再次测试。

首选的方法是做你发展针对真正的设备进行测试。 这为您的测试提供一个更稳定的平台。 如果你要做你直播设备上进行测试,您需要确保该设备的发布者 ID 对应的发布者 id,您的帐户。

公司枢纽 SDK

Windows Phone SDK 8.0 包括两类,主要负责管理以及与公司应用程序安装在机器上进行交互。 虽然有一些会将向您介绍沿途的支持对象,了解这两个类给你集线器的应用至关重要。

包类每个设备上安装的应用程序由包类表示,位于 Windows.ApplicationModel 命名空间中。 包类包含几个重要的成员,我会在这里讨论。 首先是返回 PackageId 类的 Id 属性。 该类包含了大部分的清单资料被输入到包装选项卡中的清单设计器,其中包括被分配了到 app、 应用程序名称、 发布信息和当前版本的产品 Id。

包类的其他重要成员是发射的方法,使您能够启动包表示从当前应用程序的应用程序。 这不仅是一个伟大的工具构建枢纽公司 app,也可以为其他业务线 (LOB) 应用程序非常有用。

InstallationManager 类 InstallationManager,在 Windows.Phone.Management.Deployment 命名空间,是负责在设备上安装的包的类。 这是通过 AddPackageAsync 方法来实现的。 FindPackagesFromCurrentPublisher 方法返回从当前的应用程序,包括包表示当前应用程序中,作为同一 PublisherId 的所有应用程序的列表。 此外,还可以使用类让任何应用程序具有相同的 PublisherId 的安装进度。

建筑公司集线器 App

我要把你介绍给开发公司集线器 app,核心原则使用演示所示图 1。 演示包含三页全景 app,列出可用公司在三个类别的应用程序:目前没有可用的更新与安装、 应用程序和已安装的最新版本的应用程序的应用程序。 此外,应用程序将有详细页面,提供了有关应用程序的信息,并提供的命令来安装最新版本和启动应用程序从内部集线器。 可以找到工作的解决方案作为一个可下载的资源,从 archive.msdn.microsoft.com/mag201307Hub

The Company Hub App
图 1 公司集线器 App

什么是缺少出于完整性的考虑,它是重要的是要指出几个项目并不包含在演示应用程序,但将必须创建任何真实世界的解决方案。 第一是公司可用的应用程序源。 如果您检查的 CompanyPackage 类,您就会看到它包含 GenerateData 的一种方法。 此方法用于生成安装的公司在设备上的应用程序列表、 修改的一些数据和创建一些虚构的数据,以及由假公司可用的应用程序。

第二个缺失片断是网站主办的.xap 文件中下载并安装在设备上。 为公司的集线器才能正常工作,它必须能够从某些位置下载的应用程序。 此 Web 解决方案会以及创建。

CompanyPackage 类为了代表列表中可用的应用程序,要安装在设备上,首先要定义是仿照包类的 CompanyPackage 类。 CompanyPackage 类包含显示信息和位置的安装程序包:

public class CompanyPackage
{
  public string Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
  public string Thumbnail { get; set; }
  public string Version { get; set; }
  public Uri SourceUri { get; set; }
  public CompanyPackageStatus Status { get; set; }
}

Status 属性用于确定当前安装在设备上的 app 如果有一个较新的版本。 CompanyPackageStatus 是 enum 类型的值,在本文的后面,我将探讨:

public enum CompanyPackageStatus
{
  Unknown,
  New,
  Update,
  Installed
};

创建视图模型 这个公司集线器 App,您必须创建一个单一视图模型,CompanyPackageViewModel,所示 图 2。 视图模型应该有三个属性,返回的 CompanyPackage 对象 IEnumerable 集合:NewPackage、 UpdatePackages 和 InstalledPackages。 NewPackage 属性包含任何可用的应用程序,不当前安装在机器上。 UpdatePackages 表示当前已安装在计算机上,但有较新的版本可用的任何应用程序。 最后,InstalledPackages 包含所有当前已安装且最新的设备上的应用程序。

图 2 CompanyPackage 视图模型

public class CompanyPackageViewModel : INotifyPropertyChanged
{
  private IEnumerable<CompanyPackage> _packages;
  public CompanyPackageViewModel()
  {
    LoadData();       
  }
  private void LoadData()
  {
    // Get list of packages and populate properties
    _packages = CompanyPackage.GenerateData();
    UpdatePackageStatus();
  }
  private IEnumerable<CompanyPackage> _newPackages;
  public IEnumerable<CompanyPackage> NewPackages
  {
    get
    {
      return _newPackages;
    }
  }
  private IEnumerable<CompanyPackage> _updatePackages;
  public IEnumerable<CompanyPackage> UpdatePackages
  {
    get
    {
      return _updatePackages;
    }
  }
  private IEnumerable<CompanyPackage> _installedPackages;
  public IEnumerable<CompanyPackage> InstalledPackages
  {
    get
    {
      return _installedPackages;
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
  private void NotifyPropertyChanged(String propertyName)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (null != handler)
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

定义的三种状态你可以填充的三个不同属性的视图模型之前,您需要生成代表作为集合的 CompanyPackages 的可用应用程序列表。 在所示的视图模型图 2,加载并在 LoadData 方法中填充数据。 LoadData 方法拉扯试验数据的列表,并将其存储私人 _packages 变量中。 然后,它调用 UpdatePackageStatus 方法所示图 3

图 3 种方法确定每个 CompanyPackage 的状态

public void UpdatePackageStatus()
{
  var devicePkgs = 
    InstallationManager.FindPackagesForCurrentPublisher();
  foreach (var pkg in _packages)
  {
    var qry = devicePkgs.Where(p => p.Id.ProductId == pkg.Id);
    if (qry.Count() > 0)
    {
      var devicePkg = qry.First();
      var devicePkgVersion =
        PackageVersionHelper.VersionToString(devicePkg.Id.Version);
      pkg.Status = PackageVersionHelper.IsNewer(
        pkg.Version, devicePkgVersion) ?
        CompanyPackageStatus.Update : CompanyPackageStatus.Installed;
    }
    else
    {
      pkg.Status = CompanyPackageStatus.New;
    }
  }
  _newPackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.New).ToList();
  _updatePackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.Update).ToList();
  _installedPackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.Installed).ToList();
  // Fire notifications for all properties
  NotifyPropertyChanged("NewPackages");
  NotifyPropertyChanged("UpdatePackages");
  NotifyPropertyChanged("InstalledPackages");
}

UpdatePackageStatus 方法有两个职责:确定每个可用的 CompanyPackage 类的当前状态,然后填充三个集合属性的这种地位所基于的视图模型。

Status 属性确定通过比较每个 CompanyPackage 反对在设备当前安装的应用程序。 从 InstallationManager.FindPackagesForCurrentPublisher 的静态方法获得的已安装应用程序的列表。 如果具有相同的 Id CompanyPackage 包对象不存在,它与"新"状态标记。

如果不存在具有相同 Id 的包,然后包版本属性相比的 CompanyPackage 版本。 PackageId 版本属性返回的 PackageVersion 结构。 不同的 System.Version 类,此结构缺少的两个特点。 第一个特征是能力结构转换为字符串表示形式。 如果您调用 ToString 方法,它返回的类型名称和不实际的版本号。 第二个缺失功能是进行比较以确定哪一个是较新的 PackageVersion 的两个实例的能力。

图 4 显示了实现这些缺失的功能有两个帮助器类。 VersionToString 方法返回 PackageVersion 的正确的字符串表示形式。 IsNewer 方法采用两个版本号的字符串表示形式,并确定是否丰田参数是较新的 oldVersion 参数比。 它通过将字符串转换为 System.Version 对象,并使用可用 CompareTo 方法来实现这一点。

图 4 为 PackageVersion 的的帮助器类

public static class PackageVersionHelper
{
  public static string VersionToString(PackageVersion version)
  {
    return String.Format("{0}.{1}.{2}.{3}",
                         version.Major,
                         version.Minor,
                         version.Build,
                         version.Revision);
  }
  public static bool IsNewer(string newVersion, 
    string oldVersion)
  {
    var newVer = Version.Parse(newVersion);
    var oldVer = Version.Parse(oldVersion);
    return newVer.CompareTo(oldVer) > 0;
  }
}

一旦 UpdatePackageStatus 方法计算了 Status 属性,每个公司­包对象时,它将填充三个集合的属性,使用 LINQ 查询。 最后,视图模型引发 PropertyChanged 事件的每个属性。

显示应用程序列表中的具有三个全景全景控件内显示三个可用的应用程序列表­项目,每个包含初选­选择器绑定到的列表。 每个使用相同的 DataTemplate 显示 CompanyPackage 和完整的全景 XAML 可以发现在图 5。 在可下载的项目中,您将看到的全景控件的 DataContext 继承父 PhoneApplicationPage,它被设置为 CompanyPackageViewModel 的实例的 DataContext。 你看到的这个结果图 1

图 5 全景图,以显示可用的应用程序

<phone:Panorama Title="my company hub">
  <phone:Panorama.Resources>
    <DataTemplate x:Key="listItemTemplate">
      <StackPanel Margin="0,-6,0,12" Orientation="Horizontal">
        <Image  Source="{Binding Thumbnail,
          Converter={StaticResource debugConv}}"/>
        <TextBlock Text="{Binding Name}" TextWrapping="Wrap"
          VerticalAlignment="Center"
          Style="{StaticResource PhoneTextExtraLargeStyle}"
          FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
      </StackPanel>
    </DataTemplate>
  </phone:Panorama.Resources>
  <!--Panorama New Apps-->
  <phone:PanoramaItem Header="New Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding NewPackages}"
      SelectionChanged="ItemSelected"
      ItemTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
  <!--Panorama Update Apps-->
  <phone:PanoramaItem Header="Update Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding UpdatePackages}"
      SelectionChanged="ItemSelected"
      temTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
  <!--Panorama Installed Apps-->
  <phone:PanoramaItem Header="Installed Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding InstalledPackages}"
      SelectionChanged="ItemSelected"
      ItemTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
</phone:Panorama>

CompanyPackage 详细信息视图

每个 LongListSelector 共享相同的事件处理 SelectionChanged 事件,ItemSelected。 事件处理程序使用 NavigationService 来导航到明细 PhoneApplicationPage,PackagePage,和 CompanyPackage 的 Id 在传递。 因为当前页面将被缓存在导航中,LongListSelector 的 SelectedItem 被重置为 null,以确保适当的事件,触发每个时间:

private void ItemSelected(object sender, 
  SelectionChangedEventArgs e)
{
  if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
  {
    var pkg = e.AddedItems[0] as CompanyPackage;
    NavigationService.Navigate(
      new Uri("/PackagePage.xaml?id=" + pkg.Id, 
      UriKind.Relative));
    (sender as LongListSelector).SelectedItem = null;
  }
}

PackagePage 类只接收的 CompanyPackage 要显示的 Id,因为它具有使用该 Id 来查找适当的对象。 这是通过向 CompanyPackageViewModel 添加一个 FindPackage 的方法:

public CompanyPackage FindPackage(string id)
{
  return _packages.Where(p => p.Id == id).FirstOrDefault();
}

Windows Phone 全景 App 项目公开一个全局视图模型由 ViewModel 属性添加到该应用程序类。 这一项目使用该属性公开其向主视图模型和详细信息页。

PackagePage 类重写 OnNavigatedTo 方法,以将其 DataContext 设置为匹配提供的 id CompanyPackage 然后,它调用 UpdateUI 方法的可见性和内容的两个按钮添加到基于 CompanyPackage 的状态屏幕之间切换。 每个状态类型的结果可以看到在图 6

The Different Detail Pages
图 6 不同详细页

两个按钮将公开可用的公司集线器类内的两个操作。 第一是启动应用程序的能力。 如果在设备上当前安装的 app,启动按钮是可见的。 该按钮的事件处理程序查找正确的包对象匹配当前的 CompanyPackage,并且调用启动的方法:

private void btnLaunch_Click(
  object sender, RoutedEventArgs e)
{
  var pkg = DataContext as CompanyPackage;
  var devicePkgs = InstallationManager.
    FindPackagesForCurrentPublisher();
  var devicePkg = devicePkgs.Where(p =>
    p.Id.ProductId == pkg.Id).FirstOrDefault();
  if (devicePkg != null)
  {
    devicePkg.Launch("");
  }        
}

如果 CompanyPackage 的状态为"新建"或"更新",安装按钮是在页上可见。 此按钮的事件处理程序将尝试安装最新版本的应用程序从提供的源中的 Uri­CompanyPackage 的 Uri 属性。 这是使用 InstallationManager.AddPackageAsync 方法来完成。 是否正在更新应用程序或新安装它的调用相同的方法。 此方法可以是非常喜怒无常,您需要确保要照顾的生成的任何错误。 图 7 显示事件处理程序和安装过程。 如果应用程序安装成功,UpdatePackage­CompanyPackageView 状态方法­模型调用来更新显示在主页面和页面更新页面 UpdateUI 方法的状态集合。

图 7 安装和更新按钮事件处理程序

private async void btnInstall_Click(object sender, RoutedEventArgs e)
{
  var pkg = DataContext as CompanyPackage;
  if (MessageBox.Show("Install " + pkg.Name + "?", "Install app",  
   MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  {
    try
    {
      var result =
        await InstallationManager.AddPackageAsync(pkg.Name, pkg.SourceUri);
      if (result.InstallState ==
        Windows.Management.Deployment.PackageInstallState.Installed)
      {
        MessageBox.Show(pkg.Name + " was installed.");
        App.ViewModel.UpdatePackageStatus();
        UpdateUI();
      }
      else
      {
        MessageBox.Show("An error occurred during installation.");
      }
    }
    catch (Exception)
    {
      MessageBox.Show("An error occurred during installation.");
    }
  }
}

后续步骤

在这篇文章我快速看了暴露详细信息的同时开发公司集线器 app 需要创建一个更强健的解决方案。 一些被省去了简明扼要的详细信息可以包括下载中找到。 然而,它是重要的是要记住这只是个开始。

有很多的功能,可以将其添加到公司集线器,以便向您的用户,如利用活的瓷砖,新的应用程序可用时通知用户提供极大的好处。 您可以创建仅公开向某些用户基于他们的角色在公司内的某些应用程序的解决方案。 应用程序可以提供附加功能,例如公司新闻和通知。 虽然可能性不可能是无限的但有足够多,相当一段时间就会让你忙。

Tony Champion  是冠军 DS 的总统,是微软最有价值球员,并积极参与社会的扬声器、 博主和作者。他坚持在博客上的 tonychampion.net 可以通过电子邮件在到达 tony@tonychampion.net

衷心感谢以下技术专家对本文的审阅:悬崖 Strom (Microsoft)