Tuesday, September 20, 2011

Fix “Operation is not valid due to the current state of the object” error from SPUtility.SendEmail()

Some time when you use SPUtility.SendEmail method you may get “Operation is not valid due to the current state of the object” error:

at Microsoft.SharePoint.WebControls.SPControl.SPWebEnsureSPControl(HttpContext context)
at Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(HttpContext context)
at Microsoft.SharePoint.WebControls.SPControl.GetContextSite(HttpContext context)
at Microsoft.SharePoint.MailMessage.set_Body(String value)
at Microsoft.SharePoint.Utilities.SPUtility.SendEmail(SPWeb web, Boolean fAppendHtmlTag, Boolean fHtmlEncode, String to, String subject, String htmlBody, Boolean appendFooter)
at Microsoft.SharePoint.Utilities.SPUtility.SendEmail(SPWeb web, Boolean fAppendHtmlTag, Boolean fHtmlEncode, String to, String subject, String htmlBody)

It may occur e.g. when you use this method from web service. Let’s check the implementation of this method:

   1: public static bool SendEmail(SPWeb web, bool fAppendHtmlTag,
   2:     bool fHtmlEncode, string to, string subject, string htmlBody, bool appendFooter)
   3: {
   4:     MailMessage message = new MailMessage(fAppendHtmlTag, fHtmlEncode);
   5:     message.Web = web;
   6:     if (to != null)
   7:     {
   8:         message.To = to;
   9:     }
  10:     message.From = string.Empty;
  11:     if (subject != null)
  12:     {
  13:         message.Subject = subject;
  14:     }
  15:     if (htmlBody != null)
  16:     {
  17:         message.Body = htmlBody;
  18:     }
  19:     message.AppendEmailFooter = appendFooter;
  20:     return message.FSend();
  21: }

According to stack trace error comes from line 17 when assigning body to MailMessage. Property setter set_Body() uses SPControl.GetContextSite(…) method which causes the error:

   1: public virtual void set_Body(string value)
   2: {
   3:     if (string.Compare(this.m_md.strContentType, "text/html", true,
   4:         CultureInfo.InvariantCulture) != 0)
   5:     {
   6:         this.m_md.strHTMLBody = value;
   7:     }
   8:     else
   9:     {
  10:         string str;
  11:         SPWeb contextWeb;
  12:         if (!this.m_fHtmlEncode)
  13:         {
  14:             str = value;
  15:         }
  16:         else
  17:         {
  18:             str = SPHttpUtility.HtmlEncodeAllowSimpleTextFormatting(value);
  19:         }
  20:         HttpContext current = HttpContext.Current;
  21:         if (current == null)
  22:         {
  23:             SPSite site = this.Web.Site;
  24:             contextWeb = this.Web;
  25:         }
  26:         else
  27:         {
  28:             SPControl.GetContextSite(current);
  29:             contextWeb = SPControl.GetContextWeb(current);
  30:         }
  31:         if (((((ushort) contextWeb.Language) & 0x3ff) == 1) || (((ushort) contextWeb.Language) == 0x40d))
  32:         {
  33:             str = "<DIV dir=rtl>" + str + "</DIV>";
  34:         }
  35:         if (this.m_fAppendHtmlTag)
  36:         {
  37:             this.m_md.strHTMLBody =
  38:                 "<HTML><HEAD><META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html\"></HEAD><BODY>" +
  39:                     str + "</BODY></HTML>";
  40:         }
  41:         else
  42:         {
  43:             this.m_md.strHTMLBody = str;
  44:         }
  45:         this.m_fReset = true;
  46:     }
  47: }

When call is performed outside of control and any UI components the mentioned exception is thrown. As you can see in the code (lines 20-30) it checks HttpContext.Current. So in order to fix the error you may use the following workaround:

   1: var ctx = HttpContext.Current;
   2: try
   3: {
   4:     HttpContext.Current = null;
   5:     SPUtility.SendEmail(...);
   6: }
   7: finally
   8: {
   9:     HttpContext.Current = ctx;
  10: }

After that inside MailMessage.set_Body() method another lines will be used for initialization of contextWeb variable (line 23-24) and error will disappear.

3 comments: