按惯例使用界面拦截器进行Unity注册会导致" [type]不可拦截"例外

时间:2014-04-04 17:13:44

标签: unity-container unity-interception

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

  

" [type]不可拦截"

我意识到使用具体类类型解析对象不是最佳实践,但我想知道为什么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>()
            });

更新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!";
        }
    }
}

1 个答案:

答案 0 :(得分:5)

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

我做了一个搜索,发现是否有其他人遇到过同样的问题,以为我可能做错了什么。但我最终在这个Codeplex讨论主题:Registration convention generates two registrations for each mapping。在这个线程中,randylevy(在第3个到最后一个帖子中)指出当LifetimeManager和/或注入成员被指定为约定的一部分时,这是默认行为:

  

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

     

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

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

所以在这种情况下,所有类(假设 Class1 )都将注册为单例( 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;
    }
}

同样,只要您始终从其界面解析类型,一切都应该正常工作。

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

更新:这与此处所写完全相同。我的另一个配置出错导致整个应用程序失败。一旦我修复了这个错误,就会按预期进行拦截。