原文地址:ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE

原文作者:ZANID HAYTAM

译文地址:如何在ASP.NET Core中实现面向切面编程(AOP)

译文作者:Lamond Lu

前言

在使用了Spring Boot数月之后,  我发觉ASP.NET Core中缺失了对面向切面编程(AOP)的默认支持。

维基百科中针对AOP的定义:

面向切面编程(AOP)是一种编程范例,其旨在通过允许跨领域关注点的分离来提高模块化。它通过“切入点”规范指定要修改的代码,不修改源代码本身的情况下,向现有代码提供额外行为,例如使用日志的方式记录为所有以"set"开头的方法调用记录。使用该方式,你可以向核心业务逻辑中追加一些不太重要的功能(例如日志),而不会使代码混乱。AOP为面向切换的软件开发奠定了基础。

以下是AOP的一些常用场景

  • 日志审计
  • 事务管理
  • 安全

代理模式(Proxy Pattern)也常用于Mocking(例如Moq, NSubstitute等)和延时加载(Lazy Loading)(例如EF Core, NHierante等)

C#中实现AOP

C#中其实已经支持AOP了,你可以快速Google搜索一下,AOP的实现方式有2种RealProxy真实代理和MarshalByRefObject.技术上讲,他们都可以在本地和远程使用,它看起来非常的美好,直到你明白的你的所有目标对象都必须继承MarshalByRefObject。仅此一点,就让大部分人不会考虑这种实现方式。

库更好的实现方式

幸运的是,我们在C#中可以使用一种更好的方式创建代理对象,即使用Castle.DynamicProxy库。

Castle.DynamicProxy是一个用于在运行时生成轻量级.NET代理的库。生成代理对象允许你在不修改原始代码的情况下拦截对对象成员的调用,只有virtual对象成员才能被拦截。 - Castle Project

使用Castle提供的动态代理,你可以为抽象类、接口(同时提供实现)以及带有virtual方法/属性的普通类创建代理对象。

以下是一个例子,这里我们假设创建了一个处理博客文章的服务应用。

public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool Disabled { get; set; }
public DateTime Created { get; set; }
} public interface IBlogService
{
void DisablePost(BlogPost post);
BlogPost GetPost(int id);
} public class BlogService : IBlogService
{
public BlogPost GetPost(int id)
{
return new BlogPost
{
Id = id,
Title = "Test",
Description = "Test",
Disabled = false,
Created = DateTime.UtcNow
};
} public void DisablePost(BlogPost post)
{
post.Disabled = true;
}
}

通常,你会将BlogService类注册为IBlogService接口的实现,一切都运转的非常正常。但是现在,你希望代理这个接口,当接口中任何方法被调用的时候,做点什么事情。

这里,我们首先创建一个拦截器对象以便拦截方法调用,就像RealProxy一样

public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"正在调用方法 {invocation.TargetType}.{invocation.Method.Name}.");
invocation.Proceed(); // 执行当前被拦截的方法
}
}

然后,我们将使用一个代理生成器生成代理对象。

var generator = new ProxyGenerator();
var actual = new BlogService();
var proxiedService = (IBlogService)proxyGenerator.CreateInterfaceProxyWithTarget(typeof(IBlogService), actual, new LoggingInterceptor());
// 使用proxiedService对象和你平常使用IBlogService对象是一样的

现在我们就创建出了一个实现了IBlogService接口的代理对象,其中包含了内部实现BlogService。当任何一个接口方法被调用的时候,LoggingInterceptor.Intercept方法就会被调用,当拦截器调用invocation.Proceed()方法时,它在BlogService类中的具体实现方法就会被调用。

如何在ASP.NET Core中使用Castle实现AOP

在ASP.NET Core中使用Castle实现AOP的实现思路是, 始终使用ASP.NET Core的IOC容器来创建代理服务。虽然Castle项目中包含它自己的IOC容器Castle Windor , 使得注入代理更加的容易,但是我们暂时不使用它。

这里,我们首先为我们的LoggingInterceptor添加一个简单的依赖以展示我们如何使用ASP.NET Core自带的DI来处理依赖问题。因为现实中,你的大部分拦截器都是需要一个或多个依赖项的。

public class LoggingInterceptor : IInterceptor
{
private readonly ILogger<LoggingInterceptor> _logger; public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger = logger;
} public void Intercept(IInvocation invocation)
{
_logger.LogDebug($"Calling method {invocation.TargetType}.{invocation.Method.Name}.");
invocation.Proceed();
}
}

第二步,我们在依赖注入容器中注册一个单例的ProxyGenerator对象,以及我们即将使用的所有的拦截器对象

services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, LoggingInterceptor>();

最后,我们创建一个扩展方法AddProxiedScoped, 并使用它注册其他所有服务。

public static class ServicesExtensions
{
public static void AddProxiedScoped<TInterface, TImplementation>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped(typeof(TInterface), serviceProvider =>
{
var proxyGenerator = serviceProvider.GetRequiredService<ProxyGenerator>();
var actual = serviceProvider.GetRequiredService<TImplementation>();
var interceptors = serviceProvider.GetServices<IInterceptor>().ToArray();
return proxyGenerator.CreateInterfaceProxyWithTarget(typeof(TInterface), actual, interceptors);
});
}
} // In ConfigureServices
services.AddProxiedScoped<IBlogService, BlogService>();

这里,让我们看看它是如何工作的

  1. 我们注册具体实现(例如BlogService)。这是因为具体实现可能也需要使用依赖注入容器解决依赖问题。
  2. 每当从依赖注入容器中尝试获取接口对象的时候:
    1. 我们取得了一个ProxyGenerator对象的实例
    2. 我们获得了一个接口的实现实例
    3. 我们获取到了所有注册的拦截器
    4. 使用代理生成器创建接口的代理对象,这个对象中包含了一个具体实现和其使用的拦截器。

现在,我们无论何时需要一个IBlogService接口对象,都可以通过依赖注入容器得到一个代理对象,这个代理对象会先经过所有的拦截器,然后调用BlogService中定义的实际方法。

但是这里,相较与Spring,在ASP.NET Core中实现AOP还不够简单直接,但是我们可以轻松将其转换为简单的“框架”,我们可以使用Castle.DynamicProxy的一些特定方法,来执行一些更高级的操作。

希望本篇文章对你有所帮助,Happy Coding!

最新文章

  1. 解决 VMWARE MAC 10.12无法全屏的问题
  2. 高性能 CSS3 动画
  3. Focus
  4. [Perl]抓取个人的所有闪存+格式化保存为文本
  5. 轻量级应用开发之(03)UIVIew
  6. Java 类的一些高级特征
  7. Java基础(二)
  8. UVa839 Not so Mobile
  9. 浅谈Javascript中默认参数值的设置
  10. iOS:Swift界面实例1, 简单界面
  11. 原生javascript与jquery 的比较
  12. Android自学反思总结(下)
  13. 解决iar试调时程序无法进入主函数的问题
  14. tomcat中的线程问题
  15. 如何去掉word中的回车符??
  16. C# 使用 HttpClient 调用 WebService 提示 NoSOAPAction
  17. [转]mysql优化——show processlist命令详解
  18. 转自: linux svn命令行无法拉取中文名称的文件
  19. ORA-01031: insufficient privileges 错误解决
  20. Centos7系统防火墙上开端口

热门文章

  1. 安装Scrapy的时候报错error: Microsoft Visual C++ 14.0 is required.
  2. 在龙芯mips64el平台编译bmon
  3. 01-java实现动态数组
  4. 2020-07-04:tcp三次握手干了啥?time_wait什么时候出现?
  5. 01从DataGrid中导入到Excel
  6. JavaScript基础-02
  7. 笔试算法稳了,GitHub 50k Star《labuladong的算法小抄》
  8. C#分布式登录——jwt
  9. 《Java从入门到失业》第二章:Java环境(四):IDE集成环境
  10. 分布式数据库中间件 MyCat | 分库分表实践