ASP.Net异步HTTP文件上传处理程序

4
我正在尝试使用C#制作一个异步的文件上传处理程序,通过AJAX异步请求提供文件上传进度的更新。如果请求是POST,则将一些信息加载到会话中,然后开始上传;如果请求是GET,则返回上传的当前状态(已上传字节数、总字节数等)。我不确定它是否需要成为异步处理程序,但文件可能相当大,所以我认为这样做最好。对于基本的异步处理程序,我使用了与此MSDN文章中处理程序非常相似的内容。下面是我的一些关键代码部分。我的问题是在POST完成之前,我无法收到任何GET信息。值得一提的是,在此示例中,我使用jQuery进行GET请求,使用BlueImp发布文件。

HTML和JavaScript

<input id="somefile" type="file" />

$(function () {
  name = 'MyUniqueId130';
  var int = null;
  $('#somefile').fileupload({
    url: '/fileupload.axd?key='+name,
    done: function (e, data) { clearInterval(int); }
  });

  $('#somefile').ajaxStart(function(){
    int = setInterval(function(){
    $.ajax({
      url: '/fileupload.axd?key='+name,
      dataType: 'json',
      async: true
    })
    .done(function(e1, data1){
      if(!e1.InProgress || e1.Complete || e1.Canceled)
        clearInterval(int);
    });
  }, 10000)});
});

异步处理请求方法只是调用正确的方法,无论是POST还是GET,然后调用CompleteRequest来结束请求。
private static void GetFilesStatuses(HttpContext context)
{
  string key = context.Request.QueryString["key"];
  //A dictionary of <string, UploadStatus> in the session
  var Statuses = GetSessionStore(context);
  UploadStatus ups;

  if (!String.IsNullOrWhiteSpace(key))
  {
    if (Statuses.TryGetValue(key, out ups))
    {
      context.Response.StatusCode = (int)HttpStatusCode.OK;
      context.Response.Write(CreateJson(ups));
    }
    else
    {
      context.Response.StatusCode = (int)HttpStatusCode.NotFound;
    }
  }
  else
  {
    context.Response.StatusCode = (int)HttpStatusCode.OK;
    context.Response.Write(CreateJson(Statuses.Values));
  }
}

private static void UploadFile(HttpContext context)
{
 var Statuses = GetSessionStore(context);
 string key = context.Request.QueryString["key"];

 if (String.IsNullOrWhiteSpace(key))
 {
   context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
   return;
 }

 HttpPostedFile file = context.Request.Files[0];
 string extn = file.FileName.LastIndexOf('.') == -1 ? "" :
    file.FileName.Substring(file.FileName.LastIndexOf('.'), (file.FileName.Length - file.FileName.LastIndexOf('.')));
 string temp = GetTempFileName(path, extn);
 UploadStatus status = new UploadStatus()
 {
   FileName = file.FileName,
   TempFileName = temp,
   Path = path,
   Complete = false,
   Canceled = false,
   InProgress = false,
   Success = true,
   BytesLoaded = 0,
   TotalBytes = file.ContentLength
 };
 Statuses.Add(key, status);
 byte[] buffer = new byte[bufferSize];
 int byteCount = 0;

 using (var fStream = System.IO.File.OpenWrite(context.Request.MapPath(path + temp)))
 {
   uploads.Add(status);

   while ((byteCount = file.InputStream.Read(buffer, 0, bufferSize)) > 0 && !status.Canceled)
   {
     status.InProgress = true;
     status.BytesLoaded += byteCount;
     fStream.Write(buffer, 0, byteCount);
   }

   status.Complete = !status.Canceled;
   status.InProgress = false;
   status.Success = true;

   if (status.Canceled)
   {
     Statuses.Remove(temp);
   }

   context.Response.StatusCode = (int)HttpStatusCode.OK;
 }
}

我尝试了许多方法,例如非异步处理程序、异步处理程序、确保JavaScript以异步方式运行,但现在我认为需要一些不同的视角来解决问题,因此感谢任何人可以提供的帮助。


如果有人遇到这个问题,我创建了一个静态的ConcurrentDictionary,在while循环内部添加了TryUpdate来更新状态。这解决了大部分的问题,但我还改变了javascript,使用BlueImps的beforeSend函数来启动异步请求。 - ars265
1个回答

4
我假设您正在使用默认的ASP.Net会话管理器,并且我看到您调用GetSessionStore来获取会话。不幸的是,默认的会话管理器在调用需要写入访问会话存储的请求时会序列化所有请求。这个StackOverflow问题和这个MSDN关于会话状态的文章有一些关于会话状态及其锁定行为的非常有用的信息。
现在,要解决您的问题,您需要执行一些操作,这取决于您是使用MVC控制器还是编写自定义IHttpHandler。
  • 如果您正在编写自己的IHttpHandler,请确保不要在处理程序中添加IRequiresSessionState或IReadOnlySessionState接口。这样做,管道将跳过查找会话并直接进行处理。在这种情况下,context.Session将为null。
  • 如果您使用MVC来处理请求,则需要使用SessionState属性装饰控制器类,并传递SessionStateBehavior.DisabledSessionStateBehavior

无论哪种情况,您都不能依赖Session对象来存储上传状态。您可以创建一个静态的ConcurrentDictionary,以其SessionID为键(您需要将其传递给上传查询字符串或自己读取cookie,调用Session.SessionId将再次阻止您),并在其中存储上传状态(它们看起来也是Concurrent*)。

另一个选择是用你自己的自定义提供程序替换SessionStateProvider,但在这种情况下可能会有些过度。

我移除了IRequiresSessionState并替换了GetSessionStore的内部实现,只返回对我的静态ConncurentDictionary的引用(并将Add和Remove更改为TryAdd和TryRemove),但是我仍然在获取文件状态时遇到问题。上传大文件现在需要非常长的时间,比以前要长得多,并且当它从GET请求返回时,对象引用似乎没有得到更新。 - ars265
调试器可以在这种情况下提供帮助。逐步执行两种方法(先是post,然后是get),确保设置的方式符合您的要求。多线程可能很难适应。删除会话状态不应该减慢上传速度,因此我怀疑其他问题正在发生。此外,请确保您引用的是同一个字典,而不是每个请求都使用新的字典。 - Joshua

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接