Im messing round in autofac and im having some issues binding to a specific constructor.
I have the following code:
var builder = new ContainerBuilder();
builder
.RegisterType<GenericIocFactory>()
.As<IGenericIocFactory>();
builder
.RegisterType<Product>()
.As<IProduct>()
.PropertiesAutowired();
IContainer Container = builder.Build();
IGenericIocFactory Fac = Container.Resolve<IGenericIocFactory>();
_product = Fac.Get<IProduct>(new Dictionary<string,object>() { {"returnEmpty" , false} }) as Product;
Then in the factory:
public interface IGenericIocFactory
{
T Get<T>(Dictionary<string,object> options) where T: class;
}
public class GenericIocFactory : IGenericIocFactory
{
private readonly IComponentContext _icoContext;
private object _options;
public GenericIocFactory(IComponentContext icoContext,bool isInjected = true)
{
_icoContext= icoContext;
}
public T Get<T>(Dictionary<string,object> options) where T: class
{
var _parameters = new List<Parameter>();
foreach (var parameter in options)
{
_parameters.Add(new NamedParameter(parameter.Key, parameter.Value));
}
return _icoContext.Resolve<T>(_parameters);
//operate on new object
// tried this as well
//return _icoContext.Resolve<T>(
//new NamedParameter("returnEmpty" , false)
//new TypedParameter(typeof(bool),false)
//);
}
}
This resolves a product but not with the constructor i expect.
Target constructor
public Product(bool returnEmpty)
Resolving constructor
public Product(IList<string> productCodes, string fields = "", string orderBy = "ProductCode")
There is a total of 23 constructors and the one resolving is not the biggest(so i don't think its being greedy)
ie
public Product(string strFields, string strFrom, string strFilter, string strOrderBy, string whseCode,
bool addExistsInWharehouse, string additionalAfterorderBy, bool forceUniqueRecords = false)
Nor is it the first or last in or of definition.
Im stumped can anyone see what im doing wrong.
Unfortunately Autofac doesn't provide this mechanism.
You could have implemented IConstructorSelector which select a constructor when more than one constructor is available and set it to the registration by using the UsingSelector method but unfortunately there is no way to access the available parameters of the current resolve operation.
Another solution would be to implement IInstanceActivator which is responsible of creating the instance based on the type and parameters. to use a custom IInstanceActivator you also need to implement IRegistrationBuilder which is quite difficult. To guarantee good performance, I would also recommend the use of ConstructorParameterBinding which will create an optimized factory using dynamic compiled expression.
If you can't change your constructor, the only solution I can see is to implement your own factory. Because your object don't have any dependencies, you can create them without using Autofac.
public class GenericIocFactory : IGenericIocFactory
{
public GenericIocFactory(ILifetimeScope scope)
{
this._scope = scope;
}
private readonly ILifetimeScope _scope;
public T Get<T>(params object[] args) where T: class
{
ConstructorInfo ci = this.GetConstructorInfo(args);
if (ci == null)
{
throw ...
}
var binder = new ConstructorParameterBinding(ci, args, this._scope);
T value = binder.Instanciate() as T;
if (value == null)
{
throw ...
}
if(value is IDisposable)
{
this._scope.Disposer.AddInstanceForDisposal(value);
}
return value;
}
protected virtual ConstructorInfo GetConstructorInfo<T>(params object[] args)
{
// TODO
}
}
So having read up again on the doco. I needed to bind the constructor at the start. But this wont fix my problem so I made another container ever single time an instance is requested and construct it based on the params. Its a little bit incorrect but this is a real world solution to anyone who is transitioning to autofac for a very large existing solution.
Hope this helps someone.
public interface IGenericIocFactory
{
T Get<T>(params object[] constructorParams) where T: class;
}
public interface ICustomAutoFacContainer
{
IContainer BindAndReturnCustom<T>(IComponentContext context, Type[] paramsList);
}
public class CustomAutoFacContainer : ICustomAutoFacContainer
{
public IContainer BindAndReturnCustom<T>(IComponentContext context, Type[] paramsList)
{
if (context.IsRegistered<T>())
{
// Get the current DI binding type target
var targetType = context
.ComponentRegistry
.Registrations
.First(r => ((TypedService) r.Services.First()).ServiceType == typeof(T))
.Target
.Activator
.LimitType;
// todo: exception handling and what not .targetType
var builder = new ContainerBuilder();
builder
.RegisterType(targetType)
.As<T>()
.UsingConstructor(paramsList)
.PropertiesAutowired();
return builder.Build();
}
return null;
}
}
public class GenericIocFactory : IGenericIocFactory
{
private ICustomAutoFacContainer _iCustomContainer;
private readonly IComponentContext _icoContext;
public GenericIocFactory(ICustomAutoFacContainer iCustomContainer, IComponentContext icoContext)
{
_iCustomContainer = iCustomContainer;
_icoContext = icoContext;
}
public T Get<T>(params object[] constructorParams) where T: class
{
//TODO handle reflection generation? ?? ?not needed?? ??
var parameters = constructorParams
.Select((t, index) => new PositionalParameter(index, t))
.Cast<Parameter>()
.ToList();
var parameterTypes = constructorParams
.Select((t, index) => t.GetType())
.ToArray();
return _iCustomContainer
.BindAndReturnCustom<T>(_icoContext,parameterTypes)
.Resolve<T>(parameters);
}
}
Setup and usage looks something like this:
var builder = new ContainerBuilder();
// Usually you're only interested in exposing the type
// via its interface:
builder
.RegisterType<GenericIocFactory>()
.As<IGenericIocFactory>();
builder
.RegisterType<CustomAutoFacContainer>()
.As<ICustomAutoFacContainer>();
builder
.RegisterType<Product>()
.As<IProduct>()
.PropertiesAutowired();
var container = builder.Build();
var factory = container.Resolve<IGenericIocFactory>();
_product = factory.Get<IProduct>(false) as Product;
_product = factory.Get<IProduct>("","") as Product;
Related
Note: using .NET Core 2.0 [Microsoft.Extensions.DependencyInjection].
Here's what I would like to do:
IServiceCollection collection = new ServiceCollection();
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel>(nameof(View1));
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel>(nameof(View2));
But I can't figure out a way to name services added to the collection. If the type used repeats without a name specified, the collection appears to simply overwrite the existing service with the new one. Ideally, I would be able to reuse a type, but differentiate it by supplying a name.
Please ignore the contrived example.
Q: Is this possible?
Given the fact that the ServiceDescriptor class doesn't have a Name property or any way to set a string identifier and the classes for resolving services are marked internal, I would say the answer is no.
However, it's not very difficult to build your own extensions to fake it.
NamedServiceDescriptor
class NamedServiceDescriptor
{
public NamedServiceDescriptor(string name, Type serviceType)
{
this.Name = name;
this.ServiceType = serviceType;
}
public string Name { get; private set; }
public Type ServiceType { get; private set; }
public override bool Equals(object obj)
{
if (!(obj is NamedServiceDescriptor))
return false;
var other = (NamedServiceDescriptor)obj;
return Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) &&
ServiceType.Equals(other.ServiceType);
}
public override int GetHashCode()
{
return Name.GetHashCode() ^
ServiceType.GetHashCode();
}
}
Extension Methods
public static class ServiceCollectionExtensions
{
internal static readonly IDictionary<NamedServiceDescriptor, Type> nameToTypeMap
= new ConcurrentDictionary<NamedServiceDescriptor, Type>();
public static IServiceCollection AddSingleton<TService, TImplementation>(
this IServiceCollection serviceCollection,
string name)
where TService : class where TImplementation : class, TService
{
nameToTypeMap[new NamedServiceDescriptor(name, typeof(TService))]
= typeof(TImplementation);
return serviceCollection.AddSingleton<TImplementation>();
}
}
public static class ServiceProviderExtensions
{
public static T GetService<T>(this IServiceProvider provider, string name)
{
if (provider == null)
throw new ArgumentNullException(nameof(provider));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
ServiceCollectionExtensions.nameToTypeMap.TryGetValue(
new NamedServiceDescriptor(name, typeof(T)), out Type implementationType);
return (T)provider.GetService(implementationType);
}
}
Usage
public interface IMyReusableViewModel { }
public class MyReusableViewModel1 : IMyReusableViewModel { }
public class MyReusableViewModel2 : IMyReusableViewModel { }
IServiceCollection collection = new ServiceCollection();
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel1>("View1");
collection.AddSingleton<IMyReusableViewModel, MyReusableViewModel2>("View2");
public class MyService
{
private readonly IServiceProvider provider;
public MyService(IServiceProvider provider)
{
this.provider = provider;
}
public void DoSomething()
{
var view1 = provider.GetService<IMyReusableViewModel>("View1");
var view2 = provider.GetService<IMyReusableViewModel>("View2");
// ...
}
}
NOTE: That said, I wouldn't recommend this approach. If you require such functionality, it is a sign that the application design is inadequate. A design pattern such as Abstract Factory or Strategy may be what is needed to fill the void without resorting to naming type registrations or abusing the container as a Service Locator.
Alternatively, you could use a 3rd party DI container that supports this functionality.
We have a lot of registrations within out Unity Container. About 800 lines long in fact.
As Unity does not have a validate method, we introduced a Unit Test to verify the configuration before we test the application which looks like something as follows which works very well and has picked up lots of issues within the configuration:
IUnityContainer container = UnityContainerBuilder.Build();
foreach (ContainerRegistration mapping in container.Registrations)
{
container.Resolve(mapping.RegisteredType, mapping.Name);
}
However, we have lots of classes that follow the decorator pattern in there where we use named registrations. These named registrations are then used to build up the InjectionConstructor for other registrations, for example:
container.RegisterType<IMyType2, Concrete1MyType2>("Concrete1MyType2", new ContainerControlledLifetimeManager());
container.RegisterType<IMyType2, Concrete2MyType2>("Concrete2MyType2", new ContainerControlledLifetimeManager());
container.RegisterType<IMyType1, Concrete1MyType1>(
"Concrete1MyType1",
new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IMyType2>("Concrete2MyType2")));
Is it possible to access through some code what you have configured in the InjectionConstructor's for your registrations?
This article seems to suggest you can.
The example given is:
void DisplayContainerRegistrations(IUnityContainer theContainer)
{
string regName, regType, mapTo, lifetime;
Console.WriteLine("Container has {0} Registrations:",
theContainer.Registrations.Count());
foreach (ContainerRegistration item in theContainer.Registrations)
{
regType = item.RegisteredType.Name;
mapTo = item.MappedToType.Name;
regName = item.Name ?? "[default]";
lifetime = item.LifetimeManagerType.Name;
if (mapTo != regType)
{
mapTo = " -> " + mapTo;
}
else
{
mapTo = string.Empty;
}
lifetime = lifetime.Substring(0, lifetime.Length - "LifetimeManager".Length);
Console.WriteLine("+ {0}{1} '{2}' {3}", regType, mapTo, regName, lifetime);
}
}
OK, after not getting much info on this, I had a look at the internals of Unity as I could not see any public way of doing it. I came up with the following which uses Reflection to access the private members of the various parts of Unity.
The main reasoning for this is that injecting Transient instances into Singletons is a bit of a mismatch. Transient should only last a short time whilst Singletons will probably remain throughout the life of the DI Container. There is some nice information on Simple Injector here. This was one of the reasons for trying to get the Injection information as Unity does not have a verify like Simple Injector.
So, here are my service interfaces and classes I am registering. There are 3 interfaces and 3 concrete implementations with exception to IService2 which has 2 implementations.
public class Service1 : IService1
{
private readonly IService2 _service2;
private readonly IService3 _service3;
public Service1(IService2 service2, IService3 service3)
{
_service2 = service2;
_service3 = service3;
}
public int DoSomethingForService1()
{
return 1;
}
}
public interface IService1
{
int DoSomethingForService1();
}
public class Service2 : IService2
{
public int DoSomethingForService2()
{
return 1;
}
}
public class Service3 : IService3
{
public int DoSomethingForService3()
{
return 1;
}
}
public interface IService3
{
int DoSomethingForService3();
}
public class Service2_1 : IService2
{
public int DoSomethingForService2()
{
return 1;
}
}
public interface IService2
{
int DoSomethingForService2();
}
Now the Unity Container is built up. We tend to use a separate project for this as we don't want all references to be held in the UI. We are using 2 named registrations of IService2.
public static class UnityContainerBuilder
{
public static IUnityContainer BuildDirectInUnity()
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IService2, Service2>("Service2OneAndOnly", new ContainerControlledLifetimeManager());
container.RegisterType<IService2, Service2_1>("Service2_1", new ContainerControlledLifetimeManager());
container.RegisterType<IService3, Service3>(new ContainerControlledLifetimeManager());
container.RegisterType<IService1, Service1>(new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IService2>("Service2OneAndOnly"),
new ResolvedParameter<IService3>()));
}
}
Now the fun part. Here we get the Unity Configuration:
static void Main(string[] args)
{
Console.WriteLine("Building Unity Container...");
var container = (UnityContainer)UnityContainerBuilder.BuildDirectInUnity();
Console.WriteLine("Listing Registrations...");
FieldInfo policiesField = typeof(UnityContainer).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "policies");
FieldInfo parameterValuesField = typeof(SpecifiedConstructorSelectorPolicy).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "parameterValues");
FieldInfo paramNameField = typeof(ResolvedParameter).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "name");
var policies = (PolicyList)policiesField.GetValue(container);
// build up a dictionary for loop below to use to get the lifetime manager
var typeToRegistration = new Dictionary<Tuple<Type, string>, ContainerRegistration>();
foreach (ContainerRegistration registration in container.Registrations)
{
typeToRegistration.Add(new Tuple<Type, string>(registration.RegisteredType, registration.Name), registration);
}
// now output the list
foreach (ContainerRegistration registration in container.Registrations)
{
Console.WriteLine("{0} to {1}, {2}, {3}",
registration.RegisteredType.Name,
registration.MappedToType.Name,
registration.Name ?? "[default]",
registration.LifetimeManagerType.Name);
// need to check for our InjectionConstructor - I need local = false
IConstructorSelectorPolicy constructorPolicy = policies.Get<IConstructorSelectorPolicy>(
new NamedTypeBuildKey(registration.MappedToType, registration.Name), false);
// and I need SpecifiedConstructorSelectorPolicy as we are not using the default constructor
if (constructorPolicy is SpecifiedConstructorSelectorPolicy)
{
var specifiedConstructorPolicy = constructorPolicy as SpecifiedConstructorSelectorPolicy;
// now output the ResolvedParameters for type, name, lifetime manager
var paramValues = (InjectionParameterValue[])parameterValuesField.GetValue(specifiedConstructorPolicy);
foreach (var param in paramValues)
{
if (param is ResolvedParameter)
{
var resolvedParam = param as ResolvedParameter;
var name = (string)paramNameField.GetValue(resolvedParam);
string lifeTimeManagerName =
typeToRegistration[new Tuple<Type, string>(resolvedParam.ParameterType, name)].LifetimeManagerType.Name;
Console.WriteLine("\t{0}, {1}, {2}", param.ParameterTypeName, name ?? "[default]", lifeTimeManagerName);
}
else
{
Console.WriteLine("\t{0}", param.ParameterTypeName);
}
}
}
}
Console.WriteLine("Complete");
Console.ReadLine();
}
The drawbacks of this is that it is purely reflection based hacking and that it does not support the InjectionFactory where you can configure a new instance of a type.
A module has registered a type that looks like this:
class MyType
{
public IEnumerable<IServer> Servers { get { ... } }
}
I would like to register each of these server instances with the container:
builder.Register(x => x.Resolve<MyType>().Servers).As<IEnumerable<IServer>>();
Separately I would like to register another implementation of IServer
builder.RegisterType<AnotherServer>().As<IServer>().SingleInstance;
Then I would like to get all the servers - the union os Server property and an instance of AnotherServer:
public MyClass(IEnumerable<IServer> allServers)
{
}
However, the above setup doesn't work because registering an actual instance of IEnumerable seems to override the Autofac behaviour of providing all registered implementations when an IEnumerable is requested.
Is there any way I can achieve what I want?
It's not possible to have one registration to produce multiple component.
I can see two ways for resolving your issue
Create a new IRegistrationSource to resolve IEnumerable<T>. You can have a look at the CollectionRegistrationSource.cs to see how to do it
Use a module to replace the IEnumerable during the activation.
Something like this :
public interface IContainer<T> {
IEnumerable<T> Values { get; }
}
public class MyType : IContainer<IServer>
{
public IEnumerable<IServer> Servers
{
get
{
yield return new Server1();
yield return new Server2();
}
}
IEnumerable<IServer> IContainer<IServer>.Values
{
get { return this.Servers; }
}
}
public class AggregatedEnumerableModule<T> : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
if (registration.Activator.LimitType.IsArray)
{
Type elementType = registration.Activator.LimitType.GetElementType();
if (elementType == typeof(T))
{
registration.Activating += (sender, e) =>
{
IEnumerable<T> originalValues = (IEnumerable<T>)e.Instance;
IEnumerable<T> newValues = e.Context.Resolve<IContainer<T>>().Values;
IEnumerable<T> aggregatedValues = newValues.Concat(originalValues);
e.ReplaceInstance(aggregatedValues);
};
}
}
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
Then
ContainerBuilder builder = new Autofac.ContainerBuilder();
builder.RegisterModule(new AggregatedEnumerableModule<IServer>());
builder.RegisterType<MyType>().As<IContainer<IServer>>();
builder.RegisterType<Server3>().As<IServer>();
IContainer container = builder.Build();
IEnumerable<IServer> pouets = container.Resolve<IEnumerable<IServer>>();
I have a ASP MVC 4 app that uses Structuremap. I'm trying to add logging to my application via Structuremap interception.
In a Registry, I scan a specific assembly in order to register all of it's types with the default convention:
public class ServicesRegistry : Registry
{
public ServicesRegistry()
{
Scan(x =>
{
x.AssemblyContainingType<MyMarkerService>();
x.WithDefaultConventions();
});
}
}
The interceptor:
public class LogInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var watch = Stopwatch.StartNew();
invocation.Proceed();
watch.Stop();//log the time
}
}
I can add the interceptor for one specific plugin type like this:
var proxyGenerator = new ProxyGenerator();
container.Configure(x => x.For<IServiceA>().Use<ServiceA>().DecorateWith(instance => proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor())));
but I want to make structuremap create logging proxies for all the types that were scanned in the registry.
Is there a way to achieve this?
It doesn't look like there's an easy extension point for this, but I got it working with a fairly decent solution using a custom convention. In order to help you understand the decisions I made I'll walk you through a few steps (skipping the many, many missteps I made on my way).
First lets look at the DefaultConvention which you are already using.
DefaultConvention:
public class DefaultConventionScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!TypeExtensions.IsConcrete(type))
return;
Type pluginType = this.FindPluginType(type);
if (pluginType == null || !TypeExtensions.HasConstructors(type))
return;
registry.AddType(pluginType, type);
this.ConfigureFamily(registry.For(pluginType, (ILifecycle)null));
}
public virtual Type FindPluginType(Type concreteType)
{
string interfaceName = "I" + concreteType.Name;
return Enumerable.FirstOrDefault<Type>((IEnumerable<Type>)concreteType.GetInterfaces(), (Func<Type, bool>)(t => t.Name == interfaceName));
}
}
Pretty simple, we get the type and interface pairs and check to make sure they have a constructor, if they do we register them. It would be nice to just modify this so that it calls DecorateWith, but you can only call that on For<>().Use<>(), not For().Use().
Next lets look at what DecorateWith does:
public T DecorateWith(Expression<Func<TPluginType, TPluginType>> handler)
{
this.AddInterceptor((IInterceptor) new FuncInterceptor<TPluginType>(handler, (string) null));
return this.thisInstance;
}
So this creates a FuncInterceptor and registers it. I spent a fair bit of time trying to create one of these dynamically with reflection before deciding it would just be easier to make a new class:
public class ProxyFuncInterceptor<T> : FuncInterceptor<T> where T : class
{
public ProxyFuncInterceptor() : base(x => MakeProxy(x), "")
{
}
protected ProxyFuncInterceptor(Expression<Func<T, T>> expression, string description = null)
: base(expression, description)
{
}
protected ProxyFuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null)
: base(expression, description)
{
}
private static T MakeProxy(T instance)
{
var proxyGenerator = new ProxyGenerator();
return proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor());
}
}
This class just makes it easier to work with when we have the type as a variable.
Finally I've made my own Convention based on the Default convention.
public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!type.IsConcrete())
return;
var pluginType = this.FindPluginType(type);
if (pluginType == null || !type.HasConstructors())
return;
registry.AddType(pluginType, type);
var policy = CreatePolicy(pluginType);
registry.Policies.Interceptors(policy);
ConfigureFamily(registry.For(pluginType));
}
public virtual Type FindPluginType(Type concreteType)
{
var interfaceName = "I" + concreteType.Name;
return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
}
public IInterceptorPolicy CreatePolicy(Type pluginType)
{
var genericPolicyType = typeof(InterceptorPolicy<>);
var policyType = genericPolicyType.MakeGenericType(pluginType);
return (IInterceptorPolicy)Activator.CreateInstance(policyType, new object[]{CreateInterceptor(pluginType), null});
}
public IInterceptor CreateInterceptor(Type pluginType)
{
var genericInterceptorType = typeof(ProxyFuncInterceptor<>);
var specificInterceptor = genericInterceptorType.MakeGenericType(pluginType);
return (IInterceptor)Activator.CreateInstance(specificInterceptor);
}
}
Its almost exactly the same with one addition, I create an interceptor and interceptorType for each type we register. I then register that policy.
Finally, a few unit tests to prove it works:
[TestFixture]
public class Try4
{
[Test]
public void Can_create_interceptor()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreateInterceptor(type));
}
[Test]
public void Can_create_policy()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreatePolicy(type));
}
[Test]
public void Can_register_normally()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.WithDefaultConventions();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsFalse(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Can_register_proxy_for_all()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsTrue(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Make_sure_I_wait()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
serviceA.Wait();
}
}
}
public interface IServiceA
{
void Wait();
}
public class ServiceA : IServiceA
{
public void Wait()
{
Thread.Sleep(1000);
}
}
public interface IServiceB
{
}
public class ServiceB : IServiceB
{
}
There's definitely room for some clean up here (caching, make it DRY, more tests, make it easier to configure) but it works for what you need and is a pretty reasonable way of doing it.
Please ask if you have any other questions about it.
I'd like to have an instance per matching lifetime scoped registration in Autofac, but occasionally need to request an instance from a global container (where there is no matching lifetime scope). In scenarios where no matching lifetime scope exists, I want to give a top-level instance instead of throwing an exception.
Is this possible?
I think you'd better extend Autofac by introducing a new lifetime option. I took the Autofac sources and modified them a bit:
public static class RegistrationBuilderExtensions
{
public static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> InstancePerMatchingOrRootLifetimeScope<TLimit, TActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> builder, params object[] lifetimeScopeTag)
{
if (lifetimeScopeTag == null) throw new ArgumentNullException("lifetimeScopeTag");
builder.RegistrationData.Sharing = InstanceSharing.Shared;
builder.RegistrationData.Lifetime = new MatchingScopeOrRootLifetime(lifetimeScopeTag);
return builder;
}
}
public class MatchingScopeOrRootLifetime: IComponentLifetime
{
readonly object[] _tagsToMatch;
public MatchingScopeOrRootLifetime(params object[] lifetimeScopeTagsToMatch)
{
if (lifetimeScopeTagsToMatch == null) throw new ArgumentNullException("lifetimeScopeTagsToMatch");
_tagsToMatch = lifetimeScopeTagsToMatch;
}
public ISharingLifetimeScope FindScope(ISharingLifetimeScope mostNestedVisibleScope)
{
if (mostNestedVisibleScope == null) throw new ArgumentNullException("mostNestedVisibleScope");
var next = mostNestedVisibleScope;
while (next != null)
{
if (_tagsToMatch.Contains(next.Tag))
return next;
next = next.ParentLifetimeScope;
}
return mostNestedVisibleScope.RootLifetimeScope;
}
}
Just add these classes to your project and register you component as:
builder.RegisterType<A>.InstancePerMatchingOrRootLifetimeScope("TAG");
I haven't tried it myself, but it should work.
Possible solution is to override registration in child lifetime scope.
Sample:
public enum Scopes
{
TestScope
}
public class Test
{
public string Description { get; set; }
}
public class Tester
{
public void DoTest()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Test>()
.OnActivating(args => args.Instance.Description = "FromRoot")
.SingleInstance();
var container = builder.Build();
var scope = container.BeginLifetimeScope(Scopes.TestScope, b => b
.RegisterType<Test>()
.InstancePerMatchingLifetimeScope(Scopes.TestScope)
.OnActivating(args => args.Instance.Description = "FromScope"));
var test1 = container.Resolve<Test>();
Console.WriteLine(test1.Description); //writes FromRoot
var test2 = scope.Resolve<Test>();
Console.WriteLine(test2.Description); //writes FromScope
Console.ReadLine();
}
}
The root container itself is a lifetime scope with the tag name: root, defined in LifetimeScope.RootTag
So you can just do:
using Autofac.Core.Lifetime;
builder.RegisterType<Service>().As<IService>()
.InstancePerMatchingLifetimeScope(LifetimeScope.RootTag, "foobar");