Data, Context and Interaction (DCI) & Windows Workflow (C#)

Tags: DCI, Workflow, c#

Disclaimer: This article assumes you already know the following: C#, WF4 and Extension Methods.

Lately I have been reading a bunch about Data, Context and Interaction (DCI) and a lot of my current project have involved using Windows Workflow Foundation (WF4).  Recently I've come to the realizaion that a WF4 Activity can also be the perfect DCI context because both are basically use cases.

I've taken some sample code written by Christian Horsdal about DCI in C# and applied to to a Workflow.  He's has some very good posts about DCI and C#, I'd recommend checking them out if you want to learn more.

The Data

First we'll have a look at the data models we'll be using.

We've got our base Account class and our derived classes CheckingAccount and SavingsAccount.

public abstract class Account
{
    public double Balance { get; protected set; }

    public void Withdraw(double amount)
    {
        Balance -= amount;
    }

    public void Deposit(double amount)
    {
        Balance += amount;
    }

    public void Log(string message)
    {
        Console.WriteLine("Log: {0}", message);
    }
}
public class CheckingAccount
    : Account, TransferMoneySource, TransferMoneySink
{
    public CheckingAccount()
    {
        Balance = 1000;
    }

    public override string ToString()
    {
        return "Balance " + Balance;
    }
}
public class SavingsAccount
    : Account, TransferMoneySource, TransferMoneySink
{
    public SavingsAccount()
    {
        Balance = 1000;
    }

    public override string ToString()
    {
        return "Balance " + Balance;
    }
}

The Interaction

In our Data Models, you'll notice there's a TransferMoneySource and TransferMoneySink on each of our derived account classes. Those are the Roles these accounts can play, they can be both the Source or Sink when transfering money.

The Roles are defined as Interfaces and provide just the minimum amount of data to perform that Interaction.

In C# one way of providing the DCI Interaction is through the use of extension methods (eg: the TransferTo method below).

public interface TransferMoneySink
{
    void Deposit(double amount);
    void Log(string message);
}

public interface TransferMoneySource
{
    double Balance { get; }
    void Withdraw(double amount);
    void Log(string message);
}

public static class TransferMoneySourceTrait
{
    public static void TransferTo(this TransferMoneySource self, TransferMoneySink recipient, double amount)
    {
        if (self.Balance < 0)
        {
            throw new ApplicationException("insufficient funds");
        }

        self.Withdraw(amount);
        self.Log("Withdrawing " + amount);
        recipient.Deposit(amount);
        recipient.Log("Depositing " + amount);
    }
}

The Context

For the Transfer Money use case, we're going to Withdraw money from one account and Deposit it into another using the TransferTo extension method we created above. This use case requires three things: the Source, the Sink and the Amount.

Written as a WF4 Activity, it will look like this...

public sealed class TransferMoneyActivity : CodeActivity
{
    [RequiredArgument]
    public InArgument<TransferMoneySource> Source { get; set; }

    [RequiredArgument]
    public InArgument<TransferMoneySink> Sink { get; set; }

    [RequiredArgument]
    public InArgument<double> Amount { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        var source = Source.Get(context);
        var sink = Sink.Get(context);
        var amount = Amount.Get(context);

        source.TransferTo(sink, amount);
    }
}

Executing the Activity / Context (use case)

Transfering $100 from the CheckingAccount to SavingsAccount can be as simple as this.

var savingsAccount = new SavingsAccount();
var checkingAccount = new CheckingAccount();

var arguments = new Dictionary<string, object> {
    { "Source", checkingAccount },
    { "Sink", savingsAccount },
    { "Amount", 100 }
};

WorkflowInvoker.Invoke(new DemoActivity(), arguments);

And now that our Context is a standard WF4 Activity, we can easily incorporate it into our larger Workflows.

Download the sample project here

Add a Comment