- Background
- XmlSerializer and XmlFormatter
- The XML Fetish
- Using the XmlFormatter
- Exception Handling
- Summary
- References
Exception Handling
Data contracts also assist in being able to notify clients of exceptions that may occur in a service. Too see how that works, follow these steps:
- Add this class to the Program.cs module of the Serialization project referred to previously:
public class SomeError { public string Content; }
- Create a data contract from that class using the DataContract and DataMember attributes:
[DataContract] public class SomeError { [DataMember] public string Content; }
This yields a data contract that specifies the format of a simple error message that the service might send to the client. - Add an operation to the IServiceViewOfService interface that defines the service's version of the service's contract:
[OperationContract(Name="Faulty")] decimal DivideByZero(decimal input);
- Add a fault contract to the operation, informing clients that they should anticipate that, instead of returning the expected result, the service may return an error message of the form defined by the SomeError data contract:
[OperationContract(Name="Faulty")] [FaultContract(typeof(SomeError))] decimal DivideByZero(decimal input);
- Add an implementation of the DivideByZero() method to the DerivativesCalculator class, which is the service type that implements the DerivativesCalculator service contract defined by the IServiceViewOfService interface:
public class DerivativesCalculator : IServiceViewOfService { [...] public decimal DivideByZero(decimal input) { try { decimal denominator = 0; return input / denominator; } catch (Exception exception) { SomeError error = new SomeError(); error.Content = exception.Message; throw new FaultException<SomeError>(error); } } }
By virtue of this code, when the service traps an exception in the DivideByZero() method, it creates an instance of the SomeError class to convey selected information about the exception to the caller. That information is then sent to the caller using the Windows Communication Foundation's generic, FaultException<T>. - Because of the FaultContract attribute on the DivideByZero() method, if the metadata for the service was to be downloaded, and client code generated from it using the Service Metadata Tool, the client's version of the contract would automatically include the definition of the DivideByZero() method, and its associated fault contract. However, in this case, simply add the method and the fault contract to the client's version of the contract, which is in the IClientViewOfService interface:
[ServiceContract(Name="DerivativesCalculator")] [KnownType(typeof(DerivedData))] public interface IClientViewOfService { [...] [OperationContract(Name = "Faulty")] [FaultContract(typeof(SomeError))] decimal DivideByZero(decimal input); }
- Now have the client use the Faulty operation by adding code to the static Main() method of the Program class, as shown in Listing 3.4.
Example 3.4. Anticipating a Fault
public class Program { public static void Main(string[] args) { using (ServiceHost host = new ServiceHost( typeof(DerivativesCalculator), new Uri[] { new Uri("http://localhost:8000/Derivatives") })) { host.AddServiceEndpoint( typeof(IServiceViewOfService), new BasicHttpBinding(), "Calculator"); host.Open(); Console.WriteLine("The service is available."); string address = "http://localhost:8000/Derivatives/Calculator"; ChannelFactory<IClientViewOfService> factory = new ChannelFactory<IClientViewOfService>( new BasicHttpBinding(), new EndpointAddress( new Uri(address))); IClientViewOfService proxy = factory.CreateChannel(); [...] try { Decimal quotient = proxy.DivideByZero(9); } catch (FaultException<SomeError> error) { Console.WriteLine("Error: {0}.", error.Detail.Content); } [...] } } }
Because receiving an error message from an attempt to use the operation should be anticipated, as the FaultContract for the operation indicates, the client code is written to handle that possibility. That is accomplished using the Windows Communication Foundation's FaultException<T> generic, which was also used in the code for the service to convey information about an exception to the client. The Detail property of the FaultException<T> generic provides access to an instance of T, which, in this case, is an instance of the SomeError class that the client can interrogate for information about the error that occurred.
This approach to handling exceptions provided by the Windows Communication Foundation has multiple virtues. It allows the developers of services to easily define the structure of the error messages that they want to transmit to client programmers. It also allows them to advertise to client programmers which operations of their services might return particular error messages instead of the results they would otherwise expect. The service programmers are able to easily formulate and transmit error messages to clients, and client programmers have a simple syntax, almost exactly like ordinary exception-handling syntax, for receiving and examining error messages. Most important, service programmers get to decide exactly what information about errors that occur in their services they want to have conveyed to clients.
However, the design of the Windows Communication Foundation does anticipate the utility, solely in the process of debugging a service, of being able to return to a client complete information about any unanticipated exceptions that might occur within a service. That can be accomplished using the ReturnUnknownExceptionsAsFaults behavior.
Behaviors are mechanisms internal to Windows Communication Foundation services or clients. They may be controlled by programmers or administrators.
Those behaviors that programmers are expected to want to control are manipulated using attributes. For example, if a programmer knows that a service is thread-safe, the programmer can permit the Windows Communication Foundation to allow multiple threads to access the service concurrently by adding the ServiceBehavior attribute to the service's service type class, and setting the value of the ConcurrencyMode parameter of that attribute to the Multiple value of the ConcurrencyMode enumeration:
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)] public class DerivativesCalculator : IServiceViewOfService
Behaviors that administrators are expected to want to control can be manipulated in the configuration of a service or client. The ReturnUnknownExceptionsAsFaults behavior is one of those. This configuration of a service will result in any unhandled exceptions being transmitted to the client:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service type= "DerivativesCalculator.DerivativesCalculatorServiceType, DerivativesCalculatorService" behaviorConfiguration="DerivativesCalculatorBehavior"> <endpoint address="" binding="basicHttpBinding" contract= "DerivativesCalculator.IDerivativesCalculator,DerivativesCalculatorService" /> </service> </services> <behaviors> <behavior name="DerivativesCalculatorBehavior" returnUnknownExceptionsAsFaults="true"/> </behaviors> </system.serviceModel> </configuration>
To reiterate, this configuration may be very useful for diagnosis in the process of debugging a service, but it is dangerous in debugging, because transmitting all the information about an exception to a client may expose information about the service that could be used to compromise it.