【问题标题】:How to use dependency injection in .NET Standard project?如何在 .NET Standard 项目中使用依赖注入?
【发布时间】:2020-10-25 06:22:51
【问题描述】:

是否可以在没有 ASP.NET Core Web 应用程序项目的独立 .NET Standard 项目中使用依赖注入?

由于没有startup.cs文件,我很想知道这是否可能,是否可以做到?

【问题讨论】:

  • 什么是standalone .net standard project?你的意思是Class Library Project.NET Standard 为目标?
  • 正确,就是这样
  • 类库没有自己的依赖注入。它具有连接到调用应用程序的 DI 的构造函数。你能解释一下你的用例吗?
  • 类库不能也不能独立运行。那么,这个库将在哪里使用和调用呢?这可能会更好地回答你的问题。
  • @mko:你会(介意我)edit 你的问题是包括 .NET Standard 项目和类库吗?显然,.NET Standard 项目类库,但这个问题中的所有内容以及答案(包括我的答案)都更普遍地适用于类库。例如,这仍然与使用 .NET 6 类库的人相关。

标签: asp.net-core .net-core dependency-injection .net-standard


【解决方案1】:

是的,你绝对可以做到这一点。事实上,即使您的类库与 ASP.NET Core 实现在 same 程序集中,最好让您的库完全不知道您的特定依赖项和您正在使用的任何依赖项注入容器.

TL;DR: 您的依赖项和您正在使用的任何依赖项注入容器都应该在您的应用程序 的组合根目录中进行配置(例如,Startup) ,不在您的中。请继续阅读以了解详细信息。

核心概念

首先,有必要记住,从根本上说,依赖注入是一组用于实现松散耦合代码的模式和实践,不是特定的依赖注入容器。这一点很重要,因为您不希望您的库依赖于您可能选择使用的任何依赖注入容器,例如 ASP.NET Core 的内置容器。容器只是一个工具,可以帮助自动化构建、管理和将依赖项注入到您的库中。

鉴于此,您的 .NET 标准类库应写入 允许 依赖项从任何正在实现它的应用程序中注入,同时完全不知道正在使用哪些依赖项甚至容器.通常这是通过 构造函数注入 完成的,其中依赖项作为参数暴露在类的构造函数中。在这种情况下,您只想通过它们的抽象来公开构造函数中的依赖关系——即通过描述实现的接口。

这些概念通过一个例子更容易理解。

示例

假设您需要为用户访问某种类型的数据持久层(例如数据库),但您不希望您的类库与任何单个实现(例如 SQL Server 数据库)紧密耦合。

抽象

在这种情况下,您可以创建一个IUserRepository 抽象,您的具体实现可以实现该抽象。这是一个准系统示例,它公开了一个 GetUser() 方法:

public interface IUserRepository 
{
    User GetUser(int userId);
}

构造函数注入

然后,对于依赖于该服务的任何类,您将实现一个构造函数,该构造函数允许将IUserRepository 抽象注入其中:

public class MyClass 
{
    private readonly IUserRepository _userRepository;
    public MyClass(IUserRepository userRepository)
    {
        _userRepository = userRepository?? throw new ArgumentNullException(nameof(userRepository));
    }
    public string GetUserName(int userId) => _userRepository.GetUser(userId).Name;
}

具体实现

现在,您可以创建IUserRepository 的具体实现——让我们说SqlUserRepository

public class SqlUserRepository: IUserRepository
{
    private readonly string _connectionString;
    public SqlUserRepository(string connectionString) 
    {
        _connectionString = connectionString?? throw new ArgumentNullException(nameof(connectionString));
    }
    public GetUser(int userId) 
    {
        //Implementation
    }
}

关键的是,这个实现可以在一个完全独立的程序集中;包含IUserRepositoryMyClass 的程序集根本不需要知道它。

依赖注入

那么实际的依赖注入发生在哪里呢?在任何实现您的 .NET 标准类库的应用程序中。因此,例如,如果您有一个 ASP.NET Core 应用程序,您可以通过您的 Startup 类配置您的依赖注入容器,以便为任何依赖于 IUserRepository 的类注入一个 SqlUserRepository 实例:

public void ConfigureServices(IServiceCollection services) 
{
    //Register dependencies
    services.AddScoped<IUserRepository>(c => new SqlRepository("my connection string"));
}

在这方面,您的前端应用程序在您的 .NET 标准类库和它所依赖的任何服务之间提供了粘合剂

重要提示:这可以很容易地改为控制台应用程序。它可以使用第三方依赖注入容器,例如 Ninject。或者,您可以在 composition root 中手动连接您的依赖关系图。这里重要的是,您的 .NET Standard 类库不知道也不关心,只要 something 为其提供依赖项。

程序集参考

在上面的示例中,您的程序集结构可能如下所示:

  • My.Library.dll
    • IUserRepository
    • MyClass
  • My.Library.Sql.dll(参考My.Library.dll
    • SqlUserRepository
  • My.Web.dll(参考My.Library.dll,My.Library.Sql.dll
  • My.Console.dll(参考My.Library.dll,My.Library.Sql.dll

请注意,My.Library 完全不知道具体实现(例如,My.Library.Sql.dll)或将实现它的应用程序(例如,My.Web.dllMy.Console.dll)。它只知道IUserRepository 在构造时会被注入MyClass

重要提示:这些可能都在同一个程序集中。但即使它们是,将它们考虑分开来保持依赖注入容器、核心业务逻辑和具体实现之间的松散耦合也是有用的。

最佳实践

虽然不是严格要求,但最佳做法是让您的依赖项成为 必需 参数,这些参数被您的类视为 只读。如果您公开可选的依赖项或可以在对象的生命周期内替换的依赖项,您可能会为自己制造很多问题。

上面的类结构通过需要参数演示了理想的实现:

public MyClass(IUserRepository userRepository) 

添加保护子句以防止空值:

_userRepository = userRepository?? throw new ArgumentNullException(nameof(userRepository));

最后,将其分配给 readonly 字段,这样以后就不能用不同的实现来替换它:

private readonly IUserRepository _userRepository;

【讨论】:

    【解决方案2】:

    假设您正在编写一个从多个来源获取数据的日志阅读器库:

    public interface IDataProvider
    {
        Task<(IEnumerable<LogModel>, int)> FetchDataAsync(
            int page,
            int count,
            string level = null,
            string searchCriteria = null
        );
    }
    

    并且您对上述接口有多种实现:

    public class SqlServerDataProvider : IDataProvider
    {
        private readonly RelationalDbOptions _options;
    
        public SqlServerDataProvider(RelationalDbOptions options)
        {
            _options = options;
        }
    
        public async Task<(IEnumerable<LogModel>, int)> FetchDataAsync(
            int page,
            int count,
            string logLevel = null,
            string searchCriteria = null
        )
        { ... }
    

    这里是RelationalDbOptions

    public class RelationalDbOptions
    {
        public string ConnectionString { get; set; }
    
        public string TableName { get; set; }
    
        public string Schema { get; set; }
    }
    

    让我们创建一个ServiceCollectionextension 方法来注册依赖:

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddSqlServerLogReader(
            this IServiceCollection services,
            Action<RelationalDbOptions> options
        )
        {
            if (services == null)
                throw new ArgumentNullException(nameof(services));
    
            if (optionsBuilder == null)
                throw new ArgumentNullException(nameof(optionsBuilder));
    
            var relationalDbOptions = new RelationalDbOptions();
            options.Invoke(relationalDbOptions );
            services.AddSingleton(relationalDbOptions);
    
            Services.AddScoped<IDataProvider, SqlServerDataProvider>();
    
            return services;
        }
    }
    

    现在,如果您想将日志阅读器库与 ASP.NET Core 或控制台应用程序一起使用,您可以通过在 ConfigureServices 方法或您正在创建的任何位置 ServiceCollection 中调用 AddSqlServerLogReader 来注册日志阅读器依赖项:

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddSqlServerLogReader(options => { 
               options.ConnectionString = ""; 
               options.LogTableName = ""
        });
        ...
    }
    

    注册库依赖项是一种常见的模式。签出实际实现here

    【讨论】:

    • 我在问题中提到,我没有 ASP.NET Core 或控制台应用程序。它是一个独立的类库项目
    • @mko:这不是问题。请记住,您的类库不能自行运行。它最终将从应用程序中调用。这可能是 ASP.NET Core Web 应用程序、.NET 控制台应用程序、Unity 游戏、Xamarin 手机应用程序、WCF 桌面应用程序或这些的某种组合。如果是,则由该应用程序来构建和注入依赖项——可能使用依赖项注入容器。 ASP.NET Core 附带一个开箱即用的此类容器,但还有其他可在任何 .NET Core 或 .NET Framework 应用程序中使用的容器。或者您可以手动连接它们。
    • @mko 作为 .NET 开发人员,您可能知道 Class Library 不会也不能自行运行,您必须将该库与 ASP.NET Core Web 应用程序、.NET 控制台一起使用应用程序,桌面应用程序...正如@Jeremy Caney 所说。
    【解决方案3】:

    众所周知,类库不能自己执行。您必须从控制台或 ASP.NET Core 项目中引用它——让我们调用那些可执行文件——然后它们将调用你的库。

    您的依赖注入配置在您的类库中的实际代码之前运行。在库中,我们没有任何入口点来配置依赖注入容器;我们必须创建一个,然后从可执行文件的StartupMain 调用它。

    例如,EF Core 只是一个库,但它有一个扩展方法(作为入口点),允许您使用 DI 对其进行配置:

    public static IServiceCollection AddDbContext<TContext>(this IServiceCollection serviceCollection, Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext
    {
        library configurations
    }
    

    您可以在代码中采用相同的方法。

    【讨论】:

      猜你喜欢
      • 2018-01-27
      • 1970-01-01
      • 2017-05-10
      • 2017-07-23
      • 2020-12-20
      • 1970-01-01
      • 2019-03-14
      • 2020-04-17
      • 2023-04-05
      相关资源
      最近更新 更多