.NET Core開發日誌——依賴註入

来源:https://www.cnblogs.com/kenwoo/archive/2018/07/29/9384835.html
-Advertisement-
Play Games

依賴註入(DI)不是一個新的話題,它的出現是伴隨著系統解耦的需要而幾乎必然產生的。 在SOLID設計原則中,DIP(Dependency inversion principle)——依賴倒置,規定了“需依賴抽象,而非實現”的準則,該原則主要目的是通過引入抽象(比如介面)的方式降低模塊之間的耦合性。與 ...


依賴註入(DI)不是一個新的話題,它的出現是伴隨著系統解耦的需要而幾乎必然產生的。

在SOLID設計原則中,DIP(Dependency inversion principle)——依賴倒置,規定了“需依賴抽象,而非實現”的準則,該原則主要目的是通過引入抽象(比如介面)的方式降低模塊之間的耦合性。與此原則相擬而又有所不同的是IoC(inversion of control)——控制反轉設計原則。這項原則定義了應該由通用框架而非外部代碼決定控制流(control flow)的概念。對控制反轉的實現有數種技術,DI(Dependency injection)——依賴註入便是其中之一,而依賴註入技術同時又支持依賴倒置的設計原則,所以它被廣泛使用並不是件令人意外的事情。

依賴註入的基本特性是藉由一個對象提供對另一對象的依賴。這樣的一個對象通常又被稱為容器。容器負責被依賴對象的註冊(register),解析(resolve)與釋放(release),並具有將被依賴對象註入到依賴對象內部的功能。

在之前的ASP.NET開發過程中,要想使用到依賴註入技術必需依賴第三方類庫,而在ASP.NET Core中,這項技術已經被引入到其自身的框架中。

容器

ASP.NET Core中使用ServiceProvider作為依賴註入的容器,它是在WebHostBuilder類中被引入的。

public IWebHost Build()
{
    ...

    IServiceProvider GetProviderFromFactory(IServiceCollection collection)
    {
        var provider = collection.BuildServiceProvider();
        var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();

        if (factory != null)
        {
            using (provider)
            {
                return factory.CreateServiceProvider(factory.CreateBuilder(collection));
            }
        }

        return provider;
    }
}

註冊

所需依賴的對象通過ServiceCollectionServiceExtensions中的各種擴展方法被加入到ServiceCollection類中。ServiceCollection類內部維護著一個ServiceDescriptor集合。而ServiceCollection又會被傳入ServiceProvider的構造方法。

public static IServiceCollection AddTransient(
    this IServiceCollection services,
    Type serviceType,
    Type implementationType)
{
    ...

    return Add(services, serviceType, implementationType, ServiceLifetime.Transient);
}

public static IServiceCollection AddScoped(
    this IServiceCollection services,
    Type serviceType,
    Type implementationType)
{
    ...

    return Add(services, serviceType, implementationType, ServiceLifetime.Scoped);
}

public static IServiceCollection AddSingleton(
    this IServiceCollection services,
    Type serviceType,
    Type implementationType)
{
    ...

    return Add(services, serviceType, implementationType, ServiceLifetime.Singleton);
}

private static IServiceCollection Add(
    IServiceCollection collection,
    Type serviceType,
    Type implementationType,
    ServiceLifetime lifetime)
{
    var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
    collection.Add(descriptor);
    return collection;
}

解析

要想獲得已註冊的對象,可以通過ServiceProviderServiceExtensions類的擴展方法GetService。

public static T GetService<T>(this IServiceProvider provider)
{
    if (provider == null)
    {
        throw new ArgumentNullException(nameof(provider));
    }

    return (T)provider.GetService(typeof(T));
}

ServiceProvider的GetService方法其實是調用了它內部各種引擎的父類ServiceProviderEngine的方法。這些引擎間區別在於實現方式以及性能上,功能方面都是一樣的。預設引擎是DynamicServiceProviderEngine。

private readonly IServiceProviderEngine _engine;

internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
  IServiceProviderEngineCallback callback = null;
  if (options.ValidateScopes)
  {
      callback = this;
      _callSiteValidator = new CallSiteValidator();
  }
  switch (options.Mode)
  {
      case ServiceProviderMode.Dynamic:
          _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
          break;
      case ServiceProviderMode.Runtime:
          _engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
          break;
#if IL_EMIT
      case ServiceProviderMode.ILEmit:
          _engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);
          break;
