File: ServiceProvider.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj (Microsoft.Extensions.DependencyInjection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection.ServiceLookup;
 
namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// The default IServiceProvider.
    /// </summary>
    [DebuggerDisplay("{DebuggerToString(),nq}")]
    [DebuggerTypeProxy(typeof(ServiceProviderDebugView))]
    public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IDisposable, IAsyncDisposable
    {
        private readonly CallSiteValidator? _callSiteValidator;
 
        private readonly Func<ServiceIdentifier, ServiceAccessor> _createServiceAccessor;
 
        // Internal for testing
        internal ServiceProviderEngine _engine;
 
        private bool _disposed;
 
        private readonly ConcurrentDictionary<ServiceIdentifier, ServiceAccessor> _serviceAccessors;
 
        internal CallSiteFactory CallSiteFactory { get; }
 
        internal ServiceProviderEngineScope Root { get; }
 
        [FeatureSwitchDefinition("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability")]
        internal static bool VerifyOpenGenericServiceTrimmability { get; } =
            AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false;
 
        [FeatureSwitchDefinition("Microsoft.Extensions.DependencyInjection.DisableDynamicEngine")]
        internal static bool DisableDynamicEngine { get; } =
            AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.DisableDynamicEngine", out bool disableDynamicEngine) ? disableDynamicEngine : false;
 
        internal static bool VerifyAotCompatibility =>
#if NETFRAMEWORK || NETSTANDARD2_0
            false;
#else
            !RuntimeFeature.IsDynamicCodeSupported;
#endif
 
        internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
        {
            // note that Root needs to be set before calling GetEngine(), because the engine may need to access Root
            Root = new ServiceProviderEngineScope(this, isRootScope: true);
            _engine = GetEngine();
            _createServiceAccessor = CreateServiceAccessor;
            _serviceAccessors = new ConcurrentDictionary<ServiceIdentifier, ServiceAccessor>();
 
            CallSiteFactory = new CallSiteFactory(serviceDescriptors);
            // The list of built in services that aren't part of the list of service descriptors
            // keep this in sync with CallSiteFactory.IsService
            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProvider)), new ServiceProviderCallSite());
            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceScopeFactory)), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProviderIsService)), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProviderIsKeyedService)), new ConstantCallSite(typeof(IServiceProviderIsKeyedService), CallSiteFactory));
 
            if (options.ValidateScopes)
            {
                _callSiteValidator = new CallSiteValidator();
            }
 
            if (options.ValidateOnBuild)
            {
                List<Exception>? exceptions = null;
                foreach (ServiceDescriptor serviceDescriptor in serviceDescriptors)
                {
                    try
                    {
                        ValidateService(serviceDescriptor);
                    }
                    catch (Exception e)
                    {
                        exceptions ??= new List<Exception>();
                        exceptions.Add(e);
                    }
                }
 
                if (exceptions != null)
                {
                    throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
                }
            }
 
            DependencyInjectionEventSource.Log.ServiceProviderBuilt(this);
        }
 
        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <param name="serviceType">The type of the service to get.</param>
        /// <returns>The service that was produced.</returns>
        public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
 
        /// <summary>
        /// Gets the service object of the specified type with the specified key.
        /// </summary>
        /// <param name="serviceType">The type of the service to get.</param>
        /// <param name="serviceKey">The key of the service to get.</param>
        /// <returns>The keyed service.</returns>
        public object? GetKeyedService(Type serviceType, object? serviceKey)
            => GetKeyedService(serviceType, serviceKey, Root);
 
        internal object? GetKeyedService(Type serviceType, object? serviceKey, ServiceProviderEngineScope serviceProviderEngineScope)
            => GetService(new ServiceIdentifier(serviceKey, serviceType), serviceProviderEngineScope);
 
        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <param name="serviceType">The type of the service to get.</param>
        /// <param name="serviceKey">The key of the service to get.</param>
        /// <returns>The keyed service.</returns>
        /// <exception cref="InvalidOperationException">The service wasn't found.</exception>
        public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
            => GetRequiredKeyedService(serviceType, serviceKey, Root);
 
        internal object GetRequiredKeyedService(Type serviceType, object? serviceKey, ServiceProviderEngineScope serviceProviderEngineScope)
        {
            object? service = GetKeyedService(serviceType, serviceKey, serviceProviderEngineScope);
            if (service == null)
            {
                throw new InvalidOperationException(SR.Format(SR.NoServiceRegistered, serviceType));
            }
            return service;
        }
 
        internal bool IsDisposed() => _disposed;
 
        /// <inheritdoc />
        public void Dispose()
        {
            DisposeCore();
            Root.Dispose();
        }
 
        /// <inheritdoc/>
        public ValueTask DisposeAsync()
        {
            DisposeCore();
            return Root.DisposeAsync();
        }
 
        private void DisposeCore()
        {
            _disposed = true;
            DependencyInjectionEventSource.Log.ServiceProviderDisposed(this);
        }
 
        private void OnCreate(ServiceCallSite callSite)
        {
            _callSiteValidator?.ValidateCallSite(callSite);
        }
 
        private void OnResolve(ServiceCallSite? callSite, IServiceScope scope)
        {
            if (callSite != null)
            {
                _callSiteValidator?.ValidateResolution(callSite, scope, Root);
            }
        }
 
        internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
        {
            if (_disposed)
            {
                ThrowHelper.ThrowObjectDisposedException();
            }
            ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, _createServiceAccessor);
            OnResolve(serviceAccessor.CallSite, serviceProviderEngineScope);
            DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
            object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);
            System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));
            return result;
        }
 
        private void ValidateService(ServiceDescriptor descriptor)
        {
            if (descriptor.ServiceType.IsGenericType && !descriptor.ServiceType.IsConstructedGenericType)
            {
                return;
            }
 
            try
            {
                ServiceCallSite? callSite = CallSiteFactory.GetCallSite(descriptor, new CallSiteChain());
                if (callSite != null)
                {
                    OnCreate(callSite);
                }
            }
            catch (Exception e)
            {
                throw new InvalidOperationException($"Error while validating the service descriptor '{descriptor}': {e.Message}", e);
            }
        }
 
        private ServiceAccessor CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
        {
            ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());
            if (callSite != null)
            {
                DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceIdentifier.ServiceType, callSite);
                OnCreate(callSite);
 
                // Optimize singleton case
                if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
                {
                    object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
                    return new ServiceAccessor { CallSite = callSite, RealizedService = scope => value };
                }
 
                Func<ServiceProviderEngineScope, object?> realizedService = _engine.RealizeService(callSite);
                return new ServiceAccessor { CallSite = callSite, RealizedService = realizedService };
            }
            return new ServiceAccessor { CallSite = callSite, RealizedService = _ => null };
        }
 
        internal void ReplaceServiceAccessor(ServiceCallSite callSite, Func<ServiceProviderEngineScope, object?> accessor)
        {
            _serviceAccessors[new ServiceIdentifier(callSite.Key, callSite.ServiceType)] = new ServiceAccessor
            {
                CallSite = callSite,
                RealizedService = accessor
            };
        }
 
        internal IServiceScope CreateScope()
        {
            if (_disposed)
            {
                ThrowHelper.ThrowObjectDisposedException();
            }
 
            return new ServiceProviderEngineScope(this, isRootScope: false);
        }
 
        private ServiceProviderEngine GetEngine()
        {
            ServiceProviderEngine engine;
 
#if NETFRAMEWORK || NETSTANDARD2_0
            engine = CreateDynamicEngine();
#else
            if (RuntimeFeature.IsDynamicCodeCompiled && !DisableDynamicEngine)
            {
                engine = CreateDynamicEngine();
            }
            else
            {
                // Don't try to compile Expressions/IL if they are going to get interpreted
                engine = RuntimeServiceProviderEngine.Instance;
            }
#endif
            return engine;
 
            [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
                Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715
            ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this);
        }
 
        private string DebuggerToString() => Root.DebuggerToString();
 
        internal sealed class ServiceProviderDebugView
        {
            private readonly ServiceProvider _serviceProvider;
 
            public ServiceProviderDebugView(ServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
 
            public List<ServiceDescriptor> ServiceDescriptors => new List<ServiceDescriptor>(_serviceProvider.Root.RootProvider.CallSiteFactory.Descriptors);
            public List<object> Disposables => new List<object>(_serviceProvider.Root.Disposables);
            public bool Disposed => _serviceProvider.Root.Disposed;
            public bool IsScope => !_serviceProvider.Root.IsRootScope;
        }
 
        private sealed class ServiceAccessor
        {
            public ServiceCallSite? CallSite { get; set; }
            public Func<ServiceProviderEngineScope, object?>? RealizedService { get; set; }
        }
    }
}