I have a WCF Service which is configured to run as a windows service but it only works if I set the InstanceContextMode to Single.When I change it to be PerCall and re-install the service it becomes inaccessible.
Could somebody please clarify if I run a WCF service as a windows service does the context HAVE to be set to Single and why is that?
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CateringSSTService : ICateringSSTService
UPDATE:Here's the hosting code
private ICateringSSTService _cateringSSTService;
private ServiceHost _serviceHost;
private Task _executionTask;
public CateringSSTServer()
{
}
public void Start()
{
_executionTask = new Task(ManagerStartup);
_executionTask.Start();
}
private void ManagerStartup()
{
try
{
_cateringSSTService = new CateringSSTService();
_serviceHost = new ServiceHost(_cateringSSTService);
_serviceHost.Open();
}
catch (Exception)
{
}
}
public void Stop()
{
try
{
if (_serviceHost != null && _serviceHost.State != CommunicationState.Closed)
{
_serviceHost.Close();
}
}
catch (Exception)
{
throw;
}
}
You are creating a single object and passing it to the singleton constructor of ServiceHost. How is this ServiceHost supposed to create a new object every call? That cannot work.
If you want PerCall (or PerSession or anything but Singleton) you need to pass the type of your class to the ServiceHost constructor so it can create new instances of this type at runtime.
_serviceHost = new ServiceHost(typeof(CateringSSTService));
If you hadn't silently swallowed the exception, the Runtime would have told you what the problem is. Apply some proper error handling for future problems.
Related
I have an EXE that I've created called logger which is a simple WinForms application. It has a richtextbox and that's pretty much it.
I then also have a suite of other applications. What I want to be able to do with these apps is to be able to get them to write output to the logger.exe I have full control over the code of all applications.
I understand I could do a process.start and specify arguments but I want these apps to be able to write to the richtextbox at will based on the methods being called within them.
I was hoping I could create an api in the logger.exe that would expose a method for appending the richtextbox.
Does anyone have any tips on how I might achieve this?
EDIT: This is what I have so far:
namespace ScreenLog
{
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, InstanceContextMode = InstanceContextMode.Single)]
public partial class Logger : Form, IFromClientToServerMessages
{
public Logger()
{
InitializeComponent();
}
public void DisplayTextOnServerAsFromThisClient(string text)
{
LogConsole.AppendText(Environment.NewLine + text);
}
}
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromClientToServerMessages
{
[OperationContract(IsOneWay = false)]
void DisplayTextOnServerAsFromThisClient(string message);
}
}
As you might have already guessed you would need any of IPC(Inter Process Communication) mechanism to send messages between different processes(Applications). WCF is one of the option, You could implement a simple WCF service module which uses net.pipe binding. This service can be hosted in managed application. In your case this service can be hosted in your logger application.
Note: If you want to host a WCF application in a managed application, Particular managed application(Logger) should have admin privilege.
Implementation of Logger Form
partial class declaration
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, InstanceContextMode = InstanceContextMode.Single)]
public partial class Logger: Form, IFromClientToServerMessages
Introduce Interface for communication
This interface should be added to a assembly which is accessible by both Logger application and any other application which sends message to logger.
[ServiceContract(SessionMode = SessionMode.Allowed)]
public interface IFromClientToServerMessages
{
[OperationContract(IsOneWay = false)]
void DisplayTextOnServerAsFromThisClient(string message);
}
Implementing Interface
Add the following method implementation to your Logger form
public void DisplayTextOnServerAsFromThisClient(string text)
{
//Add proper logic to set value to rich text box control.
richtextbox = text;
}
Hosting the WCF service in logger application
Invoke HostTheNetPipeService() within the constructor of Logger Form
private void HostTheNetPipeService()
{
serverHost = new ServiceHost(this);
serverHost.AddServiceEndpoint((typeof(IFromClientToServerMessages)), new NetNamedPipeBinding(), "net.pipe://127.0.0.1/Server");
serverHost.Open();
}
Call the service from other applications to send message/text
private void SendMessageToLogger()
{
using (ChannelFactory<IFromClientToServerMessages> factory = new ChannelFactory<IFromClientToServerMessages>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/Server")))
{
IFromClientToServerMessages clientToServerChannel = factory.CreateChannel();
try
{
clientToServerChannel.DisplayTextOnServerAsFromThisClient("Message to be displayed");
}
catch (Exception ex)
{
}
finally
{
CloseChannel((ICommunicationObject)clientToServerChannel);
}
}
}
Closing the communication channel
private void CloseChannel(ICommunicationObject channel)
{
try
{
channel.Close();
}
catch (Exception ex)
{
}
finally
{
channel.Abort();
}
}
I have a Windows service that uses net.pipe as a WCF server and a Windows forms client to connect to it. There is a class called ConfigSettings that has values I want the client to query.
I want to have the client read the current values inside the serviceConfig instance that the service uses. Ultimately, I want the client to change values in it, but baby steps first.
The form can talk to the server via named pipes, but 'return serviceConfig is sending a new empty instance back to the client. I want the data that the service is actively using (that is, serviceConfig.Setting1 = x; serviceConfig.Setting2 = "foo"; )
The Windows service and WCF server code is (updated to working version):
using System.IO;
using System.ServiceModel;
using System.ServiceProcess;
namespace WindowsServiceTest
{
public partial class Service1 : ServiceBase
{
internal static ServiceHost myServiceHost = null;
//this is the master config that the service uses
public static ConfigSettings serviceConfig = new ConfigSettings();
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
if (myServiceHost != null)
{
myServiceHost.Close();
}
myServiceHost = new ServiceHost(typeof(WCFService1));
myServiceHost.Open();
//set active default settings
serviceConfig.Setting1 = 1;
serviceConfig.Setting2 = "initial.Setting2:" + serviceConfig.Setting1;
}
protected override void OnStop()
{
if (myServiceHost != null)
{
myServiceHost.Close();
myServiceHost = null;
}
}
}
public partial class WCFService1 : IService1
{
public ConfigSettings GetConfig()
{
return Service1.serviceConfig;
}
public void SetConfig(ConfigSettings sentConfig)
{
Service1.serviceConfig = sentConfig;
}
}
[ServiceContract]
public interface IService1
{
[OperationContract]
ConfigSettings GetConfig();
[OperationContract]
void SetConfig(ConfigSettings sentConfig);
}
public class ConfigSettings
{
public int Setting1 { get; set; }
public string Setting2 { get; set; }
public ConfigSettings() { }
}
}
The client retrieves the config like this (updated with some changes):
using System;
using System.ServiceProcess;
using System.Windows.Forms;
using WindowsServiceTest;
namespace WindowsServiceTestForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
ConfigSettings config = new ConfigSettings();
//GetConfig()
private void button1_Click(object sender, EventArgs e)
{
ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
ServiceControllerPermission scp = new ServiceControllerPermission(ServiceControllerPermissionAccess.Control, Environment.MachineName, "Service1");//this will grant permission to access the Service
//get the current config and display
config = myService.GetConfig();
MessageBox.Show(config.Setting1 + "\r\n" + config.Setting2, "config");
myService.Close();
}
//SetConfig(ConfigSettings)
private void button2_Click(object sender, EventArgs e)
{ //make changes to values
config.Setting1 += 1;
config.Setting2 = "new.Setting2:" + config.Setting1;
ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
ServiceControllerPermission scp = new ServiceControllerPermission(ServiceControllerPermissionAccess.Control, Environment.MachineName, "Service1");//this will grant permission to access the Service
//send the new config
myService.SetConfig(config);
myService.Close();
}
}
}
Update:
Maybe what I'm thinking needs to be done is overkill. It seems that I'm hitting a membrane between WCF and the Windows Service.
How would YOU approach this problem?
Windows Service that needs a Form for configuration.
When service starts, it loads a config.xml file from disk. (a serialized class)
When GUI starts, I want to:
retrieve its current configuration,
make some changes to it,
push it back to the service,
trigger service to re-read and react to the new configuration.
I was trying to avoid statically/writing the config file to disk and telling service to re-read it again. It "seemed" like WCF was the way to go.
Update 2
It seems that by just changing the master config in the service to static, the WCF service can access it directly. I could have sworn I did that originally before I posted, but I guess not.
I also separated the naming of Service1 to WCFService1 above, but it turns out that doesn't matter and works either way.
New complete code has been updated above.
You are getting confused between Windows Service and WCF Service - and have tried to have them both in the same class - while this is possible - it is probably easier to understand if you split them into two classes.
In your example the Windows Service starts, creates a new instance of itself as the WCF service then sets the config elements in the Windows Service instance, meaning the config is empty in the WCF Service instance.
try this instead
using System.ServiceModel;
using System.ServiceProcess;
namespace WindowsServiceTest
{
public partial class Service1 : ServiceBase
{
internal static ServiceHost myServiceHost = null;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
if (myServiceHost != null)
{
myServiceHost.Close();
}
myServiceHost = new ServiceHost(typeof(WCFService1 ));
myServiceHost.Open();
}
protected override void OnStop()
{
if (myServiceHost != null)
{
myServiceHost.Close();
myServiceHost = null;
}
}
}
public class WCFService1 : IService1
{
public WCFService1()
{
//change master settings from null
myConfig.Setting1 = "123";
myConfig.Setting2 = "456";
}
//this is the master config that the service uses
public ConfigSettings myConfig = new ConfigSettings();
public ConfigSettings GetConfig()
{
return myConfig;
}
}
[ServiceContract]
public interface IService1
{
[OperationContract]
ConfigSettings GetConfig();
}
public class ConfigSettings
{
public string Setting1 { get; set; }
public string Setting2 { get; set; }
public ConfigSettings()
{ }
}
}
There are a few ways of doing this (injecting dependencies in the service instance). I'll show two of them.
You have a special case here because the Windows Service is also your WCF service implementation. I think that soon enough you will want to separate them. So, my samples are based on a separate WCF service implementation.
The thing is that WCF has a concept of service instance mode. By default it is PerCall, meaning that a new Service1 instance is created to handle each request. This makes it a bit more difficult to inject something in these instances.
The simplest way is to set the instance mode to Single, meaning there will be only one Service1 instance handling all requests.
The second is easy to implement:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service1Implementation : IService1
{
private ConfigSettings _configSettings;
public Service1(ConfigSettings settings)
{
//now you have the settings in the service
_configSettings = setting;
}
...
}
//in the Windows Service
myServiceHost = new ServiceHost(typeof(Service1Implementation), new Service1Implementation(myConfig));
myServiceHost.Open();
The first solution involves you creating and specifying a service instance factory which the infrastructure will use later to create service instances per call.
The factory gives you the possibility to instantiate Service1 yourself and pass the config.
There is quite some code to be written for this, but I'll show the essential. For the complete solution, read this.
For you, it's easier to make the Windows Service just implement IInstanceProvider:
public class Service1 : ServiceBase, IInstanceProvider
{
private ConfigSettings _myConfig; //assign this member later on
...
//IInstanceProvider implementation
public object GetInstance(InstanceContext instanceContext, Message message)
{
//this is how you inject the config
return new Service1Implementation(_myConfig);
}
public object GetInstance(InstanceContext instanceContext)
{
return this.GetInstance(instanceContext, null);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
...
}
I will admit that it's been a while since I've touched WCF, but it looks to me like your ConfigSettings class is missing some attributes required to make it serializable via WCF.
using System.Runtime.Serialization;
[DataContract]
public class ConfigSettings
{
[DataMember]
public string Setting1 { get; set; }
[DataMember]
public string Setting2 { get; set; }
public ConfigSettings()
{ }
}
I don't believe having your Windows service operate as your WCF service, like other answers have suggested, is the problem. But I do agree that it's best to have them be separate classes.
I have a desktop app with a duplex WCF service, but I have some troubles using the callback.
The service is started as following in main of program.cs:
ServiceHost svcHost = new ServiceHost(typeof(PeriodicService));
svcHost.Open();
Console.WriteLine("Available Endpoints :\n");
svcHost.Description.Endpoints.ToList().ForEach(endpoint => Console.WriteLine(endpoint.Address.ToString() + " -- " + endpoint.Name));
For the service I created a subscribe function where the callbackchannel is saved in a global variable, then the callback uses that global variable to talk back to the client (there will be only one client connecting).
IPeriodicCallback callbackClient;
public IPeriodicCallback Proxy
{
get
{
return this.callbackClient;
}
}
public void joinPeriodicService()
{
Console.WriteLine("Client subscribe");
this.callbackClient = OperationContext.Current.GetCallbackChannel<IPeriodicCallback>();
}
The thing I want to do now is call the callbackclient from an other class.
In the other class I created the service as:
private PeriodicService periodicService = new PeriodicService();
And I try to write data to it using:
if(this.periodicService.Proxy != null)
{
this.periodicService.Proxy.On1MinuteDataAvailable(tmpPeriod);
}
However the proxy stays null, I also tried to move the proxy part to the class but this also results in it staying null.
When the client connects I nicely get the message "Client Subscribe" but it seems there are two instances running of the periodicservice.
But my problem is I don't see an other way to access the periodicservice then creating it in my class, or is it also already created by the svcHost?
Can ayone point me in the right direction?
This repository shows the a duplex WCF imeplementation I made to answer a similar question a while ago, its a full working example with as little extra stuff as possible.
https://github.com/Aelphaeis/MyWcfDuplexPipeExample
Lets say we have a Service Contract like this :
[ServiceContract(CallbackContract = typeof(IMyServiceCallback),SessionMode = SessionMode.Required)]
public interface IMyService
{
[OperationContract(IsOneWay=true)]
void DoWork();
}
Note that I specified a CallbackContract.
If you want to make a duplex, you would want to perhaps make your Service Behavior implementation of the above contract like this :
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
public void DoWork()
{
Console.WriteLine("Hello World");
Callback.WorkComplete();
}
IMyServiceCallback Callback
{
get
{
return OperationContext.Current.GetCallbackChannel<IMyServiceCallback>();
}
}
}
The important thing here is the Callback. This is how your service would allow you to access specified to you by the Client.
You also need to define the callback interface, In my case its quite simple :
[ServiceContract]
public interface IMyServiceCallback
{
[OperationContract(IsOneWay = true)]
void WorkComplete();
}
Now I want to create a client to use this Duplex Service. The first thing I need to do is implement the IMyServiceCallback. I need to do this on the client. In this case the implementation is this:
class Callback : IMyServiceCallback
{
public void WorkComplete()
{
Console.WriteLine("Work Complete");
}
}
Now when I want to open my duplex connection with the services I would create a proxy class like this something like this:
public class MyServiceClient: IMyService, IDisposable
{
DuplexChannelFactory<IMyService> myServiceFactory { get; set; }
public MyServiceClient(IMyServiceCallback Callback)
{
InstanceContext site = new InstanceContext(Callback);
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpointAddress = new EndpointAddress(Constants.myPipeService + #"/" + Constants.myPipeServiceName);
myServiceFactory = new DuplexChannelFactory<IMyService>(site, binding, endpointAddress);
}
public void DoWork()
{
myServiceFactory.CreateChannel().DoWork();
}
public void Dispose()
{
myServiceFactory.Close();
}
}
Notice that I specified an InstanceContext. That Instance Context will be an instance of the object I created that implements IMyServiceCallback.
That's all you need to do! Simple as that!
Update :
Callback objects are just like any other object. You can store them into a collection and iterate through them and based on some condition.
One way is to create a property in the IMyServiceCallback that can uniquely identify it. When a client connects to the service it can call a method which specifies a callback object which can then be cached or saved for later use. You can then iterate the callbacks and based on some condition you can call a method for a specific client.
This is certainly more complicated; however, it is certainly manageable. I will add an example in a bit.
Update 2
This is a working example of exactly what you want; however, its a lot more complicated. I'll try to explain as simply as I can : https://github.com/Aelphaeis/MyWcfDuplexPipeExample/tree/MultiClient
Here is a list of the changes:
I've modified the client proxy (and service) so that when initialized it calls the init Method
I've also modified the Service implementation so that now it is a single instance dealing with all requests (for convenience).
I added a new OperationContract in the Service interface called Msg
I've added a new Method in the IMyServiceCallback called RecieveMessage.
I've added a way to identify the client.
In the proxy class I have the following :
public MyServiceClient(IMyServiceCallback Callback)
{
InstanceContext site = new InstanceContext(Callback);
NetNamedPipeBinding binding = new NetNamedPipeBinding();
EndpointAddress endpointAddress = new EndpointAddress(Constants.myPipeService + #"/" + Constants.myPipeServiceName);
myServiceFactory = new DuplexChannelFactory<IMyService>(site, binding, endpointAddress);
Init();
}
public void Init()
{
myServiceFactory.CreateChannel().Init();
}
In my service I have the following :
public class MyService : IMyService
{
public List<IMyServiceCallback> Callbacks { get; private set; }
public MyService(){
Callbacks = new List<IMyServiceCallback>();
}
public void Init()
{
Callbacks.Add(Callback);
}
// and so on
My IMyServiceCallback has been redefined to :
[ServiceContract]
public interface IMyServiceCallback
{
[OperationContract]
int GetClientId();
[OperationContract(IsOneWay = true)]
void WorkComplete();
[OperationContract(IsOneWay = true)]
void RecieveMessage(String msg);
}
By specifying a number, you can contact the client that corresponds with that number. If two clients have the same Id, both clients will be contacted.
I'm tring to implement my first WCF call-back server. This is my code:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ILogCallback))]
public interface ILog
{
}
public interface ILogCallback
{
[OperationContract(IsOneWay = true)]
void Push(string callbackValue);
}
public class MyLog : ILog
{
}
class Log
{
public static void initialize()
{
using (ServiceHost host = new ServiceHost(
typeof (MyLog),
new Uri[]
{
new Uri("net.pipe://localhost")
}))
{
host.AddServiceEndpoint(typeof (ILog),
new NetNamedPipeBinding(),
"PipeReverse");
host.Open();
// TODO: host.Close();
}
}
public static void Push(string s)
{
ILogCallback callbacks = OperationContext.Current.GetCallbackChannel<ILogCallback>();
callbacks.Push(s);
}
}
then I try to use my server using this code:
Log.initialize();
while (true)
{
Log.Push("Hello");
System.Threading.Thread.Sleep(1000);
}
But I got NPE, because OperationContext.Current is null. Why, what's wrong and how to fix that?
Because you are NOT in the context of an operation.
You're simply calling a static method of the Log class.
For you to be in an Operation Context your call MUST have been come from a client that is being serviced by your WCF server.
OperationContext.Current is a thread-static property that is initialized when request arrives to the server. Here's what you do to call the callback
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ILogCallback))]
public interface ILog
{
void PushOnTheClient();
}
public class MyLog : ILog
{
void PushOnTheClient()
{
ILogCallback callbacks = OperationContext.Current.GetCallbackChannel<ILogCallback>();
callbacks.Push(s);
}
}
You are missing the subscriber's subscription. The way you do this is to create a [oneway] operation in your MyLog WCF server called something like: "void SendMeLogs()".
This will open the client callback channel. You Then have to implement SendMeLogs() in lines of something like:
void SendMeLogs()
{
while(CheckLogsForNewData())
{
PushOnTheClient();
}
}
Since the SendMeLogs() function is oneway, the client will not block, but will start the subscription to your log server. (you can search the net for sample code for duplex calculator in wcf for a good example of this architecture).
The key however is that you must have a nice unsubscribe method like "StopSendingMeLogs" to break the loop, and also make the PushOnTheClient function fail safe, in case the client terminates or the specific client connection goes down.
The "CheckLogsForNewData" function should ideally be a shared (static) implementation in your case
I am very new to WCF and I am trying to learn but I think I am missing something significant here and I am aware of that so please be kind. I am working with a pre-existing console application that I have added a WCF host to, this is an oversimplified version of it but it should give you the jist of it
namespace mynamespace
{
public class MyConsoleApp
{
static void Main(string[] args)
{
CreateRemoteDebugHost();
StartLongRunningMethods();
}
public static void StartLongRunningMethods()
{
LongRunningMethod1();
LongRunningMethod2();
}
public static void LongRunningMethod1()
{}
public static void LongRunningMethod2()
{}
public void CreateRemoteDebugHost()
{
ServiceHost host = new ServiceHost(typeof(RemoteDebug), new Uri("net.pipe://localhost"));
host.AddServiceEndpoint(typeof(IRemoteDebug), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "PipeRemoteDebug");
//Create mex
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri("http://localhost:8001/RemoteDebug");
host.Description.Behaviors.Add(smb);
host.Open();
}
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IRemoteDebugCallback))]
public interface IRemoteDebug
{
[OperationContract]
string Message(string message);
}
public interface IRemoteDebugCallback
{
[OperationContract(IsOneWay = true)]
void OnMessage(string callbackValue);
}
public class RemoteDebug : IRemoteDebug
{
public string Message(string message)
{
IRemoteDebugCallback callback = OperationContext.Current.GetCallbackChannel<IRemoteDebugCallback>();
callback.OnMessage(message);
return message;
}
}
}
As you can probably tell I am trying to send debug or status messages back to a client(s) from inside of long running static methods. All the plumbing seems to be working correctly, the host comes up, I can add a service reference to my client application just fine but the trouble starts when try to invoke the WCF callback from the longrunningprocesses static methods. I can't seem to figure out how to do that properly.
What is also very confusing is that almost every example I have seen of WCF and callbacks assumes that everything you are doing is running from within the context of the WCF host itself, obviously in my example this is not the case. I know I'm probably going aobut this all wrong so could someone please set me straight on this? Any help is greatly appreciated.
TIA!
There is client (not to be confused with the client program) created as well through app.config or manually (e.g. public class MyClient: ClientBase<IRemoteDebug> or public class MyClient: DuplexClientBase<IRemoteDebug>, IRemoteDebug). This should send messages to the client programs. Example using DuplexClient above from some code I had:
[CallbackBehaviorAttribute(UseSynchronizationContext = true)]
public class SubCallback : IRemoteDebug
{
public void Event(SomeClass evt)
{
// some handling code using:
//public delegate void EventCallbackHandler(SomeClass evt);
}
}
InstanceContext ctx = new InstanceContext(new SubCallback ());
MyClient _client = new MyClient(
ctx,
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None),
new EndpointAddress("net.pipe://localhost/ServiceEndpointName"));
Also, you may want to pass some options to your service, such as:
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Single)]
public class RemoteDebug : IRemoteDebug
{}
It could be many things causing your particular issue, but this solved problems for me.