C# ConsoleApp injection startup? - c#

I have a simple C# .NET Core 5 Console Application that I need to add dependency injection (Microsoft.Extensions.DependencyInjection) to. I know how to do this if it suppose to start a micro service but what If I want to just run it as a regular Console Application with DI?
I got this code :
static void Main(string[] args)
{
var serviceName =
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
Log.Information("{#serviceName} test starting up.", serviceName);
Host.CreateDefaultBuilder(args)
.UseMyAppMQ(context => context.UseSettings(appSettings.MQSettings))
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureServices((hostContext, services) =>
{
services
.Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
.AddTransient<ITestController>(s => new TestController());
})
.Build().Run();
Log.Information("{#serviceName} test closing down.", serviceName);
}
I need a entry point where I can run my underlaying class run method, but how?
Regards

You need to implement IHostedService interface (or extend BackgroundService base class) and register it with services.AddHostedService<YourServiceClass>() or
builder.Services.AddHostedService<YourServiceClass>(), if using .NET 6 minimal API as described in official docs. In this case IHostedService.StartAsync will be your entry point.
However, it does seem very inefficient, if all you need is just a simple console app with DI. As #Panagiotis Kanavos suggests you can build the host without running it, because then you can use it as a wrapper around DI container and resolve any registered service. In this case the entry point for your code is the next line after you've built your host, where you can resolve any registered dependency with
var host = Host.CreateDefaultBuilder(args)
...
.Build();
host.Services.GetService<YouService>()
But it is still an inefficient solution for just a console app, because you only need DI container, but not the entire host. Just use any third-party DI framework (like Autofac, Ninject or any other) instead of Microsoft.Extensions.DependencyInjection. Their setup is usually quite minimalistic and you will get just DI container with you services without anything else. You can still use configuration package and loggers, just register them in your container similarly to how you've done it before with UseXxxx methods.

Related

.NET DependencyInjection and Serilog without using IHost (for console app)

When you look at solutions for Dependency Injection and Serilog on .NET projects, most documentation and blogs refers to the IHost model. It does not matter much if for current .NET 6 or other versions of .NET Core.
This is my favorite way for applications providing REST APIs since .NET Core 2.1.
I first defined a BuildLogger() method to configure Serilog with min log level and add all my enrichers/sinks I need.
So my Program.Main method used to look like:
Log.Logger = LoggerBuilder.BuildLogger();
var builder = Host.CreateDefaultBuilder(args).UseSerilog(); // serilog injected in Host.
builder.ConfigureServices(services => services.AddMyServices());
using var source = new CancellationTokenSource();
await builder.Build().RunAsync(source.Token).ConfigureAwait(false);
All fine.
Question is: how to do reuse DI and Serilog without using the IHost model from .NET 6?
My today's user case if for a console app which is only running a few tests and then close, and still reusing some parts I wrote for my previous apps.
So instead of the above, I defined that
Log.Logger = LoggerBuilder.BuildLogger();
await using var serviceProvider = new ServiceCollection()
.AddMyServices()
.AddLogging(logBuilder => logBuilder.AddSerilog()) // attach serilog.
.BuildServiceProvider()
RunTests(serviceProvider);
This seems to work as expected.
The trick with AddLogging/AddSerilog() instead of UseSerilog() might be helpful for others!

How can I end to end test this .net core console application?

