Friday, November 24, 2006
WCF Exception Shielding: Providing FaultContract Details using Generics
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.
Wednesday, November 22, 2006
WCF Contracts: Separating Structure from Data
The data contract design of my last post is well suited from getting all data about a domain object structure in one go. It can also be used to add/modify nodes to/in the structure; but as each node data contract contains a combination of node metadata and collections of related items, it is not well suited for operations that inserts or updates items in the structure. The XSD schema of the data contract has no way of conveying what will happen with any collection items contained in the data when inserting a new ProjectDocuments node. The service might just ignore them (the 'easy' way), but this will add concealed semantics to the operations and violates the "share contract, not class/implementation" tenet of SOA. It would be better if the contract was unambiguous and easily understandable for the service consumers (the 'simple' way).
So to refactor the 'project documents' data contract into something that is well suited for both retrieving and modifying data, separating structure from data is needed. The metadata of each node in the hierarchy must be extracted into a new data contract, that becomes just another element in the node along with the collections. The new contract for the metadata is what we needed to make the schemas for insert and update operations simpler to use. In addition, the service will now have less subtle semantics.
The new ProjectDocuments contract using the new DocumentCategory data contract looks like this (click to enlarge):
The takeaway is that you should "normalize" your data contracts to some extent to make them more reusable across data contracts, messages and operations.
My colleague Anders Norås made me aware that naming collections ArrayOfXxx is not according to WS-I basic profile recommendations. WCF supports specifying the both collection element name and the collection item element name using the [CollectionDataContract] attribute. This attribute cannot, however, be applied to class members, only to a whole class. Thus, you will need a separate class for each type of collection in your data contracts:
namespace KjellSJ.Sample.Application.DataContracts
{
[CollectionDataContract(Namespace = "http://kjellsj.blogspot.com/2006/11/DocumentCard", Name = "DocumentCardCollection", ItemName = "DocumentCard")]
public class DocumentCardCollection : Collection<DocumentCard>{}
}
The use of the custom named collection is straightforward:
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "DocumentCardList", Order = 3)]
private DocumentCardCollection _fieldDocumentCardList;
public DocumentCardCollection DocumentCardList
{
get { return _fieldDocumentCardList; }
}
The number of XSDs in your WSDL will increase significantly when applying custom names to collection. I recommend that you group related DataContract classes into a single namespace, e.g. the contract classes DocumentCategory, DocumentCard and DocumentCardCollection should have the same namespace. All contracts in a specific namespace must be versioned as one, so group your contracts wisely.
If you have code that uses the MetadataExchangeClient object to do MEX stuff, you must ensure that the MaximumResolvedReferences setting has room for all the new metadata. I use the MetaData Explorer from IDesign to inspect the service contract, and had to increase the resolve limit beyond the default 10.
Friday, November 17, 2006
WCF: Designing a Hierarchical Data Contract
The WSSF wizards require you to design the contracts bottom-up, as any object must exists before it can be added to the contract. Note also that a wizard cannot be rerun to expand data contracts, it can only create new contracts. This limitation is espesiaclly daft when it comes to service interfaces: you cannot add another operation to an existing service contract using the wizard. Thus, you must start by defining the leaf nodes of the structure and work your way up to the root element of the hierarchy.
I still recommend that you do a little design up front: start by identifying and writing down the use cases, identityfy the services (logical groups of operations) that you need to support the use cases, and finally write down a list of the logical operations that you think are sufficient to implement the business processes of the use cases. Use high level terms (verbs and nouns) to outline the set of operations (list, get, register, fulfill, etc; customer, offer, order, invoice, etc). Ensure that the operations in fact are business services, and not a RPC/CRUDy style programming model exposed as web-services.
The data in this example is a list of documents organized into a categorizing hierarchy. Each document again consists of metadata (the document card) and a list of files making up the document (classical DM stuff). Each file has to have an associated file type (enum).I now prefer using the contract attributes over creating XSD schemas first, even if I still am a big fan of the contract first approach. I always extract the XSD schemas and inspect them with XmlSpy to ensure that the generated schemas looks the way I would like them to be.
This is how the data contract schema should look (split into two for readability, click to enlarge):
The file type is an XSD enum:
The data contract wizard makes it easy to add value types, enums and objects to the contract. It is not, however, possible to add a System.Collections.ObjectModel Collection<T> using the wizard. To add a collection, first add just the type (T) to the contract, then generate the contract class and modify it like this:
using System;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;
namespace KjellSJ.Sample.Application.DataContracts
{
/// <summary>
/// Data Contract Class - ProjectDocuments
/// </summary>
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://kjellsj.blogspot.com/2006/11/ProjectDocuments", Name = "ProjectDocuments")]
public partial class ProjectDocuments
{
public ProjectDocuments()
{
_fieldChildNodes = new Collection<ProjectDocuments>();
_fieldDocumentCardList = new Collection<DocumentCard>();
}
private System.String _fieldId;
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "Id", Order = 1)]
public System.String Id
{
get { return _fieldId; }
set { _fieldId = value; }
}
private System.String _fieldCode;
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "Code", Order = 2)]
public System.String Code
{
get { return _fieldCode; }
set { _fieldCode = value; }
}
private System.String _fieldDescription;
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "Description", Order = 3)]
public System.String Description
{
get { return _fieldDescription; }
set { _fieldDescription = value; }
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "ChildNodes", Order = 4)]
private Collection<ProjectDocuments> _fieldChildNodes;
public Collection<ProjectDocuments> ChildNodes
{
get { return _fieldChildNodes; }
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "DocumentCardList", Order = 5)]
private Collection<DocumentCard> _fieldDocumentCardList;
public Collection<DocumentCard> DocumentCardList
{
get { return _fieldDocumentCardList; }
}
}
}
As you can see, I have made the DocumentCardList a Collection<DocumentCard> and moved the DataMember attribute to the private member instead of the public property. The reason for moving the attribute is that I made the collection property read-only, a best practice, and the data contract serializer does not support read-only properties.
Note also the self reference to ProjectDocuments in the ChildNodes collection that makes this schema a hierachy. The collection becomes a XSD array:
<xs:complexType name="ArrayOfProjectDocuments">
<xs:sequence>
<xs:element name="ProjectDocuments" type="tns:ProjectDocuments" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
Note that to adhere to the WS-I basic profile best practices, you should customize the collection element name using the [CollectionDataContract] attribute. Read more about using collections in the data contract here at MSDN.
I prefer not to mark as DataContract any enum that is referenced by a DataMember in the schemas. If you apply DataContracty to the enum, it will cause the fields using the enum to be interpreted a XSD string data type instead of a enum data type (xs:enumeration). Just leave the enum class as is and use it as any other datatype in the DataContract wizard. Read more about using enums in data contracts here at MSDN.
Wednesday, November 01, 2006
Getting Started with WCF (Indigo)
These days I am at a new project at a new customer after more than one year of rearchitecting and functionally porting an old VB6 solution to .NET 2.0 WinForms, and at the same time centralizing 88 client-server distributions to a single application server farm three tier distribution.
The new project is about creating an e-biz platform for providing services to partners and other systems; you know the buzz words: SOA and ESB. The solution will use Windows Communication Foundation (WCF) to expose and host the services implemented using .NET.
I have found these resources to be useful when getting started with WCF:
- WCF at NetFXGuide: resources, articles, blogs, etc
- Learn The ABCs Of Programming WCF
- Security in Windows Communication Foundation
- Service Contract Versioning
- Best Practices: Data Contract Versioning
- A SOA Versioning Covenant
[UPDATE] Some more useful links:
- Service Station: The Service Factory for WCF
- Versioning web-services guidance
- Fundamentals of WCF Security (6 pages)
Btw, I will be at the DevConnections in Vegas next week. See you there =D