前篇文章提及,VSTS其實可以自動透過Nuget Restore來取得專案所使用的套件,這樣不僅可以避免直接在專案中硬性加入參考,也可以解決套件散亂難以管理的問題,當然,專案中許多套件是屬於自行研發,也不打算放上Public Nuget Server給外部人使用的,這時候就需要自行架構Nuget Server。
文/黃忠成
Private Nuget Server
前篇文章提及,VSTS其實可以自動透過Nuget Restore來取得專案所使用的套件,這樣不僅可以避免直接在專案中硬性加入參考,也可以解決套件散亂難以管理的問題,當然,專案中許多套件是屬於自行研發,也不打算放上Public Nuget Server給外部人使用的,這時候就需要自行架構Nuget Server。
透過ASP.NET,自行建構Nuget Server其實很簡單,只要建立一個新的ASP.NET 專案,接著透過Nuget取得Nuget.Server套件安裝加上一些簡易設定就可完成。
圖001
裝完後透過web.config來調整設定。
圖002
只有一個部份需注意,那就是appKey的部分,這用於upload nuget package時所需要的application key,請自行輸入即可,完成後執行便可看到下圖畫面。
圖003
要特別注意的是,如果是要讓VSTS使用,那麼這個Nuget Server必須處於公開的網路環境,也就是任何人都可以透過網際網路存取到這個Nuget Server,這我想不是你所想要的,因此我們需要為這個Nuget Server加上安全機制,本例中用最簡單的Basic Authenticate,如果是真實環境下,建議至少加上SSL。
首先在專案中加入以下的程式碼。
BasicAuthenticateModule.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
namespace CiNugetServer
{
public class BasicAuthHttpModule : IHttpModule
{
private const string Realm = "My Realm";
public void Init(HttpApplication context)
{
// Register event handlers
context.AuthenticateRequest += OnApplicationAuthenticateRequest;
context.EndRequest += OnApplicationEndRequest;
}
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
// TODO: Here is where you would validate the username and password.
private static bool CheckPassword(string username, string password)
{
return username == "user" && password == "password";
}
private static void AuthenticateUser(string credentials)
{
try
{
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
int separator = credentials.IndexOf(':');
string name = credentials.Substring(0, separator);
string password = credentials.Substring(separator + 1);
if (CheckPassword(name, password))
{
var identity = new GenericIdentity(name);
SetPrincipal(new GenericPrincipal(identity, null));
}
else
{
// Invalid username or password.
HttpContext.Current.Response.StatusCode = 401;
}
}
catch (FormatException)
{
// Credentials were not formatted correctly.
HttpContext.Current.Response.StatusCode = 401;
}
}
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("basic",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
// If the request was unauthorized, add the WWW-Authenticate header
// to the response.
private static void OnApplicationEndRequest(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
response.Headers.Add("WWW-Authenticate",
string.Format("Basic realm=\"{0}\"", Realm));
}
}
public void Dispose()
{
}
}
}
接著修改web.config,加入HTTP Module設定。
web.config
<system.web>
…….
<authorization>
<deny users="?"/>
</authorization>
</system.web>
<system.webServer>
……..
<add name="MyBasicAuthenticationModule" type="CiNugetServer.BasicAuthHttpModule, CiNugetServer"/>
</modules>
注意type的定義,這裡是<TypeName>, <Assembly Name>。
完成後執行就可以看到以下的畫面。
圖004
接著就可以這個專案直接部署到Azure Web Sites去了。
由於已經加入了驗證,但Visual Studio卻沒有地方可以輸入驗證資訊,所以得用命令列方式來加入。
nuget sources add -Name "Cinuget" -Source "http://xxxxxx.azurewebsites.net/nuget" -UserName "nugettest" -Password "nugettest123"
如果已經透過Visual Stidio加入過,那麼就得使用update命令。
nuget sources update -Name "Cinuget" -Source "http://xxxxxx.azurewebsites.net/nuget" -UserName "nugettest" -Password "nugettest123"
發佈Package
其實很簡單,透過Nuget Package Explorer工具,就可以輕易地發佈自己撰寫的Package了。
https://github.com/NuGetPackageExplorer/NuGetPackageExplorer
對於.NET/Web相關的Package,Nuget Package Explorer其實就已經很夠用了,網路上也有很多說明文件,這裡就不再贅述,有興趣的可以參考黑大的文章。
http://blog.darkthread.net/post-2011-03-29-create-nuget-package.aspx
但如果要發佈的Package是一個Native,也就是C++的Package,那問題就複雜許多了,在目前的Nuget中已經支援C++類型的Package,首先必須安裝CoApp Tools。
http://coapp.org/pages/releases.html
接著準備要發佈的套件內容,本例中以Direct X SDK June為例,一般來說需要的就是include及lib目錄,這裡我建立一個新目錄,接著把SDK中的include、lib都複製過來。
圖005
然後要建立一個.autopkg檔案,這是CoApp工具所需要的檔案,他必須存放在套件的根目錄下,以本例來說就是DirectXJune目錄下。
Dxnugetsdk.autopkg
nuget{
nuspec{
id = DXSDKJUNE;
version : 11.0;
title: DirectX SDK(June);
authors: {Microsoft Corporation};
owners: {code6421, GIS};
tags: { native, SDK, DirectX };
}
files {
include: { "include\*" };
[x86] { // x86, dll
lib: { lib\x86\*.lib };
};
[x64] { // x64, dll
lib: { lib\x64\*.lib };
}
}
}
最後透過CoApp的工具來產生.nupkg檔案及上傳至Nuget Server,注意,這是Powershell。內容不難懂,重點在於include區塊,多數的C++ SDK都包含兩種平台:x86、x64,不同平台需要Link不同的lib。
產生nupk的命令列
Write-NuGetPackage .\dxjunesdk.autopkg
上傳
nuget.exe push .\DXSDKJUNE.11.0.nupkg -s http://yourserver.azurewebsites.net/ yourkey
過程中會詢問user/password。
一切無誤的話,就可以在Visual Studio使用這個套件了。
圖006
透過VSTS來建置
基本上與之前提過的方式差不多,一樣是先加入版控,接著透過VSTS建立Build,唯一要特別注意的是,這裡使用的是Private Nuget Server,而且是具備驗證機制的,所以必須透過上傳Nuget.config的方式來讓VSTS取得這些資訊,nuget.config存放於User\AppData\Roaming\Nuget目錄下。
圖007
這個檔案需要放置在版控中,通常我會放置於方案跟目錄,在建立Build的時候就可以直接指定。
圖008
習慣上我會把Clean跟Nuget package restore打勾。
圖009
對於C++這種分平台的專案來說,必須要特別注意要Build的平台 $(BuildPlatform)參數的值,這裡指定為x64(其實當開發的是混合型的專案時,這裡會很有趣,後面有機會再談談這個)。
圖010
結果如圖11,通常不會有太大問題。
圖11
必須注意的事
在我的環境中曾經發生過VSTS無法使用Nuget.config中存放的驗證資訊來Restore Package,這通常會導致Build Fail,如果你有發生這種情況,那麼試著使用以下指令來存放明碼試試。
另外一種情況比較詭異,出現莫名的MSBuild Task錯誤,這種情況後來是把Packages目錄由版控exclude後,讓VSTS在每次Build前還原整個Nuget Packages後解決。
PS: 事實上Packages本來就不該在版控中,但有時候Nuget Server或是Package上傳者的錯誤,導致某個Package在短時間出現無法由Nuget取得而導致錯誤,所以對於線上的專案,我還是會把Packages包進去。
nuget sources update -Name "Cinuget" -Source "http://xxxxxx.azurewebsites.net/nuget" -UserName "nugettest" -Password "nugettest123" -StorePasswordInClearText