How to Avoid Coupling with an IoC Container

2019-03-28 06:28发布

I'm in the process of developing an extensible framework using DI and IoC. Users must be able override existing functionality within the framework by dropping their own implementations into the container.

How can I allow users to do this without requiring them to know which IoC container I am using?

My current half-way solution is to structure my assemblies as follows:

1) Define abstract assemblies containing only interfaces.

2) Define concrete assemblies which implement these interfaces. Users may define their own to override existing functionality.

3) Define the container bindings in separate assemblies; i.e. one binding assembly per concrete assembly.

This means the concrete assemblies are not coupled with a particular IoC container, and they would be closed against change if I used a different container. However, users are still required to know which container my framework is using in order to write the binding assemblies, and they would need to release new binding assemblies if I changed the IoC container (i.e. from Ninject to Spring).

Am I missing something?

5条回答
相关推荐>>
2楼-- · 2019-03-28 06:30

Common Service Locator is one approach, but it only contains methods for resolving, not for registering.

You may want to have a look at how this is implemented in the agatha-rrsl project. There's a more complete explanation here, but in short:

  • define a container-agnostic interface for registering and resolving types
  • provide implementations for the different containers (or let users submit implementations)

Caveat: you probably won't be able to directly use your container of choice in your library.

查看更多
迷人小祖宗
3楼-- · 2019-03-28 06:37

Common approach is to abstract container with common service locator

Author of MvcExtensions have abstracted IoC away quite successfully.

查看更多
太酷不给撩
4楼-- · 2019-03-28 06:38

Write loosely coupled code. Applications should depend on containers. Frameworks should not.

查看更多
唯我独甜
5楼-- · 2019-03-28 06:48

Two simple options would be to either provide your user with some dictionary where they can register type mappings, or alternatively just provide them with a container interface that provides all of the services you think you're likely to require and allow users to supply their own wrapped containers. Apologies if these don't quite fit your scenario, I primarily use Unity, so I don't know if Ninject, etc. do fancy things Unity doesn't.

查看更多
你好瞎i
6楼-- · 2019-03-28 06:57

I've solved this by using an attribute and provide a scanner class which is used to locate all implementations and the interfaces that they provide.

public class ComponentAttribute : Attribute
{}

// class that should be registered in the container.
[Component]
public class MyService : IMyService
{}

// Contains information for each class that should be 
// registered in the container.
public interface IContainerMapping
{
    public Type ImplementationType {get;}
    public IEnumerable<T> ImplementedServices {get; }
}

public class ComponentProvider
{
    public static IEnumerable<IContainerMapping> Find() 
    {
        var componentType = typeof(ComponentAttribute);
        foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
        {
           if (type.GetCustomAttributes(componentType, false).Count == 0)
              continue;

           var mapping = new ContainerMapping(type);
           List<Type> interfaces new List<Type>();
           foreach (var interfac in type.GetInterfaces())
           {
             //only get our own interfaces.
             if (interface.Assembly != Assembly.GetExecutingAssembly())
               continue;

             interfaces.Add(interfac);
           }

           mapping.ImplementedServices = interfaces;
           yield return mapping;
        }
    }
}

This solution gives the user much flexibility. He can provide his own solution by using the [Component] attribute directly or by using your solution.

What the user should do is something like:

foreach (var mapping in ComponentProvider.Find())
    myContainer.Register(mapping.ImplementationType).As(mapping.ImplementedServices);

I usually create an all-ready solution by providing a MyProject.autofac project which registers everything in my favorite container.

查看更多
登录 后发表回答