How to use IServiceProvider inside IServiceCollection.Configure() - asp.net-core

Is it possible to use IServiceProvider inside IServiceCollection.Configure()?
I see no overload on Configure() to accept something like Func<IServiceProvider, T>. Other extension methods, like IServiceCollection.AddScoped() have an overload which accepts Func<IServiceProvider, T>.
I would like to do something like this:
public static void AddCommandHandlers(this IServiceCollection services, Assembly assembly)
{
// CommandExecutor has a dependency on CommandHandlerFactory
services.AddScoped<CommandExecutor>();
services.Configure<CommandHandlerFactory>(myFactory =>
{
// How can I use IServiceProvider here? Example scenario:
foreach(Type t in FindHandlers(assembly))
myFactory.AddHandler(serviceProvider => serviceProvider.GetService(t));
});
}
The goal is to be able to call AddCommandHandlers extension method multiple times, for different assemblies, and append found handlers (using DI) to the same CommandHandlerFactory, so that CommandExecutor can just call the factory to obtain a handler.
Or maybe there is another way?
Any help appreciated.
Thanks.

You can call the BuildServiceProvider() extension method on the IServiceCollection at any time to build a ServiceProvider. You'll need
Microsoft.Extensions.DependencyInjection
It will obviously only include any services which have already been added to the collection so you'll need to call things in the correct order.
IServiceProvider sp = services.BuildServiceProvider();

