Integrate Web and Windows Services-run ASP. NET code at a predetermined interval

Source: Internet
Author: User
Tags case statement finally block net command

Author: Andrew Needleman
Related Technologies: C #,. net, ASP. NET, and window
Reader Type: database developers and system structure designers

[Abstract]This article discusses how to arrange ASP. NET code execution and layer-N architecture design, and introduces basic knowledge of Web Services and Windows Services.

Because Web services can run in the same application context as the rest of ASP. NET applications, they can be executed in the same context as expected by the existing code. Because Windows Services can be started when Windows is started, I will use Windows Services to start web service calls.

Suppose you have compiled an excellent N-tier Application in ASP. NET and want to extend it to execute the scheduled task. For example, an email is sent to a selected user in the database every two hours, or data in the ASP. NET cache is periodically analyzed to Monitor the application running status. You do not want.. Net Applications discard your object model or separate the scheduler and ASP. if too many dependencies are created between applications, how can you avoid this while still allowing these applications to work together? In. NET Framework-based applications, the timer is often used to execute activities at a predetermined interval, so using a timer seems to be an appropriate solution. You can start a timer from the application_start handler in global. asax to run the scheduled task. Unfortunately, this solution is not robust enough for application domains, processes, or system reboots because a request must be sent to the application to start the timer. ASP. NET is a passive programming model that only responds to HTTP requests. Therefore, a process or user input must call code so that it can run.

