严格的来说,这篇文章是Google API学习1:Silverlight显示Google Calendar中的内容,但是又觉得今天的认识比昨天稍深入了一步,再加到昨天的文章似乎有点不能突出重点,因此有必要新增一篇。
昨天使用了Google API的.NET Client Library对Google Calendar进行了访问,但是一直觉得有些地方不太满意,最主要的原因可能是没有弄明白Google API的内部原理吧,老是觉得心里不踏实,今天又重新把Google API的安装文件夹看了一遍,发现在C:\Program Files\Google\Google Data API SDK\Sources\Library下面似乎有Google API的源代码,于是尝试在原来的解决方案中使用这些源代码,通过调试的方式发现Google API的实现细节,很幸运,添加了这些项目的源代码,程序居然能成功编译,运行后也能正常加载Google Calendar的数据!值得一提的是,因为我只是研究Google Calendar,所以只添加了Core Client、Calendar、Common Data Extensions三个项目。
昨天的学习最让我感到不满的是在获取所有日历的方法内部,通过CalendarService和CalendarQuery我获取到了包含所有Calendar的CalendarFeed对象,但是为了使客户端能获取CalendarFeed对象,我使用SyndicationFeed对象给“中转”了一下,最终GetAllCalendars返回的是SyndicationFeedFormatter类型的对象。
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);
ms.Seek(3, SeekOrigin.Begin);
SyndicationFeed resultCalendars = SyndicationFeed.Load(XmlReader.Create(ms));
return new Atom10FeedFormatter(resultCalendars);
在今天下午的尝试中,我又尝试返回一个string值,让客户端去处理这个string值,然后得到AtomEntry的XML数据,再使用这些XML数据构造一个SyndicationFeed对象。
服务器端
{
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;
}
客户端
{
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;
}
}
}
其实今天的这个方法更CUO,基本上没有任何意义,因为服务本来应该是完成自已应该完成的功能,而不应该把自已的职责交给客户端来完成,这样做虽然少了CalendarFeed转换SyndicationFeed的动作,但不见得效率会提高多少,而且从结构上来讲,实在是不值得。晚上想了好久,写这些代码出来,唯一的收获就是把使用XmlReader对象熟悉了一点。
晚上的想法比较简单,能不能直接把CalendarFeed输出,使客户端能接受到AtomEntry呢,第一步想到的是,如果在Google API提供一个方法,用获取到的CalendarFeed生成一个类似于SyndicationFeedFormatter的对象那就最好了,找了半天,没有找到。第二步想到的是,能不能找到生成CalendarFeed之前的AtomEntry,然后使用这个XML数据生成一个SyndicationFeedFormatter对象,通过跟踪,发现Google API内部生成CalendarFeed是使用如下代码(主要部分):
{
Tracing.Assert(reader != null, "reader should not be null");
if (reader == null)
{
throw new ArgumentNullException("reader");
}
Tracing.Assert(source != null, "source should not be null");
if (source == null)
{
throw new ArgumentNullException("source");
}
Tracing.TraceCall();
//
// atomSource =
// element atom:source {
// atomCommonAttributes,
// (atomAuthor?
// & atomCategory*
// & atomContributor*
// & atomGenerator?
// & atomIcon?
// & atomId?
// & atomLink*
// & atomLogo?
// & atomRights?
// & atomSubtitle?
// & atomTitle?
// & atomUpdated?
// & extensionElement*)
// this will also parse the gData extension elements.
// }
int depth = -1;
ParseBasicAttributes(reader, source);
while (NextChildElement(reader, ref depth))
{
object localname = reader.LocalName;
AtomFeed feed = source as AtomFeed;
if (IsCurrentNameSpace(reader, BaseNameTable.NSAtom))
{
if (localname.Equals(this.nameTable.Title))
{
source.Title = ParseTextConstruct(reader, source);
}
else if (localname.Equals(this.nameTable.Updated))
{
source.Updated = DateTime.Parse(Utilities.DecodedValue(reader.ReadString()), CultureInfo.InvariantCulture);
}
else if (localname.Equals(this.nameTable.Link))
{
source.Links.Add(ParseLink(reader, source));
}
else if (localname.Equals(this.nameTable.Id))
{
source.Id = source.CreateAtomSubElement(reader, this) as AtomId;
ParseBaseLink(reader, source.Id);
}
else if (localname.Equals(this.nameTable.Icon))
{
source.Icon = source.CreateAtomSubElement(reader, this) as AtomIcon;
ParseBaseLink(reader, source.Icon);
}
else if (localname.Equals(this.nameTable.Logo))
{
source.Logo = source.CreateAtomSubElement(reader, this) as AtomLogo;
ParseBaseLink(reader, source.Logo);
}
else if (localname.Equals(this.nameTable.Author))
{
source.Authors.Add(ParsePerson(reader, source));
}
else if (localname.Equals(this.nameTable.Contributor))
{
source.Contributors.Add(ParsePerson(reader, source));
}
else if (localname.Equals(this.nameTable.Subtitle))
{
source.Subtitle = ParseTextConstruct(reader, source);
}
else if (localname.Equals(this.nameTable.Rights))
{
source.Rights = ParseTextConstruct(reader, source);
}
else if (localname.Equals(this.nameTable.Generator))
{
source.Generator = ParseGenerator(reader, source);
}
else if (localname.Equals(this.nameTable.Category))
{
// need to make this another colleciton
source.Categories.Add(ParseCategory(reader, source));
}
else if (feed != null && localname.Equals(this.nameTable.Entry))
{
ParseEntry(reader);
}
// this will either move the reader to the end of an element
// if at the end, to the start of a new one.
reader.Read();
}
else if (feed != null && IsCurrentNameSpace(reader, BaseNameTable.gBatchNamespace))
{
// parse the google batch extensions if they are there
ParseBatch(reader, feed);
}
else if (feed != null && IsCurrentNameSpace(reader, BaseNameTable.OpenSearchNamespace(this.versionInfo)))
{
if (localname.Equals(this.nameTable.TotalResults))
{
feed.TotalResults = int.Parse(Utilities.DecodedValue(reader.ReadString()), CultureInfo.InvariantCulture);
}
else if (localname.Equals(this.nameTable.StartIndex))
{
feed.StartIndex = int.Parse(Utilities.DecodedValue(reader.ReadString()), CultureInfo.InvariantCulture);
}
else if (localname.Equals(this.nameTable.ItemsPerPage))
{
feed.ItemsPerPage = int.Parse(Utilities.DecodedValue(reader.ReadString()), CultureInfo.InvariantCulture);
}
}
else
{
// default extension parsing.
ParseExtensionElements(reader, source);
}
}
return;
}
值的一提的是这里的reader归根到底实际上是从HttpWebResponse生成的。
找了很久,貌似没有简单方便的将CalendarFeed的XML数据获取到,似乎只有使用CalendarFeed.SaveToXml方法才能获取到XML数据,很无奈,又回过头来.NET Framework中找办法,一查MSDN,有了新的发现,在MSDN中发现Atom10FeedFormatter类型有一个下面这个方法
ReadFrom | 从指定的 XmlReader 实例读取 Atom 1.0 联合源。 (继承自 Atom10FeedFormatter。) |
CalendarFeed将其XML表示形式的数据写到了一个Stream中,而XmlReader又是通过一个Stream构造出来的,这就意味着在服务端的代码中不再需要SyndicationFeed对象来中转了,客户端可以直接使用使用AtomEntry了。
于是迅速将服务端代码改写了如下样子:
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);
ms.Seek(3, SeekOrigin.Begin);
Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter();
atomFormatter.ReadFrom(XmlReader.Create(ms));
return atomFormatter;
客户端如下:
{
var reader = XmlReader.Create(new StringReader(e.Result));
var feed = SyndicationFeed.Load(reader);
this.dgCalendar.ItemsSource = feed.Items;
}
运行一下,OK,结果正常。
写到这里仍还有些地方不太明白,CalendarFeed.SaveToXml方法调用后,得到的XML数据前面会加上三个“???”,造成XML解析错误,因此不得不使用了
来解决,感觉不太好...
还没有试过在Silverlight项目中能不能直接使用Google API,也许可以,但那不是我们公司的方向,所以也就不作研究了。
当然,如果直接使用Web Service的方式来访问Google API就不会存在我现在所遇到的问题了,但是那样做工程就变得更复杂了,和做一个Google API Client没什么区别了。