views:

210

answers:

4

I have a Windows Service that performs a number of periodic activities, and I want to change the settings of this service from a Windows Forms app. I'm not sure, though, about the best way to make sure the service has the most updated user preferences in it (how often to run, what folders to use for things, whatever else the user can specify). The user can change settings any time, at will, and I'd like the service know about it almost immediately. Here are the options I'm weighing:

  1. The form and service share use the same "Settings" object from a third, shared project, and the form uses a WCF "UpdateSettings(newSettings)" call to let the service know that there have been changes (or, optionally, a call to update each individual setting, though this seems like a lot over different calls). I currently use WCF for basic messages, but the settings object can be huge, since there's a lot of other stuff in there
  2. Form and Service use a common config file (XML, or the same settings object from #1, but serialized to disk). The Form just writes a new copy of the object after it's been changed, and the service checks every so often and picks it up if it's new, updating its copy of the settings
  3. Same as #2, but with a basic WCF call that that tells the service to go get the settings. Essentially, an "on-demand" instead of "polling" version of #2.

I know best is subjective, but I'm interested in any obvious pro or con reasons for these choices. Since I'll have to save my settings between runnings of the application (reboots, etc), I'll have to serialize the settings to disk anyway, so I'm already leaning towards #2 or #3. I'll need a place on disk where I can save the settings, but maybe the AppData folder will work okay, though that will only allow Administrators to change the settings, since they're the only ones that have permission to write to this location (where every user, including the service account, can read it).

Thanks for your insight!

+1  A: 

I kinda use your number 2.

But I'm only working in .NET 2 with my application, but it should still apply.

I have a settings class that I use across my 2 programs. Inside this settings class I setup a FileSystemWatcher object that looks at the Settings file.

If the settings file is updated by the other application, my current gets an event trigger to indicate that the settings need to reload.

You can also apply the same principle in your settings screen so that if the (service) other application updates anything during the settings edit, that is reflected in your screen.

I use the AppData (my company/application name directory) to store the file.

The other thing to bear in mind, is that there can be locking on the file while it is being written so you can either use a temp name save, delete old, rename temp method or put some protective locking on the file when reading after the filewatcher event fires that changes have been made.

I use this approach in my FileSystemWatcher before proceeding

IPSDependency.FileSystem.WaitForLockOnFile(Me.mFilePath)

the code for that is like this. (upon reading this now, there may be a better method my using some sleep in here to reduce CPU thrashing)

Public Shared Function IsLockAvailable(ByVal filename As String, ByVal fnfIsOK As Boolean) As Boolean
    Dim fi As FileInfo
    fi = New FileInfo(filename)
    Return IsLockAvailable(New FileInfo(filename), fnfIsOK)
End Function

Public Shared Function IsLockAvailable(ByVal theFile As FileInfo, ByVal fnfIsOK As Boolean) As Boolean
    Dim fs As FileStream
    Try
        If theFile.Exists Then
            fs = New FileStream(theFile.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.None)
            fs.Close()
            Return True
        Else
            Return fnfIsOK
        End If
    Catch ex As IOException
        'we just let the exception go, because we are only testing the file rather than trying to use it.
        Return False
    End Try
End Function

Public Shared Sub WaitForLockOnFile(ByVal theFilename As String)
    WaitForLockOnFile(New FileInfo(theFilename))
End Sub

Public Shared Sub WaitForLockOnFile(ByVal theFile As FileInfo)
    Dim lockAvailable As Boolean
    If theFile.Exists Then
        While Not lockAvailable
            lockAvailable = IsLockAvailable(theFile, False)
        End While
    End If
End Sub
Paul Farry
+2  A: 

Usually services that perform a 'polling operation' (i.e. sync files) have enough lag time in their poll interval that you can just as easily re-read all settings each loop, or even as needed.

If your service is more along the lines of a SOA back-end, then the changes may affect settings that are generally only used once during a service's lifetime. If this is your type of application, then the option #2 you describe above is the most reliable. I can't say I care much for Paul's implementation as polling a file like that will yield unreliable results. I would recommend using a globally named wait handle to signal your process for changes. I'm sure you can find an example here on SO. If you don't want to do that then you could poll for the configuration file's last-modified time changing.

Overall my preference is for the first approach using the registry for storage. Write all your settings in discrete values in a registry hive and read them on-demand in your service. It's faster than you might think and easy to implement on both front and back-end.

csharptest.net
A: 

I have to agree with your initial lean toward #2 and #3. I particularly like the #3 since I'm not a fan of polling but ultimately I think the decision between #2 or #3 will be driven by the requirements of your service.

As for storage of user settings I would recommend exploring Isolated Storage ( http://msdn.microsoft.com/en-us/library/3ak841sy.aspx ). It provides an excellent mechanism for secure, consistent, and reliable access to user files. You won't have to worry about user's having permission unless the Administrator has completely turned off Isolated Storage. Plus if you enable roaming, users can even take their settings with them if they utilize different systems on the same domain, pretty slick eh?

Matthew
Using "Isolated Storage" appears to be user-specific - as far as my settings are concerned, they're essentially per-computer. I want users to be able to view them, but only an administrator can edit and re-save them (or, with Vista/7, somebody who has used admin elevation). Can I use isolated storage for the local admin, or the "All Users"-type storage?
rwmnau
@rwmnau Further reading into Isolated Storage types I see it is at a minimum always restricted to the user who created it. There is a IsolatedStorageScope.User flag that I thought could be turned off but that doesn't appear to be the case. I'll try to think of an alternative route.
Matthew
+1  A: 

Assuming that everything is running on the same machine, how about this:

  1. Define a common c# structure that defines the settings. All projects include this .cs file. Define this class as a struct with a StructLayout of Sequential or Explicit so it can be mapped directly into unmanaged shared memory. For example:

    [StructLayout(LayoutKind.Sequential)] unsafe struct MySharedSettings { public int setting1; public int setting2; public string setting3; // add more fields here. }

  2. Use named shared memory (aka: memory-mapped files). This allows multiple processes on the same computer to share data, without the overhead of Remoting or WCF. Shared memory is extremely fast and, unlike pipes, offers random access to the shared memory data. The service would create the named shared memory, and the UI applications would open the shared memory. You would have to use pinvoke to use the underlying Windows APIs, but this is not a big deal.

  3. The UI applications write the MySharedSettings to the shared memory, while the service reads the shared memory.

  4. Use a named Semaphore and/or named Mutex to protect access to the shared memory and to signal the availability of new settings. The service has a dedicated background thread that simply perform a WaitOne() on the semaphore, and the UI thread will signal when new data is written.

Adel Hazzah