A better solution is to use web service to provide interfaces for ASP. NET applications, and generate a Windows service that calls it at a predetermined interval. In this way, ASP. NET applications do not have to have the scheduling logic, and they only need to focus on the tasks that they can execute. Because Web services can run in the same application context as the rest of ASP. NET applications, they can be executed in the same context as expected by the existing code. Because Windows Services can be started when Windows is started, I will use Windows Services to start web service calls. Therefore, even if the server restarts, the application can start itself. This new startup feature makes Windows Services a more robust solution for this task than typical Windows-based applications. This is also why Windows Services can be used in many background processes (such as IIS.

In this article, I will demonstrate how to do this and create a minimum number of dependencies between a calendar application and an ASP. NET application. This solution simplifies the scheduling application for starting ASP. NET jobs. In a calendar application, except for the Web service endpoint that it calls, the logic specific to the ASP. NET application is not called. The Windows Service uses the app. config file to store the Ur of the web service and the interval between Windows service calls to the web service. By storing these two settings in the app. config file of the Windows service, you can change them without re-compiling the Windows service. If you need to change the behavior of an application when calling an application, you can only change the logic in the ASP. NET application. However, you do not need to change the code of the application on schedule. This means that the calendar application will be isolated from changes in the ASP. NET application.

Note: the premise of this solution is that some tasks should only be executed in the context of the running ASP. NET application. If this is not your task requirement, you should consider directly referencing the business logic assembly of ASP. NET applications from Windows Services and bypassing ASP. NET processes to stimulate these tasks.

Application Structure

Figure 1 plan

Typical ASP. NET applications are generated using a series of independent layers that execute specific functions. In my specific example, I have the database metadata class, business logic class, business process class, and ASP. NET page as the entry point for these layers (see figure 1 ).

ASP. NET pages are only used to display and retrieve data. They are the business flow interfaces that actually coordinate all work. The process class calls the business logic class in the correct order to complete specific transactions, such as ordering widgets. For example, the process class can first call the business logic to check the inventory, then order the small parts, and eventually reduce the inventory to the appropriate level.

The business logic Class determines how to call the database sequence class and process the result as needed to obtain the final result that can be used for other operations. For example, the business logic can be used to calculate the total price, including the tax of a specific State. First, you may need to use the data category to retrieve the State's tax rate and basic price from the database, and then multiply them by the total tax for each item. The databases class maintains this logic to connect to the database and return the result set in a format that can be used at a higher level (for example, dataset, able, or datareader. These classes only retrieve data from the database and update the database based on the information feedback. They do not process the results. For example, they may check the tax rate of a specific State (U.S.), but they do not calculate the total tax on the order.

Microsoft Data Access application building block simplifies the data discovery class by providing easier ways to communicate with databases and stored procedures. For example, you can call the filldataset method of its sqlhelper object to fill the dataset with a line of code based on the output of the stored procedure. Generally, you must write code to create at least a dataadapter and a command object, which requires at least four lines of code. Data Access Application Block connects to the stored procedure in the database. This stored procedure provides the SQL code required to access and modify data in the database.

Add a scheduled job to the application

ASP. net web service can provide existing ASP.. NET application interface, which acts as the service and calls ASP.. NET application to take actions between Windows Services. The Windows service then calls the ASP. NET application at a predetermined interval.

Figure 2 run a scheduled job

ASP. NET web service can provide you with interfaces for existing ASP. NET applications that keep the task logic. This interface acts as the intermediary between the service and the Windows service that calls ASP. NET Applications to take actions. The Windows service then calls the ASP. NET application at a predetermined interval. By generating an ASP. NET web service in an existing ASP. NET application, you can re-use the business objects and logic created for the ASP. NET application in the scheduled job. Figure 2 shows the detailed information of the application process-from the Windows Service Application on the client to the Web service that runs on the server to start, and runs throughout the execution of each scheduled task.

Figure 3 additional functions of the application

As you can see in Figure 3, this process requires some modifications to the standard hierarchy described above. Windows Services will wake up ASP. NET web services at specified intervals. The ASP. NET web service then calls methods in the Web application process layer to determine which scheduled jobs should be run and then run them. After implementing the basic solution, you can use the app. config file of the client to determine the time interval between Windows Services and Web services. Next, you can add the functions required by the business flow layer to traverse and run jobs. Many N-layer experts are more interested in the process layer than they are interested in the rest of the layer, so I will finally discuss database tables, database stored procedures, data access code and business logic. Finally, add code from the bottom (Database Table level) to the middle (business logic layer) to the existing layer of the application to support the job functions used by the process layer.

Generate Web Services

To generate a web service, first add jobrun ASP. NET web service to the ASP. NET application that is located in the same layer as the existing ASP. NET code. Make sure that your asp. Net project has reference to the business logic, process, and data access project. Next, to create a runjob web service method in jobrun web service, the Web service method needs to call the corresponding functions at the process layer to run the appropriate job. This means that the runjob method can be as simple as the following:

Public void runjob ()
Flow. jobflow JF = new flow. jobflow ();
JF. runallactivejobs ();

Use the runjob function to create an instance of the jobflow class (which is located in the Process Layer) and call its runallactivejobs function. The runallactivejobs of the jobflow function completes all the actual work of coordinating job running, while the runjob function only acts as the entry point of the sequence. Please note that this Code cannot prevent jobs from running in more than one thread at the same time-if the Windows service schedules tasks too frequently (the task speed exceeds the task speed ), or this may happen if another application calls an entry point. If this method is not thread-safe and multiple threads are allowed to call it at the same time, it may cause problems to the results of these jobs. For example, if job x sends an email to Mary Smith but queries the database in job y to process the email and does not update the database, Mary may receive two emails. To synchronize access to this function, I will use the mutex class in the system. Threading namespace:

Private Static mutex mut = new mutex (false, "jobschedulermutex ");

Mutex supports cross-process synchronization, so this prevents multiple processes from running at the same time, even if two different ASP. NET auxiliary processes are involved. Now, let's change the runjob method to use mutex to ensure that no other job is running before starting the job.

Code snippet 1 runjob Web Service:

Public bool runjob ()
Bool ranjob = false;
Mut. waitone ();
Flow. jobflow JF = new flow. jobflow ();
JF. runallactivejobs ();
Ranjob = true;
Mut. releasemutex ();
Return ranjob;

As you can see in the runjob function in code segment 1, you can call the mutex waitone function to wait for the thread until it becomes a unique thread before execution. Then, call the releasemutex function to indicate that you have executed the code that only needs to be run in one thread. Of course, blocking here may not be the correct solution. You can choose to return immediately when another thread has executed a job (in this case, you can specify a short timeout value for the waitone method) and return immediately from runjob when the mutex lock is not available. Put all the main operations of this function in a try-Finally block, so that even if the runallactivejobs function exits due to unexpected exceptions in the runallactivejobs function, releasemutex is also called.

You may want to use some form of authentication and authorization (Windows security may be used) to ensure the security of web services, to ensure that the service can run only with the correct authorization, however, I do not intend to discuss this in detail in this article.

Since you have generated a web service so that you can call it from another application, now let's generate a Windows service that can use it.

Generate a Windows Service

First, create a new Windows Service Project in another instance of Visual Studio. NET, and name it invokingaspnetservice. CS. Add the main method as follows to ensure that the service will be started correctly:

Public static void main ()
Servicebase. Run (New invokingaspnetservice ());

Now, add the using statement to the following namespace:

Using system. configuration;
Using system. Globalization;

Right-click the design surface of invokingaspnetservice. CS and select "add installer" to add the service installer. You should change the starttype attribute of serviceinstaller1 to automatic to enable Windows Services when Windows starts. Set the servicename attribute of serviceinstaller1 to invokingaspnetservice to name it properly in the Service Manager, and then change the serviceprocessinstaller1 account attribute to local service.

Step 3: create a web reference to the invokingaspnetservice web service and name it jobrunwebservice. Change the jobrunwebservice URL behavior attribute to dynamic to enable Visual Studio. NET to automatically expand app. config with the URL referenced by the web. The generated proxy class searches for the Web Service URL in the configuration file, so that you do not need to re-compile the Windows service to guide it to different endpoints.

Fourth, create a method in the Windows service to run the method each time the Web Service is called. The method is as follows:

Private void runcommands ()
Jobrunwebservice. jobruninterval objjob =
New jobrunwebservice. jobruninterval ();
Objjob. runjob ();

As you can see, you will declare the Web Service proxy and create it just like creating any other. Net object. Then, call the runjob method of the Web service to run the job on a remote web server. Note that each step is no different from using a local class, even if you are using a web service.

Fifth, you need to call the runcommands function in the Windows service. You should call this method at a fixed interval based on the frequency of the job you want to run on the remote server. Use the system. Timers. Timer object to ensure that the runcommands function runs at the correct interval. The elapsed event of timer enables you to trigger any function you specified after each interval (note that the interval length is specified in the interval attribute ). You will use the triggered function to call the runcommands function so that this function can be automatically executed. By default, this Timer class triggers an event only when it expires for the first time, therefore, you need to set its autoreset attribute to true to ensure that it resets itself repeatedly each time. You should declare it at the service level so that any function of the service can reference it:

Private timer;

Next, create a function to initialize timer and set all its related values:

Private void initializetimer ()
If (timer = NULL)
Timer = new timer ();
Timer. autoreset = true;
Timer. interval = 60000 * convert. todouble (
Configurationsettings. deleettings ["intervalminutes"]);
Timer. elapsed + = new elapsedeventhandler (timer_elapsed );

To change the configuration interval without re-compiling the application. the interval is stored in the config file so that the initializetimer method can use configurationsettings. the deleteworker accesses it rather than hardcoding it, as shown below:

<Add key = "intervalminutes" value = "5"/>

Make sure that timer calls the timer_elapsed function when the timer expires to process the elapsed event. The timer_elapsed method is very simple. It calls the generated runcommands function, as shown below:

Private void timer_elapsed (Object source, system. Timers. elapsedeventargs E)
Runcommands ();

Finally, you must use the installutil command to install the Windows service. The easiest way is to open the Visual Studio. NET Command Prompt window, navigate to the service directory, run the installutil utility, and specify your assembly as a parameter.

Expand the process layer to process scheduled jobs

An important task is to expand the process layer to handle the needs of running scheduled jobs (assuming that the differences between jobs are large enough to encode them, not just parameterization ). This involves collecting all jobs from databases whose next startup time has passed and running them separately. Within the process layer, you will create a base class named job to provide all the features that a job has. This includes a mechanism for initializing and retrieving jobid, a mechanism for running a job, and a public method for setting the next running (runsinglejob) in the database after successful running ), and a writable mrunjob that you want to customize for each job ).

