You may have read my older post on how to add BindingRedirect for assemblies into web.config in SharePoint, which allows you to change the DLL version number of your SharePoint customizations.
One thing I did not mention was, what if you need to use one of these redirected DLL’s in an SPTimerJob?
You will notice that assembly BindingRedirect does not apply to OWSTimer.exe, and it will throw errors such as “Could not load file or assembly 'AssmeblyName, Version=1.2.60.0, Culture=neutral, PublicKeyToken=xxxxxxxxx' or one of its dependencies. The system cannot find the file specified”, while you alraedy have a newer version deployed.
So, how do I make OWSTimer.exe aware of the new version and have my SPTimerJobs running with the latest version every time?
First, we need to understand why this doesn’t work.
Timer jobs run from OWSTimer.exe file, which is not effected by the web.config settings, so our BindingRedirect in web.config file will not help our code to find the correct assembly when running of the OWSTimer.exe file.
Now, for the solution
The bad news are that although a WSP natively allows you to add BindingRedirect to your web.config, it does not allow the same for other config files.
So, we need to do this manually.
Not to fear though! There is a rather simple way of doing that, still within the WSP with no need to come out of the WSP deployment.
It is a little know fact, that a farm feature event handler, with the “FeatureInstalled” event will have 2 special effects:
1. It will run for each web front end server, and not only on one of them
2. It will allow sufficient access to add files to the SharePoint Root.
Just make sure you do not edit / remove any of the out of the box files!
Well, taking this into consideration makes our problem a very simple one to solve.
We just need to create a config file for OWSTimer.exe and add our BindingRedirect statements there, and that’s it – the OWSTimer will know which version of our DLL to look for.
Here is a code example on how to add or update 2 BindingRedirect nodes in that config file, the same method can apply to any other config file during WSP deployment stage:
public void UpdateOWSTimerBindingRedirect()
{
try
{
string configFile = SPUtility.GetGenericSetupPath("TEMPLATE").ToLower().Replace("\\template", "\\bin") + "\\OWSTIMER.EXE.CONFIG";
//string XMLData = System.IO.File.ReadAllText(configFile, Encoding.UTF8);
XmlDocument config = new XmlDocument();
config.Load(configFile);
//ensure assemblyBinding exists
XmlNode assemblyBinding = config.SelectSingleNode("configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']");
if (assemblyBinding == null)
{
assemblyBinding = config.CreateNode(XmlNodeType.Element, "assemblyBinding", "urn:schemas-microsoft-com:asm.v1");
config.SelectSingleNode("configuration/runtime").AppendChild(assemblyBinding);
}
//Delete old entrees if exist
XmlElement current = assemblyBinding.FirstChild as XmlElement;
while (current != null)
{
XmlElement elmToRemove = null;
if (current.FirstChild != null)
{
var asmIdn = (current.FirstChild as XmlElement);
if (asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.sharepoint.foundation") ||
asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.foundation"))
elmToRemove = current;
}
current = current.NextSibling as XmlElement;
if (elmToRemove != null)
assemblyBinding.RemoveChild(elmToRemove);
}
XmlElement dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.SharePoint.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />"+
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}
dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />" +
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}
config.LoadXml(config.OuterXml.Replace("xmlns=\"\"",""));
config.Save(configFile);
}
catch { }
}
Simply call this method during the FeatureInstalled event and update the code with your DLL name and version number, and you are done.
Hope this helps you with file versioning on SharePoint, which can be a rather difficult task sometimes.
Thanks, Shai.