Click here to Skip to main content
Click here to Skip to main content
Go to top

Dynamic Object Programming With AOP Container

, 15 Dec 2011
Rate this:
Please Sign up or sign in to vote.
Discusses how to do dynamic object programming with AOP Container when using IoC Containers.

Introduction

Dynamic Object Programming (DOP) is a programming paradigm which aims to improve software development by extending objects at runtime instead of extending classes at design time. It complements object-oriented programming by avoiding changing or creating classes, and therefore, improves the flexibility of a software system and reduces the system maintenance cost.

Component-Based Object Extender (CBO Extender), as an object extensibility framework in .NET, is particularly suitable for dynamic object programming. It provides two flavors for programming tasks: use CreateProxy2 of Dynamic Decorator and use ChainAspect2 of AOP Container. You can always use CreateProxy2 to add extra behaviors to objects while ChainAspect2 is more convenient when you use some kind of IoC container in your development. In the example of the article Dynamic Object Programming, CreateProxy2 is used to show you how to do dynamic object programming. In this article, I will show you how ChainAspect2 is used to do dynamic object programming together with Windsor Container or Unity Container.

Background

When doing Dynamic Object Programming (DOP) with CBO Extender, you define a set of behaviors as aspect methods based on business and system requirements, then use either CreateProxy2 or ChainAspect2 to add them to objects in an application. Since an aspect method is a .NET method, you can do anything in it. You can use static types or dynamic types for an aspect method. One advantage of a dynamic type aspect method is that it bypasses compile-time type checking and is resolved at runtime, which makes programming simpler and more flexible.

In this article, I try to address some software concerns like logging, security checking, and transaction management using dynamic type aspect methods. These aspects are then added to objects in an application using ChainAspect2.

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.

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

Use the Code

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

Define Aspects

Aspect methods should match the signature of DecorationDelegate2, which is defined as follows:

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

A few aspect methods are defined as follows.

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 type, the compiler will not resolve their types. It is your responsibility to ensure that Target and parameter are objects of 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 exiting log, respectively.

SecurityCheck checks if the parameter is in Adminstrator's role. Again, it is your responsibility to ensure the parameter is an object of 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 show you how these dynamic methods can be used to add logging, security checking, and transaction management capabilities to an application.

Use ChainAspect2

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. Here, we list the application code where Windsor Container is used to instantiate business objects as follows. You can find the application code using Unity Container in the download.

static void Main(string[] args)
{
    IWindsorContainer windsorContainer = new WindsorContainer();
    windsorContainer.Register(AllTypes
        .FromAssembly(Assembly.LoadFrom("DataModel.dll"))
        .Where(t => (t.Name.Equals("Order") || t.Name.Equals("OrderDetail")))
        .Configure(c => c.LifeStyle.Transient)
    );

    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 = windsorContainer.Resolve<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 = windsorContainer.Resolve<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();
        }

        windsorContainer.Dispose();

        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. Last, we also like the application to generate some logs when executing.

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

public static V ChainAspect2<T, V>(T target, string methods, 
              Decoration2 preDeco, Decoration2 postDeco) where T : V

where

  • T - target type
  • V - interface to be returned
  • target - original object
  • methods - method names (separated by comma) to be added to the preprocessing aspect and/or postprocessing aspect
  • preDeco - decoration for preprocessing aspect
  • postDeco - decoration for postprocessing aspect

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

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 AOPContainer.ChainAspect2 and the above aspect methods, we can enhance the application to have transaction management, security checking, and logging compatibilities. The complete application code is listed as follows.

static void Main(string[] args)
{
    AOPContainer aopcontainer = null;

    //Register types for Windsor Container and create AOPWindsorContainer
    IWindsorContainer windsorContainer = new WindsorContainer();
    windsorContainer.Register(AllTypes
        .FromAssembly(Assembly.LoadFrom("DataModel.dll"))
        .Where(t => (t.Name.Equals("Order") || t.Name.Equals("OrderDetail")))
        .Configure(c => c.LifeStyle.Transient)
    );

    aopcontainer = new AOPWindsorContainer(windsorContainer);

    //Register types for Unity Container and create AOPUnityContainer
    //IUnityContainer unityContainer = new UnityContainer();
    //unityContainer.RegisterType<IOrder, Order>(new InjectionConstructor()
    //).RegisterType<IOrderDetail, OrderDetail>(new InjectionConstructor()
    //);

    //aopcontainer = new AOPUnityContainer(unityContainer);

    //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 = AOPContainer.ChainAspect2<IDbTransaction, IDbTransaction>(
                transactionObj,
                "Commit,Rollback",
                null,
                new Decoration2(AppConcerns.ExitLog, null)
            );

            IOrder o = aopcontainer.Resolve2<Order, IOrder>();
            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 = AOPContainer.ChainAspect2<IOrder, IOrder>(
                o,
                "InsertOrder",
                new Decoration2(AppConcerns.JoinSqlTransaction, transactionObj),
                null
            );

            o = AOPContainer.ChainAspect2<IOrder, IOrder>(
                o,
                "InsertOrder",
                new Decoration2(AppConcerns.EnterLog, null),
                new Decoration2(AppConcerns.ExitLog, null)
            );

            o = AOPContainer.ChainAspect2<IOrder, IOrder>(
                o,
                "InsertOrder",
                new Decoration2(AppConcerns.SecurityCheck, Thread.CurrentPrincipal),
                null
            );

            int iStatus;
            iStatus = o.InsertOrder();

            //throw new Exception();

            IOrderDetail od = aopcontainer.Resolve2<OrderDetail, IOrderDetail>();

            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 = AOPContainer.ChainAspect2<IOrderDetail, IOrderDetail>(
                od,
                "InsertOrderDetail",
                new Decoration2(AppConcerns.JoinSqlTransaction, transactionObj),
                null
            );

            od = AOPContainer.ChainAspect2<IOrderDetail, IOrderDetail>(
                od,
                "InsertOrderDetail",
                new Decoration2(AppConcerns.EnterLog, null),
                new Decoration2(AppConcerns.ExitLog, null)
            );

            od = AOPContainer.ChainAspect2<IOrderDetail, IOrderDetail>(
                od,
                "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();
        }

        aopcontainer.IocContainer.Dispose();

        Console.ReadLine();
    }
}

The code:

transaction = AOPContainer.ChainAspect2<IDbTransaction, IDbTransaction>(
    transactionObj,
    "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:

o = AOPContainer.ChainAspect2<IOrder, IOrder>(
    o,
    "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 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:

o = AOPContainer.ChainAspect2<IOrder, IOrder>(
    o,
    "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:

o = AOPContainer.ChainAspect2<IOrder, IOrder>(
    o,
    "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 see in the above code, you can use the proxy returned from AOPContainer.ChainAspect2 as a target to another AOPContainer.ChainAspect2 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 the 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 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(); rollbacks 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.

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].

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].

Final note: The application can be used for Unity Container as well. Just comment out the code for registering types for Windsor Container and creating AOPWindsorContainer, then uncomment the code for registering Unity Container and creating AOPUnityContainer.

Points of Interest

With AOP Container, you can do dynamic object programming for applications using IoC Containers. It provides a simpler application programming interface to add aspects to objects at runtime.

License

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

Share

About the Author

Gary H Guo

United States United States
Object-oriented (OO) is about "classes" not "objects". But I truly believe that "objects" deserve more our attentions. If you agree, read more on... Dynamic Object Programming (DOP), Component-Based Object Extender (CBO Extender), AOP Container and Dynamic Decorator Pattern.
 
Mobile development is not just another type of front end. The real challenge is actually in the back end: How to present meaningful information in time to mobile users with exponentially increased data flooding around? Here is my first mobile solution: SmartBars - Barcode Reader, Price Comparison and Coupons.
 
Gary lives in southeast Michigan. My first programming language is FORTRAN. For the last a few years, I have primarily focused on .NET technologies with Mobile Development as my newest interest.

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert28-Sep-12 7:08 
GeneralRe: My vote of 5 PinmemberGary H Guo28-Sep-12 7:20 
QuestionGreat Article PinmemberRob Grainger16-Dec-11 2:09 
AnswerRe: Great Article PinmemberGary H Guo16-Dec-11 3:29 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 15 Dec 2011
Article Copyright 2011 by Gary H Guo
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid