天天看點

使用 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 提供的資源如下:

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

Nuget.org 提供了兩種搜尋的方式,

一個是 SearchQuery,會根據包名稱、 tag、description 等資訊去比對關鍵詞,

一個是 SearchAutocomplete 根據包名稱的字首去比對包的名稱

擷取某個 nuget 包的版本資訊,可以使用 PackageBaseAddress 來擷取

ServiceIndex

傳回的資訊示例如下:

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

傳回的資訊會有一個

resources

的數組,會包含各種不同類型的資源,對應的

@id

就是調用這種類型的API要用到的位址,下面來看一個搜尋的示例

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

在每個 API 的文檔頁面可以看到會使用的

@type

,調用這個 API 的時候應該使用這些

@type

對應的資源

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

這裡的

@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. If this query parameter is excluded, only packages with SemVer 1.0.0 compatible versions will be returned (with the standard NuGet 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 for more information

packageType

用來指定 nuget 包的類型,目前支援的類型包括

Dependency

(預設)項目依賴項,

DotnetTool

(dotnetcore 2.1 引入的 dotnet cli tool),

Template

(dotnet new 用) 自定義的項目模闆

其他的 API 可以自行參考官方文檔:https://docs.microsoft.com/en-us/nuget/api/service-index

Packages

SearchQuery

傳回的資訊比較多而且可能并不準确,适用于不清楚包的名稱的時候使用,如果知道 nuget 包的名稱(PackageId) ,可以使用

SearchAutocomplete

來搜尋,這樣更精準,傳回的資訊也更簡單,隻有比對的 package 名稱

通過原始 api 調用的方式實作 nuget 包的搜尋

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<string>() == "SearchQueryService")["@id"]
    .Value<string>();
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<string>() == "SearchAutocompleteService")["@id"]
    .Value<string>();
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 傳回示例

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

Autocomplete 傳回結果

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

從上面我們可以看到 Query 接口傳回了很多的資訊,Autocomplete 接口隻傳回了 package 的名稱,傳回的資訊更為簡潔,是以如果可以使用 Autocomplete 的方式就盡可能使用 Autocomplete 的方式

Package Versions

前面我們提到了可以使用

PackageBaseAddress

來查詢某個 nuget 包的版本資訊,文檔位址:https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource,來看一下示例:

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<string>() == "PackageBaseAddress/3.0.0")["@id"]
        .Value<string>();

    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));
}
           
使用 nuget server 的 API 來實作搜尋安裝 nuget 包
注:api 位址中的 packageId 要轉小寫

Nuget Client SDK

除了上面的根據 api 自己調用,我們還可以使用 nuget 提供的用戶端 sdk 實作上述功能,這裡就不詳細介紹了,有需要可能查閱官方文檔:https://docs.microsoft.com/en-us/nuget/reference/nuget-client-sdk

下面給出一個使用示例:

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<PackageSearchResource>();
    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<AutoCompleteResource>();
    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<FindPackageByIdResource>();
    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 包下載下傳到本地了,

實作示例:

{
    var pkgDownloadContext = new PackageDownloadContext(cache);
    var downloadRes = await repository.GetResourceAsync<DownloadResource>();

    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:

{
    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

,通過指令輸出擷取

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

Reference

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/NugetSample/RawApiSample.cs
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/NugetSample/NugetClientSdkSample.cs
  • https://docs.microsoft.com/en-us/nuget/reference/nuget-client-sdk
  • https://docs.microsoft.com/en-us/nuget/create-packages/set-package-type
  • https://docs.microsoft.com/en-us/nuget/api/overview
  • https://docs.microsoft.com/en-us/nuget/api/search-autocomplete-service-resource

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。