C# - .NET 6 - Console app with Generic Host vs without - c#

I am using the new top-level statements in .NET 6 to create a simple console application, but I don't understand the advantages/disadvantages of using the "Generic Host".
Can you explain?
My code with Generic Host:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Console.WriteLine("Hello, World!");
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
})
.Build();
Test();
Console.ReadKey();
void Test()
{
var testClass = host.Services.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}
versus
using Microsoft.Extensions.DependencyInjection;
Console.WriteLine("Hello, World!");
var services = new ServiceCollection();
services.AddTransient<ITestInterface, TestClass>();
var servicesProvider = services.BuildServiceProvider();
Test();
Console.ReadKey();
void Test()
{
var testClass = servicesProvider.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}

The benefits of using the generic host is that by default a lot of services are already setup for you, see the docs.
The CreateDefaultBuilder method:
Sets the content root to the path returned by GetCurrentDirectory().
Loads host configuration from:
Environment variables prefixed with DOTNET_.
Command-line arguments.
Loads app configuration from:
appsettings.json.
appsettings.{Environment}.json.
Secret Manager when the app runs in the Development environment.
Environment variables.
Command-line arguments.
Adds the following logging providers:
Console
Debug
EventSource
EventLog (only when running on Windows)
Enables scope validation and dependency validation when the environment is Development.
The ConfigureServices method exposes the ability to add services to the Microsoft.Extensions.DependencyInjection.IServiceCollection instance. Later, these services can be made available from dependency injection.
You are not using the generic host correctly. For instance: normally one would add a hosted service so you can use proper DI instead of resolving the required services manually.
An example can be found at the docs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
If we extend this example with an implementation of Worker that takes in a dependency it will look like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
services.AddHostedService<Worker>();
});
}
internal class Worker : IHostedService
{
public Worker(ITestInterface testClass)
{
testClass.Foo();
}
public Task StartAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public interface ITestInterface
{
void Foo();
}
public class TestClass : ITestInterface
{
public void Foo()
{
}
}
Now you see a new instance of Worker is created and an instance of ITestInterface is injected. There is no need to call servicesProvider.GetRequiredService<ITestInterface>(); which is an anti-pattern.
Decision Tree
If you don't need all those additional services you can choose not to use the Generic Host like in your second code example in the question.
If you do want to make use of services like logging, app configuration etc. you should use the Generic Host.

Related

Is there a way create a service object of an NUnit test class to use dependency injection when connecting it to a console app?

I have an NUnit3 project and a console app project in .NET 5 and would like to be able to use dependency injection for a class in my NUnit project. In the console app I am creating the HostBuilder to configure the services for the dependency injection, but in the Main method when I am trying to get the service object of my test class it is returning null. I am not too familiar with DI other than a couple small ASP.NET projects I've done so I don't know if I am doing this right exactly or if this is something that can be done. Any help would be greatly appreciated.
namespace SeleniumTest
{
public interface IDriverHelper
{
public AndroidDriver<AndroidElement> Driver { get; }
}
}
namespace SeleniumTest
{
public class DriverHelper : IDriverHelper
{
private AndroidDriver<AndroidElement> driver;
private AppiumLocalService appiumLocalService;
private AppiumOptions appiumOptions;
public AndroidDriver<AndroidElement> Driver
{
get => driver;
set
{
appiumLocalService = new AppiumServiceBuilder().UsingAnyFreePort().Build();
appiumLocalService.Start();
appiumOptions = new AppiumOptions();
// options added here
driver = new AndroidDriver<AndroidElement>(appiumLocalService, appiumOptions);
}
}
}
}
Here is the beginning of the Test Class
namespace SeleniumTest
{
[TestFixture]
public class Tests
{
private readonly IDriverHelper _driverHelper;
public MyMerlin(IDriverHelper driverHelper)
{
_driverHelper = driverHelper;
}
In the code below, var test = host.Services.GetService<Tests>(); returns null so I am unable to run any of the tests in that class, ex: test.PreSetupTest();
public class Program
{
static Task Main(string[] args)
{
Console.WriteLine("Hello World!");
using IHost host = CreateHostBuilder(args).Build();
var test = host.Services.GetService<Tests>();
test.PreSetupTest();
return host.RunAsync();
}
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
services.AddSingleton<IDriverHelper, DriverHelper>());
}

How to perform dependency injection through Microsoft.Extensions.Hosting in wpf

Not the solution mentioned in the following question.
Dependency Injection in .NET Core 3.0 for WPF
Instead, by adding <EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition> to the csproj file to prevent App.xaml from automatically generating the Main method, thereby achieving a similar effect to the dependency injection of ASP.NET core.
I have seen a piece of very beautiful code that somehow get an instance of App through IServiceProvider to call Application.Run(), but I can’t remember it now, and I can’t remember the source of the original code.
Can anyone help? Thank you very much.
My current code is as follows.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
class Program
{
[STAThread]
public static void Main()
{
Host.CreateDefaultBuilder()
.ConfigureServices(ConfigureServices)
.Build()
.Run();
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<App>();
services.AddSingleton<MainWindow>();
}
}
I need to get an instance of the App class to call Application.Run().
A working solution for me is:
internal class Program
{
[STAThread]
public static void Main()
{
var host = Host.CreateDefaultBuilder()
.ConfigureServices(ConfigureServices)
.Build();
var app = host.Services.GetService<App>();
if(app != null)
{
app.InitializeComponent();
app.Run();
}
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<App>();
services.AddSingleton<MainWindow>();
}
}
The IServiceProvider can be found in IHost.Services.
class Program
{
[STAThread]
public static void Main()
{
var host = Host.CreateDefaultBuilder()
.ConfigureServices(ConfigureServices)
.Build();
host.RunAsync();
var app = host.Services.GetService<App>();
app.Run();
}
private static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<App>();
services.AddSingleton<MainWindow>();
}
}

Accessing DbContext through projects within a solution [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 2 years ago.
I currently do have the following setup these are the different projects
Project.DAL: Data Access Layer
Project.BLL: Business Logic Layer
Project.Core: Interfaces, Models
Project.API: Web API
Project.Console: Simple .NET console app that
in Project.DAL I did setup the db context
public class AppDbContext : DbContext
{
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{ }
}
while I did setup within Project.API all the Dependencies Injections
services.AddTransient<IUserService, UserService>();
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("Default"),x => x.MigrationsAssembly("Project.DAL"));
});
when I try to run Program.cs in Project.Console
class Program
{
private static AppDbContext _appDbContext;
static void Main(string[] args)
{
_appDbContext.Users.ToList();
}
}
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
any idea ?
Thanks In Advance.
You need to do the configurations for all the entry points.
Entry point is yout API
The second entry point is your console application
So you need to modify your main function:
class Program
{
private static AppDbContext _appDbContext;
static void Main(string[] args)
{
// _appDbContext is not initialized right now
// Even if you configure the injection here the _appDbContext will not be initialized.
_appDbContext.Users.ToList();
}
}
Check the net core console app dependency injection guide
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ConsoleDI.Example
{
class Program
{
static Task Main(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
ExemplifyScoping(host.Services, "Scope 1");
ExemplifyScoping(host.Services, "Scope 2");
return host.RunAsync();
}
static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
services.AddTransient<ITransientOperation, DefaultOperation>()
.AddScoped<IScopedOperation, DefaultOperation>()
.AddSingleton<ISingletonOperation, DefaultOperation>()
.AddTransient<OperationLogger>());
static void ExemplifyScoping(IServiceProvider services, string scope)
{
using IServiceScope serviceScope = services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
OperationLogger logger = provider.GetRequiredService<OperationLogger>();
logger.LogOperations($"{scope}-Call 1 .GetRequiredService<OperationLogger>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<OperationLogger>();
logger.LogOperations($"{scope}-Call 2 .GetRequiredService<OperationLogger>()");
Console.WriteLine();
}
}
}
The key code in the example above is this:
provider.GetRequiredService<OperationLogger>();
This is the one that creates the OperationLogger from the configured DI container.
You should create the AppDbContext DI rules, build CreateHostBuilder(args).Build(); and the create the instance as shown above.

