
I really don't like boilerplate. Such code is boring to write, sadly accompany and modify. I don’t like it at all when that bolierplate is mixed with the business logic of the application. The problem was described very well by
krestjaninoff 5 years ago .
If you are not familiar with the AOP paradigm, read the material on the link, it reveals the topic .
As at the time of reading this article, even now neither
PostSharp nor Spring suits me. But in the meantime, other tools appeared in .NET that allow you to pull out the “left” code from business logic, decorate it with separate reusable modules and describe it declaratively, without slipping into rewriting the resulting IL and other sodomy.
It's about the project
Castle.DynamicProxy and its application in the development of enterprise applications.
I will borrow an example from
krestjaninoff , because I see the similar code with enviable regularity, and it gives me a lot of trouble.
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException { if (!SecurityContext.getUser().hasRight("GetBook")) throw new AuthException("Permission Denied"); LOG.debug("Call method getBook with id " + bookId); BookDTO book = null; String cacheKey = "getBook:" + bookId; try { if (cache.contains(cacheKey)) { book = (BookDTO) cache.get(cacheKey); } else { book = bookDAO.readBook(bookId); cache.put(cacheKey, book); } } catch(SQLException e) { throw new ServiceException(e); } LOG.debug("Book info is: " + book.toString()); return book; }
')
So, in the example above, one “useful” operation is reading a book from the database by Id. In the load method received:
- authorization check
- caching
- exception handling
- logging
For the sake of fairness, it is worth noting that the authorization and access rights check, caching could already be provided by ASP.NET using the
[Authorize] and
[OutputCache] attributes , however, by condition, this is a “spherical web service in vacuum” (also written in Java ), therefore, the requirements for it are unknown, as, however, it is not known whether ASP.NET, WCF or a corporate framework is used.
Task

