您的位置:首页 > 其它

生成一个端到端的Windows Store应用程序-第二部分: 集成云服务

2012-09-07 13:34 344 查看
[原文地址] http://blogs.msdn.com/b/somasegar/archive/2012/08/28/building-an-end-to-end-windows-store-app-part-2-integrating-cloud-services.aspx
中文翻译:

在我之前的博客,我已经开始使用针对Windows 8的Visual Studio express 2012生成一个Windows Store应用程序, 且在这个过程中结合进一些Windows特性,比如live tiles和搜索.然而,这些天来很多应用程序都得益于消费和整合各种跑在云中的后台服务,我希望我的应用程序也能获益.



在这方面, 我们收到的开发者的反馈中涉及到最多的一个领域是希望服务采用turn-key的模式,这样客户只需要简单的进行设备运营,而不需要生成,部署和管理他们自己的服务.为了解决这方面的需要,今天我们宣布新Windows Azure移动服务预览版. 这个发布版本包括针对Windows Store应用程序的客户端SDKs, 使用JavaScript, C#, Visual Basic 或者C++编写, 且提供存储容量, 消息推送等等功能(包括可以编写JavaScript脚本在服务器上运行).



在本篇博客中,我将使用后台服务以各种形式来扩展我的应用程序,包括利用Windows Azure 移动服务.



启用共享

除了live tiles和搜索,另一个我想在我的应用程序中启用的特性是共享.就像在http://msdn.microsoft.com/library/windows/apps/hh465261.aspx中描述的一样, Windows 8使应用程序可以通过Share charm发布数据给其它应用程序使用.
例如, 我想通过邮件给朋友发送一封有趣的RSS文件,我不需要在我的应用程序中创建所有的UI和邮件周边逻辑. 取而代之的是我只需要分享出相关的内容,和我系统中的一个邮件客户端(比如Mail应用程序), 该客户端可以像共享目标一样注册处理相关方面的操作. 通过这种方式,我可以通过其它应用程序将服务集成到我的应用程序中.



为了让这个功能可以工作,我在我的ItemDetailPage.xaml.cs文件中添加了几行代码:

privateDataTransferManager _dtm;

protected override void OnNavigatedTo(NavigationEventArgs e)

{

base.OnNavigatedTo(e);

_dtm = DataTransferManager.GetForCurrentView();

_dtm.DataRequested += ShareDataRequested;

}

protected override void OnNavigatedFrom(NavigationEventArgs e)

{

base.OnNavigatedFrom(e);

_dtm.DataRequested -= ShareDataRequested;

_dtm = null;

}

voidShareDataRequested(DataTransferManager sender,
DataRequestedEventArgs args)

{

var toShare =(SampleDataItem)flipView.SelectedItem;

if (toShare!=
null)

{

args.Request.Data.Properties.Title = toShare.Title;

args.Request.Data.Properties.Description = string.Empty;

args.Request.Data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(toShare.Content));

}

}

以上代码为当前类DataTranferManager中的DataRequested事件注册委托。当请求到来的时候,它会为已经分享到DataRequestedEventArgs 结构的数据填写一些参数,然后自己保存数据。在这个例子中,被保存的数据是HTML标记,所以我首先使用来自WinRT的HtmlFormatHelper.CreateHtmlFormat方法来确保所有正确的数据头和数据尾都在内容中,然后使用SetHtmlFormat方法将数据发送到目的地。剩下的事情Windows会处理。



你可以在这里看到结果。打开项目页面,我使用Share charm来分享我的一篇博文,选择邮件应用程序为目标地址:







启用漫游

我不想强迫我应用程序的用户在每次运行应用程序时都要重新输入他们最喜爱的blog feeds。而是希望能够在每次的运行中保存数据,就像之前运行载入的feedURLs能够在下次运行时自动载入。此外,如果一个用户在多个Windows 设备上安装了我的应用程序,我不希望他们要在每个设备上重新输入相同的feeds。而是希望他们最喜欢的这些feeds能够跟着用户漫游,无论他们登录哪个设备都将有效.



