简体   繁体   中英

how to upload a large file with ASP.NET MVC4 Web Api with progressbar

how can i upload a large file with ASP.NET MVC4 Web Api
and also get a progress?

i saw this post and i understand how to handle the uploaded file but how i can get the progress data? How To Accept a File POST

please don't send me links to upload products. i want to understand how handle this in the MVC4 Web Api way... here is an example code of handling a file upload in 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"));
        }
    }

now when

   await Request.Content.ReadAsMultipartAsync(provider)

how can i get how bytes loaded?

There is a limitation to the size of files to be uploaded by default at two places. One at the request level, and second , if you hosting on IIS, then on web server level. I added couple of configs as mentioned in this blog , and i was able to upload a 36mb file without any issues. I have posted the snippet below.

Basically

1.

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

2.

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

Its easy to find the size of the file loaded into the server if you wish. In your code

while reading through the filedata in the stream, for each item in you file data, you can read the local file name as shown below.

 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

Hope this helps. I wonder why this was marked down?

I use this solution:

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);
    }
}

and small configure for non-buffered input stream:

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

implemented this:

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

I Ended Up using an HttpModule but even the HttpModule won't show the progress bar I found out something very interesting it's seems that when i upload the file in a Secure Protocol(over https://) then the progress are working but in non secure protocl (http://) the progress is not working and the file is fully buffered i don't know way is like that i believe it's a bug somewhere between the IIS to Asp.net Framework when the Request are get Procced.

now because i success make it work over https with an HttpModule i believe it is possible to make it work also with Mvc Web Api but i currently don't have the time to check that.

for parsing Mutlipart form data i used Nancy HttpMultipart parser here: https://github.com/NancyFx/Nancy/tree/master/src/Nancy just grabbed the classes:
HttpMultipart.cs
HttpMultipartBoundary.cs
HttpMultipartBuffer.cs
HttpMultipartSubStream.cs

here is the HttpModule Source:

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; }
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM