user47589
user47589

Reputation:

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?

Upvotes: 5

Views: 411

Answers (2)

fourpastmidnight
fourpastmidnight

Reputation: 4234

A better solution to this problem is to define your contract class properly. It is advised that when you create a class containing contracts for your assembly that the class is private, and abstract:

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

public class Foo : IFoo { ... }

[ContractClassFor(typeof(IFoo))]
private abstract class ContractClassForIFoo : IFoo
{
    public int DoThing(string x)
    {
        Contract.Requires<ArgumentNullException>(x != null);
        throw new NotImplementedException();
    }
}

Now, the class is private, so AutoFac shouldn't be able to see it—but of course it may since it's probably using reflection; but since it's private, it shouldn't attempt to register it. In addition to that, it's abstract, and so can't be instantiated outright anyway. This solves all the problems.

Plus, all methods in contract classes should throw new NotImplementedException();. That way, if you forget to mark it as private or abstract, all the methods throw. You should find out about that really quick during development. Just using degenerate forms of methods may escape your notice.

This is the pattern recommended by the Code Contracts manual and community.

Upvotes: 0

Cyril Durand
Cyril Durand

Reputation: 16192

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.

Upvotes: 1

Related Questions