经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
如何使用csproj构建C#源代码组件NuGet包?
来源:cnblogs  作者:VAllen  时间:2024/6/19 15:15:23  对本文有异议

一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。

至于打包和分发C#源代码文件的做法,比较少见。

那么这种打包源代码文件的做法,有什么优点和缺点呢?

优点:

  1. 方便阅读源代码。
  2. 方便断点调试。
  3. 减少 Assembly 程序集模块加载个数。
  4. 更利于发布期间的剪裁(PublishTrimmed 选项)。
  5. 更利于混淆和保护代码(Internal 级别的源代码)。

缺点:

  1. 容易外泄原始的源代码文件。
  2. 随着引入源代码组件越多,越容易引发命名空间和类型名称重复冲突。

经验:

  1. 不建议也不推荐分发 public 级别的源代码。
  2. 尽可能严格规范命名类型名称。
  3. 向目标项目写入源代码组件 version 和 git commit sha-1,方便出问题时排查版本问题。
  4. 每次改动源代码文件时,尽可能做到向下兼容。

正文:

接下来,我们一起看看如何制作仅打包C#源代码文件,不打包dll程序集文件的C#源代码组件NuGet包

首先是创建 AllenCai.BuildingBlocks 项目,目录结构如下:

  1. .
  2. ├── build
  3. └── src
  4. ├── AllenCai.BuildingBlocks
  5. ├── AllenCai.BuildingBlocks.csproj
  6. ├── Properties
  7. ├── PackageInfo.cs
  8. ├── Assets
  9. ├── build
  10. └── AllenCai.BuildingBlocks.targets
  11. └── buildMultiTargeting
  12. └── AllenCai.BuildingBlocks.targets
  13. ├── Collections
  14. ├── ArrayBuilder.cs
  15. ├── other...
  16. ├── Functional
  17. ├── Result.cs
  18. ├── other...
  19. ├── ObjectPooling
  20. ├── DictionaryPool.cs
  21. ├── other...
  22. ├── Text
  23. ├── StringBuffer.cs
  24. ├── other...
  25. ├── Threading
  26. ├── ValueTaskEx.cs
  27. ├── other...
  28. ├── bin
  29. ├── Release
  30. └── other...
  31. ├── Debug
  32. └── other...
  33. └── obj
  34. ├── other...
  35. ├── icon.png
  36. ├── other...
  37. ├── AllenCai.BuildingBlocks.sln
  38. └── Directory.Build.targets
  39. ├── .gitattributes
  40. ├── .gitignore
  41. ├── README.md

其中 Directory.Build.targets 文件,用来生成描述源代码组件包版本信息的C#源代码文件,输出文件路径为:Properties\PackageInfo.cs

之所以输出到 Properties 目录,是因为 PackageInfo.cs 的作用其实和以前 .NET Framework 时代每个项目都会包含的 AssemblyInfo.cs 相同。

那么,为什么需要生成这个 PackageInfo.cs 文件呢?

  1. 因为不再是编译和发布dll,而是直接打包和提供源代码文件,原本被内嵌到dll程序集的版本信息是丢失的。
  2. 懒,也不希望每次手工维护写入 Versiongit commit sha-1

