news 2026/9/6 19:13:42

ASP.NET Core 构建系统中的 `<Reference>` 引用解析机制:集中化依赖管理与 darc 自动化详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASP.NET Core 构建系统中的 `<Reference>` 引用解析机制:集中化依赖管理与 darc 自动化详解

ASP.NET Core 构建系统中的<Reference>引用解析机制:集中化依赖管理与 darc 自动化详解

【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

在 dotnet/aspnetcore 仓库中,绝大多数项目文件使用<Reference>而非<PackageReference><ProjectReference>来声明依赖。这不是随意的风格选择,而是一套完整的自定义引用解析系统:构建系统会根据 servicing(维护版)与版本更新规则,把<Reference>自动解析为正确的引用类型与版本。本文基于 docs/ReferenceResolution.md 与 eng/targets/ResolveReferences.targets 的实现源码,完整讲解这套机制的设计动机、解析流程、关键配置文件,以及添加新依赖、新项目和 darc 依赖自动化的可复制操作手册。读完本文,你可以独立向该仓库添加新项目和新包依赖,并理解构建系统如何强制保证依赖版本的一致性。

为什么用<Reference>:设计动机

普通 .NET 项目直接用<PackageReference Include="X" Version="1.2.3" />即可,但 ASP.NET Core 作为一个同时产出共享框架(shared framework)和多个扩展包的大型仓库,有三个难以用原生 NuGet 语义满足的需求(原文档逐条列出):

  1. 外部依赖版本一致且易查:所有外部依赖的版本必须在仓库中集中、可发现地管理,而不是散落在几百个项目文件里;
  2. 新版本的包不能引用比上一个发布版本更低的依赖版本:即依赖版本只允许单调递增,避免升级框架包反而"降级"某个传递依赖;
  3. 维护版(servicing release)不得增删现有包中的依赖:servicing 构建只能修补已有包的内容,不能改变其依赖图。

文档还提到一个次要好处:这套机制让项目文件更简洁(less verbose)——你只写一个引用名,不用关心它最终解析成项目引用还是 NuGet 包、该用哪个版本。

实现这一切的核心是 eng/targets/ResolveReferences.targets。它的文件头注释明确说明"more details, see /docs/ReferenceResolution.md",即本文档是该实现的官方说明文档。

解析实现:ResolveReferences.targets源码剖析

入口与开关

构建系统在属性阶段决定是否为当前项目启用自定义解析。从源码结构看(eng/targets/ResolveReferences.targets):

<PropertyGroup> <EnableCustomReferenceResolution Condition="'$(EnableCustomReferenceResolution)' == '' AND ('$(DotNetBuildSourceOnly)' != 'true' OR '$(ExcludeFromSourceOnlyBuild)' != 'true')">true</EnableCustomReferenceResolution> <ResolveReferencesDependsOn> ResolveCustomReferences; $(ResolveReferencesDependsOn); </ResolveReferencesDependsOn> </PropertyGroup>

要点:

  • EnableCustomReferenceResolution默认为true(source-build 场景下可被排除),也可由项目显式覆盖;
  • 关键目标ResolveCustomReferences被挂到ResolveReferencesDependsOn前面,意味着每次 NuGet restore 和构建都会先执行自定义解析,把<Reference>转换成真正的<PackageReference>/<ProjectReference>后再交给标准 SDK 流程。

项目可通过三个属性影响解析行为(文件头注释):

属性含义
UseLatestPackageReferences是否把<Reference>解析为 eng/Dependencies.props 中LatestPackageReference的最新版本
UseProjectReferences是否优先使用项目引用而非包
IsProjectReferenceProvider本项目产出的程序集是否可以作为"项目引用提供者"被其他项目用<Reference>间接引用

"何时用最新版包"的四条规则

UseLatestPackageReferences的自动推断逻辑(ResolveReferences.targets)精确编码了文档中"servicing 不增删依赖"的要求:

<UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsServicingBuild)' != 'true' ">true</UseLatestPackageReferences> <UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsPackableInNonServicingBuild)' != 'true' ">true</UseLatestPackageReferences> <UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsPackageInThisPatch)' == 'true' ">true</UseLatestPackageReferences> <UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' ">false</UseLatestPackageReferences>

即:满足以下任一条件就使用最新依赖版本——

  • 当前不是servicing 构建(例如准备新的 major/minor 发布);
  • 项目不是可打包的正常发布项目(如测试项目、示例项目);
  • 该包正在本次补丁中发布新版本(外部依赖在补丁中尽量跟随更新)。

反之,若这是 servicing 构建、且项目打包、且包不在本补丁内,则解析结果落在false——使用固定版本,保证维护版不改变依赖图。而UseProjectReferences默认几乎总是true

解析顺序:先项目引用,再包

第一阶段:把<Reference>转成<ProjectReference>(ResolveReferences.targets):

<ItemGroup Condition=" '$(EnableCustomReferenceResolution)' == 'true' AND '$(UseProjectReferences)' == 'true' "> <ProjectReferenceProvider Update="@(ProjectReference->'%(Filename)')" DirectUse="1" /> <!-- Find Reference items satisfied using project reference providers. --> <Reference Update="@(ProjectReferenceProvider)" ProjectPath="%(ProjectReferenceProvider.ProjectPath)" /> <ProjectReference Include="@(Reference->Distinct()->'%(ProjectPath)')" /> <Reference Remove="@(Reference->HasMetadata('ProjectPath'))" /> </ItemGroup>

逻辑是:拿所有<Reference>项,与ProjectReferenceProvider项(由 eng/ProjectReferences.props 提供,映射"程序集名 → 产出该程序集的项目文件路径")做匹配;匹配上的<Reference>被加上ProjectPath元数据,随后物化为真实的<ProjectReference>并从<Reference>中移除。注释特别强调"Order matters; this comes before package resolution because projects should be used when possible instead of packages"——仓库内源码工程优先于外部 NuGet 包

同时有一条边界规则:对"应当用<Reference>引用却直接用<ProjectReference>"的提供者项目,会被打上DirectUse=1标记并在稍后报错(见下文)。这正是文档中"只在测试项目里用<ProjectReference>"这一条建议的强制执行手段。

第二阶段:把剩余<Reference>转成<PackageReference>。核心目标是ResolveCustomReferences(ResolveReferences.targets),它在CheckForImplicitPackageReferenceOverrides;CollectPackageReferences;ResolvePackageAssets之前运行,即跑在 NuGet restore 收集包引用之前。其关键步骤:

  1. .Sources共享源码包特殊处理:凡引用名以.Sources结尾的<Reference>会被打上IsSharedSource=true,只消费ContentFiles;Build资产并设置PrivateAssets=All,保证共享源码只参与编译、不进入产物依赖图。
  2. 版本关联:当UseLatestPackageReferences=true时,用 MSBuild 的JoinItems任务把@(Reference)@(LatestPackageReference)(来自 eng/Dependencies.props)做内连接,得到带版本的包引用,并标记IsImplicitlyDefined="true"(隐式引入,区别于显式声明):
<JoinItems Left="@(Reference)" Right="@(LatestPackageReference)" LeftMetadata="*" RightMetadata="Version" Condition=" '$(UseLatestPackageReferences)' == 'true' "> <Output TaskParameter="JoinResult" ItemName="_LatestPackageReferenceWithVersion" /> </JoinItems> <ItemGroup> <PackageReference Include="@(_LatestPackageReferenceWithVersion)" IsImplicitlyDefined="true" /> <Reference Remove="@(_LatestPackageReferenceWithVersion)" /> </ItemGroup>
  1. 禁止显式<PackageReference>:除 SDK 隐式定义和带AllowExplicitReference=true元数据的项之外,任何项目自己写的<PackageReference>都会触发硬错误:
<Error Condition="'$(DisablePackageReferenceRestrictions)' != 'true' AND '@(_ExplicitPackageReference->Count())' != '0'" Text="PackageReference items are not allowed. Use &lt;Reference&gt; instead to replace the reference to @(_ExplicitPackageReference, ', '). See docs/ReferenceResolution.md for more details." />

注意错误信息直接指向本文档——构建系统把docs/ReferenceResolution.md当作面向贡献者的权威指引。

  1. 未解析引用报错:如果一个<Reference>既找不到项目提供者、也找不到包版本,且文件实体不存在,则报MSB3245风格的错误:"Did you update dependencies lists? See docs/ReferenceResolution.md for more details."——这提示你大概率忘了更新 eng/Dependencies.props 或 eng/ProjectReferences.props。
  2. 元数据误用警告BUILD004警告把%(Private)用在包引用上(应改用%(PrivateAssets));BUILD006警告把%(PrivateAssets)用在程序集引用上(应改用%(Private))。两者语义不同、不可互换,这是新人常见错误。

共享框架边界检查

_CheckForReferenceBoundaries目标(ResolveReferences.targets)还负责两条硬性约束:

  • 共享框架内的项目(IsAspNetCoreApp=true)若引用了不在共享框架内的程序集,直接报错并指向docs/SharedFramework.md
  • 框架外的项目不能逐个引用框架内程序集,必须以整个Microsoft.AspNetCore.App框架引用为单位;
  • 任何对"项目引用提供者"直接使用<ProjectReference>的行为都会被拒绝,错误信息是:"use a Reference item."
<Error Condition=" '$(EnableCustomReferenceResolution)' == 'true' AND '@(ProjectReferenceProvider->WithMetadataValue('DirectUse', '1')->Count())' != '0' " Text="Cannot reference &quot;%(Identity)&quot; with a ProjectReference item; use a Reference item." />

关键文件清单

文档列出的五个关键文件及其在机制中的角色(均可在仓库中直接查看):

文件作用
eng/Dependencies.props仓库中所有可能使用的外部包引用清单,以<LatestPackageReference Include="..." />表达,是引用解析的输入,可被转换成项目中的<PackageReference>
eng/Versions.props版本属性清单,部分可被自动化(darc/Maestro)更新;MSBuild 用它做 restore 与构建
eng/Version.Details.xml供自动化更新 eng/Versions.props 中的依赖变量,以及 SDK 和msbuild工具集的 global.json
eng/ProjectReferences.props自动生成的"程序集名 → 本地项目"映射,列出哪些程序集/包可以作为本地项目被引用
eng/tools/DependabotDiscovery/DependabotDiscovery.csproj把非 Maestro 管理的包以普通<PackageReference>重新声明一遍,让 Dependabot 能发现并更新它们;永不被构建

Dependencies.props的版本命名约定

eng/Dependencies.props 中每个<LatestPackageReference>只写包名,版本通过一段"命名约定 + MSBuild 动态属性"自动关联(eng/Dependencies.props):

<ItemGroup Label="Dependencies with versions."> <!-- Get name prefixes for version properties. --> <LatestPackageReference Update="@(LatestPackageReference)"> <VersionName>$([System.String]::new('%(Identity)').Replace('.',''))</VersionName> </LatestPackageReference> <!-- Get versions. --> <LatestPackageReference Update="@(LatestPackageReference)"> <Version>$(%(VersionName)Version)</Version> <VersionName /> </LatestPackageReference> </ItemGroup>

规则:包名去掉所有点后拼接Version后缀,即为 eng/Versions.props 中的 MSBuild 属性名。例如包System.Banana对应属性$(SystemBananaVersion),包Microsoft.Extensions.AI对应$(MicrosoftExtensionsAIVersion)(可在 eng/Versions.props 中验证,如<StackExchangeRedisVersion>2.7.27</StackExchangeRedisVersion><MessagePackVersion>2.5.302</MessagePackVersion>)。该文件还包含几个值得注意的分层:

  • Label=".NET team dependencies":dotnet 团队产出的包,多数由 Maestro 自动化管理;
  • Label="External dependencies":第三方包(AngleSharp、MessagePack、StackExchange.Redis 等),由 Dependabot 管理;
  • Version="$(XunitV3Version)"等显式Version元数据的例外项,用于覆盖命名约定;
  • 特殊 case:所有Microsoft.NETCore.App.Runtime.*/Microsoft.NETCore.App.Crossgen2.*的 RID 变体包统一映射到单一属性$(MicrosoftNETCoreAppRefVersion),方便新增 RID。

ProjectReferences.props:自动生成的项目映射

eng/ProjectReferences.props 文件头注明"自动生成的,运行./eng/scripts/GenerateProjectList.ps1更新"。它是一个巨大的ProjectReferenceProvider项列表,例如:

<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Core" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />

这正是文档建议".csproj文件名必须与程序集名一致"的原因:ProjectReferenceProviderInclude就是程序集名,ProjectPath必须能被确定性地推导出来,<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Core" />才能匹配上。

DependabotDiscovery.csproj:给 Dependabot 的"影子项目"

由于本仓库几乎不写<PackageReference>,而 Dependabot 的 NuGet 更新器只能识别字面量<PackageReference>/<PackageVersion>,于是仓库用了一个巧妙的设计:DependabotDiscovery.csproj 把非 Maestro 管理的包原样重新声明为普通<PackageReference>(引用同一套$(...Version)属性)。Dependabot 在这个"影子项目"里 bump 版本属性后,同一属性经由 eng/Versions.props 流入所有真实项目。DependabotDiscovery/README.md 进一步说明了准入规则:必须同时满足"未被 Maestro/IdentityModel 管理"、"不是ProjectReferenceProvider名"、"在eng/Versions.props有真实的版本属性"、"可被 Dependabot 报告为顶层可更新依赖";项目同时 targetnet472$(DefaultNetCoreTargetFramework)以覆盖只支持单一 TFM 的包(如Microsoft.Owin.*只有 net45 资产),并通过NoWarn抑制 NU1605 降级冲突——因为它永远不会真正构建。

编写.csproj的推荐清单

文档给出的规则(配合上文源码可看到每条都有强制执行机制):

  • <Reference>——解析器唯一支持的自定义依赖声明方式;
  • 不要用<PackageReference>——ResolveCustomReferences会直接报错;
  • 需要新包时,先加到 eng/Dependencies.props 和 eng/Versions.props;
  • 若包来自 partner 团队且需自动更新版本,还要在 eng/Version.Details.xml 添加条目;
  • 否则(无 Maestro 自动化),把包加进 DependabotDiscovery.csproj 让 Dependabot 能发现并更新它(详见其 README);
  • 只在测试项目中用<ProjectReference>——边界检查目标会拒绝其他用法;
  • .csproj文件名与程序集名保持一致;
  • 新增项目后运行eng/scripts/GenerateProjectList.ps1(或build.cmd /t:GenerateProjectList)重新生成项目映射。

GenerateProjectList.ps1的实现(eng/scripts/GenerateProjectList.ps1)只是薄封装:它调用eng/common/msbuild.ps1对 eng/CodeGen.proj 执行GenerateProjectList目标;该目标对每个项目调用GetReferencesProvided(在 ResolveReferences.targets 中定义,递归收集每个 TFM 下项目"提供"的程序集),最终写出ProjectReferences.props等生成文件。

示例一:向仓库添加新项目

文档给出的三步流程:

  1. 创建.csproj(文件名与程序集名一致,用<Reference>声明依赖);
  2. 运行eng/scripts/GenerateProjectList.ps1重新生成 eng/ProjectReferences.props;
  3. 把新项目加入 AspNetCore.slnx 及相关的*.slnf文件。

