ASP.NET Core依赖注入深入讨论

这篇文章我们来深入探讨ASP.NET Core、MVC Core中的依赖注入,我们将示范几乎所有可能的操作把依赖项注入到组件中。

依赖注入是ASP.NET Core的核心,它能让您应用程序中的组件增强可测试性,还使您的组件只依赖于能够提供所需服务的某些组件。

举个例子,这里我们有一个接口和它的实现类:

public interface IDataService

{

    IList<DataClass> GetAll();

}


public class DataService : IDataService

{

    public IList<DataClass> GetAll()

    {

        //Get data...

        return data;

    }

}

如果另一个服务依赖于DataService,那么它们依赖于特定的实现,测试这样的服务可能会非常困难。如果该服务依赖于IDataService,那么它们只关心接口提供的契约。实现什么并不重要,它使我们能够通过一个模拟实现来测试服务的行为。

1.服务生命周期

在我们讨论如何在实践中进行注入之前,了解什么是服务生命周期至关重要。当一个组件通过依赖注入请求另一个组件时,它所接收的实例是否对该组件的实例来说是唯一的,这取决于它的生命周期。设置生命周期从而决定组件实例化的次数,以及组件是否共享。

在ASP.NET Core中,内置的DI容器有三种模式:

  • Singleton

  • Scoped

  • Transient

Singleton意味着只会创建一个实例,该实例在需要它的所有组件之间共享。因此始终使用相同的实例。

Scoped意味着每个作用域创建一个实例。作用域是在对应用程序的每个请求上创建的,因此,任何注册为Scoped的组件每个请求都会创建一次。

Transient每次请求时都会创建瞬态组件,并且永远不会共享。

理解这一点非常重要,如果将组件A注册为单例,则它不能依赖于具有ScopedTransient生命周期的组件。总而言之:

组件不能依赖比自己的生命周期小的组件。

违反这条规则的后果显而易见,依赖的组件可能会在依赖项之前释放。

通常,您希望将组件(如应用程序范围的配置容器)注册为Singleton。数据库访问类(如Entity Framework上下文)建议使用Scoped,以便可以重复使用连接。但是如果您想并行运行任何东西,请记住Entity Framework上下文不能由两个线程共享。如果您需要这样做,最好将上下文注册为Transient,这样每个组件都有自己的上下文实例而且可以并行运行。

2.服务注册

注册服务是在Startup类的ConfigureServices(IServiceCollection)方法中完成的。

这是一个服务注册的例子:

services.Add(new ServiceDescriptor(typeof(IDataService), typeof(DataService), ServiceLifetime.Transient));

这行代码将DataService添加到服务集合中。服务类型设置为IDataService,因此如果请求了该类型的实例,则它们将获得DataService的实例。生命周期也设置为Transient,这样每次都会创建一个新实例。

ASP.NET Core提供了很多扩展方法,使注册各种生命周期的服务和其他设置更加方便。

下面是使用扩展方法的更简单的示例:

services.AddTransient<IDataService, DataService>();

是不是更简单一点?封装后它当然更容易调用,这样做更简单。对于不同的生命周期,也有类似的扩展方法,你也许可以猜到它们的名字。

如果愿意,您也可以在使用单一类型注册(实现类型=服务类型):

services.AddTransient<DataService>();

但是呢,当然组件必须取决于具体的类型,所以这可能是不需要的。

2.1.实现工厂

在一些特殊情况下,您可能想要接管某些服务的实例化。在这种情况下,您可以在服务描述符上注册一个实现工厂(Implementation Factory)。这有一个例子:

services.AddTransient<IDataService, DataService>((ctx) =>

{

    IOtherService svc = ctx.GetService<IOtherService>();

    //IOtherService svc = ctx.GetRequiredService<IOtherService>();

    return new DataService(svc);

});

它使用另一个组件IOtherService实例化DataService。您可以使用GetService<T>()GetRequiredService<T>()来获取在服务集合中注册的依赖项。

区别在于GetService<T>()如果找不到T类型服务,则返回nullGetRequiredService<T>()如果找不到它,则会引发InvalidOperationException异常。

2.2.单例作为常量注册

如果您想自己实例化一个单例,你可以这样做:

services.AddSingleton<IDataService>(new DataService());

它允许一个非常有趣的场景,假设DataService实现两个接口。如果我们这样做:

services.AddSingleton<IDataService, DataService>();

services.AddSingleton<ISomeInterface, DataService>();

