In the last post in this series,
Part 3 – Custom Data Service Provider, I described the implementation of the Custom Data Service Provider. At this point we now have an OData Service that can be configured via metadata to expose an underlying data store. The next challenge is now to get an instance of this custom OData service up and running. This post will delve into the details on how this is achieved.
Overview
The core idea is to host the OData services from within a WCF service. In the above diagram each of the Domain Model Service instances, in green, are listening at an address relative to a configured base address. For example http://localhost:port/Vx/DomainModelService, where Vx refers to the version of the Domain Model that the service is exposing, for example V1.
WCF hosting challenge
Each instance of a
DomainModelService type that is to be exposed from the WCF service host, expects an instance of a
DomainModelDescriptor to be passed to the constructor.
/// <summary>
/// Initializes a new instance of the <see cref="DomainModelService"/> class.
/// </summary>
/// <param name="domainDescriptor">The <see="DomainModelDescriptor"> that this service has to represent.</param>
public DomainModelService(DomainModelDescriptor domainDescriptor) : this(domainDescriptor, DynamicTypeGenerator.Instance)
{
}
This non default constructor presents a challenge when using the
WebServiceHost class to host an instance/instances of the
DomainModelService. The
WebServiceHost class is used, as opposed to the
ServiceHost class, is due to the fact that the
DomainModelService is an OData service and cannot be hosted using the
ServiceHost class. The
WebServiceHost class supports two modes of instantiation.
//
// Summary:
// Initializes a new instance of the System.ServiceModel.Web.WebServiceHost
// class with the specified singleton server instance and base address.
//
// Parameters:
// singletonInstance:
// A service instance to be used as the singleton instance.
//
// baseAddresses:
// The base address of the service.
public WebServiceHost(object singletonInstance, params Uri[] baseAddresses);
//
// Summary:
// Initializes a new instance of the System.ServiceModel.Web.WebServiceHost
// class with the specified service type and base address.
//
// Parameters:
// serviceType:
// The service type.
//
// baseAddresses:
// The base address of the service.
public WebServiceHost(Type serviceType, params Uri[] baseAddresses);
The constructor
public WeServiceHost(Type serviceType, params Uri[] baseAddresses) cannot be used due to the fact that the
DomainModelService does not have a non default constructor. Invoking this constructor will throw the following exception:
System.InvalidOperationException: The service type provided could not be loaded as a service because it does not have a default (parameter-less) constructor. To fix the problem, add a default constructor to the type, or pass an instance of the type to the host.
The second constructor
public WebServiceHost(object singletonInstance, params Uri[] baseAddresses) can be used to host a instance of a DomainModelService. There is however one drawback to using this method of instantiation and that is the instance of the hosted service is a singleton. Having one instance to service all requests for data could potentially be a bottleneck, where by requests queue on the server side.
Solution
The solution to enabling the WCF framework to instance
DomainModelService types is to control the instancing behaviour of the framework ourselves. This is done by implementing an
IInstanceProvider derived type and an
IServiceBehavior derived type to apply the
IInstanceProvider.
The implementation of the
IInstanceProvider derived
DomainModelInstanceProvider, below, is pretty straight forward. It is passed an instance of an
IDomainModelServiceMetadataProvider derived type. invokes the GetDomainModelMetadata method and stores the result. When the WCF framework invokes one of the
GetInstance method overloads the
DomainModelInstanceProvider constructs an instance of an
DomainModelService passing the
DomainModelDescriptor to the constructor.
/// <summary>
/// The instance provider for instances of <see cref="DomainModelServices"/>
/// </summary>
public class DomainModelInstanceProvider : IInstanceProvider
{
private DomainModelDescriptor _domainModelDescriptor;
/// <summary>
/// Constructor
/// </summary>
/// <remarks>Retrieves an instance of a <see cref="DomainModelDescriptor"/> from the <see cref="IDomainModelServiceMetadataProvider"/> and stores it.</remarks>
/// <param name="domainModelServiceMetadataProvider">The <see cref="IDomainModelServiceMetadataProvider"/> to retrieve a <see cref="DomainModelDescriptor"/> from</param>
public DomainModelInstanceProvider(IDomainModelServiceMetadataProvider domainModelServiceMetadataProvider)
{
_domainModelDescriptor = domainModelServiceMetadataProvider.GetDomainModelMetadata(DomainModelServiceSettings.Current.DomainModelServiceActivationTimeout);
}
/// <summary>
/// Get an instance of a <see cref="DomainModelService"/>
/// </summary>
/// <param name="instanceContext">The context for the instance</param>
/// <param name="message">The message</param>
/// <returns>An instance of a <see cref="DomainModelService"/></returns>
public object GetInstance(InstanceContext instanceContext, Message message)
{
return new DomainModelService(_domainModelDescriptor);
}
/// <summary>
/// Get an instance of a <see cref="DomainModelService"/>
/// </summary>
/// <param name="instanceContext">The context for the instance</param>
/// <returns>An instance of a <see cref="DomainModelService"/></returns>
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
/// <summary>
/// Release instance.
/// <remarks>
/// Nothing for this provider to do for this type
/// </remarks>
/// </summary>
/// <param name="instanceContext"></param>
/// <param name="instance"></param>
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
return;
}
}
/// <summary>
/// Defines methods for retrieving <see cref="DomainModelDescriptor"/> metadata
/// </summary>
public interface IDomainModelServiceMetadataProvider
{
/// <summary>
/// Retrieve an instance of a <see cref="DomainModelDescriptor"/>
/// </summary>
/// <param name="timeout">The time out for retreiving the metadata.</param>
/// <returns>An instance of a <see cref="DomainModelDescriptor"/></returns>
DomainModelDescriptor GetDomainModelMetadata(TimeSpan timeout);
}
/// <summary>
/// A custom instancing behaviour to apply to a service.
/// </summary>
public class DomainModelServiceInstancingBehaviour : IServiceBehavior
{
private DomainModelInstanceProvider _instanceProvider;
/// <summary>
/// Constructor
/// </summary>
/// <param name="domainModelServiceMetadataProvider">An instance of an <see cref="IDomainModelServiceMetadataProvider"/></param>
public DomainModelServiceInstancingBehaviour(IDomainModelServiceMetadataProvider domainModelServiceMetadataProvider)
{
_instanceProvider = new DomainModelInstanceProvider(domainModelServiceMetadataProvider);
}
/// <summary>
/// Provides the ability to pass custom data to binding elements to support the contract implementation.
/// </summary>
/// <param name="serviceDescription">The service description of the service.</param>
/// <param name="serviceHostBase">The host of the service.</param>
/// <param name="endpoints">The service endpoints.</param>
/// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
return;
}
/// <summary>
/// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
/// </summary>
/// <remarks>
/// This <see cref="IServiceBehavior"/> type applies an instance of an <see cref="DomainModelInstanceProvider"/> to each of the <see cref="EndpointDispatcher"/> for the service host.
/// </remarks>
/// <param name="serviceDescription">The service description.</param>
/// <param name="serviceHostBase">The host that is currently being built.</param>
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = _instanceProvider;
}
}
}
}
/// <summary>
/// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
/// </summary>
/// <param name="serviceDescription">The service description.</param>
/// <param name="serviceHostBase">The service host that is currently being constructed.</param>
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
return;
}
}
The
DomainModelInstanceProvider is applied to the service through the use of a behaviour. Again the behaviour is pretty straight forward implementation. It constructs and instance of an
DomainModelInstanceProvider type and applies this instance to the
EndpointDispatcher .DispatchRuntime.InstanceProvider property for each of the endpoints.
/// <summary>
/// A custom instancing behaviour to apply to a service.
/// </summary>
public class DomainModelServiceInstancingBehaviour : IServiceBehavior
{
private DomainModelInstanceProvider _instanceProvider;
/// <summary>
/// Constructor
/// </summary>
/// <param name="domainModelServiceMetadataProvider">An instance of an <see cref="IDomainModelServiceMetadataProvider"/></param>
public DomainModelServiceInstancingBehaviour(IDomainModelServiceMetadataProvider domainModelServiceMetadataProvider)
{
_instanceProvider = new DomainModelInstanceProvider(domainModelServiceMetadataProvider);
}
/// <summary>
/// Provides the ability to pass custom data to binding elements to support the contract implementation.
/// </summary>
/// <param name="serviceDescription">The service description of the service.</param>
/// <param name="serviceHostBase">The host of the service.</param>
/// <param name="endpoints">The service endpoints.</param>
/// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
return;
}
/// <summary>
/// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
/// </summary>
/// <remarks>
/// This <see cref="IServiceBehavior"/> type applies an instance of an <see cref="DomainModelInstanceProvider"/> to each of the <see cref="EndpointDispatcher"/> for the service host.
/// </remarks>
/// <param name="serviceDescription">The service description.</param>
/// <param name="serviceHostBase">The host that is currently being built.</param>
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = _instanceProvider;
}
}
}
}
/// <summary>
/// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
/// </summary>
/// <param name="serviceDescription">The service description.</param>
/// <param name="serviceHostBase">The service host that is currently being constructed.</param>
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
return;
}
}
The behaviour is applied to the service host through the use of a
WebServiceHostFactory derived type. The
DomainModelServiceHostFactory type adds the
DomainModelServiceInstancingBehaviour to the host. The
DomainModelServiceHost type is derived from
WebServiceHost and an interface
IServiceHost, the interface is to abstract the underlying implementation to facilitate testing.
public class DomainModelServiceHostFactory : WebServiceHostFactory, IServiceHostFactory
{
private readonly IDomainModelServiceMetadataProvider _domainModelServiceMetadataProvider;
static DomainModelServiceHostFactory()
{
IoCContainer.Current = new IocContainerProxy();
}
/// <summary>
/// Initializes a new instance of the <see cref="DomainModelServiceHostFactory"/> class.
/// </summary>
/// <param name="domainModelServiceMetadataProvider">The domain model service metadata provider.</param>
public DomainModelServiceHostFactory(IDomainModelServiceMetadataProvider domainModelServiceMetadataProvider)
{
_domainModelServiceMetadataProvider = domainModelServiceMetadataProvider;
}
/// <summary>
/// Creates an instance of the specified <see cref="T:System.ServiceModel.Web.WebServiceHost"/> derived class with the specified base addresses.
/// </summary>
/// <param name="serviceType">The type of service host to create.</param>
/// <param name="baseAddresses">An array of base addresses for the service.</param>
/// <returns>
/// An instance of a <see cref="T:System.ServiceModel.ServiceHost"/> derived class.
/// </returns>
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = new DomainModelServiceHost(serviceType, baseAddresses);
host.Description.Behaviors.Add(new DomainModelServiceInstancingBehaviour(_domainModelServiceMetadataProvider));
return host;
}
/// <summary>
/// Creates the service host.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <param name="baseAddresses">The base addresses.</param>
/// <returns></returns>
IServiceHost IServiceHostFactory.CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return CreateServiceHost(serviceType, baseAddresses) as IServiceHost;
}
}
At this point we now have the ability to host and instantiate instances of the
DomainModelService. The final piece that is missing is a mechanism to orchestrate and manage the hosted
DomainModelService instances. This responsibility falls to the
DomainModelServiceManager class. It is responsible for the managing of the host instances and providing the metadata for each of the host instances that are started. It is not responsible for the validation and verification of the
DomainModelMetadata, that is the responsibility of the containing WCF service.
public class DomainModelServiceManager : IDomainModelServiceManager, IDomainModelServiceMetadataProvider, IDisposable
{
….
}
The
DomainModelServiceManager derives from the
IDomainModelServiceManager and
IDomainModelServiceMetadataProvider interfaces.
/// <summary>
/// An interface to define the methods and properties for a domain model service manager
/// </summary>
public interface IDomainModelServiceManager
{
/// <summary>
/// The event that is raised when a <see cref="DoaminModelService"/> is opened
/// </summary>
event EventHandler<DomainModelServiceOpenedEventArgs> OnDomainModelServiceOpened;
/// <summary>
/// The event that is raised when a <see cref="DoaminModelService"/> is closed
/// </summary>
event EventHandler<DomainModelServiceClosedEventArgs> OnDomainModelServiceClosed;
/// <summary>
/// The event that is raised when a <see cref="DoaminModelService"/> is closed
/// </summary>
event EventHandler<DomainModelServiceFaultedEventArgs> OnDomainModelServiceFaulted;
/// <summary>
/// Deacivate a <see cref="DoaminModelService"/> for the given idenitfier.
/// </summary>
/// <param name="identifier">The identifier of the <see cref="DomainModelDescriptor"/></param>
void DeactivateDomainModelService(Guid identifier);
/// <summary>
/// Activate a <see cref="DoaminModelService"/> for a <see="DomainModelDescriptor"/>
/// </summary>
/// <param name="domainModelDescriptor">The descriptor that describes the structure of the entites of the domain model</param>
/// <param name="baseAddress">The base address that the hosted <see cref="DoaminModelService"/> should be bound too</param>
/// <param name="servicePath">The relative service path for the bound address. For example baseaddress/servicePath</param>
/// <param name="activationTimeout">The amount of time to wait for the service host to be activated.</param>
void ActivateDomainModelService(DomainModelDescriptor domainModelDescriptor, Uri baseAddress, String servicePath, TimeSpan activationTimeout);
/// <summary>
/// Gets a list of active <see cref="DoaminModelService"/>
/// </summary>
/// <returns>A <see cref="IDictionary<Guid, Uri>"/> the <see cref="Guid"/> is a unique identifier for the instance. The <see cref="Uri"/> is the address that the </returns>
IDictionary<Guid, Uri> GetActiveDomainModelServices();
/// <summary>
/// Indicates if this instance has been initalised.
/// </summary>
bool IsInitalised { get; }
/// <summary>
/// Initalise the <see cref="IDomainModelServiceManager"/> instance
/// </summary>
/// <param name="domainModelDescriptors">The list of <see cref="DoaminModelService"/> to initalise</param>
/// <param name="baseAddress">The base address that the hosted <see cref="DoaminModelService"/> should be bound too</param>
/// <param name="servicePath">The relative service path for the bound address. For example baseaddress/servicePath</param>
/// <param name="activationTimeout">The amount of time to wait for a given service host to be activated</param>
void Initalise(IList<DomainModelDescriptor> domainModelDescriptors, Uri baseAddress, String servicePath, TimeSpan activationTimeout);
}
The key part of the
DomainModelServiceManager is the activation and deactivation of
DomainModelService instances. There are other functions that the
DomainModelServiceManager performs such as logging, handling faulted Service Host instances, but these are ancillary to its core function of managing instances of hosted
DomainModelService instances. Discussion of these features are excluded for the sake of brevity. The following code snippets describe the activation process in more detail.
private BlockingCollection<DomainModelDescriptor> _activatableDomainModels;
private List<DomainModelServiceInstance> _activatedDomainModels;
private IServiceHostFactory _serviceHostFactory;
public DomainModelServiceManager()
{
......
// the DomainModelServiceManager is an IDomainModelServiceMetadataProvider derived type and can provide the metadata
_serviceHostFactory = new DomainModelServiceHostFactory(this);
}
/// <summary>
/// Activate a <see cref="DoaminModelService"/> for a <see="DomainModelDescriptor"/>
/// </summary>
/// <param name="domainModelDescriptor">The descriptor that describes the structure of the entites of the domain model</param>
/// <param name="baseAddress">The base address that the hosted <see cref="DoaminModelService"/> should be bound too</param>
/// <param name="servicePath">The relative service path for the bound address. For example baseaddress/servicePath</param>
/// <param name="activationTimeout">The amount of time to wait for the service host to be activated.</param>
public void ActivateDomainModelService(DomainModelDescriptor domainModelDescriptor, Uri baseAddress, String servicePath, TimeSpan activationTimeout)
{
_activatableDomainModels.Add(domainModelDescriptor);
lock (_activatedDomainModels)
{
var domainModelService = CreateDomainModelServiceInstance(baseAddress, servicePath, domainModelDescriptor);
_activatedDomainModels.Add(domainModelService);
_logger.Debug("Domain model activation started");
domainModelService.ServiceHostInstance.Open(activationTimeout);
}
}
/// <summary>
/// Create a hosted instance of a <see cref="DomainModelService"/>
/// </summary>
/// <param name="domainModelDescriptor">The descriptor that describes the structure of the entites of the domain model</param>
/// <param name="baseAddress">The base address that the hosted <see cref="DoaminModelService"/> should be bound too</param>
/// <param name="servicePath">The relative service path for the bound address. For example baseaddress/servicePath</param>
/// <returns>A <see cref="DomainModelServiceInstance"/></returns>
private DomainModelServiceInstance CreateDomainModelServiceInstance(Uri baseAddress, String servicePath, DomainModelDescriptor domainModelDescriptor)
{
UriBuilder builder = new UriBuilder(baseAddress);
builder.Path = String.Format("V{0}/{1}", domainModelDescriptor.Version, servicePath);
// The service host factory adds the instanceing behavior
var host = _serviceHostFactory.CreateServiceHost(typeof(DomainModelService), new Uri[] { builder.Uri });
host.Faulted += HandleHostFaulted;
host.Opened += HandleHostOpened;
host.Closed += HandleHostClosed;
DomainModelServiceInstance instance = new DomainModelServiceInstance(domainModelDescriptor, host);
return instance;
}
public DomainModelDescriptor GetDomainModelMetadata(TimeSpan timeout)
{
DomainModelDescriptor descriptor = null;
if(_activatableDomainModels.TryTake(out descriptor, timeout) == false)
{
throw new TimeoutException(String.Format("Unable to get domain model metadata within timeout '{0}'", timeout));
}
_logger.Debug("Retrived metadata for DomainModel {0} Version {1}", descriptor.Identifier, descriptor.Version);
return descriptor;
}
/// <summary>
/// Handle the host opened event
/// </summary>
/// <remarks>
/// This removes the entry in the pending list and moves it to the activated list.
/// Rasies an event to signal that the open has occured
/// </remarks>
/// <param name="sender">The event sender</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected internal void HandleHostOpened(object sender, EventArgs e)
{
IServiceHost serviceHost = ((IServiceHost)sender);
_logger.Debug("Service host started {0}", serviceHost.BaseAddresses[0]);
DomainModelServiceInstance openedDomainModel = null;
lock (_activatedDomainModels)
{
openedDomainModel = (from dm in _activatedDomainModels where dm.ServiceHostInstance == sender select dm).FirstOrDefault();
}
if (openedDomainModel != null)
{
EventHelpers.RaiseEvent<DomainModelServiceOpenedEventArgs>(
new DomainModelServiceOpenedEventArgs()
{
DomainModel = openedDomainModel.DomainModelMetadata
}, OnDomainModelServiceOpened, this);
}
else
{
_logger.Warn("Faulted {0} could not be found for address {1}", typeof(DomainModelServiceInstance), serviceHost.BaseAddresses[0]);
}
}
Deactivation of a
DomainModelService is a far less involved process. The host simply has to be closed and the event handlers cleaned up.
/// <summary>
/// Deacivate a <see cref="DoaminModelService"/> for the given idenitfier.
/// </summary>
/// <param name="identifier">The identifier of the <see cref="DomainModelDescriptor"/></param>
public void DeactivateDomainModelService(Guid identifier)
{
DomainModelServiceInstance domainModelServiceInstance = null;
lock (_activatedDomainModels)
{
domainModelServiceInstance = _activatedDomainModels.Find(
delegate(DomainModelServiceInstance instance)
{
return instance.DomainModelMetadata.Identifier == identifier;
});
}
if (domainModelServiceInstance != null)
{
// close the service and detach event handlers
DeactivateDomainModelService(domainModelServiceInstance);
lock (_activatedDomainModels)
{
_activatedDomainModels.Remove(domainModelServiceInstance);
}
}
}
In the next post in the series
Part 5 - End To End Example I will put together a detailed end to end example of how all of these component pieces fit together. Hopefully it will aid in the solidification of how the various ideas and concepts hang together.