【问题标题】:How to Implement a Web API controller to accept chunked uploads using JQuery File Upload?如何使用 JQuery 文件上传实现 Web API 控制器以接受分块上传?
【发布时间】:2014-10-24 10:38:03
【问题描述】:

正如标题所述,我需要一些帮助来实现 Web API 控制器以使用 JQuery 文件上传来接受分块上传。任何帮助(包括现有文章/教程的链接)将不胜感激。

【问题讨论】:

    标签: c# asp.net-web-api jquery-file-upload


    【解决方案1】:

    首先让我们从客户端开始。 您必须为分块上传设置 maxChunkSize 选项。之后,您需要每个文件的唯一标识符,以便识别服务器上的每个块并将相应的块数据附加到正确的文件中。

    $('#fileupload')
            .bind('fileuploadsubmit', function (e, data) {
                data.headers = $.extend(data.headers,
                    {"X-File-Identifier": generateFileUniqueIdentifier(data)})
                });
            });
    
    generateFileUniqueIdentifier = function(data){
        var file=data.files[0],
        var result = file.relativePath||file.webkitRelativePath||file.fileName||file.name;
        
        return result.replace(/[^0-9a-zA-Z_-]/img,"") + "-" + i.size + "-" + $.now()
    } 
    

    现在在服务器端:ApiController

     public class UploadController : ApiController
     {
            [HttpPost]
            [Route("upload/{targetFolder:int}")]
            [ValidateMimeMultipartContentFilter]
            public async Task<IHttpActionResult> UploadDocument(int targetFolder)
            {
                var uploadFileService = new UploadFileService();
                UploadProcessingResult uploadResult = await uploadFileService.HandleRequest(Request);
    
                if (uploadResult.IsComplete)
                {
                    // do other stuff here after file upload complete    
                    return Ok();
                }
    
                return Ok(HttpStatusCode.Continue);
    
            }
    }
    

    实际上传文件的服务类。这支持块或整个文件。

    public class UploadFileService
    {
            private readonly string _uploadPath;
            private readonly MultipartFormDataStreamProvider _streamProvider;
    
            public UploadFileService()
            {
                _uploadPath = UserLocalPath;
                _streamProvider = new MultipartFormDataStreamProvider(_uploadPath);
            }
        
            #region Interface
    
            public async Task<UploadProcessingResult> HandleRequest(HttpRequestMessage request)
            {
                await request.Content.ReadAsMultipartAsync(_streamProvider);
                return await ProcessFile(request);
            }
    
            #endregion    
    
            #region Private implementation
    
            private async Task<UploadProcessingResult> ProcessFile(HttpRequestMessage request)
            {
                if (request.IsChunkUpload())
                {
                    return await ProcessChunk(request);
                }
    
                return new UploadProcessingResult()
                {
                    IsComplete = true,
                    FileName = OriginalFileName,
                    LocalFilePath = LocalFileName,
                    FileMetadata = _streamProvider.FormData
                };
            }
    
            private async Task<UploadProcessingResult> ProcessChunk(HttpRequestMessage request)
            {
                //use the unique identifier sent from client to identify the file
                FileChunkMetaData chunkMetaData = request.GetChunkMetaData();
                string filePath = Path.Combine(_uploadPath, string.Format("{0}.temp", chunkMetaData.ChunkIdentifier));
    
                //append chunks to construct original file
                using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate | FileMode.Append))
                {
                    var localFileInfo = new FileInfo(LocalFileName);
                    var localFileStream = localFileInfo.OpenRead();
    
                    await localFileStream.CopyToAsync(fileStream);
                    await fileStream.FlushAsync();
    
                    fileStream.Close();
                    localFileStream.Close();
    
                    //delete chunk
                    localFileInfo.Delete();
                }
    
                return new UploadProcessingResult()
                {
                    IsComplete = chunkMetaData.IsLastChunk,
                    FileName = OriginalFileName,
                    LocalFilePath = chunkMetaData.IsLastChunk ? filePath : null,
                    FileMetadata = _streamProvider.FormData
                };
    
            }
    
            #endregion    
    
            #region Properties
    
            private string LocalFileName
            {
                get
                {
                    MultipartFileData fileData = _streamProvider.FileData.FirstOrDefault();
                    return fileData.LocalFileName;
                }
            }
    
            private string OriginalFileName
            {
                get
                {
                    MultipartFileData fileData = _streamProvider.FileData.FirstOrDefault();
                    return fileData.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
                }
            }
    
            private string UserLocalPath
            {
                get
                {
                   //return the path where you want to upload the file                   
                }
            }
    
            #endregion    
        }
    

    HttpRequestMessagge 上的扩展用于识别块请求

    public static class HttpRequestMessageExtensions
    {
            public static bool IsChunkUpload(this HttpRequestMessage request)
            {
                return request.Content.Headers.ContentRange != null;
            }
    
            public static FileChunkMetaData GetChunkMetaData(this HttpRequestMessage request)
            {
                return new FileChunkMetaData()
                {
                    ChunkIdentifier = request.Headers.Contains("X-DS-Identifier") ? request.Headers.GetValues("X-File-Identifier").FirstOrDefault() : null,
                    ChunkStart = request.Content.Headers.ContentRange.From,
                    ChunkEnd = request.Content.Headers.ContentRange.To,
                    TotalLength = request.Content.Headers.ContentRange.Length
                };
            }
        }
    

    最后是服务响应模型和块元数据

    public class FileChunkMetaData
    {
        public string ChunkIdentifier { get; set; }
    
        public long? ChunkStart { get; set; }
    
        public long? ChunkEnd { get; set; }
    
        public long? TotalLength { get; set; }
    
        public bool IsLastChunk
        {
            get { return ChunkEnd + 1 >= TotalLength; }
        }
    }
    
    public class UploadProcessingResult
    {
        public bool IsComplete { get; set; }
    
        public string FileName { get; set; }
    
        public string LocalFilePath { get; set; }
    
        public NameValueCollection FileMetadata { get; set; }
    }
    

    MultiPartContentFilter 只是一个用于验证内容的 ActionFilter (from damienbod)

    public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (!actionContext.Request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }
        }
    
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
    
        }
    
    }
    

    【讨论】:

    • 由于还没有cmets,我已经用jQuery FileUpload & asp.net web api成功实现了这个方法,谢谢!
    • 需要考虑请求不是由同一台服务器处理的云环境。需要一种方法不仅可以同步文件 ID,还需要在它们可能分散的服务器场中进行缓存。
    • ValidateMimeMultipartContentFilter 我们从哪里得到这个?
    【解决方案2】:

    您可以在这里找到灵感:ASP.NET Multiple File Upload With Drag & Drop and Progress Bar Using HTML5。分块上传控制器方法的示例以UploadFile 开头。在客户端,jquery文件上传选项maxChunkSize需要根据https://github.com/blueimp/jQuery-File-Upload/wiki/Options设置。

    【讨论】:

      猜你喜欢
      • 2020-09-29
      • 1970-01-01
      • 2020-04-12
      • 2020-05-15
      • 2017-08-28
      • 2016-01-14
      • 2012-11-24
      • 2015-11-30
      • 1970-01-01
      相关资源
      最近更新 更多