달봉이넷 [dalbong2.net] : 위치로그 : 태그 : 방명록 : 관리자 : 새글쓰기

몇 달간 해외 어학 연수를 하기로 했다.

선수금을 지불하고 등록 절차중이다.

필리핀의 바기오라는 지방인데 좀 외진 곳인듯하다. 이곳의 "더유닛"이라는 학원에 등록했다. 

여기서 혼자서도 할 수 있는 자생력 정도까지만 키워올 생각이다.

 

또 연재를 "막무가내"로 종료해야 할 것 같다.

아직 Spring.NET에 대해서 할 것이 남았는데..쓰으.

흑흑 또 뒷심 부족이다.

 

▶ IoC 컨테이너에 대해 좀 더

지금까지의 Configuration 설정에 의해서 컨테이너에 생성된 객체들은 Singleton 객체들이었다. 즉 애플리케이션 실행동안 하나의 각 타입별로 하나의 객체만 생성된다. 그러나 때로는 singleton 타입이 아닌 객체(prototype 객체)로 설정할 수도 있다.

ProxyFactoryObject 속성

- IsSingleton

- TargetName. p.139

▶ Attribute를 이용한 AOP 프로그래밍

▶ Expression evaluaion 프레임워크

- Spring.Expressions

▶ Validation 프레임워크 - Spring.Validation

▶ NHibernate.net

▶ iBatis.NET

▶ Ajax 지원

▶ Spring.NET MVC 패턴 애플리케이션

▶ 예외 처리

▶ Localization & Message Source

▶ 실제 적용 전략

- 스마트클라이언트, 웹애플리케이션

- 실제로 기업형 애플리케이션에 적용하기 위한 전략 및 예제를 만들어본다. Good!!

 

돌아와서 할 수 있게 되길 바란다.


위로
From. BlogIcon 정성태 2008/10/24 01:01Delete / ModifyReply
와... ~~~ 어학연수를 가시는 군요. 가족이 있는 상황에서 쉽지 않은 결심이셨을 같은데. ^^ (저도 요즘 부쩍 공감하고 있는 부분으로써, 가고 싶은 마음 굴뚝같지만,,, 토끼같은 자식과 여우같은 마눌님이 저만 바라보고 있는 상황이라. ^^;)

아무쪼록 엄청난 어학실력을 쌓고 오시기를 바라고요. ^^ 나중에 건강한 모습으로, 활발한 블로그 활동 기대하겠습니다. ^^
달봉이 2008/10/30 16:23Delete / Modify
저희 집에서 저는 짐입니다. 결정은 힘들어도 행동은 쉬웠어요.
영어를 포기하든지 아니면 극복하든지 해야지. 그렇지 않으면 평생의 적으로 남을 것 같아서 결심을 하게 됐습니다.
미기적거리고 애만 태우다 시간만 가고 지금이 되어버렸어요. 이거 그대로 방치했다가는 참호전이 될 것 같아 떠나기로 했습니다. 길게는 떠나지 못하지만.
From. 2008/10/28 15:54Delete / ModifyReply
관리자만 볼 수 있는 댓글입니다.
조조 2008/10/30 15:24Delete / Modify
어어라;; 잘 갔다 오시라고 달았는데 비밀글이 되어버렸네;;
달봉이 2008/10/30 16:24Delete / Modify
ㄳ합니다.
이거 관리자 페이지로 왜 못들어가는지 모르겠어요. 비밀글은 지금 보지 못하고 있습니다.
블로그를 다시 설치해야 할 것 같은데. 시간과 환경이 되지 않는군요.

▶ Result Mapping 이란.

 

Result Mapping ! 이것이 뭐냐면 "애플리케이션의 흐름"을 제어하는 방법중의 하나다. 애플리케이션 흐름? Response.Redirect, Server.Transfer 등이 바로 애플리케이션의 흐름을 제어하는 메소드들이다.  여기서 말하는 애플리케이션의 흐름은 페이지의 수행 결과에 따라서 이 페이지 저 페이지로 리다이렉트되는 것을 말하고 있다. Spring.NET에서는 이렇게 결과에 따라서 적절한 페이지로 리다이렉트될 수 있도록 사전에 매핑을 설정할 수 있는 방법이 있다.

