One of the projects I've been working on recently required each of our web applications to have their own IIS websites and bespoke files (master pages, images, styles etc.) but also each have access to a shared code library which would reside in a central location on the server. Dealing with the shared UI layer (pages, controls etc) was easy, we could just set up a virtual directory in IIS 'SharedCode' and point it to the central location. Dealing with the assemblies generated from our shared code library (C#) projects and having them reside in a central location, outside of each application however, was not so straight forward.
The usual way to achieve this would be to put these shared assemblies into the global assembly cache (GAC). However, for this particular project, the GAC wasn't such a viable option. There are many benefits in using the GAC (proper version control is an absolute must in some cases) but there are also a number of reasons why adding assemblies to the GAC may not be viable or sensible for your particular project, the main reasons that affected us were:
- Some of the assemblies referenced other third-party, closed-source assemblies which were not signed with a strong name. - In order to add any assembly to the GAC, the assembly itself AND all assemblies it references must be signed. Unless you have access to the source of these third party assembly, there is no way to sign it.
The project and the shared code in your assemblies will be constantly being developed by a team of developers – It's advisable that your development platforms mirror the set-up of your live server. If you plan on putting your shared assemblies in the GAC on the live server, then ideally they will also be in the GAC on your development platforms. If you have a team of developers working on a shared code library, it would get extremely difficult and time consuming to make sure every developer has the correct version of the code in their GAC. In our case, the shared code library wouldn't be incremented in versions but rather always have a single current version.
However, we still needed a way of centralising the assemblies and allowing each separate application to find and use them. We already had a shared directory that contained the common UI layer of my application, this had been set up as a virtual directory on each website. Ideally I needed to structure my web application like this:
Luckily, there is a way to tell your application to look for it's assemblies in locations other the the 'bin' directory.
If you put your assemblies in the root 'bin' directory of your web app, you don't need to tell the ASP.Net CLR to reference them or where to look for them, this is all done automatically. If you put your assemblies in the GAC, you don't need to tell ASP.Net where to look for them but you do need to register them. If you you put your assemblies anywhere else, you need to both register them and tell ASP.Net where to find them. Both of these tasks can be achieved in the web.config.
Registering the assemblies
You might already recognise this step. Whenever your reference an assembly in the GAC or one that's part of the CLR, you also use this method. However, in this case, since our shared assemblies aren't strongly named, we don't have to fully qualify our assembly reference (with a text name, version, culture, and public key token). To register the assemblies (tell the app to look for them), add this line for each assembly:
Telling ASP.Net where to look with a probing path
To tell ASP.Net where to look for these assemblies, you need to create a probing path in your web.config. ASP.Net will add this path to it's list of places to look for it's assemblies.
<probing privatePath="SharedCode\Bin" />
Creating a NTFS Junction Point for your shared code
As mentioned above, originally, all of our applications had access the the 'SharedCode' folder via a Virtual Directory set up in IIS. Unfortunately (but probably for good reason), you can't load assemblies from virtual directories. You also cannot tell a web application to look for assemblies outside of the scope of the application (e.g <probing privatePath=”C:\Bin”> will not work). To get around this problem I created an NTFS junction point in the root of the applications that pointed to the shared code directory. NTFS junction points are basically direct links to other directories on the local machine. For more information on NTFS junction points see:
*Note: pay special attention to the section 'Usage Recommendations'. Using NTFS Junction points can be dangerous because what you do to the junction point will also affect the target directory.
*Also note: For some reason Windows Server 2003 has problems recognising Junction points created by some third party applications. For this reason, it's easier to use linkd.exe which can be found in the Microsoft Windows Server 2003 Resource Kit.