问题
I am learning IoC & DI with Asp.net core. I have setup my dbcontext and other classes to be injected into my controllers. Currently my startup.cs looks like this:
// 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"));
As you can see amongst other things I am injecting AppSettings class. I have no problem accessing this class like this:
private readonly AppSettings _appSettings;
public HomeController(UserManager<ApplicationUser> userManager,
ApplicationDbContext dbContext,
ViewRender view,
IHostingEnvironment env,
IOptions<AppSettings> appSettings
)
{
Passing it into the constructor of a controller works fine.
But I need to access the AppSettings in a class, and was hoping there was a static method I could use to inject the class into any random class. Is this possible? Or do I need to inject it into the controller and pass it to each other class?
回答1:
Prevent injecting IOptions<T>
dependencies into application classes. Doing so is riddled with problems as described here.
Likewise is the injection of a AppSettings
into classes a problem, because this means that all classes get all configuration values injected, while they only use one or two of those values. This makes those classes harder to test, and it becomes much harder to figure out which configuration value such class actually requires. It also pushes the verification of the configuration to inside your application, which makes your application much more fragile; you'll find out much later when a configuration value is missing, instead of finding out when the application is started.
A class should specify the things it requires in its constructor, and should not pass those dependencies around through other classes. This holds for both injected components and configuration values. This means that in case a class requires a specific configuration value, it should specify that -and only that- value in its constructor.
Update
The "email class" that contains this SendVerification()
method that you mention, seems like an application component to me. Since that class sends the actual mail, it is the one that requires all those mail configuration settings; not the controller! So those settings should be injected directly into that component. But again, refrain from injecting anything general (such as IOptions<T>
, AppSettings
or IConfiguration
) into that class. 1 Be as specific as possible as what that class needs and 2. make sure configuration values are read at application startup where you can let the application fail fast when the application starts up.
So I imagine your "mail class" to be defined by an abstraction as follows:
public interface IVerificationSender
{
void SendVerification(User user);
}
This allows your controller to take a dependency on this abstraction. Note that no component should create dependencies of application components itself. This is an anti-pattern known as Control Freak (see this book).
// 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);
}
}
Now we have a IVerificationSender
implementation that uses mail to send messages (that's your "mail class" thingy). That class is companioned by a Parameter Object that holds all the configuration values that this class requires (but absolutely nothing more than that).
// 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);
}
}
}
}
Using this approach, registration of both the controller and the EmailVerificationSender
should be trivial. You can even use this SmtpVerificationSenderSettings
as serializable object that is loaded from the configuration file:
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));
Where the settings.json
might look as follows:
{
"SmtpVerificationSenderSettings": {
"MailHost" : "localhost",
"UserName" : "foobar",
// etc
}
}
来源:https://stackoverflow.com/questions/41304249/how-to-access-asp-net-core-di-container-from-class