Automatically resolve Interface<T> to Implementation<T> in StructureMap (differ only by generic type T)
Asked Answered
T

5

23

I have an interface (IRepository<T>) that is currently being extended for each specific repository, ie: IUserRepository : IRepository<User>.

Each of these interfaces has corresponding concrete classes, ie: UserRepository : Repository<User>, IUserRepository.

These individual repositories don't add any additional functionality, they are all empty interfaces/classes that are used simply to pass the generics around.

I use StructureMap to resolve IUserRepository into UserRepository using a Registry with an assembly scanner and some naming conventions.

I'd like this to move to a way more optimised state, where instead of passing around instances of IUserRepository and getting it resolved to UserRepository, I can pass around IRepository<User> and have it resolved to Repository<User>.

This would remove the need to create these extra empty interfaces and classes.

I can't work out a way to use StructureMap's configuration to setup this generic mapping. Something like this:

For(typeof(IRepository<>).Use(typeof(Repository<>)).WithTheGenericTypeFromTheInterfaceSuppliedAsATypeParameter();

Edit

After getting the first couple of answers, I want to clarify this a bit more.

I don't want to create individual classes for the For bit of the configuration. I want to have the following classes/interfaces in my code:

  • IRepository<T> where T : Entity
  • Repository<T> : IRepository<T> where T : Entity
  • Person : Entity
  • Product : Entity
  • Order : Entity
  • Whatever : Entity

And have the following mappings achieved with convention:

IRepository<Person> => Repository<Person>
IRepository<Product> => Repository<Product>
IRepository<Order> => Repository<Order>
IRepository<Whatever> => Repository<Whatever>

But I do not want to have to create a mapping for each one, ala:

For<IRepository<Person>>().Use<Repository<Person>>();
For<IRepository<Product>>().Use<Repository<Product>>();
For<IRepository<Order>>().Use<Repository<Order>>();
For<IRepository<Whatever>>().Use<Repository<Whatever>>();

I want a single mapping that will work for any IRepository:

For<IRepository<>>().Use<Repository<>>().WithTheSameGenericType();

I would then use this to inject the repositories into services:

public MyService(IRepository<User> userRepository)

And expect that to be resolved to a Repository<User> at runtime.

Telegram answered 25/1, 2011 at 1:0 Comment(2)
Your desired configuration is already real (without the need for WithTheGenericTypeFromTheInterfaceSuppliedAsATypeParameter). Did you try it and it didnt work?Presidency
I must have been doing it wrong - it wouldn't compile at first, so I think I using the generic version of the method (even though when I wrote the question I used the non-generic). One of those mornings.Telegram
T
27

Turns out there is no fancy method to call, or no fancy wiring to do, you just use For and Use (the non generic versions):

public class DataRegistry : Registry
{
    public DataRegistry()
    {
        For(typeof (IRepository<>)).Use(typeof(Repository<>));
    }
}

When I inject a IRepository<Person> it is being resolved as a Repository<Person> now.

I encountered error 104 saying Repository wasn't pluggable for IRepository. This was because Repository was marked abstract. Making it non-abstract fixed that error and it is working as desired.

Telegram answered 25/1, 2011 at 2:19 Comment(4)
That's nice to know, will save a lot of configuration.Deter
With this setup, how would you add a specific method for say the Repository<Person> i.e. AllByLastName()??? Or are you just returning IQueryable?Sunk
Just returning IQueryable. Domain specific logic such as AllByLastName sits in our services layer. Repo has get, delete, query, upsert etc.Telegram
@MichaelShimmins How would you register this: Person : Entity, ISomeInterfaceCampinas
A
7

I suggest you to take a look at the AddAllTypesOf method. I had some similar code and achieved my objectives by using it (and kept the auto register feature working).

In your case, you should just change

For<IRepository<Person>>().Use<Repository<Person>>();
For<IRepository<Product>>().Use<Repository<Product>>();
For<IRepository<Order>>().Use<Repository<Order>>();
For<IRepository<Whatever>>().Use<Repository<Whatever>>();

to

AddAllTypesOf(typeof(IRepository<>));

In the end, your container will be similar to:

return new Container(x =>
        {
            x.Scan(y =>
            {
                y.TheCallingAssembly();
                y.AddAllTypesOf(typeof(IRepository<>));
                y.WithDefaultConventions();
            });
        });
Apocrine answered 7/12, 2011 at 21:53 Comment(2)
Tyron, you solved a very long-running problem in my world. THANK YOU.Tungting
Just to note, there are some moments when the generic class depends upon two or more classes (like the Mappers, for example). In that case, just use the form: y.AddAllTypesOf(typeof(IMapper<,>));Apocrine
D
1

Here is an example. Structure map will allow you to do both also.

 //IRepository
 For<IMemberRepository>().Add<MemberRepository>();

 //IRepository<T>
 For<IRepository<Member>>().Add<MemberRepository>();

Then it is useful to ask for the types by just knowing the generic type at runtime:

Type repositoryType = typeof(IRepository<>).MakeGenericType(modelType);
IocResolve.Resolve(repositoryType);
Deter answered 25/1, 2011 at 1:55 Comment(4)
Thanks - I think my question is a bit unclear. I will update it shortly. I'm trying to find a solution so that I do not need to create a class MemberRepository, but rather have IRepository<Member> mapped to Repository<Member> with a convention.Telegram
How many individual lines of configuration do you imaging you are going to save by this? Do you have that many classes?Deter
Different repositories? Yes - a few. But it is more about not having to create the individual IXXXREpository and XXXRepository classes, since they are empty and are used simply for the generic type resolution. Rather than having to create the entity, create an interface, create a class, edit the registry and then use the new interface, it would be better for everyone to just to create the entity and use IRepository<NewEntity>. A lot less work.Telegram
Worked it out - was trivially easy.Telegram
B
0

Have a look at IRegistrationConvention interface.

public class DomainRegistry : Registry
{
    public DomainRegistry()
        : base()
    {
        Scan(y =>
        {
            y.AssemblyContainingType<IRepository>();
            y.Assembly(Assembly.GetExecutingAssembly().FullName);
            y.With(new RepositoryTypeScanner());
        });

Where RepositoryTypeScanner:

public class RepositoryTypeScanner : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
      ...
Beeswax answered 25/1, 2011 at 1:38 Comment(1)
Thanks - but that doesn't really solve the issue. I'm using a type scanner already, and using naming conventions to wire up any concrete type to its corresponding interface. I'm wanting to do away with all those interfaces and concrete types though and have structuremap automatically pass the generic parameter between IRepository<T> and Repository<T>.Telegram

© 2022 - 2024 — McMap. All rights reserved.