惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
V
Visual Studio Blog
P
Privacy International News Feed
月光博客
月光博客
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
Lohrmann on Cybersecurity
N
News and Events Feed by Topic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
Webroot Blog
Webroot Blog
T
Threatpost
宝玉的分享
宝玉的分享
The Last Watchdog
The Last Watchdog
小众软件
小众软件
L
LINUX DO - 最新话题
C
Cisco Blogs
T
Troy Hunt's Blog
Schneier on Security
Schneier on Security
酷 壳 – CoolShell
酷 壳 – CoolShell
www.infosecurity-magazine.com
www.infosecurity-magazine.com
雷峰网
雷峰网
G
GRAHAM CLULEY
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
博客园 - 叶小钗
罗磊的独立博客
V
V2EX
博客园 - Franky
P
Proofpoint News Feed
SecWiki News
SecWiki News
腾讯CDC
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Jina AI
Jina AI
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
PCI Perspectives
PCI Perspectives
V2EX - 技术
V2EX - 技术
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
aimingoo的专栏
aimingoo的专栏
Cisco Talos Blog
Cisco Talos Blog
N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题

博客园 - Angelo Dell'inferno

[ASP.NET] 基于.Net Remoting 的网站访问监控模块 [ASP.NET] HyperLink + Image 实现动态图片链接 [ASP.NET] (转)地址栏参数的判断 [ASP.NET] 实现Label自动换行 [ASP.NET] 验证码生成 [ASP.NET] 实现客户端浏览服务端目录的页面 [ASP.NET]Treeview 控件显示服务端目录文件夹及文件 [C#] GridView导出到Excel [C#] 获取两个时间点的时间间隔 [SQL Server][转]数据库并发控制——活锁&死锁 [JavaScript] 防止页面被嵌入Iframe [SQL Server] 存储过程事务 [SQL Server] T-SQL 连接数据库方法 [C#] 杀Excel进程 [ASP.NET] (原创)自定义GridView分页 [C#] 将DataSet内容导入到Excel (矩阵区域导出) [ASP.NET] 服务器端下载文件实现 [Oracle] 日期相关操作 [ORACLE] 函数大全
[转]Run a Http Response Filter together with an Ajax Update Panel
Angelo Dell'inferno · 2009-02-17 · via 博客园 - Angelo Dell'inferno

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