SharePoint 2007

Activating Features in a SharePoint Farm

I have been pounding my head against a wall for the last few days trying to get a couple of features to activate in my projects new High Availability Environment (HA).  The environment consists of two MOSS Web Front Ends (WFE) named web1 and web2.  Our env. also includes web3 which runs the Central Admin site, but does not have any of the externally accessible sites (the ones that run on port 80 and 443).  This has lead to the following issues.

  1. Attempt to activate the feature fails when executed from the web app.  This issue was easily resolved when I read The Kid’s blog, basically the permissions proposed by Microsoft for the MOSS configuration does not allow this.
  2. Actual values never appear in the web.config for web1 or web2
    1. I have a theory about this issue.  In our development environment we have two WFEs, but one of them is also running Central Admin.  I believe that I need to have the web applications running on the server that is also running Central Admin in order for this to work.  As I work through this issue with our system admins I will update my findings.
    2. UPDATE: After activating the Microsoft SharePoint Web Application Services on the Central Admin server the Web Config modifications were propagated to all WFE’s in the farm.

Per the results here is my recommendation about configuring a SharePoint environment.  When you are running within a SharePoint farm ensure that the Central Admin server is also running the Web Application Service.  You do not necessarily have to allow it to be one of the Network Load Balanced WFE’s but having it running the Web Applications seems to be important to allow configuration changes through out the farm.

Since I have now gotten this to work consistently I feel comfortable with my code implementation, so here it is.  This IS how you can get a SharePoint Solution Feature Listener to actually modify the web.config file in a single server or multi-server SharePoint installation.

First we create the class that is going to be our feature listener.  To do this using Visual Studio create a new class file and add inheritance from SPFeatureReceiver.  After adding this inheritance add the appropriate "using" statement, this can be done using the Intelli-Sense menu.  Next, using the Intelli-Sense menu add the four abstract methods FeatureActivated, FeatureDeactivating, FeatureInstalled, FeatureUninstalling.  For the Installing, and Uninstalling functions we will just leave those blank. 

My personal preference is to now create a function that will return a list of SPWebConfigModifications, however I have seen other concepts on the web as to how best create the SPWebConfigModifications so it is up to you.  For this blog I am simply going to assume we have a function that returns a list of SPWebConfigModificaitons, the method you choose is up to you.

FeatureActivated

        public override void FeatureActivated(SPFeatureReceiverProperties properties)

        {

            List<SPWebConfigModification> modifications = CreateConfigModification();

            SPWebApplication webApp = null;

 

            if (properties.Feature.Parent is SPSite)

            {

                SPSite spSite = properties.Feature.Parent as SPSite;

                webApp = spSite.WebApplication;

            }

            else if (properties.Feature.Parent is SPWebApplication)

            {

                webApp = properties.Feature.Parent as SPWebApplication;

            }

 

            foreach (SPWebConfigModification mod in modifications)

            {

                webApp.WebConfigModifications.Add(mod);

            }

 

            webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

            webApp.Update();

        }

  1. To allow a more generic approach to the activation getting the SPWebApplication object is done so we can handle Web Application, Site, etc. feature activation scopes.
  2. Once we have created the list of SPWebConfigModifications we simply loop through it and apply them to the SPWebApplication object.
  3. Finally, call the SPWebApplication’s Farm Service to Apply the web.config modifications.  This causes the changes to setup across the farm.  ALWAYS follow this with the SPWebApplication’s update function, which essentially causes the modifications to "stick".

FeatureDeacting

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

            List<SPWebConfigModification> modifications = CreateConfigModification();

            SPWebApplication webApp = null;

 

            if (properties.Feature.Parent is SPSite)

            {

                SPSite spSite = properties.Feature.Parent as SPSite;

                webApp = spSite.WebApplication;

            }

            else if (properties.Feature.Parent is SPWebApplication)

            {

                webApp = properties.Feature.Parent as SPWebApplication;

            }

 

            foreach (SPWebConfigModification mod in modifications)

            {

                webApp.WebConfigModifications.Remove(mod);

            }

 

            webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

            webApp.Update();

        }

  1. To remove the modifications simply cycle through the same list of modifications we used orignally during activation.  I like this method because it is very clean and a bit more efficient.  However there is a problem with this approach!  If you do not name your modification correctly the modification will not be removed.  Therefore you may wish to cycle through the SPWebApplication’s WebConfigModification list first matching the owner name to this feature’s owner name and then cycle though that list removing the modifications as shown below

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

            List<SPWebConfigModification> modifications = new List<SPWebConfigModification>();

            SPWebApplication webApp = null;

 

            if (properties.Feature.Parent is SPSite)

            {

                SPSite spSite = properties.Feature.Parent as SPSite;

                webApp = spSite.WebApplication;

            }

            else if (properties.Feature.Parent is SPWebApplication)

            {

                webApp = properties.Feature.Parent as SPWebApplication;

            }

 

            foreach (SPWebConfigModification mod in webApp.WebConfigModifications)

            {

                if (mod.Owner.Equals(this.GetType().ToString()))

                    modifications.Add(mod);

            }

 

            foreach (SPWebConfigModification mod in modifications)

            {

                webApp.WebConfigModifications.Remove(mod);

            }

 

            webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

            webApp.Update();

        }

  1. I usually use the typename as the owner, since the typename’s must all be unique this ensures that all of my owners are unique.  I have also seen other developers use the Feature ID for the Owner property, that is up to you but make sure your’s is always going to be unique if you use this method.

