转载请注明出处,谢谢
废话:不太想说关于云计算的事情,主要原因是这个问题太庞大,话说起来太长,更主要的原因是我觉得云计算这个概念对我来说就好像是个抢版的预告片,现在还无法说个究竟。这两天突然发现Google真的伟大的Very!
本文将介绍使用Silverlight 3.0 + WCF 访问 Google Calendar。
这两天仅学习了Google Calendar API,因此本文将只涉及到这一个API。使用.NET访问Google Calendar API非常简单,首先到这里下载Google Data API Setup。安装完成后就可以开始开始实际的工作了。顺便提一句http://code.google.com/下面有非常多的开发资源,当然包括了Google API的文档。
一个Google Calendar的用户,可以在Google Calendar中建立多个Calendar,我们今天的任务就是获取指定用户的所有Calendar,并将其显示在Silverlight页面的DataGrid控件中。
1.新建一个Silverlight 3.0项目,同时也需要建立该Silverlight的Host项目。Google Calendar 使用CalendarEntry数据类型表示一个对象(CalendarEntry继承自AtomEntry),而且CalendarEntry也没有声明DataContract特性,很显然WCF不能使用CalendarEntry类型数组作为返回值,如果自已创建一个数据契约似乎太麻烦了,页且SOAP协议也过于复杂,数据量也大,直接使用ATOM格式更简单高效,即Silverlight调用一个WCF Service,该Service直接返回一个Atom<Entry>,这样样既不需要声明数据契约,也使用数据传输量大大减少。查了MSDN发现要使用ATOM格式的数据,需要建立Syndication Service Library项目,为了省事,同时也为了测试WCF的Syndication Service是否可以使用IIS作为Host,我将WCF服务也建在了Silverlight的Host项目中了。在下文中,我将Silverlight项目称为SL项目,将Silverlight的Host项目称为SL.Web项目。
为SL.Web项目添加Google API的引用,将Google Data API Access Control Library、Google Data API Calendar Library、Google Data API Core Library添加到SL.Web项目。为了使用ATOM格式,添加System.ServiceModel.Web的引用。
2.在SL.Web项目中,新建一个WCF Service,名称为"CalendarSvc.svc";将ICalendarSvc修改为下面代码所示:
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface ICalendarSvc
{
[OperationContract]
[WebGet(UriTemplate = "*", BodyStyle = WebMessageBodyStyle.Bare)]
SyndicationFeedFormatter GetAllCalendars();
}
3.在SL.Web项目中,将CalendarSvc.svc修改为下面代码所示:
{
public SyndicationFeedFormatter GetAllCalendars()
{
CalendarService service = new CalendarService("think8848");
service.setUserCredentials("CleverSoftDev@Gmail.com", "think848");
CalendarQuery query = new CalendarQuery("http://www.google.com/calendar/feeds/default/allcalendars/full");
CalendarFeed calendars = service.Query(query) as CalendarFeed;
MemoryStream ms = new MemoryStream();
calendars.SaveToXml(ms);
ms.Seek(3, SeekOrigin.Begin);
SyndicationFeed resultCalendars = SyndicationFeed.Load(XmlReader.Create(ms));
return new Atom10FeedFormatter(resultCalendars);
}
}
这里对调用Google Calendar API做一点解释,要想获取Google Calendar指定用户的所有Calendar,首先要创建一个CalendarService实例,并给该实例指定登录Google Calendar的用户名和密码。然后创建一个CalendarQuery实例,貌似(还没有做较深入研究,不能很确定)所有关于Calendar的查询都是通过CalendarQuery对象来进行的,指下查询路径为"http://www.google.com/calendar/feeds/default/allcalendars/full",然后使用CalendarService实例来进行查询,得到的查询结果为一个CalendarFeed对象,CalendarFeed.Entris里面包含了该用户的所有Calendar。接下来的工作是如何把CalendarFeed对象转换为.NET平台上的等价类型,经查MSDN得知.NET的SyndicationFeed和CalendarFeed是等价类型,转换的方式有两种,一种是对象赋值,另一种是使用CalendarFeed对象的XML数据生成一个SyndicationFeed对象,为了方便我使用了第二种方式,这里值的一提的是,如果使GetAllCalendars方法直接返回CalendarFeed对象的XML格式字符串,然后在客户端代码中使用该字符中看起来更效率高点,但是如果直接使用string类型作返回值,WCF又会给将这个字符中序列化成string的XML表示形式,使表示CalendarFeed对象的ATOM格式变成了string对象的XML表示形式,使用该string的XML数据无法方便的转换成SyndicationFeed对象,为了方便,我先将CalendarFeed的存入到一个MemoryStream,然后再读取这个MemoryStream,生成一个SyndicationFeed对象,然后返回一个SyndicationFeedFormatter对象。
---------------2009-11-24修改内容-------------------
今天又做了点修改,修改内容为在WCF服务里面,直接返回string类型,而不是将Google AtomEntry转成SyndicationFeed,然后再使用SyndicationFeedFormatter作为返回值,这样做到底能不能提高效率现在还没有深入思考,这里只是提出另一种作法,而且从XML数据提取string类型值的作法个人也感觉不满意,如果大家还有更好的作法,还请指教。
服务契约代码修改为:
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface ICalendarSvc
{
[OperationContract]
[WebGet(UriTemplate = "GetAllCalendars", BodyStyle = WebMessageBodyStyle.Bare)]
string GetAllCalendars();
}
服务实现代码修改为:
{
CalendarService service = new CalendarService("think8848");
service.setUserCredentials("CleverSoftDev@Gmail.com", "think8848");
CalendarQuery query = new CalendarQuery("http://www.google.com/calendar/feeds/default/allcalendars/full");
CalendarFeed calendars = service.Query(query) as CalendarFeed;
MemoryStream ms = new MemoryStream();
calendars.SaveToXml(ms);
ASCIIEncoding encoding = new ASCIIEncoding();
string atomEntry = encoding.GetString(ms.ToArray());
atomEntry = atomEntry.Substring(3, atomEntry.Length - 3);
return atomEntry;
}
XL的MainPage.xaml.cs中的相关方法修改为:
{
using (var tmpReader = XmlReader.Create(new StringReader(e.Result)))
{
tmpReader.ReadStartElement();
using (var reader = XmlReader.Create(new StringReader(tmpReader.Value)))
{
var feed = SyndicationFeed.Load(reader);
this.dgCalendar.ItemsSource = feed.Items;
}
}
}
------------------------------------------------------
4.因为我们需要使用Syndication Service,因此这个普通的WCF Service还不能直接使用,必须要修改Web.config中的配置才可以。修改Web.config中的system.serviceModel节点,以下面代码所示(您可能需要根据自已的实际情况修改下面代码中的某些值以适应自已的项目):
<!--<behaviors>
<serviceBehaviors>
<behavior name="SLCalendarTest.Web.WCFServices.CalendarSvcBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>-->
<services>
<!--<service behaviorConfiguration="SLCalendarTest.Web.WCFServices.CalendarSvcBehavior" name="SLCalendarTest.Web.WCFServices.CalendarSvc">
<endpoint address="" binding="wsHttpBinding" contract="SLCalendarTest.Web.WCFServices.ICalendarSvc">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>-->
<service name="SLCalendarTest.Web.WCFServices.CalendarSvc">
<host>
<baseAddresses>
<add baseAddress="http://localhost:2224/Design_Time_Addresses/SyndicationService/" />
</baseAddresses>
</host>
<endpoint contract="SLCalendarTest.Web.WCFServices.ICalendarSvc" address="CalendarSvc" binding="webHttpBinding" behaviorConfiguration="SLCalendarTest.Web.WCFServices.CalendarSvcBehavior"/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="SLCalendarTest.Web.WCFServices.CalendarSvcBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
5.搭建好了服务端,我们回过头来看客户端(SL项目)。将MainPage.xmal修改成下面代码所示:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="btnLoad" Width="75" Height="23" Content="Load" Grid.Row="0" Click="btnLoad_Click"></Button>
<data:DataGrid Grid.Row="1" x:Name="dgCalendar" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding Id}" Header="Id" />
<data:DataGridTextColumn Binding="{Binding Title.Text}" Header="Title"/>
<data:DataGridTextColumn Binding="{Binding Summary.Text}" Header="Summary"/>
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
</UserControl>
将MainPage.xaml.cs修改下面代码所示:
{
public MainPage()
{
InitializeComponent();
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
var request = new WebClient();
request.DownloadStringCompleted += new DownloadStringCompletedEventHandler(request_DownloadStringCompleted);
request.DownloadStringAsync(new Uri("http://localhost:2224/WCFServices/CalendarSvc.svc/CalendarSvc/"));
}
private void request_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var reader = XmlReader.Create(new StringReader(e.Result));
var feed = SyndicationFeed.Load(reader);
this.dgCalendar.ItemsSource = feed.Items;
}
}
OK,不出意外,程序将正确执行,点击"Load"按钮将如下图所示:
现在有一个问题,如果这个WCF Service中有多个方法时该如何调用,换句话说,如果通过指定方法名称的方式调用该方法?
只需要在SL.Web项目的ICalendarSvc中指定下面语句:
SyndicationFeedFormatter GetAllCalendars();
并在SL项目的MainPage.xaml.cs中指定下面语句:
就可以了。
参考资料:
http://code.google.com/intl/zh-CN/apis/calendar/data/2.0/developers_guide_dotnet.html