#endif
      case ServiceProviderMode.Expressions:
          _engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);
          break;
      default:
          throw new NotSupportedException(nameof(options.Mode));
  }
}

public object GetService(Type serviceType) => _engine.GetService(serviceType);

ServiceProviderEngine類的CreateServiceAccessor方法創建了CallSite。

internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
{
    if (_disposed)
    {
        ThrowHelper.ThrowObjectDisposedException();
    }

    var realizedService = RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
    _callback?.OnResolve(serviceType, serviceProviderEngineScope);
    return realizedService.Invoke(serviceProviderEngineScope);
}

private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
{
    var callSite = CallSiteFactory.CreateCallSite(serviceType, new CallSiteChain());
    if (callSite != null)
    {
        _callback?.OnCreate(callSite);
        return RealizeService(callSite);
    }

    return _ => null;
}

假設註冊是用的public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType)方法,那麼之後的處理會生成一個SingletonCallSite對象並且包含ConstructorCallSite參數值。

private IServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
{
    if (serviceType == descriptor.ServiceType)
    {
        IServiceCallSite callSite;
        if (descriptor.ImplementationInstance != null)
        {
            callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);
        }
        else if (descriptor.ImplementationFactory != null)
        {
            callSite = new FactoryCallSite(descriptor.ServiceType, descriptor.ImplementationFactory);
        }
        else if (descriptor.ImplementationType != null)
        {
            callSite = CreateConstructorCallSite(descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
        }
        else
        {
            throw new InvalidOperationException("Invalid service descriptor");
        }

        return ApplyLifetime(callSite, descriptor, descriptor.Lifetime);
    }

    return null;
}

private IServiceCallSite CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain)
{
    callSiteChain.Add(serviceType, implementationType);

    var constructors = implementationType.GetTypeInfo()
        .DeclaredConstructors
        .Where(constructor => constructor.IsPublic)
        .ToArray();

    IServiceCallSite[] parameterCallSites = null;

    if (constructors.Length == 0)
    {
        throw new InvalidOperationException(Resources.FormatNoConstructorMatch(implementationType));
    }
    else if (constructors.Length == 1)
    {
        var constructor = constructors[0];
        var parameters = constructor.GetParameters();
        if (parameters.Length == 0)
        {
            return new CreateInstanceCallSite(serviceType, implementationType);
        }

        parameterCallSites = CreateArgumentCallSites(
            serviceType,
            implementationType,
            callSiteChain,
            parameters,
            throwIfCallSiteNotFound: true);

        return new ConstructorCallSite(serviceType, constructor, parameterCallSites);
    }

    ...
}

private IServiceCallSite ApplyLifetime(IServiceCallSite serviceCallSite, object cacheKey, ServiceLifetime descriptorLifetime)
{
    if (serviceCallSite is ConstantCallSite)
    {
        return serviceCallSite;
    }

    switch (descriptorLifetime)
    {
        case ServiceLifetime.Transient:
            return new TransientCallSite(serviceCallSite);
        case ServiceLifetime.Scoped:
            return new ScopedCallSite(serviceCallSite, cacheKey);
        case ServiceLifetime.Singleton:
            return new SingletonCallSite(serviceCallSite, cacheKey);
        default:
            throw new ArgumentOutOfRangeException(nameof(descriptorLifetime));
    }
}

ServiceProvider真正解析的是這個生成出來的CallSite對象。

protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
{
    var callCount = 0;
    return scope =>
    {
        if (Interlocked.Increment(ref callCount) == 2)
        {
            Task.Run(() => base.RealizeService(callSite));
        }
        return RuntimeResolver.Resolve(callSite, scope);
    };
}

public object Resolve(IServiceCallSite callSite, ServiceProviderEngineScope scope)
{
    return VisitCallSite(callSite, scope);
}

protected virtual TResult VisitCallSite(IServiceCallSite callSite, TArgument argument)
{
    switch (callSite.Kind)
    {
        case CallSiteKind.Factory:
            return VisitFactory((FactoryCallSite)callSite, argument);
        case  CallSiteKind.IEnumerable:
            return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
        case CallSiteKind.Constructor:
            return VisitConstructor((ConstructorCallSite)callSite, argument);
        case CallSiteKind.Transient:
            return VisitTransient((TransientCallSite)callSite, argument);
        case CallSiteKind.Singleton:
            return VisitSingleton((SingletonCallSite)callSite, argument);
        case CallSiteKind.Scope:
            return VisitScoped((ScopedCallSite)callSite, argument);
        case CallSiteKind.Constant:
            return VisitConstant((ConstantCallSite)callSite, argument);
        case CallSiteKind.CreateInstance:
            return VisitCreateInstance((CreateInstanceCallSite)callSite, argument);
        case CallSiteKind.ServiceProvider:
            return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
        case CallSiteKind.ServiceScopeFactory:
            return VisitServiceScopeFactory((ServiceScopeFactoryCallSite)callSite, argument);
        default:
            throw new NotSupportedException($"Call site type {callSite.GetType()} is not supported");
    }
}

因為上例中CallSite的類型是Constructor,所以最終通過VisitConstructor方法獲得所依賴的對象。

protected override object VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
{
    object[] parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
    for (var index = 0; index < parameterValues.Length; index++)
    {
        parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], scope);
    }

    try
    {
        return constructorCallSite.ConstructorInfo.Invoke(parameterValues);
    }
    catch (Exception ex) when (ex.InnerException != null)
    {
        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        // The above line will always throw, but the compiler requires we throw explicitly.
        throw;
    }
}

至於創建對象的方法是用反射,表達式樹(Expression Tree)還是IL Emit,則取決於所使用的內部引擎。

釋放

創建ServiceProviderEngine的時候會為其Root屬性綁定ServiceProviderEngineScope類型的值,Root = new ServiceProviderEngineScope(this);

在ServiceProviderEngineScope類內部有著用於釋放資源的Dispose方法。

public void Dispose()
{
    lock (ResolvedServices)
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;
        if (_disposables != null)
        {
            for (var i = _disposables.Count - 1; i >= 0; i--)
            {
                var disposable = _disposables[i];
                disposable.Dispose();
            }

            _disposables.Clear();
        }

        ResolvedServices.Clear();
    }
}

internal object CaptureDisposable(object service)
{
    _captureDisposableCallback?.Invoke(service);

    if (!ReferenceEquals(this, service))
    {
        if (service is IDisposable disposable)
        {
            lock (ResolvedServices)
            {
                if (_disposables == null)
                {
                    _disposables = new List<IDisposable>();
                }

                _disposables.Add(disposable);
            }
        }
    }
    return service;
}

並不是所有對象都會通過ServiceProvider容器釋放資源,只有容器自己創建的才可以。如果是新建對象再傳入容器,容器不會為其作處理。

public void ConfigureServices(IServiceCollection services)
{
    // 容器創建了實例所以會釋放它。
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // 容器沒有創建實例所以不會釋放它。
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

註入

ASP.NET Core中最常用的是Constructor Inject(構造器註入)方式。在其MVC框架中,通過DefaultControllerActivator生成Controller時,就可以跟蹤到依賴註入是如何被其使用的。

public virtual object Create(ControllerContext controllerContext)
{
    ...

    var serviceProvider = controllerContext.HttpContext.RequestServices;
    return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());
}

DefaultControllerActivator的Create方法使用了TypeActivatorCache類,其內部用到了ActivatorUtilities.CreateFactory方法。

public class TypeActivatorCache : ITypeActivatorCache
{
    private readonly Func<Type, ObjectFactory> _createFactory =
        (type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes);
    private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =
           new ConcurrentDictionary<Type, ObjectFactory>();

    public TInstance CreateInstance<TInstance>(
        IServiceProvider serviceProvider,
        Type implementationType)
    {
        ...

        var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory);
        return (TInstance)createFactory(serviceProvider, arguments: null);
    }
}

ActivatorUtilities類位於ServiceProvider同樣的程式集中。

public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes)
{
    FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap);

    var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
    var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray");
    var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray);

    var factoryLamda = Expression.Lambda<Func<IServiceProvider, object[], object>>(
        factoryExpressionBody, provider, argumentArray);

    var result = factoryLamda.Compile();
    return result.Invoke;
}

留意BuildFactoryExpression方法中GetServiceInfo變數。

