I have a background job where i need to have tenantId.
I did authenticate and I tested the tenantId, it is not null. I used other endpoints and it works wonderful but when I test the backgroundjob tenantId is always null.
I don't know if I am missing something or I need to send tenantId in the args.
This is the BJ
public class BackgroundNotificationJob : AsyncBackgroundJob<NotificationArgs>, ITransientDependency
{
private readonly FirebaseAppService _firebaseAppService;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public BackgroundNotificationJob (
FirebaseAppService firebaseAppService ,
IUnitOfWorkManager unitOfWorkManager)
{
_firebaseAppService = firebaseAppService;
_unitOfWorkManager = unitOfWorkManager;
}
public override async Task ExecuteAsync (NotificationArgs args)
{
foreach (var notification in args.Notifications)
{
await _firebaseAppService.CreateMessage(notification.Key, notification.Value.ToString(), args.UserId);
}
}
}
The config:
public override void ConfigureServices ( ServiceConfigurationContext context )
{
var configuration = context.Services.GetConfiguration();
ConfigureHangfire(context, configuration);
}
private void ConfigureHangfire (ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddHangfire(config =>
{
config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
});
}
Related
I need to create a wrapper class for IAzureMediaServicesClient which if injected as a scoped service (in a single http request) can return same client object to the callers.
This is the current wrapper code that needs to be fixed.
public class AzureMediaServicesClientProvider : IAzureMediaServicesClientProvider
{
private readonly IConfiguration _configuration;
public AzureMediaServicesClientProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task<IAzureMediaServicesClient> GetClient()
{
ServiceClientCredentials credentials = await ApplicationTokenProvider.LoginSilentAsync(
_configuration[ConfigurationConstants.AadTenantId],
_configuration[ConfigurationConstants.AmsAadClientId],
_configuration[ConfigurationConstants.AmsAadSecret]);
return new AzureMediaServicesClient(new Uri(_configuration[ConfigurationConstants.ArmEndpoint]), credentials)
{
SubscriptionId = _configuration[ConfigurationConstants.SubscriptionId],
};
}
}
The class is registered as a Scoped service in DI
public static IServiceCollection AddAzureMediaServiceClient(this IServiceCollection serviceCollection)
{
return serviceCollection.AddScoped<IAzureMediaServicesClientProvider, AzureMediaServicesClientProvider>();
}
and a sample usage in code
public async Task<Job> CreateJobAsync(string transformName, Job job)
{
IAzureMediaServicesClient client = await _azureMediaServicesClientFactory.GetClient();
return await client.Jobs.CreateAsync(_resourceGroupName, _accountName, transformName, job.Name, job);
}
public async Task<Job> GetJobAsync(string transformName, string jobName)
{
IAzureMediaServicesClient client = await _azureMediaServicesClientFactory.GetClient();
return await client.Jobs.GetAsync(_resourceGroupName, _accountName, transformName, jobName);
}
Now the methods GetJobAsync and CreateJobAsync can be used in the same request and currently in such scenario for each of them a new client would be created. How can the provider class be rewritten so that in a single request same client object would be returned ? (I know I could inject it in a higher level and just pass the value to these methods but this is a simplified example and the real world use case would require a lot of refactoring to achieve this).
public async Task TestMethod()
{
var job = await GetJobAsync(...);
// Do some code modifications
await CreateJobAsync(...);
// How can we make sure here that both GetJobAsync and
// CreateJobAsync used the same client AzureMediaServicesClient instance ?
}
Below sample shows the intent but wouldn't be thread safe if I understand correctly ?
public class AzureMediaServicesClientProvider : IAzureMediaServicesClientProvider
{
private readonly IConfiguration _configuration;
private IAzureMediaServicesClient _client;
public AzureMediaServicesClientProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task<IAzureMediaServicesClient> GetClient()
{
if (_client == null)
{
ServiceClientCredentials credentials = await ApplicationTokenProvider.LoginSilentAsync(
_configuration[ConfigurationConstants.AadTenantId],
_configuration[ConfigurationConstants.AmsAadClientId],
_configuration[ConfigurationConstants.AmsAadSecret]);
_client = new AzureMediaServicesClient(new Uri(_configuration[ConfigurationConstants.ArmEndpoint]), credentials)
{
SubscriptionId = _configuration[ConfigurationConstants.SubscriptionId],
};
}
return _client;
}
}
You can use the AsyncLazy<T> from the package Microsoft.VisualStudio.Threading:
public class AzureMediaServicesClientProvider : IAzureMediaServicesClientProvider
{
private readonly IConfiguration _configuration;
private readonly AsyncLazy<IAzureMediaServicesClient> _lazyClient;
public AzureMediaServicesClientProvider(IConfiguration configuration)
{
_configuration = configuration;
_lazyClient = new AsyncLazy<IAzureMediaServicesClient>(CreateClient);
}
public Task<IAzureMediaServicesClient> GetClient()
{
return _lazyClient.GetValueAsync();
}
private async Task<IAzureMediaServicesClient> CreateClient()
{
ServiceClientCredentials credentials = await ApplicationTokenProvider.LoginSilentAsync(
_configuration[ConfigurationConstants.AadTenantId],
_configuration[ConfigurationConstants.AmsAadClientId],
_configuration[ConfigurationConstants.AmsAadSecret]);
return new AzureMediaServicesClient(new Uri(_configuration[ConfigurationConstants.ArmEndpoint]), credentials)
{
SubscriptionId = _configuration[ConfigurationConstants.SubscriptionId],
};
}
}
AsyncLazy<T> is thread-safe for all members.
Goal:
Fundamentally I am trying to add a background job, that has dependencies injected, to a console application.
Problem:
Although the jobs are queued, they are never executed.
Program.cs
var appSettings = ConfigHelper.GetConfig();
Console.WriteLine("Initialising Hangfire Server...");
GlobalConfiguration.Configuration.UseSqlServerStorage(appSettings.ConnectionString);
using (var server = new BackgroundJobServer())
{
Console.WriteLine("Hangfire Server started.");
var t = serviceProvider.GetService<ITestService>();
t.Test();
Console.ReadKey();
}
ServiceProviderFactory.cs
public static void Setup()
{
IServiceCollection services = new ServiceCollection();
...
services.AddDbContext<Db>(x => x.UseSqlServer(appSettings.ConnectionString));
services.AddTransient<IInsertLogJob, InsertLogJob>();
services.AddTransient<ITestService, TestService>();
_serviceProvider = services.BuildServiceProvider();
}
TestService.cs
public interface ITestService
{
void Test();
}
public class TestService : ITestService
{
private readonly ILogger<TestService> _logger;
public TestService(ILogger<TestService> logger)
{
_logger = logger;
}
public void Test()
{
logger.LogInformation("Test");
_logger.LogError("Error");
}
}
Logger.cs
public class Logger : ILogger
{
...
Log log = new Log()
{
Message = message,
EventId = eventId.Id,
ObjectId = eventId.Name,
LogLevel = logLevel.ToString(),
CreatedTime = DateTime.Now
};
BackgroundJob.Enqueue<IInsertLogJob>(j => j.Insert(log));
}
InsertLogJob.cs
public interface IInsertLogJob
{
void Insert(Log log);
}
public class InsertLogJob : IInsertLogJob
{
private Db _dataContext;
public InsertLogJob(Db context)
{
_dataContext = context;
}
public void Insert(Log log)//<-- this never happens
{
_dataContext.Logs.Add(log);
_dataContext.SaveChanges();
}
}
DB Record
So all the code up to the point where the data has to be inserted into the database runs, the Hangfire job gets inserted as per the picture above, but the code is never executed.
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.
I'm using Owin.Testing as test env. In my controller i need to get remote ip address from the caller.
//in my controller method
var ip = GetIp(Request);
Util
private string GetIp(HttpRequestMessage request)
{
return request.Properties.ContainsKey("MS_HttpContext")
? (request.Properties["MS_HttpContext"] as HttpContextWrapper)?.Request?.UserHostAddress
: request.GetOwinContext()?.Request?.RemoteIpAddress;
}
As a result Properties does not contains MS_HttpContext and RemoteIpAddress of OwinContext is null.
Is there any option to get IP?
Found the solution. Use testing middleware for this. Everything in your tests project:
public class IpMiddleware : OwinMiddleware
{
private readonly IpOptions _options;
public IpMiddleware(OwinMiddleware next, IpOptions options) : base(next)
{
this._options = options;
this.Next = next;
}
public override async Task Invoke(IOwinContext context)
{
context.Request.RemoteIpAddress = _options.RemoteIp;
await this.Next.Invoke(context);
}
}
Handler:
public sealed class IpOptions
{
public string RemoteIp { get; set; }
}
public static class IpMiddlewareHandler
{
public static IAppBuilder UseIpMiddleware(this IAppBuilder app, IpOptions options)
{
app.Use<IpMiddleware>(options);
return app;
}
}
Testing startup:
public class TestStartup : Startup
{
public new void Configuration(IAppBuilder app)
{
app.UseIpMiddleware(new IpOptions {RemoteIp = "127.0.0.1"});
base.Configuration(app);
}
}
And then create test server via TestStartup:
TestServer = TestServer.Create<TestStartup>();
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();
}
}