【问题标题】:how to upload a large file with ASP.NET MVC4 Web Api with progressbar如何使用带有进度条的 ASP.NET MVC4 Web Api 上传大文件
【发布时间】:2013-03-08 13:11:53
【问题描述】:

如何使用 ASP.NET MVC4 Web Api 上传大文件
也有进步?

我看到了这篇文章,我了解如何处理上传的文件,但我如何才能获得进度数据? How To Accept a File POST

请不要向我发送上传产品的链接。 我想了解如何以 MVC4 Web Api 方式处理这个问题...... 这是在 MVC4 WebApi 中处理文件上传的示例代码

    public async Task<HttpResponseMessage> Post()
    {
        if (Request.Content.IsMimeMultipartContent())
        {
            var path = HttpContext.Current.Server.MapPath("~/App_Data");

            var provider = new MultipartFormDataStreamProvider(path);

            await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
            {
                if (t.IsFaulted || t.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);
            });

            return Request.CreateResponse(HttpStatusCode.OK); 
        }
        else
        {
            throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
        }
    }

现在

   await Request.Content.ReadAsMultipartAsync(provider)

我怎样才能知道字节是如何加载的?

【问题讨论】:

    标签: c# asp.net asp.net-mvc asp.net-mvc-4


    【解决方案1】:

    我使用这个解决方案:

    public class UploadController : ApiController
    {
        private static ConcurrentDictionary<string, State> _state = new ConcurrentDictionary<string, State>();
    
        public State Get(string id)
        {
            State state;
    
            if (_state.TryGetValue(id, out state))
            {
                return state;
            }
    
            return null;
        }
    
    
        public async Task<HttpResponseMessage> Post([FromUri] string id)
        {
            if (Request.Content.IsMimeMultipartContent())
            {
                var state = new State(Request.Content.Headers.ContentLength);
                if (!_state.TryAdd(id, state))
                    throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Conflict));
    
                var path = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data");
    
                var provider = new FileMultipartStreamProvider(path, state.Start, state.AddBytes);
    
                await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
                {
                    _state.TryRemove(id, out state);
    
                    if (t.IsFaulted || t.IsCanceled)
                        throw new HttpResponseException(HttpStatusCode.InternalServerError);
                });
    
    
                return Request.CreateResponse(HttpStatusCode.OK);
            }
            else
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
            }
        }
    }
    
    
    public class State
    {
        public long? Total { get; set; }
    
        public long Received { get; set; }
    
        public string Name { get; set; }
    
        public State(long? total = null)
        {
            Total = total;
        }
    
        public void Start(string name)
        {
            Received = 0;
            Name = name;
        }
    
        public void AddBytes(long size)
        {
            Received = size;
        }
    }
    
    public class FileMultipartStreamProvider : MultipartStreamProvider
    {
        private string _rootPath;
        private Action<string> _startUpload;
        private Action<long> _uploadProgress;
    
        public FileMultipartStreamProvider(string root_path, Action<string> start_upload, Action<long> upload_progress)
            : base()
        {
            _rootPath = root_path;
            _startUpload = start_upload;
            _uploadProgress = upload_progress;
        }
    
        public override System.IO.Stream GetStream(HttpContent parent, System.Net.Http.Headers.HttpContentHeaders headers)
        {
            var name = (headers.ContentDisposition.Name ?? "undefined").Replace("\"", "").Replace("\\", "_").Replace("/", "_").Replace("..", "_");
    
            _startUpload(name);
    
            return new WriteFileStreamProxy(Path.Combine(_rootPath, name), _uploadProgress);
        }
    
    }
    
    public class WriteFileStreamProxy : FileStream
    {
        private Action<long> _writeBytes;
    
        public WriteFileStreamProxy(string file_path, Action<long> write_bytes)
            : base(file_path, FileMode.Create, FileAccess.Write)
        {
            _writeBytes = write_bytes;
        }
    
        public override void EndWrite(IAsyncResult asyncResult)
        {
            base.EndWrite(asyncResult);
    
    #if DEBUG
            System.Threading.Thread.Sleep(100);
    #endif
    
            if (_writeBytes != null)
                _writeBytes(base.Position);
    
        }
    
        public override void Write(byte[] array, int offset, int count)
        {
            base.Write(array, offset, count);
    
    #if DEBUG
            System.Threading.Thread.Sleep(100);
    #endif
            if (_writeBytes != null)
                _writeBytes(base.Position);
        }
    }
    

    以及非缓冲输入流的小配置:

    config.Services.Replace(typeof(IHostBufferPolicySelector), new CustomPolicy());
    

    实现了这个:

    public class CustomPolicy : System.Web.Http.WebHost.WebHostBufferPolicySelector
    {
        public override bool UseBufferedInputStream(object hostContext)
        {
            return false;
        }
    }
    

    【讨论】:

    • @Nik 你能提供任何关于 HTML/JavaScript 的例子吗?
    【解决方案2】:

    我最终使用了 HttpModule,但即使 HttpModule 也不会显示进度条 我发现了一些非常有趣的事情,似乎当我以安全协议(通过 https://)上传文件时,进度正常,但在非安全协议(http://)中,进度不起作用,文件是完全缓冲我不知道方法是这样的,当请求被处理时,我相信这是 IIS 到 Asp.net 框架之间的某个错误。

    现在,因为我成功使用 HttpModule 使其在 https 上运行,我相信它也可以与 Mvc Web Api 一起使用,但我目前没有时间检查。

    为了解析 Mutlipart 表单数据,我在这里使用了 Nancy HttpMultipart 解析器: https://github.com/NancyFx/Nancy/tree/master/src/Nancy 刚刚上课:
    HttpMultipart.cs
    HttpMultipartBoundary.cs
    HttpMultipartBuffer.cs
    HttpMultipartSubStream.cs

    这里是 HttpModule 源代码:

    public class HttpUploadModule : IHttpModule
    {
        public static DateTime lastClean = DateTime.UtcNow;
        public static TimeSpan cleanInterval = new TimeSpan(0,10,0);
        public static readonly object cleanLocker = new object();
    
        public static readonly Dictionary<Guid,UploadData> Uploads = new Dictionary<Guid,UploadData>();
    
        public const int KB = 1024;
        public const int MB = KB * 1024;
    
        public static void CleanUnusedResources( HttpContext context) 
        {
            if( lastClean.Add( cleanInterval ) < DateTime.UtcNow ) {
    
                lock( cleanLocker ) 
                {
    
                    if( lastClean.Add( cleanInterval ) < DateTime.UtcNow ) 
                    {
                        int maxAge = int.Parse(ConfigurationManager.AppSettings["HttpUploadModule.MaxAge"]);
    
                        Uploads.Where(u=> DateTime.UtcNow.AddSeconds(maxAge) > u.Value.createdDate ).ToList().ForEach(u=>{    
                            Uploads.Remove(u.Key);
                        });
    
                        Directory.GetFiles(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/'))).ToList().ForEach(f=>{     
                            if( DateTime.UtcNow.AddSeconds(maxAge) > File.GetCreationTimeUtc(f)) File.Delete(f);
                        });
    
                        lastClean = DateTime.UtcNow;
                    }
                }
    
            }
        }
    
        public void Dispose()
        {   
    
        }
    
        public void Init(HttpApplication app)
        {
            app.BeginRequest += app_BeginRequest;
        }
    
        void app_BeginRequest(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
    
            Guid uploadId = Guid.Empty;
    
            if (context.Request.HttpMethod == "POST" && context.Request.ContentType.ToLower().StartsWith("multipart/form-data"))
            {
                IServiceProvider provider = (IServiceProvider)context;
                HttpWorkerRequest wr = (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
                FileStream fs = null;
                MemoryStream ms = null;
    
                CleanUnusedResources(context);                
    
    
                string contentType = wr.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentType);
                NameValueCollection queryString = HttpUtility.ParseQueryString( wr.GetQueryString() );
    
                UploadData upload = new UploadData { id = uploadId ,status = 0, createdDate = DateTime.UtcNow };
    
    
                if(
                                        !contentType.Contains("boundary=") ||   
                /*AT LAST 1KB        */ context.Request.ContentLength < KB ||
                /*MAX 5MB            */ context.Request.ContentLength > MB*5 || 
                /*IS UPLOADID        */ !Guid.TryParse(queryString["upload_id"], out uploadId) || Uploads.ContainsKey( uploadId )) {
                    upload.id = uploadId;
                    upload.status = 2;
                    Uploads.Add(upload.id, upload);
    
                    context.Response.StatusCode = 400;
                    context.Response.StatusDescription = "Bad Request";
                    context.Response.End();
    
    
                }
    
                string boundary = Nancy.HttpMultipart.ExtractBoundary( contentType );
    
                upload.id = uploadId;
                upload.status = 0;
                Uploads.Add(upload.id, upload);
    
    
                try {
    
                    if (wr.HasEntityBody())
                    {
                        upload.bytesRemaining = 
                        upload.bytesTotal     = wr.GetTotalEntityBodyLength();
    
                        upload.bytesLoaded    = 
                        upload.BytesReceived  = wr.GetPreloadedEntityBodyLength();
    
                        if (!wr.IsEntireEntityBodyIsPreloaded())
                        {
                            byte[] buffer = new byte[KB * 8];
                            int readSize = buffer.Length;
    
                            ms = new MemoryStream();
                            //fs = new FileStream(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/')+'/' + uploadId.ToString()), FileMode.CreateNew);
    
                            while (upload.bytesRemaining > 0)
                            {
                                upload.BytesReceived = wr.ReadEntityBody(buffer, 0, readSize);
    
                                if(upload.bytesRemaining == upload.bytesTotal) {
    
                                }
    
                                ms.Write(buffer, 0, upload.BytesReceived);
    
                                upload.bytesLoaded += upload.BytesReceived;
                                upload.bytesRemaining -= upload.BytesReceived;
    
                                if (readSize > upload.bytesRemaining)
                                {
                                    readSize = upload.bytesRemaining;
                                }
    
                            }
    
                            //fs.Flush();
                            //fs.Close();
                            ms.Position = 0;
                            //the file is in our hands
                            Nancy.HttpMultipart multipart = new Nancy.HttpMultipart(ms, boundary);
                            foreach( Nancy.HttpMultipartBoundary b in multipart.GetBoundaries()) {
                                if(b.Name == "data")   {
    
                                    upload.filename = uploadId.ToString()+Path.GetExtension( b.Filename ).ToLower();
    
                                    fs = new FileStream(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/')+'/' + upload.filename  ), FileMode.CreateNew);
                                    b.Value.CopyTo(fs);
                                    fs.Flush();
                                    fs.Close();
    
                                    upload.status = 1;
    
                                    context.Response.StatusCode = 200;
                                    context.Response.StatusDescription = "OK";
                                    context.Response.Write(  context.Request.ApplicationPath.TrimEnd('/') + "/images/temp/" +  upload.filename  );
                                }
                            }
    
                        }
    
                    }
                }
                catch(Exception ex) {
                    upload.ex = ex;
                }
    
                if(upload.status != 1)
                {
                    upload.status = 2;
                    context.Response.StatusCode = 400;
                    context.Response.StatusDescription = "Bad Request";
                }
                context.Response.End();
            }
        }
    }
    
    public class UploadData {
        public Guid id { get;set; }
        public string filename {get;set;}
        public int bytesLoaded { get; set; }
        public int bytesTotal { get; set; }
        public int BytesReceived {get; set;}
        public int bytesRemaining { get;set; }
        public int status { get;set; }
        public Exception ex { get;set; }
        public DateTime createdDate { get;set; }
    } 
    

    【讨论】:

      【解决方案3】:

      默认上传的文件大小有两个限制。一个在请求级别,第二个,如果您在 IIS 上托管,那么在 Web 服务器级别。我添加了this blog 中提到的几个配置,我能够上传一个 36mb 的文件而没有任何问题。我在下面发布了sn-p。

      基本上

      1.

        <system.web> 
          <httpRuntime maxRequestLength="2097152"/>
        </system.web>
      

      2.

      <system.webServer> 
        <security> 
            <requestFiltering> 
               <requestLimits maxAllowedContentLength="2147483648" /> 
            </requestFiltering> 
        </security><system.webServer> 
      

      如果您愿意,很容易找到加载到服务器中的文件的大小。在你的代码中

      在读取流中的文件数据时,对于文件数据中的每一项,您可以读取本地文件名,如下所示。

       string savedFile = fileData.LocalFileName;
       // use the file info class to derive properties of the uploaded file
       FileInfo file = new FileInfo(savedFile);
      //this will give the size of the uploaded file 
      long size = file.length/1024
      

      希望这会有所帮助。我想知道为什么这被降价了?

      【讨论】:

      • 感谢您提供有用的信息,我已经知道上传限制不是我所要求的,我找不到获取上传字节进度的方法,我的操作被调用后所有请求正文已上传。
      • 这不是 OP 要求的
      猜你喜欢
      • 2014-03-03
      • 1970-01-01
      • 2019-11-13
      • 2014-12-24
      • 2021-06-23
      • 1970-01-01
      • 1970-01-01
      • 2012-10-12
      • 1970-01-01
      相关资源
      最近更新 更多