private static Expression BuildFactoryExpression(
    ConstructorInfo constructor,
    int?[] parameterMap,
    Expression serviceProvider,
    Expression factoryArgumentArray)
{
    var constructorParameters = constructor.GetParameters();
    var constructorArguments = new Expression[constructorParameters.Length];

    for (var i = 0; i < constructorParameters.Length; i++)
    {
        var constructorParameter = constructorParameters[i];
        var parameterType = constructorParameter.ParameterType;
        var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out var defaultValue);

        if (parameterMap[i] != null)
        {
            constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i]));
        }
        else
        {
            var parameterTypeExpression = new Expression[] { serviceProvider,
                Expression.Constant(parameterType, typeof(Type)),
                Expression.Constant(constructor.DeclaringType, typeof(Type)),
                Expression.Constant(hasDefaultValue) };
            constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression);
        }

        ...
    }

    return Expression.New(constructor, constructorArguments);
}

GetServiceInfo變數申明瞭對GetService方法的調用,而此GetService其實正是對ServiceProvider的調用。

private static readonly MethodInfo GetServiceInfo =
    GetMethodInfo<Func<IServiceProvider, Type, Type, bool, object>>((sp, t, r, c) => GetService(sp, t, r, c));

private static object GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
{
    var service = sp.GetService(type);
    ...
    
    return service;
}

通過以上的處理,在創建Controller時,其構造方法中所需參數的類型也會被容器解析,創建相應實例,從而實現依賴註入功能。

生命周期

ASP.NET Core容器可以創建三種生命周期的對象:

  • Transient, 每次取得的都是新的對象。
  • Scoped, 每次ASP.NET請求生成不同的對象。
  • Singleton,同一對象只會生成一次。

檢視這三者類型的構造方法,可以很容易理解Scoped與Singleton是通過緩存的方式實現對象的重用。

public TransientCallSite(IServiceCallSite serviceCallSite)
{
    ServiceCallSite = serviceCallSite;
}

public ScopedCallSite(IServiceCallSite serviceCallSite, object cacheKey)
{
    ServiceCallSite = serviceCallSite;
    CacheKey = cacheKey;
}

public SingletonCallSite(IServiceCallSite serviceCallSite, object cacheKey) : base(serviceCallSite, cacheKey)
{
}

性能

由於ServiceProvider容器使用了反射,表達式樹以及IL Emit方式創建對象,可能會對其性能有所擔憂,但實際檢測的結果,除了Runtime引擎表現不盡如人意外,其它引擎的性能還是在可接受範圍內的。

public class GetServiceBenchmark
{
    private const int OperationsPerInvoke = 50000;

    private IServiceProvider _transientSp;
    private ServiceProviderMode _mode;

    [Params("Expressions", "Dynamic", "Runtime", "ILEmit")]
    public string Mode {
        set {
            _mode = (ServiceProviderMode)Enum.Parse(typeof(ServiceProviderMode), value);
        }
    }

    [Benchmark(Baseline = true, OperationsPerInvoke = OperationsPerInvoke)]
    public void NoDI()
    {
        for (int i = 0; i < OperationsPerInvoke; i++)
        {
            var temp = new A(new B(new C()));
            temp.Foo();
        }
    }

    [GlobalSetup(Target = nameof(Transient))]
    public void SetupTransient()
    {
        var services = new ServiceCollection();
        services.AddTransient<A>();
        services.AddTransient<B>();
        services.AddTransient<C>();
        _transientSp = services.BuildServiceProvider(new ServiceProviderOptions()
        {
            Mode = _mode
        });
    }

    [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
    public void Transient()
    {
        for (int i = 0; i < OperationsPerInvoke; i++)
        {
            var temp = _transientSp.GetService<A>();
            temp.Foo();
        }
    }

    private class A
    {
        public A(B b)
        {

        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Foo()
        {

        }
    }

    private class B
    {
        public B(C c)
        {

        }
    }

    private class C
    {

    }
}
// ***** BenchmarkRunner: Finish  *****

// * Export *

// * Detailed results *
GetServiceBenchmark.NoDI: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Dynamic]
Runtime = ; GC = 
Mean = 5.5175 ns, StdErr = 0.0116 ns (0.21%); N = 15, StdDev = 0.0449 ns
Min = 5.4490 ns, Q1 = 5.4860 ns, Median = 5.5207 ns, Q3 = 5.5641 ns, Max = 5.5972 ns
IQR = 0.0781 ns, LowerFence = 5.3688 ns, UpperFence = 5.6813 ns
ConfidenceInterval = [5.4695 ns; 5.5654 ns] (CI 99.9%), Margin = 0.0480 ns (0.87% of Mean)
Skewness = 0.15, Kurtosis = 1.67


GetServiceBenchmark.Transient: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Dynamic]
Runtime = ; GC = 
Mean = 43.1601 ns, StdErr = 0.0677 ns (0.16%); N = 15, StdDev = 0.2620 ns
Min = 42.7731 ns, Q1 = 42.9117 ns, Median = 43.2403 ns, Q3 = 43.3580 ns, Max = 43.5392 ns
IQR = 0.4464 ns, LowerFence = 42.2421 ns, UpperFence = 44.0276 ns
ConfidenceInterval = [42.8800 ns; 43.4402 ns] (CI 99.9%), Margin = 0.2801 ns (0.65% of Mean)
Skewness = -0.2, Kurtosis = 1.59


GetServiceBenchmark.NoDI: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Expressions]
Runtime = ; GC = 
Mean = 5.6964 ns, StdErr = 0.0388 ns (0.68%); N = 33, StdDev = 0.2226 ns
Min = 5.5148 ns, Q1 = 5.5603 ns, Median = 5.6042 ns, Q3 = 5.6769 ns, Max = 6.2460 ns
IQR = 0.1166 ns, LowerFence = 5.3854 ns, UpperFence = 5.8518 ns
ConfidenceInterval = [5.5561 ns; 5.8368 ns] (CI 99.9%), Margin = 0.1404 ns (2.46% of Mean)
Skewness = 1.48, Kurtosis = 3.69


GetServiceBenchmark.Transient: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Expressions]
Runtime = ; GC = 
Mean = 43.6662 ns, StdErr = 0.0995 ns (0.23%); N = 13, StdDev = 0.3586 ns
Min = 43.1083 ns, Q1 = 43.5089 ns, Median = 43.6051 ns, Q3 = 43.7178 ns, Max = 44.6669 ns
IQR = 0.2089 ns, LowerFence = 43.1956 ns, UpperFence = 44.0311 ns
ConfidenceInterval = [43.2368 ns; 44.0957 ns] (CI 99.9%), Margin = 0.4295 ns (0.98% of Mean)
Skewness = 1.41, Kurtosis = 5.18


GetServiceBenchmark.NoDI: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=ILEmit]
Runtime = ; GC = 
Mean = 5.6016 ns, StdErr = 0.0071 ns (0.13%); N = 13, StdDev = 0.0255 ns
Min = 5.5547 ns, Q1 = 5.5896 ns, Median = 5.5996 ns, Q3 = 5.6226 ns, Max = 5.6400 ns
IQR = 0.0330 ns, LowerFence = 5.5401 ns, UpperFence = 5.6721 ns
ConfidenceInterval = [5.5712 ns; 5.6321 ns] (CI 99.9%), Margin = 0.0305 ns (0.54% of Mean)
Skewness = -0.47, Kurtosis = 2.12


GetServiceBenchmark.Transient: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=ILEmit]
Runtime = ; GC = 
Mean = 43.1397 ns, StdErr = 0.0726 ns (0.17%); N = 15, StdDev = 0.2812 ns
Min = 42.7061 ns, Q1 = 42.9064 ns, Median = 43.1052 ns, Q3 = 43.3093 ns, Max = 43.6443 ns
IQR = 0.4028 ns, LowerFence = 42.3022 ns, UpperFence = 43.9135 ns
ConfidenceInterval = [42.8392 ns; 43.4403 ns] (CI 99.9%), Margin = 0.3006 ns (0.70% of Mean)
Skewness = 0.28, Kurtosis = 1.9


GetServiceBenchmark.NoDI: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Runtime]
Runtime = ; GC = 
Mean = 6.4814 ns, StdErr = 0.0762 ns (1.18%); N = 100, StdDev = 0.7617 ns
Min = 5.4979 ns, Q1 = 5.8327 ns, Median = 6.3039 ns, Q3 = 6.9775 ns, Max = 8.0420 ns
IQR = 1.1448 ns, LowerFence = 4.1155 ns, UpperFence = 8.6947 ns
ConfidenceInterval = [6.2231 ns; 6.7397 ns] (CI 99.9%), Margin = 0.2583 ns (3.99% of Mean)
Skewness = 0.52, Kurtosis = 1.94


