1. 程式人生 > WINDOWS開發 >從零開始製作 NuGet 原始碼包(全面支援 .NET Core / .NET Framework / WPF 專案)

從零開始製作 NuGet 原始碼包(全面支援 .NET Core / .NET Framework / WPF 專案)

=========================================================================================

微軟官方建立NuGet包說明文件

技術分享圖片

具體的可以參考:https://docs.microsoft.com/zh-cn/nuget/create-packages/overview-and-workflow

=========================================================================================

從零開始製作 NuGet 原始碼包(全面支援 .NET Core / .NET Framework / WPF 專案)

預設情況下,我們打包 NuGet 包時,目標專案安裝我們的 NuGet 包會引用我們生成的庫檔案(dll)。除此之外,我們也可以專門做 NuGet 工具包,還可以做 NuGet 原始碼包。然而做原始碼包可能是其中最困難的一種了,目標專案安裝完後,這些原始碼將直接隨目標專案一起編譯。

本文將從零開始,教你製作一個支援 .NET 各種型別專案的原始碼包。


本文內容

前置知識

在開始製作一個原始碼包之間,建議你提前瞭解專案檔案的一些基本概念:

當然就算不了解也沒有關係。跟著本教程你也可以製作出來一個原始碼包,只不過可能遇到了問題的時候不容易除錯和解決。

製作一個原始碼包

接下來,我們將從零開始製作一個原始碼包。

我們接下來的將建立一個完整的解決方案,這個解決方案包括:

  1. 一個將打包成原始碼包的專案
  2. 一個除錯專用的專案(可選)
  3. 一個測試原始碼包的專案(可選)

第一步:建立一個 .NET 專案

像其他 NuGet 包的引用專案一樣,我們需要建立一個空的專案。不過差別是我們需要建立的是控制檯程式。

技術分享圖片

當建立好之後,Main 函式中的所有內容都是不需要的,於是我們刪除 Main 函式中的所有內容但保留 Main 函式。

這時 Program.cs 中的內容如下:

namespace Walterlv.PackageDemo.SourceCode
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

雙擊建立好的專案的專案,或者右鍵專案 “編輯專案檔案”,我們可以編輯此專案的 csproj 檔案。

在這裡,我將目標框架改成了 net48。實際上如果我們不製作動態原始碼生成,那麼這裡無論填寫什麼目標框架都不重要。在這篇部落格中,我們主要篇幅都會是做靜態原始碼生成,所以你大可不必關心這裡填什麼。

提示:如果 net48 讓你無法編譯這個專案,說明你電腦上沒有裝 .NET Framework 4.8 框架,請改成 net473,net472,net471,net47,net462,net 461,net46,net45,netcoreapp3.0,netcoreapp2.1,netcoreapp2.0 中的任何一個可以讓你編譯通過的目標框架即可。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net48</TargetFramework>
  </PropertyGroup>

</Project>

第二步:組織專案的目錄結構

接下來,我們會讓這個專案像一個 NuGet 包的樣子。當然,是 NuGet 原始碼包。

請在你的專案當中建立這些檔案和資料夾:

- Assets
    - build
        + Package.props
        + Package.targets
    - buildMultiTargeting
        + Package.props
        + Package.targets
    - src
        + Foo.cs
    - tools
+ Program.cs

在這裡,- 號表示資料夾,+ 號表示檔案。

Program.cs 是我們一開始就已經有的,可以不用管。src 資料夾裡的 Foo.cs 是我隨意建立的一個類,你就想往常建立正常的類檔案一樣建立一些類就好了。

比如我的 Foo.cs 裡面的內容很簡單:

using System;

namespace Walterlv.PackageDemo.SourceCode
{
    internal class Foo
    {
        public static void Print() => Console.WriteLine("Walterlv is a 逗比.");
    }
}

props 和 targets 檔案你可能在 Visual Studio 的新建檔案的模板中找不到這樣的模板檔案。這不重要,你隨便建立一個文字檔案,然後將名稱修改成上面列舉的那樣即可。接下來我們會依次修改這些檔案中的所有內容,所以無需擔心模板自動為我們生成了哪些內容。

為了更直觀,我將我的解決方案截圖貼出來,裡面包含所有這些檔案和資料夾的解釋。

技術分享圖片

我特別說明了哪些檔案和資料夾是必須存在的,哪些檔案和資料夾的名稱一定必須與本文說明的一樣。如果你是以教程的方式閱讀本文,建議所有的檔案和資料夾都跟我保持一樣的結構和名稱;如果你已經對 NuGet 包的結構有一定了解,那麼可自作主張修改一些名稱。

第三步:編寫專案檔案 csproj

現在,我們要雙擊專案名稱或者右鍵“編輯專案檔案”來編輯專案的 csproj 檔案

技術分享圖片

我們編輯專案檔案的目的,是讓我們前一步建立的專案資料夾結構真正成為 NuGet 包中的資料夾結構。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net48</TargetFramework>

    <!-- 要求此專案編譯時要生成一個 NuGet 包。-->
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>

    <!-- 這裡為了方便,我將 NuGet 包的輸出路徑設定在瞭解決方案根目錄的 bin 資料夾下,而不是專案的 bin 資料夾下。-->
    <PackageOutputPath>..\bin\$(Configuration)</PackageOutputPath>

    <!-- 建立 NuGet 包時,專案的輸出檔案對應到 NuGet 包的 tools 資料夾,這可以避免目標專案引用我們的 NuGet 包的輸出檔案。
         同時,如果將來我們準備動態生成原始碼,而不只是引入靜態原始碼,還可以有機會執行我們 Program 中的 Main 函式。-->
    <BuildOutputTargetFolder>tools</BuildOutputTargetFolder>

    <!-- 此包將不會傳遞依賴。意味著如果目標專案安裝了此 NuGet 包,那麼安裝目標專案包的專案不會間接安裝此 NuGet 包。-->
    <DevelopmentDependency>true</DevelopmentDependency>
    
    <!-- 包的版本號,我們設成了一個預覽版;當然你也可以設定為正式版,即沒有後面的 -alpha 字尾。-->
    <Version>0.1.0-alpha</Version>
    
    <!-- 設定包的作者。在上傳到 nuget.org 之後,如果作者名與 nuget.org 上的賬號名相同,其他人瀏覽包是可以直接點選連結看作者頁面。-->
    <Authors>walterlv</Authors>

    <!-- 設定包的組織名稱。我當然寫成我所在的組織 dotnet 職業技術學院啦。-->
    <Company>dotnet-campus</Company>
  </PropertyGroup>

  <!-- 在生成 NuGet 包之前,我們需要將我們專案中的資料夾結構一一對映到 NuGet 包中。-->
  <Target Name="IncludeAllDependencies" BeforeTargets="_GetPackageFiles">
    <ItemGroup>

      <!-- 將 Package.props / Package.targets 檔案的名稱在 NuGet 包中改為需要的真正名稱。
           因為 NuGet 包要自動匯入 props 和 targets 檔案,要求檔案的名稱必須是 包名.props 和 包名.targets;
           然而為了避免我們改包名的時候還要同步改四個檔案的名稱,所以就在專案檔案中動態生成。-->
      <None Include="Assets\build\Package.props" Pack="True" PackagePath="build\$(PackageId).props" />
      <None Include="Assets\build\Package.targets" Pack="True" PackagePath="build\$(PackageId).targets" />
      <None Include="Assets\buildMultiTargeting\Package.props" Pack="True" PackagePath="buildMultiTargeting\$(PackageId).props" />
      <None Include="Assets\buildMultiTargeting\Package.targets" Pack="True" PackagePath="buildMultiTargeting\$(PackageId).targets" />

      <!-- 我們將 src 目錄中的所有原始碼對映到 NuGet 包中的 src 目錄中。-->
      <None Include="Assets\src\**" Pack="True" PackagePath="src" />

    </ItemGroup>
  </Target>

</Project>

第四步:編寫編譯檔案 targets

接下來,我們將編寫編譯檔案 props 和 targets。注意,我們需要寫的是四個檔案的內容,不要弄錯了。

如果我們做好的 NuGet 原始碼包被其他專案使用,那麼這四個檔案中的其中一對會在目標專案被自動匯入(Import)。在你理解 理解 C# 專案 csproj 檔案格式的本質和編譯流程 一文內容之前,你可能不明白“匯入”是什麼意思。但作為從零開始的入門部落格,你也不需要真的理解匯入是什麼意思,只要知道這四個檔案中的程式碼將在目標專案編譯期間執行就好。

buildMultiTargeting 資料夾中的 Package.props 檔案

你只需要將下面的程式碼拷貝到 buildMultiTargeting 資料夾中的 Package.props 檔案即可。注意將包名換成你自己的包名,也就是專案名。

<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>
  
  <!-- 為了簡單起見,如果匯入了這個檔案,那麼我們將直接再匯入 ..\build\Walterlv.PackageDemo.SourceCode.props 檔案。
       注意到了嗎?我們並沒有寫 Package.props,因為我們在第三步編寫專案檔案時已經將這個檔案轉換為真實的包名了。-->
  <Import Project="..\build\Walterlv.PackageDemo.SourceCode.props" />

</Project>

buildMultiTargeting 資料夾中的 Package.targets 檔案

你只需要將下面的程式碼拷貝到 buildMultiTargeting 資料夾中的 Package.targets 檔案即可。注意將包名換成你自己的包名,也就是專案名。

<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>
  
  <!-- 為了簡單起見,如果匯入了這個檔案,那麼我們將直接再匯入 ..\build\Walterlv.PackageDemo.SourceCode.targets 檔案。
       注意到了嗎?我們並沒有寫 Package.targets,因為我們在第三步編寫專案檔案時已經將這個檔案轉換為真實的包名了。-->
  <Import Project="..\build\Walterlv.PackageDemo.SourceCode.targets" />

</Project>

build 資料夾中的 Package.props 檔案

下面是 build 資料夾中 Package.props 檔案的全部內容。可以注意到我們幾乎沒有任何實質性的程式碼在裡面。即便我們在此檔案中還沒有寫任何程式碼,依然需要建立這個檔案,因為後面第五步我們將新增更復雜的程式碼時將再次用到這個檔案完成裡面的內容。

現在,保持你的檔案中的內容與下面一模一樣就好。

<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>

</Project>

build 資料夾中的 Package.targets 檔案

下面是 build 資料夾中的 Package.targets 檔案的全部內容。

我們寫了兩個編譯目標,即 Target。_WalterlvDemoEvaluateProperties 沒有指定任何執行時機,但幫我們計算了兩個屬性:

  • _WalterlvDemoRoot 即 NuGet 包的根目錄
  • _WalterlvDemoSourceFolder 即 NuGet 包中的原始碼目錄

另外,我們添加了一個 Message 任務,用於在編譯期間顯示一條資訊,這對於除錯來說非常方便。

_WalterlvDemoIncludeSourceFiles 這個編譯目標指定在 CoreCompile 之前執行,並且執行需要依賴於 _WalterlvDemoEvaluateProperties 編譯目標。這意味著當編譯執行到 CoreCompile 步驟時,將在它執行之前插入 _WalterlvDemoIncludeSourceFiles 編譯目標來執行,而 _WalterlvDemoIncludeSourceFiles 依賴於 _WalterlvDemoEvaluateProperties,於是 _WalterlvDemoEvaluateProperties 會插入到更之前執行。那麼在微觀上來看,這三個編譯任務的執行順序將是:_WalterlvDemoEvaluateProperties -> _WalterlvDemoIncludeSourceFiles -> CoreCompile

_WalterlvDemoIncludeSourceFiles 中,我們定義了一個集合 _WalterlvDemoCompile,集合中包含 NuGet 包原始碼資料夾中的所有 .cs 檔案。另外,我們又定義了 Compile 集合,將 _WalterlvDemoCompile 集合中的所有內容新增到 Compile 集合中。Compile 是 .NET 專案中的一個已知集合,當 CoreCompile 執行時,所有 Compile 集合中的檔案將參與編譯。注意到我沒有直接將 NuGet 包中的原始碼檔案引入到 Compile 集合中,而是經過了中轉。後面第五步中,你將體會到這樣做的作用。

我們也新增一個 Message 任務,用於在編譯期間顯示資訊,便於除錯。

<Project>

  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
  </PropertyGroup>

  <Target Name="_WalterlvDemoEvaluateProperties">
    <PropertyGroup>
      <_WalterlvDemoRoot>$(MSBuildThisFileDirectory)..\</_WalterlvDemoRoot>
      <_WalterlvDemoSourceFolder>$(MSBuildThisFileDirectory)..\src\</_WalterlvDemoSourceFolder>
    </PropertyGroup>
    <Message Text="1. 初始化原始碼包的編譯屬性" />
  </Target>

  <!-- 引入 C# 原始碼。 -->
  <Target Name="_WalterlvDemoIncludeSourceFiles"
          BeforeTargets="CoreCompile"
          DependsOnTargets="_WalterlvDemoEvaluateProperties">
    <ItemGroup>
      <_WalterlvDemoCompile Include="$(_WalterlvDemoSourceFolder)**\*.cs" />
      <Compile Include="@(_WalterlvDemoCompile)" />
    </ItemGroup>
    <Message Text="2 引入原始碼包中的所有原始碼:@(_WalterlvDemoCompile)" />
  </Target>

</Project>

這四個檔案分別的作用

我們剛剛花了很大的篇幅教大家完成 props 和 targets 檔案,那麼這四個檔案是做什麼的呢?

如果安裝我們原始碼包的專案使用 TargetFramework 屬性寫目標框架,那麼 NuGet 會自動幫我們匯入 build 資料夾中的兩個編譯檔案。如果安裝我們原始碼包的專案使用 TargetFrameworks(注意複數形式)屬性寫目標框架,那麼 NuGet 會自動幫我們匯入 buildMultiTargeting 資料夾中的兩個編譯檔案。

如果你對這個屬性不熟悉,請回到第一步看我們一開始建立的程式碼,你會看到這個屬性的設定的。如果還不清楚,請閱讀部落格:

體驗和檢視 NuGet 原始碼包

也許你已經從本文拷貝了很多程式碼過去了,但直到目前我們還沒有看到這些程式碼的任何效果,那麼現在我們就可以來看看了。這可算是一個階段性成果呢!

先編譯生成一下我們一直在完善的專案,我們就可以在解決方案目錄的 bin\Debug 目錄下找到一個 NuGet 包。

技術分享圖片

技術分享圖片

現在,我們要開啟這個 NuGet 包看看裡面的內容。你需要先去應用商店下載 NuGet Package Explorer,裝完之後你就可以開始直接雙擊 NuGet 包檔案,也就是 nupkg 檔案。現在我們雙擊開啟看看。

技術分享圖片

我們的體驗到此為止。如果你希望在真實的專案當中測試,可以閱讀其他部落格瞭解如何在本地測試 NuGet 包。

第五步:加入 WPF 專案支援

截至目前,我們只是在原始碼包中引入了 C# 程式碼。如果我們需要加入到原始碼包中的程式碼包含 WPF 的 XAML 檔案,或者安裝我們原始碼包的目標專案包含 WPF 的 XAML 檔案,那麼這個 NuGet 原始碼包直接會導致無法編譯通過。至於原因,你需要閱讀我的另一篇部落格來了解:

即便你不懂 WPF 程式的編譯過程,你也可以繼續完成本文的所有內容,但可能就不會明白為什麼接下來我們要那樣去修改我們之前建立的檔案。

接下來我們將修改這些檔案:

  • build 資料夾中的 Package.props 檔案
  • build 資料夾中的 Package.targets 檔案

build 資料夾中的 Package.props 檔案

在這個檔案中,我們將新增一個屬性 ShouldFixNuGetImportingBugForWpfProjects。這是我取的名字,意為“是否應該修復 WPF 專案中 NuGet 包自動匯入的問題”。

我做一個開關的原因是懷疑我們需要針對 WPF 專案進行特殊處理是 WPF 專案自身的 Bug,如果將來 WPF 修復了這個 Bug,那麼我們將可以直接通過此開關來關閉我們在這一節做的特殊處理。另外,後面我們將採用一些特別的手段來除錯我們的 NuGet 原始碼包,在除錯專案中我們也會將這個屬性設定為 False 以關閉 WPF 專案的特殊處理。

    <Project>
    
      <PropertyGroup>
        <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
    
++      <!-- 當生成 WPF 臨時專案時,不會自動 Import NuGet 中的 props 和 targets 檔案,這使得在臨時專案中你現在看到的整個檔案都不會參與編譯。
++           然而,我們可以通過欺騙的方式在主專案中通過 _GeneratedCodeFiles 集合將需要編譯的檔案傳遞到臨時專案中以間接參與編譯。
++           WPF 臨時專案不會 Import NuGet 中的 props 和 targets 可能是 WPF 的 Bug,也可能是刻意如此。
++           所以我們通過一個屬性開關 `ShouldFixNuGetImportingBugForWpfProjects` 來決定是否修復這個錯誤。-->
++      <ShouldFixNuGetImportingBugForWpfProjects Condition=" ‘$(ShouldFixNuGetImportingBugForWpfProjects)‘ == ‘‘ ">True</ShouldFixNuGetImportingBugForWpfProjects>
++    </PropertyGroup>
    
    </Project>

build 資料夾中的 Package.targets 檔案

請按照下面的差異說明來修改你的 Package.targets 檔案。實際上我們幾乎刪除任何程式碼,所以其實你可以將下面的所有內容作為你的新的 Package.targets 中的內容。

    <Project>
    
      <PropertyGroup>
        <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
      </PropertyGroup>

++    <PropertyGroup>
++      <!-- 我們增加了一個屬性,用於處理 WPF 特殊專案的原始碼之前,確保我們已經收集到所有需要引入的原始碼。 -->
++      <_WalterlvDemoImportInWpfTempProjectDependsOn>_WalterlvDemoIncludeSourceFiles</_WalterlvDemoImportInWpfTempProjectDependsOn>
++    </PropertyGroup>
    
      <Target Name="_WalterlvDemoEvaluateProperties">
        <PropertyGroup>
          <_WalterlvDemoRoot>$(MSBuildThisFileDirectory)..\</_WalterlvDemoRoot>
          <_WalterlvDemoSourceFolder>$(MSBuildThisFileDirectory)..\src\</_WalterlvDemoSourceFolder>
        </PropertyGroup>
        <Message Text="1. 初始化原始碼包的編譯屬性" />
      </Target>
    
      <!-- 引入 C# 原始碼。 -->
      <Target Name="_WalterlvDemoIncludeSourceFiles"
              BeforeTargets="CoreCompile"
              DependsOnTargets="_WalterlvDemoEvaluateProperties">
        <ItemGroup>
          <_WalterlvDemoCompile Include="$(_WalterlvDemoSourceFolder)**\*.cs" />
++        <_WalterlvDemoAllCompile Include="@(_WalterlvDemoCompile)" />
          <Compile Include="@(_WalterlvDemoCompile)" />
        </ItemGroup>
--      <Message Text="2 引入原始碼包中的所有原始碼:@(_WalterlvDemoCompile)" />
++      <Message Text="2.1 引入原始碼包中的所有原始碼:@(_WalterlvDemoCompile)" />
      </Target>
    
++    <!-- 引入 WPF 原始碼。 -->
++    <Target Name="_WalterlvDemoIncludeWpfFiles"
++            BeforeTargets="MarkupCompilePass1"
++            DependsOnTargets="_WalterlvDemoEvaluateProperties">
++      <ItemGroup>
++        <_WalterlvDemoPage Include="$(_WalterlvDemoSourceFolder)**\*.xaml" />
++        <Page Include="@(_WalterlvDemoPage)" Link="%(_WalterlvDemoPage.FileName).xaml" />
++      </ItemGroup>
++      <Message Text="2.2 引用 WPF 相關原始碼:@(_WalterlvDemoPage)" />
++    </Target>

++    <!-- 當生成 WPF 臨時專案時,不會自動 Import NuGet 中的 props 和 targets 檔案,這使得在臨時專案中你現在看到的整個檔案都不會參與編譯。
++         然而,我們可以通過欺騙的方式在主專案中通過 _GeneratedCodeFiles 集合將需要編譯的檔案傳遞到臨時專案中以間接參與編譯。
++         WPF 臨時專案不會 Import NuGet 中的 props 和 targets 可能是 WPF 的 Bug,也可能是刻意如此。
++         所以我們通過一個屬性開關 `ShouldFixNuGetImportingBugForWpfProjects` 來決定是否修復這個錯誤。-->
++    <Target Name="_WalterlvDemoImportInWpfTempProject"
++            AfterTargets="MarkupCompilePass1"
++            BeforeTargets="GenerateTemporaryTargetAssembly"
++            DependsOnTargets="$(_WalterlvDemoImportInWpfTempProjectDependsOn)"
++            Condition=" ‘$(ShouldFixNuGetImportingBugForWpfProjects)‘ == ‘True‘ ">
++      <ItemGroup>
++        <_GeneratedCodeFiles Include="@(_WalterlvDemoAllCompile)" />
++      </ItemGroup>
++      <Message Text="3. 正在欺騙臨時專案,誤以為此 NuGet 包中的檔案是 XAML 編譯後的中間程式碼:@(_WalterlvDemoAllCompile)" />
++    </Target>

    </Project>

我們增加了 _WalterlvDemoImportInWpfTempProjectDependsOn 屬性,這個屬性裡面將填寫一個到多個編譯目標(Target)的名稱(多個用分號分隔),用於告知 _WalterlvDemoImportInWpfTempProject 這個編譯目標在執行之前必須確保執行的依賴編譯目標。而我們目前的依賴目標只有一個,就是 _WalterlvDemoIncludeSourceFiles 這個引入 C# 原始碼的編譯目標。如果你有其他考慮有引入更多 C# 原始碼的編譯目標,則需要把他們都加上(當然本文是不需要的)。為此,我還新增了一個 _WalterlvDemoAllCompile 集合,如果存在多個依賴的編譯目標會引入 C# 原始碼,則需要像 _WalterlvDemoIncludeSourceFiles 一樣,將他們都加入到 Compile 的同時也加入到 _WalterlvDemoAllCompile 集合中。

為什麼可能有多個引入 C# 原始碼的編譯目標?因為本文我們只考慮了引入我們提前準備好的原始碼放入原始碼包中,而我們提到過可能涉及到動態生成 C# 原始碼的需求。如果你有一兩個編譯目標會動態生成一些 C# 原始碼並將其加入到 Compile 集合中,那麼請將這個編譯目標的名稱加入到 _WalterlvDemoImportInWpfTempProjectDependsOn 屬性(注意多個用分號分隔),同時將集合也引入一份到 _WalterlvDemoAllCompile 中。

_WalterlvDemoIncludeWpfFiles 這個編譯目標的作用是引入 WPF 的 XAML 檔案,這很容易理解,畢竟我們的原始碼中包含 WPF 相關的檔案。

請特別注意

  1. 我們加了一個 Link 屬性,並且將其指定為 %(_WalterlvDemoPage.FileName).xaml。這意味著我們會把所有的 XAML 檔案都當作在專案根目錄中生成,如果你在其他的專案中用到了相對或絕對的 XAML 檔案的路徑,這顯然會改變路徑。但是,我們沒有其他的方法來根據 XAML 檔案所在的目錄層級來自定指定 Link 屬性讓其在正確的層級上,所以這裡才寫死在根目錄中。
    • 如果要解決這個問題,我們就需要在生成 NuGet 包之前生成此專案中所有 XAML 檔案的正確的 Link 屬性(例如改為 Views\%(_WalterlvDemoPage.FileName).xaml),這意味著需要在此專案編譯期間執行一段程式碼,把 Package.targets 檔案中為所有的 XAML 檔案生成正確的 Link 屬性。本文暫時不考慮這個問題,但你可以參考 dotnet-campus/SourceYard 專案來了解如何動態生成 Link
  2. 我們使用了 _WalterlvDemoPage 集合中轉地存了 XAML 檔案,這是必要的。因為這樣才能正確通過 % 符號獲取到 FileName 屬性。

