一、从.NET项目的“版本混乱”说起

做.NET开发的人,大概率都遇到过这种糟心事:团队里有三五个开发,每人改完代码都要打包发布,结果A同学改了登录模块的bug,打包的版本号是1.0.1;B同学加了支付功能,打包的版本号却写成了1.0.2;测试同学测的时候拿错了包,把B的包当成A的测,最后上线出了问题,找半天原因才发现是版本对不上。还有更麻烦的:如果项目依赖了其他团队开发的公共组件,比如一个封装了短信发送的类库,对方更新了版本,你这边要么手动找文件复制粘贴,要么在项目里改配置,不仅慢还容易漏改。

其实这些问题的核心,就是版本管理和自动化部署没跟上。以前大家习惯用“复制粘贴+改配置”的方式来管理依赖和发布,效率低不说,还特别容易出错。而NuGet作为.NET生态里专门管包的工具,刚好能解决这些问题——它就像一个专门给.NET项目用的“超市”,你需要的组件、自己打包的项目,都能放在这里,要的时候直接“扫码拿货”,版本还能自动匹配,不用再手动折腾。

二、NuGet在自动化部署中的核心作用

要理解NuGet怎么帮我们,得先搞清楚它最基础的两个功能:一是打包,二是拉取。打包就是把你的.NET项目(比如类库、控制台程序、Web程序)按照固定的格式打成一个包,里面包含编译好的代码、配置文件、依赖信息这些内容;拉取就是在需要的时候,把这个包下载到本地,自动配置好依赖关系。

在自动化部署的场景里,NuGet的作用就像一个“中间桥梁”。比如你改完代码提交到代码仓库(比如Git),自动部署工具(比如GitHub Actions、Azure DevOps)先把代码拉下来,然后用NuGet把代码打成指定版本的包,再把这个包推到内部的NuGet服务器上;之后测试环境或者生产环境的部署工具,就可以直接从NuGet服务器拉取指定版本的包,自动完成部署。整个过程不用人工干预,版本号也能自动生成,不会再出现手动改版本的混乱。

举个最简单的例子,比如你开发了一个公共的工具类库,叫MyCommonUtils,里面有个方法用来格式化日期。你改了这个方法,提交代码后,自动部署工具会自动把这个项目打成版本号为1.0.3的包,推到内部NuGet服务器。其他项目如果需要用这个新方法,只要在自己的项目里更新这个包的版本就行,不用再手动复制代码。

三、完整的自动化部署示例(技术栈:.NET 6 + GitHub Actions + NuGet 包管理)

接下来我们用一个完整的例子,把NuGet在自动化部署里的应用讲清楚。这个例子的技术栈是.NET 6(最常用的.NET LTS版本)、GitHub Actions(免费的自动化工具,适合小团队)、内部NuGet服务器(这里用GitHub Packages,GitHub自带的NuGet服务器,不用自己搭)。整个流程分为三步:配置项目、配置自动化打包、配置自动化部署。

3.1 配置.NET项目的打包信息

首先,我们要把项目配置成能被NuGet打包的格式。比如我们有一个.NET 6的类库项目,叫MyCommonUtils,打开项目里的.csproj文件(就是项目的配置文件),添加打包需要的信息:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <!-- 打包需要的信息:包ID、版本号、作者、描述 -->
    <PackageId>MyCommonUtils</PackageId> <!-- 包的唯一标识,不能重复 -->
    <Version>1.0.0</Version> <!-- 初始版本号,后面会自动更新 -->
    <Authors>Your Team Name</Authors> <!-- 作者 -->
    <Description>一个公共工具类库,包含日期格式化、字符串处理等方法</Description>
    <PackageTags>dotnet, common, utils</PackageTags> <!-- 标签,方便搜索 -->
    <PackageProjectUrl>https://github.com/你的仓库地址</PackageProjectUrl> <!-- 项目地址 -->
  </PropertyGroup>

</Project>

这个配置里的PackageId和Version很重要,PackageId是这个包的“身份证”,其他项目要引用这个包,必须用这个ID;Version是版本号,我们后面会通过自动化工具自动更新,不用手动改。

接下来,我们在项目里写一个简单的工具方法,用来格式化日期:

using System;

namespace MyCommonUtils
{
    public class DateUtils
    {
        /// <summary>
        /// 把DateTime格式化成yyyy-MM-dd HH:mm:ss的字符串
        /// </summary>
        /// <param name="date">要格式化的日期</param>
        /// <returns>格式化后的字符串</returns>
        public static string FormatDate(DateTime date)
        {
            return date.ToString("yyyy-MM-dd HH:mm:ss");
        }
    }
}

