Switch service contract implementation at runtime in WCF - c#

How would I switch the implementation of the service contract at runtime?
Say I have:
[ServiceContract]
public interface IService {
[OperationContract]
DoWork();
}
public class ServiceA : IService {
public string DoWork() {
// ....
}
}
public class ServiceB : IService {
public string DoWork() {
// ....
}
}
I'd like to be able to switch the implementation that is being used from say a config file or a value in a database between the two. Would it also be possible to do this while the WCF service is hot?

You need to write a servicebehavior by implementing IServiceBehavior, and initialize the service instance using an instance provider. The following initializes a new service instance, you may implement a different logic:
public class XInstanceProviderServiceBehavior : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var item in serviceHostBase.ChannelDispatchers)
{
var dispatcher = item as ChannelDispatcher;
if (dispatcher != null)
{
dispatcher.Endpoints.ToList().ForEach(endpoint =>
{
endpoint.DispatchRuntime.InstanceProvider = new XInstanceProvider(serviceDescription.ServiceType);
});
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
}
And have your instance provider class implement IInstanceProvider and return related instance in GetInstance method.
public XInstanceProvider :IInstanceProvider
{
...
public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
{
return new ServiceX();
}
}
Then all you need is to add servicebehaviour to service; something like
[XInstanceProviderServiceBehavior()]
public class MyService : IMyService

Related

How to inject dependencies in a WCF Service

I'm trying to inject a some business services in a WCF service. I read this really interessting post: How do I pass values to the constructor on my wcf service?
And I have done the following:
Custom ServiceHost
public class UnityServiceHost : ServiceHost
{
public UnityServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}
foreach (ContractDescription contractDescription in ImplementedContracts.Values)
{
contractDescription.Behaviors.Add(new UnityInstanceProvider(serviceType, container));
}
}
}
Custom Instance Provider
public class UnityInstanceProvider : IInstanceProvider, IContractBehavior
{
private readonly Type m_serviceType;
private readonly IUnityContainer m_container;
public UnityInstanceProvider(Type serviceType, IUnityContainer container)
{
m_serviceType = serviceType;
m_container = container;
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return GetInstance(instanceContext);
}
public object GetInstance(InstanceContext instanceContext)
{
return m_container.Resolve(m_serviceType);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
(instance as IDisposable)?.Dispose();
}
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, 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) { }
}
The service behavior that I've is looking like this:
[ServiceBehavior]
public class MyService : IMyService
{
public ServerInformationService(ISomeDependency coreManager)
{
//...
}
}
But I don't get into the GetInstance, and when I run the code, I get this:
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.
What have I done wrong? It look like my instance provider isn't even used
I finally found the solution. I was applying my InstanceProvider to Contact and not to services.
Here is my final (Working) solution:
public class UnityServiceHost : ServiceHost
{
public UnityServiceHost(IUnityContainer container, Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}
Description.Behaviors.Add(new UnityInstanceProvider(serviceType, container));
}
}
And the behavior+instance provider:
public class UnityInstanceProvider : IInstanceProvider, IServiceBehavior
{
private readonly Type m_serviceType;
private readonly IUnityContainer m_container;
public UnityInstanceProvider(Type serviceType, IUnityContainer container)
{
m_serviceType = serviceType;
m_container = container;
}
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 (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = (ChannelDispatcher)channelDispatcherBase;
foreach (EndpointDispatcher ed in cd.Endpoints)
{
if (!ed.IsSystemEndpoint)
{
ed.DispatchRuntime.InstanceProvider = this;
}
}
}
}
public object GetInstance(InstanceContext instanceContext)
{
return m_container.Resolve(m_serviceType);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return GetInstance(instanceContext);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
(instance as IDisposable)?.Dispose();
}
}
It works because I'm self-hosting the service(new UnityServiceHost(...) ...., if it was on IIS, I could not create the behavior that way

How to inject object into WCF's IErrorHandler?

