Avoid using the WebBulder and use the Startup file - c#

I've a .NET Core application that needs to peform operation based on a scheduler.
I've used the following code which also installs Kestrel but I don't need to use it at all
public class Program
{
public static void Main(string[] args)
{
var processModule = System.AppDomain.CurrentDomain.BaseDirectory;
var assemblyName = Assembly.GetCallingAssembly().GetName();
var version = assemblyName.Version;
Directory.SetCurrentDirectory(processModule);
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var applicationName = configuration.GetValue<string>("Properties:Application");
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration).Enrich.WithProperty("Version", version).Enrich
.WithProperty("ApplicationName", applicationName)
.CreateLogger();
Log.Logger = logger;
Log.Logger.Information("Started {ApplicationName} with version : {Version}", applicationName, version);
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }).UseSerilog()
.UseWindowsService();
}
And the Startup.cs is as follow :
class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
DataConnection.DefaultSettings = new Linq2DBSettings(configuration);
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//OMISS
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
Is there a way I can have Startup.cs (or IServiceCollection ) so that I can initialize my DI in this way?
Thanks

If you have all your services available in separate libraries, or you at least have the option to move them there from Web app, you could create some extension to configure DI both in your Web and Console applications
Library project
using Microsoft.Extensions.DependencyInjection;
namespace ClassLibrary
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection ApplyMyServices(this IServiceCollection services)
{
services.AddScoped<MyService>();
return services;
}
}
public class MyService
{ }
}
Console app
using ClassLibrary;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.ApplyMyServices();
var serviceProvider = serviceCollection.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var myService = scope.ServiceProvider.GetService<MyService>();
}
}
}
Web app
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.ApplyMyServices();
}

Related

IHost extension in .Net6

I am trying to migrate an IHost extension for EF Seed migration from .Net5 to .Net6 . Can you please lead me the proper way to do this conversion.
public static class HostExtensions
{
public static IHost MigrateDatabase<TContext>(this IHost host,
Action<TContext, IServiceProvider> seeder,
int? retry = 0) where TContext : DbContext
{
int retryForAvailability = retry.Value;
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
<code removed for brevity>
}
return host;
}
}
The extension is being called in .Net5 as follows:
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.MigrateDatabase<OrderContext>((context, services) =>
{
var logger = services.GetService<ILogger<OrderContextSeed>>();
OrderContextSeed
.SeedAsync(context, logger)
.Wait();
})
.Run();
}
MigrateDatabase :
public static void MigrateDatabase(this IServiceProvider sp)
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
// operations
}
Program.cs :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Services.MigrateDatabase();
apo.Run();
use like this in Program.cs :)
var app = builder.Build();
app.MigrateDatabase<WebSocketAcceptContext>();

DI in Program.cs stopped working after upgrade .NET Core from 2 to 3

I have ASP.NET Core application where I need to get database credentials from external AWS service. Essentially, I need to inject CredentialRetrievalService into Startup.cs. Awhile ago, I found an example that describes perfectly how to do it:
In Program.cs:
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(serviceCollection =>
serviceCollection.AddScoped<ISomeService, SomeService>())
.UseStartup<Startup>()
.Build();
}
and in Startup.cs:
private ISomeService _someService;
public Startup(IConfiguration configuration, ISomeService someService)
{
_someService = someService;
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Just to see that everything works - and it does!
services.AddMvc(options =>
{
options.MaxModelValidationErrors = _someService.GetNumber(Configuration.GetValue<int>("MaxModelValidationErrors"));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
Now I am trying to upgrade the application to ASP.NET Core 3. I can see that there is breaking change, and the code above doesn't work any more. Recommended action is to Inject services into the Startup.Configure method instead. But what should I do if I need to invoke injected service in ConfigureServices?
After more analysis, this is what seems to work:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<ISomeService, SomeService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IOptions<MvcOptions> options, ISomeService someService)
{
// ...
int value = Configuration.GetValue<int>("MaxModelValidationErrors");
options.Value.MaxModelValidationErrors = someService.GetNumber(value);
// ...
}
But it doesn't work with my real application where SomeService is DbContext
Consider changing the approach and taking advantage of dependency injection when configuring options
Reference Use DI services to configure options
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services) {
//...
services.AddScoped<ISomeService, SomeService>();
services.AddMvc(); //options will be configured lower down
//Use DI services to configure MVC options
services.AddOptions<MvcOptions>()
.Configure<ISomeService>((options, someService) => {
int value = Configuration.GetValue<int>("MaxModelValidationErrors");
options.MaxModelValidationErrors = someService.GetNumber(value);
});
//...
}
//...

Get environment variables in class library (.NET Core console app)

I have console app, where I have console app project and class library
I create appSettings.json file, where I store all data.
In console app I create this code in Program.cs to work with envVariables
class Program
{
public static IConfigurationRoot Configuration;
private static ServiceThread _serviceThread;
static async Task Main(string[] args)
{
MainAsync(args).Wait();
// Run with console or service
var asService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) => { services.AddHostedService<MonitoringService>(); });
builder.UseEnvironment(asService ? EnvironmentName.Production : EnvironmentName.Development);
if (asService)
{
await builder.RunAsServiceAsync();
}
else
{
_serviceThread = new ServiceThread();
_serviceThread.Start("Started");
await builder.RunConsoleAsync();
}
}
static async Task MainAsync(string[] args)
{
// Create service collection
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
// Create service provider
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
// Print connection string to demonstrate configuration object is populated
Console.WriteLine(Configuration.GetConnectionString("DataConnection"));
}
private static void ConfigureServices(IServiceCollection serviceCollection)
{
// Build configuration
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
.AddJsonFile("appSettings.json", false)
.Build();
// Add access to generic IConfigurationRoot
serviceCollection.AddSingleton<IConfigurationRoot>(Configuration);
}
}
Now in class library I want to work with those variables.
I tried like this
public class HelpersAppService
{
private readonly IConfigurationRoot _configuration;
public HelpersAppService(IConfigurationRoot configuration)
{
_configuration = configuration;
}
public ServerUrlsDto GetServerUrls()
{
var serverUrls = _configuration.GetSection("ServerUrls").Get<ServerUrlsDto>();
return serverUrls;
}
public AuthDto GetAuth()
{
var authData = _configuration.GetSection("Auth").Get<AuthDto>();
return authData;
}
}
But problem, that I have null configuration in this method. What I'm doing wrong?
The .NET Core framework provides many helpful extensions for you. I would suggest using them like this:
static async Task Main(string[] args)
{
// Run with console or service
var asService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = Host
.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) => config
.SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
.AddJsonFile("appSettings.json", false))
.ConfigureServices((context, services) => services
.AddSingleton<HelpersAppService>()
.AddHostedService<MonitoringService>())
.UseEnvironment(asService ? EnvironmentName.Production : EnvironmentName.Development);
if (asService)
{
await builder.RunAsServiceAsync();
}
else
{
_serviceThread = new ServiceThread();
_serviceThread.Start("Started");
await builder.RunConsoleAsync();
}
}
Update:
You will also need to inject an IConfiguration instead of an IConfigurationRoot like this:
private readonly IConfiguration _configuration;
public HelpersAppService(IConfiguration configuration)
{
_configuration = configuration;
}
Note:
You need to also add the HelpersAppService in the ConfigureServices method for it to be part of DI and have the IConfiguration available.

Programmatic add / remove services at runtime

As far as I have been able to do is to add new services though the IServiceCollection
Is there a way to add and remove these services at run time?
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
AzureMultiTenantServiceBuilders.Build(services);
}
I am trying to figure out how to configure additional AzureAd for our tenants at run time without having to restart the system.
public static class AzureMultiTenantServiceBuilders
{
public static void Build(IServiceCollection services)
{
foreach (var tenant in Tenant.GetAll())
{
services.AddAuthentication()
.AddAzureADTenanted(options =>
{
options.ClientId = tenant.ClientId;
options.TenantId = tenant.TenantId;
options.Instance = "https://login.microsoftonline.com";
});
}
I found this Installing a new middleware at runtime in ASP.Net Core which was close as its adding middleware but what i am trying to do is adding a service at runtime so this isnt helping.
public static class RuntimeMiddlewareExtensions
{
public static IServiceCollection AddRuntimeMiddleware(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
services.Add(new ServiceDescriptor(typeof(RuntimeMiddlewareService), typeof(RuntimeMiddlewareService), lifetime));
AzureMultiTenantServiceBuilders.Build(services);
return services;
}
public static IApplicationBuilder UseRuntimeMiddleware(this IApplicationBuilder app, Action<IApplicationBuilder> defaultAction = null)
{
var service = app.ApplicationServices.GetRequiredService<RuntimeMiddlewareService>();
service.Use(app);
if (defaultAction != null)
{
service.Configure(defaultAction);
}
return app;
}
}

Dependency Injection in .Net Web Api 2.2 endpoint not available

I have a console application which works quit like a web api.
At the Program.cs I register
var collection = new ServiceCollection();
collection.AddScoped<IInfoBusinessComponent, InfoBusinessComponent>();
The InfoBusinessComponent need also a dependency injection which I do before adding the InfoBusinessComponent. Also I register my ILogger.
At my InfoController I use the di like that:
public InfoController(IInfoBusinessComponent businessComponent, ILogger<InfoController> logger)
When I call now that endpoint, I get immediately a 500 response.
When I erase the arguments from the controller, than the process is going into the constructor and controller. But that's not what I want.
public InfoController()
Why is the constructor not getting the dependency injection or why is the constructor not called?
public class Program
{
#region fields and propetries
public IConfiguration Configuration { get; }
//# if DEBUG
//#endif
public static IConnection Connection { get; set; }
public static ITimeSeriesBusinessComponent TimeSeriesBusinessComponent { get; set; }
public static IInfoBusinessComponent InfoBusinessComponent { get; set; }
private static int counter;
#endregion fields and propetries
public static void Main(string[] args)
{
IConfiguration config = GetConfigurations();
ILogger logger = GetLogger();
ServiceProvider appServiceProvider = GetServiceProvider(config);
Parallel.Invoke
(
() =>
{
BuildWebHost(args).Build().Run();
},
() =>
{
//...
}
);
}
private static IConfiguration GetConfigurations()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
return config;
}
private static ILogger GetLogger()
{
ILogger logger = new LoggerFactory().AddNLog().CreateLogger<Program>();
return logger;
}
private static ServiceProvider GetServiceProvider(IConfiguration config)
{
var collection = new ServiceCollection();
collection.AddLogging(configuration => configuration.AddNLog());
//...
collection.AddScoped<IInfoRepository>(serviceProvider =>
{
return new InfoRepository(
config["ConnectionStrings:MainConnection"],
config["ConnectionStrings:MetaDataConnection"],
config["InfoFunctionName"],
config["UserName"],
config["Password"],
config["VirtualHost"],
config["ConnectionHostName"]);
});
collection.AddScoped<IInfoBusinessComponent, InfoBusinessComponent>();
var appServiceProvider = collection.BuildServiceProvider();
return appServiceProvider;
}
public static IWebHostBuilder BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseUrls("http://0.0.0.0:5003")
.UseNLog();
}
Here the Startup.cs:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "My CLI"
});
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My CLI");
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
c.RoutePrefix = string.Empty;
});
app.UseMvc();
}
}
The problem is that the endpoint you create with BuildWebHost uses its own instance of ServiceProvider. The instance of ServiceProvider that you create doesn't get into the pipeline.
Why: ServiceCollection doesn't use any kind of singleton registry, so it's not enough to register services through some instance of ServiceCollection and build some instance of ServiceProvider. You have to make the endpoint use your specific instance of ServiceCollection/ServiceProvider. Or you can copy your ServiceCollection into one that's used by the endpoint - that's how I'd solve it.
So, let's use a ServiceCollection to register your services (as it is now). Then, instead of doing collection.BuildServiceProvider(), let's use that ServiceCollection in the Startup, to copy all registrations into the service collection used by the pipeline.
First, let's expose your ServiceCollection to be accessible from Startup:
class Program
{
public static ServiceCollection AppServices { get; set; }
public static void Main(string[] args)
{
// ...other stuff...
AppServices = GetServiceCollection(config);
// ...other stuff...
}
// renamed from GetServiceProvider
private static ServiceCollection GetServiceCollection(IConfiguration config)
{
var collection = new ServiceCollection();
// ... register services...
return collection;
}
}
Then in the Startup class, use Program.AppServices in ConfigureServices() as follows:
EDIT: pay attention to the usings in Startup.cs
// make sure these usings are present:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
....
public class Startup
{
// ... other members ...
public void ConfigureServices(IServiceCollection services)
{
// ... the usual stuff like services.AddMvc()...
// add this line:
services.TryAdd(Program.AppServices);
}
// ... other members ...
}

Categories