如何使用csproj构建C#源代码组件NuGet包?

news/2024/9/29 10:23:11

一般我们构建传统的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 项目,目录结构如下:

.
├── build
└── src├── AllenCai.BuildingBlocks│   ├── AllenCai.BuildingBlocks.csproj│   ├── Properties│   │   ├── PackageInfo.cs│   ├── Assets│   │   ├── build│   │   │   └── AllenCai.BuildingBlocks.targets│   │   └── buildMultiTargeting│   │       └── AllenCai.BuildingBlocks.targets│   ├── Collections│   │   ├── ArrayBuilder.cs│   │   ├── other...│   ├── Functional│   │   ├── Result.cs│   │   ├── other...│   ├── ObjectPooling│   │   ├── DictionaryPool.cs│   │   ├── other...│   ├── Text│   │   ├── StringBuffer.cs│   │   ├── other...│   ├── Threading│   │   ├── ValueTaskEx.cs│   │   ├── other...│   ├── bin│   │   ├── Release│   │   │   └── other...│   │   ├── Debug│   │   │   └── other...│   └── obj│   │   ├── other...│   ├── icon.png│   ├── other...├── AllenCai.BuildingBlocks.sln└── Directory.Build.targets
├── .gitattributes
├── .gitignore
├── 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 文件代码如下所示:

<Project><!--将代码版本信息输出到C#文件中,使用者在项目中引入本组件源码,能够看到版本信息。且在使用者项目编译为程序集文件后,也能够保留本组件版本信息。--><Target Name="GeneratePackageInfoToFile" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Release'"><PropertyGroup><SharedPackageInfoFile>$(ProjectDir)Properties\PackageInfo.cs</SharedPackageInfoFile></PropertyGroup>
​<ItemGroup><AssemblyAttributes Include="AssemblyMetadata"><_Parameter1>PackageVersion</_Parameter1><_Parameter2>$(Version)</_Parameter2></AssemblyAttributes><AssemblyAttributes Include="AssemblyMetadata"><_Parameter1>PackageBuildDate</_Parameter1><_Parameter2>$([System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss"))</_Parameter2></AssemblyAttributes><AssemblyAttributes Include="AssemblyMetadata" Condition="'$(SourceRevisionId)' != ''"><_Parameter1>PackageSourceRevisionId</_Parameter1><_Parameter2>$(SourceRevisionId)</_Parameter2></AssemblyAttributes></ItemGroup>
​<MakeDir Directories="$(ProjectDir)Properties"/><WriteCodeFragment Language="C#" OutputFile="$(SharedPackageInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" /><Message Importance="high" Text="SharedPackageInfoFile --> $(SharedPackageInfoFile)" />
​<ItemGroup><Compile Include="$(SharedPackageInfoFile)" Pack="true" BuildAction="Compile" /></ItemGroup></Target>
</Project>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/47039.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【解决方案】Java 互联网项目中消息通知系统的设计与实现(上)

消息通知系统(notification-system)作为一个独立的微服务,完整地负责了 App 端内所有消息通知相关的后端功能实现。该系统既需要与文章系统、订单系统、会员系统等相关联,也需要和其它业务系统相关联,是一个偏底层的通用服务系统。目录前言一、需求分析1.1发送通知1.2撤回…

在 windows 上搭建一台 Linux

前言 看这篇文章之前,首先得要给大家先介绍一下什么是虚拟机。 虚拟机是一种 软件,它可以在 一台物理服务器 上,也就是我们平时所使用的电脑,虚拟出多台逻辑服务器,这个逻辑服务器怎么理解呢? 逻辑服务器是指在物理服务器基础上,通过虚拟化技术或软件配置来划分和管理的…

中间件漏洞

Nginx Nginx不安全配置漏洞 /usr/local/soft/vulhub/nginx/insecure-configuration 1.目录穿越漏洞 http://your-ip:8081/files../2.CRLF注入漏洞 location / { return 302 https://$host$uri; } 原本的目的是为了让http的请求跳转到https上利用方式 %0d%0a%0d%0a<script>…

Day 26| 39. 组合总和 、 40.组合总和II 、 131.分割回文串

组合总和本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制 题目链接/文章讲解:https://programmercarl.com/0039.组合总和.html 视频讲解:https://www.bilibili.com/video/BV1KT4y1M7HJ 给定一个无重复元素的数组 candidates 和一个目标数…

2024嵌入式大作业

2024年上海交通大学嵌入式课程大作业 本学年是嵌入式第一次上升到4学分,即在原本的2学分的理论课程之上增设了2学分的实验课,因而成为了一门大课。 不同老师之间大作业的要求不同,所以我把我们班的实验要求罗列出来,并谈一谈我的实现方案,希望能够作为前人的智慧,起到抛砖…

【Playwright+Python】系列教程(一)环境搭建及脚本录制

前言 看到这个文章,有的同学会说: 六哥,你为啥不早早就写完python系列的文章。 因为有徒弟需要吧,如果你也想学自学,那这篇文章,可以说是我们结缘一起学习的开始吧! 如果对你有用,建议收藏和转发! Playwright是什么? 微软开源自动化测试工具Playwright,支持主流浏览…

苹果CMS 阿里云OSS插件

直接下载插件上传到CMS的addons目录解压点击启用插件点击配置插件配置完毕后进入系统菜单>附件参数配置保存方式改为阿里云OSS即可需要插件直接联系我 :vx:qianjingchuangqi本文来自博客园,作者:ikay,转载请注明原文链接:https://www.cnblogs.com/ikay/p/18255405