[原]Google API学习2:Google API稍深入一步

严格的来说,这篇文章是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类型的对象。

 

            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);
            ms.Seek(
3, SeekOrigin.Begin);
            SyndicationFeed resultCalendars 
= SyndicationFeed.Load(XmlReader.Create(ms));
            
return new Atom10FeedFormatter(resultCalendars);

 

在今天下午的尝试中,我又尝试返回一个string值,让客户端去处理这个string值,然后得到AtomEntry的XML数据,再使用这些XML数据构造一个SyndicationFeed对象。

服务器端

public 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;
        }

客户端

 

        private void request_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            
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是使用如下代码(主要部分):

        protected void ParseSource(XmlReader reader, AtomSource source)
        {
            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, thisas AtomId;
                        ParseBaseLink(reader, source.Id);
                    }
                    
else if (localname.Equals(this.nameTable.Icon))
                    {
                        source.Icon 
= source.CreateAtomSubElement(reader, thisas AtomIcon;
                        ParseBaseLink(reader, source.Icon);
                    }
                    
else if (localname.Equals(this.nameTable.Logo))
                    {
                        source.Logo 
= source.CreateAtomSubElement(reader, thisas 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了。

于是迅速将服务端代码改写了如下样子:

            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);
            ms.Seek(
3, SeekOrigin.Begin);
            Atom10FeedFormatter atomFormatter 
= new Atom10FeedFormatter();
            atomFormatter.ReadFrom(XmlReader.Create(ms));
            
return atomFormatter;

客户端如下:

        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,结果正常。

写到这里仍还有些地方不太明白,CalendarFeed.SaveToXml方法调用后,得到的XML数据前面会加上三个“???”,造成XML解析错误,因此不得不使用了

ms.Seek(3, SeekOrigin.Begin);

来解决,感觉不太好...

还没有试过在Silverlight项目中能不能直接使用Google API,也许可以,但那不是我们公司的方向,所以也就不作研究了。

当然,如果直接使用Web Service的方式来访问Google API就不会存在我现在所遇到的问题了,但是那样做工程就变得更复杂了,和做一个Google API Client没什么区别了。

 

原文地址:https://www.cnblogs.com/think8848/p/1609872.html