当前位置: 移动技术网 > IT编程>开发语言>.net > ABP框架的基础配置及依赖注入讲解

ABP框架的基础配置及依赖注入讲解

2017年12月12日  | 移动技术网IT编程  | 我要评论

亚洲杯赛程表,邹锡昌,魅力研习社155

配置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来得到配置的引用。如下代码:

2016615173022790.png (920×215)

译者注:模块配置是一个静态类,因为我们需要重复使用它。静态方法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 也是相似的。你不用关心对象的创建和释放。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网