페이지 수행 결과 리다이렉트될 대상 페이지
"SUCCESS" OK.aspx
"FAIL" Sorry.aspx

Spring.NET에서는 이런 설정을 configuration에 포함시킬 수 있다는 것이다. 그리고 실제의 aspx.cs에서는 Response.Redirect, Server.Transfer 메소드를 없앤다. Spring.NET이 지원하는 Result Mapping 방법을 이용하면 Controller가 직접 다른 View를 참조할 필요가 없다. 예를 들어 만약 수행 결과가 "SUCCESS" 인 경우 "success.aspx" 페이지로 리다이렉트되어야 한다면 OK.apsx로 설정되어 있는 내용을 success.aspx로 변경만 하면 된다. success.aspx로 넘겨야 하는 파라미터가 있다면 그 값도 configuration 할 수 있다.

 

▶ 기존 방법의 MVC 패턴 위배

 

aspx.cs에서 이런 메소드를 사용해서 직접 개발자가 페이지를 대상 페이지로 리다이렉트시키는 방법은 앞에서 말한 MVC 패턴에도 맞지 않는 부분이 있다. 앞에서 말한 MVC 패턴에서는 개발 작업에서 UI를 비즈니스 로직에서 분리시키는 것이 중요한 목표였다. 그러나  Controller 즉 Page 객체에서 직접 Redirect, Transfer 메소드를 사용하면 다음과 같은 상태가 되고 만다.

image

이런 상태가 되면 애플리케이션의 흐름이 변경된 경우 재 컴파일이 필요하고 필요하면 테스트, 배포 작업도 다시 해야 할 수 있다. 

 

▶ 수행 결과 추상화 -  Result  클래스

 

Spring.NET에서는 페이지의 수행 결과를 Spring.Web.Support 네임스페이스의 Result 타입으로 추상화하고 있다. 예를 들어 페이지 수행 결과 "SUCCESS", "FAIL"을 표현할 수 있는 객체이다.

다음과 같은 시나리오를 생각해보자. 사용자 등록 페이지 UserRegistration.aspx가 있다고 하자. 이 페이지에서 저장 버튼을 클릭하면 사용자 정보를 저장하고 나서 홈 페이지 Default.aspx로 리다이렉트되고 그리고 사용자 등록 페이지에서 취소 버튼을 클릭하면 로그인 페이지 login.aspx로 이동한다고 하자.  여기서 "홈 페이지로 이동"하고 "로그인 페이지로 이동"하는 것을 Spring.Web.Result로 표현하면 아래에서 첫번째, 두번째 <object> 설정과 같다. 

<object id="homePageResult" type="Spring.Web.Result, Spring.Web">

  <property name="TargetPage" value="~/Default.aspx"/>

  <property name="Mode" value="Transfer" />

  <property name="Parameters">

    <dictionary>

      <entry key="literal" value="My Text"/>

      <entry key="name" value="%{UserInfo.FullName}" />

      <entry key="host" value="%{Request.UserHostName}"/>

    </dictionary>

  </property>

</object>

 

<object id="loginPageResult" type="Spring.Web.Result, Spring.Web">

  <property name="TargetPage" value="Login.aspx"/>

  <property name="Mode" value="Transfer" />

</object>

 

<object type="UserRegistration.aspx" parent="basePage">

  <property name="UserManager" ref="userManager"/>

  <property name="Results">

    <dictionary>

      <entry key="SUCCESS" value-ref="homePageResult"/>

      <entry key="CANCEL" value-ref="loginPageResult"/>

    </dictionary>

  </property>

</object>

각 Result 타입에는 해당 결과로 인해 리다이렉트될 대상 페이지를 나타내는 TargetPage 속성이 있다.  위 설정에서 "homePageResult"라는 결과가 사용될때는 "~/Default.aspx"로 리다이렉트되고 "loginPageResult" 결과가 사용될때는 "Login.aspx"로 이동한다. 그리고 Result 타입에는 Mode 속성이 있다. 이 속성은 리다이렉트 방법을 나타낸다. 이 속성의 값으로는 "Transfer", "TransferNoPreserve", "Redirect"가 있는데 설정하지 않으면 "Transfer"가 사용된다. "TransferNoPreserve"는 Tranfer 메소드에서 "preserveForm=false"와 같다.

public void Transfer(
   string path,
   bool preserveForm
);

preserveForm 인자가 false이면 QueryStringForm 컬렉션 데이터는 지워진다.

세번째 <object/> 요소에서는 "UserRegistration.aspx" 페이지에서 이 Result 객체를 참조해서 사용하고 있다는 것을 설정하고 있다. 즉 UserRegistration.aspx의 실행 결과가 "SUCCESS"로 설정되는 경우는 "homePageResult"에서 정의한대로 리다이렉트로되고 결과가 "CANCEL"로 설정되는 경우는 "loginPageResult"에서 설정한대로 리다이렉트된다.

만약 리다이렉트될 대상 페이지에 파라미터를 넘기고 싶다면, Result 타입의 딕션너리 속성 Parameters을 사용한다. 이 속성에 <entry/> 요소를 파라미터가 필요한대로 포함시킨다. homePageResult 결과 설정예를 보면, "literal", "name", "host" 파라미터가 추가되어 있다. "literal" 속성처럼 리터럴 문자열이 파라미터 값으로 사용될 수도 있지만, 호출하는 페이지의 속성 값을 동적으로 설정할 수도 있다. "name", "host" 파라미터에는 현재 호출하는 페이지 즉 UserRegisteration 페이지의 UserInfo 속성의 FullName 속성값이 설정된다. 현재 호출하는 페이지를 나타내는 Page 객체에 FullName 속성을 갖는 UserInfo 속성이 public 속성으로 노출되어야 한다는 것을 말한다. 참고로 이때  "%{....}" 내부의 문자열을 해석하는 expression evaluation 프레임워크가 사용된다.

이렇게 추가된 파라미터들은 Result의 속성 Mode에 따라서 대상 페이지로 다르게 전달된다. Mode를 redirect로 설정하면 모든 파라미터들은 문자열로 변환되어 쿼리 문자열에 추가된다. 반면 transfer로 설정되면 HttpContext.items에 추가되어 대상 페이지로 전달된다.

UserRegistration.aspx 페이지에서 결과를 설정하는 코드 예를 보면 다음과 유사하게 된다.

protected override void OnInit(EventArgs e)

{

    //...

    this.saveButton.Click += new EventHandler(this.SaveUser);

    this.cancelButton.Click += new EventHandler( this.Cancel );

    //...

}

 

private void SaveUser(Object sender, EventArgs e)

{

    UserManager.SaveUser(UserInfo);

    SetResult("SUCCESS");

}

 

private void Cancel(Object sender, EventArgs e)

{

    SetResult("CANCEL");

}

Spring.Web.UI.Page에서 제공하는 SetResult 메소드를 사용하면 현재 페이지의 결과를 지정할 수 있다. 이제 이렇게 지정된 결과와 설정에 따라서 적절한 페이지로 리다이렉트된다.

 

정리를 하자면, Response.Redirect("~/Default.aspx") 대신에 SetResult("SUCCESS")를 사용한다는 것이다. 그래서 음....UI와 비즈니스 로직 분리,  MVC 패턴 준수 뭐 그렇다는 것이다.

이상!


위로
From. BlogIcon Jung 2008/10/21 08:35Delete / ModifyReply
안녕하세요. 비밀글 풀었습니다.

아쉬울때만 질문드려서 죄송합니다.
Data관련 DAL에 질문이 있어서 문의 드립니다.
Spring에서 DB를 Access할때 Entity Object(도메인하고 멥핑하는것)를 쓰는 것 처럼 보이는데요. Entity Object할때 문제점이 두가지가 있습니다.

A) Entity Object 사용시 Select 의 문제점: Entity Object(Spring에서는 Domain)를 사용하지 않을경우에는 DataSet으로 불러오는 Stored procedure에 여러번의 Select를 해도 Dataset Table과 자동 순차적으로 Mapping 이 되어서 사용하는데 매우 편리하였습니다. 하지만 Object를 사용할 경우에는 한번에 하나의Select를 사용해야 하므로 Stored procedure를 나누어 만들어야 하는 것 처럼 보이는데요 다른 대안이 없을까요?

B) Entity Object 사용시 Update의 문제점: Entity Object(Spring에서는 Domain)를 사용할 경우에는 DB table의 각 컬럼이 Entity Object와 각각 Mapping되어 있는데 많은 양의 Data를 Update 할경우(예를들면 어떤 WholeSales 회사는 Invoice입력이나 변경시 1000rows 이상의 Item list를 Update해야할 경우가 발생한다고 봤을때.) Data column를 하나나 몇개의 String으로 묶어서 DB Stored procedure( varchar()로받아서 Item별로 Parsing해서 Update)에 넘길 경우 Entity Object의 사용이 그다지 쓸모가 없어보이는데요 Spring.net에는 대안이 없을까요?

제가 잘 설명하였는지 모르겠네요.
감사합니다.
달봉이 2008/10/20 15:17Delete / Modify
죄송합니다.
지금 블로그에 이상이 생겨, 관리자로 로그인하 수 없습니다.
해서 댓그을 볼 수가 없군요.

Spring.NET의 트랜잭션을 알아볼까 NHibernate를 설명해볼까 했었다. 일단 둘 다 개념은 어느 정도 알겠는데 막상 글로 옮기려다 보니 쉽게 정리가 안된다. 해서 또 뒤로 미루겠다. 대신 이번에는 UI 프레임워크에 대해서 알아보도록 하겠다.

여기서 말하는 UI 프레임워크란 Spring.NET이 지원하고 있는 MVC 패턴을 말한다. 패턴을 공부하다보면 주로 제일 먼저 나오는 패턴중의 하나이다. 달봉이도 자바쪽 프로그래밍에 대해서는 잘 모르지만 이야기를 들어보면 자바쪽 웹 프로그램쪽에서는 MVC 패턴에 기반한 프로그래밍이 예전부터 이뤄지고 있다고 한다. 그래서 많은 개발자가 처음 프로그래밍을 배우면서부터 자연스럽게 이 패턴에 익숙해진다는 것이다.

우선 많은 사람들이 MVC 패턴에 대해서 들어봤겠지만 한번 더 간단히 정리해보고 가자. 상세히는 하지 않겠다. 왜? 말빨을 지원해줄만한 지식이 딸린다.  일단 많이 본 그림을 다시 보자.

image

패턴 공부를 시작한 사람들이라면 많이 봤을 그림이다. 그렇지만 좀 시간만 지나면 다시 까먹는다. 그림이 뭘 말하는지 까먹고 또 까먹고. 다시 볼때마다 네모와 화살표만 보인다-_-;; 달봉이도 그랬다. 현실적으로 이 패턴을 활용할 기회가 없었기 때문이다. 

이 패턴에 대한 좀 더 아카데믹한 설명은 다른 전문 패턴 설명 문서를 참조하기를 바란다. 달봉이의 추측성 설명보다는 그쪽이 더 바람직할 것이다. 다만 여기서는 이 패턴이 ASP.NET으로 어떻게 구현될 수 있는지 HOW-TO 위주로 알아본다. 다음 그림은 이 패턴의 각 요소에 대응되는 ASP.NET 웹 애플리케이션의 요소이다.

image

아직 다는 이해가 되지 않지만 익숙한 aspx, aspx.cs를 보니 쪼옴 숨이 트일 것이다. "애플리케이션 도메인 객체"라는 새로운 요소가 나타나 있다. MVC 패턴의 Model에 해당하는 것이 "애플리케이션 도메인 객체"로 되어 있다. 기존의 .NET 애플리케이션에서는 데이터 액세스 레이어, 비즈니스 레이어 그리고 UI 레이어간에 데이터를 전달할때 흔히 Dataset 객체를 사용하는 경우가 흔했다. 이런 구조에 익숙한 사람이라면 "애플리케이션 도메인 객체"라는 용어를 들어볼 기회가 별로 없었을 듯 싶다. 

사용자 정보를 관리하는 페이지를 예로 해서 MVC 패턴을 좀 더 이해해보도록 하자. MVC 패턴에 맞게 구성하면 다음과 유사하게 될 것이다. "사용자 정보"라는 사용자 정의 객체가 필요한데, 이것이 Model 객체가 된다.  이 Model 객체는 사용자에 대한 정보를 가지고 있게 된다. 이 "사용자 정보" 객체는 사용자에 대한 정보( 사원번호, 이름, 현재 부서 등 )를 간직한다.  이 객체는 그림에서처럼 Controller인 Page 객체로부터 상태 변경 요청을 받기도 하고 View로 부터 상태 조회 요청을 받기도 한다.

aspx는 특정 시점의 사용자 정보 즉 실제 Model 객체의 상태를 보여주는 View 를 제공한다.

Controller인 Page 객체는 사용자가 View를 통해 입력한 Model 객체에 대한 정보를 HTTP로 받아서 해석한다. 그런 다음 Model 객체로 전달해서 상태 변경을 요청한다. 또는 Model 객체에서 변경된 상태를 View에 보내서 반영을 요청하기도 한다.

Controller는 도메인 객체의 현재 상태를 조회해서 UI의 컨트롤에 출력을 요청하기도 한다. 그러나 상태를 UI에 출력하기 위해서는 그림에서처럼 때로는 View에서 직접 도메인 객체에 접근해서 그 상태롤 요청하는 경우도 있을 수 있다. 뒤의 샘플 코드에서 보겠지만 미리 살짝 언급하면 사용자가 입력한 정보를 Model 객체에 반영하고 Model 객체의 상태를 UI에 출력하는 방법으로 데이터 바인딩 기술이 사용될 수 있다.  

참조를 나타내는 화살표 방향을 이해해 두는 것도 중요할 듯 싶다.  달봉이가 이해한 대로 그렸지만 용어나 그림이 정확한지는 달봉이도 잘 모르겠다.

아직 이 구조가 몸에 착 달라붙지는 않을 것이다. 샘플 코드를 보자. 다음에 보여주는 코드를 통해서는 MVC  패턴이 실제로 어떻게 구현되는지 그 구조 이해에 중점을 두도록 한다. 화살표가 제대로 구현되고 있는지 확인해 보도록 한다. 이 샘플 코드는 Spring.NET의 소스 코드와 함께 제공되는 샘플 프로젝트중에서 SpringAir.Web.2005를 참조하고 있다.

다음 샘플 코드에서 보여줄 페이지에서는 사용자가 비행기 예약을 하고 취소할 수 있는 페이지이다. 이 페이지를 위한 Model 객체가 어떻게 정의되어 있는지 먼저 보자. 


▶ Model - Trip 객체


namespace SpringAir.Domain

{

    [Serializable]

    public class Trip

    {

        #region Fields


        private TripMode mode = TripMode.RoundTrip;

        private TripPoint startingFrom = new TripPoint();

        private TripPoint returningFrom = new TripPoint();


        #endregion


        #region Constructor (s) / Destructor


        public Trip()

        {

        }


        public Trip(TripMode mode, TripPoint startingFrom, TripPoint returningFrom)

        {

            this.mode = mode;

            this.startingFrom = startingFrom;

            this.returningFrom = returningFrom;

        }


        #endregion


        #region Properties


        public TripMode Mode

        {

            get { return this.mode; }

            set { this.mode = value; }

        }


        public TripPoint StartingFrom

        {

            get { return this.startingFrom; }

            set { this.startingFrom = value; }

        }


        public TripPoint ReturningFrom

        {

            get { return this.returningFrom; }

            set { this.returningFrom = value; }

        }


        #endregion


        /// <summary>

        /// Returns a <see cref="System.String"/> representation of this

        /// <see cref="SpringAir.Domain.Trip"/>.

        /// </summary>

        /// <returns>

        /// A <see cref="System.String"/> representation of this

        /// <see cref="SpringAir.Domain.Trip"/>.

        /// </returns>

        public override string ToString()

        {

            StringBuilder buffer = new StringBuilder();

            buffer

                .Append(Mode).Append(", from ")

                .Append(StartingFrom).Append(" to ")

                .Append(ReturningFrom);

            return buffer.ToString();

        }

    }

}

코드를 보면 알겠지만, Trip 클래스는 출발지와 반환지를 표현하기 위해서 TripPoint 타입을 사용해서 StartingFrom, ReturningFrom 속성으로 노출하고 있다. 그리고 그 여행이 편도인지 왕복인지를 나타내기 위해서 TripMode 타입의 Mode 속성을 노출하고 있다.

aspx.cs에서 DataSet 객체를 구성해서 바로 비즈니스 레이어 객체를 호출해서 넘기는 방식의 코딩에 익숙한 대부분의 ASP.NET 웹 애플리케이션 개발자들에게는 이런 Model 객체가 익숙하지 않을 것이다. 그러나 MVC 패턴에서는 조회나 수정에서 이런 Model 객체를 사용하게 된다. 따라서 비즈니스 설계 또한 필요하다면 이 Model 객체를 도출할 수 있도록 수정될 필요도 있을 것이다.

이제 특정 시점에서의 이 Model 객체의 정보(상태)를 출력하는 UI를 보도록 하자.


▶ View - TripForm.aspx


<asp:Content ID="body" ContentPlaceHolderID="body" runat="server">

  <div style="text-align: center">

    <h4>

      <asp:Label ID="caption" runat="server"></asp:Label>

    </h4>

    <spring:ValidationSummary ID="validationSummary" runat="server" />

    <table>

      <tr class="formLabel">

        <td>&nbsp;</td>

        <td colspan="3">

          <spring:RadioButtonGroup ID="tripMode" runat="server">

            <asp:RadioButton ID="OneWay" onclick="showReturnCalendar(false);" runat="server" />

            <asp:RadioButton ID="RoundTrip" onclick="showReturnCalendar(true);" runat="server" />

          </spring:RadioButtonGroup>

        </td>

      </tr>

      <tr>

        <td class="formLabel" align="right">

          <asp:Label ID="leavingFrom" runat="server" />

        </td>

        <td nowrap="nowrap">

          <asp:DropDownList ID="leavingFromAirportCode" runat="server" />

          <spring:ValidationError id="departureAirportErrors" runat="server" />

        </td>

        <td class="formLabel" align="right">

          <asp:Label ID="goingTo" runat="server" />

        </td>

        <td nowrap="nowrap">

          <asp:DropDownList ID="goingToAirportCode" runat="server" />

          <spring:ValidationError id="destinationAirportErrors" runat="server" />

        </td>

      </tr>

      <tr>

        <td class="formLabel" align="right">

          <asp:Label ID="leavingOn" runat="server" />

        </td>

        <td nowrap="nowrap">

          <spring:Calendar ID="leavingFromDate" runat="server" Width="75px" AllowEditing="true" Skin="system" />

          <spring:ValidationError id="departureDateErrors" runat="server" />

        </td>

        <td class="formLabel" align="right">

          <asp:Label ID="returningOn" runat="server" />

        </td>

        <td nowrap="nowrap">

          <div id="returningOnCalendar">

            <spring:Calendar ID="returningOnDate" runat="server" Width="75px" AllowEditing="true" Skin="system" />

            <spring:ValidationError id="returnDateErrors" runat="server" />

          </div>

        </td>

      </tr>

      <tr>

        <td class="buttonBar" colspan="4">

          <br/>

          <asp:Button ID="findFlights" OnClick="SearchForFlights" runat="server"/>

        </td>

      </tr>

    </table>

텍스트가 없는 몇개의 레이블 컨트롤이 있다. 텍스트값은 런타임시에 할당된다. 이것은 애플리케이션의 지역화와 관계된 것으로서 지금의 MVC 패턴 설명에서는 별로 중요하지 않은 부분이다. 중요한 것은 UI에 입력 컨트롤들이 있다는 것이다 .  라디오 버튼 그룹 컨트롤인 tripMode, 드롭 다운 리스트 컨트롤인 leavingFromAirportCode, goingToAirportCode 그리고 달력 컨트롤인 departureDate, returnDate 등이 배치되어 있다. 어떤 것은 ASP.NET에서 제공하는 표준 컨트롤이고 어떤 것은 Spring에서 제공하는 커스터마이징 컨트롤이다. 이 컨트롤들은 Model 객체의 상태값을 출력하게 될 것이다. 그 출력은 MVC의 Controller가 담당한다고 했다. ASP.NET에서는 코드 비하인드 페이지의 Page 객체가 담당하게 된다.

이제 이 Model 객체의 상태값을 UI 컨트롤에 출력하는 Page 객체를 보도록 하자.


▶ Controller 객체 - TripForm 페이지 객체


public partial class TripForm : Page

{

    #region Fields


    private const string DisplaySuggestedFlights = "displaySuggestedFlights";


    private IBookingAgent bookingAgent;

    private IAirportDao airportDao;

    private Trip trip;

    #endregion


    #region Properties


    /// Biz 레이어의 객체로서 Spring IoC 컨테이너에 의해서 페이지 객체에 injected된다.

    public IBookingAgent BookingAgent

    {

        set { bookingAgent = value; }

    }


    /// Dao 레이어의 객체로서 Spring IoC 컨테이너에 의해서 페이지 객체에 injected된다.

    public IAirportDao AirportDao

    {

        set { airportDao = value; }

    }

    /// 이 도메인 객체의 상태는 정의한 바인딩 규칙에 따라 UI의 컨트롤이 제공하는 값으로 채워진다. 

    public Trip Trip

    {

        get { return trip; }

        set { trip = value; }

    }



    #endregion


    #region Model Management and Data Binding Methods


    //--> 베이스 페이지의 Init 이벤트에서 포스트백이 아닌 경우 호출된다.

    protected override void InitializeModel()  

    {

        trip = new Trip();

        trip.Mode = TripMode.RoundTrip;

        trip.StartingFrom.Date = DateTime.Today;

        trip.ReturningFrom.Date = DateTime.Today.AddDays(1);

    }


    //--> 베이스 페이지의 Init 이벤트에서 포스트백인 경우 호출된다.

    protected override void LoadModel(object savedModel)

    {

        trip = (Trip)savedModel;

    }


    // --> 베이스 페이지의 PreRender 이벤트에서 호출된다.

    protected override object SaveModel()

    {

        return trip;

    }



    //--> 베이스 페이지의 Init 이벤트에서 포스트백인 경우 호출된다.

    //--> InitializeModel()보다 먼저 호출된다.

    protected override void InitializeDataBindings()

    {

        BindingManager.AddBinding("tripMode.Value", "Trip.Mode");

        BindingManager.AddBinding("leavingFromAirportCode.SelectedValue", "Trip.StartingFrom.AirportCode");

        BindingManager.AddBinding("goingToAirportCode.SelectedValue", "Trip.ReturningFrom.AirportCode");

        BindingManager.AddBinding("leavingFromDate.SelectedDate", "Trip.StartingFrom.Date");

        BindingManager.AddBinding("returningOnDate.SelectedDate", "Trip.ReturningFrom.Date");

    }


    #endregion


    #region Page Lifecycle Methods


    protected override void OnInitializeControls(EventArgs e)

    {

        if (!IsPostBack)

        {

            BindAirportDropdowns();

        }

    }



    /// 페이지가 로딩되면서, 출발지, 도착지를 나타내는 드롭다운 컨트롤이 채워진다.

    private void BindAirportDropdowns()

    {

        ArrayList airportList = new ArrayList();

        airportList.Add(new Airport(0, string.Empty, string.Empty, "-- " + GetMessage("selectAirport") + " --"));

        airportList.AddRange(airportDao.GetAllAirports());


        leavingFromAirportCode.DataSource = airportList;

        leavingFromAirportCode.DataTextField = "Description";

        leavingFromAirportCode.DataValueField = "Code";

        leavingFromAirportCode.DataBind();


        goingToAirportCode.DataSource = airportList;

        goingToAirportCode.DataTextField = "Description";

        goingToAirportCode.DataValueField = "Code";

        goingToAirportCode.DataBind();

    }

    #endregion


    #region Controller Methods


    protected void SearchForFlights(object sender, EventArgs e)

    {

        if (Validate(trip, tripValidator))

        {

            FlightSuggestions suggestions = this.bookingAgent.SuggestFlights(Trip);

            if (suggestions.HasOutboundFlights)

            {

                Session[Constants.SuggestedFlightsKey] = suggestions;

                SetResult(DisplaySuggestedFlights);

            }

        }

    }


    #endregion

}

이 코드에서 어떤 일이 일어나는지 차례대로 정리해보자.

1. 페이지가 처음 로딩될때( IsPostback == false )부터 보자. InitializeModel 메소드는  페이지가 처음 로딩될때만 호출된다. 그 메소드에서는 Trip객체가 생성되고 기본 속성값으로 상태가 세팅된다.  페이지가 렌더링되기직전 즉 베이스 페이지의 PreRender 이벤트에서 SaveModel 메소드가 호출되는데 이때 Trip 객체가 베이스 클래스로 반환되어 HTTP 세션에 캐싱된다.

2. 그런 다음 페이지가 포스트백될때 LoadModel 메소드가 호출되는데 이때 베이스 클래스에서는 앞에서 HTTP 세션에 저장한 Trip 객체을 복원해서 LoadModel의 인자로 넘겨준다.

샘플 페이지에서는 Model이 Trip 객체하나로 구성되어 있지만 실제로는 여러개의 Model 객체로 구성된 딕션너리가 SaveModel 메소드에서 저장되고 LoadModel 메소드에서 복원될 것이다.

3. InitailizeDatabindings 메소드에서는 View의 컨트롤과 Model 객체의 속성들간의 바인딩 규칙을 지정하고 있다. 이 메소드는 페이지가 처음 호출될때 호출되어 바인딩 규칙을 구성하여 캐싱하고 이후부터는 캐싱된 결과를 이용한다. 바인딩 규칙을 추가하기 위해서 BindingManager 속성의 AddBinding 메소드를 사용하고 있는데, 넘겨지는 인자들이 모두 문자열로 되어 있다. 이 문자열들은 컨트롤의 속성, 적절한 Model 객체의 속성으로 파싱된다. 이때 Spring.NET의 Expression Language를 사용하게 된다. 그 파싱 규칙도 이해해 둘 필요가 있을 것이다. 이 규칙을 이해하면 폼위의 컨트롤과 Model 객체외에도 바인딩의 대상을 넓혀 좀 더 유용하게 응용할 수 있을 것이다.

4. 폼 위의 버튼에 대한 핸들러로 SearchForFlights 메소드가 정의되어 있다. 이 메소드를 보면 View의 컨트롤에 대한 참조가 전혀 없다. 이 메소드에서는 injected된 서비스 레이어의 BookingAgent 객체와 Model 객체 trip만을 사용하고 있다. 이곳에서 만약 서비스 레이어의 객체를 호출한 결과를 이용해서 Model 객체 trip의 상태를 변경하면 자동으로 View의 컨트롤의 상태 출력도 변경되어 있을 것이다.


샘플 페이지에서 MVC 패턴을 구현해서 controller 객체 즉 TripForm에서 View쪽의 컨트롤에 대한 참조를 없애는 것이었다. 즉 View와 Controller를 디커플링시키는 것이다. 비즈니스 로직이 포함될 수 있는 Controller쪽에서는 View측의 어떠한 컨트롤에 대한 참조도 없기때문에 좀 더 자유롭게 Controller쪽에 있을 지도 모르는 비즈니스 관련 코드를 좀 더 자유롭게 수정할 수 있게 된다. 이런 MVC 패턴 구현이 가능하게 된 것은 Spring.NET의 웹 프레임워크에서 제공하는 바인딩 기술때문이라는 것을 마지막으로 지적하고 싶다.

Controller 역할을 다시 생각해보자. 사용자로부터 입력된 정보를 Model 객체에 반영하고 Model 객체의 변경된 상태를 View에 출력하도록 요청한다고 했다.  TriplForm 객체는 이런 일을 앞에서 말한 바인딩 기술로 구현하고 있다.  즉 View와 Model의 상태 동기화를 바인딩 기술을 이용하고 있다.