[转]Run a Http Response Filter together with an Ajax Update Panel

quote:

Erik Lenaerts
(http://www.dotnet6.com/blogs/erik_lenaerts/archive/2007/07/21/run-a-http-response-filter-together-with-an-ajax-update-panel.aspx)


Run a Http Response Filter together with an Ajax Update Panel

This post continues from part one. In this previous post we created a Http Response Filter that translated content based on a Token replacement technique.

In this article we'll discuss how to apply this technique when you are using an Ajax Update Panel. The problem with the update panel is that the XmlHttp request made to the server is responded with partial html fragments. Nothing wrong with that in essence, however, our Http Response Filter from part one requires a full html document, more exactly it looks for the end html tag (</html>) to know when all of the content has been received. 

 

Ajax update panel Specifics

Requests made by Ajax for the Updatepanel are a bit different than regular requests more exactly:

  • They are of content-type "text/plain"
  • They are requested with a Request Header "x-microsoftajax"

Also the responses server by the Ajax server side code are different:

  • They are not wrapped in <html> tags because they are partial html fragments
  • They contain a 'special header' (read here and here for very useful info).
  • They contain two parts, one part with the content and another one containing things like the viewstate .

Mmh, so if we don't have any end html tag, how will we know when we have received all of our content? Well the answer lies in the "special header" which looks like :

Header: Length + ‘|’ + type + ‘|’ + id + ‘|’

Body:     html content 

Footer: ‘|’

The first part in this header contains the length of the body so that's really interesting. As you might recall from the previous part, ASP.Net serves our data in chunks of +-28K by calling the write method several times. So, we'll keep on collecting the data until we have received as much as bytes as specified in the length.

If you intercept the response of an Update panel by using for example Fiddler, you'll notice that besides the content there's also a second part that contains data like the viewstate. This is surely not something you want to translate. So, our goal is to only process part one "the content".

So, after all of the content has been collected, we can translate the tokens. However, when changing the content, the length of it will change as well. So, we need to recalculate the new length and set it accordingly into the special header.

 

The filter

Knowing all of this, we can start by creating our filter.

 

    /// <summary>
    /// The <c>AjaxTranslationFilter</c> class  
    /// </summary>
    
public class AjaxTranslationFilter HttpFilter
    { 
        private StringBuilder _responseHtml;
        
private int _contentLength 0;
        
private bool _partOne = true

        
/// <summary>
        /// Initializes a new instance of the <see cref="AjaxTranslationFilter"/> class.
        /// </summary>
        /// <param name="stream">The stream on which the filter will work.</param>
        
public AjaxTranslationFilter(Stream stream)
            : 
base(stream)
        
{
        } 

        /// <summary>
        /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances 
        /// the current position within this stream by the number of bytes written.
        /// </summary>
        /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
        /// <param name="count">The number of bytes to be written to the current stream.</param> 
        
public override void Write(byte[] bufferint offsetint count)
        
{
            
string newContent = null;

            
// Note that this method is potentially called several times by ASP.NET
            // The buffer is not written at once, but depending on the size, in blocks of 27~29 Kbytes
            //
            // Since this is an Ajax call, we'll find the total number of bytes in the 'special' Ajax header
            //
            // Info
            // * http://weblogs.asp.net/leftslipper/archive/2007/02/26/sys-webforms-pagerequestmanagerparsererrorexception-what-it-is-and-how-to-avoid-it.aspx
            // * http://www.manuelabadia.com/blog/SyndicationService.asmx/GetRssCategory?categoryName=Ajax 

            // get buffer content
            
string strBuffer System.Text.UTF8Encoding.UTF8.GetString(bufferoffsetcount);

            
// This filter is called in two parts but only the first part contains content that could be translated
            
if (_partOne)
            
{
                
// determine the content length during first run
                
if (_contentLength == 0)
                
{
                    
// Check for a valid Ajax header
                    
Regex regEx = new Regex(@"^(?<length>\d+)\|[^\|]*\|[^\|]*\|"RegexOptions.Singleline);
                    
Match m regEx.Match(strBuffer);
                    
if (m.Success)
                    
{
                        
// Read the length  
                        Group group 
m.Groups["length"];
                        
_contentLength Convert.ToInt32(group.Value);

                        
// initialise the StringBuilder (we  assume that translations increase
                        // the size by 20%
                        
_responseHtml = new StringBuilder((int)(_contentLength 1.2));

                    
}
                    
else
                        throw new 
SystemException("Unable to parse content length from Ajax header");
                
}

                
// Add buffer to total buffer
                
_responseHtml.Append(strBuffer);

                
// Is all data received?
                
if (_responseHtml.Length >= _contentLength)
                
{
                    
//we have received all the data by now, so we can translate the content 
                 

                    
string ajaxContent _responseHtml.ToString();

                    
// Translate the tokens in the html 
                    
string translatedContent TranslateContent(ajaxContent);

                    
// Calculate new content length 
                    
int newContentLength translatedContent.Length - (_responseHtml.Length _contentLength);

                    // Set new content length  
                    Regex regex2 = new Regex(@"^(?<length>\d+)(?<rest>\|[^\|]*\|[^\|]*\|)"RegexOptions.Singleline);
                   
newContent regEx2.Replace(translatedContentnewContentLength "${rest}"); 

                    
if (translatedContent != null)
                    
{
                        
byte[] data System.Text.UTF8Encoding.UTF8.GetBytes(newContent);

                        
// Write to the stream
                        
BaseStream.Write(data0data.Length);
                    
}

                    _partOne 
= false;
                
}
            }
            
else
            
{
                
// After the first part has been processed, just forward the other content to the browser.
                // this can also occur in multiple times if this 'rest'-data is totally larger than +-28K
                
BaseStream.Write(bufferoffsetcount);
            
}

        } 
    }

 

When to register the Filter

We know that the content contains 2 parts. The first part where the actual content is provided and in the second part stuff like viewstate and client side event binding is produced. As mentioned, for our translations, we are only interested in the first part.

If we look at the points in time when these two parts are rendered by Ajax then we can see the following order:

PreRequestHandlerExecute
- - Part One
PostRequestHandlerExecute
ReleaseRequestState
- - Part two

So I figured, I just unregister the filter on the PostRequestHandlerExecute event, but I didn't found any way to do that. Because of this, I had to deal with the concept of part one and two in my filter code.

As opposed to HtmlTranslationFilter of my previous article we can't register the Filter on the ReleaseRequestState, since it would be too late to receive Part One. However when we register our filter before the Page/Control Request Handler (as explained here), then we don't know our content type and we can't check for "text/plain" content types. Too solve this dilemma, we check to register our AjaxTranslationFilter based on the Header=x-microsoftajax. Since the header is part of the request we can register our filter at any point in time before the PreRequestHandlerExecute in our case the BeginRequest:

    /// <summary>
    /// Handles the BeginRequest event of the httpApplication.
    /// </summary> 
    
private void httpApplication_BeginRequest(object senderEventArgs e)
    
{
        
// Check if we need an AjaxTranslationFilter.
        // Note that the check requires an Request header, therefore we can register this filter early
        // if we would register it too late then we would miss out 
        // some data that is handled by the Ajax ScriptModule HttpModule
        
if (!string.IsNullOrEmpty(_context.Request.Headers["x-microsoftajax"]))
        
{
            
// Create a new filter and insert it onto the page
            // This filter will later on, translate all content
            
_context.Response.Filter = new AjaxTranslationFilter(_context.Response.Filter); 
        
    }

To execution chain is then as this:

BeginRequest
- - Ajax Filter Registered
PreRequestHandlerExecute
- - Ajax Filter - Part One
PostRequestHandlerExecute
ReleaseRequestState
- - Ajax Filter - Part two

- Enjoy

posted @ 2009-02-17 17:34  Angelo Dell'inferno  阅读(1366)  评论(1编辑  收藏  举报