I am working on refactoring an ASP.Net Core v1 application to v2. The gist of the current effort is moving the database seeding logic to Program.Main() as indicated in the MS docs...
In 2.0 projects, move the SeedData.Initialize call to the Main
method of Program.cs:
The problem I am encountering is that I need to get a config flag out the appsettings.json file. This appears to be loaded by default when WebHost.CreateDefaultBuilder is called as per the source, but my code isn't able to retrieve a simple flag from my appsettings.json.
public static IWebHost BuildWebHost(string[] args)
{
var builder = WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
string env = builder.GetSetting("environment");
var envMsg = "ASPNETCORE_ENVIRONMENT/Environment variable ";
if (string.IsNullOrWhiteSpace(env))
throw new ArgumentNullException("environment", envMsg + "missing!");
else
Console.WriteLine(envMsg + "found!");
string doSeed = builder.GetSetting("SeedDb");
var seedMsg = "SeedDb in appsettings.json ";
if (string.IsNullOrWhiteSpace(doSeed))
throw new ArgumentNullException("SeedDb", seedMsg + "missing!");
else
Console.WriteLine(seedMsg + "found!");
return builder.Build();
}
The environment variable is set (as expected), but the values from the json file do not appear to be. No exception on env check, but there is on the seed flag.
Repo here to verify if needed. What am I missing? I've seen mention of prefixing the setting to search for with "AppSettings", etc... but I don't see that indicated in the source (and can't verify what it should be, if anything.)
Combining the results from this SO, I have it figured out...
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var config = services.GetService<IConfiguration>(); // the key/fix!
var s = config.GetValue<string>("SeedDb");
var doSeed = bool.Parse(s); // works!
}
host.Run();
}
Or just var doSeed = config.GetValue<bool>("SeedDb");!
Related
While upgrading from NLog 4.7.15 to 5.0.1 I found this test in our code base:
[Test]
public void CustomLogFactoryShouldBehaveCompletelyIndependent()
{
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
var customConfig = new LoggingConfiguration();
customConfig.AddTarget("UnitTestLogger4", memoryTarget4);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
customConfig.AddTarget("UnitTestLogger2", memoryTarget5);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5));
using (var customFactory = new LogFactory(customConfig)) // <<-- This ctor is marked obsolete
{
// Log logger defined only in custom config
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1));
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();
//... More cases tested here
}
}
The test works fine both in the old and the new version, but only after I disable the deprecation warning CS0618 for the constructor LogFactory(LoggingConfiguration).
To work around this, I tried to use the suggested alternative LoggingConfiguration(LogFactory), which connects the factory and the configuration basically the other way round.
[Test]
public void CustomLogFactoryShouldBehaveCompletelyIndependent()
{
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
using (var customFactory = new LogFactory())
{
var customConfig = new LoggingConfiguration(customFactory);
customConfig.AddTarget("UnitTestLogger4", memoryTarget4);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
customConfig.AddTarget("UnitTestLogger2", memoryTarget5);
customConfig.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5));
// customFactory.ReconfigExistingLoggers(); // <<-- Adding this changes nothing
// Log logger defined only in custom config
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1)); // <<-- Fails here, nothing was added to memoryTarget4.
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();
}
}
At least that's what I think should be changed. But the test fails now. The configuration is not applied, as the target does not get any logs.
What did I miss? What's the correct way of replacing the deprecated LogFactory(LoggingConfiguration) constructor here?
The constructor new LogFactory(customConfig) became obsolete to ensure that the LoggingConfiguration.Factory-option had the expected value (Using the intended isolated LogFactory instead of the global static LogManager.Factory)
You can replace:
customFactory.ReconfigExistingLoggers();
With this so the configuration is activated:
customFactory.Configuration = customConfig;
Alternative you could do this:
using var customFactory = new NLog.LogFactory().Setup().LoadConfiguration(builder => {
var memoryTarget4 = new MemoryTarget();
memoryTarget4.Layout = "${level}|${logger}|${message}${exception}";
var memoryTarget5 = new MemoryTarget();
builder.Configuration.LoggingRules.Add(new LoggingRule("UnitTestLogger4", LogLevel.Trace, memoryTarget4));
builder.Configuration.LoggingRules.Add(new LoggingRule("UnitTestLogger2", LogLevel.Info, memoryTarget5)));
}).LogFactory;
var logger4 = customFactory.GetLogger("UnitTestLogger4");
logger4.Trace("Test4");
Assert.That(memoryTarget4.Logs.Count, Is.EqualTo(1));
Assert.That(memoryTarget5.Logs, Is.Empty);
memoryTarget4.Logs.Clear();
When running integration tests, I would like to use a partially randomly generated connection string for the DbContext where the database part of the connection string has a format like AppName_{RandomGuid}. This would allow multiple integration test instances to run at the same time against a real database without anyone stepping on anyone else, as the integration test databases would be created, and destroyed as needed on the central dev database server. The entire connection string would look like Server=tcp:0.0.0.0,49172;Database=AppName_{RandomGuid};User Id=;Password=;TrustServerCertificate=True.
What I have tried is something like this with the WebApplicationFactory:
private static readonly WebApplicationFactory<Program> WebApplication = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
var testConfigurationSettings = new Dictionary<string, string>
{
{ $"Database:ConnectionString", $"Server=tcp:0.0.0.0,49172;Database=AppName_{Guid.NewGuid()};User Id=;Password=;TrustServerCertificate=True" }
};
var testConfiguration = new ConfigurationBuilder()
.AddInMemoryCollection(testConfigurationSettings)
.Build();
builder.UseConfiguration(testConfiguration);
});
This would work expect that in my Program.cs I have this line:
var builder = WebApplication.CreateBuilder(args);
// THIS LINE
builder.Configuration.AddJsonFile(path: $"appsettings.Development.{Environment.MachineName}.json", optional: true);
builder.Services.AddApplicationServices(builder.Configuration);
var app = builder.Build();
await app.RunAsync();
This allows developers (based on their machine name) to override their own settings as they want when running the application locally; importantly, each dev will have their own specific config file that, at a minimum, has a connection string which points to their own dev specific database. My problem with this solution is the line which adds the developer specific config is overriding the config registered in the WithWebHostBuilder for the integration test, because, the Program.cs executes after the WithWebHostBuilder code.
The first part is my actual problem, and the second is my current attempted solution which is so close, and clean to doing exactly what I want.
This is the solution I am using for now that works, but might be considered a bit hacky. If no other solutions come soon, then I'll mark this as the accepted answer.
Basically, I added a field to my appsettings called IsTesting, and by default it is false, but is set to true when running the integration tests:
private static readonly WebApplicationFactory<Program> WebApplication = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
var testConfigurationSettings = new Dictionary<string, string>
{
{ "IsTesting", "true" },
{ $"Database:ConnectionString", $"Server=tcp:0.0.0.0,49172;Database=AppName_{Guid.NewGuid()};User Id=;Password=;TrustServerCertificate=True" }
};
var testConfiguration = new ConfigurationBuilder()
.AddInMemoryCollection(testConfigurationSettings)
.Build();
builder.UseConfiguration(testConfiguration);
});
This way I can check for the setting in my Program.cs and skip applying the developer configuration files if needed that were previously overriding the integration test settings:
var config = configurationManager.Get<ApplicationConfiguration>();
if (config.IsTesting)
{
return;
}
configurationManager.AddJsonFile(path: $"appsettings.Development.{Environment.MachineName}.json", optional: true);
i'm creating a web site using Asp.net core and i want to add some records to some tables at first time i run my website (on host i mean) for example category names or any .
Does any know how can i do this ?
You can use one of the described approaches from this article.
public void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
DataSeeder.SeedCountries(context);
}
host.Run();
}
I'm using Microsoft.Extensions.DependencyInjection in my Project and want to use Microsoft.Extension.Logging, with NLog as the actual Logging Framework.
Since I want to be able to decide the Logging Configuration on Startup programmatically (to get around pathing issues) i'm doing something like this:
var config = new LoggingConfiguration();
var dirPath = Path.Combine(publicStorage, "Test");
var logFileTarget = new NLog.Targets.FileTarget()
{
CreateDirs = true,
FileName = Path.Combine(dirPath, "test.log"),
FileNameKind = NLog.Targets.FilePathKind.Absolute,
Layout = "${date}|${level:uppercase=true}|${message} ${exception}|${logger}|${all-event-properties}",
Name = "FileLog",
};
//Add Logging Targets
config.AddTarget(logFileTarget);
//Add Rules
config.AddRuleForAllLevels(logFileTarget, "*", false);
LogManager.Configuration = config;
But this does not configure those Loggers returned by ServiceProvider.GetService<ILogger<T>>.
If I however use LogManager.GetLogger("TestLogger") The Logging is configured as would be expected.
LogManager.ReconfigExistingLoggers(); doesn't solve the Problem as well and my search came up empty
Edit
The DI Setup looks like this:
return new ServiceCollection()
// Configure Logging Provider
.AddLogging(builder =>
{
builder.AddDebug();
//Add NLog
builder.AddNLog(new NLogProviderOptions()
{
CaptureMessageTemplates = true,
CaptureMessageProperties = true,
});
})
... // Registering additional dependencies
.BuildServiceProvider();
Using LogManager.GetCurrentClassLogger gives the same "incorrect behaviour" as seen with the Loggers returned by serviceProvider. Since
the rule is setup with "*" I would have expected that all loggers including those using class names are caught by this rule
This last sentence is incorrect it is actually working, but the non-appearance of ILogger by di messages still exists
Remember the missing:
builder.SetMinimumLevel(LogLevel.Trace);
See also wiki: https://github.com/NLog/NLog.Extensions.Logging/wiki/Getting-started-with-.NET-Core-2---Console-application#32-setup-the-dependency-injector-di-container
I am looking to see if anyone knows of any method signatures or method overloads that would allow me to remove my preexisting hard coded value for the credentials,
config.StorageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));.
Allowing for a console application to prompt a user for Username and AccessToken which would then be able to be passed back to `CloudStorageAccount.
CloudStorageAccount.Parse should work if you take the user's input and build a connection string.
static void Main(string[] args)
{
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={args[0]};AccountKey={args[1]}";
var storageAccount = CloudStorageAccount.Parse(connectionString);
As it says in project settings, configuration settings can be accessed programmatically and dynamically updated.
You just need to declare a string variable that will contain the default endpoint protocol, your storage account name, and your storage access key and then use CloudStorageAccount.Parse to create a CloudStorageAccount, just like #Stephen McDowell's code.
Thank you Stephen. I used your logic and altered it bit to fit my needs. I have included my code below, thank you again!
UserUI()
public static List<string> UserUI()
{
List<string> accessCredentials = new List<string>();
Console.WriteLine("Account Name: ");
string accountName = Console.ReadLine();
accessCredentials.Add(accountName);
Console.WriteLine("Account Key: ");
string accountKey = Console.ReadLine();
accessCredentials.Add(accountKey);
return accessCredentials;
}
ApplicationStartFromUserOptions()
public static string ApplicatonStartFromUserOptions(List<string> accessCredentials)
{
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={accessCredentials[0]};AccountKey={accessCredentials[1]}";
return connectionString;
}
I then take the connectionString from ApplicationStartFromUserOptions() and pass that into my GetConfig() which handles most of the heavy work.
GetConfig()
public static StartConfig GetConfig(string connectionString)
{
var config = new StartConfig();
// Retrieve storage account from connection string.
config.StorageAccount = CloudStorageAccount.Parse(connectionString);
// Create the blob object.
config.BlobClient = config.StorageAccount.CreateCloudBlobClient();
config.ListContainerData = ListContainer(config);
foreach (var item in config.ListContainerData.Item2)
{
config.Container = config.BlobClient.GetContainerReference(item);
ShowSasTokenForContainer(config);
}
//Create the container if it does not exisit.
config.Container.CreateIfNotExists();
return config;
}
Lastly I am calling this all within my Main() within my console application with StartConfig config = GetConfig(ApplicatonStartFromUserOptions(UserUI()));