Complement Your Web Application using a Windows Service

Jul 20, 11:00 pm

Article Author: Christopher Schmidt
.NET 3.5 Books

Introduction


Often times developers do not think to use a Windows service in web applications. Why would you ever use a Windows service in your existing or new web applications? This article will build a case for several different scenarios where a Windows service can help by automating a process, off loading work from a web server or to provide scheduling capabilities.


Well for starters, a Windows service is great for scheduling/automating tasks. All sorts of tasks can be scheduled such as sending emails, generating reports requested by users, asynchronous processing, moving data from OLTP to OLAP, import/export of data between applications and so on. Windows services provide an automated program that runs in the background and allows the developer to do whatever is required without user intervention.


Another great reason to use a Windows service is to move some of the processing from a web server or middle tier to another server. For instance, instead of actually sending an email directly within ASP.NET, just queue the email in a table in a database. Then, a Windows service can periodically check the queue and send the emails.


In addition, you can leverage existing assemblies, web services and .NET remoting components you already currently use in your applications within a Windows service.


This article will start off by giving a short primer explaining some of the basics about Windows services. Next I will delve into the mechanics of how to create a Windows service, installing and uninstalling the service and finally some tips on how to debug it.


Lastly I will develop a real world example to put it all together. The example will allow users to request a report from an ASP.NET application. A Windows service will process the requests, create the report and email it to the requesting user. Although this is a simple example, the ideas can be expanded on and applied to your own domain.


System Requirements


To run the code for the sample you should have:


  • IIS and SMTP services running on Windows 2000 or later

  • The .NET Framework version 1.1

  • Visual Studio .NET 2003

  • SQL Server 2000 with the Pubs database installed

  • A Web Browser

Installing and Compiling the Sample Code


The sample download for this article contains a VS.NET solution containing 4 projects:


  • ASPTodayReportTool – web application

  • ASPTodayFramework – class library used by the web services and Windows service

  • ASPTodayWebService – web service

  • ASPTodayService – Windows service

To install the sample, you should first create two IIS virtual directories called ASPTodayReportTool and ASPTodayWebService , which point to the folders where you unzipped the sample project.


You will also need to run the SQL script called AlterPubs.sql in order to set up some tables, data and stored procedures accessed through the web service.


In addition you will need to update the configurations files to reflect your environment as shown below:


For the web application, the URL should be updated for the web services:



<appSettings>
  <add key="ASPTodayReportTool.JobManager.JobManager" 
value="http://localhost/ASPTodayWebService/JobManager.asmx"/>
</appSettings>


For the web service, the database connection will need to be updated:



<appSettings>
  <!—  DATABASE CONNECTION
    Database connection for Web Service
  —>
<add key="dbconnection" value="Data Source=localhost;Initial Catalog=pubs; User Id={your user name}; Password={your password};"/>
</appSettings>


Finally, for the Windows service, the URLs should be updated for the web services in the app.config file:



<add key="ASPTodayService.JobManager.JobManager" 
   value="http://localhost/ASPTodayWebService/JobManager.asmx"/>
<add key="ASPTodayService.ReportData.ReportData" value="http://localhost/ASPTodayWebService/ReportData.asmx"/>


Architecture Overview


If you followed the standard approach to developing a presentation layer, business layer and data access layer, then integrating a Windows service into your overall architecture is rather easy. Your Windows service can be designed and developed to take advantage of .NET remoting components, web services, and class libraries that you have already developed. Using .NET remoting components or web services allows for easy reuse and allows for the distribution of load across multiple servers if needed. In the example provided in this article, the Windows service calls two web services as well as providing the client an asynchronous reporting model.


Figure 1 shows a basic diagram of the architecture for a system that uses these various standard architecture components as well as the addition of a Windows service.



Figure 1. Overview of Architecture


Primer on Windows Services


A Windows service (formerly known as an NT service) enables a developer to create applications that runs on its own in the background. A Windows service runs under a specific account that is configurable in order to provide specific permissions. Windows services must be installed using the Services Control Manager ( SCM ) and can be configured to automatically be started when the server boots or they can be manually started as needed. In addition, they can be paused, restarted, stopped, and started again. One interesting aspect of Windows services is that they typically do not have a user interface.


The Windows Service Control Manager is a Microsoft Windows process that manages all of the services installed on a particular server. In order to start the SCM, open the Control Panel , double click Performance and Maintenance , double click Administration Tools and then double click the Services icon. This will open the SCM. Another way to open the SCM is to go to Start button then click the Run menu option , type in services.msc /s and click the Ok button.


Windows services are often system components that run behind the scenes on our servers. A few common examples of Windows Services that are used on a daily basis include Internet Information Services (IIS), Indexing Service, and Simple Mail Transfer Protocol (SMTP) service to name just a few.


Behaviors


As I mentioned previously, Windows services can be started, stopped, paused and restarted using the SCM. These are called commands. Each of these commands can be tied to a behavior or method within the service. For example, when you start a service, the SCM sends a command/message to the service which the service in turn calls the OnStart() method. Below is a list of common behaviors and a description as to when they are invoked:

























Behaviors/MethodsDescription
OnStart()Used to specify the processing that occurs when a Start command is received. Usually a Timer Class is set up and an event is called every X milliseconds to do some sort of processing by the service.
OnStop()Used to specify the processing that occurs when a Stop command is received.
OnPause()Performs the necessary processing to pause the service.
OnContinue()Performs processing so a service can return to normal functioning after the Pause command was processed.
OnShutdown()Specifies what should happen right before the system shuts down.
OnPowerEvent()Used to specify what processing should occur when the power status changes. This is necessary if your service will be running on a laptop.



These behaviors must be implemented in a derived class of System.ServiceProcess.ServiceBase . When creating a Windows service from the project templates in Visual Studio .NET, a class is automatically created that is derived from ServiceBase and the OnStart() and OnStop() methods are already created for you.


Creating a Windows Service


Here are the steps to create a Windows service project using Visual Studio .NET 2003. This should be very similar in future versions of VS .NET as well:


  1. Open Microsoft Visual Studio .Net 2003

  2. Go to menu FileNewProject

  3. Select your language of choice

  4. On the right, scroll down to and double click Windows Service

  5. After entering a name and selecting a location, click OK

The first thing we need to do is change the name of the service. I am going to use ASPTodayService as my name. In order to do this, double click the Service1.cs file created in your new project which will open the file in Design mode. Then right click in the design are and select Properties from the menu. Notice, that ServiceName is a property. Change it to whatever you want your service to be called.


Other read/write properties that are of interest when creating a Windows service are shown in the table below:






















PropertySetting
CanStopTrue if the service can be stopped. If the service is started, in the properties dialog of the Windows Service, the Stop button will be enabled when this value is true.
CanShutdownTrue if the service wants to be notified that the computer it is installed on is being shutdown.
CanPauseAndContinueTrue if the service can be paused and then restarted. If you do not overload OnPause and OnContinue, you want to set this to false. If the service is started, in the properties dialog of the Windows Service, the Pause and Resume button will be enabled accordingly.
CanHandlePowerEventTrue if the service should be notified of a change in the computer’s power status.
AutoLogTrue if the service should write events to the Application Event Log when an action is performed.



Debugging Windows Services


Debugging Windows services can be a challenge since most of the time there is no user interface and it can be hard to use the debugger with an automated process. This section will show you an easy technique for debugging the OnStart() and OnEnd() events for a service. There are several ways you could do this, but, in my opinion the easiest way is to use the following snippet of code in your Main() function of your service.



// The main entry point for the process
static void Main()
{ // Setting this flag to true allows you so run the Service // in the IDE. This will help you debug such service // behaviours as OnStart and OnStop bool bRunInIDE = true; if (bRunInIDE) { try { Service1 svc = new Service1(); svc.OnStart(null); //Pause thread so the timer for the service // will execute at least a couple of times // This helps us debug the windows service System.Threading.Thread.Sleep(580000); svc.OnStop(); } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.Message); } return; } else { // More than one user Service may run within the same process. To add // another service to this process, change the following line to // create a second service object. For example, // ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service1() }; System.ServiceProcess.ServiceBase.Run(ServicesToRun); }
}


