I have a simple function app that uses MediatR pattern and the base function looks like this:
public class ShareEnvelopesFunction
{
private readonly IMediator _mediator;
public ShareEnvelopesFunction(IMediator mediator)
{
_mediator = mediator;
}
[FunctionName(nameof(ShareEnvelopesFunction))]
public async Task Run([TimerTrigger("%ScheduleShareEnvelopesSetting%")]TimerInfo timer, ILogger log)
{
log.LogInformation("Starting Share Envelopes function {0}", timer);
var result = await _mediator.Send(new ShareEnvelopesCommandRequest { log = log });
}
}
As you can see ShareEnvelopesCommandRequest is a class that has only one property which is the ILogger
public class ShareEnvelopesCommandRequest : IRequest<ShareEnvelopesCommandResponse>
{
public ILogger log { get; set; }
}
Now in my command handler, if I use request.log.LogInformation, it logs messages to my console. A sample command handler code looks like this:
public class ShareEnvelopesCommandHandler : IRequestHandler<ShareEnvelopesCommandRequest, ShareEnvelopesCommandResponse>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IDocuSignApiService _docuSignService;
public ShareEnvelopesCommandHandler(IUnitOfWork unitOfWork
, IDocuSignApiService docuSignService
)
{
_unitOfWork = unitOfWork;
_docuSignService = docuSignService;
}
public async Task<ShareEnvelopesCommandResponse> Handle(ShareEnvelopesCommandRequest request, CancellationToken cancellationToken)
{
request.log.LogInformation("Starting to share envelopes");
await _docuSignService.ShareEnvelopes(fromGroupUser.UserId, deltaUsers);
return new ShareEnvelopesCommandResponse() {isSuccess=true };
}
Now the real issue here is that, if you see the above code, I am injecting a docusign service and inside this service, I need to log certain information into the console. My sample docusign service looks like this:
public class DocuSignApiService : IDocuSignApiService
{
public IGroupsApi _groupsApi { get; set; }
public IAccountsApi _accountsApi { get; set; }
public DocuSignApiService(IGroupsApi groupsApi, IAccountsApi accountsApi)
{
_groupsApi = groupsApi;
_accountsApi = accountsApi;
}
public async Task ShareEnvelopes(string fromUserId, List<string> toUsersList)
{
//_logger.LogInformation("This is a test");
}
}
Now I need to be able to log any information from this service to the console. Now I can pass the ShareEnvelopesCommandRequest request to this service but I don't think that would be very efficient. So here is what I have tried:
I injected ILogger into the service:
public class DocuSignApiService : IDocuSignApiService
{
public IGroupsApi _groupsApi { get; set; }
public IAccountsApi _accountsApi { get; set; }
private readonly ILogger _logger;
public DocuSignApiService(IGroupsApi groupsApi, IAccountsApi accountsApi, ILogger<ShareEnvelopesCommandRequest> logger )
{
_groupsApi = groupsApi;
_accountsApi = accountsApi;
_logger = logger;
}
public async Task ShareEnvelopes(string fromUserId, List<string> toUsersList)
{
_logger.LogInformation("This is a test");
}
}
The configuration on DI in my startup class looked like this:
services.AddScoped<IDocuSignApiService>(_ => new DocuSignApiService(docuSignConfig,
_.GetService<IGroupsApi>(),
_.GetService<IAccountsApi>(),
_.GetService<ILogger<ShareEnvelopesCommandRequest>>()
)
);
However, doing that didn't log information into the azure function app console. Are there an ideas on how I can go about logging messages from a service into the console? Thanks in advance.
Maybe you have to setup log level in host.json like this:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
},
"logLevel": {
"YourNamespace.DocuSignApiService": "Information",
"YourNamespace.ShareEnvelopesFunction": "Information",
}
}
}
I'm guessing you get the ILogger instance in your Run method directly from azure, whenever you run the function.
_.GetService<ILogger<ShareEnvelopesCommandRequest>>()
That above probably gets another instance unrelated to the one azure provides you and maybe that's why no logs happen.
If you're going to use DI, you gotta do it from the start of the Run method of your function class (ShareEnvelopesFunction).
public class ShareEnvelopesFunction
{
private readonly IMediator _mediator;
private readonly ServiceCollection _serviceCollection;
public ShareEnvelopesFunction(IMediator mediator, ServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;//inject this from your startup
_mediator = mediator;
}
[FunctionName(nameof(ShareEnvelopesFunction))]
public async Task Run([TimerTrigger("%ScheduleShareEnvelopesSetting%")] TimerInfo timer, ILogger log)
{
_serviceCollection.AddSingleton(log);//this should ensure that all log instances injected into your services are references to the one azure provided, or something like that
_serviceCollection.AddScoped<IDocuSignApiService,DocuSignApiService>();//now that your servicecollection knows what ILogger to inject, you can also inject your service normally, not sure if this needs to be done here tho, leaving it in your startup should be fine
log.LogInformation("Starting Share Envelopes function {0}", timer);
var result = await _mediator.Send(new ShareEnvelopesCommandRequest { log = log });
}
}
I've done this in a recent AWS Lambda project, their ILambdaContext also serve as logger. I was having the same issue as you, no logs in DI dependent services.
If that doesn't do the trick, try rebuilding your ServiceProvider before Mediator does it's thing.
using (IServiceProvider serviceProvider = _serviceCollection.BuildServiceProvider())
{
log.LogInformation("Starting Share Envelopes function {0}", timer);
_mediator = serviceProvider.GetService<IMediator>();
var result = await _mediator.Send(new ShareEnvelopesCommandRequest { log = log });
}
Related
I am using MassTransit 7.2.2 in a .NET Core application with RabbitMQ(for local development) and SQS(for deployment) where a single message processing can result in multiple new messages getting created and processed.
All the messages share the same base type
public class BaseMessage : CorrelatedBy<Guid>
{
public BaseMessage()
{
CorrelationId = Guid.NewGuid();
CreationDate = DateTime.UtcNow;
}
public Guid CorrelationId { get; init; }
public DateTime CreationDate { get; }
public Guid? ConversationId { get; set; }
}
The basic flow of processing is same for all messages, there is a Service per Consumer.
public class FirstMessage : BaseMessage
{
}
public class FirstConsumer : IConsumer<FirstMessage>
{
private readonly ILogger<FirstConsumer> _logger;
private readonly FirstService _service;
public FirstConsumer(ILogger<FirstConsumer> logger, FirstService service)
{
_logger = logger;
_service = service;
}
public Task Consume(ConsumeContext<FirstMessage> context)
{
_logger.LogInformation($"FirstConsumer CorrelationId: {context.CorrelationId} and ConversationId: {context.ConversationId} and InitiatorId: {context.InitiatorId}");
_service.Process(context.Message);
return Task.CompletedTask;
}
}
public class FirstService
{
private readonly IBusControl _busControl;
private readonly ILogger<FirstService> _logger;
public FirstService(IBusControl busControl, ILogger<FirstService> logger)
{
_busControl = busControl;
_logger = logger;
}
public Task Process(FirstMessage firstMessage)
{
var secondMessage = new SecondMessage();
_busControl.Publish(secondMessage);
return Task.CompletedTask;
}
}
The above code is an example and the actual code base has 30+ consumers and all have the same pattern, i.e there is a Service per Consumer and the message is passed to the Service for processing.
I am trying to implement a solution for tracing messages end to end by using the Ids.
ConversationId - Unique Id for tracing logs of all Consumers in a graph
CorrelationId - Unique Id for tracing logs within a Consumer
InitiatorId - Parent Id
There is a message processing graph that looks like
FirstConsumer -> SecondConsumer -> ThirdConsumer.
I have the following Filters
ConsumeFilter
public class SimpleConsumeMessageFilter<TContext, TMessage> : IFilter<TContext>
where TContext : class, ConsumeContext<TMessage>
where TMessage : class
{
public SimpleConsumeMessageFilter()
{
}
public async Task Send(TContext context, IPipe<TContext> next)
{
LogContext.PushProperty("CorrelationId", context.CorrelationId);
LogContext.PushProperty("ConversationId", context.ConversationId);
LogContext.PushProperty("InitiatorId", context.InitiatorId);
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("consume-filter");
}
}
public class SimpleConsumeMessagePipeSpec<TConsumer, TMessage> : IPipeSpecification<ConsumerConsumeContext<TConsumer, TMessage>>
where TConsumer : class
where TMessage : class
{
public void Apply(IPipeBuilder<ConsumerConsumeContext<TConsumer, TMessage>> builder)
{
builder.AddFilter(new SimpleConsumeMessageFilter<ConsumerConsumeContext<TConsumer, TMessage>, TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimpleConsumePipeSpecObserver : IConsumerConfigurationObserver
{
public void ConsumerConfigured<TConsumer>(IConsumerConfigurator<TConsumer> configurator)
where TConsumer : class
{
}
public void ConsumerMessageConfigured<TConsumer, TMessage>(IConsumerMessageConfigurator<TConsumer, TMessage> configurator)
where TConsumer : class
where TMessage : class
{
configurator.AddPipeSpecification(new SimpleConsumeMessagePipeSpec<TConsumer, TMessage>());
}
}
PublishFilter
public class SimplePublishMessageFilter<TMessage> : IFilter<PublishContext<TMessage>> where TMessage : class
{
public SimplePublishMessageFilter()
{
}
public async Task Send(PublishContext<TMessage> context, IPipe<PublishContext<TMessage>> next)
{
if (context.Headers.TryGetHeader("ConversationId", out object #value))
{
var conversationId = Guid.Parse(#value.ToString());
context.ConversationId = conversationId;
}
else
{
if (context.Message is BaseMessage baseEvent && !context.ConversationId.HasValue)
{
context.ConversationId = baseEvent.ConversationId ?? Guid.NewGuid();
context.Headers.Set("ConversationId", context.ConversationId.ToString());
}
}
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("publish-filter");
}
}
public class SimplePublishMessagePipeSpec<TMessage> : IPipeSpecification<PublishContext<TMessage>> where TMessage : class
{
public void Apply(IPipeBuilder<PublishContext<TMessage>> builder)
{
builder.AddFilter(new SimplePublishMessageFilter<TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimplePublishPipeSpecObserver : IPublishPipeSpecificationObserver
{
public void MessageSpecificationCreated<TMessage>(IMessagePublishPipeSpecification<TMessage> specification)
where TMessage : class
{
specification.AddPipeSpecification(new SimplePublishMessagePipeSpec<TMessage>());
}
}
Added to config via
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConnectConsumerConfigurationObserver(new SimpleConsumePipeSpecObserver());
cfg.ConfigurePublish(ppc =>
{
ppc.ConnectPublishPipeSpecificationObserver(new SimplePublishPipeSpecObserver());
});
cfg.UseDelayedMessageScheduler();
cfg.ConfigureEndpoints(context);
cfg.Host("localhost", rmq =>
{
rmq.Username("guest");
rmq.Password("guest");
});
});
With the above approach the 'CorrelationId' header is lost when the SecondConsumer's filters are run.
I have tried the following change and it seems to flow the Ids across the Consumers.
However, taking this approach will impact large sections of code / tests that rely on the IBusControl interface. I am keeping this as a backup option in case I can't find any other solution.
public class FirstService
{
private readonly ILogger<FirstService> _logger;
public FirstService(ILogger<FirstService> logger)
{
_logger = logger;
}
public Task Process( ConsumeContext<FirstMessage> consumeContext)
{
var secondMessage = new SecondMessage();
consumeContext.Publish(secondMessage);
return Task.CompletedTask;
}
}
Question: Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages ?
Many thanks
As explained in the documentation, consumers (and their dependencies) must use one of the following when sending/publishing messages:
ConsumeContext, typically within the consumer itself
IPublishEndpoint or ISendEndpointProvider, typically used by scoped dependencies of the consumer
IBus, last resort, as all contextual data is lost from the inbound message
As for your final question, "Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages?" the answer is no. The consume context would be needed to access any of the contextual data.
I want to pass a logger into a constructor of an object. Normally, the logger is dependency injected into a class, but I want to log inside a class that is not dependency injected.
I have a .NET Core worker service. In that service, I have 0-many instances of a class that are created at runtime. I also want to be able to log messages to the logger inside that class.
In my example, I can pass the ILogger that I get from inside the TimedWorker, but the type is wrong.
Currently, I have:
class TimedWorker : BackgroundService
{
private List<Dog> m_dogs;
private ILogger<TimedWorker> m_logger;
private IConfiguration m_configuration;
public TimedWorker(ILogger<TimedWorker> logger, IConfiguration configuration)
{
m_logger = logger;
m_configuration = configuration;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
m_dogs= new List<Dogs>();
List<DogOption> dogOptions= new List<DogOption>();
m_configuration.GetSection("Dogs").Bind(dogOptions);
foreach(DogOption option in dogOptions)
{
m_dogs.Add(new Dog(option, m_logger));
}
}
///...
}
class Dog {
public string Name { get; }
private int Age;
private ILogger m_logger;
public Dog(DogOption option, ILogger logger)
{
m_logger = logger;
Name = option.Name;
Age = option.Age;
m_logger.LogInformation($"Dog '{Name}' created");
}
}
The problem is that when it logs, the logger is of type TimedWorker, not Dog. So when I look at the logs, they are labeled under TimedWorker instead of Dog. I do not know how to pass in a ILogger into the Dog class. Any advice?
You can inject the IServiceProvider and resolve the dependency manually.
You can do it like this:
public class Worker
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var specificLogger = _serviceProvider.GetService(typeof(ILogger<Dog>)) as ILogger<Dog>;
var dog = new Dog(specificLogger);
}
}
public class Dog
{
public Dog(ILogger<Dog> logger)
{
logger.LogInformation("Hello");
}
}
This creates the specific log based on the class.
2020-11-18 18:41:58.1275||INFO|ConsoleApp.Dog|Hello
You can create a logger directly. For example:
var logger = LoggerFactory.Create(options => {}).Create<Worker>();
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.
A user can trigger a long-running job by sending a request to an ASP.NET Core controller. Currently, the controller executes the job and then sends a 200 OK response. The problem is that the client has to wait rather long for the response.
This is why I am currently trying to process the job in a background task. I am using an IBackgroundTaskQueue where all jobs are stored and an IHostedService that processes the jobs whenever a new one is enqueued. It is similar to the code in the Microsoft documentation.
But the job does need access to the database and therefore the user has to authenticate using Active Directory. Hence, I need access to the HttpContext.User property in the background task. Unfortunately, the HttpContext is disposed when the response is sent and before the processing of the job begins.
Demonstration
public class Job
{
public Job(string message)
{
Message = message;
}
public string Message { get; }
}
The controller enqueues a new job in the task queue.
[HttpGet]
public IActionResult EnqueueJob()
{
var job = new Job("Hello World");
this.taskQueue.QueueBackgroundWorkItem(job);
return Accepted();
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Job job)
{
jobs.Enqueue(job);
signal.Release();
}
public async Task<Job> DequeueAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
jobs.TryDequeue(out var job);
return job;
}
}
The IHostedService creates a new JobRunner for each job it dequeues. I'm using a IServiceScopeFactory here to have dependency injection available. JobRunner also has a lot more dependencies in the real code.
public class JobRunnerService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IBackgroundTaskQueue taskQueue;
public JobRunnerService(IServiceScopeFactory serviceScopeFactory, IBackgroundTaskQueue taskQueue)
{
this.serviceScopeFactory = serviceScopeFactory;
this.taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var job = await taskQueue.DequeueAsync(stoppingToken);
using (var scope = serviceScopeFactory.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var runner = serviceProvider.GetRequiredService<JobRunner>();
runner.Run(job);
}
}
}
}
public class JobRunner
{
private readonly ILogger<JobRunner> logger;
private readonly IIdentityProvider identityProvider;
public JobRunner(ILogger<JobRunner> logger, IIdentityProvider identityProvider)
{
this.logger = logger;
this.identityProvider= identityProvider;
}
public void Run(Job job)
{
var principal = identityProvider.GetUserName();
logger.LogInformation($"{principal} started a new job. Message: {job.Message}");
}
}
public class IdentityProvider : IIdentityProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetUserName()
=> httpContextAccessor.HttpContext.User.Identity.Name; // throws NullReferenceException
}
Now, when sending a request, a NullReferenceException is thrown in JobRunner.Run() because httpContextAccessor.HttpContext is null.
What I've tried
I haven't had a good idea yet how to approach this problem. I know that it would be possible to copy the necessary information from the HttpContext, but don't know how to make them available to dependency injection services.
I thought that maybe I could create a new IServiceProvider that uses the services of an old one, but replaces the implementation for IHttpContextAccesor, but it does not seem to be possible.
How can I use the HttpContext in the background task although the response has been completed?
I would like to implement a recurring (timed) IHostedService instance in ASPNET Core that can be stopped and started on demand. My understanding is that IHostedService(s) are started by the framework on application startup.
However, I would like to be able to start/stop the service 'manually', perhaps using an on/off toggle via a UI. Ideally the "off" state would dispose of currently running service, and the "on" state would then create a new instance.
I've read the MS docs here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1.
My initial thought was to get an instance of the running service and then call the public StopAsync(CancellationToken token) method. However I'm a little stuck when it comes to which token I should pass in, and the same could be said for the StartAsync(CancellationToken cancellationToken) method.
Any ideas on how this should be done, or if it's even advisable? Is my approach somehow going against the intended design of hosted services in ASPNET Core?
EDIT 7.27.2018
So it appears after some more research (aka actually reading the documentation :D) that hosted services StartAsync/StopAsync methods are indeed meant to coincide with the lifetime of the application. Registered IHostedServices seem to not be added to the DI container for injection into other classes.
Therefore I do not think my initial idea will work. For now I registered my services with configuration dependencies (IOptions<T>) that can be updated at runtime. As the hosted services is processing, it will check the configuration to see if it should continue, otherwise it will just wait (instead of stopping or disposing of the hosted service).
I'll probably mark this as my answer soon, unless I hear of some other ideas.
For StopAsync(CancellationToken token), you could pass new System.Threading.CancellationToken(). In the defination of public CancellationToken(bool canceled), canceled indicates state for the token. For your scenario, there is no need to specify the canceled since you want to Stop the service.
You could follow below step by step:
Create IHostedService
public class RecureHostedService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public RecureHostedService(ILogger<RecureHostedService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Starting");
_timer = new Timer(DoWork,null,TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object state)
{
_log.LogInformation("Timed Background Service is working.");
}
}
Register IHostedService
services.AddSingleton<IHostedService, RecureHostedService>();
Start and Stop Service
public class HomeController : Controller {
private readonly RecureHostedService _recureHostedService;
public HomeController(IHostedService hostedService)
{
_recureHostedService = hostedService as RecureHostedService;
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
_recureHostedService.StopAsync(new System.Threading.CancellationToken());
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
_recureHostedService.StartAsync(new System.Threading.CancellationToken());
return View();
} }
Using Blazor Server, you can start and stop background services in the following ways. Asp.net Core MVC or Razor is the same principle
First, implement an IHostService
public class BackService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public bool isRunning { get; set; }
public BackService(ILogger<V2rayFlowBackService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation($"begin {DateTime.Now}");
_timer = new Timer(DoWorkAsync, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
isRunning = false;
_log.LogInformation($"{DateTime.Now} BackService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWorkAsync(object state)
{
_log.LogInformation($"Timed Background Service is working. {DateTime.Now}");
try
{
isRunning = true;
// dosometing you want
}
catch (Exception ex)
{
isRunning = false;
_log.LogInformation("Error {0}", ex.Message);
throw ex;
}
}
}
Registration Service In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BackService>();
services.AddHostedService(sp => sp.GetRequiredService<BackService>());
}
Inject background services into Blazor components
public class IndexBase:ComponentBase
{
[Inject]
BackService BackService { set; get; }
protected override void OnInitialized()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
base.OnInitialized();
}
public void on()
{
if (!BackService.isRunning)
{
BackService.StartAsync(new System.Threading.CancellationToken());
}
}
public void off()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
}
}
#page "/"
#inherits IndexBase
<h1>Hello, world!</h1>
Welcome to your new app.
<button #onclick="on">Start</button>
<button #onclick="off">Stop</button>
reference
You should inherit your HostedService from your own Interface and register your service as Singleton, but in a difference way:
First register your service with AddHostedService generic method.
services.AddHostedService<TimerHostedService>();
Then add a public static field to your class named Instance, that holds the instance reference of your class and set its value in the constructor!
Then put a factory in the ConfigureServices for registering your service as singleton that returns the static instance field !
Here is the sample code:
(in your HostedService.cs: )
public interface ITimerHostedService : IHostedService
{
}
public class TimerHostedService : ITimerHostedService
{
private static TimerHostedService _instance;
public static TimerHostedService Instance => _instance;
public TimerHostedService(ILogger<TimerHostedService> logger)
{
if(_instance == null)
{
_instance = this;
}
}
}
And here is the code for registering your service as singleton ( in Startup.cs ):
services.AddHostedService<TimerHostedService>();
services.AddSingleton<ITimerHostedService, TimerHostedService>(serviceProvider =>
{
return TimerHostedService.Instance;
});
And here is the code in your Controller to manually start / stop your HostedService:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ITimerHostedService _hostedService;
public HomeController(ILogger<HomeController> logger, ITimerHostedService hostedService)
{
_logger = logger;
_hostedService = hostedService;
}
public async Task<IActionResult> Start()
{
await _hostedService.StartAsync(default);
return Ok();
}
public async Task<IActionResult> Stop()
{
await _hostedService.StopAsync(default);
return Ok();
}
}
Happy Coding!
Enjoy Your Lovely Moments :X