Mapping exception properties to fault properties in a FaultContractExceptionHandler

Topics: Exception Handling Application Block
May 8, 2013 at 9:11 PM
I have posted the same question on stack overflow here.

How do you map additional properties of an exception to your custom fault contract when using Enterprise Library 6's Exception Handling Application Block? I have a fault contract like so:
[DataContract]
public class SalaryCalculationFault
{
  [DataMember]
  public Guid FaultID { get; set; }

  [DataMember]
  public string FaultMessage { get; set; }

  [DataMember]
  public string StoredProcedureName { get; set; }
}
Here is the mapping and policy.
var mappings = new NameValueCollection();
mappings.Add("FaultID", "{Guid}");
mappings.Add("FaultMessage", "{Message}");
mappings.Add("StoredProcedureName", "{Procedure}");   //SqlException has a Procedure property

var testPolicy = new List<ExceptionPolicyEntry>
{
    {
        new ExceptionPolicyEntry(typeof(SqlException), 
            PostHandlingAction.ThrowNewException, 
            new IExceptionHandler[]
            {
                new FaultContractExceptionHandler(typeof(SalaryCalculationFault), mappings)
            }) 
    }
};

var policies = new List<ExceptionPolicyDefinition>();
policies.Add(new ExceptionPolicyDefinition(
    "TestPolicy", testPolicy));

exManager = new ExceptionManager(policies);
ExceptionPolicy.Reset();
ExceptionPolicy.SetExceptionManager(exManager);
When I catch the FaultException<SalaryCalculationFault> on the client, the StoredProcedureName is ALWAYS null. Why? The FaultId and FaultMessage are populated just fine. What am I missing. Thanks.
May 12, 2013 at 6:00 AM
I'm not able to recreate the behavior you are seeing. Are you sure that the SqlException.Procedure property is populated on the server? In some scenarios the property will not be populated (e.g. invalid stored procedure name).

Here's my code:
    [DataContract]
    public class SalaryCalculationFault
    {
        [DataMember]
        public Guid FaultID { get; set; }

        [DataMember]
        public string FaultMessage { get; set; }

        [DataMember]
        public string StoredProcedureName { get; set; }
    }

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        [FaultContract(typeof(SalaryCalculationFault))]
        string GetData(int value);
    }

    [ExceptionShielding("TestPolicy")]
    public class Service1 : IService1
    {
        static Service1()
        {
            var mappings = new NameValueCollection();
            mappings.Add("FaultID", "{Guid}");
            mappings.Add("FaultMessage", "{Message}");
            mappings.Add("StoredProcedureName", "{Procedure}");   //SqlException has a Procedure property

            var testPolicy = new List<ExceptionPolicyEntry>
            {
                {
                    new ExceptionPolicyEntry(typeof(SqlException), 
                        PostHandlingAction.ThrowNewException, 
                        new IExceptionHandler[]
                        {
                            new FaultContractExceptionHandler(typeof(SalaryCalculationFault), mappings)
                        }) 
                }
            };

            var policies = new List<ExceptionPolicyDefinition>();
            policies.Add(new ExceptionPolicyDefinition(
                "TestPolicy", testPolicy));

            var exManager = new ExceptionManager(policies);
            ExceptionPolicy.Reset();
            ExceptionPolicy.SetExceptionManager(exManager);
        }

        public string GetData(int value)
        {
            string queryString = "EXECUTE BadSP";
            string connectionString = @"data source=.\SQLEXPRESS;Integrated Security=SSPI;User Instance=false;Database=MyDB";

            using (SqlConnection connection = new SqlConnection(connectionString))
            using (SqlCommand command = new SqlCommand(queryString, connection))
            {
                command.Connection.Open();
                command.ExecuteNonQuery();
            }

            return string.Format("You entered: {0}", value);
        }
    }

Then using WcfTestClient I receive the response:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body>
    <s:Fault>
      <faultcode>s:Client</faultcode>
      <faultstring xml:lang="en-US">The EXECUTE permission was denied on the object 'BadSP', database 'MyDB', schema 'dbo'.</faultstring>
      <detail>
        <SalaryCalculationFault xmlns="http://schemas.datacontract.org/2004/07/WcfEhabShielding" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
          <FaultID>d97ccaa1-d991-4943-b17c-ba047680afac</FaultID>
          <FaultMessage>The EXECUTE permission was denied on the object 'BadSP', database 'MyDB', schema 'dbo'.</FaultMessage>
          <StoredProcedureName>BadSP</StoredProcedureName>
        </SalaryCalculationFault>
      </detail>
    </s:Fault>
  </s:Body>
</s:Envelope>
If you're still having an issue a sample application to recreate the issue would help track down the problem.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 13, 2013 at 1:32 PM
Edited May 13, 2013 at 1:34 PM
Thanks Randy. The issue seems to come from this one line in my application. I wrap the stored procedure call in the service method inside a the exception manager's .Process() method.
exManager.Process(() => wimDAL.Execute_NonQueryNoReturn(sc), "TestPolicy");
If I don't call .Process() and just execute the method as normal then the fault propagates to the client just fine.
wimDAL.Execute_NonQueryNoReturn(sc);
Why don't I have to use .Process(). On page 72 and 73 of the new "Developer's Guide to Microsoft Enterpise Library-Preview.pdf" it says Process or HandleException are the two ways to manage exceptions using the EHAB.

Also, if interested, you can see a more detailed post about it on the MSDN forums here.. This post shows that the procedure name is populated until you get to the client.

Please let me know why I was in the wrong here. Thanks.
May 13, 2013 at 10:51 PM
Edited May 13, 2013 at 10:51 PM
I see what you are saying.

For exception shielding to work you need to let the exception bubble up and have the mapping done in the ExceptionShieldingErrorHandler. This is just the way that it is implemented. If you are using the vanilla Exception Handling Block then there are two ways to manage exceptions. However, if you are using the optional WCF functionality for exception shielding then the actual implementation is done using WCF extension points. This approach is actually quite elegant, IMO, since you can shield exceptions from your service without the need for any code in the service (just some configuration and a line of bootstrap code).

So, in your case you don't need to use the Process() method since the exception will be handled by the ExceptionShieldingBehavior. Now, you could use the Process() method and have it perform a wrap or replace of an exception as long as it throws an exception that would be handled by Exception Shielding.

For example you could have a "DALPolicy" that replaced a SqlException with your own DalException and had a post handling action of throw ThrowNewException. Then you could have a "WCF Shielding Policy" that mapped a DalException to a FaultException<MyFault>. I'm not saying you would want to do that but it is possible depending on the scenario. :)

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 14, 2013 at 1:27 PM
Great. Thank you very much for the clarification. Everything makes sense now. ;o)