從零開始制作 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 包看看里面的內容。你需要先去應用商店下載 NuGet Package Explorer,裝完之后你就可以開始直接雙擊 NuGet 包文件,也就是 nupkg 文件。現在我們雙擊打開看看。

NuGet 包中的內容

我們的體驗到此為止。如果你希望在真實的項目當中測試,可以閱讀其他博客了解如何在本地測試 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 ),不得用於商業目的,基於本文修改后的作品務必以相同的許可發布。如有任何疑問,請 與我聯系 (walter.lv@qq.com)

 

 

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

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

使用命令行打包 nuget 包

 

對於那些不打算涉及這么復雜而又想制作自己的 nuget 包的園友們,我是推薦使用 Nuget Package Explorer 來制作的。關於這個圖形化的 nuget 包管理軟件的使用,博客園內有相關的文章,大家可以搜索看看。

 

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

QQ截圖20151021183619

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

 

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

QQ截圖20151021184741

那么我們先下載這個命令行工具下來。

 

然后准備我們需要打包的文件(就是 dll 之類的東西)。

看了下文檔,說是支持讀取直接 csproj 文件打包,那我們先試一下吧,畢竟程序員的准則就是能簡單就簡單,能懶就懶。

這里我的項目結構是這樣的:

QQ截圖20151021185512

然后我們運行 cmd 並輸入命令

QQ截圖20151021185839

報了個錯,重新生成一下項目吧,再次運行。

QQ截圖20151021185937

好像成了,看看目錄。

QQ截圖20151021192431

QQ截圖20151021192516

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

 

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

要建立這么一個描述文件也很簡單,跑個命令

QQ截圖20151021192847

QQ截圖20151021192857

然后我們用文本編輯器來打開這個新的 Package.nuspec 文件。

QQ截圖20151021193036

可以看見是一個 xml 文件,然后修改下里面的屬性就可以了。

再次執行。

QQ截圖20151021193538

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

 

那么繼續找找文檔,最后我們可以發現這個。

QQ截圖20151021193744

修改下我們的 nuspec 文件。

QQ截圖20151021194102

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

再次執行打包命令。

QQ截圖20151021194224

這次沒警告了。

而且包的結構也沒問題。

QQ截圖20151021194328

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

 

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

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

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

那么最后就變成這樣:

QQ截圖20151021200418

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

 

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

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

QQ截圖20151021201000

按這里就可以了。

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

 

到最后一步我們已經使用批處理來做了,那么可以再玩的花樣就多去了,例如用 PowerShell 來自動化包的版本。在項目的 AssemblyInfo.cs 有一個 AssemblyVersion 的 Attribute,然后我們就可以用 PowerShell 來先讀取這個版本號,然后修改 nuspec 文件里的 version 節點,再生成包。其實我現在就已經是這么做了QQ圖片20151021201734,鑒於我 PowerShell 也沒學過,那段代碼就不放出來丟臉了QQ圖片20151021201800。大家可以發散思維,期望在 nuget 上能看見園友發布的包包bba_thumb

 

 

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對話框
20171124103240-1

若界面上沒有找到,則從工具-NuGet Package Manager下找
20171124103518-2

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.0' to NuGet.Client.
Successfully added 'AsyncIO 0.1.26.0' to NuGet.Client.
Adding 'NetMQ 4.0.0.1' to NuGet.Client.
Successfully added 'NetMQ 4.0.0.1' to NuGet.Client.
Adding 'AsyncModule.NetMQ 1.1.0' to NuGet.Client.
Successfully added 'AsyncModule.NetMQ 1.1.0' to 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.0' to NuGet.Client.
Successfully added 'AsyncModule.NetMQ 1.1.0' to NuGet.Client.

當然也可以使用圖形界面找到上圖中的Manager NuGet Package For Solution...打開圖形界面,在需要安裝的庫包右側點擊安裝,和輸入命令是一樣的。
20171124103850-3

界面左側列表包含已安裝庫包,在線,更新等篩選,在線里面根據數據源分類。中間則是當前數據源庫包列表,右側則是搜索欄和選中庫包的詳細信息。

當安裝了依賴包我們可以在項目根目錄找到packages.config文件,會記錄我們安裝的庫包及版本信息
20171124105559-6

同時在我們的項目文件夾下會有個packages的文件夾用於保存我們下載下來的庫包
20171124105740-7

制作NuGet庫包

若我們需要上傳我們的dll到NuGet服務器中,首先需要讓我們VS編譯時能導出NuGet所支持的.nupkg文件
在解決方案上面右擊找到Enable NuGet Package Restore點擊開啟功能
20171124111344-8
開啟后我們需要手動在項目的.csproj文件中在PropertyGroup下加入以下節點

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

2017112411257-9
同時在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>

21.png

再次編譯項目就會自動編譯出.nupkg文件。

如果是.Net Standard 項目直接在程序右鍵打包即可打包。

搭建NuGet服務器

