Deploying workflow actions with SharePoint features

You can use a SharePoint feature, which interacts with both SharePoint 2013 and Nintex Workflow 2013, to programmatically deploy a custom workflow action.

Note: The following procedures assume that you have used the Custom Workflow Action Adapter project template, included with the Nintex Workflow 2013 SDK, to implement a workflow action adapter.

The feature is added to the SharePoint solution project containing the workflow action adapter, and an event receiver is added to the feature to interact with Nintex Workflow 2013 object model. The event receiver provides functionality similar to that provided in Nintex Workflow Management for manually deploying a custom workflow action in Nintex Workflow 2013.

Developing a SharePoint feature for deploying a custom workflow action typically involves the following steps:

Adding an element for the action definition file

The SharePoint feature needs to interact with the action definition (.nwa) file defined for the custom workflow action adapter. Adding an element to the SharePoint solution project in which the action definition file is stored allows the event receiver to more easily find the action definition file during the deployment of the custom workflow action. The following procedure creates a new empty element, named NWAFile, into which the action definition file for the custom workflow action adapter is moved.

To add an element for the action definition file

  1. In Visual Studio 2012, right-click the project for the custom workflow action adapter in Solution Explorer, select Add, and then click New Item.

  2. In the Add New Item dialog, select the Empty Element item template, under the Office/SharePoint category.

  3. Set the value of Name to NWAFile.

  4. Click Add.

  5. In Solution Explorer, delete the Elements.xml file from the NWAFile element.

  6. Drag and drop the action definition file on the NWAFile element.

    The action definition file should now appear as a child of the NWAFile element in Solution Explorer.

  7. Select the action definition file.

  8. In Properties, set the following properties to the specified values:

    Property name Property value
    Deployment Location {SharePointRoot}\Template\Features\{FeatureName}\
    Deployment Type ElementFile

Adding the SharePoint feature

A new SharePoint feature is added to the SharePoint project for the custom workflow action adapter. The new feature manages the deployment of the custom workflow action.

To add the deployment SharePoint feature in Visual Studio 2012

  1. In Solution Explorer, expand the SharePoint 2013 project for the custom workflow , right-click the Features folder, and then click Add Feature.

  2. Set the values of Title and Description to appropriate values, as desired for your solution.

    These values are displayed in the Manage Web Application Features dialog of SharePoint Central Administration.

  3. Set the value of Scope to WebApplication.

  4. Add the NWAFile element to the feature.

  5. In Packaging Explorer, select the feature.

  6. In Properties, set the following properties to the specified values:

    Property name Property value
    Activate on Default False
    Always Force Install True
    Image Url $Resources:NWResource,WebPartFeature_ImageUrl_2010;
    Version 14.0.0.0

Creating the event receiver

The event receiver for the SharePoint feature performs the following tasks when the feature is activated in SharePoint:

The event receiver handles the following events, by performing the actions described for that event:

