Sample Image - Using_MSMQ_ASPnet_Window.jpg

Introduction

We all have experiences of programming web programs. But very few of us actually have experience of long running Web programs that often times-out in the browser or kills the machine due to memory waste. We had this requirement of having to print about 20000 reports from the web directly to the printer. The idea was to create PDF at runtime and then send them to printer. However, with Crystal Reports runtime and ASP.NET worker processes, the entire process was going extremely slow; besides, the memory usage by aspnet worker process was tremendously high and no other processes could be run. At this point, the decision was taken to do asynchronous programming using MSMQ and Windows Service.

我们都有编写web编程的经历。但很少经历过那些经常在浏览器中显示超时的长时间运行的web程序或者使机器崩溃的内存垃圾。我们有个这样的需求,直接从web上打印20000份报表到打印机上。方法是在运行时创建PDF文件,然后把他们发送到打印机。然而,使用水晶报表运行时和asp.net工作进程,整个处理过程极为缓慢;另外asp.net所占有的内存也非常高。导致其他的进程运行不了。基于这一点,我们决定使用MSMQ和windows service进行异步编程。

Background

The reader must have some basic understanding of MSMQ, Windows Services, and the .NET platform. Here are a few important web links for clearing the basic concepts.

 

Creating the Code

Before peeking at the code, we must have an idea of what we are supposed to do and how. Take a look at the above diagram. The diagram tries to illustrate the overall architecture and flow of the program to be discussed. This initially looks a little arcane, but as we go along the article, every line in this diagram will be as easy as anything you can think of.

在看代码之前我们必须考虑好我们要做什么和怎么做。看一下最上面的图表。它一开始看起来有些神秘,但图表中的每一行都和你可以想象的一样容易。

Let's start with the blue ASP.NET Cluster. The user uses a web user control to submit message(s) into the messaging queue. Instead of posting a simple message, we post a custom RequestMessage object into the queue. This object is maintained in a separate assembly - Assembly 1. So the web user control submit button actually does two things:

让我们从蓝色的ASP.NET那一块开始。用户使用web控件提交消息到消息队列中。我们使用提交一个自定义的REQUESTMESSAGE 对象插入队列来替代简单的提交一个消息。这个对象处于一个独立的程序集中 -assembly 1。

所以web 用户控件提交按钮可以做两件事:

  1. Creates the RequestMessage object and hydrates it with appropriate data from the user inputs.
  2. Submits the RequestMessage object to the Message queue using appropriate formatter. The web user thereby rests in peace.

1.创建一个RequestMessage 对象并根据用户的输入插入适当的数据。

2.提交RequestMessage对像给消息对列,使用适当的格式化器。web用户就可以安静的休息。

Collapse Copy Code
//the web user control sample code
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Messaging;
using System.ServiceProcess;
//refer the assembly1 containing the requestmessage object
using Assembly1;
/*other functions and code goes here.
we will concentrate on our specific task */
........
.......
......
private void btnPrint_Click(object sender, System.EventArgs e)
{
SendPrintRequestToMessageQueue();
}
private void SendPrintRequestToMessageQueue()
{
try
{
//pick up the queuename from the web.config file
        string queueName =
ConfigurationSettings.AppSettings["PrintChecksMessageQueue"];
MessageQueue destMsgQ = new MessageQueue(queueName);
/*instantiate the message request object with correct parameter
in this case we can assume that the parameter
comes from a drop downlist selection*/
PrintCheckRequestMessage objMessage = new
PrintCheckRequestMessage(ddlBatchRun.SelectedItem.Text.Trim());
//Create a new message object
        Message destMsg = new Message();
/*In case of unsuccessful attempts to post messages
, send them to dead letter queue for audit traceability*/
destMsg.UseDeadLetterQueue = true;
/*Use binary formatter for posting the request message
object into the queue*/
destMsg.Formatter = new System.Messaging.BinaryMessageFormatter();
//Assign the request message object to the message body
        destMsg.Body = objMessage;
//Post the message
        destMsgQ.Send(destMsg);
}
catch(System.Exception ex)
{
//display the error message somewhere (on a label may be)
    }
}

On the other hand, we have a pink Windows Service Cluster written in C# for communicating with the MSMQ. So there are two different programs communicating with the MSMQ to facilitate the asynchronous communication. The provider, i.e., the Web application posting messages to the queue, and the consumer, i.e., the Windows service which is receiving messages from the queue by polling it at regular intervals. Now, let's talk about the Windows service. I assume that you have atleast a basic familiarity of writing Windows services in .NET. Also assuming that the following code will not be difficult to understand. At a pre-specified time interval, the Windows Service calls the Message Queue and 'receives' one message at a time from the queue, unwraps the message using the formatter with which it was wrapped and posted by the ASP.NET program, rehydrates an instance of the RequestMessage object, and uses that object to do some meaningful database activity that will solve the client's business requirements. If that sounds fun, then follow on.. check out the PickupFromMSMQ function called from the timer elapsed event handler function.

Collapse Copy Code
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Messaging;
//refer the assembly1 containing the requestmessage object
using Assembly1;
//refer the assembly2 containing the sqlprovider object
using Assembly2;
namespace windowsMQPollService
{
public class windowsMQPollService : System.ServiceProcess.ServiceBase
{
// a timer object for polling MSMQ at regular intervals
        private System.Timers.Timer timerPolling;
private bool isTimerStarted;
private System.Collections.Hashtable htConfigData;
private string m_StrQueuePath=string.Empty;
create the requestmessage object
public  PrintCheckRequestMessage objCheckMsgRequest =
new PrintCheckRequestMessage(string.Empty);
private System.ComponentModel.Container components = null;
private System.Diagnostics.EventLog exceptionEventLog;
//create the SqlProvider
        private SqlDataProvider checkProvider = new SqlDataProvider();
public windowsMQPollService()
{
// This call is required by the Windows.Forms Component Designer.
            InitializeComponent();
if (!System.Diagnostics.EventLog.SourceExists(this.ServiceName))
{
System.Diagnostics.EventLog.CreateEventSource(this.ServiceName,
"Application");
}
exceptionEventLog.Source = this.ServiceName;
exceptionEventLog.Log = "Application";
}
private void InitializeComponent()
{
this.exceptionEventLog = new System.Diagnostics.EventLog();
((System.ComponentModel.ISupportInitialize)
(this.exceptionEventLog)).BeginInit();
this.ServiceName = "windowsMQPollService";
((System.ComponentModel.ISupportInitialize)
(this.exceptionEventLog)).EndInit();
}
//Set things in motion so your service can do its work.
        protected override void OnStart(string[] args)
{
//Read config file entries for the Windows service
            //into a hashtable for faster retrieval
            GetConfigurationData();
/*check whether the config file had
correct entries or there were some
errors while populating the hash*/
if (htConfigData["Service.MSMQPath"] == null)
{
exceptionEventLog.WriteEntry("Failed to read" +
" the config file information.",
System.Diagnostics.EventLogEntryType.Error);
}
int iPollingInterval = 60;
if (htConfigData["Service.PollingInterval"] == null)
{
exceptionEventLog.WriteEntry("Polling Interval not specified." +
" The service is starting assuming " +
"polling interval to be 60 minutes.",
System.Diagnostics.EventLogEntryType.Warning);
}
else
{
try
{
//get the configured polling interval
                    iPollingInterval =
int.Parse((string)htConfigData["Service.PollingInterval"]);
}
catch
{
exceptionEventLog.WriteEntry("Not a valid value" +
" specified for Polling Interval. The service is starting" +
" assuming polling interval to be 60 minutes.",
System.Diagnostics.EventLogEntryType.Warning);
}
}
//Create timer of interval iPollingInterval.
            timerPolling = new System.Timers.Timer();
timerPolling.Elapsed += new
System.Timers.ElapsedEventHandler(OnTimer);
timerPolling.Interval = (double)(iPollingInterval * 60 * 10);
timerPolling.Enabled = true;
isTimerStarted=true;
//start the polling activity of the Windows service

timerPolling.Start();
}
private void OnTimer(object source, System.Timers.ElapsedEventArgs e)
{
if(isTimerStarted)
{
timerPolling.Stop();
//At appropriate intervals get messages from the queue
                PickupFromMSMQ();
timerPolling.Start();
}
}
private bool PickupFromMSMQ()
{
//read the queuepath from the hashtable
            m_StrQueuePath = htConfigData["Service.MSMQPath"].ToString();
string formattedMessage = string.Empty;
try
{
System.Messaging.MessageQueue mqRecvQueue =
new System.Messaging.MessageQueue(m_StrQueuePath);
//use binary formater for receiving messages from the queue
                mqRecvQueue.Formatter = new
System.Messaging.BinaryMessageFormatter();
DataSet allChecksDS;
//receive the message from the queue
                System.Messaging.Message msgSrcMessage = mqRecvQueue.Receive();
//recreate the requestmessage object
                objCheckMsgRequest =
(PrintCheckRequestMessage)msgSrcMessage.Body;
string strBatchNumber = objCheckMsgRequest.AuditId;
//use the dataprovider to call functions to get DataSet
                allChecksDS =
checkProvider.GetDataSetByBatchNumber(strBatchNumber);
/*do your own stuff with the DataSet.
For us it was calling another assembly
to print uncountable crytsal reports*/
....................
....................
return true;
}
catch(Exception ex)
{
//write exception message into the eventlog
                exceptionEventLog.WriteEntry("Error while reading from Queue :" +
ex.Message +"--"+
ex.StackTrace,System.Diagnostics.EventLogEntryType.Error );
return false;
}
}
private void GetConfigurationData()
{
try
{
htConfigData = new System.Collections.Hashtable();
System.Collections.Specialized.NameValueCollection colNameVal;
colNameVal =
System.Configuration.ConfigurationSettings.AppSettings;
foreach(string sKey in colNameVal.AllKeys)
{
htConfigData.Add(sKey,colNameVal[sKey]);
}
}
catch(System.Configuration.ConfigurationException e)
{
exceptionEventLog.WriteEntry("The configuration file is missing." +
" Could not start the service",
System.Diagnostics.EventLogEntryType.Error);
throw(new Exception("Service Startup Error : " +e.Message));
}
}
}
}
//some amount of code have been intentionally not mentioned

