一般我们构建传统的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

如何使用csproj构建C#源代码组件NuGet包?的更多相关文章

  1. 一个技术汪的开源梦 —— 基于 .Net Core 的组件 Nuget 包制作 & 发布

    一个技术汪的开源梦 —— 目录 微软的 ASP.Net Core 强化了 Nuget 的使用,所有的 .Net Core 组件均有 Nuget 管理,所以有必要探讨一下 .Net Core 组件制作 ...

  2. 使用GUI工具高效构建你自己的Nuget包

    写这篇文章的原因是我在学习构建nuget包的时候,发现了一个官方推荐的GUI工具,而官方的工具介绍文章已经过时,一些地方和现在最新版本的工具有些差异,所以特意利用假期最后一个下午写下来,希望能帮助更多 ...

  3. 记录使用 Cake 进行构建并制作 nuget 包

    书接上一回(https://www.cnblogs.com/h82258652/p/4898983.html)?[手动狗头] 前段时间折腾了一下,总算是把我自己的图片缓存控件(https://gith ...

  4. 容器环境下如何将NuGet包XML文档添加到Swagger

    容器环境下将NuGet包XML文档添加到Swagger 在.NET Core项目开发过程中,为了实现代码复用,我们将可以重复使用的部分拆分成一个个小的NuGet包.这些NuGet包可以在其他系统中复用 ...

  5. 自定义Nuget包的技巧一二

    背景: 在项目中, 通常会拆分成核心库(Core)和应用(App)两个部分.核心库由专人维护, 不同的App是不同的团队,但都引用了核心库.当核心库需要升级更新时,有的应用会更新,有的不会--可能是没 ...

  6. 让你发布的nuget包支持源代码调试

    前情概要 在不久的从前(也还是要以年为单位哈), 我们如果需要调试第三方代码, 或者框架代码很麻烦. 需要配置symbols, 匹配原始代码路径等. 为此, MS推出了 Source Link 功能, ...

  7. [UWP] 为WinRT组件创建Nuget包

    Nuget 是 dotnet 开发中必不可少的包管理工具,但不仅仅局限于 dotnet 项目,在 VS 中使用 C++ 开发的时候,也可以使用 Nuget 来引用第三方组件.同样也可以用 Nuget ...

  8. 在 VS 2013/2015 中禁用 nuget 包的源代码管理

    对于加入源代码管理如TFS的解决方案,当使用nuget获取包时,下载的包并没有自动从源代码管理中排除,导致包(packages文件夹)会一同上传到服务器. 若要排除nuget包的源代码管理,须在 解决 ...

  9. .NET持续集成与自动化部署之路第二篇——使用NuGet.Server搭建公司内部的Nuget(包)管理器

    使用NuGet.Server搭建公司内部的Nuget(包)管理器 前言     Nuget是一个.NET平台下的开源的项目,它是Visual Studio的扩展.在使用Visual Studio开发基 ...

  10. 基于 Vue.js 之 iView UI 框架非工程化实践记要 使用 Newtonsoft.Json 操作 JSON 字符串 基于.net core实现项目自动编译、并生成nuget包 webpack + vue 在dev和production模式下的小小区别 这样入门asp.net core 之 静态文件 这样入门asp.net core,如何

    基于 Vue.js 之 iView UI 框架非工程化实践记要   像我们平日里做惯了 Java 或者 .NET 这种后端程序员,对于前端的认识还常常停留在 jQuery 时代,包括其插件在需要时就引 ...

随机推荐

  1. IIncrementalGenerator 判断程序集的引用关系

    本文将告诉大家如何在 IIncrementalGenerator 增量 Source Generator 生成代码里面,在 Roslyn 分析器里面判断两个程序集是否存在引用关系 先上核心代码实现,核 ...

  2. WPF 开源二维绘画小工具 GeometryToolDemo 项目

    这是一个演示 WPF 进行二维绘画的小工具 Demo 项目,基于 MIT 协议在 GitHub 上完全开源 源作者是 YuWeiCong 我只是帮助开源的工具人 软件运行界面效果: 开源地址: htt ...

  3. 大模型必备 - 中文最佳向量模型 acge_text_embedding

    近期,上海合合信息科技股份有限公司发布的文本向量化模型 acge_text_embedding 在中文文本向量化领域取得了重大突破,荣获 Massive Text Embedding Benchmar ...

  4. Rails向数据库添加新字段和修改字段

    目录 添加字段 控制台上执行下面的命令 会生成文件db/migrate/20210529131328_add_column_to_black_ips.rb 执行迁移 执行结果 修改字段 添加迁移文件 ...

  5. Ubuntu 上安装 Docker

    步骤 1:删除任何现有的 Docker 包 但在跳到安装部分之前,有必要删除所有以前安装的 Docker. 要 卸载以前的 Docker,请使用以下命令. sudo apt remove docker ...

  6. HDU-Employment Planning题解

    题目在这里 -------------------------------- Employment Planning 简单的一道dp 关键的点在于想到用枚举实现各种情况的讨论 关键的注释写在代码里了 ...

  7. 回顾复习x学习笔记

    从头回顾(截至搜索) #define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++) #define foo(x,y,z) for(int (x)=(y);( ...

  8. SSMS表设计器显示说明(注释)字段

    原文地址:https://www.giantliu.cn/2020/09/14/200914SSMSTableDesignAddDescription/ SQL Server Management S ...

  9. Laravel 实现自定义资源路由

    Laravel 如何实现自定义资源路由 最近在开发过程中,发现总有一些路由需要重复定义,比如切换状态,导出,回收站啊之类的.如果使用 Laravel 自带的资源路由方法,还不足以满足重复劳动得过程.所 ...

  10. DashVector x 通义千问大模型:打造基于专属知识的问答服务

    本教程演示如何使用向量检索服务(DashVector),结合LLM大模型等能力,来打造基于垂直领域专属知识等问答服务.其中LLM大模型能力,以及文本向量生成等能力,这里基于灵积模型服务上的通义千问 A ...