zony.me

Replacing Abp's DI Framework with DryIoc

1. Background

You might ask: Castle Windsor works fine, so why go to all the trouble of replacing it with DryIoc? The answer is performance. DryIoc is a good, lightweight DI framework. The whole project is just two source files, around 10,000 lines in total (PS: most of that is comments).

In the performance benchmarks of the various IoC containers, DryIoc does very well, and that’s why I picked it. The Castle Windsor that Abp uses is very slow when it resolves complex objects, and after switching to DryIoc it got more than 150% faster.

[Note]

This post only changes and tests the .NET Core libraries. I didn’t touch or test the .NET Framework ones.

2. Preparation

Here’s what you need:

  1. A copy of the Abp source code.
  2. A project to test with.
  3. Visual Studio 2017 or Rider.
  4. One .NET developer.

3. Analysis

First, most of what the Abp framework does goes through the IIocManager interface. It’s an abstraction layer that acts as the DI framework’s operation class. Its default implementation uses Castle Windsor to register and resolve components, so all we need to do is make it use a DryIoc container instead.

Second, Abp uses Castle Windsor’s IWindsorContainer object in a lot of places. In most of them it’s used to register components or to hook the component registration event, and we can reimplement all of that ourselves.

Once that’s done, all Abp components can be registered and resolved through DryIoc. To integrate with ASP.NET Core we still need an IServiceProvider adapter, and DryIoc already provides one, so we can just use it.

So the projects we need to change are the core Abp library and the Abp.AspNetCore module. Besides those two, the Abp.EntityFrameworkCore libraries need changes too, because they also use the IWindsorContainer object directly to work with the container.

4. Let’s Get Started

4.1 Changing the Abp Library

Almost everything that needs changing in the Abp library is in the Dependency folder. I’ve covered this folder before. It holds basically all the dependency injection types and interfaces.

Besides the dependency injection types, we also need to change the places where interceptors get attached. Before, when Abp wanted to attach an interceptor to a type, it used the component registration event provided by the IWindsorContainer interface.

First, add a NuGet reference to the DryIoc package in the Abp library. I installed version 3.1.0-preview-06.

4.1.1 Changing IocManager

Let’s start with the IIocManager interface. It’s defined like this:

C#
/// <summary>
/// This interface is used to directly perform dependency injection tasks.
/// </summary>
public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
{
	/// <summary>
	/// Reference to the Castle Windsor Container.
	/// </summary>
	IWindsorContainer IocContainer { get; }

	/// <summary>
	/// Checks whether given type is registered before.
	/// </summary>
	/// <param name="type">Type to check</param>
	new bool IsRegistered(Type type);

	/// <summary>
	/// Checks whether given type is registered before.
	/// </summary>
	/// <typeparam name="T">Type to check</typeparam>
	new bool IsRegistered<T>();
}

It has an IWindsorContainer property, and we change that to IContainer. Once you do this, a pile of errors will show up in the other Abp projects. Don’t panic, we’ll go step by step.

Next, go to IocManager, the implementation of IIocManager. Change the type of IocContainer to IContainer there as well, then look at the constructor, which has the following code:

C#
public IocManager()
{
    IocContainer = CreateContainer();
    _conventionalRegistrars = new List<IConventionalDependencyRegistrar>();

    //Register self!
    IocContainer.Register(
        Component
        .For<IocManager, IIocManager, IIocRegistrar, IIocResolver>()
        .Instance(this)
    );
}

Since IocContainer has changed, we can’t use the CreateContainer() method to create the DryIoc container any more. The self-registration below also uses the IWindsorContainer registration API, so that needs to change too. The updated constructor looks like this:

C#
public IocManager()
{
    // Rules turns on tracking of disposable transients here. It's off by default.
    IocContainer = new Container(Rules.Default.WithTrackingDisposableTransients());
    _conventionalRegistrars = new List<IConventionalDependencyRegistrar>();

    // Register itself
    IocContainer.UseInstance(typeof(IocManager),this);
    IocContainer.UseInstance(typeof(IIocManager),this);
    IocContainer.UseInstance(typeof(IIocRegistrar),this);
    IocContainer.UseInstance(typeof(IIocResolver),this);
}

Next, look at the methods that still show errors. One is ApplyLifestyle, a private helper for the registration methods. It converts the lifestyle Abp defines into the lifetime constant of the specific IoC container. It used to return a ComponentRegistration<T>, which is a Castle Windsor class, so it needs to change. After the change it looks like this:

C#
private static IReuse ApplyLifestyle(DependencyLifeStyle lifeStyle)
{
	switch (lifeStyle)
	{
		case DependencyLifeStyle.Transient:
			return Reuse.Transient;;
		case DependencyLifeStyle.Singleton:
			return Reuse.Singleton;
		default:
			return Reuse.Transient;
	}
}

After that, what’s left is changing the registration and resolve methods. The registration and resolve methods on IocManager just call the underlying IoC container, and DryIoc’s IContainer provides almost everything IWindsorContainer does. Only a few methods are different.

Here are some of the updated registration and resolve methods (see the code on GitHub for all of them):

C#
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
	IocContainer.Register(type,ApplyLifestyle(lifeStyle));
}

// ... other methods
public void Register(Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
	if (type == impl)
	{
		// The made parameter tells DryIoc to prefer the constructor whose arguments it can resolve
		IocContainer.Register(type,impl,ApplyLifestyle(lifeStyle),
			made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments));
		RegisterTypeEventHandler?.Invoke(this,type,impl);
	}
	else
	{
		IocContainer.RegisterMany(new[]
			{
				type,
				impl
			},
			impl,
			made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments),
			reuse: ApplyLifestyle(lifeStyle));
		
		RegisterTypeEventHandler?.Invoke(this,type,impl);
		RegisterTypeEventHandler?.Invoke(this,impl,impl);
	}
}

// ... other methods

One thing to watch out for is the resolve method that takes arguments, public T Resolve<T>(object argumentsAsAnonymousType). Unlike Castle Windsor, DryIoc only accepts an array of arguments, not an anonymous object holding a set of arguments. So we need to change the parameter to object[]. Since this changes the method signature, we also have to change the signatures of the resolve methods that take arguments in ScopedIocResolver, IIocResolver and IocResolverExtensions.

C#
public T Resolve<T>(object[] argumentsAsAnonymousType)
{
    return IocContainer.Resolve<T>(argumentsAsAnonymousType);
}

Another one is public T[] ResolveAll<T>(), which calls IocContainer.ResolveAll internally. DryIoc doesn’t have that method, but it has ResolveMany(), which does the same thing. Here are all the overloads of ResolveAll() after the change:

C#
///<inheritdoc/>
public T[] ResolveAll<T>()
{
	return IocContainer.ResolveMany<T>().ToArray();
}

///<inheritdoc/>
public T[] ResolveAll<T>(object[] argumentsAsAnonymousType)
{
	return IocContainer.ResolveMany<T>(args:argumentsAsAnonymousType).ToArray();
}

///<inheritdoc/>
public object[] ResolveAll(Type type)
{
	return IocContainer.ResolveMany(type).ToArray();
}

///<inheritdoc/>
public object[] ResolveAll(Type type, object[] argumentsAsAnonymousType)
{
	return IocContainer.ResolveMany(type, args:argumentsAsAnonymousType).ToArray();
}

There’s also Release for releasing objects. DryIoc has no release method, so the only option is to call the object’s Dispose() method explicitly.

C#
public void Release(object obj)
{
    if(obj is IDisposable disposeObj)
    {
        disposeObj.Dispose();
    }
}

After these changes, there’s still one place showing an error:

C#
public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config)
{
	var context = new ConventionalRegistrationContext(assembly, this, config);

	foreach (var registerer in _conventionalRegistrars)
	{
		registerer.RegisterAssembly(context);
	}

	if (config.InstallInstallers)
	{
         // This still uses a method of IWindsorContainer
		IocContainer.Install(FromAssembly.Instance(assembly));
	}
}

If you’ve read my earlier posts on the Abp source code, you’ll know that what Install() does is quite simple. It goes through the types of the given assembly, looks for objects that implement the IWindsorInstaller interface, and calls their Install() method. Inside that Install(), the registration is usually done through the IIocContainer or IIocManager object that gets passed in.

Here we can write two extension methods Intsall() for IocManager, plus an IDryIocInstaller interface, to get the same behavior.

C#
namespace Abp.Dependency
{
    public interface IDryIocInstaller
    {
        void Install(IIocManager iocManager);
    }
}

The extension methods:

C#
using System;
using System.Linq;
using System.Reflection;

namespace Abp.Dependency
{
    public static class IocManagerExtensions
    {
        public static void Install(this IIocManager iocManager,IDryIocInstaller installer)
        {
            installer.Install(iocManager);
        }

        public static void Install(this IIocManager iocManager, Assembly assembly)
        {
            // Get all the installer types in the given assembly
            var installers = assembly.GetTypes().Where(type => type.GetInterfaces().Any(@interface => @interface == typeof(IDryIocInstaller)));

            // Go through the types, create each one with Activator and call it
            foreach (var installer in installers)
            {
                (Activator.CreateInstance(installer) as IDryIocInstaller)?.Install(iocManager);
            }
        }
    }
}

Now go back to the place that showed the error at the start, and change its Install() call to use our new extension method.

C#
public void RegisterAssemblyByConvention(Assembly assembly, ConventionalRegistrationConfig config)
{
    var context = new ConventionalRegistrationContext(assembly, this, config);

    foreach (var registerer in _conventionalRegistrars)
    {
        registerer.RegisterAssembly(context);
    }

    if (config.InstallInstallers)
    {
        // Call the extension method we wrote earlier
        this.Install(assembly);
    }
}

4.1.2 Changing the Dependency Injection Helper Interfaces

The Abp library provides two interfaces (ITransientDependency and ISingletonDependency) that let you register an object quickly. Together with the AddConventionalRegistrar() and RegisterAssemblyByConvention() methods of IocManager, they can register all the types in an assembly that match the convention. (PS: this flow is a lot like the Installer approach from before)

With Castle Windsor, Abp didn’t have to do much to get this working. DryIoc doesn’t provide such high-level features, but the idea isn’t complicated: scan all the types in the assembly and check them one by one.

The original BasicConventionalRegistrar automatically registered types that implement ITransientDependency, ISingletonDependency or IInterceptor. So we end up with the following implementation:

C#
using System;
using System.Linq;
using System.Reflection;
using Abp.Extensions;
using Castle.DynamicProxy;

namespace Abp.Dependency
{
    public class AssemblyType
    {
        public Type ServiceType { get; set; }

        public Type ImplType { get; set; }
    }
    
    /// <summary>
    /// This class registers types that implement <see cref="ITransientDependency"/> and <see cref="ISingletonDependency"/>.
    /// </summary>
    public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
    {
        public void RegisterAssembly(IConventionalRegistrationContext context)
        {
            // Register transient objects
            var waitRegisterTransient = GetTypes<ITransientDependency>(context.Assembly).ToList();

            foreach (var transientType in waitRegisterTransient)
            {
                context.IocManager.RegisterIfNot(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient);
            }
            
            // Register singleton objects
            var waitRegisterSingleton = GetTypes<ISingletonDependency>(context.Assembly).ToList();

            foreach (var singletonType in waitRegisterSingleton)
            {
                context.IocManager.RegisterIfNot(singletonType.ServiceType,singletonType.ImplType,DependencyLifeStyle.Singleton);
            }
            
            // Register Castle.DynamicProxy interceptors
            var waitRegisterInterceptor = GetTypes<IInterceptor>(context.Assembly).ToList();

            foreach (var interceptorType in waitRegisterInterceptor)
            {
                context.IocManager.RegisterIfNot(interceptorType.ServiceType,interceptorType.ImplType,DependencyLifeStyle.Transient);
            }
        }

        private ParallelQuery<AssemblyType> GetTypes<TInterface>(Assembly assembly)
        {
            Type GetServiceType(Type type)
            {
                var interfaces = type.GetInterfaces().Where(i => i != typeof(TInterface));

                // Prefer the interface whose name matches the type name with the I removed
                var defaultInterface = interfaces.FirstOrDefault(i => type.Name.Equals(i.Name.RemovePreFix("I")));
                if (defaultInterface != null) return defaultInterface;
                if (interfaces.FirstOrDefault() != null) return interfaces.FirstOrDefault();
                return type;
            }

            return assembly.GetTypes()
                .AsParallel()
                .Where(type => typeof(TInterface).IsAssignableFrom(type))
                .Where(type => type.GetInterfaces().Any() && !type.IsInterface)
                .Where(type => !type.IsGenericTypeDefinition)
                .Where(type => !type.IsAbstract)
                .Select(type => new AssemblyType
                {
                    ServiceType = GetServiceType(type),
                    ImplType = type
                });
        }
    }
}

In our new registration convention, the core of it is the GetTypes() method, which has the fairly complicated checking logic. The transient and singleton registration is just a call to the registration methods of the IIocManager interface.

4.1.3 Binding Interceptors

We’re no longer using Castle Windsor, so how do we use interceptors, and how do we bind them to types?

The DryIoc docs say that its interception is also built on Castle Dynamic Proxy, so all we need is a static extension class as a helper.

C#
using System;
using System.Linq;
using Castle.DynamicProxy;
using DryIoc;
using ImTools;

public static class DryIocInterception
{
    static readonly DefaultProxyBuilder ProxyBuilder = new DefaultProxyBuilder();

    public static void Intercept(this IRegistrator registrator,Type serviceType,Type interceptorType,Type implType, object serviceKey = null)
    {
        // Check whether the type passed in is an interface or a class, so we can build the proxy type
        Type proxyType;
        if (serviceType.IsInterface())
            proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(
                serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
        else if (serviceType.IsClass())
            proxyType = ProxyBuilder.CreateClassProxyTypeWithTarget(
                serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
        else
            throw new ArgumentException(
                $"Intercepted service type {serviceType} is not a supported, cause it is nor a class nor an interface");

        // Create the DryIoc decorator
        var decoratorSetup = serviceKey == null
            ? Setup.DecoratorWith(useDecorateeReuse: true)
            : Setup.DecoratorWith(r => serviceKey.Equals(r.ServiceKey), useDecorateeReuse: true);

        // Replace the original registration so the interface resolves to the new proxy type
        registrator.Register(serviceType, proxyType,
            made: Made.Of((Type type) => type.GetConstructors().SingleOrDefault(c => c.GetParameters().Length != 0), 
                Parameters.Of.Type<IInterceptor[]>(interceptorType.MakeArrayType()),
                // Don't leave this out, or property injection won't work
                PropertiesAndFields.Auto),
            setup: decoratorSetup);
    }
    
    public static void Intercept<TService,TImplType, TInterceptor>(this IRegistrator registrator, object serviceKey = null) 
        where TInterceptor : class, IInterceptor
    {
        Intercept(registrator,typeof(TService),typeof(TInterceptor),typeof(TImplType),serviceKey);
    }
}

You’ll see how this extension class is used later on.

4.1.4 Binding the Interceptor Registrars to the Event

When did Abp originally bind its interceptors to concrete types? It was when Castle Windsor registered a component: each interceptor registrar listened to the component registration event. When the event fired, each Abp interceptor registrar ran a series of checks to decide which interceptor the current type should be bound to.

Abp has 5 built-in interceptors: unit of work, parameter validation, authorization, audit logging and entity history. All five are set up when AbpBootstrapper runs its create method, and they listen to the component registration event at that point.

The problem now is that we don’t use Castle Windsor, so we can’t use IWindsorContainer to listen to the component registration event. DryIoc doesn’t have such an event either, so we move it up into IocManager: whenever one of the registration methods of IocManager is called, we raise an event explicitly to notify the interceptor registrars.

First go to the IIocManager interface and add a public delegate property. The delegate definition is below as well.

The delegate definition:

C#
using System;

namespace Abp.Dependency
{
    public delegate void RegisterTypeEventHandler(IIocManager iocManager, Type registerType,Type implementationType);
}

The property added to IIocManager:

C#
using System;
using DryIoc;

namespace Abp.Dependency
{
    /// <summary>
    /// Dependency injection container manager.
    /// This interface is used to perform injection operations.
    /// </summary>
    public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
    {
        IContainer IocContainer { get; }

        new bool IsRegistered(Type type);

        new bool IsRegistered<T>();

        event RegisterTypeEventHandler RegisterTypeEventHandler;
    }
}

After that, we raise this event explicitly inside the Register() methods of IocManager.

C#
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
	IocContainer.Register(type,ApplyLifestyle(lifeStyle),
		made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments));
	RegisterTypeEventHandler?.Invoke(this,type,type);
}

Like this, the event fires every time a component is registered. The Initialize() method of each registrar receives an IIocManager object, so all we need to do is change the original event subscription to our own event.

Here’s the unit of work interceptor registrar as an example:

C#
using System.Linq;
using System.Reflection;
using Abp.Dependency;
using Castle.Core;
using Castle.MicroKernel;