Now you can activate and deactivate the feature for the farm.  I want to recommend adding The Kid’s Web Modification Manager page to your central admin site as well.  This will help you see if and when the modifications have been added, and will also allow you to remove ones that get orphaned.  If you are doing activation and deactivation especially in the development process, this will be a life (or sanity) saver.

A web configuration modification operation is already running.

I have been using the SPWebConfigModification class for a while, and every now and then I hit the issue that when I attempt to activate a feature that modifies the web.config file I get the following error:

A web configuration modification operation is already running. at Microsoft.SharePoint.Administration.SPWebService.ApplyWebConfigModifications()
  at …

The biggest issue here is that I can no longer activate any other features that are going to edit the web.config, BIG PROBLEM!  So with some investigation here is what I have found that will allow you to overcome this issue.  Open your SharePoint Central Admin site, click on the Operations tab and click on the Job Definitions link under the Global Configuration heading.  Here you will see a number of Timer Jobs that execute Daily, Disabled, Hourly, Mintues, etc.  Here we are looking for a timer job titled Windows SharePoint Services Web.Config Update which executes One Time and will have an N/A listed for the Web Application.  If you click on the title you will be directed to the Edit Timer Job page.  This page provides you with three options, Delete, OK, Cancel.  You can probably guess the next step, but before I confirm let me provide a warning.  BY DELETING THE TIMER JOB BAD THINGS COULD HAPPEN!  Now, go ahead and delete the timer job.  You should now be able to activate your feature.

Here are a few blogs about how to properly deploy and activate features that should help you avoid this issue.

Modifying the web.config with SPWebConfigModifications

A confusing aspect of using the SPWebConfigModifications is what should you use as the Name of your modification?

Here are my recommendations, I do not claim to have a complete or exact list, but this should help get you started.

  1. If you are adding items that will have repeating xml node names , like section or add, then your name should be something like the following to prevent overwriting any previously added modifications.
    1. add[@name=’name’] for <add name="name" …/>
    2. section[@name=’name’] for <section name="name" …/>
    3. etc.
  2. If you are adding custom xml nodes that will have child nodes then you should use the name of that node, this includes standard nodes located in the web.config file.
    1. membership for <membership>…</membership>
    2. etc.

If you need help, have haning web.config modifications etc, here is a link I found that provides a nice .aspx page that will allow you to remove those changes.  Also available at the link is more information about doing the web.config changes using SPWebConfigModification objects.

http://blog.thekid.me.uk/archive/2007/03/24/web-config-modification-manager-for-sharepoint.aspx

If you fail to name your modification properly then you might find that the next modification actually overwrites what you just added.


Another question I run across often is how to change the attribute of a tag that already exists.  It actually turns out that this is very simple.  Using the membership section of a web.config I will show you.

<system.web>
  <membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
      <providers>
        <clear />
        <add name="AspNetActiveDirectoryMembershipProvider" type="…" connectionStringName="…" … />
      </providers>
  </membership>
</system.web>

Lets say we have our own membership provider and you plan on referencing it using the name "MyCustomMembershipProvider" your SPWebConfigModification would look like the following.  Rember you would have to also add a membership provider using a SPWebConfigModification and the naming described above.

SPWebConfigModification membershipProviderPrimary = new SPWebConfigModification("defaultProvider", "configuration/system.web/membership");
                membershipProviderPrimary.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
                membershipProviderPrimary.Sequence = 0;
                membershipProviderPrimary.Owner = GetType().ToString();
                membershipProviderPrimary.Value = "MyCustomMembershipProvider";

Notice here set the name to the name of the xml attribute who’s value we want to set.  The XPath provided does not include the xml attribute, only the xml node which contains that attribute.  Finally, we set the SPWebConfigModification’s value to the desired value ("MyCustomMembershipProvider"). Once this change is commited you will see that the defaultProvider has been changed to "MyCustomMembershipProvider".

SharePoint Web Application Extension

The urls below are ficticious so if you attempt to navigate to them.  I have no idea what will happen nor do I claim responsibility or support for any content on the http://www.devsite.org if it even exits.  I have used this name in an attempt to provide a complete example of what occoured and how to correct the problem.
 
A team member recently deployed a web application capability to our development environment (web application name: devsite80) which caused an error in the web.config.  When I went to central admin to deactivate the feature I tested using our internal url (http://devsite) which is the default zone’s url for devsite80 in SharePoint alternate access mappings.  The site was back up, then I attempted to reconnect to our dev site from my local machine, again using the public address.  The site was down, with the same web.config issue.  What happened?
 
Background
When we created the public development site, we extended the internal development site (http://devsite) site.  This allowed the public development site to be configured in the same mannor as the internal development site, but also added the proper host headers, etc for us.
 
When features were deployed and activated everything worked fine.  When feature were deactivated, only the http://devsite site was effected.  (Quick Note:  In Central Admin only http://devsite was avaible for deploy/activate/deactivation, http://www.devsite.org was not.)  After inspecting the file system we noted that there were two independent folders, devsite80 and http://www.devsite.org80.  The other thing I found was that devsite80’s web.config had the feature’s xml changes removed, but http://www.devsite.org80’s web.config did not.  We, a system admin and myself, decided that managing one folder would be much easier than trying to manage two (kinda obvious).  So here is what we did.
 
Steps
  1. Central Admin->Application Management->Remove SharePoint from IIS Web Site
    1. Selected http://devsite as the Web Application
    2. Selected http://www.devsite.org from the Select IIS Web site and zone to remove
    3. Selected the Yes radio button for Delete IIS Web sites
    4. OK
  2. Central Admin->Operations->Alternate access mappings
    1. Select Add Internal Urls
      1. Selected Devsite80 from the Alternate Access Mapping Collection
      2. Entered http://www.devsite.org for the URL protocol, host and port
      3. Selected Internet from the Zone dropdown
      4. Save
  3. Opened IIS management console (Right Click My Computer->Manage)
    1. Navigated to the Web Sites folder (IIS Manager->local computer->Web Sites)
    2. Right clicked on Harmonieweb80 site and selected Properties
      1. Selected Advanced… on the Web Site tab (next to the IP Address drop down list)
      2. Selected Add… in the Multiple identities for this Web site.
        1. Selected (All Unassigned) from the dropdown list for the IP Address (This site did not have any specific IP address assigned so this was appropriate, you might have to select an appropriate IP address)
        2. Entered 80 for the TCP port
        3. Entered http://www.devsite.org for the Host Header value
        4. OK
      3. OK
    3. Apply
    4. OK
  4. Restart IIS

Following this we ended up with one web application, one web application folder, and the ability to activate and deactivate successfully.  We were also able to now access the site from both the http://devsite and the http://www.devsite.org urls.

Recommendation

If you ever need to add additional urls for a site use alternate access mapping with host headers.  This may take you a few extra steps, but for management it will save you a lot of time.  DO NOT use web application extension to create a new zone entry for an already existing application unless you want them to function and act independently.

Spell Check Your Code (Funy Storie)

Before I tell this story I must admit that without Spell Check I never would have graduated college.  It is not because I mistyped words, I am a horrible speller!
 
I recently developed a solution allowing SharePoint KPIs to be associated with GeoRSS polygons.  The solution also leveraged IDV Solutions (www.idvsolutions.com) Visual Fusion Suite as the map display and region creation tool.  The KPI would be used to determine the color of the region when it was activated on the map.  This required me to perform the KPI calculations for the dynamic KPIs, could not find any method of getting the dynamic value.  One of the dynamic calculations is to find the Minimum value of the items in the list…simple enough.  I developed the code and tested, fixed a few bugs and then set it on to our tester.  While our tester was looking at the tool another developer performed a code review and remarked that I had misspelled "Minimun" in the code.  So I went back and fixed the issues from the code review and redeployed for our tester.  Next day I spoke with the tester and several tests had all of a sudden begun to fail!  This was very strange because most of the code review comments had been violations of our variable and method naming schema, code compiled so all of that should have been working…
 
After using U2U’s CAML viewer I discoverd I hadn’t misspelled Minimun, SharePoint actually uses Minimun!  Of course the value in the User Interface is spelled Minimum.
 
Moral of the story, spell check your code.  I have been told this before in software development classes and seminars, and never really understood, cared, or ever did it.  I understand now, that simple spelling mistake can really cause issues for others using your code.  Worse it can cause you problems later in your code when you actually spell that variable name correctly.

JavaScript your WebControls

I recently ran into an issue recently where I needed to get javascript access to controls that were created programatically to my WebControl/WebPart.  I passed the control’s ID to the javascript function, which was rendered as the page was rendered, but was still not able to get access to the control.  When I looked at the HTML rendering of the page I found that the ID of the control, when I requested it from within my C# code was not the same as what the webpart rendered in the HTML. 
 
Because the webpart class implents the INamingContainer interface all of its controls are added to the page with IDs that include namespacing from the INamingContainer interface.  This can be a major pain to get around, but there is a very simple resolution. 
 
By adding:
 
myControl.Attributes.Add("ID", myControl.ClientID);
 
I was able to override the INamingContainer’s ID attribute and use the ClientID I was expecting.