Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dynamic Object Programming

4.95/5 (5 votes)
11 Dec 2011CPOL9 min read 31.9K   338  
Discuss dynamic object programming and how it is used with the CBO Extender in .NET.

Introduction

What is dynamic object programming? To answer this question, we need to answer two other questions first: What is dynamic programming? And what is object programming?

In this article, I try to answer these questions, and then present some examples to demonstrate how dynamic object programming can improve software development and maintenance in .NET.

What is Dynamic Programming

The dynamic type in C# 4 enables operations to bypass compile-time type checking and be resolved at runtime. It is best known that the dynamic type can be used to simplify accesses to objects coming from a dynamic language.

Since the dynamic type is native to C#, you can take advantage of its runtime features without involving objects from a dynamic language. Would it be nice for a C# program to have dynamic behaviors without using a dynamic language? Or put it another way, would it be nice to use C# language to do dynamic programming? That is our focus for this article. In this article, I show you how the dynamic type is used to do dynamic programming in C#.

What is Object Programming

In object-oriented programming, the focus is on classes. We design classes, create objects of the classes, and use the objects in our applications. An object behaves as defined in its class, no more and no less. In object programming, however, the focus is on objects. Extra functionality can be added to an object without changing its class, which means an object can have behaviors not defined in its class.

With object programming, a set of behaviors is defined as methods based on business and system requirements. These behaviors are, then, attached to the object's methods as needed in an application. At runtime, the extra behaviors are performed before and/or after the invocation of the object's methods.

What is Dynamic Object Programming

With the explanations of dynamic programming and object programming, we should be able to answer what dynamic object programming is now. Dynamic object programming is object programming that uses the dynamic type in the definitions of extra behaviors.

With dynamic object programming, the extra behaviors bypass compile-time type checking and are resolved at runtime. It makes the definition of extra behaviors more flexible and powerful. It also simplifies attaching behaviors to objects in an application.

Dynamic Object Programming with CBO Extender

Component-Based Object Extender (CBO Extender) is an object extensibility framework. It works with interfaces and extends objects directly. It is a general-purpose tool for adding functionality to objects by attaching behaviors to interface methods of objects. With the introduction of the dynamic type in its aspect methods (see article Dynamic Type With Component-Based Object Extender for more details), it can be used to do dynamic object programming.

In this article, I try to address some software concerns like logging, security checking, transaction management, etc., for an application using dynamic object programming with CBO Extender. These concerns are considered as extra behaviors aside from the functionality of business objects and are defined as aspect methods with the dynamic type. They are, then, added to objects as needed in an application.

CBOExtender 1.2 is the latest release of CBO Extender and can be downloaded as a NuGet package. You can also download the source code, latest updates, and more examples at http://centurytechs.com/CBOExtender.html.

To install CBOExtender to your project from Visual Studio 2010, click Tools->Library Package Manager->Manage NuGet Packages... to open the Manage NuGet Packages dialog. Type in CBOExtender as shown.

Image 1

You probably need to install NuGet for Visual Studio 2010 before you can download the package.

Using the Code

In the following sections, several aspect methods are defined. They then are used to enhance an application by attaching them to objects in the application.

Define Aspects

The aspect methods should match the signature of DecorationDelegate2, which is defined as follows.

C#
public delegate void DecorationDelegate2(AspectContext2 ctx, dynamic dynPara);

A few aspect methods are defined as follows.

C#
public static void JoinSqlTransaction(AspectContext2 ctx, dynamic parameter)
{
    try
    {
        ctx.Target.Command.Transaction = parameter;
        return;
    }
    catch (Exception ex)
    {
        throw new Exception("Failed to join transaction!", ex);
    }
}

public static void EnterLog(AspectContext2 ctx, dynamic parameters)
{
    IMethodCallMessage method = ctx.CallCtx;
    string str = "Entering " + 
       ((object)ctx.Target).GetType().ToString() + "." + 
       method.MethodName + "(";
    int i = 0;
    foreach (object o in method.Args)
    {
        if (i > 0)
            str = str + ", ";
        str = str + o.ToString();
    }
    str = str + ")";

    Console.WriteLine(str);
    Console.Out.Flush();

}

public static void ExitLog(AspectContext2 ctx, dynamic parameters)
{
    IMethodCallMessage method = ctx.CallCtx;
    string str = ((object)ctx.Target).GetType().ToString() + 
                  "." + method.MethodName + "(";
    int i = 0;
    foreach (object o in method.Args)
    {
        if (i > 0)
            str = str + ", ";
        str = str + o.ToString();
    }
    str = str + ") exited";

    Console.WriteLine(str);
    Console.Out.Flush();
}

public static void SecurityCheck(AspectContext2 ctx, dynamic parameter)
{
    if (parameter.IsInRole("BUILTIN\\" + "Administrators"))
        return;

    throw new Exception("No right to call!");
}

JoinSqlTransaction sets the Transaction property of the Command of the Target. Note that both Target and parameter are dynamic types, the compiler will not resolve their types. It is your responsibility to ensure that Target and parameter have proper types when you use the method in your application. Otherwise you will get a runtime exception.

EnterLog and ExitLog write to Console entering log and exit log, respectively.

SecurityCheck checks if parameter is in Adminstrator's role. Again, it is your responsibility to ensure parameter has the proper type when you use the method in your application since the compiler does not resolve its type during compilation.

In the following section, I will show you how these dynamic methods can be used to add logging, security checking, and transaction management capabilities to an application.

Using CreateProxy2

Before we use the above aspect methods, let's write a small application to insert a record into the [Sales].[SalesOrderHeader] and [Sales].[SalesOrderDetail] tables of the AdventureWorks database shipped with Microsoft SQL Server. The application code is listed as follows.

C#
static void Main(string[] args)
{
    string connStr = "Integrated Security=true;" + 
           "Data Source=(local);Initial Catalog=AdventureWorks";
    using(IDbConnection conn = new SqlConnection(connStr))
    {
        IDbTransaction transaction = null;

        try
        {
            conn.Open();

            IOrder o = new Order();
            o.CustomerID = 18759;
            o.DueDate = DateTime.Now.AddDays(1);
            o.AccountNumber = "10-4030-018759";
            o.ContactID = 4189;
            o.BillToAddressID = 14024;
            o.ShipToAddressID = 14024;
            o.ShipMethodID = 1;
            o.SubTotal = 174.20;
            o.TaxAmt = 10;
            ((ISqlOperation)o).Command = new SqlCommand();
            ((ISqlOperation)o).Command.Connection = (SqlConnection)conn;

            int iStatus;
            iStatus = o.InsertOrder();

            //throw new Exception();

            IOrderDetail od = new OrderDetail();
            od.SalesOrderID = o.OrderID;
            od.OrderQty = 5;
            od.ProductID = 708;
            od.SpecialOfferID = 1;
            od.UnitPrice = 28.84;
            ((ISqlOperation)od).Command = new SqlCommand();
            ((ISqlOperation)od).Command.Connection = (SqlConnection)conn;

            iStatus = od.InsertOrderDetail();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);

            if (transaction != null)
                transaction.Rollback();
        }
        finally
        {
            conn.Close();
        }

        Console.ReadLine();
    }
}

When executing, the InsertOrder method of the Order component inserts a record into the table [Sales].[SalesOrderHeader] while the InsertOrderDetail method of the OrderDetail component inserts a record into the table [Sales].[SalesOrderDetail].

There is an issue with the above code: the two insertions are not in a transaction. If the program fails right after the first insertion but before the second insertion, the data for the table [Sales].[SalesOrderDetail] will get lost. You can simulate this issue by uncommenting the line //throw new Exception();.

To fix this issue, we need to add transaction management capability to this application by putting the insertion operations in a single transaction so that all or no operations are performed. In addition to transaction management, we also like to add security checking to the application so that only an administrator can insert the record to the tables by running this program. Lastly, we also like the application to generate some logs when executing.

