While I was looking for a general solution to edit Web.Config using a SharePoint feature, I found an excellent article from Ryan McIntyre about creating an external XML file with all the custom modifications and using a common feature receiver, it would take the changes from the external xml file and merge them in web.config using SPWebConfigModification object.
I am impressed with this approach, as this will eliminate frequent changes to the feature receiver class and avoid compilation and rebuilding WSP. Just change the XML file and re activate the feature, it’s that simple. Enough said, details including the feature receiver class is in his site below.
But the code Ryan used for feature receiver class, works only for element modifications not for creating new sections. In my case I got to create a new section in the web.config file for example “connectionStrings”. After further exploring the code, he used EnsureChildNode modification type, this will be good only for modifications, if you want create new sections we must use EnsureSection. But EnsueSection has a disadvantage, we can’t remove the sections from web.config on de activating the feature. But I felt it is fine to leave an empty section in the web.config, it doesn’t harm. Following are my modifications (highlighted) to the feature receiver class that Ryan developed, to accommodate both sections and elements.
protected SPFeatureReceiverProperties _properties;
public override void FeatureActivated(SPFeatureReceiverProperties properties) {
string fileLoc = properties.Definition.RootDirectory + “\\WebConfigChanges.xml”;
//Check to see if a WebConfigChanges.xml file exists. If yes, we have work to do
if (System.IO.File.Exists(fileLoc))
{
//Grab the properties
_properties = properties;
this.ProcessChanges(fileLoc, false);
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
string fileLoc = properties.Definition.RootDirectory + “\\WebConfigChanges.xml”;
//Check to see if a WebConfigChanges.xml file exists. If yes, we have work to do
if (System.IO.File.Exists(fileLoc))
{ //Grab the properties
_properties = properties;
this.ProcessChanges(fileLoc, true);
}
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties) {
/* no op */
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {
/* no op */
}
private void ProcessChanges(string FileLocation, bool removeModification)
{
string xPathLocation;
string elementName;
string sectionName;
Dictionary<string, string> attributes = new Dictionary<string, string>();
using (XmlReader reader = XmlReader.Create(FileLocation))
{
//Loop through all of the changes
while(reader.ReadToFollowing(“WebConfigChange”))
{
//Clean out any attributes from past iterations
attributes.Clear();
xPathLocation = reader.GetAttribute(“XPathLocation”);
elementName = reader.GetAttribute(“ElementName”);
//Check if there is any new Section required
sectionName = reader.GetAttribute(“SectionName”);
elementName = reader.GetAttribute(“ElementName”);
//Make sure we have at least a path and element
if (xPathLocation == null || elementName == null || sectionName == null)
throw new Exception(“WebConfigChange missing required XPathLocation or ElementName or SectionName attributes”);
//Get the Attributes to apply
if (reader.ReadToDescendant(“Attribute”))
{
do
{
attributes.Add(reader.GetAttribute(“Name”), reader.GetAttribute(“Value”));
} while (reader.ReadToNextSibling(“Attribute”));
}
//Do the update
UpdateWebConfig(xPathLocation, elementName, sectionName, attributes, removeModification);
}
}
}
private void UpdateWebConfig(string XPathLocation, string ElementName, string SectionName,
Dictionary<string, string> Attributes, bool removeModification)
{
try
{
SPWebApplication webApp = null;
//Get the web app
//First check if it was deployed to a Site Collection
SPSiteCollection siteCol = _properties.Feature.Parent as SPSiteCollection;
if (siteCol == null)
{
//Check if it was deployed to a site
SPSite site = _properties.Feature.Parent as SPSite;
if (site == null)
{
//Check if it was deployed to a Site
SPWeb web = _properties.Feature.Parent as SPWeb;
if (web != null)
webApp = web.Site.WebApplication;
}
else
webApp = SPWebApplication.Lookup(new Uri(site.Url));
}
else
webApp = siteCol.WebApplication;
if (webApp != null)
{
SPWebConfigModification modification;
//If no section name
if (SectionName == null)
{
modification = new SPWebConfigModification(ElementName + CreateAttributeString(Attributes), XPathLocation);
modification.Owner = “Company.MOSS.FeatureReceiver”;
modification.Sequence = 0;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Value = string.Format(CultureInfo.InvariantCulture, CreateModificationValueString(ElementName, Attributes), CreateModificationValueArgs(Attributes));
}
else
{
modification = new SPWebConfigModification(SectionName, XPathLocation);
modification.Owner = ” Company.MOSS.FeatureReceiver”;
modification.Sequence = 0;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureSection;
modification.Value = String.Format(“<{0}/>”, SectionName);
}
if (removeModification)
webApp.WebConfigModifications.Remove(modification);
else
webApp.WebConfigModifications.Add(modification);
webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
}
else
throw new ApplicationException(“Could not locate a web application”);
}
catch (Exception ex)
{
System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
el.Source = “WebConfigFeature”;
el.WriteEntry(ex.Message);
}
}
/// <summary>
/// Accepts a dictionary object with all of the attributes for the web modification and
/// creates a string representing the attribute values which can be used when creating
/// the SPWebConfigModification object.
/// </summary>
/// <param name=”Attributes”></param>
/// <returns></returns>
private string CreateAttributeString(Dictionary<string, string> Attributes)
{
//Create a string that looks like this (no line breaks):
//[@Assembly=\"Company.Moss.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4\"]
//[@Namespace=\"Company.Moss.Activities\"]
//[@TypeName=\"*\"][@Authorized=\"True\"]
string result = “”;
//Check if there are attributes
if (Attributes.Count > 0)
{
foreach (KeyValuePair<string, string> kvp in Attributes)
{
result += “[@" + kvp.Key + "=\"" + kvp.Value + "\"]“;
}
}
return result;
}
private string CreateModificationValueString(string ElementName, Dictionary<string, string> Attributes)
{
//Create a string that looks like this:
//”<authorizedType Assembly=\”{0}\” Namespace=\”{1}\” TypeName=\”{2}\” Authorized=\”{3}\”/>”
string result = “<” + ElementName;
//Check if there are attributes (Kind of silly if there aren’t!)
if (Attributes.Count > 0)
{
int i = 0;
foreach (string key in Attributes.Keys)
{
result += ” ” + key + “=\”{” + i.ToString() + “}\”";
i++;
}
}
result += ” />”;
return result;
}
private object[] CreateModificationValueArgs(Dictionary<string, string> Attributes)
{
//Create an object that looks like this:
//”Company.Moss.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4″, “Company.Moss.Activities”, “*”, “True”
object[] result = new object[Attributes.Count];
int i = 0;
foreach(string value in Attributes.Values)
{
result[i] = value;
i++;
}
return result;
}
Here is the sample WebConfigChanges.xml file with connectionStrings section and other elements.
<?xml version=”1.0″ encoding=”utf-8″ ?>
<WebConfigChanges>
<WebConfigChange XPathLocation=”configuration” SectionName=”connectionStrings”>
</WebConfigChange>
<WebConfigChange XPathLocation=”configuration/connectionStrings” ElementName=”add”>
<Attributes>
<Attribute Name=”name” Value=”MOSSConnectionString” />
<Attribute Name=”connectionString” Value=”Data Source=XXX;Initial Catalog=XXX;User Id=XXX; Password=XXX” />
<Attribute Name=”providerName” Value=”System.Data.SqlClient” />
</Attributes>
</WebConfigChange>
<WebConfigChange XPathLocation=”configuration/system.web/httpHandlers” ElementName=”add”>
<Attributes>
<Attribute Name=”verb” Value=”*” />
<Attribute Name=”path” Value=”ChartImg.axd” />
<Attribute Name=”type” Value=”System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ />
</Attributes>
</WebConfigChange>
</WebConfigChanges>
Remember to place this file in the folder where elements.xml of the feature file is located under the 12 hive.