Note:

  • Both the Windows service and the ASP.NET application must understand the MSMQ and the RequestMessage object. So if you look at the diagram, we have placed the Assembly 1 containing the RequestMessage class and the MSMQ instance at the center of both the clusters (Blue ASP.NET Cluster and Pink Windows Service Cluster) to give you an idea of how the plausible deployment scenario can be. Assembly 1 is shared by both the ASP.NET application and the Windows Service; System.Messaging namespace is used by both the ASP.NET application and the Windows Service for accessing MSMSQ.
  • The SqlProvider assembly, in our case, is only used by the Windows Service and not by the Web application, so we have the Assembly 2 only in the Pink cluster.
  • If the SqlDataProvider component makes use of any configuration file (say for storing DB connection information), then that file must be a available in the \bin folder of the Windows service along with its own app.config file, or else the service could not be run successfully.
  • The Windows Service could make use of other assemblies to do other stuff. Like in our programs, we called another assembly with the DataSet values retrieved that contained code for printing out uncountable number of Crystal reports to a pre-specified printer.
  • The queue name must be parameterized in both the Web application and the Windows service, and we should use config files for that. In case of the provider web application, use web.config file, and for the Windows Service, use app.config file. Below are examples of sample entries in the config files.
Collapse Copy Code
    //Web.config entry
<appSettings>
<add key="PrintChecksMessageQueue"
value="name_of_the_machine_running_msmq\private$\Queue_Name"/>
</appSettings>
//Windows Service App.config entry
<appSettings>
<add key="Service.MSMQPath"
value="name_of_the_machine_running_msmq\private$\Queue_Name"/>
<add key="Service.PollingInterval" value="10"/>
</appSettings>

Points of Interest

Programming with ASP.NET, MSMQ, Windows Service using different assemblies can be fun and seemingly easy at a glance, but depending on the business requirements and the type of deployment scenario, things can be quite complicated. Gaining an idea of the various messaging models possible is a healthy way to start off asynchronous messaging programming. Do check those links given above for detailed understanding.

posted @ 2008-12-02 11:28 songjie 阅读(347) 评论(0) 编辑

原文地址:http://www.codeproject.com/KB/ajax/aspnetajaxtips.aspx

1.批量调用不一定经常快

Batch Calls Are Not Always Faster

2.坏的调用会让好的调用超时

Bad Calls Make Good Calls Time Out
解决方法:重写客户端方法,Sys.Net.WebRequestProxy.invoke
Sys.Net.WebServiceProxy.retryOnFailure =
    function(result, userContext, methodName, retryParams, onFailure)
{
    if( result.get_timedOut() )
    {
        if( typeof retryParams != "undefined" )
        {
            debug.trace("Retry: " + methodName);
            Sys.Net.WebServiceProxy.original_invoke.apply(this, retryParams );
        }
        else
        {
            if( onFailure ) onFailure(result, userContext, methodName);
        }
    }
    else
    {
        if( onFailure ) onFailure(result, userContext, methodName);
    }
}

Sys.Net.WebServiceProxy.original_invoke = Sys.Net.WebServiceProxy.invoke;
Sys.Net.WebServiceProxy.invoke =
    function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, useGet,
        params, onSuccess, onFailure, userContext, timeout)
{  
    var retryParams = [ servicePath, methodName, useGet, params,
        onSuccess, onFailure, userContext, timeout ];
   
    // Call original invoke but with a new onFailure

    // handler which does the auto retry

    var newOnFailure = Function.createDelegate( this,
        function(result, userContext, methodName)
        {
            Sys.Net.WebServiceProxy.retryOnFailure(result, userContext,
                methodName, retryParams, onFailure);
        } );
       
    Sys.Net.WebServiceProxy.original_invoke(servicePath, methodName, useGet,
        params, onSuccess, newOnFailure, userContext, timeout);
}