在过去,这样一个特性需要执行一个定制的后台服务,该服务的一些类型是用来与应用程序沟通的。然而,在Windows 8和 WinRT中,我们把这些服务集成到了内置中。就像在博客
http://blogs.msdn.com/b/windowsappdev/archive/2012/07/17/roaming-your-app-data.aspx中描述的那样,使用 href="http://msdn.microsoft.com/library/windows/apps/windows.storage.applicationdata.aspx" target=_blank>ApplicationData类和从ApplicationData.Current.RoamingSetting.Values
中返回的信息集,应用程序数据可以被漫游。简单的存储一对key/value在值中,无论你的应用程序在哪里跑,它都将可用。



为了利用这个优势,我在应用程序中添加了两个方法(在新PersistedState.cs文件中),一个用来序列化我收集的feeds到RoamingSettings中,另一个用来反序列化并且从RoamingSettings中返回数据:

internalstatic void SaveFeedUrls()

{

var serializer=
newDataContractSerializer(typeof(List<string>));

var feeds =
SampleDataSource.AllGroups.Select(g=> g.UniqueId).ToList();

using(var tmpStream=
newMemoryStream())

{

serializer.WriteObject(tmpStream, feeds);

ApplicationData.Current.RoamingSettings.Values["feeds"] =tmpStream.ToArray();

}

}

internalstatic IEnumerable<string> LoadFeedUrls()

{

if (!ApplicationData.Current.RoamingSettings.Values.ContainsKey("feeds"))

returnEnumerable.Empty<string>();

var serializer=
newDataContractSerializer(typeof(List<string>));

using(var tmpStream=
newMemoryStream(

(byte[])ApplicationData.Current.RoamingSettings.Values["feeds"]))

{

return (List<string>)serializer.ReadObject(tmpStream);

}

}

然后,在GroupedItemPage.LoadState(在GroupdItemPage.xaml.cs中)中,我添加所有之前保存的feeds:

protectedoverride async void LoadState(

objectnavigationParameter,
Dictionary<string,
object> pageState)

{

this.DefaultViewModel["Groups"] =
SampleDataSource.AllGroups;

foreach(var feed
inPersistedState.LoadFeedUrls())

awaitAddFeedAsync(feed);

}

在成功地加添一个新的feed后,我会添加一个调用来保存现在的设置:

if (awaitSampleDataSource.AddGroupForFeedAsync(feed))

{

UpdateTile();

PersistedState.SaveFeedUrls();

}

通过这个方法,数据就成功地被保存并通过他们的Windows设备为用户加载到我的应用程序中.



启用定制后台服务

在启用共享中,我利用了系统中被其它应用程序所支持的服务优势. 在启用漫游中,我利用了被WinRT所支持的本地化服务使其在用户设备间共享数据的优势.不过在用户间共享数据我又能利用什么优势呢?例如, 我想让一个服务允许用户发布feeds给其他用户, 让他们可以找到并且与应用程序建立关系。



为了达到这一点,我可以利用新Windows Azure 移动服务,它使从云中存放和获得数据变成一桩小事(一个简短的学习指南可以从这里获得)。



首先,我通过WindowsAzure门户网站创建一个新的移动服务:




在几秒钟之内,我就有了一个准备就绪的新移动服务等待我去定制:




通过门户网站界面,我在我的移动服务中创建一个新的”表格”用来存储有关feeds的信息,这些信息被我的应用程序所共享:




我不需要为表格配置任何模式,当我把数据加入的时候,服务默认会为我自动处理好这些数据。然而,有一件事我希望去做的是我可以利用移动服务功能的优势, 编写服务器端JavaScript脚本来处理插入, 更新,读写和删除事件。对我而言,在同样的feed URL提交不止一次的时候,我不需要重复存储条目, 所以我写了一个基本的”插入”脚本帮助我过滤掉这些重复的条目:

function insert(item, user,
request) {

tables.getTable("SharedFeed").where( { Url
: item.Url
}).read({

success :function(results) {

if (results.length
>0) {

request.respond(200, results[0]);

}

else {

request.execute();

}

},

error:function(err){

console.error(err);

request.respond(500,"Error");

}

});

}

这个编写可以使用Azure门户网站浏览器中的编辑器来完成:




