Tuesday 28 October 2008

Sample code from my top WCM tips presentation

Since AC suggested I post some sample code for some of the points I was making in my top 5 WCM tips presentation, I've now done just that. In the code you can find examples of the following:

  • Exception handler HTTP module - sends notification e-mails when an unhandled exception occurs and redirects the user to a configurable 'friendly' error page
  • Custom base class for master page - this is almost a skeleton class since your implementation will vary, but demonstrates how to use a custom base class
  • Trace helper class and matching ReSharper templates - for logging what happens in your code (this one's a bonus since I recommend these classes implement trace!)

These files can be downloaded in a Visual Studio project from:

http://sharepointchris.googlepages.com/wcmsamplecode

But for those of you who'd just like a quick look at the code, see below.  The actual classes in the project have comments to help you 'use' these things in your site - these have been removed below for readability:

Exception handler HTTP module code:

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using COB.SharePoint.Utilities;

namespace COB.Demos.WcmSamples
{
public class ExceptionHandlerHttpModule : IHttpModule
{
#region -- Private members --

private TraceSwitch traceSwitch = new TraceSwitch("COB.Demos.WcmSamples.ExceptionHandlerHttpModule",
"Trace switch for the COB.Demos.WcmSamples.ExceptionHandlerHttpModule.");

private TraceHelper trace = null;

#endregion

#region -- Constructor

public ExceptionHandlerHttpModule()
{
trace = new TraceHelper(this);
}

#endregion

public void Init(HttpApplication context)
{
context.Error += context_Error;
}

private void context_Error(Object sender, EventArgs e)
{
trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "context_Error(): Entered error-handling module.");

// collect the last error..
var exception = HttpContext.Current.Server.GetLastError();

// process it/send e-mail..
processError(exception);

trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "context_Error(): Finished handling error, " +
"about to redirect.");

// and finally clear the error and redirect to the friendly error page..
HttpContext.Current.Server.ClearError();
HttpContext.Current.Response.Clear();
string sErrorUrl = ConfigStore.GetValue("Errors", "FriendlyErrorPageUrl");
HttpContext.Current.Response.Redirect(sErrorUrl);
}

private void processError(Exception exception)
{
trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "processError(): Entered.");

string sMessage = buildEmailText(exception);
trace.WriteLineIf(traceSwitch.TraceInfo, TraceLevel.Info, "processError(): Built error string '{0}', calling SendEmail.",
sMessage);

sendEmail(sMessage);

trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "processError(): Leaving.");
}

private static string buildEmailText(Exception exception)
{
var messageBuilder = new StringBuilder();
messageBuilder.AppendLine("<strong>Date: </strong>" + DateTime.Now);
messageBuilder.AppendLine("<br /><strong>Message: </strong>" + exception.Message);
messageBuilder.AppendLine("<br /><strong>Source: </strong>" + exception.Source);
messageBuilder.AppendLine("<br /><strong>Current user: </strong>" + Thread.CurrentPrincipal.Identity.Name);
messageBuilder.AppendLine("<br /><strong>Machine name: </strong>" + Environment.MachineName);
messageBuilder.AppendLine("<br /><strong>Url: </strong>" + HttpContext.Current.Request.RawUrl);
messageBuilder.AppendLine("<br /><br /><strong>Exception details: </strong>" + exception);
return messageBuilder.ToString();
}

private void sendEmail(String message)
{
trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "sendEmail(): Entered.");

try
{
var errorToAddress = ConfigStore.GetValue("Errors", "SendToEmail");
var sErrorSubject = ConfigStore.GetValue("Errors", "EmailSubject");

trace.WriteLineIf(traceSwitch.TraceInfo, TraceLevel.Info,
String.Format("SendEmail(): About to send error e-mail to recipient list '{0}' using SPUtility.SendEmail().",
errorToAddress));

SPUtility.SendEmail(SPContext.Current.Web, false, false, errorToAddress, sErrorSubject, message);
}
catch (Exception exception)
{
var traceMessage = String.Format("Exception thrown attempting to send error e-mail using SPUtility.SendEmail(). " +
"Exception Details: {0}", exception);
trace.WriteLineIf(traceSwitch.TraceError, TraceLevel.Error, String.Format("sendEmail(): {0}", traceMessage));
}

trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "sendEmail(): Leaving.");
}

public void Dispose()
{
// nothing to do here..
}
}
}



Skeleton base class for master page:


using System;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
using Microsoft.SharePoint;

namespace COB.Demos.WcmSamples
{
public class BasePage : MasterPage
{
#region -- Private members --

private TraceSwitch traceSwitch = new TraceSwitch("COB.Demos.WcmSamples.BasePage",
"Trace switch for the base page class.");

private TraceHelper trace = null;

#endregion

public BasePage()
{
trace = new TraceHelper(this);
}

protected override void OnLoad(EventArgs e)
{
trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "OnLoad(): Entered.");

// TODO: add custom code here..

base.OnLoad(e);

trace.WriteLineIf(traceSwitch.TraceVerbose, TraceLevel.Verbose, "OnLoad(): Leaving.");
}

/// <summary>
/// Showing an example base page property..
/// </summary>
public bool UserIsAuthenticated
{
get { return HttpContext.Current.User.Identity.IsAuthenticated; }
}
}
}

2 comments:

Anonymous said...

I like this.

Do you know if it is possible to add a httpmodule First using SPWebConfigModification and app.Farm.Services.GetValue...SPWebService...().ApplyWebConfigModifications();

We are not allowed to mess with web.config using notepad so all changes has to be done by code (Farm with 2 frontends, 1 CA and one indexing) ... ;(

Chris O'Brien said...

Niklas,

I'm pretty sure you can target anything in web.config with SPWebConfigModification since you supply the XPath for the element you wish to add/modify.

So yes, should be possible :-)

Chris.