Hello Windows Azure / Twitter 스타일 방명록 만들기 #2

지난번 글 (2010/07/27 – [Cloud Development] – Hello Windows Azure / Twitter 스타일 방명록 만들기 #1)에 이어서 오늘 시간에는 ASP.NET MVC 2를 사용하는 Web Role 위에서 jQuery, jTemplate을 이용하여 기본적인 방명록 UI를 꾸며보고, 별 다른 Worker Role의 구현 없이 Windows Azure Table Storage를 경유하여 방명록의 글을 삽입, 삭제, 변경하는 기능을 구현해보기로 하겠습니다.


시작하기 전에 (2010.08.09 Update)


지난번 코드에서 누락되거나 교정될 필요가 있는 코드를 포함하여 업데이트를 할 부분이 있어 말씀을 전합니다. TwistDataSource.cs 파일의 내용을 다음과 같이 작성해야 하며, 지난번 코드에서 변경된 부분을 밑줄로 표시해두었습니다.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure;
using System.Data.Services.Client;
using Microsoft.WindowsAzure.StorageClient;


namespace TwistBook.DataModel
{
    public class TwistDataSource
    {
        private static CloudStorageAccount storageAccount;
        private TwistDataServiceContext serviceContext;


        static TwistDataSource()
        {
            // 중요: 실제로 응용프로그램을 Cloud 환경에 배포할 때에는
            // Cloud Project 내의 다른 환경 설정 문자열을 이용하도록
            // 호출을 변경해야 합니다.
            storageAccount = CloudStorageAccount.DevelopmentStorageAccount;


            CloudTableClient.CreateTablesFromModel(
                typeof(TwistDataServiceContext),
                storageAccount.TableEndpoint.AbsoluteUri,
                storageAccount.Credentials);
        }


        public TwistDataSource()
        {
            this.serviceContext = new TwistDataServiceContext(storageAccount);
            this.serviceContext.RetryPolicy = RetryPolicies.Retry(
                3, TimeSpan.FromSeconds(1));
        }


        public DataServiceResponse Insert(TwistModel model)
        {
            this.serviceContext.AddObject(
                TwistDataServiceContext.TwistModelName,
                model);


            return this.serviceContext.SaveChangesWithRetries();
        }


        public IEnumerable<TwistModel> Select()
        {
            var results = from eachTwist in this.serviceContext.TwistModel
                          select eachTwist;


            var query = new CloudTableQuery<TwistModel>(
                results as DataServiceQuery<TwistModel>,
                RetryPolicies.Retry(3, TimeSpan.FromSeconds(1)));


            return query.Execute();
        }


        public DataServiceResponse Delete(TwistModel model)
        {
            // 이 부분의 코드가 삭제되었습니다.
            this.serviceContext.DeleteObject(model);
            return this.serviceContext.SaveChanges();
        }


        public DataServiceResponse Update(TwistModel model)
       
{
           
this.serviceContext.UpdateObject(model);
           
return this.serviceContext.SaveChanges();
        }

    }
}


Web Role 완성하기


1. ASP.NET MVC 2 응용프로그램의 특성을 잘 살리기 위하여 AJAX 기술을 활용하는 방식으로 예제를 설명하고자 합니다. 이를 위하여 필요한 것이 jQuery와 jTemplate 라이브러리인데, jQuery의 경우 ASP.NET MVC 2 프로젝트를 만들면 자동으로 아래의 Scripts 디렉터리에 1.4 버전이 번들링되어있으니 별도로 받으실 필요가 없습니다.



 


자바스크립트 라이브러리들의 경우, 근래 들어서는 4GL 개발 도구들의 영향으로 Debug Version과 Release Version 라이브러리를 각기 개별적으로 제공하는 경우가 늘었습니다. jQuery도 이러한 추세를 잘 따르고 있으며, 위의 화면에서 jquery-1.4.1-vsdoc.js 파일은 Debug 목적 + Visual Web Developer용 Intellisense 지원을 위한 버전이고, jquery-1.4.1.js 파일은 원래의 소스 코드가 있는 그대로 (as-is) 제공되는 버전입니다. 그리고 jquery-1.4.1.min.js 파일은 원래의 소스 코드에서 주석과 공백 제거, 변수명 최소화와 같은 Obfuscation Process를 포함한 Minified Process를 거친 전송에 최적화된 버전입니다.


자바스크립트 전송에 필요한 대역폭을 좀 더 아낄 필요가 있고, 접속하는 브라우저들이 모두 G-ZIP 압축 해제 기능을 지원한다는 점을 확신할 수 있다면, WSFU (Windows Service For Unix)나 Cygwin, GNU for Win32 등을 통해서 액세스할 수 있는 GZIP 압축 유틸리티를 이용하여 Minified Version을 GZIP 파일로 한 번 더 묶어서 이를 다운로드하도록 구성하는 것도 좋은 선택이 될 수 있습니다. WSFU는 http://www.microsoft.com/downloads/details.aspx?FamilyID=896c9688-601b-44f1-81a4-02878ff11778&DisplayLang=en 에서 다운로드 가능합니다.


2. jTemplate은 jQuery를 기반으로 만들어진 플러그인으로 HTML이나 XML 컨텐츠를 지정된 지시자에 맞추어 반복 생성하거나, 내용을 치환하거나, 수식을 계산하는 등의 복잡한 연산 작업을 가능하게 합니다. 특히 JSON (Java Script Object Notation) 기반의 데이터를 내려보내어줄 것이므로 이러한 기능은 필수적입니다. jTemplate은 http://plugins.jquery.com/project/jTemplates 에서 다운로드받으실 수 있고, 압축 파일을 다운로드받으면 아래와 유사한 형태로 나타납니다.



3. jquery-jtemplates.js 파일을 선택하여 ASP.NET MVC 2 프로젝트의 Scripts 디렉터리 아래로 복사합니다. jQuery 라이브러리와 같은 위치에 배치하여 불러오기 쉽도록 만들기 위한 선택입니다.



4. Visual Studio 솔루션 탐색기에서 방금 압축 해제한 jTemplate 라이브러리의 소스 코드를 추가해야 합니다. 솔루션 탐색기에서 Web Role 프로젝트 아래의 Scripts 디렉터리를 아래 그림과 같이 클릭하고 상단 도구 모음의 “모든 파일 표시” 버튼을 클릭하면 아직 등록되지 않은 jTemplate 라이브러리의 파일이 나타납니다.




 


5. jquery.jtemplates.js 파일을 오른쪽 버튼으로 클릭하고 “프로젝트에 포함” 메뉴를 클릭하면 솔루션의 일부로 편입됩니다. 이 때, jquery.jtemplates.js 파일을 오른쪽 버튼으로 클릭하고 속성 메뉴를 선택하여 나타나는 속성 창에서 빌드 작업이 “내용”으로 선택되어있는지 반드시 확인하여 주세요. “내용”으로 선택되어있지 않은 파일은 실제 배포 때 제외될 수도 있습니다.







6. 이제 마스터 페이지에 jQuery와 jTemplate 라이브러리를 추가해야 합니다. 여기서 마스터 페이지란 페이지 전반에 걸쳐서 기본 바탕이 되는 ASP.NET 사이트 수준의 골격 템플릿입니다. PowerPoint의 마스터 슬라이드와 비슷한 개념으로 이해해도 됩니다. 마스터 페이지는 Views 폴더 아래의 Shared 폴더 아래의 Site.Master 파일이며 아래와 같은 위치에 나타납니다.




7. Site.Master 파일을 열어서 아래와 같이 수정합니다. 원래 내용에서 수정된 부분을 굵게 표시하였으며 자세한 내용은 각주를 참조하여 주십시오.


<%@ Master Language=”C#” Inherits=”System.Web.Mvc.ViewMasterPage” %>


<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd“>
<html xmlns=”http://www.w3.org/1999/xhtml“>
<head runat=”server”>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″/>
    <title><asp:ContentPlaceHolder ID=”TitleContent” runat=”server” /></title>
    <link href=”<%= Url.Content(“~/Content/Site.css”) %>” rel=”stylesheet” type=”text/css” /> [footnote]기본으로 제공되는 템플릿 코드로부터 수정한 부분으로, 마스터 페이지는 처리 과정 도중에 해석되는 파일이지만 브라우저의 입장에서 서비스를 하는 페이지가 아니기 때문에, 기본으로 제공되는 경로인 ../../Content/Stie.css는 잘못 해석될 가능성이 있습니다.

이를 예방하기 위하여 Inline Expression을 사용하여 Url.Content 메서드로 정확한 경로를 다시 가져오도록 만든 것입니다.[/footnote]
    http://%=Url.Content( [footnote]기본으로 제공되는 템플릿 코드로부터 수정한 부분으로, 마스터 페이지는 처리 과정 도중에 해석되는 파일이지만 브라우저의 입장에서 서비스를 하는 페이지가 아니기 때문에, 기본으로 제공되는 경로인 ../../Content/jquery-1.4.1.min.js는 잘못 해석될 가능성이 있습니다.

이를 예방하기 위하여 Inline Expression을 사용하여 Url.Content 메서드로 정확한 경로를 다시 가져오도록 만든 것입니다.[/footnote]
    http://%=Url.Content( [footnote]이번 시간에 JSON 기반의 데이터를 표현하기 위하여 사용할 jTemplate 라이브러리를 여기에서 지정합니다. 앞의 URL들과 마찬가지의 방법을 사용하여 정확하게 경로가 참조될 수 있도록 만들어줍니다.[/footnote]

</head>


<body>
   

<

div class=”page”>


       

<

div id=”header”>
           

               

내 MVC 응용 프로그램

           

             
           

               
           

           
           

        </div>


       

<

div id=”main”>
           


           

        </div>
    </div>
</body>
</html>


8. 웹 페이지를 위한 기본 준비는 끝났습니다. 이제 Twitter Style의 방명록을 입력받을 수 있고 보여줄 수 있는 서비스를 만들기 위하여 서비스의 중심이 되는 Controller를 구성해보도록 하겠습니다. 편의를 위하여 HomeController를 편집하도록 하겠습니다. 솔루션 탐색기에서 TwistBook.WebRole 프로젝트의 Controllers 폴더 아래의 HomeController.cs 파일을 아래 그림과 같이 선택하여 엽니다.




9. ASP.NET MVC에서 컨트롤러 내에서 Public 접근자로 노출된 각각의 Method는 이전의 ASP.NET Web Form에 비유하였을 때 개별 처리기 (ASHX 파일)에서 웹 페이지를 결정하여 내보내는 것과 같은 개념으로 최초에 사용자가 페이지에 접근할 때나, 페이지의 FORM 태그로부터 응답이 되돌아온 시점에서 모두 사용이 가능합니다. 이러한 특성을 바탕으로, HomeController는 그 자체로 API의 역할을 수행할 수 있으며 역으로 페이지를 렌더링하기 위한 컨텐츠 단위로서의 역할도 수행이 가능합니다.


HomeController.cs 파일의 내용을 아래와 같이 수정합니다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using TwistBook.DataModel;


namespace TwistBook.WebRole.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        public HomeController()
            : base()
        {
        }


        public ActionResult Index()
        {
            ViewData[“Message”] = “Windows Azure 방명록 예제”;
            return View(“Index”); [footnote]기본 Index 메서드에서는 return View(); 로 호출하였지만, 다른 Controller Method에서 이 메서드를 호출하게 되는 경우, Index View가 아닌 Controller 그 자신의 View를 찾도록 기본 설계가 구성되어있기 때문에, 이를 방지하고 재 사용하기 쉬운 형태로 만들기 위해 특별히 “Index”라는 뷰 이름을 찾도록 명시한 것입니다.[/footnote]
        }


        [HttpPost] [footnote]POST 요청에 의해서만 메시지가 JSON 형식으로 내려가도록 구성하기 위한 것으로, 필요에 따라이 Attribute를 누락하고, 아래의 Json 메서드 호출에서 AllowGet 인자를 지정하면 GET 요청에 의해서도 조회 결과가 JSON으로 반환될 수 있습니다.[/footnote]
        public ActionResult RetrieveMessages()
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;
            var dataSource = new TwistDataSource();


            var results = from eachItem in dataSource.Select()
                          orderby eachItem.WrittenDate descending
                          select eachItem;


            return Json(results); [footnote]ActionResult 클래스를 상위 클래스로 두는 JSON Serialization Result 객체를 반환합니다.[/footnote]
        }


        [HttpPost]
        public ActionResult AddMessage(string name, string message, string imageUrl)
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;
            var dataSource = new TwistDataSource();
            dataSource.Insert(new TwistModel()
            {
                WriterName = name,
                WrittenDate = DateTime.Now,
                MessageBody = message,
                ImageUrl = imageUrl
            }); [footnote]데이터 삽입 직후 Commit 연산까지 한번에 처리하도록 설계된 메서드를 부르는 것입니다.[/footnote]


            return Index(); [footnote]중요: 이 함수의 결과로 나타나는 View가 AddMessage가 아니라 Index입니다.[/footnote]
        }


        public ActionResult UpdateMessage(string partitionKey, string rowKey) [footnote]동일한 메서드에 대한 오버로드이지만, GET 방식으로 호출될 수 있고, 인자를 2개를 받도록 구성되어있으므로 이 버전의 메서드에서는 View를 렌더링하는데 사용됩니다.[/footnote]
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;
            var dataSource = new TwistDataSource();
            var results = from eachItem in dataSource.Select()
                          where eachItem.PartitionKey == partitionKey
                          where eachItem.RowKey == rowKey
                          select eachItem;


            ViewData[“PartitionKey”] = partitionKey;
            ViewData[“RowKey”] = rowKey; [footnote]ViewData 컬렉션에 Update 동작을 구현하기 위해 필요한 정보를 다시 전달합니다. 나중에 View에서 이 정보를 참조하여 페이지를 렌더링하게 됩니다.[/footnote]


            if (results.Count() > 0)
            {
                var result = results.First();
                ViewData[“Name”] = result.WriterName;
                ViewData[“Message”] = result.MessageBody; [footnote]조회된 결과를 페이지 렌더링을 위하여 ViewData 컬렉션에 보관합니다.[/footnote]
            }


            return View();
        }


        [HttpPost]
        public ActionResult UpdateMessage(string partitionKey, string rowKey, string name, string message, string imageUrl) [footnote]동일한 버전의 UpdateMessage 메서드에 대한 오버로드이지만, POST 요청에만 동작하도록 설계된 버전의 Controller Method입니다.[/footnote]
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;
            var dataSource = new TwistDataSource();
            var results = from eachItem in dataSource.Select()
                          where eachItem.PartitionKey == partitionKey
                          where eachItem.RowKey == rowKey
                          select eachItem;


            if (results.Count() > 0)
            {
                var result = results.First();


                if (result != null)
                {
                    result.WriterName = name;
                    result.MessageBody = message;
                    result.WrittenDate = DateTime.Now;
                    result.ImageUrl = imageUrl;
                    dataSource.Update(result);
                    return View(“PopupUpdateView”); [footnote]편집을 마친 후, 미리 구성된 PopupUpdateView를 찾아 이동합니다. 이 뷰는 팝업창 형태로 열린 편집 창을 닫고, 팝업 창의 부모 (window.opener)를 새로 고침하도록 디자인된 뷰입니다.[/footnote]
                }
                else
                    return View(“PopupUpdateFailView”);
            }
            else
                return View(“PopupUpdateFailView”); [footnote]업데이트에 실패할 경우 보여줄 View를 지정합니다.[/footnote]
        }


        public ActionResult DeleteMessage(string partitionKey, string rowKey)
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;
            var dataSource = new TwistDataSource();
            var results = from eachItem in dataSource.Select()
                          where eachItem.PartitionKey == partitionKey
                          where eachItem.RowKey == rowKey
                          select eachItem;


            if (results.Count() > 0)
            {
                dataSource.Delete(results.First());
                return Index(); [footnote]데이터 삭제 후 Index 뷰를 다시 로드하도록 만들었습니다.[/footnote]
            }
            else
                return Index();
        }


        public ActionResult About()
        {
            return View();
        }
    }
}


10. 방명록의 기본 기능을 만들기 위하여 이제 Views 폴더 아래의 Home 폴더 아래의 Index.aspx 파일을 열어서 편집해야 합니다. 아래 그림과 같은 위치에 존재합니다.




<%@ Page Language=”C#” MasterPageFile=”~/Views/Shared/Site.Master” Inherits=”System.Web.Mvc.ViewPage” %>


<asp:Content ID=”Content1″ ContentPlaceHolderID=”TitleContent” runat=”server”>
    홈 페이지
</asp:Content>


<asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>
   
        $(document).ready(function () {
            $.ajax({
                type: ‘POST’, [footnote]XmlHttpRequest 객체를 이용하여 전송할 때 POST 방식으로 요청하는 것을 명시하고 있습니다.[/footnote]
                url: ”, [footnote]RetrieveMessages Controller Method를 정확히 찾을 수 있도록 전체 경로를 반환하는 함수를 사용하여 스크립트 위에 렌더링합니다.[/footnote]
                data: ‘{}’,
                contentType: ‘application/json; charset=utf-8’,
                dataType: ‘json’, [footnote]JSON 방식의 결과 집합이 필요함을 명시하고, JSON 방식으로 데이터를 받아들이도록 구성하고 있습니다.[/footnote]
                success: function (data) {
                    var targetDiv = $(‘#guestbookList’);  [footnote]jTemplate 엔진으로 치환된 내용을 렌더링할 대상 div element를 찾습니다.[/footnote]
                    targetDiv.setTemplate($(‘#templateContent’).html()); [footnote]기준이 되는 템플릿 컨텐츠를 로드합니다. 이스케이프 문자로 복잡하게 처리하지 않고 편리하게 다룰 수 있도록 만들기 위하여, JavaScript나 VBScript로 해석되지 않도록 처리한 별도의 SCRIPT element로부터 로드하도록 구성하였습니다.[/footnote]
                    targetDiv.processTemplate(data); [footnote]jTemplate 엔진을 이용하여 주어진 데이터를 통해 렌더링을 시작합니다.[/footnote]
                }
            });
        });
   
   
   
    {#foreach $T as record}
   

       
       

           

RT @{$T.record.WriterName} {$T.record.MessageBody}

           

{$T.record.WrittenDate} via cloud

            ?partitionKey={$T.record.PartitionKey}&rowKey={$T.record.RowKey}’, ‘editWindow’, ‘location=1,status=1,scrollbars=1,width=300,height=200’);”>편집
             | 
            ?partitionKey={$T.record.PartitionKey}&rowKey={$T.record.RowKey}” target=”_self”>삭제
       

       

   

    {#/for}
    [footnote]렌더링에 필요한 템플릿 코드가 이곳에 기술됩니다. 이 부분은 스크립트 태그 안에 있지만 스크립트 해석기에 의하여 처리되지는 않으며, 또한 시각적으로 드러나지도 않습니다. (as-is string으로 해석됩니다.)[/footnote]


    <h2><%= ViewData[“Message”] %></h2>
   

<

div>
       

                           { %> [footnote]메시지를 추가하기 위한 form 데이터를 구성하고 있습니다.[/footnote]
              
               [footnote]Controller Method의 name 매개 변수와 이름을 같게 지정합니다.[/footnote]
              


              
              
[footnote]Controller Method의 message 매개 변수와 이름을 같게 지정합니다.[/footnote]


                 [footnote]전송 버튼과 초기화 버튼이 trigger 역할을 하여 데이터를 전송하거나 리셋하는 역할을 합니다.[/footnote]
              

           
       

        <br /><br />
       

 [footnote]방명록 목록은 이 요소 아래에 rendering 될 것입니다.[/footnote]
    </div>
</asp:Content>


11. 방명록 내용을 편집하기 위한 팝업 창을 위한 뷰와, 댓글 편집이 끝난 뒤 취할 동작을 프로그래밍한 자바스크립트 코드를 위한 뷰는 Partial View로 디자인해야 합니다. 이 중에서 우선 방명록 항목 편집을 위한 Partial View를 추가하기 위해, 솔루션 탐색기에서 Views 디렉터리 아래의 Home 디렉터리를 오른쪽 버튼으로 클릭하고, View 추가 메뉴를 아래 그림과 같이 선택합니다.




12. View의 이름은 UpdateMessage로 지정하고, Partial View에 체크하여 아래 대화 상자와 같이 옵션을 구성한 후 확인 버튼을 클릭합니다.




13. UpdateMessage.ascx 파일의 내용을 다음과 같이 작성합니다.


<%@ Control Language=”C#” Inherits=”System.Web.Mvc.ViewUserControl<dynamic>” %>

           { %> [footnote]UpdateMessage의 POST 전송 대상을 찾아 업데이트 작업을 수행하도록 만듭니다.[/footnote]
      
       [footnote]링크에 의하여 GET 방식으로 전달된 매개 변수를 다시 렌더링하여 재사용합니다.[/footnote]


        
       [footnote]ViewData에 저장된 기존 데이터를 꺼내옵니다.[/footnote]
      


       [footnote]ViewData에 저장된 기존 데이터를 꺼내옵니다.[/footnote]
      


         [footnote]전송 버튼과 초기화 버튼이 trigger 역할을 하여 데이터를 전송하거나 리셋하는 역할을 합니다.[/footnote]
      

   


14. 이어서 솔루션 탐색기에서 Views 디렉터리 아래의 Home 디렉터리를 오른쪽 버튼으로 클릭하고, View 추가 메뉴를 11단계에서와 같이 선택합니다.



15. View의 이름은 PopupUpdateView로 지정하고, Partial View에 체크하여 아래 대화 상자와 같이 옵션을 구성한 후 확인 버튼을 클릭합니다.




16. PopupUpdateView.ascx 파일의 내용을 다음과 같이 작성합니다.


<%@ Control Language=”C#” Inherits=”System.Web.Mvc.ViewUserControl<dynamic>” %>

    try {
        window.close(); [footnote]팝업 창을 닫습니다.[/footnote]
        if (window.opener && !window.opener.closed) {
            window.opener.location.href = ”; [footnote]팝업 부모 창이 유효하다면, 정확한 Index View의 URL을 찾아 다시 로드하도록 만듭니다.[/footnote]
        }
    } catch (ex) {
    }


17. 이어서 솔루션 탐색기에서 Views 디렉터리 아래의 Home 디렉터리를 오른쪽 버튼으로 클릭하고, View 추가 메뉴를 11단계에서와 같이 선택합니다.




18. View의 이름은 PopupUpdateFailView로 지정하고, Partial View에 체크하여 아래 대화 상자와 같이 옵션을 구성한 후 확인 버튼을 클릭합니다.




19. PopupUpdateFailView.ascx 파일의 내용을 다음과 같이 작성합니다.


<%@ Control Language=”C#” Inherits=”System.Web.Mvc.ViewUserControl<dynamic>” %>
<h3>업데이트에 실패하였습니다.</h3>
<a href=”#” onclick=”window.close()”>창 닫기</a>


20. 기본적인 방명록 글 남기기와 조회 기능이 올바르게 작동하는지 확인하기 위하여 시뮬레이터를 디버그 모드로 시작해야 합니다. 일반적인 응용프로그램 개발 때와 마찬가지로 F5키를 눌러서 디버그 모드로 시뮬레이터에 패키지를 배포하고 디버거를 연결할 수 있습니다. 이 때, 아래 그림과 같은 오류 메시지가 나타나면 관리자 권한이 아닌 상태에서 Visual Studio를 시작한 것이므로 Visual Studio를 종료한 뒤 “개발 도구 시작하기 및 프로젝트 생성하기” Chapter의 1단계를 참고하여 관리자 모드로 Visual Studio를 다시 시작해야 합니다.




21. 아래의 그림들에서처럼 기능들이 정상적으로 진행된다면 우선 이번 시간에 진행할 기본 기능들에 대한 소개와 작업이 끝난 것입니다.





이번 Article을 작성하면서 발견한 Windows Azure SDK 1.2에 대한 문제 한 가지


좀 더 완성에 가까워질수록 해결될 문제들 중에 한 가지가 될 예정이긴 하겠습니다만 실습하는 도중 불편함이 예상되어 제가 발견한 문제를 블로그 아티클을 통하여 미리 공유하고자 합니다. 간혹 Windows Azure Local Storage의 Table Storage에 아래와 같이 MBCS (Multi-Byte Character Set) 문자가 포함된 데이터를 삽입하려고 할 때 별 다른 까닭없이 HTTP/404 오류가 나타나는 경우가 있습니다.



사용자 코드에서 System.Data.Services.Client.DataServiceRequestException이(가) 처리되지 않았습니다.
  Message=이 요청을 처리하는 동안 오류가 발생했습니다.
  Source=Microsoft.WindowsAzure.StorageClient
  StackTrace:
       위치: Microsoft.WindowsAzure.StorageClient.Tasks.Task1.get_Result()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: Microsoft.WindowsAzure.StorageClient.Tasks.Task1.ExecuteAndWait()
       위치: Microsoft.WindowsAzure.StorageClient.TaskImplHelper.ExecuteImplWithRetry[T](Func2 impl, RetryPolicy policy)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: Microsoft.WindowsAzure.StorageClient.TableServiceContext.SaveChangesWithRetries(SaveChangesOptions options)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: Microsoft.WindowsAzure.StorageClient.TableServiceContext.SaveChangesWithRetries()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: TwistBook.DataModel.TwistDataSource.Insert(TwistModel model) 파일 d:users남정현documentsvisual studio 2010ProjectsTwistBookTwistBook.DataModelTwistDataSource.cs:줄 42<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: TwistBook.WebRole.Controllers.HomeController.AddMessage(String name, String message, String imageUrl) 파일 d:users남정현documentsvisual studio 2010ProjectsTwistBookTwistBook.WebRoleControllersHomeController.cs:줄 44<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: lambda_method(Closure , ControllerBase , Object[] )<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters)
       위치: System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: System.Web.Mvc.ControllerActionInvoker.&lt;&gt;c__DisplayClassd.&lt;InvokeActionMethodWithFilters&gt;b__a()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 위치: System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func1 continuation)
  InnerException: System.Data.Services.Client.DataServiceClientException
       Message=<?xml version=”1.0″ encoding=”utf-8″ standalone=”yes”?>
<error xmlns=”http://schemas.microsoft.com/ado/2007/08/dataservices/metadata“>
  <code>InvalidInput</code>
  <message xml:lang=”ko-KR”>One of the request inputs is not valid.</message>
</error>
       Source=System.Data.Services.Client
       StatusCode=400
       StackTrace:
            위치: System.Data.Services.Client.DataServiceContext.SaveResult.<HandleBatchResponse>d__1e.MoveNext()
       InnerException:






실제 Windows Azure 실행 환경에서는 이러한 현상이 나타나지 않는 것으로 보입니다. 추후, 이러한 문제점을 해결할 수 있는 방안이 발견되면 별도의 업데이트 소식을 통하여 정보가 전달될 수 있도록 하겠습니다. 예제를 기반으로 테스트 패브릭 위에서 테스트하시는 동안에는 Table Storage에 한글, 히라가나, 카타카나, 번체, 간체, 한자 등의 데이터가 들어가지 않는 범위에서 테스트가 필요할 것 같습니다.


다음 시간에는


다음 시간에는 각 Role이 어떤 방법으로 Windows Azure 환경에서 실행되는지, Web Role과 Worker Role이 Cloud Computing 환경에서 상호 작용하고 통신하는 방법을 본격적으로 소개하고, 오늘 만든 Web Role을 어떤 방식으로 수정하게 될 것이고, Worker Role이 어떤 방식으로 데이터를 교환하게 될 것인지를 보여드릴 예정입니다. 그리고 이번 시간에 언급하지 않은 BLOB Storage에 이미지를 저장하고 가져오는 방법에 대해서도 소개하겠습니다. 🙂


더운 여름 날씨에 건강 유의하시고, 활기찬 여름 보내시기 바랍니다. 감사합니다.


ps. Windows Azure Cafe (http://cafe.naver.com/wazure) 에서 2010년 8월 14일부터 본격적으로 Offline Study를 진행합니다. Windows Azure Platform의 학습에 관심있으신 개발자 여러분들의 많은 관심과 참여 부탁드리며, 아울러 Visual Studio 2010 공식 팀 블로그에서 Cloud Computing 관련 Article을 집필하실 열정적인 Blogger 여러분도 함께 모시고 있습니다. 이에 관련된 모든 상세한 내용은 Windows Azure Cafe를 통하여 저에게 연락 주시면 상세히 안내해드리겠습니다. 감사합니다. 🙂


댓글 남기기