You might argue that the best solution would be to buckle up and just fix the dang web parts.. but there were a lot of them, and the coders responsible for the mess had moved on (sounds familiar?) so we had the idea to use a general all-purpose exception handling strategy for the parts.
We wanted a crash in a web part or web control etc. not to crash the entire page, but instead to output the stacktrace inline in the web page during development, and to have a friendly message (defined by the webparts themselves, or a standard message) outputted to the user in run time, in stead of bringing the entire page down.
At the time we didn't know of any such solution. (I still don't).
The best, I think, would be if something like what I made (the file is included) was included into the ASP.Net framework. (if any of you ASP.Net guys are reading this: feel free to take my code and adapt it. It works better the higher it is in the class hierarchy
It could for instance be a part of System.Web.UI.WebControls.WebControl, with some web.config setting you could tweak to activate it... it would help heaps during development.. at least it would help me :) )
so, on to the solution we ended with:
I made a BaseWebPart which all the other web parts (the ones that were crashing intermittently) would inherit from. When inherited from, the BaseWebPart would ensure that a try/catch surrounded all the calls coming from the ASP.Net framework (Render, CreateChildControls .. etc.) so that a crash happening in one of these was handled nicely.
Each web part could then override a method called HandleException to control what happened if it crashed. the default behaviour (if they didn't override) was set to be
render the stacktrace when in debug mode
or
render a standard error message when in release mode
In order to wrap each framework call with exception handling,
we had a two-tiered approach in which
1) the first tier (ExceptionHandlingWebPartBase) overrides and seals methods,
2) then applies try/catch to a new set of methods, forwarding method parameters
3) these new methods are overridden in the second tier (BaseWebPart)
4) where they are sealed and a call is made to new virtual methods that are named the same as the framework methods.
5) These methods (now with a catch-block around them) are then overridden as needed in a regular web part that inherits from BaseWebPart. The exception handling is thus transparent to the inheritor.
The only downside of this module was that MOSS has several "base" web parts you would inherit from to make your own web parts, so this BaseWebPart had to be duplicated once for each of these.
We also wanted this functionality on web controls,
so we had to basically duplicate the code to make a BaseWebControl also.
If this error handling module was included higher in the hierarchy, for instance in WebControl or Control, once would be enough to cover all the different cases. Unfortunately c# doesn't allow mixins, or we would probably be able to get away with applying the exception handling all over in one fell swoop. As it stands, 4-5 classes of duplicated, identical code was found to be preferrable to 40-50 classes with duplicated exception handling logic.
"did it work?" you ask? well the benefits derived from this setup included
1) no need to emergency hotfix testing environments during testing
2) guaranteed logging of exceptions in production
3) a generally more sober error classification from the testers (before they would just say "everyting is down, this page doesn't work - this is unacceptable, level 1 error"
4) some exceptions in the web parts snuck into production, but instead of causing the entire page to halt, a friendly message was shown, buying us a great deal of time in fixing the problem ( next release instead of hotfix)
I'm definately using this setup again the next time I'm in a project with lots of web parts, web controls or custom controls.
Here is the code:
using System;
using System.IO;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
///*'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
/// This is the work of Andreas Knudsen (andreas.knudsen [ at ] gmail.com)
/// Anyone may use this work freely as it stands, or change it to suit their needs
/// on one condition: All use is on your own risk. It works for me, but I will not be
/// liable for any losses incurred by the use of this work.
///
/// If you would hold me responsible, you are not to use this work.
/// ************************************************************************************
///
/// In order to be truly useful, customizations are needed on lines 120 and 130
///
/// ************************************************************************************
namespace Util
{
/// <summary>
/// Base class for web part development.
/// All web parts should inherit from this class.
/// Exceptions thrown from web parts inheriting from this base
/// will not crash the Page the web part is on, but rather do one of two things:
///
/// 1)If compiled in debug mode: Render the stacktrace of the exception inline in the web page
/// 2)If compiled in release mode: Render a friendly error message inline in the web page.
///
/// This behaviour can be overridden by inheritors by overriding the method HandleException
///
/// HOW THIS WORKS:
/// -------
/// In order to wrap each framework call with exception handling,
/// we have a two-tiered approach in which
/// 1) the first tier (ExceptionHandlingWebPartBase) overrides and seals methods,
/// 2) then applies try/catch to a new set of methods, forwarding method parameters
/// 3) these new methods are overridden in the second tier (BaseWebPart)
/// 4) where they are sealed and a call is made to new virtual methods that are named the
/// same as the framework methods.
/// 5) These methods (now with a catch-block around them) are then overridden as needed in a
/// regular web part that inherits from BaseWebPart. The exception handling is thus
/// transparent to the inheritor.
/// </summary>
public abstract class BaseWebPart : ExceptionHandlingWebPartBase
{
#region temp methods for method piping (overrides and seals methods from ExceptionHandlingWebPartBase)
/*
* These methods re part of the plumbing necessary to give inheritors
* the expected interface.
*/
public override sealed void RenderWebPart(HtmlTextWriter writer)
{
Render(writer);
}
public override sealed void CreateWebPartChildControls()
{
CreateChildControls();
}
public override sealed void InitWebPart(EventArgs e)
{
OnInit(e);
}
public sealed override void PreRenderWebPart(EventArgs e)
{
OnPreRender(e);
}
public sealed override void LoadWebPart(EventArgs e)
{
OnLoad(e);
}
#endregion
#region Methods in which exceptions are now handled.
protected new virtual void Render(HtmlTextWriter writer)
{
base.RenderWebPart(writer);
}
protected new virtual void CreateChildControls()
{
base.CreateWebPartChildControls();
}
protected new virtual void OnInit(EventArgs e)
{
base.InitWebPart(e);
}
protected new virtual void OnLoad(EventArgs e)
{
base.LoadWebPart(e);
}
protected new virtual void OnPreRender(EventArgs e)
{
base.PreRenderWebPart(e);
}
#endregion
}
public abstract class ExceptionHandlingWebPartBase : WebPart
{
#region Exception handling section
private StringBuilder _errorOutput;
private bool _abortProcessing;
public virtual bool AbortProcessing
{
get { return _abortProcessing; }
set { _abortProcessing = value; }
}
public virtual void HandleException(Exception e, HtmlTextWriter writer)
{
#if !DEBUG
writer.Write("TODO: Insert helpful error message here");
#else
writer.Write(e.Message + "<br/>" + e.StackTrace);
#endif
}
public void ExceptionHappened(Exception ex)
{
AbortProcessing = true;
//TODO: use own logging framework here:
//Logger.Log(Severity.Error, ex.Message + " " + ex.StackTrace);
HandleException(ex, new HtmlTextWriter(new StringWriter(_errorOutput)));
}
#endregion
#region Override framework methods for method piping
protected override sealed void CreateChildControls()
{
if (!AbortProcessing)
{
try
{
CreateWebPartChildControls();
}
catch (Exception e)
{
ExceptionHappened(e);
}
}
}
protected override sealed void OnInit(EventArgs e)
{
AbortProcessing = false;
_errorOutput = new StringBuilder();
try
{
InitWebPart(e);
}
catch (Exception ex)
{
ExceptionHappened(ex);
}
}
protected override sealed void Render(HtmlTextWriter writer)
{
StringBuilder tempOutput = new StringBuilder();
if (!AbortProcessing)
{
HtmlTextWriter tempWriter = new HtmlTextWriter(new StringWriter(tempOutput));
try
{
RenderWebPart(tempWriter);
}
catch (Exception ex)
{
ExceptionHappened(ex);
}
}
if (AbortProcessing)
{
writer.Write(_errorOutput.ToString());
}
else
{
writer.Write(tempOutput.ToString());
}
}
protected override sealed void OnLoad(EventArgs e)
{
if (!AbortProcessing)
{
try
{
LoadWebPart(e);
}
catch (Exception ex)
{
ExceptionHappened(ex);
}
}
}
protected override sealed void OnPreRender(EventArgs e)
{
if (!AbortProcessing)
{
try
{
PreRenderWebPart(e);
}
catch (Exception ex)
{
ExceptionHappened(ex);
}
}
}
#endregion
#region Temp methods for method piping (will be overridden and sealed in subclass)
public virtual void RenderWebPart(HtmlTextWriter writer)
{
EnsureChildControls();
base.Render(writer);
}
public virtual void CreateWebPartChildControls()
{
base.CreateChildControls();
}
public virtual void InitWebPart(EventArgs e)
{
base.OnInit(e);
}
public virtual void LoadWebPart(EventArgs e)
{
base.OnLoad(e);
}
public virtual void PreRenderWebPart(EventArgs e)
{
base.OnPreRender(e);
}
#endregion
}
}
9 comments:
Thanks, Andreas.
Great work. Must definitely try this out next time I'm creating a web part.
Great post, trying to make it work in VB.NET. Having a problem with the "sealed" and "override" C# parameters. For example, if Render is override sealed (NotOverridable Overrides in VB) in the ExceptionHandlingWebPartBase class, I get an error when I try to use Render in the BaseWebPart, Visual Studio 2008 tells me that Render should be declared "overrides" there. Can you help with the VB.NET equivalent of what you are doing? Thanks
Hey -
This is cool - I did something similar but added data caching / asynchronous processing / data binding functionality as well. We've proven these things are necessary in high availability SP Apps.
So I've got a question - do you deploy the abstract base class in the same assembly as the concrete WebParts?
I had a random problem today on a fresh install to a new client where SP couldn't load any of the WebParts built against the abstract base classes.
In my case the base WebPart class is defined in a separate assembly.
The approach worked in every dev, test / staging environment, but failed in the client staging environment.
Chris - he is using the "new modifier C#": http://msdn.microsoft.com/en-us/library/435f1dw2(v=vs.80).aspx
Need not override when you can hide. There are pros and cons to this.
Cheers,
Matt
hello, can you describe how to implement this in my code, I have webpart.ascx.cs, and webpart.cs, what i understand is that to make webpart.cs inherit from this class that you wrote? what to do after that? thanks.
hello, can you describe how to implement this in my code, I have webpart.ascx.cs, and webpart.cs, what i understand is that to make webpart.cs inherit from this class that you wrote? what to do after that? thanks.
Daniel,
Your webpart.cs file inherits from another base class, I think it is Microsoft.SharePoint.WebPartPages.WebPart.
So, the class definition for your webpart.cs file has a line like this:
public class webpart : WebPart
if you have the line
using Microsoft.SharePoint.WebPartPages;
otherwise, you have the line:
public class webpart : Microsoft.SharePoint.WebPartPages.WebPart
The class after the colon is the base class you are inheriting from.
So, if you add the code from this example as another .cs file, you now have BaseWebPart as a class to inherit from, which has all the properties of Microsoft.SharePoint.WebPartPages.WebPart as well as the additional error handling functionality.
So, to answer your question, change the definition of your class to read:
public class webpart : BaseWebPart
and your class now inherits from BaseWebPart.
Thanks for your comments, but to handle exception, how do I do that? how do I call the exception handling? when catching an exception what should I call?
First, you don't HAVE to handle exceptions. Normally, if a web part throws an error, it breaks the ENTIRE page, and the user gets the typical exception page, with the bright red letters at the top, and the yellow background for the bottom half of the page.
If you are using these classes; when your web part throws an error, ONLY THE WEB PART is replaced with an error message, and the rest of your SharePoint page is rendered correctly. This is a much better experience for your user, believe me. It also immediately pinpoints which web part is broken.
Beyond that, you should use try/catch exception handling on all events, like you would with an ASP.NET page. For example, all of your code within the CreateChildControls() event and all button Click() events should be wrapped within a Try/Catch tag.
For example:
protected override void CreateChildControls()
{
try
{
// Your code here
}
catch (Exception e)
{
// Handle your exception here
}
);
I would recommend adding a Literal Control to the top of your web part, and showing the error in the Literal Control, perhaps in a span tag using red and bold formatting.
Any errors that occur within a method will bubble up to one of your events, where you handle them the same.
Any errors that occur OUTSIDE of the events you override should be handled by the underlying classes. The advantage of these classes is, once you have established them, perhaps in a base library for all your projects, you can pretty much forget about them. Just always reference them as your base class for new web parts, and use try/catch blocks as I describe above.
Hi,If you have a products page you can simply test different formats of the page. Once you stop the test, you can then see whichWeb Design Cochin achieved the higher results. Often times a small difference to a page can make a massive difference to the effectiveness of it.Thanks..............
Post a Comment