Property Injection in ASP.NET Core

I am trying to port an ASP.NET application to ASP.NET Core. I have property injection (using Ninject) on my UnitOfWork implementation like this:

[Inject]
public IOrderRepository OrderRepository { get; set; }
[Inject]
public ICustomerRepository CustomerRepository { get; set; }

Is there a way to achieve the same functionality using built-in DI on .NET Core? Also, is it possible to use convention-based binding?


Solution 1:

No, the built-in DI/IoC container is intentionally kept simple in both usage and features to offer a base for other DI containers to plug-in.

So there is no built-in support for: Auto-Discovery, Auto-Registrations, Decorators or Injectors, or convention based registrations. There are also no plans to add this to the built-in container yet as far as I know.

You'll have to use a third party container with property injection support.

Please note that property injection is considered bad in 98% of all scenarios, because it hides dependencies and there is no guarantee that the object will be injected when the class is created.

With constructor injection you can enforce this via constructor and check for null and the not create the instance of the class. With property injection this is impossible and during unit tests its not obvious which services/dependencies the class requires when they are not defined in the constructor, so easy to miss and get NullReferenceExceptions.

The only valid reason for Property Injection I ever found was to inject services into proxy classes generated by a third party library, i.e. WCF proxies created from an interface where you have no control about the object creation. And even there, its only for third party libraries. If you generate WCF Proxies yourself, you can easily extend the proxy class via partial class and add a new DI friendly constructor, methods or properties.

Avoid it everywhere else.

Solution 2:

Is there a way to achieve the same functionality using built-in DI on .NET Core?

No, but here is how you can create your own [inject] attributes with the help of Autofac's property injection mechanism.

First create your own InjectAttribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class InjectAttribute : Attribute
{
  public InjectAttribute() : base() { }
}

Then create your own InjectPropertySelector that uses reflection to check for properties marked with [inject]:

public class InjectPropertySelector : DefaultPropertySelector
{
  public InjectPropertySelector(bool preserveSetValues) : base(preserveSetValues)
  { }

  public override bool InjectProperty(PropertyInfo propertyInfo, object instance)
  {
    var attr = propertyInfo.GetCustomAttribute<InjectAttribute>(inherit: true);
    return attr != null && propertyInfo.CanWrite
            && (!PreserveSetValues
            || (propertyInfo.CanRead && propertyInfo.GetValue(instance, null) == null));
  }
}

Then use your selector in your ConfigureServices where you wire up your AutofacServiceProvider:

public class Startup
{
  public IServiceProvider ConfigureServices(IServiceCollection services)
  {
    var builder = new ContainerBuilder();
    builder.Populate(services);
    
    // use your property selector to discover the properties marked with [inject]
    builder.RegisterType<MyServiceX>().PropertiesAutowired((new InjectablePropertySelector(true)););

    this.ApplicationContainer = builder.Build();
    return new AutofacServiceProvider(this.ApplicationContainer);
  }
}

Finally in your service you can now use [inject]:

public class MyServiceX 
{
    [Inject]
    public IOrderRepository OrderRepository { get; set; }
    [Inject]
    public ICustomerRepository CustomerRepository { get; set; }
}

You surely can take this solution even further, e.g. by using an attribute for specifying your service's lifecycle above your service's class definition...

[Injectable(LifetimeScope.SingleInstance)]
public class IOrderRepository

...and then checking for this attribute when configuring your services via Autofac. But this would go beyond the scope of this answer.

Solution 3:

I don't want to argue because I'm new to .NET Core. But imagine such a situation: class A uses the services Service1, ..., Service5 and their interfaces IService1, ..., IService5 are passed in its constructor as dependencies; class B inherits from class A, class C from class B, etc. and all these derived classes should therefore pass at least the IService1, ..., IService5 interfaces in their constructors. However, class B and all classes derived from it are placed in consumer libraries or applications and are therefore beyond the reach of the class A developer.

Well, now the developer of class A will find that some functionality incorporated into class A would deserve refactoring and outsourcing into an external service, because it needs to be used elsewhere, out of reach of class A. So it outsources it to Service6 with interface IService6. But by adding another argument to the class A constructor, the developer violates the contract with users of this class: The constructor is used in derived classes and knows only its 5 arguments.

Solutions? Alternatively:

  1. Inject IService6 by a property.

  2. Instead passing 5 interfaces of individual services to the constructor, pass a single interface, which will provide 5 interfaces to the above mentioned 5 services in its properties. This interface will be part of the package shipped with class A. If any class A functionality needs to be outsourced to a service, another property will be added to this interface and another service will be added to the bundled package. All class A dependencies (perhaps except options) will be clearly summarized in this interface.

  3. Is there any better solution?

Solution 4:

The top voted answer is not quite correct. While the built-in DI container (IServiceProvider) is intentionally kept simple and feature-light, it is also quite extensible, and it is possible to provide the functionality you require with a bit of plumbing.

This is what the Quickwire NuGet package provides out of the box. This works as you would expect.

First decorate the class with [RegisterService], and the properties to be injected using the [InjectService] attribute:

[RegisterService(ServiceLifetime.Scoped)]
public class BusinessLogic
{
    [InjectService]
    public IOrderRepository OrderRepository { get; set; }
    [InjectService]
    public ICustomerRepository CustomerRepository { get; set; }

    // ...
}

And then use ScanCurrentAssembly from ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.ScanCurrentAssembly();

    // Register other services...
}

This will scan the current assembly for classes decorated with RegisterService and register them in the IServiceCollection, also ensuring dependencies are injected in setters decorated with [InjectService].

Once again, this approach does not require you to use a third-party dependency injection container like Autofac. All your existing code will continue to work without any modification.