Services usually require some
sort of configuration to make them do what you have coded them to do,
which will vary from service to service. You will seldom see—and you
should never create—services that require recoding each time you want to
modify something that should have been configurable.
You
can configure a service in several ways. You are only limited by being
able to expose that administrative capability to external users. Imagine
you wrote a service that downloaded files from different sources and
placed them in a different location. This type of activity is quite
common, especially between the extranet and the intranet.
When writing such a
service, you should take into account whether it should be
single-threaded and only able to download one file from one source at a
time, or multithreaded and able to download one or more files from one
or more sources at a time. With either service, you would need to know
the following information to make the service perform. Although this is
not an exhaustive list, it demonstrates that you should always consider
some important information when deciding how to best administer and
configure your service.
Types of Configuration Data
I always consider two
main types of configuration data when I write a service: configuration
information about how the service should run, and the configuration data
used by the service to perform the tasks it was written to do. Let’s
take a closer look at the difference between these two types of data.
Service Configuration Data
The difference between
service and user configuration data is that the service relies upon its
data to determine how it will run, not what it will do. Although this
may seem like a fine distinction, a few items, which I’ll cover in the
next sections, are specific to the service itself.
Number of Active Threads
Imagine that
your service can create multiple instances of a particular class and
have that class perform some work. Now imagine that the service itself
starts each class instance on its own thread. The number of threads that
the service is allowed to create would be specific to the service and
how it runs, not to how it does the work.
Global Configuration Data
In many cases your
service will need to do things such as send e-mail notifications or
raise alerts to a central location. Because this information can be
user-specific or globally used by the service, you should determine the
granularity of the data itself. If appropriate, you should then place
the data in the configuration file that is automatically associated to
your application.
Location of User Configuration Data
The
user-specific data location is another example of service-specific
information. It would do no good for the class instances in our previous
example to know where the data is if the service can’t first retrieve
it to even start the class instances.
Storing Service-Specific Configuration Data
When you use the
Microsoft .NET Framework, any application that you write has a default
location for service-specific data in a file that follows this format: applicationname.exe.config.
You can store all of your information in this one place, but usually it
is better to separate application data from user data.
This file is
available directly from your code at run time and design time; you
should use it to store information that your service needs to run.
Creating Application Settings
Creating application
settings is quite simple in Microsoft Visual Studio 2008. Create a
sample project or open a project that you have already created. I have
created a sample project called AppConfigSample and configured it to be a
console application.
Right-click the project name and choose Properties. In the Properties window, click the Settings tab.
You need to configure the following four pieces of data for each setting:
Name This setting represents the name used by your application to retrieve the value that you assign to this setting.
Type
This setting represents the type of information that you are storing.
You are able to store any type of information that can be represented by
a value in the configuration file.
Scope
This setting represents whether this is a user- or application-level
setting. User settings are updatable by the user during run time;
application-level settings are not modifiable.
Value
This setting represents the data that you want to retrieve while your
application is running. This data should represent the internal .NET
Framework object type you have associated to the setting so that it can
be easily recast into that type when used by your application.
Accessing Application Settings
In my sample application I created a user-level setting called testsetting, of type string, and assigned it the value of teststring. The following graphic demonstrates what the setting looks like after I add it to my AppConfigSample console application.
When the application
user setting is in place, we can write the code to access the value and
use it in our code. Currently the value is not very useful—we’re using
it for demonstration purposes only.
In my sample application, I use the Main method to read the value from the configuration file appconfigsample.exe.config and write it to the console. Listing 1 shows this code.
Listing 1. Sample user setting access.
Module Module1
Sub Main()
Console.WriteLine( _
"The User Setting Value is: " + _
My.Settings.testsetting + _
System.DateTime.Now.ToString())
End Sub
End Module
|
You can see that I use the My object to access the Settings instances for this sample application and then access the testsetting
setting. Any setting that you create will be accessible through this
same method. When I run this code I get the following output.
The sample
application demonstrates the ability to use application-level settings
that can affect both the application and the user. Application settings
represent changes to how the application runs, not how it does the tasks
at hand, which are usually more user-level settings.
In
this case, I used a user scope setting. However, from a service
perspective, I do not consider this to be a good configuration model
because in this scope, user-level settings are defined for how they
affect the appearance or use of that specific user on a specific system,
not how a single instance of an application in a central location
treats the requirements of each user. Therefore, putting every possible
user setting in the application-level configuration file really isn’t
the best option.
Each time your
application runs, it will have access to this file directly. You can
update this file while in run-time mode so that in the future you can
access any new attributes or settings that you define. Now let’s look at
the user configuration.
User Configuration Data
Users aren’t the only ones that need to connect to services. Services could be automated and not require any user interaction.
When a service is
automated with no user interaction, it still may allow users to
configure for themselves how it does the work it is programmed to do on
their behalf. For instance, if you create a service that downloads files
from different locations, each location can be owned by a different
user, or each file type that you download can be owned by a different
group of users. For each of these users you need a way to configure the
service to know how and what to do specifically for them.
User data is
information required by a service to perform the same tasks in a
different way for a different user. For that reason, user data is used
to explain to the service how a particular user expects something to
happen. Aside from explaining how things are done, user data explains
how that user wants to be notified of specific errors or successes that
occur within the service.
Going back to my
previous example of a service creating instances of a class that does
the work on separate threads, this definition of user data would mean
that each class instance needs to receive its particular configuration
data so that it can run properly.
From a service
perspective, we should use a service-level, application-specific setting
to tell us where the configuration data resides. From a storage
perspective, we can keep this information anywhere. Let’s look at
several options.
Microsoft SQL Server
This option is used by larger services to store both user-specific and
application-specific configuration data. In many cases an entry or
entries in the application-specific configuration file stores the
connection settings required to connect to SQL Server and gather the
configuration information. When user information is stored here, it is
up to the developers to create the tables and stored procedures required
to store and retrieve the data.
Microsoft Access or FoxPro
These options are used to create localized data stores that act
similarly to Microsoft SQL Server but are much smaller and less
scalable.
Flat files These
files are used to store configuration data, but they require developers
to create parsing, reading, and writing routines so that information
can be properly read. You can use the service-level application log to
store the location of this file for later retrieval.
XML files
These files are similar to flat files except that they already have
built-in classes in .NET to read from and write to them. The developer
still needs to create the schema for the file, but after that, reading
and writing are easy.
Advanced Service Administration
The general steps taken by a
service are to start, read the application-specific file, load the user
configuration data, and then begin performing the tasks it is designed
to do. The problem is that in many cases you want to be able to change
the behavior of your service to perform more tasks, or change how it
performs the current tasks. You might change the way the service
performs based on user requests or administrative requests.
Imagine that your
service is running and you can configure the number of threads it can
create or the number of users who connect at the same time. Imagine that
you’re monitoring your service and notice that although the service is
running fine, users are complaining about performance. When users
connect, or their specific task begins, things run just fine, but it’s
taking a while for the service to start performing its task or to get
connected.
In this case it
might be necessary to reconfigure the service to allow for more threads,
more user connections, or new work loads because of new user
configuration requests. Restarting the service each time you make a
change can become an administrative burden. Not only does this require
administrative interaction each time a user requests a change, but it
also causes an impact to every other user when the service is stopped.
Dynamic Service Configuration
Let’s look at how to make your service dynamically configurable. Three levels of configuration change are possible:
Service
This configuration change reflects, for example, how many threads or
connections the service allows at one time. Increasing the number of
threads or connections might be easy to code, but you need to take into
account some maximum number so that your service doesn’t become
overloaded.
User User
preferences include pointing to a new file location or new storage
location for an existing and already running process. Allowing users to
change preferences on the fly requires you to have a way to map the user
change request to an already existing class instance and data set. This
means you must have a way to uniquely identify users and user
configurations.
New
This includes user configuration data where new threads, connections,
and processing have to take place. New user configurations are usually
the easiest because they only require you to validate that the new
configuration won’t overload your system in advance. The steps to launch
new configuration data should be nearly identical to the steps required
to launch data when it was first read as the service started. Therefore
it requires only small coding changes.
Each one of these
changes can have a different effect on your system, and you have to take
that into account when deciding on which changes to make dynamic.
Dynamic File Configurations
If you use files to do
your configurations, you need a way to know when the file has been
updated so that you can reflect those changes in your service. The great
thing is that Microsoft Visual Studio 2008 provides this capability in
the FileSystemWatcher class.
The FileSystemWatcher
class allows us to monitor for new files or changes to existing files.
You can use an instance of this class to monitor when your configuration
file changes and then reload the file and make the changes where
appropriate.
Caution
Be
careful when monitoring for new or changed files. The system can report
that a file has been created or changed before the locks from the
system or modifying application have been released. You will need to
take this into account when monitoring for a change. |
You have to be able to
identify a change and associate it to an already existing instance. This
means you need to have code that will identify, shut down, clean up,
and restart any instances that are affected by the change. Making sure
that the code cleanly shuts down the processing that is already
occurring for a given user is important so that you can avoid losing
important data or transactions.
If you want to allow for dynamic configuration, be sure to take the following precautions:
Make sure
that all user data is uniquely identifiable—at least at the top root of
the information required to run the user request. If you use a name
property, make sure that it is unique and enforced. Because you are
using a flat file or XML files, you may need to validate the
configuration file first by looping through the names. For any
duplicates found, throw an error and then ignore that request.
Make
sure that if you are using multiple configuration files and are
monitoring for new or updated files, you take into account that the file
may still be locked even though you
received the event. I recommend making multiple attempts (with some
delay between attempts) to access and read the file. If you are still
unable to read the file after a specified amount of time and number of
tries, throw a final error and then decide to shut down or send out an
alert to administrators.
Microsoft Database Dynamic Configuration
Whether you are using
Microsoft Access, FoxPro, or SQL Server, you can monitor the data store
for any changes. Depending on the data store, you might even be able to
create events—such as in Microsoft SQL 2005 stored procedures—that will
notify your service when a change occurs.
Your service can
use two primary types of monitoring events to create your required
reactionary events: data store and code-based monitoring.
Data Store
Data store events are one way to acknowledge changes in the underlying
data. There may be requirements for complicated business logic to make
sure that the data is completely written or configured properly before
letting the service know it is ready to be updated. Stored procedures,
Microsoft SQL Server Notification Services, Microsoft SQL Agent, and
triggers are some ways to know within the data store itself that a
change has occurred. How you get that change to the service depends on
the service.
Code-based
For code-based monitoring of the data store, you need a thread or set
of threads within the service to monitor for any changes in the data
store from the time you load the data to the time it changes. In many
cases you can use state columns on the data, which would reflect whether
that data is in use and its current state. Imagine that a user disables
her data configuration. The state of the use of the data would be
inactive even though the data itself could be up to date. Monitoring for
activated data sets with states of new or updated would allow you to properly read configuration data on the fly.
Administrative UI
Previously service
developers created a console application that you ran from the control
panel to update or modify services. Now Microsoft gives you ways to
create Microsoft Management Console (MMC) snap-ins that can provide
access to your configuration data.
Whether you are using a
data store or a flat file, your administration would be much easier if
you created an administrative UI instead of trying to always modify the
files in Notepad or directly in the tables using stored procedures that
you create. Although an administrative UI is not necessary, it is
something you should consider when you develop your service.
Administrative UIs
should allow users to view the status of their service and make changes
or add new configuration options. This ties in directly with the ability
to make dynamic content changes; however, the UI itself is independent
from the code that looks for changes, because it merely makes those
changes.
If
you are using a data store, you may want to consider using a Web-based
solution for making changes to your configuration data so that it is
more flexible and requires less maintenance when
it comes to software updates. Windows-based clients require you to
update everyone using the application each time it is changed.