login register Sysop! about ME  
qrcode
    최초 작성일 :    2012년 10월 15일
  최종 수정일 :    2012년 10월 15일
  작성자 :    songgun
  편집자 :    songgun (송 원석)
  읽음수 :    29,937

강좌 목록으로 돌아가기

필자의 잡담~

ASP.net에서 제공되는 자습서 시리즈 중, 두 번째로 MVC Music Store 관련 자습서들을 번역하여 제공해드리는 것입니다. 원문은 총 10편으로 구성되어 있으며, 각각의 분량을 고려하여 한 편씩 제공되거나 두 편이 함께 제공됩니다.

먼저 편역이 마무리 된 ASP.NET MVC 4 자습서 - 만들면서 배우기 시리즈를 읽어보지 않으신 분들은, 우선 해당 자습서 시리즈부터 읽어보시는 것을 권해드립니다.
본문은 ASP.net의 공식 MVC 관련 자습서인 Part 9: Registration and CheckoutPart 10: Final Updates to Navigation and Site Design, Conclusion을 편역한 글입니다. 마이크로소프트의 공식 번역 문서가 아니며 태오 사이트 MS 컬럼 번역팀에서 번역한 내용입니다. 그렇기에, 일부 오역이나 오타가 존재할 수 있는 점 미리 양해를 드립니다. 원문에 대한 모든 저작권은 마이크로소프트에 있으며, 컬럼 내용과 관련한 질의 문답 역시 원문 사이트에 문의하시는 것을 추천드립니다.

파트 9: 등록 및 결제

MVC 뮤직 스토어 응용 프로그램은 ASP.NET MVC와 Visual Studio for Web Development를 소개하고, 그 사용법을 단계별로 살펴보기 위한 자습용 응용 프로그램으로, 온라인 음반 판매 기능을 비롯하여, 기초적인 사이트 관리, 사용자 로그인, 장바구니 기능 등이 구현된 간단한 전자상거래 사이트 예제입니다.

본 자습서 시리즈에서는 ASP.NET MVC 뮤직 스토어 응용 프로그램을 구축하기 위해서 필요한 모든 단계들을 자세하게 살펴볼 것입니다. 이번 파트 9에서는 등록 및 결제에 관해서 살펴봅니다.

이번 파트에서는 구매자의 주소와 지불 정보를 수집하는 Checkout 컨트롤러를 구현해보려고 합니다. 또한, 사용자로 하여금 MVC 뮤직 스토어에 등록하도록 유도하기 위해서, 결제 완료를 위해 이 컨트롤러에 접근하려면 반드시 권한이 필요하도록 구현해볼 것입니다.

결제 절차를 진행하려면 장바구니 페이지 상단의 "Checkout" 버튼을 클릭하면 됩니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image096.jpg

만약, 로그인이 되어 있지 않은 상태라면, 다음과 같은 로그인 프롬프트가 나타나게 됩니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image097.png

정상적으로 로그인을 하고 나면, "Address and Payment" 뷰 화면이 나타납니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image098.png

사용자가 폼을 작성하고 주문을 전송하면, 주문 확인 화면이 나타납니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image099.png

만약, 존재하지 않는 주문이나 현재 로그인한 사용자의 것이 아닌 주문을 보려고하면 Error 뷰가 나타납니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image100.png

역주 본 자습서의 원문 자체의 오류인지, 아니면 ASP.NET MVC 3의 기본 템플릿 버그인지 역자도 정확하게 알 수는 없지만, 원문의 지시를 충실하게 따라하더라도 현재 상태에서는 새로운 사용자를 등록할 수 없습니다. 그 이유는 등록 화면에 필수 입력 필드인 "보안 질문"과 "보안 대답"을 입력할 수 있는 텍스트박스가 존재하지 않기 때문입니다. 따라서, 임시로나마 이 문제점을 해결하려면, AccountController.cs 파일을 열고 Register POST 메서드에서 다음과 같은 코드 라인을 찾으십시요.

    Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, ...

그리고, 이 메서드 호출의 네 번째와 다섯 번째 매개변수를 null이나 빈 문자열이 아닌 임의의 문자열로 대체합니다.

    Membership.CreateUser(model.UserName, model.Password, model.Email, "dummy", "dummy", ...

장바구니 전환하기

MVC 뮤직 스토어의 쇼핑 과정 자체는 익명으로도 가능하지만, Checkout 버튼을 클릭해서 결제 과정에 진입하려고 시도하면 등록 및 로그인을 요구받게 됩니다. 사용자들은 그 과정 중에도 장바구니의 정보가 계속 유지되기를 바라기 때문에, 등록이나 로그인 처리가 완료되는 시점에 장바구니의 현재 정보를 등록 또는 로그인한 사용자와 연결시켜줘야 합니다.

그런데, 사실 이 처리는 매우 간단합니다. 왜냐하면, 이전 파트에서 ShoppingCart 클래스를 작성할 때, 현재 장바구니에 존재하는 모든 음반들을 특정 사용자 이름과 연결해주는 메서드를 이미 구현해놨기 때문입니다. 그러므로, 사용자 등록이나 로그인 처리가 마무리 되는 시점에 그저 이 메서드를 호출해주기만 하면 됩니다.

먼저, 멤버십 및 권한을 설정하면서 추가했던 AccountController 클래스 파일을 엽니다. 그리고, 이 클래스 상단에 MvcMusicStore.Models 네임스페이스를 참조하는 using 문을 추가하고, 다음과 같은 MigrateShoppingCart 메서드도 추가합니다:

private void MigrateShoppingCart(string UserName) 
{ 
    // 장바구니의 음반들을 로그인한 사용자와 연결합니다. 
    var cart= ShoppingCart.GetCart(this.HttpContext); 
    cart.MigrateCart(UserName); 
    Session[ShoppingCart.CartSessionKey] = UserName; 
}

그런 다음, LogOn POST 액션을 수정해서 사용자의 유효성이 검증된 직후, 다음과 같이 MigrateShoppingCart 메서드를 호출합니다:

// 
// POST: /Account/LogOn 
[HttpPost] 
public ActionResult LogOn(LogOnModel model, string returnUrl) 
{ 
    if (ModelState.IsValid) 
    { 
        if (Membership.ValidateUser(model.UserName, model.Password)) 
        { 
            MigrateShoppingCart(model.UserName); 

            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); 
            if (Url.IsLocalUrl(returnUrl) && 
                 returnUrl.Length > 1 && 
                 returnUrl.StartsWith("/") && 
                !returnUrl.StartsWith("//") && 
                !returnUrl.StartsWith("/\\")) 
            { 
                return Redirect(returnUrl); 
            } 
            else 
            { 
                return RedirectToAction("Index", "Home"); 
            } 
        } 
        else 
        { 
            ModelState.AddModelError("", "The user name or password provided is incorrect."); 
        } 
    } 

    // 이 경우 오류가 발생한 것이므로 폼을 다시 표시하십시오. 
    return View(model); 
}

계속해서 같은 요령으로 Register POST 액션을 수정해서 사용자의 계정이 생성된 직후, MigrateShoppingCart 메서드를 호출합니다:

// 
// POST: /Account/Register 
[HttpPost] 
public ActionResult Register(RegisterModel model) 
{ 
    if (ModelState.IsValid) 
    { 
        // 사용자를 등록해 보십시오. 
        MembershipCreateStatus createStatus; 
        Membership.CreateUser(model.UserName, model.Password, model.Email, 
            "question", "answer", true, null, out createStatus); 

        if (createStatus == MembershipCreateStatus.Success) 
        { 
            MigrateShoppingCart(model.UserName); 
                      
            FormsAuthentication.SetAuthCookie(model.UserName,  
                false /* createPersistentCookie */); 
            return RedirectToAction("Index", "Home"); 
        } 
        else 
        { 
            ModelState.AddModelError("", ErrorCodeToString(createStatus)); 
        } 
    } 

    // 이 경우 오류가 발생한 것이므로 폼을 다시 표시하십시오. 
    return View(model); 
}

이것이 필요한 작업의 전부입니다. 이제 사용자 계정이 정상적으로 등록되거나 로그인 될 때마다 익명 장바구니의 음반 정보들이 해당 사용자 계정의 장바구니로 정보로 전환될 것입니다.

Checkout 컨트롤러 생성하기

이번에는 솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 버튼으로 클릭한 다음, Empty controller 템플릿을 선택해서 CheckoutController라는 이름으로 새로운 컨트롤러를 프로젝트에 추가합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image101.png

먼저, Controller 클래스 선언 바로 위에 Authorize 어트리뷰트를 추가해서 결제를 하려면 반드시 사용자들이 등록이나 로그인을 해야만 하도록 만듭니다:

namespace MvcMusicStore.Controllers 
{ 
    [Authorize] 
    public class CheckoutController : Controller
노트: 이 작업은 이전 파트에서 StoreManager 컨트롤러에 대해서 수행했던 작업과 비슷합니다. 다만, 지난 번에는 사용자가 Administrator 역할에 포함되어 있는 경우에만 접근을 허용하도록 Authorize 어트리뷰트를 설정했었습니다. 그러나, 이번 Checkout 컨트롤러에서는 사용자에게 로그인은 요구하지만, 반드시 특정 역할에 포함되기를 바라지는 않습니다.

본 자습서에서는 예제를 간단히 처리하기 위해서 실제 지불 정보를 처리하지는 않을 것입니다. 대신, 프로모션 코드를 이용해서 결제 과정을 수행할 수 있도록 구현할 계획입니다. 참고로 이 프로모션 코드는 PromoCode라는 이름의 상수에 저장될 것입니다.

그런 다음, Store 컨트롤러에서처럼 MusicStoreEntities 클래스의 인스턴스를 담을 storeDB라는 이름의 필드를 선언합니다. 이 MusicStoreEntities 클래스를 사용하려면 클래스에 MvcMusicStore.Models 네임스페이스를 참조하는 using 구문도 추가해야 합니다. 여기까지 작업을 마치고 나면 Checkout 컨트롤러의 처음 부분의 코드는 다음과 비슷한 모습일 것입니다.

using System; 
using System.Linq; 
using System.Web.Mvc; 
using MvcMusicStore.Models; 

namespace MvcMusicStore.Controllers 
{ 
    [Authorize] 
    public class CheckoutController : Controller 
    { 
        MusicStoreEntities storeDB = new MusicStoreEntities(); 
        const string PromoCode = "FREE";

이 Checkout 컨트롤러는 다음과 같은 컨트롤러 액션들을 갖게 됩니다:

  • AddressAndPayment GET 메서드는 사용자들이 자신의 정보를 입력할 수 있는 폼을 출력해줍니다.
  • AddressAndPayment POST 메서드는 사용자들이 입력한 정보들의 유효성 검사를 수행하고 주문을 처리합니다.
  • Complete 메서드는 사용자들이 결제 과정을 정상적으로 마치고 난 뒤에 나타나는 주문 확인 뷰를 출력해줍니다. 이 뷰에는 주문 확인에 필요한 사용자의 주문 번호가 나타납니다.

먼저, 컨트롤러가 생성될 때 자동으로 만들어진 Index 컨트롤러 액션의 이름을 AddressAndPayment로 변경합니다. 이 컨트롤러 액션은 단지 결제(Checkout) 폼을 출력하는 작업만 수행하므로 모델 정보가 필요 없습니다.

// 
// GET: /Checkout/AddressAndPayment 
public ActionResult AddressAndPayment() 
{ 
    return View(); 
}

그리고, AddressAndPayment POST 메서드는 StoreManager 컨트롤러 등에서 지금까지 사용했던 것과 동일한 패턴을 따르게 됩니다. 즉, 제출된 폼을 받아서 주문을 완료하려고 시도하고, 만약 주문에 실패하면 폼을 다시 출력하게 됩니다.

다음의 예제 코드에서는 폼 입력이 주문에 적합한지 유효성 검사를 수행하기 위해서 PromoCode 폼 값을 직접 검사하고 있습니다. 모든 정보가 올바른 경우, 갱신된 주문 정보를 저장하고, ShoppingCart 개체에게 주문을 완료하도록 지시한 다음, Complete 액션으로 사용자를 재전송합니다.

// 
// POST: /Checkout/AddressAndPayment 
[HttpPost] 
public ActionResult AddressAndPayment(FormCollection values) 
{ 
    var order = new Order(); 
    TryUpdateModel(order); 

    try 
    { 
        if (string.Equals(values["PromoCode"], PromoCode, 
            StringComparison.OrdinalIgnoreCase) == false) 
        { 
            return View(order); 
        } 
        else 
        {  
            order.Username = User.Identity.Name; 
            order.OrderDate = DateTime.Now; 
   
            // 주문을 저장합니다. 
            storeDB.Orders.Add(order); 
            storeDB.SaveChanges(); 

            // 주문을 처리합니다. 
            var cart = ShoppingCart.GetCart(this.HttpContext); 
            cart.CreateOrder(order); 

            return RedirectToAction("Complete", new { id = order.OrderId }); 
        } 
    } 
    catch 
    { 
        // 유효하지 않음 - 오류 메시지와 함께 폼을 다시 출력합니다. 
        return View(order); 
    } 
}

이처럼 정상적으로 결제 처리가 완료되고 나면 Complete 컨트롤러 액션으로 사용자가 재전송되는데, 이 액션에서는 확인을 위한 주문 번호를 보여주기 전에, 해당 주문이 실제로 현재 로그인 한 사용자의 주문인지 여부를 확인하는 간단한 검사를 수행합니다.

// 
// GET: /Checkout/Complete 
public ActionResult Complete(int id) 
{ 
    // 로그인한 고객의 주문인지 여부를 확인합니다. 
    bool isValid = storeDB.Orders.Any( 
        o => o.OrderId == id && o.Username == User.Identity.Name); 

    if (isValid) 
    { 
        return View(id); 
    } 
    else 
    { 
        return View("Error"); 
    } 
}
노트: Error 뷰는 프로젝트를 만들 때, /Views/Shared 폴더에 자동으로 만들어집니다.

모든 작업을 마친 완전한 Checkout 컨트롤러의 코드는 다음과 같습니다:

using System; 
using System.Linq; 
using System.Web.Mvc; 
using MvcMusicStore.Models; 

namespace MvcMusicStore.Controllers 
{ 
    [Authorize] 
    public class CheckoutController : Controller 
    { 
        MusicStoreEntities storeDB = new MusicStoreEntities(); 
        const string PromoCode = "FREE"; 

        // 
        // GET: /Checkout/AddressAndPayment 
        public ActionResult AddressAndPayment() 
        { 
            return View(); 
        } 

        // 
        // POST: /Checkout/AddressAndPayment 
        [HttpPost] 
        public ActionResult AddressAndPayment(FormCollection values) 
        { 
            var order = new Order(); 
            TryUpdateModel(order); 

            try 
            { 
                if (string.Equals(values["PromoCode"], PromoCode, 
                    StringComparison.OrdinalIgnoreCase) == false) 
                { 
                    return View(order); 
                } 
                else 
                {  
                    order.Username = User.Identity.Name; 
                    order.OrderDate = DateTime.Now; 
   
                    // 주문을 저장합니다. 
                    storeDB.Orders.Add(order); 
                    storeDB.SaveChanges(); 

                    // 주문을 처리합니다. 
                    var cart = ShoppingCart.GetCart(this.HttpContext); 
                    cart.CreateOrder(order); 

                    return RedirectToAction("Complete", new { id = order.OrderId }); 
                } 
            } 
            catch 
            { 
                // 유효하지 않음 - 오류 메시지와 함께 폼을 다시 출력합니다. 
                return View(order); 
            } 
        } 

        // 
        // GET: /Checkout/Complete 
        public ActionResult Complete(int id) 
        { 
            // 로그인한 고객의 주문인지 여부를 확인합니다. 
            bool isValid = storeDB.Orders.Any( 
                o => o.OrderId == id && o.Username == User.Identity.Name); 

            if (isValid) 
            { 
                return View(id); 
            } 
            else 
            { 
                return View("Error"); 
            } 
        } 
    } 
}

AddressAndPayment 뷰 추가하기

이제 AddressAndPayment 뷰를 작성해보겠습니다. AddressAndPayment 컨트롤러의 액션들 중 하나를 마우스 오른쪽 버튼으로 클릭한 다음, 다음과 같이 Edit 템플릿을 사용해서 Order 모델 클래스에 대한 강력한 형식인 뷰를 AddressAndPayment라는 이름으로 추가합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image102.png

이 뷰에서는 StoreManager Edit 뷰를 작성하면서 살펴봤던 다음과 같은 두 가지 기법들을 사용하게 됩니다:

  • Html.EditorForModel() 도우미 메서드를 사용해서 Order 모델의 폼 필드들을 출력합니다.
  • Order 클래스에 적용된 유효성 검사 어트리뷰트들을 이용해서 유효성 검사 규칙을 적용합니다.

먼저, Html.EditorForModel() 도우미 메서드를 사용하도록 폼 코드를 수정한 다음, 프로모션 코드를 입력하기 위한 별도의 텍스트박스를 추가합니다. 작업을 마친 완전한 AddressAndPayment 뷰 템플릿의 코드는 다음과 같습니다.

@model MvcMusicStore.Models.Order 
@{  
    ViewBag.Title = "Address And Payment";  
} 

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" 
    type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" 
    type="text/javascript"></script>  

@using (Html.BeginForm()) { 
      
    <h2>Address And Payment</h2> 
    <fieldset> 
        <legend>Shipping Information</legend>  
        @Html.EditorForModel() 
    </fieldset> 
    <fieldset> 
        <legend>Payment</legend> 
        <p>We're running a promotion: all music is free  with the promo code: "FREE"</p> 
        <div class="editor-label">  
            @Html.Label("Promo Code") 
        </div> 
        <div class="editor-field"> 
            @Html.TextBox("PromoCode") 
        </div> 
    </fieldset> 
      
    <input type="submit" value="Submit Order" /> 
}

Order 모델 클래스에 유효성 검사 규칙 선언하기

뷰가 준비되었으므로 Album 모델에 적용했던 것과 동일한 방식으로 Order 모델에 유효성 검사 규칙을 설정해보도록 하겠습니다. 이번에는 Album 모델에 적용했던 유효성 검사 어트리뷰트들 뿐만아니라, 전자우편 주소에 대한 유효성 검사를 수행하기 위한 RegularExpression 어트리뷰트도 사용해봅니다.

using System.Collections.Generic; 
using System.ComponentModel; 
using System.ComponentModel.DataAnnotations; 
using System.Web.Mvc; 

namespace MvcMusicStore.Models 
{ 
    [Bind(Exclude = "OrderId")] 
    public partial class Order 
    { 
        [ScaffoldColumn(false)] 
        public int OrderId { get; set; } 

        [ScaffoldColumn(false)] 
        public System.DateTime OrderDate { get; set; } 

        [ScaffoldColumn(false)] 
        public string Username { get; set; } 

        [Required(ErrorMessage = "First Name is required")] 
        [DisplayName("First Name")] 

        [StringLength(160)] 
        public string FirstName { get; set; } 

        [Required(ErrorMessage = "Last Name is required")] 
        [DisplayName("Last Name")] 
        [StringLength(160)] 
        public string LastName { get; set; } 

        [Required(ErrorMessage = "Address is required")] 
        [StringLength(70)] 
        public string Address { get; set; } 

        [Required(ErrorMessage = "City is required")] 
        [StringLength(40)] 
        public string City { get; set; } 

        [Required(ErrorMessage = "State is required")] 
        [StringLength(40)] 
        public string State { get; set; } 

        [Required(ErrorMessage = "Postal Code is required")] 
        [DisplayName("Postal Code")] 
        [StringLength(10)] 

        public string PostalCode { get; set; } 
        [Required(ErrorMessage = "Country is required")] 
        [StringLength(40)] 

        public string Country { get; set; } 
        [Required(ErrorMessage = "Phone is required")] 
        [StringLength(24)] 

        public string Phone { get; set; } 
        [Required(ErrorMessage = "Email Address is required")] 
        [DisplayName("Email Address")] 
         
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", 
            ErrorMessage = "Email is is not valid.")] 
        [DataType(DataType.EmailAddress)] 
        public string Email { get; set; } 

        [ScaffoldColumn(false)] 
        public decimal Total { get; set; } 
        public List<OrderDetail> OrderDetails { get; set; } 
    } 
}

이제 정보를 누락하거나 잘못된 정보를 입력하고 폼을 제출하려고 시도하면, 클라이언트 측 유효성 검사에 의해 오류 메시지가 나타나게 됩니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image103.png

이제 결제 과정에 필요한 대부분의 작업들을 마쳤습니다. 마지막으로 간단한 뷰를 하나 추가하고 Error 뷰를 보완하기만 하면 작업이 마무리됩니다.

Complete 뷰 추가하기

이 Complete 뷰는 매우 단순한데, 그저 확인을 위한 주문 번호만 출력해주면 됩니다. Complete 컨트롤러 액션을 마우스 오른쪽 버튼으로 클릭한 다음, int에 대해 강력한 형식인 Complete라는 이름의 뷰를 추가합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image104.png

그리고, 다음과 같이 뷰의 코드를 수정해서 주문 번호를 출력합니다.

@model int  
@{  
    ViewBag.Title = "Checkout Complete";  
} 

<h2>Checkout Complete</h2> 
<p>Thanks for your order! Your order number is: @Model</p> 
<p>How about shopping for some more music in our 
    @Html.ActionLink("store", "Index", "Home") 
</p>

Error 뷰 수정하기

기본 템플릿에는 /Views/Shared 폴더에 Error 뷰가 포함되어 있어서, 사이트 내부 어디에서나 재사용이 가능합니다. 다만 이 Error 뷰는 너무 단순한 오류 정보만 보여주는데다가 레이아웃도 사용하지 않으므로 이 부분을 보완해보도록 하겠습니다.

이 뷰는 범용적인 오류 페이지이기 때문에 내용이 너무 단순합니다. 메시지를 추가하고 사용자가 직전의 액션을 다시 한 번 시도해볼 수 있도록 히스토리 상의 이전 페이지로 이동할 수 있는 링크를 추가합니다.

@{
    ViewBag.Title = "Error";
}

<h2>Error</h2> 

<p> 
    We're sorry, we've hit an unexpected error. 
    <a href="javascript:history.go(-1)">Click here</a>   
    if you'd like to go back and try that again. 
</p>

파트 10: 탐색 및 사이트 디자인 최종 수정, 결론

MVC 뮤직 스토어 응용 프로그램은 ASP.NET MVC와 Visual Studio for Web Development를 소개하고, 그 사용법을 단계별로 살펴보기 위한 자습용 응용 프로그램으로, 온라인 음반 판매 기능을 비롯하여, 기초적인 사이트 관리, 사용자 로그인, 장바구니 기능 등이 구현된 간단한 전자상거래 사이트 예제입니다.

본 자습서 시리즈에서는 ASP.NET MVC 뮤직 스토어 응용 프로그램을 구축하기 위해서 필요한 모든 단계들을 자세하게 살펴볼 것입니다. 이번 파트 10에서는 탐색 및 사이트 디자인을 수정해보고 마지막으로 결론을 정리하도록 하겠습니다.

대부분의 핵심적인 MVC 뮤직 스토어 기능들은 구현이 모두 끝났지만, 사이트 탐색과 홈 페이지, 그리고 Store 영역의 Browse 페이지에 추가해야 할 기능들이 몇 가지 남아 있습니다.

장바구니 요약 파샬 뷰 작성하기

먼저, MVC 뮤직 스토어에서 모든 페이지에서 사용자의 장바구니에 담겨 있는 음반들의 갯수를 보여주고자 합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image105.png

이 기능은 파샬 뷰를 작성한 다음, 이를 레이아웃에 추가해서 손쉽게 구현할 수 있습니다.

이전 파트에서 살펴봤던 것처럼, ShoppingCart 컨트롤러에는 파샬 뷰를 반환하는 CartSummary 액션 메서드가 이미 다음과 같이 존재합니다:

// 
// GET: /ShoppingCart/CartSummary 
[ChildActionOnly] 
public ActionResult CartSummary() 
{ 
    var cart = ShoppingCart.GetCart(this.HttpContext); 
    ViewData["CartCount"] = cart.GetCount(); 
    return PartialView("CartSummary"); 
}

이 액션 메서드에 대한 CartSummary 파샬 뷰를 작성하려면 마우스 오른쪽 버튼으로 Views/ShoppingCart 폴더를 클릭한 다음, Add View를 선택합니다. 그리고, 대화 상자에서 다음과 같이 뷰의 이름을 CartSummary로 지정하고 "Create a partial view" 체크박스를 선택합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image106.png

이 CartSummary 파샬 뷰의 내용은 매우 간단합니다. 클릭하면 장바구니의 Index 뷰로 이동하고 장바구니에 담겨 있는 음반의 갯수를 보여주는 링크일 뿐입니다. 완전한 CartSummary.cshtml 뷰의 코드는 다음과 같습니다:

@Html.ActionLink("Cart (" + ViewData["CartCount"] + ")",  
    "Index",  
    "ShoppingCart",  
    new { id = "cart-status" })

레이아웃에서 Html.RenderAction 도우미 메서드를 사용하면 이 파샬 뷰를 MVC 뮤직 스토어의 모든 페이지에 출력할 수 있습니다. 이 때, 다음과 같이 액션 이름("CartSummary")과 컨트롤러 이름("ShoppingCart")을 전달해줘야 합니다.

@Html.RenderAction("CartSummary", "ShoppingCart")

그러나, 레이아웃을 변경하기 전에, 먼저 레이아웃에 추가할 또 다른 파샬 뷰인 장르 메뉴까지 준비한 다음, 한 번에 사이트의 모든 페이지를 수정해보겠습니다.

장르 메뉴 파샬 뷰 작성하기

MVC 뮤직 스토어에 존재하는 모든 장르들의 목록을 제공하는 장르 메뉴를 제공하면 사용자들이 훨씬 편리하게 사이트를 탐색할 수 있을 것입니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image107.png

먼저, CartSummary 파샬 뷰와 동일한 방식으로 GenreMenu 파샬 뷰를 작성한 다음, 두 파샬 뷰를 레이아웃에 한 번에 추가해보도록 하겠습니다. 일단, 다음의 GenreMenu 컨트롤러 액션을 Store 컨트롤러에 추가합니다:

// 
// GET: /Store/GenreMenu 
[ChildActionOnly] 
public ActionResult GenreMenu() 
{ 
    var genres = storeDB.Genres.ToList(); 
    return PartialView(genres); 
}

이 액션은 잠시 뒤에 작성할 파샬 뷰에 출력될 장르들의 목록을 반환해줍니다.

노트: 이 컨트롤러 액션에는 파샬 뷰 전용임을 나타내는 [ChildActionOnly] 어트리뷰트가 지정되어 있습니다. 그 결과, 브라우저에서 /Store/GenreMenu URL에 접급해서 이 컨트롤러 액션을 실행하는 것이 불가능합니다. 모든 파샬 뷰에 반드시 이 어트리뷰트를 지정해야만 하는 것은 아니지만, 컨트롤러 액션이 우리들이 의도하는 대로 동작할 것이라는 점을 확신할 수 있다는 면에서 대체로 좋은 접근방식입니다. 또한, 뷰가 아닌 파샬 뷰를 반환하고 있기 때문에, 뷰 엔진이 이 뷰는 다른 뷰에 포함되는, 레이아웃을 적용하면 안되는 뷰라는 사실을 인식할 수 있게 됩니다.

다음과 같이 GenreMenu 컨트롤러 액션을 마우스 오른쪽 버튼으로 클릭한 다음, Genre 모델 클래스에 대해 강력한 형식인 이름이 GenreMenu인 파샬 뷰를 작성합니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image108.png

그리고, GenreMenu 파샬 뷰의 코드를 다음과 같이 수정하여 정렬되지 않은 목록을 이용해서 항목들을 출력합니다.

@model IEnumerable<MvcMusicStore.Models.Genre> 
<ul id="categories"> 
    @foreach (var genre in Model) 
    { 
        <li> 
            @Html.ActionLink(genre.Name, "Browse", "Store",  new { Genre = genre.Name }, null) 
        </li> 
    } 
</ul>

레이아웃을 수정해서 파샬 뷰 출력하기

이제 사이트의 레이아웃(/Views/Shared/_Layout.cshtml)에서 Html.RenderAction() 도우미 메서드를 호출하는 방식으로 파샬 뷰를 추가할 수 있습니다. 다음과 같이 두 가지 파샬 뷰를 비롯한 약간의 추가적인 마크업을 함께 추가합니다:

<!DOCTYPE html> 
<html> 
<head> 
    <title>@ViewBag.Title</title> 
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> 
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script> 
</head> 
<body> 
    <div id="header"> 
        <h1><a href="/">ASP.NET MVC MUSIC STORE</a></h1> 
        <ul id="navlist"> 
            <li class="first"> 
                <a href="@Url.Content("~")" id="current">Home 
            </a></li> 
            <li><a href="@Url.Content("~/Store/")">Store</a></li> 
            <li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li> 
            <li><a href="@Url.Content("~/StoreManager/")">Admin</a></li> 
        </ul> 
    </div>  
    @{Html.RenderAction("GenreMenu", "Store");} 
    <div id="main">  
        @RenderBody() 
    </div>  
    <div id="footer">  
        built with <a href="http://asp.net/mvc">ASP.NET MVC 3</a> 
    </div> 
</body> 
</html>

이제 응용 프로그램을 다시 실행시켜보면, 페이지 좌측 탐색 영역의 장르 목록과 페이지 상단의 장바구니 요약 정보를 확인하실 수 있습니다.

역주 이 코드를 복사해서 레이아웃 파일에 붙여 넣는 방식으로 레이아웃의 기존 코드를 모두 변경한 경우에는, 프로젝트의 Scripts 폴더에 실제로 존재하는 jQuery 자바스크립트 라이브러리의 버전과 뷰 템플릿에 참조된 버전(jquery-1.4.4.min.js)이 일치하는지 확인해보시기 바랍니다.

Store 영역의 Browse 페이지 보완하기

비록 Store 영역의 Browse 페이지는 기능적이기는 하지만 그다지 멋져보이지는 않습니다. 다음과 같이 뷰 코드(/Views/Store/Browse.cshtml)를 수정해서 보다 멋진 레이아웃으로 음반들이 나열될 수 있도록 보완합니다:

@model MvcMusicStore.Models.Genre  
@{  
    ViewBag.Title = "Browse Albums";  
} 

<div class="genre"> 
    <h3><em>@Model.Name</em> Albums</h3> 
     
