概述
该实验室将为Azure移动服务(Azure Mobile Services)提供几个学习要点。
- 如何创建一个通用项目,旨在共享Windows商店的应用程序与Windows Phone应用程序之间的代码。
- 如何实现让Toast消息得以出现在设备上的推送通知功能。
- 如何处理数据库,以便添加表、列和数据。
- 如何为Windows Phone应用程序添加代码,让你能够检索记录,并通过列表视图控件显示记录。
前提条件
想完成这个动手实验室,需要下列内容:
- 订阅Windows Azure,可在此注册下载免费试用版:http://azure.microsoft.com/en-us/pricing/free-trial/?WT.mc_id=AEDA13C29
- Windows Phone开发者帐户
- Windows 8.1和Visual Studio 2013更新版
具体步骤
这个动手实验室包括下面几大步骤:完成该实验室估计所用的时间为90分钟。
***个步骤:创建一个通用项目
在这个步骤中,你将配置在实验室中需要用到的三个Windows Azure虚拟机。具体来说,你要完成下列任务:
第1个任务:开发一个通用应用程序
通用应用程序让你可以共享平板电脑应用程序和手机应用程序之间的代码。Visual Studio解决方案含有三个项目。一个项目面向基于Windows 8台式机、笔记本电脑或平板电脑的应用程序。第二个项目面向手机应用程序。第三个项目是另外两个项目之间共享的代码库。
1. 你需要创建一个新的手机项目。从File(文件)菜单,选择New/Project(新建/项目)。
创建新的手机项目
2.从左边的模块面板上,选择Universal apps(通用应用程序),如下所示。下一步,选择Hub App模板,然后为你的项目提供一个name(名称)。
创建并命名你的项目
第2个任务:添加推送通知功能
这个任务侧重于将推送通知功能添加到手机应用程序中。你***要为手机应用程序预留一个名称。你还要修改及/或创建在微软数据中心托管、支持推送通知的移动服务。
1. 你的项目现已创建完毕。下一步就是添加推送通知服务。现在你添加支持push notifications(推送通知)的功能。鼠标右击解决方案资源管理器中的手机项目,依次选择Add/Push Notification(添加/推送通知)。
添加推送通知
2. 向导会告知你接下来会发生什么:
(1)会创建推送通道URI。这是客户端应用程序用来侦听和处理推送通知的一种机制;
(2)手机项目还会由代码加以改动,以便处理收到的推送通知;
(3)你的移动服务帐户将被修改,以便支持推送通知。我们将创建一项新的移动服务,以支持该应用程序。
了解将要进行的变更
3.你需要登入到Windows Phone Developer Portal(Windows Phone开发者门户)。这一步假设你有一个有效的Windows Phone开发者手机帐户可以使用。如果你没有Windows Phone开发者帐户,那可以在此注册:http://www.windowsphone.com/account。现在,你可以为你的手机应用程序表明名称。这是会出现在Windows Phone商店里的那个名称。名称必须具有唯一性。
预留一个手机应用程序名称
4.你现在要创建一项服务,这意味着将使用你的Windows Azure帐户,创建一项的Azure移动服务。这意味着你之前已经过注册,可以使用Windows Azure。
创建一项新的Azure移动服务
5. 现在你要为Azure移动服务提供一个名称。你还要选择运行时环境、区域、数据库、用户名和密码。我们选择创建计费的SQL数据库。你的名称会有别于universal-push-notif。你不用改动默认的运行时环境:JavaScript,但可以选择你的区域。
创建一项移动服务
6. 输入服务器用户名。
提供用户名
7. 你现在会注意到,你的Azure移动服务已创建完毕。你的名称会不一样。
验证刚创建的Azure移动服务
8. 现在你要验证摘要页面。你会看到,你的手机应用程序名称和Azure移动服务都已经创建完毕。系统在此还会告知你:Visual Studio中的项目源代码将被修改。
查看Visual Studio向导的摘要信息
9. 现在你可以看到Visual Studio中的已完成项目。
查看Visual Studio中的已完成项目
第3个任务:测试推送通知
在第3个任务中,你要验证推送通知功能操作正常。你要在仿真器中运行手机应用程序,并加以验证,确保看到文本Sample Toast。
1. 鼠标右击手机项目,选择"set as a startup project"(设为开始项目)。
设置开始项目
2. 从任务栏中运行应用程序和仿真器
开始应用程序和仿真器
3.现在你能够看到Toast通知已出现。
查看推送通知和应用程序
4. Sample Toast信息现在会出现。
查看Toast消息
#p#
第二个步骤:充分利用应用程序中的关系型数据
在这个步骤中,我们将充分利用手机应用程序中的SQL Server数据。
第1个任务:添加表、插入数据
在该任务中,我们将添加表、插入列,然后插入数据。
1.从左面板中选择mobile services(移动服务)。我们之前创建的服务名为universal-push-notif。点击图中所标的箭头,即可深入分析该服务。
深入分析universal-push-notif
2.你现在从菜单中选择data(数据)。
从菜单中选择数据
3. 你要add a table(添加表)
添加表
4. 将该表命名为ViewStatus。
为新表命名
5.现在你要验证该表已创建完毕。下几个步骤将包括adding a column(添加列),然后inserting some data(插入一些数据)。点击图中所标的箭头,即可深入分析ViewStatus。
深入分析ViewStatus表
6.请注意:该表随带一些默认的列。点击columns(列),然后点击屏幕底部的add column(添加列)。
添加一个新的列
7.新的列名称将是statustext。不用改动数据类型的默认值:string(字符串)。
为你添加的新列命名
8.现在你验证新列已添加完毕。在下面几个步骤中,我们将为该刚修改的列添加数据。
验证表结构
9.现在你浏览到门户的不同部分。在左边菜单上,选择SQL database(SQL数据库)。你应该会看到数据库的名称。我的数据库名叫universal-push-notif。你的名称会不一样。点击图中所标的箭头,即可深入了解该数据库的详细信息。
深入数据库,以便能够插入数据
10.为了能够插入数据,你需要管理数据库。从底部菜单选择Manage(管理)。
管理数据库,以便插入数据
11.你需要确认你目前的IP地址将被添加到现有防火墙规则列表,让你目前的计算机能够连接到该数据库。开发者定义了允许哪些IP地址连接到数据库。
为防火墙规则添加一个新的IP地址
12.要注意:现在你能创建一个新的查询。从上方的菜单栏选择New Query(新建查询)。
构建新的查询,以便插入数据
13.现在你要键入4个sql插入语句,如下所示。要注意:数据库名称已换成了有破折号的下划线。在键入4个插入语句后,从顶部菜单栏选择Run(运行)。
键入4个插入语句,然后运行这些语句。
第2个任务:添加代码以查看数据
Windows Phone应用程序已经拥有从数据库检索数据所需要的大部分基础架构。在该任务中,我们将添加必要的C#和XAML代码,以便检索和显示数据。
1.打开文件HubPage.xaml.cs。你要添加连接到数据库的代码。添加下列代码,以便你可以从ViewStatus表检索记录列表。
从ViewStatus表检索数据
2.在文件顶部,添加下列using语句。
添加using语句
3.你需要添加TableViewStatus.cs类文件。鼠标右击手机项目,选择Add class(添加类)。粘贴下列代码:
TableViewStatus.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using Microsoft.WindowsAzure.MobileServices;
- using Newtonsoft.Json;
- namespace UniversalPushNotif
- {
- public class ViewStatus
- {
- public string Id { get; set; }
- [JsonProperty(PropertyName = "StatusText")]
- public string StatusText { get; set; }
- }
- }
1.你现在执行实际查询数据库记录的任务。你要修改NavigationHelper_LoadState()方法。添加代码,所下所示。
添加执行查询的代码
2.你现在添加采集来源,采集来源将充当数据库记录的容器。你在下一步添加的列表视图控件将引用该采集来源作为来源。
添加采集来源
3.现在你要修改HubPage.xaml标记代码。你要添加显示记录的列表视图控件。本文末尾提供了完整代码。
为HubPage.xaml添加代码
4.我们现在运行项目,以便你可以测试数据库记录已被检索。运行项目。
运行项目
5.你现在可以看到,记录已检索成功。你还能看到sample toast消息也正常显示。
查看正确的结果
#p#
结束语
这篇指导文章演示了一些重要的概念:
- 如何创建一个通用项目,旨在共享Windows商店的应用程序与Windows Phone应用程序之间的代码。
- 如何实现让Toast消息得以出现在设备上的推送通知功能。
- 如何处理数据库,以便添加表、列和数据。
- 如何为Windows Phone应用程序添加代码,让你能够检索记录,并通过列表视图控件显示记录。
附文:代码清单
你可以在此下载整个项目:http://1drv.ms/1hRB1tP
HubPage.xaml
- <Page
- x:Class="UniversalPushNotif.HubPage"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="using:UniversalPushNotif"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
- d:DataContext="{Binding Source={d:DesignData Source=../UniversalPushNotif.Shared/DataModel/SampleData.json, Type=data:SampleDataSource}}"
- xmlns:data="using:UniversalPushNotif.Data"
- mc:Ignorable="d">
- <Page.Resources>
- <ResourceDictionary>
- <ResourceDictionary.ThemeDictionaries>
- <ResourceDictionary x:Key="Default">
- <ImageBrush
- x:Key="HubBackgroundImageBrush"
- ImageSource="Assets/HubBackground.png"/>
- </ResourceDictionary>
- <ResourceDictionary x:Key="HighContrast">
- <ImageBrush
- x:Key="HubBackgroundImageBrush"
- ImageSource="{x:Null}"/>
- </ResourceDictionary>
- </ResourceDictionary.ThemeDictionaries>
- <!--Grid-appropriate item template as seen in section 2 -->
- <DataTemplate x:Key="Standard200x180TileItemTemplate">
- <Grid Width="180">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- </Grid.RowDefinitions>
- <Border
- Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"
- Height="173"
- Width="173"
- Grid.Row="0"
- HorizontalAlignment="Left">
- <Image
- Source="{Binding ImagePath}"
- Stretch="UniformToFill"
- AutomationProperties.Name="{Binding Title}"
- Height="173"
- Width="173"/>
- </Border>
- <TextBlock
- Text="{Binding Title}"
- Style="{ThemeResource BaseTextBlockStyle}"
- Typography.Capitals="SmallCaps"
- Grid.Row="1"
- Margin="0,12,0,0"
- IsTextScaleFactorEnabled="False"/>
- </Grid>
- </DataTemplate>
- <DataTemplate x:Key="StandardTripleLineItemTemplate">
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="*"/>
- </Grid.ColumnDefinitions>
- <Border
- Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"
- Height="99"
- Width="99"
- Grid.Column="0"
- HorizontalAlignment="Left">
- <Image
- Source="{Binding ImagePath}"
- Stretch="UniformToFill"
- AutomationProperties.Name="{Binding Title}"
- Height="99"
- Width="99"/>
- </Border>
- <StackPanel
- Grid.Column="1"
- Margin="12,0,0,0">
- <TextBlock
- Text="{Binding Title}"
- Style="{ThemeResource ListViewItemTextBlockStyle}"/>
- <TextBlock
- Text="{Binding Subtitle}"
- Style="{ThemeResource ListViewItemSubheaderTextBlockStyle}"/>
- <TextBlock
- Text="{Binding Description}"
- Style="{ThemeResource ListViewItemContentTextBlockStyle}"/>
- </StackPanel>
- </Grid>
- </DataTemplate>
- <DataTemplate x:Key="StandardDoubleLineItemTemplate">
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="*"/>
- </Grid.ColumnDefinitions>
- <Border
- Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"
- Height="99"
- Width="99"
- Grid.Column="0"
- HorizontalAlignment="Left">
- <Image
- Source="{Binding ImagePath}"
- Stretch="UniformToFill"
- AutomationProperties.Name="{Binding Title}"
- Height="99"
- Width="99"/>
- </Border>
- <StackPanel
- Grid.Column="1"
- Margin="12,0,0,0">
- <TextBlock
- Text="{Binding Title}"
- Style="{ThemeResource ListViewItemTextBlockStyle}"/>
- <TextBlock
- Text="{Binding Subtitle}"
- Style="{ThemeResource ListViewItemContentTextBlockStyle}"/>
- </StackPanel>
- </Grid>
- </DataTemplate>
- <CollectionViewSource
- x:Name="viewStatusCollection"
- Source="{Binding Items}"/>
- </ResourceDictionary>
- </Page.Resources>
- <Grid x:Name="LayoutRoot">
- <Hub
- x:Name="Hub"
- x:Uid="Hub"
- Header="application name"
- Background="{ThemeResource HubBackgroundImageBrush}">
- <HubSection
- x:Uid="HubSection1"
- Header="SECTION 1">
- <DataTemplate>
- <ListView
- x:Name="ListView2"
- Margin="0,0,0,4"
- Width="520"
- Height="405"
- Background="#FF1D1D1D"
- ShowsScrollingPlaceholders="False"
- BorderThickness="0"
- VerticalAlignment="Stretch"
- HorizontalAlignment="Left"
- ScrollViewer.HorizontalScrollBarVisibility="Disabled"
- ItemsSource="{Binding Source={StaticResource viewStatusCollection}}"
- FontFamily="Global User Interface">
- <ListView.ItemsPanel>
- <ItemsPanelTemplate>
- <VirtualizingStackPanel VerticalAlignment="Top"/>
- </ItemsPanelTemplate>
- </ListView.ItemsPanel>
- <ListView.ItemContainerTransitions>
- <TransitionCollection>
- <EntranceThemeTransition/>
- </TransitionCollection>
- </ListView.ItemContainerTransitions>
- <ListView.ItemTemplate>
- <DataTemplate>
- <StackPanel
- HorizontalAlignment="Left"
- Orientation="Horizontal"
- Background="#FF1D1D1D"
- Margin="5,5,5,5">
- <Grid
- VerticalAlignment="Center"
- Background="#FF1D1D1D">
- <StackPanel
- Orientation="Vertical"
- VerticalAlignment="Center">
- <TextBlock
- Text="{Binding StatusText}"
- FontSize="14.667"
- FontFamily="Segoe UI"
- Margin="5,0,0,0"/>
- </StackPanel>
- </Grid>
- </StackPanel>
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </DataTemplate>
- </HubSection>
- <HubSection
- x:Uid="HubSection2"
- Header="SECTION 2"
- Width="Auto"
- DataContext="{Binding Groups[0]}">
- <DataTemplate>
- <GridView
- ItemsSource="{Binding Items}"
- AutomationProperties.AutomationId="ItemGridView"
- AutomationProperties.Name="Items In Group"
- ItemTemplate="{StaticResource Standard200x180TileItemTemplate}"
- SelectionMode="None"
- IsItemClickEnabled="True"
- ItemClick="ItemView_ItemClick"
- ContinuumNavigationTransitionInfo.ExitElementContainer="True">
- <GridView.ItemsPanel>
- <ItemsPanelTemplate>
- <ItemsWrapGrid/>
- </ItemsPanelTemplate>
- </GridView.ItemsPanel>
- </GridView>
- </DataTemplate>
- </HubSection>
- <HubSection
- x:Uid="HubSection3"
- Header="SECTION 3"
- DataContext="{Binding Groups[1]}">
- <DataTemplate>
- <ListView
- AutomationProperties.AutomationId="ItemListViewSection3"
- AutomationProperties.Name="Items In Group"
- SelectionMode="None"
- IsItemClickEnabled="True"
- ItemsSource="{Binding Items}"
- ItemTemplate="{StaticResource StandardTripleLineItemTemplate}"
- ItemClick="ItemView_ItemClick"
- ContinuumNavigationTransitionInfo.ExitElementContainer="True">
- </ListView>
- </DataTemplate>
- </HubSection>
- <HubSection
- x:Uid="HubSection4"
- Header="SECTION 4"
- DataContext="{Binding Groups[2]}">
- <DataTemplate>
- <ListView
- AutomationProperties.AutomationId="ItemListViewSection4"
- AutomationProperties.Name="Items In Group"
- SelectionMode="None"
- IsItemClickEnabled="True"
- ItemsSource="{Binding Items}"
- ItemClick="ItemView_ItemClick"
- ContinuumNavigationTransitionInfo.ExitElementContainer="True">
- <ListView.ItemTemplate>
- <DataTemplate>
- <StackPanel>
- <TextBlock
- Text="{Binding Title}"
- Style="{ThemeResource ListViewItemTextBlockStyle}"/>
- <TextBlock
- Text="{Binding Subtitle}"
- Style="{ThemeResource ListViewItemContentTextBlockStyle}"/>
- </StackPanel>
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </DataTemplate>
- </HubSection>
- <HubSection
- x:Uid="HubSection5"
- Header="SECTION 5"
- DataContext="{Binding Groups[3]}">
- <DataTemplate>
- <ListView
- AutomationProperties.AutomationId="ItemListViewSection5"
- AutomationProperties.Name="Items In Group"
- SelectionMode="None"
- IsItemClickEnabled="True"
- ItemsSource="{Binding Items}"
- ItemTemplate="{StaticResource StandardDoubleLineItemTemplate}"
- ItemClick="ItemView_ItemClick"
- ContinuumNavigationTransitionInfo.ExitElementContainer="True">
- </ListView>
- </DataTemplate>
- </HubSection>
- </Hub>
- </Grid>
- </Page>
HubPage.xaml.cs
- using UniversalPushNotif.Common;
- using UniversalPushNotif.Data;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Runtime.InteropServices.WindowsRuntime;
- using Windows.ApplicationModel.Resources;
- using Windows.Foundation;
- using Windows.Foundation.Collections;
- using Windows.Graphics.Display;
- using Windows.UI.Core;
- using Windows.UI.ViewManagement;
- using Windows.UI.Xaml;
- using Windows.UI.Xaml.Controls;
- using Windows.UI.Xaml.Controls.Primitives;
- using Windows.UI.Xaml.Data;
- using Windows.UI.Xaml.Input;
- using Windows.UI.Xaml.Media;
- using Windows.UI.Xaml.Media.Imaging;
- using Windows.UI.Xaml.Navigation;
- using Microsoft.WindowsAzure.MobileServices;
- using Newtonsoft.Json;
- // The Universal Hub Application project template is documented at http://go.microsoft.com/fwlink/?LinkID=391955
- namespace UniversalPushNotif
- {
- ///
- /// A page that displays a grouped collection of items.
- ///
- public sealed partial class HubPage : Page
- {
- private readonly NavigationHelper navigationHelper;
- private readonly ObservableDictionary defaultViewModel = new ObservableDictionary();
- private readonly ResourceLoader resourceLoader = ResourceLoader.GetForCurrentView("Resources");
- // Add this code
- // An asynchronous data source that can wrap the results of a Mobile Services query
- // in a way that's easily consumed by Xaml collection controls like ListView,
- // GridView or ListBox.
- private MobileServiceCollection<ViewStatus, ViewStatus> viewStatusList;
- // Get a list of records from the ViewStatus table
- private IMobileServiceTable<ViewStatus> tableViewStatus =
- App.universal_push_notifClient.GetTable<ViewStatus>();
- public HubPage()
- {
- this.InitializeComponent();
- // Hub is only supported in Portrait orientation
- DisplayInformation.AutoRotationPreferences = DisplayOrientations.Portrait;
- this.NavigationCacheMode = NavigationCacheMode.Required;
- this.navigationHelper = new NavigationHelper(this);
- this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
- this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
- }
- ///
- /// Gets the <>"NavigationHelper"/> associated with this <>"Page"/>.
- ///
- public NavigationHelper NavigationHelper
- {
- get { return this.navigationHelper; }
- }
- ///
- /// Gets the view model for this <>"Page"/>.
- /// This can be changed to a strongly typed view model.
- ///
- public ObservableDictionary DefaultViewModel
- {
- get { return this.defaultViewModel; }
- }
- ///
- /// Populates the page with content passed during navigation. Any saved state is also
- /// provided when recreating a page from a prior session.
- ///
- /// <>"sender">
- /// The source of the event; typically <>"NavigationHelper"/>
- ///
- /// <>"e">Event data that provides both the navigation parameter passed to
- /// <>"Frame.Navigate(Type, object)"/> when this page was initially requested and
- /// a dictionary of state preserved by this page during an earlier
- /// session. The state will be null the first time a page is visited.
- private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
- {
- // TODO: Create an appropriate data model for your problem domain to replace the sample data
- var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
- this.DefaultViewModel["Groups"] = sampleDataGroups;
- viewStatusList = await tableViewStatus.ToCollectionAsync();
- viewStatusCollection.Source = viewStatusList;
- }
- ///
- /// Preserves state associated with this page in case the application is suspended or the
- /// page is discarded from the navigation cache. Values must conform to the serialization
- /// requirements of <>"SuspensionManager.SessionState"/>.
- ///
- /// <>"sender">The source of the event; typically <>"NavigationHelper"/>
- /// <>"e">Event data that provides an empty dictionary to be populated with
- /// serializable state.
- private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
- {
- // TODO: Save the unique state of the page here.
- }
- ///
- /// Shows the details of a clicked group in the <>"SectionPage"/>.
- ///
- /// <>"sender">The source of the click event.
- /// <>"e">Details about the click event.
- private void GroupSection_ItemClick(object sender, ItemClickEventArgs e)
- {
- var groupId = ((SampleDataGroup)e.ClickedItem).UniqueId;
- if (!Frame.Navigate(typeof(SectionPage), groupId))
- {
- throw new Exception(this.resourceLoader.GetString("NavigationFailedExceptionMessage"));
- }
- }
- ///
- /// Shows the details of an item clicked on in the <>"ItemPage"/>
- ///
- /// <>"sender">The source of the click event.
- /// <>"e">Defaults about the click event.
- private void ItemView_ItemClick(object sender, ItemClickEventArgs e)
- {
- var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
- if (!Frame.Navigate(typeof(ItemPage), itemId))
- {
- throw new Exception(this.resourceLoader.GetString("NavigationFailedExceptionMessage"));
- }
- }
- #region NavigationHelper registration
- ///
- /// The methods provided in this section are simply used to allow
- /// NavigationHelper to respond to the page's navigation methods.
- ///
- /// Page specific logic should be placed in event handlers for the
- /// <>"NavigationHelper.LoadState"/>
- /// and <>"NavigationHelper.SaveState"/>.
- /// The navigation parameter is available in the LoadState method
- /// in addition to page state preserved during an earlier session.
- ///
- ///
- /// <>"e">Event data that describes how this page was reached.
- protected override void OnNavigatedTo(NavigationEventArgs e)
- {
- this.navigationHelper.OnNavigatedTo(e);
- }
- protected override void OnNavigatedFrom(NavigationEventArgs e)
- {
- this.navigationHelper.OnNavigatedFrom(e);
- }
- #endregion
- }
- }