I have this situation: I have WCF service. I'm handling all exceptions by MyErrorHandler with implemented interface IErrorHandler. There whole working code is below.
What I want to do, but have no idea how: I want to inject an object (for example ILogger) into MyErrorHandler class. It basically means I have to inject an object here: [GlobalErrorHandlerBehaviour(typeof(MyErrorHandler))]. Could you please help me solve this problem?
[ServiceContract]
public interface ITestService
{
[OperationContract]
int GetTest();
}
[GlobalErrorHandlerBehaviour(typeof(MyErrorHandler))]
public class TestService : ITestService
{
public TestService(ILogger logger)
{
// Here, I'm already injecting logger.
// It's not imported for my question so I removed it for now
}
public int GetTest()
{
throw new Exception("Test");
}
}
// This is attribute added to TestService class
// How can I inject (via constructor) ILogger, or any other class??
public class GlobalErrorHandlerBehaviourAttribute : Attribute, IServiceBehavior
{
private readonly Type errorHandlerType;
public GlobalErrorHandlerBehaviourAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
IErrorHandler handler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach(ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
if (channelDispatcher != null)
{
channelDispatcher.ErrorHandlers.Add(handler);
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
public class MyErrorHandler : IErrorHandler
{
//--------------------------------------------------//
// I MUST INJECT ILOGGER HERE (or any other object) //
//--------------------------------------------------//
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException fe = new FaultException();
MessageFault message = fe.CreateMessageFault();
fault = Message.CreateMessage(version, message, null);
}
}
Btw. I want to use DI and inject something in IErrorHandler I don't want to use private static readonly method with logger.
This question is related to yours. Basically, you don’t need GlobalErrorHandlerBehaviourAttribute. You can add behaviour to your service manually. What you have to do is to create your ServiceHost. In this answer I explained more explicitly how to do it.
Here is the working code of host application, that has injected ILogger into IErrorHandler:
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace ConsoleHost
{
class Program
{
static void Main(string[] args)
{
var logger = new DummyLogger();
var errorHandler = new TestErrorHandler(logger);
ServiceHost host = new TestServiceHost(errorHandler, typeof(TestService), new Uri("net.tcp://localhost:8002"));
host.Open();
Console.WriteLine("Press enter to exit");
Console.ReadKey();
}
}
[ServiceContract]
public interface ITestService
{
[OperationContract]
string Test(int input);
}
public class TestService : ITestService
{
public string Test(int input)
{
throw new Exception("Test exception!");
}
}
public class TestErrorHandler : IErrorHandler
{
private ILogger Logger { get; }
public TestErrorHandler(ILogger logger)
{
Logger = logger;
}
public bool HandleError(Exception error)
{
Logger.Log(error.Message);
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
FaultException fe = new FaultException();
MessageFault message = fe.CreateMessageFault();
fault = Message.CreateMessage(version, message, null);
}
}
public class TestServiceHost : ServiceHost
{
private readonly IErrorHandler errorHandler;
public TestServiceHost(IErrorHandler errorHandler, Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
this.errorHandler = errorHandler;
}
protected override void OnOpening()
{
Description.Behaviors.Add(new ErrorHandlerBehaviour(errorHandler));
base.OnOpening();
}
class ErrorHandlerBehaviour : IServiceBehavior, IErrorHandler
{
private readonly IErrorHandler errorHandler;
public ErrorHandlerBehaviour(IErrorHandler errorHandler)
{
this.errorHandler = errorHandler;
}
bool IErrorHandler.HandleError(Exception error)
{
return errorHandler.HandleError(error);
}
void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
errorHandler.ProvideFault(error, version, ref fault);
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
channelDispatcher.ErrorHandlers.Add(this);
}
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
}
// Dummy logger
public interface ILogger
{
void Log(string input);
}
public class DummyLogger : ILogger
{
public void Log(string input) => Console.WriteLine(input);
}
}
Config:
<system.serviceModel>
<services>
<service name="ConsoleHost.TestService">
<endpoint address="net.tcp://localhost:8002/TestService"
binding="netTcpBinding"
contract="ConsoleHost.ITestService" />
</service>
</services>
</system.serviceModel>
This is interesting question but setting up DI container is not that straight forward in WCF.
You have perform following setup:
Create a custom Instance provider
Create a custom service host
If its not self-hosting then a custom ServiceHostFactory setup.
See the complete code sample on MSDN for how to setup DI in WCF. Once DI is setup you just need to change the ErrorHandler implementation to use ILogger via constructor injection:
public class MyErrorHandler : IErrorHandler
{
private ILogger logger;
public MyErrorHandler(ILogger logger)
{
this.logger = logger;
}
}
Here's an additional source for more options of setting up InstanceProvider with another type of DI.

Type casting issue in C# and wcf header in ASP.NET

I am now developing a WCF service using Visual Studio 2013. My service contains a custom header field must be passed. Field name is "lisenseKey" I developed it. WCF service is working fine. Now I am about to call my service to test. I added a service reference to client project. Now I am calling like this.
MyService.Myservice proxy = new MyService.Myservice();
proxy.LisenseKey = "xxxxxx";
Label1.Text = proxy.GetMessage(TextBox1.Text);
When I assign lisense key value like this, it is showing error with red underline that "Cannot implicitly convert type string to MyService.MyService.string". How can I type cast in this condition? Or is there any way to pass header ? And I do not know how to use "MessageHeader".
Here is my server side code. I am trying to consume "GetMessage" method.
//using System.ServiceModel;
//using System.ServiceModel.Dispatcher;
//using System.ServiceModel.Channels;
//using System.ServiceModel.Description;
//using System.Collections.ObjectModel;
[ServiceContract]
public interface IMyservice
{
[OperationContract]
void DoWork();
[OperationContract]
string GetMessage(string name);
}
public class MyServiceMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if(request.Headers.FindHeader("LisenseKey","")==-1)
{
throw new FaultException("Lisense Key Was Not Provided");
}
var lisenseKey = request.Headers.GetHeader<string>("LisenseKey", "");
if (string.IsNullOrEmpty(lisenseKey))
{
throw new FaultException("Lisnse key is not valid");
}
if(lisenseKey!="12345x")
{
throw new FaultException("Lisense key is not valid");
}
return instanceContext;
}
public void BeforeSendReply(ref Message reply,object correlationState)
{
}
}
public class MyServiceMessageInspectorBehaviour:Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach(ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach(var endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyServiceMessageInspector());
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}

