Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WCF Exception Handling

2.33/5 (7 votes)
14 Aug 2013CPOL2 min read 39.1K  
WCF exception handling.

WCF, being a service, needs to manage all its exceptions internally and let only the very basic and general information flow out. This although being a general design requirement for all the components, with WCF it becomes all the more necessary as the service may be running in its own Application Domain, and along with on another machine on the network. Since direct debugging would not be available in this case, any exceptions raised need to be properly handled, otherwise the application can suffer a big downtime.

WCF has exceptions represented as FaultContracts, which are data contracts specifically meant for exceptions (kind of logical "Decorator" pattern). To make a class used to denote exception, we need to inherit it with FaultException and when applying to an operation contract, mark the method with FaultContract attribute. This tells that the particular operation may have such listed faults as expected behavior. In case of any unlisted faults, the channel gets broken and the whole system wireup is done again.

C#
1:  [DataContract(Namespace = "http://nitinsingh.com/code/wcf/")]
2:  public class MyCustomFault : FaultException
3:  {
4:       public string FaultDetails { get; set; }
5:  }
6:

Now the question is, can we always place such fault contracts on each necessary operation contract? Should be, but in real scenarios, its not possible always. There would be always some cases that certain exceptions get missed.

The solution....

Create a 'catch-all' exception handler for uncaught ones. WCF offers service behaviors to customize how a given service acts in specific cases. Within the behavior, we can get the dispatcher being used for the binding, and apply custom handling to any type of runtime event.

The IErrorHandler functionality provides such an generic error handling. Any unhandled exception within the service interaction is caught by this through its two methods - HandleError and ProvideFault and hence saves the channel.

To create this handler, we create a class inheriting from ServiceModel.Dispatcher.IErrorHandler (for error handling) and also ServiceModel.Description.IServiceBehavior (to attach this handler to the service behavior). Following is the code:

C#
  1:  using System;
  2:  using System.Configuration;
  3:  using System.Linq;
  4:  using System.ServiceModel;
  5:  using System.ServiceModel.Channels;
  6:  using System.ServiceModel.Description;
  7:  using System.ServiceModel.Dispatcher;
  8:  using WCFSamples.Contracts;
  9:  using WCFSamples.Service;
 10:   
 11:  // Want the error handler as a behavior within the WCF pipeline
 12:  public class MyErrorHandler : IErrorHandler, IServiceBehavior
 13:  {
 14:      #region IErrorHandler Members
 15:   
 16:      public bool HandleError(System.Exception error)
 17:      {
 18:          if (error != null)
 19:          {
 20:              // Provide logging ....
 21:   
 22:              // The error is handled
 23:              return true;
 24:          }
 25:          return false;
 26:      }
 27:   
 28:      public void ProvideFault(System.Exception error,
 29:          System.ServiceModel.Channels.MessageVersion version,
 30:          ref System.ServiceModel.Channels.Message fault)
 31:      {
 32:          // If error is already a FaultException, then let it pass through
 33:          if (!(error is FaultException))
 34:          {
 35:              // Create custom fault
 36:              MyCustomFault customFault = new MyCustomFault() { FaultDetails = "Something gone wrong" };
 37:   
 38:              // Create the fault exception of the type of fault
 39:              FaultException>MyCustomFault&lt f = new FaultException>MyCustomFault<(
 40:                  customFault, error.Message,
 41:                  FaultCode.CreateSenderFaultCode("MyCustomFault", "http://nitinsingh.com/code/wcf/"));
 42:              // Create the message fault
 43:              MessageFault mf = customFault.CreateMessageFault();
 44:              // Update reference to point to the message
 45:              fault = Message.CreateMessage(version, mf, Constants.Action);
 46:          }
 47:      }
 48:   
 49:      #endregion
 50:   
 51:      #region IServiceBehavior Members
 52:   
 53:      public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
 54:                                       System.Collections.ObjectModel.Collection endpoints,
 55:                                       System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
 56:      {
 57:          return;
 58:      }
 59:   
  60:      /// <summary>
  61:      /// Bind the error handler to the service behavior
  62:      /// </summary>
 63:      public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
 64:      {
 65:          foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
 66:          {
 67:              dispatcher.ErrorHandlers.Add(this);
 68:          }
 69:      }
 70:   
  71:      /// <summary>
  72:      /// Validate the whether all operation contracts have the FaultContracts defined
  73:      /// </summary>
 74:      public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
 75:      {
 76:          foreach (var svcEndPoint in serviceDescription.Endpoints)
 77:          {
 78:              // Don't check mex
 79:              if (svcEndPoint.Contract.Name != "IMetaDataExchange")
 80:              {
 81:                  foreach (var opDesc in svcEndPoint.Contract.Operations)
 82:                  {
 83:                      // Operation contract has no faults associated with them
 84:                      if (opDesc.Faults.Count == 0)
 85:                      {
 86:                          string msg = string.Format(
                                 "MyCustomEventHandlerBehvior requires a FaultContract(typeof(MyCustomFault))" +
 87:                              " on each operation contract. The {0} contains no FaultContracts.",
 88:                              opDesc.Name);
 89:                          throw new InvalidOperationException(msg);
 90:                      }
 91:                      // Operation contract has faults
 92:                      var fcExists = from fc in opDesc.Faults
 93:                                     where fc.DetailType == typeof(MyCustomFault)
 94:                                     select fc;
 95:                      // but not of our custom fault type
 96:                      if (fcExists.Count() == 0)
 97:                      {
 98:                          string msg =
                                  "MyCustomEventHandlerBehvior requires a FaultContract(typeof(MyCustomFault)) " +
 99:                              " on each operation contract.";
100:                          throw new InvalidOperationException(msg);
101:                      }
102:                  }
103:              }
104:          }
105:      }
106:   
107:      #endregion
108:   
109:   
110:      private ConfigurationPropertyCollection _prop = null;
111:      protected override ConfigurationPropertyCollection Properties
112:      {
113:          get {
114:              if (this._prop == null)
115:              {
116:                  this._prop = new ConfigurationPropertyCollection();
117:                  this._prop.Add(
118:                      new ConfigurationProperty("ServiceName", typeof(string), "",
119:                          ConfigurationPropertyOptions.IsRequired));
120:              }
121:              return this._prop;
122:          }
123:      }
124:  }

This creates an error handler and attaches to the service behavior of the service being bound.

Now that our behavior is completed, we need a mechanism to attach it to our service via configuration or custom attribute. For this, we need to create a class inheriting from ServiceModel.Configuration.BehaviorExtensionElement. As per definition, this "represents a configuration element that contains sub-elements that specify behavior extensions, which enable the user to customize service or endpoint behaviors". Here we are configuring a service behavior as this should work with all the endpoints which the service is listening on. Following is the code:

C#
1:  using System;
   2:  using System.ServiceModel.Configuration;
   3:   
   4:  namespace WCFSamples.Service
   5:  {
   6:      public class MyCustomBehaviorExtensionElement : BehaviorExtensionElement
   7:      {
   8:          public override Type BehaviorType
   9:          {
  10:              get { return typeof(MyErrorHandler); }
  11:          }
  12:   
  13:          protected override object CreateBehavior()
  14:          {
  15:              return new MyErrorHandler();
  16:          }
  17:   
  18:          [System.Configuration.ConfigurationProperty("ServiceName", 
  19:              DefaultValue = "", IsRequired = true)]
  20:          public string ServiceName { 
  21:              get { return (string)base["ServiceName"]; } 
  22:              set { base["ServiceName"] = value; } 
  23:          }
  24:      }
  25:  }

Now with our extension ready to be binded, all it takes is a little configuration entry in the app.config file.

XML
 1:  <?xml version="1.0" encoding="utf-8"?>
 2:  <configuration>
 3:      <system.serviceModel>
 4:          <behaviors />
 5:          <extensions>
 6:              <behaviorExtensions>
 7:                  <add name="CustomErrorHandler"
 8:                       type="WCFSamples.Service.MyCustomBehaviorExtensionElement, WCFSamples.Service,
 9:                       Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>

10:              </behaviorExtensions>
11:          </extensions>
12:      </system.serviceModel>
13:  </configuration>

That's it. By customizing the IErrorHandler class to log the exceptions occurring, we can identify what are the missed FaultContracts. Then either we can provide their entries within ProvideFault or place the FaultContract attribute on the operation contract itself (better).

Happy deploying :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)