Inconvenient programmatically working with SharePoint users (SPWeb.EnsureUser)
Development, Inconvenient SharePoint, SharePointAccording 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.

















August 4th, 2009 at 9:33 pm
Great post. Although I still cant figure out how to get the console app working to add my users using SPWeb.EnsureUser. I am only using a custom membership provider and I added the following entry to my app.config but it still wont work. Am I missing something?
August 4th, 2009 at 10:37 pm
@Calvin: Have you included the context information as well?
August 5th, 2009 at 12:48 am
I am not sure what you mean? I have added the providers node and added the connection string node for good measure. I have not instantiated an spcontext object in my code because it's implied with the SPSite inject isn't it? My code looks just like yours In the second last example…
August 5th, 2009 at 6:11 am
@Calvin: the problem is: it isn't. That's why you have to provide the context information yourself before calling the SPWeb.EnsureUser method.
What I mean is that you first need to instantiate the SPSite/SPWeb, setup the context information as described above in the context paragraph and then call the SPWeb.EnsureUser method. This all of course assuming that there is a yourapp.exe.config file with your membership provider registered there.
August 5th, 2009 at 8:40 pm
I can\'t believe how inconvienent this really is…but thanks for your help on this. So I added the code above that you mentioned (Setting the HttpContext) right before my first web.EnsureUser() call and it actually works…but then it stopped working after a short time. I did an IIS reset and now it simply does not work at all. Odd…
August 5th, 2009 at 9:16 pm
@Calvin: while I've heard about some weird issues with authentication/permissions and IIS reset, I've never experienced the problem you've described. Are you sure that nothing has been changed about the site/farm configuration in the meanwhile?
August 5th, 2009 at 9:21 pm
Yeah I built this configuration from scratch and I am the only one with access right now…I hate admitting defeat – which I am not of course
but I have a work around (add the users via the site users page) so my code to customize and create sites for all of them works. I will have to revisit this later on as the working/not working is really starting to eat away at my sanity.
Thanks again for all your help on this. I will let you know my progress if/when I make any.
January 28th, 2010 at 4:03 am
I wanted to say thanks… this was a GREAT help.
Word of caution to anyone working on this – HTTPContext doesn't work from a remote location or a mapped drive. So, BE sure you're running this from the local machine… otherwise there are other configs you'll need to deal with.
January 30th, 2010 at 2:12 pm
@Richard: It's not only HttpContext but the whole code. As it's code based on the SharePoint API it has to be executed on a SharePoint server.
May 4th, 2010 at 1:12 pm
Hi,
The above code pull me outof the struggle of 4days.
Thank you,
Thanq verymuch…
June 10th, 2010 at 9:55 am
Hi Waldek Mastykarz,
I use the same code to add FBA user into SPGroup on 2010, but it doesn't work.
If I use
string login = "forms:UserName";
SPUser user = web.EnsureUser(login); –> throw exception: "The specified user forms:UserName could not be found".
Then I add more code:
if (!SPClaimProviderManager.IsEncodedClaim(loginName))
{
SPClaim claim = SPClaimProviderManager.Local.ConvertIdentifierToClaim(loginName, SPIdentifierTypes.FormsUser);
loginName = claim.ToEncodedString();
}
I don't see that exception, But I can not add user to group, I see another exception: "The user does not exist or is not unique.". But I add this user through UI Sharepoint. It is ok. Could you know what happen i have?
Thanks,
Peter.
June 11th, 2010 at 7:04 am
@Peter: this code sample has been created for SharePoint 2007 and hasn't been tested with Claims in SharePoint 2010. I can imagine that while working with Claims you would need some different code to resolve the user name.
July 8th, 2010 at 11:50 am
Thanks for this article. it helped me a lot.
August 4th, 2010 at 9:49 pm
Is there any downside to using "web.EnsureUser(login)" ? For example, if I have an internal SharePoint site with potentially thousands of users, would there be any concerns, performance-wise or other, with having so many domain accounts added to the site?
I'm using EnsureUser in order to assign a task item in a workflow. Thank you.
August 4th, 2010 at 11:11 pm
@Mikester: Although I don't have any hard numbers, I'd say that using it in a Workflow shouldn't be any issue, since Workflows are not something that is being used very heavily (like hundreds users starting the same Workflow at the same time).