.NET, SharePoint, Windows Workflow

How to Deploy a Custom Activity as a Feature

Henry asked me to figure out a way to easily deploy the custom activity I wrote, so with a suggestion and a pointer from him I was on my way.  The end result is deployment of the activity as a feature.  Here’s how it’s done.

The concept is simple.  Use the normal Feature deployment framework to do the three things needed to deploy the activity.  Those are:

  1. Deploy the dll to the GAC
  2. Add the .ACTIONS file to the 12 hive
  3. Update the authorizedTypes in the web.config

The first two are pretty simple, but I’ll show the manifest so you get an idea if you’re not sure how that works.

<Solution SolutionId="153F5D62-5662-4288-B915-903F5B950E39" xmlns="http://schemas.microsoft.com/sharepoint/" ResetWebServer="TRUE">

<
FeatureManifests>
<
FeatureManifest Location="CustomActivitiesFeature.xml"/>
</
FeatureManifests>

<
RootFiles>
<
RootFile Location="TEMPLATE1033WorkflowFindManager.ACTIONS" />
</
RootFiles>

<
Assemblies>
<
Assembly DeploymentTarget="GlobalAssemblyCache" Location="MyAssembly.dll">
<
SafeControls>
<
SafeControl Assembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4" Namespace="MyNamespace" TypeName="*" />
</
SafeControls>
</
Assembly>
</
Assemblies>
</
Solution>

To update the authorizedTypes in the web.config I needed some help (remember, I’m new!)  Henry pointed me to the SPWebConfigModification class and the concept of an SPFeatureReceiver.  The theory was to use an SPFeatureReceiver and override the FeatureActivated method and use that to call into SPWebConfigModification to update the web.config file with the new authorizedType entry.  So with that, I was off!!

Other than the MSDN documentation, I found a couple other valuable resources.  The first was a post by Tony Bierman on Using SPWebConfigModification to modify a SharePoint web application’s web.config for all servers in the farm.  Sounds like what I need, right?!  Just about.  The biggest thing missing was how to get the right SPWebApplication reference.  His example was using a console app in which the web app was passed in as a parameter.  I played around with SPContext (not sure why, since there’s no http context during deployment of a wsp), as well as different options using the Parent of the Feature.  I never quite figured it out.  Then I ran into Daniel Larson’s posting on the same subject.  (I think he and I ran into each other a few years ago on a project in Parker, by the way.  I’m not sure.)

Dan was grabbing the web app using the following:

        SPWebApplication app = null;
        SPSiteCollection site = properties.Feature.Parent as SPSiteCollection;
        if (site == null)
        {
            SPWeb web = properties.Feature.Parent as SPWeb;
            if (web != null)
                app = web.Site.WebApplication;
        }
        else
            app = site.WebApplication;

Basically, using properties.Feature.Parent, as I was attempting.  He first checks to see if it’s a Site Collection and then, if not, assumes it’s a Web.  Well, guess what?  I wasn’t deploying my feature as either!  Maybe I need to rethink that, but I’m deploying it to a specific Site.  This helped me get to the right web application by doing:

        SPSite site = (SPSite)properties.Feature.Parent;
        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

After that I needed to spend some time reviewing how SPWebConfigModification works.  I started out by creating a modification object for my authorizedTypes entry and calling webApp.WebConfigModifications.Add, but that didn’t work.  If you take a look at the MSDN docs you’ll see why I started there.  I was missing a few steps before I could call .Add.  I needed to add a few more settings to the modification, as you can see in the code below.  That’s about it for the tricky stuff, so I’ll post the code.  One more thing to notice, though, is that I also overrode FeatureDeactivating to remove the .config entry.

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPSite site = (SPSite)properties.Feature.Parent;
                SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

                if (webApp != null)
                    UpdateWebConfig(webApp, false);
                else
                    throw new ApplicationException(“Could not locate a web application”);
               
                //Write log entry for success
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(“Activation complete.”);
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(ex.Message);
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPSite site = (SPSite)properties.Feature.Parent;
                SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

                if (webApp != null)
                    UpdateWebConfig(webApp, true);
                else
                    throw new ApplicationException(“Could not locate a web application”);

                //Write log entry for success
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(“De-Activation complete.”);
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(ex.Message);
            }
        }

        private static void UpdateWebConfig(SPWebApplication webApp, bool removeModification)
        {
            SPWebConfigModification modification =
                new SPWebConfigModification(“authorizedType[@Assembly=”MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4″][@Namespace=”MyNamespace”][@TypeName=”*”][@Authorized=”True”]”, “configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes”);
         
            modification.Owner = “Ryan”;
            modification.Sequence = 0;
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value =
                 string.Format(CultureInfo.InvariantCulture,
                 “<authorizedType Assembly=”{0}” Namespace=”{1}” TypeName=”{2}” Authorized=”{3}”/>”,
                 new object[] { “MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4”, “MyNamespace”, “*”, “True” });

            if (removeModification)
                webApp.WebConfigModifications.Remove(modification);
            else
                webApp.WebConfigModifications.Add(modification);

            SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
        }

Leave a Reply

Your email address will not be published. Required fields are marked *