Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C# 4.0

The Data Exchange Mechanism

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
1 Oct 2012CPOL31 min read 24.4K   269   23   6
Implementation of the generalized dataflow model between objects.

Introduction

In order to generalize the information dataflow between objects that communicate with specific data types, we shall present a mechanism that will enable us to transmit the said data and modify it in a predetermined way. This purely generic black box mechanism will act as an intermediary between the data source, the modifying components of the data, and its destination, thus providing us with a higher degree of component decoupling.

Program Dataflow

1.1 Dataflow Between Objects

We are now taking under inspection, the problem of data exchange between objects. For this particular problem, an example application will be constructed and we shall take a look at its interactions, while its data is being modified when passed between objects. The data exchanges can be said to be performed in the following way.

The first step is where the data object is created from the object representing the source of our data. Then, the object containing the data is sent to another object, which represents the object that will process the data, in a certain predefined way. While the last step consists of simply sending the processed data object to an object representing the destination for our data to be used by the end user.

This process is always triggered by a certain action. Therefore this action is going to take a special position in our exposition of this problem.

1.2 Generalizing the Dataflow Concept

As explained above the data exchange can be modeled as a three step process where we get the source of the data, modify the relevant data, and set the data to its destination. If we were to be able to generalize this process so that it can use any data type and perform any operation on this data, we would then be in possession of a generalized model of data exchange.

We are now going to present such a model and implement it as a simple assembly which can later be used by any application. The advantage of using a generalized data exchange model is that our triggering actions do not need to be implemented with modification objects that will modify the relevant data, in every specific case. The only thing that needs to be done is for the developer to implement our Data Exchange Mechanism and specify the data type to use.

Since the business applications are in a lot of cases implemented to use business objects, we shall model our Data Exchange Mechanism, where the data in question is represented by a business object. In other words, the data that is being exchanged by our Data Exchange Mechanism is a specific business object relevant for certain data exchange.

Our model can also be displayed by the following diagram, where in the first block we can see the Data Source object containing the Data object, i.e. a business object, which gets passed to a Data processing object to get modified while in the end, the Processed data gets sent to the Data destination object.

Image 1

1. Generalized model of the Data Exchange Mechanism.

The Mechanism

2.1 Examining the Mechanism

Our Data Exchange Mechanism is constructed from three interacting units, which are the following:

  1. Exchange Interfaces
  2. The Mechanism Core
  3. Data Modification Unit

Let us now examine all the units in detail and explain how they interact.

First, let us observe the Exchange interfaces. Since the Data Exchange Mechanism will be transferring data from its source to its destination, we shall have to construct some interfaces that will be able to abstract the relevant data flow. The interfaces are defined as follows.

C#
// (1.1)
public interface IExchangeSource<T>
{
    void GetSourceData(T data);
}
 
public interface IExchangeModify<T>
{
    void ModSourceData(T data);
}
 
public interface IExchangeResult<T>
{
    void SetSourceData(T data);
}

As we can see, we have defined three open generic interfaces, where each interface contains a single method which takes as its single parameter the open generic type T and returns void.

The GetSourceData(T) method in the IExchangeSource<T> interface is used as an entry point for our data that is going to be processed. An object representing the source of the data is going to implement this interface, and its containing method. Within this method, we are going to fill the the object of type T with the relevant data. By doing this we have managed to acquire the data to be processed.

Next, the ModSourceData(T) method in the IExchangeModify<T> interface is used to serve as an entry point to an object whose class definition will implement the said interface. The object in question will then be used to modify our data of type T.

For the last step, we use the SetSourceData(T) method in the IExchangeResult<T> interface to set the processed data on the object which will implement this interface. Once all the relevant classes, the data source class, data modification class and the data result class have implemented the interfaces and have used the same type for their generic type T, we have succeeded in creating a uniform data type flow between these objects.

2.2 The Mechanism Core

Let us now turn to the Mechanism Core. In this case, the core consists of two distinct class definitions. The relevant definitions are as follows.

  1. The Exchange Container
  2. The Exchange Mechanism

The Exchange Container is defined in the following way.

C#
// (1.2)

class Exchange<T> where T : new()
{
    public T Data = new T();
    public IExchangeModify<T> ExchangeModify = new Modify<T>();
    public IExchangeSource<T> ExchangeSource { get; set; }        
    public IExchangeResult<T> ExchangeResult { get; set; }
}

We can observe that the Exchange<T> class is an open generic class where its generic type T is constrained by the new() keyword. The reason for this is that this class will be instantiating an object, which our previous interfaces will have the job of being the entry points to, for various data objects. Therefore, the Data field is the one in which we have instantiated our relevant data object of the desired type.

Next, we can see a field ExchangeModify, that represents an object which implements the IExchangeModify<T> interface. We also instantiate this field with the Modify<T> class, which will be used to process our data.

