-1

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 don't appear to be setting any values when you create the instance of `myConfig` - if that is the case, then of course you'll get an empty instance back. – Tim Aug 14 '14 at 08:20

3 Answers3

1

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()
        { }
    }
}
ajg
  • 1,743
  • 12
  • 14
  • The actual program i'm writing does separate WCF and the windows service, but for brevity for the post i was trying to combine them. – Lord Chariot Aug 14 '14 at 20:24
  • OK - have you tried my answer? I do think the issue is that when you use the ServiceHost it makes a new instance of Service1 and you haven't set the config in that instance. Easy way to prove it - move the config setting lines into the Service1 constructor and see if it starts working as desired. – ajg Aug 14 '14 at 22:51
  • I think you almost cracked the code. I did separate the WCFService1 and Service1 and fully qualified the reference to myConfig. However, that wasn't quite it. The actual key was to turn the myConfig to static. I'll repost the working version, with some changes. – Lord Chariot Aug 15 '14 at 16:16
0

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)
  {
  }
  ...
}
Marcel N.
  • 13,726
  • 5
  • 47
  • 72
  • Yes, I knew about InstanceContextMode.Single to keep it persistent across multiple requests. I was more concerned initially that the windows service could provide values it was actively using first. I will probably use .Single eventually. I'm not having much luck twisting my example into your suggestions. Seems like overkill. Must rethink this. – Lord Chariot Aug 14 '14 at 20:12
  • @LordChariot: Well, this is usually how it's done. I mean, having the windows service class be a WCF service as well is an exception case (can work out for small apps/services). Anyway, what I have showed you there is nothing but official WCF support to get this working. If you have any particular issues, let me know. – Marcel N. Aug 14 '14 at 20:15
  • I understand how this is supposed to work, but way to much effort to get what i want. (despite it actually being the best practice) – Lord Chariot Aug 15 '14 at 16:34
0

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.

Matt Davis
  • 45,297
  • 16
  • 93
  • 124
  • [DataContract/DataMember] in and of themselves have no effect on the results. Values themselves are returned if i set them in the accessors. Testing other answers now. – Lord Chariot Aug 14 '14 at 16:08