SPWeb.AssociatedGroups.Contains Lies
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 to add an SPWeb
group association, which most simply could look something like this:
SPGroup group = web.SiteGroups[groupName];
if (!web.AssociatedGroups.Contains(group))
{
web.AssociatedGroups.Add(group);
web.Update();
}
While testing on a few groups, I noticed that the Contains()
call lies, always returning false
. This behavior can also be verified with PowerShell:
PS > $w.AssociatedGroups | ?{ $_.Name -eq 'Designers' } | select Name
Name
----
Designers
PS > $g = $w.SiteGroups['Designers']
PS > $w.AssociatedGroups.Contains($g)
False
Of course, it’s not actually lying—it just doesn’t do what we expect. Behind the scenes, AssociatedGroups
is implemented as a simple List<SPGroup>
that is populated with group objects retrieved by IDs stored in the SPWeb
‘s vti_associategroups
property. The problem is that List<T>.Contains()
uses EqualityComparer<T>.Default
to find a suitable match, which defaults to reference equality for reference types like SPGroup
that don’t implement IEquatable<T>
or override Equals()
.
To get around this, SPExLib provides a few extension methods to make group collections and SPWeb.AssociatedGroups
easier to work with and more closely obey the Principle of Least Surprise:
public static bool NameEquals(this SPGroup group, string name)
{
return string.Equals(group.Name, name, StringComparison.OrdinalIgnoreCase);
}
public static bool Contains(this SPGroupCollection groups, string name)
{
return groups.Any<SPGroup>(group => group.NameEquals(name));
}
public static bool HasGroupAssociation(this SPWeb web, string name)
{
return web.AssociatedGroups.Contains(name);
}
public static bool HasGroupAssociation(this SPWeb web, SPGroup group)
{
if (group == null)
throw new ArgumentNullException("group");
return web.HasGroupAssociation(group.Name);
}
public static void EnsureGroupAssociation(this SPWeb web, SPGroup group)
{
if (web.HasGroupAssociation(group))
web.AssociatedGroups.Add(group);
}
The code should be pretty self-explanatory. The name comparison logic in NameEquals()
is written to align with how SharePoint compares group names
internally, though they use their own implementation of case
insensitivity because the framework’s isn’t good enough. Or something
like that.
There should be two lessons here:
- Don’t assume methods that have a notion of equality, like
Contains()
, will behave like you expect. - Use SPExLib and contribute other extensions and helpers you find useful. 🙂