使用jquery $ .download处理可能非常大的表单创建/发布的最佳方法

时间:2011-05-12 15:35:09

标签: jquery asp.net-mvc performance internet-explorer download

我的流程如下:

  1. 用户执行ajaxly的复杂搜索,返回一堆id(可能是1,可能是10000)

  2. 一旦他们有了用户,他们就可以选择一些内容,然后下载一个文件(这是一个基于ID的报告,以及他们选择的内容)

  3. 为实现此目的,我使用了$.download

    的高度修改版本

    见到这里:

    jQuery.download = function (url, data, method, loadingHolderDivId) {
    if (url && typeof data == 'object') {
        //for this version, data needs to be a json object.  
        //loop through the data object..
        $('#' + loadingHolderDivId).html($('#LoadingScreen').html());
    
        var theForm = $('<form></form>').attr('action', url).attr('method', method).attr('id', 'jqueryDownloadForm').attr('target', 'iframeX');
    
        $.each(data, function (propertyName, propertyVal) {
            if (propertyVal != null) {
                if (typeof propertyVal == 'object') {
    
                    //HANDLE ARRAYS!
                    for (var i = 0, len = propertyVal.length; i < len; ++i) {
                        theForm.append($("<input />").attr('type', 'hidden').attr('id', propertyName + i.toString).attr('name', propertyName).val(propertyVal[i]));
                    }
                }
                else {
                    theForm.append($("<input />").attr('type', 'hidden').attr('id', propertyName).attr('name', propertyName).val(propertyVal));
                }
            }
        });
    
    
        var iframeX;
        var downloadInterval;
    
        // remove old iframe if has
        $("#iframeX").remove();
        // create new iframe
        iframeX = $('<iframe src="javascript:false;" name="iframeX" id="iframeX"></iframe>').appendTo('body').hide();
    
        if ($.browser.msie) {
            downloadInterval = setInterval(function () {
                // if loading then readyState is “loading” else readyState is “interactive”
                if (iframeX && iframeX[0].readyState !== "loading") {
                    $('#' + loadingHolderDivId).empty();
                    clearInterval(downloadInterval);
                }
            }, 23);
        }
        else {
            iframeX.load(function () {
                $('#' + loadingHolderDivId).empty();
            });
        }
    
    
        theForm.appendTo('body').trigger('submit').remove();
        return false;
    }
    else {
        //they didn't fill in the params.  do nothing
    }
    
    
    };
    

    基本上做的是解析数据中的内容,并从中构建表单。当没有很多ID时,这很有效。但是当有8000时,它在IE中需要5或10秒,真的不足为奇,它很清楚IE很糟糕的操作。

    另一个问题是,在IE中。 $('#' + loadingHolderDivId).html($('#LoadingScreen').html());在构建表单之后才会实际发生。我猜这是因为它需要一秒钟才能做到这一点,在它完成之前它已经忙于构建表单了。

    我以这种方式构建表单的原因是默认模型绑定器会很高兴并将我的表单绑定到一个可爱的模型中。 id列表绑定到ilist(整数)

    以下是控制器操作的示例:

    Function ExportUsers(ByVal model As ExportUsersPostModel) As ActionResult
    

    以下是模型的示例:

    <Serializable()> _
    Public Class ExportUsersPostModel
        Public Property FilterUserIds As IList(Of Integer) = New List(Of Integer)
        Public Property FilterColumnIds As IList(Of Integer) = New List(Of Integer)
        public property ShowThis as boolean
        public property OtherStuff as string = string.empty
        Public Property FormatId As Integer
    End Class
    

    所以实际问题有两个方面:

    1. 如何制作“加载”讯息 在它开始之前出现 非常缓慢的死亡形式

    2. 我如何加快表单构建速度,或者以不会慢的方式构建表单,但这仍然可以保持模型绑定器的快乐?

    3. 先谢谢!

2 个答案:

答案 0 :(得分:2)

如果您能够将模型作为JSON传递,则可以创建自定义ModelBinder来处理将JSON映射到数据结构的过程。我最近做了一个无法自动映射的对象类型。 Json.Net提供了一个名为JObject的类,它接受一个JSON字符串并将其映射到动态C#对象。然后,您可以将动态对象映射到强类型对象。

要创建自定义ModelBinder,只需创建一个继承自IModelBinder的类并实现BindModel方法。这是我的实现的副本。你的显然会略有不同:

internal class FilterBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
        if (bindingContext == null)
            throw new ArgumentNullException("bindingContext");

        if ((controllerContext.HttpContext.Request.Form.Count > 1 || (controllerContext.HttpContext.Request.Form.Count == 1 && !string.IsNullOrWhiteSpace(controllerContext.HttpContext.Request.Form.AllKeys[0]))) || (controllerContext.HttpContext.Request.QueryString.Count > 1 || (controllerContext.HttpContext.Request.QueryString.Count == 1 && !string.IsNullOrWhiteSpace(controllerContext.HttpContext.Request.QueryString.AllKeys[0]))))
        {
            ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            string value = val == null || string.IsNullOrEmpty(val.AttemptedValue) ? string.Empty : val.AttemptedValue;
            if (string.IsNullOrEmpty(value)) return null;
            dynamic obj = JObject.Parse(value);
            return new FilterSet(obj);
        }
        else
            return null;
    }
}

我有一堆检查,以确保我得到的是有效的,你可能需要或不需要。然后,在获得JObject之后,我将它传递给执行映射的构造函数。

答案 1 :(得分:0)

好吧,也许只是我,但iFrame +尝试ajax文件下载+每次查询10+秒(可能)= cloog。也许这是我作为一名UI工程师专注于客户看到的东西的年代,但是我有一个真正的问题,就是让某人在我的一个应用程序上等待那么久。必须有一种更好的方法,我认为它可以在UI端进行改进。

简而言之,您需要进行大搜索,深入了解细节,然后转储数据。因此,从用户模式来看,我首先要查看实现每项任务的选项。

搜索很简单....将模式发送到服务器,获得结果。这里没什么了不起的,虽然阿贾克斯可能会很好看。

现在,“深入研究”数据。由于您可能在这里处理大量数据,因此您需要一种方法,允许用户以有条理和有效的方式快速轻松地浏览大量的“东西”。对我来说,这是一个网格尖叫。我的偏好是DataTables。这给你带来的是效率,组织,分页,以及最重要的是用户能够轻松地与数据交互以挑选“东西”的能力。您从步骤1开始的ajax查询将通过ajax "pipelining"填充DataTables。也就是说,它会抓住用户想要看到的内容 - 比如25个结果 - 然后抢先一步并可能稍后加速接口。所有数据都可用,它一次只能抓取一个部分,这样可以加快查询速度。用户可以对界面上的数据进行排序,过滤,排序和限制,使他们更容易highlight to select data

现在,进入第3步,下载可下载的报告。为了使它非常简单,Datatables有一个名为"TableTools"的插件,它会自动吐出你提供给用户的Excel,PDF,Text和Printable版本的数据。这是几行代码和一些图像......大约需要10分钟来配置。瞧,完了。是的,它是 - 那么 - 简单。

至于能够处理那么多记录,是的。我有一个应用程序,通过标准的MySQL数据库(非集群)以这种方式处理250万条记录因为你一次只抓取一片“数据”,所以它永远不需要做任何奇特的,巨大的查询除了记录数。用户可以通过偶尔的偶然延迟来翻阅结果。这真的很美。