Second Life of a Hungarian SharePoint Geek

June 26, 2011

Injecting HttpContext and SPContext into the event receiver context

Filed under: Event receivers, SP 2010 — Tags: , — Peter Holpar @ 14:59

Last week I met an interesting challenge and would like to show you a way of solution. Before going into details, I have to say that the method I show you probably far from what is called a best practice, but given the situation I think it might be a quick and dirty solution for the issue.

Assume we have the following static class:

  1. public static class Lists
  2. {
  3.     public static readonly String MyList = Resource.GetResFieldByKey("Lists_MyList");
  4.     public static readonly String AnotherList = Resource.GetResFieldByKey("Lists_AnotherList");
  5. }

We use this class to store “configurable” constants. The GetResFieldByKey is a static method to read resource strings. The resource file is stored in the App_GlobalResources folder of the web application. In the original implementation the path of the resource file was computed like this:

String rsPath = HttpContext.Current.Server.MapPath("~/App_GlobalResources") + "\\" + resourceFile + ".resx";

As the reference to the  HttpContext.Current in the above line suggests this code was planned to be used only from a web context.

The requirements were changed and we had to extend the solution with an after (-ed) list item event receiver. How could we use the same set of constants in the new code?

If we would have a reference to the right SPSite instance, we could get the resource file location like this:

  1. SPIisSettings iisSet = (SPIisSettings)site.WebApplication.IisSettings[SPUrlZone.Default];
  2. String relativePath = String.Format("\\App_GlobalResources\\{0}.resx", resourceFile);
  3. String rsPath = String.Format("{0}{1}", iisSet.Path.FullName, relativePath);

But passing the site as a parameter to the static properties makes no sense, so I tried to find instead a workaround.

First idea was to play with my favorite Reflection and walk up on the call stack in the GetResFieldByKey method to get the SPSite reference from the SPItemEventProperties parameter of the ItemUpdated method (like properties.Web.Site) but it would not be a great idea due to the performance costs of the solution. Then I started experimenting with HttpContext and SPContext.

See the following block of code that I’ve included in ItemUpdated. First part is only to validate there is really neither HttpContext nor SPContext at this point. It will get importance after we create our dummy contexts to check that out injected context do not remain attached to the process on subsequent events. The second part of the code is about creating and injecting the contexts:

  1. try
  2. {
  3.     HttpContext currentContext = HttpContext.Current;
  4.     Trace.TraceInformation("Try to get HTTP context");
  5.  
  6.     if (currentContext != null)
  7.     {
  8.         // this block is only to validate there is
  9.         // neither HttpContext nor SPContext when event receiver method
  10.         Trace.TraceInformation("Try to get SP context");
  11.  
  12.         SPContext context = SPContext.Current;
  13.         if ((context != null) && (context.Web != null))
  14.         {
  15.             Trace.TraceInformation("SPWeb URL: {0}", context.Web.Url);
  16.             Trace.TraceInformation("SPWeb user: {0}", context.Web.CurrentUser);
  17.         }
  18.         else
  19.         {
  20.             Trace.TraceInformation("HTTP context found but no usable SP context");
  21.         }
  22.     }
  23.     else
  24.     {
  25.         Trace.TraceInformation("Create dummy HTTP context");
  26.         HttpRequest request =
  27.              new HttpRequest(string.Empty, properties.WebUrl, string.Empty);
  28.  
  29.         HttpResponse response = new HttpResponse(
  30.              new System.IO.StreamWriter(new System.IO.MemoryStream()));
  31.  
  32.         HttpContext dummyContext = new HttpContext(request, response);
  33.         // these lines required to inject SPContext as well
  34.         // if you don't need that it can be deleted
  35.         if (properties.Web != null)
  36.         {
  37.             dummyContext.Items["HttpHandlerSPWeb"] = properties.Web;
  38.         }
  39.         HttpContext.Current = dummyContext;
  40.     }
  41.  
  42.     // here we try to get a static value bound to a resource string
  43.     String myList = Lists.MyList;
  44.     Trace.TraceInformation("MyList list name: {0}", myList);
  45.  
  46.     base.ItemUpdated(properties);
  47. }
  48. catch (Exception ex)
  49. {
  50.     Trace.TraceError("Exception: '{0}', '{1}', '{2}'", ex.Message, ex.InnerException, ex.StackTrace);
  51. }

