Good day all, I am trying to host two BackgroundService instances in a single IHost and provide them different configuration sections. Unfortunately I cant seem to find a way to do it other than going for the options pattern. here is what my IHost looks like
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder) =>
{
configurationBuilder.Sources.Clear();
configurationBuilder.Add(configurationHelperSource);
})
.ConfigureLogging((hostBuilderContext, loggingBuilder) =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddProvider(logHelperProvider);
})
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ProcessPendingEmails>();
services.AddHostedService<LoadFilesToDatabase>();
})
.Build();
host.Run();
I wish for both of these services to get different IConfiguration sections. Hoping for some help here. Cheers
tried other stackoverflow questions, msdn and I was looking to avoid using the IOptions pattern
The .Net Configuration system is based on key and value pairs. You are not forced to create classes to map individual values if you don't want to.
Simply inject IConfiguration and read the configuration values by querying using the key.
From this Microsoft page:
// Ask the service provider for the configuration abstraction.
IConfiguration config = host.Services.GetRequiredService<IConfiguration>();
// Get values from the config given their key and their target type.
int keyOneValue = config.GetValue<int>("KeyOne");
bool keyTwoValue = config.GetValue<bool>("KeyTwo");
string? keyThreeNestedValue = config.GetValue<string>("KeyThree:Message");
Just do the same, except maybe for using GetRequiredService. Instead just ask for IConfiguration in the services' constructors.
Related
Being able to customize the Host configuration in integration tests is useful to avoid calling services that shouldn't run at all during testing. This was easily achievable using the standard Startup model, overriding the CreateHostBuilder from the WebApplicationFactory. I tried many many things using the "Minimal APIs" approach and couldn't figure it out.
A full example of what was possible using the Startup model, would be something like this:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
// This is a service that downloads Configuration from the internet, to be used
// as a source for `IConfiguration`, just like any `appsettings.json`.
// I don't want this running during testing
.AddConfigServer();
}
As you can imagine, the AddConfigServer calls an external web server to download the configuration I want to use in my app startup, but I definitely don't want my integration tests calling this external web server for several reasons: don't want to depend on external services, don't want to hammer my config server with testing requests, don't want my server customizations to be exposed to my tests, etc.
Using the Startup model, I could easily change this behavior with this:
public class MyWebApplicationFactory : WebApplicationFactory<Program>
{
protected override IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder()
// No AddConfigServer for my integration tests
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
//... other customizations
}
However, trying this same approach throws an exception on my integration test startup:
System.InvalidOperationException : No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via StartupAssemblyKey in the web host configuration.
Since I don't have a Startup class, I couldn't figure out how to properly do this. In fact, since this is common in all my web projects, I abandoned Minimal APIs altogether for the moment, until I figure out how to properly achieve this without a Startup class.
PS.: I have all the "partial Program" and "InternalsVisibleTo" in place, as described here.
In order to make this work, you have to provide your configuration values twice both before and after the minimal API Program.cs runs.
Here's how I did it:
public class MyFixture : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configurationValues = new Dictionary<string, string>
{
{ "MyConfigSetting", "Value" }
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configurationValues)
.Build();
builder
// This configuration is used during the creation of the application
// (e.g. BEFORE WebApplication.CreateBuilder(args) is called in Program.cs).
.UseConfiguration(configuration)
.ConfigureAppConfiguration(configurationBuilder =>
{
// This overrides configuration settings that were added as part
// of building the Host (e.g. calling WebApplication.CreateBuilder(args)).
configurationBuilder.AddInMemoryCollection(configurationValues);
});
}
}
In a .net core application, why I am always in "production", even if the ASPNETCORE_ENVIRONMENT is 'development'?
class Program {
static void Main(string[] args) {
var builder = new ConfigurationBuilder();
BuildConfig(builder);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Build())
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
// ...
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
switch (serviceToUse) { // ... } })
.UseSerilog()
.Build();
IService svc;
// ... got the corresp service...
svc.Run();
}
static void BuildConfig(IConfigurationBuilder builder) {
string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
?? "Production";
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true)
.AddUserSecrets<Program>()
.AddEnvironmentVariables();
}
}
However, when I test
I get rather unexpected values in the test setting:
[13:36:55 ERR] Environment.GetEnvironmentVariable('ASPNETCORE_ENVIRONMENT')=Development
[13:36:55 ERR] Environment.GetEnvironmentVariable('DOTNET_ENVIRONMENT')=Development
[13:36:55 ERR] The test setting is 'test value from secrets.json';
having this in projet:
My question is why does it take the Production settings if I am in Development mode?
It’s unclear to me where you are retrieving the configuration using this._config.GetValue<string>("test"). My guess is that you are retrieving it from the IConfiguration injected via DI.
In that case, you should note that the Host.CreateDefaultBuilder will also already come with its own configuration setup which does not really care about what you are doing before with your BuildConfig.
The default host builder will already set up configuration using the files appsettings.json and appsettings.<env>.json (and others) and will register that configuration as the IConfiguration singleton that is being provided to your application. In order to register the environment-specific file, it will use the environment from the IHostEnvironment which is what decides what actual environment your application is running on.
With ASP.NET Core and the web host, it is possible to configure this environment using the ASPNETCORE_ENVIRONMENT environment variable. However, since you are not using ASP.NET Core but just the host builder on its own, this environment variable is not being used. Instead, the default host builder will look for the environment variable DOTNET_ENVIRONMENT.
Alternatively, you can also call hostBuilder.UseEnvironment to set the environment explicitly if you want to take it from a different environment variable.
Finally, when you are setting up your own configuration, I would suggest you to reconfigure your host so that it will use this configuration object itself instead of building its own (which may or may not match your desired setup). You can do that by calling hostBuilder.ConfigureAppConfiguration.
I have a question, about how to set up a project for multiple clients. We currently develop an ASP Core application. All clients use the same database structure, so they have identical databases, just filled with different users,... We currently use one publish, set up two test websites on IIS and each of those publishes has a different JSON config file containing the DB context (read out at Startp.cs).
Our problem here is, that if we have multiple clients, we have to copy our publish multiple times to maintain multiple websites for the IIS. We didn't find a way to just use one publish and define the config file to be used dependent on the URL/port of the connected client. We tried a tutorial for tenancy under ASP Core, but it failed at the User Manager.
Can someone point me out, to what's the best solution for this kind of webproject, which requires one shared website/publish under IIS, different DB contexts for each client (URL/port), user authentication (we used Identity for that). We tried this for weeks now, but failed to get it working. Our main problem is, that the DB context is created in the Startup.cs before the host is built, so we don't have any URL's from the request and can't create a specific DB context when the application starts. This means, we can't fill the UserStore and can't get the login to work. We know we could make a master table with all the users and domains, but we really would like to avoid this approach, as we are not sure, if the websites will run on the same server, or if one client may use his own internal company server.
EDIT: I need to be more specific, as I made a small mistake. The databases are identical. So i actually won't need different context classes, but just different connection strings. But they are set in the Startup.cs in the AddDbContext function...
EDIT2: I tried a few solutions now, and I'm always failing on Identity. It doesn't seem to work and crashes, when no DbContext is created directly during Startup.
EDIT3: As I failed so far, here are some Code Snippets from our Projekt
In our Startup.cs/ConfigureServices(IServiceCollection services)
We are loading our DB settings from a config file
IConfigurationSection DbSection = Configuration.GetSection("Database");
We are adding the DB Context
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(MyDbContext.createConnectionString(DbSection), b => b.MigrationsAssembly("MyNamespace.DB").EnableRetryOnFailure()));
And setting up Identity
services.AddScoped<Microsoft.AspNetCore.Identity.SignInManager<MyUser>, MySignInManager>();
services.AddScoped<Microsoft.AspNetCore.Identity.IUserStore<MyUser>, MyUserStore>();
services.AddScoped<Microsoft.AspNetCore.Identity.UserManager<MyUser>, Services.LogIn.MyUserManager>();
services.AddIdentity<MyUser, MyRole>()
.AddUserManager<MyUserManager>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
We are adding authorization
services.AddAuthorization(options =>
{ options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
In our Startup.cs/Configure(...)
- We set UseAuthorization
app.UseAuthentication();
app.UseAuthorization();
In our MyDbContext class we are creating the connection string
public static string createConnectionString(IConfigurationSection configurationSection)
{
return #"server=" + configurationSection.GetSection("Server").Value +
";database=" + configurationSection.GetSection("Database").Value +
";persist security info=True;user id=" + configurationSection.GetSection("User").Value +
";password=" + configurationSection.GetSection("Password").Value + ";multipleactiveresultsets=True;";
}
So basically all we are trying to achieve, is to make different connections for different urls. Let's say we have 2 cients:
clientone.mysite.com:123 -> database_clientone
clienttwo.mysite.com:456 -> database_clienttwo
This does not work, as we don't have the domain when creating the DbContext. But we are required to have each database store it's own login credentials, instead of using a master table with all the logins for each existing users/databases...
And Identity doesn't seem to work with this scenario either.
If you are registering your DB context with Core's DI via AddDbContext call you can leverage it's overload which provides you access to IServiceProvider:
public void ConfigureServices(IServiceCollection services)
{
services
.AddEntityFrameworkSqlServer()
.AddDbContext<MyContext>((serviceProvider, options) =>
{
var connectionString = serviceProvider.GetService // get your service
// which can determine needed connection string based on your logic
.GetConnectionString();
options.UseSqlServer(connectionString);
});
}
Also here something similar is achieved with Autofac.
You can have multiple appsettings.json files and depending on client load appropriate json file .
With above case you will still be creating Db
Context in Startup.cs
public class ConfigurationService : IConfigurationService
{
public IConfiguration GetConfiguration()
{
var env = GetCurrentClient();//You can also set environment variable for client
CurrentDirectory = CurrentDirectory ?? Directory.GetCurrentDirectory();
return new ConfigurationBuilder()
.SetBasePath(CurrentDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables()
.Build();
}
}
Instead of using AddDbContext , try to use inject DBContext lazily.
Add a class DataContext which will implement DBContext
public class DataContext : DbContext
{
public DataContext(IConfigurationService configService)
: base(GetOptions( GetConnectionString())
{
this.ChangeTracker.LazyLoadingEnabled = true;
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
public static string GetConnectionString()
{
//Your logic to read connection string by host
// This method will be called while resolving your repository
}
}
In your Startup.cs
remove AddDbContext() call
and instead use
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<DbContext, YourDBContext>();
//likewise Addscope for your repositories and other services
//Code to build your host
}
DI DbContext in your controller's constructor or in service's constructor
Since your DBContext is now being loaded lazily , you will be able decide on connection string depending on your host.
Hope this works for you!!
I tried to create an environment variable via web.config and reading it out within our application. But I couldn't find a way, how to setup multiple websites in IIS7 with the SAME publish/file-folder, but DIFFERENT web.config files. This didn't work either, or at least I had no clue how to achieve this...
In my Core 1.0 app, Startup ctor loaded several extra json files into the configuration...
public Startup(IHostingEnvironment env) {
Environment = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddJsonFile($"activityGroups.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Then in ConfigureServices, I grabbed the data from these out of the config into strongly typed lists...
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<Models.DbContext>(
options => options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]
)
);
services.AddScoped<Repository>();
services.AddTransient<DbSeed>();
services.AddOptions();
services.Configure<List<Models.Task>>(Configuration.GetSection("ActivityGroups"));
}
Then, in Configure, the strongly typed list was injected and use for db init..
public void Configure(
IApplicationBuilder app,
DbSeed seed,
ILoggerFactory logging,
IOptions<List<ActivityGroup>> activityGroupConfig
) {
app.UseAuthentication();
app.UseMvc();
bool seedDb;
bool.TryParse(Configuration.GetSection("SeedDb").Value, out seedDb);
if (seedDb) {
seed.EnsureSeeded(activityGroupConfig.Value);
}
}
However...
In 2.0 projects, move the SeedData.Initialize call to the Main method
of Program.cs...
As of 2.0, it's bad practice to do anything in BuildWebHost except
build and configure the web host. [configure web host includes data seeding?] Anything that's about running the
application should be handled outside of BuildWebHost — typically in
the Main method of Program.cs.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
Yet, in "new" Main(string[] args) we don't have access to the dbcontext (until Startup.ConfigureServices). Even if in the SeedData.Initialize(services); call as above in the MS documentation, how would SeedData have access to the connection string that is not defined until later in configure services?
The adoption of this new 2.0 pattern is highly recommended and is
required for product features like Entity Framework (EF) Core
Migrations to work.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#update-main-method-in-programcs
I've been googling and digging through docs and samples (that example seeds with hardcoded values) without getting a clear picture (yet) of the expectations or recommended design (beyond, "dont do what you did previously".)
Anyone here more familiar or experienced with how to do database seeding from json on disk in ASP.NET Core 2.0?
So, with help from this SO question, I got the gist of getting the services from within Program.cs
public class Program {
public static void Main(string[] args) {
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILoggerFactory>();
var ctx = services.GetRequiredService<DataContext>();
// Now I can access the DbContext, create Repository, SeedData, etc!
Now, the problem is in trying to figure out how to access values from appsettings.json, new SO question here.
I'm trying to implement a new extension method for the IWebHostBuilder that executes some code, like registering somewhere.
The important part is, that it also unregisters when the application is shutting down.
I added some code which shows what I want to do. The problem here is, that the instance of IApplicationLifetime is not available yet. This new extension method was added last in the pipeline of building the WebHost.
public static IWebHostBuilder UseHttpGatewayappRegistration(this IWebHostBuilder webHostBuilder)
{
webHostBuilder.ConfigureServices(services =>
{
var sp = services.BuildServiceProvider();
// This instance is not available
IApplicationLifetime applicationLifetime = sp.GetService<IApplicationLifetime>();
// These instances are ok
AppSettings appSettings = sp.GetService<AppSettings>();
ILoggerFactory loggerFactory = sp.GetService<ILoggerFactory>();
var registration = new Registration(loggerFactory.CreateLogger<Registration>());
applicationLifetime.ApplicationStopping.Register(() =>
{
registration.Unregister();
});
});
return webHostBuilder;
}
Why is the IApplicationLifetime instance null, even though I added this extension method last in the pipeline of building the WebHost pipeline? It would be great, if someone would provide me with some information about the execution order of all "ConfigureServices" methods and how or if it is at all possible to use IApplicationLifetime in a ConfigureServices method.
I know I could do this all without the WebHostBuilder, but it seems logical to me to do it there and I also think there has to be a way.
Unfortunately I couldn't find much information online...
Thank you.
EDIT: I found a way to use DI in ConfigureServices methods. I edited the question accordingly. This helped me: How to Resolve Instance Inside ConfigureServices in ASP.NET Core
You can't get access to IApplicationLifetime instance from ConfigureServices method. It's by design. Check the "Services Available in Startup" section here:
Application Startup in ASP.NET Core.
Resolve IApplicationLifetime in the IWebHostBuilder.Configure method (it replaces the Startup.Configure method if it is used later in IWebHostBuilder configuration pipeline):
public static IWebHostBuilder Foo(this IWebHostBuilder webHostBuilder)
{
webHostBuilder.Configure(x =>
{
var service = x.ApplicationServices.GetService<IApplicationLifetime>();
});
return webHostBuilder;
}
It's also possible to extend the original Startup.Configure method instead of replace (a lot of Reflection logic should be added to make this solution reliable):
.Configure(app =>
{
var env = app.ApplicationServices.GetService<IHostingEnvironment>();
dynamic startup = Activator.CreateInstance(typeof(TStartup), env);
//Carefully resolve all input parameters.
//Make sure all required services are registered in DI.
startup.Configure(app, env);
var lifetime = app.ApplicationServices.GetService<IApplicationLifetime>();
})