C#下IOC/依赖注入框架Grace介绍
对依赖注入或控制反转不了解的童鞋请先自行学习一下这一设计,这里直接介绍项目和实现步骤。
Grace是一个开源、轻巧、易用同时特性丰富、性能优秀的依赖注入容器框架。从这篇IOC容器评测文章找到的Grace,评测显示这款开源轻巧的框架性能挺成熟优秀的,但是中文资料几乎找不到,作者文档也不多,Get Started在各种项目中的案例也没有。这里贴一下介绍和纯后台以及ASP.NET Core的使用Demo,部分翻译自项目,更多内容可以直接看项目的Readme和Tests——Tests作者分类很好,可以作为需求切入点了解。
作者:Ian Johnson
主项目Nuget:https://www.nuget.org/packages/Grace/
主项目Github:https://github.com/ipjohnson/Grace
ASP.Net Core Nuget: https://www.nuget.org/packages/Grace.AspNetCore.MVC
ASP.Net Core Github:https://github.com/ipjohnson/Grace.DependencyInjection.Extensions
目录:
一、介绍
Grace有如下特性:
- 配置提供允许最大限度扩展的流式(Fluent)接口/属性
- 支持子容器和轻量级生命周期作用域
- 支持绑定上下文化(类似NInject)
- 容器创建的IDisposable对象将被跟踪和释放,除非另有配置
- 性能特点使它成为最快的容器之一
- 支持特殊类型
- IEnumerable<T> - 支持将集合解析为IEnumerable<T>,包括其他如List<T>,ReadOnlyCollection<T>,T[]和其他实现ICollection<T>的集合。具体可以查看这里,可以实现批量自动注册绑定。
- Func<T> - 支持自动解析Func<T>
- Lazy<T> - 当解析一个Lazy<T>对象时,其将在自己创建的生命周期内创建和解析对象T
- Owned<T> - 在一个Owned<T>对象内解析时,其将有与自己Owned<T>相关联的生命周期(与Autofac类似)
- Meta<T> - 在一个Meta<T>内解析时,其元数据也会跟着解析
- 自定义委托 - 任何返回一个类型的委托都能被自动解析
- 用Grace.Factory自定义接口
- 支持多种生命周期,包括单例、作用域内单例、请求内单例(MVC4, MVC5 和 WCF 扩展包中)、对象图内单例、基类上单例和弱单例。如果以上都没有符合需求的,可以使用ICompiledLifeStyle接口实现
- 内置支持装饰器设计
- 支持自定义包装(Func<T>和Meta<T>是内置包装的举例)
- ASP.Net Core支持(测试似乎只支持.Net Standard 1.0,DotNetCore 2.0Linux下不行)
- ASP.Net MVC 4 & 5支持
二、纯C#使用Grace的Demo
假设我们有Repository和Service模式,有用户(User)和账户(Account)这两个DAO对象,分别如下定义好接口和类,此部分在附件的“IOCFramework.Dao”工程中。
1 public interface IAccountRepository 2 { 3 string Get(); 4 } 5 6 public class AccountRepository : IAccountRepository 7 { 8 public string Get() 9 { 10 return "[Account]简单注册调用[Repo]"; 11 } 12 } 13 14 public interface IAccountService 15 { 16 string Get(); 17 } 18 19 public class AccountService : IAccountService 20 { 21 IAccountRepository _accountRepository; 22 public AccountService(IAccountRepository accountRepository) 23 { 24 _accountRepository = accountRepository; 25 } 26 27 public string Get() 28 { 29 return _accountRepository.Get() + "[Service]"; 30 } 31 } 32 33 public interface IUserRepository 34 { 35 string Get(); 36 } 37 38 public class UserRepositoryA : IUserRepository 39 { 40 public string Get() 41 { 42 return "[User]键值注册调用A[Repo]"; 43 } 44 } 45 46 public class UserRepositoryB : IUserRepository 47 { 48 public UserRepositoryB(string param1, string param2) 49 { 50 Console.WriteLine($"Ctor param1:{param1}"); 51 } 52 53 public string Get() 54 { 55 return "[User]键值注册调用B[Repo]"; 56 } 57 } 58 59 public interface IUserService 60 { 61 string Get(); 62 } 63 64 public class UserService : IUserService 65 { 66 67 IUserRepository _userRepository; 68 69 public UserService(IUserRepository userRepository) 70 { 71 _userRepository = userRepository; 72 } 73 74 public string Get() 75 { 76 return _userRepository.Get() + "[Service]"; 77 } 78 }
接下来可以使用源码也可以添加Nuget的方式添加Grace。下面的代码实现了纯C#的容器的注册和实现,过程简单,看注释即可,不在赘述,此部分在附件的“IOCFramework.Demo”工程中。
1 public static void Exec() 2 { 3 //容器 4 var container = new DependencyInjectionContainer(); 5 6 container.Configure(m => 7 { 8 //这里演示如何简单注册同一个接口对应其实现 9 m.Export<AccountRepository>().As<IAccountRepository>(); 10 m.Export<AccountService>().As<IAccountService>(); 11 12 //这里演示如何使用键值注册同一个接口的多个实现 13 m.Export<UserRepositoryA>().AsKeyed<IUserRepository>("A"); 14 //这里同时演示使用带参数构造器来键值注册 15 m.Export<UserRepositoryB>().AsKeyed<IUserRepository>("B").WithCtorParam<string>(() => { return "kkkkk"; }); 16 17 //这里演示依赖倒置而使用构造器带键值注入 18 m.Export<UserService>().As<IUserService>().WithCtorParam<IUserRepository>().LocateWithKey("B"); 19 }); 20 21 //获取简单注册实例 22 var accountRepo = container.Locate<IAccountRepository>(); 23 Console.WriteLine(accountRepo.Get());//输出:[Account]简单注册调用[Repo] 24 var accountSvc = container.Locate<IAccountService>(); 25 Console.WriteLine(accountSvc.Get());//输出:[Account]简单注册调用[Repo][Service] 26 27 Console.WriteLine(); 28 29 //获取指定键值的实例 30 var userRepo = container.Locate<IUserRepository>(withKey: "A"); 31 Console.WriteLine(userRepo.Get());//输出:[User]键值注册调用A[Repo] 32 33 var userSvc = container.Locate<IUserService>();//输出:Ctor param1:kkkkk 34 Console.WriteLine(userSvc.Get());//输出:[User]键值注册调用B[Repo][Service] 35 }
三、ASP.NET Core使用Grace的Demo
此部分Demo在附件的“WebCoreApplicationUseGrace”工程中。
1. 先在项目中添加Grace.AspNetCore.Hosting包。依赖项有Grace包和Grace.DependencyInjection.Extensions包,也可以使用这里的源码:https://github.com/ipjohnson/Grace.DependencyInjection.Extensions。
2. 然后在Program.cs添加Grace组件:
1 public static IWebHost BuildWebHost(string[] args) => 2 WebHost.CreateDefaultBuilder(args) 3 .UseKestrel() 4 .UseIISIntegration() 5 .UseContentRoot(Directory.GetCurrentDirectory()) 6 .UseGrace() //添加Grace 7 .UseStartup<Startup>() 8 .Build();
3. 在Startup.cs中添加方法ConfigureContainer方法,然后在IInjectionScope里面注册接口和类型,具体看下面代码。这里可以将Startup.cs修改为部分类而单独将ConfigureContainer方法放于新建的Startup.cs部分类中,方便以后添加配置
1 public partial class Startup 2 { 3 // 添加此方法 4 public void ConfigureContainer(IInjectionScope scope) 5 { 6 scope.Configure(m => 7 { 8 //这里演示如何简单注册同一个接口对应其实现 9 m.Export<AccountRepository>().As<IAccountRepository>(); 10 m.Export<AccountService>().As<IAccountService>(); 11 12 //这里演示如何使用键值注册同一个接口的多个实现 13 m.Export<UserRepositoryA>().AsKeyed<IUserRepository>("A"); 14 //这里同时演示使用带参数构造器来键值注册 15 m.Export<UserRepositoryB>().AsKeyed<IUserRepository>("B").WithCtorParam<string>(() => { return "kkkkk"; }); 16 17 //这里演示依赖倒置而使用构造器带键值注入 18 m.Export<UserService>().As<IUserService>().WithCtorParam<IUserRepository>().LocateWithKey("B"); 19 }); 20 21 scope.SetupMvc();//这一句需先添加Grace.AspNetCore.MVC 22 } 23 }
4. Grace提供了自定义控制器和视图激活器,用一些自定义特性提供了更好的性能。这里添加Grace.AspNetCore.MVC Nuget包/源码项目,然后在上一步方法中添加scope.SetupMvc(),至此配置完毕。
5. 在控制器中使用方法如下。一般的可以使用构造器参数方式赋值实例,有特殊注入的(如下面带键值的注入)可以先实例IExportLocatorScope出来,再用其Locate来获得实例。
1 public class HomeController : Controller 2 { 3 //IExportLocatorScope可以获取从InjectionScope和LifeTimeScope注入的类型实例 4 IExportLocatorScope locator; 5 6 IAccountRepository accountRepo; 7 IAccountService accountSvc; 8 IUserRepository userRepo; 9 IUserService userSvc; 10 11 public HomeController(IExportLocatorScope locator, IAccountRepository accountRepo, IAccountService accountSvc, IUserService userSvc) 12 { 13 this.locator = locator; 14 //获取简单注册实例 15 this.accountRepo = accountRepo; 16 this.accountSvc = accountSvc; 17 //获取指定键值的实例 18 this.userRepo = this.locator.Locate<IUserRepository>(withKey: "A"); 19 this.userSvc = userSvc; 20 21 } 22 23 public IActionResult Index() 24 { 25 return Json(new 26 { 27 accountRepoGet = accountRepo.Get(), 28 accountSvcGet = accountSvc.Get(), 29 userRepoGet = userRepo.Get(), 30 userSvcGet = userSvc.Get(), 31 }); 32 } 33 34 }
以上代码输出如下:
{ "accountRepoGet": "[Account]简单注册调用[Repo]", "accountSvcGet": "[Account]简单注册调用[Repo][Service]", "userRepoGet": "[User]键值注册调用A[Repo]", "userSvcGet": "[User]键值注册调用B,Ctor param1:kkkkk[Repo][Service]" }
四、ASP.NET MVC使用Grace的Demo
Grace支持ASP.NET MVC4和MVC5,这里以MVC5为例,此部分Demo在附件的“WebCoreApplicationUseGrace”工程中。
1. 先在项目中添加Grace.MVC5包,依赖项有Grace包。也可以使用这里的源码:https://github.com/ipjohnson/Grace.MVC。
2. 新建一个类,用于配置注册接口及其实现
1 public class DependencyInjectionScope 2 { 3 public static DependencyInjectionContainer GetContainer() 4 { 5 //容器 6 var container = new DependencyInjectionContainer(); 7 container.Configure(m => 8 { 9 //这里演示如何简单注册同一个接口对应其实现 10 m.Export<AccountRepository>().As<IAccountRepository>(); 11 m.Export<AccountService>().As<IAccountService>(); 12 13 //这里演示如何使用键值注册同一个接口的多个实现 14 m.Export<UserRepositoryA>().AsKeyed<IUserRepository>("A"); 15 //这里同时演示使用带参数构造器来键值注册 16 m.Export<UserRepositoryB>().AsKeyed<IUserRepository>("B").WithCtorParam<string>(() => { return "kkkkk"; }); 17 18 //这里演示依赖倒置而使用构造器带键值注入 19 m.Export<UserService>().As<IUserService>().WithCtorParam<IUserRepository>().LocateWithKey("B"); 20 }); 21 22 return container; 23 } 24 }
3. 在Global.asax.cs中MvcApplication类的Application_Start方法指定MVC的控制器实例工厂为Grace的DisposalScopeControllerActivator,至此配置完毕。
1 public class MvcApplication : System.Web.HttpApplication 2 { 3 protected void Application_Start() 4 { 5 AreaRegistration.RegisterAllAreas(); 6 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 7 RouteConfig.RegisterRoutes(RouteTable.Routes); 8 BundleConfig.RegisterBundles(BundleTable.Bundles); 9 10 //获取注册容器 11 var graceIocContainer = DependencyInjectionScope.GetContainer(); 12 //MVC指定Grace的工厂为控制器实例工厂 13 ControllerBuilder.Current.SetControllerFactory(new DisposalScopeControllerActivator(graceIocContainer)); 14 } 15 }
4. 在控制器中使用方法如下。一般的可以使用构造器参数方式赋值实例,有特殊注入的(如下面带键值的注入)可以先实例IExportLocatorScope出来,再用其Locate来获得实例。
1 public class HomeController : Controller 2 { 3 //IExportLocatorScope可以获取从InjectionScope和LifeTimeScope注入的类型实例 4 IExportLocatorScope locator; 5 6 IAccountRepository accountRepo; 7 IAccountService accountSvc; 8 IUserRepository userRepo; 9 IUserService userSvc; 10 11 public HomeController(IExportLocatorScope locator, IAccountRepository accountRepo, IAccountService accountSvc, IUserService userSvc) 12 { 13 this.locator = locator; 14 //获取简单注册实例 15 this.accountRepo = accountRepo; 16 this.accountSvc = accountSvc; 17 //获取指定键值的实例 18 this.userRepo = this.locator.Locate<IUserRepository>(withKey: "A"); 19 this.userSvc = userSvc; 20 21 } 22 23 public ActionResult Index() 24 { 25 return Json(new 26 { 27 accountRepoGet = accountRepo.Get(), 28 accountSvcGet = accountSvc.Get(), 29 userRepoGet = userRepo.Get(), 30 userSvcGet = userSvc.Get(), 31 }, JsonRequestBehavior.AllowGet); 32 } 35 }
以上代码输出如下:
{ "accountRepoGet": "[Account]简单注册调用[Repo]", "accountSvcGet": "[Account]简单注册调用[Repo][Service]", "userRepoGet": "[User]键值注册调用A[Repo]", "userSvcGet": "[User]键值注册调用B,Ctor param1:kkkkk[Repo][Service]" }
五、多个构造方法
Grace支持多个构造函数,当然是单一构造函数注入的,有相关Tests可以参考:https://github.com/ipjohnson/Grace/blob/master/tests/Grace.Tests/Classes/Simple/MultipleConstructorImport.cs
在以下代码中有两个构造方法,Grace选了最多参数的构造方法进行Import,这里构造方法的继承语句(: this(userSvc))是无关的,用不用都可以。
1 public class MultipleConstructorImportService : IMultipleConstructorImportService 2 { 3 IUserService userSvc; 4 IAccountService accountSvc; 5 public MultipleConstructorImportService(IUserService userSvc) 6 { 7 this.userSvc = userSvc; 8 9 Console.WriteLine($"构造函数1:userSvc:{this.userSvc != null}, accountSvc: {this.accountSvc != null}"); 10 //输出:构造函数1:userSvc:True, accountSvc: False 11 } 12 13 14 public MultipleConstructorImportService(IAccountService accountSvc, IUserService userSvc) 15 : this(userSvc) 16 { 17 //this.userSvc = userSvc; 18 this.accountSvc = accountSvc; 19 20 Console.WriteLine($"构造函数2:userSvc:{this.userSvc != null}, accountSvc: {this.accountSvc != null}"); 21 //输出:构造函数2:userSvc:True, accountSvc: True 22 } 23 24 }
结尾:
Grace的其他特性用法可以参看项目的Tests例子和项目里面的Wiki,后面有时间再慢慢贴上来咯。这里给大家附上文章的源码Demo或者查看GitHub项目