After converted all my tables to start using Guid type in identity columns, I failed to seed data, so I simplified a lot the code to localize the error, and ended with a seeding class as follows:
public class SeedTest
{
private readonly MyDbContext _context;
public SeedTest(MyDbContext context)
{
_context = context;
}
public async Task SeedTest()
{
Values value1 = new Values
{
Id = Guid.Parse("29c48913-1b5c-47b8-g144-08d6d2273deb"),
ValueName = "value 1",
Created = DateTime.Now
};
_context.Values.Add(value1);
await _context.SaveChangesAsync();
}
public SeedTest()
{
}
}
This class is called from another one:
public interface IDatabaseInitializer
{
Task SeedAsync();
}
public class DatabaseInitializer : IDatabaseInitializer
{
public async Task SeedAsync()
{
SeedTest _seedTest = new SeedTest();
await _seedTest.SeedTest();
}
}
which is called from startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
private readonly IHostingEnvironment _hostingEnvironment;
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
_hostingEnvironment = env;
}
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMyDbContext<MyDbContext>(options =>
options.UseSqlServer("ConnectionStrings:MyCn"));
...
// DB Seeding
services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
...
...
}
And here is how it is triggered from program.cs
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
databaseInitializer.SeedAsync().Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogCritical(LoggingEvents.INIT_DATABASE, ex, LoggingEvents.INIT_DATABASE.Name);
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Unfortunately this implementation didn't seed any data in the database, the unique error I could find is in the logs files, and it says:
System.NullReferenceException: Object reference not set to an instance
of an object. and it points to the last line of SeedTest class.
So what am I doing wrong ?
new SeedTest() does not initialize its _context field. You could use DI on your DatabaseInitializer to instantiate a SeedTest with a MyDbContext.
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly MyDbContext _context;
public DatabaseInitializer(MyDbContext context)
{
_context = context;
}
public async Task SeedAsync()
{
SeedTest _seedTest = new SeedTest(_context);
await _seedTest.SeedTest();
}
}
You are explicitly newing an instance of SeedTest in DatabaseInitialize, while the instance of DatabaseInitialize is being created by the dependency injection service. Register the SeedTest class in the services with the correct scope and let the dependency injection do its thing.
In ConfigureServices add something like
services.AddTransient<SeedTest>();
Modify DatabaseInitializer
public class DatabaseInitializer : IDatabaseInitializer{
private readonly SeedTest _seedTest;
public DatabaseInitializer(SeedTest seedTest)
{
_seedTest = seedTest;
}
public async Task SeedAsync()
{
await _seedTest.SeedTest();
}
}
Remove the parameterless SeedTest constructor and make sure the MyDbContext type registered is what is passed in the other constructor as you have both MyDbContext and DbContext.
You can try this, i have used .net core 2.2 for this sample -
MyDbContext.cs
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
Database.EnsureCreated();
}
public DbSet<Values> Values { get; set; }
}
SeedTest.cs
public class SeedTest
{
private readonly MyDbContext _context;
public SeedTest(MyDbContext context)
{
_context = context;
}
public async Task SeedTest1()
{
Values value1 = new Values
{
Id = Guid.Parse("AFE1052A-A694-48AF-AA77-56D2D945DE31"),
ValueName = "value 1",
Created = DateTime.Now
};
_context.Values.Add(value1);
var value = await _context.SaveChangesAsync();
}
public SeedTest()
{
}
}
Service
public interface IDatabaseInitializer
{
Task SeedAsync();
}
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly MyDbContext _cotext;
// Inject DbContext
public DatabaseInitializer(MyDbContext dbContext)
{
_cotext = dbContext;
}
public async Task SeedAsync()
{
// Object with contructor which having DbContext parameter
SeedTest _seedTest = new SeedTest(_cotext);
await _seedTest.SeedTest1();
}
}
startup.cs
services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
services.AddDbContext<MyDbContext>(option=> option.UseSqlServer("Data Source=localhost;Initial Catalog=StackOverFlow1;Integrated Security=True"));
program.cs
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var databaseInitializer = services.GetRequiredService<IDatabaseInitializer>();
databaseInitializer.SeedAsync().Wait();
}
catch (Exception ex)
{
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
You can read more about seed data.
How can I use .NET Core's default dependency injection in Hangfire?
I am new to Hangfire and searching for an example which works with ASP.NET Core.
See full example on GitHub https://github.com/gonzigonz/HangfireCore-Example.
Live site at http://hangfirecore.azurewebsites.net/
Make sure you have the Core version of Hangfire:
dotnet add package Hangfire.AspNetCore
Configure your IoC by defining a JobActivator. Below is the config for use with the default asp.net core container service:
public class HangfireActivator : Hangfire.JobActivator
{
private readonly IServiceProvider _serviceProvider;
public HangfireActivator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public override object ActivateJob(Type type)
{
return _serviceProvider.GetService(type);
}
}
Next register hangfire as a service in the Startup.ConfigureServices method:
services.AddHangfire(opt =>
opt.UseSqlServerStorage("Your Hangfire Connection string"));
Configure hangfire in the Startup.Configure method. In relationship to your question, the key is to configure hangfire to use the new HangfireActivator we just defined above. To do so you will have to provide hangfire with the IServiceProvider and this can be achieved by just adding it to the list of parameters for the Configure method. At runtime, DI will providing this service for you:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IServiceProvider serviceProvider)
{
...
// Configure hangfire to use the new JobActivator we defined.
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(serviceProvider));
// The rest of the hangfire config as usual.
app.UseHangfireServer();
app.UseHangfireDashboard();
}
When you enqueue a job, use the registered type which usually is your interface. Don't use a concrete type unless you registered it that way. You must use the type registered with your IoC else Hangfire won't find it.
For Example say you've registered the following services:
services.AddScoped<DbManager>();
services.AddScoped<IMyService, MyService>();
Then you could enqueue DbManager with an instantiated version of the class:
BackgroundJob.Enqueue(() => dbManager.DoSomething());
However you could not do the same with MyService. Enqueuing with an instantiated version would fail because DI would fail as only the interface is registered. In this case you would enqueue like this:
BackgroundJob.Enqueue<IMyService>( ms => ms.DoSomething());
DoritoBandito's answer is incomplete or deprecated.
public class EmailSender {
public EmailSender(IDbContext dbContext, IEmailService emailService)
{
_dbContext = dbContext;
_emailService = emailService;
}
}
Register services:
services.AddTransient<IDbContext, TestDbContext>();
services.AddTransient<IEmailService, EmailService>();
Enqueue:
BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));
Source:
http://docs.hangfire.io/en/latest/background-methods/passing-dependencies.html
Note: if you want a full sample, see my blog post on this.
All of the answers in this thread are wrong/incomplete/outdated. Here's an example with ASP.NET Core 3.1 and Hangfire.AspnetCore 1.7.
Client:
//...
using Hangfire;
// ...
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddHangfire(config =>
{
// configure hangfire per your requirements
});
}
}
public class SomeController : ControllerBase
{
private readonly IBackgroundJobClient _backgroundJobClient;
public SomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = backgroundJobClient;
}
[HttpPost("some-route")]
public IActionResult Schedule([FromBody] SomeModel model)
{
_backgroundJobClient.Schedule<SomeClass>(s => s.Execute(model));
}
}
Server (same or different application):
{
//...
services.AddScoped<ISomeDependency, SomeDependency>();
services.AddHangfire(hangfireConfiguration =>
{
// configure hangfire with the same backing storage as your client
});
services.AddHangfireServer();
}
public interface ISomeDependency { }
public class SomeDependency : ISomeDependency { }
public class SomeClass
{
private readonly ISomeDependency _someDependency;
public SomeClass(ISomeDependency someDependency)
{
_someDependency = someDependency;
}
// the function scheduled in SomeController
public void Execute(SomeModel someModel)
{
}
}
As far as I am aware, you can use .net cores dependency injection the same as you would for any other service.
You can use a service which contains the jobs to be executed, which can be executed like so
var jobId = BackgroundJob.Enqueue(x => x.SomeTask(passParamIfYouWish));
Here is an example of the Job Service class
public class JobService : IJobService
{
private IClientService _clientService;
private INodeServices _nodeServices;
//Constructor
public JobService(IClientService clientService, INodeServices nodeServices)
{
_clientService = clientService;
_nodeServices = nodeServices;
}
//Some task to execute
public async Task SomeTask(Guid subject)
{
// Do some job here
Client client = _clientService.FindUserBySubject(subject);
}
}
And in your projects Startup.cs you can add a dependency as normal
services.AddTransient< IClientService, ClientService>();
Not sure this answers your question or not
Currently, Hangfire is deeply integrated with Asp.Net Core. Install Hangfire.AspNetCore to set up the dashboard and DI integration automatically. Then, you just need to define your dependencies using ASP.NET core as always.
If you are trying to quickly set up Hangfire with ASP.NET Core (tested in ASP.NET Core 2.2) you can also use Hangfire.MemoryStorage. All the configuration can be performed in Startup.cs:
using Hangfire;
using Hangfire.MemoryStorage;
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(opt => opt.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
}
protected void StartHangFireJobs(IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
//TODO: move cron expressions to appsettings.json
RecurringJob.AddOrUpdate<SomeJobService>(
x => x.DoWork(),
"* * * * *");
RecurringJob.AddOrUpdate<OtherJobService>(
x => x.DoWork(),
"0 */2 * * *");
}
public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
{
StartHangFireJobs(app, serviceProvider)
}
Of course, everything is store in memory and it is lost once the application pool is recycled, but it is a quick way to see that everything works as expected with minimal configuration.
To switch to SQL Server database persistence, you should install Hangfire.SqlServer package and simply configure it instead of the memory storage:
services.AddHangfire(opt => opt.UseSqlServerStorage(Configuration.GetConnectionString("Default")));
I had to start HangFire in main function. This is how I solved it:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var liveDataHelper = services.GetRequiredService<ILiveDataHelper>();
var justInitHangfire = services.GetRequiredService<IBackgroundJobClient>();
//This was causing an exception (HangFire is not initialized)
RecurringJob.AddOrUpdate(() => liveDataHelper.RePopulateAllConfigDataAsync(), Cron.Daily());
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Can't start " + nameof(LiveDataHelper));
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Actually there is an easy way for dependency injection based job registration.
You just need to use the following code in your Startup:
public class Startup {
public void Configure(IApplicationBuilder app)
{
var factory = app.ApplicationServices
.GetService<IServiceScopeFactory>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
}
}
However i personally wanted a job self registration including on demand jobs (recurring jobs which are never executed, except by manual trigger on hangfire dashboard), which was a little more complex then just that. I was (for example) facing issues with the job service activation, which is why i decided to share most of my implementation code.
//I wanted an interface to declare my jobs, including the job Id.
public interface IBackgroundJob {
string Id { get; set; }
void Invoke();
}
//I wanted to retrieve the jobs by id. Heres my extension method for that:
public static IBackgroundJob GetJob(
this IServiceProvider provider,
string jobId) => provider
.GetServices<IBackgroundJob>()
.SingleOrDefault(j => j.Id == jobId);
//Now i needed an invoker for these jobs.
//The invoker is basically an example of a dependency injected hangfire job.
internal class JobInvoker {
public JobInvoker(IServiceScopeFactory factory) {
Factory = factory;
}
public IServiceScopeFactory Factory { get; }
public void Invoke(string jobId)
{
//hangfire jobs should always be executed within their own scope.
//The default AspNetCoreJobActivator should technically already do that.
//Lets just say i have trust issues.
using (var scope = Factory.CreateScope())
{
scope.ServiceProvider
.GetJob(jobId)?
.Invoke();
}
}
//Now i needed to tell hangfire to use these jobs.
//Reminder: The serviceProvider is in IApplicationBuilder.ApplicationServices
public static void RegisterJobs(IServiceProvider serviceProvider) {
var factory = serviceProvider.GetService();
GlobalConfiguration.Configuration.UseActivator(new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
var manager = serviceProvider.GetService<IRecurringJobManager>();
var config = serviceProvider.GetService<IConfiguration>();
var jobs = serviceProvider.GetServices<IBackgroundJob>();
foreach (var job in jobs) {
var jobConfig = config.GetJobConfig(job.Id);
var schedule = jobConfig?.Schedule; //this is a cron expression
if (String.IsNullOrWhiteSpace(schedule))
schedule = Cron.Never(); //this is an on demand job only!
manager.AddOrUpdate(
recurringJobId: job.Id,
job: GetJob(job.Id),
cronExpression: schedule);
}
//and last but not least...
//My Method for creating the hangfire job with injected job id
private static Job GetJob(string jobId)
{
var type = typeof(JobInvoker);
var method = type.GetMethod("Invoke");
return new Job(
type: type,
method: method,
args: jobId);
}
Using the above code i was able to create hangfire job services with full dependency injection support. Hope it helps someone.
Use the below code for Hangfire configuration
using eForms.Core;
using Hangfire;
using Hangfire.SqlServer;
using System;
using System.ComponentModel;
using System.Web.Hosting;
namespace eForms.AdminPanel.Jobs
{
public class JobManager : IJobManager, IRegisteredObject
{
public static readonly JobManager Instance = new JobManager();
//private static readonly TimeSpan ZeroTimespan = new TimeSpan(0, 0, 10);
private static readonly object _lockObject = new Object();
private bool _started;
private BackgroundJobServer _backgroundJobServer;
private JobManager()
{
}
public int Schedule(JobInfo whatToDo)
{
int result = 0;
if (!whatToDo.IsRecurring)
{
if (whatToDo.Delay == TimeSpan.Zero)
int.TryParse(BackgroundJob.Enqueue(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName)), out result);
else
int.TryParse(BackgroundJob.Schedule(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName), whatToDo.Delay), out result);
}
else
{
RecurringJob.AddOrUpdate(whatToDo.JobType.Name, () => RunRecurring(whatToDo.JobType.AssemblyQualifiedName), Cron.MinuteInterval(whatToDo.Delay.TotalMinutes.AsInt()));
}
return result;
}
[DisplayName("Id: {0}, Type: {1}")]
[HangFireYearlyExpirationTime]
public static void Run(int jobId, string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(jobId);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Job Id: {jobId}, Type: {jobType}", ex);
}
}
[DisplayName("{0}")]
[HangFireMinutelyExpirationTime]
public static void RunRecurring(string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(0);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Recurring Type: {jobType}", ex);
}
}
public void Start()
{
lock (_lockObject)
{
if (_started) return;
if (!AppConfigSettings.EnableHangFire) return;
_started = true;
HostingEnvironment.RegisterObject(this);
GlobalConfiguration.Configuration
.UseSqlServerStorage("SqlDbConnection", new SqlServerStorageOptions { PrepareSchemaIfNecessary = false })
//.UseFilter(new HangFireLogFailureAttribute())
.UseLog4NetLogProvider();
//Add infinity Expiration job filter
//GlobalJobFilters.Filters.Add(new HangFireProlongExpirationTimeAttribute());
//Hangfire comes with a retry policy that is automatically set to 10 retry and backs off over several mins
//We in the following remove this attribute and add our own custom one which adds significant backoff time
//custom logic to determine how much to back off and what to to in the case of fails
// The trick here is we can't just remove the filter as you'd expect using remove
// we first have to find it then save the Instance then remove it
try
{
object automaticRetryAttribute = null;
//Search hangfire automatic retry
foreach (var filter in GlobalJobFilters.Filters)
{
if (filter.Instance is Hangfire.AutomaticRetryAttribute)
{
// found it
automaticRetryAttribute = filter.Instance;
System.Diagnostics.Trace.TraceError("Found hangfire automatic retry");
}
}
//Remove default hangefire automaticRetryAttribute
if (automaticRetryAttribute != null)
GlobalJobFilters.Filters.Remove(automaticRetryAttribute);
//Add custom retry job filter
GlobalJobFilters.Filters.Add(new HangFireCustomAutoRetryJobFilterAttribute());
}
catch (Exception) { }
_backgroundJobServer = new BackgroundJobServer(new BackgroundJobServerOptions
{
HeartbeatInterval = new System.TimeSpan(0, 1, 0),
ServerCheckInterval = new System.TimeSpan(0, 1, 0),
SchedulePollingInterval = new System.TimeSpan(0, 1, 0)
});
}
}
public void Stop()
{
lock (_lockObject)
{
if (_backgroundJobServer != null)
{
_backgroundJobServer.Dispose();
}
HostingEnvironment.UnregisterObject(this);
}
}
void IRegisteredObject.Stop(bool immediate)
{
Stop();
}
}
}
Admin Job Manager
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Start();
new SchedulePendingSmsNotifications().Schedule(new Core.JobInfo() { JobId = 0, JobType = typeof(SchedulePendingSmsNotifications), Delay = TimeSpan.FromMinutes(1), IsRecurring = true });
}
}
protected void Application_End(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Stop();
}
}
}
I'm trying to use FluentValidation in a WebApi project (not asp.net Core).
I have the following code:
public static class UnityConfig
{
public static void RegisterComponents(UnityContainer container)
{
// Register validators
RegisterValidators(container);
// Mediatr
container.RegisterType<IMediator, Mediator>();
container.RegisterTypes(AllClasses.FromAssemblies(true, Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces, GetName, GetLifetimeManager);
container.RegisterInstance<SingleInstanceFactory>(t => container.Resolve(t));
container.RegisterInstance<MultiInstanceFactory>(t => container.ResolveAll(t));
// Automapper profiles
var profileTypes = typeof(BaseProfile).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BaseProfile)));
var config = new MapperConfiguration(cfg => new MapperConfiguration(x =>
{
foreach (var type in profileTypes)
{
var profile = (BaseProfile)Activator.CreateInstance(type);
cfg.AddProfile(profile);
}
}));
container.RegisterInstance<IConfigurationProvider>(config);
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
static LifetimeManager GetLifetimeManager(Type type)
{
return IsNotificationHandler(type) ? new ContainerControlledLifetimeManager() : null;
}
static string GetName(Type type)
{
return IsNotificationHandler(type) ? string.Format("HandlerFor" + type.Name) : string.Empty;
}
private static void RegisterValidators(IUnityContainer container)
{
var validators = AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly());
validators.ForEach(validator => container.RegisterType(validator.InterfaceType, validator.ValidatorType));
}
}
I'm scanning the assemblies and registrering the validators, of which there's only one right now, it sits here: (don't mind the weird validations, I'm trying to have it fail)
public class Query : IRequest<Result>
{
public Guid? Id { get; set; }
}
public class QueryValidator : AbstractValidator<Query>
{
public QueryValidator()
{
RuleFor(q => q.Id).Empty();
RuleFor(q => q.Id).Equal(Guid.NewGuid());
}
}
My Application_start looks like this:
protected void Application_Start()
{
var container = new UnityContainer();
UnityConfig.RegisterComponents(container);
GlobalConfiguration.Configure(WebApiConfig.Register);
var factory = new UnityValidatorFactory2(GlobalConfiguration.Configuration);
FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, x => x.ValidatorFactory = factory);
}
And I have the following validatorFactory:
public class UnityValidatorFactory2 : ValidatorFactoryBase
{
private readonly HttpConfiguration _configuration;
public UnityValidatorFactory2(HttpConfiguration configuration)
{
_configuration = configuration;
}
public override IValidator CreateInstance(Type validatorType)
{
var validator = _configuration.DependencyResolver.GetService(validatorType) as IValidator;
return validator;
}
}
Now; when I call the action on the controller, 'CreateInstance' tries to resolve a validatorType of the type:
IValidator<Guid>
instead of:
IValidator<Query>
and of course finds nothing, this means that my validations does not run.
Does anyone have an ideas as to why this is? it seems faily straight forward, so I have trouble seeing what goes wrong.
After having slept on it, I found the answer myself.
I was posting a Guid to my controller instead of the model I was trying to validate (which only contains a guid)
After posting the right model, it now validates correctly.
According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
public class HomeController : Controller
{
private readonly DBData _context;
public HomeController(DBData context)
{
_context = context;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
//somehow configure it to use AddTransient
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();
services.AddMvc();
}
public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactory : IDisposable
{
DBData Context { get; }
}
public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
{
public UnitOfWorkFactory(DBData context)
{
Context = context;
}
public DBData Context
{
get; private set;
}
public void Dispose()
{
Context.Dispose();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IUnitOfWorkFactory CreateUoWinCurrentThread()
{
var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
public class HomeController : Controller
{
private readonly IUnitOfWorkFactoryPerCall _contextFactory;
public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
{
_contextFactory = contextFactory;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I'm trying to set up a signalr hub on my project, using simpleinjector for dependency injection.
I have some configuration in Startup.cs
[assembly: OwinStartup(typeof(Startup))]
namespace CallCentre.Client
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = new Container();
container.Register<TwilioHub>();
container.Verify();
var config = new HubConfiguration()
{
Resolver = new SignalRSimpleInjectorDependencyResolver(container)
};
app.MapSignalR(config);
}
}
}
DI Resolver
public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver
{
private readonly Container _container;
public SignalRSimpleInjectorDependencyResolver(Container container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
return ((IServiceProvider)_container).GetService(serviceType)
?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances(serviceType)
.Concat(base.GetServices(serviceType));
}
}
My hub
public class TwilioHub : Hub
{
public void Send(int callCount, int queueId)
{
var context = GlobalHost.ConnectionManager.GetHubContext<TwilioHub>();
context.Clients.All.updateQueueCount(callCount, queueId);
}
}
A class elsewhere in the solution where the hub is called
public class QueueStateHandler : IQueueStateHandler
{
private readonly TwilioHub _twilioHub;
public QueueStateHandler(TwilioHub twilioHub)
{
_twilioHub = twilioHub;
}
public void IncrementQueueById(int id)
{
_twilioHub.Send(5,1);
}
}
And my frontend code
$(function () {
var hub = $.connection.twilioHub;
hub.logging = true;
var queue = $('#QueueCount');
hub.client.updateQueueCount = function(queueCount, id) {
alert(queueCount);
};
$.connection.hub.start();
});
I can set everything up using straight web api, skipping the DI and everything works fine. As soon as I bring in DI I start running into problems. At the moment its silently falling over somewhere. I can step through the code and end up in the hub, but nothing happens in my frontend.
Any pointers as to what I'm doing wrong would be greatly appreciated.