Console apps don't use the Startup file with configure services like web apps do and I'm struggling to understand the crucial concept of Dependency Injection.
(Please note the below example does not compile)
Here is a basic example of how I think it should work (please do point out anything unconventional or wrong):
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddUserSecrets<Settings>()
.Build();
var services = new ServiceCollection()
.AddLogging(b => b
.AddConsole())
.AddDbContext<UnderstandingDIContext>(options =>
options.UseSqlite(builder.GetConnectionString("DefaultConnection")))
.BuildServiceProvider();
var logger = services.GetService<ILoggerFactory>()
.CreateLogger<Program>();
logger.LogInformation("Starting Application");
var worker = new Worker();
logger.LogInformation("Closing Application");
}
But how do I use these services inside my 'Worker' class?:
public Worker(ILogger logger, IConfiguration configuration)
{
logger.LogInformation("Inside Worker Class");
var settings = new Settings()
{
Secret1 = configuration["Settings:Secret1"],
Secret2 = configuration["Settings:Secret2"]
};
logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
logger.LogInformation($"Secret 2 is '{settings.Secret2}'");
using (var context = new UnderstandingDIContext())
{
context.Add(new UnderstandingDIModel()
{
Message = "Adding a message to the database."
});
}
}
UnderstandingDIContext
public class UnderstandingDIContext : DbContext
{
public UnderstandingDIContext(DbContextOptions<UnderstandingDIContext> options)
: base(options)
{ }
public DbSet<UnderstandingDIModel> UnderstandingDITable { get; set; }
}
The problems with this code are as follows:
Worker() is expecting to be passed ILogger and IConfiguration parameters but I thought Dependency Injection should cover that?
I cannot run 'dotnet ef migrations add Initial' because I'm not correctly passing in the connection string (error: 'Unable to create an object of type 'UnderstandingDIContext'.')
'using (var context = new UnderstandingDIContext())' won't compile because I'm misunderstanding the DbContext bit.
I've searched around A LOT and there's lots of examples for web apps but very little for Console apps. Am I just completely misunderstanding the entire concept of Dependency Injection?
When using constructor injection, dependencies will only be resolved when the object you are creating is actually created through dependency injection itself. So the key to make dependency injection work within your Worker is to actually resolve Worker through the dependency injection container as well.
This is actually pretty simple:
var services = new ServiceCollection()
.AddLogging(b => b.AddConsole())
.AddDbContext<UnderstandingDIContext>(options =>
options.UseSqlite(builder.GetConnectionString("DefaultConnection")));
// register `Worker` in the service collection
services.AddTransient<Worker>();
// build the service provider
var serviceProvider = services.BuildServiceProvider();
// resolve a `Worker` from the service provider
var worker = serviceProvider.GetService<Worker>();
var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("Starting Application");
worker.Run();
logger.LogInformation("Closing Application");
In addition, since you are using a database context which gets registered as a scoped dependency by default, I would recommend you to create a service scope as well—or alternatively change the lifetime of the database context when you register it.
var serviceProvider = services.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var worker = serviceProvider.GetService<Worker>();
worker.Run();
}
Note that I also made an explicit method Run on your worker, so that you don’t have the logic within the constructor.
public class Worker
{
private readonly ILogger<Worker> _logger = logger;
private readonly IConfiguration _configuration = configuration;
private readonly UnderstandingDIContext _dbContext = dbContext;
public Worker(ILogger<Worker> logger, IConfiguration configuration, UnderstandingDIContext dbContext)
{
_logger = logger;
_configuration = configuration;
_dbContext = dbContext;
}
public void Run()
{
_logger.LogInformation("Inside Worker Class");
var settings = new Settings()
{
Secret1 = configuration["Settings:Secret1"],
Secret2 = configuration["Settings:Secret2"]
};
_logger.LogInformation($"Secret 1 is '{settings.Secret1}'");
_logger.LogInformation($"Secret 2 is '{settings.Secret2}'");
_dbContext.Add(new UnderstandingDIModel()
{
Message = "Adding a message to the database."
});
_dbContext.SaveChanges();
}
}
Take a look at this API.
https://github.com/Akeraiotitasoft/ConsoleDriving
Nuget: Akeraiotitasoft.ConsoleDriving
Related
I'm trying to use Hangfire in an ASP.NET Core 5 web application. I'm using dependency injection to pass in various dependencies to a hangfire task that will run every minute. Unfortunately, when I attempt to execute a query using entity framework core, I receive the following error message:
System.ObjectDisposedException: 'Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'AppDbContext'.'
Here is the task I want to run each minute with Hangfire:
public class ReportContentGenerator : IReportContentGenerator
{
private readonly ILogger<ImageCopier> _logger;
private readonly CosmosDbService _cosmosDbService;
private readonly IBlobStorageService _blobStorageService;
private readonly AppDbContext _dbContext;
public ReportContentGenerator(ILogger<ImageCopier> logger, IBlobStorageService blobStorageService, CosmosDbService cosmosDbService, AppDbContext appDbContext)
{
_logger = logger;
_blobStorageService = blobStorageService;
_cosmosDbService = cosmosDbService;
_dbContext = appDbContext;
Console.WriteLine("Image copier instantiated...");
}
public async void TransposeImageAnalysisIntoReportContent()
{
// Query CosmosDB for all orders that have not yet had their images copyed from the staging storage into the processing storage.
var orders = await _cosmosDbService.GetItemsAsync("SELECT * FROM c WHERE c['status']['value'] = 'Processing' OR c['status']['value'] = 'Draft' ");
var filaments = await _dbContext.Filaments.ToListAsync();
var recommendationsContent = await _dbContext.ReportContents
.ToListAsync();
// Ommitted for brevity
}
}
Here is my COnfigureServices...
// Add Hangfire services.
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd");
services.AddAuthentication()
.AddJwtBearer("ApiAuth", options =>
{
options.Audience = Configuration["AzureAdApi:ResourceId"];
options.Authority = $"{Configuration["AzureAdApi:Instance"]}{Configuration["AzureAdApi:TenantId"]}";
});
services.AddSingleton(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
services.Configure<BlobStorageOptions>(Configuration.GetSection("BlobStorageOptions"));
services.AddSingleton<IBlobStorageService, BlobStorageService>();
services.AddSingleton<IImageCopier, ImageCopier>();
services.AddScoped<IReportContentGenerator, ReportContentGenerator>();
Here is where I instantiate my Hangfire tasks in Startup Configure:
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env)
{
//Ommitted for brevity...
RecurringJob.AddOrUpdate<IImageCopier>(x => x.CopyImages(), Cron.Minutely);
RecurringJob.AddOrUpdate<IReportContentGenerator>(x => x.TransposeImageAnalysisIntoReportContent(), Cron.Minutely);
//Ommitted for brevity...
}
you have to configure database connection for hangfire
Hangfire Configration for .NET Core
For anyone else who hits this, the fix is to replace the void return type in my tasks with the Task return type:
public async Task TransposeImageAnalysisIntoReportContent() {
...
}
Bit of a newbie question. I am having trouble getting access to dependency injected services from within my own custom class in ASP.NET Core 3.1
I can access services fine from within a controller or razor page e.g. I can get hold of configuration and data context information:
public class DetailModel : PageModel
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
public DetailModel(MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
}
etc......
}
I now wish to access these from the constructor of a custom class that is not a controller or razor page. e.g. I am using:
public class ErrorHandling
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
public ErrorHandling(MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
}
}
The problem is that when I instantiate my class it insists on me passing the service values into the constructor:
var myErrorHandler = new ErrorHandling(`<wants me to pass context and config values here>`)
This defeats the whole point of DI. I think I am missing something fundamental here!
What am I missing?
You can register ErrorHandling as a service too, in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// other stuff..
services.AddScoped<ErrorHandling>(); // this should work as long as both 'MyDataContext' and 'IConfiguration' are also registered
}
If you need an instance of ErrorHandling in your page model, you can specify it in the constructor and ASP.NET Core will resolve it for you at runtime.
This way you won't have to new it:
public class DetailModel : PageModel
{
private readonly MyDataContext _context;
private readonly IConfiguration _config;
private readonly ErrorHandling _errorHandling;
public DetailModel(ErrorHandling errorHandling, MyDataContext context, IConfiguration config)
{
_context = context;
_config = config;
_errorHandling = errorHandling;
}
}
This article can be useful: Dependency injection in ASP.NET Core
If you don't want register as a service, you can use ActivatorUtilities.CreateInstance to resolve ErrorHandling.
Instantiate a type with constructor arguments provided directly and/or from an IServiceProvider.
e.g.:
// IServiceProvider serviceProvider = ...;
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(serviceProvider);
BUT you need to be careful about this solution:
ServiceProvider scope should equal with dependency object (MyDataContext, IConfiguration). Otherwise, you will get an exception like:
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(app.ApplicationServices);
// An exception of type 'System.InvalidOperationException' occurred
// in Microsoft.Extensions.DependencyInjection.dll but was not handled in user cod
// e: 'Cannot resolve scoped service 'three.MyDataContext' from root provider.'
For this, you can create an scope to resolve ErrorHandling:
using (var scope = app.ApplicationServices.CreateScope())
{
var errorHandling = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
}
Dependency injection service would not call Dispose on IDisposable instances even out of scope.
For this, you should call Dispose() by yourself:
using (var scope = app.ApplicationServices.CreateScope())
{
using var disposablClass = ActivatorUtilities.CreateInstance<DisposablClass>(scope.ServiceProvider);
}
ActivatorUtilities.CreateInstance will new an instance even you use the same ServiceProvider:
using (var scope = app.ApplicationServices.CreateScope())
{
var errorHandling1 = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
Console.WriteLine(errorHandling1.GetHashCode());
// 11903911
var errorHandling2 = ActivatorUtilities.CreateInstance<ErrorHandling>(scope.ServiceProvider);
Console.WriteLine(errorHandling2.GetHashCode());
// 40026340
}
I've been reading several posts but nothing seems to fit to my problem.
I'm developing a console app with .Net Core 2.1 and EF Core trying to follow Microsoft's advices but I'm facing with the next problem.
I've a project named myproject.data which contains all the interfaces and services. This one, for example
ILeagueService.cs
public interface ILeaguesService
{
List<Leagues> GetAllLeaguesValids();
}
LeagueService.cs
private statsContext _context;
public LeaguesService(statsContext context)
{
_context = context;
}
public List<Leagues> GetAllLeaguesValids()
{
return _context.Leagues.Where(x => (x.DateFirstMatch != null || x.CurrentLastSeason == true) && x.Active == true).OrderBy(x => x.Id).ToList();
}
Then, I have all the methods of my app separated and all of them inherit from the same class. In this Base.cs class I setup the ServiceProvider
Base.cs
public ServiceProvider _serviceProvider;
public Base()
{
ConfigureServices();
_config = new HelperConfig(CONFIG_FILE);
_html = GetHelperHtml();
_context = GetContext();
}
private void ConfigureServices()
{
_serviceProvider = new ServiceCollection()
.AddScoped<ILeaguesService, LeaguesService>()
.BuildServiceProvider();
}
When I try to use the LeagueService in one of the methods I get the 'Unable to resolve service for type myproject.stats.statsContext' error
GetNextMatches.cs
private ILeaguesService _leagueService;
public GetNextMatches()
{
_config.GetSection(AppsettingsModel.BetExplorerUrlsSection).Bind(betExplorerSectionKeys);
_leagueService = _serviceProvider.GetService<ILeaguesService>(); <-- In this line I get the error
}
When using the ServiceProvider DI you have to register all of the classes in the hierarchy. The DI container is trying to create your LeagueService class but to call its constructor it needs to create an instance of statsContext. However it cannot find that in its registry so it throws the exception.
The solution is to add statsContext to your services collection.
private void ConfigureServices()
{
_serviceProvider = new ServiceCollection()
.AddScoped<ILeaguesService, LeaguesService>()
.AddScoped<statsContext>()
.BuildServiceProvider();
}
I am going to assume your _context variable is the statsContext you want to inject so you can use your GetContext() method to create the context for you:
private void ConfigureServices()
{
_serviceProvider = new ServiceCollection()
.AddScoped<ILeaguesService, LeaguesService>()
.AddSingleton<statsContext>(GetContext())
.BuildServiceProvider();
}
This will call your GetContext() once to create your single instance of statsContext. Now, whenever you call
_leagueService = _serviceProvider.GetService<ILeaguesService>();
DI will inject the singleton instance of statsContext when it creates your LeageService class.
I'm using hangfire to lauch background job but I have a problem when I try to use the hangfire autofac integration with generic to resolve automaticly the task service it's not working because it's is not able to be resolve one of the dependency. I suppose because I don't receive any error.
BackgroundJob.Enqueue<IBackgroundTask>(x => x.RunAsync() );
If I use the other way around by resolving by myselft it's working.
var service = ApplicationContainer.Resolve<IBackgroundTask>();
BackgroundJob.Enqueue(() => service.RunAsync() );
I figured out that in my constructor I have a test service that cause the problem. If I remove the service in the constructor the service get resolved.
public class ConvertCarteCreditService : IBackgroundTask
{
private readonly ILogger logger;
private readonly ITest testService;
public BackgroundTask(ILogger logger, **ITest test**)
{
this.logger = logger;
this.testService = test;
// this.testService = Startup.Resolve<ITest>();
}
I have configured autofac in the startup class like this :
public void ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
ServiceLayerInstaller.ConfigureServices(builder);
DataLayerInstaller.ConfigureServices(builder, connectionString, readOnlyConnectionString);
builder.RegisterAssemblyTypes(typeof(WorkerRoleInstaller).
GetTypeInfo().Assembly).Where(t => t.Name.EndsWith("Test"))
.AsImplementedInterfaces();
WorkerRoleInstaller.ConfigureServices(builder);
builder.Populate(services);
ApplicationContainer = builder.Build();
var autofacJobActivator = new AutofacJobActivator(ApplicationContainer);
GlobalConfiguration.Configuration.UseActivator(autofacJobActivator);
}
I found my probleme was that I didn't return the service provider** from the configures services function but instead create the function as void and return nothing.
public **IServiceProvider** ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDirectoryBrowser();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
var builder = new ContainerBuilder();
builder.Populate(services);
ServiceLayerInstaller.ConfigureServices(builder);
WorkerRoleInstaller.ConfigureServices(builder);
ApplicationContainer = builder.Build();
var autofacJobActivator = new AutofacJobActivator(ApplicationContainer, false);
GlobalConfiguration.Configuration.UseActivator(autofacJobActivator);
**return new AutofacServiceProvider(ApplicationContainer);**
}
IServiceProvider is an interface with single method:
object GetService(Type serviceType);
It's used to create instances of types registered in .NET Core native DI container.
An instance of IServiceProvider itself can be obtained by calling a BuildServiceProvider method of an IServiceCollection. IServiceCollection is a parameter of ConfigureServices method in a Startup class. It seems to be magically called with an instance of IServiceCollection by the framework.
I want to create an instance of IServiceProvider without having Setup method at all. I need it to resolve dependencies in an integration test assembly. Is it possible to get it at all in this scenario?
As goaty mentioned it's enough to create new ServiceCollection. Here's example class which can be used to access DI container in .NET Core:
public static class ServiceProviderFactory
{
public static IServiceProvider ServiceProvider { get; }
static ServiceProviderFactory()
{
HostingEnvironment env = new HostingEnvironment();
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";
Startup startup = new Startup(env);
ServiceCollection sc = new ServiceCollection();
startup.ConfigureServices(sc);
ServiceProvider = sc.BuildServiceProvider();
}
}
Startup class is taken from tested project so the service registrations don't need to be repeated.
Then in test class simply use:
var foo = ServiceProviderFactory.ServiceProvider.GetServices(typeof(IFoo));
This is the default implementation of IServiceCollection from Microsoft:
https://github.com/aspnet/DependencyInjection/blob/master/src/DI/ServiceCollection.cs
Looking at the code then you should be able to get an IServiceCollection simply by calling:
var serviceCollection = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
Hope that helps :)
To get access to existing DI of ASP.NET Core application e.g. in some controller, you should just resolve it in a constructor. Example with some manager and workers:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IFooManager, FooManager>();
services.AddTransient<IFooWorker, FooWorker>();
}
Manually resolve workers for manager:
public class FooManager: IFooManager
{
private readonly IServiceProvider _serviceProvider;
public FooManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var w1 = _serviceProvider.GetRequiredService<IFooWorker>(); // new instance of FooWorker
var w2 = _serviceProvider.GetRequiredService<IFooWorker>(); // new instance of FooWorker
}
}
First you need to install the Microsoft.Extensions.DependencyInjection NuGet package. (docs, API, API)
Then you create a new ServiceCollection and method chain it with the BuildServiceProvider method. In between that you can also register any service providers.
var serviceProvider = new ServiceCollection()
.AddSingleton<IFooService, FooService>()
.BuildServiceProvider();
Here is an updated approach:
var host = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder =>
{
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
env.ContentRootPath = Directory.GetCurrentDirectory();
env.EnvironmentName = "Development";
});
builder.UseStartup<Startup>();
}).Build();
Example usage:
host.Services.GetService<IFoo>();
You can find it in Program.cs
public static IServiceProvider ServiceProvider { get; private set; }
public static void Main(string[] args)
{
IHost build = CreateHostBuilder(args).Build();
ServiceProvider = build.Services;
build.Run();
}
use this
ServiceProviderA = new ServiceCollection(). . . . . .BuildServiceProvider() .GetRequiredService<IServiceProvider>();
this ServiceProviderA contain itself