4

WithMappings.FromMatchingInterface特定のインターフェースを実装するすべてのクラスを規約で Unity に登録したい。さらに、登録されているすべてのオブジェクトを、インターフェイス インターセプト動作を使用してインターセプトしたいと考えています。問題は、Unity が具象クラス間のマッピングも登録し、それらのクラスが解決されると、次のメッセージとともに例外がスローされることです。

「[type] は傍受できません」

具象クラス タイプを使用してオブジェクトを解決することはベスト プラクティスではないことは理解していますが、慣例に従って登録するときに、Unity がインターフェース -> 具象クラスと具象クラス -> 具象クラスの両方のマッピングを自動的に追加するのはなぜでしょうか? これは、インターフェイス インターセプターを追加して具象型を使用して解決すると、機能しないことを意味します。

これに対する私の望ましい結果は、Unity が、慣例に従って登録し、インターフェイス インターセプターを与えるときに、具象型 -> 具象型マッピングを追加しなかったことです。そうすれば、必要に応じて具象型を使用してクラスを解決できます。インターセプトを取得しません。

VirtualMethodInterceptorインターセプトが機能するためにクラスに変更を加えたくないので、 を使用したくありません。これには からの継承が含まれMarshalByRefます。また、すべてのオブジェクトを個別に登録することも避けたいです。

したがって、私の質問は、規則に従って登録するときにインターフェイス マッピングだけを登録するにはどうすればよいですか?

更新:クラスを個別に登録すると同じ問題が発生するため、オブジェクトがインターフェイスインターセプターに登録されると、具象型を使用して解決できないと想定されます。

新しい登録コード:

container.RegisterType<ISomeService, SomeService>(new InjectionMember[]
            {
                new Interceptor<InterfaceInterceptor>(), 
                new InterceptionBehavior<TraceInterceptor>()
            });
        container.RegisterType<ISomeRepository, SomeRepository>(new InjectionMember[]
            {
                new Interceptor<InterfaceInterceptor>(), 
                new InterceptionBehavior<TraceInterceptor>()
            });

更新 2すべてのインターフェイスにデフォルトのインターセプターを追加することは機能しているようですが、このソリューションはややハックです。このソリューションでは、規約による標準登録の直前に少しコードを追加InterfaceInterceptorし、規約ベースの登録で を削除する必要があります。

事前登録コード

foreach (var type in types)
{
   container
       .Configure<Interception>()
       .SetDefaultInterceptorFor(type.GetInterface("I" + type.Name), new InterfaceInterceptor());
}

ジレンマを説明するいくつかのコード:

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();
            container.AddNewExtension<Interception>();

            var types = AllClasses.FromAssemblies(typeof(ISomeService).Assembly).Where(type => type == typeof(SomeService) || type == typeof(SomeRepository));

            container.RegisterTypes(
                types,
                WithMappings.FromMatchingInterface,
                getLifetimeManager: WithLifetime.ContainerControlled,
                getInjectionMembers: c => new InjectionMember[]
                {
                    new Interceptor<InterfaceInterceptor>(), 
                    new InterceptionBehavior<TraceInterceptor>()
                });

            // this works fine, the interceptor does what it is supposed to.
            var someService1 = container.Resolve<ISomeService>();
            someService1.SomeServiceMethod("Hello from main method");

            // should I by any chance resolve using the concrete service directly, it has a meltdown due to the interface interceptor.
            var someService2 = container.Resolve<SomeService>();
            someService2.SomeServiceMethod("This will never be shown due to a hissy fit thrown by the container about the concrete SomeService is not interceptable.");
        }
    }

    public class TraceInterceptor : IInterceptionBehavior
    {
        public System.Collections.Generic.IEnumerable<System.Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Trace.WriteLine(string.Format("Hello from trace interception behavior for type [{0}]!", input.Target.GetType().FullName));

            return getNext().Invoke(input, getNext);
        }

        public bool WillExecute
        {
            get { return true; }
        }
    }

    public interface ISomeService
    {
        string SomeServiceMethod(string someParameter);
    }

    public class SomeService : ISomeService
    {
        private ISomeRepository _someRepository;

        public SomeService(ISomeRepository someRepository)
        {
            _someRepository = someRepository;
        }

        public string SomeServiceMethod(string someParameter)
        {
            return _someRepository.SomeRepositoryMethod(someParameter);
        }
    }

    public interface ISomeRepository
    {
        string SomeRepositoryMethod(string someParameter);
    }

    public class SomeRepository : ISomeRepository
    {
        public string SomeRepositoryMethod(string someParameter)
        {
            Trace.WriteLine(someParameter);

            return "Hello from repository!";
        }
    }
}
4

1 に答える 1

5

私はあなたの質問にいくつかの光を当てることができると信じています. 私も、同じ具象型をそのインターフェイスにマッピングする登録と共に、具象型が登録された理由を疑問に思っていました。

私が何か間違ったことをしたのではないかと考えて、他の誰かが同じ問題に遭遇したかどうかを調べるために検索を行いました. しかし、私はこの Codeplex ディスカッション スレッドにたどり着きました: Registration Convention generate two registrations for each mapping。このスレッドで、randylevy (最後の投稿から 3 番目の投稿) はLifetimeManager、規則の一部として a および/または注入メンバーが指定されている場合、これが既定の動作であると述べています。

表示されている動作に関しては、これは設計によるものだと思います。LifetimeManagerまたはを指定するとinjectionMembers、Unity は指定された値で渡されたすべてのタイプを登録します。ユーザーは、渡された型に対して必要なさまざまな構成を指定しているため、これは一般的に理にかなっています。

たとえば、慣例による登録を使用して、型を登録することもできます。

    container.RegisterTypes(
        AllClasses.FromLoadedAssemblies(false, false, false, false),
        WithMappings.None,
        WithName.Default, 
        t => new ContainerControlledLifetimeManager(), 
        null, 
        true);

したがって、この場合、すべてのクラス (たとえば) は、インターフェイス マッピングなし( ) のClass1シングルトン ( ) として登録されます。生涯マネージャーが指定されていない場合は、登録されていません。ただし、ライフタイム マネージャーが指定されているため、正しいユーザー指定のライフタイム マネージャーを使用するように登録をセットアップする必要があります。ContainerControlledLifetimeManagerWithMappings.NoneClass1

慣例による登録を使用するときに、特定の型のインターフェイス型マッピングに加えて、具体的な型マッピングがある理由についての質問に答えると思います。そのディスカッションスレッドのOPが述べたように、具体的な型マッピングを無効にするために、コンベンションクラスによる登録で設定できるオプションがあったことを私も望んでいます。

しかし、結局のところ、それが大きな違いを生むかどうかはわかりません。コントラクトに対してプログラミングしている場合 (たとえば、コンストラクター/メソッド/プロパティ引数にインターフェイス タイプを使用している場合)、コンテナーは常にインターフェイス マッピング登録を使用して解決されます。また、そのインターフェース登録でインジェクション/インターセプトが設定されている場合は、解決時にタイプに適切なオブジェクトがインジェクトされ、構成されたインターセプトが発生する必要があります。

私が働いている場所では、いくつかの異なる種類の一般的な登録を行う必要があります。つまり、サービス、リポジトリ、その他のクラス/インターフェースのカテゴリです。例として、インターフェイスに登録する必要があり、バリデーターが関連付けられている Service クラスがたくさんあるとします。命名規則はMyService、対応するインターフェースIMyServiceと対応するバリデーターMyServiceValidatorです。ServiceRegistrationConventionこれを達成するためのクラスを次のように作成しました。

public class ServiceRegistrationConvention : RegistrationConvention
{
    /// <summary>
    /// Gets a function to get the types that will be requested for 
    /// each type to configure.
    /// </summary>
    public override Func<Type, IEnumerable<Type>> GetFromTypes()
    {
        return WithMappings.FromMatchingInterface;
    }

    /// <summary>
    /// Gets a function to get the additional 
    /// <see cref="T:Microsoft.Practices.Unity.InjectionMember" /> 
    /// objects for the registration of each type. Defaults to no injection members.
    /// </summary>
    public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers()
    {
        return GetServiceValidator;
    }

    /// <summary>
    /// Gets a function to get the 
    /// <see cref="T:Microsoft.Practices.Unity.LifetimeManager" /> 
    /// for the registration of each type. Defaults to no 
    /// lifetime management (e.g. transient).
    /// </summary>
    public override Func<Type, LifetimeManager> GetLifetimeManager()
    {
        // Where I work, we use this lifetime manager for everyting.
        // I wouldn't recommend this right off the bat--I'm looking 
        // into changing this, personally, but right now, this is 
        // what we use and it works for our MVC application.
        return WithLifetime.Custom<PerRequestLifetimeManager>;
    }

    /// <summary>
    /// Gets a function to get the name to use for the registration of each type.
    /// </summary>
    public override Func<Type, string> GetName()
    {
        return WithName.Default;
    }

    /// <summary>
    /// Gets types to register.
    /// </summary>
    public override IEnumerable<Type> GetTypes()
    {
        // You may want to further restrict the search for classes to register
        // by doing some sort of namespace matching:
        //
        //     t.Namespace.StartsWith(
        //         "MyCompanyNamespacePrefix", StringComparison.Ordinal
        //     )
        //
        // for example.
        return AllClasses.FromLoadedAssemblies()
            .Where(t => t.Name.EndsWith("Service", StringComparison.Ordinal));
    }

    /// <summary>
    /// Given a type, get the type's corresponding validator, if any.
    /// </summary>
    private IEnumerable<InjectionMember> GetServiceValidator(Type type)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        // In our case, the validators live in the same assembly 
        // as the class its validating...
        string matchingValidatorName = string.Concat(type.Name, "Validator");
        Type vType = AllClasses.FromAssemblies(new[] { type.Assembly })
            .FirstOrDefault(t => 
                string.Equals(t.Name, matchingValidatorName, StringComparison.Ordinal)
        );

        return (vType != null) ? 
            new List<InjectionMember> 
            {
                new Interceptor<InterfaceInterceptor>(),
                new InterceptionBehavior(vType)
            }
            :
            null;
    }
}

繰り返しになりますが、常にインターフェイスから型を解決している限り、すべてがうまく機能するはずです。

更新:残念ながら、インターセプトが正しく機能していません。問題を発見したら、必ず回答を更新します。

更新:これはここに書かれているとおりに機能します。別の構成でエラーが発生し、アプリケーション全体が失敗しました。そのエラーを修正すると、インターセプトが期待どおりに発生しています。

于 2014-07-05T17:03:52.897 に答える