Sometimes we need to share JavaScript, CSS or images files amongst several web applications.
This post shows how to do using Visual Studio's linked files feature, combined
with a simple MSBuild target to ensure the shared content files can be found by the browser.
Sharing files with Visual Studio's linked files
You may have a number of web applications that share one or more content files.
For example, JavaScript libraries, or an image with the corporate logo. When
such a shared file changes, you don't want to have to remember to copy the file
to all the web applications.
A solution is to store the shared files in a central location, and then in
Visual Studio add links to those files, rather than copying them. That way,
Visual Studio always sees the latest version.
To create a link in Visual Studio:
- Right click on the directory where you want to add the link.
- Choose Add | Existing Item.
- In the file dialog, navigate to the directory with shared files and select the
one you want.
- Open the drop down on the Add button (click the little down arrow) and
choose Add As Link.
The shared file now appears in the Solution Explorer, with a "link" icon.
When you now edit a shared file, you'll find that the change is immediately
visible in all web applications. If you keep the file in source control in TFS,
TFS will automatically check out the file when you change the file from any of
the web applications.
Moreover, when you publish a web application, the shared files will be copied
along with the other files of the site. Sweet!
Using an MSBuild target to physically copy the linked files
There is one problem though. When you hit F5 to debug the site, the browser
can't find your linked shared files. This is because Visual Studio keeps a link
to the shared file - it doesn't copy it to the directory where you placed the
link in Solution Explorer.
The challenge now is to extend the build, so each time the project is built (by
hitting F5 or otherwise), all linked files are copied to the directories where
you placed the links. This way, you still automatically pick up changes to the
shared files, while also making them available to the browser.
To see how this is going to work, we'll need to have a look at how Visual
Studio stores file links:
- Add a link to a file to your project if you haven't already done so.
- Open the .csproj file with your favorite text editor (Notepad will do).
- Find the file that you linked to - search for its name.
You'll find that whilst most content files have entries like this:
<Content Include="Scripts\jsnlog_test.js" />
File links look like this:
<Content Include="..\..\JSNLog\Scripts\jsnlog.js">
<Link>Scripts\jsnlog\jsnlog.js</Link>
</Content>
In other words, their distinguishing feature is that they have a Link meta with
the location where the file would have been had it been physically copied over.
This means that we can copy the linked files at each build by adding the
following at the end of the .csproj file, just before the final </Project>
tag:
<Target Name="CopyLinkedContentFiles" BeforeTargets="Build">
<Copy SourceFiles="%(Content.Identity)"
DestinationFiles="%(Content.Link)"
SkipUnchangedFiles='true'
OverwriteReadOnlyFiles='true'
Condition="'%(Content.Link)' != ''" />
</Target>
This target uses BeforeTargets to ensure it is executed each time a
build happens, even when no files in the project need to be built.
The Copy task uses a Condition to filter out all items in the
Content item group that do not have a Link meta. This way, the
copy is only applied to links. To speed up the process, it sets
SkipUnchangedFiles to true, so if a linked file has not been changed, it
won't be copied.