写完代码后,我们可以先手动打包测试一下,打开命令行,进入项目所在的文件夹,执行下面的命令:

# 用NuGet打包项目,生成的包会放在bin/Release文件夹里
dotnet pack --configuration Release

执行完后,你会看到bin/Release文件夹里多了一个MyCommonUtils.1.0.0.nupkg的文件,这个就是NuGet的包文件。

3.2 配置GitHub Actions自动打包并推到NuGet服务器

接下来我们配置GitHub Actions,让代码提交后自动打包、自动推到GitHub Packages(NuGet服务器)。首先在GitHub仓库的根目录下,创建一个.github/workflows的文件夹,然后在里面创建一个叫publish-nuget.yml的文件,内容如下:

name: 打包并发布NuGet包
on:
  push:
    branches: [ main ] # 只有推到main分支才触发
jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    steps:
      # 第一步:拉取代码
      - name: 拉取代码
        uses: actions/checkout@v4
      # 第二步:安装.NET 6 SDK
      - name: 安装.NET 6 SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '6.0.x'
      # 第三步:自动生成版本号(用提交次数作为版本号的最后一位)
      - name: 自动生成版本号
        id: generate_version
        run: |
          # 获取当前仓库的提交次数
          commit_count=$(git rev-list --count HEAD)
          # 版本号格式:主版本.次版本.修订版本,这里主版本和次版本固定,修订版本用提交次数
          version="1.0.$commit_count"
          echo "VERSION=$version" >> $GITHUB_OUTPUT
      # 第四步:打包项目,指定自动生成的版本号
      - name: 打包项目
        run: dotnet pack --configuration Release -p:Version=${{ steps.generate_version.outputs.VERSION }}
      # 第五步:推到GitHub Packages(NuGet服务器)
      - name: 发布NuGet包
        run: dotnet nuget push bin/Release/*.nupkg --source https://nuget.pkg.github.com/你的用户名/index.json --api-key ${{ secrets.GITHUB_TOKEN }}

这个配置的核心是自动生成版本号的步骤,我们用仓库的提交次数作为版本号的最后一位,这样每次提交代码,版本号都会自动加1,不会出现重复的情况。还有最后一步的--api-key,这里的secrets.GITHUB_TOKEN是GitHub自动生成的密钥,不用自己手动配置,只要仓库有权限就行。

配置完这个文件后,我们把代码提交到GitHub的main分支,GitHub Actions就会自动跑起来,等执行完,你就能在GitHub仓库的Packages里看到刚刚发布的NuGet包了。

3.3 配置.NET项目自动部署(拉取NuGet包)

接下来我们看另一个项目,比如一个叫MyWebApp的.NET 6 Web项目,这个项目需要引用我们刚刚发布的MyCommonUtils包。我们先手动配置一下引用,打开MyWebApp的.csproj文件,添加引用:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <!-- 引用我们发布的NuGet包,指定版本号 -->
    <PackageReference Include="MyCommonUtils" Version="1.0.1" />
  </ItemGroup>

</Project>

这里的Version可以写成*,表示自动拉取最新的版本,比如:

<PackageReference Include="MyCommonUtils" Version="*" />

配置完后,我们在MyWebApp的代码里用这个工具方法:

using Microsoft.AspNetCore.Mvc;
using MyCommonUtils;

namespace MyWebApp.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            // 用MyCommonUtils里的方法格式化当前时间
            string currentTime = DateUtils.FormatDate(DateTime.Now);
            ViewData["CurrentTime"] = currentTime;
            return View();
        }
    }
}

接下来我们配置MyWebApp的自动化部署,让它自动拉取最新的NuGet包,然后部署到服务器。我们还是用GitHub Actions,在MyWebApp的仓库里创建.github/workflows/deploy.yml文件,内容如下:

name: 部署Web项目
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 第一步:拉取代码
      - name: 拉取代码
        uses: actions/checkout@v4
      # 第二步:安装.NET 6 SDK
      - name: 安装.NET 6 SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '6.0.x'
      # 第三步:配置NuGet源,添加GitHub Packages的源(因为我们的包在GitHub上)
      - name: 配置NuGet源
        run: |
          dotnet nuget add source https://nuget.pkg.github.com/你的用户名/index.json --name github-packages --username 你的用户名 --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
      # 第四步:恢复依赖(自动拉取最新的NuGet包)
      - name: 恢复依赖
        run: dotnet restore
      # 第五步:编译项目
      - name: 编译项目
        run: dotnet build --configuration Release
      # 第六步:部署到服务器(这里用scp命令,把编译好的文件传到服务器)
      - name: 部署到服务器
        run: |
          scp -r bin/Release/net6.0/publish/* 你的服务器用户名@你的服务器IP:/var/www/mywebapp/
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}

这个配置里的核心是配置NuGet源和恢复依赖的步骤,因为我们的MyCommonUtils包是发布在GitHub Packages上的,所以要先配置这个源,才能拉取到包。恢复依赖的时候,因为我们的包引用写的是*,所以会自动拉取最新的版本,这样MyWebApp就能用到MyCommonUtils的最新功能了。

四、NuGet在自动化部署中的应用场景

NuGet的应用场景其实覆盖了.NET项目从开发到部署的全流程,最常见的有这几种: 第一种是公共组件的管理,比如团队内部开发的短信组件、支付组件、日志组件,都可以打包成NuGet包,推到内部服务器,其他项目引用的时候直接拉取,不用再复制代码,也不用再改配置,大大减少了重复劳动。 第二种是多项目的依赖管理,比如一个大项目拆分成多个子项目,每个子项目都可以打包成NuGet包,主项目通过引用这些包来组合,版本管理清晰,哪个子项目更新了,主项目只要更新对应的包版本就行,不会出现代码混乱的情况。 第三种是自动化部署的版本控制,比如测试环境需要用指定版本的包来测试,生产环境需要用稳定版本的包来部署,通过NuGet可以轻松指定版本号,不用再手动找对应的包文件,也不用再担心版本错配的问题。 第四种是第三方组件的管理,比如.NET项目需要用第三方的类库,比如Newtonsoft.Json、AutoMapper,这些组件都可以通过NuGet来引用,自动管理版本,不用再手动下载dll文件,也不用再担心依赖冲突的问题。

五、NuGet在自动化部署中的优缺点

5.1 优点

第一是提升版本管理的效率,以前手动改版本号、手动复制包,现在通过自动化工具自动生成版本号、自动推包、自动拉包,整个过程不用人工干预,节省了大量的时间。 第二是提升版本管理的准确性,自动生成的版本号不会出现重复,自动拉取的包不会出现错配,避免了因为版本问题导致的线上故障。 第三是简化了依赖管理,不管是内部组件还是第三方组件,都可以通过NuGet来管理,依赖关系清晰,不会出现依赖冲突的问题。 第四是支持自动化流程,NuGet可以和各种自动化工具(比如GitHub Actions、Azure DevOps、Jenkins)无缝集成,轻松实现从代码提交到部署的全自动化。

5.2 缺点

第一是有一定的学习成本,对于刚接触.NET的开发者来说,NuGet的打包配置、源配置、版本号规则这些内容,需要花时间学习,不像复制粘贴那么简单。 第二是内部服务器的维护成本,如果团队自己搭NuGet服务器,需要维护服务器的可用性、安全性、存储空间,增加了运维的负担。 第三是版本号的管理需要规范,虽然自动生成版本号很方便,但如果没有规范的版本号规则(比如主版本代表大的功能更新,次版本代表小的功能更新,修订版本代表bug修复),版本号会变得混乱,反而起不到管理的作用。

六、使用NuGet的注意事项

第一是要规范版本号规则,建议遵循语义化版本规范(主版本.次版本.修订版本),主版本号代表不兼容的更新,次版本号代表兼容的功能更新,修订版本号代表兼容的bug修复,这样其他项目引用的时候,就能根据版本号判断是否需要更新。 第二是要配置好NuGet源,内部项目要引用内部的NuGet源,第三方项目要引用官方的NuGet源,避免出现找不到包的情况。 第三是要注意包的安全性,内部NuGet服务器要配置权限,只有授权的用户才能推包和拉包,避免恶意包被引入项目。 第四是要定期清理旧版本的包,NuGet服务器的存储空间是有限的,旧版本的包如果不用了,要及时清理,避免占用存储空间。 第五是要测试打包和部署流程,在正式使用之前,要先测试打包、推包、拉包、部署的整个流程,确保流程是通的,避免上线的时候出问题。

七、总结

NuGet作为.NET生态的核心工具,在自动化部署和版本管理中起到了至关重要的作用。它解决了.NET项目版本混乱、依赖管理麻烦、自动化部署难的问题,提升了开发效率和部署的准确性。通过配置自动化打包、推包、拉包、部署的流程,团队可以实现从代码提交到部署的全自动化,大大减少了人工干预的环节,也减少了因为版本问题导致的故障。

当然,NuGet也不是万能的,它需要一定的学习成本和维护成本,需要团队规范版本号规则,配置好源和权限,才能发挥它的最大作用。对于.NET开发团队来说,掌握NuGet的使用,是提升开发效率和部署质量的必经之路。