I am a bit torn between the IOperationInvoker
and the IInstanceProvider
.
The IOperationInvoker
has turned out to be fairly complicated for what I need, since I need to extend both synchronous and asynchronous calls. But it's advantage is that it is specifically made to perform actions before and after each method call. Although I'm still not entirely sure how to pass on an object to any service method, which I can use to track the use, lower in the hierarchy. And Carlos Figueira's blog on WCF Extensibility unfortunately doesn't touch on this in his example (he shows how to cache calls).
The IInstanceProvider
turned out to be more simple for me to implement, and also makes it possible to perform actions before and after each operation - as long as the InstanceContextMode
is PerCall
. If I were to change it to PerSession
, I would suddenly perform the actions once per session instead. But in my case, that's acceptable, since the primary objective is to merge the data as much as possible:
One of my Service classes with the custom ServiceBehavior
Attribute
and inheriting an abstract type that dictates we have a constructor that takes a PerformanceContext
:
[ServiceContract]
public interface IJobsService { ... }
[PerformanceInstanceProviderBehavior]
public partial class JobsService : PerformanceMonitoredService, IJobsService
{
public PerformanceContext PerformanceContext { get; protected set; }
JobsService() { ... }
JobsService(PerformanceContext perfContext) : this()
{
PerformanceContext = perfContext;
}
...
}
IInstanceProvider
which allows calling a specific constructor and injecting an IExtension
into the pipeline, which we can obtain after the Service instance is released:
public class ServiceInstanceProvider : IInstanceProvider
{
public Type ServiceType { get; set; }
public ServiceInstanceProvider(Type serviceType) { ServiceType = serviceType; }
public object GetInstance(InstanceContext instanceContext)
{
return this.GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
var perfContext = new PerformanceInstanceContext();
instanceContext.Extensions.Add(new PerformanceInstanceExtension(perfContext));
return ServiceFactory.Create(ServiceType, perfContext);
//return new JobsService(perfContext);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
var perfContext = (instanceContext.Extensions.FirstOrDefault(ice =>
ice is PerformanceInstanceExtension)
as PerformanceInstanceExtension
)?.PerformanceContext;
//Handle the object which has been through the pipeline
//Note (IErrorHandler):
//This is called after "ProvideFault", but before "HandleError"
}
}
The IServiceBehavior
and Attribute
that will be added to all services that needs a PerformanceContext
injected.
public class PerformanceInstanceProviderBehaviorAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
if (!ed.IsSystemEndpoint)
{
//Each Service Type is getting their own InstanceProvider,
//So we can pass the type along,
//and let a factory create the appropriate instances:
ed.DispatchRuntime.InstanceProvider =
new ServiceInstanceProvider(serviceDescription.ServiceType);
}
}
}
}
...
}
The IExtension
that we can attach to the InstanceContext
through the instance provider pipeline:
public class PerformanceInstanceExtension : IExtension<InstanceContext>
{
public PerformanceInstanceExtension()
{
PerformanceContext = new PerformanceContext();
}
public PerformanceInstanceExtension(PerformanceContext perfContext)
{
PerformanceContext = perfContext;
}
public PerformanceContext PerformanceContext { get; private set; }
public void Attach(InstanceContext owner) {}
public void Detach(InstanceContext owner) {}
}
The abstract service type that should allow this injection:
public abstract class PerformanceMonitoredService
{
public abstract PerformanceContext PerformanceContext { get; protected set; }
public PerformanceMonitoredService() {}
public PerformanceMonitoredService(PerformanceContext perfContext) {}
}
A factory for services that inherit PerformanceMonitoredService
:
public class PerformanceServiceFactory
{
private static ConcurrentDictionary<Type, ConstructorInfo> Constructors
= new ConcurrentDictionary<Type, ConstructorInfo>();
public static object Create(Type type, PerformanceContext perfContext)
{
ConstructorInfo ctor;
if(Constructors.TryGetValue(type, out ctor))
{
return InvokeConstructor(ctor, perfContext);
}
else if (type.IsSubclassOf(typeof(PerformanceMonitoredService))
||type.IsAssignableFrom(typeof(PerformanceMonitoredService)))
{
ConstructorInfo newCtor = type.GetConstructor(
new[] { typeof(PerformanceContext) }
);
if(Constructors.TryAdd(type, newCtor))
{
return InvokeConstructor(newCtor, perfContext);
} else if(Constructors.TryGetValue(type, out ctor))
{
return InvokeConstructor(ctor, perfContext);
}
}
throw new ArgumentException(
$"Expected type inheritable of {typeof(PerformanceMonitoredService).Name}"}",
"type");
}
private static object InvokeConstructor(ConstructorInfo ctor,
PerformanceContext perfContext)
{
return ctor.Invoke(new object[] { perfContext });
}
}