Monday, April 14, 2008

Code Camp Presentation Posted - Tapping the Power of LINQ

Well, I had the opportunity to present again at the Twin Cities Code Camp, this time #4, which was for Spring 2008. I did a general talk on LINQ including LINQ to Objects, LINQ to XML, and LINQ to SQL.

Anyway, thanks again to Jason Bock of Magenic Technologies for organization this event. From what I heard, we had a record turn out!

If you want to download the presentation and/or check out the sample code, you can do so here.

Thursday, April 10, 2008

Managing Unhandled Exceptions in ASP.NET Web Services

We've been working on building a web services at my current project at work for quite some time now and we're finally wrapping things up. One of the items to unfortunately get addressed toward the end of the project was error handling and logging (doesn't it always). And to my dismay the error handling scenario for ASP.NET Web Services (even in .NET 3.5) is not as pretty as it is with a standard ASP.NET Web Forms website.

Specifically, you can't just add a few lines of code to the Global.aspx.cs file in the Application_Error event handler method to log unhandled exceptions because the ASP.NET Web Services pipeline bypasses this event so it can return all the exceptions back to the caller. It does this by wrapping the unhandled exception in a SoapException, which ultimately gets deserialized as a SOAP fault. Out of the box, there is very little you can do with these unhandled exceptions before they are sent to the client. For example, there is no quick and easy way to log them like you can with .aspx pages.

The SoapExtension Solution

The most popular approach to solving this problem (that I've found so far) is to code up a custom SoapExtension subclass, which privodes you low-level access to the SoapMessage at different stages of the web service request and response. When the unhandled exception occurs, the SoapException can be captured in the response SoapMessage (via the Exception property). The SoapException's InnerException property then contains the actual unhandled exception. At this point you can at log the exception on its way out the door so when you get that email from your tech support department, you've got something to go on.

But if you've ever developed a real public-facing web service (like ours is), you'll quickly agree that simply logging the exception isn't enough. Often times you want a little more control over the contents of the SoapException itself that gets sent back to the caller. The SoapExtension subclass approach gives you the ability to modify the SoapException, but at a very low level. Specifically, it's already been deserialized into its representative SOAP fault XML and you have to do some fancy stream manipulation to insert any custom content into that fault (ex: add a detail element). In my opinion, this is a hack and not a very ellegant solution. Instead we should have more control of the SoapException before it gets thrown. Ideally if we could create and throw the SoapException ourselves, we could have much more control over the contents of the resuling SOAP fault (like the fault code and detail). Then we wouldn't have to bother with intercepting and manipulating the raw SOAP messages themselves.

Client-Caused vs. Server-Caused Errors

A great example of this is when you want to return a client-caused SOAP fault vs. a server-caused SOAP fault. A client-caused SOAP fault is one caused because the client sent invalid data in their request. For example, they're not authenticated or they sent a null for a value of a required web method parameter or data transfer object (DTO) property. The SOAP fault infrastructure allows you to classify this kind of error by throwing a SOAP fault with a Code = Client (or Sender in SOAP 1.2). It's also helpful to send information in the detail section of the SOAP fault about what is invalid and how to fix it. It's up to the client to then fix their request before sending it again; otherwise, they'll get another client-caused SOAP fault.

A server-caused SOAP fault is one where something actually bad happenned on the server. In this case the client is really helpless to fix the issue. Operations or an engineer on the development team needs to determine the issue and fix it on the server side. When this kind of error occurs, you want very little detail to be sent to the client, certainly not a full stack trace! Rather you want a simple error message like "An unexpected error has occurred on the server." Optionally you could also send some kind of error identifier, which the client could use when communicating with customer support so that the error can easily be referened in the server logs. Like the client-caused SOAP fault, the server-caused SOAP fault has a special fault Code = Server (or Receiver in SOAP1.2).

The Try/Catch Solution

The best way to implement catching all exceptions and throwing your own SoapException is with a try/catch around the code that can cause the unhandled exception, which essentially is every web method in your web service. Unfortunately, since there's no single place in code you can do this, you're faced with putting try/catch statements around the logic of every single web method. The code for one particular web method might look like this:


[WebMethod]
public string WebMethod1(int arg1, int arg2)
{
try
{
//web method logic
}
catch (ExceptionType1 ex)
{
//handle ExceptionType1
}
catch (ExceptionType2 ex)
{
//handle ExceptionType2
}
catch (Exception ex)
{
//handle all other exceptions
}
}

While this works, it's a ton of duplicated code, especially all the logic in the catch statements. Wouldn't it be nice if there was a way to add a little bit of code to each web method but put the bulk of the error handling logic in a single method somewhere else?

Well, there is!

The Try/Catch Using Anonymous Methods Solution

The trick is to put all of your even handling logic (including the try/catch) into a separate utility method and then invoke that method by passing it a delegate to your web method logic. This utility method might look something like this:


private T Execute<T>(Func<T> body)
{
//wrap everything in common try/catch
try
{
return body();
}
catch (SoapException)
{
//rethrow any pre-generated SOAP faults
throw;
}
catch (ValidationException ex)
{
//validation error caused by client
ClientError innerError = new ClientError();
//TODO: populate client error as needed
//throw SOAP fault
throw this.GenerateSoapException(
"An error occurred while validating the client request.",
SoapException.ClientFaultCode,
innerError);
}
catch (Exception ex)
{
//everything else is treated as an error caused by server
ServerError innerError = new ServerError();
//TODO: populate server error as needed
//TODO: log error
//throw SOAP fault
throw this.GenerateSoapException(
"An unexpected error occurred on the server.",
SoapException.ServerFaultCode,
innerError);
}
}

A few things to note about the Execute method above:


  • The delegate type being passed in is the generic Func<TResult> delegate, which is new to .NET 3.5 (Visual Studio 2008/C# 3.0). It works with any method that has no arguments and returns a value of the generic type TResult. If you're coding this in .NET 2.0 you could simply declare your own delegate that has the same signature like this
private delegate TResult Func<TResult>();

  • The same generic type is also specific in the Execute method and is also the return type of the Execute method. And since we execute the delegate and return its result, everything is done in a strongly-typed fashion with no casting. It's even more elegant since the caller doesn't have to explicitly specify the type T since it can be derrived from the actual parameters of the method this is passed (which we'll see in a moment).
  • The error-handling logic expcitly catches and rethrows SoapExceptions. The assumption here is that if something lower down threw an actual SoapException, they probably want that exception to drive the resulting SOAP fault generated by ASP.NET instead of the one we're going to create (in the catch statements that follow).
  • The error-handling logic explicitly catches an exception type that represents all client-caused errors (in this case a made-up exception class called ValidationException). If there are multiple exception types that represent this, then you'd have multiple catches.
  • The final catch (Exception) is a catch-all for everything else, which is, by definition, an unexpected server error. It has a TODO placeholder for logging the error.
  • The last two catch statements employ another utility method that generates the SoapException itself (which I will show shortly). This method uses XML serialization to serialize a custom error object DTO and put it in the SoapException Detail.

So, what would a web method look like that's using this Execute method? Back to our first web method example:

[WebMethod]
public string WebMethod1(int agr1, arg2)
{
return this.Execute(delegate
{
string result;
//do something with arg1 and arg2 and generate a string result
return result;
});
}


Notice how by putting the web method logic within an anonymous method, the call to the Execute method is nothing more than a thin wrapper. Also notice that we don't have to call the Execute method with a string type parameter (i.e. Execute(...)) because it's inferred from the return type of the anonymous method.

The beauty is that now you've got a single place where you can do all of your plumbing-related coding. Error handling is one thing you can do (which was the original focus of this blog post), but you can also do logging, add instrumentation code, or whatever, all in one spot!

And here's the handy code for the GenerateSoapException utility method referenced before:


private SoapException GenerateSoapException(string message, XmlQualifiedName faultCode, object detailError)
{
using (Stream stream = new MemoryStream())
{
using (XmlWriter writer = new XmlTextWriter(stream, Encoding.Default))
{
//build detail XML by serializing detailError object
XmlSerializer serializer = new XmlSerializer(detailError.GetType(), "your-webservice-namespace-here");
serializer.Serialize(writer, detailError);
stream.Position = 0;
XmlDocument doc = new XmlDocument();
XmlElement detail = this.SoapVersion == SoapProtocolVersion.Soap12
? doc.CreateElement("soap", "Detail", "http://www.w3.org/2003/05/soap-envelope")
: doc.CreateElement("detail");
doc.Load(stream);
detail.AppendChild(doc.DocumentElement);
//build exception
return new SoapException(
message,
faultCode,
this.Context.Request.Url.AbsoluteUri,
detail);
}
}
}

Enjoy!

~pete