新建一個項目
20171124115849-10
20171124115856-11
這里使用3.0版本的NuGet.Server,需要.Net Framework 4.6支持。
然后引用NuGet.Server庫包

PM> Install-Package NuGet.Server

安裝完成后,編譯啟動即可,就是這么簡單,然后托管到IIS上。
2017112412219-12
上傳庫包的時候可能需要apikey,需要在web.config中設置。

上傳NetGet庫包

編譯出NuGet我們需要將包上傳到NuGet服務器中,這樣我們才能在VS中從NuGet服務器中下載下來。這里我使用NuGet Package Explorer工具進行上傳,官方支持Win10商店和使用Chocolatey下載。
若需要上傳到NuGet官方服務器中可以在NuGet官網上傳,但是我們一般需要上傳到指定NuGet服務器上,如我們自己的NuGet服務器。
2017112412320-13
選擇第一項找到本地的.nupkg文件
2017112412435-14
2017112412514-15
左側可以編譯一下信息,
2017112412612-16
20171124134626-17
當上傳了多個版本的dll,NuGet.Server會根據包Id和Version進行分組
20171124135034-18

在輸入命令的時候可以用TAB鍵智能提示出當前所有版本號
2017112414219-19

我們也可用通過命令上傳

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可以增加數據源
2017112414424-20
點擊右上角的加號新增,輸入完地址后點一下更新即可。

總結

通過此片文章講解了如何使用、部署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:

image

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

image

安裝完成后,Castle.Core的程序集就被引用到項目中了,同時在項目中多出了一個packages.config文件,以向NuGet表明,當前項目使用了哪些Package,版本是什么,以及是基於哪個版本的.NET Framework。

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

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

image

不過這些也都不是本文的重點,本文的重點是,介紹如何將自己的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

此時會提示創建成功:

image

用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

成功后,提示:

image

注意:由於我們的項目通過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

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

image

STEP 8:測試已發布的類庫包

新建一個控制台應用程序,在項目上點右鍵,選擇Manage NuGet Packages,在搜索框中輸入DaxnetNugetTest,此時我們發布的Package已經可以顯示了:

SNAGHTMLecfd75

單擊Install按鈕,NuGet會自動分析組件依賴關系,然后把所需要的所有程序集都下載下來並添加到項目引用中:

image

寫一點代碼來測試:

?
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);
     }
}

輸出如下:

image

STEP 9:更新類庫包

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

現在,我們重新打開DaxnetNugetTest項目,將CastleHelper類中的20,30改為40,50,然后打開AssemblyInfo.cs,版本號升級為2.0.0.0,重新編譯項目,並重新產生、發布nupkg:

image

再打開用來測試的控制台程序,同樣打開Manage NuGet Packages對話框,我們可以在Updates中看到,DaxnetNugetTest有了更新:

image

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

image

STEP 10:刪除已發布的包

原則上,NuGet不允許用戶刪除已發布的包,而只能將其設置為不顯示在Manage NuGet Packages的列表中。打開www.nuget.org,用已注冊的賬戶登錄后,可以在My Account頁面選擇Manage My Packages鏈接進入管理頁面:

image

進入后,可以看到我們已發布的Packages:

image

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

image

總結

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

 

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

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

個人總結

首先從NuGet官方網站下載NuGet.exe程序,並添加到環境變量里面(大家應該都會),下面代碼僅供參考:

wget -nc https://dist.nuget.org/win-x86-commandline/latest/nuget.exe;
或者
if not exist ".\nuget.exe" powershell -Command "(new-object System.Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '.\nuget.exe')"

 

查看nuget版本等相關信息,確定nuget可以正常使用。

nuget help  或者 nuget ?

能夠正常顯示版本信息及nuget的相關幫助說明,表示

創建creatPack.bat文件,內容如下:

@echo off    
if not exist ".\nuget.exe" powershell -Command "(new-object System.Net.WebClient).DownloadFile('https://dist.nuget.org/win-x86-commandline/latest/nuget.exe', '.\nuget.exe')"
nuget spec AppLogger.csproj -Force
nuget pack

pause

再創建pushPack.bat文件,內容如下:

@echo off

::nuget push xxx.nupkg nuget_apikey -Source https://api.nuget.org/v3/index.json
if not "%1"=="" (
nuget push %1 oy2o4uu2ztnyagk4b****************iu77jqyy672pm= -Source https://api.nuget.org/v3/index.json
) else (
echo.
echo 錯誤:參數為空
echo 用法:%0 待發布的nuget包名
echo 例如:%0 test.AppLogger.1.0.7.nupkg
)

pause

注意:保存文件格式為ANSI格式的文件,否則可能會有中文輸出為亂碼

隱藏代碼
::nuget push jackmeng.test.AppLogger.1.0.7.nupkg oy2o4uu2ztnyagk4bkgqknuhdeke5yvuriu77jqyy672pm -Source https://api.nuget.org/v3/index.json

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM