2016-04-06 12 views
0

私たちのプロジェクトでは、SimpleInjectorを使用しており、それを愛しています! 私たちは、サービスの登録後の登録は、以下のスレッドの安全性の要求に準拠して検証することをコンテナに拡張子を追加したい:コンテナ登録のスレッド安全性を確認する方法

  1. 場合は登録されているすべてのシングルトンが自作の[スレッドセーフ]
  2. で注釈付けする必要があります[ThreadSafe]でサービスに注釈を付けると、実装タイプも[ThreadSafe]に登録する必要があります。

またGetCurrentRegistrations()のようなオープンジェネリック型の登録によって提供されていませんデータSimpleInjectorを追跡する同様の登録方法を公開しSimpleInjectorのコンテナを、ラップThreadSafetyVerifyingContainerを持っていることによって、今このことを確認します。このアプローチは機能しますが、条件付き登録でFuncファクトリをサポートするのに苦労するような制限があり、コンテナにVerifyを呼び出してスレッドセーフな検証を挿入するための検証プロセスに突入する方法があるかどうか自問します。現在のコードは次のとおりです:

/// <summary> 
/// A wrapper around C that exposes the subset of <see cref="Container"/> API required for registration at the moment, 
/// that during <see cref="Verify"/> it verifies registrations using the underline container and additionally 
/// verifies that current registrations conform to thread-safety expectations. 
/// </summary> 
public class ThreadSafetyVerifyingContainer 
{ 
    public ThreadSafetyVerifyingContainer() 
    { 
     _underlineContainer = new Container(); 
     _openGenericExplicitRegistrations = new List<RegistrationDescription>(); 
     _threadSafetyExternalAnnotations = new HashSet<Type>(); 
     MarkWellKnownExternalThreadSafeTypes(); 
    } 

    public ContainerOptions Options => _underlineContainer.Options; 

    public Container Verify(VerificationOption options = VerificationOption.VerifyAndDiagnose) 
    { 
     _underlineContainer.Verify(options); 
     VerifyThreadSafety(); 

     return _underlineContainer; 
    } 

    // Marks a type as thread-safe as a replacement to applying <see cref="ThreadSafeAttribute"/> to a type. 
    // Should be applied to external types not defined in the application, since <see cref="ThreadSafeAttribute"/> 
    // exists also for documentation purposes to let developers know some types are expected to be thread safe. 
    public void MarkExternalTypeAsThreadSafe(Type externalType) 
    { 
     _threadSafetyExternalAnnotations.Add(externalType); 
    } 

    private void VerifyThreadSafety() 
    { 
     var allRegistrationsDescriptions = _underlineContainer 
      .GetCurrentRegistrations() 
      .Select(RegistrationDescription.FromInstanceProducer) 
      .Concat(_openGenericExplicitRegistrations) 
      .ToArray(); 

     var invalidSingletonRegistrations = allRegistrationsDescriptions 
      .Where(registration => 
       registration.Lifestyle == Lifestyle.Singleton && 
       !IsImplementationTypeMarkedAsThreadSafe(registration)) 
      .Select(registration => 
       $"The type {registration.ImplementationType} is registered as singleton but isn't marked as thread-safe using {nameof(ThreadSafeAttribute)}"); 

     var invalidThreadSafeServiceRegistrations = allRegistrationsDescriptions 
      .Where(registration => 
       IsTypeMarkedAsThreadSafe(registration.ServiceType) && 
       !IsImplementationTypeMarkedAsThreadSafe(registration)) 
      .Select(registration => 
       $"The type {registration.ImplementationType} isn't marked as thread-safe using {nameof(ThreadSafeAttribute)} and is registered as implementation of {registration.ServiceType} which is marked as thread-safe"); 

     var violations = invalidSingletonRegistrations.Concat(invalidThreadSafeServiceRegistrations).ToArray(); 

     if (violations.Length > 0) 
     { 
      string errorMessage = 
       $"The container has thread-safety violating registrations:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}"; 
      throw new ThreadSafetyViolationException(errorMessage); 
     } 
    } 

    private void MarkWellKnownExternalThreadSafeTypes() 
    { 
     MarkExternalTypeAsThreadSafe(typeof(Container)); // SimpleInjector's Container is registered as singleton and is thread safe, ignore it 
     MarkExternalTypeAsThreadSafe(typeof(IEnumerable<>)); // Collections are IEnumerable<> implementations registered by the container as singletons, ignore them 
    } 

    private bool IsImplementationTypeMarkedAsThreadSafe(RegistrationDescription registration) 
    { 
     return IsTypeMarkedAsThreadSafe(registration.ImplementationType); 
    } 

    private bool IsTypeMarkedAsThreadSafe(Type type) 
    { 
     return 
      type.GetCustomAttribute<ThreadSafeAttribute>() != null || 
      _threadSafetyExternalAnnotations.Contains(type) || 
      _threadSafetyExternalAnnotations.Any(threadSafeType => threadSafeType.IsGenericTypeDefinition && type.IsGenericOf(threadSafeType)); 
    } 

    private readonly HashSet<Type> _threadSafetyExternalAnnotations; 
    #endregion 

    public void Register<TService, TImplementation>(Lifestyle lifestyle) 
     where TService : class where TImplementation : class, TService 
    { 
     _underlineContainer.Register<TService, TImplementation>(lifestyle); 
    } 

    public void Register(Type openGenericServiceType, Assembly assembly, Lifestyle lifestyle) 
    { 
     _underlineContainer.Register(openGenericServiceType, new [] { assembly }, lifestyle); 
    } 

    public void RegisterSingleton<TService, TImplementation>() 
     where TImplementation : class, TService 
     where TService : class 
    { 
     _underlineContainer.RegisterSingleton<TService, TImplementation>(); 
    } 

    public void RegisterSingleton<TConcrete>() 
     where TConcrete : class 
    { 
     _underlineContainer.RegisterSingleton<TConcrete>(); 
    } 

    public void RegisterSingleton<TService>(TService instance) where TService : class 
    { 
     _underlineContainer.RegisterSingleton(instance); 
    } 

    public void RegisterScoped<TService, TImplementation>() 
     where TImplementation : class, TService 
     where TService : class 
    { 
     _underlineContainer.Register<TService, TImplementation>(Lifestyle.Scoped); 
    } 

    public void RegisterTransient<TService, TImplementation>() 
     where TService : class where TImplementation : class, TService 
    { 
     _underlineContainer.Register<TService, TImplementation>(); 
    } 

    public void RegisterTransient<TImplementation>() 
     where TImplementation : class 
    { 
     _underlineContainer.Register<TImplementation>(); 
    } 

    public void RegisterTransient<TService>(Func<TService> instanceCreator) 
     where TService : class 
    { 
     _underlineContainer.Register(instanceCreator); 
    } 

    public void RegisterCollectionScoped<TService>(IEnumerable<Type> concreteTypes) 
     where TService : class 
    { 
     _underlineContainer.RegisterCollection<TService>(concreteTypes.Select(type => Lifestyle.Scoped.CreateRegistration(type, _underlineContainer))); 
    } 

    public void Register(Type serviceType, Type implementationType, Lifestyle lifestyle) 
    { 
     _underlineContainer.Register(serviceType, implementationType, lifestyle); 
     RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle); 
    } 

    public void RegisterConditional(Type serviceType, Type implementationType, Lifestyle lifestyle, Predicate<PredicateContext> predicate) 
    { 
     _underlineContainer.RegisterConditional(serviceType, implementationType, lifestyle, predicate); 
     RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle); 
    } 

    public void RegisterImplementationByConsumerContext(Type notGenericServiceType, Type openGenericImplementationType, Lifestyle lifestyle) 
    { 
     Guard.CheckNullArgument(notGenericServiceType, nameof(notGenericServiceType)); 
     Guard.CheckNullArgument(openGenericImplementationType, nameof(openGenericImplementationType)); 
     Guard.CheckArgument(notGenericServiceType.IsGenericType, nameof(notGenericServiceType), $"Type {notGenericServiceType} shouldn't be a generic type but it is"); 
     Guard.CheckArgument(!openGenericImplementationType.ContainsGenericParameters, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} isn't an open-generic type"); 
     Guard.CheckArgument(openGenericImplementationType.GetGenericArguments().Length != 1, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} is open-generic but has more than 1 generic parameter"); 

     _underlineContainer.RegisterConditional(
      notGenericServiceType, 
      context => openGenericImplementationType.MakeGenericType(context.Consumer.ImplementationType), 
      lifestyle, 
      context => true); 
     RecordIfExplicitOpenGenericRegistration(notGenericServiceType, openGenericImplementationType, lifestyle); 
    } 

    private void RecordIfExplicitOpenGenericRegistration(Type serviceType, Type implementationType, Lifestyle lifestyle) 
    { 
     if (implementationType.ContainsGenericParameters) 
     { 
      _openGenericExplicitRegistrations.Add(new RegistrationDescription(serviceType, implementationType, lifestyle)); 
     } 
    } 

    private readonly List<RegistrationDescription> _openGenericExplicitRegistrations; 

    [DebuggerDisplay("{ServiceType.Name} -> {ImplementationType.Name} with {Lifestyle.Name} life style")] 
    private class RegistrationDescription 
    { 
     public RegistrationDescription(Type serviceType, Type implementationType, Lifestyle lifestyle) 
     { 
      ServiceType = serviceType; 
      ImplementationType = implementationType; 
      Lifestyle = lifestyle; 
     } 

     public static RegistrationDescription FromInstanceProducer(InstanceProducer instanceProducer) 
     { 
      return new RegistrationDescription(
       instanceProducer.ServiceType, 
       instanceProducer.Registration.ImplementationType, 
       instanceProducer.Registration.Lifestyle); 
     } 

     public Type ServiceType { get; } 
     public Type ImplementationType { get; } 
     public Lifestyle Lifestyle { get; } 
    } 

    private readonly Container _underlineContainer; 
} 
+0

私はExpressionBuildとExpressionBuildingイベントで演奏し、それが組み込みの検証プロセスに参加しているすべての登録をカバーするので、彼らはスレッドの安全性違反(または任意の他のサービスのリストを埋めるために使用することができ気づきました-implementatイオンライフスタイルのカスタムルール違反)を使用して、ビルトイン検証プロセスの終了時に例外を発生させます。 – Eldar

答えて

3

現在のところ、Verifyに接続する方法はありません。 Simple Injectorから多くの情報を取得して自分で分析する方法はいくつかありますが、たとえば、既知のInstanceProducerインスタンスのリストを取得する方法は、Container.GetCurrentRegistrationsです。オブジェクトグラフを反復的に反復できるようにするInstanceProducer.GetKnownRelationshipsメソッドがあります(診断サービスはこのメソッドとKnownRelationshipインスタンスを広く使用します)。

しかしながら、あなたはILifestyleSelectionBehaviorを上書きしている別のアプローチを見るかもしれません。カスタムライフスタイルを選択すると、明示的なライフスタイルが選択されていない場合にSimple Injectorが選択するライフスタイルに影響を与えることができます。デフォルトでは、Simple Injectorは一時的なライフスタイルを提供します。次のようにあなたのケースでは、カスタム動作がなります

class ThreadSafeAsSingletonLifestyleSelectionBehavior : ILifestyleSelectionBehavior { 
    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) { 
     var sa = serviceType.GetCustomAttribute<ThreadSafeAttribute>(); 
     var ia = implementationType.GetCustomAttribute<ThreadSafeAttribute>(); 
     if (sa != null && ia == null) throw new InvalidOperationException(
      "If a service is annotated with [ThreadSafe] its implementation "+ 
      "type should also be registered with [ThreadSafe]."); 

     return ia == null ? Lifestyle.Transient : Lifestyle.Singleton; 
    } 
} 

次のようにあなたがあなたのカスタム動作を登録することができます。

container.Options.LifestyleSelectionBehavior = 
    new ThreadSafeAsSingletonLifestyleSelectionBehavior(); 

を自動的にする[ThreadSafe]でマークされているすべての実装をしているこれが可能にシングルトンとして登録されており、サービスタイプがそうである場合に実装が[ThreadSafe]属性を持つように強制します。

これは、毎回あなたが明示的に、ライフスタイルを持つタイプを登録する例におけるライフスタイルの選択行動キックしていないことを意味します。

container.Register<IThreadSafeService, ThreadSafeImplementation>(); 

しかし、この登録はまだ過渡的なライフスタイルを取得することに注意してください:

container.Register<IThreadSafeService, ThreadSafeImplementation>(Lifestyle.Transient); 
関連する問題