Just make sure the bRunInIDE flag to true for debuging ( false for normal operation), set a breakpoint, hit F5 and next thing you know you’re stepping into the OnStart() method.


Notice in this code how I call the Sleep() method before calling OnStop() for the current thread. This is helpful because in addition to being able to debug the OnStart() and OnEnd() methods without having to install the service you can also debug the functionality you implemented within your service.


Creating an Installer Class


You need an installer class in order to install and uninstall the Windows service on the target server. The installer class creates the proper registry keys in order for the SCM to manage your Windows service. Non-service applications do not need to create special registry keys for the SCM since the SCM does not manage them. This section will explain how to create the installer class:


  1. After creating a Windows service, right click on the project name and select Add New Item

  2. Select the Installer Class template, change the name of the class and then click Open

  3. Using the ToolBox , under Components , drag and drop the ServiceProcessInstaller and ServiceInstaller Components in Design mode for the Installer class.

Note: If these components are not available in your tool box, then you need to add them by right clicking on Components and selecting Add/Remove Items .


Alright, now you know how to create the Installer class. Pretty easy, huh? This primer has given you a good introduction on creating Windows services which we will build on throughout the rest of this article.


Installing a Windows Service


The easiest way to install a .NET Windows service is to open a Visual Studio .NET command prompt and use the installutil utility. You will need to navigate to the Windows service’s folder and run the installutil utility making sure to specify the Windows service assembly as the parameter. Now, this most likely won’t work when you try to install the Windows service on a production server because Visual Studio probably won’t be installed on it. Instead, you will need to locate the installutil utility which should be located under the version folder in the framework folder under the Microsoft .NET folder in the OS Windows folder.


The installutil utility has the following command line syntax:



installutil [/uninstall][option […]]assemblyname ] 
  [option […]]assemblyname


The "assemblyname" is the name of assembly of the windows service you want to install.


Below is a list of the options available for this command:































OptionDescription
/h[elp]Displays the command syntax and various options for the tool.
/help assemblynameDisplays other additional options by other individual installers within the specified assembly.
/logfile=[filename]Sets the name of the log file where the install progress is stored. The default is aessmblyname.InstallLog .
/logtoconsole={true/false}Setting this to true displays the install progress to the console. Setting this to false, suppresses the output to the console.
/showCallStackThe call stack will be printed to the log if any exception occurs during the installation.
/u[uninstall]Uninstalls the assembly. This is applied to all assemblies regardless to where is appears in the command line.
/? assemblynameSame as /help assemblyname
/?Same as /h[elp]



The Sample Application


In our example, the architecture shown in Figure 2 will be used to demonstrate how to use a Windows service in a typical ASP.NET architecture. Via a web application, users will be able to request reports via the ASPToday Report Tool Web Application. When a user makes a request for a report, a request will be inserted into the database using the JobManager web service. A Windows service will be running in the background executing every X minutes depending on its configuration. When it executes, it will use the JobManager to retrieve a list of new requests. In addition it will call the ReportData web service to get the data in the form of a DataSet for the report. Next, it will generate the report. Finally, it will update the request as complete on the database and send an email to the requestor with the report.


In the following sections I provide a high level overview of the components in the system. I also provide a detailed look at the Windows service since that is the main topic of this article.



Figure 2. Architecture for the sample application


Database Design


Using the Pubs database installed with SQL Server 2000, I added a couple extra tables and stored procedures. I created a table called report_types . This look up table contains the various reports a user can request.



Figure 3. Definition of the report_types table


Next, I created a table called report_jobs . When a user requests a report, a record is inserted into this table which stores the report type, the user’s email address, status, request date/time and a completed data time.



Figure 4. Definition of the report_jobs table


This example does not use the report_xsl column in the report_types table but you could easily store the path and name of the XSL file used to create the report.


The table below describes the stored procedures I created in the pubs database:



















Procedure NameDescription
ReportJobComplete()This updates a request in the report_jobs as complete.
ReportJobNew()This adds a new request to the report_jobs table.
ReportJobsSelect()The returns a list of new report requests from the report_jobs table. These are the requests the Windows service will need to process.
ReportTypes()This returns a list of the types of reports a user can generate. The types of reports are in the report_types table.



Creating the Web Services


For this example, I created two web services: JobManager and ReportData.


You will need to add some form of security if you want to use the web services in a production environment. This task has been left up to you to determine the authentication and authorization you wish to use. In almost all of the applications that I build, my client chooses to use Windows Security.


The JobManager web service has the following methods:



















MethodDescription
AddReportJob()Adds a new request to the report_jobs table.
CompleteReportJob()Once the report is sent via an email, this method is called to mark the request is marked as complete.
GetReportTypes()Retrieves a list of report types the user can request.
GetNewReportJobs()Retrieves a list of new requests for reports.



The ReportData web service has the following methods:










MethodDescription
GetAuthorsDetailReportDataGets a DataSet of Author information.



If you setup these web services using the code provided, you will need to change the database connection string in the web.config file for this web service.


ASP.NET Web Application


This example has a simple user interface. In order to request a report, the user will navigate to the RequestReport.aspx web page. On this page, the user will be able to select the report type and enter an email address. Upon clicking the Submit button the AddReportJob() method of the JobManager web service is called and a new request is added to the database.



Figure 5. Requesting a report in the sample application


The Windows Service


As shown previously the OnStart() and OnEnd() methods are created for you in the derived class for the service if you use VS.NET wizard to create your application. But before adding code to these methods we have a few things we need to do.


First, I added some settings to the app.config file which will be used for configuring the Windows service. The idea is to be able to run various types of jobs within the same service. These various jobs would be configurable via the app.config . So for starters, I added a setting called RunReportJob .



<!— Which Jobs should we run —>
<!— This value can be any of the following: ‘Y’, ‘y’, ‘1’ which indicates to run the job. —>
<add key="RunReportJob" value="Y"/>
<add key="RunSomeOtherJob" value="N"/>
<!— Each job will execute based on the time interval configured here. —>
<!— The time interval is the number of minutes before the job runs again —>
<add key="ReportJobTimeInterval" value="1"/>
<add key="SomeOtherJobTimeInterval" value="10"/>


If the value of this appsetting is set to Y , y or 1 , the Report Tool will be turned on when the Windows service is started and the requested reports will be processed. Another setting called ReportJobTimeInterval will be used to set the time interval to wait between executions of the service.


Next I created a class called Config to the project. This is a basic wrapper used to retrieve the settings from the app.config file. Since the time interval is stored in minutes, when retrieving the setting it has to be converted into milliseconds in order for the Timer object to use it.



