views:

510

answers:

3

A set of software products differ only by their resource strings, binary resources, and by the strings / graphics / product keys used by their Visual Studio Setup projects. What is the best way to create, organize, and maintain them?

i.e. All the products essentially consist of the same core functionality customized by graphics, strings, and other resource data to form each product. Imagine you are creating a set of products like "Excel for Bankers", Excel for Gardeners", "Excel for CEOs", etc. Each product has the the same functionality, but differs in name, graphics, help files, included templates etc.

The environment in which these are being built is: vanilla Windows.Forms / Visual Studio 2008 / C# / .Net.

The ideal solution would be easy to maintain. e.g. If I introduce a new string / new resource projects I haven't added the resource to should fail at compile time, not run time. (And subsequent localization of the products should also be feasible).

Hopefully I've missed the blindingly-obvious and easy way of doing all this. What is it?

============ Clarification(s) ================

By "product" I mean the package of software that gets installed by the installer and sold to the end user.

Currently I have one solution, consisting of multiple projects, (including a Setup project), which builds a set of assemblies and create a single installer.

What I need to produce are multiple products/installers, all with similar functionality, which are built from the same set of assemblies but differ in the set of resources used by one of the assemblies. What's the best way of doing this?

------------ The 95% Solution -----------------

Based upon Daminen_the_unbeliever's answer, a resource file per configuration can be achieved as follows:

  1. Create a class library project ("Satellite").
  2. Delete the default .cs file and add a folder ("Default")
  3. Create a resource file in the folder "MyResources"
  4. Properties - set CustomToolNamespace to something appropriate (e.g. "XXX")
  5. Make sure the access modifier for the resources is "Public". Add the resources. Edit the source code. Refer to the resources in your code as XXX.MyResources.ResourceName)
  6. Create Configurations for each product variant ("ConfigN")
  7. For each product variant, create a folder ("VariantN")
  8. Copy and Paste the MyResources file into each VariantN folder
  9. Unload the "Satellite" project, and edit the .csproj file
  10. For each "VariantN/MyResources" <Compile> or <EmbeddedResource> tag, add a Condition="'$(Configuration)' == 'ConfigN'" attribute.
  11. Save, Reload the .csproj, and you're done...

This creates a per-configuration resource file, which can (presumably) be further localized. Compile error messages are produced for any configuration that where a a resource is missing. The resource files can be localized using the standard method (create a second resources file (MyResources.fr.resx) and edit .csproj as before).

The reason this is a 95% solution is that resources used to initialize forms (e.g. Form Titles, button texts) can't be easily handled in the same manner - the easiest approach seems to be to overwrite these with values from the satellite assembly.

A: 

Is what you are looking for multiple configurations? Take a look at: Build Configurations

JonnyBoats
I can't see a way to vary the resources used in a project according to the build configuration - or I have I missed something?
MZB
I think you have missed something. With VS 2008 click on <Build><Configuration Manager> and create a new configuration "Test" and copy either your Release of Debug configuration.Then Click on <Project> <...Properties> which will bring up a multi-tabbed dialog which will let you change resources, add post-build commands(which could be any program you like), settings, file paths etc.So suppose you wanted 4 different configurations, call them B1, B2 ..B4. They could all build to different target directories, link in from different directories etc.
JonnyBoats
Only the "Build" tab seems to allow per-configuration options - which limits me to changing the output directory and defining conditional defines. The problem is 4 different sets of resources or settings - neither of which seem to be sensitive to which configuration is chosen.
MZB
+5  A: 

You can add conditionals to elements within the MSBuild file. So for instance, if you have "Debug" resources and "Release" resources, you can place these within two separate folders (e.g. Debug and Release). Then, within your MSBuild file you might have:

  <ItemGroup>
    <Compile Include="Debug\Resource1.Designer.cs" Condition=" '$(Configuration)' == 'Debug' ">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resource1.resx</DependentUpon>
    </Compile>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Queue.cs" />
    <Compile Include="Release\Resource1.Designer.cs" Condition=" '$(Configuration)' == 'Release' ">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>Resource1.resx</DependentUpon>
    </Compile>
    <Compile Include="Stack.cs" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="XMLFile1.xml" />
  </ItemGroup>
  <ItemGroup>
    <EmbeddedResource Include="Debug\Resource1.resx" Condition=" '$(Configuration)' == 'Debug' ">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
      <CustomToolNamespace>Resources</CustomToolNamespace>
    </EmbeddedResource>
    <EmbeddedResource Include="Release\Resource1.resx" Condition=" '$(Configuration)' == 'Release' ">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
      <CustomToolNamespace>Resources</CustomToolNamespace>
    </EmbeddedResource>
  </ItemGroup>

Provided all of your access to your resources are via the Resources.Resource1 class, then you get two different sets of resources for debug and release builds. Obviously, this can be extended to further configurations.

Unfortunately, I don't think you can force the resources to use the same baseName (as provided to ResourceManager constructor), since it's based on the path within the project, and I can't find a way to override that. If you do need them to use the same name (if you're manually creating ResourceManagers, for example), then I'd suggest having a Resources1.resx (plus associated cs file) at the top level of the project, and not under source control. As a pre-build event, copy the required .resx file out from the Debug or Release directory as appropriate. (In this situation, you'd probably want to force it to not compile the .Designer.cs files within the subdirectories.

Edit

Forgot to mention (though it's seen in the above excerpt from the MSBuild file) that you have to set the Custom Tool Namespace on each .resx file to the same value (e.g. Resources), otherwise it also defaults to including the folder name.

Edit 2

In response to query about checking that each resource file contains the same resources - If you're using the Resource class (e.g. Resources.Resource1.MyFirstStringResource) to access your resources, then switching configurations will result in build errors if the required resource doesn't exist, so you'll find that quite quickly.

For the truly paranoid (i.e. if your build process takes 3 days to build all configurations, or something equally mad), at the end of the day, .resx files are just XML files - you just need something to check that each .resx file with the same filename contains the same number of <data> elements, with the same name attributes.

Damien_The_Unbeliever
I think this is what I am looking for - just got to figure out how to get there from here... (Can't find other than reference documentation on the MSBuild format, and not sure of its interaction with the IDE.) Looks like I should create 1 extra resource file for each product (in the IDE), each in a separate directories, only refer to Resources in program code, then edit the .csproj file per the above to only compile/embed the appropriate resource in each configuration. Is my understanding correct?
MZB
@Mike - Well, the above excerpt from my "play area" .csproj file only took five minutes to knock together. There's no IDE support for it per se, but it compiles fine when switching between configurations in the IDE. Once you've created two resource files with the same name (in different directories), and changed their Namespace, you'll at least get build errors until you've made the conditional edits to the csproj file.
Damien_The_Unbeliever
+1  A: 

Your approach - of using multiple, separate resource-only projects (and assemblies) - is sound, and is typical for localized apps, and in other scenarios. There are some namespace and scoping issues you need to be aware of - VS2005 always generates resource types as internal (or Friend in VB), while VS2008 allows you to vary that. You will have to do the right thing in order to be able to access multiple satellite assemblies from your main assembly -- scope the resource types publicly.

Once you have the main DLL, and the various resource DLLs, you have options for deployment. One is to deploy the distinct DLLs separately, and load the proper one at runtime. Suppose your main EXE is called app.exe; you would then also have Sat1.dll, sat2.dll, sat3.dll,... etc, for all of the satellite assemblies. Your setup project(s) would just include whatever DLLs are appropriate.

The other option is to merge the DLLs, with ILMerge. In this case you'd merge app.exe with sat1.dll, obtaining app1.exe. Likewise app.exe+sat2.exe => app2.exe.

To google around on this, use "localization" and "Satellite assemblies".

Cheeso