按照惯例,使用接口拦截器进行的Unity注册会导致"[type]不可拦截".例外 [英] Unity registration by convention with interface interceptor causes "[type] is not interceptable" exception

查看:87
本文介绍了按照惯例,使用接口拦截器进行的Unity注册会导致"[type]不可拦截".例外的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用WithMappings.FromMatchingInterface约定将实现特定接口的所有类注册到Unity中.另外,我希望使用接口拦截行为来拦截所有注册的对象.问题在于,Unity还注册了具体类之间的映射,并且在解析了这些类之后,会在消息中引发异常:

I want to register all classes that implements a specific interface into Unity with the WithMappings.FromMatchingInterface convention. In addition, I want all of the registered objects to be intercepted using an interface interception behaviour. The problem is that Unity also registers mappings between the concrete classes, and when those classes are resolved, an exception is thrown with the message:

"[类型]不可拦截"

"[type] is not interceptable"

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

I realise that resolving an object using the concrete class type is not best practice but I wonder why Unity automatically adds mappings for both interface -> concrete class as well as concrete class -> concrete class when registering by convention? This means that it will never work if you add an interface interceptor and resolve using the concrete type.

我希望得到的结果是,在按照约定进行注册并为其提供接口拦截器时,Unity没有添加具体类型->具体类型映射,这样,如果我们愿意,我们可以使用其具体类型来解析该类,我们只是不会被拦截.

My desired outcome for this would be that Unity did not add the concrete type -> concrete type mappings when registering by convention and giving it an interface interceptor, that way we could resolve the class using its concrete type if we wanted to, we would just not get interception.

我不想使用VirtualMethodInterceptor,因为我不想对类进行更改以使侦听工作正常进行,这包括从MarshalByRef继承.我也想避免单独注册所有对象.

I do not want to use the VirtualMethodInterceptor as I do not want to impose changes to the classes in order for interception to work, this includes inheriting from MarshalByRef. I also want to avoid individually registering all the objects.

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

My questions is thus, how do I register just the interface mappings when registering by convention?

更新:单独注册类会遇到相同的问题,因此,假设一个对象通过接口拦截器注册后就无法通过使用具体类型来解决.

Update : registering the classes individually gives the same problem, so the assumption is therefore that once an object is registered with an interfaceinterceptor then it cannot be resolved by using the concrete type.

新的注册码:

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.

Update 2 Adding a default interceptor for all the interfaces seems to work although this solution is rather hacky. This solution requires a little code just before the standard registration by convention, and removal of the InterfaceInterceptor in the conventions-based registration.

预注册代码

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

推荐答案

我相信我可以阐明您的问题.我也想知道为什么要注册具体类型以及将相同具体类型映射到其接口的注册.

I believe I can shed some light on your questions. I, too, was wondering why the concrete type was registered along with a registration mapping the same concrete type to its interface.

我进行了一次搜索以发现是否有人遇到相同的问题,以为我做错了什么.但是,我最终来到了这个Codeplex讨论线程:注册约定为每个映射生成两个注册.在该线程中,randylevy(在最后一篇文章的第3条中)指出,这是惯例中指定LifetimeManager和/或注入成员时的默认行为:

I did a search to discover if anyone else encountered the same issue, thinking I may have done something wrong. But I ended up at this Codeplex discussion thread: Registration convention generates two registrations for each mapping. In this thread, randylevy (in the 3rd to last post) states that this is the default behavior when a LifetimeManager and/or injection members are specified as part of the convention:

就您所看到的行为而言,我相信这是设计使然.如果您指定LifetimeManagerinjectionMembers,则Unity将注册使用提供的值传递的任何类型.一般来说,这很有意义,因为用户已经为传入的类型指定了所需的各种配置.

In terms of the behavior you are seeing, I believe this is by design. If you specify a LifetimeManager or injectionMembers then Unity will register any types passed in with the supplied values. This makes sense in general because the user has specified the various configuration they desire for the types passed in.

例如,您还可以按约定使用注册来注册类型:

For example you can also use registration by convention to just register types:

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

因此,在这种情况下,所有类(比如说 Class1 )将被注册为单例( ContainerControlledLifetimeManager ),并且没有接口映射( ).如果未指定生命周期管理器,则不会注册 Class1 .但是,由于指定了生命周期管理器,因此需要将注册设置为使用正确的用户指定的生命周期管理器.

So in this case all classes (lets say Class1) will be registered as singletons (ContainerControlledLifetimeManager) with no interface mapping (WithMappings.None). If the lifetime manager was not specified then Class1 would not have been registered. But because the lifetime manager is specified the registration needs to be setup to use the correct user specified lifetime manager.

我相信您可以回答有关以下问题的问题:为什么在使用约定注册时,除了给定类型的接口类型映射外,还有具体的类型映射.正如该讨论线程上的OP所述,我也希望您可以在按约定注册类中设置一些选项以禁用具体的类型映射.

I believe that answers your question as to why there is a concrete type mapping in addition to the interface type mapping for a given type when using registration by convention. As the OP on that discussion thread stated, I too wish that there was some option you could set in the registration by convention class to disable the concrete type mapping.

但是,最终我不确定这会带来很大的不同.如果您要针对合同进行编程(例如,使用构造函数/方法/属性参数的接口类型),则容器将始终使用接口映射注册进行解析;并且如果在该接口注册上设置了任何注入/拦截,则在解析该类型时,应该注入适当的对象并进行配置的拦截.

In the end however, I'm not sure it makes a huge difference. If you're programming against contracts (e.g. using the interface type for the constructor/method/property arguments), then the container will always resolve using the interface mapping registration; and if any injection/interception is setup on that interface registration, then when resolved, the type should have the appropriate objects injected and the configured interception should occur.

在我工作的地方,我们需要进行几种不同类型的通用注册,即服务,存储库和其他类别的类/接口.举例来说,假设我有一堆Service类需要向其接口注册,并且还具有与之关联的验证器.命名约定为MyService和相应的接口IMyService和相应的验证器MyServiceValidator.我创建了一个ServiceRegistrationConvention类来完成此操作,如下所示:

At where I work, we have several different types of common registrations that need to occur, i.e. services, repositories, and other categories of classes/interfaces. For the sake of example, let's suppose I have a whole bunch of Service classes that need to be registered with their interfaces and which also have validators associated with them. The naming convention is MyService and a corresponding interface IMyService and a corresponding validator MyServiceValidator. I have created a ServiceRegistrationConvention class to accomplish this as follows:

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;
    }
}

同样,只要您始终在其接口中解析类型,一切都应该可以正常工作.

Again, so long as you're always resolving the type from its interface, everything should work just fine.

更新:很遗憾,拦截无法正常运行.当我发现问题时,一定会更新答案.

UPDATE: Well, unfortunately, interception is not working correctly. When I discover the issue, I'll be sure to update my answer.

更新:它的工作原理与此处的完全相同.我在使用另一个配置时出错,导致整个应用程序失败.一旦我纠正了该错误,就会按预期进行拦截.

UPDATE: This does work exactly as written here. I had an error with another configuration that caused the whole application to fail. Once I fixed that error, interception is occurring as expected.

这篇关于按照惯例,使用接口拦截器进行的Unity注册会导致"[type]不可拦截".例外的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