adding profiles to automapper after initialisation - c#

having some trouble with adding profiles after some external assembly already added mapperconfiguration on the DI-setup.
first i just added some code to add the profiles
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<DataMappingProfile>();
});
mapperConfiguration.AssertConfigurationIsValid();
services.AddSingleton<IMapper>(new Mapper(mapperConfiguration));
but then i overwrote some other mappingprofiles.
so i was thinking, i should try to add mine to the existing mappingconfiguration.
so i was going this way
var sp = services.BuildServiceProvider();
var autoMapper = sp.GetService<IMapper>();
var mapperConfiguration = autoMapper?.ConfigurationProvider as MapperConfiguration;
var configuration = new MapperConfigurationExpression();
configuration.AddProfile<LpisMappingProfile>();
if (mapperConfiguration == null)
{
mapperConfiguration = new MapperConfiguration(configuration);
}
else
{
//add the previous as well
//?? add this `configuration` ?
}
mapperConfiguration.AssertConfigurationIsValid();
services.AddSingleton<IMapper>(new Mapper(mapperConfiguration));
but i am a little stuck on the else flow.
Any advice?
thnx!

That's not possible. If an assembly uses a private MapperConfiguration, then that's its own business. If it wants to collaborate with the app, it should not define a MapperConfiguration, it should define profiles to be scanned by the app and added to the singleton MapperConfiguration owned by the app. The AM configuration is read-only, after the init phase it's not possible to change it.

Related

Access ASP.NET Core settings during service configuration when using minimal api

I am trying to access my settings in order to configure my services. I am using AWS systems manager to store my settings
// Settings class
public class Settings
{
public string Url { get; set; }
}
// my Program.cs code
var builder = WebApplication.CreateBuilder();
// Clear default sources
builder.Configuration.Sources.Clear();
// Get configuration
builder.Configuration.AddSystemsManager(source =>
{
source.Path = $"/{env}";
source.ParameterProcessor = new DefaultParameterProcessor();
});
// Load up the settings
builder.Services.Configure<Settings>(builder.Configuration.GetSection(appName));
// Access the settings to use for configuring the services
var settings = builder.Configuration.Get<Settings>();
When I check the settings object, the values are null. However when I inject the settings into an endpoint using IOptions<Setting>, the values are populated.
I tried the .Get<Settings> after I called Build();, but that also returned null values. Is there something I am missing?
You said you want to access the settings to configure some services. You can accomplish that with something like:
builder.Services.AddSingleton<IMyService>(x => {
// x is IServiceProvider
var settings = x.GetRequiredService<IOptions<Settings>>();
var url = settings.Value.Url;
if(url is null)
throw new Exception("missing url");
var myservice = new MyService(url);
// this is the instance that will be used, use singleton/scoped/transient as needed
return myservice;
});
where MyService is the service you want to configure, if not needed you can avoid the IMyService interface and of course you need to use the desired lifetime (singleton/scoped/transient)
Doing so though will somehow force you to explicitly declare the dependencies of "MyService".
Another way of fetching that value is:
// get the configuration section
var section = builder.Configuration.GetSection("appname:Settings");
// create the POCO
var settings = new Settings();
// bind the values to the POCO
section.Bind(settings);
// you can access the value
var yourUrl = settings.Url;
// you can still inject it as IOption<>
builder.Services.Configure<Settings>(section);
I am assuming you have an "appname" section with a "Settings" section in your configuration, if not then use .GetSection("appname"); instead of .GetSection("appname:Settings");
According to the [docs], you should be able to retrieve your options using extension method:
builder.Configuration.GetAWSOptions()
As an alternative, if you have those Settings options in appsettings.json you should be able to retrieve those using: builder.Configuration["Settings:Url"]
Also, as you have used builder.Configuration.GetSection(appName) doesn't it work for you when you try to retrieve options using:
builder.Configuration.GetSection("Settings")?

Alter DbContext connection string for integration tests

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);

Is it possible to programmatically configure NLog when used with Microsoft.Extensions.DependencyInjection?

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

IWebHostBuilder.GetSetting() doesnt have appsettings.json data

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");!

Retrieving SQL Generated by Entity Framework Core

I'm trying to retrieve the raw SQL generated by Entity Framework for the following LINQ query:
pagedItemResults = from firstItem in dbData.Accession
join secondItem in pagedRowNumberResults
on firstItem.AccessionNumber equals secondItem
select new PaginationResultRow
{
Number = firstItem.AccessionNumber,
ID = firstItem.AccessionId,
Name = firstItem.AcquisitionType.Name,
Description = firstItem.Description
};
Although it may be extremely simple and similar to the other answers already out there for previous versions of EF, I've had no luck and found nothing online.. any ideas??
You can turn on logging by implementing ILoggerProvider. See details in documentation.
You only need to register the logger with a single context instance. Once you have registered it, it will be used for all other instances of the context in the same AppDomain.
using (var db = new BloggingContext())
{
var serviceProvider = db.GetInfrastructure<IServiceProvider>();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddProvider(new MyLoggerProvider());
}
You can also define categories what you want to log.
private static string[] _categories =
{
typeof(Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory).FullName,
typeof(Microsoft.Data.Entity.Storage.Internal.SqlServerConnection).FullName
};
You can log tsql generated to output window by :
Microsoft.Extensions.Logging.Debug
First, get it from Nuget, then in your context, you must define a LoggerFactory.
After that, use it in OnConfiguring in your context.
public static readonly Microsoft.Extensions.Logging.LoggerFactory _loggerFactory =
new LoggerFactory(new[] {
new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider()
});
optionsBuilder.UseLoggerFactory(_loggerFactory);
I really like MiniProfiler, see http://miniprofiler.com/. Short of something like this, I would say you'd have to use a profiler on the actual database.

Categories