To add an event receiver to the feature

  1. In Visual Studio 2012, right-click the SharePoint feature, and then click Add Event Receiver.

  2. Add the following directives to the event receiver:

    using Microsoft.SharePoint.Administration;
    using System.Xml;
    using Nintex.Workflow;
    using Nintex.Workflow.Common;
    using Nintex.Workflow.Administration;
    using Microsoft.SharePoint.Utilities;
    using System.Collections.ObjectModel;
    using System.IO;
    
  3. Add the following code to the class for the event receiver, at the beginning of the class:

    // The path and file name, relative to the feature, of the action definition file.
    // TODO: This value must match the relative path & file name of the action definition file as 
    //       displayed in the designer for the SharePoint feature.
    const string pathToNWA = "ExecuteSqlScalarAction.nwa";
    // The full name of the .NET Framework type that represents the workflow action adapter.
    // TODO: This value must match the value of the AdapterType element in the action definition file.
    const string adapterType = "Crestan.NintexWorkflowActions.ExecuteSqlScalarActionAdapter";
    // The full four-part name of the .NET Framework assembly that represents the workflow action adapter.
    // TODO: This value must match the value of the AdapterAssembly element in the action definition file.
    const string adapterAssembly = "Crestan.NintexWorkflowActions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8e8490bab64b4ec6";
    
  4. Add the following code to the class for the event receiver, at the end of the class:

    private XmlDocument GetNWADefinition(SPFeatureReceiverProperties properties)
    {
        // Using the NWAFile element defined in the SharePoint feature, load the
        // action definition file as an XML document.
        using (Stream stream = properties.Definition.GetFile(pathToNWA))
        {
            XmlDocument nwaXml = new XmlDocument();
            nwaXml.Load(stream);
    
            return nwaXml;
        }
    }
  5. Replace the commented FeatureActivated method of the class for the event receiver with the following code:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        // Retrieve a reference to the parent web application for the feature.
        SPWebApplication parent = (SPWebApplication)properties.Feature.Parent;
    
        // Retrieve the contents of the action definition file.
        XmlDocument nwaXml = GetNWADefinition(properties);
    
        // Instantiate an ActivityReference object from the action definition file.
        ActivityReference newActivityReference = ActivityReference.ReadFromNWA(nwaXml);
    
        // Attempt to instantiate an ActivityReference object from the the workflow action adapter 
        // identified by the AdapterType and AdapterAssembly elements from the action definition file.
        // For new deployments, action is set to null; otherwise, the existing ActivityReference 
        // for the custom workflow action is retrieved.
        ActivityReference action = ActivityReferenceCollection.FindByAdapter(
            newActivityReference.AdapterType, newActivityReference.AdapterAssembly);
    
        // If the custom workflow action has been previously deployed, 
        // update the ActivityReference for the custom action; otherwise, 
        // add a new Activityreference for the custom action and then
        // instantiate it. 
        if (action != null)
        {
            // Update the ActivityReference for the custom workflow action.
            ActivityReferenceCollection.UpdateActivity(
                action.ActivityId, 
                newActivityReference.Name, 
                newActivityReference.Description, 
                newActivityReference.Category, 
                newActivityReference.ActivityAssembly, 
                newActivityReference.ActivityType, 
                newActivityReference.AdapterAssembly, 
                newActivityReference.AdapterType, 
                newActivityReference.HandlerUrl, 
                newActivityReference.ConfigPage, 
                newActivityReference.RenderBehaviour, 
                newActivityReference.Icon, 
                newActivityReference.ToolboxIcon, 
                newActivityReference.WarningIcon, 
                newActivityReference.QuickAccess, 
                newActivityReference.ListTypeFilter);
        }
        else
        {
            // Add a new ActivityReference for the custom workflow action.
            ActivityReferenceCollection.AddActivity(
                newActivityReference.Name, 
                newActivityReference.Description, 
                newActivityReference.Category, 
                newActivityReference.ActivityAssembly, 
                newActivityReference.ActivityType, 
                newActivityReference.AdapterAssembly, 
                newActivityReference.AdapterType, 
                newActivityReference.HandlerUrl, 
                newActivityReference.ConfigPage, 
                newActivityReference.RenderBehaviour, 
                newActivityReference.Icon, 
                newActivityReference.ToolboxIcon, 
                newActivityReference.WarningIcon, 
                newActivityReference.QuickAccess, 
                newActivityReference.ListTypeFilter);
    
            // Instantiate the newly-added ActivityReference.
            action = ActivityReferenceCollection.FindByAdapter(
                newActivityReference.AdapterType, newActivityReference.AdapterAssembly);
        }
    
        // Add a modification to the web.config file for the web application, to install the 
        // custom workflow activity to the collection of authorized activity types for the 
        // web application.
        string activityTypeName = string.Empty;
        string activityNamespace = string.Empty;
    
        // Extract the type name and namespace name from the value of the ActivityType property.
        Utility.ExtractNamespaceAndClassName(action.ActivityType, 
            out activityTypeName, out activityNamespace);
    
        // Add the assembly, namespace, and type of the workflow activity to the collection of 
        // authorized activity types for the web application.
        AuthorisedTypes.InstallAuthorizedWorkflowTypes(parent, 
            action.ActivityAssembly, activityNamespace, activityTypeName);
    
        // Activate the custom workflow action. 
        ActivityActivationReference reference = new ActivityActivationReference(
            action.ActivityId, Guid.Empty, Guid.Empty);
        reference.AddOrUpdateActivationReference();
    }
  6. Add the following code to the class for the event receiver, at the end of the class:

    private bool IsAuthorizedTypeMatch(string modification, string activityAssembly, string activityType, string activityNamespace)
    {
        // Indicates whether the specified type, namespace, and assembly match for the authorized type
        // in the specified web config modification. 
    
        // Load the web.config modification as an XML document.
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(modification);
    
        // If the XML document contains an authorizedType element, compare the values of the 
        // TypeName, Namespace, and Assembly attributes against the specified 
        // type, namespace, and assembly for the workflow activity. If authorizedType does
        // not exist, or if the attribute values do not match the specified values, return
        // false; otherwise, return true.
        if (doc.FirstChild.Name == "authorizedType")
        {
            return (doc.SelectSingleNode("//@TypeName").Value == activityType
                    && doc.SelectSingleNode("//@Namespace").Value == activityNamespace
                    && doc.SelectSingleNode("//@Assembly").Value == activityAssembly);
    
        }
    
        return false;
    }
    
    private bool IsFeatureActivatedInAnyWebApp(SPWebApplication thisWebApplication, Guid thisFeatureId)
    {
    
        // Indicates whether the feature is activated for any other web application in the 
        // SharePoint farm.
    
        // Attempt to access the Web service associated with the content application.
        SPWebService webService = SPWebService.ContentService;
        if (webService == null)
            throw new ApplicationException("Cannot access the Web service associated with the content application.");
    
        // Iterate through the collection of web applications. If this feature is 
        // activated for any web application other than the current web application,
        // return true; otherwise, return false.
        SPWebApplicationCollection webApps = webService.WebApplications;
        foreach (SPWebApplication webApp in webApps)
        {
            if (webApp != thisWebApplication)
                if (webApp.Features[thisFeatureId] != null)
                    return true;
        }
    
        return false;
    }
  7. Replace the commented FeatureDeactivating method of the class for the event receiver with the following code:

    public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
    {
        // Retrieve a reference to the parent web application for the feature.
        SPWebApplication parent = (SPWebApplication)properties.Feature.Parent;
    
        // Instantiate an ActivityReference object from the 
        // workflow action adapter identified by the constants defined in this class.
        ActivityReference action = ActivityReferenceCollection.FindByAdapter(adapterType, adapterAssembly);
    
        if (action != null)
        {
            // If the feature is not activated in any other web application, remove the action 
            // definition from the configuration database.
            if (!IsFeatureActivatedInAnyWebApp(parent, properties.Definition.Id))
                ActivityReferenceCollection.RemoveAction(action.ActivityId);
    
            // Remove the modification from the web.config file for the web application, to uninstall the 
            // custom workflow activity from the collection of authorized activity types for the 
            // web application.
            string activityTypeName = string.Empty;
            string activityNamespace = string.Empty;
    
            // Extract the type name and namespace name from the value of the ActivityType property.
            Utility.ExtractNamespaceAndClassName(action.ActivityType, out activityTypeName, out activityNamespace);
    
            // Identify and remove the modification from the web.config file.
            Collection<SPWebConfigModification> modifications = parent.WebConfigModifications;
            foreach (SPWebConfigModification modification in modifications)
            {
                // If the modification was added by Nintex Workflow, compare the assembly, namespace, and type of the workflow activity to the collection of 
                // authorized activity types in the modification. If they match, remove the modification.
                // NOTE: AuthorizedTypes.OWNER_TOKEN is the owner token for any modification added by 
                // Nintex Workflow.
                if (modification.Owner == AuthorisedTypes.OWNER_TOKEN) 
                {
                    if (IsAuthorizedTypeMatch(modification.Value, action.ActivityAssembly, activityTypeName, activityNamespace))
                    {
                        // Remove the modification.
                        modifications.Remove(modification);
                        // Apply the updated modifications to the SharePoint farm containing the web application.
                        parent.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
                        break;
                    }
                }
            }
        }
    }

Deploying and publishing the solution

You can deploy the solution to a local SharePoint site directly from Visual Studio 2012, for testing purposes. Once tested, you can then publish the solution as a SharePoint solution package (.wsp) file that can be redistributed and deployed to a SharePoint farm using various tools, such as Windows PowerShell.

For more information about how to deploy and publish a SharePoint solution in Visual Studio 2012, see How to: Deploy and Publish a SharePoint Solution to a Local SharePoint Site.

Activating the feature

Once the solution is deployed to your SharePoint farm, the feature contained by the solution must be activated for each web application that will contain workflows which use the custom workflow action. You can activate a feature for a web application using a variety of tools, such as SharePoint Central Administration or Windows PowerShell.

When the feature is activated in SharePoint, the custom workflow action represented by the feature is also added to the list of allowed actions in Nintex Workflow 2013, as one of the tasks performed by the event receiver when the FeatureActivated method is invoked for the feature.