none
Propagation of Generic Fault Exceptions through intermediary service RRS feed

  • Question

  • Hi,

    I am having an issue with generic FaultException<T> not being propagated when the exception originates in a secondary service.  

    I have two services; DoSomethingService and DoAnotherThingService.  

    • DoSomethingService has a single method on it, which is called DoSomething().
    • DoAnotherThing also has a single method, called DoAnotherThing().
    • When a client of the DoSomethingService calls DoSomething(), this method calls the DoAnotherThing() method on the DoAnotherThingService.  
    • DoAnotherThing() throws a FaultException<CustomFaultDetails>.   This is propagated back to the DoSomethingService as a FaultException<CustomFaultDetails>.  DoSomething() catches this exception and rethrows it back to the client.
    • The client has a catch block for FaultException<CustomFaultDetails>, but it is never hit.  Instead, it falls through to the non-generic FaultException catch block.

    I want the Client to receive a FaultException<CustomFaultDetails>, but it is only receiving a non-generic FaultException... why?

    Also notable, if I call the DoAnotherThingService.DoAnotherThing() method directly from the client, the generic FaultException<T> propagates correctly.  It seems to only loose its 'genericness' when passing through an intermediary service.  Furthermore, if I catch the exception and create a new exception of the same type (essentially rewrapping it), the client will receive the generic FaultException<T>.  I'd rather avoid rewrapping all generic fault exceptions, as this seems more like a workaround or bandage solution.

    Here's the code that will reproduce this behaviour:

    [DataContract]
    public class CustomFaultDetails
    {
    	public CustomFaultDetails(string message)
    	{
    		Message = message;
    	}
    
    	[DataMember]
    	public string Message { get; set; }
    }
    
    [ServiceContract]
    public interface IDoAnotherThingService
    {
    	[OperationContract]
    	[FaultContract(typeof(CustomFaultDetails))]
    	void DoAnotherThing();
    }
    
    public class DoAnotherThingService : IDoAnotherThingService
    {
    	public void DoAnotherThing()
    	{
    		throw new FaultException<CustomFaultDetails>(new CustomFaultDetails("Something bad happened."));
    	}
    }
    
    [ServiceContract]
    public interface IDoSomethingService
    {
    	[OperationContract]
    	[FaultContract(typeof(CustomFaultDetails))]
    	void DoSomething();
    }
    
    public class DoSomethingService : IDoSomethingService
    {
    	public void DoSomething()
    	{
    		var datEndpoint = new ServiceEndpoint(
    			ContractDescription.GetContract(typeof(IDoAnotherThingService)),
    			new NetNamedPipeBinding(),
    			new EndpointAddress("net.pipe://localhost/DoAnotherThingService"));
    
    		var datChannelFactory = new ChannelFactory<IDoAnotherThingService>(datEndpoint);
    		var channel = datChannelFactory.CreateChannel();
    
    		try
    		{
    			channel.DoAnotherThing();
    		}
    		catch (FaultException<CustomFaultDetails> fex)
    		{
    			Console.WriteLine(fex.Message);
    			throw;
    		}
    	}
    }
    
    [TestClass]
    public class WcfFaultExceptionTests
    {
    	[TestMethod]
    	public void TestWcfFaultPropagation()
    	{
    		// Start the DoAnotherThing service
    		ServiceHost datHost = new ServiceHost(typeof(DoAnotherThingService));
    		var datEndpoint = new ServiceEndpoint(
    					ContractDescription.GetContract(typeof (IDoAnotherThingService)),
    					new NetNamedPipeBinding(),
    					new EndpointAddress("net.pipe://localhost/DoAnotherThingService"));
    		datHost.AddServiceEndpoint(datEndpoint);
    		datHost.Open();
    
    		// Start the DoSomething service
    		ServiceHost dsHost = new ServiceHost(typeof(DoSomethingService));
    		var dsEndpoint = new ServiceEndpoint(
    					ContractDescription.GetContract(typeof(IDoSomethingService)),
    					new NetNamedPipeBinding(),
    					new EndpointAddress("net.pipe://localhost/DoSomethingService"));
    		dsHost.AddServiceEndpoint(dsEndpoint);
    		dsHost.Open();
    
    		var dsChannelFactory = new ChannelFactory<IDoSomethingService>(dsEndpoint);
    		var channel = dsChannelFactory.CreateChannel();
    
    		try
    		{
    			channel.DoSomething();
    		}
    		catch (FaultException<CustomFaultDetails> fex)
    		{
    			Console.WriteLine("Successful propagation of generic fault exception.");
    			throw;
    		}
    		catch (FaultException ex)
    		{
    			Assert.Fail("Type of FaultException not propagated.");
    		}
    	}
    }
    

    Thanks in advance for any insight.

    Mark

    Thursday, November 27, 2014 7:44 PM

Answers

  • Figured it out, but I'll leave the answer here for others.

    The reason the FaultException<T> wasn't being caught was because I hadn't explicitly set the Action on the FaultContract.  Since the Action was not specified, it auto generates an action that looks something like http://tempuri.org/IDoAnotherThingService/DoAnotherThingCustomFaultDetailsFault.  On the client side, it is expecting the Action to refer to the operation it called on the first service, which would be more like http://tempuri.org/IDoSomethingService/DoSomethingCustomFaultDetailsFault.  Since these don't match, it falls through to the non-generic FaultException catch block.

    So here's what I did to fix this:

    • On each of the service contracts where you are decorating the methods with FaultContract attributes, you can also set the action to a known string.  Setting this to be the same on the methods of both services that use that contract will ensure that the FaultException<T> looks the same to the client and will be handled as a generic FaultException<T>.

    In the example code I provided, changing the FaultContract line on each of the services to the following will fix my problem:

    [FaultContract(typeof(CustomFaultDetails), Action="http://mycompany.com/something/customfaultdetails")]

    Hope this post might help someone else who gets stuck on this.

    Cheers,

    Mark

    • Marked as answer by Mark Shoemaker Friday, November 28, 2014 1:12 PM
    Friday, November 28, 2014 1:11 PM

All replies

  • Handling exceptions in WCF Service is different than usual exceptions in .NET. As the WCF clients may not be based on .NET Technologies we cannot utilize the .NET Exception object to exchange exception details over the wire. For this purpose we should have a generic way to pass exception details to all type of clients, who consumes WCF Service.

    For this reason we have SOAP faults, SOAP faults are the way to propagate the exceptions from the service to the client application. .Net has FaultException<T> generic class which can raise SoapFault exception. Service should throw FaultException<T> instead of usual CLR Exception object. T can be any type which can be serialized.

    Friday, November 28, 2014 6:24 AM
  • Figured it out, but I'll leave the answer here for others.

    The reason the FaultException<T> wasn't being caught was because I hadn't explicitly set the Action on the FaultContract.  Since the Action was not specified, it auto generates an action that looks something like http://tempuri.org/IDoAnotherThingService/DoAnotherThingCustomFaultDetailsFault.  On the client side, it is expecting the Action to refer to the operation it called on the first service, which would be more like http://tempuri.org/IDoSomethingService/DoSomethingCustomFaultDetailsFault.  Since these don't match, it falls through to the non-generic FaultException catch block.

    So here's what I did to fix this:

    • On each of the service contracts where you are decorating the methods with FaultContract attributes, you can also set the action to a known string.  Setting this to be the same on the methods of both services that use that contract will ensure that the FaultException<T> looks the same to the client and will be handled as a generic FaultException<T>.

    In the example code I provided, changing the FaultContract line on each of the services to the following will fix my problem:

    [FaultContract(typeof(CustomFaultDetails), Action="http://mycompany.com/something/customfaultdetails")]

    Hope this post might help someone else who gets stuck on this.

    Cheers,

    Mark

    • Marked as answer by Mark Shoemaker Friday, November 28, 2014 1:12 PM
    Friday, November 28, 2014 1:11 PM