Showing posts with label Faults. Show all posts
Showing posts with label Faults. Show all posts

Wednesday, June 18, 2008

WCF: Using Shared Types

In WCF you can use shared types from existing assemblies when generating a service proxy. Use the advanced options in VS2008, otherwise use SVCUTIL.EXE with the /reference /r option. Note that you can only use /reference when your WSDL operations, types and messages adheres to the DataContractSerializer rules that I described this spring.

I'm currently trying to research why we are not able to use shared types for our wsdl:fault message parts.

<wsdl:message name="GetCustomerRequest">
<wsdl:part element="tns:GetCustomerRequest" name="parameters" />
</wsdl:message>
<wsdl:message name="GetCustomerResponse">
<wsdl:part element="tns:GetCustomerResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="FaultDetailList">
<wsdl:part element="fault:FaultDetailList" name="detail" />
</wsdl:message>

<wsdl:portType name="CustomerService">
<wsdl:operation name="GetCustomer">
<wsdl:input message="tns:GetCustomerRequest" name="GetCustomerRequest" />
<wsdl:output message="tns:GetCustomerResponse" name="GetCustomerResponse" />
<wsdl:fault message="tns:FaultDetailList" name="FaultDetailList" />
</wsdl:operation>
</wsdl:portType>


<wsdl:binding name="CustomerServiceSoap11" type="tns:CustomerService">
<soap:binding style="document" transport="
http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="GetCustomer">
<soap:operation soapAction="" />
<wsdl:input name="GetCustomerRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="GetCustomerResponse">
<soap:body use="literal" />
</wsdl:output>
<wsdl:fault name="FaultDetailList">
<soap:fault name="FaultDetailList" use="literal" />
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>

Even if the fault details contains only xs:string elements, we get this error when generating the proxy:

Cannot import wsdl:portTypeDetail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporterError: Referenced type 'SharedDataContracts.FaultDetailType, MyService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' with data contract name 'FaultDetailType' in namespace 'urn:kjellsj.blogspot.com/FaultDetail/1.0' cannot be used since it does not match imported DataContract. Need to exclude this type from referenced types.

I thought that writing a unit-test to import the WSDL using the underlying SVCUTIL importer directly should help me deduce the cause of the error. You will find all the code that you need to import WSDL using DataContractSerializerMessageContractImporter here at the "nV Framework for Enterprise Integration" project as CodePlex.

Add these lines to enforce the use of /namespace, /reference and /ct from code (note that these switches can be used multiple times):

xsdDataContractImporter.Options.Namespaces. Add(new KeyValuePair<string, string>("*", "MyService"));
xsdDataContractImporter.Options.ReferencedCollectionTypes. Add(typeof(List<>));
xsdDataContractImporter.Options.ReferencedTypes. Add(typeof(SharedDataContracts.FaultDetailList));
xsdDataContractImporter.Options.ReferencedTypes. Add(typeof(SharedDataContracts.FaultDetailType));

Sure enough, I get the exact same exception in my unit-test. Inspecting the imported contract shows that the operation has a fault with a "null" DetailType XML Schema type (watch: contracts[0].Operations[0].Faults[0].DetailType). I suspect that it is the "null" DetailType in the FaultDescription of the imported ServiceDescription for the WSDL that causes the error for the referenced shared types.

As it turns out this is a bug that has been fixed in .NET 3.5 SP1. The workaround for earlier versions of SVCUTIL is to add nillable="true" to all elements that are reference types (strings and complexTypes) in your wsdl:types XSD schemas. Or just drop the /reference switch and cut'n'paste the generated code to use your shared types.

Wednesday, April 30, 2008

EntLib3 Replace Handler, ExceptionHandlingException

Using the Enterprise Library 3 exception replace handler can help you handle e.g. WCF FaultException<T> on the client and replace it with your own exception derived from ApplicationException. This will shield your client app from the different types of FaultException<T> where T: FaultContract types defined in the WSDL as wsdl:fault schemas and provide a normalized application exception type. This client-side exception shielding mechanism ensures that the proxy fault types need not be exposed outside your service agent.