namespace Abp.Domain.Uow
{
    /// <summary>
    /// This class is used to register interceptor for needed classes for Unit Of Work mechanism.
    /// </summary>
    internal static class UnitOfWorkRegistrar
    {
        /// <summary>
        /// Initializes the registerer.
        /// </summary>
        /// <param name="iocManager">IOC manager</param>
        public static void Initialize(IIocManager iocManager)
        {
            iocManager.RegisterTypeEventHandler += (manager, type, implementationType) =>
            {
                var implType = implementationType.GetTypeInfo();

                HandleTypesWithUnitOfWorkAttribute(implType,manager);
                HandleConventionalUnitOfWorkTypes(iocManager, implType);
            };
        }

        private static void HandleTypesWithUnitOfWorkAttribute(TypeInfo implementationType,IIocManager iocManager)
        {
            if (IsUnitOfWorkType(implementationType) || AnyMethodHasUnitOfWork(implementationType))
            {
                // This uses the extension method we wrote above
                iocManager.IocContainer.Intercept(implementationType,typeof(UnitOfWorkInterceptor));
            }
        }

        private static void HandleConventionalUnitOfWorkTypes(IIocManager iocManager, TypeInfo implementationType)
        {
            if (!iocManager.IsRegistered<IUnitOfWorkDefaultOptions>())
            {
                return;
            }

            var uowOptions = iocManager.Resolve<IUnitOfWorkDefaultOptions>();

            if (uowOptions.IsConventionalUowClass(implementationType.AsType()))
            {
                // This uses the extension method we wrote above
                iocManager.IocContainer.Intercept(implementationType,typeof(UnitOfWorkInterceptor));
            }
        }

        private static bool IsUnitOfWorkType(TypeInfo implementationType)
        {
            return UnitOfWorkHelper.HasUnitOfWorkAttribute(implementationType);
        }

        private static bool AnyMethodHasUnitOfWork(TypeInfo implementationType)
        {
            return implementationType
                .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Any(UnitOfWorkHelper.HasUnitOfWorkAttribute);
        }
    }
}

Follow the same steps for the rest of the interceptor registrars.

4.1.5 Finishing Up

If you’ve done all of the above, there are only a few small errors left, in AbpBootstrapper and AbpKernelModule.

Let’s see what errors AbpBootstrapper still shows and fix them.

C#
// First place
public virtual void Initialize()
{
    ResolveLogger();

    try
    {
        RegisterBootstrapper();
        
        // IocManager.IocContainer.Install(new AbpCoreInstaller());
        // This is still IWindsorContainer's Install method, change it to the new one
        IocManager.Install(new AbpCoreInstaller());

        IocManager.Resolve<AbpPlugInManager>().PlugInSources.AddRange(PlugInSources);
        IocManager.Resolve<AbpStartupConfiguration>().Initialize();

        _moduleManager = IocManager.Resolve<AbpModuleManager>();
        _moduleManager.Initialize(StartupModule);
        _moduleManager.StartModules();
    }
    catch (Exception ex)
    {
        _logger.Fatal(ex.ToString(), ex);
        throw;
    }
}

It still reports an error, so go to AbpCoreInstaller, change its interface from IWindsorInstaller to IDryIocInstaller and reimplement the interface method.

C#
using Abp.Application.Features;
using Abp.Auditing;
using Abp.BackgroundJobs;
using Abp.Configuration.Startup;
using Abp.Domain.Uow;
using Abp.EntityHistory;
using Abp.Localization;
using Abp.Modules;
using Abp.Notifications;
using Abp.PlugIns;
using Abp.Reflection;
using Abp.Resources.Embedded;
using Abp.Runtime.Caching.Configuration;
using DryIoc;

namespace Abp.Dependency.Installers
{
    /// <summary>
    /// Installer for the core ABP framework classes
    /// This class registers the core components of the ABP framework
    /// </summary>
    internal class AbpCoreInstaller : IDryIocInstaller
    {
        public void Install(IIocManager iocManager)
        {
            iocManager.IocContainer.RegisterMany(new[] {typeof(IUnitOfWorkDefaultOptions), typeof(UnitOfWorkDefaultOptions)}, typeof(UnitOfWorkDefaultOptions), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(INavigationConfiguration), typeof(NavigationConfiguration)}, typeof(NavigationConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(ILocalizationConfiguration), typeof(LocalizationConfiguration)}, typeof(LocalizationConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAuthorizationConfiguration), typeof(AuthorizationConfiguration)}, typeof(AuthorizationConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IValidationConfiguration), typeof(ValidationConfiguration)}, typeof(ValidationConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IFeatureConfiguration), typeof(FeatureConfiguration)}, typeof(FeatureConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(ISettingsConfiguration), typeof(SettingsConfiguration)}, typeof(SettingsConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IModuleConfigurations), typeof(ModuleConfigurations)}, typeof(ModuleConfigurations), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IEventBusConfiguration), typeof(EventBusConfiguration)}, typeof(EventBusConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IMultiTenancyConfig), typeof(MultiTenancyConfig)}, typeof(MultiTenancyConfig), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(ICachingConfiguration), typeof(CachingConfiguration)}, typeof(CachingConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAuditingConfiguration), typeof(AuditingConfiguration)}, typeof(AuditingConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IBackgroundJobConfiguration), typeof(BackgroundJobConfiguration)}, typeof(BackgroundJobConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(INotificationConfiguration), typeof(NotificationConfiguration)}, typeof(NotificationConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IEmbeddedResourcesConfiguration), typeof(EmbeddedResourcesConfiguration)}, typeof(EmbeddedResourcesConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpStartupConfiguration), typeof(AbpStartupConfiguration)}, typeof(AbpStartupConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IEntityHistoryConfiguration), typeof(EntityHistoryConfiguration)}, typeof(EntityHistoryConfiguration), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(ITypeFinder), typeof(TypeFinder)}, typeof(TypeFinder), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpPlugInManager), typeof(AbpPlugInManager)}, typeof(AbpPlugInManager), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAbpModuleManager), typeof(AbpModuleManager)}, typeof(AbpModuleManager), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(IAssemblyFinder), typeof(AbpAssemblyFinder)}, typeof(AbpAssemblyFinder), Reuse.Singleton);
            iocManager.IocContainer.RegisterMany(new[] {typeof(ILocalizationManager), typeof(LocalizationManager)}, typeof(LocalizationManager), Reuse.Singleton);
        }
    }
}

