WCF service not interceptable using Unity 2.0 Interception

Jan 6, 2012 at 9:00 AM

In my WCF web application I have configured the Unity container for Interception. Following is my unity configuration.

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> 
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>

    <assembly name="Infrastructure" />
    <assembly name="WCFServiceLib1"/>

    <namespace name="Infrastructure"/>
    <namespace name="WCFServiceLib1" />

    <container>
      <extension type="Interception" />
      <register type="IService1" mapTo="Service1">
        <interceptor type="InterfaceInterceptor"/>
        <interceptionBehavior type="LogPerformanceDataBehavior"/>
      </register>
    </container>
</unity>

When I try to invoke a method on the service using wcftestclient tool, following exception is thrown.

ArgumentException - The type WCFServiceLib1.Service1 is not interceptable.
Parameter name: interceptedType

I used the svctraceviewer tool to get the above exception details.

Following is the implementation of the class LogPerformanceDataBehavior

public class LogPerformanceDataBehavior : IInterceptionBehavior
{
    public IEnumerable<Type> GetRequiredInterfaces()
    {
        return Type.EmptyTypes;
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        var watch = new Stopwatch();

        watch.Start();
        IMethodReturn methodReturn = getNext()(input, getNext);
        watch.Stop();

        string sb = string.Format("Method {0}.{1} executed in: ({2} ms, {3} ticks){4}",
                                  input.MethodBase.DeclaringType.Name, input.MethodBase.Name,
                                  watch.ElapsedMilliseconds, watch.ElapsedTicks, Environment.NewLine);

        using (StreamWriter outfile = new StreamWriter(@"c:\logs\Performance.txt"))
        {
            outfile.Write(sb);
        }

        return methodReturn;
    }

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

What could possibly be wrong?

Jan 8, 2012 at 8:41 AM
Edited Jan 10, 2012 at 6:25 AM

First off, I'm assuming that you are using the approach outlined by this article: Dependency Injection and WCF Services.

The issue is that the serviceType that the container is trying to resolve is the concrete type and not an interface and cannot be intercepted.

The easiest way to enable interception would be to change the service to extend MarshalByRefObject and change the interception type in the config file to TransparentProxyInterceptor.

If you really wanted to use an interface, you would have to change the plumbing code to resolve based on an interface.  One way to do that would be to "overload" the configuration to pass in the concrete class and the interface:

    <serviceHostingEnvironment multipleSiteBindingsEnabled="true">
      <serviceActivations>
        <add factory="UnityWCFInterception.UnityServiceHostFactory"
             service="UnityWCFInterception.Service1;UnityWCFInterception.IService1"
             relativeAddress="Service1.svc" />
      </serviceActivations>
    </serviceHostingEnvironment>

Next you would have to parse that in the ServiceHostFactory to extract the concrete type and the interface type and pass the interfaceType into the UnityInstanceProvider:

    public class UnityServiceHostFactory : ServiceHostFactory
    {
        public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            string[] types = constructorString.Split(';');

            if (types.Length > 2)
            {
                throw new ArgumentException("constructorString can only contain 1 or 2 types separated by a semicolon", "constructorString");
            }

            Type concreteType = Type.GetType(types.ElementAt(0));
            string interfaceString = types.ElementAtOrDefault(1);
            Type interfaceType = null;

            if (interfaceString != null)
            {
                interfaceType = Type.GetType(interfaceString);
            }

            return new UnityServiceHost(concreteType, interfaceType, baseAddresses);
        }
    }

    public class UnityServiceHost : ServiceHost
    {
        private Type interfaceType;

        public UnityServiceHost()
            : base()
        {
        }

        public UnityServiceHost(Type serviceType, Type interfaceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
            this.interfaceType = interfaceType;
        }

        protected override void OnOpening()
        {
            this.Description.Behaviors.Add(new UnityInstanceProviderServiceBehavior(interfaceType));
            base.OnOpening();
        }
    }

    public class UnityInstanceProviderServiceBehavior : IServiceBehavior
    {
        private Type interfaceType;

        public UnityInstanceProviderServiceBehavior(Type interfaceType)
        {
            this.interfaceType = interfaceType;
        }

        #region IServiceBehavior Members

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            serviceHostBase.ChannelDispatchers.ToList().ForEach(channelDispatcher =>
            {
                ChannelDispatcher dispatcher = channelDispatcher as ChannelDispatcher;

                if (dispatcher != null)
                {
                    dispatcher.Endpoints.ToList().ForEach(endpoint =>
                    {
                        endpoint.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(interfaceType ?? serviceDescription.ServiceType);
                    });
                }
            });
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }

    public class UnityInstanceProvider : IInstanceProvider
    {
        private readonly Type serviceType;
        private readonly IUnityContainer container;  // TODO:  Configure your Unity container

        public UnityInstanceProvider(Type serviceType)
        {
            container = new UnityContainer();
            container.LoadConfiguration();

            this.serviceType = serviceType;
        }

        #region IInstanceProvider Members

        public object GetInstance(InstanceContext instanceContext, Message message)
        {
            return container.Resolve(serviceType);  // This is it, the one and only call to Unity in the entire solution!
        }

        public object GetInstance(InstanceContext instanceContext)
        {
            return GetInstance(instanceContext, null);
        }

        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
            if (instance is IDisposable)
                ((IDisposable)instance).Dispose();
        }

        #endregion
    }

Or course, you could also go the convention over configuration approach and simply assume that every service class of type Xyz will be implementing an interface IXyz.  If the class does not the Type will be null so the code above will fall back to the using the serviceType.  E.g.:

 

    public class UnityServiceHostFactory : ServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            Type interfaceType = serviceType.GetInterface(serviceType.Namespace + ".I" + serviceType.Name);

            return new UnityServiceHost(serviceType, interfaceType, baseAddresses);
        }
    }

 

Each approach has pros and cons but hopefully one should suit you.

 

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Nov 28, 2013 at 6:27 AM
thanks for sharing~a good approach~