.NET Core 原生DI+AOP实现注解式编程

2019 年 9 月 9 日 DotNet

(给DotNet加星标,提升.Net技能


转自: 三合视角
cnblogs.com/hezp/p/11346120.html

写过spring boot之后,那种无处不在的注解让我非常喜欢,比如属性注入@autowire,配置值注入@value,声明式事物@Transactional等,都非常简洁优雅,那么我就在想,这些在net core里能实现么?经过一番摸索,终于实现并整理成此文。


IOC方面,个人非常喜欢.NET Core 自带的DI,因为他注册服务简洁优雅,3个生命周期通俗易懂,所以就没使用autofac等其他容器,AOP方面,使用了业内鼎鼎大名的Castle.DynamicProxy(简称DP),所以要在nuget中添加Castle.Core的依赖包,这里大家可能会有疑问,利用mvc的actionFilter不就可以实现了么,为什么还要引用DP呢,因为呀,actionFilter只在controller层有效,普通类他就无能为力了,而DP无所不能。


一、定义注解和需要用到的类


属性注入注解


[AttributeUsage(AttributeTargets.Property)]
public class AutowiredAttribute : Attribute
{
}


配置值注入注解


[AttributeUsage(AttributeTargets.Property)]
public class ValueAttribute : Attribute
{
public ValueAttribute(string value = "")
{
this.Value = value;
}
public string Value { get; }
}


声明式事物注解


[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Attribute
{
}


工作单元接口及实现类,用来实现事物操作,注入级别为scope,一次请求公用一个工作单元实例


public interface IUnitOfWork : IDisposable
{
/// <summary>
/// 开启事务
/// </summary>
void BeginTransaction();

/// <summary>
/// 提交
/// </summary>
void Commit();

/// <summary>
/// 事物回滚
/// </summary>
void RollBack();
}
public class UnitOfWork : IUnitOfWork
{
public void BeginTransaction()
{
Console.WriteLine("开启事务");
}
public void Commit()
{
Console.WriteLine("提交事务");
}
public void Dispose()
{
//throw new System.NotImplementedException();
}
public void RollBack()
{
Console.WriteLine("回滚事务");
}
}


拦截器


/// <summary>
/// 事物拦截器
/// </summary>
public class TransactionalInterceptor : StandardInterceptor
{
private IUnitOfWork Uow { set; get; }
public TransactionalInterceptor(IUnitOfWork uow)
{
Uow = uow;
}
protected override void PreProceed(IInvocation invocation)
{
Console.WriteLine("{0}拦截前", invocation.Method.Name);
var method = invocation.TargetType.GetMethod(invocation.Method.Name);
if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)
{
Uow.BeginTransaction();
}
}
protected override void PerformProceed(IInvocation invocation)
{
invocation.Proceed();
}
protected override void PostProceed(IInvocation invocation)
{
Console.WriteLine("{0}拦截后, 返回值是{1}", invocation.Method.Name, invocation.ReturnValue);
var method = invocation.TargetType.GetMethod(invocation.Method.Name);
if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)
{
Uow.Commit();
}
}
}


用来测试注入效果的接口以及实现类,这里我们定义一辆汽车,汽车拥有一个引擎(属性注入),它能点火启动,点火操作带事物,这里为了演示【接口-实现类】注入和【实现类】注入2种情况,引擎就没添加接口,只有实现类,并且AOP拦截仅有【实现类】的类时,只能拦截虚方法,所以Start和Stop函数为虚函数。


/// <summary>
/// 汽车引擎
/// </summary>
public class Engine
{
[Value("HelpNumber")]
public string HelpNumber { set; get; }
public virtual void Start()
{
Console.WriteLine("发动机启动");
Stop();
}
public virtual void Stop()
{
Console.WriteLine("发动机熄火,拨打求救电话" + HelpNumber);
}
}
public interface ICar
{
Engine Engine { set; get; }
void Fire();
}
public class Car : ICar
{
[Autowired]
public Engine Engine { set; get; }
[Value("oilNo")]
public int OilNo { set; get; }
[Transactional]
public void Fire()
{
Console.WriteLine("加满" + OilNo + "号汽油,点火");
Engine.Start();
}
}


控制器HomeController


public class HomeController : Controller
{
[Autowired]
public ICar Car{ set; get; }
[Value("description")]
public string Description { set; get; }
public IActionResult Index()
{
var car = Car;
Console.WriteLine(Description);
Car.Fire();
return View();
}
}


修改appsettings.json,添加一些测试键值对,(如果测试时发现输出的中文乱码,把appsettings.json保存为utf8格式即可),具体代码如下,


{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"oilNo": 95,
"HelpNumber": "110",
"description": "我要开始飙车了"
}


2、效果图



从上图可以看到,正常注入,正常开启拦截器和事务,正确读取配置值。



从上图可以看到,我们的控制器,ICar和Engine全部都是动态代理类,注入正常。


三、核心代码


第一部分,添加一个扩展类,名叫SummerBootExtentions.cs,代码如下


public static class SummerBootExtentions
{
/// <summary>
/// 瞬时
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <typeparam name="TImplementation"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbTransient<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);
}

/// <summary>
/// 请求级别
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <typeparam name="TImplementation"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbScoped<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);
}

/// <summary>
/// 单例
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <typeparam name="TImplementation"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbSingleton<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);
}

public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, Type implementationType,
ServiceLifetime lifetime, params Type[] interceptorTypes
)
{
services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime));

object Factory(IServiceProvider provider)
{
var target = provider.GetService(implementationType);
var properties = implementationType.GetTypeInfo().DeclaredProperties;

foreach (PropertyInfo info in properties)
{
//属性注入
if (info.GetCustomAttribute<AutowiredAttribute>() != null)
{
var propertyType = info.PropertyType;
var impl = provider.GetService(propertyType);
if (impl != null)
{
info.SetValue(target, impl);
}
}
//配置值注入
if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)
{
var value = valueAttribute.Value;
if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)
{
var pathValue = configService.GetSection(value).Value;
if (pathValue != null)
{
var pathV = Convert.ChangeType(pathValue, info.PropertyType);
info.SetValue(target, pathV);
}
}

}
}
List<IInterceptor> interceptors = interceptorTypes.ToList()
.ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);

var proxy = new ProxyGenerator().CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray());

return proxy;
};
var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);
services.Add(serviceDescriptor);

return services;
}

/// <summary>
/// 瞬时
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbTransient<TService>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes);
}

/// <summary>
/// 请求
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbScoped<TService>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes);
}

/// <summary>
/// 单例
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <param name="services"></param>
/// <param name="interceptorTypes"></param>
/// <returns></returns>
public static IServiceCollection AddSbSingleton<TService>(this IServiceCollection services, params Type[] interceptorTypes)
{
return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes);
}

public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType,
ServiceLifetime lifetime, params Type[] interceptorTypes
)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (serviceType == (Type)null)
throw new ArgumentNullException(nameof(serviceType));

object Factory(IServiceProvider provider)
{
List<IInterceptor> interceptors = interceptorTypes.ToList()
.ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);
var proxy = new ProxyGenerator().CreateClassProxy(serviceType, interceptors.ToArray());
var properties = serviceType.GetTypeInfo().DeclaredProperties;
foreach (PropertyInfo info in properties)
{
//属性注入
if (info.GetCustomAttribute<AutowiredAttribute>() != null)
{
var propertyType = info.PropertyType;
var impl = provider.GetService(propertyType);
if (impl != null)
{
info.SetValue(proxy, impl);
}
}
//配置值注入
if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)
{
var value = valueAttribute.Value;
if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)
{
var pathValue = configService.GetSection(value).Value;
if (pathValue != null)
{
var pathV = Convert.ChangeType(pathValue, info.PropertyType);
info.SetValue(proxy, pathV);
}
}
}
}
return proxy;
};
var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);
services.Add(serviceDescriptor);
return services;
}
/// <summary>
/// 添加summer boot扩展
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IMvcBuilder AddSB(this IMvcBuilder builder)
{
if (builder == null)
throw new ArgumentNullException(nameof(builder));
ControllerFeature feature = new ControllerFeature();
builder.PartManager.PopulateFeature<ControllerFeature>(feature);
foreach (Type type in feature.Controllers.Select<TypeInfo, Type>((Func<TypeInfo, Type>)(c => c.AsType())))
builder.Services.TryAddTransient(type, type);
builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, SbControllerActivator>());
return builder;
}
}


第二部分,添加一个自定义控制器激活类,用以替换掉mvc自带的激活类,这个类命名为SbControllerActivator.cs,代码如下


public class SbControllerActivator : IControllerActivator
{
/// <inheritdoc />
public object Create(ControllerContext actionContext)
{
if (actionContext == null)
throw new ArgumentNullException(nameof(actionContext));
Type serviceType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();
var target = actionContext.HttpContext.RequestServices.GetRequiredService(serviceType);
var properties = serviceType.GetTypeInfo().DeclaredProperties;
var proxy = new ProxyGenerator().CreateClassProxyWithTarget(serviceType, target);
foreach (PropertyInfo info in properties)
{
//属性注入
if (info.GetCustomAttribute<AutowiredAttribute>() != null)
{
var propertyType = info.PropertyType;
var impl = actionContext.HttpContext.RequestServices.GetService(propertyType);
if (impl != null)
{
info.SetValue(proxy, impl);
}
}
//配置值注入
if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)
{
var value = valueAttribute.Value;
if (actionContext.HttpContext.RequestServices.GetService(typeof(IConfiguration)) is IConfiguration configService)
{
var pathValue = configService.GetSection(value).Value;
if (pathValue != null)
{
var pathV = Convert.ChangeType(pathValue, info.PropertyType);
info.SetValue(proxy, pathV);
}
}
}
}
return proxy;
}
/// <inheritdoc />
public virtual void Release(ControllerContext context, object controller)
{
}
}


第三部分,在Startup.cs中,修改ConfigureServices方法如下


public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddSB();
services.AddSbScoped<Engine>(typeof(TransactionalInterceptor))
services.AddScoped<IUnitOfWork,UnitOfWork>();
services.AddScoped(typeof(TransactionalInterceptor));
services.AddSbScoped<ICar, Car>(typeof(TransactionalInterceptor));
}


从上面代码我们可以看到,在addMvc后加上了我们替换默认控制器的AddSB方法,接管了控制器的生成。AddSbScoped<Engine>和AddSbScoped<ICar, Car>这种添加依赖注入的方式也保持了net core自带DI的原滋原味,简洁优雅,并且实现了动态代理,只需要在参数里添加拦截器,就能实时拦截,这里参数为params Type[],可以添加N个拦截器。


四、写在最后


见证了.NET Core从1.0到快出3.0,这也是第一次尝试着写博客,为.NET Core的发扬光大尽自己的一份力。


推荐阅读

(点击标题可跳转阅读)

.NET Core 3.0 Preview 9 发布

密钥分离 .NET程序猿不再背锅

ASP.NET Core JWT与用户授权


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

好文章,我在看❤️

登录查看更多
8

相关内容

.NET 框架(.NET Framework) 是由微软开发,一个致力于敏捷软件开发、快速应用开发、平台无关性和网络透明化的软件开发平台。
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
56+阅读 · 2020年6月26日
Python导论,476页pdf,现代Python计算
专知会员服务
253+阅读 · 2020年5月17日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
223+阅读 · 2020年3月22日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
《动手学深度学习》(Dive into Deep Learning)PyTorch实现
专知会员服务
116+阅读 · 2019年12月31日
用 GitLab 的 Merge Request 做代码评审
DevOps时代
4+阅读 · 2019年5月5日
请快点粘贴复制,这是一份好用的TensorFlow代码集
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
教你实现超流行的骨架屏预加载动态效果
IMWeb前端社区
71+阅读 · 2018年11月27日
.NET Core 环境下构建强大且易用的规则引擎
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
Tensor Flow、Caffe、Torch共同之处:敞开的漏洞!
Directions for Explainable Knowledge-Enabled Systems
Arxiv
26+阅读 · 2020年3月17日
Deep learning for cardiac image segmentation: A review
Arxiv
21+阅读 · 2019年11月9日
VIP会员
相关资讯
用 GitLab 的 Merge Request 做代码评审
DevOps时代
4+阅读 · 2019年5月5日
请快点粘贴复制,这是一份好用的TensorFlow代码集
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
教你实现超流行的骨架屏预加载动态效果
IMWeb前端社区
71+阅读 · 2018年11月27日
.NET Core 环境下构建强大且易用的规则引擎
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
Tensor Flow、Caffe、Torch共同之处:敞开的漏洞!
Top
微信扫码咨询专知VIP会员