AbpBootstrapper has one more problem, which is also a use of a method from IWindsorContainer. Switch it to the DryIoc one.

C#
private void RegisterBootstrapper()
{
    if (!IocManager.IsRegistered<AbpBootstrapper>())
    {
        //                IocManager.IocContainer.Register(
        //                    Component.For<AbpBootstrapper>().Instance(this)
        //                    );
        IocManager.IocContainer.UseInstance(this);
    }
}

The second problem is in AbpKernelModule. It’s the same kind of error as in the previous type. The first is a call to the old Install method, with an installer that doesn’t inherit from IDryIocInstaller. The other is the use of a registration method from IWindsorContainer.

C#
public override void Initialize()
{
    foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
    {
        replaceAction();
    }

    //            IocManager.IocContainer.Install(new EventBusInstaller(IocManager));
    IocManager.Install(new EventBusInstaller(IocManager));

    IocManager.Register(typeof(IOnlineClientManager<>), typeof(OnlineClientManager<>), DependencyLifeStyle.Singleton);

    IocManager.RegisterAssemblyByConvention(typeof(AbpKernelModule).GetAssembly(),
                                            new ConventionalRegistrationConfig
                                            {
                                                InstallInstallers = false
                                            });
}

The changes to EventBusInstaller:

C#
using System.Reflection;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Events.Bus.Factories;
using Abp.Events.Bus.Handlers;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using DryIoc;

namespace Abp.Events.Bus
{
    /// <summary>
    /// Installs event bus system and registers all handlers automatically.
    /// </summary>
    internal class EventBusInstaller : IDryIocInstaller
    {
        private readonly IIocResolver _iocResolver;
        private readonly IEventBusConfiguration _eventBusConfiguration;
        private IEventBus _eventBus;

        public EventBusInstaller(IIocResolver iocResolver)
        {
            _iocResolver = iocResolver;
            _eventBusConfiguration = iocResolver.Resolve<IEventBusConfiguration>();
        }
        
        public void Install(IIocManager iocManager)
        {
            if (_eventBusConfiguration.UseDefaultEventBus)
            {
                iocManager.IocContainer.UseInstance<IEventBus>(EventBus.Default);
            }
            else
            {
                iocManager.IocContainer.Register<IEventBus,EventBus>(Reuse.Singleton);
            }

            _eventBus = iocManager.Resolve<IEventBus>();
            iocManager.RegisterTypeEventHandler += (manager, type, implementationType) =>
            {
                if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(implementationType))
                {
                    return;
                }

                var interfaces = implementationType.GetTypeInfo().GetInterfaces();
                foreach (var @interface in interfaces)
                {
                    if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface))
                    {
                        continue;
                    }

                    var genericArgs = @interface.GetGenericArguments();
                    if (genericArgs.Length == 1)
                    {
                        _eventBus.Register(genericArgs[0], new IocHandlerFactory(_iocResolver, implementationType));
                    }
                }
            };
        }
    }
}

Another change:

C#
private void RegisterMissingComponents()
{
    if (!IocManager.IsRegistered<IGuidGenerator>())
    {
        //                IocManager.IocContainer.Register(
        //                    Component
        //                        .For<IGuidGenerator, SequentialGuidGenerator>()
        //                        .Instance(SequentialGuidGenerator.Instance)
        //                );
        IocManager.IocContainer.UseInstance<IGuidGenerator>(SequentialGuidGenerator.Instance);
        IocManager.IocContainer.UseInstance<SequentialGuidGenerator>(SequentialGuidGenerator.Instance);
    }

    IocManager.RegisterIfNot<IUnitOfWork, NullUnitOfWork>(DependencyLifeStyle.Transient);
    IocManager.RegisterIfNot<IAuditingStore, SimpleLogAuditingStore>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<IPermissionChecker, NullPermissionChecker>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<IRealTimeNotifier, NullRealTimeNotifier>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<INotificationStore, NullNotificationStore>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<IUnitOfWorkFilterExecuter, NullUnitOfWorkFilterExecuter>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<IClientInfoProvider, NullClientInfoProvider>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<ITenantStore, NullTenantStore>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<ITenantResolverCache, NullTenantResolverCache>(DependencyLifeStyle.Singleton);
    IocManager.RegisterIfNot<IEntityHistoryStore, NullEntityHistoryStore>(DependencyLifeStyle.Singleton);

    if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
    {
        IocManager.RegisterIfNot<IBackgroundJobStore, InMemoryBackgroundJobStore>(DependencyLifeStyle.Singleton);
    }
    else
    {
        IocManager.RegisterIfNot<IBackgroundJobStore, NullBackgroundJobStore>(DependencyLifeStyle.Singleton);
    }
}

4.1.6 Testing

After these changes, create a new console app, reference this Abp library project, and run the following code to test it.

C#
using System;
using Abp;
using Abp.Modules;
using Abp.Runtime.Session;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // Test the Abp framework
            using (var bootstarp = AbpBootstrapper.Create<StartupModule>())
            {
                bootstarp.Initialize();
                
                // Resolve IAbpSession to check that injection works
                var session = bootstarp.IocManager.Resolve<IAbpSession>();

                if (session != null && session is ClaimsAbpSession claimsSession)
                {
                    Console.WriteLine("当前 Session 已经成功被注入为 ClaimAbpSession");
                }
            }

            Console.ReadLine();
        }
    }

    [DependsOn(typeof(AbpKernelModule))]
    public class StartupModule : AbpModule
    {
        
    }
}
1542620303834

