다음을 통해 공유


ASP.NET Web API HTML 양식 데이터 보내기: 파일 업로드 및 다중 파트 MIME

2부: 파일 업로드 및 다중 파트 MIME

이 자습서에서는 웹 API에 파일을 업로드하는 방법을 보여줍니다. 또한 다중 파트 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>

여름 휴가 텍스트와 이미지 파일 파일 선택기가 있는 이미지 캡션 필드를 보여 주는 HTML 양식의 스크린샷.

이 양식에는 텍스트 입력 컨트롤과 파일 입력 컨트롤이 포함되어 있습니다. 양식에 파일 입력 컨트롤이 포함된 경우 enctype 특성은 항상 "multipart/form-data"여야 합니다. 이 특성은 양식이 다중 파트 MIME 메시지로 전송되도록 지정합니다.

다중 파트 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

(Binary data not shown)
-----------------------------41184676334--

이 메시지는 각 양식 컨트롤에 대해 하나씩 두 부분으로 나뉩니다. 부분 경계는 대시로 시작하는 선으로 표시됩니다.

참고

파트 경계에는 메시지 파트 내에 경계 문자열이 실수로 나타나지 않도록 임의 구성 요소("41184676334")가 포함됩니다.

각 메시지 파트에는 하나 이상의 헤더와 파트 내용이 포함됩니다.

  • Content-Disposition 헤더에는 컨트롤의 이름이 포함됩니다. 파일의 경우 파일 이름도 포함합니다.
  • Content-Type 헤더는 파트의 데이터를 설명합니다. 이 헤더를 생략하면 기본값은 text/plain입니다.

이전 예제에서 사용자는 콘텐츠 형식 image/jpeg를 사용하여 GrandCanyon.jpg 파일을 업로드했습니다. 텍스트 입력의 값이 "여름 휴가"였습니다.

파일 업로드

이제 다중 파트 MIME 메시지에서 파일을 읽는 Web API 컨트롤러를 살펴보겠습니다. 컨트롤러는 파일을 비동기적으로 읽습니다. Web API는 작업 기반 프로그래밍 모델을 사용하여 비동기 작업을 지원합니다. 먼저 비기 및 await 키워드를 지원하는 .NET Framework 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()
    {
        // Check if the request contains 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
        {
            // Read the form data.
            await Request.Content.ReadAsMultipartAsync(provider);

            // This illustrates how to get the file names.
            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 메서드는 요청에 다중 파트 MIME 메시지가 포함되어 있는지 여부를 확인합니다. 그렇지 않은 경우 컨트롤러는 HTTP 상태 코드 415(지원되지 않는 미디어 형식)를 반환합니다.

MultipartFormDataStreamProvider 클래스는 업로드된 파일에 대한 파일 스트림을 할당하는 도우미 개체입니다. 다중 파트 MIME 메시지를 읽으려면 ReadAsMultipartAsync 메서드를 호출합니다. 이 메서드는 모든 메시지 파트를 추출하고 MultipartFormDataStreamProvider에서 제공하는 스트림에 씁니다.

메서드가 완료되면 MultipartFileData 개체의 컬렉션인 FileData 속성에서 파일에 대한 정보를 가져올 수 있습니다.

  • MultipartFileData.FileName 은 파일이 저장된 서버의 로컬 파일 이름입니다.
  • MultipartFileData.Headers 에는 파트 헤더(요청 헤더가 아님 )가 포함됩니다. 이를 사용하여 Content_Disposition 및 Content-Type 헤더에 액세스할 수 있습니다.

이름에서 확인할 수 있듯이 ReadAsMultipartAsync 는 비동기 메서드입니다. 메서드가 완료된 후 작업을 수행하려면 연속 작업(.NET 4.0) 또는 await 키워드(keyword)(.NET 4.5)을 사용합니다.

다음은 이전 코드의 .NET Framework 4.0 버전입니다.

public Task<HttpResponseMessage> PostFormData()
{
    // Check if the request contains 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);

    // Read the form data and return an async task.
    var task = Request.Content.ReadAsMultipartAsync(provider).
        ContinueWith<HttpResponseMessage>(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
            {
                Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
            }

            // This illustrates how to get the file names.
            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>

MultipartFormDataStreamProviderFormData 속성에서 컨트롤 값을 가져올 수 있습니다.

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

        // Show all the key-value pairs.
        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>

Round-Trip 원이 채워지고 논스톱 항공편만 표시 및 내 여행 날짜가 유연한 확인란이 선택된 HTML 양식의 스크린샷.

요청 본문은 다음과 같을 수 있습니다.

-----------------------------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: 왕복
  • 옵션: 논스톱
  • 옵션: 날짜
  • seat: window