Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class

A well-architected application has separate layers so different concerns don’t interact more than needed. Imagine you’re designing a loosely coupled and maintainable application, but in the middle of the development, you see some requirements that might not fit well in the architecture, such as:

  • The application must have an authentication system, used before any query or update.
  • The data must be validated before it’s written to the database.
  • The application must have auditing and logging for sensible operations.
  • The application must maintain a debugging log to check if operations are OK.
  • Some operations must have their performance measured to see if they’re in the desired range.

Any of these requirements need a lot of work and, more than that, code duplication. You have to add the same code in many parts of the system, which goes against the “don’t repeat yourself” (DRY) principle and makes maintenance more difficult. Any requirement change causes a massive change in the program. When I have to add something like that in my applications, I think, “Why can’t the compiler add this repeated code in multiple places for me?” or, “I wish I had some option to ‘Add logging to this method.’”

The good news is that something like that does exist: aspect-oriented programming (AOP). It separates general code from aspects that cross the boundaries of an object or a layer. For example, the application log isn’t tied to any application layer. It applies to the whole program and should be present everywhere. That’s called a crosscutting concern.

AOP is, according to Wikipedia, “a programming paradigm范例 that aims to increase modularity模块性 by allowing the separation of crosscutting concerns.” It deals with functionality that occurs in multiple parts of the system and separates it from the core of the application, thus improving separation of concerns while avoiding duplication of code and coupling.

In this article, I’ll explain the basics of AOP and then detail how to make it easier by using a dynamic proxy via the Microsoft .NET Framework class RealProxy.

Implementing AOP

The biggest advantage of AOP is that you only have to worry about the aspect in one place, programming it once and applying it in all the places where needed. There are many uses for AOP, such as:

  • Implementing logging in your application.
  • Using authentication before an operation (such as allowing some operations only for authenticated users).
  • Implementing validation or notification for property setters (calling the PropertyChanged event when a property has been changed for classes that implement the INotifyPropertyChanged interface).
  • Changing the behavior of some methods.

As you can see, AOP has many uses, but you must wield使用 it with care. It will keep some code out of your sight, but it’s still there, running in every call where the aspect is present. It can have bugs and severely严重地 impact the performance of the application. A subtle微妙的 bug in the aspect might cost you many debugging hours. If your aspect isn’t used in many places, sometimes it’s better to add it directly to the code.

AOP implementations use some common techniques:

  • Adding source code using a pre-processor, such as the one in C++.
  • Using a post-processor to add instructions on the compiled binary code.
  • Using a special compiler that adds the code while compiling.
  • Using a code interceptor拦截器 at run time that intercepts execution and adds the desired code.

In the .NET Framework, the most commonly used of these techniques are post-processing and code interception.

The former is the technique used by PostSharp (postsharp.net) and the latter is used by dependency injection (DI) containers such as Castle DynamicProxy (bit.ly/JzE631) and Unity (unity.codeplex.com).

These tools usually use a design pattern named Decorator or Proxy to perform the code interception.

The Decorator Design Pattern

The Decorator design pattern solves a common problem: You have a class and want to add some functionality to it. You have several options for that:

  • You could add the new functionality to the class directly. However, that gives the class another responsibility and hurts the “single responsibility” principle.
  • You could create a new class that executes this functionality and call it from the old class. This brings a new problem: What if you also want to use the class without the new functionality?
  • You could inherit a new class and add the new functionality, but that may result in many new classes. For example, let’s say you have a repository class for create, read, update and delete (CRUD) database operations and you want to add auditing. Later, you want to add data validation to be sure the data is being updated correctly. After that, you might also want to authenticate the access to ensure that only authorized users can access the classes. These are big issues: You could have some classes that implement all three aspects, and some that implement only two of them or even only one. How many classes would you end up having?
  • You can “decorate” the class with the aspect, creating a new class that uses the aspect and then calls the old one. That way, if you need one aspect, you decorate it once. For two aspects, you decorate it twice and so on. Let’s say you order a toy (as we’re all geeks, an Xbox or a smartphone is OK). It needs a package for display in the store and for protection. Then, you order it with gift wrap, the second decoration, to embellish装饰 the box with tapes, stripes, cards and gift paper. The store sends the toy with a third package, a box with Styrofoam balls for protection. You have three decorations, each one with a different functionality, and each one independent from one another. You can buy your toy with no gift packaging, pick it up at the store without the external box or even buy it with no box (with a special discount!). You can have your toy with any combination of the decorations, but they don’t change its basic functionality.

