Wow what a long post title! Let me explain..
Overview
SharePoint apps on the same server shares a common GAC. How do we reuse Framework libraries across these apps, yet:
- Keep the Framework runtime code isolated for each client system to:
- Reduce risk and regression testing efforts
- Allow each client system to evolve independently
- Maintain virtually one copy of the Framework source code for manageability.
The Problem
Mature Dev teams should develop and maintain core reusable code libraries (herein referred to as Framework libraries) that get incorporated into various systems to help with speedy development, consistency, and placing guidance around how things should be done.
The Framework and its client systems however is technically a 1-to-many relationship. Each client system evolves at its own pace. A change to the Framework must not break any client system. Ideally, all client systems should be regression tested with each change to the Framework.
In reality however, the client systems would more likely be tied to a particular version of the Framework. When the Framework is updated to version 1.5 to cater for SystemA for example, SystemB continues to use version 1.4 because the Test team has not had time to perform regression testing on SystemB with version 1.5.
In ASP.NET this could be catered for because SystemA and SystemB would have its own Bin directory and the Framework libraries could be referenced from there and be isolated from one another. In SharePoint however this is not possible because the Framework libraries will need to be in the GAC, which are common for all SharePoint apps on a particular server. We could increment the assembly version of the Framework libraries, but this however will require us to update a large number of deployed SharePoint artefacts (e.g. event receivers, webpart DLLs, etc) with the new assembly signature.
Solution
To overcome this we adopted a strategy that compiles the Framework from virtually one source code, but deploys them as different assembly set, one for each client system. We also used a branching strategy in TFS that maintain a branch for each assembly set. Below is a walkthrough of this setup.
Let say there are 2 client systems: StaffSurvey and BillingPortal. Let say our Framework consists of 2 assemblies, Framework and Framework.SharePoint. The latter has a reference to the first (make sure they are using project reference). Below is the structure of the Framework solution in Visual Studio:
In this solution we will setup 2 configurations using Visual Studio’s Configuration Manager. One configuration will compile the DLLs in the solution as Framework.StaffSurvey.dll and Framework.SharePoint.StaffSurvey.dll. The other will compile them as Framework.BillingPortal.dll and Framework.SharePoint.BillingPortal.dll.
Create 2 new configurations, call them StaffSurvey and BillingPortal. You can base them on the Debug configuration. The result should look like below:
Next, in Visual Studio, in the Solution Explorer, right click on the Framework project and choose Unload Project. Right click on the Framework project again and choose to edit the Framework.csproj file.
In the csproj file (XML), you should see 5 child <PropertyGroup> elements underneath the root <Project> element, similar to below:
<PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'StaffSurvey|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'BillingPortal|AnyCPU' ">
The first PropertyGroup element (the one without the Condition attribute) contains default settings for all configurations in our solution. The other 4 PropertyGroup are for the 4 configurations we have defined in our solution (Debug, Release, StaffSurvey, BillingPortal). The settings contain in these 4 PropertyGroup are used to overwrite the default settings defined in the first PropertyGroup element.
Underneath the first <PropertyGroup> element, locate the child element <AssemblyName>Framework</AssemblyName>. Copy this element and paste it underneath the <PropertyGroup> for the StaffSurvey configuration. Change the Framework value to Framework.StaffSurvey. The result should look like below:
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'StaffSurvey|AnyCPU'"> <AssemblyName>Framework.StaffSurvey</AssemblyName>
As you may have guessed, this setting makes VS produces the assembly as Framework.StaffSurvey.dll when the StaffSurvey configuration is selected. Do the same thing for the BillingPortal configuration. Save the csproj file and right click the Framework project in the Solution Explorer and choose to Reload Project. Repeat these steps for the Framework.SharePoint project.
Now, when you build the solution using the Debug/Release configurations, the dlls will be produced as Framework.dll and Framework.SharePoint.dll as normal. Under the StaffSurvey/BillingPortal configurations, the dlls will be produced as Framework.StaffSurvey.dll and Framework.SharePoint.StaffSurvey.dll.
In the client system, e.g. StaffSurvey, create a folder call BuildDependency. Copy the Framework.StaffSurvey.dll and Framework.SharePoint.StaffSurvey.dll to this folder. When adding reference to the Framework libraries for the StaffSurvey solution, select only the Framework libraries in this BuildDependency folder.
Next, setup the branches in TFS for the Framework solution as shown below:
The Dev branch is where most of the development for the Framework will occur. The release branches are used to track the Framework versions that were actually released to each of the client system.
To make a release of the Framework to a particular client system, you should follow these steps:
- In the Dev branch, build and test the Framework using the Debug configuration.
- Merge the Dev branch to the Release branch for the client system that you want to perform the release for (e.g. Releases\StaffSurvey).
- In the Releases\StaffSurvey branch, build and test the Framework using the Debug configuration (to make sure the merge did not introduce any bugs).
- In the Releases\StaffSurvey branch, build the Framework using the StaffSurvey configuration.
- Copy the output dlls from the bin\StaffSurvey folder to the BuildDependency folder of the StaffSurvey solution.
- Build and test the StaffSurvey solution.
With the above elements in place – a configuration for each client system and a branching strategy – you now can:
- Choose when to update the Framework for a client system by merging from the Dev branch to the appropriate Release branch.
- Isolate the Framework runtime code for different client systems. Changes to Framework.StaffSurvey.dll will not impact Framework.BillingPortal.dll for example.
- Yet, maintain virtually one source code for the Framework, i.e. the Dev branch.
- In exceptional cases, hot fixes can be applied to one Release branch (where the bug was found), then merge back into the Dev branch, then merge out into other Release branches.
Conclusion
You can achieve runtime isolation and source code manageability for Framework libraries in SharePoint by deploying Framework libraries using client system specific dll names (using the Configuration Manager in VS) and adopting a thoughtful branching/merging convention.