Helper Class for Avoiding Memory Leaks with SPSite and SPWeb Objects

Microsoft provides a sophisticated API/Object Model for programmatic access to the SharePoint from .NET custom code. 

Invariably, most calls to the object model begin with a call to the SPSite and SPWeb objects, which represent the site collection and web accessed accordingly.  Developers must dispose the memory of any instance of SPSite and SPWeb.  This includes calls to site.AllWebs[], web.ParentWeb etc.  The only exception is when using site and web instances from the SPContext singleton, provided by the object model.

For example, the following is code that I have sometimes observed:

   1:  SPSite site = new SPSite("http://myMOSSsite/");
   2:  SPWeb web = site.OpenWeb();
   3:  SPList list = web.Lists["MyList"];

The above code leaks memory and resources, since the developer is allocating a site and web instance and never disposing of the objects.  A better approach is the following code:

   1:  using (SPSite site = new SPSite("http://myMOSSsite/"))
   2:  {
   3:      using (SPWeb web = site.OpenWeb())
   4:      {
   5:          SPList list = web.Lists["MyList"];
   6:      }
   7:  }

The second code example guarantees the clean up of SPSite and SPWeb objects when the execution leaves scope, the problem occurs with the use of the SPList object instance.  Occasionally I have seen the above code wrapped into a method, which then returns the SPList object.  Surprisingly, SharePoint allows you to use the list object instance, even though the parent web and site objects were disposed at the end of the method.  This situation should be avoided and SharePoint will leave plenty of error messages in the logs to warn you of this fact.

A best practice is to never return SPSite and SPWeb objects from methods, since it is not immediately clear whether the caller of your method is responsible for disposing web ands site objects (the method may be using SPContext or maybe allocating a new instance).  Developers should also avoid returning SPSite and SPWeb dependent objects unless the SPWeb instance is passed to the method or the method is categorically using SPContext.

Since we’re avoiding passing around references to these potentially leaky objects, how should developers write code?