After this, we declare two more properties, of type IExchangeSource<T> and IExchangeResult<T>, which will serve as placeholders for objects that will be the source of our data that we are going to process, and the final destination for our processed data, once we have modified it by the ExchangeModify object, respectively.

Next, we shall turn to the Exchange Mechanism itself. It is defined as follows.

C#
// (1.3)

class ExchangeMechanism<T> where T : new()
{
    public ExchangeMechanism(Exchange<T> exchange)
    {
        exchange.ExchangeSource.GetSourceData(exchange.Data);
        exchange.ExchangeModify.ModSourceData(exchange.Data);
        exchange.ExchangeResult.SetSourceData(exchange.Data);
    }
}

We can again observe a generic class that constrains its generic type T with the new() keyword. We can also see that we have defined a constructor that takes a single parameter of type Exchange<T>.

It should now be noted that once this class is instantiated and gets an object of type Exchange<T> as its parameter, it will access all the fields and properties that implement the interfaces defined in (1.1) and call their respective methods passing to them the Data field from our Exchange<T> container.

Once our Data field is instantiated to a specified type, by passing it to the GetSourceData(T) method, we shall fill the container with the relevant data. After that, by passing the Data field to the ModSourceData(T) method, we are going to pass the field to an object which is going to process our data in a desired way. While for the last step, after passing the Data field to the SetSourceData(T) method, we are simply going to pass the processed data to an object which is going to display or store the relevant data. With this construction now in place, we have achieved the generalized notion of dataflow between any object and any data type.

2.3 Data Modification Unit

Let us now turn to the Data Modification Unit. This unit consists of two elements, which are as follows.

  1. Modification Element
  2. Dependency Injection Element

This Modification Element is represented by the Modify<T> class, which is used as an object that is going to call another object to process our data. We can see it defined in the following manner.

C#
// (1.4)

class Modify<T> : IExchangeModify<T>
{
    public void ModSourceData(T data)
    {
        new Activate<T>(data);
    }
}

The Modify<T> class implements the IExchangeModify<T> interface ensuring the uniform dataflow between the data source and that destination of the processed data. We can see that in the ModSource(T) method, we have instantiated an object of type Activate<T>. This object is going to instantiate a specific object depending on the type of our generic type T, and pass to it the data parameter to modify it.

This task is achieved by using the mechanism of Dependency Injection Element[1]. To explain the workings of the element, let us first observe the Activate<T> class, which is defined as follows.

C#
// (1.5)

class Activate<T>
{
    public Activate(T c)
    {
        Activator.CreateInstance(new TypeOf<T>().Type, c);
    }
}

The Activate<T> class will facilitate us with the required Dependency Injection abilities we need. It simply defines a constructor which takes a generic parameter of type T, and passes it to an object that is going to be instantiated by the Activator class. Thus the object in question can simply get the access to, and modify our data. The type of the object instantiated is determined and contained by the Typeof<T> class and its Type property.

Let us now turn our attention to the TypeOf<T> class. We can see it defined as follows.

C#
// (1.6)

class TypeOf<T> : XmlConfiguration<T>
{
    public Type Type { get; set; }
 
    public TypeOf()
    {
        Type = Assembly.LoadFrom(XmlConfig.Element("Assembly").Value)
                       .GetType(XmlConfig.Element("Type").Value);
    }
}

The TypeOf<T> class has a single property Type which gets its value set in the constructor. The Type property is set with an instance of the type Type, in order for our Activate<T> class to instantiate it.

This is done by loading a specified assembly and getting the type of the class we wish to use to modify the data. This is achieved by accessing an XML file and getting the values from the elements Assembly and

Type
, which represent the assembly and the type respectively, where the relevant class is defined.

This is achieved by the XmlConfiguration<T> class from which our TypeOf<T> class derives from. We can see it defined in the following way.

C#
// (1.7)

class XmlConfiguration<T>
{
    protected XElement XmlConfig =
              XDocument.Load(typeof(T).FullName.Replace(".", @"\") + ".xml").Root;
}

The XmlConfiguration<T> class simply defines a field of type XElement which will hold an XML file with the names of the relevant assembly and the type to instantiate. We can see that this is a generic class, and the reason for this is that its generic type T is the same generic type that we have declared in our interfaces defined in (1.1).

In this way, we have achieved the prerequisite that our Data Exchange Mechanism is specific to the type of the data we are going to modify.

To understand this, let us explain how the XmlConfig field is instantiated. Once we have decided which type our generic type T is going to be within our application, this information will reach the XmlConfiguration<T> class. The XmlConfig field is then filled in the following way.

We use the XDocument class, and invoke its Load(string) method. By using the value in the FullName property gained by calling the typeof(T) keyword, we have effectively produced the namespace and the class name from our data's type. Which is of the form:

Namespace1.Namespace2. ... .NamespaceN.ClassName     (1.8)

Then, by using the Replace(string, string) method on our newly acquired information and using the string "." and @"\" as the parameters, we have now transformed the namespace and the class name of our data's type, into a relative path of the form:

Namespace1\Namespace2\ ... \NamespaceN\ClassName    (1.9)

In other words, from the namespace and class name definitions, we have produced the names of folders in a relative path. At the end, when we add the string ".xml" to the string containing the relative path, we have transformed the last folder name, into an XML filename, of the following form:

Namespace1\Namespace2\NamespaceN\ ... \ClassName.xml  (2.0)

In other words, based on our generic type's full name, we have constructed a relative path to an XML file which is contained in a folder structure that corresponds to the type's namespace structure, while the filename of the XML file corresponds to the type's class name.

Thus when this string is loaded into the Load(string) method, the method will load the XML file corresponding to the name of our generic type T and whose relative path starts at our main application's  executing folder.

With this mechanism now in place, it is easy to load any XML file, depending on the type we determine to use. Also, a clean folder structure is enforced, for storing the relevant XML files, corresponding to the namespaces of our generic type's class.

Once the data is loaded, the XML of the following structure is read.

XML
// (2.1)
 
<?xml version="1.0" encoding="utf-8"?>
<root>
  <Assembly>FolderName\Assembly.dll</Assembly>
  <Type>Namespace.SubNamespace.ClassName</Type>
</root>

It should now be easy to see that once the Assembly and the Type elements are read, we will get the names of the assembly we want to load, and the name of the class that we wish to instantiate. When this information is sent to the LoadFrom(string) and GetType(string) methods in the definition (1.6), we are going to be in the possession of the type that we want our data, of generic type T, to be modified with.

It goes without saying that the modifying class in question should have as its single parameter the generic type T, we defined previously. Then, the definition (1.5) simply instantiates this class and passes to its constructor, the data we wish to modify.

With this mechanism now in place, we have achieved the second prerequisite of being able to modify our data, in any way. Since it will be possible to simply create libraries and types that will be used to process the aforementioned data, and by using the corresponding XML file, we can determine which class exactly do we want to use to modify our data. Since this form of Dependency Injection is done by the use of Reflection[2], we can decide which class shall be used even after we have compiled the main application.

With both prerequisite 1 and prerequisite 2 now achieved, we have successfully constructed the generalized model of the Data Exchange Mechanism.

It will now suffice for us to simply wrap the ExchangeMechanism<T> class defined in (1.3) so it can be used properly. That definition is the following.

C#
// (2.2)

public class CommitExchange<T> where T : new()
{
    public CommitExchange(IExchangeSource<T> source, IExchangeResult<T> result)
    {
        new ExchangeMechanism<T>(new Exchange<T>() { ExchangeSource = source,
                                                     ExchangeResult = result });
    }
}

The CommitExchange<T> class takes two parameters of type IExchangeSource<T> and IExchangeResult<T> in which the objects containing the source data and the destination of processed data will be passed to, respectively. Then the constructor simply loads those object to the properties of the Exchange<T> object and passes it to our ExchangeMechanism<T> object.

Once the object is passed to the mechanism the data exchange shall occur and our initial source data will be modified, and then stored to the object that is determined to hold the processed data.

With this final definition, we have finished presenting the Data Exchange Mechanism. We shall now turn to the exposition of our example application, that is, how we are supposed to implement the mechanism.

Implementing the Mechanism   

3.1 A Simple Example

We are now going to produce an example of how the Data Exchange Mechanism is implemented. Also, we shall show one of the possible optimal implementations. Our example is going to be a simple WPF application concerned with calculating both Simple Interest and Semi-Annually Compounded Interest.

The solution consists of four projects which are the following:

  1. TestExchange
  2. Data Exchange
  3. Interest
  4. Types

The architecture of the solution is set up in the following way.

Image 2

2. The application project structure.

As we can clearly see, our main application containing the WPF project is referencing the Data Exchange project that is going to provide us with the Data Exchange Mechanism. This strong reference is indicated by a solid line. Our main project also references the Types project where the types that are going to be modified are defined, that is, our business objects.

We can also see the Interest project which contains the classes which will be used to calculate the interest, in other words, to modify our data. This project also references the Types project.

It should be noted that our main project does not reference the Interest project because the Interest assembly will be loaded by the Data Exchange Mechanism at runtime, thus we are free to later on add or remove the assemblies used for modifying our data as the need for them arises. The fact that this assembly is not strongly referenced by the Data Exchange Project, is indicated by a dotted line.

Let us now examine our projects briefly.

3.2 Types

The Types assembly is the simplest of our assemblies and it only contains the definitions for types that will represent our data. In our case, what we want to do is calculate the Simple and Compounded Interest for some initial investment. Thus, our class definitions are as follows.

C#
// (2.3)

public class SimpleRate
{
    public double Investment { get; set; }
    public double Rate { get; set; }
    public double Time { get; set; }
    public double Payment { get; set; }
}
 
public class CompoundedRate
{
    public double Investment { get; set; }
    public double Compounding { get; set; }
    public double Rate { get; set; }
    public double Time { get; set; }
    public double Payment { get; set; }
}

As can easily be seen, both the SimpleRate and CompoundedRate classes that represent our compounding conventions have the same properties with the CompoundedRate also having the Compounding property which will represent the amount of times we compound our interest in a year.

The properties that are contained in both classes represent the following values. The Investment property represents our initial monetary investment, the Rate property represents the rate at which the investment will be calculated, the Time property represents the number of years our investment will be held, while the Payment property represents our final payment, that is, our earned interest.

We can also see that this project is referenced by both the TestExchange project and the Interest project. The reason for this is that our Data Exchange Mechanism is going to send the data from the main TestExchange project to the Interest project to be modified, while retaining for us the strongly typed data, and thus allowing us to have IntelliSense at our disposal.

3.3 Interest

The Interest project is the one that represents our modifying project. We are going to use the types defined in this project to modify our data. Since we are going to calculate both Simple and Compounded interest, we will need to construct at least two types to perform the job.

We shall start constructing our calculation mechanism by defining the IConvention<T> interface in the following way.

C#
// (2.4)

interface IConvention<T>
{
    void GetInterest(T rate);
}

This interface is used to identify the compounding convention used. In our case, we have the Simple Interest and the Semi-Annually Compounded Interest. Thus, we can say that in the first case we have no compounding while in the second case we have the semi-annual compounding.

The generic type T shall represent the convention. The types from the definition (2.3) are used to represent the conventions. We can also observe that our interface has a single method named GetInterest(T), which will be used to calculate the interest itself.

Let us now define the class that will be used for calling the GetInterest(T) method.

C#
// (2.5)

class Calculate<T>
{
    public Calculate(IConvention<T> convention, T rate)
    {
        convention.GetInterest(rate);
    }
}

The generic Calculate<T> class simply defines a constructor that takes two parameters. The first parameter is of type IConvention<T> to which we shall pass an object implementing the IConvention<T> interface, and in its GetInterest(T) method, containing the operations to perform the calculations.

The second parameter is the generic type T parameter which represents the compounding convention. This is the object containing all the relevant data for our object to calculate the interest.

The next two definitions are the classes that will implement the IConvention<T> interface.

C#
// (2.6)

class Simple : IConvention<SimpleRate>
{
    public void GetInterest(SimpleRate rate)
    {
        rate.Payment = rate.Investment * (rate.Rate / 100.0) * rate.Time;
    }
}
 
class Compounded : IConvention<CompoundedRate>
{
    public void GetInterest(CompoundedRate rate)
    {
        double temp = 1 + ((rate.Rate / 100.0) / rate.Compounding);
 
        temp = Math.Pow(temp, (rate.Compounding * rate.Time));
 
        rate.Payment = rate.Investment * temp;
    }
}

The operations defined in these two classes are not particularly important. It suffices for us to say that the classes are simply calculating the simple and the compounded interest. What is important is that both classes implement the IConvention<T> interface. While each class implements either the SimpleRate or the CompoundedRate for their generic types, depending on whether they are used to calculate the Simple or Compounded Interest.

For our last two class definition, we are going to show the classes that are exposed outside the assembly and are going to be invoked by our Data Exchange Mechanism.

C#
// (2.7)

public class SimpleInterest
{
    public SimpleInterest(SimpleRate rate)
    {
        new Calculate<SimpleRate>(new Simple(), rate);
    }
}
 
public class CompoundedInterest
{
    public CompoundedInterest(CompoundedRate rate)
    {
        new Calculate<CompoundedRate>(new Compounded(), rate);
    }
}

Both class definitions are similar, and differ in only having the different type of the single parameter in their constructors. The SimpleInterest class takes the SimpleRate type as its parameter, then it instantiates the Calculate<T> class, with the SimpleRate as its generic type and passes to its first parameter the object of type Simple, which will calculate the Simple Interest while for its second parameter, it passes to it the object that contains the relevant data to use for the calculation.

It should be easy to see, that the CompoundedInterest class does the same for us to calculate the Semi-Annually Compounded rate.

3.4 – TestExchange

The TestExchange project is our main project which is the entry point to our example application. Without going into a detailed description, we shall briefly explain the key features of the project.

Our main window is designed in the following way. 

Image 3

3. The application GUI

We can see its interface is formed so that we can enter the relevant values in the TextBox controls, invoking the relevant interest-specific calculations by the Button controls, and that we can also see the calculated values on the bottom Label control.

These three TextBox controls are actually contained in a complex custom control used to enter the data into our application. Therefore, this control is going to represent our object which will serve as our data source. The bottom Label control is a simple custom control that is going to be used to show the processed data, therefore this is the object used by our Data Exchange Mechanism to display the processed data. While the classes in the Interest project are used as the modifying classes by our Data Exchange Mechanism.

Let us now observe the method for constructing the complex custom control that represents our data source. We shall start with the following class definition.

C#
// (2.8)
abstract class AbstractControl<T> : DockPanel where T : new()
{
    protected T Controls = new T();
 
    public AbstractControl()
    {
        new AddControlsToGrid(this);
    }
}

This abstract class definition derives from the DockPanel class and constrains its generic T type with the keyword new(). This is done so that we can automatically create an instance of the generic type T using the Controls field. This is where our controls, that are going to be displayed by our custom control, are going to be held.

Furthermore, the constructor simply instantiates the AddControlsToGrid class and passes to it the this keyword, representing the object of the current class itself. This is done so that the instantiated object can add all the relevant child controls to our complex control which will display all the child controls on the main window. This is done in the following way.

C#
// (2.9)

class AddControlsToGrid
{
    public AddControlsToGrid(DockPanel dockPanel)
    {
        var Grid = new Grid();
 
        new AddChildControls(dockPanel, Grid);
        new AddGridToMainControl(dockPanel, Grid); 
    }
}

We can observe that our AddControlsToGrid class contains a single variable of type Grid which will hold all of the controls that we wish to have on our main custom control.  The constructor takes a single parameter of type DockPanel, representing our main custom control.

In order to now add all the relevant controls to the Grid object and then to the main custom control, we first instantiate an object of type AddChildControls and then the AddGridToMainWindowControl object, and pass to them a reference of our main custom control, and then the Grid object itself.

Let us now observe the definition of the AddChildControls class as follows:

C#
// (3.0)

class AddChildControls
{
    public AddChildControls(DockPanel dockPanel, Grid grid)
    {
        var control = new GetControl(dockPanel).Control;
 
        control.GetType()
               .GetFields()
               .Where(x => x.FieldType.ImplementsInterface<IChild>())
               .ToList()
               .ForEach(x => grid.Children.Add((UIElement)x.GetValue(control)));
    }
}

We first need to get the field from our main custom control that holds all the relevant child controls. This is done by the GetControl class. Once this is done, we shall simply get the type of the field we just selected, get its type and its fields if they implement the IChild interface. This way we shall distinguish the controls that need to be displayed on the custom control, from other helper fields. Once we get all the relevant fields, we create a list from those fields, and by invoking the ForEach(Action<T>) method, we add every field to the Grid object.

Let us now look at how the GetControl class has performed its part.

C#
// (3.1)

class GetControl
{
    public object Control { get; set; }
 
    public GetControl(DockPanel dockPanel)
    {
        Control = dockPanel.GetType()
                           .GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                           .Where(x => x.FieldType.ImplementsInterface<IControl>())
                           .First()
                           .GetValue(dockPanel);
    }
}

Once an instance of our custom control is passed to the constructor, we get the control's type, then get its protected fields only if they implement the IControl interface. This way we shall distinguish the field that contains our controls that need to be displayed on the custom control, from other fields. And then we get the instance from the custom control we passed as the parameter.

For the last step, we shall observe the definition of the AddGridToMainControl class.

C#
// (3.2)

class AddGridToMainControl
{
    public AddGridToMainControl(DockPanel dockPanel, Grid grid)
    {
        var children = dockPanel.GetType()
                                .GetProperty("Children")
                                .GetValue(dockPanel, null);
 
        children.GetType()
                .GetMethod("Add")
                .Invoke(children, new UIElement[] { grid });
    }
}

Once the AddGridToMainControl class is instantiated, we use it to get the Children property from our custom control and invoke its Add(UIElement) method, passing to it the grid parameter which represents the object of type Grid, which we have previously loaded with all the relevant child controls. This is done so we can add this object to our main custom control as its child, so that it can display all the relevant child controls on the application's main window.

We shall now continue to our concrete implementation of our custom control that is going to be displaying controls on the application's main window.

C#
// (3.3)

class InterestControl : AbstractControl<Interest>, 
      IExchangeSource<SimpleRate>, IExchangeSource<CompoundedRate>
{
    public void GetSourceData(SimpleRate data)
    {
        if (new TestForValue().Within(Controls.InvestmentTextBox, Controls.RateTextBox, Controls.TimeTextBox))
        {
            data.Investment = Controls.InvestmentTextBox.GetData().ToDouble();
            data.Rate = Controls.RateTextBox.GetData().ToDouble();
            data.Time = Controls.TimeTextBox.GetData().ToDouble();
        }
    }
 
    public void GetSourceData(CompoundedRate data)
    {
        if (new TestForValue().Within(Controls.InvestmentTextBox, Controls.RateTextBox, Controls.TimeTextBox))
        {
            data.Investment = Controls.InvestmentTextBox.GetData().ToDouble();
            data.Rate = Controls.RateTextBox.GetData().ToDouble();
            data.Time = Controls.TimeTextBox.GetData().ToDouble();
            data.Compounding = 2.0;
        }
    }
}

We can now see that our InterestControl derives from the AbstractControl<T> class, and uses the type Interest for its generic T type. Needless to say, this will automatically have an effect of instantiating a class of type Interest in our derived field Controls. Furthermore, all the controls defined in the Interest class will automatically be added to our InterestControl.

Let us now turn to the central point of our example. The implementation of the IExchangeSource<T> interface for our Data Exchange Mechanism. We can instantly notice that we have actually used two IExchangeSource<T> interfaces, and not just one. This will demonstrate the fact that we are able to use any number of types with a single control, and the Data Exchange Mechanism will be able to handle them all in the same way.

Since we are interested in calculating both the Simple and the Compounded Interest rate, our first interface is defined with the SimpleRate type, while the second one is defined with the CompoundedRate type.

In order to implement those interfaces we define two GetSourceData(T) methods, implemented with SimpleRate and CompoundedRate types accordingly. These methods will get the data that we will enter in the TextBox controls on our main window.

Both methods act as a way of telling the Data Exchange Mechanism that this is how the custom control communicates with the mechanism and sends it the relevant data. Both methods validate the data and fill the appropriate values that will be sent back to the Data Exchange Mechanism to be processed.

The only difference is that the second method also has the Compounding property which will represent the Semi-annually compounded rate, and therefore, we set this property to the value of 2.0. Thus we have now successfully implemented the first part of our Data Exchange Mechanism.

The only relevant point left to explain is the Interest class which is going to serve as a container for our controls displayed by the InterestControl class on our main window. The class is simply defined in the following way.

C#
// (3.4)

class Interest : IControl
{
    public IChild InvestmentTextBox = new InvestmentTextBox();
    public IChild RateTextBox = new RateTextBox();
    public IChild TimeTextBox = new TimeTextBox();
 
    public IChild InvestmentLabel = new InvestmentLabel();
    public IChild RateLabel = new RateLabel();
    public IChild TimeLabel = new TimeLabel();
}

The class derives from the IControl interface which is an empty interface, simply in order to be distinguished in the definition (3.1). It also contains the instantiated fields of type IChild, that are going to be serving as elements on our main window. While the IChild interface is also used in the definition (3.0) to distinguish our controls from other helper fields, it also contains the GetData() method which is used for validation in the definition (3.3) by the Within(params IChild[] children) method. This method simply tests for the existence of values within our TextBox controls and is not currently important to us.

C#
// (3.5)

public interface IChild
{
    string GetData();
}

Let us now turn to the second step in the process of implementing the Data Exchange Mechanism. We shall now implement the IExchangeResult<T> interface within a Label control, in order to display the relevant data.

C#
// (3.6)

class PaymentLabel : AbstractLabel, IExchangeResult<SimpleRate>, IExchangeResult<CompoundedRate>
{
    public PaymentLabel()
    {
        Content = "Value";
 
        VerticalContentAlignment = VerticalAlignment.Top;
 
        Height = 124;
        Width = 698;
    }
 
    public void SetSourceData(SimpleRate data)
    {
        Content = data.Payment.ToString("C");
    }
 
    public void SetSourceData(CompoundedRate data)
    {
        Content = data.Payment.ToString("C");
    }
}

Aside from the AbstractLabel class which is unimportant to the discussion of how to implement the Data Exchange Mechanism, our Payment Label control implements the IExchangeResult<T> interface in two ways. One implementation uses the SimpleRate type, while the other one uses the CompoundedRate.

Both of these interfaces are implemented by defining a SetSourceData(T) method which will serve for setting the relevant data to the Content property of our Payment Label control. We can observe that we implemented both instances to display the value as a currency by using the ToString(string) method.

It should be easy to see now that once our data is processed, it will be displayed by this Label control exactly as it is specified by these two methods.

For our lest step, we shall deal with entering the relevant data, processing it, and displaying it to the end user. We should mention that we do not need to implement the IExchangeModify<T> interface because this interface is, as it has been shown in definition (1.4) already implemented by the Modify<T> class that is contained in the Data Exchange Mechanism itself.

The only thing left now to do is to set the relevant XML file to point to a certain assembly which will contain the class definition that can process our data, that is, that can calculate Simple and Compounded Interest. Those classes are defined in the definition (2.6). What is left to do is to place the assembly containing these class definitions in a folder to which our XML file will point to.

Let us now turn to implementing the last step, which will calculate the Simple interest rate.

Since our business object for transferring the Simple Interest rate data is contained in the class named SimpleInterest, which is located in the namespace Types.Interest, it is only natural to place our XML configuration file in the corresponding directory structure, that is, Types\Rate, within our main executing directory. We should also name the XML file as SimpleInterest.xml.

The Data Exchange Mechanism will now look for our XML file in this exact place and look for this exact filename. Once the assembly and the XML configuration file are in place, we can set the XML file as follows.

XML
// (3.7)
 
<?xml version="1.0" encoding="utf-8"?>
<root>
  <Assembly>Components\Interest.dll</Assembly>
  <Type>Interest.CalculateInterest.SimpleInterest</Type>
</root>

From this definition, we can notice that we have placed the assembly that contains the relevant definition that will calculate the Simple Interest rate, namely the Interest.dll in the Components folder within our main executing directory.

Also, we have pointed the Data Exchange Mechanism to load the SimpleInterest class, defined in the Interest.CalculateInterest namespace within the Interest.dll assembly.

Without the loss of generality, we shall only mention that the same process is performed for calculating the Compounded interest, with the only difference being the the names of the XML configuration file, and the class that will calculate the Compounded Interest, which are named CompoundedInterest in both cases.

Before calculating our interest, we are now only left with implementing the Button control which will trigger our Data Exchange Mechanism to perform its job. So let us now try and explain how we can construct a Button control, which can be used optimally with the Data Exchange mechanism.

First, we start by defining the AbstractButton class in the following way.

C#
// (3.8)

abstract class AbstractButton : Button, IChild
{
    public AbstractButton()
    {
        HorizontalAlignment = HorizontalAlignment.Center;
 
        Height = 25;
        Width = 250;
    }
 
    public void SetData<T>(IExchangeSource<T> source, IExchangeResult<T> result) where T : new()
    {
        Click += new RoutedEventHandler((object o, RoutedEventArgs e) => new CommitExchange<T>(source, result));
    }
 
    public string GetData()
    {
        return "";
    }
}

While observing the current definition, aside from the constructor defining the display-specific properties, and the IChild interface with its validation method we can notice that our abstract class derives from the Button class.

These definitions are currently not important for the implementation of the Data Exchange Mechanism. The only important definition is the SetData<T>(IExchangeSource<T>, IExchangeResult<T>) method. This method shall be used to set our Button control in order to use the Data Exchange Mechanism.

This is achieved in the following way. We can notice that the generic type T derives from the new() keyword. This is needed in order for our Data Exchange Mechanism to create an instance of our business object we are processing.

Next, we can see that once we pass the objects to both parameters of our method, one representing the source of our data, and the other representing the destination, they will both be placed as arguments for the instance of the CommitExchange<T> type object.

The generic type T represents a certain business object, for which the source object is going to serve the data, and the result object is going to serve as its destination, once the data is processed.

Furthermore this single line of code in our method is constructed in such a way, so as to represent an anonymous method placed as a parameter for an EventHandler of type RoutedEventHandler. Clearly, this object is added to the Button controls' Click event.

Thus once this method is invoked with a certain generic type definition and two controls, one serving as the source of data and another providing the destination for the data, the Button control will, once it is clicked initiate the Data Exchange Mechanism, for this particular generic type.

We should now simply make a concrete Button control which will implement the AbstractButton class in the following way.

C#
// (3.9)

class SimpleButton : AbstractButton
{
    public SimpleButton()
    {
        Content = "Simple Interest";
    }
}

This is a sufficient definition to represent the Button control that will be used to trigger the Data Exchange Mechanism in order to calculate the Simple Interest. Without the loss of generality, the same is done for the Compounded Interest.

We are now only left with initiating our Button controls to actually use the Data Exchange Mechanism for processing the data. For this we have the following definition.

C#
// (4.0)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
 
        SimpleButton.SetData<SimpleRate>(CustomInterest, Payment);
        CompoundingButton.SetData<CompoundedRate>(CustomInterest, Payment);
    }
}