There is one exception best practice to remember to implement for the @replaceExceptionType configuration to work: your derived exception must implement the three recommended constructors default, message, message and inner exception. If you forget, the entlib HandleException call will throw an "unable to handle exception" ExceptionHandlingException error.

Note the [[fault contract type]] syntax to be able to specify handlers for generic exceptions:

<add type="System.ServiceModel.FaultException`1[[MyServiceAgent.Proxy.GetCustomerListFault, MyServiceAgent.Proxy, Version=1.0.0.0]], System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="ThrowNewException" name="MyApplicationException">


You have to edit the .config file manually as the EntLib3 editor does not support generics

Monday, May 07, 2007

WCF: Exception Handling with Logging

In my 'WCF Exception Shielding using Generics' post there was a "//TODO: add logging as applicable" comment, which I now have implemented using the EntLib3 Logging Application Block.

As part of the error information I wanted to write to the Windows Application Event Log was the actual request message causing the service operation exception. Thus, I googled for a solution for how to get the XML of the incoming message, so that I could log it along with the stack trace. There are a lot of options for getting at the message in WCF:

The problem of the first option is that I only want to get at the message XML when handling exceptions. The second option is not an option (duh!) as I want my operations to be strongly typed using my data contracts. The third option was more like what I wanted, but it seemed to be unneccessary complicated code.

The code to get at the message XML from within an operation is quite simple:

private static string GetMessageXml(DefaultMessage request)
{
return OperationContext.Current. RequestContext.RequestMessage.ToString();
}

This approach does not work for operations with no RequestContext, such as one-way operations.

The logging code is added after creating the fault details, this makes it possible to use fault details data in the log. This is how my WCF exception shielding with logging looks like:

public static FaultException<T> NewFaultException<T>(Exception ex)
where T : FaultContracts.DefaultFaultContract, new()
{
return NewFaultException<T>(ex, null);
}

public static FaultException<T> NewFaultException<T>(Exception ex, DefaultMessage request)
where T : FaultContracts.DefaultFaultContract, new()
{
return CreateNewFaultException<T>(ex, request);
}

private static FaultException<T> CreateNewFaultException<T>(Exception ex, DefaultMessage request)
where T : FaultContracts.DefaultFaultContract, new()
{
StackFrame stackFrame = new StackFrame(2);
string methodName = stackFrame.GetMethod().Name;

T fault = new T();
Type type = fault.GetType();
type.InvokeMember("SetFaultDetails", BindingFlags.Public BindingFlags.Instance BindingFlags.InvokeMethod, null, fault, new object[] { ex });

string msg = String.Format("An unexpected error occurred in {0}.", methodName);
Dictionary<string, object> props = new Dictionary<string, object>();
props.Add("stackTrace", ex.ToString());
if(request!=null)
{
string xml = GetMessageXml(request);
props.Add("requestMessage", xml);
}
Logger.Write(msg, Category.General, Priority.Highest, (int)EventLogId.Exception, TraceEventType.Error, ex.Message, props);

string reasonText = methodName + " failed, check detailed fault information.";

#if DEBUG
reasonText = methodName + " error: " + ex.ToString();
#endif

return new FaultException<T>(fault, new FaultReason(reasonText));
}

Note that I now use an inner private method to create the new fault to be thrown and thus must skip two stack frames to get the details of the original method called. The DEBUG section is there to make it simple to see full exception details in NUnit or with Fiddler when debugging the service. This DEBUG code must never make it into release builds, as this is not exactly exception shielding.
I have not looked into the WCF app-blocks integration mechanisms added in the april release of EntLib3, that is next on my task list for quieter days. There is not much info about this to be found, but Guy Burstein provides some samples. Btw, you should check out the Patterns and Practices Guidance site, it contains useful stuff and tutorials about EntLib3.

Friday, November 24, 2006

WCF Exception Shielding: Providing FaultContract Details using Generics

