HTML 폼 데이터 전송하기 - 파트 2
- 본 번역문서의 원문은 Sending HTML Form Data: File Upload and Multipart MIME www.asp.net 입니다.
파트 2: 파일 업로드와 Multipart MIME
본 자습서에서는 Web API에 파일을 업로드하는 방법을 살펴보고, Multipart MIME 데이터를 처리하는 방법에 관해서도 살펴봅니다.
다음은 파일 업로드를 위한 예제 HTML 폼 입니다:
<form name="form1" method="post" enctype="multipart/form-data" action="api/upload"> <div> <label for="caption">Image Caption</label> <input name="caption" type="text" /> </div> <div> <label for="image1">Image File</label> <input name="image1" type="file" /> </div> <div> <input type="submit" value="Submit" /> </div> </form>
이 예제 폼에는 텍스트 입력 컨트롤과 파일 입력 컨트롤이 함께 존재하는데, 이와 같이 폼에 파일 입력 컨트롤이 존재하는 경우에는 반드시 enctype 어트리뷰트의 값이 "multipart/form-data"로 지정되어야 하며, 이는 폼의 데이터가 Multipart MIME 메시지 형태로 전송되도록 지시합니다.
Multipart MIME 메시지 포멧의 구조는 요청 예제를 한 번 보기만해도 이해할 수 있을 정도로 단순합니다:
POST http://localhost:50460/api/values/1 HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------41184676334 Content-Length: 29278 -----------------------------41184676334 Content-Disposition: form-data; name="caption" Summer vacation -----------------------------41184676334 Content-Disposition: form-data; name="image1"; filename="GrandCanyon.jpg" Content-Type: image/jpeg (이진 데이터는 생략합니다.) -----------------------------41184676334--
이 메시지를 살펴보면 각각의 폼 컨트롤에 대응하는 두 부분(Part)으로 나뉘어 있는 것을 알 수 있습니다. 또한, 각 부분들의 경계는 대시(-) 문자들로 구성된 특정 문자열 줄로 구분되어 있습니다.
각 메시지 부분에는 메시지의 콘텐트에 따라, 다음 중 하나 이상의 헤더들이 포함될 수 있습니다.
- Content-Disposition 헤더: 컨트롤 이름을 담고 있습니다. 컨트롤이 파일 컨트롤인 경우, 파일명도 함께 포함됩니다.
- Content-Type 헤더: 데이터 유형을 지정합니다. 이 헤더가 생략되면 기본값으로 text/plain이 사용됩니다.
그러므로 위의 요청 예제를 살펴보면 GrandCanyon.jpg라는 이름의 파일을 image/jpeg 콘텐트 형식으로 업로드했으며, 텍스트 입력 컨트롤에 "Summer Vacation"이라는 값을 입력했다는 것을 알 수 있습니다.
파일 업로드
계속해서 Multipart MIME 메시지에서 파일을 읽어오는 Web API 컨트롤러 예제를 살펴보겠습니다. 이 컨트롤러에서는 파일을 비동기적으로 읽어올 것입니다. 참고로, Web API는 Task-기반 프로그래밍 모델 (Task-Based Programming Model)을 통해서 비동기적 액션을 지원합니다. 먼저, 다음 코드는 async 키워드와 await 키워드가 지원되는 .NET 프레임워크 4.5를 사용하는 예제입니다.
using System.Diagnostics; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Web.Http; public class UploadController : ApiController { public async Task<HttpResponseMessage> PostFormData() { // 요청이 multipart/form-data를 담고 있는지 확인합니다. if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string root = HttpContext.Current.Server.MapPath("~/App_Data"); var provider = new MultipartFormDataStreamProvider(root); try { // 폼 데이터 읽기. await Request.Content.ReadAsMultipartAsync(provider); // 다음 코드를 통해서 파일 이름을 읽어오는 방법을 알 수 있습니다. foreach (MultipartFileData file in provider.FileData) { Trace.WriteLine(file.Headers.ContentDisposition.FileName); Trace.WriteLine("Server file path: " + file.LocalFileName); } return Request.CreateResponse(HttpStatusCode.OK); } catch (System.Exception e) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e); } } }
이 컨트롤러 액션이 매개변수를 받지 않는다는 점에 유의하시기 바랍니다. 그 이유는 미디어-형식 포멧터를 호출하지 않고 직접 액션 내부에서 요청 본문을 처리하기 때문입니다.
IsMultipartContent 메서드는 요청에 Multipart MIME 메시지가 포함되어 있는지 여부를 점검합니다. 그렇지 않다면, 컨트롤러가 HTTP 상태 코드 415, 지원하지 않는 미디어 형식을 반환합니다.
그리고, MultipartFormDataStreamProvider 클래스는 업로드 된 파일들에 대해 파일 스트림을 할당해주는 도우미 개체입니다. Multipart MIME 메시지는 ReadAsMultipartAsync 메서드를 호출해서 읽을 수 있습니다. 이 메서드는 메시지 부분들을 모두 추출해서 MultipartFormDataStreamProvider가 제공해주는 스트림에 기록합니다.
이 메서드의 호출이 완료되면 MultipartFileData 개체들의 컬렉션인 FileData 속성을 이용해서 파일들의 정보를 읽어올 수 있습니다.
- MultipartFileData.FileName 속성은 파일이 저장된 서버의 로컬 파일 이름을 담고 있습니다.
- MultipartFileData.Headers 속성은 부분 헤더들을 담고 있습니다 (주의, 요청 자체의 헤더가 아닙니다). 이 속성을 통해서 Content-Disposition 헤더나 Content-Type 헤더에 접근할 수 있습니다.
메서드 이름에서 짐작할 수 있듯이 ReadAsMultipartAsync 메서드는 비동기 메서드입니다. 이 메서드가 완료된 뒤에 후속 작업을 계속해서 수행하려면, .NET 4.0을 사용하고 있다면 연속 작업(Continuation Task)을, .NET 4.5를 사용하고 있다면 await 키워드를 사용하면 됩니다.
다음은 이번 예제 코드의 .NET 프레임워크 4.0 버전입니다:
public Task<HttpResponseMessage> PostFormData() { // 요청이 multipart/form-data를 담고 있는지 확인합니다. if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string root = HttpContext.Current.Server.MapPath("~/App_Data"); var provider = new MultipartFormDataStreamProvider(root); // 폼 데이터 읽고 비동기 Task를 반환합니다. var task = Request.Content.ReadAsMultipartAsync(provider). ContinueWith<HttpResponseMessage>(t => { if (t.IsFaulted || t.IsCanceled) { Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception); } // 다음 코드를 통해서 파일 이름을 읽어오는 방법을 알 수 있습니다. foreach (MultipartFileData file in provider.FileData) { Trace.WriteLine(file.Headers.ContentDisposition.FileName); Trace.WriteLine("Server file path: " + file.LocalFileName); } return Request.CreateResponse(HttpStatusCode.OK); }); return task; }
폼 컨트롤 데이터 읽어오기
본문의 앞 부분에서 살펴본 HTML 폼을 다시 살펴보면 파일 입력 컨트롤 외에도 텍스트 입력 컨트롤이 존재한다는 것을 알 수 있습니다.
<div> <label for="caption">Image Caption</label> <input name="caption" type="text" /> </div>
MultipartFormDataStreamProvider의 FormData 속성을 이용하면 컨트롤의 값을 읽어올 수 있습니다.
public async Task<HttpResponseMessage> PostFormData() { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string root = HttpContext.Current.Server.MapPath("~/App_Data"); var provider = new MultipartFormDataStreamProvider(root); try { await Request.Content.ReadAsMultipartAsync(provider); // 모든 키-값 쌍들을 출력합니다. foreach (var key in provider.FormData.AllKeys) { foreach (var val in provider.FormData.GetValues(key)) { Trace.WriteLine(string.Format("{0}: {1}", key, val)); } } return Request.CreateResponse(HttpStatusCode.OK); } catch (System.Exception e) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e); } }
이 FormData 속성은 폼 컨트롤들에 대한 키/값 쌍을 담고 있는 NameValueCollection 개체인데, 이 컬렉션은 중복되는 키들을 포함할 수도 있습니다. 가령, 다음과 같은 폼을 가정해보겠습니다:
<form name="trip_search" method="post" enctype="multipart/form-data" action="api/upload"> <div> <input type="radio" name="trip" value="round-trip"/> Round-Trip </div> <div> <input type="radio" name="trip" value="one-way"/> One-Way </div> <div> <input type="checkbox" name="options" value="nonstop" /> Only show non-stop flights </div> <div> <input type="checkbox" name="options" value="airports" /> Compare nearby airports </div> <div> <input type="checkbox" name="options" value="dates" /> My travel dates are flexible </div> <div> <label for="seat">Seating Preference</label> <select name="seat"> <option value="aisle">Aisle</option> <option value="window">Window</option> <option value="center">Center</option> <option value="none">No Preference</option> </select> </div> </form>
이 폼의 요청 본문은 다음 예제와 비슷할 형태일 것입니다:
-----------------------------7dc1d13623304d6 Content-Disposition: form-data; name="trip" round-trip -----------------------------7dc1d13623304d6 Content-Disposition: form-data; name="options" nonstop -----------------------------7dc1d13623304d6 Content-Disposition: form-data; name="options" dates -----------------------------7dc1d13623304d6 Content-Disposition: form-data; name="seat" window -----------------------------7dc1d13623304d6--
이 경우, FormData 컬렉션에는 다음과 같은 키/값 쌍들이 포함됩니다:
- trip: round-trip
- options: nonstop
- options: dates
- seat: window
- 여러분의 첫 번째 ASP.NET Web API (C#) 2013-05-27 20:30
- CRUD 작업을 지원하는 Web API 작성하기 2013-06-14 20:30
- ASP.NET 웹폼에서 Web API 사용하기 2013-06-25 20:30
- ASP.NET Web API와 라우팅 2013-07-10 20:30
- ASP.NET Web API 도움말 페이지 작성하기 2013-08-14 15:43
- .NET 클라이언트에서 Web API 호출하기 (C#) 2013-08-21 16:26
- WPF 응용 프로그램에서 Web API 호출하기 (C#) 2013-08-28 22:41
- HttpClient 메시지 처리기 2013-09-04 17:16
- ASP.NET Web API 예외 처리 2013-09-11 21:23
- HTML 폼 데이터 전송하기 - 파트 1 2013-09-18 21:23
- HTML 폼 데이터 전송하기 - 파트 2 2013-09-25 01:11
- ASP.NET Web API와 HTTP 쿠키 2013-10-02 09:00
- 자체-호스트(Self-Host) Web API (C#) 2013-10-09 15:59
- OWIN을 이용한 ASP.NET Web API 자체 호스트(Self-Host) 2014-01-17 08:00
- 보안: ASP.NET Web API의 인증(Authentication)과 권한(Authorization) 2014-01-20 08:00
- 보안: 기본 인증 2014-01-22 08:00
- 보안: ASP.NET Web API와 개별 사용자 계정 2014-01-24 08:00
- 보안: 폼 인증 2014-01-27 08:00
- 보안: 통합 Windows 인증 2014-01-29 08:00
- 보안: 크로스 사이트 요청 위조(Cross-Site Request Forgery) 공격 방지하기 2014-02-03 08:00
- 보안: Web API에서 SSL 사용하기 2014-02-05 08:00
- 보안: 외부 인증 서비스 (C#) 2014-02-07 08:00
- 보안: ASP.NET Web API 교차-원본 요청(Cross-Origin Request) 활성화하기 2014-02-10 08:00