How to get access to a service provider after creating and running the default host?

I have a .NET Core console application and create a new default host instance. I'm configuring my services in a static extensions class and there is one service that is a singleton. In my Program file I have to get access to the instance once so I can call a method from it.
Given this sample code
class Program
{
public static void Main(string[] args)
=> new Program()
.MainAsync(args)
.GetAwaiter()
.GetResult();
private async Task MainAsync(string[] args)
{
await Host.CreateDefaultBuilder(args)
.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
// This comes from another class configuring all the services for the serviceCollection
serviceCollection.AddSingleton<MyService>();
})
.Build()
.RunAsync();
// Get access to the service provider
// Get MyService instance from the service provider
}
}
Is there a way to create a new instance of a service provider on the fly below the method RunAsync?
Host.CreateDefaultBuilder returns IHostBuilder which provides IHostBuilder.Build which in turn returns IHost. IHost contains property IHost.Services representing configured DI container. In case you want to access it, store IHost inside a variable before starting it.
private async Task MainAsync(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
// This comes from another class configuring all the services for the serviceCollection
serviceCollection.AddSingleton<MyService>();
})
.Build();
await host.StartAsync();
var myService = host.Services.GetRequiredService<MyService>();
/* some useful code */
await host.WaitForShutdownAsync();
}

