首页 > 解决方案 > 并行文件上传 XMLHttpRequest 请求以及为什么它们不起作用

问题描述

我正在尝试使用 XMLHttpRequest 并行上传许多(目前为 3 个)文件。如果有一些代码可以将它们从许多已删除文件的列表中提取出来,并确保我每时每刻都发送 3 个文件(如果可用)。

这是我的代码,据我所知这是标准的:

            var xhr = item._xhr = new XMLHttpRequest();
            var form = new FormData();
            var that = this;

            angular.forEach(item.formData, function(obj) {
                angular.forEach(obj, function(value, key) {
                    form.append(key, value);
                });
            });

            form.append(item.alias, item._file, item.file.name);

            xhr.upload.onprogress = function(event) {
                // ...
            };

            xhr.onload = function() {
                // ...
            };

            xhr.onerror = function() {
                // ...
            };

            xhr.onabort = function() {
                // ...
            };

            xhr.open(item.method, item.url, true);

            xhr.withCredentials = item.withCredentials;

            angular.forEach(item.headers, function(value, name) {
                xhr.setRequestHeader(name, value);
            });

            xhr.send(form);

查看 Opera 开发人员工具中的网络监视器,我发现这有点工作,并且我总是得到 3 个“正在进行中”的文件:

网络流量

但是,如果我查看请求的进展方式,我会看到 3 个上传中的 2 个(这里是看似长时间运行的那些)处于“待处理”状态,并且 3 个请求中只有 1 个真正处于活动状态时间。这也反映在上传时间中,因为这种并行性似乎没有发生时间改进。

我已经在我的代码中放置了控制台日志,看起来这不是我的代码的问题。

我应该知道的并行上传文件是否有任何浏览器限制?据我所知,AJAX 限制的请求数量比我在这里使用的要高得多……向请求中添加文件是否会改变事情?

标签: javascriptajaxbrowserasp.net-mvc-5xmlhttprequest

解决方案


原来是 ASP.NET 导致了这个问题。来自同一个 SessionId 的多个请求会被序列化,因为它们会锁定会话对象。

这里

我的解决方法是让会话对于这个特定的操作是只读的。这样,就不需要锁定。这是我的代码(取自这里的原始代码):

public class CustomControllerFactory : DefaultControllerFactory
{
    protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return SessionStateBehavior.Default;
        }

        var actionName = requestContext.RouteData.Values["action"].ToString();
        MethodInfo actionMethodInfo;
        var methods = controllerType.GetMethods(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        actionMethodInfo = methods.FirstOrDefault(x => x.Name == actionName && x.GetCustomAttribute<ActionSessionStateAttribute>() != null);
        if (actionMethodInfo != null)
        {
            var actionSessionStateAttr = actionMethodInfo.GetCustomAttributes(typeof(ActionSessionStateAttribute), false)
                .OfType<ActionSessionStateAttribute>()
                .FirstOrDefault();

            if (actionSessionStateAttr != null)
            {
                return actionSessionStateAttr.Behavior;
            }
        }
        return base.GetControllerSessionBehavior(requestContext, controllerType);
    }
}


[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ActionSessionStateAttribute : Attribute
{
    public SessionStateBehavior Behavior { get; private set; }
    public ActionSessionStateAttribute(SessionStateBehavior behavior)
    {
        this.Behavior = behavior;
    }
}

// In your Global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
    // .........
    ControllerBuilder.Current.SetControllerFactory(typeof(CustomControllerFactory));
}


// You use it on the controller action like that:
[HttpPost]
[Authorize(Roles = "Administrators")]
[ValidateAntiForgeryToken]
[ActionSessionState(SessionStateBehavior.ReadOnly)]
public async Task<ActionResult> AngularUpload(HttpPostedFileBase file){}

这是光荣的结果: 结果


推荐阅读