WCF Exception Handling






2.33/5 (7 votes)
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.
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:
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< 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:
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.
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 :)