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为所有接口添加默认拦截器似乎可行,尽管此解决方案相当 hacky。此解决方案需要在按约定进行标准注册之前的一些代码,并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 讨论线程:注册约定为每个映射生成两个注册。在这个线程中,randylevy(在第 3 篇到最后一篇文章中)指出,当 aLifetimeManager和/或注入成员被指定为约定的一部分时,这是默认行为:

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

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

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

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

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

但是,最后,我不确定它是否会产生巨大的差异。如果您正在针对合约进行编程(例如,使用接口类型作为构造函数/方法/属性参数),那么容器将始终使用接口映射注册来解析;并且如果在该接口注册上设置了任何注入/拦截,则在解析时,该类型应该注入适当的对象,并且应该发生配置的拦截。

在我工作的地方,我们需要进行几种不同类型的常见注册,即服务、存储库和其他类别的类/接口。举个例子,假设我有一大堆服务类需要注册它们的接口,并且它们也有与之关联的验证器。命名约定是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 回答