login register Sysop! about ME  
qrcode
    최초 작성일 :    2015년 06월 02일
  최종 수정일 :    2015년 06월 02일
  작성자 :    Jake
  편집자 :    Jake (류 지형)
  읽음수 :    18,928

강좌 목록으로 돌아가기

필자의 잡담~

류지형입니다 .

MVC 5 번역한다고 중단했던 (그러면 안됐는데 말입니다..) 시리즈를 다시 번역하고 있습니다.
이 컬럼 이후에는 ASP.NET의 차세대 버전인 5.0에 대한 컬럼을 건드려볼까 생각하고 있습니다.
본 번역문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 번역되어 제공되는 문서로, 원문을 비롯한 모든 저작권은 마이크로소프트사에 있습니다. 마이크로소프트사의 요청이 있을 경우, 언제라도 게시가 중단될 수 있습니다. 본 번역문서에는 오역이 포함되어 있을 수 있으며 주석도 번역자 개인의 견해일뿐입니다. 마이크로소프트사는 본 문서의 번역된 내용에 대해 일체의 보장을 하지 않습니다. 번역이 완료된 뒤에도 제품이 업그레이드 되거나 기능이 변경됨에 따라 원문도 변경되거나 보완되었을 수 있으므로 참고하시기 바랍니다. 본 번역문서의 원문은 http://www.asp.net/mvc/overview/getting-started/introduction/examinin..view www.asp.net 입니다.

이번 강좌에서는 자동으로 생성된 Edit 액션 메서드와 movie 컨트롤러를 위한 뷰를 살펴 보겠습니다. 본론으로 들어가기에 앞서 release date를 보기 좋게 꾸미는 것으로 시작하겠습니다. Models\Movies.cs 파일을 열고 아래 하일라이트된 행을 추가합니다.

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }
        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

날짜를 사용자의 문화권에 맞게 설정하고 싶다면 DateFormatString을 아래와 같이 설정할 수도 있습니다.

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

다음 강좌에서 DataAnnotations에 대해 자세히 알아보겠지만, Display 어트리뷰트는 필드의 이름을 어떻게 출력할 것인지 결정합니다 (이 예제에서는 "ReleaseDate" 대신 사이에 빈칸을 넣은 "Release Date"를 지정했습니다). DataType 어트리뷰트는 데이터의 형식을 결정하는데, 여기서는 날짜이므로 필드에 저장된 시간 정보는 출력되지 않습니다. DisplayFormat 어트리뷰트는 크롬 브라우저에서 날짜 포맷을 다르게 표현하는 버그 때문에 필요합니다.

응용 프로그램을 실행하고 Movies 컨트롤러로 갑니다. 마우스 포인터를 Edit 링크위에 위치시키고 잠시 두면, 연결된 URL을 브라우저 하단에서 확인할 수 있습니다.

EditLink_sm

Edit 링크는 Views\Movies\Index.cshtml 뷰에 있는 Html.ActionLink에 의해 생성되었습니다.

@Html.ActionLink("Edit", "Edit", new { id=item.ID }) 

Html.ActionLink

Html 객체는 System.Web.Mvc.WebViewPage 라는 부모 클래스의 속성을 사용하여 노출된 헬퍼입니다. 이 헬퍼의 ActionLink 메서드는 컨트롤러의 액션 메서드에 대한 HTML 하이퍼링크를 동적으로 쉽게 만들어 줍니다. ActionLink 메서드의 첫 번째 인자는 예를 들어, <a>Edit Me</a> 처럼 렌더되는 링크 텍스트입니다. 두 번째 인자는 실행하려는 액션 메서드의 이름입니다 (이 경우에, Edit 액션). 마지막 인자는 익명 객체로서 라우트 데이터를 생성합니다 (이 경우에, 4 라는 ID).

이전 그림에서의 결과로 생성된 링크는 http://localhost:1234/Movies/Edit/4 입니다. (App_Start\RouteConfig.cs)에 정의한 대로 기본 라우트는 {controller}/{action}/{id} URL 패턴을 따릅니다. 그러므로, ASP.NET 은 http://localhost:1234/Movies/Edit/4 에 대한 요청을 Movies 컨트롤러의 Edit 액션 메서드에 대한 요청으로 해석하면서 매개변수로 ID, 4를 전달합니다. 다음의 코드를 App_Start\RouteConfig.cs 파일에서 확인해 보세요. MapRoute 메서드는 올바른 컨트롤러와 액션 메서드로 HTTP 요청을 라우트하는데 사용되고 ID 매개변수를 옵션으로 제공합니다. MapRoute 메서드는 또한, 라우트 데이터를 포함하여 주어진 컨트롤러, 액션 메서드로의 URL을 생성하는 ActionLink 같은 HtmlHelpers 에 의해서도 사용됩니다.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", 
id = UrlParameter.Optional }
    );
}

액션 메서드에 매개변수를 전달하기 위해 쿼리스트링을 사용할 수도 있습니다. 예를 들어, http://localhost:1234/Movies/Edit?ID=3 라는 URL은 Movies 컨트롤러의 Edit 메서드로 3 이라는 값을 ID 매개변수로서 전달합니다.

EditQueryString

Open the Movies controller. The two Edit action methods are shown below.

Movies 컨트롤러 파일을 열면 아래와 같이 두 개의 Edit 메서드가 있습니다.

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}
// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

두 번째 Edit 메서드는 HttpPost 어트리뷰트가 먼저 위치하고 있는 것에 주목하세요. 이 어트리뷰트를 통해 오버로드된 Edit 메서드는 POST 요청에 대해서만 실행됩니다. 첫 번째 Edit 메서드에 HttpGet 어트리뷰트를 적용할 수도 있지만 이 어트리뷰트가 기본 값이기 때문에 필요치 않습니다. (우리는 암시적으로 HttpGet 어트리뷰트가 적용된 액션 메서드들을 HttpGet 메서드로 다룰 겁니다.) Bind 어트리뷰트는 해커들이 오버포스팅(over-posting) 하지 못하도록 지켜주는 중요한 보안 메커니즘입니다. (역주: User라는 모델에 IsAdmin 속성이 있다고 할때, 우리가 비록 입력폼에 이 속성을 노출하지 않았다 하더라도 해커가 이 속성을 유추하여 포스팅 데이터에 추가하는 것을 오버포스팅이라 한다.) 반드시 변경을 원하는 속성만 bind 어트리뷰트에 지정하도록 합니다. 오버포스팅에 대한 보다 자세한 내용은 필자의 글, overposting security note 을 참조하십시오. 비교적 간단한 모델을 사용하는 우리 예제에서는 모델의 모든 데이터를 바인딩합니다. ValidateAntiForgeryToken 어트리뷰트는 요청 위조를 방지하고 @Html.AntiForgeryToken() 와 함께 사용됩니다. 이 메서드는 Edit 뷰 (Views\Movies\Edit.cshtml) 에서 사용되며 그 내용은 아래와 같습니다.

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)
        <div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
    @Html.EditorFor(model => model.Title)
    @Html.ValidationMessageFor(model => model.Title)
</div>
        </div>

@Html.AntiForgeryToken() 메서드는 감춰진 폼 위조방지 토큰을 생성하는데 이 토큰은 Movies 컨트롤러의 Edit 액션 메서드에서 반드시 매치되어야 합니다. 교차 사이트 요청 위조(cross-site request forgery, XSRF 또는 CSRF 라고도 알려짐)에 대한 자세한 내용은 필자의 다른 자습서인 XSRF/CSRF Prevention in MVC 을 참조하십시오.

HttpGet Edit 메서드는 영화 ID를 매개변수로 취하고 Entity Framework의 Find 메서드를 사용해 해당 영화를 찾고, 찾은 영화를 Edit 뷰에 돌려 줍니다. 해당 영화를 찾을 수 없다면 HttpNotFound 를 반환합니다. 스캐폴드 시스템을 통해서 생성된 Edit 뷰는 Movie 클래스를 분석해서 모든 속성을 <label>, <input> 요소로 렌더합니다. 다음의 예제는 Visual Studio의 스캐폴딩 시스템을 사용해서 생성한 Edit 뷰의 내용을 보여줍니다.

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)
        <div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
    @Html.EditorFor(model => model.Title)
    @Html.ValidationMessageFor(model => model.Title)
</div>
        </div>
        <div class="form-group">
@Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
<div class="col-md-10">
    @Html.EditorFor(model => model.ReleaseDate)
    @Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
<div class="col-md-offset-2 col-md-10">
    <input type="submit" value="Save" class="btn btn-default" />
</div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

주목할 부분은 뷰 템플릿이 파일 상단에 갖고 있는 @model MvcMovie.Models.Movie 문장입니다. 이 것은 뷰 템플릿을 위한 모델로서 Movie 형식의 모델을 기대하고 있다고 명시하고 있습니다.

스캐폴딩된 코드는 HTML 마크업을 간소화하기 위해 몇 가지의 헬퍼 메서드를 사용합니다. Html.LabelFor 헬퍼는 "Title","ReleaseDate", "Genre", "Price" 등과 같이 필드명을 출력합니다. Html.EditorFor 헬퍼는 HTML <input> 요소를 렌더합니다. Html.ValidationMessageFor 헬퍼는 속성과 연관된 유효성 검사 메시지를 출력합니다.

애플리케이션을 실행하고 /Movies URL로 이동하여 Edit 링크를 클릭합니다. 브라우저에서 페이지에 대한 소스 코드를 보면 아래와 같은 HTML로 폼 요소가 생성됩니다.

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <legend>Movie</legend>
      <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
      <div class="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
<input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
<span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
<input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
<span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
<span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>
      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="7.99" />
<span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>
      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

소스 코드를 보면, <input> 요소는 /Movies/Edit URL로 폼의 내용을 전송하는 action 어트리뷰트를 갖는 <form> 요소에 존재합니다. Save 버튼을 클릭하면 폼 데이터가 서버로 전송됩니다. 위 코드에서 두 번째 줄은 숨겨진 XSRF 토큰이고 이 것은 @Html.AntiForgeryToken() 메서드를 통해 생성됩니다.

POST 요청 처리하기

다음 코드는 Edit 액션 메서드의 HttpPost 버전입니다.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

ValidateAntiForgeryToken 어트리뷰트는 뷰에서 @Html.AntiForgeryToken() 에 의해 생성된 XSRF 토큰을 인증합니다.

ASP.NET MVC 모델 바인더는 폼 값을 전송받아 Movie 형식의 매개변수, movie 를 생성하여 돌려줍니다. ModelState.IsValid 메서드는 전송된 폼 값이 Movie 객체를 수정하는데 쓰일 수 있는지, 다시말해 모델이 정의하고 있는 속성에 유효한 값인지 검증합니다. 데이터에 문제가 없다면 db(MovieDBContext 인스턴스)의 Movies 컬렉션에 영화 데이터가 저장됩니다. 새로운 영화 데이터는 MovieDBContextSaveChanges 메서드를 호출함으로써 저장됩니다. 저장이 끝나면, 코드는 사용자를 MoviesControllerIndex 액션 메서드로 리다이렉트합니다. 이 화면은 방금 변경된 내용을 포함하여 영화 컬렉션을 보여줍니다.

클라이언트 단의 유효성 검사에서 어떤 필드가 유효하지 않다고 판단되면 에러 메시지가 곧바로 출력됩니다. 만약 자바스크립트를 비활성화 되어 있다면 클라이언트 단의 유효성 검사가 수행되지 않겠지만, 서버 단에서 전송된 값을 받이 유효하지 않은 값을 발견하면 에러 메시지와 함께 그 값을 다시 화면에 다시 보여줄 겁니다. 이 자습서의 후반부에서 유효성 검사에 대해 더욱 자세히 알아보겠습니다.

Edit.cshtml 뷰 템플릿의 Html.ValidationMessageFor 헬퍼는 적합한 에러 메시지를 출력하는 것을 신중하게 다룹니다.

abcNotValid

모든 HttpGet 메서드는 비슷한 패턴을 따릅니다. Index 의 경우에는 movie 객체의 집합을, 다른 메서드는 movie 객체를 받아 뷰에 전달합니다. Create 메서드는 Create 뷰에 빈 movie 객체를 전달합니다. 생성, 수정, 삭제와 같이 데이터에 변경을 가하는 모든 메서드들은 HttpPost 오버로드 메서드에서 해당 작업을 합니다. HTTP Get 메서드에서 데이터를 수정하는 것은 ASP.NET MVC Tip #46 ? Don’t use Delete Links because they create Security Holes 글에서 다룬 것과 같이 보안상 위험합니다. REST 패턴에서 GET 요청은 애플리케이션의 상태를 수정해서는 안된다고 규정하고 있습니다. GET 메서드를 통해 데이터를 수정하는 것은 이 패턴에 위배되고 또한, HTTP 베스트 프랙티스를 위반하는 일입니다. 다른 말로 하면, GET 동작은 안전해야 한다, 즉 어떤 부작용도 일으켜서는 안되고 데이터를 수정해서도 안된다는 것입니다.

미국 영어를 사용하는 컴퓨터에서 작업하는 독자라면 이번 절은 건너 뛰고 다음 자습서를 시작하셔도 됩니다. 언어 국제화에 대해서는 2부로 구성된 훌륭한 자습서, Nadeem's ASP.NET MVC 5 Internationalization 를 참조하십시오.

노트 소수점으로 컴마(",")를 사용하거나 미국 날짜 형식을 사용하지 않는 문화권에서 jQuery 유효성 검사를 지원하기 위해서는 다음과 같은 사항이 필요합니다. globalize.js 파일과 특정 문화권에 종속된 cultures/globalize.cultures.js 파일을 추가하고, 자바스크립트가 Globalize.parseFloat 를 사용하도록 해야 합니다. NuGet에서 jQuery non-English validation을 받으면 되는데 English locale을 사용하면 Globalize를 설치 하지 마십시오.

[역주] 이 방식은 이 글을 번역하는 시점에서 완전히 다른 방향으로 변경되었기 때문에, jQuery/Globalize 에서 새로운 방법을 참조 하십시오.

다음 자습서에서는 검색 기능을 구현해 보도록 하겠습니다.


authored by

  solgun
  2015-11-19(10:27)
캐릭 이미지
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")]
Movie movie)
여기서 사용된 Bind가 오버포스팅을 막기 위해 사용된다고 하였는데 어떤원리로 막을 수 있는
지 간단한 설명 부탁드려도될까요?


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})