Though I haven’t actually used the term before, I’ve discussed a number of higher-order functions in the past. Simply put, a higher-order function either accepts a function as a parameter, returns a function, or both. The terminology might be foreign, but the technique is used all over the place:
- Callbacks
- Most LINQ operators
- SPSecurity.RunWithElevatedPrivileges
- Elegant SPSite Elevation
- Thinking Functional: Using
- Anywhere you see
Action<...>
orFunc<...>
Another use of higher-order functions is to ensure the existence of a SharePoint resource. For example, I often need to fetch a SharePoint list and create it if doesn’t exist. A standard implementation might look something like this:
public static SPList GetOrCreateList(this SPWeb web, string listName, string description, SPListTemplate template) { SPListCollection webLists = web.Lists; SPList list = webLists.Cast<SPList>() .FirstOrDefault(l => l.Title == listName); if (list == null) { Guid newListID = webLists.Add(listName, description, template); list = webLists[newListID]; } return list; }
While there’s nothing wrong with this implementation, per se, it’s not exceedingly flexible. What if we want to use a different overload of SPListCollection.Add
? What if we need to elevate privileges to create the list? We could certainly create a dozen variations based on this pattern, but that’s a bunch of duplicate code that we would much rather avoid. Instead, we can use a single higher-order function:
public static SPList GetOrCreateList(this SPWeb web, string listName, Func<SPWeb, string, SPList> listBuilder) { SPList list = web.Lists.Cast<SPList>() .FirstOrDefault(l => l.Title == listName); if(list == null && listBuilder != null) list = listBuilder(web, listName); return list; }
And then specify exactly how the list should be created. We could redefine our original method like this:
public static SPList GetOrCreateList(this SPWeb web, string listName, string description, SPListTemplate template) { return GetOrCreateList(web, listName, (builderWeb, builderName) => { var builderLists = builderWeb.Lists; Guid newListID = builderLists.Add(builderName, description, template); return builderLists[newListID]; }); }
Or we can just as easily specify a builder that uses elevated privileges and a different Add
overload:
public static SPList GetOrCreateTasksList(this SPWeb web) { return GetOrCreateList(web, "Tasks", (builderWeb, builderName) => { Guid newListId = web.SelectAsSystem(sysWeb => sysWeb.Lists.Add(builderName, null, SPListTemplateType.Tasks)); return builderWeb.Lists[newListId]; }); }
Or my preference is to define a (testable) builder method and just use the higher-order function without a wrapper:
private static SPList CreateGenericList(SPWeb web, string name) { var id = web.Lists.Add(name, null, SPListTemplateType.GenericList); return web.Lists[id]; } void DoSomething(SPWeb web) { SPList list = web.GetOrCreateList("Some List", CreateGenericList); if (list == null) throw new SPException("Some List does not exist and could not be created."); // Do something }
GetOrCreateGroup
Another use for this pattern is the creation of SharePoint groups, inspired by Adam Buenz’s recent post. His code is correct (though I believe an ordinal comparison is more appropriate than invariant culture), but it can’t easily handle scenarios requiring elevation, AllowUnsafeUpdates
, etc. Instead, we can define a higher-order function like this:
public static SPGroup GetOrCreateGroup(this SPWeb web, string groupName, Func<SPWeb, string, SPGroup> groupBuilder, Action<SPWeb, SPGroup> associateGroup) { SPGroup group = web.SiteGroups.Cast<SPGroup>() .FirstOrDefault(g => string.Equals(g.Name, groupName, StringComparison.OrdinalIgnoreCase)); if (group == null && groupBuilder != null) group = groupBuilder(web, groupName); if (group != null && associateGroup != null) associateGroup(web, group); return group; }
With which the original method is easily rewritten:
public static SPGroup GetGroupOrCreate(SPWeb web, string name, string description, SPUser owner, SPUser defaultUser, bool associate) { return web.GetOrCreateGroup(name, (builderWeb, builderName) => { var builderGroups = builderWeb.SiteGroups; builderGroups.Add(builderName, owner, defaultUser, description); return builderGroups[name]; }, (assocWeb, assocGroup) => { if (associate && !assocWeb.AssociatedGroups.Contains(assocGroup)) { web.AssociatedGroups.Add(assocGroup); web.Update(); } }); }
Again, the advantage is that we can easily tweak how the group is created and associated independent from the common get-or-create logic.
June 1, 2009 at 1:23 am
[…] More SharePoint Higher-Order Functions […]
January 27, 2010 at 6:04 am
[…] January 27, 2010 — Keith Dahlby While working on SPExLib (several months ago), I revisited this post, which presented a functional approach to a solution Adam describes here. Both posts include logic […]