We can now see our MainWindow definition that our application will display. The only two method invocations we need to execute, are calls to the SetData<T>(IExchangeSource<T>, IExchangeResult<T>) method on each Button control. Setting each Button control to its respective business object type, will let use invoke the Data Exchange Mechanism to calculate the Simple and Compounded interest rate accordingly.

Furthermore, by placing our CustomInterest object, defined in (3.3) and Payment object defined in (3.6) as parameters to our method, acting as the source of our data and the destination respectively, we have set up our Button controls to use these two objects as parameters for our Data Exchange Mechanism, once the Button control is clicked.

We should notice that the advantage of this implementation is that we have defined a Button control in one place, but we have left the actual implementation to the very last moment. It should be obvious that the SimpleButton, if invoked again, with the CompoundedRate for its generic type T would be able to serve as the trigger for calculating the Compounded Interest rate also.

And so with this last part of our mechanism now in place, we have successfully created and demonstrated a generalized type of data exchange, for business objects, using our Data Exchange Mechanism.

Alternative Implementations

4.1 Button Controls

Let us now look at some examples of alternative implementations of the Data Exchange Mechanism. We shall start out with the Button controls. It is possible to implement our Button controls in the following way.

C#
// (4.1)

abstract class AbstractButton<T> : Button, IChild where T : new()
{
    public AbstractButton()
    {
        HorizontalAlignment = HorizontalAlignment.Center;
 
        Height = 25;
        Width = 250;
    }
 
    public void SetData(IExchangeSource<T> source, IExchangeResult<T> result)
    {
        Click += new RoutedEventHandler((object o, RoutedEventArgs e) => 
                           new CommitExchange<T>(source, result));
    }
 
    public string GetData()
    {
        return "";
    }
}

It is possible to define our AbstractButton class as a generic AbstractButton<T> class, where its generic T type derives from the new() keyword. We also remove the generic definition from the SetData<T>(IExchangeSource<T>,  IExchangeResult<T>) method, and make it a non-generic one.

C#
// (4.2)

class SimpleButton : AbstractButton<SimpleRate>
{
    public SimpleButton()
    {
        Content = "Simple Interest";
    }
}

We now implement our SimpleButton control by making it derive from the AbstractButton<T> class and implementing its generic T type with the SimpleRate class. In this way, we have concretely defined our SimpleButton class to only trigger data exchanges where the SimpleRate type is used for our business object.

It is obvious that this implementation is less flexible. Since we can not use any other type with this particular Button control, unless it derives from the SimpleRate class. But now we do not need to specify which type to use when we are setting the source and the destination of our data. We can see this in the following example.

C#
// (4.3)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
 
        SimpleButton.SetData(CustomInterest, Payment);
        CompoundingButton.SetData(CustomInterest, Payment);
    }
}

