Interpretation of ASP. NET 5 & MVC6 series of tutorials (7): dependency injection, interpretation of ASP. NET
In the previous section (Middleware), we mentioned the Dependency Injection function, ASP. NET 5 officially implements full-featured dependency injection so that developers can develop more flexible component programs, MVC6 also uses the dependency injection function to re-design the service injection functions of Controller and View. In the future, the dependency injection function may provide more APIs, if you haven't started to get started with dependency injection, you have to learn about it.
In previous versions of the dependency injection function, the dependency injection entry is inIControllerFactory
And in Web APIsIHttpControllerActivator
In the new version of ASP. NET5, dependency injection becomes the underlying basic support. MVC, Routing, SignalR, Entity Framrwork, and so on all rely on Injection-dependentIServiceProvider
For this interface, Microsoft provides the default implementationServiceProvider
As well as Ninject and AutoFac packages, you can also use other third-party dependency injection containers, such as Castle Windsor. Once a third-party container is applied, all dependency resolutions are routed to the third-party container.
For the parsing and creation of common dependency types, Microsoft defines four types of lifecycles by default, respectively:
Type |
Description |
Instance |
You can only use a specific instance object at any time. developers need to initialize the object. |
Transient |
Create an instance every time. |
Singleton |
Create a singleton and return the singleton object each time you call it. |
Scoped |
In the current scope, no matter how many calls, it is an instance. If the scope is changed, the instance will be created again, similar to a singleton in a specific role. |
Type registration and Examples
The registration of the dependency injection type is generally in the program Startup entry, such as ConfigureServices in Startup. cs. The main purpose of this class is to register the dependency injection type. Because dependency injection is mainly embodied in interface programming, in this example, I use interfaces and implementation classes as an example.
First, declare an interface ITodoRepository and the implementation class TodoRepository1. The Code is as follows:
public interface ITodoRepository{ IEnumerable<TodoItem> AllItems { get; } void Add(TodoItem item); TodoItem GetById(int id); bool TryDelete(int id);}public class TodoItem{ public int Id { get; set; } public string Name { get; set; }}public class TodoRepository : ITodoRepository{ readonly List<TodoItem> _items = new List<TodoItem>(); public IEnumerable<TodoItem> AllItems { get { return _items; } } public TodoItem GetById(int id) { return _items.FirstOrDefault(x => x.Id == id); } public void Add(TodoItem item) { item.Id = 1 + _items.Max(x => (int?)x.Id) ?? 0; _items.Add(item); } public bool TryDelete(int id) { var item = GetById(id); if (item == null) { return false; } _items.Remove(item); return true; }}
To demonstrate different types of declaration cycles, we recommend that you implement several classes, such as TodoRepository2, TodoRepository3, and TodoRepository4.
Then, register the interface ITodoRepository type and corresponding implementation class in the ConfigureServices method. In this example, different implementation classes are registered according to different lifecycles. The specific example is as follows:
// Register the singleton mode. The example of the ITodoRepository interface in the entire application cycle is a singleton instance services of todorepository1. addSingleton <ITodoRepository, TodoRepository1> (); services. addSingleton (typeof (ITodoRepository), typeof (TodoRepository1); // equivalent form // register a specific instance model, the example of the ITodoRepository interface during the entire application cycle is a fixed initialized Singleton instance TodoRepository2services. addInstance <ITodoRepository> (new TodoRepository2 (); services. addInstance (typeof (ITodoRepository), new TodoRepository2 (); // equivalent form // register the scope type. In a specific scope, the example of ITodoRepository is TodoRepository3services. addScoped <ITodoRepository, TodoRepository3> (); services. addScoped (typeof (ITodoRepository), typeof (TodoRepository3); // equivalent form // when obtaining the ITodoRepository instance, Class 4 services of todorepositorywill be instantiated each time. addTransient <ITodoRepository, TodoRepository4> (); services. addTransient (typeof (ITodoRepository), typeof (TodoRepository); // equivalent form // if the class to be injected has no interface, you can directly inject your own type, such as services. addTransient <LoggingHelper> ();
Currently, dependency injection is used in MVC in three ways: Controller constructor, attribute, and Inject in View. The constructor injection is the same as that in MVC. The sample code is as follows:
Public class TodoController: Controller {private readonly ITodoRepository _ repository; // The dependency injection framework will automatically find the example of the ITodoRepository implementation class and assign it to the constructor public TodoController (ITodoRepository repository) {_ repository = repository;} [HttpGet] public IEnumerable <TodoItem> GetAll () {return _ repository. allItems; // you can use this object here }}
Property injection is performed by adding[FromServices]
To automatically obtain the instance.
Public class TodoController: Controller {// The dependency injection framework will automatically find the ITodoRepository implementation class example and assign the value to this attribute [FromServices] public ITodoRepository Repository {get; set ;} [HttpGet] public IEnumerable <TodoItem> GetAll () {return Repository. allItems ;}}
Note: This method is applicable only to Controller and subclass, and not to common classes.
In addition, you can obtain more system instance objects, suchActionContext
,HttpContext
,HttpRequest
,HttpResponse
,ViewDataDictionary
, AndActionBindingContext
.
In the view, you can use@inject
Keyword to extract injection-type instances, for example:
@using WebApplication1@inject ITodoRepository repository<div> @repository.AllItems.Count()</div>
The most common method is to obtainIServiceProvider
To obtainIServiceProvider
There are currently several instance methods (but the range is different ):
Var provider1 = this. request. httpContext. applicationServices; Servicevar provider2 = Context registered in the current application. requestServices; // Servicevar provider3 = Resolver registered in the current request scope in the Controller; //
The GetService and GetRequiredService methods are used to obtain instances of the specified type, for example:
Var _ repository1 = provider1.GetService (typeof (ITodoRepository); var _ repository2 = provider1.GetService <LoggingHelper> (); // equivalent form // the above two objects may be empty var _ repository3 = provider1.GetRequiredService (typeof (ITodoRepository); var _ repository4 = provider1.GetRequiredService <LoggingHelper> (); // equivalent form // the above two objects are certainly not empty, because if they are empty, an exception is automatically thrown.
Dependency injection for common classes
In the new version of ASP. NET5, it not only supports the interface class dependency injection we mentioned above, but also supports the normal type of dependency injection. For example, we have a common class, for example:
public class AppSettings{ public string SiteTitle { get; set; }}
To ensure that there is no parameter constructor in the above common classes, the registration usage should be as follows:
services.Configure<AppSettings>(app =>{ app.SiteTitle = "111";});
You must obtainIOptions<AppSettings>
Type instance, and its Options attribute is the instance of the AppSettings. The Code is as follows:
var appSettings = app.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Options;
Of course, we can also use@inject
Example code:
@inject IOptions<AppSettings> AppSettings<title>@AppSettings.Options.SiteTitle</title>
Dependency Injection Based on Scope Lifecycle
Normal Scope dependency Injection
When creating a Scope-based instance, you must first create a Scope and then obtain a specific instance in the Scope. Let's look at an example and verify it. First, register the dependency injection type. The Code is as follows:
services.AddScoped<ITodoRepository, TodoRepository>();
Create a scope and obtain the instance in the scope:
Var serviceProvider = Resolver; var scopeFactory = serviceProvider. getService <IServiceScopeFactory> (); // obtain the Scope factory class using (var scope = scopeFactory. createid () // create a Scope {var containerScopedService = serviceProvider. getService <ITodoRepository> (); // obtain the common instance var scopedService1 = scope. serviceProvider. getService <ITodoRepository> (); // gets the Thread of the current Scope instance. sleep (200); var scopedService2 = scope. serviceProvider. getService <ITodoRepository> (); // obtain the instance Console of the current Scope. writeLine (containerScopedService = scopedService1); // output: False Console. writeLine (scopedService1 = scopedService2); // output: True}
In addition, Scope can also be nested, And the instances obtained by nested internal and external scopes are also different. The instance code is as follows:
Var serviceProvider = Resolver; var outerScopeFactory = serviceProvider. getService <IServiceScopeFactory> (); using (var outerScope = outerScopeFactory. createid () // external Scope {var innerScopeFactory = outerScope. serviceProvider. getService <IServiceScopeFactory> (); using (var innerScope = innerScopeFactory. createid () // internal Scope {var outerScopedService = outerScope. serviceProvider. getService <ITodoRepository> (); var innerScopedService = innerScope. serviceProvider. getService <ITodoRepository> (); Console. writeLine (outerScopedService = innerScopedService); // output: False }}
Scope dependency Injection Based on HTTP requests
In many popular DI containers, it is very popular to retain a single instance object in the request scope for each request, that is, during each request, a type of object instance is created only once, which can greatly improve the performance.
In ASP. NET5, Scope dependency Injection Based on HTTP requests is implemented throughContainerMiddleware
When the Middleware is called, a DI container with a limited scope is created to replace the existing default DI container in the current request. In this Pipeline, all subsequent Middleware will use this new DI container. After the request completes the entire Pipeline,ContainerMiddleware
The action ends, the scope will be destroyed, and the instance objects created in the scope will also be destroyed and released.
ContainerMiddleware
The sequence diagram is as follows:
The usage is as follows:
app.Use(new Func<RequestDelegate, RequestDelegate>(nextApp => new ContainerMiddleware(nextApp, app.ApplicationServices).Invoke));
Dependency injection for common classes
Currently, only constructor functions are supported for dependency injection of common classes. For example, we must setTestService
Class, the Code is as follows:
public class TestService{ private ITodoRepository _repository; public TestService(ITodoRepository r) { _repository = r; } public void Show() { Console.WriteLine(_repository.AllItems); }}
Passed in the constructorITodoRepository
Class parameters to use this instance. When using this instance, you must first register the class to the DI container. The Code is as follows:
services.AddScoped<ITodoRepository, TodoRepository>();services.AddSingleton<TestService>();
Then, you can use the following statement:
var service = serviceProvider.GetRequiredService<TestService>();
In addition, you must note that you cannot use[FromServices]
To use the dependency injection function. For exampleTestService2
An error occurs during the instance process:
public class TestService2{ [FromServices] public ITodoRepository Repository { get; set; } public void Show() { Console.WriteLine(Repository.AllItems); }}
Get HttpContext instance in common class
In MVC6, we cannot use HttpContent. current to get the context object, so when using it in a common class, there will be a problem. To use this context object in a common class, you need to get the HttpContext instance through dependency injection, microsoft in ASP. in NET5IHttpContextAccessor
Interface is used to obtain the context object. That is to say, we can put this type of parameter in the constructor to obtain the context instance. The Code is as follows:
Public class TestService3 {private partition _ httpContextAccessor; public TestService3 (export httpContextAccessor) {_ httpContextAccessor = httpContextAccessor;} public void Show () {var httpContext = _ httpContextAccessor. httpContext; // obtain the context object instance Console. writeLine (httpContext. request. host. value );}}
In use, you can use the following statement directly. The Code is as follows:
var service = serviceProvider.GetRequiredService<TestService3>();service.Show();
Tip: In the constructor of a common class, data supported by multiple DI containers can be imported as a parameter.
Use a third-party DI container
Currently,. NETCore is not supported and can only be used on the. NET framework of the Full-function version. Therefore, pay attention to it when using it. Third-party DI container replacement is usually carried out in the Configure method of Startup. cs, and is replaced at the beginning of the method, so that later Middleware will use the relevant dependency injection function.
First, introduce a third-party container. Take Autofac as an example, introduce Microsoft. Framework. DependencyInjection. Autofac, and then add the replacement code in the following example:
App. useServices (services => {services. addMvc (); // AddMvc should register var builder = new ContainerBuilder () here; // construct the container build class builder. populate (services); // routes existing Services to the IContainer container = builder in the Autofac management set. build (); return container. resolve <IServiceProvider> (); // returns the IServiceProvider implemented by AutoFac });
Note: when using the above method, you must register the Mvc code.services.AddMvc();
Must beConfigureServices
To this expression. Otherwise, an exception will be reported, waiting for Microsoft to solve the problem.
In addition, there is another way that Microsoft's current instance project has not yet been made public. By analyzing some code, we can find that inMicrosoft.AspNet.Hosting
In the programStartupLoader.cs
Executes the program entry point. In this file, we know that the first step is to callStartup.cs
InConfigureServices
Method, and then callConfigure
Method.ConfigureServices
The return value is of the void type. However, in the source code analysis, it is found thatConfigureServices
Method, it first determines whether the return type isIServiceProvider
If yes, execute this method and use the newIServiceProvider
Instance; if not, continue searchingvoid
TypeConfigureServices
Method. Therefore, we can replace the third-party DI container in this way. The instance code is as follows:
// Delete the void ConfigureServices method public IServiceProvider ConfigureServices (IServiceCollection services) {var builder = new ContainerBuilder (); // construct the container build class builder. populate (services); // routes existing Services to the IContainer container = builder in the Autofac management set. build (); return container. resolve <IServiceProvider> (); // returns the IServiceProvider implemented by AutoFac}
In this way, you can use the Autofac method to manage dependency types, as in the past. The example is as follows:
public class AutofacModule : Module{ protected override void Load(ContainerBuilder builder) { builder.Register(c => new Logger()) .As<ILogger>() .InstancePerLifetimeScope(); builder.Register(c => new ValuesService(c.Resolve<ILogger>())) .As<IValuesService>() .InstancePerLifetimeScope(); }}
Address: https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs
Another case about Autofac integration: http://alexmg.com/autofac-4-0-alpha-1-for-asp-net-5-0-beta-3/
Best practices
When using dependency injection, we should follow the following best practices.
Before doing anything, register all the dependency types in advance at the program entry point. Avoid directly using the IServiceProvider interface. On the contrary, explicitly add the type of dependency to the constructor so that the dependency injection engine can parse the instance by itself. Once dependency is difficult to manage, the abstract factory is used. Programming Based on interfaces rather than implementation.
Reference 1: http://social.technet.microsoft.com/wiki/contents/articles/28875.dependency-injection-in-asp-net-vnext.aspx
Reference 2: http://blogs.msdn.com/ B /webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx