I have written a small program which has 2 main classes :
ServerSideMMS
: This can access the SharePoint Managed Metadata Services, but only from the server which is running SharePointClientSideMMS
: This can access the SharePoint Managed Metadata Services from any client machine
These can both be used to access MMS services and retrieve the data. In my case, I only need to grab the actual set of terms (which is the lowest level of metadata).
Server
using Microsoft.SharePoint;
using Microsoft.SharePoint.Taxonomy;
namespace CallTaxonomyWebService
{
/// <summary>
/// A class used for retrieving MMS data. This must be run on the server
/// which is hosting the metadata.
/// </summary>
class ServerSideMMS
{
private const string ManagedMetadataService = "Managed Metadata Service";
private TaxonomySession Taxonomy;
private SPSite Site;
/// <summary>
/// The constructor assigns the given SPSite and the resulting TaxonomySession
/// to some private fields.
/// </summary>
/// <param name="spSite"></param>
public ServerSideMMS(string spSite)
{
Site = new SPSite(spSite);
Taxonomy = new TaxonomySession(Site);
}
/// <summary>
/// Get all the terms which are stored in the given termset under the given group
/// </summary>
/// <param name="groupName"></param>
/// <param name="setName"></param>
/// <returns></returns>
public TermCollection GetTermSet(string groupName, string setName)
{
var group = GetGroup(groupName);
var termset = group.TermSets[setName];
return termset.Terms;
}
/// <summary>
/// Returns an MMS group of the given name
/// </summary>
/// <param name="groupName"></param>
/// <returns></returns>
public Group GetGroup(string groupName)
{
var Mms = Taxonomy.TermStores[ManagedMetadataService];
return Mms.Groups[groupName];
}
}
}
Client
I wasn't particularly happy with the outcome of this class. As you can see I had to split the TermSet
retrieval into 2 classes, because I wasn't sure how to create and return an empty TermCollection
object in case something wasn't found, so I resorted to just assigning this to a class field and returning it from the main GetTermSet
method :
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
namespace CallTaxonomyWebService
{
/// <summary>
/// A class used for retrieving MMS data. This can be run from a client machine.
/// Because of the more complicated nature of these queryies (when compared to
/// the server-side calls), I have defined a few private fields to store
/// return data.
///
/// For more on what we can do with CSOM and MMS, see https://github.com/OfficeDev/PnP/tree/dev and
/// https://msdn.microsoft.com/en-us/library/office/dn904534.aspx.
/// </summary>
class ClientSideMMS
{
private TaxonomySession Taxonomy;
private ClientContext Context;
private TermStore Store;
private TermGroup Group;
private TermCollection Collection;
/// <summary>
/// The constructor sets and validates the ClientContext before loading
/// the required elements of each Store, Group, TermSet and Term. This can
/// be amended if we every want to retrieve more data but for now I am
/// just getting the names of each.
/// </summary>
/// <param name="spSite"></param>
public ClientSideMMS(string spSite)
{
Context = new ClientContext(spSite)
{ AuthenticationMode = ClientAuthenticationMode.Default};
Taxonomy = TaxonomySession.GetTaxonomySession(Context);
Store = Taxonomy.GetDefaultSiteCollectionTermStore();
Context.Load(Store,
store => store.Name,
store => store.Groups.Include(
group => group.Name,
group => group.TermSets.Include(
termSet => termSet.Name,
termSet => termSet.Terms.Include(
term => term.Name)
)
)
);
Context.ExecuteQuery();
}
/// <summary>
/// This returns the private "Set" field after calling "LoadTermSet" (which assigns the
/// requested TermSet to Set)
/// </summary>
/// <param name="groupName"></param>
/// <param name="setName"></param>
/// <returns></returns>
public TermCollection GetTermSet(string groupName, string setName)
{
LoadTermSet(groupName, setName);
return Collection;
}
/// <summary>
/// Iterate through each Group until we find the right one (Name = groupName). Then
/// find the correct TermSet in that group and assign the Set field to that value.
///
/// This is structured like this to avoid local "return" variables ...
/// </summary>
/// <param name="groupName"></param>
/// <param name="setName"></param>
private void LoadTermSet(string groupName, string setName)
{
foreach (var group in Store.Groups)
{
if (group.Name == groupName)
{
Group = group;
foreach (var set in group.TermSets)
{
if (set.Name == setName)
{
// I would much rather this function just return the Collection,
// but as I can't guaruntee that a collection will be found, this
// could cause an error, hence the private field ...
Collection = set.Terms;
return;
}
}
}
}
}
}
}
Calling the classes
using System.Linq;
namespace CallTaxonomyWebService
{
static class Program
{
private const string site = @"http://mysharepointsite/";
private static void Main(string[] args)
{
var mms = new ClientSideMMS(site);
var termcollection = mms.GetTermSet("People", "Department");
var localTerms = termcollection.Select(term => term.Name).ToList();
var remoteMms = new ServerSideMMS(site);
var remoteCollection = remoteMms.GetTermSet("People", "Department");
var remoteTerms = remoteCollection.Select(term => term.Name).ToList();
}
}
}
Any feedback is much appreciated, particularly in relation to my use of a private field instead of a return type to try and avoid NullRefernceException
s in the client side class.
For example, would it be better to try and initialise an empty return value inside the LoadTermSet
method and just return that at the end?