You could register each command handler against a common interface:
foreach(Type t in FindHandlers(assembly))
services.AddScoped<ICommandHandler>(t);
Then you could make the factory accept IEnumerable<ICommandHandler> as a constructor parameter.
public class CommandHandlerFactory
{
public CommandHandlerFactory(IEnumerable<ICommandHandler> handlers)
{
foreach(var handler in handlers)
AddHandler(handler);
}
// The rest of the factory
}
Or, if you can't change the constructor, you could setup the factory like this:
services.AddSingleton(serviceProvider =>
{
var factory = new CommandHandlerFactory();
foreach(var handler in serviceProvider.GetServices<ICommandHandler>();
factory.AddHandler(handler);
return factory;
});

Related

Registering a BackgroundService from a Type

Is there any way to register a background service from a type?
With the code below, if I remove the cast to BackgroundService, the code won't compile.
As it stands below, then I only get a single one of the services from the array registered.
If I change it to IHostedService, then I receive this exception.
System.ArgumentException: Implementation type cannot be 'Microsoft.Extensions.Hosting.IHostedService' because it is indistinguishable from other services registered for 'Microsoft.Extensions.Hosting.IHostedService'. (Parameter 'descriptor')
My code:
.ConfigureServices((hostContext, services) =>
{
Type[] backgroundServices = GetBackgroundServices(assemblies).ToArray();
foreach (var #type in backgroundServices)
services.AddHostedService((ihs) => (BackgroundService)ihs.GetRequiredService(#type));
})
Thanks very much
The extension AddHostedService simply does this:
services.AddTransient<IHostedService, THostedService>();
Where THostedService is a class that implements IHostedService.
So, if you wanted to inject a hosted service by type, you would simply do:
services.AddTransient(typeof(IHostedService), typeof(MyBackgroundService));
MyBackgroundService in this case would need to be set up as either this:
public class MyBackgroundService : IHostedService { ... }
Or this:
public class MyBackgroundService : BackgroundService { ... }
BackgroundService implements IHostedService... so both of theses cases would work with the above method.

How to replace a dependency in ASP.NET core?

In the startup.cs we have this:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
}
And so our Index Razor page can have this:
public IndexModel(IStringLocalizer<Strings> localizer) {
I want to wrap the localizer in another class and replace it's presence in the IoC container with a singleton of the wrapper.
The problem is that it does not appear possible to retrieve or remove entries from the container while in the COnfigureServices method.
Essentially, I want to replace the registered instance provided by the AddLocalization call so that I don't need to replace every instance of the wrapper class's injection in the solution.
Is this possible?
You can solve this problem with Decorator pattern.
First of all configure the services so that you can access the StringLocalizer<> directly. This is for the MyLocalizer<> class, because it needs a direct instance of StringLocalizer<> type, not the interface (IStringLocalizer<>). If you don't register it MyLocalizer<> would not get resolved.
services.AddTransient(typeof(StringLocalizer<>));
Then register the decorator (the dependency that you want to be replaced). Note that I assumed AddLocalization() is called before this line of code. This is the rule of the DI container; it always resolves the last registered type. So, after this line of code all dependents of IStringLocalizer<> will get MyLocalizer<> instead of the standard StringLocalizer<>.
// be careful about using Singleton scope
services.AddSingleton(typeof(IStringLocalizer<>), typeof(MyLocalizer<>));
Decorator Implementation:
Decorator pattern allows you to add extra features to an existing object. Suppose the IStringLocalizer<T> object returns a simple string that I need to just make it upper-case.
public class MyLocalizer<T> : IStringLocalizer<T>
{
public MyLocalizer(StringLocalizer<T> original)
{
_original = original;
}
private readonly StringLocalizer<T> _original;
// the decorator behavior is the same for all other methods.
// But for this particular method it adds a little feature to the original one! Beautiful :)
public LocalizedString this[string name] =>
new LocalizedString(name, _original[name].Value.ToUpper());
public LocalizedString this[string name, params object[] arguments] =>
_original[name, arguments];
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
_original.GetAllStrings(includeParentCultures);
public IStringLocalizer WithCulture(CultureInfo culture) =>
_original.WithCulture(culture);
}
Now, nothing in your dependent classes will change. They just use MyLocalizer<T> instead of MVC's StingLocalizer<T>.
Wish that helps!

Property Injection in Microsoft.Extensions.DependencyInjection

I am relatively new to working with DI containers and have hit a bit of a roadblock.
SimpleInjector has a method with the following signature:
Container.RegisterInitializer<TService>(Action<TService>)
In our code base we do use it like this:
// this is a property injection of the abstract file system
container.RegisterInitializer<IFileSystemInjection>(
fs => fs.FileSystem = new FileSystem());
I am wondering how I would achieve the same using the IServiceCollection parameter in the ConfigureServices method in the Startup.cs class. So far I have been able to register all my types using the services.AddTransient(); but I am not sure how what the equivalent simpleinjector.RegisterInitializer is within the IServiceCollection.
You'd use the factory overload(s) of AddSingleton, AddScoped, and AddTransient. I'm not sure what scope IFileSystemInjection should be in, but it sounds like something that could be a singleton. If not, change the method you call appropriately:
service.AddSingleton<IFileSystemInjection>(p =>
{
var fs = new FileSystemInjection();
fs.FileSystem = new FileSystem();
});
In short, if you provide a factory, then you're responsible for the entire object initialization, hence the new FileSystemInjection(), which I'm subbing as the actual implementation of IFileSystemInjection your using.
If that implementation has dependencies that need to be injected in order to create it, you can pull those from p, which is an instance of IServiceProvider:
var myDep = p.GetRequiredService<MyDep>();
var fs = new FileSystemImplementation(myDep);
You can use this nuget package, that extends standard Microsoft Dependency Injection and adds property injection:
https://www.nuget.org/packages/DJMJ.Extensions.DependencyInjection.Property/1.1.0
Mark property for injection
using Microsoft.Extensions.DependencyInjection;
public class FooService
{
[Inject]
public IBooService BooService { get; set; }
public void Foo()
{
// just start using injected property
BooService...
}
}
Add services scan method in ConfigureServices
using Microsoft.Extensions.DependencyInjection;
...
host.ConfigureServices((services)=>
{
services.AddTransient<IBooService, BooService>();
services.AddTransient<IFooService, FooService>();
// scan method
services.AddPropertyInjectedServices();
});
If you using this extension in asp net and want add property injection support in controllers too, you should add in ConfigureServices this statement:
services.AddControllers().AddControllersAsServices()

AutoMapper implementation in ASP.NET Core MVC

I am trying to implement AutoMapper in an ASP.NET Core MVC application using the techniques described in https://lostechies.com/jimmybogard/2016/07/20/integrating-automapper-with-asp-net-core-di.
Here is my startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
…
services.AddMvc();
services.AddAutoMapper();
…
// Autofac configuration
return ConfigureAutofacContainer(services);
}
Here is my AutoMapper.Profile implementation
public class AutoMapperProfile_NetCore_DtoFromDao : Profile
{
#region ctor
public AutoMapperProfile_NetCore_DtoFromDao()
{
CreateMaps();
}
#endregion
#region Methods
protected void CreateMaps()
{
if (Mapper.Configuration.FindTypeMapFor(typeof(AddressType),
typeof(AddressTypeDto)) == null)
CreateMap<AddressType, AddressTypeDto>();
Mapper.Configuration.AssertConfigurationIsValid();
}
}
AutoMapperProfile_NetCore_DtoFromDao.CreateMaps() is being called by ServiceCollectionExtensions.AddAutoMapperClasses():
public static class ServiceCollectionExtensions
{
…
private static void AddAutoMapperClasses(IServiceCollection services,
Action<IMapperConfigurationExpression> additionalInitAction,
IEnumerable<Assembly> assembliesToScan)
{
…
Mapper.Initialize(cfg =>
{
additionalInitAction(cfg);
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
…
}
}
I’m getting the following exception:
An exception of type 'System.InvalidOperationException' occurred in
AutoMapper.dll but was not handled in user code
Q - Is this due to the profile calling Mapper.Configuration.FindTypeMapFor() during Mapper.Initialization()?
Q - Is it possible to test for an existing mapping configuration before adding one during initialzation?
System.InvalidOperationException was unhandled by user code
HResult=-2146233079 Message=Mapper not initialized. Call Initialize
with appropriate configuration. If you are trying to use mapper
instances through a container or otherwise, make sure you do not have
any calls to the static Mapper.Map methods, and if you're using
ProjectTo or UseAsDataSource extension methods, make sure you pass in
the appropriate IConfigurationProvider instance. Source=AutoMapper
StackTrace:
at AutoMapper.Mapper.get_Configuration()
at Dna.NetCore.Core.BLL.Mappers.AutoMapperProfile_NetCore_DtoFromDao.CreateMaps()
in
C:\Src\AutoMapper.Extensions.Microsoft.DependencyInjection\src\Dna.NetCore.Core.BLL\Mappers\AutoMapperProfile_NetCore_DtoFromDao.cs:line
22
at Dna.NetCore.Core.BLL.Mappers.AutoMapperProfile_NetCore_DtoFromDao..ctor()
in
C:\Src\AutoMapper.Extensions.Microsoft.DependencyInjection\src\Dna.NetCore.Core.BLL\Mappers\AutoMapperProfile_NetCore_DtoFromDao.cs:line
13 InnerException:
OK. A few things here. Your AutoMapper config, the easiest way to build this is just:
services.AddAutoMapper(typeof(Startup));
That scans the assembly from the Startup class for Profiles, and automatically adds them using Mapper.Initialize. DO NOT call Mapper.Initialize after this.
Next, your profile. You're doing a lot of things you shouldn't. First, your profile is calling AssertConfigurationIsValid - don't. Next, it's checking for existing TypeMaps - don't. Just call the base CreateMap method, that's it.
Finally, you've got an extra AddAutoMapperClasses call. Don't use that. Get rid of it. You just need the "services.AddAutoMapper". The AddAutoMapper method calls Mapper.Initialize, with the Profile classes found in the assembly you've passed in.

Create an instance of ISession per ViewModel

here is my problem: I'm building a desktop application, with the following tools:
Caliburn
Ninject
NHibernate
All my view models and repositories are instanciated with Ninject. My repositories all need an ISession in their constructor.
I'd like to follow ayende's advice concerning the ViewModels: each ViewModel opens a new session.
Is it possible to configure Ninject to open a new session when a ViewModel is created, and use this session inside the repositories used by this view model?
I had a look to the InScope function of Ninject, as well as the ICurrentSessionContext interface in NHibernate, but I don't know how to model all of that to get what I want...
Did someone make something like that before?
Thanks in advance
Mike
I solved a similar scenario leveraging the ViewModel lifecycle: I created an ISessionAware interface (with a SetSession method) to be implemented by repositories, then I initialized the repositories through ISessionAware in the OnInitialize method of the ViewModel (which is enforced by Caliburn when the VM is managed by a ScreenConductor).
Using reflection to inspect the properties holding the repositories, I could put all the infrastructure on a BaseDataVM class.
Using a scope in the container would be more elegant, I think, but I don't know Ninject.
I have a very similar project (except I'm not using Caliburn) and have been trying to figure out how to do this as well. I did come up with one method that works well for constructor injection using Ninject's InScope() method.
I have a static class called IoC that wraps access to Ninject's kernel. Since the dependencies are all injected into the constructor, the context is only relevant when the object is being created. So it doesn't matter what is supplied for context, but a Guid feels like the safe choice. Program.OpenSession() is a static method to open a new ISession.
public static class Ioc
{
private static readonly IKernel _kernel;
static IoC()
{
_kernel = new StandardKernel();
_kernel.Load(new ContextModule());
}
private static object _context;
public static T ResolveInContext<T>(object context)
{
_context = context;
var result = _kernel.Get<T>();
_context = null;
return result;
}
private class ContextModule : NinjectModule
{
public override void Load()
{
Bind<ISession>().ToMethod(x => Program.OpenSession()).InScope(x => _context);
Bind<frmCompanyViewer>().ToSelf().InScope(x => _context);
}
}
}
Usage is:
var frm = IoC.ResolveInContext<frmCompanyViewer>(Guid.NewGuid());
The form's constructor signature is:
public frmCompanyViewer(ISession session, ICompanyRepository companyRepository)
I verified that with InScope on the bindings, the same ISession that is used to construct frmCompanyViewer is also used to construct companyRepository. If I remove InScope then two ISessions are used.
Edited to add: This would also work, see comments. This should be made thread safe for a real application. I changed the method name to ConstructInContext to clarify that the context only applies during object construction.
public static T ConstructInContext<T>()
{
_context = Guid.NewGuid();
var result = _kernel.Get<T>();
_context = null;
return result;
}
We have this with AOP, in unhaddins.
Is called "Conversation per Business Transaction".
search in google
here you have ;)
http://groups.google.com/group/unhaddins/browse_thread/thread/29eca74a83df5faf/d9fab4062d4cb4c4?lnk=gst&q=ninject#d9fab4062d4cb4c4
Well, I've found a solution thanks to the ninject group.
The solution here is to use the function InScope when I bind ISession, and browse in the IContext variable to inspect the services. If one service in the request hierarchy is assignable to the base class of my view models, I use the context as scope.
So the first time an ISession will be injected in the constructor of my ViewModel, a new scope is used. And all subsequent calls to ISession inside the constructor of the ViewModel will be resolved with the same scope. And then only one session is created for my ViewModel.
Here is the code:
Bind<ISession>().ToMethod(ctx =>
{
var session = ctx.Kernel.Get<INHibernateSessionFactoryBuilder>()
.GetSessionFactory()
.OpenSession();
session.FlushMode = FlushMode.Commit;
return session;
})
.InScope(ctx =>
{
var request = ctx.Request;
if (request.Service is IScreen)
return request;
while ((request = request.ParentRequest) != null)
if (typeof(IScreen).IsAssignableFrom(request.Service))
return request;
return new object();
});
And the constructor of the viewmodel must contains all the injected dependencies which rely on the ISession:
[Inject]
public PlayersManagementViewModel(ISession session, IPlayersRepository playersRepository)
{
}
Hope that helps