视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
ABP框架的基础配置及依赖注入讲解
2020-11-27 22:37:13 责编:小采
文档


配置ABP
配置是通过在自己模块的PreInitialize方法中来实现的
代码示例如下:

public class SimpleTaskSystemModule : AbpModule
{
 public override void PreInitialize()
 {
 //在你的应用中添加语言包,这个是英语和作者的土耳其语。
 Configuration.Localization.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flag-england", true));
 Configuration.Localization.Languages.Add(new LanguageInfo("tr", "Türkçe", "famfamfam-flag-tr"));

 Configuration.Localization.Sources.Add(
 new XmlLocalizationSource(
 "SimpleTaskSystem",
 HttpContext.Current.Server.MapPath("~/Localization/SimpleTaskSystem")
 )
 );

 //配置导航和菜单
 Configuration.Navigation.Providers.Add<SimpleTaskSystemNavigationProvider>();
 }

 public override void Initialize()
 {
 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
 }
}

和orchard类似,abp框架一开始就被设计成模块化的,不同的模块可以通过abp框架来进行配置。举个例子吧,不同的模块都可以添加导航,通过导航添加菜单项到自己定义的主菜单,具体的细节大家可以参照:

本地化:http://www.aspnetboilerplate.com/Pages/Documents/Localization
导航:http://www.aspnetboilerplate.com/Pages/Documents/Navigation

配置模块
和.net框架原生的启动配置相比较,abp有哪些不一样呢?abp框架的模块可以通过IAbpModuleConfigurations接口进行个性化的扩展,这样的话,模块配置更加简单、方便。
示例代码如下:

...
using Abp.Web.Configuration;
...
public override void PreInitialize() 
{
 Configuration.Modules.AbpWeb().SendAllExceptionsToClients = true;
}
...

在上面这个例子中,我们通过配置AbpWeb模块,发送异常到客户端。当然了,不是每一个模块都需要这种配置,通常情况下我们需要,是当一个模块需要在多个不同的应用中重复使用,我们才进行这样的配置。

为一个模块创建配置
如下代码,假如我们有一个命名为MyModule的模块,并且这各模块有一些自己的配置。那么我们首先要创建一些类,这些类定义为属性(译者注:属性有自动的get和set访问器。),代表了不同的配置。

public class MyModuleConfig
{
 public bool SampleConfig1 { get; set; }

 public string SampleConfig2 { get; set; }
}

接下来,我们通过依赖注入,注册这个类。

IocManager.Register<MyModuleConfig>(); //译者注:在IocManager中注册了一个类,换句话说,我们通过IocManager可以得到这个类MyModuleConfig的实例。至于IOC的原理这里就不在详细说了,总之,就是可以得到一个类的实例。

最后,我们通过创建一个扩展的方法IModuleConfigurations来得到配置的引用。如下代码:

译者注:模块配置是一个静态类,因为我们需要重复使用它。静态方法Mymodule返回的是一个配置接口,参数是ImoduleConfigurations接口。

现在,在其他模块中也可以配置我们自定义的这个MyModule模块了。

Configuration.Modules.MyModule().SampleConfig1 = false;
Configuration.Modules.MyModule().SampleConfig2 = "test";

在某种意义上,MyModule需要这些配置,你能注射MyModuleConfig并且可以使用这些值。

public class MyService : ITransientDependency
{
 private readonly MyModuleConfig _configuration;

 public MyService(MyModuleConfig configuration)
 {
 _configuration = configuration;
 }

 
 public void DoIt()
 {
 if (_configuration.SampleConfig2 == "test")
 {
 //...
 }
 }
}

这意味着,在abp框架的系统中,所有的模块都可以集中配置。


ABP依赖注入
什么是依赖注入
如果你已经知道依赖注入的概念,构造函数和属性注入模式,你可以跳过这一节。

维基百科说:“依赖注入是一种软件设计模式的一个或多个依赖项注入(或服务),或通过引用传递,为依赖对象(或客户)和客户端状态的一部分。模式之间建立一个客户的依赖关系的行为,它允许程序设计是松散耦合的,依赖倒置和单一职责原则。它直接对比service locator模式,它允许客户了解他们所使用的系统找到依赖。”。

如果不使用依赖注入技术,很难进行依赖管理、模块化开发和应用程序模块化。

传统方式的问题

在一个应用程序中,类之间相互依赖。假设我们有一个应用程序服务,使用仓储(repository)类插入实体到数据库。在这种情况下,应用程序服务类依赖于仓储(repository)类。看下例子:

 public class PersonAppService
 {
 private IPersonRepository _personRepository;
 
 public PersonAppService()
 {
 _personRepository = new PersonRepository(); 
 }
 
 public void CreatePerson(string name, int age)
 {
 var person = new Person { Name = name, Age = age };
 _personRepository.Insert(person);
 }
 }

PersonAppService使用PersonRepository插入Person到数据库。这段代码的问题:

PersonAppService通过IPersonRepository调用CreatePerson方法,所以这方法依赖于IPersonRepository,代替了PersonRepository具体类。但PersonAppService(的构造函数)仍然依赖于PersonRepository。组件应该依赖于接口而不是实现。这就是所谓的依赖性倒置原则。
如果PersonAppService创建PersonRepository本身,它成为依赖IPersonRepository接口的具体实现,不能使用另一个实现。因此,此方式的将接口与实现分离变得毫无意义。硬依赖(hard-dependency)使得代码紧密耦合和较低的可重用。
我们可能需要在未来改变创建PersonRepository的方式。即,我们可能想让它创建为单例(单一共享实例而不是为每个使用创建一个对象)。或者我们可能想要创建多个类实现IPersonRepository并根据条件创建对象。在这种情况下,我们需要修改所有依赖于IPersonRepository的类。
有了这样的依赖,很难(或不可能)对PersonAppService进行单元测试。
为了克服这些问题,可以使用工厂模式。因此,创建的仓储类是抽象的。看下面的代码:
 

 public class PersonAppService
 {
 private IPersonRepository _personRepository;
 
 public PersonAppService()
 {
 _personRepository = PersonRepositoryFactory.Create(); 
 }
 
 public void CreatePerson(string name, int age)
 {
 var person = new Person { Name = name, Age = age };
 _personRepository.Insert(person);
 }
 }

PersonRepositoryFactory是一个静态类,创建并返回一个IPersonRepository。这就是所谓的服务定位器模式。以上依赖问题得到解决,因为PersonAppService不需要创建一个IPersonRepository的实现的对象,这个对象取决于PersonRepositoryFactory的Create方法。但是,仍然存在一些问题:

此时,PersonAppService取决于PersonRepositoryFactory。这是更容易接受,但仍有一个硬依赖(hard-dependency)。
为每个库或每个依赖项乏味的写一个工厂类/方法。
测试性依然不好,由于很难使得PersonAppService使用mock实现IPersonRepository。
解决方案:

有一些最佳实践(模式)用于类依赖。

构造函数注入

重写上面的例子,如下所示:

 public class PersonAppService
 {
 private IPersonRepository _personRepository;
 
 public PersonAppService(IPersonRepository personRepository)
 {
 _personRepository = personRepository;
 }
 
 public void CreatePerson(string name, int age)
 {
 var person = new Person { Name = name, Age = age };
 _personRepository.Insert(person);
 }
 }

这被称为构造函数注入。现在,PersonAppService不知道哪些类实现IPersonRepository以及如何创建它。谁需要使用PersonAppService,首先创建一个IPersonRepository PersonAppService并将其传递给构造函数,如下所示:

 var repository = new PersonRepository();
 var personService = new PersonAppService(repository);
 personService.CreatePerson("Yunus Emre", 19);

构造函数注入是一个完美的方法,使一个类创建依赖对象。但是,上面的代码有一些问题:

创建一个PersonAppService变得困难。想想如果它有4个依赖,我们必须创建这四个依赖对象,并将它们传递到构造函数PersonAppService。
从属类可能有其他依赖项(在这里,PersonRepository可能有依赖关系)。所以,我们必须创建PersonAppService的所有依赖项,所有依赖项的依赖关系等等. .如此,依赖关系使得我们创建一个对象变得过于复杂了。
幸运的是,依赖注入框架自动化管理依赖关系。

属性注入

构造函数注入模式是一个完美的提供类的依赖关系的方式。通过这种方式,您不能创建类的实例,而不提供依赖项。它也是一个强大的方式显式地声明是什么类的需求正确地工作。

但是,在某些情况下,该类依赖于另一个类,但也可以没有它。这通常是适用于横切关注点(如日志记录)。一个类可以没有工作日志,但它可以写日志如果你提供一个日志对象。在这种情况下,您可以定义依赖为公共属性,而不是让他们放在构造函数。想想,如果我们想在PersonAppService写日志。我们可以重写类如下:  

public class PersonAppService
 {
 public ILogger Logger { get; set; }
 
 private IPersonRepository _personRepository;
 
 public PersonAppService(IPersonRepository personRepository)
 {
 _personRepository = personRepository;
 Logger = NullLogger.Instance;
 }
 
 public void CreatePerson(string name, int age)
 {
 Logger.Debug("Inserting a new person to database with name = " + name);
 var person = new Person { Name = name, Age = age };
 _personRepository.Insert(person);
 Logger.Debug("Successfully inserted!");
 }
 }

NullLogger.Instance 是一个单例对象,实现了ILogger接口,但实际上什么都没做(不写日志。它实现了ILogger实例,且方法体为空)。现在,PersonAppService可以写日志了,如果你为PersonAppService实例设置了Logger,如下面:

 var personService = new PersonAppService(new PersonRepository());
 personService.Logger = new Log4NetLogger();
 personService.CreatePerson("Yunus Emre", 19);

假设Log4NetLogger实现ILogger实例,使得我们可以使用Log4Net库写日志。因此,PersonAppService可以写日志。如果我们不设置Logger,PersonAppService就不写日志。因此,我们可以说PersonAppService ILogger实例是一个可选的依赖。

几乎所有的依赖注入框架都支持属性注入模式

依赖注入框架

有许多依赖注入框架,都可以自动解决依赖关系。他们可以创建所有依赖项(递归地依赖和依赖关系)。所以你只需要根据注入模式写类和类构造函数&属性,其他的交给DI框架处理!在良好的应用程序中,类甚至于DI框架。整个应用程序只会有几行代码或类,显示的与DI框架交互。

ABP的依赖注入基于 Castle Windsor框架。Castle Windsor最成熟的DI框架之一。还有很多这样的框架,如Unity,Ninject,StructureMap,Autofac等等。

在使用一个依赖注入框架时,首先注册您的接口/类到依赖注入框架中,然后你就可以resolve一个对象。在Castle Windsor,它是这样的:

 var container = new WindsorContainer();

 container.Register(
 Component.For<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(),
 Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient()
 );
 
 var personService = container.Resolve<IPersonAppService>();
 personService.CreatePerson("Yunus Emre", 19);

我们首先创建了WindsorContainer。然后注册PersonRepository 和 PersonAppService及它们的接口。然后我们要求容器创建一个IPersonAppService实例。它创建PersonAppService对象及其依赖项并返回。在这个简单的示例中,使用DI框架也许不是那么简洁,但想象下,在实际的企业应用程序中你会有很多类和依赖关系。当然,注册的依赖项只在程序启动的某个地方创建一次。

请注意,我们只是讲对象声明为临时对象(transient)。这意味着每当我们创建这些类型的一个对象时,就会创建一个新的实例。有许多不同的生命周期(如Singletion)。

ABP依赖注入的基础结构
在编写应用程序时遵循最佳实践和一些约定,ABP几乎让依赖注入框架使用变得无形。

注册:

在ABP中,有很多种不同的方法来注册你的类到依赖注入系统。大部分时间,常规方法就足够了。

常规注册:

按照约定,ABP自动注册所有 Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。例如,您可能有一个IPersonAppService 接口和实现类PersonAppService:

 public interface IPersonAppService : IApplicationService
 {
 //...
 }
 
 public class PersonAppService : IPersonAppService
 {
 //...
 }

ABP会自动注册它,因为它实现IApplicationService接口(它只是一个空的接口)。它会被注册为transient (每次使用都创建实例)。当你注入(使用构造函数注入)IPersonAppService接口成一个类,PersonAppService对象会被自动创建并传递给构造函数。

命名约定在这里非常重要。例如你可以将名字PersonAppService改为 MyPersonAppService或另一个包含“PersonAppService”后缀的名称,由于IPersonAppService包含这个后缀。但是你可以不遵循PeopleService命名您的服务类。如果你这样做,它将不会为IPersonAppService自动注册(它需要自注册(self-registration)到DI框架,而不是接口),所以,如果你想要你应该手动注册它。

ABP按照约定注册程序集。所以,你应该告诉ABP按照约定注册您的程序集。这很容易:

 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

Assembly.GetExecutingAssembly()得到一个对包括此代码的程序集的引用。你可以通过RegisterAssemblyByConvention方法注册其他程序集。这同在你的模块初始化(AbpModule.Initialize())时完成。请查看ABP的模块系统获得更多信息。

您可以通过实现IConventionalRegisterer接口和调用IocManager。AddConventionalRegisterer方法编写自己的约定注册类。你应该将它添加到模块的pre-initialize方法中。

帮助接口

你可以注册一个特定的类,不遵循传统的约定制度规则。ABP提供了ITransientDependency和ISingletonDependency接口的快捷方法。例如:

 public interface IPersonManager
 {
 //...
 }
 
 public class MyPersonManager : IPersonManager, ISingletonDependency
 {
 //...
 }