After testing the Abp library, basically all Abp components are now registered and resolved through DryIoc. But changing Abp alone isn’t enough. Besides the Abp core library, the thing we use most is database access. Some code in Abp.EntityFrameworkCore and Abp.EntityFramework.Common used IWindsorContainer directly to register and resolve things, so we have to fix the errors there too.

4.2.1 Repository Registration

In the EfGenericRepositoryRegistrar type of the Abp.EntityFramework.Common library, there’s a component registration call on IWindsorContainer that registers the generic IRepository<,> repositories. The code below shows the result after the change:

C#
private void RegisterForDbContext(
	Type dbContextType, 
	IIocManager iocManager,
	Type repositoryInterface,
	Type repositoryInterfaceWithPrimaryKey,
	Type repositoryImplementation,
	Type repositoryImplementationWithPrimaryKey)
{
	foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType))
	{
		var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
		if (primaryKeyType == typeof(int))
		{
			var genericRepositoryType = repositoryInterface.MakeGenericType(entityTypeInfo.EntityType);
			if (!iocManager.IsRegistered(genericRepositoryType))
			{
				var implType = repositoryImplementation.GetGenericArguments().Length == 1
					? repositoryImplementation.MakeGenericType(entityTypeInfo.EntityType)
					: repositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType,
						entityTypeInfo.EntityType);

//                        iocManager.IocContainer.Register(
//                            Component
//                                .For(genericRepositoryType)
//                                .ImplementedBy(implType)
//                                .Named(Guid.NewGuid().ToString("N"))
//                                .LifestyleTransient()
//                        );
				iocManager.IocContainer.Register(genericRepositoryType,implType,Reuse.Transient);
			}
		}

		var genericRepositoryTypeWithPrimaryKey = repositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType,primaryKeyType);
		if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey))
		{
			var implType = repositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2
				? repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType)
				: repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType);

//                    iocManager.IocContainer.Register(
//                        Component
//                            .For(genericRepositoryTypeWithPrimaryKey)
//                            .ImplementedBy(implType)
//                            .Named(Guid.NewGuid().ToString("N"))
//                            .LifestyleTransient()
//                    );
			iocManager.IocContainer.Register(genericRepositoryTypeWithPrimaryKey,implType,Reuse.Transient);
		}
	}
}

After this change, Abp.EntityFramework.Common should compile.

4.2.2 Changing the DbContext Configuration Class

The AbpEfCoreConfiguration type also uses the IWindsorContainer interface. Change it like this:

C#
using System;
using Abp.Dependency;
using Castle.MicroKernel.Registration;
using DryIoc;
using Microsoft.EntityFrameworkCore;

namespace Abp.EntityFrameworkCore.Configuration
{
    public class AbpEfCoreConfiguration : IAbpEfCoreConfiguration
    {
        private readonly IIocManager _iocManager;

        public AbpEfCoreConfiguration(IIocManager iocManager)
        {
            _iocManager = iocManager;
        }

        public void AddDbContext<TDbContext>(Action<AbpDbContextConfiguration<TDbContext>> action) 
            where TDbContext : DbContext
        {
//            _iocManager.IocContainer.Register(
//                Component.For<IAbpDbContextConfigurer<TDbContext>>().Instance(
//                    new AbpDbContextConfigurerAction<TDbContext>(action)
//                ).IsDefault()
//            );
            _iocManager.IocContainer.UseInstance<IAbpDbContextConfigurer<TDbContext>>(new AbpDbContextConfigurerAction<TDbContext>(action));
        }
    }
}

4.2.3 Changing the EFCore Module

This error is in the Initialize() method of the AbpEntityFrameworkCoreModule module, and again it comes from using the registration method of IWndsorContainer.

C#
public override void Initialize()
{
    IocManager.RegisterAssemblyByConvention(typeof(AbpEntityFrameworkCoreModule).Assembly);

//            IocManager.IocContainer.Register(
//                Component.For(typeof(IDbContextProvider<>))
//                    .ImplementedBy(typeof(UnitOfWorkDbContextProvider<>))
//                    .LifestyleTransient()
//                );
    IocManager.IocContainer.Register(typeof(IDbContextProvider<>),typeof(UnitOfWorkDbContextProvider<>),Reuse.Transient);

    RegisterGenericRepositoriesAndMatchDbContexes();
}

The other error is inside the RegisterGenericRepositoriesAndMatchDbContexes() method:

C#
private void RegisterGenericRepositoriesAndMatchDbContexes()
{
    // ... other code
	using (IScopedIocResolver scope = IocManager.CreateScope())
	{
		foreach (var dbContextType in dbContextTypes)
		{
			Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName);

			scope.Resolve<IEfGenericRepositoryRegistrar>().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default);

//                    IocManager.IocContainer.Register(
//                        Component.For<ISecondaryOrmRegistrar>()
//                            .Named(Guid.NewGuid().ToString("N"))
//                            .Instance(new EfCoreBasedSecondaryOrmRegistrar(dbContextType, scope.Resolve<IDbContextEntityFinder>()))
//                            .LifestyleTransient()
//                    );
			IocManager.IocContainer.UseInstance<ISecondaryOrmRegistrar>(new EfCoreBasedSecondaryOrmRegistrar(dbContextType,
				scope.Resolve<IDbContextEntityFinder>()));
		}

		scope.Resolve<IDbContextTypeMatcher>().Populate(dbContextTypes);
	}
}

4.2.4 Changing the DbContext Resolver

The problem in this resolver is different from the earlier ones. The error here is because constructor arguments have to be passed in when constructing the DbContext. With our earlier changes, Resolve() now takes an object[] array instead of an object, so a small change is needed here.

C#
using Abp.Dependency;
using Abp.EntityFramework;
using Abp.EntityFrameworkCore.Configuration;
using Microsoft.EntityFrameworkCore;
using System;
using System.Data.Common;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.Linq;

namespace Abp.EntityFrameworkCore
{
    public class DefaultDbContextResolver : IDbContextResolver, ITransientDependency
    {
		// ... other code
	
        public TDbContext Resolve<TDbContext>(string connectionString, DbConnection existingConnection)
            where TDbContext : DbContext
        {
		
			// ... other code

            try
            {
                if (isAbstractDbContext)
                {
//                    return (TDbContext) _iocResolver.Resolve(concreteType, new
//                    {
//                        options = CreateOptionsForType(concreteType, connectionString, existingConnection)
//                    });
                    
                    return (TDbContext) _iocResolver.Resolve(concreteType, new object[]
                    {
                        CreateOptionsForType(concreteType, connectionString, existingConnection)
                    });
                }

//                return _iocResolver.Resolve<TDbContext>(new
//                {
//                    options = CreateOptions<TDbContext>(connectionString, existingConnection)
//                });

                return _iocResolver.Resolve<TDbContext>(new object[]
                {
                    CreateOptions<TDbContext>(connectionString, existingConnection)
                });
            }
            catch (Castle.MicroKernel.Resolvers.DependencyResolverException ex)
            {
				// ... other code
            }
			
			// ... other code
        }

		// ... other code
    }
}

With that, the changes to the EFCore libraries are done.

So far we’ve made a few not-so-big changes to the Abp core library and the EF Core library. The only thing left is the Abp.AspNetCore library. .NET Core has its own DI framework. As I mentioned in the earlier source code analysis, if you change the return value of the ConfigureService() method of the Startup class to IServiceProvider, you can replace the built-in DI framework with another one.

In the AbpServiceCollectionExtensions extension class of the original Abp.AspNetCore library, you can see this code:

C#
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
	where TStartupModule : AbpModule
{
	var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);

	ConfigureAspNetCore(services, abpBootstrapper.IocManager);

	return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
}

Here Abp uses the WindsorRegistrationHelper class to create and return an IServiceProvider. Does DryIoc give us something similar? It does. DryIoc provides an adapter in DryIoc.Microsoft.DependencyInjection, which can create an IServiceProvier based on DryIoc to replace the default DI framework.

First add the DryIoc.Microsoft.DependencyInjection NuGet package to the Abp.AspNetCore library, then edit the method above:

C#
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
	where TStartupModule : AbpModule
{
	var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);

	ConfigureAspNetCore(services, abpBootstrapper.IocManager);

	var newContainer = new Container(rules =>
			rules.WithAutoConcreteTypeResolution())
		.WithDependencyInjectionAdapter(services);
	
	abpBootstrapper.IocManager.InitializeInternalContainer(newContainer);
	
	return abpBootstrapper.IocManager.IocContainer.BuildServiceProvider();
}

4.3.1 Automatic Registration of View Components and Other Components

Besides the problem above, the Abp.AspNetCore library has a registrar, AbpAspNetCoreConventionalRegistrar, which also uses the registration method of the IWindsorContainer interface. That needs to change too.

C#
using System.Linq;
using Abp.Dependency;
using Microsoft.AspNetCore.Mvc;

namespace Abp.AspNetCore
{
    public class AbpAspNetCoreConventionalRegistrar : IConventionalDependencyRegistrar
    {
        public void RegisterAssembly(IConventionalRegistrationContext context)
        {
            //ViewComponents
            var types = context.Assembly.GetTypes()
                .AsParallel()
                .Where(type => typeof(ViewComponent).IsAssignableFrom(type))
                .Where(type => !type.IsGenericTypeDefinition)
                .Where(type => !type.IsAbstract)
                .AsSequential();

            foreach (var type in types)
            {
                context.IocManager.Register(type);
            }
        }
    }
}

After that, create 4 new projects: AspNetCoreApp (web project), AspNetCoreApp.Core (library), AspNetCore.Application (library) and AspNetCoreApp.EntityFrameworkCore (library), and set up the dependencies between them.

4.3.2 The IServiceProvider Adapter

First, change the ConfigureService() method and the Configure() method in AspNetCoreApp like this:

C#
using System;
using Abp.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCoreApp
{
    public class Startup
    {
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            return services.AddAbp<AspNetCoreAppModule>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
            app.UseAbp(op=>op.UseCastleLoggerFactory = false);
        }
    }
}

If nothing goes wrong, you’ll get this exception:

1542627145106

The exception says it can’t resolve the Microsoft.AspNetCore.Hosting.Internal.WebHostOptions object. That means our DryIoc container doesn’t have the objects that MVC registered during service initialization.

I put a breakpoint inside AddAbp<TStartupModule>() to check whether the objects registered inside ConfigureAspNetCore() are in the IContainer. They’re not.

1542627667706

After some testing, I found that the MVC components are only picked up correctly if you new up a fresh Container and call WithDependencyInjectionAdapter() on it.

Result:

1542629020349

Then the IocContainer inside IocManager has to be set to the newContainer created here, but the IocContainer property defined by the IIocManager interface is read-only. So I added an InitializeInternalContainer() method to the IIocManager interface to initialize the IocContainer property.

C#
public interface IIocManager : IIocRegistrar, IIocResolver, IDisposable
{

	// ... other code
	
	/// <summary>
	/// Type registration event
	/// </summary>
	event RegisterTypeEventHandler RegisterTypeEventHandler;

	/// <summary>
	/// Initializes the container inside IocManager
	/// </summary>
	void InitializeInternalContainer(IContainer dryIocContainer);
}

IocManager has to implement this method, and the registration code in its constructor moves into InitializeInternalContainer().

C#
public class IocManager : IIocManager
{
	// ... other code

	public IocManager()
	{
		_conventionalRegistrars = new List<IConventionalDependencyRegistrar>();
	}
	
	public void InitializeInternalContainer(IContainer dryIocContainer)
	{
		IocContainer = dryIocContainer;
		
		//Register self!
		IocContainer.UseInstance(typeof(IocManager),this);
		IocContainer.UseInstance(typeof(IIocManager),this);
		IocContainer.UseInstance(typeof(IIocRegistrar),this);
		IocContainer.UseInstance(typeof(IIocResolver),this);
	}
	
	// ... other code
}

Back to where we started, the final implementation of AddAbp<TStartupModule>() looks like this:

C#
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
	where TStartupModule : AbpModule
{
	var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);

	ConfigureAspNetCore(services, abpBootstrapper.IocManager);

	var newContainer = new Container().WithDependencyInjectionAdapter(services);
	abpBootstrapper.IocManager.InitializeInternalContainer(newContainer);
	
	return abpBootstrapper.IocManager.IocContainer.BuildServiceProvider();
}

Run the AspNetCoreApp project and you can see it works.

1542629737749

5. Known Issues

5.1 Property Injection Doesn’t Work in ApplicationService

In the sample project, I created a TestApplicationService in the AspNetCoreApp.Application library. It has a GetJson() method.

Inside it, I use the AbpSession property from the base class. Normally the implementation of that property should be ClaimsAbpSession, but after testing I got this result:

1542635621160

As you can see, it was filled with the default empty implementation. The reason is that when DryIoc registers an object, you have to ask for property injection explicitly. Otherwise property injection is off by default.

Because of this, we add an isAutoInjectProperty parameter to the Register() methods on IIocRegistrar and IocManager, which decides whether to turn on property injection at registration time.

C#
public interface IIocRegistrar
{
	/// <summary>
	/// Registers a type as self registration.
	/// </summary>
	/// <typeparam name="T">Type of the class</typeparam>
	/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
	void Register<T>(DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
		where T : class;

	/// <summary>
	/// Registers a type as self registration.
	/// </summary>
	/// <param name="type">Type of the class</param>
	/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
	void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false);

	/// <summary>
	/// Registers a type with it's implementation.
	/// </summary>
	/// <typeparam name="TType">Registering type</typeparam>
	/// <typeparam name="TImpl">The type that implements <see cref="TType"/></typeparam>
	/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
	void Register<TType, TImpl>(DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
		where TType : class
		where TImpl : class, TType;

	/// <summary>
	/// Registers a type with it's implementation.
	/// </summary>
	/// <param name="type">Type of the class</param>
	/// <param name="impl">The type that implements <paramref name="type"/></param>
	/// <param name="lifeStyle">Lifestyle of the objects of this type</param>
	void Register(Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false);
}

The implementation then uses isAutoInjectProperty to decide whether to enable property injection. Here’s one Register() method as an example.

C#
public void Register(Type type, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton,bool isAutoInjectProperty = false)
{
	IocContainer.Register(type,
		ApplyLifestyle(lifeStyle),
		made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments,
			propertiesAndFields: isAutoInjectProperty
				? PropertiesAndFields.Auto
				: null));
	RegisterTypeEventHandler?.Invoke(this,
		type,
		type);
}

Now go back to the BasicConventionalRegistrar registrar. Application services are all transient objects and all inherit from the IApplicationService interface. So we add a check: if the type is an application service, property injection is allowed when it is registered.

C#
public class BasicConventionalRegistrar : IConventionalDependencyRegistrar
{
	// ... other code

	public void RegisterAssembly(IConventionalRegistrationContext context)
	{
		// Register transient objects
		var waitRegisterTransient = GetTypes<ITransientDependency>(context.Assembly).ToList();

		foreach (var transientType in waitRegisterTransient)
		{
			if (typeof(IApplicationService).IsAssignableFrom(transientType.ImplType))
			{
				context.IocManager.Register(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient,true);
				continue;
			}
			
			context.IocManager.RegisterIfNot(transientType.ServiceType,transientType.ImplType,DependencyLifeStyle.Transient);
		}
		
		// ... other code
	}
}

After this change, call the API again and you can see the properties are now injected correctly.

PS:

Make sure that the AspNetCoreAppAppicationModule module in the AspNetCoreApp.Application library calls IocManager.RegisterAssemblyByConvention(typeof(AspNetCoreAppApplicationModule).Assembly); in its Initialize() method. Otherwise the application services won’t be registered in the IoC container.

1542638775526

5.2 Can’t Get the Real Type in an Interceptor

This problem is in the interceptors. In DryIoc, if a type has several interceptors bound to it, they form a hierarchy, like in the screenshot below:

1542641541541

So if you need to get the real object from an outer interceptor, for now the only way is recursion.

C#
public static Type GetUnproxiedType(object instance)
{
    if (instance is IProxyTargetAccessor proxyTargetAccessor)
    {
        var newInstance = proxyTargetAccessor.DynProxyGetTarget();
        return GetUnproxiedType(newInstance);
    }

    return instance.GetType();          
}

Usage looks like this:

C#
public void Intercept(IInvocation invocation)
{
    _authorizationHelper.Authorize(invocation.MethodInvocationTarget, TypeExtensions.GetUnproxiedType(invocation.Proxy));
    invocation.Proceed();
}

I’ve already reported this problem on Github, and the author says they’re working on it.

6. Conclusion

The post makes the whole process look simple and easy, but I ran into quite a few pitfalls when I did it. Together with my earlier Abp source code analysis posts, this should help you understand the structure of the whole Abp framework better.

This way, you aren’t limited to DryIoc. You can swap in whatever other DI framework you like.

In Abp vNext, the IoC container is designed to be easy to replace, so you won’t have to go through this kind of trouble any more.

PS: The official project already has extensions for AutoFac and Castle Windsor.

The finished code and the demo are on GitHub: https://github.com/GameBelial/Abp-DryIoc.git

Replacing Abp's DI Framework with DryIoc
By Zony · published 2018-11-20
Licensed under CC BY-NC-SA 4.0
Contents