Extending / Custom FaultContractExceptionHandler for Exception Handling in WCF

Topics: Exception Handling Application Block, Logging Application Block
Oct 27, 2012 at 3:04 AM

Hi

 

I have successfully configured exception sheilding with WCF and exception handling block. I have also configured Logging application block and I am logging an Extended property called CorrelationId. I have created a Logger class which keeps a GUID for each request using unity application block (lifetime per thread). Now I want to have a custom FaultContractExceptionHandler to append CorrelationId in exception and fault so that it can be logged and sent back to client with a message including CorrelationId so that troubleshooting can be fruitfull with CorrelationId. Something similar to what SharePoint does, all the trace, exceptions, information, messages are logged with one CorrelationId which helps in identifying all related messages.

 

I tried to extend FaultContractExceptionHandler  but problem is how should I inject the dependency of it which seems like coded in Common.Configuration. Please suggest me.

 

Thanks

Rohit

 

Oct 31, 2012 at 6:58 AM

I understand the use case you are trying to address but I'm not 100% of the approach you have taken.  Instead of extending FaultContractExceptionHandler I would re-implement (as well as FaultContractExceptionHandlerData) using Enterprise Library source combined with your modifications.

That said I think you should be able to address your scenario without creating complicated Enterprise Library artifacts.

My approach would use the Trace.CorrelationManager.ActivityId since this will create thread static state Guid.  This property already exists in the LogEntry and also ties in with WCF tracing and correlation (see Extending Tracing) so you would be able to use those tools for even more information.  

In order for all service logging (using Enterprise Library) to have the ActivityId logged, I would use a formatter template to access this property so that it appears in the logs.

Now to pass this information back to the client I would create a custom Exception that exposes an ActivityId property.  The exception would be set using a WrapHandler.  This exception would then be mapped to a FaultContract class that also exposed a MyActivityId property.

OK, at this point it's a bit abstract so let's give an example.

In the service or service factory (if you have one) set the ActivityId (if necessary since WCF will have set it already if tracing is enabled):

    [ExceptionShielding("Policy")]
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            if (Trace.CorrelationManager.ActivityId == Guid.Empty)
            {
                Trace.CorrelationManager.ActivityId = Guid.NewGuid();
            }

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

When logging specify the ActivityId for the LogEntry or it looks like Enterprise Library will overwrite the ActivityId,  

LogEntry logEntry = new LogEntry()
{
    Message = "Test",
    Categories = new string[] { "General" },
    ActivityId = Trace.CorrelationManager.ActivityId
};

Logger.Write(logEntry);

Now we create a template that will log the ActivityId:

    <formatters>
      <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;ActivityId: {property(ActivityId)}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
        name="Text Formatter" />
    </formatters>

Next we create an exception to hold the ActivityId (so we can map that to our FaultContract):

    public class WrappedException : Exception
    {
        public WrappedException(string message, Exception innerException)
            : base(innerException.Message, innerException)
        {
        }

        public Guid ActivityId
        {
            get
            {
                return Trace.CorrelationManager.ActivityId;
            }
        }
    }

As I mentioned we also create a FaultContract

    [DataContract]
    public class MyFaultContract
    {
        [DataMember]
        public string MyMessage
        {
            get;
            set;
        }

        [DataMember]
        public Guid MyActivityId
        {
            get;
            set;
        }
    }

And then configure the exception handlers:

<exceptionHandling>
  <exceptionPolicies>
    <add name="Policy">
      <exceptionTypes>
        <add name="All Exceptions" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
          postHandlingAction="ThrowNewException">
          <exceptionHandlers>
            <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              wrapExceptionType="WcfEhabShielding.WrappedException, WcfEhabShielding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            <add type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.FaultContractExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              faultContractType="WcfEhabShielding.MyFaultContract, WcfEhabShielding"
              name="Fault Contract Exception Handler">
              <mappings>
                <add source="Message" name="MyMessage" />
                <add source="ActivityId" name="MyActivityId" />
              </mappings>
            </add>
            <add name="Logging Exception Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              logCategory="General" eventId="100" severity="Error" title="Enterprise Library Exception Handling"
              formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
              priority="0" />
          </exceptionHandlers>
        </add>
      </exceptionTypes>
    </add>
  </exceptionPolicies>
</exceptionHandling>

Then on the client we can use the following code to get the ActivityId:

    try
    {
        client.GetData(1);
    }
    catch (FaultException<MyFaultContract> e)
    {
        MyFaultContract myFaultContract = e.Detail as MyFaultContract;
        Console.WriteLine(string.Format("Message: {0}, ActivityId: {1}",
            myFaultContract.MyMessage, myFaultContract.MyActivityId));
    }

One thing to keep in mind is if you place the LoggingExceptionHandler before the FaultContractExceptionHandler, the ActivityId is overwritten.  If that is a problem, then I think creating a custom LoggingExceptionHandler that explicitly sets the ActivityId (it would be a one line change to the existing source code) would fix that.  I also found that if I enabled WCF tracing then this approach wasn't working (ActivityIds were incorrect) so you may have to take further control but hopefully this has given you some ideas.  Also be aware that if you are using per-thread lifetime with WCF you could run into problems. See http://blogs.msdn.com/b/atoakley/archive/2010/12/29/unity-lifetime-managers-and-wcf.aspx.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Nov 16, 2012 at 5:34 PM

Thanks for the reply, I managed to do it with a wrapper around exceptionsheilding attribute and ExceptionShieldingErrorHandler. I feel it is neat implelemntation and good way to extend enterprise library.

Thanks

Rohit