Dependency injection in ASP.NET Core

原文

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

View or download sample code (how to download)

A dependency is any object that another object requires

The MyDependency class is a dependency of the IndexModel class:

public class IndexModel : PageModel
{
    MyDependency _dependency = new MyDependency();

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

The class creates and directly depends on the MyDependency instance :错误实现方式

why?

Code dependencies (such as the previous example) are problematic and should be avoided for the following reasons:

  • To replace MyDependency with a different implementation, the class must be modified.
  • If MyDependency has dependencies, they must be configured by the class. In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • This implementation is difficult to unit test. The app should use a mock or stub MyDependency class, which isn‘t possible with this approach.

如何解决?

Dependency injection addresses these problems through:

  • The use of an interface to abstract the dependency implementation.
  • Registration of the dependency in a service container. ASP.NET Core provides a built-in service container, IServiceProvider. Services are registered in the app‘s Startup.ConfigureServices method.
  • Injection of the service into the constructor of the class where it‘s used. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it‘s no longer needed.

It‘s not unusual to use dependency injection in a chained fashion.

Each requested dependency in turn requests its own dependencies.

The container resolves the dependencies in the graph and returns the fully resolved service. The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

ILogger<TCategoryName> is registered by the logging abstractions infrastructure, so it‘s a framework-provided service registered by default by the framework.

The registration scopes the service lifetime to the lifetime of a single request.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));

    // OperationService depends on each of the other Operation types.
    services.AddTransient<OperationService, OperationService>();
}

  

Each services.Add{SERVICE_NAME} extension method adds (and potentially configures) services.

For example, services.AddMvc() adds the services Razor Pages and MVC require.

We recommended that apps follow this convention.

Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

If the service‘s constructor requires a primitive, such as a string, the primitive can be injected by using configuration or the options pattern

An instance of the service is requested via the constructor of a class where the service is used and assigned to a private field. The field is used to access the service as necessary throughout the class.

Framework-provided services

The Startup.ConfigureServices method is responsible for defining the services the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC.

Initially, the IServiceCollection provided to ConfigureServices has the following services defined (depending on how the host was configured):

Service Type Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient
Microsoft.AspNetCore.Hosting.IApplicationLifetime Singleton
Microsoft.AspNetCore.Hosting.IHostingEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient
Microsoft.Extensions.Logging.ILogger<T> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<T> Transient
Microsoft.Extensions.Options.IOptions<T> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

When a service collection extension method is available to register a service (and its dependent services, if required), the convention is to use a single Add{SERVICE_NAME} extension method to register all of the services required by that service.

The following code is an example of how to add additional services to the container using the extension methods AddDbContext, AddIdentity, and AddMvc:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();
}

For more information, see the ServiceCollection Class in the API documentation.

Service lifetimes

Choose an appropriate lifetime for each registered service.

ASP.NET Core services can be configured with the following lifetimes:

  • Transient

    Transient lifetime services are created each time they‘re requested. This lifetime works best for lightweight, stateless services.

  • Scoped

    Scoped lifetime services are created once per request.

   When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Don‘t inject via constructor injection  because it forces the service to behave like a singleton. For more information, see ASP.NET Core Middleware.

  • Singleton

    Singleton lifetime services are created the first time they‘re requested (or when ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance. If the app requires singleton behavior, allowing the service container to manage the service‘s lifetime is recommended. Don‘t implement the singleton design pattern and provide user code to manage the object‘s lifetime in the class.

     It‘s dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.

Constructor injection behavior

Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities – Permits object creation without service registration in the dependency injection container. ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.

Constructors can accept arguments that aren‘t provided by dependency injection, but the arguments must assign default values.

When services are resolved by IServiceProvider or ActivatorUtilities, constructor injection requires a public constructor.

When services are resolved by ActivatorUtilities, constructor injection requires that only one applicable constructor exists. Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.

Entity Framework contexts

Entity Framework contexts should be added to the service container using the scoped lifetime. This is handled automatically with a call to the AddDbContext method when registering the database context. Services that use the database context should also use the scoped lifetime.

Lifetime and registration options

To demonstrate the difference between the lifetime and registration options, consider the following interfaces that represent tasks as an operation with a unique identifier, OperationId. Depending on how the lifetime of an operations service is configured for the following interfaces, the container provides either the same or a different instance of the service when requested by a class:

An OperationService is registered that depends on each of the other Operation types. When OperationService is requested via dependency injection, it receives either a new instance of each service or an existing instance based on the lifetime of the dependent service.

  • If transient services are created when requested, the OperationId of the IOperationTransient service is different than the OperationId of the OperationService. OperationService receives a new instance of the IOperationTransient class. The new instance yields a different OperationId.
  • If scoped services are created per request, the OperationId of the IOperationScoped service is the same as that of OperationService within a request. Across requests, both services share a different OperationId value.
  • If singleton and singleton-instance services are created once and used across all requests and all services, the OperationId is constant across all service requests.

In Startup.ConfigureServices, each type is added to the container according to its named lifetime:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));

    // OperationService depends on each of the other Operation types.
    services.AddTransient<OperationService, OperationService>();
}

  

Observe which of the OperationId values vary within a request and between requests:

  • Transient objects are always different. Note that the transient OperationId value for both the first and second requests are different for both OperationService operations and across requests. A new instance is provided to each service and request.
  • Scoped objects are the same within a request but different across requests.
  • Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in ConfigureServices.

Call services from main

Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app‘s scope. This approach is useful to access a scoped service at startup to run initialization tasks. The following example shows how to obtain a context for the MyScopedService in Program.Main:

public static void Main(string[] args)
{
    var host = CreateWebHostBuilder(args).Build();

    using (var serviceScope = host.Services.CreateScope())
    {
        var services = serviceScope.ServiceProvider;

        try
        {
            var serviceContext = services.GetRequiredService<MyScopedService>();
            // Use the context here
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred.");
        }
    }

    host.Run();
}

  

Scope validation

When the app is running in the Development environment, the default service provider performs checks to verify that:

  • Scoped services aren‘t directly or indirectly resolved from the root service provider.
  • Scoped services aren‘t directly or indirectly injected into singletons.

The root service provider is created when BuildServiceProvider is called. The root service provider‘s lifetime corresponds to the app/server‘s lifetime when the provider starts with the app and is disposed when the app shuts down.

Scoped services are disposed by the container that created them. If a scoped service is created in the root container, the service‘s lifetime is effectively promoted to singleton because it‘s only disposed by the root container when app/server is shut down. Validating service scopes catches these situations when BuildServiceProvider is called.

For more information, see ASP.NET Core Web Host.

Request Services

The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.

Request Services represent the services configured and requested as part of the app. When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.

Generally, the app shouldn‘t use these properties directly. Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. This yields classes that are easier to test.

Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

Design services for dependency injection

Best practices are to:

  • Design services to use dependency injection to obtain their dependencies.
  • Avoid stateful, static method calls (a practice known as static cling).
  • Avoid direct instantiation of dependent classes within services. Direct instantiation couples the code to a particular implementation.

By following the SOLID Principles of Object Oriented Design, app classes naturally tend to be small, well-factored, and easily tested.

If a class seems to have too many injected dependencies, this is generally a sign that the class has too many responsibilities and is violating the Single Responsibility Principle (SRP). Attempt to refactor the class by moving some of its responsibilities into a new class.

Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. Business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

Disposal of services

The container calls Dispose for the IDisposable types it creates.

If an instance is added to the container by user code, it isn‘t disposed automatically.

// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // The container creates the following instances and disposes them automatically:
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // The container doesn‘t create the following instances, so it doesn‘t dispose of
    // the instances automatically:
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

  

Default service container replacement

The built-in service container is meant to serve the needs of the framework and most consumer apps. We recommend using the built-in container unless you need a specific feature that it doesn‘t support.

Some of the features supported in 3rd party containers not found in the built-in container:

  • Property injection
  • Injection based on name
  • Child containers
  • Custom lifetime management
  • Func<T> support for lazy initialization

See the Dependency Injection readme.md file for a list of some of the containers that support adapters.

The following sample replaces the built-in container with Autofac:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

  To use a 3rd party container, Startup.ConfigureServices must return IServiceProvider.

  • Configure Autofac in DefaultModule:
public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
    }
}

  At runtime, Autofac is used to resolve types and inject dependencies. To learn more about using Autofac with ASP.NET Core, see the Autofac documentation.

Thread safety

Singleton services need to be thread safe.

If a singleton service has a dependency on a transient service, the transient service may also need to be thread safe depending how it‘s used by the singleton.

The factory method of single service, such as the second argument to AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), doesn‘t need to be thread-safe. Like a type (static) constructor, it‘s guaranteed to be called once by a single thread.

