Friday, October 23, 2009

SharePoint Feature that adds web.config settings and copies SSRS files to IIS folder

I wanted to create SSRS reports for my SharePoint ASP.NET application. I wanted to make everything about the reports to be a part of a Feature to add.

I will have a Web.Config change to make and RDLC files to copy over to the IIS folder with User permissions set.

Here is what I came up with. On FeatureActivated, I call a function to add the Web.Config settings and then another to copy over the RDLC files.

I created an array of SPWebconfigModifications. You will see a couple I commented out so you can get a better feel for how these settings work. I have three modifications occur. The first adds the HTTPHandler needed for the ReportViewer. The second updates that same record if it already exists. I did this because we had a web.config that had the HTTPHandler already but with version 8.0.0.0. So my add child node did not do anything because it was already there. So the update attribute did the trick. The third change is to add a Remove in the appSettings. There is a setting that is automatically added, but it will error in SharePoint, so I've found the answer out there to be to remove that from the appSettings. The only thing I would want to do to upgrade this is to add code to see if the HTTPHandler is already there or not, then only do the first or second modification but not both.

You will notice I am running in Elevated Privileges mode. You will need this unless your feature is a WebApplication feature. Anything lower will not have permission to change web.config settings.

While copying the files over, I first add a folder if not already present. Didn’t want all those RDLC files sitting in the main folder where the web.config file is. There could be one issue here, which is worrying about the URI length. I did not worry about it in this code, but if you want to add that change, please feel free to post back the changes needed.

This is a great way to add all those web.config settings needed for AJAX, just throw them in the array of SPWebconfigModifications and you will have a Feature that loads/unloads the AJAX web.config settings.





using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web.Hosting;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using GroupPublishing.VBSPro.Core;
using System.Diagnostics;
using GroupPublishing.VBSPro.Core.Configuration;
using System.Collections.ObjectModel;
using System.Security.AccessControl;
using System.Security.Principal;

namespace GroupPublishing.VBSPro.Web.FeatureReceivers
{
class VBSProReportsFeatureReceiver : SPFeatureReceiver
{
private static SPWebConfigModification[] WebConfigModifications = {
//new SPWebConfigModification("CallStack", "configuration/SharePoint/SafeMode")
// { Owner = "motion10DebugSwitch", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute, Value = "true" },
//new SPWebConfigModification("mode", "configuration/system.web/customErrors")
// { Owner = "motion10DebugSwitch", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute, Value = "Off" },
//new SPWebConfigModification("debug", "configuration/system.web/compilation")
// { Owner = "motion10DebugSwitch", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute, Value = "true" },

// Add HttpHandler
new SPWebConfigModification("add[@path='Reserved.ReportViewerWebControl.axd']", "configuration/system.web/httpHandlers")
{ Owner = "FeatureOwner", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode, Value = @"<add verb='*' path='Reserved.ReportViewerWebControl.axd' type='Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' />" },


// Update HttpHandler
new SPWebConfigModification("type", "configuration/system.web/httpHandlers/add[@path='Reserved.ReportViewerWebControl.axd']")
{ Owner = "FeatureOwner", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute, Value = "Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" },


// Add appSetting
new SPWebConfigModification("remove[@key='ReportViewerMessages']", "configuration/appSettings")
{ Owner = "FeatureOwner", Sequence = 0, Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode, Value = @"<remove key='ReportViewerMessages' />" }


};

private static string FolderName = "Reports";

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
try
{
//Deploy WebConfig Settings
DeployWebConfigSettings(properties);

//Deploy Report Files
DeployReportFiles(properties);
}
catch (Exception ex)
{
throw new SPException("Error during Activation. See event log for further details");
}
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
try
{
// Remove WebConfig Settings
RemoveWebConfigSettings(properties);

// Remove Reports
RemoveReportFiles(properties);

}
catch (Exception ex)
{
throw new SPException("Error during Feature Deactivation. See event log for further details");
}
}

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}

private void DeployWebConfigSettings(SPFeatureReceiverProperties properties)
{
//Remove any previous WebConfig Settings by this Feature
RemoveWebConfigSettings(properties);

SPSecurity.RunWithElevatedPrivileges(
delegate()
{
String _Owner;
// Get Owner Name
_Owner = properties.Feature.DefinitionId.ToString();

// Get SPSite URL
string spSiteURL = ((SPSite)properties.Feature.Parent).Url;

// Open new instance of SPSIte
using (SPSite oSiteCollection = new SPSite(spSiteURL))
{
// Get SPWebApplication
SPWebApplication webApp = oSiteCollection.WebApplication;

// Add WebConfig Modifications
foreach (SPWebConfigModification modification in WebConfigModifications)
{
modification.Owner = _Owner;
webApp.WebConfigModifications.Add(modification);
}

// Apply Web App Mod
webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

// Serialize the web application state and propagate changes across the farm.
webApp.Update();
}
});
}

private void RemoveWebConfigSettings(SPFeatureReceiverProperties properties)
{
SPSecurity.RunWithElevatedPrivileges(
delegate()
{
String _Owner;
// Get Owner Name
_Owner = properties.Feature.DefinitionId.ToString();

// Get SPSite URL
string spSiteURL = ((SPSite)properties.Feature.Parent).Url;

// Open new instance of SPSIte
using (SPSite oSiteCollection = new SPSite(spSiteURL))
{
// Get SPWebApplication
SPWebApplication webApp = oSiteCollection.WebApplication;

// Delete any WebApp Mods for this Feature
Collection<SPWebConfigModification> collection = webApp.WebConfigModifications;
int iStartCount = collection.Count;
// Remove any modifications that were originally created by the owner.
for (int c = iStartCount - 1; c >= 0; c--)
{
SPWebConfigModification configMod = collection[c];
if (configMod.Owner == _Owner)
collection.Remove(configMod);
}

// Apply changes only if any items were removed.
if (iStartCount > collection.Count)
{
Microsoft.SharePoint.Administration.SPFarm.Local.Services.GetValue<Microsoft.SharePoint.Administration.SPWebService>().ApplyWebConfigModifications();
webApp.Update();
}
}
});
}

private void DeployReportFiles(SPFeatureReceiverProperties properties)
{
// first remove report files if they exist
RemoveReportFiles(properties);

SPSecurity.RunWithElevatedPrivileges(
delegate()
{
// Get SPSite URL
string spSiteURL = ((SPSite)properties.Feature.Parent).Url;

// Open new instance of SPSIte
using (SPSite oSiteCollection = new SPSite(spSiteURL))
{
// Get SPWebApplication
SPWebApplication webApp = oSiteCollection.WebApplication;

// loop through each IisSettings dictionary pair (zones [dafault, intranet, internet, etc.] configured for web application)
foreach (KeyValuePair<SPUrlZone, SPIisSettings> pair in webApp.IisSettings)
{
// add folder if not exists
if (!Directory.Exists(pair.Value.Path.FullName.ToString() + @"\" + FolderName))
{
Directory.CreateDirectory(pair.Value.Path.FullName.ToString() + @"\" + FolderName);
}

string[] fileList = System.IO.Directory.GetFiles(SPUtility.GetGenericSetupPath(@"TEMPLATE\FEATURES\VBSProReports\Reports"));
foreach (string fil in fileList)
{
string[] finalFile = fil.ToString().Split('\\');
string _destination = pair.Value.Path.FullName.ToString() + @"\" + FolderName + @"\" + finalFile.GetValue(finalFile.Length - 1).ToString();
File.Copy(fil, _destination, true);

// add security
FileSecurity fs2 = File.GetAccessControl(_destination);
FileSystemAccessRule accessRule = new FileSystemAccessRule("Users", FileSystemRights.ReadAndExecute, AccessControlType.Allow);
fs2.AddAccessRule(accessRule);
File.SetAccessControl(_destination, fs2);
}
}
}
});
}

private void RemoveReportFiles(SPFeatureReceiverProperties properties)
{
SPSecurity.RunWithElevatedPrivileges(
delegate()
{
// Get SPSite URL
string spSiteURL = ((SPSite)properties.Feature.Parent).Url;

// Open new instance of SPSIte
using (SPSite oSiteCollection = new SPSite(spSiteURL))
{
// Get SPWebApplication
SPWebApplication webApp = oSiteCollection.WebApplication;

// loop through each IisSettings dictionary pair (zones [dafault, intranet, internet, etc.] configured for web application)
foreach (KeyValuePair<SPUrlZone, SPIisSettings> pair in webApp.IisSettings)
{
string[] fileList = System.IO.Directory.GetFiles(SPUtility.GetGenericSetupPath(@"TEMPLATE\FEATURES\VBSProReports\Reports"));
foreach (string fil in fileList)
{
string[] finalFile = fil.ToString().Split('\\');
if (Directory.Exists(pair.Value.Path.FullName.ToString() + @"\" + FolderName))
{
string _destination = pair.Value.Path.FullName.ToString() + @"
\" + FolderName + @"\" + finalFile.GetValue(finalFile.Length - 1).ToString();
File.Copy(fil, _destination, true);
// if report exists, delete
if (File.Exists(_destination))
File.Delete(_destination);
}
}
}
}
});
}
}
}




5 comments:

  1. Nice work, Mick. I used this to help me automate copying some WCF files on feature activation.

    ReplyDelete
  2. Thanks Mick for sharing knowledge.your are deleting all changes done by owner can i delete and update only one by name ?

    Thanks
    Ronak

    ReplyDelete
  3. Ronak

    Are you saying that you want to deploy a web.config setting on Feature activate, but when the Feature is deactivated you want some of the changes to stay?

    If that is what you are looking for then you would want to change the if statement:

    if (configMod.Owner == _Owner)
    collection.Remove(configMod);

    There are other properties you can filter on such as Path, Name, Sequence, Type and Value. So you might want something like this:

    if (configMod.Owner == _Owner && configMod.Value.toString().Contains('ReportViewerMessages'))
    collection.Remove(configMod);

    ReplyDelete
  4. Thanks Mick for your reply.
    I want to do add,delete and update in ListItemEventReceiver so i think i can filter by name and do action as per that.

    Thanks
    Ronak

    ReplyDelete