我们得到两个实例,两个接口都有一个。如果我们打算共享一个实例,这是一种方法:

var dataService = new DataService();

services.AddSingleton<IDataService>(dataService);

services.AddSingleton<ISomeInterface>(dataService);

如果组件具有依赖关系,则可以从服务集合构建服务提供者并从中获取必要的依赖项:

IServiceProvider provider = services.BuildServiceProvider();


IOtherService otherService = provider.GetRequiredService<IOtherService>();


var dataService = new DataService(otherService);

services.AddSingleton<IDataService>(dataService);

services.AddSingleton<ISomeInterface>(dataService);

请注意,您应该在ConfigureServices的末尾执行此操作,以便在此之前确保已经注册了所有依赖项。

3.注入

我们已经注册了我们的组件,现在我们就可以实际使用它们了。

在ASP.NET Core中注入组件的典型方式是构造函数注入,针对不同的场景确实存在其他选项,但构造器注入允许您定义在没有这些其他组件的情况下此组件不起作用。

举个例子,我们来做一个基本的日志记录中间件组件:

public class LoggingMiddleware

{

    private readonly RequestDelegate _next;


    public LoggingMiddleware(RequestDelegate next)

    {

        _next = next;

    }


    public async Task Invoke(HttpContext ctx)

    {

        Debug.WriteLine("Request starting");

        await _next(ctx);

        Debug.WriteLine("Request complete");

    }

}


在中间件中注入组件有三种不同的方式:

  • 构造函数

  • Invoke方法参数

  • HttpContext.RequestServices

让我们使用三种全部方式注入我们的组件:

public class LoggingMiddleware

{

    private readonly RequestDelegate _next;

    private readonly IDataService _svc;


    public LoggingMiddleware(RequestDelegate next, IDataService svc)

    {

        _next = next;

        _svc = svc;

    }


    public async Task Invoke(HttpContext ctx, IDataService svc2)

    {

        IDataService svc3 = ctx.RequestServices.GetService<IDataService>();

        

        Debug.WriteLine("Request starting");

        await _next(ctx);

        Debug.WriteLine("Request complete");

    }

}

中间件在应用的整个生命周期中仅实例化一次,因此通过构造函数注入的组件对于所有通过的请求都是相同的

作为Invoke方法的参数注入的组件是中间件绝对必需的,如果它找不到要注入的IDataService,它将引发InvalidOperationException异常。

第三个通过使用HttpContext请求上下文的RequestServices属性的GetService<T>()方法来获取可选的依赖项。RequestServices属性的类型是IServiceProvider,因此它与实现工厂中的提供者完全相同。如果您打算要求拿到这个组件,可以使用GetRequiredService<T>()

如果IDataService被注册为Singleton,我们会在它们中获得相同的实例。

如果它被注册为Scopedsvc2svc3将会是同一个实例,但不同的请求会得到不同的实例。

Transient的情况下,它们都是不同的实例。

每种方法的用例:

  • 构造函数:所有请求都需要的单例(Singleton)组件

  • Invoke参数:在请求中总是必须的作用域(Scoped)和瞬时(Transient)组件

  • RequestServices:基于运行时信息可能需要或可能不需要的组件

如果可能的话,我会尽量避免使用RequestServices,并且只在中间件必须能够在缺少某些组件一样可以运行的情况下才使用它。

3.1.Startup类

Startup类的构造函数中,您至少可以注入IHostingEnvironmentILoggerFactory。它们是官方文档中提到的仅有两个接口。可能有其他的,但我不知道。

public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory)

{

    ...

}

IHostingEnvironment通常用于为应用程序设置配置。您可以使用ILoggerFactory设置日志记录。


Configure方法允许您注入已注册的任何组件。

public void Configure(

    IApplicationBuilder app,

    IHostingEnvironment env,

    ILoggerFactory loggerFactory,

    IDataService dataSvc)

{

    ...

}

因此,如果在管道配置过程中有需要的组件,您可以在这里简单地要求它们。

如果使用app.Run()/app.Use()/app.UseWhen()/app.Map()在管道上注册简单中间件,则不能使用构造函数注入。事实上,通过ApplicationServicesRequestServices是获取所需组件的唯一方法。

这里有些例子:

IDataService dataSvc2 = app.ApplicationServices.GetService<IDataService>();

app.Use((ctx, next) =>

{

    IDataService svc = ctx.RequestServices.GetService<IDataService>();

    return next();

});


app.Map("/test", subApp =>

{

    IDataService svc1 = subApp.ApplicationServices.GetService<IDataService>();

    subApp.Run((context =>

    {

        IDataService svc2 = context.RequestServices.GetService<IDataService>();

        return context.Response.WriteAsync("Hello!");

    }));

});


app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/test2"), subApp =>

{

    IDataService svc1 = subApp.ApplicationServices.GetService<IDataService>();

    subApp.Run(ctx =>

    {

        IDataService svc2 = ctx.RequestServices.GetService<IDataService>();

        return ctx.Response.WriteAsync("Hello!");

    });

});

因此,您可以在配置时通过IApplicationBuilder上的ApplicationServices请求组件,并在请求时通过HttpContext上的RequestServices请求组件。

3.2.在MVC Core中注入

在MVC中进行依赖注入的最常见方法是构造函数注入。

您可以在任何地方做到这一点。在控制器中,您有几个选项:

public class HomeController : Controller

{

    private readonly IDataService _dataService;


    public HomeController(IDataService dataService)

    {

        _dataService = dataService;

    }


    [HttpGet]

    public IActionResult Index([FromServices] IDataService dataService2)

    {

        IDataService dataService3 = HttpContext.RequestServices.GetService<IDataService>();

        

        return View();

    }

}

如果您希望稍后根据运行时决策获取依赖项,则可以再次使用Controller基类(技术上讲,ControllerBase最好)的HttpContext属性上可用的RequestServices

您也可以通过在特定的Action上添加参数,并使用FromServicesAttribute特性对其进行装饰来注入所需的服务,这会指示MVC Core从服务集合中获取它,而不是尝试对其进行模型绑定。

Razor视图

您还可以使用新的关键字@inject在Razor视图中注入组件:

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

在这里,我们在_ViewImports.cshtml中注入了一个视图本地化器,因此我们将它作为Localizer在所有视图中提供。


请注意,不应滥用此机制将本应该来自控制器的数据带入视图。


Tag helper

构造函数注入也适用于Tag Helper:

[HtmlTargetElement("test")]

public class TestTagHelper : TagHelper

{

    private readonly IDataService _dataService;


    public TestTagHelper(IDataService dataService)

    {

        _dataService = dataService;

    }

}

视图组件

视图组件也一样:

public class TestViewComponent : ViewComponent

{

    private readonly IDataService _dataService;


    public TestViewComponent(IDataService dataService)

    {

        _dataService = dataService;

    }


    public async Task<IViewComponentResult> InvokeAsync()

    {

        return View();

    }

}

在视图组件中也可以获得HttpContext,因此有权访问RequestServices

过滤器

MVC过滤器也支持构造函数注入,以及有权访问RequestServices

public class TestActionFilter : ActionFilterAttribute

{

    private readonly IDataService _dataService;


    public TestActionFilter(IDataService dataService)

    {

        _dataService = dataService;

    }


    public override void OnActionExecuting(ActionExecutingContext context)

    {

        Debug.WriteLine("OnActionExecuting");

    }


    public override void OnActionExecuted(ActionExecutedContext context)

    {

        Debug.WriteLine("OnActionExecuted");

    }

}

但是,通过构造函数注入我们不能像往常一样在控制器上添加特性,因为它在运行的时候必须要获得依赖项。

这里我们有两种方式可以将其添加到控制器或Action级别:

[TypeFilter(typeof(TestActionFilter))]

public class HomeController : Controller

{

}

// or

[ServiceFilter(typeof(TestActionFilter))]

public class HomeController : Controller

{

}

以上这两种方式关键的区别是TypeFilterAttribute会先找出过滤器的依赖项并通过DI获取它们,然后创建过滤器。另一方面,ServiceFilterAttribute则是直接尝试从服务集合中寻找过滤器!

所以,为了使[ServiceFilter(typeof(TestActionFilter))]正常工作,我们需要多一点配置:

public void ConfigureServices(IServiceCollection services)

{

    services.AddTransient<TestActionFilter>();

}

现在ServiceFilterAttribute就可以找到过滤器了。

如果您想添加全局过滤器:

public void ConfigureServices(IServiceCollection services)

{

    services.AddMvc(mvc =>

    {

        mvc.Filters.Add(typeof(TestActionFilter));

    });

}

这样就不需要将过滤器添加到服务集合,它的工作方式就好像您已经在每个控制器上添加了TypeFilterAttribute一样。

HttpContext

我已经多次提到过HttpContext。如果您想访问控制器/视图/视图组件之外的HttpContext,那怎么办?例如,要访问当前登录用户的声明?

您只要简单地注入IHttpContextAccessor,如下所示:

public class DataService : IDataService

{

    private readonly HttpContext _httpContext;


    public DataService(IOtherService svc, IHttpContextAccessor contextAccessor)

    {

        _httpContext = contextAccessor.HttpContext;

    }

    //...

}

这样可以让您的服务层直接访问HttpContext,而不需要通过调用方法来传递它。

4.结论

相对于Ninject或Autofac等较大、较老的DI框架来说,ASP.NET Core提供的依赖注入容器在功能上比较基本,但它仍然非常适合大多数需求。

您可以在任何需要的地方注入组件,从而使组件在此过程中更具可测试性。

5.链接

  • 在 ASP.NET Core 依赖注入 | Microsoft Docs

  • 控制器中的依赖关系注入 | Microsoft Docs

  • 视图中的依赖关系注入 | Microsoft Docs


原文:http://www.cnblogs.com/esofar/p/8625619.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321741.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

Hypermedia As The Engine Of Application State (HATEOAS)HATEOAS&#xff08;Hypermedia as the engine of application state&#xff09;是 REST 架构风格中最复杂的约束&#xff0c;也是构建成熟 REST 服务的核心。它的重要性在于打破了客户端和服务器之间严格的契约&…

【招聘(北京)】.NETCORE开发工程师(微服务方向)

组织&#xff1a;华汽集团北京研发中心位置&#xff1a;北京市朝阳区焦奥中心官网&#xff1a;www.sinoauto.com邮箱&#xff1a;taoxu.weisinoauto.com 项目&#xff1a;打造面向国内汽车后市场用户的一站式云服务平台&#xff08;华汽云&#xff09;&#xff0c;形态包括B2B、…

确保线程安全下使用Queue的Enqueue和Dequeue

场景是这样&#xff0c;假设有一台设备会触发类型为Alarm的告警信号&#xff0c;并把信号添加到一个Queue结构中&#xff0c;每隔一段时间这个Queue会被遍历检查&#xff0c;其中的每个Alarm都会调用一个相应的处理方法。问题在于&#xff0c;检查机制是基于多线程的&#xff0…

编写一个Java程序,其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)

编写一个Java程序&#xff0c;其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)。他们的行动如下: 厨师准备菜肴&#xff0c;每次准备一个。服务员等待菜肴准备好&#xff0c;然后将其送到顾客那里。顾客等待服务员送来菜看后才开始吃。所有三个角色应该循环进行…

Hangfire使用ApplicationInsigts监控

起因我司目前使用清真的ApplicationInsights来做程序级监控。&#xff08;ApplicationInsights相关文档: https://azure.microsoft.com/zh-cn/services/application-insights/ &#xff09;其实一切都蛮好的&#xff0c;但是我们基于Hangfire的Job系统却无法被Ai所监控到&#…

NET主流ORM框架分析

接上文我们测试了各个ORM框架的性能&#xff0c;大家可以很直观的看到各个ORM框架与原生的ADO.NET在境删改查的性能差异。这里和大家分享下我对ORM框架的理解及一些使用经验。ORM框架工作原理所有的ORM框架的工作原理都离不开下面这张图&#xff0c;只是每个框架的实现程度不同…

20、java中的类加载机制

1、类加载机制是什么&#xff1f; 类加载机制指的就是jvm将类的信息动态添加到内存并使用的一种机制。 2、那么类加载的具体流程是什么呢&#xff1f; 一般说类加载只有三步&#xff1a;加载、连接和初始化&#xff0c;其中连接包括验证、准备和解析&#xff0c;用于将运行时加…

【北京】BXUG第12期活动基于 .NET Core构建微服务和Xamarin

分享主题&#xff1a;基于 .NET Core构建微服务实战分享分享者&#xff1a;薛锋 北京切尔思科技架构师 兼任东北大学信息安全工程师和技术主播&#xff0c;行业内专注于研究 .NET Core和Web应用&#xff0c;具有比较扎实的技术基础和数年的从业经历。在GitHub上主持数个开…

谈谈ASP.NET Core中的ResponseCaching

前言前面的博客谈的大多数都是针对数据的缓存&#xff0c;今天我们来换换口味。来谈谈在ASP.NET Core中的ResponseCaching&#xff0c;与ResponseCaching关联密切的也就是常说的HTTP缓存。在阅读本文内容之前&#xff0c;默认各位有HTTP缓存相关的基础&#xff0c;主要是Cache-…

使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API

上一篇写的是使用静态基类方法的实现步骤: 使用dynamic (ExpandoObject)的好处就是可以动态组建返回类型, 之前使用的是ViewModel, 如果想返回结果的话, 肯定需要把ViewModel所有的属性都返回, 如果属性比较多, 就有可能造成性能和灵活性等问题. 而使用ExpandoObject(dynamic)就…

使用 BenchmarkDotnet 测试代码性能

先来点题外话&#xff0c;清明节前把工作辞了&#xff08;去 tm 的垃圾团队&#xff0c;各种拉帮结派、勾心斗角&#xff09;。这次找工作就得慢慢找了&#xff0c;不能急了&#xff0c;希望能找到个好团队&#xff0c;好岗位吧。顺便这段时间也算是比较闲&#xff0c;也能学习…

2017西安交大ACM小学期数论 [阅兵式]

阅兵式 发布时间: 2017年6月25日 12:53 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 阅兵式上&#xff0c;将士们排成一个整齐的方阵&#xff0c;每个将士面朝前方。问正中心的将士能向前看到几个将士&#xff1f;注意&#xff0c;一条直线上的将…

28、jdbc操作数据库(5)

介绍一个稍微封装了jdbc的工具类org.apache.commons.dbutils&#xff0c;使用dbutils可以简化对数据库操作程序的开发。 API介绍 接下来通过实例的方式说一下dbutils的具体使用 添加jar包&#xff1a;commons-dbutils-1.7.jar 增、删、改 进行增、删、改操作&#xff0c;在…

2017西安交大ACM小学期数论 [等差数列]

等差数列 发布时间: 2017年6月25日 13:42 最后更新: 2017年7月3日 09:27 时间限制: 1000ms 内存限制: 128M 描述 给定正整数n&#xff0c;试问存在多少个和为n的等差数列&#xff1f; 当然&#xff0c;等差数列中每一项要为非负整数&#xff0c;且不考虑降序的等差数列。…

上古时期(大雾)的数据结构pdf

分块点分治Treap byWYCby\ WYCby WYC Part1 分块 概念 就是将nnn个数分成若干个块&#xff0c;然后要处理的时候整块一起的加上局部的直接暴力。 如果将块的大小分配好一般每次都是O(n)O(\sqrt n)O(n​)的。 而且因为十分暴力&#xff0c;所以有很多优秀的性质。 实现方法 …

33、JAVA_WEB开发基础之会话机制

会话是什么 一个客户端浏览器与web服务器之间连续发生的一系列请求和响应过程就是会话&#xff0c;这些过程中产生的一系列信息就是会话信息&#xff0c;会话机制就是用于维护这些信息一致性的一种技术。通俗的说就是&#xff0c;一个A账号访问服务器&#xff0c;进行多次交互…

35、JAVA_WEB开发基础之过滤器

是什么 过滤器javaweb的一个重要组件&#xff0c;一种规范&#xff0c;可以对发送到serlvet的请求进行拦截和响应进行过滤。实际开发中可以使用过滤器来对访问服务器的请求进行过滤&#xff0c;以提高安全性 过滤器的原理 可以配置过滤器对指定的请求进行过滤&#xff0c;就…

2、安装和连接mysql

安装mysql 1、官网下载mysql 下载网址&#xff1a;https://www.mysql.com/ 2、解压并配置mysql 解压下载的&#xff08;前提下载的zip版本的mysql&#xff09;mysql安装包&#xff0c;放到指定磁盘 配置环境变量&#xff1a;将mysql下的bin目录的全路径名配置到环境变量的p…

6、mysql中字段

对数据表的操作是比较重要的&#xff0c;在实际开发中&#xff0c;日常做的主要工作就是对数据表的操作 对数据表的操作分为两大部分&#xff1a;操作数据表的结构、操作数据表中的数据 组成数据表的基本单元就是字段&#xff0c;所以&#xff0c;接下来先介绍一下mysql中的字…

在Linux环境下使用Apache部署ASP.NET Core

在前几篇文章中我们一起探讨了如何在Linux环境中安装ASP.NET Core运行时环境及将ASP.NET Core项目部署在Jexus中&#xff0c;这篇文章中我们将探讨如何将ASP.NET Core部署于Apache&#xff08;阿帕奇&#xff09;中。 很幸运能够和大家一起学习和探讨ASP.NET Core本文章运行…