How to inject DI Services in a WPF ViewModel? - c#

I'm implementing a DI container in a .NET Core WPF app.
The below setup works fine, but I don't know how to inject an interface in any of my view model's constructor.
In the MainWindow I want to instantiate two or more pages with their respective dependencies (side menu item click). How do I get the dependency container in order to resolve those dependencies?
public partial class App : Application
{
private readonly ServiceProvider _serviceProvider;
private IConfiguration _configuration { get; set; }
public App()
{
InitializeConfiguration();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
_serviceProvider = serviceCollection.BuildServiceProvider();
}
private void InitializeConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
_configuration = builder.Build();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IConfiguration>(s => _configuration);
services.AddTransient<IRepository, Repository>();
services.AddTransient<IDeliveryService, DeliveryService>();
services.AddSingleton<MainWindow>();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = _serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
}
Many thanks!

Related

How to Get An IConfiguration For Dependency Injection In A Console App

I have some code from a Asp.Net Core 5 web app that I want to call from a console application.
It's coming together but I have one issue that I have not yet found a solution to. Given the console main code:
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
serviceProvider.GetService<CodeGen>().Generate();
}
private static void ConfigureServices(IServiceCollection serviceCollection)
{
var configurationRoot = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", false)
.Build();
serviceCollection.AddOptions();
serviceCollection.Configure<AppSettings>(configurationRoot.GetSection("Configuration"));
// add services
serviceCollection.AddScoped<IDataRepo, DataRepo>()
.AddScoped<CodeGen>();
}
}
And the class that is doing the work:
public class CodeGen
{
private readonly IDataRepo _dataRepo;
private readonly AppSettings _appSettings;
public CodeGen(IDataRepo dataRepo, AppSettings appSettings)
{
_dataRepo = dataRepo;
_appSettings = appSettings;
}
public void Generate()
{
Console.WriteLine("Generates code... ");
CodeGenWordRules.Init(_appSettings.OutputFolder, _dataRepo);
}
}
The problem is that the DataRepo constructor has a dependency on IConfiguration.
public DataRepo(IConfiguration configuration, ICodeGenManager codeGenManager)
How do I get the an IConfiguration from the IConfigurationRoot that I can add to the serviceCollection so that the DataRepo will have it's dependency?
Try to add IConfiguration this way
private static void ConfigureServices(IServiceCollection services)
{
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", false)
.Build();
services.AddSingleton<IConfiguration>(configuration);
.... another DI
}

How do I use a SimpleInjector container with HostBuilder?

I'm building .NetCore console app which uses SimpleRabbit (RabbitMQ).
I am attempting to use HostBuilder() to this purpose (dependecy injection, configuration, services).
I create a SimpleInjector container in the Bootstrap class.
I would like to inject this container for use into HostBuilder.
As you can see below, the FooSubscriberService constructor is expecting a container.
I see HostBuilder exposes a ConfigureContainer() method which sounds like what I'm looking for.
I've looked around but I'm unclear as how to use this/inject the container.
Supporting Class Snippets
public class FooSubscriberService : IFooSubscriberService
{
Container container;
private readonly ILogger<FooSubscriberService> logger;
public FooSubscriberService(Container container, ILogger<DiscoverySubscriberService> logger)
{
this.container = container;
this.logger = logger;
}
....
}
public class QueueManagementService : BasicRabbitService, IQueueManagementService
{
ILogger _logger;
public QueueManagementService(RabbitConfiguration config, ILogger<QueueManagementService> _logger) : base(config)
{
this._logger = _logger;
}
....
}
Container Creation
public class Bootstrap
{
public static Container container;
public static void ConfigureServices(IConfigurationRoot configurationRoot)
{
container = new SimpleInjector.Container();
container.Options.ResolveUnregisteredConcreteTypes = false;
container.Options.EnableAutoVerification = false;
RabbitConfiguration rabbitConfiguration = new RabbitConfiguration();
configurationRoot.GetSection("RabbitConfiguration").Bind(rabbitConfiguration);
var runtimeModel = new RuntimeConfigurationModel();
var runtimeStatus = new RuntimeServicesStatus();
container.ConfigureServices(rabbitConfiguration, runtimeModel, runtimeStatus, searchJobStatus);
}
}
Code Snippet for initializing and running HostBuilder.
Bootstrap.ConfigureServices(configurationRoot);
var container = Bootstrap.container;
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
})
.ConfigureServices((context, services) =>
{
var config = context.Configuration;
services
.AddRabbitConfiguration(configurationRoot.GetSection("RabbitConfiguration"))
.AddSubscriberConfiguration(configurationRoot.GetSection("Subscribers"))
.AddPublisherServices()
.AddSubscriberServices()
.AddSingletonMessageHandler<FooSubscriberService>()
.AddSingleton<IQueueManagementService, QueueManagementService>()
.AddSingleton<IHostedService, FooConsoleService>();
});
// ??? .ConfigureContainer<Container>( => { }); ???
await builder.RunConsoleAsync();
What is the method for injecting my Container into HostBuilder()?
Are there additional NuGet packages I need to use?

.NET Core WPF DI'd Named OptionsMonitor always null

I have a .NET Core 3.1 WPF application, and I'm attempting to inject configuration values into a service in a lower layer (Infrastructure) through an IOptionsMonitor<T> using Named Options. Values are always populated as null.
Here's the file contents:
appsettings.json
{
"Repositories": {
"Repo1": {
"BaseUrl": "default1",
"Fragment1": "defaultFragment1"
},
"Repo2": {
"BaseUrl": "default2",
"Fragment1": "defaultFragment2"
}
}
}
appsettings.development.json
{
"Repositories": {
"Repo1": {
"BaseUrl": "https://developmentRepo1.com",
"Fragment1": "developmentFragment1"
},
"Repo2": {
"BaseUrl": "https://developmentRepo2.com",
"Fragment1": "developmentFragment2"
}
}
}
I have an Environment Variable which is DOTNET_ENVIRONMENT and it's set to development.
On the main App.xaml.cs I have this:
App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
var builder = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.development.json", optional: true, reloadOnChange: true);
Configuration = builder.Build();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
ServiceProvider = serviceCollection.BuildServiceProvider();
var mainWindow = ServiceProvider.GetRequiredService<PricingWizard>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<RepositoryConfiguration>(
RepositoryConfiguration.Repo1Config,
Configuration.GetSection("Repositories:Repo1"));
services.AddScoped<Infrastructure.SomeContext>();
}
My configuration Class looks something like this:
RepositoryConfiguration.cs:
public class RepositoryConfiguration
{
public const string Repo1Config = "Repo1Config";
public string BaseUrl { get; set; }
public string Fragment1 { get; set; }
}
And the service in the layer below:
SomeContext.cs:
public class SomeContext : IDisposable
{
private readonly RepositoryConfiguration configuration;
public SomeContext(IOptionsMonitor<RepositoryConfiguration> configuration)
{
this.configuration = configuration.Get(RepositoryConfiguration.Repo1Config);
}
public void SomeMethod()
{
var url = configuration.BaseUrl;
}
}
For some reason, both BaseUrl and Fragment1 are always null.
Please check the appsettings files, make sure you are using the correct file and it contains the related value, and check the code where you call the SomeMethod() method.
According to your code, I have tested your code on my application, it seems that everything works well. Code as below:
Code in the SomeContext.cs:
public class SomeContext : IDisposable
{
private readonly RepositoryConfiguration configuration;
public SomeContext(IOptionsMonitor<RepositoryConfiguration> configuration)
{
this.configuration = configuration.Get(RepositoryConfiguration.Repo1Config);
}
public void Dispose()
{
throw new NotImplementedException();
}
public string SomeMethod()
{
var url = configuration.BaseUrl;
return url;
}
}
Code in the App.xaml.cs:
public partial class App : Application
{
public IServiceProvider ServiceProvider { get; private set; }
public IConfiguration Configuration { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
// Create a service collection and configure our dependencies
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
// Build the our IServiceProvider and set our static reference to it
ServiceProvider = serviceCollection.BuildServiceProvider();
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
services.AddScoped<ISampleService, SampleService>();
services.Configure<RepositoryConfiguration>(RepositoryConfiguration.Repo1Config, Configuration.GetSection("Repositories:Repo1"));
services.AddScoped<SomeContext>();
services.AddTransient(typeof(MainWindow));
}
}
Code in the MainWindow.xaml.cs
public partial class MainWindow : Window
{
private readonly ISampleService sampleService;
private readonly AppSettings settings;
private readonly SomeContext someContext;
public MainWindow(ISampleService sampleService, IOptions<AppSettings> settings, SomeContext context)
{
InitializeComponent();
this.sampleService = sampleService;
this.settings = settings.Value;
this.someContext = context;
lblBaseUrl.Content = context.SomeMethod();
}
private void ButtonExit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
The appsettings.json content as below:
{
"AppSettings": {
"StringSetting": "Value",
"IntegerSetting": 42,
"BooleanSetting": true
},
"Repositories": {
"Repo1": {
"BaseUrl": "https://developmentRepo1.com",
"Fragment1": "developmentFragment1"
},
"Repo2": {
"BaseUrl": "https://developmentRepo2.com",
"Fragment1": "developmentFragment2"
}
}
}
It seems that the code works well on my side, screenshot as below:
Here is an article about Using .NET Core 3.0 Dependency Injection and Service Provider with WPF, you could check it.

ConfigurationProvider with other dependencies

I've implemented my customs IConfigurationProvider and IConfigurationSource.
public class MyConfigurationSource : IConfigurationSource
{
public string Foo { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MyConfigurationProvider(this);
}
}
internal class MyConfigurationProvider : ConfigurationProvider
{
public MyConfigurationSource Source { get; };
public MyConfigurationProvider()
{
Source = source
}
public override void Load()
{
// I'd like to assign here my configuration data by using some dependencies
Data = ....
}
}
I do the build of my Configuration in the Startup constructor (I override the configuration created by CreateDefaultBuilder):
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.AddMyConfiguration("myfoovalue")
.Build();
Extension method:
public static IConfigurationBuilder AddMyConfiguration(this IConfigurationBuilder builder, string foo)
{
return builder.Add(new MyConfigurationSource
{
Foo = url
});
}
I wish I could somehow inject services to be used in Load method. The problem here is that the configuration build is done in the Startup constructor. I can only inject dependencies that I have available in this constructor: IWebHostEnvironment, IHostEnvironment, IConfiguration and all I added when I built the WebHost. Also these dependencies would have to be passed the moment I call the AddMyConfiguration extension method. How could I use dependencies that don't even exist at that moment?
A bit late answer.
It's obvious there's no way to use the container, that was built using Startup.ConfigureServices, in the MyConfigurationSource/MyConfigurationProvider simply because by the time ConfigurationBuilder.Build is invoked, ServiceCollection.BuildServiceProvider has not been invoked.
A typical workaround would be to create another instance of IServiceProvider with required configuration and use it inside MyConfiguration....
Something like
internal class MyConfigurationProvider : ConfigurationProvider
{
public MyConfigurationSource Source { get; };
public MyConfigurationProvider()
{
Source = source;
ServiceProvider = BuildServiceProvider();
}
public override void Load()
{
// I'd like to assign here my configuration data by using some dependencies
Data = ServiceProvider.GetRequiredService<IMyService>().GetData();
}
protected virtual void ConfigureServices(IServiceCollection services)
{
services.AddMyService();
// etc.
}
private IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
ConfigureServices(services);
return services.BuildServiceProvider();
}
}
but this might not always be appropriate so I would also consider setting the value directly (though I didn't find any official information about how good this approach is)
public class Startup
{
public void Configure(IApplicationBuilder app)
{
...
app.SetupMyConfiguration();
...
}
}
...
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder SetupMyConfiguration(this IApplicationBuilder app)
{
var configuration = app
.ApplicationServices
.GetRequiredService<IConfiguration>(); // alternatively IOptions<MyOptions>
var myService = app
.ApplicationServices
.GetRequiredService<IMyService>();
configuration["MyKey"] = myService.GetData("MyKey");
}
}
UPD.
There's also an alternative with using strongly typed options object and IConfigureOptions.
public class MyConfigurationBuilder : IConfigureOptions<MyConfiguration>
{
private readonly IConfiguration _configuration;
private readonly IMyService _service;
public MyConfigurationBuilder(
IConfiguration configuration,
IMyService service)
{
_configuration = configuration;
_service = service;
}
public void Configure(MyConfiguration myConfiguration)
{
// you may set static configuration values
_configuration
.GetSection(nameof(MyConfiguration))
.Bind(myConfiguration);
// or from DI
myConfiguration.Data = _service.GetData();
// here we still can update IConfiguration,
// though it doesn't seem to be a good idea
_configuration["MyKey"] = _service.GetData("MyKey");
}
}
services.AddSingleton<IConfigureOptions<MyConfiguration>, MyConfigurationBuilder>();
or inject dependencies directly into MyConfiguration
services.Configure<MyConfiguration>(
serviceProvider =>
ActivatorUtilities.CreateInstance<MyConfiguration>(serviceProvider, "staticConfigValue"));
public class MyConfiguration
{
public MyConfiguration(string staticValue, IMyService service)
{
...
}
}
public class Service
{
public Service(IOptions<MyConfiguration> options) {}
}

WPF Core with Autofac dependency injection - migrations error

I'm trying to get Autofac dependency injection for ef core in a WPF Core 3.1 app but I cannot do any migrations.
I keep getting this error when trying to add migrations through CLI:
Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Any suggestion?
Those are my methods:
Startup Class:
public partial class App : Application
{
public IServiceProvider ServiceProvider { get; private set; }
private void App_OnStartup(object sender, StartupEventArgs e)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient(typeof(MainWindow));
ServiceProvider = serviceCollection.BuildServiceProvider();
var bootstrapper = new Bootstrapper(ServiceProvider);
var container = bootstrapper.Bootstrap();
var mainWindow = container.Resolve<MainWindow>();
//var mainWindow = new MainWindow(
// new RecruitmentViewModel(new ContractorDataService()));
mainWindow.Show();
}
}
Bootstrapper which acts like a start-up
private readonly IServiceProvider _serviceProvider;
public Bootstrapper(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
var config = new ConfigurationBuilder();
config.AddJsonFile("appsettings.json");
var configuration = config.Build();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<RecruitmentViewModel>().AsSelf();
builder.RegisterType<ContractorDataService>().As<IContractorDataService>();
RegisterContext<ApplicationDbContext>(builder, configuration);
return builder.Build();
}
public void RegisterContext<TContext>(ContainerBuilder builder, IConfiguration configuration)
where TContext : DbContext
{
builder.Register(componentContext =>
{
var dbContextOptions = new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>());
var optionsBuilder = new DbContextOptionsBuilder<TContext>(dbContextOptions)
.UseApplicationServiceProvider(_serviceProvider)
.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
serverOptions => serverOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null));
return optionsBuilder.Options;
}).As<DbContextOptions<TContext>>()
.InstancePerLifetimeScope();
builder.Register(context => context.Resolve<DbContextOptions<TContext>>())
.As<DbContextOptions>()
.InstancePerLifetimeScope();
builder.RegisterType<TContext>()
.AsSelf()
.InstancePerLifetimeScope();
}

Categories

Resources