Saturday, September 20, 2008

Generic Type Parameters

I ran into something interesting with the .NET framework this week regarding how to express a fully qualified type name for a generic type. 

In my specific scenario, I was looking to use the Enterprise Library Exception Handling Application Block to be able to apply some runtime policy on whether  a particular operation needed to be retried if a specific exception had been thrown across the service boundary. 

The specific exception I was looking to apply this “Retry Policy” against?  FaultException<ConsultantNotFoundFault>.   ConsultantNotFoundFault is a business processing exception that my client code might need to be able to react to gracefully.

Note, FaultException “is a” generic type of FaultException and “has a” ConsultantNotFoundFault.  When using the EL Exception Handling Block, if it were just a FaultException I was looking to act on, then I would declare this via configuration as:

<exceptionPolicies>
<add name="Retry Message">
<exceptionTypes>
<add type="System.ServiceModel.FaultException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="None" name="FaultException" />
</exceptionTypes>
</add>



This should be fairly comfortable syntax and allows me to apply policy if my client code catches a FaultException.  That doesn’t help too much when I need to be able to react to a specific exception, though I’ll explain in a minute why you might be able to get by with this using code.



A Tale of Two Brackets



However, that still left me wondering how to indicate that the type I’m looking to declare is the generic version  FaultException, FaultException<T>?




<exceptionPolicies>
<add name="Retry Message">
<exceptionTypes>
<add type="System.ServiceModel.FaultException`1, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="None" name="FaultException" />
</exceptionTypes>
</add>



Note the ‘mangled’ type name of FaultException`1, somewhat reminisce of C++ days.  This is almost there.  This allows my client to catch any type thrown across the service boundary that is a FaultException<>.  However, I’m still in the situation of not knowing what type of exception the FaultException<> contains.   Again, I can figure it out in code if I have a limited number of these exception cases I can handle – but its a code solution.  Not the configuration declarative solution I’m looking for.



I had the answer in front of me, but I missed it.  It took a nudge from Fernando, an EL community member, to push me in the right direction.  The AssemblyQualifiedName was the value I was looking to declare in my configuration – but I missed that there were TWO square brackets (‘[[ ]]’) around the generic type parameter(s). 



The double square bracket makes sense – now.  What we have is the generic type, FaultException`1, then a set of square brackets to contain all the generic type parameters, with each generic type parameter enclosed in square brackets.  This is described in the help documentation for Type.AssemblyQualifiedName property.


 


Now that we have all the required syntax in order, we can get back to using the EL Exception Handling block to declaratively describe which exceptions we want to apply our “Retry Message” policy for:



<exceptionPolicies>
<add name="Retry Message">
<exceptionTypes>
<add type="System.ServiceModel.FaultException`1[[ELEaxPolicy.ConsultantNotFoundFault, ELEaxPolicy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="None" name="ConsultantNotFoundFault" />
</exceptionTypes>
</add>
<exceptionPolicies>



This will allow me to centralize my exception processing and declaratively determine if I want to try retry that message/operation:




catch (Exception eax)
{

bool continueProcessing = ExceptionPolicy.HandleException(eax, "Retry Message");

if (!continueProcessing)
{
Debug.WriteLine("Retry Message");
}
else
{
// if we aren't going to handle the exception
// let it propagate.
//
throw;
}

}



What About Slinging Code?



I mentioned earlier that you can solve this problem directly code.  If you only have a limited number of exceptions you need to react to, then you can consider doing a little manual runtime determination to figure out if the Exception you just caught is of interest.



With a little helper magic (but no chickens) you can figure it out:




/// <summary>
/// Create a FaultException<> that contains the genericTypeParameter.
///
/// Straight from Anderson's brain!
///
/// </summary>
/// <param name="genericTypeParameter">The generic type parameter to use when constructing FaultException<>.</param>
/// <returns>The type representing the generic FaultException containing the generic type parameter.</returns>
static Type GetTypeAsFaultException(Type genericTypeParameter)
{
//Create this for use later in creating the final type
Type genericFault = typeof(FaultException<>);

return genericFault.MakeGenericType(genericTypeParameter);
}

This allows us to still catch Exception and figure out if we need to do any special processing in our handler.  Its not as declarative as using the Enterprise Library Exception Handling Block, but it might do in a pinch.



catch (Exception eax)
{

Type consultantNotFoundFault = GetTypeAsFaultException(typeof(ConsultantNotFoundFault));

if (consultantNotFoundFault == eax.GetType())
{
// retry operation/message
//
Debug.WriteLine("Retry Message");
}
}

You can also use similar logic if you find yourself needing to roll your own exception policy in the event you have some business reason that wouldn’t allow you to use the Exception Handling Block directly. 

3 comments:

  1. Anonymous5:04 PM

    Glad my brain was good for something.

    ReplyDelete
  2. Your lucky they haven't figured out the Vulcan mind meld, or you'd have to go into hiding!

    Still, I guess cloning is always an option. :)

    ReplyDelete
  3. Anonymous3:04 AM

    Zach,

    This was EXACTLY what I was looking for. Wasn't sure this could be done. Thanks a bunch! I'm going to try this in my app very soon.

    Vidya

    ReplyDelete