Directory.Build.targets 文件代码如下所示:

  1. <Project>
  2. <!--
  3. 将代码版本信息输出到C#文件中,使用者在项目中引入本组件源码,能够看到版本信息。
  4. 且在使用者项目编译为程序集文件后,也能够保留本组件版本信息。
  5. -->
  6. <Target Name="GeneratePackageInfoToFile" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Release'">
  7. <PropertyGroup>
  8. <SharedPackageInfoFile>$(ProjectDir)Properties\PackageInfo.cs</SharedPackageInfoFile>
  9. </PropertyGroup>
  10. ?
  11. <ItemGroup>
  12. <AssemblyAttributes Include="AssemblyMetadata">
  13. <_Parameter1>PackageVersion</_Parameter1>
  14. <_Parameter2>$(Version)</_Parameter2>
  15. </AssemblyAttributes>
  16. <AssemblyAttributes Include="AssemblyMetadata">
  17. <_Parameter1>PackageBuildDate</_Parameter1>
  18. <_Parameter2>$([System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss"))</_Parameter2>
  19. </AssemblyAttributes>
  20. <AssemblyAttributes Include="AssemblyMetadata" Condition="'$(SourceRevisionId)' != ''">
  21. <_Parameter1>PackageSourceRevisionId</_Parameter1>
  22. <_Parameter2>$(SourceRevisionId)</_Parameter2>
  23. </AssemblyAttributes>
  24. </ItemGroup>
  25. ?
  26. <MakeDir Directories="$(ProjectDir)Properties"/>
  27. <WriteCodeFragment Language="C#" OutputFile="$(SharedPackageInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
  28. <Message Importance="high" Text="SharedPackageInfoFile --> $(SharedPackageInfoFile)" />
  29. ?
  30. <ItemGroup>
  31. <Compile Include="$(SharedPackageInfoFile)" Pack="true" BuildAction="Compile" />
  32. </ItemGroup>
  33. </Target>
  34. </Project>

AllenCai.BuildingBlocks.targets 文件,将会被打包到NuGet包。

当这个包被添加引用到目标项目中,MsBuild 将会自动调用它,执行一系列由你定义的动作。

那么,又为什么需要这个 AllenCai.BuildingBlocks.targets 文件呢?

它其实是非必须的,根据项目实际情况而定,没有这个 targets 文件也是可以的。

但这样的话,可能引用这个源代码组件包的开发者会在刚引入时遇到一系列问题,导致这个源代码组件包对开发者不友好。

比如源代码文件中使用了不安全代码,而目标项目的<AllowUnsafeBlocks>属性值是 false,那么目标项目在编译时就会报错。

因此需要这个 targets 文件来检查和自动设置为 true

如以下示例代码(build\AllenCai.BuildingBlocks.targets):

  1. <Project>
  2. <Target Name="UpdateLangVersionAndAllowUnsafeBlocks" BeforeTargets="BeforeCompile">
  3. <PropertyGroup>
  4. <OldAllowUnsafeBlocks>$(AllowUnsafeBlocks)</OldAllowUnsafeBlocks>
  5. <AllowUnsafeBlocks Condition=" '$(AllowUnsafeBlocks)' == '' or $([System.String]::Equals('$(AllowUnsafeBlocks)','false','StringComparison.InvariantCultureIgnoreCase')) ">True</AllowUnsafeBlocks>
  6. </PropertyGroup>
  7. ?
  8. <!--当属性项被修改时,在Build控制台输出提示-->
  9. <Message Importance="high" Condition=" '$(AllowUnsafeBlocks)' != '$(OldAllowUnsafeBlocks)' " Text="Update AllowUnsafeBlocks to $(AllowUnsafeBlocks)" />
  10. </Target>
  11. </Project>
  12. 以及 buildMultiTargeting\AllenCai.BuildingBlocks.targets 文件代码如下所示:
  13. <Project>
  14. <Import Project="..\build\AllenCai.BuildingBlocks.targets" />
  15. </Project>

需要注意的是,这个 targets 文件需要与 ProjectName 或 PackageId 保持一致。

最后 AllenCai.BuildingBlocks.csproj 文件代码如下所示:

  1. <Project Sdk="Microsoft.NET.Sdk">
  2. <PropertyGroup>
  3. <TargetFrameworks>net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
  4. <LangVersion>default</LangVersion>
  5. <Nullable>enable</Nullable>
  6. <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  7. <ImplicitUsings>disable</ImplicitUsings>
  8. <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
  9. <GenerateDocumentationFile>false</GenerateDocumentationFile>
  10. <Version>0.0.1</Version>
  11. </PropertyGroup>
  12. ?
  13. <!--一些与NuGet包相关的属性项-->
  14. <PropertyGroup>
  15. <Title>AllenCai BuildingBlocks</Title>
  16. <Description>提供一组最常用的通用软件模块,以文件链接的方式被包含到引用项目中。</Description>
  17. <Authors>Allen.Cai</Authors>
  18. <Copyright>Copyright ? Allen.Cai 2015-$([System.DateTime]::Now.Year) All Rights Reserved</Copyright>
  19. <ContentTargetFolders>contentFiles\cs\any\AllenCai.BuildingBlocks;content\cs\any\AllenCai.BuildingBlocks</ContentTargetFolders>
  20. <!--该属性项声明了仅在开发期间依赖,并且不传递其自身的依赖项,这将导致目标项目需要主动引入间接依赖项-->
  21. <DevelopmentDependency>true</DevelopmentDependency>
  22. <!--打包时不包含编译输出的文件-->
  23. <IncludeBuildOutput>false</IncludeBuildOutput>
  24. <!--该属性项仅用于源生成器(SourceGenerator)项目,从 Visual Studio 2022 v16.10及以上版本开始支持-->
  25. <!--<IsRoslynComponent>true</IsRoslynComponent>-->
  26. <!--跳过包分析-->
  27. <NoPackageAnalysis>true</NoPackageAnalysis>
  28. <PackageProjectUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks/</PackageProjectUrl>
  29. <PackageReadmeFile>README.md</PackageReadmeFile>
  30. <RepositoryUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks.git</RepositoryUrl>
  31. <RepositoryType>git</RepositoryType>
  32. <PackageIcon>icon.png</PackageIcon>
  33. </PropertyGroup>
  34. ?
  35. <ItemGroup>
  36. <!-- <PackageReference Include="System.Reactive" Version="5.0.0" /> -->
  37. <None Include="icon.png" Pack="true" PackagePath="\" />
  38. <None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="\" />
  39. <Content Include="**\*.cs" Exclude="obj\**\*.cs" Pack="true" BuildAction="Compile" />
  40. </ItemGroup>
  41. </Project>

其中三个属性比较重要,DevelopmentDependencyIncludeBuildOutput 以及 ContentTargetFolders

  1. DevelopmentDependency 设置为 true,表示这个 NuGet 包仅在开发期间依赖?。
  2. IncludeBuildOutput 设置为 false,表示打包时不包含编译输出的 dll? 文件。
  3. 重写 ContentTargetFolders,将会改变这些源代码文件在目标项目中的虚拟?文件系统布局。

如有不明白,欢迎留言,互相探讨。

截止本文,我刚搜到有 MVP大佬-吕毅 也写了类似教程,?大家也可以参考:从零开始制作 NuGet 源代码包(全面支持 .NET Core / .NET Framework / WPF 项目) - walterlv

原文链接:https://www.cnblogs.com/VAllen/p/18255504/how-to-use-csproj-to-build-the-csharp-source-code-component-nuget-package

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号