若项目希望被其他项目用<Reference>间接引用,还需在项目中设置IsProjectReferenceProvider=true——_GetReferencesProvided目标据此把程序集名写入ProvidesReference项,成为项目列表生成器的输入。

示例二:添加新的包依赖

文档以添加System.Banana为例给出完整步骤,这里保留原步骤并结合源码补齐细节。

第 1 步:在 .csproj 中写

<Reference Include="System.Banana" />

第 2 步:在 eng/Dependencies.props 添加

<LatestPackageReference Include="System.Banana" />

第 3 步(二选一):根据包的来源选择自动化路径

路径 A:来自其他 dotnet 团队,由 Maestro 机器人自动更新

  1. 在 eng/Versions.props 添加版本属性(按命名约定,System.Banana对应SystemBananaVersion):

    <SystemBananaVersion>0.0.1-beta-1</SystemBananaVersion>
  2. 在 eng/Version.Details.xml 的<ProductDependencies>中添加依赖条目:

    <ProductDependencies> <!-- ... --> <Dependency Name="System.Banana" Version="0.0.1-beta-1"> <Uri>https://github.com/dotnet/corefx</Uri> <Sha>000000</Sha> </Dependency> <!-- ... --> </ProductDependencies>

    若不知道 "0.0.1-beta-1" 对应的源码提交哈希,可以用000000占位,机器人下次运行时会自动修正。当前仓库中 eng/Version.Details.xml 的实际条目都带有真实的UriSha(甚至BarId/SourceBuildTarball元数据),可作为格式参照。

  3. 若依赖来自 dotnet/runtime 且你在更新 dotnet/aspnetcore-tooling,需要给<Dependency>元素加CoherentParentDependency属性:

    <Dependency Name="System.Banana" Version="0.0.1-beta-1" CoherentParentDependency="Microsoft.CodeAnalysis.Razor"> <!-- ... --> </Dependency>

    其含义:System.Banana应采用的 dotnet/runtime 依赖版本,基于产出所选Microsoft.CodeAnalysis.Razor的那个 dotnet/aspnetcore 构建来确定——即 dotnet/runtime 与 dotnet/aspnetcore 的依赖必须"相干"(coherent),避免两个仓库的包混用不一致的 runtime 版本。按文档说明,在 dotnet/aspnetcore-tooling 中该属性值应为"Microsoft.CodeAnalysis.Razor"

路径 B:无 Maestro 自动化,交给 Dependabot

在 DependabotDiscovery.csproj 中添加:

<PackageReference Include="System.Banana" Version="$(SystemBananaVersion)" />

注意 CI 会强制同步:eng/scripts/CodeCheck.ps1 会比对eng/Dependencies.props变更是否伴随DependabotDiscovery.csproj的对应更新,缺失则构建失败(脚本中明确检查 "eng/Dependencies.props changed but ... was not updated")。

darc 速查手册:依赖自动化操作

darc是 dotnet 生态仓库间依赖管理的命令行工具。文档给出了完整速查表;安装方式是运行eng/common目录下的darc-init脚本(仓库中存在 eng/common/darc-init.ps1 与 eng/common/darc-init.sh),安装后需按官方 Darc 文档配置相应的访问令牌。文档同时提示:以下大部分功能现在也可通过 Maestro Web UI 完成,推荐优先尝试 UI。

查看仓库的订阅列表

订阅(subscription)定义了监听哪些生态仓库的更新、更新频率等元数据:

darc get-subscriptions --target-branch main --target-repo aspnetcore$ --regex

启用 / 禁用订阅

darc subscription-status --id {subscriptionIdHere} --enable darc subscription-status --id {subscriptionIdHere} --disable

触发订阅

触发订阅会搜索其依赖的更新,并通过 dotnet-maestro 机器人在目标仓库开一个 PR 带入这些变更:

darc trigger-subscriptions --id {subscriptionIdHere}

手动更新依赖