Recommendations

  • async/await and Task based service resolution is not supported. C# does not support asynchronous constructors, therefore the recommended pattern is to use asynchronous methods after synchronously resolving the service.
  • Avoid storing data and configuration directly in the service container. For example, a user‘s shopping cart shouldn‘t typically be added to the service container. Configuration should use the options pattern. Similarly, avoid "data holder" objects that only exist to allow access to some other object. It‘s better to request the actual item via DI.
  • Avoid static access to services (for example, statically-typing IApplicationBuilder.ApplicationServices for use elsewhere).
  • Avoid using the service locator pattern. For example, don‘t invoke GetService to obtain a service instance when you can use DI instead. Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. Both of these practices mix Inversion of Control strategies.
  • Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Exceptions are rare—mostly special cases within the framework itself.

DI is an alternative to static/global object access patterns. You may not be able to realize the benefits of DI if you mix it with static object access.

Additional resources

原文地址:https://www.cnblogs.com/panpanwelcome/p/10189671.html

时间: 12-28

Dependency injection in ASP.NET Core的相关文章

Dependency Injection in ASP.NET Web API 2

What is Dependency Injection? A dependency is any object that another object requires. For example, it's common to define a repository that handles data access. Let's illustrate with an example. First, we'll define a domain model: public class Produc

Professional C# 6 and .NET Core 1.0 - 40 ASP.NET Core

本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处:Professional C# 6 and .NET Core 1.0 - 40 ASP.NET Core ----------------------------------------------------------------------- What's In This Chapter? Understanding ASP.NET Core 1.0 and Web Technologies Using

ASP.NET Core 第一章:简介

英文原版:Introduction to ASP.NET Core 关于ASP.NET Core ASP.NET Core 是一个全新的开源.跨平台框架,可以用它来构建基于网络连接的现代云应用程序,比如:Web 应用,IoT(Internet Of Things,物联网)应用和移动后端等.ASP.NET Core可以运行在 .NET Core 或完整的 .NET Framework 之上,其架构为发布到云端或本地运行的应用提供了一个最佳的开发框架,由开销很小的模块化组件构成,这就保持了你构造解决

ABP官方文档翻译 6.2.1 ASP.NET Core集成

ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Action过滤器 异常过滤器 结果过滤器 Ajax请求的结果缓存 模型绑定器 视图 客户端代理 集成测试 介绍 本文档描述了ABP如何集成ASP.NET Core.ASP.NET Core通过Abp.AspNetCore nuget包实现集成. 迁移到ASP.NET Core? 如果你已经有一个工程并考虑

ASP.NET Core 中文文档 第三章 原理(7)配置

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 原文:Configuration 作者:Steve Smith.Daniel Roth 翻译:刘怡(AlexLEWIS) 校对:孟帅洋(书缘) ASP.NET Core 支持多种配置选项.应用程序配置数据内建支持读取 JSON.XML 和 IN

初识ASP.NET Core 1.0

本文将对微软下一代ASP.NET框架做个概括性介绍,方便大家进一步熟悉该框架. 在介绍ASP.NET Core 1.0之前有必要澄清一些产品名称及版本号.ASP.NET Core1.0是微软下一代ASP.NET 框架,在这之前ASP.NET版本稳定在ASP.NET  4.6,对应的.NET Framework版本为.net 4.6.1. 曾经一段时间微软将下一代ASP.NET 命名为ASP.NET 5和MVC 6,在ASP.NET 5 is dead – Introducing ASP.NET

ASP.NET Core中的依赖注入(2):依赖注入(DI)

参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/project-layout.html http://www.yuanjiaocheng.net/ASPNET-CORE/projectjson.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-configuration.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-middleware.htm

使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(六)-- 依赖注入

本篇将介绍Asp.Net Core中一个非常重要的特性:依赖注入,并展示其简单用法. 第一部分.概念介绍 Dependency Injection:又称依赖注入,简称DI.在以前的开发方式中,层与层之间.类与类之间都是通过new一个对方的实例进行相互调用,这样在开发过程中有一个好处,可以清晰的知道在使用哪个具体的实现.随着软件体积越来越庞大,逻辑越来越复杂,当需要更换实现方式,或者依赖第三方系统的某些接口时,这种相互之间持有具体实现的方式不再合适.为了应对这种情况,就要采用契约式编程:相互之间依

Building simple plug-ins system for ASP.NET Core(转)

Recently I built plug-ins support to my TemperatureStation IoT solution web site. The code for .NET Core is different from what we have seen on full .NET Framework (application domains etc) but there’s still nothing complex. This blog post describes

【翻译】在Visual Studio中使用Asp.Net Core MVC创建你的第一个Web API应用(一)

HTTP is not just for serving up web pages. It's also a powerful platform for building APIs that expose services and data. HTTP is simple, flexible, and ubiquitous. Almost any platform that you can think of has an HTTP library, so HTTP services can re