File: EmptyServiceProvider.cs
Web Access
Project: src\runtime\src\libraries\Microsoft.Extensions.DependencyInjection.Abstractions\src\Microsoft.Extensions.DependencyInjection.Abstractions.csproj (Microsoft.Extensions.DependencyInjection.Abstractions)
// 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.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// Provides an <see cref="IServiceProvider"/> that contains no application services.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Like the <see cref="IServiceProvider"/> returned by <c>new ServiceCollection().BuildServiceProvider()</c>,
    /// this provider still exposes the built-in services (<see cref="IServiceProvider"/>,
    /// <see cref="IServiceScopeFactory"/>, <see cref="IServiceProviderIsService"/> and
    /// <see cref="IServiceProviderIsKeyedService"/>) and resolves requests for
    /// <see cref="IEnumerable{T}"/> to an empty sequence.
    /// </para>
    /// <para>
    /// Use <see cref="Instance"/> to access the shared singleton instead of writing a custom empty provider.
    /// </para>
    /// </remarks>
    public sealed class EmptyServiceProvider : IKeyedServiceProvider, IServiceProviderIsKeyedService
    {
        private EmptyServiceProvider()
        {
        }

        /// <summary>
        /// Gets the shared <see cref="EmptyServiceProvider"/> instance.
        /// </summary>
        public static EmptyServiceProvider Instance { get; } = new EmptyServiceProvider();

        /// <inheritdoc />
        object? IServiceProvider.GetService(Type serviceType) => GetService(serviceType);

        /// <inheritdoc />
        object? IKeyedServiceProvider.GetKeyedService(Type serviceType, object? serviceKey) => GetKeyedService(serviceType, serviceKey);

        /// <inheritdoc />
        object IKeyedServiceProvider.GetRequiredKeyedService(Type serviceType, object? serviceKey)
        {
            object? service = GetKeyedService(serviceType, serviceKey);
            if (service is null)
            {
                ThrowNoServiceRegistered(serviceType, serviceKey);
            }

            return service;
        }

        /// <inheritdoc />
        bool IServiceProviderIsService.IsService(Type serviceType)
        {
            ArgumentNullException.ThrowIfNull(serviceType);

            return !serviceType.IsGenericTypeDefinition && (IsEnumerable(serviceType) || IsBuiltInService(serviceType));
        }

        /// <inheritdoc />
        bool IServiceProviderIsKeyedService.IsKeyedService(Type serviceType, object? serviceKey)
        {
            ArgumentNullException.ThrowIfNull(serviceType);

            if (serviceType.IsGenericTypeDefinition)
            {
                return false;
            }

            return IsEnumerable(serviceType) || (serviceKey is null && IsBuiltInService(serviceType));
        }

        private object? GetService(Type serviceType)
        {
            ArgumentNullException.ThrowIfNull(serviceType);

            if (serviceType == typeof(IServiceProvider) ||
                serviceType == typeof(IServiceProviderIsService) ||
                serviceType == typeof(IServiceProviderIsKeyedService))
            {
                return this;
            }

            if (serviceType == typeof(IServiceScopeFactory))
            {
                return EmptyServiceScope.Instance;
            }

            return IsEnumerable(serviceType) ? CreateEmptyEnumerable(serviceType) : null;
        }

        private object? GetKeyedService(Type serviceType, object? serviceKey)
        {
            ArgumentNullException.ThrowIfNull(serviceType);

            if (ReferenceEquals(serviceKey, KeyedService.AnyKey) &&
                (!serviceType.IsGenericType || serviceType.GetGenericTypeDefinition() != typeof(IEnumerable<>)))
            {
                throw new InvalidOperationException(SR.Format(SR.KeyedServiceAnyKeyUsedToResolveService, nameof(IServiceProvider), nameof(IServiceScopeFactory)));
            }

            if (serviceKey is null)
            {
                return GetService(serviceType);
            }

            return IsEnumerable(serviceType) ? CreateEmptyEnumerable(serviceType) : null;
        }

        private static bool IsBuiltInService(Type serviceType) =>
            serviceType == typeof(IServiceProvider) ||
            serviceType == typeof(IServiceScopeFactory) ||
            serviceType == typeof(IServiceProviderIsService) ||
            serviceType == typeof(IServiceProviderIsKeyedService);

        private static bool IsEnumerable(Type serviceType) =>
            serviceType.IsConstructedGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>);

        // Wraps RuntimeFeature.IsDynamicCodeSupported across target frameworks.
        private static bool IsDynamicCodeSupported =>
#if NETFRAMEWORK || NETSTANDARD2_0
            true;
#else
            RuntimeFeature.IsDynamicCodeSupported;
#endif

        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "The element type is guaranteed not to be a ValueType when dynamic code isn't supported.")]
        private static Array CreateEmptyEnumerable(Type serviceType)
        {
            Type itemType = serviceType.GenericTypeArguments[0];
            if (!IsDynamicCodeSupported && itemType.IsValueType)
            {
                // Native AOT apps are not able to make an Enumerable of a ValueType service
                // since there is no guarantee the ValueType[] code has been generated.
                throw new InvalidOperationException(SR.Format(SR.AotCannotCreateEnumerableValueType, itemType));
            }

            return Array.CreateInstance(itemType, 0);
        }

        [DoesNotReturn]
        private static void ThrowNoServiceRegistered(Type serviceType, object? serviceKey)
        {
            throw serviceKey is null
                ? new InvalidOperationException(SR.Format(SR.NoServiceRegistered, serviceType))
                : new InvalidOperationException(SR.Format(SR.NoKeyedServiceRegistered, serviceType, serviceKey.GetType()));
        }

        private sealed class EmptyServiceScope : IServiceScopeFactory, IServiceScope
        {
            public static EmptyServiceScope Instance { get; } = new EmptyServiceScope();

            public IServiceProvider ServiceProvider => EmptyServiceProvider.Instance;

            public IServiceScope CreateScope() => this;

            public void Dispose()
            {
            }
        }
    }
}