Type casting issue in C# and wcf header in .NET

I am now developing a WCF service using visual studio 2013 . My service contains a custom header field must be passed. Field name is "lisenseKey" I developed it. WCF service is working fine. Now I am about to call my service to test. I added a service reference to client project. Now I am calling like this:
MyService.Myservice proxy = new MyService.Myservice();
proxy.LisenseKey = "xxxxxx";
Label1.Text = proxy.GetMessage(TextBox1.Text);
When I assign license key value like this, it is showing error with red underline that:
"Cannot implicitly convert type string to MyService.MyService.string"
How can I type cast in this condition? Or is there any way to pass header? And I do not know how to use "MessageHeader".
Here is my service code . I am just trying to consume "GetMessage" method .
[ServiceContract]
public interface IMyservice
{
[OperationContract]
void DoWork();
[OperationContract]
string GetMessage(string name);
}
public class MyServiceMessageInspector:System.ServiceModel.Dispatcher.IDispatchMessageInspector
{
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext)
{
if(request.Headers.FindHeader("LisenseKey","")==-1)
{
throw new FaultException("Lisense Key Was Not Provided");
}
var lisenseKey = request.Headers.GetHeader<string>("LisenseKey", "");
if (string.IsNullOrEmpty(lisenseKey))
{
throw new FaultException("Lisnse key is not valid");
}
if(lisenseKey!="12345x")
{
throw new FaultException("Lisense key is not valid");
}
return instanceContext;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply,object correlationState)
{
}
}
public class MyServiceMessageInspectorBehaviour:Attribute,System.ServiceModel.Description.IServiceBehavior
{
public void AddBindingParameters(System.ServiceModel.Description.ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<System.ServiceModel.Description.ServiceEndpoint> endpoints,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(System.ServiceModel.Description.ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach(ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach(var endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyServiceMessageInspector());
}
}
}
public void Validate(System.ServiceModel.Description.ServiceDescription serviceDescription,System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
}

WCF service - initialization issue

I am currently trying to create a WCF service and then host it from a commandLine application. During hosting of the service am trying to initialize it with an instance of an object called MyProxy. All calls to the service should delegate to MyProxy.
I have created a service and made calls. But I am not able to initialize the MyProxy instance in the service. It is always null. So when any calls to the service is made, I am not able to delegate it to the proxy.
I have been trying to get this work since last two days. Am lost now, not sure what is happening. Kindly help.
public class MasOperationsService : IMasOperations
{
//This MyProxy instance should be used to delegate all calls to service.
public MyProxy myProxyInstance;
public MasOperationsService()
{
myProxyInstance = null;
}
public MasOperationsService(MyProxy proxy)
{
myProxyInstance = proxy;
}
public CoAuthorSearchResult ExtractCoAuthorsFromAuthor(long authorCellId, uint levelsToExtract)
{
//The service will delegate the call to MyProxy.
//myProxyInstance is always null
return myProxyInstance.GetProxyData(...);
}
}
public class MyInstanceProvider : IInstanceProvider
{
public object GetInstance(InstanceContext instanceContext, Message message)
{
MyProxy name = message.Headers.GetHeader<MyProxy>("Name", "http://my.namespace");
if (name != null)
{
return new MasOperationsService(name);
}
return null;
}
public object GetInstance(InstanceContext instanceContext)
{
return new MasOperationsService(null);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
public class MyServiceBehavior : IServiceBehavior
{
MyInstanceProvider myProvider = new MyInstanceProvider();
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider = this.myProvider;
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
}
The service is launched using,
class MyServiceLauncher
{
ServiceHost host;
IMasOperations proxy;
ChannelFactory<IMasOperations> factory;
public void StartService(MyProxy proxyInstance)
{
string baseAddress = "http://localhost:8730/Design_Time_Addresses/MASService/Service1";
host = new ServiceHost(typeof(MasOperationsService), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(IMasOperations), GetBinding(), "");
host.Description.Behaviors.Add(new MyServiceBehavior());
host.Open();
Console.WriteLine("Host opened");
factory = new ChannelFactory<IMasOperations>(GetBinding(), new EndpointAddress(baseAddress));
proxy = factory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)proxy))
{
OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("Name", "http://my.namespace", proxyInstance));
}
}
public void ShutDownService()
{
((IClientChannel)proxy).Close();
factory.Close();
host.Close();
}
static Binding GetBinding()
{
BasicHttpBinding result = new BasicHttpBinding();
return result;
}
}
I also put a Debugger.Launch() almost everywhere, just to see it being initialized (Both constructor of Service and in IInstanceProvider). It dosent get fired.
Here are the missing types. Also in the service, there is a call
return myProxyInstance.GetProxyData(...);
removing the dots compiles the application.
[ServiceContract]
public interface IMasOperations
{
[OperationContract]
CoAuthorSearchResult ExtractCoAuthorsFromAuthor(long AuthorCellId, uint LevelsToExtract);
}
public class CoAuthorSearchResult
{ }
public class MyProxy
{
public CoAuthorSearchResult GetProxyData()
{
return new CoAuthorSearchResult();
}
}
#kobac
asked for a piece of code showing where GetInstance is being called. I am not sure how and why I need to do this.
Currently I just create an object of the service class - MasOperationsClient at the client and call the method ExtractCoAuthorsFromAuthor().

Categories

Resources