Non capisco la documentazione ufficiale, al paragrafo sull'iniezione di dipendenza.
Dicono che posso usare un controller (ma da qui so che non ne ho bisogno perché sto usando le pagine Razor) o posso accedere direttamente a ServiceProvider:
using (var context = serviceProvider.GetService<BloggingContext>())
{
// do stuff
}
ma come recuperare il riferimento al ServiceProvider in una classe C # generica del mio progetto?
Ho configurato i servizi in startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));
services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyDbContext")));
services.AddOptions();
services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
services.AddMvc().AddDataAnnotationsLocalization();
}
Per chiarire ulteriormente la mia confusione, quello che sto cercando di fare è aggiungere / ottenere dati da una classe Worker. Qui ho trovato un esempio su come farlo:
using (var context = new BloggingContext())
{
var blog = new Blog { Url = "http://sample.com" };
context.Blogs.Add(blog);
context.SaveChanges();
Console.WriteLine(blog.BlogId + ": " + blog.Url);
}
Ma non posso usare un costruttore senza l'argomento DbContext se userò l'injection dependency. Dall'altro lato, se aggiungo l'argomento, devo passare il valore corretto quando chiamo il costruttore come nell'esempio precedente - e questa è la domanda iniziale.
Inserirò un esempio "completo". È difficile da parte mia capire, ma ci sto provando lo stesso:
Program.cs
using Hangfire;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
namespace MyProject
{
public class Program
{
public static void Main(string[] args)
{
IWebHost host = BuildWebHost(args);
BackgroundJob.Enqueue<MyClass>(x => x.ImportOperatorList());
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Hangfire;
using MyProject.Models;
namespace MyProject
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyProjectContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyProjectContext")));
services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyProjectContext")));
services.AddOptions();
services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
services.AddMvc().AddDataAnnotationsLocalization();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHangfireDashboard();
app.UseHangfireServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}
}
}
MyProjectContext.cs
using Microsoft.EntityFrameworkCore;
namespace MyProject.Models
{
public class MyProjectContext : DbContext
{
public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { }
public DbSet<Operator> Operators { get; set; }
}
public class Operator
{
public int Id { get; set; }
[MaxLength(40)]
public string Name { get; set; }
public int Password { get; set; }
}
}
MyClass.cs
using MyProject.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
namespace MyProject
{
public class MyClass
{
const string REGEX_OPERATORS = "^(?<Id>.{4})(?<Name>.{40})(?<Password>.{5})";
private readonly Regex reOperators = new Regex(REGEX_OPERATORS, RegexOptions.Compiled);
public void ImportOperatorList()
{
var path = @"F:\testdata.txt";
string[] lines = File.ReadAllLines(path);
foreach (var line in lines)
{
Match match = reOperators.Match(line);
if (match.Success)
{
string rawId = match.Groups["Id"].Value;
string rawName = match.Groups["Name"].Value;
string rawPassword = match.Groups["Password"].Value;
int Id;
try
{
Id = int.Parse(rawId, System.Globalization.NumberStyles.Integer);
}
catch (Exception)
{
throw;
}
string Name = rawName;
int Password;
try
{
Password = int.Parse(rawPassword, System.Globalization.NumberStyles.Integer);
}
catch (Exception)
{
throw;
}
using (var context = new MyProjectContext(/* ??? */))
{
var op = new Operator
{
Id = Id,
Name = Name,
Password = Password
};
context.Operators.Add(op);
Debug.WriteLine(context.SaveChanges());
}
}
}
}
}
}
Ovviamente non è completo né compilabile, perché ci sono molti altri file nel progetto (anche senza la mia specifica applicazione).
Costruire sulla tua auto risposta.
Refactor MyClass
deve dipendere dalle astrazioni e non troppo strettamente accoppiato alle concrezioni.
Ecco la refillored MyClass
public class MyClass {
const string REGEX_OPERATORS = "^(?<Id>.{4})(?<Name>.{40})(?<Password>.{5})";
private readonly Regex reOperators = new Regex(REGEX_OPERATORS, RegexOptions.Compiled);
private readonly IFileSystem File;
private readonly IProjectContext context;
public MyClass(IFileSystem File, IProjectContext context) {
this.File = File;
this.context = context;
}
public void ImportOperatorList() {
var path = @"F:\testdata.txt";
var lines = File.ReadAllLines(path);
foreach (var line in lines) {
var match = reOperators.Match(line);
if (match.Success) {
string rawId = match.Groups["Id"].Value;
string rawName = match.Groups["Name"].Value;
string rawPassword = match.Groups["Password"].Value;
var op = new Operator {
Id = int.Parse(rawId, System.Globalization.NumberStyles.Integer),
Name = rawName,
Password = int.Parse(rawPassword, System.Globalization.NumberStyles.Integer)
};
context.Operators.Add(op);
}
}
if (lines.Length > 0)
Debug.WriteLine(context.SaveChanges());
}
}
Con le seguenti modifiche
public interface IFileSystem {
string[] ReadAllLines(string path);
}
public class FileWrapper : IFileSystem {
public string[] ReadAllLines(string path) {
var lines = File.ReadAllLines(path);
return lines;
}
}
public interface IProjectContext : IDisposable {
DbSet<Operator> Operators { get; set; }
int SaveChanges();
//...add other functionality that needs to be exposed as needed
//eg: Database Database { get; }
//...
}
public class MyProjectContext : DbContext, IProjectContext {
public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { }
public DbSet<Operator> Operators { get; set; }
}
Assicurati che tutte le astrazioni siano registrate nel contenitore di servizi nella radice di composizione.
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<MyProjectContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyProjectContext")));
services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyProjectContext")));
services.AddOptions();
services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
services.AddMvc().AddDataAnnotationsLocalization();
//...adding additional services
services.AddScoped<IProjectContext, MyProjectContext>();
services.AddTransient<IFileSystem, FileWrapper>();
services.AddTransient<MyClass, MyClass>();
}
Ora, quando si utilizza il provider di servizi con ambito, è possibile chiedere la classe e tutte le dipendenze verranno iniettate quando si risolve MyClass
using (var scope = host.Services.CreateScope()) {
var services = scope.ServiceProvider;
var myClass = services.GetRequiredService<MyClass>();
myClass.ImportOperatorList();
}
Poiché l'ambito sopra è definito, il contenitore gestirà lo smaltimento di tutti i servizi creati dal contenitore quando esce dal campo di applicazione.
Devi passare manualmente l'argomento di contesto alla funzione, l'iniezione di dipendenza non lo fa per te. Quindi, in program.cs puoi aggiungere:
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<MyProjectContext>();
// pass context to relevant Classes/Functions, i.e.
MyClass myClass = new MyClass();
myClass.ImportOperatorList(context);
}
In MyClass.cs ora puoi utilizzare direttamente quella variabile:
public void ImportOperatorList(MyProjectContext context)
{
// ...
context.Operators.Add(op);
context.SaveChanges();
}