使用 nuget server 的 API 來實現搜尋安裝 nuget 包
阿新 • • 發佈:2020-06-27
# 使用 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
-
-
-
-
-
-