当有新的条目要插入表格的时候,这个”插入”方法就会被自动调用. 我的脚本就会搜索SharedFeed表格以查找与要插入feed匹配的URL. 如果该条目已经存在,脚本就会发送一个答复给调用者,把操作当作nop指令处理。然而如果这个URL还不存在, 脚本就会开始去执行最初的请求, 然后添加一条记录。 当然, 如果存在错误, 我希望能够诊断出错误, 所以我记录它并且发送给客户端一个通用的”错误”消息, 这样我也不会泄露任何敏感信息。所有的日志信息都可以在我服务的日志板块中找到:




就这样,我的服务完全配置好了,现在我需要把它使用到我的应用程序中.这么做最简单的方法是通过WindowsAzure移动服务SDK. 安装SDK后,我会添加引用……在Visual Studio对话框中为服务添加客户端库.该库是一个通过Windows\扩展类的扩展SDK:




现在我可以使用客户端APIs与我的移动服务互动。首先, 任何用户加载feed的时候, 我希望将它存储到我之前配置好的”SharedFeed”表格中。为了这么做,我为SharedFeed类型定义了三个参数: Id,标题和Url(Id将是主键):

[DataTable(Name="SharedFeed")]

public classSharedFeed

{

[DataMember(Name="Id")]
public int Id {
get; set; }

[DataMember(Name="Title")]
public string Title {
get; set; }

[DataMember(Name="Url")]
public string Url {
get; set; }

}

在GroupedItemPage.xaml.cs中,我写了以下方法来从我的应用程序插入一条记录到服务中:

async void ShareFeedOnServerAsync(string title,
string url)

{

try

{

var table =
App.MobileService.GetTable<SharedFeed>();

awaittable.InsertAsync(newSharedFeed { Title = title, Url = url });

}

catch (Exception exc) {
Debug.WriteLine(exc.Message); }

}

并且我修改AddFeedAsync方法, 这个时候除了更新livetile和存储数据外,还将调用ShareFeedOnServer:

if (awaitSampleDataSource.AddGroupForFeedAsync(feed))

{

UpdateTile();

PersistedState.SaveFeedUrls();

ShareFeedOnServerAsync();

}

每次添加feed的时候就是自动保存我服务的时候。这里你可以在Azure门户网站看到我加载一些feeds到我的应用程序之后的结果:




现在,我需要将这些记录展示给用户看到,让他们可以从feeds的列表中做选择。这样做,我再次使用了添加项目……VisualStudio对话框,之前我用来做搜索,这次用来选择添加新的屏幕页面到我的应用程序中:




这个新的PopularFeeds.xaml文件包含比我实际添加更多地XAML。我没有显示每个元素的任何“细节”,我可以删除所有跟细节有关的界面:
· 第二列的定义
· 与显示每个项目详细信息有关的整个ScrollViewer
· 与详细控制所要做的有关的所有动画关键桢,例如itemDetail,itemDetailTitlePanel和itemDetailGrid
为了显示列表结果,我将它们添加到文件页面。一个小一点新的XAML,基于StandardStyles.xaml 文件中的Standard130ItemTemplate,我更新了PopularFeeds.xaml中引用Standard130ItemTemplate或者Standard80ItemTemplate的所有代码来替换对新FeedResultItemTemplate的引用:
<DataTemplatex:Key="FeedResultsItemTemplate">

<GridMargin="6">

<Grid.ColumnDefinitions>

<ColumnDefinitionWidth="*"/>

</Grid.ColumnDefinitions>

<StackPanelGrid.Column="0"Margin="10,0,0,0">

<TextBlockText="{BindingTitle}"

Style="{StaticResourceTitleTextStyle}"

TextWrapping="NoWrap"/>

<TextBlock Text="{BindingUrl}"

Style="{StaticResourceCaptionTextStyle}"

TextWrapping="NoWrap"/>

</StackPanel>

</Grid>

</DataTemplate>
我还修改了已经存在的resultListView中的一些新属性,这样用户就可以在列表中点击(不能长时间选择)一个元素:
IsItemClickEnabled="true"

ItemClick="ItemListView_Click”