The answer is to use delegates.  The following code is a handy helper class that encapsulates the use of SPSite and SPWeb objects, and executes custom code via delegates:

   1:      /// <summary>
   2:      /// Helper class to prevent memory leakage in SharePoint Object Model calls.
   3:      /// </summary>
   4:      public static class SPHelper
   5:      {
   6:          #region Methods
   7:   
   8:          /// <summary>
   9:          /// Get a SPSite reference for a given <paramref name="url">site URL</paramref> and execute
  10:          /// a given <paramref name="action">Action</paramref>.
  11:          /// </summary>
  12:          /// <param name="url">Site Url.</param>
  13:          /// <param name="action">Action delegate.</param>
  14:          public static void GetSite(string url, Action<SPSite> action)
  15:          {
  16:              if (String.IsNullOrEmpty(url))
  17:                  throw new ArgumentNullException("url");
  18:              if (null == action)
  19:                  throw new ArgumentNullException("action");
  20:              using (var site = new SPSite(url))
  21:                  action(site);
  22:          }
  23:   
  24:          /// <summary>
  25:          /// Get an SPSite and SPWeb reference for a given <paramref name="url">site URL</paramref> and execute
  26:          /// a given <paramref name="action">Action</paramref>.
  27:          /// </summary>
  28:          /// <param name="url">Web Url.</param>
  29:          /// <param name="action">Action delegate.</param>
  30:          public static void GetWeb(string url, Action<SPSite, SPWeb> action)
  31:          {
  32:              if (String.IsNullOrEmpty(url))
  33:                  throw new ArgumentNullException("url");
  34:              if (null == action)
  35:                  throw new ArgumentNullException("action");
  36:              using (var site = new SPSite(url))
  37:              {
  38:                  using (var web = site.OpenWeb())
  39:                  {
  40:                      if (null == web)
  41:                          throw new SPException("Failed to open web");
  42:                      action(site, web);
  43:                  }
  44:              }
  45:          }
  46:   
  47:          /// <summary>
  48:          /// Get an SPSite and SPWeb (root) reference for a given <paramref name="url">site URL</paramref> and execute
  49:          /// a given <paramref name="action">Action</paramref>.
  50:          /// </summary>
  51:          /// <param name="url">Site Url.</param>
  52:          /// <param name="action">Action delegate.</param>
  53:          public static void GetRootWeb(string url, Action<SPSite, SPWeb> action)
  54:          {
  55:              if (String.IsNullOrEmpty(url))
  56:                  throw new ArgumentNullException("url");
  57:              if (null == action)
  58:                  throw new ArgumentNullException("action");
  59:              using (var site = new SPSite(url))
  60:              {
  61:                  using (var web = site.RootWeb)
  62:                  {
  63:                      action(site, web);
  64:                  }
  65:              }
  66:          }
  67:   
  68:          /// <summary>
  69:          /// Get an SPSite and SPWeb reference for a given <paramref name="url">site URL</paramref> and
  70:          /// <paramref name="username"/> and execute
  71:          /// a given <paramref name="action">Action</paramref>.
  72:          /// </summary>
  73:          /// <remarks>Username must be a user in the site.</remarks>
  74:          /// <param name="url">Site Url.</param>
  75:          /// <param name="username">Username with access to the web.</param>
  76:          /// <param name="action">Action delegate.</param>
  77:          public static void GetSecureWeb(string url, string username, Action<SPSite, SPWeb, SPUser> action)
  78:          {
  79:              if (String.IsNullOrEmpty(url))
  80:                  throw new ArgumentNullException("url");
  81:              if (String.IsNullOrEmpty(username))
  82:                  throw new ArgumentNullException("username");
  83:              if (null == action)
  84:                  throw new ArgumentNullException("action");
  85:              // Open the regular web first.
  86:              using (var site = new SPSite(url))
  87:              {
  88:                  using (var web = site.RootWeb)
  89:                  {
  90:                      // See if user is registered with the root web.
  91:                      var user = web.AllUsers[username];
  92:                      if (null == user)
  93:                          throw new SPException("No user found");
  94:                      // Open the secure site.
  95:                      using (var secureSite = new SPSite(url, user.UserToken))
  96:                      {
  97:                          using (var secureWeb = secureSite.OpenWeb())
  98:                          {
  99:                              if (null == secureWeb)
 100:                                  throw new SPException("Failed to open secure web");
 101:                              action(secureSite, secureWeb, user);
 102:                          }
 103:                      }
 104:                  }
 105:              }
 106:          }
 107:   
 108:          /// <summary>
 109:          /// Get an SPSite and SPWeb (root) reference for a given 
 110:          /// <paramref name="url">site Url</paramref> and
 111:          /// <paramref name="title">web title</paramref> and execute
 112:          /// a given <paramref name="action">Action</paramref>.
 113:          /// </summary>
 114:          /// <param name="url">Site Url.</param>
 115:          /// <param name="title">Web Title.</param>
 116:          /// <param name="action">Action delegate.</param>
 117:          public static void GetWebByTitle(string url, string title, Action<SPSite, SPWeb> action)
 118:          {
 119:              if (String.IsNullOrEmpty(url))
 120:                  throw new ArgumentNullException("url");
 121:              if (String.IsNullOrEmpty(title))
 122:                  throw new ArgumentNullException("title");
 123:              if (null == action)
 124:                  throw new ArgumentNullException("action");
 125:              using (var site = new SPSite(url))
 126:              {
 127:                  using (var web = site.AllWebs[title])
 128:                  {
 129:                      if (null == web)
 130:                          throw new SPException("Web not found");
 131:                      action(site, web);
 132:                  }
 133:              }
 134:          }
 135:   
 136:          /// <summary>
 137:          /// Get an SPSite and SPWeb (parent) reference for a given 
 138:          /// <paramref name="url">site Url</paramref> and
 139:          /// a given <paramref name="action">Action</paramref>.
 140:          /// </summary>
 141:          /// <param name="url">Site Url.</param>
 142:          /// <param name="action">Action delegate.</param>
 143:          public static void GetParentWeb(string url, Action<SPSite, SPWeb> action)
 144:          {
 145:              if (String.IsNullOrEmpty(url))
 146:                  throw new ArgumentNullException("url");
 147:              if (null == action)
 148:                  throw new ArgumentNullException("action");
 149:              using (var site = new SPSite(url))
 150:              {
 151:                  using (var web = site.OpenWeb())
 152:                  {
 153:                      if (null == web)
 154:                          throw new SPException("Cannot open web");
 155:                      if (web.IsRootWeb)
 156:                          throw new SPException("Url has no parent web.");
 157:                      using (var parentWeb = web.ParentWeb)
 158:                          action(site, parentWeb);
 159:                  }
 160:              }
 161:          }
 162:   
 163:          /// <summary>
 164:          /// Get a SPSite reference for a given <paramref name="url">site URL</paramref> and execute
 165:          /// a given <paramref name="action">Action</paramref>. Elevate to SharePoint service account
 166:          /// before opening.
 167:          /// </summary>
 168:          /// <param name="url">Site Url.</param>
 169:          /// <param name="action">Action delegate.</param>
 170:          public static void GetSiteElevated(string url, Action<SPSite> action)
 171:          {
 172:              SPSecurity.RunWithElevatedPrivileges(() => GetSite(url, action));
 173:          }
 174:   
 175:          /// <summary>
 176:          /// Get an SPSite and SPWeb reference for a given <paramref name="url">site URL</paramref> and execute
 177:          /// a given <paramref name="action">Action</paramref>. Elevate to SharePoint service account
 178:          /// before opening.
 179:          /// </summary>
 180:          /// <param name="url">Web Url.</param>
 181:          /// <param name="action">Action delegate.</param>
 182:          public static void GetWebElevated(string url, Action<SPSite, SPWeb> action)
 183:          {
 184:              SPSecurity.RunWithElevatedPrivileges(() => GetWeb(url, action));
 185:          }
 186:   
 187:          /// <summary>
 188:          /// Get an SPSite and SPWeb (root) reference for a given <paramref name="url">site URL</paramref> and execute
 189:          /// a given <paramref name="action">Action</paramref>. Elevate to SharePoint service account
 190:          /// before opening.
 191:          /// </summary>
 192:          /// <param name="url">Site Url.</param>
 193:          /// <param name="action">Action delegate.</param>
 194:          public static void GetRootWebElevated(string url, Action<SPSite, SPWeb> action)
 195:          {
 196:              SPSecurity.RunWithElevatedPrivileges(() => GetRootWeb(url, action));
 197:          }
 198:   
 199:          /// <summary>
 200:          /// Get an SPSite and SPWeb (root) reference for a given 
 201:          /// <paramref name="url">site Url</paramref> and
 202:          /// <paramref name="title">web title</paramref> and execute
 203:          /// a given <paramref name="action">Action</paramref>. 
 204:          /// Elevate to SharePoint service account before opening.
 205:          /// </summary>
 206:          /// <param name="url">Site Url.</param>
 207:          /// <param name="title">Web Title.</param>
 208:          /// <param name="action">Action delegate.</param>
 209:          public static void GetWebByTitleElevated(string url, string title, Action<SPSite, SPWeb> action)
 210:          {
 211:              SPSecurity.RunWithElevatedPrivileges(() => GetWebByTitle(url, title, action));
 212:          }
 213:   
 214:          /// <summary>
 215:          /// Get an SPSite and SPWeb (parent) reference for a given 
 216:          /// <paramref name="url">site Url</paramref> and
 217:          /// a given <paramref name="action">Action</paramref>.
 218:          /// Elevate to SharePoint service account before opening.
 219:          /// </summary>
 220:          /// <param name="url">Site Url.</param>
 221:          /// <param name="action">Action delegate.</param>
 222:          public static void GetParentWebElevated(string url, Action<SPSite, SPWeb> action)
 223:          {
 224:              SPSecurity.RunWithElevatedPrivileges(() => GetParentWeb(url, action));
 225:          }
 226:   
 227:          /// <summary>
 228:          /// Get an SPSite and SPWeb reference for a given <paramref name="url">site URL</paramref> and
 229:          /// <paramref name="username"/> and execute
 230:          /// a given <paramref name="action">Action</paramref>.
 231:          /// Elevate to SharePoint service account before opening.
 232:          /// </summary>
 233:          /// <remarks>Username must be a user in the site.</remarks>
 234:          /// <param name="url">Site Url.</param>
 235:          /// <param name="username">Username with access to the web.</param>
 236:          /// <param name="action">Action delegate.</param>
 237:          public static void GetSecureWebElevated(string url, string username, Action<SPSite, SPWeb, SPUser> action)
 238:          {
 239:              SPSecurity.RunWithElevatedPrivileges(() => GetSecureWeb(url, username, action));
 240:          }
 241:   
 242:          #endregion Methods
 243:      }

