Visual Studio 关联的文件不存在。

20
在 Visual Studio 中,您可以执行 添加 -> 现有项,然后从 添加 下拉按钮中选择 添加为链接
这很棒。这使您可以从另一个项目添加文件,并且编辑文件也会在原始项目中进行编辑。
我想使用此功能使一个配置文件(命名为 Shared.config)存在于解决方案中的所有项目中。并且让该文件始终保持相同。
解决方案
|
|- 项目1
|- Shared.config [物理]
|- 项目2
|- Shared.config [链接]
发布后,文件确实出现在所有已发布的项目中,因此没有问题。
但是,在发布之前(在构建期间进行开发),链接的文件实际上不存在。尝试在 Windows Explorer 中查看该文件表明该文件不在项目目录中。Visual Studio只会使它在解决方案浏览器中看起来像存在于那里。 (尽管在构建过程中,可能会将链接项复制到 bin 目录; 但我不想从 bin 目录中使用 / 访问文件。)
现在这会导致一些问题。在发布项目之前尝试打印 System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath("Shared.config")) 将失败,因为 Shared.config 在项目根目录中尚不存在。
我想要做的事情,并需要您的帮助是:
  • 在构建过程中,我想将所有链接的文件从原始位置复制到目标位置。
这将使 Visual Studio 既拥有链接的文件,又拥有原始文件的副本,两者都存在于具有相同名称的同一目录中。
通常情况下,如果目录中已经包含具有相同名称的文件,则 VS 不允许您在该目录中创建链接项。

但是,我已经测试了先创建一个链接项,然后使用Windows资源管理器将原始文件复制到目标目录,看到Visual Studio可以正常工作。解决方案资源管理器仅隐藏物理文件,并显示链接项。(即使在解决方案资源管理器中单击“显示所有文件”)。

解决方案:
|
|- 项目1
|- Shared.config [物理]
|- 项目2
|- Shared.config [链接]
|- Shared.config [物理,在构建期间复制到此处,在解决方案资源管理器中不可见]

这正是我想要的!尝试编辑文件时,Visual Studio将打开“链接项”。并在构建时,将复制物理文件到目标目录,以便代码尝试访问它。

现在该如何做呢? 应该使用构建事件完成吗?如果是这样,我怎样才能说“将所有链接文件的原始文件都复制到它们的目标目录”呢?


你的“解决方案”听起来非常混乱,对源代码控制来说是一场噩梦。 - Sam Harwell
@280Z28 共享文件对源代码控制来说是一场噩梦。它们在构建时被复制到输出目录中,而该目录并未存储在源代码控制中。 - Alexander Manekovskiy
“混乱”是主观的。然而,这对源代码控制没有影响,因为 OP 并不试图检入物理文件的副本。它们只是构建的产物,恰好驻留在项目目录中。 - dss539
@280Z28 这一点根本不会令人困惑。在我看来,这正是 Visual Studio 应该采用的实现方式。可以按照我提出的方式,或者通过在文件系统级别硬链接文件(类似于 Linux 中的符号链接)来实现。我认为这种 Visual Studio 的行为令人失望和误导。因为你在 Visual Studio 中看到了这个文件,但它并不存在(尚未存在)。正如 DSS 所述,源代码管理不会成为问题,因为它只会添加物理文件。链接的文件只存在于 csproj 中。 - nl-x
@AlexanderManekovskiy 他并不是要求将它们复制到输出文件夹。他要求将它们影子复制到文件夹。将共享文件复制到输出文件夹会很简单、直接,而且一点也不令人困惑。 - Sam Harwell
4个回答

27

我在使用vs2010时遇到了一些问题,参考了dss539的答案。首先,每个匹配的文件在解决方案资源管理器中都出现了重复。其次,'%link'不是属性访问器,它是一个字符串本身,并且它始终不等于空字符串'',因此,每个具有None构建操作的文件都会被匹配、重复和复制。第三,链接的文件被复制到项目的根目录中,但我需要将文件复制到链接所在的位置。所以我做了一些修改:

<Target Name="CopyLinkedFiles" BeforeTargets="Build">
  <ItemGroup>
    <LinkedItem Include="@(Content)" Condition="%(Content.Link) != ''" />
  </ItemGroup>
  <Copy SourceFiles="@(LinkedItem)" DestinationFiles="%(LinkedItem.Link)"/> 
</Target>

LinkedItem 不再在解决方案资源管理器中复制项目。为了使链接可被复制,您需要设置 Content 生成操作。


这似乎运行得非常良好。需要注意的一点是,如果您要使用它来共享MVC视图,则“共享”项目应该是一个MVC项目,而不是实际的“共享项目”类型,否则您将失去智能感知支持。 - user2880616

17

你应该使用MSBuild功能来实现这个。

编辑csproj文件(在Visual Studio中,右键单击项目并卸载它,然后右键单击并编辑)

滚动到底部,您应该会找到这一行。

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

在那行的下面立即添加这些行。

  <ItemGroup>
    <LinkedItem Include="@(None)" Condition="'%Link' != ''" />
  </ItemGroup>
  <Target Name="CopyLinkedFiles" BeforeTargets="Build" Inputs="@(LinkedItem)" Outputs="@(LinkedItem->'%(Filename)%(Extension)')">
    <Copy SourceFiles="@(LinkedItem)" DestinationFolder="$(MSBuildProjectDirectory)" />
  </Target>

现在每次构建时,在构建操作发生之前,MSBuild都会复制所有链接的文件。

Explanation

ItemGroup包含我的名为"LinkedItem"的"数组"。我通过仅添加包含链接属性的“None”项来生成此数组。

Target是MSBuild的一个概念,可以将其视为构建的特定阶段。我将此阶段命名为“CopyLinkedFiles”,但您可以将其命名为其他任何名称。

BeforeTargets是告诉MSBuild在指定阶段之前运行操作的指令。这里,我选择在“Build”阶段之前运行“CopyLinkedFiles”。

Inputs是一个优化参数。它用于通过跳过不必要的复制来加速构建。如果您不在意,可以忽略此参数。MSBuild将Inputs与预期的Outputs时间戳进行比较,以确定是否需要执行。

Copy是一个接受要复制的文件并输出到指定文件夹的MSBuild任务。

减少冗余

您可以将此内容粘贴到每个.csproj文件中,或者将其放入一个中心.proj文件并嵌入到csproj文件中。不幸的是,无论您做什么,您都必须至少编辑每个.csproj一次。 :(

在Common项目中创建一个名为WhateverNameYouLike.proj的文件,并将这些内容放入文件中。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- paste the previously shown code here -->

  <!-- you can save yourself some hassle by linking the config file here, but if you really enjoy adding the file as a link to every project, you can skip this line -->
  <None Include="..\Common\Shared.config">
    <Link>Shared.config</Link>
  </None>
</Project>

现在是比较麻烦的一部分: 在每个.csproj文件中,你都需要添加一行代码,像这样: <Import Project="..\Common\WhateverNameYouLike.proj" /> 大概是在闭合标签</Project>之前的最后位置。


3个问题:(1)我不关心时间戳或哪个是更新的。我很高兴在每次构建时将链接项的原始副本复制到项目目录中。我可以跳过输入和输出吗?(2)@(None)似乎是一个奇怪的名称链接项数组。它真的只用于链接项吗?还是该数组也可以容纳其他文件/内容?(3)链接文件的原始文件位于名为“common”的DLL项目中,该项目由所有其他项目引用。如何像您在注释中所述那样减少冗余? - nl-x
答案 1)是的,您可以跳过输入/输出,这只是一种优化。2)“None”是名称,因为运行它们的工具是“None”…… csharp 文件被添加为“Compile”,因为编译器在其上运行。如果您查找,可以在自己的 csproj 文件中看到“None”项。3)我将编辑我的答案以解释如何做到这一点。 - dss539
(1) 好的。 (2) 那么,我是否应该担心除了链接文件外的其他文件也被复制?抱歉我还没完全理解。 (3) 很棒!谢谢! - nl-x
@nl-x 2) 是的,对此要非常警惕!我将编辑另一种更安全的方法。 - dss539
@nl-x 我认为项目中的某些项可能被标记为“None”,而不会被链接。例如,Settings.settings 就是一个“None”项。另外,我想澄清一下,如果你有两个项目 AB,你不必担心 A 会看到 B 的链接文件。A 只知道在它自己的 csproj 文件中声明的项(以及任何已导入的文件)。 - dss539
显示剩余3条评论

0
右键链接的配置文件,选择属性,将 复制到输出目录从不 更改为 始终复制

1
我认为这并不真正回答了他的问题,因为OP不想要文件在输出目录中,而是在项目目录中。 - dss539
1
这确实只是将文件放入了bin目录,但这不是我想要的。在开发过程中进行调试时,由于文件不存在于解决方案资源管理器显示文件存在的位置,代码仍然会出错。 - nl-x
抱歉,我完全误解了。 - Michael Sallmen

0

在Visual Studio中,您可以创建预构建和后构建事件。

选择项目,右键单击,属性,构建事件。

对于预构建 -

copy /y "$(LocationOfShared.Config)Shared.config\" "$(TargetDir)"

关于后构建

cd $(TargetDir)
del shared.config

显然,您需要尝试实际要返回的位置,但您已经有了想法。

编辑-上面的答案更简单直接,但这应该是一个绕路的方法来完成它。


其他答案似乎没有帮助到我。那么让我们看看你的做法。首先,我看到你明确命名了“Shared.config”。我想避免这种情况。我的问题是:“如何说‘将所有链接文件的原始副本复制到它们的目标目录’?” - nl-x
@nl-x 你可能想看一下我的答案。我非常确定它正好符合你的要求。 ;) - dss539
@Alexander Ow,顺便说一下,您可以跳过后构建部分,因为我希望在调试期间(在后构建完成后开始)物理文件继续存在。实际上,我不想删除那个物理文件。 - nl-x
@dss539,直到现在你的回答才出现……我现在正在看它。 - nl-x
您似乎想将文件复制到某个位置,而不必指定文件的目标位置。我认为您无法避免说明文件的目标位置,但有办法混淆被复制的文件。如果您要在这些位置实际存在该文件,那么为什么还要链接该文件呢?只需使用预构建事件将“主”配置文件从其自己的目录复制到所有项目/位置中,以隐藏确切的复制内容即可。 - Alexander Matusiak

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