0

I'm looking for a way to keep track of the total time spent on the database, and merge it over one Service Operation call or maybe even an entire session. Since I'm currently using the default PerCall InstanceContextMode, the constructor of the Service class is called every time a service method is called, so I was thinking something like hooking into some pipeline method that is called before and after each service method, calling a non-empty constructor. And then injecting an object to pass further into the hierarchy:

[ServiceContract]
public interface IJobsService { ... }
public partial class JobsService : IJobsService
{
    public PerformanceContext PerformanceContext { get; private set; }
    JobsService() { ... }
    JobsService(PerformanceContext context) : this()
    {
        RequestContext = context;
    }
}


public class PerformanceContext
{
    private object syncObj = new object();
    private long? userID;
    public long? UserID { ... }
    public string Source { get; set; }
    private long totalTicksUsed = 0;
    public long TotalTicksUsed
    {
        get { return totalTicksUsed; }
        private set { totalTicksUsed = value; }
    }
    public void AddTicksUsed(long ticks, long? userID)
    {
        Interlocked.Add(ref totalTicksUsed, ticks);
        UserID = userID;
    }
}

Then I would have the reference of it outside the scope of the service contract, and be able to log it there.

As it is now, the "simplest" way for me to implement this behavior is to call a logging function in the very end of every single service method, but I don't find it very pretty, if there's a better way.

I've tried following Explicitly calling a service constructor when hosting a WCF web service on IIS, Hooking into wcf pipeline and some of the Carlos Figueira MSDN blog: WCF Extensibility, without much success. I'm also having trouble finding much documentation on it general. In other words, I'm stuck.

Community
  • 1
  • 1
Aske B.
  • 6,419
  • 8
  • 35
  • 62

1 Answers1

0

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 });
    }
}
Aske B.
  • 6,419
  • 8
  • 35
  • 62