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();
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 typeV
- interface to be returnedtarget
- original objectmethods
- method names (separated by comma) to be added to the preprocessing aspect and/or postprocessing aspectpreDeco
- decoration for preprocessing aspectpostDeco
- 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;
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);
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();
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.