Explained: Managing Source Control Dependencies in Visual Studio Team System

- J.D. Meier, Jason Taylor, Alex Mackman, Prashant Bansode

Applies To

  • Visual Studio 2005 Team System (VSTS)
  • Team Foundation Server (TFS)

Summary

This article explains how you should handle project and file references both within and across solutions. Dependencies inevitably change over time and as a result they will impact the build process (and the build order) of your application. A good dependency management approach improves the integration process while making builds as seamless as possible.

Contents

  • Overview
  • Referencing Projects
  • Referencing Third Party Assemblies
  • Guidelines for Referencing Projects and Assemblies
  • Referencing External Assemblies
  • Referencing Web Services
  • Referencing Databases
  • Referencing COM Components
  • Additional Resources

Overview

A consistent approach to managing dependencies in a team environment is necessary in order to reduce build instability and ongoing source control maintenance costs. This article explains how to manage the assemblies, Web services, databases and COM components that your application is dependent upon.

Referencing Projects

If you have a Visual Studio project, for example containing shared library code that is used by multiple team projects, you can either manage the project within the owning team’s project or you can create a separate team project specifically for the shared project.

If you choose the latter approach and use a common shared project, the folder structure in Team Foundation Server source control looks like this:

ManagingDependencies1.GIF

To consume the common shared project from your own team project you have two options:
  • Workspace Mapping
  • Branching

Workspace Mapping

In this case you map the shared source from the common project into the workspace on your development computer. This creates a configuration that unifies the source from the shared location and your project on the client-side.

The advantage of this approach is that shared project changes are picked up every time you retrieve the latest source into your workspace.

For example if you have two team projects $MyTeamProject and $Common, and Common is the shared project source, for referencing the code from the shared location, these projects share a common path on the client’s hard drive. The client side workspace folder structure should resemble the following:
ManagingDependencies2.GIF
The workspace mappings should resemble the following:
Source Control Folder Local Folder
$/MyTeamProject2/Main/Source/ C:\DevProjects\MyTeamProject2\Main\Source\
$/Common C:\DevProjects\MyTeamProject2\Main\Source\Common


For more information see “Working with multiple team projects in Team Build” at http://blogs.msdn.com/manishagarwal/archive/2005/12/22/506635.aspx

Branching

In this scenario, you branch the source from the common shared location into your team project. This creates a configuration that unifies the source from the shared location and their project on the server-side.

The difference is that the shared source changes are picked up as part of a merge process between the branches. This makes the decision to pick up changes in the shared source much more explicit.

For example if you have two team projects $TeamProject1 and $Common, and Common is the shared project source, you create a branch from the shared location to the project which is referencing it. The TFS folder structure should resemble the following.

ManagingDependencies3.GIF
Your workspace mapping should resemble the following:
Source Control Folder Local Folder
$/MyTeamProject1/Main/Source/ C:\MyTeamProject\Main\Source


The client side workspace folder structure should resemble the following:
ManagingDependencies4.GIF

Referencing Third Party Assemblies

If you cannot use a project reference and you need to reference an assembly outside of your current solution's project set, such as a third party library, and you do not wish to or cannot create a branch from the originating project into yours, you must set a file reference.

Managing shared binaries is similar to managing shared project source, and you must decide where you want to store the binaries and how you want your team to access the binaries. If you have binaries that are used by multiple team projects, you can either manage the binaries within the team project of the owning team or create a team project specifically for the shared binaries.

For the teams consuming the shared binaries, the same two options available for referencing projects are available for binaries.
  • Workspace Mapping
  • Branching

Workspace Mapping

In your team project that consumes the shared binaries, you reference the binaries from the shared location into your workspace on your development computer. This creates a configuration that unifies the binaries from the shared location and your project on the client-side.

The advantage of this approach is that shared binaries changes are picked up every time you retrieve the latest source into your workspace.

For example if you have two team projects $TeamProject1and $Common, where reference the binaries in Common from TeamProject1, then the client side workspace folder structure should resemble the following:
ManagingDependencies5.GIF

The workspace mappings should resemble the following:
Source Control Folder Local Folder
$/MyTeamProject2/Main/ C:\DevProjects\MyTeamProject2\Main\
$/Common/Main/Bin C:\DevProjects\MyTeamProject2\Main\Source\CommonBin


For more information see “Working with multiple team projects in Team Build” at http://blogs.msdn.com/manishagarwal/archive/2005/12/22/506635.aspx

Branching

In this scenario, you branch the binaries from the common shared location into your team project. This creates a configuration that unifies the binaries from the shared location and their project on the server-side.

The difference is that any changes to the binaries such as new versions are picked up as part of a merge process between the branches. This makes the decision to pick up changed shared binaries much more explicit.

For example if you have two team projects $TeamProject1 and $Common, and Common contains the shared binaries, you create a branch from the shared location to the project which is referencing it. The TFS folder structure should resemble the following.

ManagingDependencies6.GIF
Your workspace mapping should resemble the following:
Source Control Folder Local Folder
$/MyTeamProject1/Main C:\MyTeamProject1\Main


The client side workspace folder structure should resemble the following:
ManagingDependencies7.GIF

Guidelines for Referencing Projects and Assemblies

You can set a file reference in one of two ways:
  • To reference a .NET Framework assembly, you select the assembly from the list displayed on the .NET tab of the Add References dialog box.
  • You can use the Browse button in the Add Reference dialog box.

Assemblies such as System.XML.dll are located in the Global Assembly Cache (GAC). However, you never directly refer to an assembly within the GAC. Instead, when you select an assembly on the .NET tab of the Add References dialog box, you actually reference a copy of the assembly, located within the %windir%\Microsoft.NET\Framework\<version>\ folder

Project references are preferable to file references, so when managing assembly references, keep the following guidelines in mind:
  • Use project references whenever possible
  • Use file references only where necessary
  • Use Copy Local = True for project and file references

Use Project References Whenever Possible

You should use a project reference whenever possible because they provide the following advantages:
  • They work on all development workstations where the solution and project set are loaded. This is because a project Globally Unique Identifier (GUID) is placed in the project file, which uniquely identifies the referenced project in the context of the current solution.
  • They enable the Visual Studio build system to track project dependencies and determine the correct project build orders.
  • They avoid the potential for referenced assemblies to be missing on a particular computer.
  • They automatically track project configuration changes. For example, when you build using a debug configuration, any project references refer to debug assemblies generated by the referenced projects, while they refer to release assemblies in a release configuration. This means that you can automatically switch from debug to release builds across projects without having to reset references.
  • They enable Visual Studio to detect and prevent circular dependencies.

You can use a project reference if the assembly is already within your solution’s project set. If the assembly is outside of your solution’s project set and you still wish to you a project reference you can branch the dependency from the originating project into your project. When you wish to pick up a new version of the dependency perform a merge from the originating project into your branch.

Use File References Only Where Necessary

If you cannot use a project reference because you need to reference an assembly outside of your current solution's project set, and you do not wish to create a branch from the originating project into yours, you must set a file reference.

Use Copy Local = True for Project and File References

Every reference has an associated copy local attribute. Visual Studio determines the initial setting of this attribute (true or false) when the reference is initially added. It is set to false if the referenced assembly is found to be in the GAC; otherwise, it is set to true.
You should not change this default setting.

With copy local set to true, the Visual Studio build system copies any referenced assembly (and any dependent downstream assemblies) to the client project's output folder when the reference is set. For example, if your client project references an assembly called Lib1, and Lib1 depends on Lib2 and Lib3, then Lib1, Lib2, and Lib3 are copied to your project's local output folder automatically by Visual Studio at build time.

Automated Dependency Tracking

Each time you build your local project, the build system compares the date and time of the referenced assembly file with the working copy on your development workstation. If the referenced assembly is more recent, the new version is copied to the local folder. One of the benefits of this approach is that a project reference established by a developer does not lock the assembly dynamic-link library (DLL) on the server and does not interfere in any way with the build process.

Referencing Web Services

In simpler systems where all of the projects for the system are contained within the same team project, all developers end up with local working copies of all Web services because they are defined by Visual Studio projects within the team project. When you open a solution from source control for the first time, all projects (including any Web services) are installed locally. Similarly, if a Web service is added to the solution by another developer, you install the Web service the next time you refresh your solution from source control. In this scenario, there is no need to publish Web services on a central Web server within your team environment.

For larger systems, Web services can be published on a centrally accessed Web server and not all developers need to locally install the Web service. Developers can access it from their client projects.

Use Dynamic URLs When Referencing Web Services

If you want to call a Web service, you must first add a Web reference to your project. This generates a proxy class through which you interact with the Web service. The proxy code initially contains a static Uniform Resource Locator (URL) for the Web service, for example http://localhost or http://SomeWebServer.

Important - For Web services in your current solution that execute on your computer, always use http://localhost rather than http://MyComputerName to ensure the reference remains valid on all computers.

The static URL that is embedded within the proxy is usually not the URL that you require in either the production or test environments. Typically, the required URL varies as your application moves from development to test to production. You have three options to address this issue:
  • You can programmatically set the Web service URL when you create an instance of the proxy class.
  • A more flexible approach that avoids a hard coded URL in the proxy, is to set the URL Behavior property of the Web service reference to dynamic. This is the preferred approach. When you set the property to dynamic, code is added to the proxy class to retrieve the Web service URL from a custom configuration section of the application configuration file, Web.config for a Web application or SomeApp.exe.config for a Windows application.
  • You can also generate the proxy by using the WSDL.exe command line tool. And specifying the /urlkey command line switch. This works in a similar way to setting the URL behavior property in that it adds code to the proxy to retrieve the Web service URL, but in this case the URL is stored in the <appSettngs> section of the application configuration file.

The dynamic URL approach also lets you provide a user configuration file, which can override the main application configuration file. This allows separate developers (and members of the test team) to temporarily redirect a Web service reference to an alternate location.

How to Use Dynamic URLs and a User Configuration File

Set the URL Behavior property of your Web service reference to Dynamic to gain maximum configuration flexibility both within the development and production environments. By default Visual Studio sets the value of this property to Dynamic when you add the Web reference. To check that this value is still set to Dynamic in Solution Explorer:
  1. Expand the list of web references.
  2. Select each web reference in the list,
  3. For each web reference the value of the URL Behavior property should be Dynamic.

To specify a Web Service URL in a user configuration file
  • When you first add a Web reference Visual Studio will automatically generate the app.config for you. The configuration setting in the app.config file look like following:

<configuration>
    <configSections>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0,
         Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name=" SomeService.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, 
               Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <applicationSettings>
        <SomeService.Properties.Settings>
            <setting name="SomeService_ localhost _Service" serializeAs="String">
                <value>http://localhost/someservice/Service.asmx</value>
            </setting>
        </ SomeService.Properties.Settings>
    </applicationSettings>
</configuration>


This file contains a new configuration section that is used by the generated proxy. This configuration section contains the address of the web service that Visual Studio found when generating this proxy.

Visual Studio will also place the default value of the URL into the code generated for this proxy. This value lives in a file called Settings.Designer.cs. To see this file,
  • Right-click on the Web service reference in the Solution Explorer
  • Select View in Object Browser. In the Object Browser look for the entry that says SomeService.Properties.
  • Expand this and you will see an entry called Settings. Double-click on the Settings entry and the file will open. This file will contain a line like this

[global::System.Configuration.DefaultSettingValueAttribute("http://localhost:/webservice/Service.asmx")]

This is the default value used for the URL of the Web service if no configuration information is found.
  • Developers often need to change the URL of the web service they are calling. For example you may want to test against the web service running locally on your machine or against a version of the web service running on a test environment. It is also highly likely that the URL of the production Web service is not the same as a URL used by a developer. To manage each of these URLs the URL value should be specified through configuration information. This configuration should be held in a separate file that is referenced from the main App.config file. This file is known as a user configuration file. The name of the user configuration file is arbitrary; we’ve chosen user.config to be clear that this is where the user’s configuration would go.

To create a user.config file perform the following steps
  • Create a user.config file located in the same folder as the application configuration file
  • Copy the <SomeService.Properties.Settings> element setting from your application configuration file to the user.config. This file should just contain the element for which the runtime is redirected. There should be no other configuration element present in the user.config file. Here is an example
<SomeService.Properties.Settings>
    <setting name="SomeService_localhost_Service" serializeAs="String">
       <value>http://localhost/someservice/Service.asmx</value>
    </setting>
</SomeService.Properties.Settings>
  • The individual developer should set the contents of their user.config file as needed to reference the appropriate URL
  • You now need to specify that the configuration system should use this user.config file for this configuration data rather than the app.config file. To do this you must update the app.config file
  • Add a configSource="user.config" attribute to the <SomeService.Properties.Settings> element of your main application configuration file. This silently redirects the runtime to the named user configuration file when it accesses information from this section.
  • Delete the content of the <SomeService.Properties.Settings> element.

The app.config should now look like this
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0,
          Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="SomeService.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0,
              Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
  <applicationSettings>
    <SomeService.Properties.Settings  configSource="user.config">
    </SomeService.Properties.Settings>
   </applicationSettings>
</configuration>


In the preceding example SomeService is the proxy class name.

Important - If you use the configSource attribute then the user configuration file must be present, with only the <SomeService.Properties.Service> element. You must also ensure that when you add the configSource=”user.config” attribute you remove the XML content from the <SomeService.Properties.Service> element.
  • Ensure that the user.config file is deployed along with the application code. For this in the Solution Explorer, right click the user.config file and choose Properties option. Set the Copy To Output Directory property to Copy if newer.
  • Don't add the user.config file to source control. In this way, each developer (and the test team) can explicitly bind to specific URLs through their own user.config file entry. Source control may contain other user.config files, for example for testing and for production. These files should be managed by the users responsible for managing the testing and production environments. These test and production user.config files should not be stored as part of the Web service projects but should be in different areas of the source control system.
  • A global user.config file should be in source control. This could either contain only the root element (no <setting> element) or it could specify the default location of the web service. The user.config file must be present for the configuration system to work.

Tip: By default, the user configuration file is automatically added to source control when you add the solution. To prevent this, when you first check in the file , de-select the user.config file so it is not checked in. You can then right-click on the file in Solution Explorer and select Undo Pending Changes option, to ensure that the file never comes under source control.

Important - If the user.config file that specifies the URL only contains the root element (i.e. there is no setting element in the user.config) then the Web service proxy will use its default value for the URL. This default value is hard coded into the proxy in a file called Settings.Designer.cs. The value is specified as a DefaultValueSettings attribute and will look like this

[global::System.Configuration.DefaultSettingValueAttribute("http://localhost/webservice/Service.asmx")]

Important - For Web applications that employ a user configuration file, by default, any changes made to the file result in the Web application being automatically recycled. If you do not want to recycle the application when a value changes, add the restartOnExternalChanges="false" attribute to the configuration section definition.
<configSections>
	<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0,
           Culture=neutral, PublicKeyToken=b77a5c561934e089">
		<section name="Test.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, 
                   Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" restartOnExternalChanges="true"/>
	</sectionGroup>
</configSections>

If you add this attribute to the configuration section in the web.config file then changes to the external user.config configuration file will not cause the web application to recycle, however those changes will still be visible to the application.

It is important to realize that if you are using this mechanism then the user.config file must be present. Somebody will be responsible for ensuring the environment is correct when creating builds for production releases and for any test environments. As part of this build setup the appropriate User.confg file will need to be retrieved from the source control system and copied into the correct location for MSBuild to be able to find it.

Referencing Databases

Database references in the form of connection strings can also be managed by using an external configuration file. The advantage of this is that each developer can easily specify his or her own connection string is their own private user.config file. Any changes made by one developer, such as redirecting the connection to a local database for unit testing purposes, does not affect other developers.

