HTML 폼 데이터 전송하기 - 파트 1

등록일시: 2013-09-18 21:23,  수정일시: 2015-07-25 16:08
조회수: 13,450
이 문서는 ASP.NET Web API 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

파트 1: 폼-URL인코딩된(Form-urlencoded) 데이터

본문에서는 폼-URL인코딩된 데이터를 Web API 컨트롤러로 전송하는 방법을 살펴봅니다.

전체 프로젝트 다운로드 받기

기초적인 HTML 폼 정보

HTML 폼은 데이터를 GET이나 POST를 사용해서 서버로 전송합니다. 마크업에서 form 요소의 method 어트리뷰트를 설정하면 전송에 사용될 HTTP 메서드를 지정할 수 있습니다:

<form action="api/values" method="post">

폼 전송의 기본 메서드는 GET입니다. 폼 전송 시 GET 메서드를 사용하면, 폼의 데이터는 쿼리스트링으로 URI에 인코딩됩니다. 반면, 폼 전송 시 POST 메서드를 사용하면, 폼 데이터는 요청 본문에 담겨집니다. 더불어, POST로 전송되는 데이터는 enctype 어트리뷰트를 설정해서 요청 본문의 포멧을 지정할 수 있습니다:

enctype 설명
application/x-www-form-urlencoded 폼 데이터가 URI 쿼리스트링과 비슷한 형태, 즉 이름/값 쌍으로 인코딩됩니다. 이 포멧이 POST 메서드의 기본 포멧입니다.
multipart/form-data 폼 데이타가 Multipart MIME 메시지 형태로 인코딩됩니다. 파일을 서버로 업로드 할 때, 이 포멧이 사용됩니다.

이번 파트 1에서는 x-www-form-urlencoded 포멧만 살펴볼 것입니다. 그리고, 다음 파트 2에서는 Multipart MIME 포멧에 대해서 살펴보겠습니다.

복합 형식 전송하기

일반적인 경우, 폼 컨트롤 몇 개로부터 가져온 값들로 구성된 복합 형식을 서버로 전송하는 경우가 대부분일 것입니다. 가령, 상태 갱신을 뜻하는 다음과 같은 모델이 존재한다고 가정해보겠습니다:

namespace FormEncode.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;
    
    public class Update
    {
        [Required]
        [MaxLength(140)]
        public string Status { get; set; }
    
        public DateTime Date { get; set; }
    }
}

그리고, 다음은 POST를 통해서 Update 개체를 전달 받는 Web API 컨트롤러의 예제 코드입니다.

namespace FormEncode.Controllers
{
    using FormEncode.Models;
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Http;
    using System.Web;
    using System.Web.Http;
    
    public class UpdatesController : ApiController
    {
        static readonly Dictionary<Guid, Update> updates = new Dictionary<Guid, Update>();
    
        [HttpPost]
        [ActionName("Complex")]
        public HttpResponseMessage PostComplex(Update update)
        {
            if (ModelState.IsValid && update != null)
            {
                // 상태 텍스트에 포함되어 있는 모든 HTML 마크업을 변환합니다.
                update.Status = HttpUtility.HtmlEncode(update.Status);
    
                // 새로운 ID를 할당합니다.
                var id = Guid.NewGuid();
                updates[id] = update;
    
                // 201 응답을 생성합니다.
                var response = new HttpResponseMessage(HttpStatusCode.Created)
                {
                    Content = new StringContent(update.Status)
                };
                response.Headers.Location =
                    new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
                return response;
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }
    
        [HttpGet]
        public Update Status(Guid id)
        {
            Update update;
            if (updates.TryGetValue(id, out update))
            {
                return update;
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
    
    }
}
노트: 이 컨트롤러는 액션-기반 라우팅을 사용하고 있으며 이에 해당하는 라우트 템플릿은 "api/{controller}/{action}/{id}"입니다. 따라서, 클라이언트는 데이터를 "/api/updates/complex"로 전송해야 합니다.

계속해서 이번에는 사용자가 상태 갱신 정보를 제출할 수 있는 HTML 폼을 작성해보겠습니다.

<h1>Complex Type</h1>
<form id="form1" method="post" action="api/updates/complex"
    enctype="application/x-www-form-urlencoded">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input name="status" type="text" />
    </div>
    <div>
        <label for="date">Date</label>
    </div>
    <div>
        <input name="date" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

이 폼의 action 어트리뷰트에 컨트롤러 액션의 URI가 지정되어 있다는 점에 주목하시기 바랍니다. 다음은 폼에 임의의 값들이 입력된 모습을 보여주고 있습니다:

사용자가 이 폼의 Submit 버튼을 클릭하면, 브라우저는 다음과 비슷한 HTTP 요청을 서버로 전송하게 됩니다:

POST http://localhost:38899/api/updates/complex HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Content-Length: 47
    
status=Shopping+at+the+mall.&date=6%2F15%2F2012

이 때, 이름/값 쌍 형태의 폼 데이터가 요청 본문에 들어 있다는 점에 주목하시기 바랍니다. Web API는 자동으로 이름/값 쌍들로 구성된 데이터를 Update 클래스의 인스턴스로 변환해줍니다.

AJAX로 폼 데이터 전송하기

브라우저는 사용자가 폼을 제출하면, 현재 페이지를 떠나서 응답 메시지의 본문을 렌더링합니다. 이런 동작 방식은 응답 메시지가 HTML 페이지인 경우에는 아무런 문제도 없습니다. 그러나, Web API를 사용하는 경우에는 응답 본문이 비어 있거나 JSON 등으로 구조화된 데이터가 담겨있는 경우가 대부분입니다. 그러므로, 현재 페이지에서 응답을 계속 처리할 수 있도록 AJAX 요청을 통해서 폼 데이터를 전송하는 편이 더 합리적입니다.

다음 코드는 jQuery를 사용해서 폼 데이터를 POST 전송하는 방법을 보여줍니다.

<script type="text/javascript">
    $("#form1").submit(function () {
        var jqxhr = $.post('api/updates/complex', $('#form1').serialize())
            .success(function () {
                var loc = jqxhr.getResponseHeader('Location');
                var a = $('<a/>', { href: loc, text: loc });
                $('#message').html(a);
            })
            .error(function () {
                $('#message').html("Error posting the update.");
            });
        return false;
    });
</script>

이 코드에 사용된 jQuery submit 함수는 폼의 동작을 새로운 함수로 대체시켜줍니다. 그 결과, Submit 버튼의 기본적인 동작이 재정의됩니다. 또한, serialize 함수는 폼의 데이터들을 이름/값 쌍들로 직렬화시켜줍니다. 그리고, 폼 데이터를 서버로 전송하기 위해서는 $.post()를 호출하고 있습니다.

마지막으로, 요청이 완료되면 .success() 헨들러나 .error() 헨들러가 사용자에게 적절한 메시지를 보여주게 됩니다.

단순 형식 전송하기

이전 절에서는 복합 형식 전송에 관해서 살펴봤으며, 이 방식으로 전송된 데이터는 Web API에 의해 모델 클래스의 인스턴스로 역직렬화 되었습니다. 당연한 얘기겠지만, 문자열 같은 단순 형식도 전송이 가능합니다.

노트: 단순 형식을 전송하기에 앞서 이를 복합 형식으로 감싸서 전송하는 방식을 고려해보시기 바랍니다. 그러면 서버 측에서 수행되는 모델 유효성 검사의 이점을 얻을 수도 있고, 필요한 경우 확장하기에도 용이합니다.

단순 형식을 전송하는 방식도 기본적으로는 크게 다른 점이 없지만, 두 가지 미세한 차이점이 존재합니다. 첫 번째로 컨트롤러의 액션 매개변수 이름에 반드시 FromBody 어트리뷰트를 지정해야만 합니다.

[HttpPost]
[ActionName("Simple")]
public HttpResponseMessage PostSimple([FromBody] string value)
{
    if (value != null)
    {
        Update update = new Update()
        {
            Status = HttpUtility.HtmlEncode(value),
            Date = DateTime.UtcNow
        };

        var id = Guid.NewGuid();
        updates[id] = update;

        var response = new HttpResponseMessage(HttpStatusCode.Created)
        {
            Content = new StringContent(update.Status)
        };
        response.Headers.Location =
            new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
        return response;
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }
}

그 이유는 Web API가 기본적으로 요청 URI에서 기본 형식들을 읽어오려고 시도하기 때문입니다. 따라서, FromBody 어트리뷰트를 지정해서 Web API에게 해당 값을 요청 본문에서 읽어오도록 지시해야 합니다.

노트: Web API는 딱 한번만 응답 본문을 읽기 때문에 단 하나의 액션 매개변수만 요청 본문에서 가져올 수 있습니다. 만약, 요청 본문에서 여러 개의 값을 읽어와야 한다면 복합 형식을 정의하시기 바랍니다.

두 번째로 클라이언트는 값을 다음과 같은 형식으로 전송해야만 합니다:

=value

단순 형식에서는 이름/값 쌍의 이름 부분이 반드시 비어 있어야만 합니다. 일부 브라우저는 HTML 폼에서 이를 지원하지 않지만 다음과 같은 스크립트를 사용해서 이런 형식을 만들 수 있습니다:

$.post('api/updates/simple', { "": $('#status1').val() });

다음은 예제 폼입니다:

<h1>Simple Type</h1>
<form id="form2">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input id="status1" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

그리고, 다음은 폼 값을 서버로 제출하는 스크립트 예제입니다. 이전 스크립트와 유일하게 다른점은 post 함수에 전달되는 인자 한 가지 뿐입니다.

$('#form2').submit(function () {
    var jqxhr = $.post('api/updates/simple', { "": $('#status1').val() })
        .success(function () {
            var loc = jqxhr.getResponseHeader('Location');
            var a = $('<a/>', { href: loc, text: loc });
            $('#message').html(a);
        })
        .error(function () {
            $('#message').html("Error posting the update.");
        });
    return false;
});

또한, 단순 형식의 배열을 전송할 때도 같은 접근 방식을 사용할 수 있습니다:

$.post('api/updates/postlist', { "": ["update one", "update two", "update three"] });

다음 과정

HTML 폼 데이터 전송하기 - 파트 2