I have a .Net Core 2.0 console application. The main() method is like this:
public static async Task Main()
{
// create host builder
var hostBuilder = new HostBuilder();
var myApp = new MyApplication();
// Build host
var host = hostBuilder
.ConfigureHostConfiguration(myApp.ConfigureHostConfiguration)
.ConfigureServices(myApp.ConfigureServices)
.ConfigureLogging(myApp.ConfigureLogging)
.Build();
// Initialise
await myApp.InitialiseAppAsync(host);
// Run host
await host.RunAsync(CancellationToken.None);
}
The MyApplication class sets up the application configuration in ConfigureHostConfiguration(), it then configures up the dependencies in ConfigureServices() some of which register a Message Handlers to handle specific Messages types from an Azure Service Bus. The application needs to do some initialisation from within InitialiseAppAsync(). When host.RunAsync() is called, the a console application is run indefinitely and the Message Handler receives execution as soon as a message is available on the Azure Service Bus. This is all great and working fine.
What I'd like to do is create a new project under the same solution which contains some end to end tests (using XUnit). I'd like to be able to override some of the dependencies (with test mocks, using NSubstitute), leaving the other dependencies as they are configured in the service.
I'm guessing I'd need to create my own HostBuilder in my test, so I'll need to be able to setup the mocks before the host.RunAsync() call is made within the test.
Does anyone know how I can do this? Or what is the best practice for doing this?
Ultimately, what I'm trying to do is be able to override some (but not all) of my real dependencies in my Console Application with mocks, so I can do some end to end tests.
Thanks in advance
There are multiple ways to achieve this. You can set up the environment variable "environment" when you start up your application. Then you would need to run your application passing it like this:
dotnet "MyApplication.dll" --environment end2endTests
Then you will be able to find the value you passed as the environment at IHostEnvironment instance which is injectable. This is how your DI registrations would look like:
services.AddScoped<IFoo>(provider =>
{
var env = provider.GetRequiredService<IHostEnvironment>();
if (env.EnvironmentName == "end2endTests")
{
return new TestFoo();
}
return new RealFoo();
});

How to use Serilog in .NET Core Console app

I wanted my application to have capability of logging to a file, so I started to look for something more than default .NET Core 2.2 logging framework. I see that Serilog might do the job. However, I cannot find any document on how to setup Serilog in .NET Core Console application with Dependency Injection. All I see is ASP.NET materials, which is probably not what I need.
I started doing it myself. I installed (Nuget):
Serilog
Serilog.Extensions.Logging
Serilog.Sinks.File
Serilog.Sinks.Console (to use Serilog for all my logging)
I created an extension forServiceCollection
public static void AddLogging(this IServiceCollection services, Microsoft.Extensions.Logging.LogLevel logLevel)
{
var serilogLogger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("log.txt")
.CreateLogger();
services.AddLogging(builder =>
{
builder.SetMinimumLevel(logLevel);
builder.AddSerilog(logger: serilogLogger, dispose: true);
});
}
Logging works, however:
log level is not what I set it to. It seems that serilog is using INFO level, although I wanted to have DEBUG. Why isn't my setting respected? After all, I'm still using NET Core's logging framework, so I'm using it to setup the log level
am I actually doing this setup correctly? I am not really sure if dispose should be true. Generally, I want NET Core's Dependency Injection framework to take care of disposal of services.
I'm not sure about builder.SetMinimumLevel (it doesn't use the Serilog enum).
We set the logger level when creating the LoggerConfiguration object.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug) // restricted... is Optional
(...)
.CreateLogger();
BTW. It's worth pointing out the following section from Configuration Basics
Logger vs. sink minimums - it is important to realize that the logging level can only be raised for sinks, not lowered. So, if the logger's MinimumLevel is set to Information then a sink with Debug as its specified level will still only see Information level events. This is because the logger-level configuration controls which logging statements will result in the creation of events, while the sink-level configuration only filters these. To create a single logger with a more verbose level, use a separate LoggerConfiguration.
I'm not sure about builder.AddSerilog.
Here's what works for me.
using Serilog;
(...)
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
(...)
.CreateLogger();
(...)
return new HostBuilder()
.ConfigureHostConfiguration(...)
.ConfigureServices(...)
.UseSerilog();

Serilog RavenDb sink not working in an asp.net 5 app

