I have a website Angular frontend and WebAPI on the backend with all my controllers, I also have a service (C# class) that I call as a singleton as a long running task to listen for incoming Azure service bus messages.
FYI - I can't pass any scoped services (DbContext) to a singleton (ServiceBusConsumer), so I can't pass in my DB context to this service.
QUESTION - Once I receive an incoming service bus message, how do I call up my DB and use it?
Here is my service listening for and receiving messages.
Startup.cs
services.AddSingleton<IServiceBusConsumer, ServiceBusConsumer>();
Program.cs -> in Main() I start the service
var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();
ServiceBusConsumer.cs
public class ServiceBusConsumer : IServiceBusConsumer
{
private readonly IConfiguration _config;
private readonly ServiceBusClient _queueClient;
private readonly ServiceBusProcessor _processor;
// private readonly DataContext _context;
public ServiceBusConsumer(IConfiguration config,
// DataContext context)
{
_config = config;
// _context = context;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
public void RegisterOnMessageHandlerAndReceiveMessages() {
_processor.ProcessMessageAsync += MessageHandler;
_processor.ProcessErrorAsync += ErrorHandler;
_processor.StartProcessingAsync();
}
private async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
JObject jsonObject = JObject.Parse(body);
var eventStatus = (string)jsonObject["EventStatus"];
await args.CompleteMessageAsync(args.Message);
// _context is disposed
// want to connect to DB here but don't know how!
// var ybEvent = _context.YogabandEvents.Where(p => p.ServiceBusSequenceNumber == args.Message.SequenceNumber).FirstOrDefault();
}
private Task ErrorHandler(ProcessErrorEventArgs args)
{
var error = args.Exception.ToString();
return Task.CompletedTask;
}
}
Error
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.\nObject name: 'DataContext'.
Here is Program.cs
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
var context = services.GetRequiredService<DataContext>();
var userManager = services.GetRequiredService<UserManager<User>>();
var roleManager = services.GetRequiredService<RoleManager<Role>>();
var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occured during migration");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Here is Startup.cs -> just the ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(MappingEvents));
services.AddAutoMapper(typeof(MappingMembers));
services.AddAutoMapper(typeof(MappingUsers));
services.AddAutoMapper(typeof(MappingYogabands));
services.AddAutoMapper(typeof(MappingReviews));
// objects being passed back to the UI. Before I was passing User/Photo/etc and they
// had loops/refrences back to the user objects
services.AddControllers().AddNewtonsoftJson(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Error;
});
services.AddDbContext<DataContext>(x =>
// x.UseSqlite(_config.GetConnectionString("DefaultConnection"), y => y.UseNetTopologySuite()));
x.UseSqlServer(_config.GetConnectionString("SqlServerConnection"), y => y.UseNetTopologySuite()));
services.Configure<AuthMessageSenderOptions>(_config.GetSection("SendGrid"));
services.Configure<AuthMessageSenderOptionsNew>(_config.GetSection("SendGrid"));
services.Configure<ConfirmationOptions>(_config.GetSection("Confirmation"));
services.Configure<CloudinarySettings>(_config.GetSection("CloudinarySettings"));
services.AddApplicationServices();
services.AddIdentityServices(_config);
services.AddSwaggerDocumentation();
services.AddCors(opt =>
{
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200");
});
});
}
Here is AddApplicationServices()
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
// scoped - better option when you want to maintain state within a request
// services.AddScoped<IEventConsumer, EventConsumer>();
services.AddScoped<IServiceBusProducer, ServiceBusProducer>();
services.AddSingleton<IServiceBusConsumer, ServiceBusConsumer>();
services.AddScoped<IEmailSender, EmailSender>();
services.AddScoped<IEmailSender, EmailSenderNew>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<ITokenService, TokenService>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped(typeof(IGenericRepository<>), (typeof(GenericRepository<>)));
services.AddScoped<LogUserActivity>();
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errors = actionContext.ModelState
.Where(e => e.Value.Errors.Count > 0)
.SelectMany(x => x.Value.Errors)
.Select(x => x.ErrorMessage).ToArray();
var errorResponse = new ApiValidationErrorResponse
{
Errors = errors
};
return new BadRequestObjectResult(errorResponse);
};
});
return services;
}