若 dotnet-maestro 机器人未正确更新依赖,可用darc update-dependencies手动完成。注意:需在独立分支执行并提 PR。这些工作本来由机器人在订阅触发时自动完成(例如订阅频率为EveryBuild时,依赖方构建完成后约 15 分钟):

darc update-dependencies --channel '.NET Core 3.1 Release' darc update-dependencies --channel '.NET 5 Dev' --source-repo efcore

文档建议:优先用trigger-subscriptions创建依赖更新,而不是在自己的 PR 里手动更新依赖。

切换订阅的批量(batchable)行为

订阅可以批处理:检测到依赖更新时,darc会把该更新的提交与已有的依赖 PR 合并捆绑。切换批量行为需使用update-subscription命令:

darc update-subscription --id {subscriptionIdHere}

系统默认编辑器会打开,允许编辑订阅元数据。

  • 禁用批量:将Batchable设为False,并把Merge Policies部分设为:

    - Name: Standard Properties: {}
  • 启用批量:将Batchable设为True,并移除订阅上已设置的Merge Policies

注意:Merge policies 只能设置在非批量订阅上,切换 batchability 时必须正确设置/取消Merge Policies字段。

小结:机制全貌

把文档与源码串起来看,整套系统的闭环是:

  1. 项目只写<Reference Include="X" />
  2. ResolveReferences.targets 在 restore 前依次尝试:匹配 eng/ProjectReferences.props → 转<ProjectReference>;再匹配 eng/Dependencies.props 的LatestPackageReference→ 转<PackageReference>(版本取自 eng/Versions.props 的命名约定属性);
  3. 解析不到项目也不报错的项目,最终报"Did you update dependencies lists?"错误并指向本文档;
  4. 版本由两条自动化管道维护:dotnet 团队间用 Maestro/darc(eng/Version.Details.xml +darc命令),第三方包用 Dependabot(DependabotDiscovery.csproj 影子项目);
  5. CI 层由 CodeCheck.ps1 强制Dependencies.props与影子项目同步,由 MSBuild 错误强制"禁止显式 PackageReference""禁止绕过 Reference 引用提供者"等规则。

这套设计的净效果是:贡献者只需理解"引用名"这一个概念,而版本一致性、servicing 边界、依赖图稳定性全部由构建系统集中强制执行——这正是文档开篇所说"without requiring most ASP.NET Core contributors to understand the complex rules for how versions and references should work"的实现方式。

【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 19:10:18

超越RAG:Agentic RAG架构设计与落地实践指南

简介&#xff1a;《超越RAG&#xff1a;迈向智能体时代的Agentic RAG》PPT课件&#xff0c;围绕大模型检索增强生成的前沿演进展开&#xff0c;适合AI研究者、算法工程师及对RAG技术感兴趣的学习者阅读。内容从传统RAG的检索-生成流程讲起&#xff0c;逐步过渡到Reasoning RAG与…

作者头像 李华
网站建设 2026/9/6 19:09:02

IPD+OKR+PLM:构建企业产品研发管理体系的核心逻辑

简介&#xff1a;面向企业研发管理者、项目经理及咨询顾问的《企业产品研发管理体系构建指南》PPT课件&#xff0c;系统讲解IPD集成产品开发与CMMI、OKR、PLM的融合落地&#xff0c;覆盖产品规划、战略、立项、目标设定、进度控制、版本管理、团队领导力等完整闭环&#xff0c;…

作者头像 李华
网站建设 2026/9/6 19:05:33

Hadoop集群搭建与MapReduce开发实战:从规划到调优的完整指南

简介&#xff1a;面向大数据初学者和需要快速搭建Hadoop平台的技术人员&#xff0c;这是一份Hadoop集群搭建与MapReduce程序开发的完整操作文档。文档基于VM虚拟机中Ubuntu Kylin 16.04.4环境&#xff0c;涵盖SSH无密码登录、Java环境配置、Hadoop集群网络与分布式配置&#xf…

作者头像 李华