Search code examples
c#dependency-injectionautofaccode-contracts

Autofac and Contract classes


Suppose we had the following:

[ContractClass(typeof(ContractClassForIFoo))]
public interface IFoo
{
    int DoThing(string x);
}

public class Foo : IFoo { ... }

[ContractClassFor(typeof(IFoo))]
public class ContractClassForIFoo : IFoo
{
    public int DoThing(string x)
    {
        Contract.Requires<ArgumentNullException>(x != null);
        return 0;
    }
}

I am using Autofac to register all of my components that implement IFoo:

builder.RegisterAssemblyTypes(ThisAssembly).As<IFoo>();

When I later resolve my dependencies with:

var dependencies = container.Resolve<IFoo[]>();

I should get all of the classes that implement IFoo except the contract class(es). How do I prevent all of my contract classes from resolving without moving them to a separate assembly entirely?

I can do something like:

builder.RegisterAssemblyTypes(ThisAssembly)
    .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
    .As<IFoo>();

But I would need to do this for each component registration. Something that affects all registrations would be nicer. Is it possible to have a global exclusion on types resolved from Autofac if they have the ContractClassForAttribute attribute?


Solution

  • EDIT As explained in comment by Steven, the ContractClass and ContractClassFor are marked with [Conditional("CONTRACTS_FULL")], this solution may introduce bugs for these attributes. See Steven' comment for better explanation.


    I don't know any mechanisms that allow global filter on registrations registered by the RegisterAssemblyTypes method. The only solution to filter registration using this method is to use the Where method as shown in your code sample.

    When a registration is register inside a ComponentRegistry there is no way to remove it from the registry.

    If you don't want to use the Where method on each registration, you can create an another method.

    public static class ContractClassRegistrationExtensions
    {
        public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> NotContractClass<TLimit, TScanningActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration) where TScanningActivatorData : ScanningActivatorData
        {
            if (registration == null)
            {
                throw new ArgumentNullException("registration");
            }
    
            return registration.Where(t => t.GetCustomAttribute<ContractClassForAttribute>() == null); 
        }
    }
    

    Using this method, instead of

    builder.RegisterAssemblyTypes(ThisAssembly)
           .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null)
           .As<IFoo>();
    

    You will be able to write :

    builder.RegisterAssemblyTypes(ThisAssembly)
           .NotContractClass()
           .As<IFoo>();
    

    It is not a real solution but it is the solution I would use in similar case.

    By the way, if you really want some magic using Autofac, you can implement a IRegistrationSource

    public class FilterRegistrationSource : IRegistrationSource
    {
        private static MethodInfo _createFilteredRegistrationMethod = typeof(FilterRegistrationSource).GetMethod("CreateFilteredRegistration");
    
        public Boolean IsAdapterForIndividualComponents
        {
            get
            {
                return false;
            }
        }
    
        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            IServiceWithType serviceWithType = service as IServiceWithType;
    
            if (serviceWithType == null)
            {
                yield break;
            }
    
            Type serviceType = serviceWithType.ServiceType;
            if (!serviceType.IsClosedTypeOf(typeof(IEnumerable<>)))
            {
                yield break;
            }
            Type elementType = new Type[] { serviceType }.Concat(serviceType.GetInterfaces())
                                          .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                                          .Select(t => t.GetGenericArguments()[0])
                                          .First();
    
            yield return (IComponentRegistration)FilterRegistrationSource._createFilteredRegistrationMethod.MakeGenericMethod(elementType)
                                                                         .Invoke(this, new Object[] { serviceWithType });
        }
    
        public IComponentRegistration CreateFilteredRegistration<T>(IServiceWithType serviceWithType)
        {
            return RegistrationBuilder.ForDelegate((cc, p) => cc.ComponentRegistry
                                                                .RegistrationsFor(serviceWithType.ChangeType(typeof(T)))
                                                                .Where(r => !r.Activator.LimitType.GetCustomAttributes(typeof(ContractClassForAttribute), false).Any())
                                                                .Select(r => r.Activator.ActivateInstance(cc, p))
                                                                .Cast<T>())
                                      .As((Service)serviceWithType)
                                      .CreateRegistration();
    
        }
    }
    

    You can register it this way : builder.RegisterSource(new FilterRegistrationSource())

    I have not tested the performance penalty of this solution, use it with caution.

    Another interesting solution would be to use AOP to customize the way you register your registrations.