Using the above helper class, developers need never allocate web or site objects.  Furthermore, since each method call above cleans up memory after execution, developers never need to worry about too many web or site instances lying around waiting for garbage collection (also greeted with a nice error message in the log).

7 thoughts on “Helper Class for Avoiding Memory Leaks with SPSite and SPWeb Objects

  1. prabhash

    WOW!!! What a helpful insight. I just finished a WSS site and after reading your article, I find my application was not disposing the SPWeb and SPSite objects at all. wish I had read it before. But nevermind It will definitly help in future. Thanks Again!!!

  2. Keith Dahlby

    Hey Rob ~

    Great post! In my application of this technique, I prefer to use the simpler Action<SPWeb> rather than Action<SPSite, SPWeb> since I can always use the web’s Site property if I need it.

    One suggestion: rather than use SPSecurity.RunWithElevagedPrivileges, you’re better off using the system SPUserToken to get an elevated SPSite. Using the extensions I discuss <a href=”http://solutionizing.net/2009/01/06/elegant-spsite-elevation/” title=”Elegant SPSite Elevation”>here</a>, the code is relatively simple:

    public static void GetWebElevated(string url, Action<SPSite, SPWeb> action)
    {
    GetWeb(url, (s, w) => w.RunAsSystem(web => action(web.Site, web)));
    }

    Or my preference:

    public static void GetWebElevated(string url, Action<SPWeb> action)
    {
    GetWeb(url, w => w.RunAsSystem(action));
    }

    Cheers ~
    Keith

  3. Pingback: Helper classes for disposing SharePoint objects

Comments are closed.