I used Template Studio for WinUI to create a new application. I want to stick to the dependency injection approach which is created from the template.
The template provide the following code for App.xaml.cs:
public partial class App : Application {
private static readonly IHost _host = Host
.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
// Services
// ...
services.AddTransient<IFileOpenPickerService, FileOpenPickerService>();
// Core Services
services.AddSingleton<IFileService, FileService>();
// Views and ViewModels
services.AddTransient<SolutionListDetailsViewModel>();
services.AddTransient<SolutionListDetailsPage>();
services.AddTransient<MainViewModel>();
services.AddTransient<MainPage>();
// Configuration
})
.Build();
public static T? GetService<T>()
where T : class {
return _host.Services.GetService(typeof(T)) as T;
}
// ...
}
For a control in the main project the viewmodel creation looks like this in the template:
public sealed partial class SolutionListDetailsPage : Page {
public SolutionListDetailsViewModel ViewModel {
get;
}
public SolutionListDetailsPage() {
ViewModel = App.GetService<SolutionListDetailsViewModel>();
InitializeComponent();
}
//...
}
However, I want to implement a control in a second project, which is referenced and used by the main project, and here the line
ViewModel = App.GetService<MyOwnViewModelWhichIRegisteredAsAService>();
does not work anymore, because I do not have access to App.GetService() in the second project. Furthermore, I cannot let the service provider provide the control and pass the view model in its constructor, since it is created within XAML code. How do I access the service provider to get the view model?
One solution is to create the view model somewhere else and bind it to the DataContext-property. But then I have a different viewmodel-creation-mechanism for the new control than for the old one, which I would like to avoid. (And I am not sure if it works with WinUI's x:Bind.)
If you want to access to the registerd services from other projects, you can use the Ioc class from the CommunityTookit.
var service = Ioc.Default.GetRequiredServices<ISecondProjectClass>();
Also, regarding to several projects use cases, you can also register your services from your second project by creating an extension method:
namespace SecondProject;
public static class SecondProjectHostBuilderExtensions
{
public static IHostBuilder AddSecondProjectServices(this IHostBuilder hostBuilder)
=> hostBuilder.ConfigureServices((context, services) =>
{
_ = services
.AddSingleton<ISecondProjectClass, SecondProjectClass>();
});
}
Call it at App.xaml.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
this.InitializeComponent();
_host = CreateHost();
Ioc.Default.ConfigureServices(_host.Services);
}
private static IHost CreateHost()
{
return Host
.CreateDefaultBuilder()
.AddSecondProjectServices()
.Build();
}
Then if you want to get these services at your second project:
namespace SecondProject;
public class SomeClassAtSecondProject
{
public SomeClassAtSecondProject()
{
var secondProjectClass = Ioc.Default.GetRequiredService<ISecondProjectClass>();
}
}
My current solution is to add a static class in the second project like this:
public static class ServiceProviderWorkaround {
private static Func<Type, object?>? getService;
public static void Init(Func<Type, object?> getter) {
if (getService != null)
throw new InvalidOperationException("Provider already initialized");
getService = getter;
}
internal static T? GetService<T>() where T : class
=> getService == null
? throw new InvalidOperationException("Provider not initialized")
: getService(typeof(T)) as T;
internal static T GetRequiredService<T>() where T : class
=> getService == null
? throw new InvalidOperationException("Provider not initialized")
: (T)getService(typeof(T))!;
}
It is initialized in the constructor of the Application-class:
public App() {
InitializeComponent();
UnhandledException += App_UnhandledException;
ServiceProviderWorkaround.Init(_ => _host.Services.GetService(_));
}
and can be used like this:
var service = ServiceProviderWorkaround.GetService<MyOwnViewModelWhichIRegisteredAsAService>();
Any better solution is still welcome.
Related
I'm working on an ASP.NET Core 6.0 Web API project in C#. I'm relatively new to this and dependency injection. So far I've used DI without much problem but I'm struggling to figure out how to pass an argument to a dependent class that is injected.
I have a set of services (ServiceA and ServiceB), which are inherited from a base class which is also a BackgroundService.
The parent class (BaseService) has a the DataProcessor class as a dependency. This processor class requires a constructor argument which indicates the ID of the service (which is available as a property on the corresponding service class).
My classes:
public abstract class BaseService : BackgroundService
{
public abstract string ID { get; }
protected DataProcessor dp;
protected BaseService(DataProcessor _dp)
{
dp = _dp;
}
}
public class ServiceA : BaseService
{
public override string ID => "SA";
public ServiceA(DataProcessor _dp): base(_dp)
{
}
}
public class ServiceB : BaseService
{
public override string ID => "SB";
public ServiceA(DataProcessor _dp): base(_dp)
{
}
}
public class DataProcessor
{
private string serviceID;
public DataProcessor(string _serviceID)
{
serviceID = _serviceID;
}
}
This is how I configure this on the startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DataProcessor>();
// Add Service A
services.AddSingleton<ServiceA>();
services.AddHostedService<ServiceA>(p =>
{
return (ServiceA)p.GetServices<BaseService>().Where(a => a.GetType() == typeof(ServiceA)).First();
});
// Add Service B
services.AddSingleton<ServiceB>();
services.AddHostedService<ServiceB>(p =>
{
return (ServiceB)p.GetServices<BaseService>().Where(a => a.GetType() == typeof(ServiceB)).First();
});
}
The question is: how do I pass the ID property of the Service class to the DataProcessor class?
Note: I've simplified the code and setup for the purpose of the question.
I have tried by creating an Initialize(string ID) method on the DataProcessor class and calling that within the constructor of my BaseService class. This works but I don't like it as a solution, as I have to manually call this Initialize method.
I've searched a lot and read several similar questions/posts, but really couldn't figure it out.
It does seem like a relatively common request, so might be that I'm not using the DI correctly here or that I have the wrong expectation.
Thank you!
I am using FluentValidator for a current Blazor-Server Project.
Now i need to inject my database service class for validate duplication and stuff like that.
public class StockValidator : AbstractValidator<LagertypModel>
{
private StockOverviewService _stockservice;
public StockValidator(StockOverviewService stockservice)
{
_stockservice = stockservice;
RuleFor(LagertypModel => LagertypModel.Lagertyp).NotEmpty().MaximumLength(4).Must(Lagertyp => {
return _stockservice.validateStockTypeCU(Lagertyp).Result;
});
}
}
But when i do that i get the error that StockValidator needs to have a parameterless contructor.
How can i inject my dependency now?
Where you instantiate your StockValidator class make sure you pass your service into it like so:
Program.cs
builder.Services.AddSingleton<StockOverviewService>();
StockOverviewService.cs
private StockValidator_validator;
public StockOverviewService(LiveConnectionString connectionString)
{
_connectionString = connectionString;
_validator = new StockValidator(this);
}
StockValidator.cs
public class StockValidator : AbstractValidator<LagertypModel>
{
private StockOverviewService _stockservice;
public StockValidator(StockOverviewService stockservice)
{
_stockservice = stockservice;
RuleFor(LagertypModel => LagertypModel.Lagertyp).NotEmpty().MaximumLength(4).Must(Lagertyp => {
return _stockservice.validateStockTypeCU(Lagertyp).Result;
});
}
}
Assuming your StockOverviewService uses async methods for accessing a DB/Api, I don't recommend this approach because you're making the code run synchronously here:
return _stockservice.validateStockTypeCU(Lagertyp).Result;
but it will work.
Here is what I have so far. I am trying to create a new ThemeManagementViewModel and inject into that a resource service using:
Microsoft.Extensions.DependencyInjection version 5.0.1 nuget package
public static class Startup
{
public static IServiceProvider ServiceProvider { get; set; }
public static IServiceProvider Init()
{
var serviceProvider = new ServiceCollection().ConfigureServices()
.BuildServiceProvider();
ServiceProvider = serviceProvider;
return serviceProvider;
}
}
public static class DependencyInjectionContainer
{
public static IServiceCollection ConfigureServices(this IServiceCollection services)
{
services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddSingleton<IResourceService, ResourceService>();
services.AddTransient<ThemeManagementViewModel>();
return services;
}
}
public partial class ThemeManagementViewModel : BaseViewModel
{
private readonly IResourceService _resourceService;
public ThemeManagementViewModel(IResourceService resourceService)
{
_resourceService = resourceService;
}
}
public partial class ResourceService : IResourceService
{
private IDatabaseService _databaseService;
public ResourceService(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
}
public interface IResourceService
{
void SetResourceColors();
}
public class ThemeManagementPage : HeadingView
{
private readonly ThemeManagementViewModel _vm;
public ThemeManagementPage()
{
BindingContext = _vm = new ThemeManagementViewModel();
}
}
When I build my application it gives me a message for this line:
BindingContext = _vm = new ThemeManagementViewModel();
and this is the message that I am getting.
There is no argument given that corresponds to the required
formal parameter 'resourceService' of
'ThemeManagementViewModel.ThemeManagementViewModel(IResourceService)'
I thought that the DI was supposed to insert the service into the constructor of ThemeManagementViewModel but it seems not to be working.
Dependency injection will not simply take place anywhere where you construct an object. You need to go explicitly through your DI framework.
In this case, you need to call GetRequiredService() of your IServiceProvider object.
var _vm = Startup.ServiceProvider.GetRequiredService<ThemeManagementViewModel>();
Also, from your code, we don't see that you use your DependencyInjectionContainer class at all. You must make sure that your ConfigureServices method is called explicitly.
DI cannot do magic. The compiler doesn't know anything about it. You have to use it explicitly. It looks like it could do magic in the context of ASP.net website projects. But that is only because there it is the ASP.net framework that handles the things that you need to do explicitly in other types of projects.
Tutorial on how to use DI in .net applications
I have Login window and a Home window, inside the login class the Home is opened and the Login is closed:
Home home = new Home(user, _gitApiService);
home.Show();
Close();
Because the Home class relies on a dependency of IGitApiService, I am passing the dependency via the window class's constructor:
public partial class Home : Window
{
private readonly IGitApiService _gitApiService;
public Home(User user, IGitApiService gitApiService)
{
_gitApiService = gitApiService;
...etc
This seems like bad practice to me, is there any cleaner way of accessing/instaniating the IGitApiService?
(For context the GitApiService is just a class with api calls using HttpClient)
Assuming that there are only a few dependencies then such poor man's/pure DI isn't something really bad.
But if it is a common scenario and there are many dependencies, then by all means register a factory for the Home page (as user seems to be some domain object that can't be registered in CompositionRoot):
services.Register<Func<User, Home>>(context =>
user => new Home(user, context.Resolve<IGitApiService>());
or however explicitly or implicitly it is done in the DI framework used in the application.
Slight design change to Home window
public partial class Home : Window {
private readonly IGitApiService _gitApiService;
public Home(IGitApiService gitApiService) {
_gitApiService = gitApiService;
}
public User User { get; set; }
//...
}
I would have a window service responsible for showing a desired window
public interface IWindowService {
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window;
}
public class WindowService : IWindowService {
private readonly IServiceProvider services;
public WindowService(IServiceProvider services) {
this.services = services
}
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window {
var window = services.GetService<TWindow>();
if(configure != null) {
configure(window);
}
window.Show();
}
}
With that in place you inject your window service and use it like
windowSevie.Show<Home>(window => window.User = user);
Close();
Any explicit dependencies are injected when the window is resolved, and the configure delegate allows flexibility to populate any other members as needed
I'm working on integrating a legacy database with Asp.Net Zero. I created the model classes using EntityFramework Reverse POCO Generator in a separate Models class library project. I also reversed engineered the DbContext into a separate Data class library project. I would like to use the Data Onion framework for my repositories and unit of work. When I use the recommended IOC container Autofaq my Test Winform application works correctly.
However, the Web Project utilizes Castle.Windsor. I'm uncertain on how to do the wire-up.
I'm creating a new container called ClientDesktopContainer:
internal class ClientDesktopContainer : WindsorContainer
{
public ClientDesktopContainer()
{
RegisterComponents();
}
private void RegisterComponents()
{
var connectionstring = ConfigurationManager.ConnectionStrings["MyDbContext"].ConnectionString;
// Data Onion
Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(new DbContextConfig(connectionstring, typeof(MyDbContext), new MigrateToLatestVersion(new Seeder())));
Component.For<IDbContextScope>().ImplementedBy<DbContextScope>();
Component.For<IDbContextScopeFactory>().ImplementedBy<DbContextScopeFactory>();
Component.For<IAmbientDbContextLocator>().ImplementedBy<AmbientDbContextLocator>();
Component.For<IDbContextReadOnlyScope>().ImplementedBy<DbContextReadOnlyScope>();
// Data Onion Unit of Work
Component.For<IRepositoryLocator>().ImplementedBy<RepositoryLocator>();
// Component.For<IRepositoryResolver>().ImplementedBy<CastleWindsorRepositoryResolver>();
Component.For<IUnitOfWorkFactory>().ImplementedBy<UnitOfWorkFactory>();
Component.For<IUnitOfWork>().ImplementedBy<UnitOfWork>();
Component.For<IReadOnlyUnitOfWork>().ImplementedBy<IReadOnlyUnitOfWork>();
// Custom
Component.For<IRepository<Enrollment>>()
.ImplementedBy<BaseRepository<Enrollment, MyDbContext>>();
}
My application invocation code is Program:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
IoC.Initialize(new ClientDesktopContainer());
var dbContextScopeFactor = IoC.Resolve<IDbContextScopeFactory>();
using (var dbReadOnly = dbContextScopeFactor.CreateReadOnly())
{
var context = dbReadOnly.DbContexts.Get<MyDbContext>();
var individuals = context.Enrollments.ToList();
foreach (var individual in individuals)
{
// do stuff
}
}
Application.Run(new ViewMain());
}
}
I created a static IOC:
public static class IoC
{
private static IWindsorContainer _container;
public static void Initialize(IWindsorContainer container)
{
_container = container;
}
public static T Resolve<T>()
{
try
{
return _container.Resolve<T>();
}
catch
{
throw;
}
}
}
The Data Onion documentation mentions registering a custom Resolver for IRepositoryResolver.
I created a CastleWindsorRepositoryResolver:
public class CastleWindsorRepositoryResolver : IRepositoryResolver
{
public IRepository<TEntity> Resolve<TEntity>() where TEntity : class
{
// TODO: Resolve wire-up goes here
throw new System.NotImplementedException();
}
}
I'm receiving a ComponentNotFoundExpection:
Updated to fix constructor parameter for DbContextFactory (to RegisterComponents method):
var dbContextConfig = new DbContextConfig[]
{
new DbContextConfig(
connectionString,
typeof(MyDbContext),
new MigrateToLatestVersion(new Seeder())
)
};
// Data Onion
Register(Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(Dependency.OnValue<DbContextConfig[]>(dbContextConfig)));
Add call to Register in:
internal class ClientDesktopContainer : WindsorContainer
{
public ClientDesktopContainer()
{
RegisterComponents();
}
private void RegisterComponents()
{
var connectionstring = ConfigurationManager.ConnectionStrings["MyDbContext"].ConnectionString;
/* HERE CALL TO REGISTER: */
this.Register(
// Data Onion
Component.For<IDbContextFactory>().ImplementedBy<DbContextFactory>()
.DependsOn(new DbContextConfig(connectionstring, typeof(MyDbContext), new MigrateToLatestVersion(new Seeder()))),
Component.For<IDbContextScope>().ImplementedBy<DbContextScope>(),
Component.For<IDbContextScopeFactory>().ImplementedBy<DbContextScopeFactory>(),
Component.For<IAmbientDbContextLocator>().ImplementedBy<AmbientDbContextLocator>(),
Component.For<IDbContextReadOnlyScope>().ImplementedBy<DbContextReadOnlyScope>(),
// Data Onion Unit of Work
Component.For<IRepositoryLocator>().ImplementedBy<RepositoryLocator>(),
// Component.For<IRepositoryResolver>().ImplementedBy<CastleWindsorRepositoryResolver>(),
Component.For<IUnitOfWorkFactory>().ImplementedBy<UnitOfWorkFactory>(),
Component.For<IUnitOfWork>().ImplementedBy<UnitOfWork>(),
Component.For<IReadOnlyUnitOfWork>().ImplementedBy<IReadOnlyUnitOfWork>(),
// Custom
Component.For<IRepository<Enrollment>>()
.ImplementedBy<BaseRepository<Enrollment, MyDbContext>>() );
}
Without Register you are just creating registration object without actually putting types in container. Another thing that may help, by default Castle will register components as singletons add LifestyleTranscient or PerWebRequest to your UnitOfWork registrations.