In this article, I won’t explain what is dependency injection (DI). I will try to explain how DI in ASP.NET Core works what can we do with it and how we can use other DI containers (Autofac and Castle Windsor) with ASP.NET Core.ASP.NET Core provides a minimal feature set to use default services cotainer. But you want to use different DI containers, because DI in ASP.NET Core is vey primitive. For example, it dosn’t support property injection or advanced service registering methods.
Let’s go to the examples and try to understand basics of DI in ASP.NET Core.
class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<MyService>();
var serviceProvider = services.BuildServiceProvider();
var myService = serviceProvider.GetService<MyService>();
myService.DoIt();
}
}
public class MyService
{
public void DoIt()
{
Console.WriteLine("Hello MS DI!");
}
}
IServiceCollection
is the collection of the service descriptors. We can register our services in this collection with different lifestyles (Transient, scoped, singleton)
IServiceProvider
is the simple built-in container that is included in ASP.NET Core that supports constructor injection by default. We are getting regisered services with using service provider.
Service Lifestyle/Lifetimes
We can configure services with different types of lifestyles like following.
Transient
This lifestyle services are created each time they are requested.
Scoped
Scoped lifestyle services are created once per request.
Singleton
A singleton service is created once at first time it is requested and this instance of service is used by every sub-requests.
Let’s try to understand better with doing some examples. First, I am creating service classes.
public class TransientDateOperation
{
public TransientDateOperation()
{
Console.WriteLine("Transient service is created!");
}
}
public class ScopedDateOperation
{
public ScopedDateOperation()
{
Console.WriteLine("Scoped service is created!");
}
}
public class SingletonDateOperation
{
public SingletonDateOperation()
{
Console.WriteLine("Singleton service is created!");
}
}
Then I will create a service provider, register services and get these services with different lifestyle.
static void Main(string[] args)
{
Demo2();
}
private static void Demo2()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<TransientDateOperation>();
services.AddScoped<ScopedDateOperation>();
services.AddSingleton<SingletonDateOperation>();
var serviceProvider = services.BuildServiceProvider();
Console.WriteLine();
Console.WriteLine("-------- 1st Request --------");
Console.WriteLine();
var transientService = serviceProvider.GetService<TransientDateOperation>();
var scopedService = serviceProvider.GetService<ScopedDateOperation>();
var singletonService = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-------- 2nd Request --------");
Console.WriteLine();
var transientService2 = serviceProvider.GetService<TransientDateOperation>();
var scopedService2 = serviceProvider.GetService<ScopedDateOperation>();
var singletonService2 = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-----------------------------");
Console.WriteLine();
}
I created service instances two times to see which one is recreating. And the result is;
As you can see, when I try to create services second time, only transient service is recreated but scoped and singleton aren’t created again. But in this example, it isn’t clear scoped service life time. In this example, both first instance and second instance are in the same scope. Let’s do another example to understand better for scoped service instances. I am modify the demo like following;
private static void Demo3()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<TransientDateOperation>();
services.AddScoped<ScopedDateOperation>();
services.AddSingleton<SingletonDateOperation>();
var serviceProvider = services.BuildServiceProvider();
Console.WriteLine();
Console.WriteLine("-------- 1st Request --------");
Console.WriteLine();
using (var scope = serviceProvider.CreateScope())
{
var transientService = scope.ServiceProvider.GetService<TransientDateOperation>();
var scopedService = scope.ServiceProvider.GetService<ScopedDateOperation>();
var singletonService = scope.ServiceProvider.GetService<SingletonDateOperation>();
}
Console.WriteLine();
Console.WriteLine("-------- 2nd Request --------");
Console.WriteLine();
using (var scope = serviceProvider.CreateScope())
{
var transientService = scope.ServiceProvider.GetService<TransientDateOperation>();
var scopedService = scope.ServiceProvider.GetService<ScopedDateOperation>();
var singletonService = scope.ServiceProvider.GetService<SingletonDateOperation>();
}
Console.WriteLine();
Console.WriteLine("-----------------------------");
Console.WriteLine();
}
And result:
As you can see, scoped service instance is created more than one times in different scopes.
In this part, we tried to understand ASP.NET Core DI service provider, service collection and service registering lifestyle/lifetime.
IServiceCollection and IServiceProvider
In this part, I will do more examples about IServiceCollection and IServiceProvider to understand better how DI mechanism of ASP.NET Core is working.
In this step, we will examine some of ASP.NET Core DI features with doing examples. Because, some of these features are more important to understand how this features are used in framework. Also, if we know this features, we can design our project with better design. Let’s start to examples.
Factory Method
First, I will create objects to test.
public class MyService
{
private readonly IMyServiceDependency _dependency;
public MyService(IMyServiceDependency dependency)
{
_dependency = dependency;
}
public void DoIt()
{
_dependency.DoIt();
}
}
public class MyServiceDependency : IMyServiceDependency
{
public void DoIt()
{
Console.WriteLine("Hello from MyServiceDependency");
}
}
public interface IMyServiceDependency
{
void DoIt();
}
And I am registering the objects for DI.
static void Main(string[] args)
{
FactoryMethodDemo();
}
public static void FactoryMethodDemo()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IMyServiceDependency, MyServiceDependency>();
// Overload method for factory registration
services.AddTransient(
provider => new MyService(provider.GetService<IMyServiceDependency>())
);
var serviceProvider = services.BuildServiceProvider();
var instance = serviceProvider.GetService<MyService>();
instance.DoIt();
}
In second registration, I used factory overload method for register service and result.
Instance Registration
You can create an object instance before registering it. Object for instance registration.
public class MyInstance
{
public int Value { get; set; }
}
And demo:
static void Main(string[] args)
{
InstanceRegistrationDemo();
}
public static void InstanceRegistrationDemo()
{
var instance = new MyInstance { Value = 44 };
IServiceCollection services = new ServiceCollection();
services.AddSingleton(instance);
foreach (ServiceDescriptor service in services)
{
if (service.ServiceType == typeof(MyInstance))
{
var registeredInstance = (MyInstance)service.ImplementationInstance;
Console.WriteLine("Registered instance : " + registeredInstance.Value);
}
}
var serviceProvider = services.BuildServiceProvider();
var myInstance = serviceProvider.GetService<MyInstance>();
Console.WriteLine("Registered service by instance registration : " + myInstance.Value);
}
First, I created a new object before registering it’s type in DI. Then I resolved the registered instance by using both service.Implementation and serviceProvider.GetInstance methods. And the result:
Generic Type Registration
I think, this is one of the best features. Suppose we have a generic repository. If we want to register this repository for all types for DI, We should register all types separately like Repo, Repo, etc… But now, ASP.NET Core DI support generic registration. And its easy to register and resolve.
IServiceCollection services = new ServiceCollection();
services.AddTransient<MyClassWithValue>();
services.AddTransient(typeof(IMyGeneric<>), typeof(MyGeneric<>));
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyGeneric<MyClassWithValue>>();
Multiple Registration
ASP.NET Core DI supports multiple registration with different options. This feature gives us some flexibility. Let’s do example to understand better. I am defining objects to test this feature.
public interface IHasValue
{
object Value { get; set; }
}
public class MyClassWithValue : IHasValue
{
public object Value { get; set; }
public MyClassWithValue()
{
Value = 42;
}
}
public class MyClassWithValue2 : IHasValue
{
public object Value { get; set; }
public MyClassWithValue2()
{
Value = 43;
}
}
I will show different usage of multiple registration feature with example.
static void Main(string[] args)
{
MultipleImplementation();
MultipleImplementationWithTry();
MultipleImplementationWithReplace();
}
private static void MultipleImplementation()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, MyClassWithValue>();
services.AddTransient<IHasValue, MyClassWithValue2>();
var serviceProvider = services.BuildServiceProvider();
var myServices = serviceProvider.GetServices<IHasValue>().ToList();
var myService = serviceProvider.GetService<IHasValue>();
Console.WriteLine("----- Multiple Implemantation Services -----------");
foreach (var service in myServices)
{
Console.WriteLine(service.Value);
}
Console.WriteLine("----- Multiple Implemantation Service ------------");
Console.WriteLine(myService.Value);
}
private static void MultipleImplementationWithTry()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, MyClassWithValue>();
services.TryAddTransient<IHasValue, MyClassWithValue2>();
var serviceProvider = services.BuildServiceProvider();
var myServices = serviceProvider.GetServices<IHasValue>().ToList();
Console.WriteLine("----- Multiple Implemantation Try ----------------");
foreach (var service in myServices)
{
Console.WriteLine(service.Value);
}
}
private static void MultipleImplementationWithReplace()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, MyClassWithValue>();
services.Replace(ServiceDescriptor.Transient<IHasValue, MyClassWithValue2>());
var serviceProvider = services.BuildServiceProvider();
var myServices = serviceProvider.GetServices<IHasValue>().ToList();
Console.WriteLine("----- Multiple Implemantation Replace ------------");
foreach (var service in myServices)
{
Console.WriteLine(service.Value);
}
Console.WriteLine("--------------------------------------------------");
}
First demo of above code, I added IHasValue object twice with different types. And when I want to get service, It gives me the last registered service.
Second demo, if I use TryAdd- method, it is not registered if there is a registered service.
And last one, I can replace registered service with another. You can understand better when you see the result.
DI Options and Using Autofac and Castle Windsor
In this last part, I will explain DI options, how to use ASP.NET Core DI with autofac and castle windsor.
Options
There is a pattern that uses custom options classes to represent a group of related settings. As usual, I will explain it with example. First, download Microsoft.Extensions.Options.dll from nuget.
public class MyTaxCalculator
{
private readonly MyTaxCalculatorOptions _options;
public MyTaxCalculator(IOptions<MyTaxCalculatorOptions> options)
{
_options = options.Value;
}
public int Calculate(int amount)
{
return amount * _options.TaxRatio / 100;
}
}
public class MyTaxCalculatorOptions
{
public int TaxRatio { get; set; }
public MyTaxCalculatorOptions()
{
TaxRatio = 118;
}
}
I created a class to calculate tax that uses an option class to get information how to calculate tax. I injected this option as IOption generic type. And here is the usage;
static void Main(string[] args)
{
ServiceOptionsDemo1();
}
private static void ServiceOptionsDemo1()
{
IServiceCollection services = new ServiceCollection();
services.AddOptions();
services.AddTransient<MyTaxCalculator>();
services.Configure<MyTaxCalculatorOptions>(options =>
{
options.TaxRatio = 135;
});
var serviceProvider = services.BuildServiceProvider();
var calculator = serviceProvider.GetService<MyTaxCalculator>();
Console.WriteLine(calculator.Calculate(100));
}
IServiceCollection has an extension method that named Configure. With using this method, we can define/change option values at compile time. When I run the code, here is the result.
In this example, we can set and use options in DI with adding values hardcoded. Of course we can read option values from an file like json. Let’s do an example for this.
Configurations
The configuration API provides a way of configuring that can be read at runtime from multiple resources. These resources can be file (.ini, .json, .xml, …), cmd arguments, environment variables, in-memory objects, etc…
First, you should download Microsoft.Extensions.Options.ConfigurationExtensions.dll and Microsoft.Extensions.Configuration.Json.dll and I will add an appsettings.json to my project.
{
"TaxOptions": {
"TaxRatio": "130"
}
}
And I am modifying the demo like following.
static void Main(string[] args)
{
ServiceOptionsDemo2();
}
private static void ServiceOptionsDemo2()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"))
.Build();
IServiceCollection services = new ServiceCollection();
services.AddOptions();
services.AddScoped<MyTaxCalculator>();
services.Configure<MyTaxCalculatorOptions>(configuration.GetSection("TaxOptions"));
var serviceProvider = services.BuildServiceProvider();
var calculator = serviceProvider.GetRequiredService<MyTaxCalculator>();
Console.WriteLine(calculator.Calculate(200));
}
I added ConfigurationBuilder to read options from json file. In json file, data structure is matching with configuration.GetSection(“TaxOptions”). And result is:
Using Castle Windsor and Autofac
Using another DI container is very easy. Just we are adding the nuget package and create an instance of container.
Castle Windsor
First, I am adding Castle.Windsor.MsDependencyInjection from nuget. And usage;
IServiceCollection services = new ServiceCollection();
services.AddTransient<MyService>();
var windsorContainer = new WindsorContainer();
windsorContainer.Register(
Component.For<MyService>()
);
var serviceProvider = WindsorRegistrationHelper.CreateServiceProvider(
windsorContainer,
services
);
var myService = serviceProvider.GetService<MyService>();
Autofac
For using autofac, you should add the Autofac.Extensions.DependencyInjection nuget package to your project. And usage;
IServiceCollection services = new ServiceCollection();
services.AddTransient<MyService>();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<MyService>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(container);
var myService = serviceProvider.GetService<MyService>();
As you can see, after adding nuget packages, you can use them just using their own service provider or service register.
If you investigate source code of the example project, you can understand better.
Source code: ASP.NET Core Dependency Injection Training