Now that you know about the Decorator pattern, I’ll show how to implement it in C#.

First, create an interface IRepository<T>:

  public interface IRepository<T>
{
void Add(T entity);
void Delete(T entity);
void Update(T entity);
IEnumerable<T> GetAll();
T GetById(int id);
}

Implement it with the Repository<T> class

public class Repository<T> : IRepository<T>
{
public void Add(T entity)
{
Console.WriteLine("Adding {0}", entity);
} public void Delete(T entity)
{
Console.WriteLine("Deleting {0}", entity);
} public void Update(T entity)
{
Console.WriteLine("Updating {0}", entity);
} public IEnumerable<T> GetAll()
{
Console.WriteLine("Getting entities");
return null;
} public T GetById(int id)
{
Console.WriteLine("Getting entity {0}", id);
return default(T);
}
}

Use the Repository<T> class to add, update, delete and retrieve the elements of the Customer class

 public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}

The program could look something like

class Program
{
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
new Repository<Customer>();
var customer = new Customer
{
Id = ,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
}

When you run this code, you’ll see something like

***
Begin program - no logging

Adding AOPTest.Customer
Updating AOPTest.Customer
Deleting AOPTest.Customer

End program - no logging
***

C:\Users\clu\source\repos\GitHub\ChuckLu\Test\AOPTest\AOPTest\bin\Debug\netcoreapp3.0\AOPTest.exe (process 28984) exited with code -1.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .

Imagine your boss asks you to add logging to this class.

You can create a new class that will decorate IRepository<T>.

It receives the class to build and implements the same interface, as shown

 public class LoggerRepository<T> : IRepository<T>
{
private readonly IRepository<T> _decorated;
public LoggerRepository(IRepository<T> decorated)
{
_decorated = decorated;
} private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
} public void Add(T entity)
{
Log("In decorator - Before Adding {0}", entity);
_decorated.Add(entity);
Log("In decorator - After Adding {0}", entity);
} public void Delete(T entity)
{
Log("In decorator - Before Deleting {0}", entity);
_decorated.Delete(entity);
Log("In decorator - After Deleting {0}", entity);
} public void Update(T entity)
{
Log("In decorator - Before Updating {0}", entity);
_decorated.Update(entity);
Log("In decorator - After Updating {0}", entity);
} public IEnumerable<T> GetAll()
{
Log("In decorator - Before Getting Entities");
var result = _decorated.GetAll();
Log("In decorator - After Getting Entities");
return result;
} public T GetById(int id)
{
Log("In decorator - Before Getting Entity {0}", id);
var result = _decorated.GetById(id);
Log("In decorator - After Getting Entity {0}", id);
return result;
}
}

This new class wraps the methods for the decorated class and adds the logging feature. You must change the code a little to call the logging class, as shown

class Program
{
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
new LoggerRepository<Customer>(new Repository<Customer>());
var customer = new Customer
{
Id = ,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
}

You simply create the new class, passing an instance of the old class as a parameter for its constructor. When you execute the program, you can see it has the logging, as shown in

You might be thinking: “OK, the idea is good, but it’s a lot of work: I have to implement all the classes and add the aspect to all the methods. That will be difficult to maintain. Is there another way to do it?” With the .NET Framework, you can use reflection to get all methods and execute them. The base class library (BCL) even has the RealProxy class (bit.ly/18MfxWo) that does the implementation for you.

https://docs.microsoft.com/en-us/dotnet/api/system.runtime.remoting.proxies.realproxy?redirectedfrom=MSDN&view=netframework-4.8

Creating a Dynamic Proxy with RealProxy

The RealProxy class gives you basic functionality for proxies.

It’s an abstract class that must be inherited by overriding its Invoke method and adding new functionality.

This class is in the namespace System.Runtime.Remoting.Proxies.

To create a dynamic proxy, you use code similar to Figure 7.

RealProxy in dotnet core?

public class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, ,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
}

