简单的网站后台管理系统,商丘市网站建设推广,电子商务企业网站建设规划,视频制作公司价格表默认情况下#xff0c;我们打包 NuGet 包时#xff0c;目标项目安装我们的 NuGet 包会引用我们生成的库文件#xff08;dll#xff09;。除此之外#xff0c;我们也可以专门做 NuGet 工具包#xff0c;还可以做 NuGet 源代码包。然而做源代码包可能是其中最困难的一种了我们打包 NuGet 包时目标项目安装我们的 NuGet 包会引用我们生成的库文件dll。除此之外我们也可以专门做 NuGet 工具包还可以做 NuGet 源代码包。然而做源代码包可能是其中最困难的一种了目标项目安装完后这些源码将直接随目标项目一起编译。本文将从零开始教你制作一个支持 .NET 各种类型项目的源代码包。在开始制作一个源代码包之间建议你提前了解项目文件的一些基本概念理解 C# 项目 csproj 文件格式的本质和编译流程当然就算不了解也没有关系。跟着本教程你也可以制作出来一个源代码包只不过可能遇到了问题的时候不容易调试和解决。接下来我们将从零开始制作一个源代码包。我们接下来的将创建一个完整的解决方案这个解决方案包括一个将打包成源代码包的项目一个调试专用的项目可选一个测试源代码包的项目可选像其他 NuGet 包的引用项目一样我们需要创建一个空的项目。不过差别是我们需要创建的是控制台程序。当创建好之后Main 函数中的所有内容都是不需要的于是我们删除 Main 函数中的所有内容但保留 Main 函数。这时 Program.cs 中的内容如下双击创建好的项目的项目或者右键项目 “编辑项目文件”我们可以编辑此项目的 csproj 文件。在这里我将目标框架改成了 net48。实际上如果我们不制作动态源代码生成那么这里无论填写什么目标框架都不重要。在这篇博客中我们主要篇幅都会是做静态源代码生成所以你大可不必关心这里填什么。提示如果 net48 让你无法编译这个项目说明你电脑上没有装 .NET Framework 4.8 框架请改成 net473, net472, net471, net47, net462, net 461, net46, net45, netcoreapp3.0, netcoreapp2.1, netcoreapp2.0 中的任何一个可以让你编译通过的目标框架即可。Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet48/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 文件我们编辑项目文件的目的是让我们前一步创建的项目文件夹结构真正成为 NuGet 包中的文件夹结构。Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet48/TargetFramework !-- 要求此项目编译时要生成一个 NuGet 包。-- GeneratePackageOnBuildtrue/GeneratePackageOnBuild !-- 这里为了方便我将 NuGet 包的输出路径设置在了解决方案根目录的 bin 文件夹下而不是项目的 bin 文件夹下。-- PackageOutputPath..\bin\$(Configuration)/PackageOutputPath !-- 创建 NuGet 包时项目的输出文件对应到 NuGet 包的 tools 文件夹这可以避免目标项目引用我们的 NuGet 包的输出文件。 同时如果将来我们准备动态生成源代码而不只是引入静态源代码还可以有机会运行我们 Program 中的 Main 函数。-- BuildOutputTargetFoldertools/BuildOutputTargetFolder !-- 此包将不会传递依赖。意味着如果目标项目安装了此 NuGet 包那么安装目标项目包的项目不会间接安装此 NuGet 包。-- DevelopmentDependencytrue/DevelopmentDependency !-- 包的版本号我们设成了一个预览版当然你也可以设置为正式版即没有后面的 -alpha 后缀。-- Version0.1.0-alpha/Version !-- 设置包的作者。在上传到 nuget.org 之后如果作者名与 nuget.org 上的账号名相同其他人浏览包是可以直接点击链接看作者页面。-- Authorswalterlv/Authors !-- 设置包的组织名称。我当然写成我所在的组织 dotnet 职业技术学院啦。-- Companydotnet-campus/Company /PropertyGroup !-- 在生成 NuGet 包之前我们需要将我们项目中的文件夹结构一一映射到 NuGet 包中。-- Target NameIncludeAllDependencies BeforeTargets_GetPackageFiles ItemGroup !-- 将 Package.props / Package.targets 文件的名称在 NuGet 包中改为需要的真正名称。 因为 NuGet 包要自动导入 props 和 targets 文件要求文件的名称必须是 包名.props 和 包名.targets 然而为了避免我们改包名的时候还要同步改四个文件的名称所以就在项目文件中动态生成。-- None IncludeAssets\build\Package.props PackTrue PackagePathbuild\$(PackageId).props / None IncludeAssets\build\Package.targets PackTrue PackagePathbuild\$(PackageId).targets / None IncludeAssets\buildMultiTargeting\Package.props PackTrue PackagePathbuildMultiTargeting\$(PackageId).props / None IncludeAssets\buildMultiTargeting\Package.targets PackTrue PackagePathbuildMultiTargeting\$(PackageId).targets / !-- 我们将 src 目录中的所有源代码映射到 NuGet 包中的 src 目录中。-- None IncludeAssets\src\** PackTrue PackagePathsrc / /ItemGroup /Target/Project接下来我们将编写编译文件 props 和 targets。注意我们需要写的是四个文件的内容不要弄错了。如果我们做好的 NuGet 源码包被其他项目使用那么这四个文件中的其中一对会在目标项目被自动导入Import。在你理解 理解 C# 项目 csproj 文件格式的本质和编译流程 一文内容之前你可能不明白“导入”是什么意思。但作为从零开始的入门博客你也不需要真的理解导入是什么意思只要知道这四个文件中的代码将在目标项目编译期间运行就好。你只需要将下面的代码拷贝到 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 文件即可。注意将包名换成你自己的包名也就是项目名。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 文件的全部内容。可以注意到我们几乎没有任何实质性的代码在里面。即便我们在此文件中还没有写任何代码依然需要创建这个文件因为后面第五步我们将添加更复杂的代码时将再次用到这个文件完成里面的内容。现在保持你的文件中的内容与下面一模一样就好。Project PropertyGroup MSBuildAllProjects$(MSBuildAllProjects);$(MSBuildThisFileFullPath)/MSBuildAllProjects /PropertyGroup/Project下面是 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 Text1. 初始化源代码包的编译属性 / /Target !-- 引入 C# 源码。 -- Target Name_WalterlvDemoIncludeSourceFiles BeforeTargetsCoreCompile DependsOnTargets_WalterlvDemoEvaluateProperties ItemGroup _WalterlvDemoCompile Include$(_WalterlvDemoSourceFolder)**\*.cs / Compile Include(_WalterlvDemoCompile) / /ItemGroup Message Text2 引入源代码包中的所有源代码(_WalterlvDemoCompile) / /Target/Project我们刚刚花了很大的篇幅教大家完成 props 和 targets 文件那么这四个文件是做什么的呢如果安装我们源代码包的项目使用 TargetFramework 属性写目标框架那么 NuGet 会自动帮我们导入 build 文件夹中的两个编译文件。如果安装我们源代码包的项目使用 TargetFrameworks注意复数形式属性写目标框架那么 NuGet 会自动帮我们导入 buildMultiTargeting 文件夹中的两个编译文件。如果你对这个属性不熟悉请回到第一步看我们一开始创建的代码你会看到这个属性的设置的。如果还不清楚请阅读博客让一个 csproj 项目指定多个开发框架也许你已经从本文拷贝了很多代码过去了但直到目前我们还没有看到这些代码的任何效果那么现在我们就可以来看看了。这可算是一个阶段性成果呢先编译生成一下我们一直在完善的项目我们就可以在解决方案目录的 bin\Debug目录下找到一个 NuGet 包。现在我们要打开这个 NuGet 包看看里面的内容。你需要先去应用商店下载 NuGet Package Explorer装完之后你就可以开始直接双击 NuGet 包文件也就是 nupkg 文件。现在我们双击打开看看。我们的体验到此为止。如果你希望在真实的项目当中测试可以阅读其他博客了解如何在本地测试 NuGet 包。截至目前我们只是在源代码包中引入了 C# 代码。如果我们需要加入到源代码包中的代码包含 WPF 的 XAML 文件或者安装我们源代码包的目标项目包含 WPF 的 XAML 文件那么这个 NuGet 源代码包直接会导致无法编译通过。至于原因你需要阅读我的另一篇博客来了解WPF 程序的编译过程即便你不懂 WPF 程序的编译过程你也可以继续完成本文的所有内容但可能就不会明白为什么接下来我们要那样去修改我们之前创建的文件。接下来我们将修改这些文件build 文件夹中的 Package.props 文件build 文件夹中的 Package.targets 文件在这个文件中我们将新增一个属性 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请按照下面的差异说明来修改你的 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 Text1. 初始化源代码包的编译属性 / /Target !-- 引入 C# 源码。 -- Target Name_WalterlvDemoIncludeSourceFiles BeforeTargetsCoreCompile DependsOnTargets_WalterlvDemoEvaluateProperties ItemGroup _WalterlvDemoCompile Include$(_WalterlvDemoSourceFolder)**\*.cs / _WalterlvDemoAllCompile Include(_WalterlvDemoCompile) / Compile Include(_WalterlvDemoCompile) / /ItemGroup-- Message Text2 引入源代码包中的所有源代码(_WalterlvDemoCompile) / Message Text2.1 引入源代码包中的所有源代码(_WalterlvDemoCompile) / /Target !-- 引入 WPF 源码。 -- Target Name_WalterlvDemoIncludeWpfFiles BeforeTargetsMarkupCompilePass1 DependsOnTargets_WalterlvDemoEvaluateProperties ItemGroup _WalterlvDemoPage Include$(_WalterlvDemoSourceFolder)**\*.xaml / Page Include(_WalterlvDemoPage) Link%(_WalterlvDemoPage.FileName).xaml / /ItemGroup Message Text2.2 引用 WPF 相关源码(_WalterlvDemoPage) / /Target !-- 当生成 WPF 临时项目时不会自动 Import NuGet 中的 props 和 targets 文件这使得在临时项目中你现在看到的整个文件都不会参与编译。 然而我们可以通过欺骗的方式在主项目中通过 _GeneratedCodeFiles 集合将需要编译的文件传递到临时项目中以间接参与编译。 WPF 临时项目不会 Import NuGet 中的 props 和 targets 可能是 WPF 的 Bug也可能是刻意如此。 所以我们通过一个属性开关 ShouldFixNuGetImportingBugForWpfProjects 来决定是否修复这个错误。-- Target Name_WalterlvDemoImportInWpfTempProject AfterTargetsMarkupCompilePass1 BeforeTargetsGenerateTemporaryTargetAssembly DependsOnTargets$(_WalterlvDemoImportInWpfTempProjectDependsOn) Condition $(ShouldFixNuGetImportingBugForWpfProjects) True ItemGroup _GeneratedCodeFiles Include(_WalterlvDemoAllCompile) / /ItemGroup Message Text3. 正在欺骗临时项目误以为此 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 相关的文件。请特别注意我们加了一个 Link 属性并且将其指定为 %(_WalterlvDemoPage.FileName).xaml。这意味着我们会把所有的 XAML 文件都当作在项目根目录中生成如果你在其他的项目中用到了相对或绝对的 XAML 文件的路径这显然会改变路径。但是我们没有其他的方法来根据 XAML 文件所在的目录层级来自定指定 Link 属性让其在正确的层级上所以这里才写死在根目录中。如果要解决这个问题我们就需要在生成 NuGet 包之前生成此项目中所有 XAML 文件的正确的 Link 属性例如改为 Views\%(_WalterlvDemoPage.FileName).xaml这意味着需要在此项目编译期间执行一段代码把 Package.targets 文件中为所有的 XAML 文件生成正确的 Link 属性。本文暂时不考虑这个问题但你可以参考 dotnet-campus/SourceYard 项目来了解如何动态生成 Link。我们使用了 _WalterlvDemoPage 集合中转地存了 XAML 文件这是必要的。因为这样才能正确通过 % 符号获取到 FileName 属性。而 _WalterlvDemoImportInWpfTempProject 这个编译目标就不那么好理解了而这个也是完美支持 WPF 项目源代码包的关键编译目标这个编译目标指定在 MarkupCompilePass1 之后GenerateTemporaryTargetAssembly 之前执行。GenerateTemporaryTargetAssembly 编译目标的作用是生成一个临时的项目用于让 WPF 的 XAML 文件能够依赖同项目的 .NET 类型而编译。然而此临时项目编译期间是不会导入任何 NuGet 的 props 或 targets 文件的这意味着我们特别添加的所有 C# 源代码在这个临时项目当中都是不存在的——如果项目使用到了我们源代码包中的源代码那么必然因为类型不存在而无法编译通过——临时项目没有编译通过那么整个项目也就无法编译通过。但是我们通过在 MarkupCompilePass1 和 GenerateTemporaryTargetAssembly 之间将我们源代码包中的所有源代码加入到 _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 文件-- Project SdkMicrosoft.NET.Sdk Project SdkMicrosoft.NET.Sdk.WindowsDesktop PropertyGroup OutputTypeExe/OutputType TargetFrameworknet48/TargetFramework UseWpfTrue/UseWpf !-- 要求此项目编译时要生成一个 NuGet 包。-- GeneratePackageOnBuildtrue/GeneratePackageOnBuild !-- 这里为了方便我将 NuGet 包的输出路径设置在了解决方案根目录的 bin 文件夹下而不是项目的 bin 文件夹下。-- PackageOutputPath..\bin\$(Configuration)/PackageOutputPath !-- 创建 NuGet 包时项目的输出文件对应到 NuGet 包的 tools 文件夹这可以避免目标项目引用我们的 NuGet 包的输出文件。 同时如果将来我们准备动态生成源代码而不只是引入静态源代码还可以有机会运行我们 Program 中的 Main 函数。-- BuildOutputTargetFoldertools/BuildOutputTargetFolder !-- 此包将不会传递依赖。意味着如果目标项目安装了此 NuGet 包那么安装目标项目包的项目不会间接安装此 NuGet 包。-- DevelopmentDependencytrue/DevelopmentDependency !-- 包的版本号我们设成了一个预览版当然你也可以设置为正式版即没有后面的 -alpha 后缀。-- Version0.1.0-alpha/Version !-- 设置包的作者。在上传到 nuget.org 之后如果作者名与 nuget.org 上的账号名相同其他人浏览包是可以直接点击链接看作者页面。-- Authorswalterlv/Authors !-- 设置包的组织名称。我当然写成我所在的组织 dotnet 职业技术学院啦。-- Companydotnet-campus/Company /PropertyGroup !-- 我们添加的其他资源需要在这里 Link 到一个统一的目录下以便在此项目和安装 NuGet 包的目标项目中可以用同样的 Uri 使用。 -- ItemGroup Resource IncludeAssets\assets\Icon.ico LinkAssets\Icon.ico VisibleFalse / Resource IncludeAssets\assets\Background.png LinkAssets\Background.png VisibleFalse / /ItemGroup !-- 在生成 NuGet 包之前我们需要将我们项目中的文件夹结构一一映射到 NuGet 包中。-- Target NameIncludeAllDependencies BeforeTargets_GetPackageFiles ItemGroup !-- 将 Package.props / Package.targets 文件的名称在 NuGet 包中改为需要的真正名称。 因为 NuGet 包要自动导入 props 和 targets 文件要求文件的名称必须是 包名.props 和 包名.targets 然而为了避免我们改包名的时候还要同步改四个文件的名称所以就在项目文件中动态生成。-- None IncludeAssets\build\Package.props PackTrue PackagePathbuild\$(PackageId).props / None IncludeAssets\build\Package.targets PackTrue PackagePathbuild\$(PackageId).targets / None IncludeAssets\buildMultiTargeting\Package.props PackTrue PackagePathbuildMultiTargeting\$(PackageId).props / None IncludeAssets\buildMultiTargeting\Package.targets PackTrue PackagePathbuildMultiTargeting\$(PackageId).targets / !-- 我们将 src 目录中的所有源代码映射到 NuGet 包中的 src 目录中。-- None IncludeAssets\src\** PackTrue PackagePathsrc / !-- 我们将 assets 目录中的所有源代码映射到 NuGet 包中的 assets 目录中。-- None IncludeAssets\assets\** PackTrue PackagePathassets / /ItemGroup /Target !-- 在编译结束后将生成的可执行程序放到 Tools 文件夹中使得 Assets 文件夹的目录结构与 NuGet 包非常相似便于 Sample 项目进行及时的 NuGet 包调试。 -- Target Name_WalterlvDemoCopyOutputToDebuggableFolder AfterTargetsAfterBuild ItemGroup _WalterlvDemoToCopiedFiles Include$(OutputPath)** / /ItemGroup Copy SourceFiles(_WalterlvDemoToCopiedFiles) DestinationFolderAssets\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 Text1. 初始化源代码包的编译属性 / /Target !-- 引入主要的 C# 源码。 -- Target Name_WalterlvDemoIncludeSourceFiles BeforeTargetsCoreCompile DependsOnTargets_WalterlvDemoEvaluateProperties ItemGroup _WalterlvDemoCompile Include$(_WalterlvDemoSourceFolder)**\*.cs / _WalterlvDemoAllCompile Include(_WalterlvDemoCompile) / Compile Include(_WalterlvDemoCompile) / /ItemGroup Message Text2.1 引入源代码包中的所有源代码(_WalterlvDemoCompile) / /Target !-- 引入 WPF 源码。 -- Target Name_WalterlvDemoIncludeWpfFiles BeforeTargetsMarkupCompilePass1 DependsOnTargets_WalterlvDemoEvaluateProperties ItemGroup-- _WalterlvDemoPage Include$(_WalterlvDemoSourceFolder)**\*.xaml /-- Page Include(_WalterlvDemoPage) LinkViews\%(_WalterlvDemoPage.FileName).xaml / _WalterlvDemoRootPage Include$(_WalterlvDemoSourceFolder)FooView.xaml / Page Include(_WalterlvDemoRootPage) LinkViews\%(_WalterlvDemoRootPage.FileName).xaml / _WalterlvDemoThemesPage Include$(_WalterlvDemoSourceFolder)Themes\Walterlv.Windows.xaml / Page Include(_WalterlvDemoThemesPage) LinkViews\%(_WalterlvDemoThemesPage.FileName).xaml / _WalterlvDemoIcoResource Include$(_WalterlvDemoRoot)assets\*.ico / _WalterlvDemoPngResource Include$(_WalterlvDemoRoot)assets\*.png / Resource Include(_WalterlvDemoIcoResource) Linkassets\%(_WalterlvDemoIcoResource.FileName).ico / Resource Include(_WalterlvDemoPngResource) Linkassets\%(_WalterlvDemoPngResource.FileName).png / /ItemGroup-- Message Text2.2 引用 WPF 相关源码(_WalterlvDemoPage);(_WalterlvDemoIcoResource);(_WalterlvDemoPngResource) / Message Text2.2 引用 WPF 相关源码(_WalterlvDemoRootPage);(_WalterlvDemoThemesPage);(_WalterlvDemoIcoResource);(_WalterlvDemoPngResource) / /Target !-- 当生成 WPF 临时项目时不会自动 Import NuGet 中的 props 和 targets 文件这使得在临时项目中你现在看到的整个文件都不会参与编译。 然而我们可以通过欺骗的方式在主项目中通过 _GeneratedCodeFiles 集合将需要编译的文件传递到临时项目中以间接参与编译。 WPF 临时项目不会 Import NuGet 中的 props 和 targets 可能是 WPF 的 Bug也可能是刻意如此。 所以我们通过一个属性开关 ShouldFixNuGetImportingBugForWpfProjects 来决定是否修复这个错误。-- Target Name_WalterlvDemoImportInWpfTempProject AfterTargetsMarkupCompilePass1 BeforeTargetsGenerateTemporaryTargetAssembly DependsOnTargets$(_WalterlvDemoImportInWpfTempProjectDependsOn) Condition $(ShouldFixNuGetImportingBugForWpfProjects) True ItemGroup _GeneratedCodeFiles Include(_WalterlvDemoAllCompile) / /ItemGroup Message Text3. 正在欺骗临时项目误以为此 NuGet 包中的文件是 XAML 编译后的中间代码(_WalterlvDemoAllCompile) / /Target /Project本文涉及到的所有代码均已开源到walterlv.demo/Walterlv.PackageDemo at master · walterlv/walterlv.demo本文服务于开源项目 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.更多制作源代码包的博客可以阅读。从简单到复杂的顺序将 .NET Core 项目打一个最简单的 NuGet 源码包安装此包就像直接把源码放进项目一样 - 吕毅Roslyn 如何基于 Microsoft.NET.Sdk 制作源代码包 - 林德熙制作通过 NuGet 分发的源代码包时如果目标项目是 WPF 则会出现一些问题探索篇含解决方案 - 吕毅SourceYard 制作源代码包 - 林德熙原文地址https://walterlv.com/post/build-source-code-package-for-wpf-projects.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com