views:

415

answers:

4

I have some content files that I would like to share between a number of projects in Visual Studio.

I have put these files in their own project, set the build action to "Content", and the copy to output directory to "Copy if newer". I would like all these files to be copied to the bin/debug directory of the projects that reference them.

I can get it to work by including a reference to the "contents" project in each of the projects that need the files, but that requires that a minimal assembly be generated (3K). I assume there is a way, using MSBuild, to make this all work without creating the empty assembly?

+1  A: 

A better possible solution would be to

  • place a common directory in the solution dir and place your common content files there.

  • in VS, in each project that should share this content, right-click add existing item, browse to the desired item(s), select, click the down-arrow on the add button and select add as link. In the project, you will notice the files are added with a 'shortcut' overlay.

  • In the project, select the newly added links and right-click->properties and select Build Action: content, Copy To Output Directory: Copy Always.

This is a simple solution to the problem given.

I use this technique for things like SQL scripts and partial config files (using configSource) with great success. This allows me to make changes to these files in a single location with the assurance that they will be propigated throughout the solution.

A more robust solution would be to create a project with embedded resources. This requires a bit more work to manage the content on the receiving end but may be worth it in the long run as having a bunch of loose artifacts flying about can become problematic.

Hope that helps.

Sky Sanders
Thats pretty cool, didnt know that...
Mark Redman
That is cool... the follow on question is that I have some files that I am compiling using an internal tool (I have a custom MSBuild task). I would like to share that output with a number of projects as well. Do you have a suggestion for this as well?
Cameron Peters
@Cameron - what kind of output are we talking about? i.e. a single assembly? Multiple artifacts? Both?
Sky Sanders
I'm parsing and compressing some files that my assembly uses. I can get the files into the (bin)/$(Configuration) directory, but these files don't get copied on to other projects (within the solution) that require them. I've tried adding the files to the Content property but still doesn't work, even though all other files defined with the standard <Content Include="..." /> work just fine. There must be some other MSBuild voodoo that I don't yet understand...
Cameron Peters
@Cameron, well, it may be a hack but if you point the output directory of the content project to your 'common' directory and ensure that the content project is built before the other projects (using build order/dependencies) and then follow the steps listed above you should get the results you are looking for.
Sky Sanders
A: 

We do something similar where we have "...ReleaseBuilds" that reference dlls and content we require for specific projects. Compiling copies everything to the bin debug folder and indeed creates the empty assembly.

Within Visual Studio we have a post-build event in the "...RealeaseBuild" (in project properties) that copies/deletes or run batch files to make sure we have all the files (configs, services etc etc) required and to delete the empty assembly.

HTH

Mark Redman
A: 

A similar solution like the one Sky suggested can be found in my answer to "Is there a way to automatically include content files into asp.net project file?".

It allows to share your content but you must not touch the folder or its content inside VS because this breaks the recursive path.

This approach works best for auto-generated content - you don't have to bother about including new content files to your solution.

And of course you can reuse this in multiple solutions/projects.

Filburt
+1  A: 

Thanks to everone who took the time to make a suggestion about how to solve this problem.

It turns out that if I want my compiled content files to be treated like content files (in that they get copied to the output directory of any other project that references my project), I need to create a target which runs before GetCopyToOutputDirectoryItems, and add the full path of the compiled content files to the AllItemsFullPathWithTargetPath ItemGroup. MSBuild calls GetCopyToOutputDirectoryItems for projects on which the current project depends, and uses the resulting file list to determine the files that are copied along with the assembly.dll. Here is the XML from my .csproj, just in case someone else has a similar problem.

I have a custom task called "ZipDictionary", and I accumulate all the files that I am going to compile in an ItemGroup called DictionaryCompile. My target, "FixGetCopyToOutputDirectoryItems" is executed before "GetCopyToOutputDirectoryItems". I don't do the actual compilation there, since this target can be called multiple times by referencing projects, and it would hurt performance. The target does some transforms to get the post-compilation file names, and then returns the full paths to all the files, since relative paths will not work when copy is called from the referencing project.

<ItemGroup>
  <DictionaryCompile Include="Dictionaries\it-IT.dic">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </DictionaryCompile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="ZipDictionary" AssemblyFile="..\LogicTree.DictionaryCompiler\bin\Debug\LogicTree.DictionaryCompiler.dll"/>
<Target Name="BeforeCompile">
  <Message Text="Files @(DictionaryCompile)" Importance="high" />
  <ZipDictionary DictionaryFiles="@(DictionaryCompile)" OutputDirectory="$(OutputPath)">
    <Output TaskParameter="OutputFiles" ItemName="DictionaryOutputFiles" />
  </ZipDictionary>
</Target>
<Target Name="FixGetCopyToOutputDirectoryItems" BeforeTargets="GetCopyToOutputDirectoryItems">
  <ItemGroup>
    <_DictionaryCompile Include="@(DictionaryCompile->'$(OutputPath)Dictionaries\%(FileName).ltdic')" />
  </ItemGroup>
  <AssignTargetPath Files="@(_DictionaryCompile)" RootFolder="$(MSBuildProjectDirectory)\$(OutputPath)">
    <Output TaskParameter="AssignedFiles" ItemName="_DictionaryCompileWithTargetPath" />
  </AssignTargetPath>
  <ItemGroup>
    <AllItemsFullPathWithTargetPath Include="@(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
    <_SourceItemsToCopyToOutputDirectoryAlways Include="@(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always'" />
    <_SourceItemsToCopyToOutputDirectory Include="@(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
  </ItemGroup>
</Target>
Cameron Peters
You should accept your answer - it gives a detailed solution.
Filburt