- move the helper code to a suitable place
- make it (code) reusable for other services
In the AOP world there is a special term for the problem we are solving:
cross-cutting concerns .
Base concerns are
highlighted - the main functionality of the system, for example, business logic and
cross-cutting concerns - minor functionality (logging, access control, error handling, etc.), which is nevertheless necessary everywhere in the application code.
Most often I meet and perfectly illustrates the situation of
cross-cutting concern of this type:
dbContext.InTransaction(x => {
Absolutely everything is ugly in it, ranging from increasing code nesting, ending with shifting the functions of the system designer to the application programmer: there is no guarantee that transactions will be invoked wherever needed, it is unclear how to manage the isolation level of transactions and nested transactions and this code will be copied one hundred thousand times where necessary and not necessary.
Decision

Castle.DynamicProxy provides a simple API for creating proxy objects on the fly with the ability to define what we are missing. This approach is used in popular insulation frameworks:
Moq and
Rhino Mocks . We have
two options available :
- creating a proxy for the interface link (in this case the composition will be used)
- creating a proxy for the class (a successor will be created)
The main difference for us will be that in order to modify the methods of the class, they must be declared accessible (
public or
protected ) and virtual. The mechanism is similar to
Lazy Loading in
Nhibernate or
EF . To enrich the functionality in Castle.DynamicProxy,
Interceptors are used . For example, to ensure that all application services are transacted, you can write an
Interceptor like this:
public class TransactionScoper : IInterceptor { public void Intercept(IInvocation invocation) { using (var tr = new TransactionScope()) { invocation.Proceed(); tr.Complete(); } } }
And create a proxy:
var generator = new ProxyGenerator(); var foo = new Foo(); var fooInterfaceProxyWithCallLogerInterceptor = generator.CreateInterfaceProxyWithTarget(foo, TransactionScoper);
Or
using a container :
var builder = new ContainerBuilder(); builder.Register(c => new TransactionScoper()); builder.RegisterType<Foo>() .As<IFoo>() .InterceptedBy(typeof(TransactionScoper)); var container = builder.Build(); var willBeIntercepted = container.Resolve<IFoo>();
Similarly, error handling can be added.
public class ErrorHandler : IInterceptor { public readonly TextWriter Output; public ErrorHandler(TextWriter output) { Output = output; } public void Intercept(IInvocation invocation) { try { Output.WriteLine($"Method {0} enters in try/catch block", invoca-tion.Method.Name); invocation.Proceed(); Output.WriteLine("End of try/catch block"); } catch (Exception ex) { Output.WriteLine("Exception: " + ex.Message); throw new ValidationException("Sorry, Unhandaled exception occured", ex); } } } public class ValidationException : Exception { public ValidationException(string message, Exception innerException) :base(message, innerException) { } }
Or logging:
public class CallLogger : IInterceptor { public readonly TextWriter Output; public CallLogger(TextWriter output) { Output = output; } public void Intercept(IInvocation invocation) { Output.WriteLine("Calling method {0} with parameters {1}.", invocation.Method.Name, string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())); invocation.Proceed(); Output.WriteLine("Done: result was {0}.", invocation.ReturnValue); } }
Caching and many other operations. A distinctive feature of this approach from the implementation of the “decorator” pattern by means of OOP is the ability to add supporting functionality to any types without the need to create heirs. The approach also solves the problem of multiple inheritance. We can easily add more than one interceptor for each type:
var fooInterfaceProxyWith2Interceptors = generator.CreateInterfaceProxyWithTarget(Foo, CallLogger, ErrorHandler);
Another strength of this approach is the separation of end-to-end functionality from the business logic layer and the best separation of the infrastructure code from the application domain.
If during the registration process it is impossible to say exactly which services need to be proxied and which not, then attributes can be used to obtain information at runtime (although this approach may lead to some problems):
public abstract class AttributeBased<T> : IInterceptor where T:Attribute { public void Intercept(IInvocation invocation) { var attrs = invocation.Method .GetCustomAttributes(typeof(T), true) .Cast<T>() .ToArray(); if (!attrs.Any()) { invocation.Proceed(); } else { Intercept(invocation, attrs); } } protected abstract void Intercept(IInvocation invocation, params T[] attr); }
You can even use a
ready-made solution .
Minuses
I see four objective disadvantages of this approach:
- Not intuitiveness
- Intersection with the infrastructure code of other frameworks
- Dependence on the IOC container
- Performance
Not intuitiveness
The easiest way to deal with this code structuring is for people familiar with functional programming concepts. With a dirty number of reservations, the approach can be called reminiscent of a “
composition ”. Crookedly designed interceptors can cause a fair amount of not obvious bugs and performance problems.
Intersection with the infrastructure code of other frameworks
As I said at the beginning, the
Authorize and
OutputCache attributes are already in ASP.NET. In a sense, we are engaged in cycling. The approach is more suitable for commands for which it is important to abstract from the final execution infrastructure. In addition, the approach works in the context of partial application, not “all or nothing”. Nobody forces us to re-implement AOP-style authorization checks, if this is not required.
Dependence on the IOC container
For the service layer, minus is practically absent if you practice IOC / DI. In 99% of cases, services will be obtained using an IOC container. Entity and Dto are usually created explicitly using the new operator or the mapper. I think that this is the right state of affairs and I do not see the use of interceptors at the level of creating Entity or Dto. I have seen several examples of using interceptors to fill service fields in
Entity , but over time this approach has always been discarded. It is much better that the object itself cares about the safety of its
invariant .
Performance
I cited the previous three points for accuracy rather than pragmatic considerations. I rather attribute them to the limits of applicability of the approach, and not to the real problems. I was not so sure about the performance, so I decided to make a series of benchmarks using
BenchmarkDotNet . With the fantasy, I didn’t have much, so the time of getting a random number was measured:
public class Foo : IFoo { private static readonly Random Rnd = new Random(); public double GetRandomNumber() => Rnd.Next(); } public class Foo : IFoo { private static readonly Random Rnd = new Random(); public double GetRandomNumber() => Rnd.Next(); }
Benchmark sources and code samples
are available on github . Obviously, you have to pay for magic with reflection and dynamic compilation:
- Object creation time: ~ 2,000 ns. It doesn't matter if the services are created once, and for the “live” dependencies, such as the context of the database, another object is responsible.
- Runtime operations: also approximately ~ 1,000 extra nanoseconds inside Castle.DynamicProxy use Reflection with all the ensuing consequences.
In absolute values, this is quite a lot, but if the code runs for longer than 50 ns, for example, it writes to the database or a request over the network, the situation looks different:
public class Bus : Bar { public override double GetRandomNumber() { Thread.Sleep(100); return base.GetRandomNumber(); } }
Host Process Environment Information:
BenchmarkDotNet = v0.9.8.0
OS = Microsoft Windows NT 6.2.9200.0
Processor = Intel (R) Core (TM) i7-4710HQ CPU 2.50GHz, ProcessorCount = 8
Frequency = 2435775 ticks, Resolution = 410.5470 ns, Timer = TSC
CLR = MS.NET 4.0.30319.42000, Arch = 64-bit RELEASE [RyuJIT]
GC = Concurrent Workstation
JitModules = clrjit-v4.6.1080.0
Type = InterceptorBenchmarks Mode = Throughput GarbageCollection = Concurrent Workstation
LaunchCount = 1 WarmupCount = 3 TargetCount = 3
Method | Median | Stddev |
---|
CreateInstance | 0.0000 ns | 0.0000 ns |
CreateClassProxy | 1,972.0032 ns | 8.5611 ns |
CreateClassProxyWithTarget | 2,246.4208 ns | 5.3436 ns |
CreateInterfaceProxyWithTarget | 2,063.6905 ns | 41.9450 ns |
CreateInterfaceProxyWithoutTarget | 2,105.9238 ns | 4.9295 ns |
Foo_GetRandomNumber | 11.0409 ns | 0.1306 ns |
Foo_InterfaceProxyGetRandomNumber | 51.6061 ns | 0.2764 ns |
FooClassProxy_GetRandomNumber | 9.0125 ns | 0.1766 ns |
BarClassProxy_GetRandomNumber | 44.8110 ns | 0.4770 ns |
FooInterfaceProxyWithCallLoggerInterceptor_GetRandomNumber | 1,756.8129 ns | 75.4694 ns |
BarClassProxyWithCallLoggerInterceptor_GetRandomNumber | 1,714.5871 ns | 25.2403 ns |
FooInterfaceProxyWith2Interceptors_GetRandomNumber | 2,636.1626 ns | 20.0195 ns |
BarClassProxyWith2Interceptors_GetRandomNumber | 2,603.6707 ns | 4.6360 ns |
Bus_GetRandomNumber | 100,471,410.5375 ns | 113,713.1684 ns |
BusInterfaceProxyWith2Interceptors_GetRandomNumber | 100,539,356.0575 ns | 89,725.5474 ns |
CallLogger_Intercept | 3,841.4488 ns | 26.3829 ns |
WriteLine | 859.0076 ns | 34.1630 ns |
I think if you replace
Reflection with LambdaExpression,
you can ensure that there will be no difference in performance, but you need to rewrite DynamicProxy, add support to popular containers (now interceptors are supported exactly from
Autofac and
Castle.Windsor boxes , I don’t know about the others) . I doubt it will happen soon.
Therefore, if on average your operations are performed for at least 100 ms and the three previous minuses do not frighten you, the “container AOP” in C # is already production-ready.