How to configure Logging via DI in Azure WebJob - c#

I am working on Azure WebJobs (3.0.6) using dotnet core. I referred Microsoft's Get Started Guide. Per the example I wanted to have a console logging to begin with. The scope of that guide is limited. In my application, I will be using many classes in a different dll. I am not able to figure out how can I add logging in those classes. The sample code is
// The main method
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
// The queue trigger class
public class QueueListenerService
{
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
// how can I pass an instance of ILogger in the constructor below
MyWorker myWorker = new MyWorker();
}
}
// MyWorker Class in a different assembly
public class MyWorker
{
public MyWorker(ILogger logger)
{
// I want to use logger here being injected
}
}
I have referred several examples of DI in dotnet core console applications and they use service collection approach. I also check this blog but to me, this is what I have done and yet my ILogger is not being resolved. It ask me to pass an instance of ILogger when I create MyWorker instance

You are close to the solution. The main thing you need to change is to let the service collection create the MyWorker instance for you.
I quickly extended my recent Webjob sample project to include console logging with dependency injection. See this commit for how I added it.
You mainly need to use constructor dependency injection for your QueueListenerService.
builder.ConfigureServices(services =>
{
services.AddScoped<QueueListenerService>();
services.AddScoped<MyWorker>();
});
public class QueueListenerService
{
public QueueListenerService(MyWorker worker){
_Worker = worker;
}
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
_Worker.DoStuff()
}
}

Related

.NET 6 attach to windows session switch

I am migration an app from .NET Framework to .NET 6. It starts Kestrel web server internally and it is intended to run as a Windows Service - in the old version that was achieved using Topshelf library, so I continued using it. One of the requirements is to listen for session switch events and send messages to third-party application.
In the old version of the application it was achieved again with the help of Topshelf. However, in the callback there was used static DI container to get access to services:
return HostFactory.New((hostConfigurator) =>
{
// ...
hostConfigurator.EnableSessionChanged();
hostConfigurator.Service<WindowService>((serviceConfigurator) =>
{
serviceConfigurator.ConstructUsing(hostSettings => new WindowService());
// ...
serviceConfigurator
.WhenSessionChanged((serviceHelper, arguments) => serviceHelper.SessionChange(arguments));
// ...
});
// ...
});
And this is the callback:
private void SessionChange(SessionChangedArguments changeDescription)
{
var sessionService =
DependencyInjectionContainer.GetRequiredService<IUserSessionMonitoringService>();
if (sessionService == null)
{
LOG.Warning(...);
return;
}
sessionService.SessionChanged(...);
}
So, here's the problem: in the callback a static DependencyInjectionContainer class is used to get the needed service/s. However in the new version of the app this approach won't work as this static class was abandoned. Also, I don't even have access to built-in .NET DI Container, because during the time the Topshelf is configured, the container isn't build.
As further steps I investigated what is Topshelf using under the hood for this functionality - that's SystemEvents.SessinSwitch event. I tried to attach an event handler to it from a hosted service, in which the needed services are injected. For the sake of the test, I just wanted to log some text in a file from the event handler. However, when sign out/in there's nothing in the file from those logs.
public class SystemSessionMonitoringHostedService : IHostedService
{
private readonly IUserSessionMonitoringService userSessionMonitoring;
private readonly ILogger<SystemSessionMonitoringHostedService> logger;
public SystemSessionMonitoringHostedService(
IUserSessionMonitoringService userSessionMonitoring,
ILogger<SystemSessionMonitoringHostedService> logger)
{
this.userSessionMonitoring = userSessionMonitoring;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
SystemEvents.SessionSwitch += OnSessionChanged;
return Task.CompletedTask;
}
private void OnSessionChanged(object sender, SessionSwitchEventArgs eventArgs)
{
var json = JsonConvert.SerializeObject(eventArgs);
logger.LogCritical($"Object: {json}");
}
}
Does anyone know if those SystemEvents works in .NET Core/5+? Am I missing something? Suggestions for workarounds are also welcome.

Use logger in custom classes in my console application with Dependency Injection

I have a console application, and I added logger builder as follows in Program.cs:
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Program>();
Now say I have many classes in the project and all want to use logger. How can I make it available to other classes?
For example, I have:
internal class Test
{
public Test()
{
//use logger here
//logger.LogInformation("Calling Test");
}
}
In certain kinds of projects such as Azure Functions, the logger is readily injected in the functions, is there a similar way I can do it in Console application?
To inject stuff, classes need constructors that accept dependencies as their parameters. For instance:
public class Test
{
private ILogger logger;
public Test(ILogger<Test> logger)
{
this.logger = logger;
}
public void DoSomething()
{
//use logger here
logger.LogInformation("Calling Test");
}
}

Injecting ILogger into a function called by Thrift in .NET Core 3

I wanted to know how to inject ILogger into a function in a ASP.NET Core app that's called by a Java client through Thrift.
So a high level code demo of what I want to do:
// ExecuteRequest is called by java client through Thrift
public class ThriftLayer
{
...
public string ExecuteRequest(...params)
{
...
var result = RequestFunc1(...params);
...do processing
return result;
}
...
}
// Contains request functions called by ExecuteRequest
public class ServerRequestHandler
{
...
public string RequestFunc1(...params)
{
return TaskFunc1(...params);
}
....
}
// Functions in this class are called by both the Thrift layer(called by ServerRequestHandler) as well as a Console application
// In Console applications, we can inject the ILogger at Startup - No issues there.
public class TaskFunctions
{
private readonly ILogger<TaskFunctions> _logger;
public TaskFunctions(ILogger<TaskFunctions> logger)
{
_logger = logger;
}
public string TaskFunc1(...params)
{
_logger.logInfo("<log message>");
...do processing
return stringResult;
}
}
So I wanted to know how can I inject ILogger into TaskFunctions while calling from Thrift?
The accepted answer from this question on StackOverflow will help you.
You need to build a ServiceCollection outside of the ASP.NET Core's Startup class.
the MVC part of your application will add those services in Startup.ConfigureServices method
the other part of your application will need to build and then use the service provider as such var taskFunctionsWithInjected = ActivatorUtilities.CreateInstance<TaskFunctions>(serviceProvider); in order to get the dependencies

How to stop a MassTransit bus in an ASP.NET Core server?

I am studying MassTransit and ASP.NET Core, dependancy injection and successfully implemented something that works. I plan to use the Kestrel web server.
So I had to configure my ASP.NET core project this way in the Startup.cs.
public void ConfigureServices(IServiceCollection services) {
...
var bus = Bus.Factory.CreateUsingRabbitMq(sbc => {
var host = sbc.Host(new Uri(address), h => {
h.Username("guest");
h.Password("guest");
});
});
services.AddSingleton<IBus>(bus);
services.AddScoped<IRequestClient<GetTagRequest, GetTagAnswer>>(x =>
new MessageRequestClient<GetTagRequest, GetTagAnswer>(x.GetRequiredService<IBus>(), new Uri(address + "/gettagrequest"), timeOut));
bus.Start(); // <= ok. how is the bus supposed to stop ?
...
Although this works fine, no tutorial mentioned anything about setting bus.Stop() in an ASP.NET core project. I read in MassTransit documentation that a running bus could prevent a graceful exit.
Is this a major concern for a Kestrel web server? I have read about process recycling and I am afraid a running bus would compromise this.
At which place can I place that bus.Stop() in an ASP.NET Core project ?
You can use ApplicationLifetime events. Just make your IBus object class level variable.
public class Startup
{
private IBus _bus;
public void ConfigureServices(IServiceCollection services) {
/* ... */
_bus = Bus.Factory.CreateUsingRabbitMq ...
/* ... */
}
public void Configure(IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() => _bus.Start());
appLifetime.ApplicationStopping.Register(() => _bus.Stop());
}
}
There is IApplicationLifetime in .NET Core, which has several CancellationToken properties, including ApplicationStopped. So when you need to do something after asp.net application is shutdown and all requests are processed (like stopping your bus) - you can do it like this:
// lifetime will be injected to Configure from DI container
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime) {
// subscribe to ApplicationStopped
lifetime.ApplicationStopped.Register(OnApplicationStopped);
// the rest
}
private void OnApplicationStopped() {
_bus.Stop();
}
It's always good to explicitly release resources even on process shutdown. For example, some message might still be in transition when the process will be killed after shutdown. Doing explicit dispose will allow this transition to complete.
To add to the existing answers:
If you use the MassTransit.AspNetCore's IServiceCollection.AddMassTransit() extension method, there's no need for a class level IBus instance. Startup's Configure() supports DI, so you can do this instead:
public void Configure(IApplicationLifetime appLifetime, IBus bus)
{
appLifetime.ApplicationStarted.Register(() => bus.Start());
appLifetime.ApplicationStopping.Register(() => bus.Stop());
}
If you don't want to use the package, you can still register IBus with the DI container (like in the question) and request it from Configure().
The ASP.NET Core DI sample uses an IHostedService instead:
public class BusService : IHostedService
{
private readonly IBusControl _busControl;
public BusService(IBusControl busControl)
{
_busControl = busControl;
}
public Task StartAsync(CancellationToken cancellationToken) =>
_busControl.StartAsync(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken) =>
_busControl.StopAsync(cancellationToken);
}
The service is registered as:
services.AddSingleton<IHostedService, BusService>();
For more information on IHostedService, here's the doc page. I'm not sure I like the idea of having a background service just to stop the bus. Anyway, the sample repo I picked this example from is worth referring to.
The third option is to do it yourself in Main(). Something like:
var host = CreateWebHostBuilder(args).Build();
var bus = host.Services.GetRequiredService<IBusControl>();
await bus.StartAsync();
await host.RunAsync();
await bus.StopAsync();

WebApiRequestLifestyle and BackgroundJob Confusion

One of my dependencies (DbContext) is registered using the WebApiRequestLifestyle scope.
Now, my background job uses IoC and depends on the service that was registered above using the WebApiRequestLifestyle. I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
Any guidance would be great!
Here is my initialize code that occurs during start up:
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
var container = SimpleInjectorWebApiInitializer.Initialize(httpConfig);
var config = (IConfigurationProvider)httpConfig.DependencyResolver
.GetService(typeof(IConfigurationProvider));
ConfigureJwt(app, config);
ConfigureWebApi(app, httpConfig, config);
ConfigureHangfire(app, container);
}
private void ConfigureHangfire(IAppBuilder app, Container container)
{
Hangfire.GlobalConfiguration.Configuration
.UseSqlServerStorage("Hangfire");
Hangfire.GlobalConfiguration.Configuration
.UseActivator(new SimpleInjectorJobActivator(container));
app.UseHangfireDashboard();
app.UseHangfireServer();
}
public static Container Initialize(HttpConfiguration config)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
InitializeContainer(container);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterWebApiControllers(config);
container.RegisterMvcIntegratedFilterProvider();
container.Register<Mailer>(Lifestyle.Scoped);
container.Register<PortalContext>(Lifestyle.Scoped);
container.RegisterSingleton<TemplateProvider, TemplateProvider>();
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
config.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
return container;
}
Here is my code that kicks off the background job:
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly Mailer mailer;
public MailNotificationHandler(Mailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToSender(notification.FeedbackId));
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Finally here is the code that runs on the background thread:
public class Mailer
{
private readonly PortalContext dbContext;
private readonly TemplateProvider templateProvider;
public Mailer(PortalContext dbContext, TemplateProvider templateProvider)
{
this.dbContext = dbContext;
this.templateProvider = templateProvider;
}
public void SendFeedbackToSender(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackSender, new { Name = feedback.CreateUserId });
}
public void SendFeedbackToManagement(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackManagement, new { Name = feedback.CreateUserId });
}
public void Send(TemplateType templateType, object model)
{
MailMessage msg = templateProvider.Get(templateType, model).ToMailMessage();
using (var client = new SmtpClient())
{
client.Send(msg);
}
}
}
I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
As the design decisions describe, Simple Injector will never allow you to resolve an instance outside an active scope. So that DbContext will neither be resolved as transient or singleton; Simple Injector will throw an exception when there's no scope.
Every application type requires its own type of scoped lifestyle. Web API requires the AsyncScopedLifestyle (in previous versions WebApiRequestLifestyle), WCF an WcfOperationLifestyle and MVC the WebRequestLifestyle. For Windows Services you will typically use an AsyncScopedLifestyle.
If your Hangfire jobs run in a Windows Service, you will have to use either a ThreadScopedLifestyle or the AsyncScopedLifestyle. Those scopes require explicit starting.
When running the jobs on a background thread in a web (or Web API) application, there is no access to the required context and this means that Simple Injector will throw an exception if you try to do so.
You however are using the Hangfire.SimpleInjector integration library. This library implements a custom JobActivator implementation called SimpleInjectorJobActivator and this implementation will create start a Scope for you on the background thread. Hangfire will actually resolve your Mailer within the context of this execution context scope. So the Mailer constructor argument in your MailNotificationHandler is actually never used; Hangfire will resolve this type for you.
The WebApiRequestLifestyle and AsyncScopedLifestyle are interchangeable; the WebApiRequestLifestyle uses an execution context scope in the background and the SimpleInjectorWebApiDependencyResolver actually starts an execution context scope. So the funny thing is that your WebApiRequestLifestyle can be used for background operations as well (although it can be a bit confusing). So your solution works and works correctly.
When running in MVC, however, this will not work, and in that case you would have to create a Hybrid lifestyle, for instance:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
You can register your DbContext as follows:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
Here's some feedback on your application's design, if you don't mind.
Prevent letting application code, such as your MailNotificationHandler, from taking a direct dependency on an external library such as Hangfire. This is a direct violation of the Dependency Inversion Principle and makes your application code very hard to test and maintain. Instead, let solely your Composition Root (the place where you wire your dependencies) take a dependency on Hangfire. In your case, the solution is really straightforward and I would even say pleasant, and it would look as follows:
public interface IMailer
{
void SendFeedbackToSender(int feedbackId);
void SendFeedbackToManagement(int feedbackId);
}
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly IMailer mailer;
public MailNotificationHandler(IMailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
this.mailer.SendFeedbackToSender(notification.FeedbackId));
this.mailer.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Here we added a new IMailer abstraction and made the MailNotificationHandler dependent on this new abstraction; unaware of the existence of any background processing. Now close to the part where you configure your services, define an IMailer proxy that forwards the calls to Hangfire:
// Part of your composition root
private sealed class HangfireBackgroundMailer : IMailer
{
public void SendFeedbackToSender(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToSender(feedbackId));
}
public void SendFeedbackToManagement(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToManagement(feedbackId));
}
}
This requires the following registrations:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
Here we map the new HangfireBackgroundMailer to the IMailer abstraction. This ensures that the BackgroundMailer is injected into your MailNotificationHandler, while the Mailer class is resolved by Hangfire when the background thread is started. The registration of the Mailer is optional, but advisable, since it has become a root object, and since it has dependencies, we want Simple Injector to be aware of this type to allow it to verify and diagnose this registration.
I hope you agree that from perspective of the MailNotificationHandler, the application is much cleaner now.

Categories

Resources