4.2 – XML Configuration

The XmlConfiguration<T> class for loading the type-specific XML configuration file was shown in definition (1.7). It has been shown that it loads the XML files, from a relative path, starting from our main application's executing directory.

It is possible to implement this class in another way so as to specify where exactly in our executing directory we wish to locate our XML files. So let us observe the following definition.

C#
// (4.4)

class XmlConfiguration<T>
{
    protected XElement XmlConfig { get; set; }
 
    public XmlConfiguration()
    {
        string path = XDocument.Load(@"Configuration\Configuration.xml")
                               .Root
                               .Element("Path")
                               .Value;
 
        XmlConfig = XDocument.Load(path + typeof(T).FullName.Replace(".", @"\") + ".xml").Root;
    }
}

Our XmlConfig field is now transformed into a property which will be loaded in the constructor. If we want to put our XML folder structure in a specific starting folder we can continue our implementation in the following way.

We could create a folder named Configuration in our main application's executing directory, and create an XML file named Configuration.xml in the said folder. Now we can load this XML file and get the starting path we are going to use for our XML folder structure from its Path element. This can be done, if we define the XML file in the following way.

XML
// (4.5)
 
<?xml version="1.0" encoding="utf-8"?>
<root>
  <Path>Configuration\</Path>
</root>

If we were to now place our Types\Rate folder structure inside the Configuration folder, our Data Exchange Mechanism would load the appropriate file, starting from the new location specified in the definition (3.8).

References

  • [1] Mark Seemann: Dependency Injection in .NET; Manning Publications, 1 edition (September 28, 2011)
  • [2] Joseph Albahari, Ben Albahari: C# 4.0 in a Nutshell: The Definitive Reference; O'Reilly Media, Fourth Edition edition (February 10, 2010)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Croatia Croatia
Programming without Reflection is like driving without a car.

Comments and Discussions

 
QuestionSimple Design Pin
FatCatProgrammer24-Sep-12 4:55
FatCatProgrammer24-Sep-12 4:55 
AnswerRe: Simple Design Pin
Mario Stopfer24-Sep-12 6:08
Mario Stopfer24-Sep-12 6:08 
GeneralRe: Simple Design Pin
FatCatProgrammer24-Sep-12 6:24
FatCatProgrammer24-Sep-12 6:24 
GeneralRe: Simple Design Pin
Mario Stopfer24-Sep-12 7:44
Mario Stopfer24-Sep-12 7:44 
GeneralRe: Simple Design Pin
FatCatProgrammer24-Sep-12 9:37
FatCatProgrammer24-Sep-12 9:37 
GeneralRe: Simple Design Pin
Mario Stopfer24-Sep-12 11:08
Mario Stopfer24-Sep-12 11:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.