The process layer also generates job-specific classes for each job it executes. These classes will inherit from the basic job class, and rewrite the worker mrunjob function of the job class to customize the execution of the specific job. You also need a factory class (jobfactory) to create and initialize the jobid of the correct job class. The static createjob function creates an appropriate job based on the jobid passed to the function. Finally, the process layer must be able to determine the jobs to be run, traverse them, and run them. This is the function provided by the jobflow class through its runallactivejobs method.

First, let's create a job base class in the Process Layer Project (this class will become the parent class of each job class ). Code Segment 2 shows the core of the abstract base class of a job. It allows initialization and retrieval of jobid, and ensures that the database is updated after the job runs successfully. After a jobid is created, it does not change the value of a given job. Therefore, you must ensure that the function is not changed after initialization. When the jobfactory class of each job class is created, its own jobid value is set.

Code snippet 2 job abstract base class:

Protected bool isinitialized = false;
Protected int mjobid;
Public int jobid
Get {return mjobid ;}
If (! Isinitialized)
Mjobid = value;
Isinitialized = true;
Else throw new invalidoperationexception ("jobid already set .");

Public void runsinglejob ()
If (isinitialized)
Optional mrunjob ();
Recordjobsuccess ();

Protected abstract void extends mrunjob ();

Protected void recordjobsuccess ()
Joblogic JL = new joblogic ();
Jl. updatejobdone (jobid );

The runsinglejob function determines that the jobid of the job has been initialized, runs the job, and updates the database using the recordjobsuccess method after the job is successfully run. The isinitialized variable is used to ensure that every job initializes its jobid before running. The explain mrunjob abstract method is implemented by the derived job class and maintains the actual logic of the task. The base class calls the recordjobsuccess function, this function uses the updatejobdone method of the joblogic class in the business logic layer to record its running time in the database and the scheduled next running time. Later, I will create the joblogic class for the business logic layer.

The job class not only provides the function of initializing the jobid variable, but also provides the function of updating the database at the next running time when the job is successful. In addition, you only need to rewrite a function with class-specific code. This allows you to create subclass of the job class. To do this, you need to create two classes to run specific types of jobs and inherit from the job class to obtain other functions of them. Create a jobruntest class and a jobemailusers class, and ensure that each class is inherited from the job class, as shown below:

Public class jobruntests: Job

Now, rewrite the explain mrunjob method of these two classes as follows (using the jobruntest class as an example ):

Protected override void extends mrunjob ()
/// Do runtest specific logic here

Put the Job-specific logic inside the method. The remaining code responsible for running the job and updating the next run time in the database is inherited from the job base class. Your job combines calls to existing business logic classes to run complex processes. Now that you have an example job, let's take a look at how to use the jobfactory object to create these jobs.

The jobfactory class is used to create the corresponding sub-job class for each jobid. The jobfactory class uses the jobid variable in its static createjob function and returns an appropriate job subclass. Code snippet 3 shows the code in jobfactory.

Code snippet 3 jobfactory class:

Public static job createjob (INT currentjobid)
Job myjob;
Switch (currentjobid)
Case 1:
Myjob = new jobemailusers ();
Case 2:
Myjob = new jobruntest ();
Return NULL;
Myjob. jobid = currentjobid;
Return myjob;

The createjob function uses the current jobid and uses it in a case statement to determine which subclass of the job class should be returned. Then, initialize the current jobid and return the class derived from the job. Since you have a job base class, its job-specific subclass, and the method used to select the class to be created, then you can examine how to use the jobflow class to combine all of this.

To create a class named jobflow to collect and execute the appropriate job, add a function named "runallactivejobs" to traverse each job you want to run, and call their respective runsinglejob functions. You will need to use the runallactivejobs function to obtain the list of jobs scheduled to run from the database through the business layer, data access layer, and stored procedure, and then run them using their respective runsinglejob functions. The following code shows how the runallactivejobs method of the jobflow class achieves these objectives:

Joblogic JL = new joblogic ();
Dataset jobsactivedata = Jl. getallactivejobs ();
Foreach (datarow jobsactive in jobsactivedata. Tables [0]. Rows)
Int currentjobid = convert. toint32 (jobsactive ["jobid"]);
Job myjob = jobfactory. createjob (currentjobid );
Myjob. runsinglejob ();

Basically, you will store jobs in the database and information about the time they were last run and the interval at which the Code should wait for two consecutive runs. Then, the joblogic class with the getallactivejobs method in the businesslogic layer is used to retrieve the job to be run. The ID of each activity job is used to obtain the job object. As described above, the runsinglejob method of this object can be used to execute the task.

Job timing information

Determining which scheduled jobs should be run means you need to store basic information about them, for example, the interval between two consecutive runs, their last run time, and the next time they should be run. To do this, create a job table in the SQL Server database (see table 1 ).

Table 1 job table

Column Datatype
Jobid Int identity
Jobtitle Varchar (500)
Jobinterval Datetime
Datelastjobran Datetime
Datenextjobstart Datetime

The jobid column keeps the unique identifier of each job in the job table. The jobtitle column contains the job name so that you can determine which job is running. The jobinterval column maintains the interval between two consecutive jobs. They are dates and time intervals greater than 1/1/1900. After the job is successful, it should be added to the current time to calculate the time when the next job should run. For example, the value 1/2/1901 in the jobinterval field means that one year and one day need to be added to the time when the job was last run.

The datelastjobran column contains the datetime value of the date and time when the job was last run. The last column of datenextjobstart contains the next running time of the job. Although this column should be calculated by jobinterval plus datelastjobran, if you set this column as a regular datetime column, you can understand the application layer more vividly.

Retrieving and setting job timing information

To retrieve and set job timing information through the new stored procedures in the SQL Server database, these stored procedures must find all the jobs in the database that need to be run by the application, update the information of a single job in the database to indicate that it is running and set the next job running date of the job. Each job has a datenextjobstart column in the database to indicate the date and time the job should run. If the current date and time have exceeded the date and time of the datenextjobstart column, the job should be run in the process. The stored procedure of the job to be run is as follows:

Create procedure
DBO. job_selectjobs_nextjobstartbefore
@ Datenextjobrunstartbefore datetime
Select * from job where datenextjobstart <@ datenextjobrunstartbefore

The job selected by this stored procedure in the job table meets the following conditions. Its datenextjobstart value is earlier than (less than) @ datenextjobrunstartbefore datetime parameter value. To identify which jobs should be run, you only need to pass in the current date and time through the parameters of the stored procedure. Now that you can select the job to run, you can generate the process to update the job after it runs. The stored procedure for updating a database using the previous run date and the next run date of a single job is as follows:

Create procedure DBO. job_update_startend_calcnext
@ Jobid int,
@ Datelastjobran datetime
Update job
Datelastjobran = @ datelastjobran,
Datenextjobstart = @ datelastjobran + jobinterval
Jobid = @ jobid

In this process, a new datelastjobran is used to update the job identified by @ jobid, and the value of datenextjobstart is calculated by adding jobinterval to the passed @ datelastjobran. This process should only run after the job referenced in @ jobid, and should be called with the @ datelastjobran parameter that is equal to the date and time of the previous job run.

Call the job timing Stored Procedure

You can add a new class named jobaccess to expand the data access layer to call the job timing stored procedure. In the data access layer, functions are used to convert parameters passed to the business layer into stored procedure database queries and return results to the business layer. Parameters in functions at the data access layer mirror the parameters of the stored procedure they access because they do not execute any business logic for these values. You will access the database through the sqlhelper class of the Microsoft Data Application building block. This class contains functions used to simplify data access code, so that your code is more concise and readable.

To change the data access layer to run a scheduled job, first add the jobaccess class to the existing data access layer to maintain the functions required for job scheduling. Next, create a function in the jobaccess class to return the dataset of the job that needs to run by calling job_selectjobs_nextjobstartbefore. You also need to create a function in the jobaccess class to call the job_update_startend_calcnext stored procedure, but no results are returned.

First, add the jobaccess class to the data access layer. Then, edit the jobaccess class to add the following "using" statement:

Using system. Data;
Using system. Data. sqlclient;
Using Microsoft. applicationblocks. Data;

Now let's take a look at how to add the selectjobsbeforedate function to retrieve the list of jobs to be run. The signature of the executedataset function of sqlhelper is as follows:

Public static Dataset
Executedataset (
String connectionstring, string spname,
Params object [] parametervalues)

The following is the selectjobsbeforedate function. It uses executedataset to call the job_update_startend_calcnext stored procedure and returns the dataset of the result:

Public dataset selectjobsbeforedate (datetime beforedate)
Return sqlhelper. executedataset (
Connectioninfo. connectionstring,
"Job_selectjobs_nextjobstartbefore, myparams );
New object [] {New sqlparameter ("beforedate", beforedate )});

After a job is run, you need to execute the corresponding stored procedure to update the job status information. The updatejob method that completes the work uses the executenonquery method of the sqlhelper class. The signature is as follows:

Public static int executenonquery (
String connectionstring, string spname, Params object []

The updatejob method can be written as follows:

Public void updatejob (INT jobid, datetime datelastjobran)
String connstr = connectioninfo. connectionstring;
String spname = "job_update_startend_calcnext ";
Sqlparameter myparam1 = new sqlparameter ("jobid", jobid );
Sqlparameter myparam2 = new
Sqlparameter ("datelastjobran", datelastjobran );
Object [] myparams = {myparam1, myparam2 };
Sqlhelper. executenonquery (connstr, spname, myparams );

The updatejob function in the jobaccess class should be the parameter of the stored procedure passed to it by the image. Therefore, the updatejob function has a jobid parameter and a datelastjobran parameter, and their data types are the same as those in the job_update_startend_calcnext stored procedure. Using the jobid and datelastjobran parameters, you can create two sqlparameters, place them in the myparams object array, and use the executenonquery function to execute the stored procedure. Now that you have created the jobaccess class, you need to create the last class layer to connect the process layer to the data access layer.

Process scheduled jobs

The last layer that must be modified to process a scheduled job is the business logic layer, which I call joblogic. This class executes the basic logic for the variables between the process layer and the data access layer.

First, use the following statement to add the joblogic class to the dataaccess layer:

Using system. Data;
Using scheduledwebservice. dataaccess;

Second, generate the getallactivejobs function of the joblogic class to find all jobs that still need to run at the current time or before, as shown below:

Public dataset getallactivejobs ()
Jobaccess ja = new jobaccess ();
Return Ja. selectjobsbeforedate (datetime. Now );

The getallactivejobs function creates an instance of the jobaccess class and calls its selectjobsbeforedate with the parameter value of the current date. Getallactivejobs selects the current date to pass to this function, so you can find out which jobs are scheduled to run before the current time.

Finally, create the updatejobdone function of the joblogic class to update the database to indicate that the specified job has just been completed, as shown below:

Public void updatejobdone (INT jobid)
Jobaccess ja = new jobaccess ();
Ja. updatejob (jobid, datetime. Now );

This function creates an instance of the jobaccess class and calls its updatejob method. It passes the jobid parameter and then uses the current date of the datelastjobran parameter. You need to pass the current date and time to the updatejob function, because it is the time when the job is successfully completed.


By extending ASP. NET Applications with automatically completed tasks, you can explicitly schedule events, rather than waiting for requests to execute code. You can use this feature to execute multiple tasks-from running complex computations to creating reports on a regular basis and sending them to managers. Such tasks can reuse existing logic and objects in the ASP. NET layer at the same time, and reduce development time and improve maintainability. You can also expand the jobs started by the scheduler without changing the Windows Services that start it.

Please note that there are many different situations for the content I will discuss in this article. For example, you can use a tool that is as simple as a Windows Task Scheduler instead of creating a custom windows service to act as a scheduler, most of the functions discussed in this article are implemented without creating a custom windows service to act as a scheduler. In short,. NET Framework has greatly simplified the creation of Windows Services, so even if you have previously discovered that they are very difficult to use, you should reconsider them as an option. Similarly, Web services are a good way for applications to publish functionality to other applications, and will continue to play a role in this regard.

Author Profile

Andrew Needleman is a managing partner of blockchain code, a consulting company near Boston specializing in designing and developing N-Layer Web applications in. net. He has trained hundreds of developers in the C #,. NET Framework, and Visual Basic. Net fields.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.