Accessing contextual information during lambda registration in Autofac? [duplicate] - c#

This question already has an answer here:
Autofac: Register component and resolve depending on resolving parent
(1 answer)
Closed 6 years ago.
With Ninject I can do something like this:
Bind<ILogger>().ToMethod(context =>
{
// Get type info
var type = context.Request.Target.Member.DeclaringType;
var logger = new ConcreteLogger(type);
Kernel.Get<IFoo>().DoFoo(logger);
return logger;
});
How can I do this with Autofac?
This is the code I have:
builder.Register(context => {
var type = ?????
var logger = new ConcreteLogger(type);
context.Resolve<IFoo>().DoSomething(logger);
return logger;
}).As<ILogger>();
I can see in the debugger that context is actually of the type Autofac.Core.Resolving.InstanceLookup which has a member ComponentRegistration.Target but I cannot access that because InstanceLookup is an internal class.
It appears I can do this, but it doesn't give me the type information of the class that requires this injected type:
builder.Register(context => {
var lookup = c as IInstanceLookup;
var target = lookup.ComponentRegistration.Target as ComponentRegistration;
var logger = new ConcreteLogger(target.Activator.LimitType);
context.Resolve<IFoo>().DoSomething(logger);
return logger;
}).As<ILogger>();

What you need is to inject a component based on "parent" component. With Autofac you register components and these components doesn't known who need them.
By the way, you can do what you want by implementing a custom Module. Exemple :
public class TestModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
registration.Preparing += (sender, e) =>
{
Parameter parameter = new ResolvedParameter(
(pi, c) =>
{
return pi.ParameterType == typeof(ILogger);
}, (pi, c) =>
{
var p = new TypedParameter(typeof(Type),
e.Component.Activator.LimitType);
return c.Resolve<ILogger>(p);
});
e.Parameters = e.Parameters.Union(new Parameter[] { parameter });
};
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
and register the module like this :
builder.RegisterModule<TestModule>();
this way, each time a component will be resolved, it will add a new parameter knowing the type being constructed to create the ILogger dependency.
Be aware that by doing this you may have captive dependency : a dependency that was built for a component but used for another one. It can happens if your ILogger registration has a different scope, for example a singleton scope.

Related

Autofac variable name based injection

I want to inject some ValueType variable values using autofac, as we do same for Interfaces. I don't want to additionally mention in bootstrapper that this class have named parameterized injection or with Key.
For eg: This is easily injectable
Registration =>
builder.RegisterType<SqlProvider>().As<ISqlProvider>();
Injection=>
MyClass(ISqlProvider provider)
So Can we do something like:
Registration =>
builder.RegisterType<int>().Named<int>("maxRetries");
Injection=>
MyClass(int maxRetries)
I think what you need is property injection.
http://autofac.readthedocs.io/en/latest/register/prop-method-injection.html
You could for the sake of simplicity use a factory or settingsprovider just like your sqlprovider.
Because i think you want to create some servicelayer.
Pseudocode:
private iSqlProvider _sqlprovider;
private ISettingsProvider _settingsProvider;
MyClass(ISqlProvider sqlprovider, ISettingProvider settingProvider)
{
_sqlprovider = sqlprovider;
_settingsProvider = settingsProvider
}
public MyClassModel GetMyAwesomeModels()
{
var settings = _setingsprovider.getSetting()
settings.maxTries
//do your magic with maxtries
}
You can use a custom parameter and a module to have this feature.
A module allows you to add custom parameter for every registration. A parameter is a special class that Autofac will call each time a registration is resolved to check whether it can provide the requested parameter.
For example :
public class ResolvedNamedParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
if (pi.ParameterType.IsValueType
&& context.IsRegisteredWithName(pi.Name, pi.ParameterType))
{
valueProvider = () => context.ResolveNamed(pi.Name, pi.ParameterType);
return true;
}
else
{
valueProvider = null;
return false;
}
}
}
Then the module to add the parameter for every registration :
public class ResolvedNamedParameterModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
registration.Preparing += (sender, e) =>
{
e.Parameters = new Parameter[] { new ResolvedNamedParameter() }.Concat(e.Parameters);
};
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
And you can use it like this :
builder.RegisterModule<ResolvedNamedParameterModule>();
builder.Register(c => 3).Named<Int32>("x");
builder.Register(c => 5).Named<Int32>("y");
Try this:
Injection=>
MyClass([KeyFilter("maxRetries")]int maxRetries)
Is that what you were looking for?

How to setup composite class with microsoft dependency injection?

this is dotnet.core question
let's say i have class NotifierResolver:
public class NotifierResolver : INotifierResolver
{
private readonly List<INotifier> _notifiers;
public NotifierResolver(List<INotifier> notifiers)
{
_notifiers = notifiers;
}
public INotifier Resolve(INotification notification)
{
var notifier = _notifiers.Single(h => h.CanNotify(notification));
if (notifier == null)
{
throw new Exception($"could not resolve notification: {notification.GetType()}");
}
return notifier;
}
}
How do i setup it's dependencies under service collection ( Microsoft.Extensions.DependencyInjection ) ?
var serviceProvider = new ServiceCollection()
.????
.AddSingleton<INotifierResolver, NotifierResolver>()
.BuildServiceProvider();
You may need to build up the composite at the composition root like below.
var serviceProvider = new ServiceCollection()
.AddTransient<INotifier, SomeNotifier>()
.AddTransient<INotifier, SomeOtherNotifier>()
.AddTransient<INotifier, YetAnotherNotifier>()
.AddSingleton<INotifierResolver, NotifierResolver>(_
=> new NotifierResolver(_.GetServices<INotifier>().ToList())//<-- Note GetServices
)
.BuildServiceProvider();
So assuming you have multiple INotifier registered with the service collection, when resolving the INotifierResolver, the provider with resolve all the dependencies via IServiceProvider.GetServices extension method and inject them into the dependent class.

IdentityServer3 with autofac

I am trying to implement IdentityServer3 into an existing project that uses Autofac. The problem I have come across is that when I set up my custom services, if I run my project and try to authenticate I get this error:
"An error occurred when trying to create a controller of type 'TokenEndpointController'. Make sure that the controller has a parameterless public constructor."
Now I know this is a generic autofac error when a service has not been set up correctly.
The error actually moans about my custom UserService stating:
None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Business.IdentityServer.IdentityServerUserService' can be invoked with the available services and parameters:
Cannot resolve parameter 'Business.Providers.IUserProvider userProvider' of constructor 'Void .ctor(Business.Providers.IUserProvider)'.
Now I already had a UserProvider before I started using IdentityServer3 and it was set up in autofac like this:
builder.RegisterType<DatabaseContext>().As<DbContext>().InstancePerDependency();
builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerDependency();
This was working before, so I know that the UserProvider does actually have all it's dependencies.
My UserService looks like this:
public class IdentityServerUserService : UserServiceBase
{
private readonly IUserProvider _userProvider;
public IdentityServerUserService(IUserProvider userProvider)
{
_userProvider = userProvider;
}
public override async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = await _userProvider.FindAsync(context.UserName, context.Password);
if (user != null && !user.Disabled)
{
// Get the UserClaims
// Add the user to our context
context.AuthenticateResult = new AuthenticateResult(user.Id, user.UserName, new List<Claim>());
}
}
}
Does anyone know how I can resolve this issue?
This was due to how I was configuring the factory. I now have it like this:
private static IdentityServerServiceFactory Configure(this IdentityServerServiceFactory factory, CormarConfig config)
{
var serviceOptions = new EntityFrameworkServiceOptions { ConnectionString = config.SqlConnectionString };
factory.RegisterOperationalServices(serviceOptions);
factory.RegisterConfigurationServices(serviceOptions);
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true }); // Allow all domains to access authentication
factory.Register<DbContext>(new Registration<DbContext>(dr => dr.ResolveFromAutofacOwinLifetimeScope<DbContext>()));
factory.UserService = new Registration<IUserService>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IUserService>());
factory.ClientStore = new Registration<IClientStore>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IClientStore>());
factory.ScopeStore = new Registration<IScopeStore>(dr => dr.ResolveFromAutofacOwinLifetimeScope<IScopeStore>());
return factory;
}
My user service is still the same, so everything works.

Autofac Resolve using delegate factory by type

I am using Autofac for IoC in my project. Due to some legacy software libraries I must pass some services to the controller that can't be resolved, and must be passed as parameter.
I've made a generic control using delegate factories like this:
public MyClass<TController, TInterface> {
private delegate TController ControllerFactory(TInterface service);
protected TController _myController;
protected TController Controller {
get
{
return _controller
?? (_controller = ServiceLocator.Resolve<ControllerFactory>()
.Invoke(this);
}
}
}
This works perfect, but for this to work I need the controller's service parameter name and the delegate service parameter name be the same, because as I have read, Autofac pairs the parameter BY NAME !!
I've seen you can do it by type registering the class with generic Func<>, but due to the legacy app I would need to leave "clean" registrations i.e.:
containerBuilder.RegisterType<MyController>();
Does anyone know if it's possible to make the delegate match the parameter by type??
Does anyone know if it's possible to make the delegate match the parameter by type??
Yes, you can use predefined delegates. See dynamic instantiation section here.
Here's an quick example:
public class ComponentFactory
{
private readonly Func<Dependency, Component> _componentFactory;
public ComponentFactory(Func<Dependency, Component> componentFactory)
{
_componentFactory = componentFactory;
}
public Component Create(Dependency dependency)
{
return _componentFactory(dependency);
}
}
public class Component
{
private readonly Dependency _dependency;
public Component(Dependency dependency)
{
_dependency = dependency;
}
}
public class Dependency
{
}
Registration + Usage
var builder= new ContainerBuilder();
builder.RegisterType<ComponentFactory>();
builder.RegisterType<Component>();
builder.RegisterType<Dependency>();
var container = builder.Build();
var factory = container.Resolve<ComponentFactory>();
//Usage with typed parameters
var component = factory.Create(new Dependency());
**Be warned, if you use this method, Autofac throws an exception if you try to add parameters with of the same type. Ex. Component has two dependencies on Dependency
Exception looks something like this:
The input parameter type list
has duplicate types. Try registering a custom delegate type instead of
using a generic Func relationship.
Autofac is more specific about what type you register the controller as than most DI containers. It will only resolve the type by its type if you include .AsSelf() in the registration of the controller. Here is a module we use in our project for registering MVC controllers.
public class MvcModule
: Module
{
protected override void Load(ContainerBuilder builder)
{
var currentAssembly = typeof(MvcModule).Assembly;
builder.RegisterAssemblyTypes(currentAssembly)
.Where(t => typeof(IController).IsAssignableFrom(t))
.AsImplementedInterfaces()
.AsSelf()
.InstancePerDependency();
}
}
Using this registration, you can resolve each controller by controller type.
var type = typeof(HomeController);
var controller = container.Resolve(type);

Autofac - resolve by argument name

I'm migrating an application from Ninject to Autofac.
We used a special naming convention for injecting app settings into constructors:
public class Example{
public Example(AppSetting settingName){
...
}
}
AppSetting parameter was injected automatically using ConfigurationManager.AppSettings["settingName"].
In Ninject this was accomplished by using a custom provider:
public class AppSettingProvider : Ninject.Activation.IProvider
{
public object Create(IContext context)
{
var varName = ((Context)context).Request.Target.Name;
var value = new AppSetting(ConfigurationManager.AppSettings[varName]);
if (value.Value == null)
{
... log ...
}
return value;
}
public Type Type
{
get { return typeof(AppSetting); }
}
}
I was not able to find an alternative for this feature in Autofac. If this is not possible in an automated way I'm ok with looping over all app settings during the initial configuration step.
Any idea what to do?
Thanks,
Vilem
I have created a solution using this SO question:
Register string value for concrete name of parameter
and subsequently improved it using Travis Illig's suggestion.
Currently this seems to work exactly the same as the Ninject equivalent.
Here's the result:
public class AppSettingsModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
// Any time a component is resolved, it goes through Preparing
registration.Preparing += InjectAppSettingParameters;
}
private void InjectAppSettingParameters(object sender, PreparingEventArgs e)
{
// check if parameter is of type AppSetting and if it is return AppSetting using the parameter name
var appSettingParameter = new ResolvedParameter((par, ctx) => par.ParameterType == typeof(AppSetting), (par, ctx) => new AppSetting(ConfigurationManager.AppSettings[par.Name]));
e.Parameters = e.Parameters.Union(new List<Parameter>{ appSettingParameter});
}
}

Categories