In the constructor of the class, you must call the constructor of the base class, passing the type of the class to be decorated. Then you must override the Invoke method that receives an IMessage parameter. It contains a dictionary with all the parameters passed for the method. The IMessage parameter is typecast to an IMethodCallMessage, so you can extract the parameter MethodBase (which has the MethodInfo type).

The next steps are to add the aspect you want before calling the method, call the original method with methodInfo.Invoke and then add the aspect after the call.

You can’t call your proxy directly, because DynamicProxy<T> isn’t an IRepository<Customer>. That means you can’t call it like this:

IRepository<Customer> customerRepository =
  new DynamicProxy<IRepository<Customer>>(
  new Repository<Customer>());

To use the decorated repository, you must use the GetTransparentProxy method, which will return an instance of IRepository<Customer>.

Every method of this instance that’s called will go through the proxy’s Invoke method.

To ease this process, you can create a Factory class to create the proxy and return the instance for the repository:

 public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}

That way, the main program will be similar to

 class Program
{
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = ,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
}

As you can see, you’ve created a dynamic proxy that allows adding aspects to the code, with no need to repeat it.

If you wanted to add a new aspect, you’d only need to create a new class, inherit from RealProxy and use it to decorate the first proxy.

If your boss comes back to you and asks you to add authorization to the code, so only administrators can access the repository, you could create a new proxy as shown in

public class AuthenticationProxy<T> : RealProxy
{
private readonly T _decorated;
public AuthenticationProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (Thread.CurrentPrincipal.IsInRole("ADMIN"))
{
try
{
Log("User authenticated - You can execute '{0}' ",
methodCall.MethodName);
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, ,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"User authenticated - Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
Log("User not authenticated - You can't execute '{0}' ",
methodCall.MethodName);
return new ReturnMessage(null, null, ,
methodCall.LogicalCallContext, methodCall);
}
}

The repository factory must be changed to call both proxies, as shown

The Repository Factory Decorated by Two Proxies

  public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var decoratedRepository =
(IRepository<T>)new DynamicProxy<IRepository<T>>(
repository).GetTransparentProxy();
// Create a dynamic proxy for the class already decorated
decoratedRepository =
(IRepository<T>)new AuthenticationProxy<IRepository<T>>(
decoratedRepository).GetTransparentProxy();
return decoratedRepository;
}
}

When you change the main program to Figure 12 and run it, you’ll get the output shown

 class Program
{
static void Main(string[] args)
{
Console.WriteLine(
"***\r\n Begin program - logging and authentication\r\n"); Console.WriteLine("\r\nRunning as admin");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("Administrator"),
new[] { "ADMIN" });
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = ,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer); Console.WriteLine("\r\nRunning as user");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("NormalUser"),
new string[] { });
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine(
"\r\nEnd program - logging and authentication\r\n***");
Console.ReadLine();
}
}

The program executes the repository methods twice. The first time, it runs as an admin user and the methods are called. The second time, it runs as a normal user and the methods are skipped.

That’s much easier, isn’t it? Note that the factory returns an instance of IRepository<T>, so the program doesn’t know if it’s using the decorated version.

This respects the Liskov Substitution Principle, which says that if S is a subtype of T, then objects of type T may be replaced with objects of type S. In this case, by using an IRepository<Customer> interface, you could use any class that implements this interface with no change in the program.

Not a Replacement

With AOP you can add code to all layers of your application in a centralized way, with no need to repeat code. I showed how to create a generic dynamic proxy based on the Decorator design pattern that applies aspects to your classes using events and a predicate to filter the functions you want.

As you can see, the RealProxy class is a flexible class and gives you full control of the code, with no external dependencies. However, note that RealProxy isn’t a replacement for other AOP tools, such as PostSharp. PostSharp uses a completely different method. It will add intermediate language (IL) code in a post-compilation step and won’t use reflection, so it should have better performance than RealProxy. You’ll also have to do more work to implement an aspect with RealProxy than with PostSharp. With PostSharp, you need only create the aspect class and add an attribute to the class (or the method) where you want the aspect added, and that’s all.

On the other hand, with RealProxy, you’ll have full control of your source code, with no external dependencies, and you can extend and customize it as much as you want. For example, if you want to apply an aspect only on methods that have the Log attribute, you could do something like this:

Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class的更多相关文章

  1. Parallel Programming AND Asynchronous Programming

    https://blogs.oracle.com/dave/ Java Memory Model...and the pragmatics of itAleksey Shipilevaleksey.s ...

  2. C Programming vs. Java Programming

    Thing C Java type of language function oriented object oriented basic programming unit function clas ...

  3. "Programming"和"Programming"是同一个"Programming"吗?

    什么意思? C语言没有专门的字符串类型,但是,它同样可以处理字符串.本文不是讨论字符串的使用,而是讨论C字符串之间的关系.如题,在C语言代码中,如果定义#define STR = "Prog ...

  4. Programming In hardware Programming in software

    COMPUTER ORGANIZATION AND ARCHITECTURE DESIGNING FOR PERFORMANCE NINTH EDITION

  5. Spring学习(24)--- AOP之 Aspect instantiation models(aspect实例模式)特别说明

    重要: schema-defined aspects只支持singleton model,即 基于配置文件的aspects只支持单例模式

  6. [core python programming]chapter 7 programming MS office

    excel.pyw会有问题,解决如下: 因为python3x中没有tkMessageBox模块,Tkinter改成了tkinter你可以查看你的py当前支持的模块.在交互式命令行下输入>> ...

  7. AOP programming paradiag

    AOP https://en.wikipedia.org/wiki/Aspect-oriented_programming Typically, an aspect is scattered or t ...

  8. DCI:The DCI Architecture: A New Vision of Object-Oriented Programming

    SummaryObject-oriented programming was supposed to unify the perspectives of the programmer and the ...

  9. [转]Table-Driven and Data Driven Programming

    What is Table-Driven and Data-Driven Programming? Data/Table-Driven programming is the technique of ...

  10. Service Oriented Architecture

    What is Service Oriented Architecture (SOA)? There have been so many interpretations of this through ...

随机推荐

  1. HUD-2112 HDU Today(最短路map标记)

    题目链接:HUD-2112 HDU Today 思路: 1.最短路spfa模板. 2.map标记建图. 3.考虑距离为0或者-1的情况. 总结:下次map记得清空orz. AC代码: #include ...

  2. 线程局部存储tls的使用

    线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护.. 因此也没有多线程间资源竞争问题 ...

  3. MySQL格式化时间戳 统计当日,第二天,第三天,3个工作日以后的数据

    mysql 查询出来的处理时间和开始时间都是13位的时间戳 SELECT `END_TIME`,`CREATE_TIME` FROM t_table 需求是统计当日,第二天,第三天,3个工作日以后的时 ...

  4. java_第一年_JavaWeb(7)

    JSP执行过程 客户端发出请求访问JSP文件 JSP Container将要访问的JSP文件转译为Servlet的源代码(转译时期),并将其编译成.class文件(编译时期): 执行编译后的.clas ...

  5. [Bzoj1008][HNOI2008]越狱(组合计数)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1008 组合计数的简单题,可能越狱的方案数等于总方案数-不可能越狱的方案数,则: 总方案数 ...

  6. 解决java.net.BindException: Address already in use(Bind failed)端口占用问题

    问题描述: 解决办法: sudo lsof -i:20101ps -ef|grep 9905kill -9 9905ps -ef|grep 9905 ------------------------- ...

  7. jquery的扩展,及编辑插件的书写格式

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  8. NGUI多行输入框和滚动条结合使用(text list script 和scroll bar script)

    一,我们添加一个label,如下图:将label属性设置 二,给label添加一个box collider.然后在add component 添加test list,如下图: 三,添加一个脚本Test ...

  9. NGUI的button的创建的问题(Button Script)

    一,我们可以给了label,sprite等添加button事件 我们先添加一个label在UI_Root上,然后选中该label,右键-Attach-Box Collider,添加,当你添加完了Box ...

  10. Vue PC端图片预览插件

    *手上的项目刚刚搞完了,记录一下项目中遇到的问题,留做笔记: 需求: 在项目中,需要展示用户上传的一些图片,我从后台接口拿到图片url后放在页面上展示,因为被图片我设置了宽度限制(150px),所以图 ...