写过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无所不能。
1.定义注解和需要用到的类
属性注入注解
[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 { ////// 开启事务 /// void BeginTransaction(); /// /// 提交 /// void Commit(); /// /// 事物回滚 /// 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("回滚事务"); } }
拦截器
////// 事物拦截器 /// 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 () != 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 () != null) { Uow.Commit(); } } }
用来测试注入效果的接口以及实现类,这里我们定义一辆汽车,汽车拥有一个引擎(属性注入),它能点火启动,点火操作带事物,这里为了演示【接口-实现类】注入和【实现类】注入2种情况,引擎就没添加接口,只有实现类,并且AOP拦截仅有【实现类】的类时,只能拦截虚方法,所以Start和Stop函数为虚函数。
////// 汽车引擎 /// 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全部都是动态代理类,注入正常。
3.核心代码
第一部分,添加一个扩展类,名叫SummerBootExtentions.cs,代码如下
public static class SummerBootExtentions { ////// 瞬时 /// /// /// /// /// /// public static IServiceCollection AddSbTransient (this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes); } /// /// 请求级别 /// /// /// /// /// /// public static IServiceCollection AddSbScoped (this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes); } /// /// 单例 /// /// /// /// /// /// public static IServiceCollection AddSbSingleton (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 () != null) { var propertyType = info.PropertyType; var impl = provider.GetService(propertyType); if (impl != null) { info.SetValue(target, impl); } } //配置值注入 if (info.GetCustomAttribute () 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 interceptors = interceptorTypes.ToList() .ConvertAll (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; } /// /// 瞬时 /// /// /// /// /// public static IServiceCollection AddSbTransient (this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes); } /// /// 请求 /// /// /// /// /// public static IServiceCollection AddSbScoped (this IServiceCollection services, params Type[] interceptorTypes) { return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes); } /// /// 单例 /// /// /// /// /// public static IServiceCollection AddSbSingleton (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 interceptors = interceptorTypes.ToList() .ConvertAll (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 () != null) { var propertyType = info.PropertyType; var impl = provider.GetService(propertyType); if (impl != null) { info.SetValue(proxy, impl); } } //配置值注入 if (info.GetCustomAttribute () 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; } /// /// 添加summer boot扩展 /// /// /// public static IMvcBuilder AddSB(this IMvcBuilder builder) { if (builder == null) throw new ArgumentNullException(nameof(builder)); ControllerFeature feature = new ControllerFeature(); builder.PartManager.PopulateFeature (feature); foreach (Type type in feature.Controllers.Select ((Func )(c => c.AsType()))) builder.Services.TryAddTransient(type, type); builder.Services.Replace(ServiceDescriptor.Transient ()); return builder; } }
第二部分,添加一个自定义控制器激活类,用以替换掉mvc自带的激活类,这个类命名为SbControllerActivator.cs,代码如下
public class SbControllerActivator : IControllerActivator { ///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 () != null) { var propertyType = info.PropertyType; var impl = actionContext.HttpContext.RequestServices.GetService(propertyType); if (impl != null) { info.SetValue(proxy, impl); } } //配置值注入 if (info.GetCustomAttribute () 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; } /// public virtual void Release(ControllerContext context, object controller) { } }
第三部分,在Startup.cs中,修改ConfigureServices方法如下
public void ConfigureServices(IServiceCollection services) { services.Configure(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 (typeof(TransactionalInterceptor)); services.AddScoped (); services.AddScoped(typeof(TransactionalInterceptor)); services.AddSbScoped (typeof(TransactionalInterceptor)); }
从上面代码我们可以看到,在addMvc后加上了我们替换默认控制器的AddSB方法,接管了控制器的生成。AddSbScoped
4.写在最后
在博客园潜水了好几年,见证了net core从1.0到快出3.0,这也是第一次尝试着写博客,为net core的发扬光大尽自己的一份力。