A while ago I worked on a SharePoint project for which the client wanted no custom assemblies in the Global Assembly Cache (GAC) on any of the SharePoint servers. This article covers some of the tips and tricks required to make this happen in a complex (i.e. customized) environment.
Reasons for avoiding the GAC:
- Restricting the effective permissions of code running in the assemblies
- Isolating code into to a single web application
- Minimizing the impact of deployments, such as avoiding IISResets
- Allowing different versions of the same assembly on the same machine
I don't necessarily advocate the GAC-less approach, and for the most part, you will not need to worry about doing this. Most of this process is straightforward, but there are some special cases and "gotchas" that are not at all obvious.
(Quick disclaimer- for some advanced solutions you will have to use the GAC, at least temporarily, but this probably won't compromise whatever goals you are trying to achieve.)
Also, let's assume that for various reasons we are not going to use Sandboxed solutions.
First, you have to do things a little differently in Visual Studio.
Change the "Assembly Deployment Target" Property to "WebApplication."
Easy enough. This will take care of the main project output assembly.
But what if you need to also deploy referenced/dependent assemblies as part of the solution? Typically we like to divide our code into separate projects, so this is a common scenario. Any dependent assemblies will need to be in the web application's bin folder in order for them to get loaded into the SharePoint app pool. You can add additional assemblies to the package that will get deployed to the bin and registered in the web.config.
From the bottom of the Package explorer, click "Advanced" and choose any assemblies to add. Note when you press the "Add" you will have 2 choices:
- Choose "Add Existing Assembly" if the assembly's project is not part of the current solution (i.e. it is an assembly reference instead of a project reference).
- Choose "Add Assembly from Project Output" if the assembly's project is included as a project reference
Make sure you change "Deployment Target" to "WebApplication!"
You must include a SafeControl entry for each unique assembly and namespace in the package. Click the button in the "Safe Controls" section to do this:
Sometimes you may have multiple namespaces/types in one assembly. These will need to be registered as Safe Controls in the web.config. To do this, you must manually add them to the manifest.xml.
- Open the Package in Visual Studio.
- At the bottom, click the "Manifest" tab
- Expand the "Edit Options" section
- Click "Open in XML Editor"
- Add SafeControl references as shown here:
<>xmlversion="1.0" encoding="utf-8"?> <> xmlns="http://schemas.microsoft.com/sharepoint/"> <>> <> Location="Contoso.Common.MasterPages.dll" DeploymentTarget="WebApplication"> <>> <> Assembly="Contoso.Common.MasterPages, Version=22.214.171.124, Culture=neutral, PublicKeyToken=9d7a42d510f2802b" Namespace=" Contoso.Common.MasterPages" TypeName="*" /> <> Assembly=" Contoso.Common.MasterPages, Version=126.96.36.199, Culture=neutral, PublicKeyToken=9d7a42d510f2802b" Namespace=" Contoso.Common.MasterPages.DemoApplication" TypeName="*" /> > > > >
When you are done, you should see everything pointing to Deployment Target = "WebApplication."
Dealing with Feature Receivers
Normally (in a GAC scenario), you can just right click on the feature project item and choose "Add Event Receiver." This will add a class to the project and you could edit it from there.
However, when deploying to the WebApplication (instead of the GAC) this will not work. FeatureReceiver code must be in an assembly in the GAC [see here]. This makes sense when you consider the timing of loading assemblies into the app domain, but that's a story for another time. So, this is a case where we make use of the GAC for only the FeatureReceiver code. This code will be moved into its own separate project/assembly and deployed to the GAC as a part of this main solution.
Follow these steps:
- In the main SharePoint project, right click on the Feature and choose "Add Event Receiver." This will create the class which we will then copy to a new project.
- Create a new project of type ClassLibrary named Contoso.[Application].FeatureReceivers. Add references to Microsoft.SharePoint.
- Copy the class you created in step 1 into the new project. Change the namespace.
- Add whatever code you want to the feature receiver methods.
- Back in the main SharePoint project, open the Properties window for the Feature. We need to tell it to use our new assembly and class as the feature receiver. Change the following two properties as shown:
- Receiver Assembly: Contoso.[Application].[SharePoint].FeatureReceivers, Version=188.8.131.52, Culture=neutral, PublicKeyToken=8e5a42d510f2802b
- Receiver Class: Contoso.[Application].[SharePoint].FeatureReceivers.[YourClass]FeatureReceiver
If your feature receiver code tries to access code from another assembly that will be in the bin (as opposed to the GAC), you will receive an error saying that the assembly [name] cannot be loaded- this happens because the referenced assembly is added to the bin, then the feature receiver code runs before the app pool recycles, so the referenced assembly is never loaded into context. To work around this, add this line to your feature receiver code:
which uses Reflection to load the assembly into context for the duration of the feature receiver activation method. You can get even fancier by using the object model to determine the path to the bin.
By default, assemblies in the GAC run with Full Trust (this may be why you don't want them there… ). Assemblies in the bin have a much lower level of trust by default. You will need to implement Code Access Security (CAS) to get them working.
Rather than rewrite the book here, I will point you to a couple useful articles on how to do this:
Don't forget to set your AllowPartiallyTrustedCallers attribute to your assembly info file!
Deploying with PowerShell
When you deploy your assembly using a custom CAS policy, you must use the -CASPolicies option with SharePoint Management Shell. The command is as follows:
Install-SPSolution -Identity -CASPolicies
Also note, if you added a feature receiver in a separate assembly, you will also need to include the
"-GACDeployment" switch to allow that assembly to be placed in the GAC.
Now we're getting to the interesting stuff. Depending on how complex your solution is, you may need to take additional steps to get everything working. Let's look at a couple here. I won't be covering them all, but the principles here should allow you to solve problems for similar cases.
Excel (and other) Services without the GAC
Let's say you are deploying some customization to Excel Services that requires custom code. If you can't put your assembly in the GAC, it needs to go here:
:\Program Files\Microsoft Office Servers\14.0\WebServices\Shared\ExcelCalculationServer\
On all the Application Servers that are running Excel Services.
The same concept applies to most of the other service applications- they all have a bin directory somewhere in the SharePoint Root directory. It's usually obvious to find the directory you are looking for, but if not you can look in IIS and track backwards from the service in question. .NET assembly binding will load assemblies from these paths when executing service calls.
Typically I use basic PowerShell copy operations as part of my deployment script to put the assemblies in these bins, but you could also use a mapped folder in a Visual Studio solution.
Custom Claims Provider
This one is tough because you need to deal with having the assemblies available during registration, and also because your people pickers in Central Admin may need access to the assemblies. This approach worked for me:
- Copy the Claims Provider assembly and dependent assemblies to the SharePoint Security Token Service (STS) bin: :\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken\bin\
- Copy your custom Claims Provider assembly and dependent assemblies to the Central Admin web application bin: :\inetpub\wwwroot\wss\VirtualDirectories\24994\bin (where the port is the one used by Central Admin)
- Temporarily put your Claims Provider assembly and its dependent assemblies in the GAC (drag and drop into C:\Windows\assembly , or use gacutil commands)
- Activate whatever feature you are using to register your custom claims provider. Or, use PowerShell to register it.
- Uninstall the Claims Provider assembly and its dependent assemblies from the GAC.
Note: to use a custom Claims Provider, some services may require access to your claims provider assemblies. This will require you to place those assemblies in the bin of that service. You will get a specific error message if this is the case.
There are going to be times when you have to use the GAC, as in the Feature Receiver example above. In fact, this limitation applies to all Event Receivers.
I have not attempted to go GAC-less with custom service applications or timer jobs, though I suspect that this could work using the same approach I described for the custom Claims Provider. Remember that the custom assemblies would need to go in the same directory as the OWSTimer.exe file (somewhere on the root drive).
You shouldn't try to avoid the GAC unless you have a compelling business reason that justifies the additional complexity. If your solution will require a Feature Receiver or custom extension to any SharePoint service, then avoiding the GAC will probably be more pain than it is worth.
Consider alternatives to achieve these goals. For example, SharePoint's client side API will let you avoid certain issues. The SharePoint 2013 App Model takes all your assemblies off the SharePoint servers entirely.
If you do go down this route, the most important thing to remember is that your custom assemblies need to be copied to locations that are part of the assembly binding tree path for the process that needs to use them.