Castle DynamicProxy basic usage (AOP)

This article describes the basic concepts of AOP programming, Castle DynamicProxy (DP) of the basic usage, the use of third-party extensions to implement support for asynchronous (async), combined with Autofac demonstrates how to implement AOP programming.

AOP

Encyclopedia on AOP explanation:

AOP is an abbreviation for Aspect Oriented Programming, meaning: Aspect Oriented Programming, dynamic agent program features a technology to achieve unity maintained by way of pre-compiled and run. AOP is a continuation of OOP is a hot ...... software development is a function of a programming Yan Shengfan type. AOP can use to isolate each part of the business logic such that the business logic to reduce the degree of coupling between the parts, improve the reusability of the program, while improving efficiency of development.

In AOP, crosscutting concerns us, the general process flow is extracted, we will provide common system functions, and use in the business layer, such as log module, exception handling module. To achieve a more flexible and efficient development experience through AOP programming.

The basic usage DynamicProxy

Dynamic proxies are a way to achieve AOP, namely in the development process, we need to deal with in section (logs, etc.) work, but at runtime, to automate the dynamic proxy. Castle DynamicProxy framework is a dynamic proxy, are a lot of good projects to achieve AOP programming, EF Core, Autofac and so on.

Let's look at two pieces of code that demonstrates the benefits of AOP. Before using AOP:

public class ProductRepository : IProductRepository
{
    private readonly ILogger logger;
    
    public ProductRepository(ILogger logger)
    {
        this.logger = logger;
    }
        
    public void Update(Product product)
    {
        //执行更新操作
        //......

        //记录日志
        logger.WriteLog($"产品{product}已更新");
    }
}

After using AOP:

public class ProductRepository : IProductRepository
{
    public void Update(Product product)
    {
        //执行更新操作
        //......
    }
}

Can be clearly seen, the use of our prior ILogger ProductRepository dependent on, and after performing Update operations, logging code to write; and after use, to the logging proxy to handle the dynamic, reduced a lot the amount of development, even met a little sloppy programmer, do not delay our record log.

Then how to implement such an operation it?

  • First, referenceCastle.Core
  • Then, define interceptors to achieve IInterceptorInterface
public class LoggerInterceptor : IInterceptor
{
    private readonly ILogger logger;

    public LoggerInterceptor(ILogger logger)
    {
        this.logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        //获取执行信息
        var methodName = invocation.Method.Name;

        //调用业务方法
        invocation.Proceed();

        //记录日志
        this.logger.WriteLog($"{methodName} 已执行");
    }
}
  • Finally, add the calling code
static void Main(string[] args)
{
    ILogger logger = new ConsoleLogger();
    Product product = new Product() { Name = "Book" };
    IProductRepository target = new ProductRepository();

    ProxyGenerator generator = new ProxyGenerator();

    IInterceptor loggerIntercept = new LoggerInterceptor(logger);
    IProductRepository proxy = generator.CreateInterfaceProxyWithTarget(target, loggerIntercept);
    
    proxy.Update(product);
}

So far, we have completed a logging interceptor, when other services need to use logging, AOP can also be programmed by creating a dynamic proxy.

However, calling it is quite complex and requires how to improve it? Course dependency injection (DI) a.

Autofac integration

Autofac integrated support for DynamicProxy, we need to reference Autofac.Extras.DynamicProxy, and then create the container, registration services, create an instance, call the method, we look at the following code:

ContainerBuilder builder = new ContainerBuilder();
//注册拦截器
builder.RegisterType<LoggerInterceptor>().AsSelf();

//注册基础服务
builder.RegisterType<ConsoleLogger>().AsImplementedInterfaces();

//注册要拦截的服务
builder.RegisterType<ProductRepository>().AsImplementedInterfaces()
    .EnableInterfaceInterceptors()                  //启用接口拦截
    .InterceptedBy(typeof(LoggerInterceptor));      //指定拦截器

var container = builder.Build();

//解析服务
var productRepository = container.Resolve<IProductRepository>();

Product product = new Product() { Name = "Book" };
productRepository.Update(product);

The code to do something to explain:

  • Registration interceptor, need to register AsSelfas a service to intercept using the example of the interceptor, which can be registered way to ensure that the vessel is able to resolve the interceptor.
  • Open blocking feature: when registering to block the service, call the EnableInterfaceInterceptorsmethod, indicate on the interface interception;
  • Services associated with the interceptor: InterceptedByMethod incoming interceptor, the interceptor specified in two ways, one is our code writing, the service is non-invasive, it is recommended that such usage. The other is through Interceptto be associated characteristics, for example, we can write the above code is ProductRepositoryadded on a class characteristic[Intercept(typeof(LoggerInterceptor))]
  • Registration interceptor, the interceptor can be registered as a type, you can also register for the name of the interceptor, there will be some differences in use, mainly on the Association Interceptor, Autofac this section refer to the official documentation. We use the example of the type of registration.
  • Interceptor common interface is only valid for the method, class virtual method, use requires special attention.

The basic principle of DynamicProxy

Above, we talk about dynamic proxies only public interface method, virtual method in the class take effect, do you ever wonder why?

In fact, the dynamic proxy at run time we dynamically generated for a proxy class, by Generatorthe time generated back to us is an instance of the proxy class, and virtual method only interface methods, classes can be in a subclass rewriting.

If you do not use dynamic agency, our agency services should be what kind of it? Consider the following code, we manually create a proxy class:

The following is my understanding of the proxy class, please dialectical view, if there is not the right place, but also pointed out that hope.

Acting as the interface:

public class IProductRepositoryProxy : IProductRepository
{
    private readonly ILogger logger;
    private readonly IProductRepository target;

    public ProductRepositoryProxy(ILogger logger, IProductRepository target)
    {
        this.logger = logger;
        this.target = target;
    }

    public void Update(Product product)
    {
        //调用IProductRepository的Update操作
        target.Update(product);

        //记录日志
        this.logger.WriteLog($"{nameof(Update)} 已执行");
    }
}

//使用代理类
IProductRepository target = new ProductRepository();
ILogger logger = new ConsoleLogger();
IProductRepository productRepository = new ProductRepositoryProxy(logger, target);

The class using the interface:

public class ProductRepository : IProductRepository
{
    //改写为虚方法
    public virtual void Update(Product product)
    {
        //执行更新操作
        //......
    }
}

public class ProductRepositoryProxy : ProductRepository
{
    private readonly ILogger logger;

    public ProductRepositoryProxy(ILogger logger)
    {
        this.logger = logger;
    }

    public override void Update(Product product)
    {
        //调用父类的Update操作
        base.Update(product);
        //记录日志
        this.logger.WriteLog($"{nameof(Update)} 已执行");
    }
}

//使用代理类
ILogger logger = new ConsoleLogger();
ProductRepository productRepository = new ProductRepositoryProxy(logger);

Asynchronous (async / await) support

If you stand on the application point of view, Microsoft's asynchronous just a syntactic sugar, using an asynchronous method returns the result as a Task or Task The object, which is for DP and an int no difference, but if we want to get to the interception return in real results, we need to add some additional processing.

Castle.Core.AsyncInterceptorIs a framework to help us deal with asynchronous blocking, asynchronous processing can reduce the difficulty of using this framework.

In this section we remain bound Autofac processing, first of all the code transformation, the ProductRepository.Updatemethod was changed to asynchronous.

public class ProductRepository : IProductRepository
{
    public virtual Task<int> Update(Product product)
    {
        Console.WriteLine($"{nameof(Update)} Entry");

        //执行更新操作
        var task = Task.Run(() =>
        {
            //......
            Thread.Sleep(1000);

            Console.WriteLine($"{nameof(Update)} 更新操作已完成");
            //返回执行结果
            return 1;
        });

        //返回
        return task;
    }
}

Next, we define our asynchronous interceptor:

public class LoggerAsyncInterceptor : IAsyncInterceptor
{
    private readonly ILogger logger;

    public LoggerAsyncInterceptor(ILogger logger)
    {
        this.logger = logger;
    }

    /// <summary>
    /// 同步方法拦截时使用
    /// </summary>
    /// <param name="invocation"></param>
    public void InterceptSynchronous(IInvocation invocation)
    {
        throw new NotImplementedException(); 
    }

    /// <summary>
    /// 异步方法返回Task时使用
    /// </summary>
    /// <param name="invocation"></param>
    public void InterceptAsynchronous(IInvocation invocation)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// 异步方法返回Task<T>时使用
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="invocation"></param>
    public void InterceptAsynchronous<TResult>(IInvocation invocation)
    {
        //调用业务方法
        invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
    }

    private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
    {
        //获取执行信息
        var methodName = invocation.Method.Name;

        invocation.Proceed();
        var task = (Task<TResult>)invocation.ReturnValue;
        TResult result = await task;

        //记录日志
        this.logger.WriteLog($"{methodName} 已执行,返回结果:{result}");

        return result;
    }
}

IAsyncInterceptorThe interface is asynchronous interceptor interface, which provides three methods:

  • InterceptSynchronous: Synchronous execution of method interception
  • InterceptAsynchronous: Interception return results Task way
  • InterceptAsynchronous<TResult>: Interception return results for the Task Methods

In our above code, only to realize the InterceptAsynchronous<TResult>method.

Because IAsyncInterceptorthe interface and DP framework IInterceptorinterface is not related, so we also need a synchronous interceptor directly modify the old synchronous interceptor here:

public class LoggerInterceptor : IInterceptor
{
    private readonly LoggerAsyncInterceptor interceptor;
    public LoggerInterceptor(LoggerAsyncInterceptor interceptor)
    {
        this.interceptor = interceptor;
    }

    public void Intercept(IInvocation invocation)
    {
        this.interceptor.ToInterceptor().Intercept(invocation);
    }
}

Can be seen from the code, the asynchronous interceptor LoggerAsyncInterceptorhaving a named ToInterceptor()extension methods, which can be IAsyncInterceptorconverted to object interface IInterceptorobject interface.

Next, we modify the service registration section DI:

ContainerBuilder builder = new ContainerBuilder();
//注册拦截器
builder.RegisterType<LoggerInterceptor>().AsSelf();
builder.RegisterType<LoggerAsyncInterceptor>().AsSelf();

//注册基础服务
builder.RegisterType<ConsoleLogger>().AsImplementedInterfaces();

//注册要拦截的服务
builder.RegisterType<ProductRepository>().AsImplementedInterfaces()
    .EnableInterfaceInterceptors()                  //启用接口拦截
    .InterceptedBy(typeof(LoggerInterceptor));      //指定拦截器

var container = builder.Build();

The above is by IAsyncInterceptorway of implementing asynchronous interceptor. In addition to this way, we can also determine the result of manual handling in the return dynamic interceptor will not be repeated here.

Discussion: section Programming ASP.NET MVC

Through the above description, we have to understand the basic usage of AOP, but how in the use ASP.NET Coreof it?

  1. Registration MVC controller is done in Services, and Services itself does not support the DP. This problem can be done by integrating Autofac controller to re-register, but this operation really okay?
  2. The MVC controller is inherited from ControllerBase, Action method that implements an interface is not our custom, that there are certain difficulties for the realization of AOP. This problem can be defined by Action for virtual method to solve, but this is really consistent with our coding habits?

We know, AOP original intention was to keep the black box of the user, it is programmed via the extraction section, and this is precisely the two issues we need to make changes to users, contrary to the SOLID principles.

So, if we want to use AOP in MVC, what method do? In fact, MVC has provided two ways to achieve AOP for us:

  1. Middleware (Middleware), which is in MVC big kill, provides a set of built-in middleware logs, Cookie, authorization, can be seen, MVC does not want us to achieve AOP by DP, but to be in the pipeline fuss.
  2. Filter (Filter), Filter is a product of ASP.NET MVC, once to help us solve the exception authorization logic, we are still in the Core era in this way can be used.

Both methods more consistent with our coding habits, but also reflects the characteristics of the MVC framework.

In summary, we do not recommend the use of DP Controller in MVC. If NLayer architecture, may be the Application layer, Domain layer using DP, to achieve similar audit data, the SQL tracking process.

Although not recommended, but still shows the code, give yourself more than one way:

  • MVC controller is registered as a service
services.AddMvc()
    .AddControllersAsServices();
  • Re-registration controller configured to intercept
builder.RegisterType<ProductController>()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(ControllerInterceptor));
  • The controller is defined as a virtual method Action
[HttpPost]
public virtual Task<int> Update(Product product)
{
    return this.productRepository.Update(product);
}

Reference documents:

Guess you like

Origin www.cnblogs.com/youring2/p/10962573.html