The WCF design guidelines recommends using the exception shielding pattern to sanitize unsafe exceptions and expose only a controlled set of exceptions using fault contracts. Thus, for every operation that you implement in your service adapter (facade), your method must have a try-catch block, code to create a fault contract object containing relevant exception information, and finally code to create and throw a System.ServiceModel.FaultException. This code will look the same in all your operation methods, and you will soon conclude that the code needs to be centralized into a static utility method to achieve better design and maintainability. In addition, a single static method that creates and provides a ready-to-throw fault exception object is also a good candidate for adding e.g. tracing and logging to your service facade.

I have used a combination of custom exceptions and generics to design a flexible exception handling mechanism that implements the exception shielding mechanism of our WCF services. The design involves a a FaultException<T> generics method that constraints T to be of type
FaultContracts.DefaultFaultContract (
where T : <base class>) that allows us to create a fault exception containing any fault contract type that we add to our service. Our fault contracts do of course all derive from DefaultFaultContract.

The design also involves a set of custom exceptions derived from System.ApplicationException. This allows us to throw exceptions from any component used by the service facade and catch them in the service operation methods, and still be able to extract data from the exception for use as information in the fault contract. The exception handling mechanism relies on this exception polymorphism to be able to handle any type of exception the same way, using a single method.

This is how the exception handler code looks like:

public static FaultException<T> NewFaultException<T>(Exception ex)
where T : FaultContracts.DefaultFaultContract, new()
{
StackFrame stackFrame = new StackFrame(1);
string methodName = stackFrame.GetMethod().Name;

T fault = new T();
//fault.SetFaultDetails(ex);
Type type = fault.GetType();
type.InvokeMember("SetFaultDetails", BindingFlags.Public BindingFlags.Instance BindingFlags.InvokeMethod, null, fault, new object[] { ex });

//add tracing as applicable
//add logging as applicable

return new FaultException<T>(fault, new FaultReason(methodName + " failed, check detailed fault information."));
}

As you can see, the code is quite simple as it delegates to the object <T> to actually fill any relevent exception data and other details into the fault contract.

Note the use of reflection to call the SetFaultDetails method of the fault contract object. This is needed as .NET does not correctly resolve the different overloaded versions of a method in a generics type, it will just call the overload that has the compile time type of the parameter. Thus, it will always call the
SetFaultDetails(Exception) method, even if the run-time type is a derived exception type. Using InvokeMember circumvents this problem.


Also note the use of the StackFrame object to get the name of the method that caught the exception (i.e. the method that calls NewFaultException exception handler).

As which info is relevant will vary with the type of fault contract and the type of exception, the DefaultFaultContract class have virtual (overridable) methods for any exception that needs special treatment, in addition to a method for System.Exception that other exceptions defaults to:

[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://kjellsj.blogspot.com/2006/11/DefaultFaultContract", Name = "DefaultFaultContract")]
public class DefaultFaultContract
{
. . .

public virtual void SetFaultDetails(Exception ex)
{
//default type
this.errorId = (int) EApprovalErrorCode.Undefined;
this.errorMessage = ex.Message;
}

public virtual void SetFaultDetails(EApprovalException ex)
{
this.errorId = (int)ex.ErrorCode;
this.errorMessage = ex.Message;
}


}

Note that you should not expose implementation details in your fault contract data. The use of the exception message in the above code is just for illustration purposes. This is definitely not recommended, as the goal of using the exception shielding pattern is to sanitize the exposed information to avoid providing the service consumer with data that can be used e.g. for attacking and exploiting your system.

Finally, this is how all our service operation exception handlers look like now:

try
{
. . .
}
catch (Exception ex)
{
throw MessageUtilities.NewFaultException<FaultContracts.DefaultFaultContract>(ex);
}

The type of fault contract <T> used will of course vary with the operation. Remember that all our fault contracts inherits the DefaultFaultContract class and that the virtual SetFaultDetails method ensures that the different exceptions get the correct shielding and handling.

This exception shielding mechanism is designed to be flexible and simple, and using a one-liner for handling errors in the service operation is as easy as it gets.

[UPDATE] More details about WCF faults in this 11 part article by Nicolas Allen.