Jimmy Bogard has written a couple of posts here and here discussing the registering partially closed generic types with StructureMap. These posts helped me solve a problem I was having which is a little bit different. To set the stage let me explain what we’re doing. First we have a generic repository interface:
public interface IRepository<TEntity> : IQueryable<TEntity> where TEntity : class
{
TEntity Get(Guid id);
void Save(TEntity entity);
void Delete(TEntity entity);
void DeleteSingle(IQueryable<TEntity> query);
void DeleteMany(IQueryable<TEntity> query);
}
Then we will have an interface for a specific repository that inherits from the generic repository interface and includes convenience methods:
public interface IAccountRepository : IRepository<Account>
{
void DeleteInactiveAccountsOlderThanTwoYears();
void CreateAccountBasedOnExistingAccount(Account account);
// Etc, etc, etc
}
Then we have the concrete class that implements the repository specific interface (The NHibernateRepositoryBase class implements the members defined by IRepository<TEntity> not show below):
public class AccountRepository : NHibernateRepositoryBase<Account>, IAccountRepository
{
public void DeleteInactiveAccountsOlderThanTwoYears() {...};
public void CreateAccountBasedOnExistingAccount(Account account) {...};
// Etc, etc, etc
}
So what I wanted is to scan our persistence assembly for any concrete types (IE: AccountRepository) that implement an interface (IE: IAccountRepository) that derives from a closed generic interface (IE: IRepository<Account>) that is of a particular open generic interface type (IE: IRepository<>). Clear as mud?? :) Simply put, I want to scan for IRepository<> and want to map IAccountRepository to AccountRepository. This is cake with StructureMap using a registration convention. BTW, I’m using StructureMap 2.5.4 and from what I understand you may need this version to do what I’m doing here.
I lifted the GenericConnectionScanner class (Which is what the ConnectImplementationsToTypesClosing convenience method uses) from the StructureMap code as my starting point and modified the logic:
public class DerivedOpenGenericInterfaceConnectionScanner : IRegistrationConvention
{
private readonly Type _openType;
public DerivedOpenGenericInterfaceConnectionScanner(Type openType)
{
_openType = openType;
if (!_openType.IsInterface || !_openType.IsOpenGeneric())
throw new ApplicationException(
"This scanning convention can only be used with open generic interface types");
}
public void Process(Type type, StructureMap.Configuration.DSL.Registry registry)
{
if (!type.IsConcrete()) return;
var derivedTypes = type.GetInterfaces().
Where(i => i.GetInterfaces().
Any(i2 => i2.IsGenericType &&
i2.GetGenericTypeDefinition() == _openType));
if (derivedTypes.Count() > 0) registry.For(derivedTypes.First()).Add(type);
}
}
The constructor takes the open generic interface type. The Process method is called for each type; we only want concrete types. We check the type for interfaces that derive from an interface that has a type definition of our open generic interface type. If one exists we map the derived interface type to the concrete type.
ObjectFactory.Initialize(
x => x.Scan(
config => {
config.AssemblyContainingType(typeof(IRepository<>));
config.With(new DerivedOpenGenericInterfaceConnectionScanner(typeof(IRepository<>)));
}));
var accountRepo = ObjectFactory.GetInstance<IAccountRepository>();
System.Diagnostics.Debug.Assert(accountRepo != null && accountRepo is AccountRepository);
The above initialization scans the assembly containing our open generic interface type and specifies our registration convention using the With() method. That’s all there is to it!