如何在运行时读取给定项目中的 MSBuild 属性?

8
我想在单元测试中访问MSBuild变量,这是一个.NET 4.5类库项目(经典csproj),但我没有找到任何文章讨论如何将值从MSBuild传递到执行上下文中。
我考虑在编译期间设置环境变量,然后在执行期间读取该环境变量,但似乎需要自定义任务来设置环境变量的值,而且我有点担心变量的范围(理想情况下,我只希望它在当前执行的项目中可用,而不是全局)。
是否有已知的解决方案可以在运行时从DLL项目中读取MSBuild属性? MSBuild属性能否在执行期间以“参数”的形式“传递”?

ConditionalAttribute有什么问题? - C.J.
不确定 ConditionalAttribute 如何帮助我的情况 @CJohnson。它只允许您检查一个常量键(如 DEBUGRELEASE),而我想要来自 MSBuild 的实际动态值。 - julealgon
虽然我的问题被标记为重复,但链接的答案仅提供了 .Net Core 的答案,而不是 .Net Framework 的答案。我通过在我的 csproj 文件中添加一些额外的逻辑,成功地使其在 .Net Framework 上工作。具体方法请参见此链接:https://dev59.com/rFLTa4cB1Zd3GeqPYj2e#4306142。 - julealgon
@julealgon请在问题中添加这些细节,标签很容易被忽略。您想让(我/我们)移除重复标记并且也许添加您自己的答案吗? - Martin Ullrich
@MartinUllrich,我现在已经添加了.Net4.5标签。我认为你需要更新你在另一个线程中的答案,以包括解决这个问题所需的.Net部分,或者你可以取消将其标记为重复项。我很乐意自己添加答案。 - julealgon
3个回答

10

我最终通过使用与.Net Core项目中默认使用的相同的代码生成任务使其工作起来了。唯一的区别是我必须手动在csproj文件中添加Target才能使其工作,因为代码创建对于框架项目来说不是标准的:

<Target Name="BeforeBuild">
  <ItemGroup>
    <AssemblyAttributes Include="MyProject.SolutionFileAttribute">
      <_Parameter1>$(SolutionPath)</_Parameter1>
    </AssemblyAttributes>
  </ItemGroup>
  <WriteCodeFragment AssemblyAttributes="@(AssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="SolutionInfo.cs">
    <Output TaskParameter="OutputFile" ItemName="Compile" />
    <Output TaskParameter="OutputFile" ItemName="FileWrites" />
  </WriteCodeFragment>
</Target>

CompileFileWrites这两行是为了使代码能够与clean等工具协同使用(请参见上面我在评论中提供的链接)。其他内容应该都很容易理解。

当项目编译时,将会向程序集添加一个自定义属性,我们可以使用常规反射来获取它:

Assembly
    .GetExecutingAssembly()
    .GetCustomAttribute<SolutionFileAttribute>()
    .SolutionFile

这很有效,使我避免了对解决方案文件的任何硬编码搜索。

1

另一种版本,从多个互联网来源编译,获取构建时的环境变量,然后在代码中使用其值

文件 AssemblyAttribute.cs

namespace MyApp
{
    [AttributeUsage(AttributeTargets.Assembly)]
    public class MyCustomAttribute : Attribute
    {
        public string Value { get; set; }
        public MyCustomAttribute(string value)
        {
            Value = value;
        }
    }
}

文件 MainForm.cs

var myvalue = Assembly.GetExecutingAssembly().GetCustomAttribute<MyCustomAttribute>().Value;

在文件MyApp.csproj的末尾(获取%USERNAME%环境变量进行构建,生成SolutionInfo.cs文件,并自动将其包含到构建中)

  <Target Name="BeforeBuild">
    <ItemGroup>
      <AssemblyAttributes Include="MyApp.MyCustomAttribute">
        <_Parameter1>$(USERNAME)</_Parameter1>
      </AssemblyAttributes>
    </ItemGroup>
    <WriteCodeFragment AssemblyAttributes="@(AssemblyAttributes)" Language="C#" OutputFile="SolutionInfo.cs">
      <Output TaskParameter="OutputFile" ItemName="Compile" />
      <Output TaskParameter="OutputFile" ItemName="FileWrites" />
    </WriteCodeFragment>
  </Target>

1
我认为您有几个选择:
  • 使用环境变量,就像您已经建议的那样。可能需要一个自定义任务来完成这个过程,但是这很容易完成,您不需要任何额外的程序集。然而,全局可见性可能会成为一个问题;例如,在CI机器上进行并行构建时,请考虑这一点。
  • 在构建期间编写代码片段,并将其包含到生成的程序集中(类似于您在评论中提到的链接下找到的内容)。
  • 在构建期间编写一个文件(甚至是app.config),其中包含反映您需要拥有的MSBuild属性的设置;在测试运行期间读取这些设置。

顺便说一下,尝试在运行时(使用Microsoft.Build框架)重新读取MSBuild项目文件是没有意义的。首先,这本身就是很多工作,而且收益微不足道。更重要的是,根据属性的复杂性和依赖性,您很可能需要确保使用与实际构建期间存在的相同属性调用MSBuild库。可以说,这可能会让您回到起点。

最后两个选项最适合,因为它们具有相同的特点:它们仅限于您当前拥有的构建/测试运行(即,您可以同时运行多个构建而不会干扰)。

我可能会选择第三个,因为那似乎是最容易实现的。

实际上,在我正在开发的一个较大项目中,我已经这样做了。基本上,我们有不同的环境(数据库连接字符串等),并将它们作为后置构建步骤选择,方法是将特定的myenv.config复制到default.config。测试只会查找名为default.config的文件,并获取其中设置的任何设置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接