As you may already know, we use the aspects defined in the previous section to add these enhancements to the application. The ObjectProxyFactory.CreateProxy2 method is used to add the aspects to the objects in the application. The ObjectProxyFactory.CreateProxy2 method has the following signature:

C#
public static T CreateProxy2<T>(object target, String[] arrMethods, 
              Decoration2 preAspect, Decoration2 postAspect)

where:

  • T - interface to be returned
  • target - original object
  • arrMethods - string array of method names to be added preprocessing aspect and/or postprocessing aspect
  • preAspect - decoration for preprocessing aspect
  • postAspect - decoration for postprocessing aspect

Decoration2 encapsulates a DecorationDelegate2 and a dynamic, and has a constructor with the following signature:

C#
public Decoration2(DecorationDelegate2 aspectHandler, dynamic parameter)

aspectHandler is the delegate of an aspect method and the parameter object is passed into the aspect method as its second argument when the aspect method is invoked at runtime.

Using ObjectProxyFactory.CreateProxy2 and the above aspect methods, we can enhance the application to have transaction management, security checking, and logging capabilities. The complete application code is listed as follows:

C#
static void Main(string[] args)
{
    //Commenting out this line, the security check aspect will throw out an exception 
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    string connStr = "Integrated Security=true;" + 
           "Data Source=(local);Initial Catalog=AdventureWorks";
    using (IDbConnection conn = new SqlConnection(connStr))
    {
        IDbTransaction transaction = null;

        try
        {
            conn.Open();
            IDbTransaction transactionObj = conn.BeginTransaction();
            transaction = ObjectProxyFactory.CreateProxy2<IDbTransaction>(
                transactionObj,
                new string[] { "Commit", "Rollback" },
                null,
                new Decoration2(AppConcerns.ExitLog, null)
            );

            IOrder o = new Order();
            o.CustomerID = 18759;
            o.DueDate = DateTime.Now.AddDays(1);
            o.AccountNumber = "10-4030-018759";
            o.ContactID = 4189;
            o.BillToAddressID = 14024;
            o.ShipToAddressID = 14024;
            o.ShipMethodID = 1;
            o.SubTotal = 174.20;
            o.TaxAmt = 10;
            ((ISqlOperation)o).Command = new SqlCommand();
            ((ISqlOperation)o).Command.Connection = (SqlConnection)conn;

            o = ObjectProxyFactory.CreateProxy2<IOrder>(
                o,
                new string[] { "InsertOrder" },
                new Decoration2(AppConcerns.JoinSqlTransaction, transactionObj),
                null
            );

            o = ObjectProxyFactory.CreateProxy2<IOrder>(
                o,
                new string[] { "InsertOrder" },
                new Decoration2(AppConcerns.EnterLog, null),
                new Decoration2(AppConcerns.ExitLog, null)
            );

            o = ObjectProxyFactory.CreateProxy2<IOrder>(
                o,
                new string[] { "InsertOrder" },
                new Decoration2(AppConcerns.SecurityCheck, Thread.CurrentPrincipal),
                null
            );

            int iStatus;
            iStatus = o.InsertOrder();

            //throw new Exception();

            IOrderDetail od = new OrderDetail();

            od.SalesOrderID = o.OrderID;
            od.OrderQty = 5;
            od.ProductID = 708;
            od.SpecialOfferID = 1;
            od.UnitPrice = 28.84;
            ((ISqlOperation)od).Command = new SqlCommand();
            ((ISqlOperation)od).Command.Connection = (SqlConnection)conn;

            od = ObjectProxyFactory.CreateProxy2<IOrderDetail>(
                od,
                new string[] { "InsertOrderDetail" },
                new Decoration2(AppConcerns.JoinSqlTransaction, transactionObj),
                null
            );

            od = ObjectProxyFactory.CreateProxy2<IOrderDetail>(
                od,
                new string[] { "InsertOrderDetail" },
                new Decoration2(AppConcerns.EnterLog, null),
                new Decoration2(AppConcerns.ExitLog, null)
            );

            od = ObjectProxyFactory.CreateProxy2<IOrderDetail>(
                od,
                new string[] { "InsertOrderDetail" },
                new Decoration2(AppConcerns.SecurityCheck, Thread.CurrentPrincipal),
                null
            );

            iStatus = od.InsertOrderDetail();

            transaction.Commit();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);

            if (transaction != null)
                transaction.Rollback();
        }
        finally
        {
            conn.Close();
        }

        Console.ReadLine();
    }
}

