Today, I am going to show how to handle errors (handled or unhandled by WCF service) at the application level. This is very similar to the way error is handled in IHttpModule (or Global.asax) in ASP.Net. This way you can control the format of the FaultException.
First, let’s take a look at how operation contract can be defined with FaultContract attribute with a custom type ErrorInformation.
[code language=”csharp”]
[OperationContract]
[FaultContract(typeof(ErrorInformation))]
long Add(long value1, long value2);
[/code]
To explicitly control the behavior of the application when an exception is thrown, implement the IErrorHandler interface and add it to the Dispatcher’s ErrorHandlers property. IErrorHandler enables you to explicitly control the SOAP fault generated, decide whether to send it back to the client, and perform associated tasks, such as logging. Error handlers are called in the order in which they were added to the ErrorHandlers property. This is a direct quote from Microsoft.com.
So, let us create a CustomErrorBehavior class that implements IErrorHandler. Next, we need to hook this behavior to the ChannelDispatcher. We are going to add this CustomErrorBehavior to our existing ServiceBehavior. Of course, you need to use the ServiceBehavior as an attribute to the service implementation class. If you prefer to attach the ServiceBehavior via configuration, you will need to implement ServiceBehaviorExtensionElement.
Here is the quick 1-2-3-4 of what we are going to need to make the Error Behavior work-
1) Create a CustomErrorBehavior class that implements IErrorHandler.
2) All the supporting classes needed to support CustomErrorBehavior.
3) ServiceBehavior to attach the CustomErrorBehavior to DispatchRuntime. Then, you can simply add the ServiceBehavior as an attribute to the service implementation class.
4) If you prefer to attach the ServiceBehavior through configuration, you will need to implement ServiceBehaviorExtensionElement.
So, let’s jump on to the codes-
CustomErrorBehavior.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Globalization;
namespace www.aspnet4you.com
{
internal class CustomErrorBehavior : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
FaultException
try
{
if (error is FaultException
{
excFault = (FaultException
}
else
{
ErrorInformation errInfo = new ErrorInformation();
errInfo.Message = error.Message;
errInfo.ErrorDetails = error.ToString();
excFault = new FaultException
}
fault = Message.CreateMessage(version, excFault.Code, excFault.Reason.GetMatchingTranslation(CultureInfo.InvariantCulture).Text, excFault.Detail, excFault.Action);
}
catch (Exception exc)
{
try
{
ErrorInformation errInError = new ErrorInformation();
errInError.ErrorSeverity =”Error”;
errInError.Reason = “Unhandled Exception in CustomErrorBehavior.”;
errInError.Message = exc.Message;
errInError.ErrorDetails = exc.ToString();
}
catch { }
}
}
}
}
[/code]
ErrorInformation.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.IO;
namespace www.aspnet4you.com
{
///
/// This information is used for error logging or tracking purposes.
///
[Serializable, XmlRoot(Namespace = “http://www.aspnet4you.com/services”)]
public class ErrorInformation : ISerializable
{
private string m_Reason = “UnKnown”; //Define your own reason codes.
private string m_Message = “An unexpected error occured while executing the service method.”;
private string m_ErrorDetails = string.Empty;
private string m_ErrorSeverity = “Error”; //Define your own severity enum.
///
///
public string ErrorDetails
{
get { return m_ErrorDetails; }
set { m_ErrorDetails = value; }
}
///
///
public string ErrorSeverity
{
get { return m_ErrorSeverity; }
set { m_ErrorSeverity = value; }
}
///
///
public string Reason
{
get { return m_Reason; }
set { m_Reason = value; }
}
///
///
public string Message
{
get { return m_Message; }
set { m_Message = value; }
}
///
///
///
public override string ToString()
{
return SerializationHelper.Serialize
}
///
///
///
///
///
///
public ErrorInformation(string msg, string rsn, string errDetails, string severity)
{
this.Message = msg;
this.Reason = rsn;
this.ErrorDetails = errDetails;
this.ErrorSeverity = severity;
}
///
///
public ErrorInformation()
{
}
///
/// is very helpful for interoperatibility. This setp eleminates the Backing Field generation by
/// the .net framework.
///
/// SerializationInfo
/// StreamingContext
public ErrorInformation(SerializationInfo info, StreamingContext context):this(new CustomSerializationInfo(info), context)
{
}
///
/// is useful when an element is not present in SerializationInfo (cause serializer
/// to break with SerializationException) and we try to get it’s value. Clients are not
/// required to pass the optional elements and the serialization should not break.
///
/// CustomSerializationInfo
/// StreamingContext
private ErrorInformation(CustomSerializationInfo info, StreamingContext context)
{
ErrorDetails = info.Get
ErrorSeverity = info.Get
Reason = info.Get
Message = info.Get
}
///
///
/// SerializationInfo
/// StreamingContext
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(“ErrorDetails”, ErrorDetails);
info.AddValue(“ErrorSeverity”, ErrorSeverity);
info.AddValue(“Reason”, Reason);
info.AddValue(“Message”, Message);
}
}
}
[/code]
CustomSerializationInfo.cs:
[code language=”csharp”]
using System.Runtime.Serialization;
namespace www.aspnet4you.com
{
public class CustomSerializationInfo
{
private SerializationInfo info = null;
public CustomSerializationInfo(SerializationInfo info)
{
this.info = info;
}
public T Get
{
T result = default(T);
try
{
result = (T)this.info.GetValue(Name, typeof(T));
}
catch (SerializationException)
{
result = default(T);
}
return result;
}
public SerializationInfo GetSerializationInfo()
{
return this.info;
}
}
}
[/code]
SerializationHelper.cs:
[code language=”csharp”]
using System;
using System.Xml.Serialization;
using System.Text;
using System.Xml;
using System.IO;
namespace www.aspnet4you.com
{
public class SerializationHelper
{
///
///
///
/// Object to be serialized
///
public static string Serialize
{
if (obj == null)
{
return null;
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, obj);
return writer.ToString();
}
}
///
///
///
/// Object to be deserialized
///
public static T Deserialize
{
if (string.IsNullOrEmpty(stringXml) == true)
{
return default(T);
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringReader reader = new StringReader(stringXml))
{
return (T)serializer.Deserialize(reader);
}
}
}
}
[/code]
ServiceBehavior.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
namespace www.aspnet4you.com
{
public class ServiceBehavior: Attribute, IServiceBehavior
{
public ServiceBehavior()
{
}
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
cd.ErrorHandlers.Add(new CustomErrorBehavior());
}
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion IServiceBehavior Members
}
}
[/code]
ServiceBehaviorExtensionElement.cs (optional):
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Configuration;
namespace www.aspnet4you.com
{
public class ServiceBehaviorExtensionElement : BehaviorExtensionElement
{
#region BehaviorExtensionElement Optimized
protected override object CreateBehavior()
{
return new ServiceBehavior();
}
public override Type BehaviorType
{
get { return typeof(ServiceBehavior); }
}
#endregion BehaviorExtensionElement Optimized
}
}
[/code]
