门户网站开发软件,用凡科做网站要钱吗,邵阳seo优化,金华网站建设方案策划多年从事框架设计开发使我有了一种强迫症#xff0c;那就是见不得一个应用里频繁地出现重复的代码。之前经常Review别人的代码#xff0c;一看到这样的程序#xff0c;我就会想如何将这些重复的代码写在一个地方#xff0c;然后采用“注入”的方式将它们放到需要的程序中。…多年从事框架设计开发使我有了一种强迫症那就是见不得一个应用里频繁地出现重复的代码。之前经常Review别人的代码一看到这样的程序我就会想如何将这些重复的代码写在一个地方然后采用“注入”的方式将它们放到需要的程序中。我们知道AOP是解决这类问题最理想的方案。为此我自己写了一个AOP框架该框架被命名为Dora.Interception。Dora.Interception已经在GitHub上开源如果有兴趣的朋友想下载源代码或者阅读相关文档可以访问GitHub地址https://github.com/jiangjinnan/Dora。Demo源代码下载地址http://files.cnblogs.com/files/artech/Dora.Interception.Demo.rar 目录一、Dora 为什么叫这个名字二、Dora.Interception的设计目标三、以怎样的方式使用Dora.Interception四、如何定义一个Interceptor五、定义InterceptorAttribute六、应用InterceptorAttribute七、以Dependency Injection的形式提供Proxy 一、Dora 为什么叫这个名字
其实我最早的想法是创建一个IoC框架并将它命名为Doraemon哆啦A梦因为我觉得一个理想的IoC Container就像是机器猫的二次元口袋一样能够提供给你期望的一切服务对象。后来觉得这名字太长所以改名为Dora。虽然Dora这个名字听上去有点“娘”并且失去了原本的意思但是我很喜欢这个单词的一种释义——“上帝的礼物”之一。在接触了.NET Core的时候我最先研究的就是它基于ServiceCollection和ServiceProvider的Dependency Injection框架虽然这个框架比较轻量级但是能够满足绝大部分项目的需求所以我放弃了初衷。不过我依然保留了Dora这个开源项目名并为此购买了一个域名doranet.org我希望将我多年的一些想法以一系列开源框架的形式实现出来Dora.Interception就是Dora项目的第一个基于AOP的框架。 二、Dora.Interception的设计目标
我当初在设计Dora.Interception框架时给自己确定的几个目标
Dora.Interception一个基于运行时Run Time而不是针对编译时Compile Time的AOP框架。它通过在运行时动态创建代理对象Proxy来封装目标对象Target并自动注入应用的拦截器Interceptor而不是在编译时帮助你生成一个Proxy类型。Dora.Interception需要采用一种优雅的方式来定义和应用Interceptor。能够与.NET Core的Dependency Injection框架无缝集成能够整合其他AOP框架。实际上Dora.Interception并没有自行实现最底层的“拦截”机制我使用的是Castle的DynamicProxy。如果有其他的选择我们可以很容易地将它引入进来。
三、以怎样的方式使用Dora.Interception
Dora.Interception目前的版本为1.1.0由如下两个NuGet包来承载由于Dora.Interception.Castle依赖于Dora.Interception所以安装后者即可。
Dora.Interception: 提供基本的APIDora.Interception.Castle: 提供基于CastleDynamicProxy的拦截实现 四、如何定义一个Interceptor
接下来我们通过一个简单的实例来说明一下如何采用“优雅”的方式来定义一个Interceptor类型。我们即将定义的这个CacheInterceptor可以应用到某个具有返回值的方法上实现针对返回值的缓存。如果应用了这个Interceptor它根据传入的参数对返回的值实施缓存。如果后续调用传入了相同的参数并且之前的缓存尚未过期缓存的结果将直接作为方法的返回值从而避免了针对目标方法的重复调用。针对的缓存功能实现在如下这个CacheInterceptor类型中可以看出针对的缓存是利用MemoryCache来完成的。 1: public class CacheInterceptor 2: { 3: private readonly InterceptDelegate _next; 4: private readonly IMemoryCache _cache; 5: private readonly MemoryCacheEntryOptions _options; 6: 7: public CacheInterceptor(InterceptDelegate next, IMemoryCache cache, IOptionsMemoryCacheEntryOptions optionsAccessor) 8: { 9: _next next; 10: _cache cache; 11: _options optionsAccessor.Value; 12: } 13: 14: public async Task InvokeAsync(InvocationContext context) 15: { 16: if (!context.Method.GetParameters().All(it it.IsIn)) 17: { 18: await _next(context); 19: } 20: 21: var key new Cachekey(context.Method, context.Arguments); 22: if (_cache.TryGetValue(key, out object value)) 23: { 24: context.ReturnValue value; 25: } 26: else 27: { 28: await _next(context); 29: _cache.Set(key, context.ReturnValue, _options); 30: } 31: } 32: public class CacheKey {...} 33: }
CacheInterceptor体现了一个典型的Interceptor的定义方式
Interceptor类型无需实现任何的接口我们只需要定义一个普通的公共实例类型即可。Interceptor类型必须具有一个公共构造函数并且该构造函数的第一个参数的类型必须是InterceptDelegate后者代表的委托对象会帮助我们调用后一个Interceptor或者目标方法如果当前Interceptor已经是最后一个了。上述这个构造函数可以包含任意的参数比如CacheInterceptor构造函数中的cache和optionsAccessor。这些参数可以直接利用.NET Core的Dependency Injection的方式进行注册对于没有注册的参数需要在应用该Interceptor的时候显式提供。拦截功能实现在约定的InvokeAsync的方法中这是一个返回类型为Task的异步方法它的第一个参数类型为InvocationContext代表当前方法调用的上下文。我们可以利用这个上下文对象得到Proxy对象和目标对象代表当前调用方法的MethodInfo对象以及传入的输入参数等。除此之外我们也可以利用这个上下文直接设置方法的返回值或者输出参数。这个InvokeAsync方法可以包含任意后续参数但是要求这些参数预先以Dependency Injection的形式进行注册。这也是我没有定义一个接口来表示Interceptor的原因因为这样就不能将依赖的服务直接注入到InvokeAsync方法中了。当前Interceptor是否调用后续的Interceptor或者目标方法取决于你是否调用构造函数传入的这个InterceptDelegate委托对象。
由于依赖的服务对象比如CacheInterceptor依赖IMemoryCache 和IOptionsMemoryCacheEntryOptions对象可以直接注入到InvokeAsync方法中所以上述这个CacheInterceptor也可以定义成如下的形式 1: public class CacheInterceptor 2: { 3: private readonly InterceptDelegate _next; 4: public CacheInterceptor(InterceptDelegate next) 5: { 6: _next next; 7: } 8: 9: public async Task InvokeAsync(InvocationContext context, IMemoryCache cache, IOptionsMemoryCacheEntryOptions optionsAccessor) 10: { 11: if (!context.Method.GetParameters().All(it it.IsIn)) 12: { 13: await _next(context); 14: } 15: 16: var key new Cachekey(context.Method, context.Arguments); 17: if (cache.TryGetValue(key, out object value)) 18: { 19: context.ReturnValue value; 20: } 21: else 22: { 23: await _next(context); 24: _cache.Set(key, context.ReturnValue, optionsAccessor.Value); 25: } 26: } 27: } 五、定义InterceptorAttribute
我们采用Attribute的形式来将对应的Intercepor应用到某个类型或者方法上每个具体的Interceptor类型都具有对应的Attribute。这样的Attribute直接继承基类InterceptorAttribute。如下这个CacheReturnValueAttribute就是上面这个CacheInterceptor对应的InterceptorAttribute。 1: [AttributeUsage(AttributeTargets.Method)] 2: public class CacheReturnValueAttribute : InterceptorAttribute 3: { 4: public override void Use(IInterceptorChainBuilder builder) 5: { 6: builder.UseCacheInterceptor(this.Order); 7: } 8: }
具体的InterceptorAttribute只需要重写Use方法将对应的Interceptor添加到Interceptor管道之中这个功能可以直接调用作为参数的InterceptorChainBuilder对象的泛型方法UseTInterceptor来实现。对于这个泛型方法来说泛型参数类型代表目标Interceptor的类型而第一个参数表示注册的Interceptor在整个管道中的位置。如果创建目标Interceptor而调用的构造函数的参数尚未采用Dependency Injection的形式注册我们需要在这个方法中提供。对于CacheInterceptor依赖的两个对象IMemoryCache 和IOptionsMemoryCacheEntryOptions都可以采用Dependency Injection的形式注入所以我们在调用UseCacheInterceptor方法是并不需要提供这个两个参数。
假设我们定义一个ExceptionHandlingInterceptor来实施自动化异常处理当我们在创建这个Interceptor的时候需要提供注册的异常处理类型的名称那么我们需要采用如下的形式来定义对应的这个IntercecptorAttribute。如下面的代码片段所示我们在调用UseExceptionHandlingInterceptor方法的时候就需要显式指定这个策略名称。 1: [AttributeUsage(AttributeTargets.Method|AttributeTargets.)] 2: public class HandleExceptionAttribute : InterceptorAttribute 3: { 4: public string ExceptionPolicy {get;} 5: public string HandleExceptionAttribute(string exceptionPolicy) 6: { 7: this.ExceptionPolicy exceptionPolicy; 8: } 9: public override void Use(IInterceptorChainBuilder builder) 10: { 11: builder.UseExceptionHandlingInterceptor(this.Orderthis.ExceptionPolicy); 12: } 13: } 有的时候IntercecptorAttribute在注册对应Interceptor的时候需要使用到应用到当前方法或者类型上的其他Attribute。举个简单的例子上述的这个HandleExceptionAttribute实际上是自动提供异常处理策略名称假设异常处理系统自身使用另外一个独立的ExceptionPolicyAttribute采用如下的形式来提供这个策略。 1: public class Foobar 2: { 3: [ExceptionPolicy(DefaultPolicy) 4: public void Invoke() 5: { 6: ... 7: } 8: } 这个问题很好解决因为InterceptorAttribute自身提供了应用到目标方法或者类型上的所有Attribute所以上述这个HandleExceptionAttribute可以采用如下的定义方式。 1: [AttributeUsage(AttributeTargets.Method)] 2: public class HandleExceptionAttribute : InterceptorAttribute 3: { 4: public override void Use(IInterceptorChainBuilder builder) 5: { 6: ExceptionPolicyAttribute attribute this.Attributes.ofTypeExceptionPolicyAttribute().First(); 7: builder.UseException(this.Order, attribute.ExceptionPolicy); 8: } 9: } 六、应用InterceptorAttribute
Interceptor通过对应的InterceptorAttribute被应用到某个方法或者类型上我们在应用InterceptorAttribute可以利用其Order属性确定Interceptor的排列执行顺序。如下面的代码片段所示 HandleExceptionAttribute和CacheReturnValueAttribute分别被应用到Foobar类型和Invoke方法上我要求ExceptionHandlingInterceptor能够处理CacheInterceptor抛出的异常 那么前者必须由于后者执行所以我通过Order属性控制了它们的执行顺序。值得一提的是目前我们支持两个拦截机制一种是基于接口另一种是基于虚方法。如果采用基于接口的拦截机制我要求InterceptorAttribute应用在实现类型或者其方法上应用在接口和其方法上的InterceptorAttribute将无效。 1: [HandleException(defaultPolicy, Order 1)] 2: public class Foobar: IFoobar 3: { 4: [CacheReturnValue(this.Order 2)] 5: public Data LoadData() 6: { 7: ... 8: } 9: }
如果我们在类型上应用了某个InterceptorAttribute但是对应的Interceptor却并不希望应用到某个方法中我们可以利用NonInterceptableAttribute采用如下的形式将它们屏蔽 1: [CacheReturnValue] 2: public class Foobar 3: { 4: ... 5: [NonInterceptable(typeof(CacheReturnValueAttribute)] 6: public Data GetRealTypeData() 7: {...} 8: } 七、以Dependency Injection的形式提供Proxy
我们知道应用在目标类型或者其方法上的Interceptor能够生效要求方法调用针对的是封装目标对象的Proxy对象换句话说我们希望提供的对象是一个Proxy而不是目标对象。除此之外我们在上面的设计目标已经提到过我们希望这个AOP框架能够与.NET Core的Dependency Injection框架进行无缝集成所以现在的问题变成了如何让Dependency Injection的ServiceProvider提供的是Proxy对象而不是目标对象。我提供的两种方案来解决这个问题接下来我们通过一个ASP.NET Core MVC应用来举例说明。
为了能够使用上面提供的CacheInterceptor并且能够以很直观的方式感受到缓存的存在我定义了如下这个表示系统时钟的ISystemClock接口和具体实现类型SystemClock。从如下的代码片段可以看出GetCurrentTime方法总是返回实时的时间但是由于应用了CaheReturnValueAttribute如果CacheInterceptor生效返回的时间在缓存过期之前总是相同的。 1: public interface ISystomClock 2: { 3: DateTime GetCurrentTime(); 4: } 5: 6: public class SystomClock : ISystomClock 7: { 8: [CacheReturnValue] 9: public DateTime GetCurrentTime() 10: { 11: return DateTime.UtcNow; 12: } 13: }
我们在HomeController中以构造器注入的方式来使用ISystemClock。在默认情况下如果我们注入的类型ISystemClock接口那么毫无疑问那么GetCurrentTime方法调用的就是SystemClock对象本身所以根本不可能起到缓存的作用。所以我们将注入类型替换成IInterceptableISystomClock后者的Proxy属性将会返回我们希望的Proxy对象。 1: public class HomeController : Controller 2: { 3: private readonly ISystomClock _clock; 4: public HomeController(IInterceptableISystomClock clockAccessor) 5: { 6: _clock clockAccessor.Proxy; 7: } 8: 9: [HttpGet(/)] 10: public async Task Index() 11: { 12: this.Response.ContentType text/html; 13: await this.Response.WriteAsync(htmlbodyul); 14: for (int i 0; i 5; i) 15: { 16: await this.Response.WriteAsync($li{_clock.GetCurrentTime()}({DateTime.UtcNow})/li); 17: await Task.Delay(1000); 18: } 19: await this.Response.WriteAsync(/ulbody/html); 20: } 21: }
当然我们需要注册Dora.Interception一些必须的服务这些服务采用如下的形式通过调用扩展方法AddInterception来实现。 1: public class Startup 2: { 3: public void ConfigureServices(IServiceCollection services) 4: { 5: services 6: .AddScopedISystomClock, SystomClock() 7: .AddInterception(builderbuilder.SetDynamicProxyFactory()) 8: .AddMvc(); 9: } 10: public void Configure(IApplicationBuilder app) 11: { 12: app.UseMvc(); 13: } 14: }
虽然IInterceptableT能够解决Proxy的提供问题但是这种编程模式其实是很不好的。理想的编程模式应该是依赖某个服务就注入对应的服务接口就可以。这个问题其实也好解决我们首先将HomeController还原成典型的编程模式 1: public class HomeController : Controller 2: { 3: private readonly ISystomClock _clock; 4: public HomeController(ISystomClock clock) 5: { 6: _clock clock; 7: } 8: 9: [HttpGet(/)] 10: public async Task Index() 11: { 12: this.Response.ContentType text/html; 13: await this.Response.WriteAsync(htmlbodyul); 14: for (int i 0; i 5; i) 15: { 16: await this.Response.WriteAsync($li{_clock.GetCurrentTime()}({DateTime.UtcNow})/li); 17: await Task.Delay(1000); 18: } 19: await this.Response.WriteAsync(/ulbody/html); 20: } 21: }
接下来我们只需要修改Startup的ConfigureServices的两个地方同样达到相同的目的。如下面的代码片段所示我们让ConfigureServices返回一个IServiceProvider对象这个对象直接调用我们定义的扩展方法BuilderInterceptableServiceProvider来创建。 1: public class Startup 2: { 3: public IServiceProvider ConfigureServices(IServiceCollection services) 4: { 5: services 6: .AddScopedISystomClock, SystomClock() 7: .AddMvc(); 8: return services.BuilderInterceptableServiceProvider(builder builder.SetDynamicProxyFactory()); 9: } 10: 11: public void Configure(IApplicationBuilder app) 12: { 13: app.UseMvc(); 14: } 15: }
对于上述的两种编程模式运行程序后浏览器上都会呈现出相同的时间 相关文章
为了支持AOP的编程模式我为.NET Core写了一个轻量级的Interception框架[开源]Autofac 之 基于 Castle DynamicProxy2 的 Interceptor 功能[Asp.Net Core轻量级Aop解决方案]AspectCore Project 介绍.Net Aop(静态织入)框架 BSF.Aop
原文地址http://www.cnblogs.com/artech/p/dora-interception.html .NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注