上个月发布了一个WP8.1应用:NuGet Search,并在GitHub开了源:https://github.com/EdiWang/WP-NuGetSearch

NuGet的服务接口是个WCF OData Service,为了装逼,我尝试使用了跨Framework的PCI工程,结果no zuo no die了。和一般.NET类库不同,PCL有些tricky的地方要爆。下面是开荒成功的代码:

public async Task<Response<IEnumerable<V2FeedPackage>>> GetDataAsync(string searchTerm, int pageIndex, bool includePreRelease = false)
 {
     try
     {
         IDictionary<string, object> queryOptions = new Dictionary<string, object> {
             { "filter", "IsLatestVersion" },
             { "searchTerm", "'" + UrlEncodeOdataParameter(searchTerm) + "'" },
             { "includePrerelease", includePreRelease.ToString().ToLower() }
         };
 
 
         if (pageIndex < 1)
         {
             pageIndex = 1;
         }
 
 
         int startRow = (pageIndex - 1) * PageSize;
         var nquery = context.CreateQuery<V2FeedPackage>("Search")
                             .OrderByDescending(p => p.DownloadCount)
                             .Skip(startRow)
                             .Take(PageSize);
 
 
         var query = (DataServiceQuery<V2FeedPackage>)nquery;
         if (queryOptions.Any())
         {
             query = queryOptions.Aggregate(query, (current, pair) => current.AddQueryOption(pair.Key, pair.Value));
         }
 
 
         var taskFactory = new TaskFactory<IEnumerable<V2FeedPackage>>();
         var result = await taskFactory.FromAsync(query.BeginExecute(null, null), query.EndExecute);
         var packages = result.ToList();
 
 
         return new Response<IEnumerable<V2FeedPackage>>()
         {
             IsSuccess = true,
             Item = packages
         };
     }
     catch (Exception e)
     {
         return new Response<IEnumerable<V2FeedPackage>>()
         {
             Message = e.Message
         };
     }
 }

需要注意的:

1. 对于非标准的操作符,需要使用“AddQueryOption()”方法把它们撸进去,就像我代码里一样,用了个字典包装这些参数

IDictionary<string, object> queryOptions = new Dictionary<string, object> {
    { "filter", "IsLatestVersion" },
    { "searchTerm", "'" + UrlEncodeOdataParameter(searchTerm) + "'" },
    { "includePrerelease", includePreRelease.ToString().ToLower() }
};

2. 由于OData Client的PCL版本只支持异步操作,但它又没暴露基于Task的异步方法(你TM在逗我?),我们只能用TaskFactory把IAsyncResult撸成awaitable的Task

var taskFactory = new TaskFactory<IEnumerable<V2FeedPackage>>();
var result = await taskFactory.FromAsync(query.BeginExecute(null, null), query.EndExecute);

3. 对于标准LINQ操作,OData库可以正确翻译到URL,所以可以放心写这样的查询语句:

var nquery = context.CreateQuery<V2FeedPackage>("Search")
                    .OrderByDescending(p => p.DownloadCount)
                    .Skip(startRow)
                    .Take(PageSize);

翻译后的URL:

https://www.nuget.org/api/v2/Search()?$orderby=DownloadCount
desc&$skip=0&$top=30

带参数字典的:

https://www.nuget.org/api/v2/Search()?$orderby=DownloadCount
desc&$skip=0&$top=30&filter=IsLatestVersion&searchTerm=''&includePrerelease=false

4. Response格式的例子:

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="https://www.nuget.org/api/v2/curated-feeds/microsoftdotnet/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>https://www.nuget.org/api/v2/curated-feeds/microsoftdotnet/Search</id>
  <title type="text">Search</title>
  <updated>2014-06-01T02:31:18Z</updated>
  <link rel="self" title="Search" href="Search" />
  <entry>
    <id>https://www.nuget.org/api/v2/curated-feeds/microsoftdotnet/Packages(Id='EntityFramework',Version='6.1.0')</id>
    <category term="NuGetGallery.V2FeedPackage" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="V2FeedPackage" href="Packages(Id='EntityFramework',Version='6.1.0')" />
    <title type="text">EntityFramework</title>
    <summary type="text">Entity Framework is Microsoft's recommended data access technology for new applications.</summary>
    <updated>2014-05-20T18:39:08Z</updated>
    <author>
      <name>Microsoft</name>
    </author>
    <link rel="edit-media" title="V2FeedPackage" href="Packages(Id='EntityFramework',Version='6.1.0')/$value" />
    <content type="application/zip" data-original="https://www.nuget.org/api/v2/curated-feeds/package/EntityFramework/6.1.0" />
    <m:properties>
      <d:Version>6.1.0</d:Version>
      <d:NormalizedVersion>6.1.0</d:NormalizedVersion>
      <d:Copyright m:null="true" />
      <d:Created m:type="Edm.DateTime">2014-03-17T21:38:19.813</d:Created>
      <d:Dependencies></d:Dependencies>
      <d:Description>Entity Framework is Microsoft's recommended data access technology for new applications.</d:Description>
      <d:DownloadCount m:type="Edm.Int32">5345924</d:DownloadCount>
      <d:GalleryDetailsUrl>https://www.nuget.org/packages/EntityFramework/6.1.0</d:GalleryDetailsUrl>
      <d:IconUrl>http://go.microsoft.com/fwlink/?LinkID=386613</d:IconUrl>
      <d:IsLatestVersion m:type="Edm.Boolean">true</d:IsLatestVersion>
      <d:IsAbsoluteLatestVersion m:type="Edm.Boolean">false</d:IsAbsoluteLatestVersion>
      <d:IsPrerelease m:type="Edm.Boolean">false</d:IsPrerelease>
      <d:Language>en-US</d:Language>
      <d:Published m:type="Edm.DateTime">2014-03-17T21:38:19.877</d:Published>
      <d:PackageHash>u/M0OEfqxTUsL5BwzD66eBGm278/ozqdLK3JvMO6QwUxxc+z7ZUkTYm4suDhWRqzkc6mOhvXDQY5dZUwbldxyQ==</d:PackageHash>
      <d:PackageHashAlgorithm>SHA512</d:PackageHashAlgorithm>
      <d:PackageSize m:type="Edm.Int64">4222059</d:PackageSize>
      <d:ProjectUrl>http://go.microsoft.com/fwlink/?LinkID=320540</d:ProjectUrl>
      <d:ReportAbuseUrl>https://www.nuget.org/package/ReportAbuse/EntityFramework/6.1.0</d:ReportAbuseUrl>
      <d:ReleaseNotes m:null="true" />
      <d:RequireLicenseAcceptance m:type="Edm.Boolean">true</d:RequireLicenseAcceptance>
      <d:Tags>Microsoft EF Database Data O/RM ADO.NET</d:Tags>
      <d:Title>EntityFramework</d:Title>
      <d:VersionDownloadCount m:type="Edm.Int32">345877</d:VersionDownloadCount>
      <d:MinClientVersion m:null="true" />
      <d:LastEdited m:type="Edm.DateTime" m:null="true" />
      <d:LicenseUrl>http://go.microsoft.com/fwlink/?LinkID=320539</d:LicenseUrl>
      <d:LicenseNames m:null="true" />
      <d:LicenseReportUrl m:null="true" />
    </m:properties>
  </entry>
.....
.....
</feed>