Home > Article > Backend Development > Integration with third-party IoC/DI frameworks in ASP.NET Core applications
In ASP.NET Core applications, integration of third-party DI frameworks can be achieved by returning a ServiceProvider in the ConfigureServices method that defines the Startup type. But it’s not that easy. Let’s share it with you through an example.
1. The ServiceProvider returned by the ConfigureServices method is useless!
We can use a simple example to Explain this problem. We first define the following MyServiceProvider, which is actually an encapsulation of another ServiceProvider. For simplicity, we use a dictionary to save the mapping relationship between the service interface and the implementation type. This relationship can be registered by calling the Register method. In the GetService method that provides a service instance, if the service type provided has been registered, we will create and return the corresponding instance object, otherwise we will use the encapsulated ServiceProvider to provide services. In order to ensure that the service instance can be recycled normally, if the service type implements the IDisposable interface, we will add it to the collection represented by the field _disposables. When the Dispose method of MyServiceProvider is called, the Dispose methods of these provided service instances will be called.
public class MyServiceProvider : IServiceProvider, IDisposable { private IServiceProvider _innerServiceProvider; private Dictionary<Type, Type> _services; private List<IDisposable> _disposables; public MyServiceProvider(IServiceProvider innerServiceProvider) { _innerServiceProvider = innerServiceProvider; this._services = new Dictionary<Type, Type>(); _disposables = new List<IDisposable>(); } public MyServiceProvider Register<TFrom, TTo>() where TTo: TFrom, new() { _services[typeof(TFrom)] = typeof(TTo); return this; } public object GetService(Type serviceType) { Type implementation; if (_services.TryGetValue(serviceType, out implementation)) { object service = Activator.CreateInstance(implementation); IDisposable disposbale = service as IDisposable; if (null != disposbale) { _disposables.Add(disposbale); } return service; } return _innerServiceProvider.GetService(serviceType); } public void Dispose() { (_innerServiceProvider as IDisposable)?.Dispose(); foreach (var it in _disposables) { it.Dispose(); } _disposables.Clear(); } }
We use MyServiceProvider in an ASP.NET Core application as follows. As in the following code snippet, in the registered Starup type, we let the ConfigureServices method return a MyServiceProvider object. The mapping between the service interface IFoobar and the implementation type Foobar is registered on this MyServiceProvider object. When processing the request, we use the RequestServices attribute of the current HttpContext object to get the ServiceProvider that provides services for request processing, and try to use it to get the registered IFoobar service.
public class Program { public static void Main(string[] args) { new WebHostBuilder() .UseKestrel() .UseStartup<Startup>() .Build() .Run(); } } public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { return new MyServiceProvider(services.BuildServiceProvider()) .Register<IFoobar, Foobar>(); } public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage() .Run(async context => await context.Response.WriteAsync(context.RequestServices.GetRequiredService<IFoobar>().GetType().Name)); } } public interface IFoobar { } public class Foobar : IFoobar { }
The entire application is so simple, and there seems to be no problem. However, when we start the application and use the browser to access the application, the following error will occur. The error message indicates that the service interface IFoobar has not been registered.
#2. What is the reason?
We clearly registered the mapping relationship between IFoobar and Foobar in the returned ServiceProvider. Why does the ServiceProvider returned by RequestServices say that the service has not been registered yet? The only explanation is that the ServiceProvider returned by the ConfigureServices method and the ServiceProvider returned by HttpContext's RequestServices are not the same at all. In fact, they are not the same object.
The ServiceProvider returned by the ConfigureServices method will be used as the ServiceProvider of WebHost. For each received request, WebHost will create a new ServiceProvider based on this ServiceProvider as the RequestServices attribute of HttpContext. These two ServiceProviders have parent-child management. As usual, if the ServiceProvider returned by RequestServices is created based on the ServiceProvider returned by the ConfigureServices method, then it should also be able to identify the registered service type IFoobar, so why does the error still occur?
To understand this problem, you need to know how this so-called "child ServiceProvider" is created, which involves the concept of ServiceScope. Simply put, ServiceScope is an encapsulation of a ServiceProvider, and the former determines the life cycle of the latter. ServiceScope is created by ServiceScopeFactory, which is registered as a service to the "parent ServiceProvider". When the "parent ServiceProvider" needs to create a "child ServiceProvider", it will call the GetService method to get the ServiceScopeFactory object (the service interface used is IServiceScopeFactory), and use the latter to create a ServiceScope. The ServiceProvider provided by this ServiceScope is the returned "child ServiceProvider".
But for our MyServiceProvider object, when its GetService method is called to try to obtain the ServiceScopeFactory object, what is obtained is actually the ServiceScopeFactory associated with the encapsulated SerivceProvider, so it is natural to create a "child ServiceProvider" ” also has nothing to do with MyServiceProvider.
3. How to solve this problem?
Now that we know the root of the problem, we naturally have a solution. The solution is not complicated, we only need the GetService method of MyServiceProvider to return the ServiceScopeFactory that reflects its own service registration. For this purpose we define the following ServiceScope and corresponding ServiceScopeFactory.
internal class ServiceScope : IServiceScope { private MyServiceProvider _serviceProvider; public ServiceScope(IServiceScope innserServiceScope, Dictionary<Type, Type> services) { _serviceProvider = new MyServiceProvider(innserServiceScope.ServiceProvider, services); } public IServiceProvider ServiceProvider { get { return _serviceProvider; } } public void Dispose() { _serviceProvider.Dispose(); } } internal class ServiceScopeFactory : IServiceScopeFactory { private IServiceScopeFactory _innerServiceFactory; private Dictionary<Type, Type> _services; public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary<Type, Type> services) { _innerServiceFactory = innerServiceFactory; _services = services; } public IServiceScope CreateScope() { return new ServiceScope(_innerServiceFactory.CreateScope(), _services); } }
In addition, we added a constructor for MyServiceProvider, and the GetService method also added corresponding code for IServiceScopeFactory.
public class MyServiceProvider : IServiceProvider, IDisposable { public MyServiceProvider(IServiceProvider innerServiceProvider, Dictionary<Type, Type> services) { _innerServiceProvider = innerServiceProvider; _services = services; _disposables = new List<IDisposable>(); } public object GetService(Type serviceType) { if (serviceType == typeof(IServiceScopeFactory)) { IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService<IServiceScopeFactory>(); return new ServiceScopeFactory(innerServiceScopeFactory, _services); } ... } ... }
The above sharing, I hope it will be helpful to friends who need to solve such problems!
The above is the detailed content of Integration with third-party IoC/DI frameworks in ASP.NET Core applications. For more information, please follow other related articles on the PHP Chinese website!