성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] A graphical depiction of the steps ...
[정성태] 질문을 주셔서 출판사 측에 문의를 했습니다. 약 한 달 정도 후...
[Thorondor
] @정성태 개인 블로그인데도 거의 커뮤니티 급 인 것 같아요. 요...
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - REST API를 이용해 NuGet 저장소 제어</h1> <p> NuGet 저장소의 REST API는 다음의 공식 문서에서 자세하게 설명하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NuGet API ; <a target='tab' href='https://learn.microsoft.com/en-us/nuget/api/overview'>https://learn.microsoft.com/en-us/nuget/api/overview</a> </pre> <br /> 물론 다음과 같은 잘 정리된 라이브러리를 이용해 제어하는 것도 가능하지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NuGet.Protocol ; <a target='tab' href='https://www.nuget.org/packages/NuGet.Protocol/4.8.0'>https://www.nuget.org/packages/NuGet.Protocol/4.8.0</a> </pre> <br /> 이 글에서는 그냥 HTTP 통신으로 만들어 보겠습니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 시작점은 다음의 JSON 결과물로부터 출발합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <a target='tab' href='https://api.nuget.org/v3/index.json'>https://api.nuget.org/v3/index.json</a> [index.json 파일 내용] { "version": "3.0.0", "resources": [ { <span style='color: blue; font-weight: bold'>"@id": "https://api-v2v3search-0.nuget.org/query", "@type": "SearchQueryService",</span> "comment": "Query endpoint of NuGet Search service (primary)" }, { <span style='color: blue; font-weight: bold'>"@id": "https://api-v2v3search-1.nuget.org/query", "@type": "SearchQueryService",</span> "comment": "Query endpoint of NuGet Search service (secondary)" }, ...[생략]... { "@id": "https://api.nuget.org/v3/registration3-gz-semver2/", "@type": "RegistrationsBaseUrl/Versioned", "clientVersion": "4.3.0-alpha", "comment": "Base URL of Azure storage where NuGet package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }, { "@id": "https://api.nuget.org/v3/catalog0/index.json", "@type": "Catalog/3.0.0", "comment": "Index of the NuGet package catalog." } ], "@context": { "@vocab": "http://schema.nuget.org/services#", "comment": "http://www.w3.org/2000/01/rdf-schema#comment" } } </pre> <br /> 보는 바와 같이 "@type"에 해당하는 기능을 "@id"에서 제공하는 링크를 통해 서비스를 제공합니다. 따라서, 다음과 같은 코드로 시작할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace NugetRestClient { class NugetClient { const string _sourceUrl = "https://api.nuget.org/v3/index.json"; CookieContainer _cookies; HttpClient _httpClient; ServiceIndex _serviceIndex; private string GetServiceEndPoint(string serviceTypeName) { foreach (var item in _serviceIndex.resources) { if (item.type == serviceTypeName) { return item.id; } } throw new ApplicationException("ServiceNotFound: " + serviceTypeName); } public NugetClient() { HttpClientHandler handler = new HttpClientHandler(); _cookies = new CookieContainer(); handler.CookieContainer = _cookies; HttpClient hc = new HttpClient(handler); _httpClient = hc; } public async Task GetFeedAsync() { string text = await _httpClient.GetStringAsync(_sourceUrl); _serviceIndex = Newtonsoft.Json.JsonConvert.DeserializeObject<ServiceIndex>(text); } } } </pre> <br /> 위의 코드를 이용해 다음과 같이 service index 정보를 가져옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static async Task<int> Main(string[] args) { NugetClient client = new NugetClient(); await client.GetFeedAsync(); return 0; } </pre> <br /> 이제 NuGet으로부터 등록된 패키지의 정보를 다음과 같은 식으로 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public async Task<SearchQueryServiceResult> GetPackageInfoAsync(string packageId, bool includePrerelease) { <span style='color: blue; font-weight: bold'>string url = GetServiceEndPoint("SearchQueryService");</span> string query = string.Format("{0}?q={1}", url, packageId); if (includePrerelease == true) { query += "&prerelease=true"; } string text = await _httpClient.GetStringAsync(query); return Newtonsoft.Json.JsonConvert.DeserializeObject<SearchQueryServiceResult>(text); } </pre> <br /> 위의 query 변수는 index.json에 정의된 SearchQueryService 서비스의 url에 대해 다음과 같은 쿼리를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > {servicequery}?q={packageId} {servicequery}?q={packageId}&prerelease=true </pre> <br /> NuGet에 전송합니다. Search와 관련해 어떤 유형의 서비스들이 있는지는 문서에 잘 나와 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Docs / NuGet / API - Search ; <a target='tab' href='https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource'>https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource</a> </pre> <br /> 위의 문서에 보면, @type으로 다음의 값들이 가능하다고 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>SearchQueryService</span> : The initial release <span style='color: blue; font-weight: bold'>SearchQueryService/3.0.0-beta</span> : Alias of SearchQueryService <span style='color: blue; font-weight: bold'>SearchQueryService/3.0.0-rc</span> : Alias of SearchQueryService </pre> <br /> 현재(2018-09-18) 기준으로 정식 서비스는 "SearchQueryService"이므로 이 글에서는 그 옵션을 사용한 것입니다. 이와 함께 query에 전달할 수 있는 인자들을 다음과 같이 소개하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL} </pre> <br /> 보면 페이징 기능도 있으므로 적절하게 사용하시면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 예제 시나리오를 하나 작성해서 구현해 보겠습니다. ^^<br /> <br /> 빌드 시스템을 통해 생성된 바이너리를 NuGet 패키지의 새 버전에 등록할 때마다 alpha, alpha2, alpha3, alpha4, ...와 같은 식으로 버전을 늘려 가며 등록하는 것입니다. 정식 릴리스는 아니므로 기존 alpha(N) 버전이 있다면 unlist로 만들고 다음 릴리스 번호 값을 구하는 정도까지만 구현해 보겠습니다.<br /> <br /> 이를 위해서는 다음의 2개 메서드를 추가 구현하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public async Task<PackageMetadata> GetPackageMetadataAsync(string packageId, string vesrionPostfix) { <span style='color: blue; font-weight: bold'>string url = GetServiceEndPoint("RegistrationsBaseUrl");</span> string query = string.Format("{0}{1}/{2}.json", url, packageId.ToLower(), vesrionPostfix); HttpResponseMessage hrm = await _httpClient.GetAsync(query); if (hrm.StatusCode == HttpStatusCode.NotFound) { return null; } string text = await hrm.Content.ReadAsStringAsync(); PackageMetadata searchResult = Newtonsoft.Json.JsonConvert.DeserializeObject<PackageMetadata>(text); return searchResult; } public async Task<bool> UnlistVersionPackageAsync(string packageId, string vesrionPostfix) { <span style='color: blue; font-weight: bold'>string url = GetServiceEndPoint("PackagePublish/2.0.0");</span> string query = string.Format("{0}/{1}/{2}", url, packageId.ToLower(), vesrionPostfix); HttpResponseMessage hrm = await _httpClient.DeleteAsync(query); return hrm.StatusCode == HttpStatusCode.OK; } </pre> <br /> 주의할 것은, 등록된 패키지의 특정 버전 상태(Status)를 "Unlisted"로 바꾸기 위해서는 API Key를 HTTP 요청의 "X-NuGet-ApiKey" 헤더에 전송해야 합니다. 이 작업은 HttpClient를 생성할 때 미리 해주는 것으로 처리하면 편리합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public NugetClient(string apiKey = "") { HttpClientHandler handler = new HttpClientHandler(); _cookies = new CookieContainer(); handler.CookieContainer = _cookies; HttpClient hc = new HttpClient(handler); _httpClient = hc; if (string.IsNullOrEmpty(apiKey) == false) { <span style='color: blue; font-weight: bold'>_httpClient.DefaultRequestHeaders.Add("X-NuGet-ApiKey", apiKey);</span> } } </pre> <br /> 래퍼 API가 만들어졌으니 이제 다음과 같이 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string packageId = "MyTestPackage"; string versionPrefix = "1.0.0.0"; string checkPostfix = versionPrefix + "-alpha"; int alphaNumber = 1; while (true) { PackageMetadata item = await client.GetPackageMetadataAsync(packageId, checkPostfix); if (item == null) { break; } if (item.listed == true) { await client.UnlistVersionPackageAsync(packageId, checkPostfix); } alphaNumber++; checkPostfix = $"{versionPrefix}-alpha{alphaNumber}"; } Console.WriteLine("Next candidate version: " + checkPostfix); </pre> <br /> 이 정도면 제법 감각을 익히셨을 테니 여러분이 필요한 나머지 기능들도 쉽게 구현할 수 있을 것입니다. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1373&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, NuGet.exe 프로그램에 대한 배포 목록도 다음의 json 파일로 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > https://dist.nuget.org/tools.json https://dist.nuget.org/tools.schema.json </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
8535
(왼쪽의 숫자를 입력해야 합니다.)