
Implementing contracts in services
Once we have decided upon an interface definition for a service, we are able to move forward with the service which implements this interface. For those of you who have previously built .NET interface classes, and then realized those interfaces in subsequent concrete classes, the WCF model is quite natural. In fact, it's the same. We build a concrete service class, and choose to implement the WCF service contract defined earlier. For this example, we take the previously-built interface (which has since had its Insert
operations replaced by a single operation that takes a data contract parameter) and implement the service logic.
public class VendorService : IVendorContract
{
public void InsertVendor(VendorType newVendor)
{
System.Console.WriteLine("Vendor {0} inserted by service ...", newVendor.VendorId);
}
public bool DeleteVendor(string vendorId)
{
System.Console.WriteLine("Vendor {0} deleted ...", vendorId);
return true;
}
}
A WCF service may have metadata attributes applied to it in order to influence or dictate behavior. For instance, WCF has very robust support for creating and consuming transactional services. While specific attributes are applied directly to the service contract to affect how the service respects transactions, the attributes on the concrete service itself establish the way the service processes those transactions. In order to identify whether a service will accept transactions or not, an attribute is added to the service contract.
[ServiceContract(Name="VendorService", Namespace="http://Seroter.BizTalkSOA/Contracts")]
public interface IVendorContract
{
[OperationContract(Name="DeleteVendor")]
[TransactionFlow(TransactionFlowOption.Allowed)]
bool DeleteVendor(string vendorId);
}
However, this doesn't dictate the implementation details. That is left for attributes on the service itself. The ServiceBehavior
attribute has numerous available properties used to shape the activities of the service. Likewise, an OperationBehavior
applied to the implemented contract operations enables us to further refine the actions of the operation. In the following code snippet, I've instructed the service to put a tight rein on the transaction locks via the Serializable
isolation level. Next, I commanded the DeleteVendor
operation to either enlist in the flowed transaction or create a new one (TransactionScopeRequired
), and to automatically commit the transaction upon operation conclusion (TransactionAutoComplete
).
[ServiceBehavior(TransactionIsolationLevel= System.Transactions.IsolationLevel.Serializable)] public class VendorService : IVendorContract { [OperationBehavior(TransactionAutoComplete=true, TransactionScopeRequired=true)] public bool DeleteVendor(string vendorId) { System.Console.WriteLine("Vendor {0} deleted ...", vendorId); return true; } }
Be aware of the nuances of where WCF attributes may be applied (e.g. service contract, concrete service, service operation) and the rich capabilities that these metadata tags can offer you.
Tip
Critical point
While this example showed how to attach transactions to services, but you need to be extremely cautious and judicious with the usage of transactions across service boundaries. While WCF makes this seem transparent, try to make your services as encapsulated as possible so that they have few explicit or implied dependencies on other services.
Throwing custom service faults
Whenever possible, you should avoid returning the full exception stack back to the service caller. You may inadvertently reveal security or implementation details that allow a malicious user to engage in mischief. Within WCF, a fault contract is a custom data contract that allows you to shape the exception being returned to the service consumer. Let's say we defined a controlled fault contract that looks like this:
[DataContract(Name = "InsertFault")] public class InsertFaultType { private string friendlyMessage; [DataMember()] public string FriendlyMessage { get { return friendlyMessage; } set { friendlyMessage = value; } } }
So far, that looks like any old data contract. And in reality, that's all it is. However, we associate this fault contract with a particular operation by adding the FaultContract
attribute to the operation in the service contract.
[OperationContract(Name="InsertVendor")]
[FaultContract(typeof(InsertFaultType))]
void InsertVendor(VendorType newVendor);
While implementing the service, we explicitly produce and throw these custom exception types back to the service consumer. This is done by catching the .NET exception, and creating a new fault object from the custom fault we created earlier. Then, we throw a new FaultException
typed to our custom fault definition.
public void InsertVendor(VendorType newVendor) { try { //do complex database update ... } catch (System.Data.SqlClient.SqlException sqlEx) { //log actual fault to admin log //throwing SQL exception back to caller is bad. //Create new exception out of custom fault contract InsertFaultType insertFault = new InsertFaultType(); insertFault.FriendlyMessage = "Insert operation failed"; //throw custom fault throw new FaultException<InsertFaultType>( insertFault, "illegal insert"); } }
By defining and throwing custom service faults, you achieve better control over how your service communicates to the outside world and better insulate yourself from critical implementation leakage.