    <ul id="album-list">  
        @foreach (var album in Model.Albums)  
        {  
            <li> 
                <a href="@Url.Action("Details", new { id = album.AlbumId })"> 
                    <img alt="@album.Title" src="@album.AlbumArtUrl" /> 
                    <span>@album.Title</span> 
                </a> 
            </li>  
        } 
    </ul> 
</div>

이 뷰 코드에서는 Html.ActionLink 메서드 대신 Url.Action 메서드를 사용해서 음반의 표지(Artwork)를 포함하고 있는 특수한 형태의 링크를 구현하고 있습니다.

노트: 이 예제에서는 모든 음반에 공통적인 샘플 표지를 사용합니다. 이 정보들은 데이터베이스에 저장되어 있으며 StoreManager 컨트롤러를 이용해서 수정이 가능합니다. 원한다면 얼마든지 여러분이 원하는 표지로 변경 가능합니다.

이제 임의의 장르에 대한 페이지로 이동해보면, 음반 표지와 함께 그리드 형식으로 나타나는 음반 정보들을 확인할 수 있습니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image109.png

판매 순위가 높은 음반들을 Home 페이지에 출력하기

매출 신장을 위해서 판매 순위가 높은 음반들을 Home 페이지에 보여주고자 합니다. 이 기능을 구현하기 위해서 Home 컨트롤러를 일부 수정하고 약간의 그래픽도 추가해보겠습니다.

먼저, Album 클래스에 탐색 속성을 하나 추가해서 Entity Framework에게 관련 정보를 인식시킵니다. 이 작업을 마치고 나면 Album 클래스의 마지막 몇 라인의 코드는 다음과 같은 모습일 것입니다:

 
        public virtual Genre Genre { get; set; } 
        public virtual Artist Artist { get; set; } 
        public virtual List<OrderDetail> OrderDetails { get; set; } 
    } 
}
노트: 이 작업은 System.Collections.Generic 네임스페이스를 참조하는 using 문의 추가를 필요로 합니다.

그리고, 다른 컨트롤러들처럼 Home 컨트롤러에도 MvcMusicStore.Models 네임스페이스를 참조하는 using 문과 storeDB 필드를 추가합니다. 그런 다음, 데이터베이스에서 OrderDetails를 기준으로 판매 순위가 높은 음반들을 질의하는 다음의 메서드를 추가합니다.

private List<Album> GetTopSellingAlbums(int count) 
{ 
    // 주문 상세 정보를 기준으로 판매 순위가 
    // 높은 음반들을 지정한 갯수만큼 반환합니다. 
    return storeDB.Albums 
        .OrderByDescending(a => a.OrderDetails.Count()) 
        .Take(count) 
        .ToList(); 
}

이 메서드는 컨트롤러 액션으로 사용되는 것을 방지하기 위해서 private 접근지정자로 선언되었습니다. 그리고, 본문에서는 편의를 위해서 이 메서드를 Home 컨트롤러에 선언했지만, 사실 이런 업무 로직들은 적절한 별도의 서비스 클래스로 이동하는 것이 좋습니다.

이제, Index 컨트롤러 액션을 수정해서 판매 순위가 높은 5개의 음반들을 질의한 다음, 이를 뷰로 전달할 수 있습니다.

public ActionResult Index() 
{ 
    // 판매 순위가 높은 5개의 음반들을 가져옵니다. 
    var albums = GetTopSellingAlbums(5); 

    return View(albums); 
}

작업을 마친 완전한 Home 컨트롤러의 코드는 다음과 같습니다.

using System.Collections.Generic; 
using System.Linq; 
using System.Web.Mvc; 
using MvcMusicStore.Models; 

namespace MvcMusicStore.Controllers 
{ 
    public class HomeController : Controller 
    { 
        MusicStoreEntities storeDB = new MusicStoreEntities(); 

        // 
        // GET: /Home/ 
        public ActionResult Index() 
        { 
            // 판매 순위가 높은 5개의 음반들을 가져옵니다. 
            var albums = GetTopSellingAlbums(5); 

            return View(albums); 
        } 

        private List<Album> GetTopSellingAlbums(int count) 
        { 
            // 주문 상세 정보를 기준으로 판매 순위가 
            // 높은 음반들을 지정한 갯수만큼 반환합니다. 
            return storeDB.Albums 
                .OrderByDescending(a => a.OrderDetails.Count()) 
                .Take(count) 
                .ToList(); 
        } 
    } 
}

마지막으로, Home Index 뷰를 수정해서 음반들의 목록이 나타나도록 Model 형식을 추가하고 페이지 하단에 음반 목록을 추가해야 합니다. 그리고, 이번 기회에 페이지에 헤더와 프로모션 영역도 추가합니다.

@model List<MvcMusicStore.Models.Album> 
@{  
    ViewBag.Title = "ASP.NET MVC Music Store";  
} 

<div id="promotion"> 
</div> 
<h3><em>Fresh</em> off the grill</h3> 
<ul id="album-list">  
    @foreach (var album in Model)  
    {  
        <li> 
            <a href="@Url.Action("Details", "Store", new { id = album.AlbumId })">  
                <img alt="@album.Title" src="@album.AlbumArtUrl" /> 
                <span>@album.Title</span>  
            </a> 
        </li> 
    }  
</ul>

이제 응용 프로그램을 다시 실행시켜보면, 판매 순위가 높은 음반들의 목록과 프로모션 메시지가 나타나는 수정된 Home 페이지가 나타나게 됩니다.


출처 : http://i1.asp.net/asp.net/images/mvc/msv30/image110.jpg

결론

지금까지 ASP.NET MVC를 이용해서 데이터베이스 접근, 멤버십, AJAX 등을 활용하는 세련된 웹사이트를 대단히 신속하게 구축하는 방법을 살펴봤습니다. 부디 본 자습서가 여러분이 원하는 ASP.NET MVC 응용 프로그램 구축을 시작하기 위한 도구가 될 수 있기를 바랍니다!

질문이나 의견이 있으시면 http://mvcmusicstore.codeplex.com/을 방문해주시기 바랍니다.


authored by

  TikTak
  2012-10-16(08:15)
캐릭 이미지
감사합니다.`

  davelee
  2012-10-17(11:42)
캐릭 이미지
수고하셨습니다.
  songgun
  2012-10-18(10:22)
캐릭 이미지
감사합니다. ^^;;

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

로딩 중입니다...

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