ASP.NET Core 2 + Get instance of db context

I am trying to get an instance of the DbContext (so I can do some additional work upon startup with it), I get the following error when trying to get an instance in the Configure method:
System.InvalidOperationException: 'Cannot resolve scoped service 'MyApp.Data.MyDbContext' from root provider.'
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var dbContext = app.ApplicationServices.GetService(typeof(MyDbContext)) as MyDbContext;
}
I can access an instance of the DbContext fine via the controller, etc
Paul Hiles comment is correct but that method works better in .NET Core 1.0.
In ASP.NET Core 2.0 it's generally a bad idea to run any database setup in Startup.cs. This is because if you run any migrations from the CLI or Visual Studio it will run all of Startup.cs and try to run your configuration which will fail. Of course if you don't use Entity-Framework then this isn't a problem however its still not the recommended way of doing it in 2.0. It's now recommended to do it in Program.cs.
For example you can create a extension method of IWebHost that will run any setup you need.
public static IWebHost MigrateDatabase(this IWebHost webHost)
{
var serviceScopeFactory = (IServiceScopeFactory)webHost.Services.GetService(typeof(IServiceScopeFactory));
using (var scope = serviceScopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<YourDbContext>();
dbContext.Database.Migrate();
}
return webHost;
}
And then in Program.cs you can then call that method before running.
public static void Main(string[] args)
{
BuildWebHost(args)
.MigrateDatabase()
.Run();
}
Update for Core 2.1 onwards
Just to add to #Travis Boatman's excellent answer, the preferred Main method syntax has changed slightly from Core 2.1 onwards and the default Main method now has CreateWebHostBuilder instead of BuildWebHost.
The revised code to call the extension method is shown below.
NB: the order is important here, the Build method returns a WebHost, which is what the extension method is extending, so you need to call the migrate method after Build() and before Run()):
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.MigrateDatabase()
.Run();
}
Migrating more than one DbContext
We have more than one DbContext in our project, so I changed the extension method to a generic method that can take any type of DbContext:
public static IWebHost MigrateDatabase<T>(this IWebHost webHost) where T:DbContext
{
var serviceScopeFactory = (IServiceScopeFactory)webHost
.Services.GetService(typeof(IServiceScopeFactory));
using (var scope = serviceScopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<T>();
dbContext.Database.Migrate();
}
return webHost;
}
You can then chain the calls to migrate the different contexts:
CreateWebHostBuilder(args)
.Build()
.MigrateDatabase<ApiAuthDbContext>()
.MigrateDatabase<MainDbContext>()
.MigrateDatabase<SomeOtherDbContext>()
.Run();
see this question and he answerd himself in the 'Update' section
Add in program.cs at CreateWebHostBuilder method
.UseDefaultServiceProvider(options => {
options.ValidateScopes = false;//to use any scoped
option.validateOnBuild = false;//to use dbContext
})
The full code:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
Host.CreateWebHostBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
option.ValidateOnBuild = false;
});
})
}

Categories