GetServiceBenchmark.Transient: Job-NHLENA(Toolchain=InProcessToolchain, RunStrategy=Throughput) [Mode=Runtime]
Runtime = ; GC = 
Mean = 581.5066 ns, StdErr = 1.6962 ns (0.29%); N = 15, StdDev = 6.5695 ns
Min = 571.4934 ns, Q1 = 576.3829 ns, Median = 580.8121 ns, Q3 = 587.2645 ns, Max = 596.3317 ns
IQR = 10.8816 ns, LowerFence = 560.0605 ns, UpperFence = 603.5869 ns
ConfidenceInterval = [574.4834 ns; 588.5297 ns] (CI 99.9%), Margin = 7.0232 ns (1.21% of Mean)
Skewness = 0.58, Kurtosis = 2.48


Total time: 00:03:11 (191.85 sec)

第三方容器

如果想用第三方容器替換ASP.NET Core原有的容器也是可以辦到的。以最常見的Autofac為例,有兩種實現方式:

藉助ConfigureContainer方法,要先在Program類中掛載AddAutofac方法。

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args).ConfigureServices(services => services.AddAutofac())
            .UseStartup<Startup>();
}

然後在Startup類中加入ConfigureContainer方法。

public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterModule(new DefaultModule());

}

例中的DefaultModule類按照Autofac的通用方式實現。

public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<HelloWorld>().As<IHelloWorld>();
    }
}

如果不想使用ConfigureContainer方法,也可以直接利用ConfigureServices方法:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

需要註意的是,使用這種方式時,ConfigureServices方法的返回類型要從void改成IServiceProvider。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、線性表的定義 線性表是n(n>=0)個具有相同特性的數據元素的有限序列。 線性表是最簡單、最常用的一種數據結構 線性表屬於線性結構的一種 如果一個數據元素序列滿足: (1)除第一個和最後一個數據元素外,每個數據元素只有一個前驅數據元素和一個後繼數據元素; (2)第一個數據元素沒有前驅數據元素; ...
  • 題意 給出三個已經排好序的數組$a, b, c$ 在$100$次詢問內找出第$k$小的元素 Sol 一種很顯然的$log^2n$的做法:首先在$a$中二分,然後再$b,c$中二分。這樣可以得到$60$分的好成績。 然而這演算法就沒什麼優化的空間了。。。 考慮另一種做法。 我們每次對三個數組詢問第$\f ...
  • 有時候,可能會有一些類似這樣的需求,具體如圖所見,取首字母。 ...
  • 前言 Spring框架為什麼如此流行? 原來Spring框架解決了一個很關鍵的問題,它可以把對象之間的依賴關係轉為用配置文件來管理,也就是它的依賴註入機制。IOC容器用來管理這些Bean,管理Bean的關係以及生命周期,然而這與之前將應用程式主動new對象不同,Spring實現使用IOC容器創建對象 ...
  • 接著 上篇 目前也算是交代清楚了相關的類。那麼框架具體是如何來實例化的呢?整個的流程是怎麼樣的。 我們參考源碼中的Test文件夾來看看: var collection = new ServiceCollection(); collection.AddTransient<DependOnNonexis ...
  • 本文主要以一個簡單的小例子,描述C# Winform程式異常關閉時,如何進行捕獲,並記錄日誌。 ...
  • 誤解一:併發就是多線程 實際上多線程只是併發編程的一種形式,在C 中還有很多更實用、更方便的併發編程技術,包括非同步編程、並行編程、TPL 數據流、響應式編程等。 誤解二:只有大型伺服器程式才需要考慮併發 伺服器端的大型程式要響應大量客戶端的數據請求,當然要充分考慮併發。但是桌面程式和手機、平板等移動 ...
  • 1. 程式集和CIL: 程式集是由.NET語言的編譯器接受源代碼文件產生的輸出文件,通常分為 exe和dll兩類,其中exe包含Main入口方法可以雙擊執行,dll則需要被其他程式集調用執行。 CIL(Common Intermediate Language): 公共中間語言①,需要被編譯成二進位機 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...