The code:

C#
transaction = ObjectProxyFactory.CreateProxy2<IDbTransaction>(
    transactionObj,
    new string[] { "Commit", "Rollback" },
    null,
    new Decoration2(AppConcerns.ExitLog, null)
);

attaches the exit log method to the methods Commit and Rollback of transactionObj as a postprocessing aspect. When using transaction to access these methods, exit log is output right after the execution of the methods.

The code:

C#
o = ObjectProxyFactory.CreateProxy2<IOrder>(
    o,
    new string[] { "InsertOrder" },
    new Decoration2(AppConcerns.JoinSqlTransaction, transactionObj),
    null
);

attaches the join transaction method to the method InsertOrder of the object o of the Order component as a preprocessing aspect. Note that the variable o originally references to an object of the Order component. Now, it references to a proxy of the object. You can chain multiple aspects to an object like this. When using o to call the method, it adds the method to the transaction first and then invokes the method itself.

The code:

C#
o = ObjectProxyFactory.CreateProxy2<IOrder>(
    o,
    new string[] { "InsertOrder" },
    new Decoration2(AppConcerns.EnterLog, null),
    new Decoration2(AppConcerns.ExitLog, null)
);

attaches the entering log method and the exit log method to the method InsertOrder of the proxy o as a preprocessing aspect and a postprocessing aspect, respectively. When using o to call the method, it writes the entering log first, then invokes the method, and last writes the exit log.

The code:

C#
o = ObjectProxyFactory.CreateProxy2<IOrder>(
    o,
    new string[] { "InsertOrder" },
    new Decoration2(AppConcerns.SecurityCheck, Thread.CurrentPrincipal),
    null
);

attaches the security checking method to the method InsertOrder of the proxy o as a preprocessing aspect. When using o to call the method, it does security checking first, then invokes the method.

As you can see in the above code, you can use the proxy returned from ObjectProxyFactory.CreateProxy2 as a target to another ObjectProxyFactory.CreateProxy2 call to chain multiple aspects together. When the line iStatus = o.InsertOrder(); executes, if everything goes as expected, it checks security first, then writes the entering log, and then joins the method of the original object to the transaction. At this point, it finishes all preprocessing aspects and starts to invoke the method InsertOrder() of the original object. After this, it writes the exit log.

The rest of code does the same thing to the object od of the OrderDetail component. It adds join transaction, entering log, exit log, and security checking aspects to it. The code iStatus = od.InsertOrderDetail(); checks security, writes the entering log, joins the method to the transaction, invokes the method, and writes the exit log.

The code transaction.Commit(); commits the transaction and writes the exit log.

The code transaction.Rollback(); rolls back the transaction and writes the exit log.

Run the application, and you will see the following screen. And a new record is inserted into the [Sales].[SalesOrderHeader] and [Sales].[SalesOrderDetail] tables, respectively.

Image 2

Uncomment the line //throw new Exception(); and run it again, and you will see the following screen. And no record is inserted into [Sales].[SalesOrderHeader] or [Sales].[SalesOrderDetail].

Image 3

Comment out the line Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); and run it again, and you will see the following screen. The security checking fails. No log is generated and no record is inserted into [Sales].[SalesOrderHeader] or [Sales].[SalesOrderDetail].

Image 4

Points of Interest

Object programming unveils a programming paradigm which aims to improve software development by extending objects at runtime instead of extending classes at design time. Combined with dynamic programming, it makes software development more flexible and adaptive to changes.

With the CBO Extender, you can do dynamic object programming by defining a set of methods and attaching them to objects as needed at runtime. It complements object-oriented programming by avoiding changes of class, and therefore improving the flexibility of a software system and reducing its maintenance cost. It limits programming to interfaces and interface methods.

License

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