• 让UpdatePanel支持文件上传(2):服务器端组件 .


    我们现在来关注服务器端的组件。目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查找特定的项,但是我们在向IFrame中POST数据时无法修改Header。所以我们必须使用一个方法来“欺骗”ScriptManager。

      目前使用的解决方案是,我们在POST数据之前在页面中隐藏的输入元素(<input type="hidden" />)中放入一个特定的标记,然后我们开发的服务器端组件(我把它叫做AjaxFileUplaodHelper)会在它的Init阶段(OnInit方法)中在Request Body中检查这个标记,然后使用反射来告诉ScriptManager目前的请求为一个异步请求。

      但是事情并不像我们想象的那么简单,让我们在写代码之前来看一个方法:

    PageRequestManager.OnInit
    internal sealed class PageRequestManager
    {
        // ...
    
        internal void OnInit()
        {
            if (_owner.EnablePartialRendering && !_owner._supportsPartialRenderingSetByUser)
            {
                IHttpBrowserCapabilities browser = _owner.IPage.Request.Browser;
                bool supportsPartialRendering =
                    (browser.W3CDomVersion >= MinimumW3CDomVersion) &&
                    (browser.EcmaScriptVersion >= MinimumEcmaScriptVersion) &&
                    browser.SupportsCallback;
    
                if (supportsPartialRendering)
                {
                    supportsPartialRendering = !EnableLegacyRendering;
                }
                _owner.SupportsPartialRendering = supportsPartialRendering;
            }
    
            if (_owner.IsInAsyncPostBack)
            {
                _owner.IPage.Error += OnPageError;
            }
        }
    
        // ...
    }


      上面这段代码会在ScriptManager的OnInit方法中被调用。请注意红色部分的代码,“_owner”变量是当前页面上的ScriptManager。在页面受到一个真正的异步会送之后,PageRequestManager会响应页面的Error事件,并且将错误信息用它定义的格式输出。如果我们只是修改了ScriptManager的私有field,那么如果在异步回送时出现了一个未捕获的异常,那么页面就会输出客户端未知的内容,导致在客户端解析失败。所以我们必须保证这种情况下的输出和真正的异步回送是相同的,所以我们就可以使用以下的做法来解决错误处理的问题。

    代码实现
    internal static class AjaxFileUploadUtility
    {
        internal static bool IsInIFrameAsyncPostBack(NameValueCollection requestBody)
        { 
            string[] values = requestBody.GetValues("__AjaxFileUploading__");
    
            if (values == null) return false;
    
            foreach (string value in values)
            {
                if (value == "__IsInAjaxFileUploading__")
                {
                    return true;
                }
            }
    
            return false;
        }
    
        // ...
    }
    
    [PersistChildren(false)]
    [ParseChildren(true)]
    [NonVisualControl]
    public class AjaxFileUploadHelper : Control
    {
        // ScriptManager members;
        private static FieldInfo isInAsyncPostBackFieldInfo;
        private static PropertyInfo pageRequestManagerPropertyInfo;
    
        // PageRequestManager members;
        private static MethodInfo onPageErrorMethodInfo;
        private static MethodInfo renderPageCallbackMethodInfo;
    
    
        static AjaxFileUploadHelper()
        {
            Type scriptManagerType = typeof(ScriptManager);
            isInAsyncPostBackFieldInfo = scriptManagerType.GetField(
                "_isInAsyncPostBack",
                BindingFlags.Instance | BindingFlags.NonPublic);
            pageRequestManagerPropertyInfo = scriptManagerType.GetProperty(
                "PageRequestManager",
                BindingFlags.Instance | BindingFlags.NonPublic);
    
            Assembly assembly = scriptManagerType.Assembly;
            Type pageRequestManagerType = assembly.GetType("System.Web.UI.PageRequestManager");
            onPageErrorMethodInfo = pageRequestManagerType.GetMethod(
                "OnPageError", BindingFlags.Instance | BindingFlags.NonPublic);
            renderPageCallbackMethodInfo = pageRequestManagerType.GetMethod(
                "RenderPageCallback", BindingFlags.Instance | BindingFlags.NonPublic);
        }
    
        public static AjaxFileUploadHelper GetCurrent(Page page)
        {
            return page.Items[typeof(AjaxFileUploadHelper)] as AjaxFileUploadHelper;
        }
    
        private bool isInAjaxUploading = false;
    
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
    
            if (this.Page.Items.Contains(typeof(AjaxFileUploadHelper)))
            {
                throw new InvalidOperationException("One AjaxFileUploadHelper per page.");
            }
    
            this.Page.Items[typeof(AjaxFileUploadHelper)] = this;
    
            this.EnsureIsInAjaxFileUploading();
        }
    
        private void EnsureIsInAjaxFileUploading()
        {
            this.isInAjaxUploading = 
    AjaxFileUploadUtility.IsInIFrameAsyncPostBack(this.Page.Request.Params); if (this.isInAjaxUploading) { isInAsyncPostBackFieldInfo.SetValue( ScriptManager.GetCurrent(this.Page), true); this.Page.Error += new EventHandler(Page_Error); } } private void Page_Error(object sender, EventArgs e) { // ... } private object _PageRequestManager; private object PageRequestManager { get { if (this._PageRequestManager == null) { this._PageRequestManager = pageRequestManagerPropertyInfo.GetValue( ScriptManager.GetCurrent(this.Page), null); } return this._PageRequestManager; } } // ... }


      这段实现并不复杂。如果Request Body中的“__AjaxFileUploading__”的值为“__IsInAjaxFileUploading__”,我们就会使用反射修改ScirptManager控件中的私有变量“_isInAsyncPostBack”。此后,我们使用了自己定义的Page_Error方法来监听页面的Error事件,当页面的Error事件被触发时,我们定义的新方法就会将能够正确解析的内容发送给客户端端。

      自然,AjaxFileUploadHelper也需要将程序集中内嵌的脚本文件注册到页面中。我为组件添加了一个开关,可以让用户开发人员使用编程的方式来打开/关闭对于AJAX文件上传的支持。这部分实现更为简单:

    注册脚本文件
    public bool SupportAjaxUpload
    {
        get { return _SupportAjaxUpload; }
        set { _SupportAjaxUpload = value; }
    }
    
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
    
        if (this.isInAjaxUploading)
        {
            this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback));
        }
    
        if (this.Page.IsPostBack || !this.SupportAjaxUpload) return;
    
        if (!ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack)
        {
            ScriptReference script = new ScriptReference(
                "Jeffz.Web.AjaxFileUploadHelper.js", this.GetType().Assembly.FullName);
            ScriptManager.GetCurrent(this.Page).Scripts.Add(script);
        }
    }


      如果用户希望关闭对于AJAX文件上传的支持,他可以使用下面的代码将页面上AjaxFileUploadHelper控件的SupportAjaxUpload属性关闭:

    关闭AJAX上传支持
    AjaxFileUploadHelper.GetCurrent(this.Page).SupportAjaxUpload = false;


      等一下,这是什么?我是指在“OnPreRender”方法中的代码:

    截获输出方式
    if (this.isInAjaxUploading)
    {
        this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback));
    }


      解释如下:在ScirptManager的“OnPreRender”方法执行时,页面的Render方法会被服务器端PageRequestManager类的RenderPageCallback方法替代。上面代码的作用是在“我们的”异步回送时,再次使用我们定义的方法来替换页面的Render方法。请注意之前的Page_Error方法也是我们重新定义的方法,当异步回送时遇到了未捕获的异常时会使用它来输出,请注意下面的代码:

    自定义的输出方法
    private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
    {
        AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true);
    
        StringBuilder sb = new StringBuilder();
        HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb));
        renderPageCallbackMethodInfo.Invoke(
    this.PageRequestManager, new object[] { innerWriter, pageControl }); writer.Write(sb.Replace("*/", "*//*").ToString()); AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false); } private void Page_Error(object sender, EventArgs e) { AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true); onPageErrorMethodInfo.Invoke(this.PageRequestManager, new object[] { sender, e }); AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false); }


      究竟什么是“AjaxFileUploadUtility.WriteScriptBlock”方法呢?我们为什么要这样写?其实这么做的目的是为了兼容各种浏览器,使它们都能够正确通过iframe正确收到服务器端获得的信息。这可以说是整个项目中最有技巧的部分了,我将会使用一个部分来单独讲一下这部分的机制。

  • 相关阅读:
    解决运行vue项目的报错This relative module was not found:
    Iterator 迭代器
    Strategy 策略模式
    Observer 观察者
    工厂模式总结(简单工厂,工厂方法,抽象工厂)
    Abstract Factory 抽象工厂
    Factroy 简单工厂
    Singleton 多线程
    Singleton 单例模式
    设计模式总结
  • 原文地址:https://www.cnblogs.com/zhycyq/p/3291217.html
Copyright © 2020-2023  润新知