I am looking at serilog and running a few tests. So far it is working fine writing to the console or file. However I am not having any luck getting it to work with the RavenDb sink. I am trying to get this working in an asp.net 5 app.
I have reviewed the following articles:
http://nblumhardt.com/2015/05/diagnostic-logging-in-dnx-asp-net-5/
http://nblumhardt.com/2013/06/serilog-and-ravendb/
I started with an empty app, and added the following dependencies in project.json.
"Serilog.Framework.Logging": "1.0.0-rc1-final-10071",
"Serilog.Sinks.RavenDB": "1.5.4",
"RavenDB.Client": "3.0.30000"
I also removed dnxcore.
Then I added the following code in startup.cs:
public Startup()
{
var documentStore = new DocumentStore()
{
Url = "http://localhost:8080",
DefaultDatabase = "Logs"
}.Initialize();
Log.Logger = new LoggerConfiguration()
.WriteTo.File(#"c:\temp\log.txt")
.WriteTo.Console()
.WriteTo.RavenDB(documentStore)
.CreateLogger();
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
app.UseIISPlatformHandler();
app.Run(async (context) =>
{
Log.Information("Hello World");
await context.Response.WriteAsync("Hello World!");
});
}
Everything gets logged to the file and console just fine, and the Logs database gets created, but no log entries are stored in RavenDb.
I have tried various log levels. I tried reducing the batch size. I suspected this had something to do with the lifecycle of the document store, so I added the following in the ConfigureServices method.
services.AddSingleton(x =>
{
return new DocumentStore()
{
Url = "http://localhost:8080/",
DefaultDatabase = "Test",
}.Initialize();
}
Then I moved the logger configuration code into the Configure method and used DI instance, but that doesn't work either. I can store other objects in RavenDb using the same DocumentStore just fine.
Have I missed a configuration setting or something?
I was able to get this working with the latest RavenDb client. I created a new (package) style library, added Serilog nuget package v2.0.0-beta-403, added the RavenDB.Client v3 nuget package, and dropped in the .cs files from the existing Serilog.Sinks.RavenDb library. It compiled and worked, I didn't have to change any code.
I haven't had a chance to test it much yet, but it seem to be working fine. Of course I don't know how stable Serilog v2 beta is, or how long until it is released. The nice thing is that serilog v2 supports .netcore. Unfortunately the RavenDb client doesn't, at least not yet.

Dependency Injection and project structure for Console applications

I have 4 projects:
Core (IServer):
System
System.Core
DependencyResolver:
Core
StructureMap
Infrastructure (Service):
Core
External dependency
Console:
Core
DependencyResolver
Requierements:
I am trying to use StructureMap only in the DependencyResolver.
Furthermore the Console application should not know anything about Infrastucture.
When I do not want to reference StructureMap on my Console Application I have to build a ServiceLocator.
In the DependencyResolver I have a Bootstrapper that is responsible for calling StructureMap registry stuff (Register)
In my Console application I want to get an instance. For this I need to reference StructureMap. Another way would be to write a little wrapper around StructureMaps resolving methods.
Is there any other better way of decoupling the console from StructureMap?
While I see a reason for separating IoC register,resolve,release from the implementation of the application, I don't see any reason why the IoC container shouldn't be in the console application (the composition root) and the application implemention in another assembly instead.
That way the console application is very easy:
Create the container
Load the container configuration
Resolve the Application
Call run on the application and pass the console arguments along
dispose the container when the application exits the run method
With SM it look about like this:
public void Main(params string[] args)
{
using (var container = new Container())
{
container.LoadAllConfigurationModules();
container.AddRegistry<SomeRegistry>();
container.GetInstance<Application>().Run(args);
}
}
For things you can't create at startup you create a factory interface in your application assembly:
interface ISomeFactory { ISomeDependency CreateSomeDependency() }
and implement this interface in the console application by injecting the container and use it to resolve the instance. I guess the SM implementation looks like this:
public class SomeFactory : ISomeFactory
{
public SomeFactory(IContainer sontainer) { this.container = container; }
ISomeDependency CreateSomeDependency() { this.container.GetInstance<ISomeDependency>(); }
}
Other IoC container even have the functionallity to implement these interface factories automatically.

Categories

Resources