如何在类中访问ASP.NET Core DI容器

4

我正在学习使用Asp.net core进行IoC和DI。 我已经设置了我的DbContext和其他类注入到我的控制器中。

目前我的startup.cs文件如下:

// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));


services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 5;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireUppercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

services.AddMvc();

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

正如您可以看到的,我正在注入AppSettings类。我可以像这样轻松访问此类:

private readonly AppSettings _appSettings;

public HomeController(UserManager<ApplicationUser> userManager, 
    ApplicationDbContext dbContext,
    ViewRender view,
    IHostingEnvironment env,
    IOptions<AppSettings> appSettings
    )
{
}

将其传递到控制器的构造函数中可以正常工作。
但是我需要在一个类中访问AppSettings,并希望有一个静态方法可以用来将该类注入到任意随机类中。这可能吗?还是我需要将其注入到控制器中并将其传递给每个其他类?
1个回答

5

避免将IOptions<T>依赖注入到应用程序类中。这样做存在很多问题,如此处所述

同样地,将AppSettings注入到类中也是一个问题,因为这意味着所有的类都会被注入所有的配置值,而它们只使用其中的一两个。这使得这些类难以测试,并且变得更加难以确定这个类实际上需要哪个配置值。它还将验证配置推向了应用程序内部,这使得应用程序非常脆弱;你会在应用程序启动时发现缺少了一个配置值,而不是在之前找出来。

一个类应该在其构造函数中指定它需要的东西,并且不应该通过其他类传递这些依赖项。这适用于注入组件和配置值。这意味着如果一个类需要特定的配置值,它应该在构造函数中指定那个-仅仅那个-值。

更新

你提到的包含此SendVerification()方法的“邮件类”对我来说似乎是一个应用程序组件。由于该类发送实际的邮件,因此它是需要所有这些邮件配置设置的类;而不是控制器!因此,应该将这些设置直接注入到该组件中。但是,仍要避免将任何通用内容(如IOptions<T>AppSettingsIConfiguration)注入到该类中。1.尽可能明确该类需要的内容,2.确保在应用程序启动时读取配置值,可以让应用程序在启动时快速失败。

因此,我想象您的“邮件类”由以下抽象定义:

public interface IVerificationSender
{
    void SendVerification(User user);
}

这使得您的控制器可以依赖于此抽象。请注意,任何组件都不应该自行创建应用程序组件的依赖关系。 这是一个反模式,称为Control Freak(请参见此书)。

// Controller that depends on the IVerificationSender abstraction
public class HomeController : Controller
{
    private readonly IVerificationSender verificationSender;
    public HomeController(IVerificationSender verificationSender, ...) {
        this.verificationSender = verificationSender;
    }

    public void SomeAction() {
        this.verificationSender.SendVerification(user);  
    }
}

现在我们有一个使用邮件发送消息的IVerificationSender实现(也就是您所谓的“mail class”)。该类配合着一个参数对象,该对象仅保存该类所需的所有配置值(没有多余的内容)。
// Settings class for the IVerificationSender implementation
public class SmtpVerificationSenderSettings
{
    public string MailHost { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public bool EnableSsl { get; set; }
    // etc
}

public class EmailVerificationSender : IVerificationSender
{
    private readonly SmtpVerificationSenderSettings settings;
    public EmailVerificationSender(SmtpVerificationSenderSettings settings) {
        if (settings == null) throw new ArgumentNullException("settings");
        this.settings = settings;
    }

    public void SendVerification(User user) {
        using (var client = new SmtpClient(this.settings.MailHost, 25)) {
            smtpClient.EnableSsl = this.settings.EnableSsl;
            using (MailMessage mail = new MailMessage()) {
                mail.From = new MailAddress("info@foo", "MyWeb Site");
                mail.To.Add(new MailAddress(user.Email));
                mail.Body = $"Hi {user.Name}, Welcome to our site.";
                client.Send(mail);
            }
        }
    }
}

使用这种方法,控制器和 EmailVerificationSender 的注册都应该是轻松的。您甚至可以将这个 SmtpVerificationSenderSettings 作为可序列化对象,从配置文件中加载:
IConfiguration config = new ConfigurationBuilder()
    .SetBasePath(appEnv.ApplicationBasePath)
    .AddJsonFile("settubgs.json");
    .Build();

var settings = config.GetSection("SmtpVerificationSenderSettings")
    .Get<SmtpVerificationSenderSettings>();

// Verify the settings object
if (string.IsNullOrWhiteSpace(settings.MailHost)
    throw new ConfigurationErrorsException("MailSettings MailHost missing.");
if (string.IsNullOrWhiteSpace(settings.MailHost)
    throw new ConfigurationErrorsException("MailSettings UserName missing.");
// etc

// Register the EmailVerificationSender class
services.AddSingleton<IVerificationSender>(new EmailVerificationSender(settings));

settings.json 文件可能如下所示:

{
    "SmtpVerificationSenderSettings": {
        "MailHost" : "localhost",
        "UserName" : "foobar",
        // etc
    }
}

感谢您的回复。在我的示例中,我正在传递Appsettings(现在我也会查看Ioptions问题)。但基本上您所说的是,我需要从控制器将所有设置(即邮件主机、用户名、SSL等)传递到电子邮件类中?在过去,我可以直接从类中调用ConfigurationManager,因此对调用类了解(或检索)设置的依赖性较小。 - Michael
@micheal,在早期的时候,你的类可能有相同数量的依赖项,但是它们被隐藏了起来,这可能会在运行时导致应用程序崩溃。 - Steven
@michael 如果你的控制器本身需要这些设置,它们应该被注入到该控制器中。这并不意味着该类需要每个值都有一个构造函数参数:你可以将它们分组成一个参数对象。 - Steven
@michael 但考虑到邮件设置,我觉得你的控制器不应该实际包含这种邮件行为。这种行为通常应该被提取成自己的组件。 - Steven
谢谢Steven,我已经调整了我的代码,将IConfiguration注入到控制器中。然后在需要发送电子邮件的方法中,使用.GetSection("SMTP")(创建一个smtp对象)...然后在同一个控制器方法中,实例化我的电子邮件类(带有smtp对象),并调用"SendVerification()"。这个逻辑看起来正常吗?(我对你的评论“这种行为通常应该被提取到自己的组件中”有点困惑) - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接