SelectionMode="None"
为了确保在添加了可点击的feed后点击一个元素时能够导航回之前的屏幕,我在PopularFeeds.xaml文件中添加了如下代码:
async void ItemListView_Click(object sender,
ItemClickEventArgs e)

{

var url = ((SharedFeed)e.ClickedItem).Url;

awaitSampleDataSource.AddGroupForFeedAsync(url);

PersistedState.SaveFeedUrls();

this.IsEnabled=
false;

this.Frame.GoBack();

}
在新页面的后台代码中,除了DetermineVisualState方法,我删除了所有方法(我删除了其中的logicalPageBack概念),并且重新了它的LoadState方法用我移动服务中ShareFeed表格的所有条目来填充列表结果,但是只显示那些我之前没有加载过的条目:
protected override async void LoadState(

objectnavigationParameter,
Dictionary<string,
object> pageState)

{

var items =

(awaitApp.MobileService.GetTable<SharedFeed>().ToEnumerableAsync())

.Where(item => SampleDataSource.GetGroup(item.Url) ==
null)

.ToList();

this.DefaultViewModel["Items"] = items;

}
就这样,我受欢迎的Feeds页面就做好了,且导航到它的结果屏幕,用户可以轻松地选择我应用程序中其他用户使用的feeds。



在我应用程序中的主页面中(GroupedItemPage.xaml文件),我需要创建一个简单的方法让用户可以导航到受欢迎feeds的页面。这样做,我在之前创建的AppBar中添加了一个新的按钮(与我之前加入AppBar的按钮相比,我取消了StandardStyle.xaml的按钮样式):
<Buttonx:Name="btnPopularFeeds"Click="btnPopularFeeds_Click"

Style="{StaticResourceDownloadAppBarButtonStyle}" />
我添加按钮的点击事件(在GroupdItemPage.xaml.cs中)来导航到受欢迎的feeds页面:
private void btnPopularFeeds_Click(object sender,
RoutedEventArgs e)

{

this.Frame.Navigate(typeof(PopularFeeds));

}
启用消息推送
我的“受欢迎feeds”特性已经基本完成。我应用程序的用户现在可以与其他用户共享他们的feeds,并且可以轻松地添加其他用户使用的feeds。然而,一个用户添加了新的feeds并没有通知其他用户。为了解决这个问题,我想让Visual Studio, Windows 8和Windows Azure 移动服务一起支持消息推送。我只要稍作努力就可以添加消息推送功能到我的应用程序中。
首先,我需要配置我的移动服务使它支持消息推送。对于这一点,我打开我的浏览器并且导航到http://manage.dev.live.com/build,按照里面列出的步骤来注册我的应用程序。我必须填写一个简短的表格,用来自我应用程序中的Package.appxmanifest的Packaging章节的两条信息:



当我点击“我接受”按钮时,我呈现了三个信息:一个新包名称,一个客户端密码和一个包安全标识符。我将包名称复制到我应用程序Package.appxmanifest中的“包名称”字段中(在清单中,我还要将“Toastcapable”字段改为“是”),并且我将所有客户端密码和包标识符复制到我移动服务的“推送”选项卡:



随着服务配置方式的出台,我需要增加我的服务以使它可以存储连接客户的信息,这样才能给他们推送消息。这样做,我创建了一个新“Channel”表格,按照我创建“SharedFeed”表格相同的步骤来做。我还要复制“插入”脚本以淘汰重复内容,代码唯一要修改的就是搜索的表格不同。
就这样,我设置了客户端跟踪,并且当有新的feed加入的时候我能够得给他们发送所有的消息。为了达到这一目的,我再次为我的ShareFeed表格修改了“插入”脚本,添加了一个“sendNotification”方法并且在新项目插入后从“插入”脚本中调用它。
function sendNotifications(item) {

tables.getTable("Channel").read({

success: function(results)

{

results.forEach(function(channel) {

push.wns.sendToastText04(channel.channelUri, {

text1:"Newfeed recommended!",

text2: item.Title,

text3: item.Url

}, {

success:function(data) { console.log(data);
}

});

});

}

});

}

