2015-01-11 26 views
10

Chúc ngày vui vẻ!Cách tải tệp lên máy chủ API Web và gửi tham số cùng với hành động?

Tôi đang làm việc trên dự án ASP.NET Web API 2. Tại một thời điểm nhất định, cần phải tải lên một số tệp. Các tệp cần được liên kết với một FileModel nhất định (lớp riêng của chúng tôi). Vì vậy, khách hàng cần gửi IEnumerable như tham số và các tập tin như nội dung. Bởi vì đó là một RESTful API, cả hai đều phải được gửi trong cùng một yêu cầu.

Điều tốt nhất chúng tôi có thể đưa ra là hành động điều khiển sau:

public async Task<HttpResponseMessage> Add([FromUri] IEnumerable<FileModel> fileModels) 
{ 
    // REQUEST INTEGRITY TESTING 

    var streamProvider = new CustomMultipartFormDataStreamProvider(fileSavePath, fileModels); 
    // Read the MIME multipart content using the stream provider we just created. 
    var work = await Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(async t => 
     { 
      // SOME WORK DONE AFTER SAVING THE FILES TO THE HARD DRIVE 
     } 

} 

Vấn đề là như sau: các tập tin được tải lên với Content-Type tiêu đề một 'multipart/form-data'. Chúng ta cần biết nội dung của FileModels trước khi thao tác các tập tin ở phía máy chủ. Nếu chúng ta sử dụng MultipartFormDataStreamProvider, chúng ta chỉ có thể truy cập các tham số không tập tin sau khi các tập tin đã được lưu vào ổ đĩa cứng.

Cách giải quyết duy nhất cho điều này mà chúng tôi có thể tìm thấy là gửi tham số < FileModel> IEnumerable trong URL. Nhưng với điều kiện URL có độ dài tối đa giới hạn, đây không phải là phương pháp đáng tin cậy.

Câu hỏi đặt ra là: Có cách nào để nộp cả IEnumerable < FileModel> tham số fileModels và các tập tin trong cơ thể của các yêu cầu và được tiếp cận với các thông số fileModels trước khi truy cập các tập tin? Chúng tôi cũng muốn có thể sử dụng HttpContext.Current.Request.Files.Count;

jQuery hiện tại của chúng tôi để tải lên tập tin trông như thế này (cho mục đích thử nghiệm sớm, nó chỉ hỗ trợ một tập tin tải lên):

$('#Upload').click(function(e) { 
      e.preventDefault(); 

      var headers = new Array(); 
      headers["SessionId"] = sessionId; 

      var files = $('#fileInput').get(0).files; 
      var formData = new FormData(); 
      formData.append("files", files[0]); 

      var fileModel = $('#fileSubmission').serialize(); 

      $.ajax({ 
       url: "api/Submissions/Add/?" + fileModel, 
       headers: headers, 
       type: 'POST', 
       data: formData, 
       cache: false, 
       contentType: false, 
       processData: false, 
       dataType: 'json' 
      }); 
     }); 

Cảm ơn bạn rất nhiều!

+0

Bạn đã xem là gửi toàn bộ dữ liệu như JSON và chỉ đơn giản là deserializing nó vào một lớp có chứa một 'Stream', và các thông số siêu dữ liệu? –

+0

Có, nhưng Tệp luôn kết thúc bằng không. –

+0

Bạn đã thử sử dụng MultipartFormDataStreamProvider chưa. Nhìn vào mẫu này https://damienbod.wordpress.com/2014/03/28/web-api-file-upload-single-or-multiple-files/ – usp

Trả lời

5

Tôi xin lỗi vì câu trả lời trễ, nhưng chúng tôi đã giải quyết được vấn đề (Tôi quên rằng tôi không tải lên câu trả lời ở đây). Về cơ bản những gì chúng tôi đã làm là chúng tôi gọi phương thức ReadAsMultiPartAsync trên một vị trí tạm thời và sau đó chúng tôi trích xuất các tham số khác từ yêu cầu. Sau đó, chúng tôi đã xác thực đầu vào và chuyển các tệp từ tạm thời sang vị trí cố định.

Nếu bạn muốn xem mã, đây là những gì làm việc cho ví dụ cụ thể của chúng tôi và tôi tin rằng đó là khá thẳng về phía trước để thích ứng với bất kỳ trường hợp công việc kịch bản:

Về phía khách hàng, chúng tôi có dưới hình thức (có, thực hiện này là dành cho mục đích demo và chỉ hỗ trợ gửi một tệp ... cũng vậy, trường nhập = "tệp" thực sự nằm ngoài biểu mẫu; mục đích thử nghiệm)

<input type="file" name="data" id="fileInput" multiple="multiple" /> 

<form id="fileSubmission">    
    <input type="text" width="10" onchange="getFileDetails()" autocomplete="off" placeholder="FileId" name="files[0].Id" id="fileId" /> 
    <input type="hidden" name="files[0].FileName" id="FileName"/> 
    <input type="hidden" name="files[0].Extension" id="Extension"/> 
    <input type="hidden" name="files[0].EntityId" id="EntityId"/> 
    <br /><br /> 
    <input type="submit" id="Upload" value="Upload" /> 
</form> 

nơi getFileDetails() điền thứ e các trường nhập khác.Ngoài ra, hình thức đã được gửi đến máy chủ bằng cách sử dụng như sau jQuery/Javascript:

$('#Upload').click(function(e) { 
      e.preventDefault(); 

      var courseId = $('#courseId').val(); 
      var fileId = $('#fileId').val(); 
      if (!courseId || !fileId) { 
       return; 
      } 

      var headers = new Array(); 
      headers["SessionId"] = sessionId; 
      headers["contentType"] = "application/json; charset=UTF-8"; 

      var formData = new FormData(); 
      var opmlFile = $('#fileInput').get(0).files; 

      // this is like the model we're expecting on the server 
      var files = []; 
      files.push({ 'Id': $('#fileId').val(), 'OriginalFileName': opmlFile[0].name, 'FileName': $('#FileName').val(), 'Extension': $('#Extension').val(), 'EntityId': $('#EntityId').val() }); 

      formData.append("fileModels", JSON.stringify(files)); 
      formData.append("File_0", opmlFile[0]); 


      $.ajax({ 
       url: "api/Courses/" + courseId + "/Submissions/Add/", 
       headers: headers, 
       type: 'POST', 
       data: formData, 
       cache: false, 
       contentType: false, 
       processData: false, 
       dataType: 'json' 
      }); 
     }); 

Về phía server, chúng tôi đã điều sau đây:

// POST: api/Courses/{courseId}/Submissions/Add 
[HttpPost] 
[ValidateModelState] 
[ValidateMimeMultipartContent] 
[PermissionsAuthorize(CoursePermissions.CanCreateSubmissions)] 
public async Task<HttpResponseMessage> Add(int courseId) 
    { 
     // the same as in the jQuery part 
     const string paramName = "fileModels"; 

     // Put the files in a temporary location 
     // this way we call ReadAsMultiPartAsync and we get access to the other data submitted 
     var tempPath = HttpContext.Current.Server.MapPath("~/App_Data/Temp/" + Guid.NewGuid()); 
     Directory.CreateDirectory(tempPath); 

     var streamProvider = new MultipartFormDataStreamProvider(tempPath); 
     var readResult = await Request.Content.ReadAsMultipartAsync(streamProvider); 

     if (readResult.FormData[paramName] == null) 
     { 
      // We don't have the FileModels ... delete the TempFiles and return BadRequest 
      Directory.Delete(tempPath, true); 
      return Request.CreateResponse(HttpStatusCode.BadRequest); 
     } 

     // The files have been successfully saved in a TempLocation and the FileModels are not null 
     // Validate that everything else is fine with this command 
     var fileModels = JsonConvert.DeserializeObject<IEnumerable<FileModelExtension>>(readResult.FormData[paramName]).ToList(); 

     // AT THIS POINT, ON THE SERVER, WE HAVE ALL THE FILE MODELS 
     // AND ALL THE FILES ARE SAVED IN A TEMPORARY LOCATION 

     // NEXT STEPS ARE VALIDATION OF THE INPUT AND THEN 
     // MOVING THE FILE FROM THE TEMP TO THE PERMANENT LOCATION 

     // YOU CAN ACCESS THE INFO ABOUT THE FILES LIKE THIS: 
     foreach (var tempFile in readResult.FileData) 
      { 
       var originalFileName = tempFile.Headers.ContentDisposition.FileName.Replace("\"", string.Empty); 

       var localTempPath = tempFile.LocalFileName; 
      } 

    } 

Tôi hy vọng điều này sẽ giúp bất cứ ai có thể gửi tệp và các thông số khác cùng một lúc đến máy chủ bằng cách sử dụng các yêu cầu Bài đăng! :)

LƯU Ý: Một số thuộc tính được sử dụng trên máy chủ là tùy chỉnh. PermissionAuthorize, ValidateModelState và ValidateMimeMultiPartContent là các Bộ lọc tùy chỉnh mà chúng tôi đã sử dụng. Việc thực hiện sau hai đã được lấy cảm hứng từ http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc

Thuộc tính multipartcontent chỉ làm một kiểm tra về actionContext.Request.Content.IsMimeMultipartContent(), như thế này:

public class ValidateMimeMultipartContent : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     if (!actionContext.Request.Content.IsMimeMultipartContent()) 
     { 
      actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, Messages.UnsupportedMediaType); 
     } 
    } 
} 
Các vấn đề liên quan