Tuesday, October 30, 2012

Fix NullReferenceException when use user profiles service application without Sharepoint context

In some cases you may need to use Sharepoint object model outside of the Sharepoint context (i.e. when SPContext.Current is null) and without http context (HttpContext.Current is null), e.g. from console application. If internal methods of the API require not nullable SPContext.Current, you may fake it using the following method (see my another post Override SPContext.Current.Web and Site with sites opened with elevated privileges for explanations why it works like this):

   1: using (var site = new SPSite(args[0]))
   2: {
   3:     using (var web = site.OpenWeb())
   4:     {
   5:         HttpRequest request = new HttpRequest("", web.Url, "");
   6:         HttpContext.Current = new HttpContext(request,
   7:             new HttpResponse(TextWriter.Null));
   8:         HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
   9:         ...
  10:     }
  11: }

After this SPContext.Current won’t be null, and code which requires it will work. However it brings one problem. If you will try to use User profiles service application with such fake context, you will get NullReferenceException. For example, suppose that we need to enumerate all imported user profiles using the following code:

   1: using (var site = new SPSite(url))
   2: {
   3:     var serviceContext = SPServiceContext.GetContext(site);
   4:     var upm = new UserProfileManager(serviceContext);
   5:     foreach (UserProfile profile in upm)
   6:     {
   7:         ...
   8:     }
   9: }

When you will run it in console application you will get NullReferenceException with the following stack trace:

at Microsoft.Office.Server.UserProfiles.UserProfileGlobal.IsWindowsAuth()
at Microsoft.Office.Server.UserProfiles.UserProfileManager..ctor(SPServiceContext serviceContext, Boolean IgnoreUserPrivacy, Boolean backwardCompatible)
at Microsoft.Office.Server.UserProfiles.UserProfileManager..ctor(SPServiceContext serviceContext)

Let’s check implementation of UserProfileGlobal.IsWindowsAuth() method which causes exception:

   1: internal static bool IsWindowsAuth()
   2: {
   3:     HttpContext current = HttpContext.Current;
   4:     if (current != null)
   5:     {
   6:         return (current.User.Identity is WindowsIdentity);
   7:     }
   8:     return true;
   9: }

As you replace context with fake instance, condition on line 4 will be true and execution will go to the line 6 where it will fail, because context.User is null. In order to fix it you have to specify user when you fake HttpContext. The way how you should do that depends on the authentication type used in your web application: standard or claims based.

For standard windows authentication you should use the following code:

   1: AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
   2: ...
   3: HttpRequest request = new HttpRequest("", web.Url, "");
   4: HttpContext.Current = new HttpContext(request, new HttpResponse(TextWriter.Null));
   5: HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
   6: HttpContext.Current.User = Thread.CurrentPrincipal as WindowsPrincipal;

For claims based use the following code:

   1: HttpRequest request = new HttpRequest("", web.Url, "");
   2: HttpContext.Current = new HttpContext(request, new HttpResponse(TextWriter.Null));
   3: HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
   4: HttpContext.Current.User = new ClaimsPrincipal(Thread.CurrentPrincipal);

After that you will be able to use UserProfileManager and work with user profiles in the console application.

No comments:

Post a Comment