User configuration files can also be used to control environmental-specific settings, such as those required by a test environment. The test environment can also use a user.config file which references the test database.

The procedure is similar to the preceding Web references example, except that in that example the Web service proxy contains the code to retrieve the Web service URL from the configuration file. For database connection strings, you must provide the code to read the connection string.

How to Use User Configuration Files for Database Connection Strings

The following procedure explains how to store and then reference a database connection string within a user configuration file.
To use a user configuration file to store database connection strings
  • Add a configSource="user.config" attribute to the <connectionStrings> element of your main application configuration file.

 <configuration>
   <connectionStrings configSource=”user.config”/>
</configuration> 
  • To override the main application configuration file, create a user.config file (located in the same folder as the application configuration file), and then add a similar <connectioStrings> entry to the file. Notice that the following connection string references a local database.
<connectionStrings>
      <add name="DBConnStr"
     connectionString="server=localhost;Integrated Security=SSPI;database=Accounts"/>
   </connectionStrings>
</configuration>
  • Within your project, use the following code to obtain the connection string from the user configuration file. This code uses the static ConnectionStrings property of the System.Configuration.ConfigurationManager class. In Win Form application you will have to add reference to System.Configuration.dll explicitly.
using System.Configuration;
private string GetDBaseConnectionString()
{
  return ConfigurationManager.ConnectionStrings["DBConnStr"].ConnectionString;
}
  • Ensure that the user.config file is deployed along with the application code. For this in the Solution Explorer right click the user.config file and select the Properties option. In the properties pane, set the Copy To Output Directory property to Copy if newer.
  • Don't add the user.config file to source control. In this way, each developer (and the test team) can explicitly specify the connection string through their own user.config file entry. Source control may contain other user.config files, for example for testing and for production. These files should be managed by the users responsible for managing the testing and production environments. These test and production user.config files should not be stored as part of the database projects but should be in different areas of the source control system.
  • In source control you should have a user.config file for each of the environments that you use the database (production, test). These configuration files should specify the connection string for the database. The user.config file must be present for the configuration system to work.

Tip: By default, the user configuration file is automatically added to source control when you add the solution. To prevent this, when you first check in the files, de-select the user.config file so it is not checked in. You can then right-click on the file in Solution Explorer and select Under Pending Changes to ensure that the file never comes under source control.

It is important to realize that if you are using this mechanism then the user.config file must be present. Somebody will be responsible for ensuring the environment is correct when creating builds for production releases and for any test environments. As part of this build setup the appropriate user.confg file will need to be retrieved from the source control system and copied into the correct location for MSBuild to be able to find it.

Related Items

For step-by-step guidance for structuring Visual Studio solutions and projects with Team Foundation Server, see:
For more information about creating branched folders, see:

Additional Resources

Last edited Jul 5, 2007 at 10:44 PM by jtaylorsi, version 13

Comments

laughingskeptic Apr 2, 2008 at 1:34 PM 
There are thousands of components in our products. Our build is configured such that we build into a common target directory. Our tree looks like:
root\Bin
root\Sln\<solution files here>
root\Src\NSParticle1\NSParticle2\...\<csproj files here>
For this configuration we do not want Copy Local=true as this causes problems with the build (not sure why it fails to recognize that source=destination and do nothing, but it does not). Is there any way to make Copy Local=false?

johnsons Jul 25, 2007 at 1:57 PM 
Is there anything that needs to happen to the Settings.settings file?

johnsons Jul 25, 2007 at 1:51 PM 
Regarding "How to Use Dynamic URLs and a User Configuration File", I have followed the steps but still cannot get the "dynamic" url behavior to work as described. Suggestions?

RickGlos Jun 8, 2007 at 12:28 AM 
Instead of "select Under Pending Changes to" I think you mean to say "select Undo Pending Changes to".

We use "Exclude from project" instead - this way your not checking in a broken *proj files missing any *.config files. I would be interested to know what the benefit is with checking in a missing .config file... perhaps the error messages you get from having missing files?