The default WCF service factory doesn’t offer much: there is no control over how service instances are created and then disposed after the request has been handled. Fortunately, this is possible with a custom service host factory. Years ago I’ve blogged on creating WCF hosts programmatically and today I am going to borrow much of those ideas.
The goal is to have a full control: the service host should allow me to use an IoC container to resolve my service implementation to inject external components. Also, I would like to have an explicit disposal moment so that external components could be disposed when the service is disposed by the host.
We will need three core components: a service host factory, a service host and an instance provider. These three can be defined anywhere, for example in a shared assembly referenced in a web application project.
Let’s start with a custom service host factory which requires an IoC container and creates a service host.
public class UnityServiceHostFactory : ServiceHostFactory
{
private IUnityContainer container;
public UnityServiceHostFactory( IUnityContainer container )
{
this.container = container;
}
protected override System.ServiceModel.ServiceHost
CreateServiceHost( Type serviceType, Uri[] baseAddresses )
{
return new UnityServiceHost( container, serviceType, baseAddresses );
}
}
The service host itself doesn’t do much as it delegates the lifetime management to the instance provider:
/// <summary>
/// http://stackoverflow.com/questions/12277858/pass-a-parameter-to-a-wcf-service-constructor
/// </summary>
public class UnityServiceHost : ServiceHost
{
public UnityServiceHost(
IUnityContainer container,
Type serviceType,
params Uri[] baseAddresses )
: base( serviceType, baseAddresses )
{
foreach (var cd in this.ImplementedContracts.Values)
{
cd.Behaviors.Add( new UnityInstanceProvider( container, serviceType ) );
}
}
}
The instance provider has a major responsibility here. It uses the IoC to create a service and also it disposes the service when the host is disposed:
public class UnityInstanceProvider :
IInstanceProvider,
IContractBehavior
{
private Type _serviceType;
private IUnityContainer _container;
public UnityInstanceProvider( IUnityContainer container, Type serviceType )
{
this._container = container;
this._serviceType = serviceType;
}
#region IInstanceProvider Members
public object GetInstance(
InstanceContext instanceContext,
System.ServiceModel.Channels.Message message )
{
return GetInstance( instanceContext );
}
public object GetInstance( InstanceContext instanceContext )
{
return _container.Resolve( _serviceType );
}
public void ReleaseInstance( InstanceContext instanceContext, object instance )
{
if ( instance != null &&
instance is IDisposable
)
( (IDisposable)instance ).Dispose();
}
#endregion
#region IContractBehavior Members
public void AddBindingParameters(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters )
{
}
public void ApplyClientBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
ClientRuntime clientRuntime )
{
}
public void ApplyDispatchBehavior(
ContractDescription contractDescription,
ServiceEndpoint endpoint,
DispatchRuntime dispatchRuntime )
{
dispatchRuntime.InstanceProvider = this;
}
public void Validate(
ContractDescription contractDescription,
ServiceEndpoint endpoint )
{
}
#endregion
}
With all these three in my toolbox (UnityServiceHostFactory, UnityServiceHost and UnityInstanceProvider) I can have a base host factory class for all my WCF services:
public abstract class BaseServiceHostFactory : ServiceHostFactory
{
#region Injectables
private static Func<ServiceHostFactory> _hostFactoryProvider;
protected static Func<ServiceHostFactory> HostFactoryProvider
{
get
{
if ( _hostFactoryProvider == null )
throw new Exception( "Host factory has not been set in Composition Root" );
return _hostFactoryProvider;
}
}
/// <summary>
/// Do ustawiania - wołać z composition root
/// </summary>
/// <param name="provider"></param>
public static void SetProvider( Func<ServiceHostFactory> provider )
{
_hostFactoryProvider = provider;
}
#endregion
#region Properties
protected virtual ServiceDebugBehavior ServiceDebug
{
get
{
ServiceDebugBehavior debug = new ServiceDebugBehavior();
debug.IncludeExceptionDetailInFaults = true;
debug.HttpHelpPageEnabled = true;
debug.HttpsHelpPageEnabled = true;
return debug;
}
}
protected abstract Type ServiceType { get; }
protected virtual Type EndpointType
{
get
{
return ServiceType;
}
}
#endregion
protected virtual bool IsMetadataEnabled
{
get
{
return false;
}
}
public override ServiceHostBase CreateServiceHost(
string constructorString,
Uri[] baseAddresses )
{
// Host our WCF Service
ServiceHost serviceHost =
(ServiceHost)HostFactoryProvider().CreateServiceHost( ServiceType.FullName, baseAddresses );
bool isSslEnabled = false;
bool isNonSslEnabled = false;
foreach ( var addresse in baseAddresses )
{
ServiceEndpoint endpoint = null;
if ( addresse.Scheme.Equals( "https" ) )
{
isSslEnabled = true;
endpoint = serviceHost.AddServiceEndpoint(
EndpointType, GetBinding( true ), addresse.ToString() );
}
else if ( addresse.Scheme.Equals( "http" ) )
{
isNonSslEnabled = true;
endpoint = serviceHost.AddServiceEndpoint(
EndpointType, GetBinding( false ), addresse.ToString() );
}
}
// metadata
serviceHost.Description.Behaviors.Remove<ServiceMetadataBehavior>();
if ( IsMetadataEnabled )
serviceHost.Description.Behaviors.Add( GetServiceMetadata( isSslEnabled, isNonSslEnabled ) );
serviceHost.Description.Behaviors.Remove<ServiceDebugBehavior>();
if ( IsMetadataEnabled )
serviceHost.Description.Behaviors.Add( this.ServiceDebug );
return serviceHost;
}
protected virtual ServiceMetadataBehavior GetServiceMetadata(
bool isSslEnabled,
bool isNonSslEnabled )
{
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
metadata.HttpGetEnabled = isNonSslEnabled;
metadata.HttpsGetEnabled = isSslEnabled;
return metadata;
}
protected virtual BasicHttpBinding GetBinding( bool isSslBinding )
{
BasicHttpBinding binding = new BasicHttpBinding();
if ( isSslBinding )
binding.Security.Mode = BasicHttpSecurityMode.Transport;
return binding;
}
}
The base service host factory relies on a custom service host factory, configured in the Composition Root. To bind the two, the BaseServiceHostFactory and the UnityServiceHostFactory just set up the provider in your global.asax:
/// <summary>
/// Global application class
/// </summary>
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ComposeRoot(); // composition root
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register( GlobalConfiguration.Configuration );
FilterConfig.RegisterGlobalFilters( GlobalFilters.Filters );
RouteConfig.RegisterRoutes( RouteTable.Routes );
}
/// <summary>
/// Compose application-wide dependencies
/// </summary>
protected void ComposeRoot()
{
// IoC
var container = CreateContainer();
// WCF
UnityServiceHostFactory hostFactory = new UnityServiceHostFactory( container );
BaseServiceHostFactory.SetProvider( () => hostFactory );
}
protected IUnityContainer CreateContainer()
{
var container = new UnityContainer();
// set up the container manually or load the configuration
return container;
}
As the provider has been set up, I can finally create my services:
/// <summary>
/// Service factory
/// </summary>
public class ExampleServiceHostFactory : BaseServiceHostFactory
{
protected override Type ServiceType
{
get { return typeof( ExampleServiceImpl ); }
}
protected override Type EndpointType
{
get { return typeof( IExampleService ); }
}
protected override bool IsMetadataEnabled
{
get
{
return true;
}
}
}
/// <summary>
/// Service interface
/// </summary>
[ServiceContract]
public interface IExampleService
{
[OperationContract]
public string HelloWorld();
}
// An example of data-aware service
public class ExampleServiceImpl :
IExampleService,
IDisposable
{
private MyDatabaseContext _context;
// The constructor injection will be handled by the GetInstance method
// of the UnityInstanceProvider
// and resolved by the container set up in the composition root
public ExampleServiceImpl( MyDatabaseContext context )
{
this._context = context;
}
// Dispose will be called by the ReleaseInstance method
// of the UnityInstanceProvider
public void Dispose()
{
if ( this._context != null )
this._context.Dispose();
}
}
And this is it, the ExampleServiceHostFactory can be used to set up a self-hosted service or to server as a factory for *.svc service in a web application. The idea is quite open to extensions, for example if another IoC container should be used to resolve instances, instead of UnityServiceHostFactory, UnityServiceHost and UnityServiceProvider have your WhateverServiceHostFactory, WhateverServiceHost and WhateverServiceProvider and set up the service host factory provider in the composition root:
// WCF
WhateverServiceHostFactory hostFactory = new WhateverServiceHostFactory( whatever );
BaseServiceHostFactory.SetProvider( () => hostFactory );
1 comment:
Witam serdecznie. Bardzo ciesze sie, ze jest Pan moim rodakiem, gdyz moze moglby mi Pan lepiej pomoc. Znalazlem Pana odpowiedz pomocna tutaj:I ja mam wlasciwie dosc podobne problem dotyczacy Google Calendar API v3. Chodzi mi o takie cos, ze zauwazylem, ze kiedy token wygasl, wtedy przy laczeniu sie i wpisaniu poprawnego client id i client secret, wlacza sie przegladarka gdzie mamy sie zalogowac. Moj problem polega na tym, ze o ile uzywam odwolania: credential.Token.ExpiresInSeconds=(long)0.01; - tak przykladowo taki czas wygasniecia, o tyle wygasa on różnie. Chodz mi generalnie o to, ze chcialbym tak zaprogramowac moją aplikację, aby po wcisnieciu przycisku, token wygasal, a po nastepnym wcisnieciu danego przycisku, wlaczala sie przegladarka z zalogowaniem. Program piszę w WinForm. Może pokażę kod(C#):
public static string exc = "";
protected static CalendarService service=null;
public UserCredential credential = null;
public void Authenticate(string text2, string text3)
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = text2,
ClientSecret = text3,
},
new[] { CalendarService.Scope.Calendar },
"user",
CancellationToken.None).Result;
service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
}
i dla danego przycisku mamy wewnatrz metody zdarzeniowej:
DateTime actual = DateTime.Now;
auth.credential.Token.ExpiresInSeconds = (long)0.01;
Gdzie auth to obiekt klasy Authentication w ktorej zamiescilem te metode Authenticate. I tutaj wlasnie, jesl nie podaje client id i client secret i wciskam kilka razy przycisk dla ktorego ma wykasowac docelowo token, to w koncu za ktoryms razem wlacza sie exception przy probie polaczenia i przy nastepnym kliknieciu do polaczenia wlacza sie przegladarka.
Chcialbym sie dowiedziec jak zrobic, aby mozna bylo na dany moment wtedy kiedy bede chcial wykasowac token dzieki czemu wyskoczy przy 1 probie polaczenia exception a potem przy drugiej probie polaczenia wlaczy sie przegladarka.
Chodzi o to zeby docelowy uzytkownik obslugujacy aplikacje za kazdym wlaczeniem ponownym aplikacji musial na nowo wcisnac polaczenie- wlacza sie przegladarka, i zalogowac sie na konto google- i dopiero wtedy dostaje nowy token.
Post a Comment