2013-11-01 1 views
24

추가 매개 변수를 보내는 파일을 업로드해야합니다.Webapi formdata 추가 매개 변수를 사용하여 업로드 (DB로)

I에 유래에서 다음 게시물을 발견 : Webapi ajax formdata upload with extra parameters

그것은 파일 서버에이 MultipartFormDataStreamProvider를 사용하여 데이터 저장을 수행하는 방법에 대해 설명합니다. 나는 파일을 서버에 저장하는 대신 DB로 저장할 필요가있다. 그리고 이미 MultipartMemoryStreamProvider를 사용하여 코드 작업을하고 있지만 추가 매개 변수는 사용하지 않습니다.

webapi에서 추가 매개 변수를 처리하는 방법에 대한 단서를 줄 수 있습니까? 나는 또한 파일 및 테스트 paramater를 추가하는 경우 예를 들어

는 :

if (Request.Content.IsMimeMultipartContent()) 
{    
    var streamProvider = new MultipartMemoryStreamProvider(); 
    var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<FileModel>>(t => 
    { 
     if (t.IsFaulted || t.IsCanceled) 
     { 
      throw new HttpResponseException(HttpStatusCode.InternalServerError); 
     } 

     _fleDataService = new FileDataBLL(); 
     FileData fle; 

     var fleInfo = streamProvider.Contents.Select(i => {   
      fle = new FileData(); 
      fle.FileName = i.Headers.ContentDisposition.FileName; 

      var contentTest = i.ReadAsByteArrayAsync(); 
      contentTest.Wait(); 
      if (contentTest.Result != null) 
      { 
       fle.FileContent = contentTest.Result; 
      }      

      // get extra parameters here ?????? 

      _fleDataService.Save(fle); 

      return new FileModel(i.Headers.ContentDisposition.FileName, 1024); //todo 
     }); 
     return fleInfo; 
    }); 
    return task; 
} 

답변

26

당신은 그리에이를 수 있습니다 여기에

data.append("myParameter", "test"); 

여분의 paramater없이 파일 업로드를 처리하는 내 webapi입니다 -에서 다중 부분 콘텐츠의 FormData를 구문 분석하기위한 논리를 복제하는 사용자 지정 DataStreamProvider을 구현하여 매우 깨끗한 방식으로 처리 할 수 ​​있습니다.

MultipartFormDataStreamProviderMultiPartFileStreamProvider에서 하위 클래스로 만든 이유는 모르겠지만 적어도 FormData 컬렉션을 식별하고 노출하는 코드를 추출하지 않고도 여러 파트 데이터를 포함하는 많은 작업에 유용하기 때문에 디스크에 파일.

어쨌든 다음 공급자가 문제를 해결하는 데 도움이됩니다. 공급자 콘텐츠를 반복 할 때 파일 이름이없는 항목은 모두 무시해야합니다 (특히 streamProvider.Contents.Select() 성명을 사용하면 formdata를 DB에 업로드 할 위험이 있습니다). 따라서 공급자에게 요청하는 코드는 HttpContent IsStream()입니다.이 코드는 약간 해킹되었지만 가장 간단했습니다.

근본적으로 소스 코드 MultipartFormDataStreamProvider에서 자르고 붙여 넣기 할 수 있습니다. 엄격한 테스트를 거치지 않았습니다 (this answer에서 영감을 얻음).

public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider 
{ 
    private readonly Collection<bool> _isFormData = new Collection<bool>(); 
    private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); 

    public NameValueCollection FormData 
    { 
     get { return _formData; } 
    } 

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) 
    { 
     if (parent == null) throw new ArgumentNullException("parent"); 
     if (headers == null) throw new ArgumentNullException("headers"); 

     var contentDisposition = headers.ContentDisposition; 

     if (contentDisposition != null) 
     { 
      _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); 
      return base.GetStream(parent, headers); 
     } 

     throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); 
    } 

    public override async Task ExecutePostProcessingAsync() 
    { 
     for (var index = 0; index < Contents.Count; index++) 
     { 
      if (IsStream(index)) 
       continue; 

      var formContent = Contents[index]; 
      var contentDisposition = formContent.Headers.ContentDisposition; 
      var formFieldName = UnquoteToken(contentDisposition.Name) ?? string.Empty; 
      var formFieldValue = await formContent.ReadAsStringAsync(); 
      FormData.Add(formFieldName, formFieldValue); 
     } 
    } 

    private static string UnquoteToken(string token) 
    { 
     if (string.IsNullOrWhiteSpace(token)) 
      return token; 

     if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) 
      return token.Substring(1, token.Length - 2); 

     return token; 
    } 

    public bool IsStream(int idx) 
    { 
     return !_isFormData[idx]; 
    } 
} 

(귀하의 질문에 맞게 TPL 구문을 사용하여) 다음과 같이 사용할 수 있습니다 :

[HttpPost] 
public Task<string> Post() 
{ 
    if (!Request.Content.IsMimeMultipartContent()) 
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!")); 

    var provider = new MultipartFormDataMemoryStreamProvider(); 

    return Request.Content.ReadAsMultipartAsync(provider).ContinueWith(p => 
    { 
     var result = p.Result; 
     var myParameter = result.FormData.GetValues("myParameter").FirstOrDefault(); 

     foreach (var stream in result.Contents.Where((content, idx) => result.IsStream(idx))) 
     { 
      var file = new FileData(stream.Headers.ContentDisposition.FileName); 
      var contentTest = stream.ReadAsByteArrayAsync(); 
      // ... and so on, as per your original code. 

     } 
     return myParameter; 
    }); 
} 

나는 다음과 같은 HTML 양식을 테스트 :

<form action="/api/values" method="post" enctype="multipart/form-data"> 
    <input name="myParameter" type="hidden" value="i dont do anything interesting"/> 
    <input type="file" name="file1" /> 
    <input type="file" name="file2" /> 
    <input type="submit" value="OK" /> 
</form> 
+0

var count = provider.FileData.Count; 오류가 발생합니다 (FileData의 정의가 포함되어 있지 않음)? – renathy

+0

다른 질문 : 예제에서 파일 내용을 가져 오는 방법은 무엇입니까? 어떤 이유로 든 그것을 얻을 수 없습니다. – renathy

+0

공급자 구현과 예제 컨트롤러를 업데이트했습니다. 제발보십시오. – gooid

28

gooid의 대답에 확장 , 나는 그것이 인용되는 문제가 있었기 때문에 FormData 추출을 공급자로 캡슐화했다. 이것은 내 의견으로는 더 나은 구현을 제공했다.

public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider 
{ 
    private readonly Collection<bool> _isFormData = new Collection<bool>(); 
    private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); 
    private readonly Dictionary<string, Stream> _fileStreams = new Dictionary<string, Stream>(); 

    public NameValueCollection FormData 
    { 
     get { return _formData; } 
    } 

    public Dictionary<string, Stream> FileStreams 
    { 
     get { return _fileStreams; } 
    } 

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) 
    { 
     if (parent == null) 
     { 
      throw new ArgumentNullException("parent"); 
     } 

     if (headers == null) 
     { 
      throw new ArgumentNullException("headers"); 
     } 

     var contentDisposition = headers.ContentDisposition; 
     if (contentDisposition == null) 
     { 
      throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); 
     } 

     _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); 
     return base.GetStream(parent, headers); 
    } 

    public override async Task ExecutePostProcessingAsync() 
    { 
     for (var index = 0; index < Contents.Count; index++) 
     { 
      HttpContent formContent = Contents[index]; 
      if (_isFormData[index]) 
      { 
       // Field 
       string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty; 
       string formFieldValue = await formContent.ReadAsStringAsync(); 
       FormData.Add(formFieldName, formFieldValue); 
      } 
      else 
      { 
       // File 
       string fileName = UnquoteToken(formContent.Headers.ContentDisposition.FileName); 
       Stream stream = await formContent.ReadAsStreamAsync(); 
       FileStreams.Add(fileName, stream); 
      } 
     } 
    } 

    private static string UnquoteToken(string token) 
    { 
     if (string.IsNullOrWhiteSpace(token)) 
     { 
      return token; 
     } 

     if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) 
     { 
      return token.Substring(1, token.Length - 2); 
     } 

     return token; 
    } 
} 

그리고 어떻게 사용하고 있습니까? 우리가 .NET 4.5에 있기 때문에 나는 기다렸습니다.

[HttpPost] 
    public async Task<HttpResponseMessage> Upload() 
    { 
     if (!Request.Content.IsMimeMultipartContent()) 
     { 
      return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, "Unsupported media type."); 
     } 

     // Read the file and form data. 
     MultipartFormDataMemoryStreamProvider provider = new MultipartFormDataMemoryStreamProvider(); 
     await Request.Content.ReadAsMultipartAsync(provider); 

     // Extract the fields from the form data. 
     string description = provider.FormData["description"]; 
     int uploadType; 
     if (!Int32.TryParse(provider.FormData["uploadType"], out uploadType)) 
     { 
      return Request.CreateResponse(HttpStatusCode.BadRequest, "Upload Type is invalid."); 
     } 

     // Check if files are on the request. 
     if (!provider.FileStreams.Any()) 
     { 
      return Request.CreateResponse(HttpStatusCode.BadRequest, "No file uploaded."); 
     } 

     IList<string> uploadedFiles = new List<string>(); 
     foreach (KeyValuePair<string, Stream> file in provider.FileStreams) 
     { 
      string fileName = file.Key; 
      Stream stream = file.Value; 

      // Do something with the uploaded file 
      UploadManager.Upload(stream, fileName, uploadType, description); 

      // Keep track of the filename for the response 
      uploadedFiles.Add(fileName); 
     } 

     return Request.CreateResponse(HttpStatusCode.OK, "Successfully Uploaded: " + string.Join(", ", uploadedFiles)); 
    } 
+1

Mark Seefeldt의 대답은 .NET 4.5에서 잘 작동합니다. 부 끄 러운이 기능은 기본적으로 지원되지 않습니다. – jivangilad

+0

MultipartFormDataMemoryStreamProvider에서 "내용"은 무엇입니까? – SKD

+0

기본 클래스의 Contents 속성입니다. 기본적으로 HTTP 요청의 내용. https://msdn.microsoft.com/en-us/library/system.net.http.multipartstreamprovider.contents(v=vs.118).aspx#P:System.Net.Http.MultipartStreamProvider.Contents –

1

궁극적으로, 다음은 나를 위해 일한 무엇 이었는가 : 정말 업로드 된 파일의 미디어 타입과 길이를 필요

string root = HttpContext.Current.Server.MapPath("~/App_Data"); 

var provider = new MultipartFormDataStreamProvider(root); 

var filesReadToProvider = await Request.Content.ReadAsMultipartAsync(provider); 

foreach (var file in provider.FileData) 
{ 
    var fileName = file.Headers.ContentDisposition.FileName.Replace("\"", string.Empty); 
    byte[] documentData; 

    documentData = File.ReadAllBytes(file.LocalFileName); 

    DAL.Document newRecord = new DAL.Document 
    { 
     PathologyRequestId = PathologyRequestId, 
     FileName = fileName, 
     DocumentData = documentData, 
     CreatedById = ApplicationSecurityDirector.CurrentUserGuid, 
     CreatedDate = DateTime.Now, 
     UpdatedById = ApplicationSecurityDirector.CurrentUserGuid, 
     UpdatedDate = DateTime.Now 
    }; 

    context.Documents.Add(newRecord); 

    context.SaveChanges(); 
} 
+0

하나의 문제는 각 파일에 대해 SaveChanges()를 호출한다는 것입니다. 전체 콜렉션에 대해 한 번만 호출해야하므로 저장 내용은 절대적입니다. –

+0

http://stackoverflow.com/questions/1930982/when-should-i-call-savechanges-when-creating-1000s-of-entity-framework-object에 따르면 실제로는 각 행 다음에 저장하는 것이 더 빠릅니다. – user1477388

+0

그래서 내가 더 빠르다고 주장하지 않았습니다. –

3

그래서 @ 마크 Seefeldt 다음에 약간 대답 수정 :

public class MultipartFormFile 
{ 
    public string Name { get; set; } 
    public long? Length { get; set; } 
    public string MediaType { get; set; } 
    public Stream Stream { get; set; } 
} 

public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider 
{ 
    private readonly Collection<bool> _isFormData = new Collection<bool>(); 
    private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase); 
    private readonly List<MultipartFormFile> _fileStreams = new List<MultipartFormFile>(); 

    public NameValueCollection FormData 
    { 
     get { return _formData; } 
    } 

    public List<MultipartFormFile> FileStreams 
    { 
     get { return _fileStreams; } 
    } 

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) 
    { 
     if (parent == null) 
     { 
      throw new ArgumentNullException("parent"); 
     } 

     if (headers == null) 
     { 
      throw new ArgumentNullException("headers"); 
     } 

     var contentDisposition = headers.ContentDisposition; 
     if (contentDisposition == null) 
     { 
      throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part."); 
     } 

     _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); 
     return base.GetStream(parent, headers); 
    } 

    public override async Task ExecutePostProcessingAsync() 
    { 
     for (var index = 0; index < Contents.Count; index++) 
     { 
      HttpContent formContent = Contents[index]; 
      if (_isFormData[index]) 
      { 
       // Field 
       string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty; 
       string formFieldValue = await formContent.ReadAsStringAsync(); 
       FormData.Add(formFieldName, formFieldValue); 
      } 
      else 
      { 
       // File 
       var file = new MultipartFormFile 
       { 
        Name = UnquoteToken(formContent.Headers.ContentDisposition.FileName), 
        Length = formContent.Headers.ContentLength, 
        MediaType = formContent.Headers.ContentType.MediaType, 
        Stream = await formContent.ReadAsStreamAsync() 
       }; 

       FileStreams.Add(file); 
      } 
     } 
    } 

    private static string UnquoteToken(string token) 
    { 
     if (string.IsNullOrWhiteSpace(token)) 
     { 
      return token; 
     } 

     if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) 
     { 
      return token.Substring(1, token.Length - 2); 
     } 

     return token; 
    } 
} 
관련 문제