This article aims to establish a basic framework to make your library as flexible as possible with respect to Dependency Injection (DI) and Inversion of Control (IoC) libraries such as Autofac. If your library is super simple, this should suffice. If your library is complex and spans multiple assemblies, perhaps this simple solution is not for you.

The Ideal Use Case

You have a library that consists of a single assembly and you want to make it easy for users of your library who also use Dependency Injection to automatically configure all your services.

You have simple services that depend only on other exposed services in your library.

You want to modify as little of your existing code base as possible to facilitate the optional use of Dependency Injection.

The Code

Let's start with the immediately useful code. Be sure to change the namespace to match your own libary.

ServiceAttribute.cs

using System;

namespace MyLibrary
{
    [AttributeUsage(AttributeTargets.Class)]
    internal sealed class ServiceAttribute : Attribute
    {
    }
}

ServiceMap.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace MyLibrary
{
    public struct ServiceMap
    {
        internal ServiceMap(Type concreteType, IEnumerable<Type> interfaceTypes)
        {
            ConcreteType = concreteType;
            InterfaceTypes = new ReadOnlyCollection<Type>(interfaceTypes.ToList());
        }
        
        public Type ConcreteType { get; }
        public IEnumerable<Type> InterfaceTypes { get; } 
    }
}

ServiceLocator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MyLibrary
{
    public static class ServiceLocator
    {
        private static readonly IEnumerable<ServiceMap> Services =
            typeof(ServiceAttribute)
                .Assembly
                .ExportedTypes
                .Where(t => t.GetCustomAttributes<ServiceAttribute>().Any())
                .Select(t => new ServiceMap(t, t.GetInterfaces().Except(new[] {typeof(IDisposable)})))
                .ToArray();

        public static IEnumerable<ServiceMap> Get() => Services;
    }
}

How You Will Use It

All you have to do is add the [Service] attribute to the implementations of all the services you wish to expose. Every interface that is implemented will be mapped. The consumer's DI library will automatically create services as needed.

This won't work if you implement the same interface with multiple implementations, so if this is required by your project, you might need to look into refactoring your service structure or finding a more comprehensive solution.

How End Users Will Use It

All that will be expected of users is to be able to consume the Services property on the static ServiceLocator class and map them to their container of choice.

The ConcreteType property of ServiceMap indicates the implementation of a service, and InterfaceTypes contains all the interface types that are implemented by the concrete type. IDisposable is excluded because it has special meaning in the .NET framework. Most DI libraries have special handling for classes that implement this interface.

Use as an Autofac Module

using MyLibrary;
using Autofac;

namespace MyProgram
{
    public class MyLibraryModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            foreach (var service in ServiceLocator.Get())
            {
                var registration = builder.RegisterType(service.ConcreteType);
                foreach (var type in service.InterfaceTypes)
                    registration.As(type);
            }
        }
    }
}

Conclusion

That's all there is to it! Again, this is not a solution of any kind for a complex problem; this is a simple solution for a simple problem.