SharePoint 2010 Audience Membership Workflow Activity Condition for Designer Workflows

One of my students tonight asked if it was possible to add a condition to a SharePoint Designer 2010 declarative workflow to detect if the initiating user is a member of a particular audience.

There’s nothing built-in to deliver this in SharePoint 2010.

So I knocked-up the following solution based upon the excellent reference implementations of workflows from the SharePoint Prescriptive Guidance Pack at spg.codeplex.com.

I’ve put the full version of my source code and a completed release up on CodePlex.

Firstly, the .Actions file, which must be deployed to 14\\Template\\Xml\\1033\\Workflow:

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
  <Conditions And="and" Or="or" Not="not" When="If" Else="Else if">
    <Condition Name="User is member of audience"
        FunctionName="IsUserMemberOfAudienceCondition"
        ClassName="joelblogs.co.uk.WorkflowActivities.AudienceActivity.AudienceMemberActivity"
        Assembly="joelblogs.co.uk.WorkflowActivities.AudienceActivity, 
          Version=1.0.0.0, Culture=neutral, PublicKeyToken=d926d259b11539d4"
        AppliesTo="all"
            UsesCurrentItem="True">
      <RuleDesigner Sentence="The user is a member of audience %1">
        <FieldBind Id="1" Field="_1_" Text=""/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Condition>
  </Conditions>
</WorkflowInfo>

Next, the workflow activity class itself:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;
using Microsoft.Office.Server.Audience;
using System.Workflow.ComponentModel;
using System.ComponentModel;

namespace joelblogs.co.uk.WorkflowActivities.AudienceActivity
{
    /// <summary>
    /// Windows Workflow Activity for SharePoint 2010. Checks if
    /// Initiating User is a member of the specified Audience.
    /// Written by Joel Jeffery, 2011-10-28.
    /// </summary>
    class AudienceMemberActivity : Activity
    {
        /// <summary>
        /// Returns whether the user exists in the specified audience or not
        /// -- signature to match SharePoint Designer Requirement
        /// </summary>
        /// <param name="workflowContext">Environment for activity</param>
        /// <param name="listId">ID of the list the workflow is running on (unused)</param>
        /// <param name="itemId">Item ID of the item the workflow is running on (unused)</param>
        /// <param name="siteUrl">The audience name to determine whether the user is in it or not</param>
        /// <returns>True if site exists, false if not </returns>
        public static bool IsUserMemberOfAudienceCondition(
            WorkflowContext workflowContext, string listId, int itemId, string audienceName)
        {
            string exception;
            return (IsUserMemberOfAudience(
                workflowContext.InitiatorUser.LoginName, audienceName, out exception));
        }

        /// <summary>
        /// Determines whether [is user member of audience] [the specified login name].
        /// </summary>
        /// <param name="loginName">Name of the login.</param>
        /// <param name="audienceName">Name of the audience.</param>
        /// <param name="exception">The exception.</param>
        /// <returns>
        ///   <c>true</c> if [is user member of audience] [the specified login name]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsUserMemberOfAudience(string loginName, string audienceName, out string exception)
        {
            try
            {
                exception = null;
                SPServiceContext context = SPServiceContext.Current;
                AudienceManager audManager = new AudienceManager(context);
                return audManager.IsMemberOfAudience(loginName, audienceName);
            }
            catch (Exception e)
            {
                exception = e.ToString();
                return (false);
            }
        }

        public static DependencyProperty AudienceNameProperty = 
            DependencyProperty.Register("AudienceName", typeof(string), typeof(AudienceMemberActivity));

        [Description("The absolute URL of the site or site collection to create")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string SiteUrl
        {
            get { return ((string)base.GetValue(AudienceNameProperty)); }
            set { base.SetValue(AudienceNameProperty, value); }
        }

        public static DependencyProperty ExistsProperty = 
            DependencyProperty.Register("Exists", typeof(bool), typeof(AudienceMemberActivity));
        [Description("The result of the operation indicating whether the site exists or not")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public bool Exists
        {
            get { return ((bool)base.GetValue(ExistsProperty)); }
            set { base.SetValue(ExistsProperty, value); }
        }

        public static DependencyProperty ExceptionProperty = 
            DependencyProperty.Register("Exception", typeof(string), typeof(AudienceMemberActivity));
        [Description("The exception generated while testing for the existance of the site")]
        [Browsable(true)]
        [Category("joelblogs.co.uk Activities")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string Exception
        {
            get { return ((string)base.GetValue(ExceptionProperty)); }
            set { base.SetValue(ExceptionProperty, value); }
        }
    }
}

 

You’ll also need a terribly clever feature receiver implementation from the SPG that uses the SPWebConfigModification class to add AuthorizedType blocks to the web.configs throughout our farm, or our class won’t be loaded by WF.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace joelblogs.co.uk.WorkflowActivities.AudienceActivity.Features.AudienceTestActivity
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, 
    /// installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>

    [Guid("a91d2258-b39b-4ca4-8282-2565c061378d")]
    public class AudienceTestActivityEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWebService contentService = SPWebService.ContentService;
                contentService.WebConfigModifications.Add(GetConfigModification());
                // Serialize the web application state and propagate changes across the farm. 
                contentService.Update();
                // Save web.config changes.
                contentService.ApplyWebConfigModifications();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw;
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWebService contentService = SPWebService.ContentService;
                contentService.WebConfigModifications.Remove(GetConfigModification());
                // Serialize the web application state and propagate changes across the farm. 
                contentService.Update();
                // Save web.config changes.
                contentService.ApplyWebConfigModifications();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw;
            }
        }

        public SPWebConfigModification GetConfigModification()
        {
            string assemblyValue = typeof(AudienceMemberActivity).Assembly.FullName;
            string namespaceValue = typeof(AudienceMemberActivity).Namespace;

            SPWebConfigModification modification = new SPWebConfigModification(
                string.Format(CultureInfo.CurrentCulture,
                    "authorizedType[@Assembly='{0}'][@Namespace='{1}']" +
                    "[@TypeName='*'][@Authorized='True']", assemblyValue, namespaceValue),
                "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes");

            modification.Owner = "joelblogs.co.uk";
            modification.Sequence = 0;
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value =
                string.Format(CultureInfo.CurrentCulture,
                "<authorizedType Assembly=\"{0}\" Namespace=\"{1}\" " + 
                "TypeName=\"*\" Authorized=\"True\" />", assemblyValue, namespaceValue);

            Trace.TraceInformation("SPWebConfigModification value: {0}", modification.Value);

            return modification;
        }
    }
}

You can download the full source code to my SharePoint 2010 Audience Membership Workflow Activity (Full Trust) here: http://spamwaft.codeplex.com.

Comments

  1. Kyle Lipkin says

    I am wondering if you can tweak this workflow to email every user in the audience. for example, I need a custom action to say, IF this then Email this audience.

    Can you help me?

    Thanks!

    • Joel Jeffery says

      Hi Aleksandr!

      I like the concept of the HarePoint C# “Execute Custom Code” action. Just paste in code and run! Simples!

      But…

      Does that not allow users to run code as the Farm account? I guess you guys must have put in code validation – or at least a try/catch ;) – around the compiler code… but how to you ensure that any user with Contribute permission doesn’t paste in malicious code? Maybe you’ve done something incredibly smart like run the compiled code in the Sandbox SPUCWorkerProcess.exe? That would be cool.

      I’d be really keen to find out, as I have customers that would really benefit from this feature!

      Cheers, and thanks for linking to me!

      joel

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>