按惯例使用接口拦截器进行Unity注册会导致"[type]不可拦截"异常

And*_*rew 4 unity-container unity-interception

我希望使用WithMappings.FromMatchingInterface约定将实现特定接口的所有类注册到Unity中.另外,我希望使用接口拦截行为拦截所有已注册的对象.问题是Unity还注册了具体类之间的映射,当这些类被解析时,会抛出一个异常消息:

"[类型]不可拦截"

我意识到使用具体类类型解析对象不是最佳实践,但我想知道为什么Unity会自动为接口添加映射 - >具体类以及具体类 - >按惯例注册时的具体类?这意味着如果添加接口拦截器并使用具体类型解析它将永远不会工作.

我希望得到的结果是Unity在按惯例注册并给它一个接口拦截器时没有添加具体类型 - >具体类型映射,这样我们就可以使用它的具体类型来解析类,如果我们愿意,我们只是没有拦截.

我不想使用VirtualMethodInterceptor因为我不想对类进行更改以便拦截工作,这包括继承MarshalByRef.我还想避免单独注册所有对象.

因此,我的问题是,如何按惯例注册时只注册接口映射?

更新:单独注册类会产生相同的问题,因此假设一旦对象使用interfaceinterceptor注册,则无法通过使用具体类型来解析它.

新注册码:

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

更新2为所有接口添加默认拦截器似乎工作,虽然这个解决方案相当hacky.该解决方案在按惯例进行标准注册之前需要一些代码,并且InterfaceInterceptor在基于约定的注册中删除.

预注册代码

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

一些解释困境的代码:

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!";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

fou*_*ght 5

我相信我可以对你的问题有所了解.我也想知道为什么注册具体类型以及将相同的具体类型映射到其界面的注册.

我做了一个搜索,发现是否有其他人遇到过同样的问题,以为我可能做错了什么.但我最终在这个Codeplex讨论主题:注册约定为每个映射生成两个注册.在这个线程中,randylevy(在第3个到最后一个帖子中)声明当a LifetimeManager和/或注入成员被指定为约定的一部分时,这是默认行为:

就你所看到的行为而言,我相信这是设计的.如果您指定一个LifetimeManagerinjectionMembers然后Unity将注册使用提供的值传入的任何类型.这通常是有意义的,因为用户已经为传入的类型指定了他们想要的各种配置.

例如,您也可以按惯例使用注册来注册类型:

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

所以在这种情况下,所有类(比方说Class1)都将注册为singletons(ContainerControlledLifetimeManager),没有接口映射(WithMappings.None).如果未指定终身经理,则不会Class1注册.但是因为指定了生命周期管理器,所以需要设置注册以使用正确的用户指定的生命周期管理器.

我相信,在按惯例使用注册时,除了给定类型的接口类型映射之外,还有一个问题可以解答为什么还有一个具体的类型映射.正如该讨论主题中的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,只要你总是从它的界面解析类型,一切都应该工作得很好.

更新:不幸的是,拦截无法正常工作.当我发现问题时,我一定会更新我的答案.

更新:这完全按照这里所写的方式工作.我的另一个配置出错导致整个应用程序失败.一旦我修复了该错误,拦截就会按预期发生.