1
\$\begingroup\$

I have some Managed Metadata in SharePoint which is in the following format:

Managed Metadata Service
    People
        Job Title

And I have a list of people, each of whom have a name and a job title, called MyTestList.

What I needed to do was display these people in a TreeView, sorted by their job titles, where some job titles are children of others:

enter image description here

Here is the main class of the SharePoint WebPart:

public partial class MyWebPartUserControl : UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        var url = "http://sharepointdev/";
        var subsite = "";
        var listName = "MyTestList";

        var termStore = "Managed Metadata Service";
        var termGroup = "People";
        var termSet = "Job Title";

        var targetList = SharePoint.BuildTargetList(url, subsite, listName);

        var treeBuilder = new MMTreeBuilder(url, termStore, termGroup, termSet, termSet, targetList);
        var tree = treeBuilder.Build();

        var nodeRemover = new NodeRemover(testTreeView, tree);
        nodeRemover.RemoveNodes(ref tree);
    }
}

Basic structure of the WebPart:

  1. Get list of users from SharePoint
  2. Build a TreeNode containing Managed Metadata, with 'Job Title' as the top-level node
  3. Remove any nodes which do not contain an amployee

SharePoint

A simple class - we only need 1 method here and that is to retrieve the list of employees.

static class SharePoint
{
    public static SPListItemCollection BuildTargetList(string url, string subsite, string listName)
    {
        using (var spSite = new SPSite(url))
        {
            using (var web = spSite.OpenWeb())
            {
                var list = web.Lists[listName];
                return list.Items;
            }
        }
    }
}

MMTreeBuilder

This class uses the Build method to construct a TreeNode based on Managed Metadata. We can select where the tree starts in relation to the Metadata with the topNode parameter.

RecursiveTreeNodeBuilder will build all of the Managed MetaData nodes one by one, and also calls AddUsersToNode for each node built.

AddUsersToNode iterates around our SharePoint list and checks if any of the users belong to the department that the current node represents. Note that ths checks against $"{termName}|{termId}", as we may have some nodes which share the same name (E.g. Consultant -> Good or Doctor -> Good), so we will need to differentiate.

class MMTreeBuilder
{
    const string TermGroupString = "Microsoft.SharePoint.Taxonomy.Group";
    const string TermStoreString = "Microsoft.SharePoint.Taxonomy.TermStore";

    public readonly string Url;
    public readonly string TermStoreName;
    public readonly string TermGroupName;
    public readonly string TermSetName;
    public readonly string TopNode;
    public readonly SPListItemCollection TargetList;

    public MMTreeBuilder(string url, string termStoreName, string termGroupName, string termSetName, string topNode, SPListItemCollection targetList)
    {
        Url = url;
        TermStoreName = termStoreName;
        TermGroupName = termGroupName;
        TermSetName = termSetName;
        TopNode = topNode;
        TargetList = targetList;
    }

    public TreeNode Build()
    {
        using (var spSite = new SPSite(Url))
        {
            var taxSession = new TaxonomySession(spSite);

            var termStore = taxSession.TermStores[TermStoreName];
            var group = termStore.Groups[TermGroupName];
            var termSet = group.TermSets[TermSetName];
            var topNode = new TreeNode(TermSetName);

            var tree = RecursiveTreeNodeBuilder(termSet, topNode);
            tree.CollapseAll();
            return tree;
        }
    }

    private TreeNode RecursiveTreeNodeBuilder(object term, TreeNode parentNode)
    {
        var childTerms = "Terms"; // Default
        var typeSwitch = term.GetType().ToString();

        switch (typeSwitch)
        {
            case TermStoreString:
                childTerms = "Groups";
                break;
            case TermGroupString:
                childTerms = "TermSets";
                break;
            default:
                break;
        }

        var termName = GetObjectProperty(term, "Name");
        var termId = GetObjectProperty(term, "Id");
        var newNode = new TreeNode(termName, termId);

        var termType = term.GetType();
        var property = termType.GetProperty(childTerms);
        var collection = (IEnumerable<object>)property.GetValue(term);

        AddUsersToNode(termName, termId, newNode);
        parentNode.ChildNodes.Add(newNode);

        foreach (var item in collection)
        {
            // Recurse
            RecursiveTreeNodeBuilder(item, newNode);
        }

        return newNode;
    }

    private string GetObjectProperty(object obj, string propertyName)
    {
        var type = obj.GetType();
        var property = type.GetProperty(propertyName);
        return property.GetValue(obj).ToString();
    }

    private void AddUsersToNode(string termName, string termId, TreeNode currentNode)
    {
        foreach (SPListItem listItem in TargetList)
        {
            var jobTitle = listItem[TermSetName].ToString();

            if (jobTitle == $"{termName}|{termId}")
            {
                var name = $"<span style='background-color:red;'>{listItem["Title"]}</span>";
                var userNode = new TreeNode(name);
                currentNode.ChildNodes.Add(userNode);
            }
        }
    }
}

NodeRemover

Once we have our TreeNode, we need to remove any nodes which do not represent anyone on the SharePoint list.

 class NodeRemover
{
    TreeView Tree;
    TreeNode Node;

    public NodeRemover(TreeView treeView, TreeNode treeNode)
    {
        Tree = treeView;
        Node = treeNode;
    }

    public void RemoveNodes(ref TreeNode tree)
    {
        var allNodes = new List<TreeNode>();
        GetAllNodes(ref allNodes, tree);

        var removalList = new List<TreeNode>();
        CreateRemovalList(ref removalList, tree);

        Tree.Nodes.Add(tree);

        foreach (TreeNode node in allNodes)
        {
            if (removalList.Contains(node))
            {
                var deletionNode = Tree.FindNode(node.ValuePath);
                node.Parent.ChildNodes.Remove(node);
            }
        }
    }

    private void GetAllNodes(ref List<TreeNode> nodeList, TreeNode parentNode)
    {
        nodeList.Add(parentNode);
        foreach (TreeNode node in parentNode.ChildNodes)
        {
            GetAllNodes(ref nodeList, node);
        }
    }

    private void CreateRemovalList(ref List<TreeNode> removalList, TreeNode currentNode)
    {
        var currentText = currentNode.Text;
        var parent = currentNode.Parent;
        var children = currentNode.ChildNodes;

        if (children.Count == 0 && !currentText.Contains("color"))
        {
            removalList.Add(currentNode);
        }
        else
        {
            foreach (TreeNode child in children)
            {
                CreateRemovalList(ref removalList, child);
            }
        }

        if (children.Count != 0)
        {
            var counter = 0;
            foreach (TreeNode child in currentNode.ChildNodes)
            {
                if (removalList.Contains(child))
                {
                    counter++;
                }
            }
            if (counter == children.Count)
            {
                removalList.Add(currentNode);
            }
        }
    }
}

I haven't implemented any error-handling yet, as I get the feeling that there must be a better (more concise) way of achieving what I want.

Your thoughts on structure, usage or anything else wrong with my code is much appreciated!

\$\endgroup\$
1
\$\begingroup\$

In the SharePoint Static Class:

I would recommend against naming your simple class SharePoint since that sounds awfully similar to the Microsoft.SharePoint namespace.

I'd also rename the BuildTargetList method from that class to be more descriptive of what it truly does. BuildTargetList makes me think it would be creating a list in SharePoint, but it's not really building anything, it's just returning the list item collection. GetListItemCollection would be much more explicit.

In the MMTreeBuilder Class:

I find code is more readable when all method names are verb phrases rather than nouns. Each method name should describe what the method does. You've done this for all of your methods except one.

The RecursiveTreeNodeBuilder method name sounds like a class; I'd rename it to something like BuildTreeNodeRecursively.

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.