I have an ASP.NET Core application running .NET 5 and C# 9. This also runs a Discord bot in the background. My ConfigureServices() method in Startup.cs looks like this.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var client = new DiscordSocketClient(new DiscordSocketConfig
{
AlwaysDownloadUsers = true,
MessageCacheSize = 10000,
GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMessages |
GatewayIntents.GuildMessageReactions | GatewayIntents.GuildPresences,
LogLevel = LogSeverity.Info
});
var commandService = new CommandService(new CommandServiceConfig
{
LogLevel = LogSeverity.Debug,
DefaultRunMode = RunMode.Sync,
CaseSensitiveCommands = false,
IgnoreExtraArgs = false,
});
services
.AddMediatR(Assembly.GetEntryAssembly())
.AddHostedService<StartupService>()
.AddHostedService<DiscordListener>()
.AddScoped<ITestService, TestService>()
.AddSingleton(client)
.AddSingleton(provider =>
{
commandService.AddModulesAsync(Assembly.GetEntryAssembly(), provider);
return commandService;
})
.AddSingleton(Configuration);
}
As you can see, I have added ITestService and TestService as a scoped service.
public class TestService : ITestService
{
public async Task<string> GetString()
{
await Task.Delay(1);
return "hey";
}
}
public interface ITestService
{
Task<string> GetString();
}
I then inject this service into my command module.
public class TestModule : ModuleBase<SocketCommandContext>
{
private readonly ITestService _testService;
public TestModule(ITestService testService)
{
_testService = testService;
}
[Command("ping")]
public async Task Ping()
{
var str = await _testService.GetString();
await ReplyAsync(str);
}
}
However, the application does not respond to the ping command. In fact, my handler for receiving messages is not hit at all (I have checked via breakpoint). This is the hosted services that listens for events and publishes the relevant MediatR notifications.
public partial class DiscordListener : IHostedService
{
private readonly DiscordSocketClient _client;
private readonly IServiceScopeFactory _serviceScopeFactory;
public DiscordListener(
DiscordSocketClient client,
IServiceScopeFactory serviceScopeFactory)
{
_client = client;
_serviceScopeFactory = serviceScopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_client.MessageReceived += MessageReceived;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_client.MessageReceived -= MessageReceived;
return Task.CompletedTask;
}
// Creating our own scope here
private async Task MessageReceived(SocketMessage message)
{
using var scope = _serviceScopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new MessageReceivedNotification(message));
}
}
And this is the notification handler that handles the notification.
public class CommandListener : INotificationHandler<MessageReceivedNotification>
{
private readonly IConfiguration _configuration;
private readonly DiscordSocketClient _client;
private readonly CommandService _commandService;
private readonly IServiceProvider _serviceProvider;
public CommandListener(
IConfiguration configuration,
DiscordSocketClient client,
CommandService commandService,
IServiceProvider serviceProvider)
{
_configuration = configuration;
_client = client;
_commandService = commandService;
_serviceProvider = serviceProvider;
}
public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
if (!(notification.Message is SocketUserMessage message)
|| !(message.Author is IGuildUser user)
|| user.IsBot)
{
return;
}
var argPos = 0;
var prefix = _configuration["Prefix"];
if (message.HasStringPrefix(prefix, ref argPos))
{
var context = new SocketCommandContext(_client, message);
using var scope = _serviceProvider.CreateScope();
await _commandService.ExecuteAsync(context, argPos, scope.ServiceProvider);
}
}
}
Just to clarify, the breakpoint at _client.MessageReceoved += ... is not hit. If I change the ITestService and TestService implementation to a Singleton, then the handler is hit and the command works as expected. Any idea on what I'm doing wrong?
Here is the GitHub repo to the project if you want to see the full code. It is not too large.
This a typical problem when mixing Singleton and scoped services. If you end up with situation a singleton is resolving a scoped service it is not allowed.
From docs here
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
Do not resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests. It's fine to:
Resolve a singleton service from a scoped or transient service.
Resolve a scoped service from another scoped or transient service.
By default, in the development environment, resolving a service from another service with a longer lifetime throws an exception. For more information, see Scope validation.
Also more discussion on https://dotnetcoretutorials.com/2018/03/20/cannot-consume-scoped-service-from-singleton-a-lesson-in-asp-net-core-di-scopes/amp/
Related
Every time that i try to call Send from MediatR to any Query/Command that i have, it returns this Exception:
System.InvalidOperationException: 'Error constructing handler for request of type MediatR.IRequestHandler2[CQRSHost.Recursos.Queries.GetTodosProdutosQuery,System.Collections.Generic.IEnumerable1[CQRSHost.Models.Produto]]. Register your handlers with the container. See the samples in GitHub for examples.'
Inner Exception:
InvalidOperationException: Cannot resolve 'MediatR.IRequestHandler2[CQRSHost.Recursos.Queries.GetTodosProdutosQuery,System.Collections.Generic.IEnumerable1[CQRSHost.Models.Produto]]' from root provider because it requires scoped service 'CQRSHost.Context.AppDbContext'.
But i have the AppDbContext in my DI container:
public static IHostBuilder CreateHostBuilder(string[] args)
{
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
return Host.CreateDefaultBuilder(args)
.UseSerilog()
.UseEnvironment("Development")
.ConfigureHostConfiguration(hostConfig =>
{
hostConfig.SetBasePath(Directory.GetCurrentDirectory());
hostConfig.AddEnvironmentVariables("DSO_");
})
.ConfigureServices((context, services) =>
{
services.AddSingleton(ConfigureLogger());
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
"Server=localhost;Database=newdatabase;User Id=sa;Password=P#ssw0rd!##$%;",
b => b.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName)));
services.AddHostedService<NewService>();
//services.AddMediatR(Assembly.GetExecutingAssembly());
//services.AddMediatR(typeof(GetTodosProdutosQuery).GetTypeInfo().Assembly);
services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies());
});
}
Here is the service that i use to call the query:
public class NewService : IHostedService
{
private readonly ILogger _logger;
private readonly IMediator _mediator;
public NewService(ILogger logger, IMediator mediator)
{
_logger = logger;
_mediator = mediator;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var command = new GetTodosProdutosQuery();
var response = await _mediator.Send(command);
_logger.Information($"First Name: {response.First()?.Nome}");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
And here is what my project looks like:
ProjectImage
The commented lines is what i've already tryied to solve.
What am i doing wrong?
The exception "Register your handlers with the container." is misleading. The real error is described in the inner exception:
Cannot resolve 'MediatR.IRequestHandler<GetTodosProdutosQuery, IEnumerable>' from root provider because it requires scoped service 'CQRSHost.Context.AppDbContext'.
This happens because you inject the IMediator into a singleton consumer NewService. The Mediator implementation depends on a IServiceProvider but as NewService is singleton, it is resolved from the root container, and so will all its dependencies recursively. This means that once Mediator starts resolving from its IServiceProvider, it also resolves from the root container. And scoped services can't be resolved from the root container, because that would lead to bugs, because that scoped service would be cached for the lifetime of the root container, and reused for the lifetime of the root container - which means indefinitely.
The solution is to inject an IServiceScope into NewService create a scope from within its StartAsync and resolve the IMediator from there:
public class NewService : IHostedService
{
private readonly IServiceProvider _container;
public NewService(IServiceProvider container)
{
_container = container;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await using (var scope = _container.CreateScope())
{
var logger = scope.ServiceProvider.GetRequiredService<ILogger>();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var command = new GetTodosProdutosQuery();
var response = await mediator.Send(command);
logger.Information($"First Name: {response.First()?.Nome}");
}
}
...
}
Another, perhaps more convenient option would be to ensure that the mediator always resolves from a new scope. This can be achieved using the following code:
public record ScopedSender<TSender>(IServiceProvider Provider)
: ISender where TSender : ISender
{
public Task<TResponse> Send<TResponse>(
IRequest<TResponse> request, CancellationToken ct)
{
async using (var scope = Provider.CreateScope());
var sender = scope.ServiceProvider.GetRequiredService<TSender>();
return await sender.Send(request, ct);
}
public Task<object?> Send(object request, CancellationToken ct)
{
async using (var scope = Provider.CreateScope());
var sender = scope.ServiceProvider.GetRequiredService<TSender>();
return await sender.Send(request, ct);
}
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request, CancellationToken ct)
{
async using (var scope = Provider.CreateScope());
var sender = scope.ServiceProvider.GetRequiredService<TSender>();
return await sender.CreateStream(request, ct);
}
public IAsyncEnumerable<object?> CreateStream(object request, CancellationToken ct)
{
async using (var scope = Provider.CreateScope());
var sender = scope.ServiceProvider.GetRequiredService<TSender>();
return await sender.CreateStream(request, ct);
}
}
Now configure this as follows:
services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies());
services.AddTransient<Mediator>();
services.AddSingleton<ISender, ScopedSender<Mediator>>();
Now you can safely inject your ISender into yout NewService without having to apply scoping:
public class NewService : IHostedService
{
private readonly ILogger _logger;
private readonly IMediator _sender;
public NewService(ILogger logger, ISender sender)
{
_logger = logger;
_sender = sender;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var command = new GetTodosProdutosQuery();
var response = await _sender.Send(command);
_logger.Information($"First Name: {response.First()?.Nome}");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
I was having the same problem, I solved it here by changing AddScoped to AddSingleton and adding builder.Services.AddTransient();
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.
I have a ASP.NET Core 5.0 MVC solution,
public abstract class HostedService : IHostedService, IDisposable
{
private Task _currentTask;
private readonly CancellationTokenSource _cancellationTokenSource = new
CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken token);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_currentTask = ExecuteAsync(cancellationToken);
return _currentTask.IsCompleted ? _currentTask : Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_currentTask == null) return;
try
{
_cancellationTokenSource.Cancel();
}
finally
{
await Task.WhenAny(_currentTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
public virtual void Dispose()
{
_cancellationTokenSource.Cancel();
}
}
Gets the exchange rates from URL
public class ExchangeSyncManager : HostedService
{
private readonly CurrencyServices _currencyServices;
private readonly ExchangeRateServices _exchangeRateServices;
public ExchangeSyncManager(CurrencyServices currencyServices, ExchangeRateServices
exchangeRateServices)
{
_currencyServices = currencyServices;
_exchangeRateServices = exchangeRateServices;
}
protected override async Task ExecuteAsync(CancellationToken token)
{
// işlem iptal edilmemişse…
if (!token.IsCancellationRequested)
{
var url = "http://www.tcmb.gov.tr/kurlar/today.xml";
XmlDocument xmlVerisi = new XmlDocument();
List<ExchangeRate> list = new List<ExchangeRate>();
xmlVerisi.Load(url);
foreach (var currency in _currencyServices.GetCurrencies())
{
var format = string.Format("Tarih_Date/Currency[#Kod='{0}']/ForexSelling", currency.Name);
var selectAndReplace = xmlVerisi.SelectSingleNode(format).InnerText.Replace('.', ',');
decimal value = Convert.ToDecimal(selectAndReplace);
list.Add(new ExchangeRate
{
Date = DateTime.Now,
Value = value,
CurrencyId = currency.Id
});
}
_exchangeRateServices.AddRange(list);
await Task.Delay(TimeSpan.FromDays(1), token);
}
}
}
And in startup :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//other services here
......................................................
.....................................................
services.AddScoped<CurrencyServices, CurrencyServices>();
services.AddScoped<ExchangeRateServices>();
services.AddHostedService<ExchangeSyncManager>();
}
Still I am getting this error :
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: IPMMS.Business.Managers.ExchangeSyncManager': Cannot consume scoped service 'IPMMS.Business.Services.CurrencyServices' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)'
What is wrong ?
You cannot inject a scoped services inside a singleton. They are bound to HTTP requests.
A HostedService's lifetime is singleton.
However you can use the IServiceProvider to create a scope and retrieve an instance of your scoped service.
You will find how to fix your issue here :
https://learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service
I'm trying to make call to a function every specified interval of time, for that m using Background service, here is what I have done:
Here is the Alerting controller where I have the function:
public class AlertingController : ControllerBase
{
private readonly DatabaseContext _context;
private readonly IMapper _mapper;
public AlertingController(DatabaseContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public AlertingController()
{
}
//function that adds in the DB
public async Task<AlertingResponse> GetAlertingToDB()
{
AlertingResponse dataGet;
using (var httpClient = new HttpClient())
{
using (var response = await httpClient
.GetAsync(MetricApiLink))
{
string apiResponse = await response.Content.ReadAsStringAsync();
dataGet = JsonConvert.DeserializeObject<AlertingResponse>(apiResponse);
}
}
if (dataGet.data.alerts != null || dataGet.data.alerts.Count > 0)
{
foreach (var alert in dataGet.data.alerts)
{
CreateAlertQuery QueryAlert = new CreateAlertQuery();
QueryAlert.Name = alert.labels.alertname;
QueryAlert.Instance = alert.labels.instance;
QueryAlert.Serverity = alert.labels.severity;
QueryAlert.Summary = alert.annotations.summary;
QueryAlert.State = alert.state;
QueryAlert.ActiveAt = alert.activeAt;
var _Alert = _mapper.Map<AlertingDataModel>(QueryAlert);
_context.Alertings.Add(_Alert);
await _context.SaveChangesAsync();
}
}
return null;
}
}
I have tested the method with a HTTPGET request, it works fine, add the alerts into my database:
I have created a scooped service where I called the function GetAlertingToDB():
internal interface IScopedAlertingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedAlertingService : IScopedAlertingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedAlertingService(ILogger<ScopedAlertingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
AlertingController _AlertingToDB = new AlertingController();
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await _AlertingToDB.GetAlertingToDB();
await Task.Delay(10000, stoppingToken);
}
}
}
I have also created the Class that will consume my service, and will run in the BackGround:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
I injected the dependencies on the Startup Class and added the hosted service:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedAlertingService, ScopedAlertingService>();
The functions are working just fine untill a call the GetAlertingToDB() function and it doesn't work.
Any help would be great, thanks everyone :)
Personally I would rearrange your solution so that your background service doesn't need to create a Controller. Instead the controller, if you still need it at all, should call into your ScopedAlertingService where the work is performed once. Your background service can simply loop forever, with an await Task.Delay().
public class ScopedAlertingService : IScopedAlertingService
{
public async Task DoWork(CancellationToken stoppingToken)
{
// move contents of your AlertingController.GetAlertingToDB here
}
}
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly IServiceProvider _services;
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
_services = services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(10000, stoppingToken);
using (var scope = _services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
}
}
Hangfire RecurringJob would be an option for you case. you can check it here https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html.
The benefit of using it is: you have a dashboard to check when the task will be fired and the result of the task.
There are several options for doing this.
Please read the following link from the Microsoft Documentation which has several examples on how to do this in .NET Core and ASP.NET Core:
Worker Service In NET Core
It is called Worker Services.
You basically implement two interfaces: IHostedService, IDisposable
Then you register your service inside you Startup class in your ConfigureServices method like this:
services.AddHostedService<MyCoolWorkerServiceClass>();
For a Complete Example
One last sugestion. The example uses System.Threading.Timer... But I think it is better to use a System.Timers.Timer with AutoReset = false.
The reason is to avoid overlapping runs of your service. Once a run is done then you start your timer again.
But then again it all depends on what you want to achieve.
I have a service that needs to connect to another service at the startup.
The other service is a Rabbitmq broker.
I'm listening to some event from Rabbitmq so I need it to be activated from the start of the application.
I need to connect to two different VHosts, so I need to create two connections.
The problem is that when I start the application is constantly creates connections until the server crashes!
In Rabbitmq management I can see a lot of Connection and Channels are created.
I can't find out why is this happening.
In general, I want to know whats is the proper way of connecting to other services in the startup of my application in dotnet core.
I'm using this code to do so :
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddSingleton<RabbitConnectionService>();
...
ActivatorUtilities.CreateInstance<RabbitConnectionService>(services.BuildServiceProvider());
}
And in the constructor of RabbitConnectionService I'm connecting to Rabbitmq.
public RabbitConnectionService(IConfiguration configuration)
{
ServersMessageQueue = new MessageQueue(configuration.GetConnectionString("FirstVhost"), "First");
ClientsMessageQueue = new MessageQueue(configuration.GetConnectionString("SecondVhost"), "Second");
}
MessageQueue Class :
public class MessageQueue
{
private IConnection connection;
private string RabbitURI;
private string ConnectionName;
static Logger _logger = LogManager.GetCurrentClassLogger();
public MessageQueue(string connectionUri, string connectionName)
{
ConnectionName = connectionName;
RabbitURI = connectionUri;
connection = CreateConnection();
}
private IConnection CreateConnection()
{
ConnectionFactory factory = new ConnectionFactory();
factory.Uri = new Uri(RabbitURI);
factory.AutomaticRecoveryEnabled = true;
factory.RequestedHeartbeat = 10;
return factory.CreateConnection(ConnectionName);
}
public IModel CreateChannel()
{
return connection.CreateModel();
}
...
}
To enable Background processing, you need to create a class which implements IHostedService interface.
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
This interface got two methods StartAsync and StopAsync. And you need to register the service using dependency injection, like this:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IHostedService, DemoService>();
...
}
Instead of implementing IHostedService, you can derive from BackgroundService abstract class and you can implement the ExecuteAsync abstract method. Here is an example of a minimal background service, which monitors a table in SQL Server and send emails:
public class DemoService : BackgroundService
{
private readonly ILogger<DemoService> _demoservicelogger;
private readonly DemoContext _demoContext;
private readonly IEmailService _emailService;
public DemoService(ILogger<DemoService> demoservicelogger,
DemoContext demoContext, IEmailService emailService)
{
_demoservicelogger = demoservicelogger;
_demoContext = demoContext;
_emailService = emailService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_demoservicelogger.LogDebug("Demo Service is starting");
stoppingToken.Register(() => _demoservicelogger.LogDebug("Demo Service is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
_demoservicelogger.LogDebug("Demo Service is running in background");
var pendingEmailTasks = _demoContext.EmailTasks
.Where(x => !x.IsEmailSent).AsEnumerable();
await SendEmailsAsync(pendingEmailTasks);
await Task.Delay(1000 * 60 * 5, stoppingToken);
}
_demoservicelogger.LogDebug("Demo service is stopping");
}
}
Thanks to Ian Kemp.
Refrence