1. 程式人生 > >使用 nuget server 的 API 來實現搜尋安裝 nuget 包

使用 nuget server 的 API 來實現搜尋安裝 nuget 包

# 使用 nuget server 的 API 來實現搜尋安裝 nuget 包 ## Intro nuget 現在幾乎是 dotnet 開發不可缺少的一部分了,還沒有用過 nuget 的就有點落後時代了,還不快用起來 nuget 是 dotnet 裡的包管理機制,類似於前端的 npm ,php 的 composer,java 裡的 maven ... nuget 定義了一套關於 nuget server 的規範,使得使用者可以自己實現一個 nuget server 也正是這些規範,使得我們可以根據這些規範來實現 nuget server 的包管理的功能,今天主要介紹一下,根據 nuget server 的 api 規範使用原始的 HTTP 請求來實現 nuget 包的搜尋和使用 nuget 提供的客戶端 SDK 來實現 nuget 包的搜尋和下載 ## Nuget Server Api ### Nuget 協議介紹 nuget 的協議有好幾個版本,目前主要用的是 v3,開源的 nuget server Baget 也實現了基於 nuget protocal v3 的規範 我們新增 nuget 源的時候會指定一個 source url,類似 `https://api.nuget.org/v3/index.json` 這樣的,著通常被稱為 Service Index,是一個 nuget 源的入口,有點類似於 Identity Server 裡的發現文件,通過這個地址可以獲取到一系列的資源的地址 有一些資源是協議規範裡定義的必須要實現的,有一些是可選的,具體參考官方文件,以後隨著版本變化,可能會有差異,目前 nuget.org 提供的資源如下: ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627111811819-279166472.png) Nuget.org 提供了兩種搜尋的方式, 一個是 SearchQuery,會根據包名稱、 tag、description 等資訊去匹配關鍵詞, 一個是 SearchAutocomplete 根據包名稱的字首去匹配包的名稱 獲取某個 nuget 包的版本資訊,可以使用 PackageBaseAddress 來獲取 `ServiceIndex` 返回的資訊示例如下: ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627111829479-1461215919.png) 返回的資訊會有一個 `resources` 的陣列,會包含各種不同型別的資源,對應的 `@id` 就是呼叫這種型別的API要用到的地址,下面來看一個搜尋的示例 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627111854100-624274919.png) 在每個 API 的文件頁面可以看到會使用的 `@type`,呼叫這個 API 的時候應該使用這些 `@type` 對應的資源 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627111921656-665319921.png) 這裡的 `@id` 就是上面的 resource 對應的 `@id`, 引數說明: `q` 搜尋時所用的關鍵詞, `skip`/`take` 用來分頁顯示查詢結果, `prelease` 用來指定是否限制預覽版的 package,`true` 包含預覽版的 nuget 包,`false` 只包含已經正式釋出的 nuget 包 `semVerLevel` 是用來指定包的語義版本 > The `semVerLevel` query parameter is used to opt-in to [SemVer 2.0.0 packages](https://github.com/NuGet/Home/wiki/SemVer2-support-for-nuget.org-(server-side)#identifying-semver-v200-packages). If this query parameter is excluded, only packages with SemVer 1.0.0 compatible versions will be returned (with the [standard NuGet versioning](https://docs.microsoft.com/en-us/nuget/concepts/package-versioning) caveats, such as version strings with 4 integer pieces). If `semVerLevel=2.0.0` is provided, both SemVer 1.0.0 and SemVer 2.0.0 compatible packages will be returned. See the [SemVer 2.0.0 support for nuget.org](https://github.com/NuGet/Home/wiki/SemVer2-support-for-nuget.org-(server-side)) for more information `packageType` 用來指定 nuget 包的型別,目前支援的型別包括 `Dependency`(預設)專案依賴項,`DotnetTool`(dotnetcore 2.1 引入的 dotnet cli tool),`Template` (dotnet new 用) 自定義的專案模板 其他的 API 可以自行參考官方文件:
### Packages `SearchQuery` 返回的資訊比較多而且可能並不準確,適用於不清楚包的名稱的時候使用,如果知道 nuget 包的名稱(PackageId) ,可以使用 `SearchAutocomplete` 來搜尋,這樣更精準,返回的資訊也更簡單,只有匹配的 package 名稱 通過原始 api 呼叫的方式實現 nuget 包的搜尋 ``` csharp using var httpClient = new HttpClient(new NoProxyHttpClientHandler()); // loadServiceIndex var serviceIndexResponse = await httpClient.GetStringAsync(NugetServiceIndex); var serviceIndexObject = JObject.Parse(serviceIndexResponse); var keyword = "weihanli"; //https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource var queryEndpoint = serviceIndexObject["resources"] .First(x =>
x["@type"].Value() == "SearchQueryService")["@id"] .Value(); var queryUrl = $"{queryEndpoint}?q={keyword}&skip=0&take=5&prerelease=false&semVerLevel=2.0.0"; var queryResponse = await httpClient.GetStringAsync(queryUrl); Console.WriteLine($"formatted queryResponse:"); Console.WriteLine($"{JObject.Parse(queryResponse).ToString(Formatting.Indented)}"); // https://docs.microsoft.com/en-us/nuget/api/search-autocomplete-service-resource var autoCompleteQueryEndpoint = serviceIndexObject["resources"] .First(x => x["@type"].Value() == "SearchAutocompleteService")["@id"] .Value(); var autoCompleteQueryUrl = $"{autoCompleteQueryEndpoint}?q={keyword}&skip=0&take=5&prerelease=false&semVerLevel=2.0.0"; var autoCompleteQueryResponse = await httpClient.GetStringAsync(autoCompleteQueryUrl); Console.WriteLine($"formatted autoCompleteQueryResponse:"); Console.WriteLine($"{JObject.Parse(autoCompleteQueryResponse).ToString(Formatting.Indented)}"); ``` output 示例: Query 返回示例 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627112006959-762099850.png) Autocomplete 返回結果 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627112034651-426780939.png) 從上面我們可以看到 Query 介面返回了很多的資訊,Autocomplete 介面只返回了 package 的名稱,返回的資訊更為簡潔,所以如果可以使用 Autocomplete 的方式就儘可能使用 Autocomplete 的方式 ### Package Versions 前面我們提到了可以使用 `PackageBaseAddress` 來查詢某個 nuget 包的版本資訊,文件地址:
,來看一下示例: ``` csharp using (var httpClient = new HttpClient(new NoProxyHttpClientHandler())) { // loadServiceIndex var serviceIndexResponse = await httpClient.GetStringAsync(NugetServiceIndex); var serviceIndexObject = JObject.Parse(serviceIndexResponse); // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource var packageVersionsEndpoint = serviceIndexObject["resources"] .First(x => x["@type"].Value() == "PackageBaseAddress/3.0.0")["@id"] .Value(); var packageVersionsQueryUrl = $"{packageVersionsEndpoint}/dbtool.core/index.json"; var packageVersionsQueryResponse = await httpClient.GetStringAsync(packageVersionsQueryUrl); Console.WriteLine("DbTool.Core versions:"); Console.WriteLine(JObject.Parse(packageVersionsQueryResponse) .ToString(Formatting.Indented)); } ``` output 示例: ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627112102954-342768753.png) > 注:api 地址中的 packageId 要轉小寫 ## Nuget Client SDK 除了上面的根據 api 自己呼叫,我們還可以使用 nuget 提供的客戶端 sdk 實現上述功能,這裡就不詳細介紹了,有需要可能查閱官方文件: 下面給出一個使用示例: ``` csharp var packageId = "WeihanLi.Common"; var packageVersion = new NuGetVersion("1.0.38"); var logger = NullLogger.Instance; var cache = new SourceCacheContext(); // 在 SDK 的概念裡,每一個 nuget 源是一個 repository var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); // SearchQuery { var resource = await repository.GetResourceAsync(); var searchFilter = new SearchFilter(includePrerelease: false); var results = await resource.SearchAsync( "weihanli", searchFilter, skip: 0, take: 20, logger, CancellationToken.None); foreach (var result in results) { Console.WriteLine($"Found package {result.Identity.Id} {result.Identity.Version}"); } } // SearchAutoComplete { var autoCompleteResource = await repository.GetResourceAsync(); var packages = await autoCompleteResource.IdStartsWith("WeihanLi", false, logger, CancellationToken.None); foreach (var package in packages) { Console.WriteLine($"Found Package {package}"); } } // { // get package versions var findPackageByIdResource = await repository.GetResourceAsync(); var versions = await findPackageByIdResource.GetAllVersionsAsync( packageId, cache, logger, CancellationToken.None); foreach (var version in versions) { Console.WriteLine($"Found version {version}"); } } ``` ## More 你可以使用 nuget sdk 方便的實現 nuget 包的下載安裝,內部實現了簽名校驗等,這樣就可以把本地不存在的 nuget 包下載到本地了, 實現示例: ``` csharp { var pkgDownloadContext = new PackageDownloadContext(cache); var downloadRes = await repository.GetResourceAsync(); var downloadResult = await RetryHelper.TryInvokeAsync(async () => await downloadRes.GetDownloadResourceResultAsync( new PackageIdentity(packageId, packageVersion), pkgDownloadContext, @"C:\Users\liweihan\.nuget\packages", // nuget globalPackagesFolder logger, CancellationToken.None), r => true); Console.WriteLine(downloadResult.Status.ToString()); } ``` 最後提供一個解析 nuget `globalPackagesFolder` 的兩種思路: 一個是前面有篇文章介紹的,有個預設的配置檔案,然後就是預設的配置,寫了一個解析的方法示例,支援 Windows/Linux/Mac: ``` csharp { var packagesFolder = Environment.GetEnvironmentVariable("NUGET_PACKAGES"); if (string.IsNullOrEmpty(packagesFolder)) { // Nuget globalPackagesFolder resolve if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var defaultConfigFilePath = $@"{Environment.GetEnvironmentVariable("APPDATA")}\NuGet\NuGet.Config"; if (File.Exists(defaultConfigFilePath)) { var doc = new XmlDocument(); doc.Load(defaultConfigFilePath); var node = doc.SelectSingleNode("/configuration/config/add[@key='globalPackagesFolder']"); if (node != null) { packagesFolder = node.Attributes["value"]?.Value; } } if (string.IsNullOrEmpty(packagesFolder)) { packagesFolder = $@"{Environment.GetEnvironmentVariable("USERPROFILE")}\.nuget\packages"; } } else { var defaultConfigFilePath = $@"{Environment.GetEnvironmentVariable("HOME")}/.config/NuGet/NuGet.Config"; if (File.Exists(defaultConfigFilePath)) { var doc = new XmlDocument(); doc.Load(defaultConfigFilePath); var node = doc.SelectSingleNode("/configuration/config/add[@key='globalPackagesFolder']"); if (node != null) { packagesFolder = node.Attributes["value"]?.Value; } } if (string.IsNullOrEmpty(packagesFolder)) { defaultConfigFilePath = $@"{Environment.GetEnvironmentVariable("HOME")}/.nuget/NuGet/NuGet.Config"; if (File.Exists(defaultConfigFilePath)) { var doc = new XmlDocument(); doc.Load(defaultConfigFilePath); var node = doc.SelectSingleNode("/configuration/config/add[@key='globalPackagesFolder']"); if (node != null) { packagesFolder = node.Value; } } } if (string.IsNullOrEmpty(packagesFolder)) { packagesFolder = $@"{Environment.GetEnvironmentVariable("HOME")}/.nuget/packages"; } } } Console.WriteLine($"globalPackagesFolder: {packagesFolder}"); } ``` 另一個是可以根據 nuget 提供的一個命令查詢 `nuget locals global-packages -l`,通過命令輸出獲取 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200627112133460-1742942992.png) ## Reference - - - - - -