The next code is included in the static ResXResourceSet method. Parts of the code are similar to the above one. Of course, you don’t have to include the lines used only to trace out the state of the contexts. I used these lines only to validate the contexts do not exists when entering into the event handler method but they are there in the place we need them (in this case on resource reading) after they got injected.

  1. String rsPath = null;
  2.  
  3. HttpContext currentContext = HttpContext.Current;
  4. Trace.TraceInformation("Try to get HTTP context");
  5.  
  6. if (currentContext != null)
  7. {
  8.     rsPath = HttpContext.Current.Server.MapPath("~/App_GlobalResources/PORequest") + "\\" + resourceFile + ".resx";
  9.     try
  10.     {
  11.         Trace.TraceInformation("Try to get SP context");
  12.  
  13.         SPContext context = SPContext.Current;
  14.         if ((context != null) && (context.Web != null))
  15.         {
  16.             Trace.TraceInformation("SPWeb URL: {0}", context.Web.Url);
  17.             Trace.TraceInformation("SPWeb user: {0}", context.Web.CurrentUser);
  18.         }
  19.         else
  20.         {
  21.             Trace.TraceInformation("HTTP context found but no usable SP context");
  22.         }
  23.     }
  24.     catch (Exception ex)
  25.     {
  26.         Trace.TraceError("Exception: '{0}', '{1}', '{2}'", ex.Message, ex.InnerException, ex.StackTrace);
  27.     }
  28. }
  29. else
  30. {
  31.     Trace.TraceInformation("No HTTP context");
  32. }
  33. Trace.TraceInformation("Path: " + rsPath);

Again, this method is only presented here as a technological curiosity, use it at your own risk.

About these ads

3 Comments »

  1. Nice workaround.Thanks sir.

    Is it bad on performance if we get the site using properties.Web.Site? Can you explain me the reason please? I am curious to know so that I can make myself more careful next time when I access the web/site object from event receivers’ properties.

    I noticed that you are using “SPContext context = SPContext.Current;” after building the context in your code and so I am just wondering how better this approach is for performance.

    Again, I am not questioning you in any thing. I am just curious to know about the inner details.

    Thanks Peter..

    Comment by Chaitu — June 26, 2011 @ 16:13

    • Hi,

      It is the best to use properties.Web / properties.Web.Site if you are in the event handling method or you can pass these objects to methods you call into. However, in our case we had an object whose properties were used to access resource string values, so we cannot pass the SPSite object without altering existing code at several places / creating new way of resource handling. Generally, the SPContext.Current and HttpContext.Current are null in the context of an event receiver, that is why SPWeb is provided you in the properties parameter of the event handler method. As I wrote in the post, I try to read the contexts these way is only to verify they are null (they should be), and not remained set from the former event handling. If event handlers would have set the context globally, then setting the context on one instance might mess up the working of another event handler instance. Based on my experiment the contexts are always null when the event handler starts, even they were set already on a previous run, so event receivers and contexts set would not interact with each other.

      Of course, the safest way would be probably to refactor the code, the aim of this blog post is only to illustrate the technique I found working.

      Peter

      Comment by Peter Holpar — June 27, 2011 @ 07:45

  2. Thanks. Useful post, solved a problem I’ve been having. Have to say though – I don’t understand why people have decent blog content but have a design that makes scrolling through code snippets a ball ache.

    Comment by Daniel McNulty — July 4, 2012 @ 10:51


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 54 other followers

%d bloggers like this: