原文地址: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方法/属性的普通类创建代理对象。

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

  1. public class BlogPost
  2. {
  3. public int Id { get; set; }
  4. public string Title { get; set; }
  5. public string Description { get; set; }
  6. public bool Disabled { get; set; }
  7. public DateTime Created { get; set; }
  8. }
  9. public interface IBlogService
  10. {
  11. void DisablePost(BlogPost post);
  12. BlogPost GetPost(int id);
  13. }
  14. public class BlogService : IBlogService
  15. {
  16. public BlogPost GetPost(int id)
  17. {
  18. return new BlogPost
  19. {
  20. Id = id,
  21. Title = "Test",
  22. Description = "Test",
  23. Disabled = false,
  24. Created = DateTime.UtcNow
  25. };
  26. }
  27. public void DisablePost(BlogPost post)
  28. {
  29. post.Disabled = true;
  30. }
  31. }

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

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

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

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

  1. var generator = new ProxyGenerator();
  2. var actual = new BlogService();
  3. var proxiedService = (IBlogService)proxyGenerator.CreateInterfaceProxyWithTarget(typeof(IBlogService), actual, new LoggingInterceptor());
  4. // 使用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来处理依赖问题。因为现实中,你的大部分拦截器都是需要一个或多个依赖项的。

  1. public class LoggingInterceptor : IInterceptor
  2. {
  3. private readonly ILogger<LoggingInterceptor> _logger;
  4. public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
  5. {
  6. _logger = logger;
  7. }
  8. public void Intercept(IInvocation invocation)
  9. {
  10. _logger.LogDebug($"Calling method {invocation.TargetType}.{invocation.Method.Name}.");
  11. invocation.Proceed();
  12. }
  13. }

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

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

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

  1. public static class ServicesExtensions
  2. {
  3. public static void AddProxiedScoped<TInterface, TImplementation>(this IServiceCollection services)
  4. where TInterface : class
  5. where TImplementation : class, TInterface
  6. {
  7. services.AddScoped<TImplementation>();
  8. services.AddScoped(typeof(TInterface), serviceProvider =>
  9. {
  10. var proxyGenerator = serviceProvider.GetRequiredService<ProxyGenerator>();
  11. var actual = serviceProvider.GetRequiredService<TImplementation>();
  12. var interceptors = serviceProvider.GetServices<IInterceptor>().ToArray();
  13. return proxyGenerator.CreateInterfaceProxyWithTarget(typeof(TInterface), actual, interceptors);
  14. });
  15. }
  16. }
  17. // In ConfigureServices
  18. services.AddProxiedScoped<IBlogService, BlogService>();

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

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

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

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

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

[译]如何在ASP.NET Core中实现面向切面编程(AOP)的更多相关文章

  1. Spring中的面向切面编程(AOP)简介

    一.什么是AOP AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面 ...

  2. 如何在ASP.NET Core中实现CORS跨域

    注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...

  3. 如何在ASP.NET Core中实现一个基础的身份认证

    注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...

  4. 如何在ASP.NET Core中应用Entity Framework

    注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...

  5. [转]如何在ASP.NET Core中实现一个基础的身份认证

    本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...

  6. 如何在ASP.NET Core中使用Azure Service Bus Queue

    原文:USING AZURE SERVICE BUS QUEUES WITH ASP.NET CORE SERVICES 作者:damienbod 译文:如何在ASP.NET Core中使用Azure ...

  7. 如何在ASP.NET Core中自定义Azure Storage File Provider

    文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p ...

  8. 如何在ASP.NET Core中使用JSON Patch

    原文: JSON Patch With ASP.NET Core 作者:.NET Core Tutorials 译文:如何在ASP.NET Core中使用JSON Patch 地址:https://w ...

  9. [翻译] 如何在 ASP.Net Core 中使用 Consul 来存储配置

    [翻译] 如何在 ASP.Net Core 中使用 Consul 来存储配置 原文: USING CONSUL FOR STORING THE CONFIGURATION IN ASP.NET COR ...

随机推荐

  1. 8道python练习题,能做出来的没几个

    变量的定义 程序就是用来处理数据的,而变量就是用来存储数据的 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道 ...

  2. URLDecoder异常解决方法

    URLDecoder对参数进行解码时候,代码如: URLDecoder.decode(param,"utf-8"); 有时候会出现类似如下的错误: URLDecoder异常Ille ...

  3. Go语言入门系列(四)之map的使用

    本系列前面的文章: Go语言入门系列(一)之Go的安装和使用 Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 1. 声明 map是一种映射,可以将键(key)映射到值(val ...

  4. 认识与学习BASH①——鸟叔的Linux私房菜

    文章目录 认识与学习BASH① 认识BASH 壳程序 多种shells Bash shell 的功能 type :查询指令是否为Bash shell 的内置指令 指令的换行输入和快速删除 Shell的 ...

  5. HTML基础-03

    盒子模型 盒子模型(框模型 box model) - 浏览器在渲染页面时,它会将页面中的每一个元素都想象成是一个矩形的盒子. - 想象成盒子以后,对于页面的布局就变成了如何摆放盒子 - 每一个盒子从内 ...

  6. 洛谷P1308.统计单词数(字符串匹配)

    题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...

  7. 使用对称加密来加密Spring Cloud Config配置文件

    补充 使用Spring Cloud Config加密功能需要下载JCE扩展,用于生成无限长度的密文.链接:http://www.oracle.com/technetwork/java/javase/d ...

  8. jmeter性能测试入门使用参数化

    我经常使用jmeter进行接口测试,这个工具还是很好用的.昨天收到一个需求,需要压测一下接口,jmeter进行接口测试,使用cvs文件进行多个数据参数化. 临时准备了一下发现忘记怎么做参数化了,自己百 ...

  9. git pull冲突的解决方案

    处理步骤: 1.先将本地修改存储起来 $ git stash 这样本地的所有修改就都被暂时存储起来 .使用git stash list可以看到保存的信息: git stash暂存修改 其中stash@ ...

  10. 【Flutter 实战】一文学会20多个动画组件

    老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列.过度动画.转场动画.自定义动画等. Flutter 系统提供了20多个动画组件,只要你把前面[动画核心](文末有链接)的文章 ...