<ABP framework> dependency injection: the content of the TTL framework depends on injection.
Document directory
Content of this section:
- When dependency Injection
- Traditional problems
- Solution
- Constructor injection mode
- Attribute injection mode
- Dependency injection framework
- ABC dependency injection Basics
- Register Dependencies
- Convention Injection
- Secondary interface
- Custom/direct registration
- Use IocManager
- Use the Castle Windsor API
- Analysis
- Constructor and property Injection
- IIocResolver and IIocManager
- In addition
- IShouldInitialize Interface
- Integration of Asp.net Mvc and Asp.net Web APIs
- Asp.net Core integration
- Last reminder
What is dependency injection?
If you already know the concept of dependency injection, constructor, and property injection, you can jump to the next topic.
Wikipedia: "dependency injection is a software design model. A dependent object (or client) requires one or more dependencies (or services) to be injected, or a reference is passed in as part of its status. This mode separates the creation of client Dependencies from the client behavior, which makes the program design more loose and conforms to the dependency inversion and single responsibility principle. It is compared with the service locator mode that the client understands the dependency ."
Without dependency injection technology, it is difficult to manage dependencies and develop a module and well-structured application.
Traditional problems
In an application, classes are mutually dependent. Suppose we have an application service that uses warehousing to insert entities to the database. In this case, the application service class depends on the storage class, as shown below:
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 uses PersonReopsitory to insert a Person to the database. Question about this Code:
- This service uses IPersonRepository reference in the CreatePerson method, so this method relies on IPersonRepository. Although it replaces the PersonRepository class, the service still relies on PersonRepository in its constructor. Components should depend on interfaces and rely on implementation. This is the principle of dependency inversion.
- If the service creates a PersonRepository by itself, it can only be used in a specific implementation of IPersonRepository and cannot be used in other implementations of the interface. Therefore, separating the interface from the implementation becomes meaningless. Hard dependency causes tight code usage and low reusability.
- In the future, we may need to change the creation of PersonRepository. Suppose we want to change it to a single instance (only one instance is used instead of creating one instance at a time ), or you want to create multiple IPersonRepository classes and select one based on the conditions. In this case, you need to modify all the classes dependent on IPersonRepository.
- In this dependency, It is difficult (or even impossible) to test the service unit.
To overcome these problems, you can use the factory model. Abstract The creation of warehousing. The Code is as follows:
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 is a static class that creates and returns an IPersonRepository. This is a well-known service locator model. The problem is solved. The PersonAppService does not know how to create IPersonRepository, nor does it rely on PersonRepository. But there are still some problems:
- In this case, the PersonAppService depends on the PersonRepositoryFactory, which is slightly acceptable but still hard-dependent.
- Writing a factory/method for each warehouse or dependency is boring.
- It is still difficult to test because it is difficult for PersonAppService to use the implementation of IPersonRepository simulation.
Solution
There are several best practices (modes) for dependency injection ).
Constructor injection mode
The sample code above can be rewritten as follows:
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); }}
This is the well-known constructor injection mode. In this case, the PersonAppService Code does not know which class implements IPersonRepository and how to create it. If you want to use the PersonAppService, first create an IpersonRepository and pass it to the constructor of the PersonAppService, as shown below:
var repository = new PersonRepository();var personService = new PersonAppService(repository);personService.CreatePerson("Yunus Emre", 19);
Constructor injection is a perfect way to create a class independent of the creation of dependent objects. However, the above Code also has some problems:
- It is difficult to create a PersonAppService. Considering that there are four dependencies, we must create these four dependent objects and pass them into the PersonAppService constructor.
- Dependency classes may have other Dependencies (here, PersonRepository may have other Dependencies). Therefore, we must create dependencies for all PersonAppService and dependencies. In this case ..., in this case, we cannot even create a separate object because the dependency path is too complex.
Fortunately, the dependency injection framework can automatically manage dependencies.
Attribute injection mode
Constructor injection provides a perfect method for injection of class dependencies. In this way, you cannot create an instance that does not provide dependent classes, similarly, it is a strong method in which all of its needs must be explicitly declared to work correctly.
However, in some cases, some classes depend on other classes, but they can also work without providing dependencies, which is often encountered in cross-cutting concerns (such as logs, A class can work without logs, but it can also write logs when you provide a logger to it. In this case, you can define a public dependency attribute instead of the constructor. To write a log in the PersonAppService, you can change it as follows:
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 is a singleton object that implements ILogger, but does nothing (without writing logs. It uses an empty method body to implement ILogger ). Therefore, if you set a logger for the PersonAppService object after creating it, The PersonAppService can write logs as follows:
var personService = new PersonAppService(new PersonRepository());personService.Logger = new Log4NetLogger();personService.CreatePerson("Yunus Emre", 19);
Assume that Log4NetLogger implements ILogger and uses its Log4Net library to write logs. Therefore, PersonAppService can write logs. If we do not set a logger, it will not write logs. Therefore, we can say that the ILogger of the PersonAppService is an optional dependency.
Almost all dependency injection frameworks support property injection.
Dependency injection framework
There are many dependency injection frameworks that can automatically parse dependencies. They can create all dependent objects (recursive dependencies), so you only need to write the constructor or attribute injection mode, the DI (dependency inversion) framework handles the rest of the work. Your class can even be independent of the DI framework. In your entire application, there are only a few lines of code or classes that explicitly interact with the DI framework.
ABP uses Castle Windsor as the dependency injection framework. It is the most mature DI framework. There are many other frameworks, such as Unity, Ninject, StructureMap, and Autofac.
When using the dependency injection framework, you must first register your interfaces/classes to the dependency injection framework, and then you can parse (create) an object. In Castle windsor, the code is similar to the following:
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);
We first create a WindsorContainer container, and then use their interfaces to register the PersonRepository and PersonAppService. Then we ask the container to create an IPersonAppService, which will create and return the PersonAppService with the dependency. Using the DI framework in this simple example may not clearly show the benefits, but consider if you encounter many classes and dependencies in a real enterprise application, in this case, the situation is different. Of course, you can register dependencies elsewhere before use, or you can register dependencies only once when an application is started.
When registering an object, we declare the object life cycle as a short (transient), which means that a new instance will be created when we parse this type of object. There are also some other different lifecycles (such as Singleton ).
ABC dependency injection Basics
When you write your application according to the best practices and some conventions, you can use the dependency injection framework almost implicitly.
Register Dependencies
There are a variety of different methods in the ABC to register the class into the dependency injection system. In most cases, registration is sufficient.
Agreed Registration
The ABP automatically registers all warehousing, domain services, application services, Mvc controllers, and Web Api controllers as agreed. For example, you have an IPersonAppService interface and a PersonAppService class that implements this interface:
public interface IPersonAppService : IApplicationService{ //...}public class PersonAppService : IPersonAppService{ //...}
It is automatically registered because it implements the IApplicationService interface (empty interface ). Register as temporary (create an instance for each instance ). When you inject the IPersonAppService interface (injected with the constructor) to a class, a PersonAppService object is created and automatically passed in to the constructor.
Naming Conventions: it is very important. For example, you can change the PersonAppService to MyPersonAppService or another name suffixed with "PersonAppService". Because IPersonAppService is also the suffix, there is no problem. However, you cannot name it "service". If you do so, IPersonAppService cannot be automatically registered (self-registered to the ID framework, rather than using interfaces). Therefore, you can only register manually.
You can register an Assembly according to the Conventions. You can tell the ABP to register your Assembly according to the Conventions, which is quite easy:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Aseembly. GetExecutingAssembly () gets a reference to the Assembly containing this code. You can also pass another assembly to the RegisterAssemblyByConvention method. In general, these tasks are completed when a module starts initialization. For more information, see the module system.
You can implement the IConventionalRegisterer interface, write your own agreed registration class, and then call IocManager. AddConventionalRegisterer in the pre-initialization of your module to add your class.
Secondary interface
You may want to register a specific class that does not comply with the agreed registration rules. The ABP provides shortcuts: ITransientDependency and ISingletonDependency interfaces. For example:
public interface IPersonManager{ //...}public class MyPersonManager : IPersonManager, ISingletonDependency{ //...}
In this way, you can easily register MyPersonManager. When an IPersonManager needs to be injected, MyPersonManager is used. Note that the dependency is declared as a singleton. Therefore, only one instance of MyPersonManager is created and the same instance is passed to all classes that require it. It is created for the first time and used throughout the application lifecycle.
Custom/direct registration
If the agreed registration does not fully suit your situation, you can use IocManager or Castle Windsor to register your class and dependencies.
Use IocManager
You can use IocManager to register dependencies (usually in the pre-initialization of your module definition class ):
IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);
Use the Castle Windsor API
You can use the IIocManger. IocContainer attribute to access the Castle Windsor container and register dependencies. For example:
IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
For more information, see the Windsor documentation.
Analysis
In a place where your application needs to use the IOC (control reversal) container (also called DI framework) to create an object, register your class, dependency, and life cycle to inform the IOC container. You can use the following methods to resolve the problem.
Constructor and property Injection
Best Practice: You can use the constructor and property injection to obtain the required dependencies for your class. You should use this method whenever possible. For example:
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 is injected from the constructor, and ILogger is injected from public attributes. In this way, your code is completely unaware of the dependency injection system. This is the most appropriate way to use the DI system.
IIocResolver and IIocManager
You may need to directly parse your dependencies to replace the constructor and property injection. This should be avoided as much as possible, but sometimes it is inevitable. ABP provides some easy-to-use injection services, such:
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" }); } }}
An example of MySampleClass in an application. It injects IIocResolver with the constructor and uses it to parse and release objects. There are several reloads in the Resolve method that can be used for parsing, and Release is used to Release components (objects ). If you manually parse an object, remember to call Release. Otherwise, your application may suffer memory leakage. To ensure that the object is released, try to use ResolveAsDisposable (as shown in the preceding example). It automatically calls Release at the end of the using block.
If you want to directly use the IOC container (Castle Windsor) to parse dependencies, you can use the constructor to inject IIocManager and use the IIocManager. IocContainer attribute. If you are in a static context or cannot inject IIocManager, the final choice is: You can use the single-instance object IocManager anywhere. instance, but using your code in this way is not easy to test.
In addition
IShouldInitialize Interface
Some Classes need to be initialized before they are used for the first time. IShouldInitialize has an Initialize method. If you implement it, after creating your object (before use) the Initialize method is automatically called. Of course, you should inject/parse this object so that this feature works.
Integration of Asp.net Mvc and Asp.net Web APIs
We must call the dependency Injection System to parse the root object on the dependency graph. In an Asp.net Mvc application, it is usually a Controller class. We can also use the constructor injection and attribute injection modes in the controller. When a request arrives at our application, use the IOC container to create the controller and recursive resolution of all dependencies. So who will do this? The default controller factory is automatically implemented by the ABC through the extension Mvc. Similarly, the same is true for Asp.net Web APIs. In addition, you do not need to create or destroy objects in a link.
Asp.net Core integration
Omitted
Last reminder
As long as you follow the rules and structure above, you can simplify and automatically use the dependency injection. In most cases, you don't need to do more, but as long as you need it, you can directly use all the capabilities of Castle Windsor to execute any task (such as custom registration, injection hooks, interceptors, and so on ).