3.浏览器在同一时间只允许两个调用,不要期望其他的指令
Browsers Allow Two Calls at a Time and Don't Expect any Order
浏览其不会回复当有2个以上的请求在队列中时。
Browsers Do Not Respond when More Than Two Calls Are in Queue
解决方法:建立一个队列,将所有请求封装入一个QueueCall中,
var GlobalCallQueue = {
    _callQueue : [],    // Maintains the list of webmethods to call

    _callInProgress : 0,    // Number of calls currently in progress by browser

    _maxConcurrentCall : 2, // Max number of calls to execute at a time

    _delayBetweenCalls : 50, // Delay between execution of calls

    call : function(servicePath, methodName, useGet,
        params, onSuccess, onFailure, userContext, timeout)
    {
        var queuedCall = new QueuedCall(servicePath, methodName, useGet,
            params, onSuccess, onFailure, userContext, timeout);

        Array.add(GlobalCallQueue._callQueue,queuedCall);
        GlobalCallQueue.run();
    },
    run : function()
    {
        /// Execute a call from the call queue

       
        if( 0 == GlobalCallQueue._callQueue.length ) return;
        if( GlobalCallQueue._callInProgress <
            GlobalCallQueue._maxConcurrentCall )
        {
            GlobalCallQueue._callInProgress ++;
            // Get the first call queued

            var queuedCall = GlobalCallQueue._callQueue[0];
            Array.removeAt( GlobalCallQueue._callQueue, 0 );
           
            // Call the web method

            queuedCall.execute();
        }
        else
        {
            // cannot run another call. Maximum concurrent

            // webservice method call in progress

        }           
    },
    callComplete : function()
    {
        GlobalCallQueue._callInProgress --;
        GlobalCallQueue.run();
    }
};

QueuedCall = function( servicePath, methodName, useGet, params,
    onSuccess, onFailure, userContext, timeout )
{
    this._servicePath = servicePath;
    this._methodName = methodName;
    this._useGet = useGet;
    this._params = params;
   
    this._onSuccess = onSuccess;
    this._onFailure = onFailure;
    this._userContext = userContext;
    this._timeout = timeout;
}

QueuedCall.prototype =
{
    execute : function()
    {
        Sys.Net.WebServiceProxy.original_invoke(
            this._servicePath, this._methodName, this._useGet, this._params, 
            Function.createDelegate(this, this.onSuccess), // Handle call complete

            Function.createDelegate(this, this.onFailure), // Handle call complete

            this._userContext, this._timeout );
    },
    onSuccess : function(result, userContext, methodName)
    {
        this._onSuccess(result, userContext, methodName);
        GlobalCallQueue.callComplete();           
    },       
    onFailure : function(result, userContext, methodName)
    {
        this._onFailure(result, userContext, methodName);
        GlobalCallQueue.callComplete();           
    }       
};


4.在浏览器中缓存WEB SERVICE的响应。明显的节省带宽
Caching Web Service Response on the Browser and Saving Bandwidth Significantly
解决方法:
(1)修改HTTP Response headers ,在方法中添加[ScriptMethod(UseHttpGet=true)] attributes
(2)HttpHeader 中_maxage不能直接修改,用反射方法。
(3)要在客户端使用放射方法必须在WEB.CONFIG中申明<trust Level="full">
[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet2()
{
    TimeSpan cacheDuration = TimeSpan.FromMinutes(1);

    FieldInfo maxAge = Context.Response.Cache.GetType().GetField("_maxAge",
        BindingFlags.Instance|BindingFlags.NonPublic);
    maxAge.SetValue(Context.Response.Cache, cacheDuration);

    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
    Context.Response.Cache.AppendCacheExtension(
            "must-revalidate, proxy-revalidate");

    return DateTime.Now.ToString();
}

5.什么时候this不是真正的this.
我们要知道不论何时javascript触发,this指代引发此事件的html元素。
we know that whenever JavaScript events are raised, this refers to the HTML element that produced the event.

6.http get方法比http post方法要快,但是asp.net ajax 中默认的是 post方法。

posted @ 2008-05-08 08:14 songjie 阅读(104) 评论(0) 编辑
1.用编程的方法更新UPDATAPANEL
使用updatapanel的RegisterAsyncPostBackControl(control)将控件注册为一个异步提交的控件。
调用updatapanel的Updata()方法。

2.嵌套使用UPDATAPANEL
当外层的UPDATAPANEL刷新时内层的PANEL 也会刷新,
内层的UPDATAPANEL刷新时,外层的PANEL不会刷新。

3.在一个页面中使用多个UPDATAPANEL
当updatapanel的UpdataMode设置为Always时,页面中一个 updatapanel被触发更新是,页面中所有updatapanel都会刷新,所以UpdataMode一般设置为Condtional..
posted @ 2008-03-15 11:32 songjie 阅读(164) 评论(0) 编辑