The difference between SPWeb.Users and SPWeb.SiteUsers of SharePoint

Getting lists of users is easy, when you know how. There are some subtle differences that might trip you up when you're looking to retrieve a list of users from a SharePoint Site or Portal Area. The SPWeb class has two properties, Users and SiteUsers. Here's the documentation on each:

SPWeb.Users Gets the collection of user objects belonging to the Web site.
SPWeb.SiteUsers Gets the collection of all users belonging to the site collection.

There's just a *slight* difference in wording here. After all, SPWeb.Users gives me the user objects of the web site right? Not really.

As this was something that was bugging me with a problem I had, I decided to do a small spike to prove (or disprove) what was going on. When you have a design problem or a technical complexity that has to be addressed (before staring your work to reduce the risk) you create a spike, a small side project apart from your current work. The spike solutions are developed to solve or explore critical problems. Ideally those programs will be thrown away once every developer gets a clear idea about the problem.

So we'll create the SSWP[1] with the following RenderWebPart method defined:

   63         protected override void RenderWebPart(HtmlTextWriter output)

   64         {

   65             SPSite site = SPControl.GetContextSite(Context);

   66             SPWeb web = site.OpenWeb();

   67             output.Write(string.Format("<strong>Information</strong><br>User Count={0}, Current User ID={1}</p>",

   68                 web.Users.Count, web.CurrentUser.ID));

   69 

   70             StringBuilder sb = new StringBuilder();

   71             sb.Append("<table border=1>");

   72             sb.Append("<tr><td colspan=3><strong>SPWeb.Users</strong></td></tr>");

   73             sb.Append("<tr><td>ID</td><td>LoginName</td><td>Name</td></tr>");

   74             foreach (SPUser user in web.Users)

   75             {

   76                 sb.AppendFormat("<tr><td>{0}</td><td>{1}</td><td>{2}</td></tr>",

   77                     user.ID,

   78                     user.LoginName,

   79                     user.Name);

   80             }

   81             sb.Append("</table>");

   82             output.Write(sb.ToString());

   83 

   84             sb = new StringBuilder();

   85             sb.Append("<table border=1>");

   86             sb.Append("<tr><td colspan=3><strong>SPWeb.SiteUsers</strong></td></tr>");

   87             sb.Append("<tr><td>ID</td><td>LoginName</td><td>Name</td></tr>");

   88             foreach (SPUser user in web.SiteUsers)

   89             {

   90                 sb.AppendFormat("<tr><td>{0}</td><td>{1}</td><td>{2}</td></tr>",

   91                     user.ID,

   92                     user.LoginName,

   93                     user.Name);

   94             }

   95             sb.Append("</table>");

   96             output.Write(sb.ToString());

   97 

   98             try

   99             {

  100                 SPUser currentUser = web.SiteUsers.GetByID(web.CurrentUser.ID);

  101                 output.Write(string.Format("</p>Current user is {0} (ID={1})",

  102                     currentUser.LoginName, currentUser.ID));

  103             }

  104             catch(SPException e)

  105             {

  106                 output.Write(string.Format("</p>Error getting user ({0})", e.Message));

  107             }

  108         }

So what's this thing doing? Not much but basically:

  • Get a reference to the SPSite object using the GetContextSite method of SPControl
  • Open the SPWeb object using the OpenWeb method of the SPSite
  • Write out some information about the number of users and current user id
  • Create a small table with the id, login name, and display name of each user in the SPWeb.Users collection
  • Do the same thing but with the SPWeb.SiteUsers collection
  • Retrieve the SPUser object from the Web using the GetByID method and display information about that user

It's the last part (wrapped in a try/catch block) that you should pay attention to.

Here's the output of the Web Part logging in as LOCALMACHINE\Administrator when displayed in a SharePoint Portal Server Area:

And here's the same Web Part when displayed in a Windows SharePoint Server site:

 

When you create an area or site, several users are automatically added behind the scenes. In the case of an area on the portal, you'll see that SiteUsers contains more names than what's in the WSS site. It adds in a "BUILTIN\Users" domain group (ID 4) and adds the "NT AUTHORITY\Authenticated Users" at the end (ID 6) in an area. In a site, "NT AUTHORITY\Authenticated Users" takes up the #4 slot, and the "BUILTIN\Users" is nowhere to be found.

Also when you create a site, it adds the creator (in this case "Administrator") to the list of users (go into Site Settings | Manage Users to see the list). In area, the creator of that area shows up in the SPWeb.Users list, but it's not present on the "Manage Security" page.

Here's a rundown on the user ID slots that are filled in automatically:

ID Description
1 User who created the site or area
2 Not sure on this one? Sorry.
3 Application Pool Id, crawler account.
4 BUILTIN\Users on Portal, NT AUTHORITY\Authenticated Users on WSS
5 Additional individual users you add to the site, or any new users who happen along.
6 NT AUTHORITY\Authenticated Users on Portal only, otherwise the next user you add or visits the area.

Note: these values are from most setups I've done, the way you configure your portal (turning on anonymous for example) might change these values somewhat so use them as a guideline, not a set of stone tablets that someone hands down to you after taking a siesta in the mountains.

You might say, "But Bil, I really only want the current user so SPWeb.CurrentUser should be good enough right?". Maybe. What if you want to retrieve a user by ID (that might be stored somewhere else, but corresponds to the same ID # in the site). Or you want to display all the users in the site. This will become important when I blog later this week about Impersonation (everyone's favorite topic).

So here's the same Web Part, but being rendered by a regular Joe user (spsuser) who has read access to the site. He's been added manually to the WSS site, but he's a member of the Readers group at the Portal and doesn't show up in the SPWeb.Users list. First, the Portal Area rendering:

Now the WSS site rendering:

Remember when I said to pay attention to that try/catch block? If you had used a bit of code like SPWeb.Users.GetByID (instead of using the SiteUsers property) you would have got an exception thrown while looking for a user with an ID of 5. As you can see in the SPWeb.Users list, it doesn't exist because this will only display domain groups and indvidual users who have been manually added to the site (and Administrator got added automatically when the site got created).

Another thing that's interesting is that if you did add "spsuser" to the site manually he would show up in the SPWeb.Users list above. However if you removed him, he would vanish from SPWeb.Users but he would still show up in the SPWeb.SiteUsers list. His ID # is reserved so the next user you added manually would have an ID of #6 (in the case of the site) and if you re-added "spsuser" at a later date, he would re-appear in both lists with an ID of #5.

So in a nutshell… if you're going after a list of all users or want to retrieve a user from the list but he's part of a group, then use the SiteUsers property instead of Users. Otherwise, he might not be there.

Okay, this post might have been a little confusing as we're flipping around between Users and SiteUsers and all that but hopefully the code and images describe it for you. Let me know if you're totally confused, otherwise… enjoy!

[1] Super-Simple-Web-Part: Just a web part where we hard code the values in the RenderWebPart method. Nothing fancy here. No Domain Objects. No DTOs. No Layers. Just write out some code.