I have not yet started this, I want some advice on how to proceed before I waste too much time!

I am writing a Service which listens on a COM port, parses input, and stores relevant data to a database.

I am fine with all of this, except I need users to be able to alter the configuration of the service. For example, I need the user to be able to change a setting to select a different COM.

I could make a config file, and have the program read in values from it couldn't I? My only worry is where this config file should reside? AFAIK there is no set location for services to be held, and I will be using an installer and therefore will not know the exact location of the install.

Any help appreciated.

AdamDK

Please read this post regarding services. It is unrelated to your question but it might save you a lot of heart ache.

As far as your configuration file goes you should let the .NET Framework worry about where the file resides. You can use the ConfigurationManager class to interface with your configuration. Here is an example of one of my service configurations:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using VSS.IPC;
using VSS.NamedPipe;

namespace VSS.Core
{
  /* -------------------------------------------------------------------- */
  /// <summary>
  /// Gives stringly typed names to configuration options in the xml config
  /// </summary>
  internal enum VSSConfigurationOption
  {
    LoggingEnabled,
    ConnectionString
  }
  /* -------------------------------------------------------------------- */
  public static class ServiceConfig
  {
    private static object configLock;
    private static Configuration config;
    private const string ConfigSectionName = "appSettings";
    /* -------------------------------------------------------------------- */
    private static bool _loggingEnabled;
    private static object loggingEnabledLock;
    private static string _connectionString;
    private static object connectionStringLock;
    /* -------------------------------------------------------------------- */
    public static bool LoggingEnabled
    {
      get 
      {
        lock (loggingEnabledLock)
        {
          return ServiceConfig._loggingEnabled;
        }
      }
      set
      {
        lock (loggingEnabledLock)
        {
          ServiceConfig._loggingEnabled = value;
        }
      }
    }
    public static string ConnectionString
    {
      get 
      {
        lock (connectionStringLock)
        {
          return ServiceConfig._connectionString;
        }
      }
      set
      {
        lock (connectionStringLock)
        {
          ServiceConfig._connectionString = value;
          try
          {
            SQL.Main = new Vea.Data.VConnection(Vea.Data.ConnectionWrapperType.SQL, _connectionString);
          }
          catch { }
        }
      }
    }
    /* -------------------------------------------------------------------- */
    /// <summary>
    /// Call this when the service first starts up. You could also use a static constructor for this
    /// class and start it when the assembly is loaded but that slows down your service start time
    /// and can lead to problems. I start this up in a separate thread.
    /// </summary>
    internal static void Start()
    {
      configLock = new object();
      loggingEnabledLock = new object();
      connectionStringLock = new object();
      ReloadConfig();
    }
    /* -------------------------------------------------------------------- */
    internal static void Stop()
    {
      //I have this as a place holder because I call .Start() and .Stop()
      //on all my service classes
    }
    /* -------------------------------------------------------------------- */
    /// <summary>
    /// Refreshes the configuration from the configuration file
    /// </summary>
    public static void ReloadConfig()
    {
      lock (configLock)
      {
        if (config != null)
          ConfigurationManager.RefreshSection(ConfigSectionName);

        config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        CheckFields();

        LoggingEnabled = config.AppSettings.Settings[VSSConfigurationOption.LoggingEnabled.ToString()].Equals("Y");
        ConnectionString = config.AppSettings.Settings[VSSConfigurationOption.ConnectionString.ToString()].Value;
      }
    }
    /* -------------------------------------------------------------------- */
    /// <summary>
    /// Saves settings to disk
    /// </summary>
    public static void Save()
    {
      config.AppSettings.Settings[VSSConfigurationOption.LoggingEnabled.ToString()].Value = (LoggingEnabled ? "Y" : "N");
      config.AppSettings.Settings[VSSConfigurationOption.ConnectionString.ToString()].Value = ConnectionString;
      config.Save(ConfigurationSaveMode.Full);
    }
    /* -------------------------------------------------------------------- */
    /// <summary>
    /// Adds XML elements for configuration elements.
    /// I use this to avoid dealing with null return values.
    /// </summary>
    /// <param name="config"></param>
    private static void CheckFields()
    {
      string[] values = Enum.GetNames(typeof(VSSConfigurationOption));
      foreach (string value in values)
      {
        if (config.AppSettings.Settings[value] == null)
          config.AppSettings.Settings.Add(value, string.Empty);
      }
    }
    /* -------------------------------------------------------------------- */
  }
}

I use named pipes for IPC on the localhost to change settings instead of using another application to edit the service's configuration file. This allows the two applications to talk real time, ie: "application tells service to run an internal diagnostic right now". I used this article as a building block for my service comm framework: http://www.codeproject.com/KB/threads/dotnetnamedpipespart1.aspx

Alternatively if you want to edit the file directly then you should use a FileSystemWatcher to watch for modifications to the configuration file and reload it:

private void StartWatcher(string ExeConfig)
    {
      StopWatcher();

      _fsWatcher = new FileSystemWatcher();
      _fsWatcher.Path = Path.GetDirectoryName(ExeConfig);
      _fsWatcher.Filter = Path.GetFileName(ExeConfig);
      _fsWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
      _fsWatcher.Changed += new FileSystemEventHandler(OnChanged);
      _fsWatcher.Created += new FileSystemEventHandler(OnChanged);
      _fsWatcher.Deleted += new FileSystemEventHandler(OnChanged);
      _fsWatcher.Renamed += new RenamedEventHandler(OnRenamed);
      _fsWatcher.EnableRaisingEvents = true;
    }
    private void OnChanged(object source, FileSystemEventArgs e)
    {
      ReloadConfig();
    }
...you get the idea...

I hope this helps

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.