Useful WCF Behaviors - IErrorHandler
Behaviors in WCF are so stinking useful, and once you get past the basics of WCF they're arguably a necessity. Microsoft has saved itself from hundreds of hours of cursing by including the ability to define custom behaviors.
My favorite use of behaviors is addressing some cross cutting concerns that we consistently have in our WCF services. Specifically logging, service registration, wsdl tweaking, and error handling. Which brings us around to the IErrorHandler interface.
Implementing IErrorHandler "Allows an implementer to control the fault message returned to the caller and optionally perform custom error processing such as logging". Thank you MSDN. IErrorHandler has two methods to implement: HandleError, and ProvideFault. Implement them so that they do what they say they do. HandleError should do something with the error (in our case, we log it) and ProvideFault should return a fault message.
#region IErrorHandler Members // HandleError. Log an error, then allow the error to be handled as usual. // Return true if the error is considered as already handled public bool HandleError(Exception error) { try { \\our loggin service LoggerClient logger = new LoggerClient(basic, logAddress); \\build log message StringBuilder err = new StringBuilder(); err.AppendLine("Source: " + error.Source); err.AppendLine("Target: " + error.TargetSite.ToString()); err.AppendLine("Message: " + error.Message); err.AppendLine("Stack Trace: " + error.StackTrace.ToString()); CustomLogEntry log = new CustomLogEntry(); log.AppDomainName = AppDomain.CurrentDomain.FriendlyName; log.EventId = 900; log.EventIdSpecified = true; log.MachineName = Environment.MachineName; log.Message = err.ToString(); log.Priority = 1; log.PrioritySpecified = true; log.Severity = TraceEventType.Error; log.SeveritySpecified = true; log.Title = "Exception Caught: " + error.Message; logger.Log(log); return true; } catch (Exception) { throw new Exception("error in the handler"); } } public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault) { // Shield the unknown exception FaultException faultException = new FaultException(error.Message); MessageFault messageFault = faultException.CreateMessageFault(); fault = Message.CreateMessage(version, messageFault, faultException.Action); } #endregion
Pretty straight forward.
Now we just have to figure out a way to get this to apply to our service, preferably without a lot of extra work. So we don't want to go mucking around with attributes or anything else we have to remember to stick in as we're writing the code. That's where behaviors come in. You can apply them via the config file.
We know we want this particular behavior to apply to our entire service, so we want to implement IServiceBehavior in our class along with IErrorHandler. IServiceBehavior has four methods that we have to implement, but fortunately we can ignore three of them. The one we care about in this case is ApplyDispatchBehavior, where we will apply our error handler to each of the channels in the service. In the example below ErrorHandler is the name of the class we are implementing. Inventive name, isn't it?
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { IErrorHandler errorHandler = new ErrorHandler(); foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) { ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher; channelDispatcher.ErrorHandlers.Add(errorHandler); } }
Great, that's done. Now there's only one thing left to do. We need a class that inherits from BehaviorExtensionElement so that we can add our behavior via configuration. Fortunately BehaviorExtensionElement is a breeze to implement. You need to impelement BehaviorType which just returns the type of your just implement IServiceBehavior class (ErrorHandler above), and CreateBehavior which returns an instantiation of the class.
class ErrorHandlerBehavior : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(ErrorHandler); } } protected override object CreateBehavior() { return new ErrorHandler(); } }
Now the code is done. The last thing to do is to apply the newly created behavior in the config file. Add our new BehaviorExtensionElement to the behaviorExtensions section, and then specify it in the serviceBehaviors section. One important caveat, if includeExceptionDetailInFaults is set to true, then the exception shielding (and I believe Error Handling) will not work.
<system.serviceModel> <extensions> <behaviorExtensions> <add name="ErrorLogging" type="ErrorHandlerBehavior, ErrorHandling, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8746502a48718374" /> </behaviorExtensions> </extensions> <bindings> <basicHttpBinding> <binding name="basicBinding"> </binding> </basicHttpBinding> </bindings> <services> <service behaviorConfiguration="Service1Behavior" name="Service"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicBinding" contract="Service" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="Service1Behavior"> <serviceMetadata httpGetUrl="" httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> <ErrorLogging /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
There you go. Apply this to all of your WCF services and you won't have to re-invent the wheel again.