Sunday, January 23, 2011

Setting Target Audiences with code

If you search the web you will find many articles explaining how to set audiences on web parts (also on list items). Most are curious - explaining that you need to specify the IDs of the audiences, separated by colons, and then four semi colons to finish it.

Really? four semi colons at the end without any explanation why? I just had to investigate.
It turns out that the four semi colons have a use. The syntax of the "target to" audience field type is as follows:
[Guid Ids separated by comma];;[Active directory groups' LDAP paths separated by line breaks];;[SharePoint security group names separated by commas]

For example:
decd0c08-4649-4e61-a8d6-8fdf5e4017ad,decd0c08-4649-4e61-a8d6-8fdf5e4017ad;;CN=SecGroup,CN=Users,DC=Development,DC=Local
CN=samplegroup1,CN=Users,DC=Development,DC=Local
;;Admin Office, Authors

So if you only have global (user profile) audiences, the value will be guids followed by four semi colons (because the values of the other type of audiences are blank). If you only choose security\distribution lists then you will have two semi colons before them, and two after. If you chose only sharepoint groups you will get 4 semi colons before the group names.

To make things simpler for me, I wrote a small class to create a value for me, or to parse it out for me. Here is the code:

public class AudienceFieldValue
    {
        /// <summary>
        /// A list of the IDs of the sharepoint global audiences (the one defined in the user profile database, accessible with  Microsoft.Office.Server.Audience.AudienceManager
        /// </summary>
        public List<Guid> GlobalAudienceIds;
        /// <summary>
        /// A list of sharepoint security groups
        /// </summary>
        public List<string> SharePointSecurityGroupNames;
        /// <summary>
        /// A list of active directory LDAP paths (CN=SecGroup,CN=Users,DC=Development,DC=Local) pointing to security groups or distribution lists
        /// </summary>
        public List<string> DirectoryGroupsOrDistListLDAPPaths;

        public AudienceFieldValue()
        {
            GlobalAudienceIds = new List<Guid>();
            SharePointSecurityGroupNames = new List<string>();
            DirectoryGroupsOrDistListLDAPPaths = new List<string>();
        }
        /// <summary>
        /// Use this constructor if you have a value from an existing item and you want to parse it. 
        /// You can even join values from more than one item seperated by semi colons. 
        /// For example: AudienceFieldValue val = new AudienceFieldValue(item1["Target Audiences"].toString() +;"+ item1["Target Audiences"].toString())
        /// </summary>
        /// <param name="value">The value of a "audience target to" column from a SPListItem (or more than one, seperated by semi colons)</param>
        public AudienceFieldValue(string value) 
        {
            GlobalAudienceIds = new List<Guid>();
            SharePointSecurityGroupNames = new List<string>();
            DirectoryGroupsOrDistListLDAPPaths = new List<string>();
            string[] arrTargets = value.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            foreach (string audienceGroup in arrTargets)
            {
                if (audienceGroup.Contains("CN="))
                {
                    //it is one or more AD groups (CN) split by line breaks
                    string[] adgroups = audienceGroup.Split('\n');
                    foreach (string audienceCN in adgroups)
                    {
                        DirectoryGroupsOrDistListLDAPPaths.Add(audienceCN);
                    }
                }
                else
                {
                    string[] arrAudiences = audienceGroup.Split(',');
                    foreach (string audienceName in arrAudiences)
                    {
                        try
                        {
                            Guid g = new Guid(audienceName);
                            GlobalAudienceIds.Add(g);
                        }
                        catch (Exception ex)
                        {
                            //its not a guid - so it is a local sharepoint security group
                            SharePointSecurityGroupNames.Add(audienceName);
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Get the value that you can set to a list item's target audience field
        /// </summary>
        /// <returns>A string value containing all the IDs specified in a format that sharepoint understands</returns>
        public override string ToString()
        {
            if (GlobalAudienceIds.Count == 0 && SharePointSecurityGroupNames.Count == 0 && DirectoryGroupsOrDistListLDAPPaths.Count == 0)
            {
                return "";
            }
            else
            {
                StringBuilder result = new StringBuilder();
                //first add any global audiences
                foreach (Guid id in GlobalAudienceIds)
                {
                    if (result.Length > 0)
                        result.Append(",");
                    result.Append(id.ToString());
                }
                //must add two semi colons to seperate even if there is nothing after
                result.Append(";;");
                //add any directory group path, seperated by a line break
                bool addedDirGroup = false;
                foreach (string directoryGroupPath in DirectoryGroupsOrDistListLDAPPaths)
                {
                    if (addedDirGroup)
                        result.Append("\n");
                    result.Append(directoryGroupPath);
                    addedDirGroup = true;
                }
                //must add two semi colons to seperate even if there is nothing after
                result.Append(";;");
                //add any sharepoint group names seperated by commas
                bool addedSPGroup = false;
                foreach (string spGroupName in SharePointSecurityGroupNames)
                {
                    if (addedSPGroup)
                        result.Append(",");
                    result.Append(spGroupName);
                    addedSPGroup = true;
                }

                return result.ToString();
            }
        }
    }

Tuesday, January 18, 2011

Creating static and dynamin menus in the Site Actions menu

I am presenting tonight in the Canberra SharePoint User Group (and will show the code again in the upcoming Australian SharePoint Conference) about adding actions to the site actions menu. Instead of writing all I have to say, I will just share the source code with your - trusting that you will understand the difference between the two methods I am showing (static and dynamic). Enjoy!
Source code can be downloaded from:
MenuActions.zip

Friday, January 07, 2011

Adding Modules or Elements to a VS2010 SharePoint project

If you encounter the error "The Project Item "[Item Name]" cannot be deployed through a Feature with Farm scope." when you are deploying a Visual Studio 2010 SharePoint project, the problem may be that you have done what I have just done - try to force the wrong item type to deploy...
Let me explain: Lets say you want to add a custom action - something that can be deployed as a farm feature. But by mistake you added a project item of type "Module" instead of "Empty Element". You think to yourself - I can just clear the module, and add the custom action instead in the XML. The intellisense definitly supports it.
Well, it turns out that Visual Studio remembers that you added the XML as a module, and even if you edit out the module instructions, it will not let you deploy the feature as a farm feature, since modules are not supported at the farm scope.
The solution? delete your module, and add an empty element.