function insert(item, user, request)
{

...

request.execute();

sendNotifications(item);

...

}
当“insert”方法调用“sendNotification”时,它将从channels表格中获得所有的注册客户端,并且给每个客户端发送包含新加feed消息的toast。
这是服务端所有要做的事情。在我的客户端,我也需要为通知而注册,把Windows为我创建的Uri上推到服务.在我的App.xaml.cs文件中,我添加了如下的方法,在所有其它初始化完成以后从结构函数中调用它:
private async voidRegisterForPushNotificationsAsync()

{

try

{

var channel =
awaitPushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

var jo =
newJsonObject();

jo.Add("channelUri",
JsonValue.CreateStringValue(channel.Uri));

var table =
App.MobileService.GetTable("Channel");

awaittable.InsertAsync(jo);

}

catch(Exception exc) {
Debug.WriteLine(exc.Message); }

}
这是用WinRT中的PushNotiicationChannelManager为我的应用程序来创建一个”channel”。然后我需要把channel的Uri存储到我之前创建的移动服务表格中.
要做到这一点,不是创建一个新类新来存储Uri(就像我为SharedFeed),而是使用移动服务客户端支持的非类型化方法:创建一个新的JasonObject,把channelUri属性存储到里面,并且把JSON插入到服务中.
就这样,我的推送消息就都完成了。现在,无论何时一个新的项目添加到服务的”SharedFeed”表中时,我服务器端的”insert”方法就会被调用,
谁调用我的”sendNotification”和内建的”sendToastText04”方法,谁就把项推送到所有客户端:



如果我想,我还可以再进一步。比如,在使用应用程序的时候,用户可能会收到他们已经在阅读的feeds. 为了解决这一问题,我添加以下代码到我的RegisterForPushNotificaitonAsync方法中:
channel.PushNotificationReceived += (s, e) =>

{

var node =e.ToastNotification

.Content

.SelectSingleNode(@"//text[@id=""3""]");

e.Cancel = node != null &&

SampleDataSource.GetGroup(node.InnerText)!=
null;

};
这个事件处理会在Windows显示toast之前调用(不过只局限于前台应用程序)。
代码会检查XML是否引用了一个已经添加的数据源,如果是,它就会取消toast的消息推送.
我当然还可以再进一步,例如让用户把feeds存储到我的应用程序中,并且只推动给之前没有注册过该feed的用户.
也请注意,我没有为我的服务配置任何形式的安全措施. Windows Azure
移动服务对身份和访问控制提供了丰富的支持,但是基于本篇博文的目的,我选择不涉及任何相关内容。
启用广告
现在我的应用程序已经基本完成了,我想再做一件事:启用广告.
对于这一点,我将利用针对Windows
8的微软广告SDK.
首先,我导航到http://advertising.microsoft.com/windowsadvertising/developer,
按照里面描述的详细步骤,开始安装SDK,这只需要花费几秒钟.
一旦安装完成,
我就导航到https://pubcenter.microsoft.com注册一个账户并且为我的应用程序注册:



这不仅需要提供我应用程序的名字和类型,还要定义一个”广告单元”, 提供我应用程序的广告的大小和分类等信息:



只要完成了这些,我应用程序就已经注册显示广告了,并且系统会提供给我一个应用程序ID和一个我刚包含在我应用程序中的广告单元ID:



在安装了SDK后,返回到Visual Studio, AdControl现在就准备好可以为我所用了:



我把该控件拖拽到Visual Studio的设计器中,放到我想要的XAML的位置上,然后在提供的网站上填入我的应用程序ID和广告单元ID:



现在当我运行我的应用程序时,我就能看到广告成功地被显示了:



总结
在这个由两部分组成的博文中, 我结合Windows 功能和利用各种后台服务详细地创建了一个简单的Windows存储应用程序. 其中实际要我编写的代码很少,许多繁重的处理都有Visual Studio,
Windows 8和WindowsAzure承担了. 一路上,我建立了一个很好的用户体验,其中包括live tiles,搜索,共享合同,跨设备的漫游数据,和其他用户共享数据,消息推送和广告.



这些博文向你重点展示了这些工具和平台所能提供的一些强大功能, 我将非常高兴能够看到你们用它们来创建程序。

向您鞠躬!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