_WalterlvDemoImportInWpfTempProject 這個編譯目標就不那麼好理解了,而這個也是完美支援 WPF 專案原始碼包的關鍵編譯目標!這個編譯目標指定在 MarkupCompilePass1 之後,GenerateTemporaryTargetAssembly 之前執行。GenerateTemporaryTargetAssembly 編譯目標的作用是生成一個臨時的專案,用於讓 WPF 的 XAML 檔案能夠依賴同項目的 .NET 型別而編譯。然而此臨時專案編譯期間是不會匯入任何 NuGet 的 props 或 targets 檔案的,這意味著我們特別新增的所有 C# 原始碼在這個臨時專案當中都是不存在的——如果專案使用到了我們原始碼包中的原始碼,那麼必然因為型別不存在而無法編譯通過——臨時專案沒有編譯通過,那麼整個專案也就無法編譯通過。但是,我們通過在 MarkupCompilePass1GenerateTemporaryTargetAssembly 之間將我們原始碼包中的所有原始碼加入到 _GeneratedCodeFiles 集合中,即可將這些檔案加入到臨時專案中一起編譯。而原本 _GeneratedCodeFiles 集合中是什麼呢?就是大家熟悉的 XAML 轉換而成的 xxx.g.cs 檔案。

測試和釋出原始碼包

現在我們再次編譯這個專案,你將得到一個支援 WPF 專案的 NuGet 原始碼包。

完整專案結構和原始碼

至此,我們已經完成了編寫一個 NuGet 原始碼包所需的全部原始碼。接下來你可以在專案中新增更多的原始碼,這樣打出來的原始碼包也將包含更多原始碼。由於我們將將 XAML 檔案都通過 Link 屬性指定到根目錄了,所以如果你需要新增 XAML 檔案,你將只能新增到我們專案中的 Assets\src 目錄下,除非做 dotnet-campus/SourceYard 中類似的動態 Link 生成的處理,或者在 Package.targets 檔案中手工為每一個 XAML 編寫一個特別的 Link 屬性。

另外,在不改變我們整體專案結構的情況下,你也可以任意新增 WPF 所需的圖片資源等。但也需要在 Package.targets 中新增額外的 Resource 引用。如果沒有 dotnet-campus/SourceYard 的自動生成程式碼,你可能也需要手工編寫 Resource

接下來我會貼出更復雜的程式碼,用於處理更復雜的原始碼包的場景。

目錄結構

更復雜原始碼包的專案組織形式會是下面這樣圖這樣:

技術分享圖片

我們在 Assets 資料夾中新增了一個 assets 資料夾。由於資源在此專案中的路徑必須和安裝後的目標專案中一樣才可以正確用 Uri 的方式使用資源,所以我們在專案檔案 csproj 和編譯檔案 Package.targets 中都對這兩個檔案設定了 Link 到同一個資料夾中,這樣才可以確保兩邊都能正常使用。

我們在 src 資料夾的不同子資料夾中建立了 XAML 檔案。按照我們前面的說法,我們也需要像資原始檔一樣正確在 Package.targets 中設定 Link 才可以確保 Uri 是一致的。注意,我們接下來的原始碼中沒有在專案檔案中設定 Link,原則上也是需要設定的,就像資源一樣,這樣才可以確保此專案和安裝此 NuGet 包中的目標專案具有相同的 XAML Uri。此例子只是因為沒有程式碼使用到了 XAML 檔案的路徑,所以才能得以倖免。

我們還利用了 tools 資料夾。我們在專案檔案的末尾將輸出檔案拷貝到了 tools 目錄下,這樣,我們專案的 Assets 資料夾幾乎與最終的 NuGet 包的資料夾結構一模一樣,非常利於除錯。但為了防止將生成的檔案上傳到版本管理,我在 tools 中添加了 .gitignore 檔案:

/net*/*

專案檔案

--  <Project Sdk="Microsoft.NET.Sdk">
++  <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net48</TargetFramework>
++      <UseWpf>True</UseWpf>
    
        <!-- 要求此專案編譯時要生成一個 NuGet 包。-->
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    
        <!-- 這裡為了方便,我將 NuGet 包的輸出路徑設定在瞭解決方案根目錄的 bin 資料夾下,而不是專案的 bin 資料夾下。-->
        <PackageOutputPath>..\bin\$(Configuration)</PackageOutputPath>
    
        <!-- 建立 NuGet 包時,專案的輸出檔案對應到 NuGet 包的 tools 資料夾,這可以避免目標專案引用我們的 NuGet 包的輸出檔案。
             同時,如果將來我們準備動態生成原始碼,而不只是引入靜態原始碼,還可以有機會執行我們 Program 中的 Main 函式。-->
        <BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
    
        <!-- 此包將不會傳遞依賴。意味著如果目標專案安裝了此 NuGet 包,那麼安裝目標專案包的專案不會間接安裝此 NuGet 包。-->
        <DevelopmentDependency>true</DevelopmentDependency>
    
        <!-- 包的版本號,我們設成了一個預覽版;當然你也可以設定為正式版,即沒有後面的 -alpha 字尾。-->
        <Version>0.1.0-alpha</Version>
    
        <!-- 設定包的作者。在上傳到 nuget.org 之後,如果作者名與 nuget.org 上的賬號名相同,其他人瀏覽包是可以直接點選連結看作者頁面。-->
        <Authors>walterlv</Authors>
    
        <!-- 設定包的組織名稱。我當然寫成我所在的組織 dotnet 職業技術學院啦。-->
        <Company>dotnet-campus</Company>
      </PropertyGroup>
    
++    <!-- 我們新增的其他資源需要在這裡 Link 到一個統一的目錄下,以便在此專案和安裝 NuGet 包的目標專案中可以用同樣的 Uri 使用。 -->
++    <ItemGroup>
++      <Resource Include="Assets\assets\Icon.ico" Link="Assets\Icon.ico" Visible="False" />
++      <Resource Include="Assets\assets\Background.png" Link="Assets\Background.png" Visible="False" />
++    </ItemGroup>
      
      <!-- 在生成 NuGet 包之前,我們需要將我們專案中的資料夾結構一一對映到 NuGet 包中。-->
      <Target Name="IncludeAllDependencies" BeforeTargets="_GetPackageFiles">
        <ItemGroup>
    
          <!-- 將 Package.props / Package.targets 檔案的名稱在 NuGet 包中改為需要的真正名稱。
               因為 NuGet 包要自動匯入 props 和 targets 檔案,要求檔案的名稱必須是 包名.props 和 包名.targets;
               然而為了避免我們改包名的時候還要同步改四個檔案的名稱,所以就在專案檔案中動態生成。-->
          <None Include="Assets\build\Package.props" Pack="True" PackagePath="build\$(PackageId).props" />
          <None Include="Assets\build\Package.targets" Pack="True" PackagePath="build\$(PackageId).targets" />
          <None Include="Assets\buildMultiTargeting\Package.props" Pack="True" PackagePath="buildMultiTargeting\$(PackageId).props" />
          <None Include="Assets\buildMultiTargeting\Package.targets" Pack="True" PackagePath="buildMultiTargeting\$(PackageId).targets" />
    
          <!-- 我們將 src 目錄中的所有原始碼對映到 NuGet 包中的 src 目錄中。-->
          <None Include="Assets\src\**" Pack="True" PackagePath="src" />

++        <!-- 我們將 assets 目錄中的所有原始碼對映到 NuGet 包中的 assets 目錄中。-->
++        <None Include="Assets\assets\**" Pack="True" PackagePath="assets" />
    
        </ItemGroup>
      </Target>
    
++    <!-- 在編譯結束後將生成的可執行程式放到 Tools 資料夾中,使得 Assets 資料夾的目錄結構與 NuGet 包非常相似,便於 Sample 專案進行及時的 NuGet 包除錯。 -->
++    <Target Name="_WalterlvDemoCopyOutputToDebuggableFolder" AfterTargets="AfterBuild">
++        <ItemGroup>
++        <_WalterlvDemoToCopiedFiles Include="$(OutputPath)**" />
++        </ItemGroup>
++        <Copy SourceFiles="@(_WalterlvDemoToCopiedFiles)" DestinationFolder="Assets\tools\$(TargetFramework)" />
++    </Target>

    </Project>

編譯檔案

    <Project>
    
      <PropertyGroup>
        <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
      </PropertyGroup>
    
      <PropertyGroup>
        <!-- 我們增加了一個屬性,用於處理 WPF 特殊專案的原始碼之前,確保我們已經收集到所有需要引入的原始碼。 -->
        <_WalterlvDemoImportInWpfTempProjectDependsOn>_WalterlvDemoIncludeSourceFiles</_WalterlvDemoImportInWpfTempProjectDependsOn>
      </PropertyGroup>
      
      <Target Name="_WalterlvDemoEvaluateProperties">
        <PropertyGroup>
          <_WalterlvDemoRoot>$(MSBuildThisFileDirectory)..\</_WalterlvDemoRoot>
          <_WalterlvDemoSourceFolder>$(MSBuildThisFileDirectory)..\src\</_WalterlvDemoSourceFolder>
        </PropertyGroup>
        <Message Text="1. 初始化原始碼包的編譯屬性" />
      </Target>
    
      <!-- 引入主要的 C# 原始碼。 -->
      <Target Name="_WalterlvDemoIncludeSourceFiles"
              BeforeTargets="CoreCompile"
              DependsOnTargets="_WalterlvDemoEvaluateProperties">
        <ItemGroup>
          <_WalterlvDemoCompile Include="$(_WalterlvDemoSourceFolder)**\*.cs" />
          <_WalterlvDemoAllCompile Include="@(_WalterlvDemoCompile)" />
          <Compile Include="@(_WalterlvDemoCompile)" />
        </ItemGroup>
        <Message Text="2.1 引入原始碼包中的所有原始碼:@(_WalterlvDemoCompile)" />
      </Target>
    
      <!-- 引入 WPF 原始碼。 -->
      <Target Name="_WalterlvDemoIncludeWpfFiles"
              BeforeTargets="MarkupCompilePass1"
              DependsOnTargets="_WalterlvDemoEvaluateProperties">
        <ItemGroup>
--        <_WalterlvDemoPage Include="$(_WalterlvDemoSourceFolder)**\*.xaml" />
--        <Page Include="@(_WalterlvDemoPage)" Link="Views\%(_WalterlvDemoPage.FileName).xaml" />
++        <_WalterlvDemoRootPage Include="$(_WalterlvDemoSourceFolder)FooView.xaml" />
++        <Page Include="@(_WalterlvDemoRootPage)" Link="Views\%(_WalterlvDemoRootPage.FileName).xaml" />
++        <_WalterlvDemoThemesPage Include="$(_WalterlvDemoSourceFolder)Themes\Walterlv.Windows.xaml" />
++        <Page Include="@(_WalterlvDemoThemesPage)" Link="Views\%(_WalterlvDemoThemesPage.FileName).xaml" />
++        <_WalterlvDemoIcoResource Include="$(_WalterlvDemoRoot)assets\*.ico" />
++        <_WalterlvDemoPngResource Include="$(_WalterlvDemoRoot)assets\*.png" />
++        <Resource Include="@(_WalterlvDemoIcoResource)" Link="assets\%(_WalterlvDemoIcoResource.FileName).ico" />
++        <Resource Include="@(_WalterlvDemoPngResource)" Link="assets\%(_WalterlvDemoPngResource.FileName).png" />
        </ItemGroup>
--      <Message Text="2.2 引用 WPF 相關原始碼:@(_WalterlvDemoPage);@(_WalterlvDemoIcoResource);@(_WalterlvDemoPngResource)" />
++      <Message Text="2.2 引用 WPF 相關原始碼:@(_WalterlvDemoRootPage);@(_WalterlvDemoThemesPage);@(_WalterlvDemoIcoResource);@(_WalterlvDemoPngResource)" />
      </Target>
    
      <!-- 當生成 WPF 臨時專案時,不會自動 Import NuGet 中的 props 和 targets 檔案,這使得在臨時專案中你現在看到的整個檔案都不會參與編譯。
           然而,我們可以通過欺騙的方式在主專案中通過 _GeneratedCodeFiles 集合將需要編譯的檔案傳遞到臨時專案中以間接參與編譯。
           WPF 臨時專案不會 Import NuGet 中的 props 和 targets 可能是 WPF 的 Bug,也可能是刻意如此。
           所以我們通過一個屬性開關 `ShouldFixNuGetImportingBugForWpfProjects` 來決定是否修復這個錯誤。-->
      <Target Name="_WalterlvDemoImportInWpfTempProject"
              AfterTargets="MarkupCompilePass1"
              BeforeTargets="GenerateTemporaryTargetAssembly"
              DependsOnTargets="$(_WalterlvDemoImportInWpfTempProjectDependsOn)"
              Condition=" ‘$(ShouldFixNuGetImportingBugForWpfProjects)‘ == ‘True‘ ">
        <ItemGroup>
          <_GeneratedCodeFiles Include="@(_WalterlvDemoAllCompile)" />
        </ItemGroup>
        <Message Text="3. 正在欺騙臨時專案,誤以為此 NuGet 包中的檔案是 XAML 編譯後的中間程式碼:@(_WalterlvDemoAllCompile)" />
      </Target>
    
    </Project>

開源專案

本文涉及到的所有程式碼均已開源到:

更多內容

SourceYard 開源專案

本文服務於開源專案 SourceYard,為其提供支援 WPF 專案的解決方案。dotnet-campus/SourceYard: Add a NuGet package only for dll reference? By using dotnetCampus.SourceYard,you can pack a NuGet package with source code. By installing the new source code package,all source codes behaviors just like it is in your project.

相關部落格

更多製作原始碼包的部落格可以閱讀。從簡單到複雜的順序:

本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/build-source-code-package-for-wpf-projects.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

如果你想持續閱讀我的最新部落格,請點選 RSS 訂閱,或者前往 CSDN 關注我的主頁

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯絡 ([email protected])

出處:https://blog.walterlv.com/post/build-source-code-package-for-wpf-projects.html

=========================================================================================

預設情況下,我們打包 NuGet 包時,目標專案安裝我們的 NuGet 包會引用我們生成的庫檔案(dll)。除此之外,我們也可以專門做 NuGet 工具包,還可以做 NuGet 原始碼包。然而做原始碼包可能是其中最困難的一種了,目標專案安裝完後,這些原始碼將直接隨目標專案一起編譯。

本文將從零開始,教你製作一個支援 .NET 各種型別專案的原始碼包。


本文內容

前置知識

在開始製作一個原始碼包之間,建議你提前瞭解專案檔案的一些基本概念:

當然就算不了解也沒有關係。跟著本教程你也可以製作出來一個原始碼包,只不過可能遇到了問題的時候不容易除錯和解決。

製作一個原始碼包

接下來,我們將從零開始製作一個原始碼包。

我們接下來的將建立一個完整的解決方案,這個解決方案包括:

  1. 一個將打包成原始碼包的專案
  2. 一個除錯專用的專案(可選)
  3. 一個測試原始碼包的專案(可選)

第一步:建立一個 .NET 專案

像其他 NuGet 包的引用專案一樣,我們需要建立一個空的專案。不過差別是我們需要建立的是控制檯程式。

技術分享圖片

當建立好之後,Main 函式中的所有內容都是不需要的,於是我們刪除 Main 函式中的所有內容但保留 Main 函式。

這時 Program.cs 中的內容如下:

namespace Walterlv.PackageDemo.SourceCode
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

雙擊建立好的專案的專案,或者右鍵專案 “編輯專案檔案”,我們可以編輯此專案的 csproj 檔案。

在這裡,我將目標框架改成了 net48。實際上如果我們不製作動態原始碼生成,那麼這裡無論填寫什麼目標框架都不重要。在這篇部落格中,我們主要篇幅都會是做靜態原始碼生成,所以你大可不必關心這裡填什麼。

提示:如果 net48 讓你無法編譯這個專案,說明你電腦上沒有裝 .NET Framework 4.8 框架,請改成 net473,all source codes behaviors just like it is in your project.

相關部落格

更多製作原始碼包的部落格可以閱讀。從簡單到複雜的順序:

本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/build-source-code-package-for-wpf-projects.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

如果你想持續閱讀我的最新部落格,請點選 RSS 訂閱,或者前往 CSDN 關注我的主頁

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯絡 ([email protected])

出處:https://blog.walterlv.com/post/build-source-code-package-for-wpf-projects.html

=========================================================================================

使用命令列打包 nuget 包

對於那些不打算涉及這麼複雜而又想製作自己的 nuget 包的園友們,我是推薦使用 Nuget Package Explorer 來製作的。關於這個圖形化的 nuget 包管理軟體的使用,部落格園內有相關的文章,大家可以搜尋看看。

好,迴歸正題。但是我們都知道,圖形化最大的問題就是自動化不高。

技術分享圖片

這是我其中的一個 nuget 包,可以看見裡面的檔案還是比較多的,那麼我每一次重新編譯,需要釋出新版本的時候,就得把裡面大部分的檔案都替換成新的檔案。每次都一個一個的替換,煩啊。而且有時候還得擔心有沒有替換少了。那麼自動化打包肯定是值得研究研究一番了。

在 nuget 官網上面我們可以找到一篇關於如何建立 nuget 包的教程:https://docs.nuget.org/Create/Creating-and-Publishing-a-Package

技術分享圖片

那麼我們先下載這個命令列工具下來。

然後準備我們需要打包的檔案(就是 dll 之類的東西)。

看了下文件,說是支援讀取直接 csproj 檔案打包,那我們先試一下吧,畢竟程式設計師的準則就是能簡單就簡單,能懶就懶。

這裡我的專案結構是這樣的:

技術分享圖片

然後我們執行 cmd 並輸入命令

技術分享圖片

報了個錯,重新生成一下專案吧,再次執行。

技術分享圖片

好像成了,看看目錄。

技術分享圖片

技術分享圖片

但是這 dll 所屬的分類,錯了吧,我建的可是 Win10 應用程式的 dll。。。

既然 csproj 方案不行的話,我們繼續看文件。看見有一個通過描述 nuget 包的方案:https://docs.nuget.org/Create/Creating-and-Publishing-a-Package#create-the-manifest

要建立這麼一個描述檔案也很簡單,跑個命令

技術分享圖片

技術分享圖片

然後我們用文字編輯器來開啟這個新的 Package.nuspec 檔案。

技術分享圖片

可以看見是一個 xml 檔案,然後修改下里面的屬性就可以了。

再次執行。

技術分享圖片

有警告,簡單看了下,是說不知道應該打包哪些檔案。

那麼繼續找找文件,最後我們可以發現這個。

技術分享圖片

修改下我們的 nuspec 檔案。

技術分享圖片

新加上紅色部分,也就是將這個 dll 打包進 nuget 包的 lib\uap10.0\ 這個目錄裡面。

再次執行打包命令。

技術分享圖片

這次沒警告了。

而且包的結構也沒問題。

技術分享圖片

那麼只要編寫好 nuspec 裡面的 files 節點的話,以後執行 nuget pack XX.nuspec 這個命令的話,就能夠簡單地生成 nuget 包了。

為了再懶一點,我們將上面這個命令弄成批處理。

%~dp0nuget.exe pack %~dp0Package.nuspec -OutputDirectory %~dp0

%~dp0 這個是獲取當前正在執行的這個 bat 檔案的所在目錄。

那麼最後就變成這樣:

技術分享圖片

確保這三個檔案放在一起,然後執行 package.bat 就能在當前目錄生成 nuget 包了。

接下來我們就可以釋出 nuget 包了,用命令也行,但保障一點,我還是用 GUI 工具來發布(畢竟釋出前再檢查一次還是有必要的)。

用 Nuget Package Explorer 開啟上面生成的那個 nuget 包。

技術分享圖片

按這裡就可以了。

注意:一旦釋出,nuget 上的包是不能夠刪除的!!只能隱藏,因此,請確保無誤再發布。

到最後一步我們已經使用批處理來做了,那麼可以再玩的花樣就多去了,例如用 PowerShell 來自動化包的版本。在專案的 AssemblyInfo.cs 有一個 AssemblyVersion 的 Attribute,然後我們就可以用 PowerShell 來先讀取這個版本號,然後修改 nuspec 檔案裡的 version 節點,再生成包。其實我現在就已經是這麼做了技術分享圖片,鑑於我 PowerShell 也沒學過,那段程式碼就不放出來丟臉了技術分享圖片。大家可以發散思維,期望在 nuget 上能看見園友釋出的包包技術分享圖片

https://www.cnblogs.com/h82258652/p/4898983.html

=========================================================================================

NuGet的使用、部署、搭建私有服務

目錄


前言

什麼是NuGet?

Nuget是一個.NET平臺下的開源的專案,它是Visual Studio的擴充套件。在使用Visual Studio開發基於.NET Framework的應用時,Nuget能把在專案中新增、移除和更新引用的工作變得更加快捷方便。

為什麼要使用NuGet

如果我們專案開發不需要引用任何第三方庫包或者我們自己的公共庫包,那麼使用NuGet毫無作用,但是實際情況恰恰相反,任何專案都需要記錄日誌,最好的情況是我們有一個公共的日誌模組,任何老專案或新專案我們可以引用它,就無需再做開發。就那我們自己的專案來說,FC,FGOnline,FGMain,FGClient,FGServer,目前我們沒有一個公共的日誌模組,底層使用Kernal及其他庫包可能也不是一個版本,即使是同一個版本我們開發上都是將dll手工拷來拷去。在新專案上來說這增大了工作量和開發量,因此我們需要一個庫包管理機制來管理我們私有庫包和我們需要使用的第三方庫包。

NuGet的優點

AsyncModule.NetMQ.dll舉例,AsyncModule.NetMQ.dll依賴NetMQ.dll,而NetMQ.dll又依賴AsyncIO.dll
目前我們需要資料庫連線的地方我們需要引用AsyncModule.NetMQ.dll,我們可能會把它手工烤到我們需要的專案中,但是由於AsyncModule.NetMQ.dll需要依賴NetMQ.dll,因此我們還需要手工把NetMQ.dll拷到我們的專案中,同時由於NetMQ.dll需要依賴AsyncIO.dll,因此我們還需要手工把AsyncIO.dll拷到我們的專案中。依賴這當中就會有些問題,比如我們忘記拷了,或者我們拷的版本不是我們當前需要的,就會導致很多問題。
NuGet就可以讓我們避免這個問題。若我們需要的庫包已經匯入到我們庫包伺服器中,那麼我們只需要一條語句就可以引用該dll,同時NuGet會自動將其依賴包一起引用到我們的專案中,這完全是自動的。

使用

在VS中找到 Package Manager Console對話方塊
技術分享圖片

若介面上沒有找到,則從工具-NuGet Package Manager下找
技術分享圖片

Get-Help NuGet

使用Get-Help NuGet命令檢視幫助

PM> Get-Help nuget
TOPIC
    about_NuGet
    
SHORT DESCRIPTION
    Provides information about NuGet Package Manager commands.
           
LONG DESCRIPTION
    This topic describes the NuGet Package Manager commands. NuGet is an integrated package 
    management tool for adding libraries and tools to .NET projects.

                 
    The following NuGet cmdlets are included.

        Cmdlet					Description
        ------------------		----------------------------------------------
        Get-Package				Gets the set of installed packages.  With -ListAvailable,gets the set of packages available from the package source.

        Install-Package			Installs a package and its dependencies into the project.

        Uninstall-Package		Uninstalls a package. If other packages depend on this package,the command will fail unless the –Force option is specified.

        Update-Package			Updates a package and its dependencies to a newer version.

        Add-BindingRedirect		Examines all assemblies within the output path for a project
                                and adds binding redirects to the application (or web) 
                                configuration file where necessary.
                            
        Get-Project				Returns a reference to the DTE (Development Tools Environment) 
                                for the specified project. If none is specifed,returns the 
                                default project selected in the Package Manager Console.

        Open-PackagePage        Open the browser pointing to ProjectUrl,LicenseUrl or 
                                ReportAbuseUrl of the specified package.

        Register-TabExpansion	Registers a tab expansion for the parameters of a command.

SEE ALSO
    Online documentation: http://go.microsoft.com/fwlink/?LinkID=206619
    Get-Package
    Install-Package
    Uninstall-Package
    Update-Package
    Add-BindingRedirect
    Get-Project
    Open-PackagePage
    Register-TabExpansion

Install-Package

使用Install-Package安裝庫包,安裝時會自動安裝當前Framework知道的庫包及依賴包,若不支援則會提示錯誤。

PM> Install-Package AsyncModule.NetMQ
Attempting to resolve dependency ‘NetMQ (≥ 4.0.0.1)‘.
Attempting to resolve dependency ‘AsyncIO (≥ 0.1.26)‘.
Installing ‘AsyncIO 0.1.26.0‘.
Successfully installed ‘AsyncIO 0.1.26.0‘.
Installing ‘NetMQ 4.0.0.1‘.
Successfully installed ‘NetMQ 4.0.0.1‘.
Installing ‘AsyncModule.NetMQ 1.1.0‘.
Successfully installed ‘AsyncModule.NetMQ 1.1.0‘.
Adding ‘AsyncIO 0.1.26.0to NuGet.Client.
Successfully added ‘AsyncIO 0.1.26.0to NuGet.Client.
Adding ‘NetMQ 4.0.0.1to NuGet.Client.
Successfully added ‘NetMQ 4.0.0.1to NuGet.Client.
Adding ‘AsyncModule.NetMQ 1.1.0to NuGet.Client.
Successfully added ‘AsyncModule.NetMQ 1.1.0to NuGet.Client.

安裝的時候注意對應的庫包源

Get-Package

使用Get-Package安裝庫包

PM> Get-Package

Id                             Version              Description/Release Notes                                                                                                                                     
--                             -------              -------------------------                                                                                                                                     
AsyncIO                        0.1.26.0             AsyncIO                                                                                                                                                       
AsyncModule.NetMQ              1.1.0                基於NetMQ的非同步Socket框架                                                                                                                                            
NetMQ                          4.0.0.1              A 100% native C# port of the lightweight high performance messaging library ZeroMQ                                                                            

Uninstall-Package

使用Uninstall-Package解除安裝已安裝的庫包,依賴包不會自動解除安裝,有需要則需要手工解除安裝依賴包

PM> Uninstall-Package AsyncModule.NetMQ
Removing ‘AsyncModule.NetMQ 1.1.0‘ from NuGet.Client.
Successfully removed ‘AsyncModule.NetMQ 1.1.0‘ from NuGet.Client.
Uninstalling ‘AsyncModule.NetMQ 1.1.0‘.
Successfully uninstalled ‘AsyncModule.NetMQ 1.1.0‘.

若庫包有多個版本則在庫包後面加上-Version 版本號引數安裝指定版本的庫包。若依賴包指定版本已經安裝則不會重複重新安裝。

PM> Install-Package AsyncModule.NetMQ -Version 1.1.0
Attempting to resolve dependency ‘NetMQ (≥ 4.0.0.1)‘.
Attempting to resolve dependency ‘AsyncIO (≥ 0.1.26)‘.
Installing ‘AsyncModule.NetMQ 1.1.0‘.
Successfully installed ‘AsyncModule.NetMQ 1.1.0‘.
Adding ‘AsyncModule.NetMQ 1.1.0to NuGet.Client.
Successfully added ‘AsyncModule.NetMQ 1.1.0to NuGet.Client.

當然也可以使用圖形介面找到上圖中的Manager NuGet Package For Solution...開啟圖形介面,在需要安裝的庫包右側點選安裝,和輸入命令是一樣的。
技術分享圖片

介面左側列表包含已安裝庫包,線上,更新等篩選,線上裡面根據資料來源分類。中間則是當前資料來源庫包列表,右側則是搜尋欄和選中庫包的詳細資訊。

當安裝了依賴包我們可以在專案根目錄找到packages.config檔案,會記錄我們安裝的庫包及版本資訊
技術分享圖片

同時在我們的專案資料夾下會有個packages的資料夾用於儲存我們下載下來的庫包
技術分享圖片

製作NuGet庫包

若我們需要上傳我們的dll到NuGet伺服器中,首先需要讓我們VS編譯時能匯出NuGet所支援的.nupkg檔案
在解決方案上面右擊找到Enable NuGet Package Restore點選開啟功能
技術分享圖片
開啟後我們需要手動在專案的.csproj檔案中在PropertyGroup下加入以下節點

    <BuildPackage>true</BuildPackage>
    <RestorePackages>true</RestorePackages>

技術分享圖片
同時在Project節點內增加以下內容

<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists(‘$(SolutionDir)\.nuget\NuGet.targets‘)" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information,see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists(‘$(SolutionDir)\.nuget\NuGet.targets‘)" Text="$([System.String]::Format(‘$(ErrorText)‘,‘$(SolutionDir)\.nuget\NuGet.targets‘))" />
  </Target>

技術分享圖片

再次編譯專案就會自動編譯出.nupkg檔案。

如果是.Net Standard 專案直接在程式右鍵打包即可打包。

搭建NuGet伺服器

新建一個專案
技術分享圖片
技術分享圖片
這裡使用3.0版本的NuGet.Server,需要.Net Framework 4.6支援。
然後引用NuGet.Server庫包

PM> Install-Package NuGet.Server

安裝完成後,編譯啟動即可,就是這麼簡單,然後託管到IIS上。
技術分享圖片
上傳庫包的時候可能需要apikey,需要在web.config中設定。

上傳NetGet庫包

編譯出NuGet我們需要將包上傳到NuGet伺服器中,這樣我們才能在VS中從NuGet伺服器中下載下來。這裡我使用NuGet Package Explorer工具進行上傳,官方支援Win10商店和使用Chocolatey下載。
若需要上傳到NuGet官方伺服器中可以在NuGet官網上傳,但是我們一般需要上傳到指定NuGet伺服器上,如我們自己的NuGet伺服器。
技術分享圖片
選擇第一項找到本地的.nupkg檔案
技術分享圖片
技術分享圖片
左側可以編譯一下資訊,
技術分享圖片
技術分享圖片
當上傳了多個版本的dll,NuGet.Server會根據包Id和Version進行分組
技術分享圖片

在輸入命令的時候可以用TAB鍵智慧提示出當前所有版本號
技術分享圖片

我們也可用通過命令上傳

nuget.exe push {package file} {apikey} -Source http://www.jnuget.com:10080/nuget

當我們同一個包上傳過同一個版本的時候再次上傳會報錯,我們需要刪除NuGet.Server已存在的包,後才能再次上傳。或者我們可以允許通過包同一個版本允許覆蓋上傳,將web.ConfigallowOverrideExistingPackageOnPush配置改為true即可

新增NuGet源

在Tools-Options-NuGet Package Manager-Package Sources可以增加資料來源
技術分享圖片
點選右上角的加號新增,輸入完地址後點一下更新即可。

總結

通過此片文章講解了如何使用、部署NuGet,如何編譯生成,上傳庫包到NuGet。

出處:https://www.cnblogs.com/Jack-Blog/p/7890369.html

=========================================================================================

使用NuGet釋出自己的類庫包(Library Package)

NuGet是一個為大家所熟知的Visual Studio擴充套件,通過這個擴充套件,開發人員可以非常方便地在Visual Studio中安裝或更新專案中所需要的第三方元件,同時也可以通過NuGet來安裝一些Visual Studio的外掛等。作為一名開發人員,您可能也會開發一些公共元件以供他人使用,本文將一步步介紹如何以最簡單的方式將自己所開發的類庫包釋出到nuget上,以供更多的人使用。

背景

如果你還是不知道什麼是NuGet,那麼就看這樣一個案例:我現在需要在我的專案中引用Castle.Core程式集,按照以往的做法,就是從Castle Projects官方網站,下載一個最新版本的dll,然後把它複製到專案的lib目錄下(或者隨便什麼地方都行),這樣做不僅繁瑣,而且你需要時刻關心官網上這個程式集的最新版本資訊(當然或許你也不會去關注),更煩的是,如果你是一個開源專案的Contributor,你還需要花一定的時間去管理所有的這些libs,不僅如此,如果你是使用的原始碼管理系統來管理專案原始碼,比如使用git等,那你還不得不把這些libs上傳到原始碼管理系統中,否則團隊中的其他組員即使獲得了原始碼,也無法正確編譯。但這樣做又大大增加了原始碼的儲存空間,使得程式碼克隆和下載都變得非常耗時。

現在,就可以直接使用NuGet來解決所有問題,我們先建立一個Class Library,命名為DaxnetNugetTest,然後在這個專案上點右鍵,選擇Manage NuGet Packages:

技術分享圖片

在彈出的對話方塊中,搜尋Castle關鍵字,然後在搜尋結果列表中選擇Castle.Core,單擊Install按鈕:

技術分享圖片

安裝完成後,Castle.Core的程式集就被引用到專案中了,同時在專案中多出了一個packages.config檔案,以向NuGet表明,當前專案使用了哪些Package,版本是什麼,以及是基於哪個版本的.NET Framework。

今後,如果Castle.Core程式集有版本更新,則同樣可以使用Manage NuGet Packages選單開啟上面的對話方塊,然後在左邊的Updates列表中,就會列出發生了版本更新的Package,如果有,則單擊Update按鈕即可更新。

更有趣的是,如果你在解決方案上點右鍵,選擇Enable NuGet Package Restore選單,那麼在你編譯專案的時候,NuGet會自動分析出你專案所依賴的第三方元件,然後在編譯開始之前會自動上網下載所需的版本,因此,你也就不要去維護這些libs了,更沒必要把這些libs也上傳到原始碼管理系統中。

技術分享圖片

不過這些也都不是本文的重點,本文的重點是,介紹如何將自己的Class Library釋出到NuGet上。

釋出自己的類庫包(Library Package)

STEP 1:在NuGet上註冊並獲取API Key

首先,你需要到NuGet上註冊一個新的賬號,然後在My Account頁面,獲取一個API Key,這個過程很簡單,我就不作說明了。

STEP 2:下載NuGet.exe

NuGet有個命令列工具:NuGet.exe,非常好用,不過使用之前需要下載,下載地址:http://nuget.codeplex.com/downloads/get/669083。為了方便使用,請設定機器的PATH環境變數,將NuGet.exe的路徑新增到PATH中。

STEP 3:設定API Key

使用以下命令設定NuGet API Key:

?
1 nuget setApiKey <my_api_key>

記得將上面的my_api_key替換為STEP 1中獲得的API Key。

STEP 4:開發自己的類庫(Class Library)

上面我們新建了一個類庫:DaxnetNugetTest,並通過NuGet添加了對Castle.Core的引用,現在我們新增一些程式碼,來使用Castle.Core所提供的一些功能。我們將Class1.cs改名為CastleHelper.cs,此時也會將Class1類改名為CastleHelper。在CastleHelper.cs中寫入以下程式碼:

?
1 2 3 4 5 6 7 public class CastleHelper { public static Castle.Core.Pair<int,int> GetIntPair() { return new Castle.Core.Pair<int,int>(20,30); } }

然後,開啟AssemblyInfo.cs檔案,將assembly的屬性設定好,記得再設定一下AssemblyVersion特性,以指定我們類庫的版本。目前我們使用1.0.0.0版本:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [assembly: AssemblyTitle("DaxnetNugetTest")] [assembly: AssemblyDescription("Daxnet‘s test of the NuGet.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("daxnet")] [assembly: AssemblyProduct("DaxnetNugetTest")] [assembly: AssemblyCopyright("Copyright © daxnet 2013")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("20662b9f-91de-4515-9c8c-ced3d61589e1")] [assembly: AssemblyVersion("1.0.0.0")]

全部設定好以後,編譯整個專案待用。

STEP 5:產生並修改nuspec

nuspec是NuGet將專案打包成nupkg的輸入檔案,可以通過nuget spec命令產生。在命令提示符下,進入DaxnetNugetTest.csproj檔案所在目錄,然後執行:

?
1 nuget spec

此時會提示建立成功:

技術分享圖片

用notepad開啟DaxnetNugetTest.nuspec檔案,把需要替換的資訊替換掉,不需要的tag全部刪掉,注意裡面的$xxx$巨集,這些就是引用了AssemblyInfo.cs中的設定值,在編譯產生package的時候,會使用AssemblyInfo.cs中的相應值進行替換。完成編輯後,我們的nuspec檔案如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0"?> <package > <metadata> <id>$id$</id> <version>$version$</version> <title>$title$</title> <authors>$author$</authors> <owners>$author$</owners> <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseUrl> <projectUrl>http://apworks.org</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>$description$</description> <releaseNotes>First release</releaseNotes> <copyright>Copyright 2013</copyright> </metadata> </package>

注意兩點:1、$description$使用AssemblyDescriptionAttribute的值進行替換,在產生package之前,一定要記得先編譯專案,否則會提示$description$找不到的錯誤;2、releaseNotes如果沒有,就直接刪掉這個節點,如果有,則填入自己的內容,不要使用預設內容,否則會在下一步產生警告資訊。

STEP 6:產生類庫包(Library Package)

同樣在DaxnetNugetTest.csproj路徑下,使用下面的命令產生NuGet類庫包:

?
1 nuget pack DaxnetNugetTest.csproj

成功後,提示:

技術分享圖片

注意:由於我們的專案通過NuGet引用了Castle.Core,因此,它將會作為一個依賴元件(dependency)打包到產生的nupkg檔案中。

另外,NuGet會使用預設的專案配置所產生的程式集進行打包。如果專案預設是Debug,而你需要用Release打包,則使用下面的命令:

?
1 nuget pack DaxnetNugetTest.csproj -Prop Configuration=Release

STEP 7:釋出類庫包

現在,通過以下命令釋出類庫包:

?
1 nuget push DaxnetNugetTest.1.0.0.0.nupkg

完成以後,出現以下提示:

技術分享圖片

STEP 8:測試已釋出的類庫包

新建一個控制檯應用程式,在專案上點右鍵,選擇Manage NuGet Packages,在搜尋框中輸入DaxnetNugetTest,此時我們釋出的Package已經可以顯示了:

技術分享圖片

單擊Install按鈕,NuGet會自動分析元件依賴關係,然後把所需要的所有程式集都下載下來並新增到專案引用中:

技術分享圖片

寫一點程式碼來測試:

?
1 2 3 4 5 6 7 8 9 class Program { static void Main(string[] args) { var pair = DaxnetNugetTest.CastleHelper.GetIntPair(); Console.WriteLine(pair.First); Console.WriteLine(pair.Second); } }

輸出如下:

技術分享圖片

STEP 9:更新類庫包

隨著類庫開發進度不斷向前,必然會有版本更新。更新類庫包很簡單,只需要在AssemblyInfo.cs中更新一下版本號,然後重新執行上面的STEP 6、7即可。注意在執行STEP 7的時候,nupkg的檔名應該使用新版本的檔名。

現在,我們重新開啟DaxnetNugetTest專案,將CastleHelper類中的20,30改為40,50,然後開啟AssemblyInfo.cs,版本號升級為2.0.0.0,重新編譯專案,並重新產生、釋出nupkg:

技術分享圖片

再開啟用來測試的控制檯程式,同樣開啟Manage NuGet Packages對話方塊,我們可以在Updates中看到,DaxnetNugetTest有了更新:

技術分享圖片

點選Update按鈕,將類庫更新到最新版本。重新執行這個控制檯程式,我們發現,輸出已經是最新版本的值了:

技術分享圖片

STEP 10:刪除已釋出的包

原則上,NuGet不允許使用者刪除已釋出的包,而只能將其設定為不顯示在Manage NuGet Packages的列表中。開啟www.nuget.org,用已註冊的賬戶登入後,可以在My Account頁面選擇Manage My Packages連結進入管理頁面:

技術分享圖片

進入後,可以看到我們已釋出的Packages:

技術分享圖片

點選DaxnetNugetTest左邊的小垃圾桶圖示,即可進入Listing頁面,頁面中我們也能看到“Permanently deleting packages is not supported”的提示。要將Package從Package List中移除,只需要去掉List DaxnetNugetTest 2.0.0.0 in search results選項前面的鉤鉤,然後單擊Save按鈕儲存即可:

技術分享圖片

總結

本文簡要介紹了NuGet的使用,並介紹了一種將自己開發的類庫以NuGet Package的方式釋出到NuGet伺服器的簡單方法。NuGet功能非常強大,有興趣的朋友可以上www.nuget.org進行學習研究。

出處:https://www.cnblogs.com/daxnet/archive/2013/05/07/3064577.html

=========================================================================================