Visual Studio Linked Files don't exist
Asked Answered
B

4

20

In Visual Studio, you can do Add -> Existing Item and then Add as Link from the Add drop down button.

This is great. This let's you add a file from another project, and editing the file also edits it in the original project.

I would like to use this feature to have a config file (named Shared.config) be present in all projects within one solution. And have that file always be the same.

solution
|
|- project 1
|- Shared.config [physical]
|- project 2
|- Shared.config [linked]

After publishing, the file indeed ends up in all published projects, so no problem there.

But BEFORE publishing (during development on build) the linked file doesn't really exist. Trying to see the file in the Windows Explorer proves that the file is not in the project directory. Visual Studio only makes it look as if it exists there in the solution explorer. (Though on build, linked items are probably copied to the bin directory; But I don't want to use/access files from the bin directory.)

Now this gives problems off course. Trying to print out System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath("Shared.config")) will fail before the project has been published, because of the fact that Shared.config doesn't exist in the project root directory yet.

What I would like to do, and where I need your help is:

  • I would like to ON BUILD copy all linked files from their original location to their target location.

This will make visual studio have the linked file, and a copy of the original, to exists both in the same directory with the same name.

Normally , VS won't allow you to create a linked item in a directory if that directory already contains a file with the same name.

But, I have tested by creating a linked item first; then using Windows Explorer to copy the original file to the destination directory, and see Visual Studio act ok. The solution explorer simply hides the physical file, and shows the linked item in stead. (Even if you click Show all files in the solution explorer.)

solution
|
|- project 1
|- Shared.config [physical]
|- project 2
|- Shared.config [linked]
|- Shared.config [physical, copied here during build, invisible to Solution explorer]

This is exactly what I want! When trying to edit the file, Visual Studio will open the 'linked item'. And on build, a physical file will be copied to the target directory so it exists for the code that tries to access it.

Now how do I do this? Should this be done with Build events? If so how do I say 'copy originals of all linked files to their destination directory?

Brusque answered 19/3, 2013 at 16:59 Comment(5)
Your "solution" sounds incredibly confusing and a nightmare for source control.According
@280Z28 How shared files are nightmare for a source control? They are copied on build to the output directory which is not stored in source control.Dampen
"Confusing" is subjective. However, this has no impact on source control because the OP is not trying to check in the copies of the physical file. They are an artifact of the build that just happens to reside in the project directory.Fabiolafabiolas
@280Z28 It's not at all confusing. It is IMHO exactly as it should have been implemented by VS its self. Either in the way that I propose, or by hard-linking the files at filesystem level (like sym links in linux). I find this behavior of Visual Studio disappointing and misleading. Because you see the file in Visual Studio, but it doesn't exist (yet). And as DSS stated, source controlling won't be a problem, as it will add the physical files only. The linked files only exist in the csproj.Brusque
@AlexanderManekovskiy He's not asking to copy them to the output folder. He's asking to shadow copy them to the source folder. Copying shared files to output folders would be simple, straightforward, and not at all confusing.According
F
17

You should probably use MSBuild features to implement this.

Edit the csproj file (in Visual Studio, right click the project and unload it. then right click and edit)

Scroll to the bottom and you should find this line.

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

Immediately after that line, add these lines.

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

Now every time you build, right before the build action occurs, MSBuild will copy all linked files.

Explanation

ItemGroup contains my "array" named "LinkedItem". I generate this array by adding only the "None" items that contain a link property.

Target is an MSBuild concept. You can think of it as a particular phase of the build. I named this phase "CopyLinkedFiles" but you can name it anything.

BeforeTargets is a directive that tells MSBuild to run the action before the specified phase. Here, I have chosen to run "CopyLinkedFiles" before the "Build" phase.

Inputs is an optimization parameter. It is used to speed up building by skipping the copy if not necessary. You can ignore this parameter if you don't care. MSBuild compares the Inputs to the expected Outputs timestamp to see if it needs to execute.

Copy is an MSBuild task that accepts a file to copy and outputs to the specified folder.

Reducing Redundancy

You could paste this into every .csproj file, or you could put it in a central .proj and embed that into the csproj files. Sadly, no matter what you do, you will have to edit every .csproj at least 1 time. :(

Create a file in the Common project call WhateverNameYouLike.proj Put these contents into the file.

<?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>

Now the annoying part: In every .csproj, you will have to add a line like <Import Project="..\Common\WhateverNameYouLike.proj" /> probably at the end just before the </Project> closing tag.

Fabiolafabiolas answered 19/3, 2013 at 17:25 Comment(8)
3 questions: (1) I don't care about timestamps or about which is newer. I'm happy to see the original of the linked item to be copied to the project directory on every Build. Can I just skip Inputs and Outputs? (2) @(None) seems to be a weird name linked items array. Is it really only used for linked items? Or can that array also hold other files/stuff? (3) The original of the linked files resides in a DLL project named 'common' that is referenced by all other projects. How do I reduce redundancy like you state in your Note?Brusque
Answers 1) yes you can skip Inputs/Outputs, it is just an optimization 2) "None" is the name because the tool ran on them is "None"... csharp files are added as "Compile" because the compiler runs on them. You can see the "None" item in your own csproj file if you look for it. 3) I will edit my answer to explain how to do this.Fabiolafabiolas
(1) ok. (2) uh, so should I fear other files than Linked files to also get copied? Sorry for still not understanding fully. (3) Cool! Thanks!Brusque
@Brusque 2) yes, be very afraid of this! I will edit in yet another approach that would be saferFabiolafabiolas
Edited version does not rely on you having linked the config anymore. As you point out, there could be linked files that you don't want to copy. If you really do want to copy all linked files, you can return to using the None group, since that is the group VS places them in.Fabiolafabiolas
Thanks! But I think you misunderstood my last comment. I DO want to copy ALL linked files to the place where the link is placed. I was only asking if NON-linked files could also be in the None group, for any reason, and get copied too. You say c# files are added as "compile". So how about non c# (non-linked) files like .css , .png , .js , etc. I don't want a scripts.js from one project to overwrite a scripts.js in another project, for example.Brusque
@Brusque I believe items may be included as "none" without being linked. e.g. Settings.settings is a "None" item. Also I would like to clarify, if you have two projects A and B, you do not have to worry about A seeing B's linked files. A only knows about the items declared within its own csproj file (and any Imported files)Fabiolafabiolas
I edited to show how to only operate on None files that are linked files.Fabiolafabiolas
P
27

I had some troubles with dss539's answer (tested in vs2010). First, duplicates appeared in solution explorer for every matched file. Second, '%link' is not a property accessor, it's string itself and it always not equals to empty string '', so every file with None build action was matched, duplicated and copied on build. Third, linked files are copying into project's root directory but I needed files to be copied where links are placed. So I made some modifications:

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

LinkedItem is no longer duplicates items in solution explorer. To make link to be copied you have to set Content build action.

Preciosity answered 2/7, 2013 at 14:59 Comment(1)
This seems to work pretty well. One thing to note, if you are using this to share MVC views your "shared" project should be an MVC project rather than an actual "Shared Project" type, of you'll lose intellisense support.Twerp
F
17

You should probably use MSBuild features to implement this.

Edit the csproj file (in Visual Studio, right click the project and unload it. then right click and edit)

Scroll to the bottom and you should find this line.

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

Immediately after that line, add these lines.

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

Now every time you build, right before the build action occurs, MSBuild will copy all linked files.

Explanation

ItemGroup contains my "array" named "LinkedItem". I generate this array by adding only the "None" items that contain a link property.

Target is an MSBuild concept. You can think of it as a particular phase of the build. I named this phase "CopyLinkedFiles" but you can name it anything.

BeforeTargets is a directive that tells MSBuild to run the action before the specified phase. Here, I have chosen to run "CopyLinkedFiles" before the "Build" phase.

Inputs is an optimization parameter. It is used to speed up building by skipping the copy if not necessary. You can ignore this parameter if you don't care. MSBuild compares the Inputs to the expected Outputs timestamp to see if it needs to execute.

Copy is an MSBuild task that accepts a file to copy and outputs to the specified folder.

Reducing Redundancy

You could paste this into every .csproj file, or you could put it in a central .proj and embed that into the csproj files. Sadly, no matter what you do, you will have to edit every .csproj at least 1 time. :(

Create a file in the Common project call WhateverNameYouLike.proj Put these contents into the file.

<?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>

Now the annoying part: In every .csproj, you will have to add a line like <Import Project="..\Common\WhateverNameYouLike.proj" /> probably at the end just before the </Project> closing tag.

Fabiolafabiolas answered 19/3, 2013 at 17:25 Comment(8)
3 questions: (1) I don't care about timestamps or about which is newer. I'm happy to see the original of the linked item to be copied to the project directory on every Build. Can I just skip Inputs and Outputs? (2) @(None) seems to be a weird name linked items array. Is it really only used for linked items? Or can that array also hold other files/stuff? (3) The original of the linked files resides in a DLL project named 'common' that is referenced by all other projects. How do I reduce redundancy like you state in your Note?Brusque
Answers 1) yes you can skip Inputs/Outputs, it is just an optimization 2) "None" is the name because the tool ran on them is "None"... csharp files are added as "Compile" because the compiler runs on them. You can see the "None" item in your own csproj file if you look for it. 3) I will edit my answer to explain how to do this.Fabiolafabiolas
(1) ok. (2) uh, so should I fear other files than Linked files to also get copied? Sorry for still not understanding fully. (3) Cool! Thanks!Brusque
@Brusque 2) yes, be very afraid of this! I will edit in yet another approach that would be saferFabiolafabiolas
Edited version does not rely on you having linked the config anymore. As you point out, there could be linked files that you don't want to copy. If you really do want to copy all linked files, you can return to using the None group, since that is the group VS places them in.Fabiolafabiolas
Thanks! But I think you misunderstood my last comment. I DO want to copy ALL linked files to the place where the link is placed. I was only asking if NON-linked files could also be in the None group, for any reason, and get copied too. You say c# files are added as "compile". So how about non c# (non-linked) files like .css , .png , .js , etc. I don't want a scripts.js from one project to overwrite a scripts.js in another project, for example.Brusque
@Brusque I believe items may be included as "none" without being linked. e.g. Settings.settings is a "None" item. Also I would like to clarify, if you have two projects A and B, you do not have to worry about A seeing B's linked files. A only knows about the items declared within its own csproj file (and any Imported files)Fabiolafabiolas
I edited to show how to only operate on None files that are linked files.Fabiolafabiolas
D
0

Right click linked config file, select properties, change Copy to Output Directory from Never to Copy always.

Dishonesty answered 19/3, 2013 at 17:12 Comment(3)
I don't think this really answers his question because the OP does not want the file in the output directory, but rather in the project directory.Fabiolafabiolas
This indeed just puts the file in the bin directory. Not what I want. Code still breaks when debugging during development, as the file does not exist where the solution explorer shows the file to exist.Brusque
Sorry. Completely misunderstood.Dishonesty
S
0

You can create pre and post build events in Visual Studio.

Select the project, right click, properties, build events.

For the prebuild -

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

For the post build

cd $(TargetDir)
del shared.config

Obviously you'll need to play around with the actual locations that are going to be returned but you get the idea.

Edit - the above answers are easier and straightforward, but this ought to work as a round about way of doing it.

Sharonsharona answered 19/3, 2013 at 17:13 Comment(5)
The other answers don't seem to be helping me. So let's look at what you do. First off, I see you explicitly naming "Shared.config". I would like to avoid this. My question was/is "how do I say 'copy originals of all linked files to their destination directory"Brusque
@Brusque You might want to take a look at my answer. I'm pretty sure it does exactly what you want. ;)Fabiolafabiolas
@Alexander Ow, and btw, you can skip the post build part, as I want the physical file to keep existant during debugging (which starts after post build is completed). In fact, I don't want that physical file to be deleted at all.Brusque
@Fabiolafabiolas your answer wasn't visible until just now... I'm looking at it now.Brusque
You seem to want to copy a file to a location without having to specify where the file goes. I don't think you can get away from saying where, but there are ways to obfuscate what file is copied. If you are going to have the actual file present in those locations, why bother with linking the file? Just use the pre-build event to copy the 'master' config file to all the projects/locations from it's own directory to hide what exactly is getting copied.Sharonsharona

© 2022 - 2024 — McMap. All rights reserved.