Inconvenient programmatically working with SharePoint users (SPWeb.EnsureUser)


According to the Windows SharePoint Services (WSS) v3 SDK the SPWeb.EnsureUser(String) method is all you need while programmatically working with users. Using nothing more than the login name it checks for you whether the particular user exists in the current web and adds it if required. Within a single line of code it retrieves for you a proper reference to a user no matter the membership/role provider. While it’s really that simple while working in the scope of your SharePoint Web Application, things get slightly more challenging when used in combination with a custom code outside of the HttpContext.

What’s exactly the problem?

Let’s have a look at a simple case: you want to programmatically add a user to an existing group. According to the SharePoint SDK it should be enough to call in your code:

string login = "UserName";
string groupName = "Group";
SPUser user = SPContext.Current.Web.EnsureUser(login);
SPGroup group = SPContext.Current.Web.Groups[groupName];
group.AddUser(user);

It is that simple. The above code works correctly: the EnsureUser method returns a valid SPUser object which can be added to the Group group.

First thing you should notice is the use of SPContext to get a reference to the current site. Imagine that you wanted to run the same case as described above from a Console Application: SPContext.Current returns null so in order to get to an instance of SPWeb you would have to change the code a little:

using (SPSite site = new SPSite("http://adventureworks.com"))
{
  using (SPWeb web = site.OpenWeb())
  {
    string login = "UserName";
    string groupName = "Group";
    SPUser user = web.EnsureUser(login);
    SPGroup group = web.Groups[groupName];
    group.AddUser(user);
  }
}

The above code should run in a Console Application without bigger problems. But let’s take our case one step further: let’s check if the code works with custom membership/role providers? Let’s assume our user name looks like forms:UserName:

using (SPSite site = new SPSite("http://adventureworks.com"))
{
  using (SPWeb web = site.OpenWeb())
  {
    string login = "forms:UserName";
    string groupName = "Group";
    SPUser user = web.EnsureUser(login);
    SPGroup group = web.Groups[groupName];
    group.AddUser(user);
  }
}

If you run the above piece of code (assuming you have Forms Based Authentication (FBA) configured correctly, that its membership or role provider is called forms and that there is a user called UserName), all you see is an SPException: SharePoint cannot find the user.

Surprisingly the same piece of code executed from a control or an Application Page runs correctly: the user is being retrieved and added to the group. Does it look like we miss some context here?

Is it all about the context?

Usually when things don’t work in custom Console Applications talking to SharePoint while they work perfectly in the Web UI the odds are high that the particular piece of code requires Context information. You can easily check it by including the following code snippet right before your code:

if (HttpContext.Current == null)
{
  HttpRequest request = new HttpRequest("", web.Url, "");
  HttpContext.Current = new HttpContext(request,
    new HttpResponse(new StringWriter()));
  HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
}

Unfortunately the above doesn’t solve our issue: if you make a call to the SPWeb.EnsureUser method, you will once again get an SPException. So what is wrong here?

The challenge

Usually to find out how a particular piece of SharePoint code works I would open Reflector and have a look at the code flow. By calling the particular pieces of code I would try to find out which piece fails and then try to figure out how to solve the issue. While the SPWeb.EnsureUser method itself is pretty straight-forward, things get complicated the more you dig into the code.

First of all pretty soon you will stumble upon obfuscated code. While it is doable to see what’s going on under the hood you will have to struggle with Intermediate Language (IL) – it’s all you see. Unless you have some serious development experience, it is not that easy to follow the flow of IL.

Second of all the EnsureUser method heavily relies on internal types and methods. Because of this it is complicated (but not impossible) to run pieces of code which are being called by the EnsureUser method. You have to reflect the assemblies and types to get to the right objects and methods. Once again: it’s complicated but not impossible.

So what’s the real problem?

After doing some research I’ve found that at some point one of the internal methods makes call to System.Web.Security.Roles.Providers collection. It’s a public property in the System.Web assembly so there is nothing that holds you back from calling it from your code. It turns out that before the Roles.Provider returns the collection of available role providers it checks whether the plumbing is enabled. Not surprisingly, while querying this property in your Console Application, you will get a ProviderException with a rather cryptic message: “This feature is not enabled”.

Can we do something about it?

Digging even further I have found out that at some point the Roles class tries to retrieve the system.web/roleManager configuration section. Because that section is available in the web.config of a Web Application with FBA configured, the code works properly. But Console Applications don’t have web.config, do they?

Well they don’t, but what you can do is to create a config file like ConsoleApplication1.exe.config (assuming that ConsoleApplication1.exe is the name of your executable after building) and copy there that particular piece of configuration from your web.config. If you run your Console Application now, it should work perfectly.

Inconvenience

The SPWeb.EnsureUser method encapsulates quite a large piece of code from the developers. While it allows us to get a proper user reference with just a single line of code it lacks flexibility. I can imagine quite a few scenarios when you would want to benefit of this method outside of the context of your Web Applications. It’s a shame though that it’s that difficult to find out what the real issue is and how it can be solved.

Technorati Tags: SharePoint, SharePoint 2007, MOSS 2007, WSS 3.0

Others found also helpful: