Partager via


How to make wcf service read messages from messaging queue after IIS reset?

Problem Definition

WCF service is not able to read messages from the private queue automatically after the application pool that hosts the service recycles.

Overview of the Issue

  1. An IIS hosted WCF service has a ServiceHost object which serves the incoming requests. Once the application pool restarts, the ServiceHost object gets disposed off. It is initialized only when either an explicit service call is made through some client or the services’ wsdl is browsed.
  2. The WCF service will keep on reading messages from the queue automatically only until the ServiceHost object is alive. Once the ServiceHost object dies, the messages will not be read.

The above 2 points accounts for why the wcf service does not read messages from the queue automatically after an IIS reset unless an explicit request is sent to the service.

How IIS address the issue

Before IIS 8, this situation could have been trickier to handle. You may need to write a task scheduler that periodically sends requests to the wcf service just to initialize the ServiceHost object.

However in IIS 8 and post versions, There is a feature called ApplicationInitialization that we need to use very intelligently to counter the above situation. The beauty of the ApplicationInitialization module is that It lets you send one automatic request to any website after an application pool reset/recycle so that the website’s resources can be initialized before the actual request is sent to it. It is also called as warming up the website. The steps to apply ApplicationInitialization module is provided here: https://www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-application-initialization

Creating the sample

Issue can be easily reproduced and fixed using the below sample.

  1. Client to send messages to private queue: I created a windows application that will send messages to the local private queue named TestQueue. Below is the screen shot of the message queue

MessageQueue

Code to send messages to the test queue on the click of a button is mentioned below

 

         using System.Messaging;
        private void button1_Click(object sender, EventArgs e)
        {
         try
           {
             for (int i = 0; i < 50; i++)
            {
            const string queueName = @".\private$\TestQueue";

            MessageQueue msMq = null;
            if (!MessageQueue.Exists(queueName))
            msMq = MessageQueue.Create(queueName);
            else
            msMq = new MessageQueue(queueName);

            string msgText = String.Format("Sample Message Sent At                      {0}", DateTime.Now);
            msMq.Send(msgText);
            msMq.Close();
            }
          }

          catch (Exception ex)
          {
            Console.WriteLine(ex.Message);
          }
        }

2. WCF service to read messages from the Test Queue: To create this service, use the below web.config and ServiceContract

 <system.servicemodel>
<bindings>
<msmqintegrationbinding>
<binding name="MSMQBinding" exactlyonce="false">
<security mode="None"></security>
</binding>
</msmqintegrationbinding>
</bindings>

<services>
<service name="MSMQService.MSMQService" behaviorconfiguration="svcBhv">
<endpoint bindingconfiguration="MSMQBinding" contract="MSMQService.IMSMQService" binding="msmqIntegrationBinding" address="msmq.formatname:DIRECT=OS:PANKAJDHINGRA.fareast.corp.microsoft.com\private$\TestQueue"></endpoint>
</service>
</services>

<behaviors>
<servicebehaviors>
<behavior name="svcBhv">
<servicemetadata httpsgetenabled="true" httpgetenabled="true"></servicemetadata>
<servicedebug includeexceptiondetailinfaults="true"></servicedebug>
</behavior>
</servicebehaviors>
</behaviors>
</system.servicemodel>
 [ServiceContract]
public interface IMSMQService
{
[OperationContract(IsOneWay=true)]
void GetData(MsmqMessage<string> message);
}

public class MSMQService : IMSMQService
{
public void GetData(System.ServiceModel.MsmqIntegration.MsmqMessage<string> message)
{
Debug.WriteLine(message.Body);
}
}

Host the above service under IIS.

3. To repro the issue, we need to recycle the application pool hosting the above WCF service. Following, we need to send the requests to the queue from our windows client, and we will be able to see our requests in the queue as service won’t be able to read it automatically after the app pool recycles.

  Fix the problem

  1. To fix the above issue, we will apply ApplicationInitialization module on a website hosting an aspx page. In the Load Event of this aspx page, we will make an HttpWebRequest to the service’ wsdl which will initialize the ServiceHost object for the wcf service. Once the application pool hosting this website recycles, a warmup request is sent to the website thus calling the Page_Load event of the aspx page.
  2. Website to apply ApplicationInitialization Module on: (Attached is the sample with this blog)
  • Make Sure the ApplicationInitialization module is installed on the server.

AddApplicationInitialization

 

  • Create an Application Pool named AppInit, host it under ApplicationPoolIdentity with .net Clr version 4.0. For this application pool, set the startMode to “AlwaysRunning” from the Advance Settings Dialog.

AppPoolSettings

You can also modify this setting manually in the ApplicationHost.config file.

<add name="AppInit" autoStart="true" managedRuntimeVersion="v4.0"                                                startMode="AlwaysRunning"/>

  • To create the aspx page, Extract the attached folder named AppInit. Place this AppInit folder under C:\inetpub\wwwroot (Or the path which your IIS is pointing to). Create a website or a web app named AppInit in IIS and point to the AppInit folder you just created.
  • For the AppInit Website, Go to the Advance Settings and change Preload Enabled to True.

WebsiteSettings

The same change should also modify this website’s entry in the ApplicationHost.config file under <Sites> tag. You should see the following entry.

 <application path="/AppInit" preloadEnabled="true"     applicationPool="AppInit">
<virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot\AppInit" />
</application>
  • The important pages inside AppInit folder are Default.aspx page, splash.html and Web.config. The following code is placed inside the Page_Load event of the Default.aspx page.
 protected void Page_Load(object sender, EventArgs e)
    {        
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create("pankajdhingra.fareast.corp.microsoft.com/MSMQService/MSMQService.svc?wsdl");
    HttpWebResponse response = (HttpWebResponse)req.GetResponse();
    Response.Write("This is the page.");        
   }

You can replace the above highlighted address with the wsdl address of your service. When the AppInit application pool recycles, A warm up request will be sent to the Default.aspx page calling the Page_Load event. In the web.config file, you will find the ApplicationInitialization module applied on Default.aspx page. It shows up as

<system.webServer>
<applicationInitialization remapManagedRequestsTo="splash.html" skipManagedModules="true">
<add initializationPage="/default.aspx"/>
</applicationInitialization>
</system.webServer>

  • IMPORTANT - After doing the above changes, Under IIS, you need to have ApplicationInitializationModule under Modules for AppInit Website.

ModulePage ModulesList

  • If the above module is not present here, ApplicationInitializationModule will not work. It can happen sometime if you don’t have the admin rights for IIS. However, you can manually add this module by selecting Configure Native Module (Top Right) and doing the changes as shown below

AddingModule

 

ConfigureNativeModule AddingWarmUpModule

 

Click on OK and you will find ApplicationInitializationModule in the module section.

NOTE: The Application pool where you host this AppInit website should be the one where the wcf service that wants to read the messages from the queue is hosted. Once you perform the above steps correctly, your messages in the queue will always be read by the service.

You can find the sample AppInit site at https://1drv.ms/u/s\!AguwMYFkyJ8MgRUkA-WbZpSdF8Am

HOPE IT HELPS:

Created By: Pankaj Dhingra

AppInit