public class Config
{ public Config() { } /// <summary> /// Returns true if the ‘RunReportJob’ is configured to run. /// </summary> public static bool RunReportJob { get { return IsTrue(ConfigurationSettings.AppSettings["RunReportJob"]); } } /// <summary> /// Returns the number of milliseconds which is the interval that the /// job will use to execute /// </summary> public static int ReportJobTimeInterval { get { return Int32.Parse(ConfigurationSettings.AppSettings[ "ReportJobTimeInterval"]) * 60000; } } /// <summary> /// Casts a string into a boolean /// </summary> /// <param name="value"></param> /// <returns></returns> private static bool IsTrue(string value) { if (value == null) return false; char[] trueChars = {‘Y’, ‘y’, ‘1’}; if (value.Substring(0, 1).LastIndexOfAny(trueChars) >= 0) return true; else return false; } }


Next, I added two web references for the JobManager and ReportData web services. In order to create the web service reference, right click on the Windows Service Project in VS.NET and click Web Service Reference . A dialog will be displayed for adding the web references. Once both of these references were added, I changed the URL behavior property to Dynamic in the properties dialog for each web service reference. Visual Studio .NET will automatically modify the app.config file with the web reference’s URL. Now the Windows service will use the app.config file to find the web services. When moving this solution to a production environment, you will only have to modify web service’s URL in the app.config file to point to the production web service.


Next I added a class called ReportJobHost for hosting the Report Job functionality. This class contains a Start() and Stop() method. In order to run the job at a set interval, the Start() method uses a System.Timers.Timer object. The Timer object makes sure that the report job will run at the proper time intervals. In order to do this, set the Interval property for the Timer object. Note that this setting is in milliseconds. In my case, the Timer interval is set to Config.ReportJobTimeInterval which gets the time interval from the app.config file. In addition, I created an ElapsedEventHandler() . When creating this event handler, pass in the name of the method you want called when the time interval elapses. In my case, the OnTimerElapsed() method is called each time the time interval elapses. The Timer object is a private property of the ReportJobHost class.



public void Start()
{ int interval = Config.ReportJobTimeInterval; if (interval <= 0) throw new Exception( "Error – Time interval less than 0 seconds. Must be greater than 0"); // Create a new timer _timer = new Timer(interval); _timer.AutoReset = false; _timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed); _timer.Enabled = true;
}


The Stop() method is pretty straight forward. If the Timer object exists, then just close it which stops the timer thus stopping the Report Job. Note that if the Report Job is in the middle of processing reports, it will finish the reports before the Windows service completely stops.



public void Stop()
{ if (_timer != null) _timer.Close(); _timer = null;
}


During the OnStart() of the Windows service I instantiate a ReportJobHost object. Then, I call the Start() method. When the Timer elapses, the OnTimerElapsed() method of the ReportJobHost object is called. This method uses the JobManager web service to get a list of new report requests. It then loops through the list, generating the requested report by retrieving a DataSet using the ReportTool web service. Next it gets the report and emails the report to the requestor. Lastly the table is updated to mark the report as completed.



private void OnTimerElapsed(object source, ElapsedEventArgs e)
{ // Stop the timer so it doesn’t go off in the middle _timer.Enabled = false; JobManager.JobManager jobManager = new JobManager.JobManager(); ReportData.ReportData reportData = new ReportData.ReportData(); string output = ""; DataSet newReportJobs = jobManager.GetNewReportJobs(); foreach (DataRow newRequest in newReportJobs.Tables0.Rows) { switch (newRequest["report_id"].ToString()) { case "AUTHDET": // Code to create report // For code implementation, please see download break; case "AUTHSUM": // Code to create report break; default: break; } // Send Email and update request as complete // For code implementation, please see download } _timer.Enabled = true;
}


For this example, I only implemented one of the reports: the Author Detail Report.


Note: If you are trying to debug this Windows service using VS.NET IDE, make sure you modify the bRunInIDE variable to true in the main() method in Service1.cs . Otherwise this value should be false when installing the Windows service so the Windows service will work properly in a production environment.


Further Work


Additional work is necessary to use this example as a production level application. For starters, you will need to add security to the web application and the web services. You will also need to tailor the reports to meet your requirements.


In addition, I left it up to the reader to implement the code to use the XSL column in the report_types table. This column can be used to retrieve the path of the XSL file used in the generation of the report.


Conclusion


In conclusion, this article has covered the basics of Windows services as far as creating, installing, uninstalling and debugging them. An example was then created that used the concepts that were illustrated. This example allows users to request reports which are queued and the Windows service processes the requests offline. The ideas and concepts can greatly benefit your current architecture or future applications by off loading some of the processing from the web server or middle tier. These ideas and concepts can easily be expanded on to other real world scenarios like sending emails, processing credit card transactions, importing data from other sources or processing data offline to be transmitted.


Related ASP Today Content


http://www.asptoday.com/Content.aspx?id=1579


http://www.asptoday.com/Content.aspx?id=1119


http://www.asptoday.com/Content.aspx?id=2262

Founders at Work

Commenting is closed for this article.