以这种方式,您可以很容易地注册MyPersonManager为transient。当需要注入IPersonManager时,MyPersonManager会被使用。注意,依赖被声明为单例。因此,创建的MyPersonManager同一个对象被传递给所有需要的类。只是在第一次使用时创建,那么应用程序的整生命周期使用的是同一实例。

自定义/直接 注册

如果之前描述的方法还是不足以应对你的情况,你可以使用Castle Windsor注册类和及依赖项。因此,您将拥有Castle Windsor注册的所有能力。

可以实现IWindsorInstaller接口进行注册。您可以在应用程序中创建一个实现IWindsorInstaller接口的类:

 public class MyInstaller : IWindsorInstaller
 {
 public void Install(IWindsorContainer container, IConfigurationStore store)
 {
 container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
 }
 }

Abp自动发现和执行这个类。最后,你可以通过使用IIocManager.IocContainer属性得到WindsorContainer。有关更多信息,阅读Windsor的文档。

解析(Resolving)

注册通知IOC(控制反转)容器关于你的类,它们的依赖项和生命周期。在您的应用程序需要使用IOC容器创建对象时,ASP.NET提供了一些方法解决依赖关系。

构造函数 & 属性注入

作为最佳实践,你可以使用构造函数和属性注入去获取你的类的依赖。任何可能的地方,你都应该这样做。例子:

 public class PersonAppService
 {
 public ILogger Logger { get; set; }
 
 private IPersonRepository _personRepository;
 
 public PersonAppService(IPersonRepository personRepository)
 {
 _personRepository = personRepository;
 Logger = NullLogger.Instance;
 }
 
 public void CreatePerson(string name, int age)
 {
 Logger.Debug("Inserting a new person to database with name = " + name);
 var person = new Person { Name = name, Age = age };
 _personRepository.Insert(person);
 Logger.Debug("Successfully inserted!");
 }
 }

IPersonRepository从构造函数注入,ILogger实例从公共属性注入。这样,您的代码不会体现依赖注入系统。这是使用DI系统最适当的方式。

IIocResolver 和 IIocManager

有时你可能需要直接创建你的依赖项,而不是构造函数和属性注入。应该尽可能避免这种情况,但它可能无法避免。Abp提供一些服务使得这样的注入很容易实现。例子:

  

 public class MySampleClass : ITransientDependency
 {
 private readonly IIocResolver _iocResolver;
 
 public MySampleClass(IIocResolver iocResolver)
 {
 _iocResolver = iocResolver;
 }
 
 public void DoIt()
 {
 //Resolving, using and releasing manually
 var personService1 = _iocResolver.Resolve<PersonAppService>();
 personService1.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
 _iocResolver.Release(personService1);
 
 //Resolving and using in a safe way
 using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>())
 {
 personService2.Object.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
 }
 }
 }

MySampleClass是一个应用程序的示例类。IIcResolver通过构造函数注入,然后用它来创建和释放对象。有几个解决方法的重载可以根据需要使用。Release方法用于释放组件(对象)。如果你是手动创建一个对象,调用Release方法释放对象非常重要。否则,您的应用程序会有内存泄漏问题。为了保证对象被释放,尽可能使用ResolveAsDisposable(就像上面的例子所示)。它会在using代码块结束的时候自动调用Release方法。

如果你想直接使用IOC容器(Castle Windsor)来处理依赖关系项,可以通过构造函数注入 IIocManager并使用它IIocManager.IocContainer 属性。如果你是在一个静态上下文或不能注入IIocManager,还有最后一个方法,你可以使用单例对象IocManager.Instance,你可以在任何地方获取到,它无处不在。但是,在这种情况下你的代码将变得不易容测试。

附加

IShouldInitialize 接口:

有些类在第一次使用前需要初始化。IShouldInitialize有Initialize()方法。如果你实现它,那么你的Initialize()方法自动会被自动调用在创建对象之后(在使用之前)。当然,为了使用这个特性,你应该注入/创建此对象。

ASP.NET MVC & ASP.NET Web API 集成:

当然,我们必须调用依赖注入系统处理依赖关系图的根对象。在一个ASP.NET MVC应用程序,通常是一个控制器类。我们可以使用构造函数注入模式注入控制器。当一个请求来到我们的应用程序中,控制器和所有依赖项被IOC容器递归创建。所以,谁做了这些?这是被Abp扩展的ASP.NET MVC默认控制器工厂自动完成的。ASP.NET Web API 也是相似的。你不用关心对象的创建和释放。

下载本文
显示全文
专题