You are doing it wrong.
Why are you using WCF if you are only using it as an old timer ASMX service without using the extensibility points?
First things first - clean up
[ServiceContract]
public interface IWorkingService
{
[OperationContract]
Collection<Ergebnis> UpdateEntity(int entityId);
}
The service interface is the same as yours, but:
- Why are you using
Collection<T>
as the return type? Use someting more generic.
Do not use primitives as service parameters, ever! Using primitives as parameters means that you cannot versionize your service, such as adding extra non-required properties.
public class WorkingService : IWorkingService
{
private readonly NotStaticFacade _notStaticFacade;
public WorkingService(NotStaticFacade notStaticFacade)
{
_notStaticFacade = notStaticFacade;
}
public Collection<Ergebnis> UpdateEntity(int entityId)
{
return _notStaticFacade.UpdateEntity(entityId);
}
}
The service implementation is much more slimmer then yours because it's missing the logging stuff (be patient!). The important thing is that it isn't containing a parameter-less constructor because the service has a dependency: the NotStaticService
(in your code this is the static CoreFacade
). What does this mean? We will need a custom ServiceHostFactory
because the default one cannot handle these kinds of situation, but don't worry; Ninject is with us!
Install-Package Ninject.Extensions.Wcf
The Ninject has an extension for WCF so install it via NuGet (remove App_Start
directory after installation) and after that, create our own ServiceHostFactory
:
public class WorkingServiceHostFactoryWithNinject : NinjectServiceHostFactory
{
private readonly IKernel _kernel;
public WorkingServiceHostFactoryWithNinject()
{
_kernel = new StandardKernel(new WorkingServiceNinjectModule());
// SetKernel is a static method in Ninject WCF!
SetKernel(_kernel);
}
}
Nothing fancy; we only telling the factory which IKernel
instance we want to use. As you see, you will have a NinjectModule
prepared to map the bindings.
public class WorkingServiceNinjectModule : NinjectModule
{
public override void Load()
{
// what ever other binding is need
Kernel.Bind<NotStaticFacade>().ToSelf();
}
}
I'm only telling the kernel how to resolve the NotStaticFacade
but this isn't necessary this way. We haven't finished yet because we haven't told the system to use our factory to build up our service. To do this, we need to open the *.svc markup (right-click on the file then View markup) and write into the directive the Factory attribute:
<%@ ServiceHost Language="C#" Debug="true" Service="CodeReview.WorkingService" CodeBehind="WorkingService.svc.cs"
Factory="CodeReview.WorkingServiceHostFactoryWithNinject" %>
Save and close this.
Now we have a service with dependency injection and the only one real facade is our service.
WCF extensibility - IServiceBehavior
, IErrorHandler
The WCF infrastructure has great possibilities for extending its capabilities, such as handling an error!
To do that we will create a new service behavior and we will use it for adding our error handlers to the service:
public class ErrorHandlerBehaviorWithNinjectKernel : IServiceBehavior
{
private readonly IKernel _kernel;
public ErrorHandlerBehaviorWithNinjectKernel(IKernel kernel)
{
_kernel = kernel;
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlers = _kernel.GetAll<IErrorHandler>().ToArray();
if (!errorHandlers.Any())
{
throw new Exception("No errorhandler was found");
}
foreach (var channelDispatcher in serviceHostBase.ChannelDispatchers.Select(channelDispatcherBase => channelDispatcherBase as ChannelDispatcher))
{
foreach (var errorHandler in errorHandlers)
{
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
}
What can we see here? We are adding all IErrorHandler
instance to all possible dispatchers to use it. IErrorHandler
is coming from the WCF infrastructure. You need to create your own implementation of it like this:
public class ErrorLogger : IErrorHandler
{
private readonly IAmLogger _logger;
public ErrorLogger(IAmLogger logger)
{
_logger = logger;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
//nothing to do here
}
public bool HandleError(Exception error)
{
_logger.Fatal(error);
return true;
}
}
I have created the IAmLogger
interface to fake your Log4Net stuff, nothing more:
public interface IAmLogger
{
void Info(string info);
void Fatal(Exception exception);
}
If you have created your IErrorHandler
implementation, register it in your NinjectModule
:
Kernel.Bind<IErrorHandler>().To<ErrorLogger>();
There are two ways to add an IServiceBehavior
to a service: view attributes (then our class have to derive from Attribute) and programatically.
We will use the last one because of the IKernel
dependency.
Let's bring up the factory again and extend it with overriding the CreateServiceHost
method:
public class WorkingServiceHostFactoryWithNinject : NinjectServiceHostFactory
{
private readonly IKernel _kernel;
public WorkingServiceHostFactoryWithNinject()
{
_kernel = new StandardKernel(new WorkingServiceNinjectModule());
// SetKernel is a static method in Ninject WCF!
SetKernel(_kernel);
}
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);
serviceHost.Description.Behaviors.Add(new ErrorHandlerBehaviorWithNinjectKernel(_kernel));
return serviceHost;
}
}
Now we are creating the ServiceHost
and after that we are adding our behavior (ErrorHandlerBehaviorWithNinjectKernel
) to it. That is how every exception in the system will be logged and we don't need to write anything into our service.
Logging operation calls - IOperationInvoker
, IOperationBehavior
In WCF we can specify our own Operation invoker by implementing the IOperationInvoker
interface and applying it to our operations via an IOperationBehavior
instance inside a service behavior instance. (That's all, really!)
First create an IOperationLogger
implementation:
public class OperationInvokerWithLogging : IOperationInvoker
{
private readonly IAmLogger _logger;
private readonly IOperationInvoker _invoker;
private readonly DispatchOperation _dispatchOperation;
public OperationInvokerWithLogging(IAmLogger logger, IOperationInvoker invoker, DispatchOperation dispatchOperation)
{
_logger = logger;
_invoker = invoker;
_dispatchOperation = dispatchOperation;
}
public object[] AllocateInputs()
{
return _invoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
_logger.Info(string.Format("Operation {0} called (Invoke)", _dispatchOperation.Name));
return _invoker.Invoke(instance, inputs, out outputs);
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
_logger.Info(string.Format("Operation {0} called (InvokeBegin)", _dispatchOperation.Name));
return _invoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
_logger.Info(string.Format("Operation {0} called (InvokeEnd)", _dispatchOperation.Name));
return _invoker.InvokeEnd(instance, out outputs, result);
}
public bool IsSynchronous { get { return _invoker.IsSynchronous; } }
}
This is mostly a stub for the real IOperationInvoker
what comes from the WCF infrastructure (we will see it soon). The only additional mechism is the logging parts (with out IAmLogger
instance!).
Let's create our IOperationBehavior
implementation; this will map the IOperationInvoker
to all of our operations:
public class LoggingOperationBehaviorWithNinjectKernel : IOperationBehavior
{
private readonly IKernel _kernel;
public LoggingOperationBehaviorWithNinjectKernel(IKernel kernel)
{
_kernel = kernel;
}
public void Validate(OperationDescription operationDescription)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new OperationInvokerWithLogging(_kernel.Get<IAmLogger>(), dispatchOperation.Invoker, dispatchOperation);
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
}
Only mapping the invoker, nothing more except that we are resolving the IAmInvoker
with Ninject.
Now we need a service behavior to apply the operation behavior to our service like the error handling stuff:
public class ServiceLoggingBehavior : IServiceBehavior
{
private readonly IKernel _kernel;
public ServiceLoggingBehavior(IKernel kernel)
{
_kernel = kernel;
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var operation in serviceDescription.Endpoints.SelectMany(endpoint => endpoint.Contract.Operations))
{
operation.Behaviors.Add(new LoggingOperationBehaviorWithNinjectKernel(_kernel));
}
}
}
More or less the same as above nothing new here, map it in our factory and lets see what we have got:
public class WorkingServiceHostFactoryWithNinject : NinjectServiceHostFactory
{
private readonly IKernel _kernel;
public WorkingServiceHostFactoryWithNinject()
{
_kernel = new StandardKernel(new WorkingServiceNinjectModule());
// SetKernel is a static method in Ninject WCF!
SetKernel(_kernel);
}
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var serviceHost = base.CreateServiceHost(serviceType, baseAddresses);
serviceHost.Description.Behaviors.Add(new ErrorHandlerBehaviorWithNinjectKernel(_kernel));
serviceHost.Description.Behaviors.Add(new ServiceLoggingBehavior(_kernel));
return serviceHost;
}
}
Pretty clear stuff and still no extra code in our service implementation!
About unit testing
As the others mentioned above, we are not really unit testing service implementations because, as you see, it contains a lot of infrastructure stuff in it. These are small parts which can be tested but mostly it isn't worth it.
Now you only have to test your NotStaticFacade
class and not the service itself because it's just a stub. Of course it can contain code parts which are needed to be tested (for example the service using EF entities and transforms them to DTO instances) but now you can test these thing simpler:
Create an interface for the NotStaticFacade
so you can fake it out with a simple implementation and modify your service constructor to accept the interface (ICoreStuff
or whatever) not the concrete implementation. If you created a stub for the interface you can have unit tests to check your service output.
And of course you can still have integration tests to check the whole service behavior and in those tests you can set up different or no logger for example.
I'm not sure the things above will work as the way they are because I haven't tested it, but the main implementation steps are covered in the code examples.