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.

38 Responses to “Inconvenient programmatically working with SharePoint users (SPWeb.EnsureUser)”

  1. Calvin Irwin Says:

    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?

  2. Waldek Mastykarz Says:

    @Calvin: Have you included the context information as well?

  3. Calvin irwin Says:

    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…

  4. Waldek Mastykarz Says:

    @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.

  5. Calvin Irwin Says:

    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…

  6. Waldek Mastykarz Says:

    @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?

  7. Calvin Irwin Says:

    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.

  8. Richard Bailey Says:

    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.

  9. Waldek Mastykarz Says:

    @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.

  10. Raju Says:

    Hi,
    The above code pull me outof the struggle of 4days.
    Thank you,
    Thanq verymuch…

  11. Peter Says:

    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.

  12. Waldek Mastykarz Says:

    @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.

  13. Marcin Says:

    Thanks for this article. it helped me a lot.

  14. Mikester Says:

    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.

  15. Waldek Mastykarz Says:

    @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).

  16. Sharepoint Code Says:

    Useful post – ensureuser was a pain for me – although I did what Mikester suggested without any noticible performance drop (fyi)

  17. Dilip V. Says:

    I have created a sharepoint blog site in SP2010 and turned on Anonymous access for entire website. Then I created a visual webpart for adding new user, first in SqlDatabase and then to sharepoint site database. I tried commands like spWeb.EnsureUser(), web.Groups["groupname"].AddUser(loginname, email, username, "")
    but it works only when site owner is logged in. It does not allow anonymous user to create his own user id. Anybody has a solution, please reply?

  18. Waldek Mastykarz Says:

    @Dilip V.: Are you using a custom Membership Provider? If so, have you checked the piece about providing the context information?

  19. Dilip V. Says:

    Hi Waldek Mastykarz,
    I tried using "if (HttpContext.Current == null)". While debugging HttpContext.Current is not null so, doesn't enter the line HttpRequest request = new HttpRequest("", spWeb.Url, ""). Then I tried removing "if" statement then spWeb.EnsureUser() retuned error: "This operation requires IIS integrated pipeline mode.". So, tried to search for solving error. The Link http://forums.asp.net/p/1253457/2323117.aspx tells issue. I checked my IIS 7 on server Windows Server 2008 R2 Enterprise. IIS –> Basic Settings shows .Net Framework version: v2.0 and Managed pipeline mode:Integrated. I am not able to resolve issue. Am I going in right path of resolving issue.

  20. Dilip V. Says:

    Hi Waldek Mastykarz,
    I am using FBA membership method. Only the error comes at spWeb.EnsureUser() when anonymous user tries to create his own account. Also EnsureUser checks which user is logged in current site is valid user. So I think as anonymous user tries to create new user gets failed and gets redirected to login page, telling us to first login with a valid user and then create new user. And if tried with valid user allows to create new user. But this is not a solution for a blog site visitor to create his new account in Sharepoint. Please can u guide me, I m trying from last 1 month.

  21. Waldek Mastykarz Says:

    @Dilip V.: Have you tried wrapping the code in SPSecurity.RunWithElevatedPrivileges?

  22. Dilip V. Says:

    Hi Waldek Mastykarz,
    Below is my below code:
    //For Membership
    MembershipUser newUser = Membership.CreateUser()

    //For adding in SP Database.
    SPSite spSite = new SPSite(strSiteURL);
    SPWeb spWeb = SPControl.GetContextWeb(HttpContext.Current);
    if (HttpContext.Current == null)
    {
    HttpRequest request = new HttpRequest("", spWeb.Url, "");
    HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = spWeb;
    }
    string userName = "membershipname" +UsernameTextbox.Text;
    Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate
    {
    SPUser spUser = spWeb.EnsureUser(userName);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(spUser);
    });
    After spWeb.EnsureUser(userName);
    goes to catch without giving exception error.

  23. Waldek Mastykarz Says:

    @Dilip V.: That's because you're using SPWeb object created before elevating privileges. The correct way is to create an instance of SPSite and SPWeb inside the RunWithElevatedPrivileges delegate.

  24. Dilip V. Says:

    Hi Waldek Mastykarz,
    Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate
    {
    string strSiteURL = "http://ps2010";
    SPSite spSite = new SPSite(strSiteURL);
    SPWeb spWeb = SPControl.GetContextWeb(HttpContext.Current);
    if (HttpContext.Current == null)
    {
    HttpRequest request = new HttpRequest("", spWeb.Url, "");
    HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = spWeb;
    }
    string userName = "fbamembership" + Username.Text;
    SPUser spUser = spWeb.EnsureUser(userName);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(spUser);
    });
    Tried the above code but still fails, any other idea.

  25. Dilip V. Says:

    Hi Waldek Mastykarz,
    Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate
    {
    string strSiteURL = \"http://ps2010\";
    SPSite spSite = new SPSite(strSiteURL);
    SPWeb spWeb = SPControl.GetContextWeb(HttpContext.Current);
    if (HttpContext.Current == null)
    {
    HttpRequest request = new HttpRequest(\"\", spWeb.Url, \"\");
    HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items[\"HttpHandlerSPWeb\"] = spWeb;
    }
    string userName = \"fbamembership\" + Username.Text;
    SPUser spUser = spWeb.EnsureUser(userName);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(spUser);
    });
    Tried the above code but still fails, any other idea.

  26. Dilip V. Says:

    In my code, I have not added \ which came by default before "

  27. Waldek Mastykarz Says:

    @Dilip V.: replace SPControl.GetContextWeb with spSite.OpenWeb(); Additionally if you're running in the context of a Web Application you don't need to reset the context.

  28. Dilip V. Says:

    Hi Waldek Mastykarz,

    Now I m able to create new user. Thank you very much. You are SharePoint Guru(Teacher).

    After replacing SPControl.GetContextWeb with spSite.OpenWeb(); I got error:
    The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.

    I refered the site:
    http://www.bizsupportonline.net/blog/2008/12/microsoftsharepointspexception-the-security-validation-for-this-page-is-invalid-error-infopath-browser-forms/
    and added below code
    spWeb.AllowUnsafeUpdates = true;
    string userName = "fbamembership" + Username.Text;
    SPUser spUser = spWeb.EnsureUser(userName);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(spUser);
    spWeb.AllowUnsafeUpdates = false;
    I wanted to know, r u online every time because I got ur response everytime immediately.

    Once again Thank You Guru.

  29. Dilip V. Says:

    Hi Waldek Mastykarz,

    Now I m able to create new user. Thank you very much. You are SharePoint Guru(Teacher).

    After replacing SPControl.GetContextWeb with spSite.OpenWeb(); I got error:
    The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.

    I refered the site:
    http://www.bizsupportonline.net/blog/2008/12/microsoftsharepointspexception-the-security-validation-for-this-page-is-invalid-error-infopath-browser-forms/
    and added below code
    spWeb.AllowUnsafeUpdates = true;
    string userName = \"fbamembership\" + Username.Text;
    SPUser spUser = spWeb.EnsureUser(userName);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(spUser);
    spWeb.AllowUnsafeUpdates = false;
    I wanted to know, r u online every time because I got ur response everytime immediately.

    Once again Thank You Guru.

  30. Waldek Mastykarz Says:

    @Dilip V.: great to hear you got it all working. You're welcome :)

  31. Ignacio F Says:

    You just took my stress away! Great post and the snippets work like a charm. Thank you SO much. Cheers!

  32. Moninder Says:

    Thanks a lot. This saved me lot of time troubleshooting. You rock!

  33. Moninder Says:

    Thanks a lot. This saved me lot of time troubleshooting. You rock! I was struggling getting the user's added since our web app uses claims based authentication

  34. dotNetFollower Says:

    Hello!
    Very nice article. I worked with EnsureUser and found out that there are a few other inconveniences. For example, we need almost always to use elevated privileges to deal with EnsureUser, otherwise exception will be thrown. Or for example, if user doesn't exist in user collection, it will be tried to add to this collection, but this means that spWeb object will be modified and it's required AllowUnsafeUpdates = true. Eventually, I've developed a small method-wrapper for EnsureUser. It's shown in my blog – http://dotnetfollower.com/wordpress/2011/05/sharepoint-wrapper-over-ensureuser/
    Thanks!

  35. Miryam Says:

    Hi,

    Took me a while to get a similar issue sorted out, until I stumbled on your blog. You have help me me heaps. Thankyou.

  36. Nishanth Says:

    Hi Waldek Mastykarz ,

    i have a weird problem.when i add a user to a group i get a welcome email triggered.
    but when i add user programatically to a group by the above method,i dont get a welcome mail at all.

    Any suggestions would be very helpful.

  37. Waldek Mastykarz Says:

    Could it be that sending the mail isn't a part of the logic responsible for adding the user to a group and is done from within the page itself? So if you adding user to a group programmatically you have to take care for sending the e-mail yourself?

  38. Sudarshan Vatturkar Says:

    I am also facing same issue but scenario is different-

    1. I am having Single farm with multiple web apps with ADFS 2.0/Claim authentication
    2. Custom HTTP Module is placed

    Whenever user clicks on any of the site collection link through logged in site collection and obviously user will not have access to destination site and same is achieved through claim augmentation via HTTP Module.

    custom claim has added on every site collection with contributor role and same claim will be added for current user (through http module if current user is type of SCA) who is trying to access destination site collection.

    Summary:
    1. HTTPContext.Current is destination site collection.
    2. where I am creating SPweb.Ensureuser(“user1″) for source site collection using GUID which passed as part of request url (https://destination_site.com/?scid={source_site_guid}
    3. User1 is SCA of source site collection

    Getting exception “User not found”.

    I had tried to change current context with source site collection -
    HttpContext oldContext = HttpContext.Current;
    HttpRequest request = new HttpRequest(“”, elevatedWeb.Url, “”);
    HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = elevatedWeb;

    bool oldAllowUnsafeUpdates = elevatedWeb.AllowUnsafeUpdates;
    elevatedWeb.AllowUnsafeUpdates = true;
    SPUser user = elevatedWeb.EnsureUser(httpuser);
    elevatedWeb.AllowUnsafeUpdates = oldAllowUnsafeUpdates;
    HttpContext.Current = oldContext;
    }

    Any clue whta can be done in above scenario?

Leave a Reply

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS
Copyright © 2007 - 2013 Waldek Mastykarz

Creative Commons License