Thursday, March 13, 2014

Fault Contract for Workflow Service

Today I want to share how to define fault contract and throw fault exception from workflow service and how to handle it from the client.

Concept

A little bit of story why should we use fault contract. By default in WCF service, when we throw any exception from the service, WCF automatically know how to handle the exception for you after you have set the <serviceDebug includeExceptionDetailInFaults="true"/> in your service behavior. However, when we want to host the WCF service to the production environment and especially the service is going to be exposed to the public, I am sure you would not like to let people see the exception detail. Therefore, the best practice is to set includeExceptionDetailInFaults="false", and then limit the information expose to the public by using fault contract.

Implementation

I have created another simple leave application sample with workflow. All the code in this article comes from my sample application. You can download the sample code at the very bottom of this article.

First, we need to define the fault object first. This object is used to store the information that you want to share and it is going to be used by the client and send through the network, therefore, make sure it is serializable.

[DataContract]
public class InvalidApplicationDateFault
{
    [DataMember]
    public int ErrorCode { get; set; }

    [DataMember]
    public string Message { get; set; }

    public InvalidApplicationDateFault(int errorCode, string message)
    {
        this.ErrorCode = errorCode;
        this.Message = message;
    }

}

This is my business component code, I am throwing a custom exception. It is optional, it is still okay if you throw an application exception.

public void Apply(Leave leave)
{
    leave.Status = 1; //Apply state
           
    if (leave.StartDate < DateTime.Today)
        throw new InvalidApplicationDateException();

    if (leave.EndDate < leave.StartDate)
        throw new InvalidApplicationDateException();

    var leaveDAC = new LeaveDAC();
    leaveDAC.Create(leave);

}

Then, following is my code activity which call my business component.

public class Apply : CodeActivity
{
    public InArgument<Leave> Leave { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        Leave leave = context.GetValue(this.Leave);

        LeaveComponent bc = new LeaveComponent();
        bc.Apply(leave);
    }

}

Then, from my workflow, I will try to call my code activity.



In the TryCatch activity, after catching my custom exception, I create my fault object, stuffing the information that I want to share with my client into it. And then, create a new fault exception with fault object type specific (FaultException<InvalidApplicationDateFault>), then assign the fault object into it.

Notice that I have 2 SendReplyToReceive activities? The SendReplyToReceive inside the Catch is sending the fault exception to the client. But, before that, you need to duplicate the SendReplyToReceive activity from the same Receive activity by right clicking the Receive, then select Create SendReply.



Drag the new SendReplyToReceive activity into the catch, then open the Content Definition window. After that, set a parameter with the FaultException type and then assign the fault exception which I had created.



Now, if you have done it correctly, you will see Workflow Service automatically generate a fault contract for you as you can see it in the WSDL.

Note: If you do not see your fault message generated in WSDL, it is because you are not sending FaultException<T> type in SendReplyToReceive activity, and that your client will never be able to catch the correct fault exception.


Testing

Now, I want to test the service whether it is actually throwing the fault exception to my client correctly. This is how my unit test look like:

[TestMethod, ExpectedException(typeof(FaultException<InvalidApplicationDateFault>))]
public void ApplyTest()
{
    LeaveServiceClient proxy = new LeaveServiceClient();

    try
    {
        Leave leave = new Leave();
        leave.Employee = "Tweety Bird";
               
        //Apply leave with past date is not allowed.
        //This would trigger error
        leave.StartDate = new DateTime(2013, 1, 1);
        leave.EndDate = new DateTime(2013, 1, 2);

        leave.Duration = 2;
        leave.DateSubmitted = DateTime.Now;
        leave.CorrelationID = Guid.NewGuid();

        proxy.Apply(leave);
    }
    catch(FaultException<InvalidApplicationDateFault> fex)
    {
        Assert.IsTrue(fex.Detail.ErrorCode == 123);
        throw;
    }
    catch(Exception)
    {
        Debug.WriteLine("Failed to catch FaultException.");
        Assert.Fail();
    }
    finally
    {
        proxy.Close();
    }
}

When the code execution reach the Apply method, the FaultException<InvalidApplicationDateFault> had been thrown from the service and I am able to catch it correctly in my client.

So, when your service is ready to be exposed to public, you can set the following in your service behavior.

<serviceMetadata httpGetEnabled="false"/>
<
serviceDebug includeExceptionDetailInFaults="false"/>

Happy coding!

If you are interested with my source code, feel free to download it from HERE.




No comments:

Post a Comment

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...