달력

082010  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  •  
  •  
  •  
  •  

Visual Studio 2008의 발표와 함께 ASP.NET 3.5도 정식 발표가 되었습니다.
ASP.NET 3.5로 버전이 변경되면서 2.0과의 차이점은 많이 있지는 않습니다만
그래도 아주 유용한 컨트롤이 추가가 되었습니다.
ListView라는 컨트롤 인데요. 제가 보기엔 GridView와 DataList의 장점을 빼내어
만들어진 컨트롤 같습니다. 그동안 참으로 아쉬웠던 부분이였는데...
이번에 이렇게 추가가 되어서 참으로 기쁩니다.
그럼 ListView의 설명과 사용법에 대해서 알아보도록 하겠습니다.

ListView 설명

아래 설명은 MSDN의 ListView 클래스의 설명을 사용하였습니다.
그동안 GridView를 사용하셨던 분들이라면 더욱 쉽게 접근을 하실수 있을 것입니다.
(GridView  예제 참고 : 게시판 목록을 보여주는 GridView 예제)

템플릿 형식 설명
LayoutTemplate LayoutTemplate 속성을 사용하여 ListView 컨트롤의 루트 컨테이너에 대한 사용자 지정 UI(사용자 인터페이스)를 정의할 수 있습니다. LayoutTemplate 템플릿은 ListView 컨트롤의 필수 요소입니다.

LayoutTemplate 내용에는 ItemTemplate 템플릿에 정의된 항목이나 GroupTemplate 템플릿에 정의된 그룹에 대한 테이블 행(tr) 요소 같은 자리 표시자 컨트롤이 들어 있어야 합니다. 이 자리 표시자 컨트롤의 runat 특성은 "server"로 설정되어 있어야 하고 ID 특성은 ListView 컨트롤이 그룹을 사용하고 있는지 여부에 따라 ItemPlaceholderID 또는 GroupPlaceholderID 속성의 값으로 설정되어 있어야 합니다.
ItemTemplate ItemTemplate 속성을 사용하여 데이터 항목을 표시하기 위한 사용자 지정 UI(사용자 인터페이스)를 정의할 수 있습니다. ItemTemplate 템플릿은 ListView 컨트롤의 필수 요소입니다. 이 템플릿에는 일반적으로 레코드의 필드 값을 표시하기 위한 컨트롤이 들어 있습니다. 사용자가 데이터를 수정할 수 있도록 하기 위해 일반적으로 레코드를 선택하거나, 편집 모드로 전환하거나, 레코드를 삭제할 수 있는 단추도 ItemTemplate 템플릿에 추가합니다.

컨트롤에 바인딩된 데이터 소스의 필드 값을 표시하려면 데이터 바인딩 식을 사용합니다. 자세한 내용은 데이터 바인딩 식 구문을 참조하십시오.

기본 제공 선택, 삭제 및 편집 작업을 수행하는 단추를 만들려면 단추 컨트롤을 템플릿에 추가합니다. 컨트롤의 CommandName 속성을 다음 표에 있는 값 중 하나로 설정합니다.
단추 종류 CommandName 값
삭제 "Delete"
편집 "Edit"
선택 "Select"
ItemSeparatorTemplate ItemSeparatorTemplate 속성을 사용하여 ListView 컨트롤의 단일 항목 사이에 있는 구분 기호의 내용을 정의할 수 있습니다. ItemSeparatorTemplate은 마지막 항목을 제외하고 모든 항목 뒤에 표시됩니다.
GroupTemplate GroupTemplate 속성을 사용하여 ListView에 바둑판식 레이아웃을 만들 수 있습니다. 바둑판식 테이블 레이아웃에서는 항목이 한 행에서 가로 방향으로 반복됩니다. 항목의 반복 횟수는 GroupItemCount 속성에 의해 지정됩니다.

GroupTemplate 속성에는 테이블 셀(td), div 또는 span 요소 같은 데이터 항목에 대한 자리 표시자가 들어 있어야 합니다. 이 자리 표시자의 runat 특성은 "server"로 설정되어 있어야 하고 ID 특성은 ItemPlaceholderID 속성의 값으로 설정되어 있어야 합니다. 런타임에 ListView 컨트롤은 자리 표시자를 ItemTemplate 및 AlternatingItemTemplate 템플릿의 각 항목에 대해 정의된 내용으로 바꿉니다.
GroupSeparatorTemplate 구분 기호를 사용하여 각 그룹 사이에 사용자 지정 내용이 들어 있는 요소를 넣을 수 있습니다. 그런 다음 ListView 컨트롤에서 GroupTemplate 내용과 GroupSeparatorTemplate 내용을 교대로 렌더링합니다. GroupTemplate 내용은 항상 마지막에 렌더링됩니다.

ListView 컨트롤은 LayoutTemplate 템플릿 내에서 GroupSeparatorTemplate 내용을 렌더링하므로 GroupSeparatorTemplate 템플릿의 전체 행에 대한 내용을 정의해야 합니다. 예를 들어 ListView 컨트롤에서 테이블 행(tr) 요소를 사용하여 그룹을 만들 수 있습니다. GroupItemCount 속성이 3으로 설정된 경우 GroupSeparatorTemplate 템플릿의 colspan 특성을 3으로 설정해야 합니다.
EmptyItemTemplate 현재 페이지의 마지막 그룹에 표시할 데이터 항목이 더 이상 없으면 ListView 컨트롤에 빈 항목이 표시됩니다. GroupItemCount가 1보다 큰 값으로 설정된 경우에만 이러한 상황이 발생할 수 있습니다. 예를 들어, ListView 컨트롤에서 GroupItemCount 속성이 5로 설정되어 있고 데이터 소스에서 반환된 전체 항목 수가 8개인 경우 마지막 데이터 행에는 ItemTemplate 템플릿에 정의된 3개의 항목과 EmptyItemTemplate 템플릿에 정의된 2개의 항목이 포함됩니다.

EmptyItemTemplate 속성을 사용하여 빈 항목에 대한 사용자 지정 UI(사용자 인터페이스)를 정의할 수 있습니다.
EmptyDataTemplate 컨트롤에 바인딩된 데이터 소스에 레코드가 들어 있지 않고 InsertItemPosition 속성이 InsertItemPosition..::.None으로 설정된 경우 빈 템플릿이 ListView 컨트롤에 표시됩니다. 이 템플릿은 LayoutTemplate 템플릿 대신 렌더링됩니다. InsertItemPosition 속성을 InsertItemPosition..::.None 이외의 값으로 설정하면 EmptyDataTemplate 템플릿이 렌더링되지 않습니다.

EmptyDataTemplate 속성을 사용하여 빈 템플릿에 대한 사용자 지정 UI(사용자 인터페이스)를 정의할 수 있습니다.
SelectedItemTemplate 선택한 항목을 다른 항목과 구별하기 위해 선택한 데이터 항목에 대해 렌더링할 내용을 정의합니다.
AlternatingItemTemplate 연속된 항목을 쉽게 구별하기 위해 대체 항목에 대해 렌더링할 내용을 정의합니다.
EditItemTemplate 항목을 편집할 때 렌더링할 내용을 정의합니다. EditItemTemplate 템플릿이 ItemTemplate 템플릿 대신 편집 중인 데이터 항목에 대해 렌더링됩니다.
InsertItemTemplate 항목을 삽입하기 위해 렌더링할 내용을 정의합니다. InsertItemTemplate 템플릿이 ItemTemplate 템플릿 대신 ListView 컨트롤에 표시된 항목의 처음이나 끝에서 렌더링됩니다. ListView 컨트롤의 InsertItemPosition 속성을 사용하여 InsertItemTemplate 템플릿의 렌더링 위치를 지정할 수 있습니다.

위 템플릿 설명의 이해가 잘 가지 않으시면 아래 소스를 이용한 사용 예제를 보시면 이해가 되실겁니다. 더욱 자세한 사항은 MSDN의 ListView 컨트롤 설명을 참고하시기 바랍니다.


ListView의 사용 예제

사용예제는 간단한 이미지 게시판 목록을 만들어 보도록 하겠습니다.
예제는 총 2가지로 동시에 진행되도록 하겠습니다.

첫번째 한줄에 하나의 데이터만 나오는 예제
두번째 한줄에 둘 이상의 데이터가 나오는 예제

설명은 소스를 가지고 간단히만 하도록 하겠습니다. 소스만 봐도 충분히 이해가 될것입니다.

그리고 예제에서는 DB까지 설명은 하지 않도록 하겠습니다. 단, 쿼리문은 아래와 같습니다.

Select [GoodsSeq],[Title],[Content],[ImageUrl] From TB_Goods

1. 게시판 목록에서 사용할 스타일을 지정하도록 하겠습니다.

<style type="text/css">
    body { font-family:Verdana, 굴림; font-size:9pt; }
   
    .list_title  { background-color:#5d7b9d; color:#ffffff; font-weight:bold; text-align:center;
                    height:25px; border-bottom:solid 2px #000000; border-top:solid 2px #000000;
                    padding-top:4px; }
    .list_item   { background-color:#ffffff; color:#284775; }
    .list_item2  { background-color:#eeeeee; color:#333333; }
    .list_sep    { background-color:#000000; height:1px; }
    .list_nodata { background-color:#ffffff; height:50px; text-align:center; }
</style>

2-1. 한줄에 하나의 데이터만 나오도록 하는 예제
   - 기존의 GridView 처럼 생각하시면 될거 같습니다.

<asp:ListView ID="ListView1" runat="server" ItemPlaceholderID="phItemList"
    OnItemDataBound="ListView_ItemDataBound">
    <LayoutTemplate>
        <table border="0" cellpadding="0" cellspacing="0">
            <tr>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:460px;">타이틀 / 설명</td>
            </tr>
            <asp:PlaceHolder ID="phItemList" runat="server" />
            <tr><td colspan="4" class="list_sep"></td></tr>
        </table>
    </LayoutTemplate>
    <ItemTemplate>

        <tr>
            <td class="list_item">
                <asp:Image ID="imgGoods" runat="server" Width="50px" Height="50px" Style="margin:5px;" />
            </td>
            <td class="list_item">
                <asp:HyperLink ID="hlTitle" runat="server" />
                <asp:Label ID="lblContent" runat="server" />
            </td>
        </tr>
    </ItemTemplate>
    <AlternatingItemTemplate>

            <td class="list_item2">
                <asp:Image ID="imgGoods" runat="server" Width="50px" Height="50px" Style="margin:5px;" />
            </td>
            <td class="list_item2">
                <asp:HyperLink ID="hlTitle" runat="server" />
                <asp:Label ID="lblContent" runat="server" />
            </td>
    </AlternatingItemTemplate>
    <ItemSeparatorTemplate>
        <tr><td colspan="2" class="list_sep"></td></tr>
    </ItemSeparatorTemplate>
    <EmptyDataTemplate>

        <table border="0" cellpadding="0" cellspacing="0">
            <tr>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:460px;">타이틀 / 설명</td>
            </tr>
            <tr>
                <td colspan="2" class="list_nodata">데이터가 없습니다.</td>
            </tr>
            <tr><td colspan="2" class="list_sep"></td></tr>
        </table>
    </EmptyDataTemplate>
</asp:ListView>
- 여러 템플릿 중에서 LayoutTemplate, ItemTemplate은 필수 사항입니다.
- 속성중에서 빨간색으로 된 부분 ItemPlaceholderID는 필수사항입니다.

2-1. 한줄에 두개 이상의 데이터가 나오도록 하는 예제
   - 기존의 DataList 처럼 생각하시면 될거 같습니다.

<asp:ListView ID="ListView2" runat="server" GroupPlaceholderID="phGroupList" ItemPlaceholderID="phItemList" GroupItemCount="2"
    OnItemDataBound="ListView_ItemDataBound">
    <LayoutTemplate>
        <table border="0" cellpadding="0" cellspacing="0" style="table-layout:fixed;">
            <tr>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:200px;">타이틀 / 설명</td>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:200px;">타이틀 / 설명</td>
            </tr>
            <asp:PlaceHolder ID="phGroupList" runat="server" />
            <tr><td colspan="4" class="list_sep"></td></tr>
        </table>
    </LayoutTemplate>
    <GroupTemplate>
        <tr>
            <asp:PlaceHolder ID="phItemList" runat="server" />
        </tr>
    </GroupTemplate>
    <ItemTemplate>

        <td class="list_item">
            <asp:Image ID="imgGoods" runat="server" Width="50px" Height="50px" Style="margin:5px;" />
        </td>
        <td class="list_item">
            <asp:HyperLink ID="hlTitle" runat="server" />
            <asp:Label ID="lblContent" runat="server" />
        </td>
    </ItemTemplate>
    <GroupSeparatorTemplate>

        <tr><td colspan="4" class="list_sep"></td></tr>
    </GroupSeparatorTemplate>
    <EmptyItemTemplate>

        <td colspan="2">&nbsp;</td>
    </EmptyItemTemplate>
    <EmptyDataTemplate>
        <table border="0" cellpadding="0" cellspacing="0">
            <tr>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:200px;">타이틀 / 설명</td>
                <td class="list_title" style="width:60px;">이미지</td>
                <td class="list_title" style="width:200px;">타이틀 / 설명</td>
            </tr>
            <tr>
                <td colspan="4" class="list_nodata">데이터가 없습니다.</td>
            </tr>
            <tr><td colspan="4" class="list_sep"></td></tr>
        </table>
    </EmptyDataTemplate>
</asp:ListView>
- 여러 템플릿 중에서 LayoutTemplate, GroupTemplate, ItemTemplate은 필수 사항입니다.
- 속성중에서 빨간색으로 된 부분 GroupPlaceholderID, GroupItemCount, ItemPlaceholderID는 필수사항입니다.

참고
위 2번에서 각 데이터가 들어갈 컨트롤에 직접 데이터 바인딩이 가능합니다.

<asp:Label ID="lblContent" runat="server" Text='<%#Eval("Content")%>' />

이와같이 지정을 하면 직접 데이터 바인딩이 됩니다. 참고로 Text 속성의 데이터를 지정할때 " 를 사용하지 말고 ' 를 사용해야 합니다.

그리고 이 예제에서는 직접 데이터 바인딩을 하지않고 OnItemDataBound 을 사용하여 데이터를 지정하도록 하겠습니다.

3. OnItemDataBound 사용
    - 2번의 디자인 소스에 보면 ListView 속성에 OnItemDataBound="ListView_ItemDataBound" 라는 이벤트가 지정되어 있습니다. cs 파일의 비하인드 코드에서 코드를 작성해 보도록 하겠습니다.

protected void ListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    ListViewDataItem item = (ListViewDataItem)e.Item;
    // 아이템의 타입이 DataItem일 경우
    if (item.ItemType == ListViewItemType.DataItem)
    {
        // 상품의 이미지를 설정한다.
        Image imgGoods = item.FindControl("imgGoods") as Image;
        imgGoods.ImageUrl = string.Format("~/files/images/{0}", DataBinder.Eval(item.DataItem, "ImageUrl"));

        // 상품명과 상품 상세보기 링크를 설정한다.
        HyperLink hlTitle= item.FindControl("hlTitle") as HyperLink;
        hlTitle.Text = DataBinder.Eval(item.DataItem, "Title").ToString();
        hlTitle.NavigateUrl = string.Format("~/View.aspx?Seq={0}", DataBinder.Eval(item.DataItem, "GoodsSeq"));

        // 상품의 설명을 설정한다.
        Label lblContent = item.FindControl("lblContent") as Label;
        lblContent.Text = DataBinder.Eval(item.DataItem, "Content").ToString();
    }
}

이제 브라우저로 보기를 실행해보시기 바랍니다.
아래와 같은 화면이 나왔을 것입니다. (원하시는 화면이 나왔나요??)



이것으로 ASP.NET 3.5의 새로운 컨트롤 ListView에 대한 설명을 마치도록 하겠습니다.
이 내용이 많은 도움이 되길 바랍니다!!

작성자 : 상현넘™ [http://www.shblitz.net]
Posted by 상현넘™

댓글을 달아 주세요

  1. 덩화  댓글주소 수정/삭제 댓글쓰기 2008/03/10 17:26

    좀 퍼가겠습니다. ^^

  2. cindex  댓글주소 수정/삭제 댓글쓰기 2008/05/25 20:05

    고맙습니다. 덕분에 해결했습니다. 스크랩 좀 하겠습니다. 출처는 꼭 남깁니다 ^^
    수고에 감사드립니다

  3. 몬난아  댓글주소 수정/삭제 댓글쓰기 2008/07/08 23:04

    음 리스트뷰 에디터 템플릿 예제는 어디 없을까요?

    데이터 소스 마법사나 컨트롤을 사용한것 말고요;;

    코드비하인드단에서 제어 하는걸로요;;
    Datalist랑 같겟지하고 사용해볼려다가 오늘 큰코 다쳤습니당 ㅠㅠ 생각보다 어렵내요

    • 상현넘™  댓글주소 수정/삭제 2008/07/12 08:41

      글쎄요!! 전 에디터 템플릿을 안쓰다보니..
      전 줄마다 버튼에 시퀸스(일련번호)를 저장해놓고 클릭하면 새창으로 에디터를 레이어 팝업으로 띄워서 쓰는 방법을 주로 사용합니다.
      레이어 팝업 쓰기전에 시퀀스를 이용해서 데이터를 가져오고요!!~~

  4. 몬난아  댓글주소 수정/삭제 댓글쓰기 2008/07/18 18:15

    아 생각보다 어려웟따기 보다 새로 나온 컨트롤이라 한번씩 있는 기능을 CRUD를 직접 손으로 다해보자는 취지에서 해봤엇는데요 ㅎㅎ

    보통 리피터 쓸때는 상현넘?상현님? 처럼 하는데 있는 기능을 활용해보려구 EditTemplate을 썻구요.

    제가 진짜 한참을 해맷는데 국내 싸이트엔 기록이 없다 시피하구 외국싸이트에 기록이 있드라구요
    EditeItem프로퍼티로 가져올때 0번째 인덱스를 가저와서 수정하려구 하면 null에러가 뜨는 에러가 있더라구요 ㅠㅠ(머 일단 버그라고 외국 커뮤니티에는 되어있던데요 ^^);
    그것도 모르고 왜 null을 가져오는지 몰라서 진짜 고생했습니다 ㅠㅠ;
    그래서 코드 비하인드단에서 제어 하는 소스를 찾았었습니다 ^^

    도움 되었구요 수고하세요

  5. 네꼬짱  댓글주소 수정/삭제 댓글쓰기 2008/08/22 23:10

    리스트뷰에 대한 상세한 설명덕에 게시판 리스트를 아주 정갈하게 만들긴 했는데요. 리스트뷰 컨트롤로 만든 게시판 리스트 작성자에 로그인 한 작성자 아이디를 바인딩 하고 싶은데 잘 안되네요. 다른 거는 User.Identity.Name으로 하면 잘되던데... 아~ 그리고 리스트뷰가 처음에는 어려운 것 같은데 자꾸 쓰다보니 쉬워지네요. ^^;; ㅎㅎㅎ

    • 상현넘™  댓글주소 수정/삭제 2008/08/25 17:53

      게시판 리스트에 작성자 표시하는건 원래 DB에서 가져온 데이터로 표시를 해야하지 않나요??
      로그인한 작성자로 무조건 바인딩하면 전부 작성자가 로그인한 사용자 이름으로 나올텐데요!!
      일단 물어보시니깐.. 디자인 소스에서 작성자용 라벨을 하나 추가시키고요 서버 스크립트 ListView_ItemDataBound 에서 라벨 컨트롤에 이름을 넣어주면 되겠죠!!
      Label lblName = item.FindControl("lblName") as Label;
      lblName.Text = User.Identity.Name;
      이런식으로!!

    • 네꼬짱  댓글주소 수정/삭제 2008/08/29 14:32

      상헌님 넘 감사합니다. ^▽^
      리스트뷰에 대해 궁금한 사항이 있어서 또 방문했는데
      친절하게 답글 달아주신 거 보고 넘 감격했습니다. ㅠ_ㅠ

  6. kycc11  댓글주소 수정/삭제 댓글쓰기 2008/09/26 20:24

    닷넷을 처음으로하는 초보생입니다. 다름이아니오라.
    ListView_ItemDataBound()에서

    string g_code = (string)((DataRowView)e.Item.DataItem)["g_code"];
    이런식으로 리스트부분을 가져와서

    NavigateUrl 에서 자바스크립트를 적용시킬려고하는데 잘안되네요
    fcSubject.NavigateUrl = "javascript:test(g_name);";


    <script language="javascript" type="text/javascript">
    function test(g_name){
    alert(g_name);
    }
    </script>

    이러면 그 값을 가지고 올수 있나 확인할려고 확인창에 그 값을 찍은거구요
    그 값이 넘어오지를 않는데요 어떻게해야할지...몇일동안을 고생하고 있습니다.ㅠ
    실제로 할려는 부분은 검색버튼을 누르면 창을 하나 띄웁니다. 거기에 리스트정보들이 담겨져 있구요
    우편번호처럼 값을 원하는 곳에 넣고 싶은데.. 어떻게 해야할지 감이오질 않습니다.
    리스트뷰가 원래 안되는건지.... 그럴리는 없을텐데요.ㅠ 꼭 답변 부탁드립니다..ㅠㅠ 하루가 급한시기라..ㅠㅠ 그럼 오늘 하루도 수고하십시오.

  7. 몽중인  댓글주소 수정/삭제 댓글쓰기 2009/01/13 12:22

    아래와 같은 에러가 발생을 하네요.
    혹시 뭘 더 가져다 써야하는건지.. 흠...


    데이터 바인딩: 'System.Data.DataRowView'에 이름이 'ImageUrl'인 속성이 없습니다.
    설명: 현재 웹 요청을 실행하는 동안 처리되지 않은 예외가 발생했습니다. 스택 추적을 검토하여 발생한 오류 및 코드에서 오류가 발생한 위치에 대한 자세한 정보를 확인하십시오.

    예외 정보: System.Web.HttpException: 데이터 바인딩: 'System.Data.DataRowView'에 이름이 'ImageUrl'인 속성이 없습니다.

    소스 오류:


    줄 45: // 상품의 이미지를 설정한다.
    줄 46: Image imgGoods = item.FindControl("imgGoods") as Image;
    줄 47: imgGoods.ImageUrl = string.Format("~/files/images/{0}", DataBinder.Eval(item.DataItem, "ImageUrl"));
    줄 48:
    줄 49: // 상품명과 상품 상세보기 링크를 설정한다.


    소스 파일: d:\doing_work\asp.net\project\msgSample\partPage\rsltList.aspx.cs 줄: 47

    • 상현넘™  댓글주소 수정/삭제 2009/01/23 23:46

      해당 부분은 <asp:Image ID="imgGoods" runat="server" Width="50px" Height="50px" Style="margin:5px;" /> 이 컨트롤에 대한 처리 코드입니다.
      해당 컨트롤이 추가가 안된 경우에는 Image imgGoods = item.FindControl("imgGoods") as Image; 이 부분에서 imgGoods 변수에 null 이기 때문에 imgGoods.ImageUrl 여기에 데이터를 대입을 할수 가 없습니다. 혹시 에러를 처리하고 싶으면..

      Image imgGoods = item.FindControl("imgGoods") as Image;
      if(imgGoods != null)
      {
      imgGoods.ImageUrl = string.Format("~/files/images/{0}", DataBinder.Eval(item.DataItem, "ImageUrl"));
      }
      이렇게 처리하면 안전하게 사용이 가능합니다.

  8. 김근성  댓글주소 수정/삭제 댓글쓰기 2009/06/29 18:05

    DataPager도 부탁드리면 안될까요? ^^;;

ASP.NET 2.0에는Master Page라는것이새로생겼다.  마스터 페이지의 개념을 MSDN에 나온 그대로 옮기면 다음과 같다.

"ASP.NET 마스터 페이지를 사용하면 응용 프로그램의 페이지에 대해 일관된 레이아웃을 만들 수 있습니다. 단일 마스터 페이지는 응용 프로그램의 모든 페이지 또는 페이지 그룹에 대해 원하는 모양과 느낌 및 표준 동작을 정의합니다. 그런 다음 표시할 콘텐츠가 포함된 개별 콘텐츠 페이지를 만들 수 있습니다. 사용자가 요청한 콘텐츠 페이지는 마스터 페이지와 병합되어 마스터 페이지의 레이아웃과 콘텐츠 페이지의 콘텐츠가 조합된 결과가 만들어집니다."

그런데, Master Page - Content Page가 구성이 되면, 마치 ASCX처럼 내부로 중첩된 컨트롤들의 Client ID가 아주 복잡해지는 것을 볼 수가 있다. 이렇게..

<input name="ctl00$ContentPlaceHolder1$TextBox1" type="text" id="ctl00_ContentPlaceHolder1_TextBox1" />

그래서, 자바스크립트를 사용해서 제어하고자 할 때 반드시 서버 사이드에서 그 컨트롤의 ClientID를 구해서 해야 한다.

다음은 Master Page에서 자바스크립트로 Content Page의 컨트롤을 제어하는 샘플이다.

마스터 페이지에는 버튼을 하나 올렸다.

사용자 삽입 이미지

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>제목 없음</title>
</head>
<body> <form id="form1" runat="server">   
        <input id="Button1" type="button" value="button" onclick="return Button1_onclick()" />&nbsp;
        <input id="Hidden1" type="hidden" runat="server" /><br />
        <br />
        <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
            </asp:contentplaceholder>   
    </form>
</body>
</html>

컨텐트 페이지에는 텍스트 박스를 하나 올린다.

사용자 삽입 이미지

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
</asp:Content>

목표는 마스터 페이지에서 버튼을 눌렀을 때, 컨텐트 페이지의 텍스트 박스에 "aaa"라는 값이 들어가게 하는 것이고, 이를 자바스크립트를 사용해서 수행하는 것이다. 그 코드는 다음과 같다.

protected void Page_Load(object sender, EventArgs e)
{
    //contentPlaceHolder1은 Master페이지에 기본적으로 들어있는 PlaceHolder 컨트롤의 이름이다.
    //이 내부에 각 컨텐트 페이지의 컨트롤들이 들어있다.
    //즉 ContentPlaceHolder1.Controls 를 뒤지면 나온다는 얘기다.
    //그래서 루프를 돌면서 찾도록 한다.

    foreach (Control con in this.ContentPlaceHolder1.Controls)
    {
        //원하는 것을 찾기 위해서는 종류나 ID 같은 것을 알면 된다.
        if (con.GetType().Name == "TextBox" || con.ID == "TextBox1")
        {
            //ClientID를 알 수 있다. 히든에다 넣는다.
            this.Hidden1.Value = con.ClientID;
        }
    }
    //자바스크립트를 생성한다.
    //렌더링된 자바 스크립트는 아래와 같은 모양이다.
    //        function Button1_onclick() {
    //        var conID = document.all.item("ctl00_Hidden1").value;
    //        eval("document.all.item('" + conID + "').value = 'aaa'");
    //        }
    //즉, 히든에 넣은 textbox 컨트롤의 ID를 구한 후,
    //eval문을 사용해서 컨트롤에 'aaa'라는 값을 넣는 스크립트를 실행하는 것이다.

    string scriptCode = "function Button1_onclick() {" + System.Environment.NewLine;
    scriptCode += " var conID = document.all.item("" + this.Hidden1.ClientID + "").value;" + System.Environment.NewLine;
    scriptCode += "eval("document.all.item('" + conID + "').value = 'aaa'");" + System.Environment.NewLine;
    scriptCode += "}";
    //페이지에 클라이언트 스크립트를 등록한다.
    this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Click", scriptCode, true);
}

주석에도 설명이 되어 있지만, 포인트는 마스터 페이지에 있는 PlaceHolder 컨트롤 내부에서 그 컨텐트 페이지에 있는 컨트롤을 찾는 것이다.
일단 찾은 후에는 그 ClientID 속성을 구할 수 있으므로, 이 샘플보다 복잡한 스크립트도 얼마든지 수행시킬 수가 있다.

출처 : 게으른 개발자
Posted by 상현넘™

댓글을 달아 주세요

요약: Microsoft ASP.NET 2.0에는 웹 응용 프로그램의 지역화를 위한 여러 가지 향상된 기능이 포함되어 있습니다. 하지만 이러한 훌륭한 기능이 있음에도 불구하고 사이트의 지역화를 마치고 나면 곧 확장성에 대해 염려하게 됩니다. 이 문서에서는 ASP.NET의 확장 기능을 적용하여 엔터프라이즈 지역화 시나리오를 처리하고, 지역화-개발 프로세스를 향상시킬 수 있는 방법을 설명합니다.

이 기사의 코드를 다운로드하십시오.


소개

ASP.NET 2.0에는 웹 응용 프로그램의 지역화를 위한 여러 가지 향상된 기능이 포함되어 있습니다. 이러한 새 기능에 대해서는 필자가 집필한 MSDN 기사인 "ASP.NET 2.0 Localization Features: A Fresh Approach to Localizing Web Applications (영문)"를 참조하십시오.

이러한 새 지역화 기능을 살펴보고 나면 다음과 같은 내용을 알 수 있습니다.

  • Microsoft Visual Studio 2005의 페이지 도구 모음 보기에서 로컬 리소스 생성 메뉴 항목을 사용하면 각 페이지에 대한 리소스를 손쉽게 만들 수 있습니다.
  • 향상된 리소스 편집기와 엄격한 형식의 액세스 방식 덕분에 전역 리소스의 작성과 사용이 훨씬 간편해졌습니다.
  • 선언적인 지역화 식을 사용하여 리소스 항목을 제어 속성 및 콘텐츠 영역에 깔끔하게 매핑할 수 있습니다.
  • 로컬 또는 전역 리소스를 가져와 필요에 따라 ResourceManager를 할당하는 과정을 ResXResourceProviderFactory에서 조정하므로 ResourceManager를 수동으로 인스턴스화할 필요가 없습니다.
  • 브라우저의 culture 기본 설정을 자동으로 감지하고 이 설정을 요청 스레드에 할당하므로 사용자의 culture 기본 설정에 따른 제어가 편리합니다. 익명 사용자인 경우도 마찬가지입니다.

물론 이처럼 훌륭한 기능에도 불구하고 대개 사용자는 더 많은 것을 원합니다. 이러한 기능을 사용하여 사이트를 지역화하고 나면 곧 다음과 같은 다른 사항을 염려하게 됩니다.

  • 별도의 리소스 어셈블리나 데이터베이스 원본과 같은 다른 위치에서 리소스를 가져오려면 어떻게 해야 할까?
  • 일부 로컬 및 전역 리소스를 사용하지만 다른 데이터 원본도 있는 혼합 환경을 어떻게 관리해야 할까?
  • 리소스-공급자 모델, 지역화 식 및 다른 디자이너 통합 기능과 같은 ASP.NET 2.0의 기능을 계속 활용하면서 리소스의 원본을 제어하려면 어떻게 해야 할까?
  • 기존의 지역화 기능과 새롭게 제공되는 확장 가능 옵션을 내 개발 환경과 지역화 프로세스에 맞게 활용하려면 어떻게 해야 할까?

확장성이 중요한 이유가 바로 이 때문입니다. ASP.NET 지역화 기능을 확장하고 개발 환경에 맞게 사용하는 방법은 여러 가지가 있습니다. 이 기사는 ASP.NET의 확장 기능을 적용하여 엔터프라이즈 지역화 시나리오를 처리하고, 지역화-개발 프로세스를 향상시킬 수 있는 방법을 설명할 3회 연재 기사 중 첫 번째 기사입니다.

이 기사에서는 다른 저장소 위치에서 리소스를 가져오기 위한 기능과 페이지 구문 분석, 컴파일 및 런타임 실행 기능을 통합하는 기능에 초점을 맞춥니다. 이를 위해 사용자 지정 리소스 공급자, 사용자 지정 식 작성기 및 기타 지원되는 확장 가능 형식을 조합하여 사용하는 방법을 설명합니다. 두 번째 기사에서는 선택한 리소스 저장소를 Visual Studio 2005의 기본 제공 생산성 기능과 결합하여 개발 프로세스를 향상시키는 방법을 설명하며, 세 번째 기사에서는 클라이언트측 사용자 지정과 같은 기능을 지원할 수 있는 복잡한 리소스 계층을 제어하는 다른 방법에 대해 살펴봅니다.


리소스를 어디에 두어야 할까?

지역화된 리소스를 웹 사이트에 통합하는 일은 언제나 고된 작업입니다. 리소스 생성은 일반적으로 어려운 일이고 번역을 위해 리소스를 구성하는 데도 관리되는 프로세스가 필요하지만, 웹 사이트 리소스의 지역화에 있어 무엇보다도 어려운 것은 무엇이 리소스에 해당하는지, 리소스를 어떻게 할당해야 하는지, 그리고 최상의 성능과 관리 효율을 위해 필요한 것은 무엇인지 파악하는 일입니다.

ASP.NET 2.0을 사용한 리소스 만들기 및 액세스

ASP.NET 2.0은 각 페이지에 대한 로컬 리소스를 만들 수 있는 기능을 제공합니다. 바로 이 기능이 더 효율적인 페이지 디자인과 국제화 프로세스의 바탕이 되었습니다.

  1. 페이지 디자인 - 정적 HTML 및 ASP.NET 서버 컨트롤을 조합하여 적용합니다.
  2. 지역화를 위한 정적 영역 준비 - 정적 영역을 ASP.NET Localize 컨트롤로 래핑합니다.
  3. 적절한 컨트롤 이름 제공 - 모든 서버 컨트롤에 적절한 이름을 제공하여 생성되는 이벤트 처리기와 리소스 키를 알아보기 쉽도록 합니다.
  4. 공유 리소스 만들기 - App_GlobalResources 하위 디렉터리에 공유 리소스를 만듭니다. .resx 파일이 이미 있는 경우는 그대로 사용하고, 그렇지 않으면 여러 페이지 간에 공유할 항목을 저장하기 위해 새 .resx 파일이 생성됩니다.
  5. 공유 리소스 연결 - 해당하는 경우 명시적인 리소스 식을 사용하는 속성을 통해 공유 리소스를 연결합니다. 공유 리소스는 페이지에 대한 로컬 리소스를 만들기 전에 연결하는 것이 좋습니다.
  6. 로컬 리소스 생성 - 페이지 도구 모음 보기에서 로컬 리소스 생성 메뉴 항목을 선택하여 로컬 리소스를 만듭니다.

로컬 리소스를 만든 후에는 페이지에 대한 모든 지역화 가능 속성과 컨트롤이 페이지당 하나씩 생성된 각각의 로컬 리소스 파일로 이동됩니다. 암시적 지역화 식은 페이지 파서가 공용 접두사를 사용하여 컨트롤의 각 리소스 값을 해당 속성에 매핑하도록 합니다. 샘플 코드에 있는 Expressions.aspx 페이지의 다음과 같은 암시적 식을 살펴보겠습니다.

<asp:Label ID="labHelloLocal" runat="server" Text="Hello" meta:resourcekey="labHelloLocalResource1" ></asp:Label>

리소스는 App_LocalResources 디렉터리의 Expressions.aspx.resx 파일에 저장됩니다. 이 Label 컨트롤에 대한 리소스는 "labHelloLocalResource1"이라는 접두사를 공유합니다. 예를 들어 Text 속성은 "labHelloLocalResource1.Text" 키로 저장됩니다.

공용 사용자 인터페이스 영역용 사용자 컨트롤과 마스터 페이지를 사용하여 사용자 인터페이스를 잘 구성하면 각 마스터 페이지, 사용자 컨트롤, 페이지 역시 겹치는 부분 없이 잘 구성됩니다. 이렇게 하면 일반적으로 이전 버전에서는 성가신 작업이었던 각 페이지 부분에서 사용하는 리소스를 구성하는 과정이 손쉽게 진행됩니다. 하지만 여전히 공유 위치에서 리소스를 가져와야 하는 경우가 있는데, 이 경우 다음과 같은 $Resources 식 등의 명시적 리소스 식을 제공하면 됩니다.

<asp:Label ID="labHelloGlobal" runat="server" Text="<%$ Resources:CommonTerms, Hello %>"></asp:Label>

이 경우 리소스의 위치는 App_GlobalResources 디렉터리의 CommonTerms.resx 파일입니다. 이와 같은 명시적 식은 식 편집기(앞서 언급한 MSDN 기사 참조)를 사용하여 간단히 만들 수 있습니다.

암시적 식이나 명시적 식을 사용하면 리소스 공급자를 사용하여 리소스를 가져오는 코드가 생성됩니다. 이러한 선언적 식, 코드 및 리소스 생성 기능의 조합은 이전에는 웹 응용 프로그램에 사용할 수 없었던 생산성 도구를 제공합니다.

리소스 어셈블리 및 ResourceManager

ASP.NET 2.0 응용 프로그램을 컴파일하고 배포하는 방법에는 다음과 같은 여러 가지가 있습니다.

  • 소스를 배포하고 전체 사이트를 JIT 컴파일합니다.
  • 업데이트 가능한 페이지 및 리소스로 사이트를 미리 컴파일합니다.
  • 사이트를 미리 컴파일하여 페이지나 디렉터리별로 어셈블리를 생성합니다.

어떤 방법을 사용하든 최종적으로는 사이트의 각 디렉터리에 대한 리소스 어셈블리가 생성되고, 각 culture별 디렉터리에 해당하는 위성 어셈블리가 생성됩니다. 사이트를 JIT 컴파일하는 경우에도 결과는 동일합니다. 그림 1은 하위 디렉터리 두 개가 있는 사이트를 스페인어로 지역화하기 위해 미리 컴파일한 결과를 보여 줍니다.

사용자 삽입 이미지

그림 1. ASP.NET 웹 사이트의 각 디렉터리에 생성된 리소스 및 위성 어셈블리(더 큰 이미지를 보려면 그림을 클릭하십시오.)

이러한 리소스는 런타임 시에 ResourceManager를 통해 액세스됩니다. ResourceManager는 리소스가 요청될 때 각 리소스 유형(예: Page1.aspx 및 Page2.aspx)에 할당됩니다. 각 리소스 유형에 연결된 리소스 어셈블리는 처음 액세스할 때 ASP.NET 응용 프로그램 도메인에 로드되며 응용 프로그램 도메인이 언로드될 때까지 유지됩니다. 그림 1의 경우 스페인 culture에 해당하는 \SubDir1\Page1.aspx에 처음 액세스하면 \es\App_LocalResources.subdir1.cdcab7d2.resources.dll 어셈블리가 응용 프로그램 도메인에 로드됩니다. 이 어셈블리는 \SubDir1의 모든 페이지에 대한 스페인어 리소스를 포함합니다.

그림 2는 ResourceManager가 각 페이지에 대한 로컬 리소스에 액세스하는 방식을 보여 줍니다. \SubDir1\Page1.aspx가 로드되면 암시적 식에 의한 코드 생성을 통해 ResXResourceProviderFactory가 호출되고, 여기서 LocalResXResourceProvider가 반환됩니다. 이 공급자는 App_LocalResources.subdir1.cdcab7d2 어셈블리의 Page1.aspx 유형에 대한 ResourceManager를 만듭니다. 요청 스레드의 UI culture가 “es”일 경우, \es 디렉터리에 있는 위성 리소스 어셈블리가 응용 프로그램 도메인으로 로드됩니다. UI culture에 맞는 위성 어셈블리가 없는 경우 ResourceManager는 대신 주 리소스 어셈블리를 사용합니다.

사용자 삽입 이미지

그림 2. 주 리소스 어셈블리 또는 지역화된 위성 어셈블리가 응용 프로그램 도메인에 로드되면 ResourceManager가 각 위치의 리소스에 액세스합니다(더 큰 이미지를 보려면 그림을 클릭하십시오.).

리소스 및 위성 어셈블리는 응용 프로그램 도메인에 로드된 상태로 유지됩니다. 또한 각 페이지 또는 공유 리소스 유형의 ResourceManager는 캐시되어 해당 리소스에 대한 이후 요청에 다시 사용됩니다.

이와 같은 방식으로 기본 ASP.NET 리소스-공급자 모델이 리소스에 액세스하게 됩니다. 이제 이 기본 구현에 변경이 필요한 이유를 알아보겠습니다.

다른 위치가 필요한 이유

새로운 ASP.NET 2.0 환경에서 제공하는 기본적인 리소스 생성 및 런타임 액세스 기능은 이전에 비해 훨씬 나은 결과를 만들어냅니다. 그러나 다음과 같은 이유로 인해 다른 리소스 저장 방법을 알아봐야 하는 경우가 종종 있습니다.

  • 다른 저장소에 이미 있는 리소스를 다시 사용
  • 방대한 규모의 정적인 콘텐츠를 실용적으로 저장
  • 관리 효율성 증대

리소스 저장에 널리 사용되는 두 가지 저장소는 외부 리소스 어셈블리와 데이터베이스입니다.

기존 리소스 사용 - 이전 응용 프로그램에서 사용하던 리소스가 있거나 Windows 및 웹 응용 프로그램 간에 공유하는 리소스 어셈블리가 있을 수 있습니다. 일반적으로 ASP.NET 1.1에서 2.0으로 코드를 마이그레이션할 경우 1.1 응용 프로그램에서 .resx 파일을 가져와 2.0 응용 프로그램의 App_GlobalResources 디렉터리에 복사하는 것이 좋습니다. 복사한 .resx는 ASP.NET 2.0 응용 프로그램과 함께 컴파일되며 엄격한 형식의 전역 리소스를 통해 액세스됩니다. 하지만 기존 리소스 어셈블리의 버전을 제어하거나 Windows 및 웹 응용 프로그램에 필요한 리소스 복사본을 하나만 유지하려면 이는 좋은 방법이 아닙니다. 이 경우는 이러한 리소스를 공유 리소스 전용 어셈블리에 저장하는 것이 보다 나은 방법입니다. 따라서 이러한 어셈블리에서 리소스를 가져올 방법이 필요하게 됩니다.

데이터베이스에 리소스 저장 - 웹 응용 프로그램 리소스는 데이터베이스에 저장하는 것이 좋은데, 여기에는 몇 가지 이유가 있습니다. 간단히 생각해도 사이트에 수천 개의 페이지와 리소스 항목이 있는 상태에서 어셈블리 리소스를 사용한다는 것은 이상적인 방법이 아닙니다. 런타임 메모리 사용량도 늘어날 것이고, 응용 프로그램 도메인에 로드되는 어셈블리의 수도 늘어날 것입니다. 결과적으로 규모가 매우 큰 사이트의 경우 데이터베이스 호출 대기 시간이 상당할 것입니다. 이러한 경우 데이터베이스 리소스를 사용하면 중복되는 항목이 줄어들고 복잡한 캐시 옵션을 사용할 수 있으며 보다 많은 양의 콘텐츠를 저장할 수 있으므로 보다 유연하고 관리가 수월한 환경을 만들 수 있습니다. 마지막으로, 데이터베이스에 리소스를 할당하면 번역된 콘텐츠에 보다 복잡한 계층 구조를 지원할 수 있으므로 고객이나 부서에서 지역화된 텍스트의 사용자 지정 버전을 만들 수도 있습니다.

ASP.NET 지역화를 위한 확장성 패턴은 다른 리소스 저장 위치를 지원하여 이러한 결과를 쉽게 달성할 수 있도록 설계되었습니다. 또한 디자인 타임 환경에도 연결할 수 있으므로 다른 저장소에서 리소스를 가져오는 것뿐만 아니라 다른 저장소에서 리소스를 만들 수 있습니다. 이 내용은 필자의 두 번째 기사에서 설명합니다.

외부 리소스 어셈블리나 데이터베이스에 리소스를 저장할 때마다 ASP.NET 2.0의 지역화 기능이 필요할 것입니다. “어디에 있든지” 지역화 식 및 지역화 API를 사용하여 리소스에 액세스할 수 있기 때문입니다. 이러한 기능은 지금부터 이 기사에서 설명할 확장 기능이 있기에 가능합니다.


리소스-공급자 모델

ResourceManager 형식이 런타임 시에 어셈블리에서 리소스를 가져오는 역할을 함을 설명한 바 있습니다. 이 형식은 요청 스레드의 UI culture에 따라 올바른 리소스 집합의 검색을 캡슐화합니다(그림 2 참조). 즉, 요청 스레드의 UI culture가 호출한 사용자의 기본 설정에 대해 올바르게 설정되어 있다면 올바른 위성 어셈블리에서 올바른 리소스를 선택합니다. ASP.NET 2.0 이전에는 각 리소스 유형에 대한 ResourceManager를 직접 인스턴스화하고 그 수명을 관리해야 했습니다. 때문에 각 페이지 요청마다 ResourceManager 인스턴스 작성 또는 액세스를 위한 코드가 필요했으며 리소스 항목에 액세스하기 위해 메서드를 호출해야 했습니다. 선언적인 방식으로 리소스를 페이지 요소에 바인딩하기 위해 사용자 지정 데이터 바인딩 문을 사용하는 방법도 있으나 이 역시 페이지 수준의 데이터 바인딩과 바인딩 변수 할당을 위한 코드가 필요합니다.

ASP.NET 2.0에서는 모든 페이지 또는 사용자 컨트롤에서 지역화 API 함수를 사용하여 리소스를 가져올 수 있습니다. 예를 들어 다음 두 코드에서는 각각 로컬 페이지 리소스와 전역 리소스를 가져옵니다.

this.labHelloLocal.Text = this.GetLocalResourceObject("labHelloLocalResource1.Text") as string;

this.labHelloGlobal.Text = this.GetGlobalResourceObject("CommonTerms", "Hello") as string;

선언적인 지역화 식을 사용해도 리소스의 페이지 및 컨트롤 속성을 설정할 수 있음을 앞서 설명했습니다. 즉, 다음과 같은 암시적 지역화 식과

<asp:Label ID="labHelloLocal" runat="server" Text="Hello" meta:resourcekey="labHelloLocalResource1" ></asp:Label>

다음과 같은 명시적 지역화 식 모두

<asp:Label ID="labHelloGlobal" runat="server" Text="<%$ Resources:CommonTerms, Hello %>" ></asp:Label>

GetLocalResourceObject()GetGlobalResourceObject()를 호출하는 코드 생성에 사용할 수 있습니다. 즉, ASP.NET 2.0의 리소스 액세스 방법은 편리한 선언적인 식을 사용할 때도 마찬가지로 결국은 이러한 방법을 사용한다는 것입니다.

리소스-공급자 모델이 필요한 이유가 이것입니다. 이러한 API는 기본 또는 사용자 지정 ResourceProviderFactory를 사용하여 올바른 리소스 항목을 찾고 그 값을 수집합니다. 기본 ResourceProviderFactory는 앞서 설명한 ResXResourceProviderFactory 유형입니다. 이 팩토리는 전역 리소스의 경우 GlobalResXResourceProvider의 인스턴스를, 로컬 페이지 리소스의 경우 LocalResXResourceProvider의 인스턴스를 반환합니다.

결국 이러한 공급자는 ResourceManager에 따라 적절한 위성 어셈블리에서 각 리소스에 액세스합니다. 공급자는 ResourceReader를 사용하여 페이지 구문 분석 단계 동안 페이지 리소스 모음을 수집합니다. 그림 3은 기본적인 리소스-공급자 모델과 연관된 핵심 구성 요소를 보여 줍니다.

사용자 삽입 이미지

그림 3. 기본적인 리소스-공급자 모델을 구성하는 구성 요소: 공급자 팩토리, 로컬 및 전역 리소스 공급자, 리소스 관리자 및 각 리소스 유형에 액세스하기 위한 리소스 판독기
(더 큰 이미지를 보려면 그림을 클릭하십시오.)

이 공급자 모델에는 다음과 같은 몇 가지 장점이 있습니다.

  • ResourceManager의 활성화와 수명을 제어합니다.
  • 지역화 식 및 다른 리소스 API가 공급자를 활용하여 리소스를 검색하므로 단순하고 추상화된 API를 통해 생산성을 높일 수 있습니다.
  • 공급자 모델은 확장 가능하므로 ASP.NET 2.0의 생산성 기능을 활용하면서도 리소스 저장 위치를 변경할 수 있습니다.

이제 사용자 지정 리소스 공급자를 구축하는 방법을 살펴보겠습니다.


데이터베이스 리소스 공급자 구축

사용자 지정 리소스 공급자를 사용하면 App_GlobalResources 또는 App_LocalResources가 아닌 다른 곳의 리소스에 액세스할 수 있습니다. 예를 들어 사용자 지정 리소스 공급자를 사용하면 미리 컴파일된 어셈블리에 배포된 리소스에 액세스하거나 데이터베이스의 콘텐츠에 액세스할 수 있습니다. 이 섹션에서는 데이터베이스 리소스-공급자 모델에 대해 살펴보고, 외부 리소스 어셈블리에 액세스하는 방법은 이 기사의 뒷부분에서 설명합니다.

사용자 지정 리소스 공급자는 ResourceProviderFactory를 포함하며 IResourceProvider 인터페이스를 구현하는 리소스-공급자 유형을 하나 이상 포함합니다. 로컬 또는 전역 리소스에 액세스하기 위한 적절한 IResourceProvider의 인스턴스화는 팩토리를 통해 수행됩니다. 그림 4는 이 기사의 샘플 코드에 구현된 데이터베이스 리소스-공급자 모델을 구성하는 구성 요소를 보여 줍니다.


그림 4. 사용자 지정 데이터베이스 리소스-공급자 모델의 구성 요소 계층 구조

데이터베이스 리소스 항목

먼저 실제 리소스 항목을 저장하는 데이터베이스 테이블의 구조를 검토해 보겠습니다. 샘플에는 CustomResourceProvidersSample이라는 데이터베이스에 StringResources라는 테이블을 만드는 SQL 스크립트가 있습니다. 표 1은 이 테이블에 포함된 필드를 보여 줍니다.

표 1. 리소스 항목이 있는 데이터베이스 테이블

필드 설명
resourceType 각 리소스에 대한 범주입니다. 이는 서로 다른 페이지에 대한 로컬 리소스를 구분하거나 사용자 정의 이름을 통해 전역 리소스 유형을 구분하는 데 사용될 수 있습니다.
cultureCode .NET에서 사용되는 지원 CultureInfo 코드의 culture 코드입니다. ISO 표준에 따릅니다. 누락된 코드에 대해서도 확장할 수 있습니다.
resourceKey 리소스를 가져오는 데 사용되는 리소스 키입니다.
resourceValue 리소스 값입니다. 이 테이블은 최대 4K의 문자열을 지원합니다.

이 샘플의 모든 리소스는 하나의 테이블에 저장되어 있지만 복잡하고 규모가 큰 환경에서는 일반적인 사용 패턴에 맞게 최적화할 수 있도록 여러 테이블에 데이터를 분산하여 저장할 수 있습니다. 이 테이블의 기본 키는 resourceType, cultureCoderesourceKey를 결합한 복합 키입니다. 단일 리소스 값은 대개 기본 키를 사용하여 요청됩니다. 그림 5는 테이블의 내용 중 일부를 보여 줍니다.

사용자 삽입 이미지

그림 5. 샘플의 리소스 항목 중 일부(더 큰 이미지를 보려면 그림을 클릭하십시오.)

페이지 리소스의 resourceType은 응용 프로그램 내에서의 상대 경로를 포함하는 페이지 이름입니다. 예를 들어 Expressions.aspx의 경우는 SubDir1/Expressions.aspx입니다. 이러한 규칙은 다른 하위 디렉터리에 있는 같은 이름의 페이지를 구분하기 위한 것입니다. 기본 리소스-공급자 모델에서 하위 디렉터리마다 다른 로컬 리소스 어셈블리를 사용하는 방식과 유사합니다. 컨트롤 속성에 대한 리소스 키 역시 일반적인 페이지 리소스와 같은 명명 규칙을 따릅니다. 즉 다음과 같은 형식으로 컨트롤 접두사 뒤에 속성 이름이 붙습니다.

[Prefix].[PropertyName]

전역 리소스에는 사용자 지정 resourceType이 있습니다. 샘플 코드에는 Glossary, CommonTermsConfig와 같은 여러 전역 리소스 범주가 있습니다. 이 경우 리소스 키의 이름은 해당 콘텐츠에 맞게 정해집니다.

데이터 액세스 계층인 StringResourcesDALC는 공급자 모델의 사용 패턴을 기반으로 테이블에서 리소스를 가져오는 작업을 추상화합니다.

ResourceProviderFactory 확장

ResourceProviderFactory 유형은 ASP.NET 2.0의 리소스 액세스를 위한 허브에 해당합니다. 즉 요청된 리소스 유형에 따라 전역 또는 로컬 리소스 공급자를 반환하는 기능을 합니다. ResourceProviderFactoryCreateLocalResourceProvider()CreateGlobalResourceProvider()의 두 메서드 구현을 필요로 하는 추상 기본 유형입니다. 사용자 지정 공급자 팩토리를 만들려면 이 기본 유형을 상속하고 이러한 두 메서드의 구현을 제공해야 합니다. 두 메서드는 모두 IResourceProvider 인터페이스를 구현하는 리소스 공급자의 인스턴스를 반환해야 합니다.

기본 ResourceProviderFactory 유형 선언은 목록 1과 같습니다.

목록 1. ResourceProviderFactory 추상 유형

public abstract class ResourceProviderFactory
{
      protected ResourceProviderFactory();
      public abstract IResourceProvider CreateGlobalResourceProvider(string classKey);
      public abstract IResourceProvider CreateLocalResourceProvider(string virtualPath);
}

ResourceProviderFactory는 컴파일을 위한 페이지 구문 분석 단계와 지역화 API 호출을 위한 런타임 시에 리소스 공급자를 제공합니다.

  • 페이지 파서 - 페이지는 컴파일을 위해 먼저 디자인 타임에 구문 분석되며 로컬 및 전역 리소스에 대한 명시적 식은 이 단계에서 유효성이 검사됩니다. 컴파일이 시작되면 모든 식에 대한 코드가 컴파일된 페이지에 생성됩니다. 리소스 공급자는 이 단계에서 파서에 사용됩니다.
  • 런타임 - 런타임에는 컴파일된 페이지의 식이 의미가 없습니다. 컴파일하는 동안 생성된 코드는 지역화 API를 사용하여 로컬 및 전역 리소스에 액세스합니다. 리소스 공급자는 로컬 및 전역 리소스 유형에 대해 생성됩니다.

샘플 코드에서 DBResourceProviderFactory는 두 경로 모두에 대해 DBResourceProvider를 만듭니다. 이는 로컬 및 전역 리소스가 같은 방법으로 액세스되기 때문입니다. DBResourceProviderFactory에 대한 코드는 목록 2와 같습니다.

목록 2. DBResourceProviderFactory는 ResourceProviderFactory의 사용자 지정 구현이며 데이터베이스 리소스를 지원합니다.

using System;
using System.Web.Compilation;
using System.Web;
using System.Globalization;

namespace CustomResourceProviders
{
  public class DBResourceProviderFactory : ResourceProviderFactory
  {

    public override IResourceProvider CreateGlobalResourceProvider
(string classKey)
    {
      return new DBResourceProvider(classKey);
    }

    public override IResourceProvider CreateLocalResourceProvider
(string virtualPath)
    {
      string classKey = virtualPath;
      if (!string.IsNullOrEmpty(virtualPath))
      {
        virtualPath = virtualPath.Remove(0, 1);
        classKey = virtualPath.Remove(0, virtualPath.IndexOf('/') + 1);
      }
      return new DBResourceProvider(classKey);
    }
  }
}

암시적 식 또는 로컬 리소스를 호출하는 명시적 식의 경우 페이지에 대한 공급자를 만들기 위해 GetLocalResourceProvider()가 호출됩니다. 다음은 로컬 리소스를 사용하는 암시적 식과 명시적 식의 예입니다. 이 코드는 코드 샘플의 Expressions.aspx 페이지에 정의되어 있습니다.

<asp:Label ID="labHelloLocal" runat="server" Text="HelloDefault" meta:resourcekey="labHelloLocalResource1" ></asp:Label>
<asp:Label ID="Label1" runat="server" Text="<%$ Resources:labHelloLocalResource1.Text %>" ></asp:Label>

GetLocalResourceProvider()는 단일 매개 변수를 취하며, 이는 응용 프로그램 디렉터리를 포함하는 페이지의 가상 경로입니다. 위 코드의 두 식 모두 이 매개 변수로 "/LocalizedWebSite/Expressions.aspx"를 전달합니다. 그림 5를 보면 로컬 리소스가 응용 프로그램 디렉터리가 아닌 페이지의 상대 경로를 나타내는 resourceType을 사용하여 저장됨을 알 수 있습니다. 따라서 GetLocalResourceProvider()DBResourceProvider의 인스턴스를 만들기 전에 경로에서 응용 프로그램 디렉터리 부분을 잘라냅니다.

전역 리소스를 요청하는 명시적 식의 경우 식에 직접 지정된 리소스 유형이 GetGlobalResourceProvider()로 전달됩니다. 코드 샘플의 Expressions.aspx 페이지에서 다음과 같은 명시적 식을 살펴보겠습니다.

<asp:Label ID="labHelloGlobal" runat="server" Text="<%$ Resources:CommonTerms, Hello %>"></asp:Label>

이 경우 리소스 유형이 CommonTerms이므로 GetGlobalResourceProvider()CommonTerms를 매개 변수로 전달하여 호출되며, 이 유형에 대한 DBResourceProvider가 생성됩니다.

지정된 리소스 유형에 대해 DBResourceProvider 인스턴스가 하나만 생성되며 이후 사용을 위해 캐시됩니다. 따라서 팩토리는 공급자 인스턴스가 캐시에 없는 경우에만 호출됩니다. 공급자를 만들고 캐시하는 과정은 리소스 액세스에 사용되는 지역화 API 내에 캡슐화되어 있습니다.

ResourceProviderFactory 구성

구성에 다른 ResourceProviderFactory 유형을 지정하지 않는 한 런타임에는 ResxResourceProviderFactory가 사용됩니다. 웹 구성 파일의 <globalization> 부분에서 resourceProviderFactoryType이라는 특성을 볼 수 있는데, 사용할 ResourceProviderFactory 유형을 이 특성을 통해 지정할 수 있습니다. DBResourceProviderFactory를 구성하려면 다음 설정을 추가합니다.

<system.web>

...other settings

      <globalization uiCulture="auto" culture="auto" resourceProviderFactoryType="CustomResourceProviders.DBResourceProviderFactory, CustomResourceProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f201d8942d9dbbb1" />
</system.web>

참고   제공된 샘플 코드의 DBResourceProviderFactoryCustomResourceProviders 어셈블리 내의 CustomResourceProviders 네임스페이스에 속해 있습니다. 이 어셈블리는 강력한 이름을 가지며 GAC(전역 어셈블리 캐시)에 복사할 수 있습니다.

이제 페이지 구문 분석과 런타임에 리소스 공급자를 만들기 위해 DBResourceProviderFactory가 사용됩니다.

IResourceProvider 구현

리소스-공급자 모델의 핵심은 리소스-공급자 유형입니다. ResourceProviderFactory도 중요하지만 저장 위치에 관계없이 런타임 시에 리소스 항목을 반환하는 역할을 하는 것은 결국 리소스 공급자입니다. 이전 섹션에서 설명했듯이 공급자는 ResourceProviderFactory 구현에 의해 만들어지며 이후 사용을 위해 캐시됩니다. 그림 4의 리소스-공급자 모델을 보면 DBResourceProvider 유형이 로컬 및 전역 리소스 모두에 대해 사용됨을 알 수 있습니다. 이 유형은 데이터베이스에서 리소스를 가져오는 역할을 하지만 이 작업을 처리할 때는 DBResourceReaderStringResourcesDALC 구성 요소를 사용합니다.

리소스 공급자는 목록 3과 같은 IResourceProvider 인터페이스를 구현합니다.

목록 3. IResourceProvider 인터페이스

public interface IResourceProvider
{
      object GetObject(string resourceKey, CultureInfo culture);
      IResourceReader ResourceReader { get; }
}

개별 리소스는 GetObject()를 통해 얻어지며 ResourceReader 속성은 공급자 인스턴스의 리소스 유형을 기준으로 리소스 모음을 반환합니다.

페이지 구문 분석 단계에서 페이지에 대한 모든 로컬 리소스를 가져오기 위해 공급자가 사용되며, 명시적 식의 유효성이 검사되고, 컴파일하는 동안 페이지에 대한 코드가 생성됩니다. 로컬 리소스의 경우 암시적 식에 대한 코드 생성을 위해 리소스 판독기가 사용됩니다. 로컬 및 전역 리소스에 대한 명시적 식은 적절한 공급자에 대한 GetObject() 호출과 함께 개별적으로 유효성이 검사됩니다.

런타임 시에는 파서가 생성한 코드가 페이지 초기화에 따라 로컬 및 전역 리소스를 가져오기 위해 GetObject() 호출을 트리거합니다.

개별 데이터베이스 리소스 가져오기

GetObject()에 대한 DBResourceProvider 구현은 다음과 같습니다.

public object GetObject(string resourceKey, CultureInfo culture)
{

  if (string.IsNullOrEmpty(resourceKey))
  {
    throw new ArgumentNullException("resourceKey");
  }  

  if (culture == null)
  {
    culture = CultureInfo.CurrentUICulture;
  }

  string resourceValue = m_dalc.GetResourceByCultureAndKey(culture, resourceKey);
}

실제로는 리소스를 가져오기 위한 작업이 StringResourcesDALC 유형으로 위임되어 데이터베이스 쿼리를 처리하게 됩니다(그림 4 참조). 이 구성 요소는 실제 리소스를 찾는 데 필요한 리소스 대체(필요한 리소스가 없을 때 기본 리소스를 대신 사용하는 것) 및 기타 논리를 공급자로부터 분리합니다.

GetResourceByCultureAndKey()는 데이터베이스 연결을 초기화하고 SqlDataReader를 실행하여 값을 가져옵니다. 이 값에는 리소스 대체 논리(이후에 설명함)에 필요한 값도 포함됩니다.

여러 리소스 가져오기

DBResourceProvider는 다음과 같은 ResourceReader 속성의 구현에서 DBResourceReader의 인스턴스를 반환합니다.

public System.Resources.IResourceReader ResourceReader
{
  get
  {
    ListDictionary resourceDictionary = this.m_dalc.GetResourcesByCulture(CultureInfo.InvariantCulture);

    return new DBResourceReader(resourceDictionary);
  }
}

StringResourcesDALC는 특정 유형(InvariantCulture)에 대한 기본 리소스를 수집하는 기능을 합니다. 쿼리 결과로 만들어진 ListDictionary는 열거를 위해 DBResourceReader로 래핑됩니다.

DBResourceReaderIResourceReader를 구현합니다. 이 구현의 핵심 요소는 다음과 같습니다.

public class DBResourceReader : DisposableBaseType, IResourceReader, IEnumerable<KeyValuePair<string, object>>
{
  private ListDictionary m_resourceDictionary;
  public DBResourceReader(ListDictionary resourceDictionary)
  {
    this.m_resourceDictionary = resourceDictionary;
  }

  public IDictionaryEnumerator GetEnumerator()
  {
    return this.m_resourceDictionary.GetEnumerator();
  }

  // 다른 메서드
}
(참고: 프로그래머 주석은 예제 프로그램 파일에는 영문으로 제공되며 기사에는 이해를 돕기 위해 번역문으로 제공됩니다.)

페이지 파서는 판독기의 사전 열거자를 사용하여 암시적 식에 대한 코드를 생성합니다. 판독기가 제공되지 않은 경우나 판독기의 사전이 비어 있는 경우에는 코드가 생성될 수 없습니다. 암시적 식은 명시적이지 않으므로 모든 속성 값에 값이 있어야 하는 것은 아닙니다. 따라서 암시적 식의 경우 값을 설정하기 위한 코드가 생성되지 않으면 페이지에 기본값이 적용되어 표시됩니다.

리소스 대체

리소스 대체는 리소스-공급자 구현의 중요한 부분입니다. 리소스는 요청 스레드에 대한 현재 UI culture를 기반으로 런타임 시에 요청됩니다.

System.Threading.Thread.Current.CurrentUICulture

요청 스레드의 현재 culture가 "es-EC" 또는 "es-ES"와 같은 특정 culture인 경우 리소스 공급자는 해당 culture에 대한 리소스가 존재하는지 확인해야 합니다. 그러나 중립 culture인 "es"만 지정된 리소스도 있을 수 있습니다. 중립 culture는 상위 culture이며 특정 항목을 찾을 수 없는 경우 상위 culture가 다음으로 확인됩니다. 값이 발견되면 응용 프로그램에 기본 culture가 대신 사용됩니다. 이 예의 경우 기본 culture는 "en"입니다.

리소스 대체는 데이터 액세스 구성 요소인 StringResourcesDALC에 캡슐화됩니다. 리소스를 가져오기 위한 호출이 만들어지면 GetResourceByCultureAndKey()가 호출됩니다. 이 함수는 데이터베이스 연결을 열고 리소스 대체를 수행하는 재귀 함수를 호출한 다음 데이터베이스 연결을 닫습니다. GetResourceByCultureAndKey() 구현은 다음과 같습니다.

public string GetResourceByCultureAndKey(CultureInfo culture, string resourceKey)
{
  string resourceValue = string.Empty;

  try
  {
    if (culture == null || culture.Name.Length == 0)
    {
      culture = new CultureInfo(this.m_defaultResourceCulture);
    }

    this.m_connection.Open();
    resourceValue = this.GetResourceByCultureAndKeyInternal
(culture, resourceKey);
  }
  finally
  {
    this.m_connection.Close();
  }
  return resourceValue;
}

재귀 함수인 GetResourceByCultureAndKeyInternal()은 먼저 지정한 culture에 해당하는 리소스를 찾습니다. 찾는 리소스가 없으면 상위 culture가 검색되고 쿼리가 다시 시도됩니다. 이 시도가 실패하면 리소스 항목을 찾기 위한 마지막 시도를 통해 기본 culture가 사용됩니다. 기본 culture에 대한 리소스 항목이 없으면 이 샘플의 경우 중대한 예외가 발생한 것으로 간주됩니다. GetResourceByCultureAndKeyInternal()의 코드는 다음과 같습니다.

private string GetResourceByCultureAndKeyInternal
(CultureInfo culture, string resourceKey)
{

  StringCollection resources = new StringCollection();
  string resourceValue = null;

  this.m_cmdGetResourceByCultureAndKey.Parameters["cultureCode"].Value
= culture.Name;
               
  this.m_cmdGetResourceByCultureAndKey.Parameters["resourceKey"].Value
= resourceKey;

  using (SqlDataReader reader = this.m_cmdGetResourceByCultureAndKey.ExecuteReader())
  {
    while (reader.Read())
    {
      resources.Add(reader.GetString(reader.GetOrdinal("resourceValue")));
    }
  }

  if (resources.Count == 0)
  {
    if (culture.Name == this.m_defaultResourceCulture)
    {
      throw new InvalidOperationException(String.Format(
Thread.CurrentThread.CurrentUICulture, Properties.Resources.RM_DefaultResourceNotFound, resourceKey));
    }

    culture = culture.Parent;
    if (culture.Name.Length == 0)
    {
      culture = new CultureInfo(this.m_defaultResourceCulture);
    }
    resourceValue = this.GetResourceByCultureAndKeyInternal(culture, resourceKey);
  }
  else if (resources.Count == 1)
  {
    resourceValue = resources[0];
  }
  else
  {
    throw new DataException(String.Format(Thread.CurrentThread.CurrentUICulture, Properties.Resources.RM_DuplicateResourceFound, resourceKey));
  }

  return resourceValue;
}

리소스 대체를 저장 프로시저나 SQL CLR 구성 요소에 캡슐화할 수도 있습니다. 대체 규칙은 데이터베이스 디자인과 연관된 면이 있고, 비즈니스 계층에는 그다지 중요하지 않기 때문입니다.

리소스 캐싱

기본 공급자 모델을 사용할 경우, 리소스 어셈블리에서 리소스를 가져올 때 어셈블리를 한 번 로드하여 응용 프로그램 도메인에 캐시합니다. 데이터베이스 리소스의 경우 모든 리소스 요청 때마다 데이터베이스에 연결하지 않도록 하기 위해서 자체적으로 캐싱 메커니즘을 구현해야 합니다. DBResourceProvider가 이 작업을 처리합니다.

공급자에 대한 GetObject() 구현이 어떻게 구성되는지 앞서 설명했으며 이 때는 캐싱을 사용하지 않았습니다. 데이터베이스의 리소스는 다음과 같이 데이터 액세스 계층으로의 호출을 통해 가져오게 됩니다.

resourceValue = m_dalc.GetResourceByCultureAndKey(culture, resourceKey);

각 리소스 유형마다 하나의 공급자 인스턴스가 존재하며, 반복 사용을 위해 캐시된다는 점을 유의하십시오. 공급자 내에서 각 culture 요청을 위해 사전 내에 리소스 항목을 캐시하면 이 사전 항목은 공급자와 함께 메모리 내에 캐시됩니다. 개체를 가져오기 위한 코드는 먼저 사전 캐시에서 값을 찾고, 없을 경우 데이터베이스에서 개체를 가져온 다음 캐시 항목을 만듭니다. 결과는 다음과 같습니다.

string resourceValue = null;
Dictionary<string, string> resCacheByCulture = null;
if (m_resourceCache.ContainsKey(culture.Name))
{
  resCacheByCulture = m_resourceCache[culture.Name];
  if (resCacheByCulture.ContainsKey(resourceKey))
  {
    resourceValue = resCacheByCulture[resourceKey];
  }
}

if (resourceValue == null)
{
  resourceValue = m_dalc.GetResourceByCultureAndKey(culture, resourceKey);

  lock(this)
  {
    if (resCacheByCulture == null)
    {
      resCacheByCulture = new Dictionary<string, string>();
      m_resourceCache.Add(culture.Name, resCacheByCulture);
    }
  resCacheByCulture.Add(resourceKey, resourceValue);
  }
}

return resourceValue;

캐싱은 데이터베이스에 리소스를 저장할 때 성능을 향상시키기 위해 필요합니다. 이 예에서 값은 응용 프로그램 도메인이 해제될 때까지 캐시되며, 이는 응용 프로그램을 다시 시작하지 않는 한 런타임 시에 데이터베이스 리소스의 동적 업데이트가 반영되지 않는다는 의미입니다. 이러한 동적 업데이트를 허용하려면 데이터베이스 캐시에 종속되도록 리소스를 캐시하기 위한 추가 작업이 필요합니다.

스레드 보호

웹 응용 프로그램에서 고려해야 할 또 하나의 문제는 스레드 보호입니다. 그림 4의 데이터베이스 공급자 모델에 참여하는 구성 요소는 .NET 동기화 기술을 사용하여 스레드가 보호되도록 설계되었습니다.

특정 리소스 유형에 대한 DBResourceProvider 또는 StringResourcesDALC의 인스턴스는 여러 스레드에 의해 호출될 수 있습니다. 간단한 예로 같은 페이지에 대한 두 요청에서 이 인스턴스를 호출할 수 있습니다. StringResourcesDALC 구성 요소에서는 데이터베이스에서 데이터를 가져오는 공용 메서드가 해당 유형에 대해 연결을 열고, 쿼리 매개 변수 값을 설정하고, SqlDataReader를 실행하는 인스턴스 변수를 수정합니다. 이러한 함수에서 스레드를 보호하기 위해 MethodImplAttribute가 적용되었습니다.

[MethodImpl(MethodImplOptions.Synchronized)]

이 특성은 메서드 호출이 이루어지는 동안 StringResourcesDALC 개체를 잠그고 다른 호출자를 차단합니다. 리소스가 캐시되면 성능 향상을 위해 데이터 액세스 구성 요소는 호출되지 않습니다.

DBResourceProvider에서는 사전 캐시에 대한 변경을 일반적인 lock 문을 통해 차단합니다.

lock(this)
   { ... }

이 캐싱 코드에 대한 보다 자세한 내용은 이전 섹션에서 설명했습니다. lock 문은 코드 블록이 실행되는 동안 전체 개체와 해당 멤버를 잠급니다. 즉 한 번에 한 스레드만 캐시에 값을 추가할 수 있습니다.

사용자 지정 리소스 공급자 모델 사용

ASP.NET 2.0 이전에는 각 리소스 유형에 대한 ResourceManager를 직접 인스턴스화하고 그 수명을 관리하기 위해 코드를 작성해야 했습니다. 하지만 ASP.NET 2.0에서는 지역화 API를 사용하여 프로그래밍하는 경우 리소스-공급자 모델이 이 작업을 대신 처리하며, 요청 시 리소스 공급자를 만들고 캐시합니다. 따라서 다음과 같은 방법으로 리소스에 액세스할 때는 ASP.NET 2.0 기술을 사용해야 합니다.

  • 페이지-개체 메서드
  • HttpContext 메서드
  • 지역화 식

마스터 페이지, 웹 페이지 및 사용자 컨트롤은 공용 기본 유형인 TemplateControl을 공유합니다. 앞에서도 언급했지만 이 기본 유형은 리소스 액세스 방식에 있어 GetLocalResourceObject()GetGlobalResourceObject()라는 두 개의 오버로드된 작업을 제공합니다. 이 작업에서는 캐시된 리소스 공급자를 사용하여 공급자의 GetObject() 구현을 통해 리소스를 가져옵니다. 공급자가 아직 캐시되지 않은 경우 ResourceProviderFactoryCreateLocalResourceProvider() 또는 CreateGlobalResourceProvider()를 사용하여 공급자를 만듭니다. 이 방식의 장점은 리소스 값을 가져오기 위한 페이지 코드를 손쉽게 작성할 수 있다는 점입니다.

this.labHelloLocal.Text = this.GetLocalResourceObject("labHelloLocalResource1.Text") as string;

this.labHelloGlobal.Text = this.GetGlobalResourceObject("CommonTerms", "Hello") as string;

사실 암시적 식과 명시적 식을 사용한 페이지를 컴파일할 경우 이와 매우 흡사한 코드가 생성됩니다.

HttpContext 유형에서 정적 메서드를 사용하여 로컬 및 전역 리소스에 액세스할 수도 있습니다. 이 방법은 페이지에 속하지 않는 코드를 작성할 때 유용합니다.

this.labHelloLocal.Text = HttpContext.GetLocalResourceObject("/RuntimeCode.aspx", "labHelloLocalResource1.Text") as string;

this.labHelloGlobal.Text = HttpContext.GetGlobalResourceObject("CommonTerms", "Hello") as string;

실제로는 지역화 식을 사용하는 것이 리소스에 액세스하는 데 훨씬 편리합니다. 지역화 식은 지역화 API를 통해 리소스에 액세스하기 위한 코드를 자동으로 생성하는 선언적인 모델을 제공합니다. 결국 모든 경로의 끝은 지역화 API와 구성된 ResourceProviderFactory로 연결됩니다.

데이터베이스 리소스: 장점과 단점

리소스를 데이터베이스에 저장하면 다음과 같은 여러 이점이 있습니다.

  • 호출 코드에 영향을 주지 않고도 리소스에 복잡한 계층적 요구 사항을 적용할 수 있습니다. 예를 들어 문자열을 번역할 수 있도록 허용하면서도 고객이나 부서에서 기본 문자열을 사용자 정의하도록 허용할 수 있습니다.
  • 유연한 캐싱과 메모리 사용을 통한 콘텐츠 구성 덕분에 방대한 분량의 HTML 콘텐츠를 보다 쉽게 관리할 수 있습니다. 기본적으로 위성 리소스는 포함된 모든 콘텐츠와 함께 응용 프로그램 도메인에 로드되지만 데이터베이스 리소스는 보다 정교한 알고리즘을 통해 캐시되고 해제됩니다.
  • 정보를 데이터베이스라는 통일된 장소에 저장할 수 있습니다. .resx 파일을 여러 개 사용하는 것에 비해 전체적인 관리 효율이 증대됩니다. 또한 번역 작업자와 작업하는 방식도 간단해집니다.

데이터베이스 저장소에는 몇 가지 단점도 있습니다.

  • 이 방식에는 더 많은 고민과 계획이 수반됩니다. 리소스를 테이블로 어떻게 구성할 것인가? 한 테이블에 모두 넣어야 할까? 범주별로 나누어야 할까? 이후에 여러 테이블로 분산하기 위해 단일 저장 프로시저나 SQL CLR 구성 요소로 액세스해야 할까?
  • 데이터베이스에 Visual Studio 2005의 생산성 기능을 통합하려면 추가 작업이 필요합니다. 즉, 로컬 리소스 생성을 사용하여 리소스를 자동으로 생성하거나 식 대화 상자에서 데이터베이스 정보를 볼 수 없습니다. 이 정도 수준의 통합을 위해서는 개발자를 위한 디자인 타임 환경 통합을 위한 사용자 지정 구성 요소를 구축해야 합니다. 이에 대해서는 다음 기사에서 설명하겠습니다.

데이터베이스 리소스에 생산성 기술을 통합하기 위해 추가 작업이 필요하기는 하지만, 이런 단점보다는 장점이 더 크다고 볼 수도 있습니다. 구조와 리소스 구성을 계획하는 일이 기본 리소스-할당 구조를 사용하더라도 어차피 해야 하는 일이라면 더욱 그럴 것입니다.


외부 어셈블리의 리소스에 액세스

리소스-공급자 모델을 사용하여 미리 컴파일된 외부 어셈블리의 리소스에 액세스할 수도 있습니다. 이렇게 하면 웹 및 Windows 응용 프로그램 간에 공용 리소스를 공유할 수 있으므로 버전 관리와 개발을 위한 통일된 지점을 제공할 수 있습니다. 이 섹션에서는 지금까지 설명한 개념을 외부 리소스 어셈블리 유형에 적용하는 방법에 대해 설명합니다.

그림 6은 외부 리소스-공급자 모델을 구성하는 구성 요소를 보여 줍니다.

사용자 삽입 이미지

그림 6. 외부 리소스-공급자 모델에 대한 구성 요소 계층 구조(더 큰 이미지를 보려면 그림을 클릭하십시오.)

이 구현에 대해 다음과 같은 몇 가지 사실을 알 수 있습니다.

  • 전역 리소스만 지원합니다. ASP.NET 2.0에서 기본적으로 제공되는 페이지 리소스 모델을 대체한다는 것은 적절하지 않습니다. 전역 리소스인 경우에만 외부 리소스 어셈블리에서 가져오게 됩니다.
  • ExternalResourceProviderFactory에서 LocalResXResourceProvider에 액세스할 수 없습니다. 이는 내부 유형이므로 코드에서 만들 수 없습니다. ExternalResourceProviderFactory로 기본 공급자를 대체하면 전역 리소스만 지원하게 됩니다. 다른 방법도 있으나 이후 섹션에서 설명하겠습니다.
  • 리소스에 액세스하는 데 ResourceManager가 사용됩니다. 기본 ResourceManager가 이미 어셈블리로부터 리소스를 액세스할 수 있는 방법을 제공하므로 외부 리소스를 액세스하기 위해 이 기능을 대체할 필요가 없습니다.

이제 이 구현의 주요 사항에 대해 알아보겠습니다.

같은 지역화 식, 다른 사용 사례

리소스 공급자는 지역화 식과 지역화 API로 인해 호출되며 외부 리소스에 액세스하기 위해서는 명시적 식이 사용됩니다. 이러한 식은 전역 리소스에 액세스하는 데 사용되는 것과 비슷하지만 약간의 차이가 있습니다. 특히 리소스 유형과 더불어 어셈블리 이름이 지정되어야 한다는 점이 다릅니다. 기본 공급자는 전역 리소스 어셈블리를 찾는 방법을 알고 있지만 이 외부 리소스 공급자는 같은 결과를 얻기 위해 어셈블리 이름을 필요로 합니다.

기본 공급자 모델(명시적 전역 리소스)의 경우 $Resources 식에 대한 구문은 다음과 같습니다.

<%$ Resources: [resourceType], [resourceKey] %>

구문을 다음과 같이 변경하여 ExternalResourceProviderFactory를 구성하면 외부 리소스에 액세스하는 데 같은 식을 사용할 수 있습니다.

<%$ Resources: [assemblyName]|[resourceType], [resourceKey] %>

예를 들어 전역 리소스 유형이 "CommonTerms"인 CommonResources.dll 어셈블리의 리소스에 액세스하려면 다음과 같은 명시적 식을 사용할 수 있습니다.

<asp:Label ID="labGlobalResource" runat="server" Text="<%$ Resources:CommonResources|CommonTerms, Hello %>" ></asp:Label>

그 결과로 페이지가 컴파일되면 다음과 같은 코드가 생성됩니다.

labGlobalResource.Text = this.GetGlobalResourceObject("CommonResources|CommonTerms", "Hello");

이는 올바른 정보를 제공하면 외부 리소스-공급자 모델에서 기존 식과 지역화 API의 코드를 활용할 수 있음을 의미합니다. 리소스 유형에서 어셈블리 이름을 분리하기 위해 최종적으로 정보를 구문 분석하는 것은 ExternalResourceProvider입니다.

ExternalResourceProviderFactory

DBResourceProviderFactory와 마찬가지로 ExternalResourceProviderFactoryResourceProviderFactory를 상속하고 CreateGlobalResourceProvider()CreateLocalResourceProvider()를 다시 정의합니다. 목록 4는 완전한 구현을 보여 줍니다.

목록 4. ExternalResourceProviderFactory의 구현

public class ExternalResourceProviderFactory : ResourceProviderFactory
{

  public override IResourceProvider CreateGlobalResourceProvider
(string classKey)
  {
    return new GlobalExternalResourceProvider(classKey);
  }

  public override IResourceProvider CreateLocalResourceProvider
(string virtualPath)
  {
    throw new NotSupportedException(String.Format
(Thread.CurrentThread.CurrentUICulture, Properties.Resources.Provider_LocalResourcesNotSupported, "ExternalResourceProviderFactory"));
  }
}

CreateGlobalResourceProvider()는 제공된 클래스 키를 사용하여 GlobalExternalResourceProvider 유형을 인스턴스화합니다. 이 공급자에 대한 클래스 키는 어셈블리 이름과 리소스 유형을 포함해야 한다는 점을 유념하십시오. CreateLocalResourceProvider()를 호출하면 NotSupportedException이 발생하는데, 이는 외부 어셈블리에 로컬 리소스를 저장하지 않기 때문입니다. 즉, 페이지에서 로컬 식을 사용하면 구문 분석 예외가 발생합니다. 따라서 로컬 리소스를 계속 지원하려는 경우에는 ExternalResourceProvider를 사용하는 것은 적절한 시나리오가 아닙니다. 이 문제를 해결하는 방법은 사용자 지정 지역화 식을 사용하는 것으로, 자세한 내용은 이후에 설명합니다.

기존 식과 지역화 API에 ExternalResourceProviderFactory를 연결하려면 Web.config의 <globalization> 부분을 수정해야 합니다.

<globalization uiCulture="auto" culture="auto" resourceProviderFactoryType="CustomResourceProviders.ExternalResourceProviderFactory, CustomResourceProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f201d8942d9dbbb1" />

이렇게 하면 기본 공급자 모델이 외부 리소스-공급자 모델로 바뀌게 됩니다. 공급자가 작동하는 방식을 살펴보겠습니다.

GlobalExternalResourceProvider

GlobalExternalResourceProviderIResourceProvider를 구현합니다. 이 공급자는 GlobalResXResourceProvider와 매우 유사하지만 기존 위성 어셈블리에서 전역 리소스를 가져오며, 리소스가 저장된 특정 어셈블리 이름을 알아야 한다는 점이 다릅니다.

GlobalExternalResourceProvider의 생성자는 파이프 기호("|")로 구분된 어셈블리 이름과 리소스 유형을 취하며 이 정보는 다음과 같이 구문 분석됩니다.

public GlobalExternalResourceProvider(string classKey)
{
  if (classKey.IndexOf('|') > 0)
  {
    string[] textArray = classKey.Split('|');
    this.m_assemblyName = textArray[0];
    this.m_classKey = textArray[1];
  }
  else
    throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentUICulture, Properties.Resources.Provider_InvalidConstructor, classKey));

}

생성자에 전달된 classKey 매개 변수의 형식이 잘못되었을 경우 ArgumentException이 발생하며, 페이지 구문 파서는 명시적 식에 대한 오류를 보고합니다. 지역화 API에 대해 직접 작성된 코드는 런타임 시에 실패하게 됩니다.

공급자 인스턴스는 각 고유 어셈블리와 리소스 유형 조합마다 생성됩니다. 유효성 검사를 위해 페이지를 구문 분석하는 동안, 또는 런타임 시에 리소스가 요청되면 다음과 같이 GetObject()가 호출됩니다.

public object GetObject(string resourceKey, System.Globalization.CultureInfo culture)
{
  this.EnsureResourceManager();
  if (culture == null)
  {
    culture = CultureInfo.CurrentUICulture;
  }
  return this.m_resourceManager.GetObject(resourceKey, culture);
}

내부적으로 GlobalExternalResourceProviderResourceManager 유형의 기존 기능에 의존하여 리소스를 얻고 리소스 대체를 처리합니다. 따라서 중요한 것은 올바른 어셈블리에 대한 ResourceManager를 만드는 것입니다. EnsureResourceManager()가 처음으로 호출되면 리소스 어셈블리를 로드하고 이 어셈블리 내에 지정된 유형의 ResourceManager 인스턴스를 만듭니다. 리소스 유형을 포함하지 않는 어셈블리를 지정하면 예외가 발생합니다. 어셈블리를 로드하고 ResourceManager를 만드는 코드는 다음과 같습니다.

Assembly asm = Assembly.Load(this.m_assemblyName);
ResourceManager rm = new ResourceManager(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.m_assemblyName, this.m_classKey), asm);
this.m_resourceManager = rm;

ExternalResourceProvider를 사용하면 웹 응용 프로그램의 \bin 디렉터리에 배포된 모든 어셈블리 및 GAC(전역 어셈블리 캐시)에서 리소스를 가져올 수 있습니다.

로컬 리소스는 지원되지 않기 때문에 공급자는 ResourceReader 속성에 대해서는 NotSupportedException을 반환합니다. 따라서 암시적 지역화 식은 구문 분석되지 않습니다.


사용자 지정 지역화 식 지원

모든 리소스가 다른 위치에 저장되어 있고, App_LocalResources 및 App_GlobalResources에 있는 리소스를 사용할 계획이 없다면 사용자 지정 공급자가 매우 좋은 선택이 됩니다. 로컬 및 전역 리소스에 대한 표준 구현(기본 공급자)을 지원하면서도 다른 원본에서 일부 리소스를 가져올 수 있는 방법(사용자 지정 공급자)이 필요하다면 어떻게 해야 할까요? 사용자 지정 리소스 공급자를 대상으로 하는 사용자 지정 식을 구현하면 됩니다.

ResourceExpressionBuilder의 작동 방식

식은 컴파일에 앞서 페이지 구문 분석 단계에 개입하는 식 작성기에 의해 처리됩니다. 식은 <%$ %>으로 구분된 모든 내용을 포함할 수 있으며, 여기에는 응용 프로그램 설정, 연결 문자열 및 지역화 식이 포함됩니다. 이러한 식의 구문은 다음과 같습니다.

<%$ [prefix]: [declaration] %>

이미 설명했듯이 지역화 식은 접두사 "Resources"를 사용합니다. 페이지 파서는 ResourceExpressionBuilder 유형을 사용하여 이러한 식을 처리하는데, 이는 ResourceExpressionBuilder가 <expressionBuilders> 구성의 런타임 기본값에 대해 접두사 "Resources"에 매핑되기 때문입니다.

<expressionBuilders>
<add expressionPrefix="Resources" type="System.Web.Compilation.ResourceExpressionBuilder, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</expressionBuilders>

컴파일되는 페이지에서는 다음과 같은 작업이 이루어집니다.

  • 페이지가 구문 분석되면 식 작성기의 ParseExpression 작업이 호출되어 식의 구문을 검사합니다. 식이 잘못된 경우(예: 지정한 리소스가 없는 경우) 구문 분석 오류가 생성됩니다.
  • 구문 분석이 성공하면 식 작성기의 GetCodeExpression 작업이 호출되어 식에 대한 코드 생성을 요청합니다. 이 단계가 바로 식 작성기가 페이지 초기화를 위한 코드를 만드는 시점입니다. 생성된 코드는 페이지의 컴파일된 IL에 삽입됩니다.

페이지가 컴파일되지 않도록 설정된 경우 식은 조금 다른 방식으로 처리됩니다. 페이지 컴파일은 각 페이지에 대해 해제할 수 있습니다.

<%@ Page Language="C#" CompilationMode="Never" %>

또는 Web.config 파일을 사용하여 모든 페이지를 컴파일하지 않도록 설정할 수도 있습니다.

<pages compilationMode="Never" />

이 경우 페이지가 요청되어 구문 분석되는 동안 식 작성기의 SupportsEvaluate 속성을 검사하여 컴파일 없이 페이지를 처리할 수 있는지 확인하게 됩니다. 페이지에 대한 코드는 생성되지 않습니다.

런타임 시에 SupportsEvaluate를 다시 한 번 확인하고 EvaluateExpression을 호출하여 각 지역화 식에 대한 값을 가져옵니다.

ResourceExpressionBuilderExpressionBuilder에서 파생됩니다. ExpressionBuilder는 추상 및 가상 메서드를 제공하는 공용 기본 유형으로, 페이지 구문 분석, 코드 생성 및 식 평가를 지원하기 위해 ResourceExpressionBuilder에 의해 구현됩니다. 따라서 사용자 지정 지역화 식을 지원하려면 ExpressionBuilder를 확장하고 자체적인 구현을 제공해야 합니다.

ExpressionBuilder 확장

사용자 지정 지역화 식을 지원하려면 사용자 지정 식 작성기를 구현해야 합니다. ResourceExpressionBuilder와 마찬가지로, 이를 위해서는 ExpressionBuilder를 확장하고 컴파일되지 않은 페이지에 대한 페이지 구문 분석, 코드 생성 및 식 평가를 위한 사용자 지정 구현을 제공해야 합니다.

먼저 이 샘플에 있는 사용자 지정 식 작성기의 용도를 살펴보고, 구현을 위한 구문을 알아보겠습니다. 목표는 <%$ Resources %>에 대한 기본 구현은 그대로 둔 상태에서 외부 어셈블리에서 리소스를 가져오는 기능만 추가하는 것입니다. 이를 위해 리소스 공급자를 완전히 대체하는 방법 대신 새 식을 만들어 처리하는 방법을 사용할 것입니다. 여기에는 새로운 식 접두사, 사용자 지정 ExpressionBuilder, 그리고 이 접두사를 사용자 지정 ExpressionBuilder와 연결할 방법이 필요합니다.

이 예에서는 새 접두사를 "ExternalResource"로 사용합니다. 새 식에 필요한 구문은 다음과 같습니다.

<%$ ExternalResource: [assemblyName]|[resourceType], [resourceKey] %>

이 식은 앞서 설명한 GlobalExternalResourceProvider를 사용하여 지정한 어셈블리에서 리소스를 가져옵니다. 새로운 이 식을 지원하려면 사용자 지정 유형인 ExternalResourceExpressionBuilder를 만들어야 합니다. 표 2는 다시 정의된 각 ExpressionBuilder 메서드에서 제공해야 하는 기능을 요약한 것입니다.

표 2. 다시 정의된 각 메서드에서 제공하는 기능 요약

메서드 설명
EvaluateExpression 컴파일되지 않은 페이지에서 ExternalResource 식에 대한 리소스 값을 반환합니다.
GetCodeExpression ExternalResource 식에 대해 만들어지는 코드를 반환합니다. 이 코드는 사용자 지정 리소스 공급자인 GlobalExternalResourceProvider를 호출합니다.
ParseExpression 식에 대한 리소스에 액세스를 시도하여 ExternalResource 식의 유효성을 검사합니다. 리소스를 찾을 수 없는 경우 페이지 구문 분석은 실패합니다.
SupportsEvaluate 속성 컴파일되지 않은 페이지의 평가가 지원되는지 나타냅니다. 이 구현에서는 true입니다.

ExternalResourceExpressionBuilder를 사용하면 다음과 같은 사용자 지정 지역화 식을 선언할 수 있습니다.

<asp:Label ID="labExternalResource" runat="server" Text="<%$ ExternalResources:CommonResources|CommonTerms, Hello %>" meta:localize="false" ></asp:Label>

식은 컴파일되기 전에 디자인 타임에 구문 분석됨을 기억하십시오. ParseExpression은 리소스 식이 정확하며 요청한 리소스가 실제로 존재하는지 확인하기 위해 페이지를 구문 분석하는 동안 호출됩니다. 이 구현은 다음 코드와 같습니다.

public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context)
{
  if (string.IsNullOrEmpty(expression))
  {
    throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentUICulture,Properties.Resources.Expression_TooFewParameters, expression));
  }

  ExternalResourceExpressionFields fields = null;
  string classKey = null;
  string resourceKey = null;
           
  string[] expParams = expression.Split(new char[] { ',' });
  if (expParams.Length > 2)
  {
    throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentUICulture, Properties.Resources.Expression_TooManyParameters, expression));
  }
  if (expParams.Length == 1)
  {
    throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentUICulture, Properties.Resources.Expression_TooFewParameters, expression));
  }
  else
  {
    classKey = expParams[0].Trim();
    resourceKey = expParams[1].Trim();
  }

  fields = new ExternalResourceExpressionFields(classKey, resourceKey);

             
  ExternalResourceExpressionBuilder.EnsureResourceProviderFactory();
  IResourceProvider rp = ExternalResourceExpressionBuilder.
s_resourceProviderFactory.CreateGlobalResourceProvider(fields.ClassKey);
           
  object res = rp.GetObject(fields.ResourceKey, CultureInfo.InvariantCulture);
  if (res == null)
  {
    throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentUICulture, Properties.Resources.RM_ResourceNotFound, fields.ResourceKey));
  }
  return fields;
}

코드의 대부분은 식의 유효성을 검사하기 위한 것이지만 가장 중요한 부분은 GlobalExternalResourceProvider를 만드는 부분과 리소스를 가져오기 위해 GetObject()를 호출하는 부분입니다.

페이지가 컴파일되면 페이지의 구문이 분석되고 코드가 생성됩니다. 이 시점에서 식 작성기의 GetCodeExpression 구현이 호출됩니다. 이 작업은 런타임 시에 리소스 값을 가져오기 위해 필요한 코드를 반환하며, 구현 내용은 다음과 같습니다.

public override System.CodeDom.CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
  ExternalResourceExpressionFields fields = parsedData as ExternalResourceExpressionFields;

CodeMethodInvokeExpression exp = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(ExternalResourceExpressionBuilder)), "GetGlobalResourceObject", new CodePrimitiveExpression(fields.ClassKey), new CodePrimitiveExpression(fields.ResourceKey));

return exp;
}

GetCodeExpression 호출에 의해 생성된 결과를 포함하는 코드는 다음과 같습니다.

labExternalResource.Text = ExternalResourceExpressionBuilder.GetGlobalResourceObject("CommonResources|CommonTerms", "Hello") as string;

생성된 코드가 ExternalResourceExpressionBuilder에 의해 구현된 정적 메서드에 의존하고 있음을 알 수 있습니다. GetGlobalResourceObjectGlobalExternalResourceProvider를 인스턴스화하고 리소스 항목을 가져오는 도우미 메서드입니다. 컴파일된 페이지의 경우 이 코드는 런타임 시에 외부 리소스에서 값을 가져옵니다.

컴파일되지 않은 페이지의 경우는 EvaluateExpression 호출을 통해 런타임에 식이 평가됩니다. ExternalResourceExpressionBuilderEvaluateExpression을 다시 정의하여 구현하고, GlobalExternalResourceProvider를 사용하여 적합한 리소스를 가져옵니다.

public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
  ExternalResourceExpressionFields fields = parsedData as ExternalResourceExpressionFields;

  ExternalResourceExpressionBuilder.EnsureResourceProviderFactory();
  IResourceProvider provider = ExternalResourceExpressionBuilder.
s_resourceProviderFactory.CreateGlobalResourceProvider(fields.ClassKey);

  return provider.GetObject(fields.ResourceKey, null);
}

사용자 지정 식 작성기를 구성하고 나면 외부 어셈블리에서 리소스를 가져오기 위한 선언적 문을 자유롭게 사용할 수 있으며, App_LocalResources 또는 App_GlobalResources에서 값을 가져오기 위한 기본 지역화 식도 그대로 사용할 수 있습니다.

ExpressionBuilder 구성

사용자 지정 식 작성기를 구성하려면 이를 Web.config의 <expressionBuilders> 부분에 추가해야 합니다. 이 예에서는 다음 구성을 사용하여 ExternalResourceExpressionBuilder를 "ExternalResources" 접두사에 연결했습니다.

<expressionBuilders>
  <add expressionPrefix="ExternalResources" type="CustomResourceProviders.ExternalResourceExpressionBuilder, CustomResourceProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f201d8942d9dbbb1"/>
</expressionBuilders>

이제 "ExternalResources" 접두사를 사용하는 모든 리소스 식은 이전 단원에서 설명한 ExternalResourceExpressionBuilder 구현에 따라 구문 분석 또는 평가됩니다.

로컬, 전역 및 외부 리소스에 액세스

목록 5는 기본 원본 및 사용자 지정 원본에서 리소스를 가져오기 위한 세 가지 지역화 식(암시적, 명시적, 사용자 지정 명시적)의 적용을 보여 줍니다.

목록 5. 암시적, 명시적, 사용자 지정 명시적 방식의 식을 한 페이지에 사용

<asp:Label ID="labHelloLocal" runat="server" Text="HelloDefault" meta:resourcekey="labHelloLocalResource1" ></asp:Label>

<asp:Label ID="Label1" runat="server" Text="<%$ Resources:labHelloLocalResource1.Text %>" ></asp:Label>

<asp:Label ID="labHelloGlobal" runat="server" Text="<%$ Resources:CommonTerms, Hello %>" ></asp:Label>

<asp:Label ID="labExternalResource" runat="server" Text="<%$ ExternalResources:CommonResources|CommonTerms, Hello %>" meta:localize="false" ></asp:Label>

리소스 공급자를 대체하지 않고 사용자 지정 지역화 식을 사용하면 각 페이지에 대한 로컬 리소스, 웹 사이트와 함께 컴파일되는 전역 리소스뿐만 아니라 외부 리소스(또는 다른 원본)의 맞춤형 리소스까지 사용할 수 있는 유연성을 확보할 수 있습니다. ExternalResourceExpressionBuilder를 사용하면 앞서 설명한 정적 도우미 메서드인 GetGlobalResourceObject()를 사용하여 외부 리소스에 직접 액세스할 수 있습니다.

string s = ExternalResourceExpressionBuilder.GetGlobalResourceObject("CommonResources|CommonTerms", "Hello") as string;

이 기술을 사용하면 기본 리소스 공급자를 자체 공급자로 대체할 필요가 없습니다. 대신 사용자 지정 지역화 식에서 생성되는 코드를 사용하여 필요에 따라 외부 어셈블리의 리소스를 가져올 수 있습니다.


결론

이 기사에서는 데이터베이스나 외부 리소스 어셈블리의 리소스에 액세스하기 위한 사용자 지정 리소스-공급자 모델을 만드는 방법을 배웠습니다. 또한 기본 공급자 모델에 다른 리소스 저장소를 통합하기 위해 사용자 지정 지역화 식을 만드는 방법도 살펴봤습니다. ASP.NET 2.0의 확장 기능을 사용하면 리소스 할당 및 검색을 위한 사용하기 쉬운 대체 방식을 만들 수 있습니다. 이러한 기능의 훌륭한 점은 선언적인 지역화 식을 사용하여 자연스럽게 ASP.NET 2.0 프로그래밍 모델과 연계할 수 있다는 점입니다.

이어지는 다음 기사에서는 전체 그림의 나머지 반에 해당하는 내용을 살펴볼 것입니다. 즉 리소스 식을 만들고 적절한 저장소 위치에 리소스를 생성하기 위해 디자인 타임 환경을 제어하는 방법을 배우게 됩니다.


추가 리소스

ㆍMichele의 블로그: http://www.dasblonde.net/ (영문)
IDesign Inc (영문)

출처 : 한국 마이크로소프트 MSDN (2007년 1월)

Posted by 상현넘™

댓글을 달아 주세요

요약: Microsoft Office Word 2003 및 Microsoft Office Excel 2003을 ASP.NET 2.0 웹 사이트에 통합하여 Word에서 편지 양식과 Excel에서 그래픽 보고서를 생성하는 방법에 대해 살펴봅니다.

개요

Microsoft는 Microsoft .NET Framework 2.0 및 Microsoft Visual Studio 2005를 선보이면서 Microsoft Office System용 Microsoft Visual Studio 도구 버전 2003을 사용하는 Microsoft Office 프로그래밍 기능에 새로운 방식을 추가했습니다. Microsoft Office System용 Microsoft Visual Studio 2005 도구는 Visual Studio에서 Microsoft Office Word 2003 또는 Microsoft Office Excel 2003의 IDE(통합 개발 환경)에 직접 프로그래밍하는 기능을 초기 버전에 추가하여 더욱 개선된 기능을 제공합니다. 이로써 작성한 구성 요소와 상호 작용하는 Office 응용 프로그램을 더욱 간편하게 만들 수 있게 되었습니다.

이 기사에서는 웹 서비스, 작업창, Word 및 Excel과의 상호 작용을 비롯한 Office 프로그래밍 기능의 몇 가지 새로운 측면을 활용하는 방법을 살펴보겠습니다. 웹 서비스를 사용하여 ASP.NET 2.0 웹 사이트에서 Microsoft SQL Server 데이터베이스에 연결하고, 데이터베이스의 데이터를 바탕으로 Word 편지 및 Excel 그래프를 만드는 방법을 보여 주는 코드를 설명할 것입니다. 이 기사에는 두 가지 웹 서비스를 만드는 Microsoft Visual Basic .NET 코드 샘플이 포함되어 있습니다. 첫 번째 웹 서비스는 모든 판매 직원 정보를 지역별로 추출하고 Word에서 감사 편지를 만듭니다. 두 번째 웹 서비스는 특정 기간에 대한 모든 구매 정보를 추출하고 Excel 2003에서 그래프를 작성합니다.

그림 1에서는 이 솔루션의 논리 구성도를 보여 줍니다.

그림 1. 응용 프로그램의 논리 구성도


예에서 사용된 웹 사이트는 Visual Studio 2005 시작 이벤트 중에 사용된 Adventure Works 웹 사이트에 바탕을 두고 있습니다. Adventure Works 웹 사이트는 ASP.NET 2.0 기술을 사용하여 작성되었으며 데이터베이스로 Microsoft SQL Server 2005를 사용합니다.

필요한 소프트웨어

이 솔루션에는 다음 소프트웨어가 필요합니다.

  • Microsoft Windows Server 2003 Standard Edition
  • Microsoft Office 2003 Professional Edition
  • 2007 Microsoft Office system용 Microsoft Visual Studio 2005 도구
  • Microsoft Visual Studio 2005 Professional Edition
  • Microsoft SQL Server 2005
  • Microsoft 인터넷 정보 서비스(IIS) 6.0
  • Microsoft ASP.NET 2.0


Visual Studio 2005를 사용하여 웹 서비스 만들기

먼저 Adventure Works라는 빈 프로젝트를 만듭니다.

  1. Visual Studio 2005에서 Adventure Works 프로젝트에 AdventureWeb이라는 ASP.NET 웹 서비스 프로젝트를 추가합니다.
  2. 그 다음 AdvOrders.asmx라는 첫 번째 웹 서비스를 추가합니다. 이 웹 서비스는 Excel에서 회계 연도의 판매 그래프를 만드는 데 사용됩니다. 웹 서비스 코드가 웹 프로젝트에 추가됩니다.
  3. Verify라는 함수를 만듭니다.
    참고참고:
    이 함수는 웹 서비스가 올바르게 작동하는지 확인합니다. 이를 통해 응용 프로그램을 시작하기 전에 기본 웹 서비스 기능을 확인할 수 있습니다. 웹 서비스가 작동하지 않으면 모든 응용 프로그램 상호 작용이 차단됩니다.

    함수를 만들려면 다음 코드를 웹 서비스 앞에 삽입합니다.

    <WebMethod(Description:="Returns OK if the Web service is online", _
          EnableSession:=False)> _
          Public Function Verify() As String
                Return "OK"
          End Function

    함수는 OK 값을 반환해야 합니다.


SQL Server 2005 데이터베이스에 연결

다음은 Excel 워크시트의 ListObject 개체를 채우는 데 사용되는 데이터 집합을 생성하는 함수를 만들어야 합니다.

  1. 먼저 필요한 SQL 관련 네임스페이스를 가져옵니다. 이를 위해 웹 서비스 코드에 다음 줄을 삽입합니다.
    Imports System.Web
    Imports System.Data Imports System.Data.SqlClient
    Imports System.Web.Services
    Imports System.Web.Services.Protocols

  2. 기본 SQL 관련 네임스페이스를 가져온 다음에는 데이터 집합을 생성하는 함수를 만들어야 합니다. RequestData라는 이 함수는 SQL 데이터베이스에 연결하고 Sales.SalesOrderDetail 테이블에서 데이터를 추출하는 쿼리를 시작합니다. 이 쿼리는 판매 금액을 수집하고 이를 해당 연도 값으로 합산합니다. 예를 들어 2004년에 해당하는 모든 판매 금액을 추출하여 합산합니다.

  3. 다음 함수를 코드에 추가합니다.
    <WebMethod(Description:="Returns dataset with information about orders", _
          EnableSession:=False, BufferResponse:=True, CacheDuration:=600000)> _
          Public Function RequestData() As DataSet

                Dim OrdersData As New DataSet   '반환할 데이터 집합입니다.
                Dim ConnectionToSql As New SqlConnection(System.Configuration.ConfigurationManager.AppSettings("ConnectionSql"))
                ConnectionToSql.Open()
                Dim daLinks As New SqlDataAdapter("SELECT TOP (100) PERCENT SUM(Sales.SalesOrderDetail.LineTotal) AS Total, DATEPART(yy, Sales.SalesOrderHeader.OrderDate) AS Year FROM Sales.SalesOrderDetail INNER JOIN Sales.SalesOrderHeader ON Sales.SalesOrderDetail.SalesOrderID = Sales.SalesOrderHeader.SalesOrderID GROUP BY DATEPART(yy, Sales.SalesOrderHeader.OrderDate)ORDER BY Year", ConnectionToSql)
                daLinks.Fill(OrdersData, "View1")

                Return OrdersData
          End Function

  4. 웹 서비스를 완성하였으면 저장하고 Microsoft 인터넷 정보 서비스(IIS) 6.0 및 ASP.NET 2.0이 실행되고 있는 웹 서버에 게시합니다.
    참고참고:
    web.config 파일에서 데이터베이스에 대한 연결 문자열을 설정해야 합니다.

Excel 워크시트 만들기

다음은 웹 서비스를 사용하여 추출한 데이터를 바탕으로 그래프를 생성하는 Excel 워크시트를 만듭니다.

  1. 먼저 Visual Studio 2005에서 AdventureWorks 프로젝트에 DiagramOrder라는 이름으로 Excel 프로젝트를 추가합니다. DiagramOrder.xls라는 이름의 빈 Excel 문서를 만들지 여부를 묻는 메시지가 표시됩니다.
  2. 확인을 클릭하여 문서를 만듭니다. Visual Studio 2005 내에서 Excel IDE가 열립니다.
  3. 앞에서 만든 웹 서비스에 대한 참조를 추가합니다. 이를 위해 Excel 프로젝트를 마우스 오른쪽 단추로 클릭하고 웹 참조 추가 옵션을 선택합니다. 웹 서비스를 찾기 위한 메시지가 표시됩니다.
  4. 이 솔루션 옵션에서 웹 서비스를 선택하고 이름을 AdvWebservice로 지정합니다.
  5. Visual Studio 도구 상자에서 ListObject 컨트롤을 DiagramOrder.xls에 있는 sheet1의 A-5 상자로 끌어서 놓고 이름을 List1로 지정합니다.
  6. 다음과 같이 프로젝트에 웹 서비스의 네임스페이스에 대한 참조를 추가합니다.
    Imports DiagramOrder.AdvWebservice

  7. 다음 코드를 워크시트의 시작 섹션에 삽입합니다.
    '웹 서비스가 온라인 상태인지 확인합니다.
          If AdvWebService.Verify.ToString = "OK" Then
             '웹 서비스가 온라인 상태이면 새 데이터 집합을 만들고 List1을 채웁니다.
             Dim ds As New DataSet
             ds = AdvWebService.RequestData

             List1.AutoSetDataBoundColumnHeaders = True
             '셀 자동 설정
             List1.DataSource = ds
             List1.DataMember = "View1"
          Else
             '웹 서비스가 온라인 상태가 아닌 경우 Excel에서 사용자에게 메시지를 표시합니다.
             MessageBox.Show("Attenction: The Web service is unreacheable", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, False)
          End If

  8. 이 코드는 웹 서비스가 온라인 상태인지 여부를 확인합니다. 온라인 상태인 경우 코드는 데이터 집합을 생성하고 앞서 만든 List1 컨트롤을 채웁니다.
  9. Excel 워크시트에서 ListObject 컨트롤을 선택합니다. 컨트롤에서 그래프를 볼 수 있는 메뉴가 열립니다.

    그림 2. 그래프 마법사

  10. 차트 단추를 클릭하여 차트 마법사를 시작합니다.
  11. 3차원 세로 막대형을 클릭하고 다음을 클릭합니다. 데이터 원본을 확인합니다.
  12. 계열 탭을 클릭합니다.
  13. 이름 필드에 올바른 열 머리글이 선택되어 있는지 확인합니다.
  14. 필드에 올바른 데이터가 선택되어 있는지 확인합니다.
  15. 원하는 경우 차트 제목과 축 제목을 입력한 다음 마침을 클릭합니다.

    그림 3. 차트 작성

    사용자 삽입 이미지

  16. DiagramOrder 프로젝트를 마우스 오른쪽 단추로 클릭하고 디버그를 선택합니다.
  17. 새 인스턴스 시작을 클릭합니다. 응용 프로그램이 시작됩니다.

    그림 4. 샘플 Excel 워크시트

    사용자 삽입 이미지



판매 데이터를 추출하는 웹 서비스 만들기

Word 2003에서 웹 서비스를 통해 Adventure Works 데이터베이스를 쿼리하여 판매 사원들에게 연간 판매 실적에 대한 감사 편지를 작성합니다.

이 웹 서비스에는 다음 4개의 공용 함수가 포함됩니다.

  • Verify. Word에서 웹 서비스가 올바르게 작동하는지 확인하는 데 사용됩니다.
  • TerritoryReturn. 데이터베이스에서 모든 필드를 추출합니다.
  • ListEmployeeSales. 지역에 따라 모든 직원 판매를 추출합니다.
  • EmployeeInformation. ID에 따라 모든 직원 정보를 추출합니다.

다음 절차에서는 판매 데이터를 추출하는 웹 서비스를 만드는 방법을 보여 줍니다.

  1. 먼저 AdventureWeb 웹 프로젝트에 EmployeeSales.asmx라는 웹 서비스를 만듭니다.
  2. 그런 다음 데이터베이스에서 모든 지역을 추출하고 지역의 모든 데이터를 포함하는 DataSet 개체를 반환하는 TerritoryReturn 함수를 만듭니다.

    '데이터베이스에서 모든 지역을 반환합니다.
          <WebMethod(Description:="Return all Territory", _
             EnableSession:=False)> _
             Public Function TerritoryReturn() As DataSet
             Dim TerritoryTable As New DataSet
             Dim ConnectionToSQL As New SqlConnection(System.Configuration.ConfigurationManager.AppSettings("ConnectionSql"))
             Dim daLinks As New SqlDataAdapter("SELECT Sales.SalesTerritory.*" _
             & "FROM Sales.SalesTerritory", ConnectionToSQL)

             ConnectionToSQL.Open()
             daLinks.Fill(TerritoryTable, "Territory")

             Return TerritoryTable

             ConnectionToSQL.Close()

          End Function

  3. 지역에 따라 모든 판매 직원 데이터를 추출하고 DataSet 개체를 반환하는 ListEmployeeSales 함수를 만듭니다.

    '지역에 따라 판매 직원 데이터를 포함하는 DataSet 개체를 반환합니다.
          <WebMethod(Description:="Returns dataset with information about Sales Employee", _
          EnableSession:=False, BufferResponse:=True, CacheDuration:=600000)> _
          Public Function ListEmployeeSales(ByVal Territory As String) As DataSet

             Dim SalesData As New DataSet
             '반환할 데이터 집합입니다.
             Dim ConnectionToSql As New SqlConnection(System.Configuration.ConfigurationManager.AppSettings("ConnectionSql"))
             ConnectionToSql.Open()
             Dim daLinks As New SqlDataAdapter("SELECT Person.Contact.FirstName, Person.Contact.LastName, Sales.SalesTerritory.TerritoryID, HumanResources.Employee.EmployeeID " _
       & "FROM Person.Contact INNER JOIN HumanResources.Employee ON Person.Contact.ContactID = HumanResources.Employee.ContactID " _
       & "INNER JOIN Sales.SalesPerson AS SalesPerson_1 ON HumanResources.Employee.EmployeeID = SalesPerson_1.SalesPersonID " _
       & "INNER JOIN Sales.SalesTerritory ON SalesPerson_1.TerritoryID = Sales.SalesTerritory.TerritoryID " _
       & "WHERE (SalesPerson_1.TerritoryID = " & Territory & ")", ConnectionToSql)
             daLinks.Fill(SalesData, "SalesEmployeeTable")

             Return SalesData

             ConnectionToSql.Close()
          End Function

  4. 마지막으로 만들 함수는 직원의 ID에 따라 해당 직원과 관련된 모든 정보(예: 이름 및 주소)를 추출합니다. 이 함수는 추출한 모든 정보를 포함하는 DataSet 개체를 반환합니다.

    '각 판매 직원에 대한 모든 정보를 반환합니다.
          <WebMethod(Description:="Return all information about sales employee", _
          EnableSession:=False)> _
          Public Function EmployeeInformation(ByVal EmployeeID As String) As DataSet
             Dim EmployeeInfo As New DataSet
             Dim ConnectionToSQL As New SqlConnection(System.Configuration.ConfigurationManager.AppSettings("ConnectionSql"))
             Dim daLinks As New SqlDataAdapter("SELECT Person.Contact.FirstName, Person.Contact.LastName, Sales.SalesTerritory.TerritoryID, HumanResources.Employee.EmployeeID, " _
             & "Person.Address.AddressLine1, Person.Address.City, Person.Address.PostalCode, SalesPerson_1.SalesLastYear, SalesPerson_1.SalesQuota " _
             & "FROM Person.Contact INNER JOIN " _
                & "HumanResources.Employee ON Person.Contact.ContactID = HumanResources.Employee.ContactID INNER JOIN " _
             & "Sales.SalesPerson AS SalesPerson_1 ON HumanResources.Employee.EmployeeID = SalesPerson_1.SalesPersonID INNER JOIN " _
             & "Sales.SalesTerritory ON SalesPerson_1.TerritoryID = Sales.SalesTerritory.TerritoryID INNER JOIN " _
                & "HumanResources.EmployeeAddress ON HumanResources.Employee.EmployeeID = HumanResources.EmployeeAddress.EmployeeID INNER JOIN " _
             & "Person.Address ON HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID AND " _
                & "HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID AND " _
             & "HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID AND " _
             & "HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID " _
             & "WHERE (HumanResources.Employee.EmployeeID = " & EmployeeID & ")", ConnectionToSQL)

             ConnectionToSQL.Open()
             daLinks.Fill(EmployeeInfo, "EmployeeInformation")

             Return EmployeeInfo

             ConnectionToSQL.Close()

          End Function

  5. 마지막으로, ASP.NET 2.0 및 IIS 6.0이나 IIS 5.0을 실행하는 서버에 AdventureWeb 웹 프로젝트를 다시 게시합니다.
    참고참고:
    Visual Studio 2005에서 디버그 요청을 시작하여 웹 서비스가 작동하는지 확인할 수 있습니다. localhost 모드를 사용하여 시작하면 데이터를 삽입하여 웹 서비스 및 응용 프로그램이 의도한 대로 작동하는지 확인할 수 있습니다.
그림 5. Employee Sales 웹 서비스 사용자 인터페이스
사용자 삽입 이미지


그림 6. Employee Sales 웹 서비스
사용자 삽입 이미지



Word 2003 문서 서식 파일 만들기

이 Word 문서 서식 파일은 웹 서비스에 연결하고 사용자 지정 작업창에서 드롭다운 목록 컨트롤을 사용하여 모든 지역을 표시합니다. 사용자는 지역을 선택하고 Search를 클릭하여 해당 지역의 모든 판매 직원을 추출할 수 있습니다.

작업창에 표시되는 다른 목록 컨트롤에는 직원의 성이 들어 있습니다. 사용자는 직원 이름을 클릭하고 Select Employee를 클릭하여 직원 데이터를 추출하고 이 데이터로 워드 문서를 채울 수 있습니다.

웹 서비스를 만든 다음에는 Word 2003에서 Microsoft Office System용 Microsoft Visual Studio 2005 도구(Office용 Visual Studio 도구)를 사용하여 (동적) 문서 서식 파일을 만들어야 합니다.

  1. 먼저 AdventureWorks 솔루션에 Visual Basic .NET으로 작성된 새 Word 2003 프로젝트를 추가합니다. 프로젝트의 이름을 CongratulationLetter로 지정합니다.
  2. Excel 프로젝트와 마찬가지로 프로젝트에서 기존 문서를 사용할 것인지 새 문서를 사용할 것인지 묻는 메시지가 표시됩니다.
  3. 기존 문서 복사를 선택하고 찾아보기를 클릭합니다.
  4. CongratulationLetter.doc을 선택하고 확인을 클릭합니다.

    그림 7. Visual Studio 내의 축하 편지

    사용자 삽입 이미지


Excel 프로젝트와 비슷한 방식으로, Visual Studio 2005 내에 Word 2003 IDE가 표시됩니다.

참고참고:
워드 문서 내에서 기존 양식 필드(회색 대괄호)를 볼 수 있습니다. 이러한 양식 필드는 Office용 Visual Studio 2005 도구의 개체이며 관련 속성을 가지고 있습니다. 필드의 속성을 보려면 필드 내부로 커서를 이동합니다.


작업 컨트롤 작업창 만들기

다음으로, Word 2003의 사용자 지정 작업창에 로드할 컨트롤을 만들어야 합니다.

  1. Word 2003 IDE에서 솔루션 탐색기를 마우스 오른쪽 단추로 클릭하고 TaskPane이라는 새 폴더를 추가합니다.
  2. TaskPane 폴더에서 새 항목을 추가합니다. 작업 창 컨트롤 유형을 선택하고 이름을 TaskMenu.vb로 지정합니다.
  3. 이 개체에 다음 컨트롤을 추가합니다.
    1. DropDownList 2개
    2. Label 3개
    3. Button 2개
  4. 그림 8은 이러한 컨트롤을 추가한 뒤의 작업창을 보여 줍니다.

    그림 8. Word 2003의 샘플 작업창

    사용자 삽입 이미지


  5. TaskMenu.vb 컨트롤에 앞서 다음 코드를 첫 번째 DropDownList에 추가합니다.

    Private Sub TaskMenu_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          Dim Ds As New DataSet

          Ds = AdvWebService.TerritoryReturn

          ListTerritory.DataSource = Ds
          ListTerritory.DisplayMember = "Territory.Name"
          ListTerritory.ValueMember = "Territory.TerritoryID"

    End Sub

  6. 그 다음 두 개의 단추에 대한 함수를 만듭니다.

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
          ListEmployee.Visible = True
          Label3.Visible = True

          Dim Ds2 As New DataSet

          Ds2 = AdvWebService.ListEmployeeSales(ListTerritory.SelectedValue.ToString)
          ListEmployee.DataSource = Ds2
          ListEmployee.DisplayMember = "SalesEmployeeTable.LastName"
          ListEmployee.ValueMember = "SalesEmployeeTable.EmployeeID"

    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
          Dim Ds3 As New DataSet
          Ds3.Clear()
          Ds3 = AdvWebService.EmployeeInformation(ListEmployee.SelectedValue.ToString)

          Dim FName As New Binding("Text", Ds3, "EmployeeInformation.FirstName")
          Dim LName As New Binding("Text", Ds3, "EmployeeInformation.LastName")
          Dim AddressL As New Binding("Text", Ds3, "EmployeeInformation.AddressLine1")
          Dim PCode As New Binding("Text", Ds3, "EmployeeInformation.PostalCode")
          Dim City As New Binding("Text", Ds3, "EmployeeInformation.City")
          Dim DearName As New Binding("Text", Ds3, "EmployeeInformation.FirstName")
          Dim TotalSales As New Binding("Text", Ds3, "EmployeeInformation.SalesLastYear")
          Dim TotalRevenue As New Binding("Text", Ds3, "EmployeeInformation.SalesQuota")


          CongratulationLetter.Globals.ThisDocument.FirstName.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.FirstName.DataBindings.Add(FName)

          CongratulationLetter.Globals.ThisDocument.LastName.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.LastName.DataBindings.Add(LName)

          CongratulationLetter.Globals.ThisDocument.address.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.address.DataBindings.Add(AddressL)

          CongratulationLetter.Globals.ThisDocument.zip.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.zip.DataBindings.Add(PCode)

          CongratulationLetter.Globals.ThisDocument.city.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.city.DataBindings.Add(City)

          CongratulationLetter.Globals.ThisDocument.Dear.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.Dear.DataBindings.Add(DearName)

          CongratulationLetter.Globals.ThisDocument.TotalSales.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.TotalSales.DataBindings.Add(TotalSales)

          CongratulationLetter.Globals.ThisDocument.revenue.DataBindings.Clear()
          CongratulationLetter.Globals.ThisDocument.revenue.DataBindings.Add(TotalRevenue)

    End Sub

  7. "ThisDocument_Startup" 서브루틴 내에 다음 코드를 추가합니다.

    '작업창에 사용자 컨트롤 TaskMenu.vb를 추가합니다.
    Me.ActionsPane.Controls.Add(ActionMenu)
    Me.ActionsPane.Visible = True

Word 문서 서식 파일에 사용자 지정 작업창과 TaskMenu 컨트롤이 추가되었습니다.

CongratulationLetter.doc 응용 프로그램의 디버그 버전을 시작한 다음, 지역 및 직원 데이터를 변경하면서 Word 문서의 데이터가 어떻게 달라지는지 확인해 보십시오.


결론

이 기사에서는 ASP.NET 2.0 웹 사이트의 웹 서비스를 사용하여 Office 2003 응용 프로그램을 통합하는 방법을 살펴보았습니다. 구매서, 운송 서류 등의 다른 시나리오에 대해 만들 수 있는 응용 프로그램도 생각해 보십시오.

추가 리소스

자세한 내용은 다음 리소스를 참조하십시오.

출처 : 한국 마이크로소프트 MSDN (2006년 11월)
Posted by 상현넘™

댓글을 달아 주세요

웹사이트를 만들다보면 입력박스에서 데이터의 유효성 검사를 필히 하게된다.
특히 회원가입을 할 경우 필수적이라고 할수 있다.
ASP.NET에는 이런 유효성 검사를 할수 있는 유효성 검사 컨트롤을 지원하고 있다.



1. ASP.NET에서 지원하는 유효성 검사 컨트롤
RequiredFieldValidator 사용자가 데이터를 입력 하였는지 또는 어떤 선택을 하였는지 확인한다. 만일 아무런 입력이 되지 않았으면 에러를 발생한다.
RegularExpressionValidator 사용자 입력에 대한 정규식을 확인한다. 정규식을 활용하면 우편번호나 전화번호 또는 자기가 직접 만든 정규 데이터 형식의 다양한 입력 값의 유효성을 확인을 할 수 있다.
CompareValidator 고정된 값과 입력 컨트롤 또는 다른 입력 컨트롤과의 값을 비교한다. 예를 들어 비밀번호의 경우 비밀번호 확인 텍스트 박스에 사용할 수 있다. 또한 형식화된 데이터나 숫자의 비교에 사용할 수 있다.
RangeValidator CompareValidator와 비슷하지만 입력한 값을 고정된 두 값을 비교할 수 있다.
CustomValidator 개발자가 직접 유효성 검사 프레임워크의 특정 코드를 입력하도록 허용하는 컨트롤이다.


2. 필수입력 유효성 검사 구현
 - 유효성 검사 서버 컨트롤을 사용하여 모든 필드에 값을 채워놓도록 강제하는 것을 구현하여 보자.
<asp:TextBox ID="txtUserID" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="txtUserID" ErrorMessage="아이디는 필수입력 입니다." Display="Dynamic"></asp:RequiredFieldValidator>


3. 정규식 사용하기
 - 사용자 아이디나 비밀번호 필드에 들어갈 수 있는 문자에 대한 강제하는 작업이 필요로 하다. 우리는 이 작업을 위해 "RegularExpressionValidator" 컨트롤을 사용할 것이다. 정규식은 우편번호, 전화번호, 전자우편 주소처럼 간결한 표현을 확인하고자 하는 경우 매우 유용하게 사용할 수 있다.
<asp:TextBox ID="txtUserID" runat="server"></asp:TextBox>
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="txtUserID" ValidationExpression="[a-zA-Z]{6,10}" ErrorMessage="아이디는 반드시 6~10자리의 문자로 이루어져야 합니다." Display="Dynamic"></asp:RegularExpressionValidator>
정규식 예제)
ValidationExpression=".*[@#$%^&*/].*" ErrorMessage="반드시 @#$%^&*/. 문자 중 하나를 포함해야 합니다."
ValidationExpression="[^\s]{4,12}" ErrorMessage="비밀번호는 반드시 4~12자리의 공백 없는 문자로 이루어져야 합니다."


4. 컨트롤 입력 값 비교
 - 비밀번호와 비밀번호 확인 필드가 일치하는지 확인해야 한다. 이것을 구현하기 위해 두 개의 입력 컨트롤을 동시에 비교할 수 있는 CompareValidator 컨트롤을 사용할 것이다.
<asp:TextBox ID="txtPassword" runat="server" TextMode="Password"></asp:TextBox>
<asp:TextBox ID="txtPassword2" runat="server" TextMode="Password"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="txtPassword2" ControlToCompare="txtPassword" ErrorMessage="비밀번호가 일치하지 않습니다." Display="Dynamic"></asp:CompareValidator>


5. 사용자 정의형
 - 우리는 우리들의 가상의 사이트에 아이디가 이미 존재하는지 확인하는 과정이 필요하다. 이것을 위해서는 서버상에 몇 가지 데이터를 찾아보는 작업이 필요하다. 실제는 데이터베이스를 사용하여 구현하는 것이 원칙이나 가상으로 이 상황을 구현하기 위해 아이디가 "aaa"가 아니면 중복이 아니라고 판별하는 가상의 함수를 작성해 보자.

 * 서버 이벤트 스트립트를 이용하여 작성
<script runat="server">
 protected void CheckID(object source, ServerValidateEventArgs args)
 {
  args.IsValid = !args.Value.Equals("aaa");
 }
</script>


<asp:TextBox ID="txtUserID" runat="server"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ControlToValidate="txtUserID" OnServerValidate="CheckID" ErrorMessage="이미 존재하는 아이디입니다." Display="Dynamic"></asp:CustomValidator>

 * 자바 스트립트를 이용하여 작성
<script language="javascript" type="text/jscript">
 function CheckID(oSrc, args)
 {
  args.IsValid = (args.Value != "aaa");
 }
</script>


<asp:TextBox ID="txtUserID" runat="server"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ControlToValidate="txtUserID" ClientValidationFunction="CheckID" ErrorMessage="이미 존재하는 아이디입니다." Display="Dynamic"></asp:CustomValidator>

작성자 : 상현넘™ [http://www.shblitz.net]
Posted by 상현넘™

댓글을 달아 주세요

ASP.NET 2.0에서 Web.config에 connectionStrings 섹션을 이용하여 DB 연결정보를 설정한 후 사용할 수 있다.


1. Web.config에 connectionStrings 설정
 - configuration 섹션의 자식으로 connectionStrings 섹션을 추가한다.
<connectionStrings>
 <add name="DBConnectionStringName" connectionString="Data Source=DBHostAddress;Initial Catalog=DatabaseName;User ID=UserID;Password=Password" providerName="System.Data.SqlClient"/>
</connectionStrings>


2. SqlDataSource 컨트롤에서 사용

<asp:SqlDataSource Id="MySource"
  ConnectionString="<%$ connectionStrings:DBConnectionStringName %>"
  SelectCommand="SELECT id FROM Members"
  runat="server" />


3. ConfigurationManager 클래스를 이용하여 사용
string ConnString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;

SqlConnection mDbConn = new SqlConnection(ConnString);

작성 : 상현넘™ [SHBLITZ.NET]
Posted by 상현넘™

댓글을 달아 주세요

  1. kwangho  댓글주소 수정/삭제 댓글쓰기 2009/02/18 22:50

    안녕하세요 ^^; 혹시 이렇게도 가능한가요.
    위에 있는건 Asp.net에 Web.config 설정에 접속정보 잖아요.
    DBConnectionStringNam의 접속정보를 이용하여서

    Windows Form으로 만든 새로운 프로젝트에서 클래스 파일만 가져다가 또는
    다른 방법으로 접속정보를
    string ConnString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
    이렇게 만들어서 윈폼에서도 쓸 수 없을까요?

    Asp.net과 윈폼의 접속하는 방법이 달라서 안될까요?
    연동방법이 있는 지 궁금합니다.

    • 상현넘™  댓글주소 수정/삭제 2009/02/22 09:03

      저도 윈폼은 예전에 잠시 해본거 밖에 없기 때문에 알지는 못합니다.
      machine.config 관련해서 한번 검색을 해서 찾아보시기 바랍니다.

  2. kwangho  댓글주소 수정/삭제 댓글쓰기 2009/02/22 21:38

    app.config 추가로 가능하네요 ^^; 똑같은 방법인지 몰랐습니다.

ASP.NET 2.0에는 GridView라는 컨트롤이 있다. GridView를 사용하는 예제를 보도록 하자. 참고로 아래 예제는 소스와 간단한 설명만 한다. 직접 해보면 이해를 할 수 있기때문에..^^

1. 게시판 목록의 디자인을 위한 스타일을 정의 한다.
<style type="text/css">
  .board_size {width:660px;}
  .list_title {background-color:#5D7B9D; color:#FFFFFF; height:28px; font-weight:bold;}
  .list_row1 {background-color:#EEEEEE; color:#284775; height:28px;}
  .list_row2 {background-color:#DDDDDD; color:#333333; height:28px;}
  .list_comment {font-size:7pt; font-family:Verdana;}
  .list_pager {text-align:center;}
</style>

2. DataSource를 생성하고 GridView와 연결한다.

<asp:GridView ID="GridView1" runat="server" CellPadding="4" CellSpacing="1" GridLines="None" AutoGenerateColumns="false" AllowPaging="True" CssClass="board_size" DataSourceID="SqlDataSource1" OnRowDataBound="GridView1_RowDataBound">
  <Columns>
    <asp:BoundField DataField="id" HeaderText="번호" SortExpression="id" ReadOnly="True" >
      <ItemStyle HorizontalAlign="Center" />
      <HeaderStyle Width="40px" />
    </asp:BoundField>
    <asp:TemplateField HeaderText="제목" SortExpression="title" InsertVisible="False">
      <ItemTemplate>
        <asp:HyperLink ID="hlTitle" runat="server"></asp:HyperLink>
        <asp:Label ID="lblComments" runat="server" Visible="false" CssClass="list_comment"></asp:Label>
        <asp:Image ID="imgNew" runat="server" Visible="false" />
      </ItemTemplate>
    </asp:TemplateField>
    <asp:BoundField DataField="name" HeaderText="작성자" SortExpression="name" ReadOnly="True" >
      <ItemStyle HorizontalAlign="Center" />
      <HeaderStyle Width="80px" />
    </asp:BoundField>
    <asp:TemplateField HeaderText="작성일" SortExpression="regdate" InsertVisible="False">
      <ItemTemplate>
        <asp:Label ID="lblRegdate" runat="server"></asp:Label>
      </ItemTemplate>
      <ItemStyle HorizontalAlign="Center" />
      <HeaderStyle Width="80px" />
    </asp:TemplateField>
    <asp:BoundField DataField="hit" HeaderText="조회" SortExpression="hit" ReadOnly="True" >
      <ItemStyle HorizontalAlign="Center" />
      <HeaderStyle Width="40px" />
    </asp:BoundField>
  </Columns>
  <HeaderStyle CssClass="list_title" />
  <AlternatingRowStyle CssClass="list_row1" />
  <RowStyle CssClass="list_row2" />
  <PagerSettings Mode="NumericFirstLast" />
  <PagerStyle CssClass="list_pager" />
</asp:GridView>

<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:shblitzConnectionString %>" SelectCommand="SELECT [id], [title], [comments], [name], [regdate], [hit] FROM [Board_1_Entries] WHERE ([complete] = @complete) ORDER BY [regdate] DESC">
  <SelectParameters>
    <asp:Parameter DefaultValue="True" Name="complete" Type="Boolean" />
  </SelectParameters>
</asp:SqlDataSource>

GridView 컨트롤의 속성 설명
 - CellPadding : 테이블 셀 패딩을 설정한다.
 - CellSpacing : 테이블 셀 여백을 설정한다.
 - GridLines : 테이블의 라인을 설정한다. (true:기본값 / false)
 - AutoGenerateColumns : 컬럼을 자동 설정한다. (true:기본값 / false)
 - AllowPaging : 페이징 사용여부를 설정한다. (true:기본값 / false)
 - CssClass : 테이블의 스타일을 지정한다.
 - DataSourceID : 데이터소스를 지정한다.
 - OnRowDataBound : 데이터소스에서 가져온 데이터를 한줄씩 바운딩할때 처리하는 이벤트

GridView 컨트롤의 하위 태그 설명

 - HeaderStyle : 게시판 헤더(타이틀)의 디자인을 설정한다.
 - AlternatingRowStyle : 게시판의 목록의 디자인을 설정한다.
 - RowStyle : 게시판의 목록의 디자인을 설정한다.
 - PagerSettings : 페이징 기능을 사용시 설정한다.
 - PagerStyle : 페이징의 디자인을 설정한다.

3. GridView의 OnRowDataBound 예제
 바인딩시 적용되는 내용은 TemplateField에 지정된 컨트롤을 설정할 수 있습니다.
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
  System.Web.UI.WebControls.HyperLink fcTitle;
  System.Web.UI.WebControls.Label fcComments, fcRegdate;
  System.Web.UI.WebControls.Image fcNew;

  if (e.Row.RowType == DataControlRowType.DataRow)
  {
    GridViewRow row = e.Row;

    int id = (int)((DataRowView)e.Row.DataItem)["id"];
    string title = ((DataRowView)e.Row.DataItem)["title"].ToString();

    // 제목의 길이 제한
    if (title.Length > 50)
    {
      title = title.Substring(0, 50) + "...";
    }
    title = title.Replace("<", "&lt;");
    title = title.Replace(">", "&gt;");

    fcTitle = (System.Web.UI.WebControls.HyperLink)e.Row.FindControl("hlTitle");
    fcTitle.NavigateUrl = "Read.aspx?BoardNo=1&Page=1&No=" + id;
    fcTitle.Text = title;

    // 코멘트 글이 있는 경우 제목 뒤에 코멘트 갯수를 표시해 준다.
    int comments = (int)((DataRowView)e.Row.DataItem)["comments"];
    if (comments != 0)
    {
      fcComments = (System.Web.UI.WebControls.Label)e.Row.FindControl("lblComments");
      fcComments.Text = " (" + comments + ")";
      fcComments.Visible = true;
    }

    // 24시간 이내에 올라온 글은 글 제목 뒤쪽에 이미지를 달아준다.
    DateTime regdate = (DateTime)((DataRowView)e.Row.DataItem)["regdate"];
    TimeSpan Gap = DateTime.Now - regdate;

    if (Gap.TotalMinutes < 1440)
    {
      fcNew = (System.Web.UI.WebControls.Image)e.Row.FindControl("imgNew");
      fcNew.ImageUrl = "images/icon_new.gif";
      fcNew.Visible = true;
    }

    // 작성일을 표시해준다.
    fcRegdate = (System.Web.UI.WebControls.Label)e.Row.FindControl("lblRegdate");
    fcRegdate.Text = regdate.ToString("yyyy-MM-dd");
  }
}

작성 : 상현넘™ [SHBLITZ.NET]
Posted by 상현넘™

댓글을 달아 주세요

  1. 전선희  댓글주소 수정/삭제 댓글쓰기 2007/12/06 17:01

    좋은 자료 감사합니다.
    정말 이것때문에 고생하고 있었는데 님게서 올려주신 자료덕분에... ^^
    날시가 많이 춥네요.. 감기조심하세요

[DN=cfile25.uf@165E070C4AA14607275C11.zip]Download C# source files[/DN]
[DN=cfile10.uf@161CCE0E4AA146070DE99F.zip]Download VB.NET source files (Contributed by Hudson Akridge)[/DN]

소스를 다운 받고 나서 이 애플리케이션이 어떻게 동작하는지는 아티클을 끝까지 읽어주세요.



들어가기

ASP.NET Atlas는 AJAX 형식 애플리케이션들을 ASP.NET에서 사용할 수 있도록 개발한 클라이언트측과 서버측 라이브러리입니다. 이 튜토리얼(아마도 시리즈로 발전할 수 있는)은 Atlas의 유용성에 대한 일반적인 관점을 제공합니다. Atlas는 매우 광대한 라이브러리이기 때문에 첫 번째 튜토리얼은 Atlas에서 가장 중요한 두 가지에 집중하도록 하겠습니다.

 1. 클라이언트 측 스크립트로부터 서버측 웹서비스 호출 가능
 2. 브라우저간 호환되는 자바스크립트 코드 개발을 손쉽게

배경

MFC Scribble 애플리케이션은 제가 MFC를 배울 때 사용했던 첫 번째 프로그램 가운데 하나입니다. 그러므로, 저는 이 튜토리얼에서 Scribble를 기반에 두기로 결심했습니다. Scribble 애플리케이션은 사용자가 마우스를 이용해서 자유롭게 스케치할 수 있도록 해줍니다. 저는 이와 유사한 프로그램을 인터넷에서 구현한 것은 처음 보았는데, JavaScript Draw라는 웹사이트였고 AJAX 기술을 이용했습니다. JavaScript draw 웹사이트는 오직 모질라 파이어폭스에서만 동작합니다. 이 아티클에서는 그 애플리케이션이 브라우저간에 호환되는 버전을 어떻게 빌드하는지 설명합니다. 우리는 이 시리즈에서 매 아티클 마다 이 애플리케이션을 빌드하면서 Atlas에 대해 점차 설명하는 방식으로 나아가도록 하겠습니다.

Atlas 설치

이 아티클을 작성하는 시점에서, December CTP of Atlas를 이 link를 클릭하면 다운로드 받을 수 있습니다. 만약 이 링크가 동작하지 않는다면 Atlas 웹사이트에서 정확한 링크를 얻을 수 있습니다. Atlas 라이브러리는 Visual Studio 2005 template (VSI)에서 사용할 수 있습니다. 다운로드 사이트에서는 템플릿을 어떻게 설치할 수 있는지 알려주고 있습니다.

Atlas 프로젝트 생성

Atlas 템플릿이 설치되면 메뉴 옵션에서 파일 -> 새로만들기 -> 웹 사이트 를 차례대로 클릭해 새로운 빈 Atlas 프로젝트를 생성할 수 있습니다. 차례대로 하면 아래 보이는 바와 같이 새 웹 사이트 다이얼로그 박스가 나타납니다.



아래 위치에서 파일 시스템 이나 HTTP를 선택할 수 있습니다. HTTP를 선택하면 당신은 IIS server에서 웹 사이트를 생성할 수 있습니다. 파일 시스템을 선택하면 당신의 로컬 파일 시스템(ASP.NET 개발 웹 서버를 이용해서 디버깅하고 테스트 할 수 있는)에서 웹 사이트를 생성할 수 있습니다. 당신은 어떠한 옵션도 선택할 수 있지만, 저는 이 애플리케이션이 IIS의 인터넷 익스플로러에서 더 잘 동작한다는 것을 알아냈습니다.

Atlas 빈 프로젝트

새로 생성된 Atlas 웹 사이트는 디렉토리 구조를 따르게 됩니다.

  • App_Data
    데이터 파일을 위치시킬 수 있는 빈 디렉토리
  • Bin
    Microsoft.Web.Atlas 어셈블리를 위한 dll 파일이 위치하는 디렉토리로 Atlas 라이브러리의 서버 영역을 가집니다.
  • ScriptLibrary
    애플리케이션에 사용하는 어떠한 자바스크립트 파일들이 위치하는 디렉토리
    • Atlas
      Atlas 클라이언트 스크립트들은 두 가지 다른 하위 디렉토리에 존재합니다.
      • Debug
        디버그 버전 Atlas 클라이언트측 자바스크립트 파일들은 디버그 디렉토리에 위치합니다.
      • Release
        릴리즈 버전 Atlas 클라이언트측 자바스크립트 파일들은 릴리즈 디렉토리에 위치한다. 이 디렉토리에 위치한 스크립트들은 디버그 코드가 제거된 상태로 경량화되어 쓰입니다.
Atlas 클라이언트 스크립트

Atlas December release는 다음과 같은 클라이언트 스크립트를 가집니다.

  • Atlas.js
    이 핵심 Atlas 스크립트 파일은 기본적인 유용한 기능과 클라이언트측 컨트롤, 컴포넌트들을 가지고 있습니다.
  • AtlasCompat.js
    이 파일은 모질라 파이어폭스와 애플 아이맥 사파리 웹 브라우저를 위해 제공되는 Atlas 호환 레이어를 포함하고 있다. 이 스크립트는 Atlas 코드가 브라우저간 호환될 수 있도록 합니다.
  • AtlasCompat2.js
    사파리 웹 브라우저 호환을 위한 추가적인 기능들이 있습니다.
  • AtlasRuntime.js
    핵심 Atlas 스크립트 파일을 최소화한 파일입니다. 이 스크립트 파일은 클라이언트측 컴포넌트와 컨트롤들을 가지지 않습니다. 이 스크립트 파일은 웹 페이지에서 사용되지 않는 앞서 말한 컴포넌트와 컨트롤들을 사용하고자 할 때 쓰일 수 있습니다.
  • AtlasUIDragDrop.js
    이 파일에는 웹 페이지에서 드래그 & 드랍 기능들을 제공하기 위한 유용한 기능들이 담겨져 있습니다.
  • AtlasUIGlitz.js
    이 파일에는 웹 페이지에서 애니메이션과 다른 특별한 효과를 제공하기 위한 유용한 기능들이 담겨져 있습니다.
  • AtlasUIMap.js
    이 스크립트 파일은 Virtual Earth를 사용하기 위한 Atlas 맵핑 프레임워크입니다.
다른 파일들

Atlas는 웹사이트의 루트 디렉토리에 다음과 같은 파일들을 추가적으로 제공합니다.

  • Default.aspxDefault.aspx.cs
    이 웹 페이지는 Atlas 클라이언트측 스크립트를 참조하는 렌터링 스크립트 블록을 감당하고 있는 Atlas 스크립트 매니저 컨트롤을 포함하고 있습니다. 추가적으로 클라이언트 test/xml-script 블록이 페이지에 있습니다. 이 스크립트 블록은 선언된 XML 구문을 사용하고자 할 때 쓰입니다.
  • eula.rtf
  • readme.txt
  • Web.Configweb.config는 Atlas 애플리케이션이 동작하는데 꼭 필요한 파일입니다. 이 파일에는 Atlas 와 다른 추가적인 Atlas HTTP 모듈, HTTP 핸들러를 특정하기 설정하기 위한 몇 가지 내용들이 있습니다.
Scribble 애플리케이션

Scribble 애플리케이션은 사용자가 왼쪽 마우스 버튼을 클릭하고 움직이면서 자유롭게 스케치할 수 있도록 합니다. 스케치는 사용자가 마우스 버튼을 놓았을 때나 드로윙 범위를 벗어났을 때 끝나게 됩니다. 이런 그리기는 VML을 이용한 자바스크립트를 통해서 구현하지만, 이런 예제가 VML 사용에 대한 내용으로 가지는 않을 것입니다.

Scribble 기본 웹 페이지는 이미지를 갖습니다(일반적인 HTML 이미지 - IMG 태그). 이미지에서 일어난 사용자의 마우스 이벤트는 자바스크립트 이벤트 핸들러에 의해서 캡쳐됩니다. 자바스크립트 기능은 스케치된 점들을 웹 서비스로 보냅니다. 웹 서비스는 업데이트되고 이미지 객체는 클라이언트가 보낸 모든 점들을 통해서 선들을 그려내고 세션 안에 저장됩니다. 결국엔, 클라이언트 요청에 의해 서버로부터 이미지가 업데이트 됩니다. 이 이미지 소스는 HTTP 핸들러에 의해 세션에 저장된 이미지를 클라이언트에게 출력됩니다. 여기에 애플리케이션의 메인 컴포넌트들이 있습니다.

  • Default.aspx
    다이나믹한 이미지와 Atlas 스크립트 매니저 콘트롤이 있는 페이지
  • ScribbleImage.ashx
    세션에 저장된 이미지 객체를 출력하는 HTTP 핸들러
  • ScribbleService.asmx
    모든 스크레칭 요청을 보내주는 웹 서비스. 이미지를 수정합니다.
  • Scribble.js
    파일 안에 포함된 디자인과 코드를 깔끔하게 분리시켜 애플리케이션에 재배치 시키는 자바스크립트 코드
  • Global.asax
    Session_Start 와 Session_End 이벤트는 Global.asax에서 다뤄집니다. Session_Start는 사용 가능한 세션을 생성하고, Session_End는 세션에 저장된 이미지를 해제시킵니다.
Global.asax

우리는 Global.asax. 에서 코딩 과정을 시작합니다.

  1. 웹 사이트 메뉴에서 새 항목 추가 또는 Ctrl + Shift + A를 누릅니다.
  2. 새 항목 추가 다이얼로그 박스에서 전역 응용 프로그램 클래스를 선택하고 클릭하면 됩니다. 당신은 Global.asax 파일이 생성된 것을 볼 것입니다.
  3. 우리는 System.Drawing 네임스페이스를 임포트하는 것으로 시작할 겁니다. 다음 줄에 나오는 코드를 첫 줄에 삽입해주세요.
    <%@ Import Namespace="System.Drawing" %>
  4. 이어서 Session_Start 기능 코드를 삽입해 주세요.
    void Session_Start(object sender, EventArgs e)
    {
      Bitmap bmp = new Bitmap(200, 200);
      using (Graphics g = Graphics.FromImage(bmp))
      {
         g.FillRectangle(new SolidBrush(Color.White),
            new Rectangle(0, 0, bmp.Width, bmp.Height));
         g.Flush();
      }

      Session["Image"] = bmp;
      }
    이 코드는 흰 200픽셀들로 배경이 완전히 하얗게 칠해져 있고 Image이라는 이름의 세션이 할당된 간단한 비트맵 200 픽셀을 생성합니다.
  5. Session_End 기능은 세션안에 저장된 이미지를 해제합니다.
    Bitmap bmp = (Bitmap)Session["Image"];
    bmp.Dispose();
  6. 웹 사이트 메뉴에서 참조 추가를 선택합니다.
  7. 참조 추가 다이얼로그 박스에서 System.Drawing을 선택하고, 확인 버튼을 누릅니다.
  8. 마지막으로, 빌드 메뉴에서 웹 사이트 빌드 또는Ctrl + Shift + B 를 누르면 빌드 에러가 없다는 것을 확신합니다.
ScribbleImage.ashx

웹 핸들러는 세션 변수안에 저장된 이미지를 다시 클라이언트에 출력하기 위한 목적입니다.

  1. 웹 사이트 메뉴에서 새 항목 추가 또는 Ctrl + Shift + A를 누릅니다.
  2. 새 항목 추가 다이얼로그 박스에서 제네릭 처리기를 선택하고, 이름을 ScribbleImage.ashx 라고 입력한 후 확인 버튼을 누릅니다.
  3. 웹 핸들러는 필요한 인터페이스 IRequiresSessionState를 구현할 때 세션 변수를 사용하기 위해서 필요합니다. 이건 단지 메이커 인터페이스(marker interface)이며 오버라이드하기 위한 메소드를 가지지 않습니다. 아래의 보이는 바대로 클래스 선언을 수정합니다.
    public class ScribbleImage : IHttpHandler,
    System.Web.SessionState.IRequiresSessionState
  4. 다음에 우리는 ProcessRequest 메소드 코드를 추가합니다.
    public void ProcessRequest (HttpContext context)
    {
      context.Response.ContentType = "image/png";
      context.Response.Cache.SetNoStore();
      context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
      context.Response.Cache.SetExpires(DateTime.Now);
      context.Response.Cache.SetValidUntilExpires(false);

      System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)context.Session["Image"];

      lock(bmp)
      {
           using (MemoryStream ms = new MemoryStream())
           {
               bmp.Save(ms, ImageFormat.Png);
               ms.Flush();
               context.Response.BinaryWrite(ms.GetBuffer());
           }
      }
    }
    • 첫째 줄 ContentType 헤더는 image/png 에 반응하도록 설정합니다. 이는 브라우저가 HTML 대신에 png 이미지에 반응하도록 구별합니다.
    • 다음 네 줄은 반응할 때 캐시하지 않도록 브라우저에 지시합니다. 이 코드들은 상호 브라우저간 호환되도록 하기 때문에 꼭 필요합니다. 우리는 나중에 튜토리얼에 맞추어 바꿀 것입니다.
    • 마지막으로, 세션 변수로부터 온 비트맵은 메모리 스트림에 저장이 되고, 저장된 메모리 스트림의 내용은 응답(response)시 씁니다. BinaryWrite 기능이 사용되고, 이미지는 바이너리 데이터입니다.
ScribbleService.asmx

세션 이미지를 초기화하고 응답시 이미지 내용을 출력해야 합니다. 이제 이미지 스스로 내용을 추가하도록 하기 위해 몇 가지 메소드를 추가해야 합니다. 우리는 클라이언트가 이미지에 선을 추가하기 위해서 ScribbleService.asmx 웹 서비스를 호출하리라 기대합니다.

  1. 웹 사이트 메뉴를 누르고 새 항목 추가 혹은Ctrl + Shift + A를 누릅니다.
  2. 새 항목 추가 다이얼로그 박스에서 웹 서비스를 선택하고, ScribbleService.asmx 이라는 이름을 입력한후 확인 버튼을 누릅니다. 저는 당신이 다른 파일에 코드 입력를 체크하지 않았을 것이라고 확신합니다.
  3. 다음 줄에 나오는 바와 같이 System.Drawing 네임스페이스를 임포트합니다.
    using System.Drawing;
  4. 다음엔, 점을 위한 간단한 클래스를 정의해야 합니다. 우리는 XML serializable이 아니기 때문에 System.Drawing.Point 클래스를 사용하지 않을 것입니다. 이후의 튜토리얼에서 우리는 커스텀 클래스를 대신하여 System.Drawing.Point 를 사용하는 방법을 알아 볼 것입니다. ScribbleService 클래스 선언 전에 다음 코드를 추가합니다.
    public class Point
    {
      public int X;
      public int Y;
    };
  5. 마지막으로, 우리는 주어진 점들의 집합인 스케치를 그리기 위해서 메소드를 추가해야 합니다. 우리는 우리의 웹 서비스에 Draw 라는 웹 메소드를 추가할 것입니다.
    [WebMethod(EnableSession = true)]
    public void Draw(Point[] points)
    {
      Image scribbleImage = (Image)Session["Image"];
      lock(scribbleImage)
      {
           using (Graphics g = Graphics.FromImage(scribbleImage))
           using(Pen p = new Pen(Color.Black, 2))
           {
               if (points.Length > 1)
               {
                   int startX = points[0].X;
                   int startY = points[0].Y;
                   for (long i = 1; i < points.Length; i++)
                   {
                       g.DrawLine(p, startX, startY,
                           points[i].X, points[i].Y);
                       startX = points[i].X;
                       startY = points[i].Y;
                   }
               }
           }
      }
    }
    • WebMethod(EnableSession = true) 속성은 세션 변수가 웹 서비스에서 접근 가능하도록 합니다.
    • 이미지는 잠겼기 때문에 현시점 접근시 안전합니다.
    • points 배열안에 제공된 점들을 조인하는 것처럼 드로윙은 꽤 간단합니다.
Scribble.js

우리는 서버측 이미지 핸들러와 이미지를 업그레이드 시키는 서버측 웹 서비스를 가지고 있습니다. 이제 우리는 마우스 이벤트로 인한 점들을 서버의 웹 서비스에 보내주는 scribble 애플리케이션의 클라이언트측 스크립트가 필요합니다.

  1. 솔루션 탐색기에서 ScriptLibrary 폴더를 선택합니다.
  2. 웹 사이트 메뉴를 누르고 새 항목 추가 혹은Ctrl + Shift + A를 누릅니다.
  3. 새 항목 추가 다이얼 로그 박스에서 JScript 파일,을 선택하고 Scribble.js 라고 이름을 지정한 후 확인 버튼을 누릅니다. Scribble.js 파일은 ScriptLibrary 폴더안에 위치할 것입니다.
  4. 다음엔 우리는 몇 가지 글로벌 변수를 선언해야 합니다. Scribble의 첫 번째 버전에서는 글로벌 변수를 사용하지만 차기 버전에서는 자바스크립트 객체를 사용할 것입니다.
    //The HTML image element that is to be drawn
    var image;
    //The source of the image
    var originalSrc;
    //The number of iteration
    var iter = 0;
    //The array of points
    var points = null;
    변수 선언 위의 주석은 각 변수의 목적을 묘사합니다. iter 변수는 그리기 요청 이후에 서버에 보내지는 이미지 소스를 수정하는데 쓰입니다. image.src = image.src로 설정하는 경우에 인터넷 익스플로러는 이미지를 다시 불러들이지만, 이 동일한 코드가 파이어폭스에서는 동작하지 않습니다. 이 문제를 안고 동작하기 위해서 매 시간 증가된 iter 변수를 지속시키기 위해 Draw 요청을 웹 서비스에 보냅니다. 브라우저는 캐시된 이미지를 이용하기 보다는 새 데이터를 받도록 서버에 요청할 필요가 있다고 생각하기 때문에 originalSrc 변수에 iteration number를 추가합니다.
  5. mousedown 이벤트에 반응하여 스트로크를 시작하기 위해서 function startStroke 를 정의합니다.
    function startStroke()
    {
      points = new Array();
      window.event.returnValue = false;
    }
    새로운 스트로크가 시작되면 첫 번째 줄에 지시된 새로운 점들의 집합을 생성합니다. 두 번째 줄은 이벤트의 기본 행위를 취소시킵니다. 이건 이미지에 대한 mousedown 이벤트의 기본 행위처럼 꼭 필요합니다. 드래그 동작을 시작하면 작업이 시작됨에 따라 차후 일어날 이벤트를 이끌어가기 위해 이미지는 드래그 동작을 시작합니다.
  6. mouseup 이벤트나 mouseout 이벤트가 일어나면 스트로크는 끝이 납니다. 우리는 실제로 웹 서비스를 호출하도록 할 필요가 있습니다. 이건 function endStroke에서 이루어집니다.
    function endStroke()
    {
      if (!points || points.length < 2)
         return true;

      //Send the points to the webservice
      ScribbleService.Draw(points, onWebMethodComplete,
         onWebMethodTimeout, onWebMethodError);
      points = null;
      window.event.returnValue = false;
    }
    이 기능에서 흥미를 끄는 유일한 코드 라인은 ScribbleService.Draw(points, onWebMethodComplete, onWebMethodTimeout, onWebMethodError); 입니다. 여기에서는 비동기적으로 ScribbleService.asmx 에 있는 웹 서비스 메소드 Draw 를 호출합니다. 이 기능은 Atlas 프레임워크에 위해 우리가 즉각적으로 사용할 수 있도록 합니다.
  7. onWebMethodError 는 웹 서비스 메소드에서 에러 발생시 Atlas 프레임워크에 의해 호출됩니다. onWebMethodTimeout 는 웹 메소드 호출이 Atlas 프렘임워크에 정의된 타임아웃 설정을 초과할 때 호출됩니다. 이 버전에서 우리는 사용자에게 에러 텍스트가 담긴 메시지 박스를 보여줄 겁니다.
    function onWebMethodError(fault)
    {
      alert("Error occured:\n" + fault.get_message());
    }

    function onWebMethodTimeout()
    {
      alert("Timeout occured");
    }
  8. function onWebMethodComplete 는 웹 메소드 호출이 성공되었을 때 호출됩니다. 이때 이미지는 리로드되어야 합니다.
    function onWebMethodComplete(result, response, context)
    {
      //We need to refresh the image
      var shimImage = new Image(200, 200);
      shimImage.src = originalSrc + "?" + iter++;
      shimImage.onload = function()
      {
      image.src = shimImage.src;
      }
    }
    우리는 Image 객체object shimImage 를 생성하고, 우리가 드로윙할 때 이 소스에서 이미지의 오리지널 소스로 설정합니다. 이미지 객체가 로드 될 때 페이지의 실제 HTML 이미지 요소의 소스 집합에서 임시 이미지 개체의 소스로 설정합니다. 이는 이미지를 대체할 때 flicker를 피합니다.
  9. 우리는 mousemove 이벤트가 일어날 동안 points 배열을 채워야 합니다. 이건 function addPoints에서 담당합니다.
    function addPoints()
    {
      if (points)
      {
         var point = { X : window.event.offsetX,
            Y : window.event.offsetY};
         points.push(point);

         if (points.length == 3)
         {
            endStroke();
            points = new Array();
            points.push(point);
         }

         window.event.returnValue = false;
      }
    }
    • 새로운 point 객체는 이벤트 객체의 offsetX 과 offsetY 속성을 생성하고, 그것을 points 배열에 추가합니다. offsetX 와 offsetY 속성은 이벤트를 일으킨 HTML 요소에 관해 연관된 마우스 위치를 줍니다.
    • 만약 배열의 길이가 3에 이른다면, 우리는 즉각적으로 그리기 동작을 할 것을 서버에 요청하고, points 배열을 재설정하도록 합니다. 이 동작은 사용자가 마우스 버튼을 놓기 전에 드로윙을 볼 수 있도록 합니다.
  10. 마지막으로, 이벤트들을 hook해야 합니다. 이건 function pageLoad에서 담당합니다.
    function pageLoad()
    {
      var surface = document.getElementById("drawingSurface");
      image = surface.getElementsByTagName("IMG")[0];
      originalSrc = image.src;

      surface.attachEvent("onmousedown", startStroke);
      surface.attachEvent("onmouseup", endStroke);
      surface.attachEvent("onmouseout", endStroke);
      surface.attachEvent("onmousemove", addPoints);
    }
    • function pageLoad는 Atlas framework가 로딩을 마쳤을 때 호출되는 특별한 기능입니다. 우리는 이걸 regular window 나 body load events를 대신하여 사용합니다. 그래서 우리는 Atlas가 로딩을 마쳤다는 것을 확신할 수 있습니다.
    • 실제 이미지 요소는 div 태그 와 drawingSurface의 id안에 위치한 곳에서 스케치된 것을 갖습니다. 요소의 크기는 이미지의 크기와 동일합니다. 그래서 우리는 안전하게 drawingSurface div에 이벤트를 추가할 수 있습니다.
Default.aspx

애플리케이션의 각 컴포넌트는 Default.aspx 페이지에서 어셈블(assembled)되어야 할 필요가 있습니다. 다음은 이 페이지의 코드입니다.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
  Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Atlas Scribble Sample</title>
</head>
<body>
  <form id="form1" runat="server">
       <Atlas:ScriptManager ID="AtlasScriptManager" runat="server"
           EnableScriptComponents="False" >
  <Services>
  <Atlas:ServiceReference Path="ScribbleService.asmx" />
  </Services>
  <Scripts>
  <Atlas:ScriptReference Path="ScriptLibrary/Scribble.js" />
  </Scripts>
  </Atlas:ScriptManager>
       <div id="drawingSurface" style="border:solid 1px black;height:200px;width:200px">
       <img alt="Scribble" src="ScribbleImage.ashx" style="height:200px;width:200px" galleryimg="false" />
       </div>
  </form>
</body>
</html>

이 페이지에서 가장 중요하게 보아야 할 것은 atlas:ScriptManager 서버 콘트롤입니다. ScriptManager 서버 콘트롤은 Atlas를 위한 클라이언트측 스크립트 블록과 웹 서비스 프록시 스크립트를 생성해야 할 책임을 가지고 있습니다. Default.aspx 페이지에 있는 ScriptManager 컨트롤을 이용해서 실험해볼 수 있습니다.

  1. EnableScriptComponents 속성은 false로 설정합니다. 이로 인해 AtlasRuntime.js Atlas.js대신 AtlasRuntime.js 를 참조하여 클라이언트 측 스크립트 블록을 생성합니다. 우리는 이번 scribble 버전에서 다른 어떠한 Atlas 컴포넌트와 컨트롤을 사용하지 않는 것처럼 Atlas 프레임워크의 라이트 버전를 선호합니다.
  2. ScribbleService.asmx 웹 서비스에 서비스 레퍼런스를 추가합니다. 이건 웹 서비스 프록시를 위한 클라이언트측 스크립트에 URL 레퍼런스를 생성할 겁니다.
  3. Scribble.js 와 같은 또 다른 스크립트 레퍼런스를 추가합니다.
이건 모든 조각들을 하나로 묶어주므로, 이제 당신은 컴파일 할 수 있고, 프로젝트를 실행할 수 있습니다. 저는 Atlas 스크립트 매니저에 의해 생성된 실제 클라이언트 HTML을 볼 것을 권고합니다.

Atlas 마법

여기에서는 프레임워크가 우리를 위해 어떤 일을 하는지 알려줍니다.

  1. 여기에서 우리는 상호 브라우저가 동작하기 위해서 어떤 특별한 효과를 사용하지 않고 웹 애플리케이션을 작성하였습니다. 웹 서비스 호출과 클라이언트측 이벤트 핸들링은 인터넷 익스플로러와 파이어폭스 모두에서 즉각적으로 동작합니다. Atlas 프레임워크는 인터넷 익스플로러 객체와 같이 보이게 하기 위해 파이어폭스 객체에 요구된 자바스크립트 프로토타입을 추가했습니다. 인터넷 익스플로러에 특성화된 기능들인 attachEvent 와 event.offsetX , event.offsetY 는 파이어폭스에서 동작되도록 했습니다. 당신은 이것이 어떻게 이루어지는지 AtlasCompat.js 파일에서 볼 수 있습니다.
  2. Scribble 웹 서비스 메소드를 위한 자바스크립트 프록시를 자동적으로 생성합니다. ScribbleService.asmx 파일을 위한 자바스크립트 프록시 파일은 URL ScribbleService.asmx/js를 가집니다. 이건 web.config.에 추가된 the Atlas HTTP 메소드에 의해 생성됩니다.
우리는 어디에 있는가

우리는 웹 서비스를 호출하는 방법과 Atlas를 이용하여 쉽게 브라우저간에 애플리케이션을 사용하는 방법을 알아보았습니다. 다음 튜토리얼에서 우리는 Atlas 클라이언트 클라이언트측 컨트롤과 선언적인 프로그래밍(사용자 피드백에 의존하는!)을 알아볼 것입니다. 만약 당신이 이 튜토리얼에 만족했다면 코멘트를 남겨주세요. 만약 만족스럽지 못했다면 어떻게 개선하면 좋을지 코멘트를 남겨주세요.

다운로드와 소스 실행

Atlas를 재배포 할 수 있도록 되어 있지 않기 때문에, 저는 Atlas 파일을 소스 다운로드에 포함하지 않았습니다. 소스를 동작시키기 위해서는 여기에 적힌 순서에 따라 다운로드 받아야 합니다.

  1. 당신은 Atlas 웹 사이트에서 Atlas를 다운로드 받아야 합니다.
  2. Atlas blank project template를 다운로드 받은 후에 새로운 웹 사이트를 다음 순서에 따라 생성삽니다. 파일 메뉴에서 새로 만들기를 웹 사이트를 선택합니다.
  3. 새롭게 생성된 프로젝트 디렉토리에 소스파일의 압축을 풀어 이전에 존재했던 파일들을 덮어 씁니다.
  4. 웹 사이트 메뉴에서 기존 항목 추가를 선택하고, 웹 사이트의 최상위 디렉토리에 ScribbleService.asmx 와 ScribbleImage.ashx를, ScriptLibrary 폴더에서는 Scribble.js를 추가합니다.
  5. 빌드하고 웹 사이트를 실행시킵니다.

★ 참고사항
ASP.NET 'Atlas' Web Site Template의 최신 버전은 January 2006 Release입니다.

번역 : 날개달기 (http://blog.naver.com/pinao_forte)
원문 : The Code Project (Atlas Tutorial: Creating an AJAX Scribble application)
TAG Ajax, Altas
Posted by 상현넘™

댓글을 달아 주세요

저자: Jesse Liberty, 조성재 역

제가 Atlas에 대해 읽었던 모든 책이나 기사들은 저에겐 단순한 두통거리였습니다. 그것들은 공통적으로 2가지 메시지를 담고 있었습니다. 첫 번째로 제가 제작한 서버 측 ASP.NET 응용프로그램들이 지금은 오래되었다는 것, 두 번째로 그것들을 고치기 위해서는 정말 복잡한 자바스크립트를 추가해야 한다는 것입니다. 이렇게 슬플수가...

이런 책들과 기사들 (이름은 언급하지 않겠습니다. 요즘들어 변호사 비용이 비싸졌기 때문에...) 모두 진짜 Atlas 프로그래머가 되기 위한 것들을 포함했다고 보지만, 저는 블랙박스로 감춰진 것들을 숨기지 말고, Ajax와 JavaScript 그리고 좋은 평가 수단으로서의 XMLHTTPRequest 객채들을 이해해야 한다고 생각합니다. 이것은 1985년의 진짜 C 프로그래머가 되기 위해 겪었던 악몽과도 같은 일의 재현입니다. 여러분은 실제로 무언가 잘못된 경우 때문에 어셈블러를 이해해야 할 필요가 있었으며, 레지스터에 무엇이 있었는지 보기 위해 스냅샷 디버거에 프로그램을 넣기도 했었습니다. (잠시동안 그런 일을 하지 않았지만, 전 더 많은 것들을 잃어버리지 않았습니다.)

아니, 전 그것을 갖지 않길 원했습니다. 추상화의 사다리 위로 올라가 있는 것은 좋은 것입니다. 우리가 더 높은 추상화 단계의 도구를 사용하는 것을 말했을 때가 옳았습니다. 마이크로소프트와 다른 벤더들이 제공하는 잘 테스트된 컨트롤들을 연결하는 것을 떠나서, 우리는 문제에 더 집중할 수 있습니다. 저는 자료형도 불안정하고 객체지향적이지도 않은 자바스크립트를 쓰게끔 된다면 욕을 해댈겁니다. 연기속에서, 하수도 아래에서, 문 밖에서 진행해온 20년. 우웩~

운좋게도, 그것은 얼리어답터의 조각난 현미경 렌즈를 통해 보이는 수평선에 나타난 키메라입니다. 스크립트 지향적 접근은 자바스크립트 기반이 최고라고 알고 있는 사람들이 탐사중인 먼저 배포된 기술의 인공물입니다. Ajax가 큰 관심을 얻으며 시작됐을 때, 그것을 쓰려던 사람들은 실제로 자바스크립트 애호가들이었습니다. 그들은 사용자들이 경험했던 것보다 충분히 다른 차이점을 갖춘 클라이언트 측의 자바스크립트 코드를 만들 수 있었던 사람들이었습니다.

Atlas 전문가는 스크립트 전문가와 같다고 인식됩니다. 하지만, 잘못된 인식은 노출될 필요가 있으며 가능한 빨리 깨져야 합니다. ASP.NET 프로그래밍 4판이 출간되었을 때, 우리는 Atlas를 충분히 다룰 것입니다만, 자바스크립트에 대해서는 거의 다루지 않을 것입니다. 저는 응용프로그램 프로그래머들이 응용프로그램에 초점을 두고, 컨트롤 프로그래머들이 훌륭히 캡슐화된 (사용자들이 제공할 수 없는, 블랙박스를 여는 것이 워런티를 피하는 것이 아닌) 컨트롤들을 완성하는데 초점을 둘 것이라고 생각합니다.

이 기사는 자바스크립트를 보여주지 않습니다.

이 기사를 쓰는 동안, 저는 Atlas 컨트롤들과 Visual Studio 2005에서의 그래그 앤 드랍을 사용하여 하향식으로 프로그래밍을 시작하고 있습니다. 그리고 가능한 모든 곳에서 블랙박스 안을 들여다보지 않을 것입니다. (모든 사람들이 불가능하게 보거나, 적어도 죄받을 만한 일이라고 봅니다만, 아닌가요? 그러나, 제가 어떻게 일반 ASP.NET 컨트롤들을 알려드릴지에 따라 결정될 것입니다.)

모든것을 다룰까요? 아니오. 그러나 우리는 여러분이 작업할 ASP.NET 프로그램을 어떻게 하면 빠르고 쉽게 Atlas 컨트롤들을 사용하여 개선할지에 대해 구성할 것입니다. Atlas 컨트롤들은 마이크로소프트에서 무료로 다운로드 할 수 있으며 잘 작동합니다. 그리고 제 생각엔 여러분들이 이 방법들을 배우자마자 이 방법을 지지할 것이라 생각합니다.

여러분이 Atlas 응용프로그램을 작성하는데 필요한 것

Microsoft Atlas 웹사이트는 여러분이 원하는 것에 대해, 그리고 파일들을 어떻게 설치할지에 대해 (잘 아시다시피, 비주얼 스튜디오에 어떻게 통합하는지) 아주 좋은 설명을 제공합니다. ASP.NET Atlas 홈페이지를 검색하고 JUNE CTP 와 관련된 다운로드 아이콘을 클릭해주십시오. (짐작컨데 지금 최신의 CTP가 있다면 그걸 써보고 싶고, 이 기사의 예제 코드가 잘 작동하길 바라거나 약간만 수정해서 가능하기를 바랄 것입니다.) June CTP는 문서, 예제, 설치 3 부분으로 구성됩니다. 설치부분만 사용해도 됩니다만, 여러분은 3가지 모두를 원할 것입니다. 완료했을 때 홈페이지로 돌아가서 Atlas 컨트롤 툴킷에 클릭하십시오. 이 기사 다음에 클릭 하시는 것이 좋을 것입니다.

다운로드한 것에는 여러분의 도구상자에 Atlas와 Atlas Toolkit, 두 탭을 추가하는 것과 (정확한 DLL을 검색할 수 있는 수단인) 컨트롤들을 탭에 두는 것에 대한 지시사항을 포함합니다. 이 작업은 무척 쉽습니다. 만약 드래그 앤 드랍 접근이 적용되지 않는다면, 여러분은 항상 도구상자 탭에서 오른쪽 클릭을 하고 "Choose Items"를 선택한 후 "Browse"를 클릭하여 Bin 디렉터리에 있는 DLL을 선택할 수 있습니다. 그렇게 하면 그림 1에 보이는 것처럼 모든 Atlas 컨트롤들을 dll로부터 탭에 추가할 것입니다.


그림1. dll로부터 컨토를을 더하기

동작하는 프로그램으로 시작하기

5월에 저는 Building a Web-Based Bug Tracking Application를 출판했습니다. 기사와 완벽한 소스제 웹 사이트에서 얻으실 수 있습니다. (책을 클릭하고 기사를 클릭하십시오.) 저희는 존재하는 응용프로그램을 개선하기 위해 Atlas 기능을 추가하는 시작점으로 Tiny Bug Tracker를 구성하는 응용프로그램을 이용할 것입니다. 나아가서 여러분이 이미 작성한 어떤 웹 응용프로그램을 개선하는데 Atlas 컨트롤들을 추가할 수 있을 것입니다.

Tiny Bug Tracker의 목적은 개인 프로그래머(혹은 아주 작은 그룹의 프로그머들)에게 알려진 버그들의 진행, 작업, 수정, 그리고 팀의 여러 구성원들이 버그 항목의 닫은 상태를 추적하기 위한 간단한 웹 기반 응용프로그램을 제공하는 것입니다. 그림 2에서 보이는 것처럼 웹 사이트 관리자 도구를 사용하여 사용자와 역할을 생성하는 것으로 시작합니다.


그림2. WAT로 사용자 추가하기

다음으로 그림 3에서 보이는 것처럼, 응용프로그램을 사용하기 위해 사용자가 인증을 합니다.


그림3. 로그인

그림 4에서 보이는 것처럼 사용자들은 버그를 입력하기 위해 새로운 버그 형식을 제공하는 (또한 버그를 수정하는데 사용되는) 메뉴를 클릭할 수 있습니다.


그림4. 버그 입력하기

이 버그들의 모든것은 그림 5에 나타난 것과 같이, Bugs 테이블에 각 버그에 대한 하나의 항목, BugHistories 테이블에 버그의 각 수정에 대한 하나의 항목으로 이뤄진 TBTData 데이타베이스에 위치합니다.


그림5. 버그 데이터베이스

여러분의 데이타베이스에 버그를 갖게 되면, 여러분은 TBTReview.aspx 에서 버그들을 다시 볼 수 있습니다. Details 를 클릭하는 것은 그 버그에 대한 세부사항 패널을 전달하고, 규격과 다른 영역을 나타냅니다. 그림 6 과 같이 History 를 클릭하는 것은 주어진 버그에 대한 각각의 변경된 점을 갖는 규격을 전달합니다. 그리고 그 변경된 점에 대한 세부사항을 나타나게 할 수 있습니다.


그림6. 미리보기

우리가 이 응용프로그램이 가지고 있는 문제를 해결하는데 Atlas가 도움이 됩니다. 여러분이 한 버그의 세부사항에서 다음 세부사항으로 이동할 때마다, 전체 페이지가 서버에 다시 포스트 됩니다. 거기엔 지연시간이 있으며 그 때문에 화면의 깜빡임이 있습니다. 또한, 미리보기를 스크롤하면서, 새로운 버그에 들어가려 할 때에는, 메뉴가 맨 위에 있어서 다시 스크롤을 위로 올려야 합니다. 이 부분은 사용하기 편한 메뉴가 될 것입니다. 마지막으로, 만약 여러분이 새 버그로 들어갈 때 Cancel 을 클릭하면 이 작업은 취소됩니다. 겉으로 드러내지 않고 바로 작업처리를 버리는 것입니다.

Atlas 버전 생성하기

작업을 편하게 하기 위해, 이미 있는 응용프로그램에 Atlas 컨트롤들을 추가하는 것보다, 새 Atlas 응용프로그램을 만드는 것이 좋습니다. 그 다음에 이전 응용프로그램에 있던 페이지들을 추가할 것입니다. 이 방법은 비주얼 스튜디오가 Atlas 에 필요로 하는 모든 레퍼런스들을 설치할 것이며, 이전 기사의 페이지들을 사용하여 기본 응용프로그램 코드가 작동하는지 알 수 있도록 하고, 기본적인 기능들을 다시 시험해보지 않도록 합니다. 저는 Atlas의 June CTP 버전을 사용하고 있다는 것을 주의하십시오. 만약 여러분이 다른 버전을 사용하고 있다면, 여러분의 재량에 따라 결과가 다를 것입니다.

원본 기사를 잠시 읽어본다면 다음의 내용이 훨씬 쉽게 이해될 것입니다. 전 여기서 기다릴 것입니다. 여러분이 읽어보실 시간을 갖으십시오. 전 다른 프로젝트를 작업할 것입니다. 준비되면 저를 깨워주세요.

첫번째 개선: 깜빡임 감소-각 페이지의 일부분만 갱신하기

Tiny Bug Tracker 응용프로그램의 가장 큰 문제 중 하나는 다른 버그의 세부사항에 대해 요청할 때마다, 전체 페이지가 다시 그려지고 지겨운 깜빡임을 유발한다는 것입니다. (캐쉬로 인해 데이터베이스는 매번 멈추지 않으나, 서버에 대한 라운드 트립이 현저한 정지 상태를 유발합니다.)

우리는 업데이트 되어야 할 페이지의 일부분만을 업데이트 하도록 Atlas를 사용할 수 있습니다. 그렇게 하기 위해, 우리는 TBTReview 페이지에 네가지 업데이트 패널을 추가할 것입니다. 업데이트 패널은 페이지의 다른 것들과 독립적으로 갱신되기 위해 세부적으로 설계된 Atlas 컨트롤입니다. 이것이 어떻게 동작하는지에 대한 세부사항을 아는 것은 매력있는 것이지만, 여러분이 실제로 기억할 내용은 이것이 비동기적으로 동작한다는 것입니다. 즉, 전체 페이지를 모두 다시 포스트 할 필요가 없습니다. 심지어 더 좋은 것은, 여러분이 도구상자로부터 페이지에 드래그 하여 바로 올려놓는 것으로 각 업데이트 패널을 생성할 수 있다는 것입니다. (얼쑤!) 그것 참. 비주얼 스튜디오가 여러분들을 위해 작업을 해줍니다. (또한 여러분은 소스 뷰에 수동으로 업데이트 패널을 추가하는 것으로 업데이트 패널을 생성하거나, 실행시에 C#에서 동적으로 업데이트 패널을 추가할 수 있습니다.)

단계적으로 Atlas 응용프로그램 생성하기

여러분은 여러분이 가지고 있는 응용프로그램에 Atlas 컨트롤들과 라이브러리들, 레퍼런스들을 추가하는 것으로 새 응용프로그램을 생성할 수 있습니다. 정말입니다. 그러나 제가 위에서 말했듯이, 저는 그렇게 하지 않을 것입니다. 왜냐하면 베타 소프트웨어와 함께 비주얼 스튜디오가 한 번에 모든 것을 설치하도록 하는 것을 좋아하기 때문입니다. 그래서, 저는 그림 7에서 나오듯이 새 웹 사이트를 생성하고 템플릿에 "Atlas Web Site"를 선택하는 것으로 Atlas 버그 트랙커라고 불리는 웹 사이트를 생성할 것입니다.


그림7. 새로운 Atlas 웹사이트 생성하기

기본적인 사이트는 기본적으로 포함되어 있는 readme.txt, eula.rtf, default.aspx, web.config, 그리고 bin 안에 있는 Microsoft.Web.Atlas.dll 과 같은 몇몇 파일들로 구성될 것입니다. readme.txt, eula.rtf, Default.aspx 파일을 지우세요. 여러분은 그것들을 필요로 하지 않을 것입니다.

WAT를 열고, security 탭에서 forms-based authentication을 선택한 다음, 이전 응용프로그램에 있었던 사용자 이름과 역할을 생성하십시오. (Alex, Dan, Jesse, Ctacey와 같은 이름, Developer, Manager, QA, 그리고 User 등과 같은 역할)

TBTMaster.master, TBTReview.aspx, TBTBug.aspx, TBTWelcome.aspx 페이지와 그 뒤의 코드 페이지에 복사하십시오. 그리고 그것들을 프로젝트에 추가하십시오. web.config 파일에 복사하지 마십시오.

WAT가 여러분이 Atlas 프로젝트를 선택할 때 여러분들을 위해 생성된 web.config 파일을 수정했습니다. 그래서 여러분들은 폼 기반 보안을 갖는 Atlas 페이지로서 양쪽의 조합된 설정을 동시에 가지고 있습니다.

여러분들은 프로그램을 변경하기 이전에 프로그램을 실행해보길 원할 것이고, 모든 설정이 잘되었는지 확인하길 원할 것입니다.

주의: 완성 소스 코드는 (책을 클릭하고 기사를 클릭하면 나오는) 저의 웹사이트에서 다운로드 받을 수 있습니다. 제 개인적인 지원 포럼에 대한 링크와 흥미로울만한 내용도 같이 있습니다.

스크립트 관리자

Atlas에 대한 핵심 컨트롤은 스크립트 관리자입니다. 가상적으로 모든 Atlas 컨트롤은 컨트롤이 있는 페이지가 스크립트 관리자를 필요로 합니다. 그 페이지에서 모든 Atlas 컨트롤들을 조정하는 것이 스크립트 관리자의 일입니다. 운좋게도, 이 응용프로그램은 주 페이지를 가지고 있으므로 스크립트 관리자를 주 페이지에 하나만 놓는 것으로 응용프로그램의 각 페이지에 대한 스크립트 관리자를 제공할 수 있습니다.

우리가 스크립트 관리자에 추가할 필요가 있는 한가지 속성은 EnablePartialRendering= "True" 입니다. 왜냐하면 이 속성이 페이지 전체를 포스트하지 않고 비동기적으로 페이지의 일부분을 나타나도록 하는 기능을 작동시키기 때문입니다.

여러분은 스크립트 관리자 컨트롤을 주 페이지 어디에든 위치할 수 있습니다. 저는 로그인 상태 컨트롤 아래에 바로 두었습니다.

<atlas:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="True" />

컨트롤을 도구상자로부터 드래그 앤 드랍하는 것은 아마도 페이지에 컨트롤을 추가하는 가장 쉬운 방법일 것이며, 그렇게 하는 것이 페이지에 컨트롤을 적절하게 등록하도록 보장합니다. 하지만 그렇게 할 때 그림 8에서 보이는 것처럼 Destination File Exists 대화상자를 보게 될 것입니다.


그림8. Destination File Exists

컨트롤이 페이지에 추가될 때, 그것은 대화 상자와 함께 Microsoft.Web.Atlas.dll 을 보냅니다. Yes를 클릭하고 파일을 갱신하지 않을 이유가 없습니다.

섹션에 TBTReview를 쪼개넣기

배치해놓은 스크립트 관리자로, 이미 여러분은 다양한 규격들에 대한 비동기 업데이트들과 DetailsView 객체들을 TVTReview 페이지에 추가할 준비가 되었습니다. 그렇게 하기 위해 페이지에 있는 도구상자의 Atlas 탭으로부터 4개의 UpdatePanel 객체들을 드래그하십시오. 각 UpdatePanel은 독립적으로 갱신될 수 있는 UI의 블럭을 나타냅니다. 저는 BugReviewGrid와 그것의 데이터 소스를 처음에 두고, BugDetailsView와 그 데이터를 2번째에, BugHistoryGrid와 그 데이터를 세번째, BugHistoryDetailsView와 그 데이터 소스를 4번째에 두었습니다. 여러분들도 Design 뷰에서 드래그 앤 드랍을 하거나, 소스뷰에서 HTML을 옮기는 것으로 이 작업을 할 수 있습니다.

예를 들어 그림 9에서 UpdatePanel2 를 폼에 드래그 했고 패널에 BugDetailsView를 드래깅하고 있습니다.


그림9. 업데이트 패널에 드래그하기

마우스 버튼을 떼었을 때, 소스뷰 안에서 HTML에 반영되는 것으로 BugDetailsView와 이것의 SqlDataSource가 Update Panel 안에 내장될 것입니다. (간소하게 여기선 생락되었습니다.)

<span class="style1"><atlas:UpdatePanel ID="UpdatePanel2" runat="server">
     <ContentTemplate></span>
         <asp:DetailsView ...  </asp:DetailsView>
          <asp:SqlDataSource ...>   </asp:SqlDataSource>
     <span class="style1"></ContentTemplate>
  </atlas:UpdatePanel></span>
</span>

성능상으로 변경된 것들은 즉각적이고 놀랄만한 것입니다. 갑자기 페이지가 껌뻑임을 멈췄습니다. 여러분이 어떤 버그의 세부사항에서 다른 버그로 이동할 때, 부드럽고 빠르며 껌뻑임도 없습니다. 차이점이 실제 그러한지 확인하기 위해 마스터 페이지에 있는 스크립트 관리자에서 EnablePartialRendering="True" 속성을 제거하고 응용프로그램을 다시 실행해보십시오. 결코 모르지 않을 것입니다. (속성을 되돌려 놓는 것을 잊지 마십시오!)

엄마 봐봐! 자바스크립트가 없어.

잠시 멈추고 자바스크립트를 쓸 시간이 없었다는 것에 주목하십시오. 여러분은 XMLHttpRequest를 건들지 않았으며, 이것이 어떻게 동작을 완료하는지 생각하지도 않았습니다. 이것은 예를 들어 DataGrid 컨트롤이 스스로 작동하는 것처럼 모두 아주 놀라울 만한 것들입니다. 제 말의 요점이 이것입니다. 단순히 괜찮은 것이 아니라 엄청나게 좋습니다. 이러한 무시가 여러분들이 시도하려는 것에 초점을 맞추도록 만들며, Microsoft가 컨트롤들을 제작하는데 힘을 쏟도록 만듭니다. (이봐요! 그만 훌쩍거려요. 만약 당신이 정말로 그게 어떻게 동작하는지 알기 원한다면, 제가 장담하죠. 우리는 모든 잔인할 정도의 세부사항을 지향하는 자바스크립트와 Atlas 프로그램에 대한 책을 엄청나게 많이 가지고 있습니다. 그러나 작업을 완료하기 위한 관점에서는 한순간으로 충분하지 않나요?)

그러나 더 많은게 있습니다...

좋아요, 우리는 비동기식 업데이트를 완성했고, 여기까지는 Atlas 에 관한 아주 좋은 감정을 느낄 수 있습니다만, 잠시만 컨트롤 익스텐더들에 대해 짚고 넘어가자고요. 여러분은 Atlas 도구키트로 아주 일부를 얻었습니다. (그리고 더 많은 것들은 시간이 알려주겠죠.) 제가 정말로 좋아하는 것은 AlwaysVisible 익스텐더 입니다. 이것은 여러분이 어떤 컨트롤을 갖출 수 있도록 하며, "사용자들이 이것으로 스크롤 하도록 내 페이지에 보이게 하길 원해." 라고 말하게 할 것입니다. 그뿐만이 아니라 이것은 이것저것 따져봐도, 아주 인상깊으며, 실제로 아주 유용합니다. 이것은 여러분들에게 어떤 혼란 없이 프레임들의 힘과 (아까 전에 제 디자이너 친구들이 저에게 말하는 프레임들)을 부여할 것입니다.

우리의 주 페이지는 익스텐더에 대한 몇몇 수정될 항목을 가지고 있습니다. 그 중의 하나가 사용자가 버그를 추가하도록 하게 해주는 메뉴입니다. 심지어 그/그녀가 다시보기 페이지에서 스크롤을 했을 때에도 사용자가 그 메뉴를 쓸 수 있다면 좋을 것입니다. 이것은 황당할 정도로 너무 쉽게 고칠 수 있습니다.

TBTMaster.master 를 열고 페이지에 AlwaysvisiblecontrolExtender 의 인스턴스를 드래그 합니다. 소스 뷰에서 이렇게 하세요. 여는 태그와 닫는 태그 사이에 여러분은 AlwaysVisibleControlProperties 형식의 한가지 요소를 추가할 것입니다. (여러분들의 지력으로 여러분들 스스로 알아맞춰 보세요.) 여러분들은 이미 속성에 대해 알고 있겠지만, 저는 그것들을 여러분들에게 보여줄 것입니다.

TargetControlID="mnuTBTNavigation"
VerticalSide ="top"
VerticalOffset = "10"
HorizontalOffset ="10"
HorizontalSide ="right"
ScrollEffectDuration =".1" />

TargetControlID는 여러분이 보이도록 만들 컨트롤입니다. 이 경우는 menu 입니다. 수직, 수평부분은 (그림 10에서처럼) 메뉴를 어디에 나타내게 하는지 지시합니다. 오프셋은 왼쪽 상단으로부터 여러분이 원하는 만큼 얼마나 떨어지게 하는지, 그리고 스크롤 효과 시간을 여러분이 얼마나 유지할 것인지에 대한 값입니다. (이 경우 0.1초 입니다.) 저는 이것을 허용하는 것을 싫어합니다만 이것은 여러분들이 결정하십시오. Atlas는 나머지를 제거합니다. 스크롤을 하면서 메뉴는 애완견처럼 여러분을 따라다닙니다.


그림10. 항상 보이는 메뉴

여러분이 버그의 세부사항을 찾아보기 위해 스크롤하는 만큼 메뉴가 따라다니다니 놀랍군요. 이렇게 간단한 것만으로도 다른 사람들은 엄청 힘들게 만들만한 것을 쉽게 만들다니... 전 이런 모든 것들을 폼에 드래그 하고, 이것이 작동하도록 마이크로소프트사가 하나의 컨트롤에 캡슐화 했다는 사실이 너무나 좋습니다. --풉!-- 네, 시간이 지났어요. 저는 그것들이 어떻게 동작하는지 알았으면 합니다만, 여러분은 어떤가요? 전 마감할 때가 다 되었고, 이 프로그램은 잘 동작합니다.

취소를 위한 트랩 생성하기

우리가 십수번 (혹은 천 번 넘게) 쓰는 코딩 중 하나가 취소 버튼을 위한 "트랩"입니다. 우리는 사용자가 Cancel을 클릭할 때, 사용자에게 그/그녀가 작업한 내용을 모두 취소한다는 내용을 확인하기 위한 팝업창을 원합니다. 이것은 ASP.NET 응용프로그램 안에서 장난처럼 작성될 수 있습니다. 몇몇 자바스크립트에서 필요로 하는 대화상자처럼 말이죠. 다시 한 번 Atlas는 이런 하찮은 것뿐만이 아니라, 나머지 부분도 똑같이 구성하여, 현재 가지고 있는 응용프로그램과 경계없이 통합됩니다.

TBTBug.aspx 파일은 btnSave와 btnCancel, 두 컨트롤들을 가지고 있습니다. btnCancel에 대한 트랩을 추가하기 위해 여러분이 해야 할 모든것은 ConfirmExtender를 두 버튼이 있는 셀에 드래그 하는 것입니다.

<td colspan="2">
  <AtlasToolkit:ConfirmButtonExtender
  ID="ConfirmButtonExtender1" runat="server">
     <AtlasToolkit:ConfirmButtonProperties ConfirmText="Are you sure you want to discard this?"
      TargetControlID="btnCancel" />
  </AtlasToolkit:ConfirmButtonExtender>
   <asp:Button ID="btnSave" runat="server" Text="Save" BackColor="Lime" Font-Bold="True" OnClick="btnSave_Click" />
   <asp:Button ID="btnCancel" runat="server" Text="Cancel" BackColor="Red" Font-Bold="True" ForeColor="Yellow" OnClick="btnCancel_Click" />
</td>

이 효과는 그림 11과 같습니다.


그림11. 취소 확인

ConfirmExtender는 자체적으로 두 가지 속성을 갖는 ConfirmButtonProperties 형식의 내부적인 속성을 가집니다. 한 속성은 메시지 박스 안에 표시될 텍스트에 대한 것이고, 다른 하나는 확인을 알리는 버튼을 확인하기 위한 것입니다.

이 작업을 위해, 컨트롤은 꼭 등록되어 있어야 합니다만, 이것은 여러분이 폼에 컨트롤을 드래그 할 때에 이미 완료되었습니다. 얼씨구나! 제가 사용하는 버전 안에선 태그가 CC1에 설정되어 있지만, 여러분이 원하는 대로 태그를 변경하는 것도 간단합니다. 저는 그것을 AtlasToolkit 으로 변경했습니다.

<%@ Register Assembly="AtlasControlToolkit" Namespace="AtlasControlToolkit" TagPrefix="AtlasToolkit" %>

그래서 제가 컨트롤을 사용 할 때, 저는 이렇게 코드를 쓸 수 있습니다. <AtlasToolkit:ConfirmButtonProperties, 컨트롤의 소스 중 일부입니다. 꼭 필요하진 않지만, 좋습니다.

Toolkit과 함께 유용한 컨트롤들이 많이 있습니다. 그리고 Atlas에서 볼 수 있는 How Do I 시리즈 비디오 튜토리얼을 보기를 권합니다. 그것은 이 부분과 이후의 것도 모두 다룰 것입니다. 여러분은 어떤 Atlas 컨트롤들이 발표되었는지 꾸준히 업데이트 되는 내용을 RSS 피드를 통해 구독하길 원할지도 모르겠습니다.

이 글 전체를 통틀어, 어느 누구도 여러분에게 Atlas가 어렵다고 강요할 수 없습니다. 주안점은 이것이 ASP.NET처럼 쉽고, 여러분의 응용프로그램을 구성하는데 초점을 두도록 하며 밑바닥에서부터 구성하도록 만들지 않는다는 것입니다.

Jesse Liberty 는 Microsoft .NET MVP 이며 베스트셀러인 오라일리 미디어의 ASP.NET 프로그래밍, C# 프로그래밍, VB2005 프로그래밍, 그리고 수십권의 웹과 객체지향 언어에 대한 다른 책들의 저자입니다. 그는 .NET 에서 계약 프로그래밍, 상담, 그리고 온라인 강좌를 제공하는 자유연합의 대표입니다.

출처 : 한빛 네트워크
Posted by 상현넘™

댓글을 달아 주세요

이 문서 내용:
 - ASP.NET “Atlas” 소개
 - Atlas 아키텍처
 - 클라이언트 및 서버 컨트롤
 - Atlas와 웹 서비스

목차
 - Atlas란?
 - Atlas 아키텍처
 - 클라이언트 스크립트 핵심 라이브러리
 - 클라이언트 스크립트 컨트롤과 구성 요소
 - 서버 컨트롤
 - 웹 서비스
 - 결론

2005년 9월, ASP.NET 팀은 "Atlas"라는 코드명의 ASP.NET에 도입된 새 기능에 대한 첫 CTP(Community Technology Preview) 버전을 발표했습니다.

Microsoft .NET Framework 2.0에 적용되는 이 확장 기능을 사용하여 개발자는 브라우저와 서버의 기능을 모두 활용하여 풍부한 기능의 대화형 웹 사이트를 보다 쉽게 만들 수 있습니다.

Atlas에서 제공하는 풍부한 개발 환경을 흔히 AJAX(Asynchronous JavaScript and XML)라고 하며, 이는 그 동안 사용된 여러 가지 기술의 이름을 결합하여 만든 비교적 새로운 머리글자어입니다. 요즘에 사용되는 브라우저에는 JavaScript에서 서버를 호출하는 데 사용할 수 있는 XMLHttpRequest 개체가 포함되어 있는데, 이 개체를 사용하면 웹 페이지에서 사용자가 입력한 내용에 반응하여 전체 페이지를 새로 고치지 않고도 대역 외 작업을 수행할 수 있습니다. 이 개념 자체는 단순하지만 AJAX 라이브러리는 서버와 통신하고 웹 서비스에서 반환되는 XML을 처리하기 위해 클라이언트 쪽 JavaScript를 작성해야 하는 고된 작업을 크게 줄여줄 수 있습니다.

AJAX를 통해 해결하려는 일반적인 문제들은 대개 HTTP 프로토콜 자체의 특성에서 비롯됩니다. HTTP는 브라우저가 웹 서버와 통신하여 웹 페이지를 가져오고 데이터를 웹 서버에서 받아 다시 게시하기 위해 사용하는 표준 프로토콜로서, 상태를 저장하지 않는 방식의 프로토콜이므로 페이지를 새로 고치기 전에는 사용자의 입력이 서버의 코드에 전달되지 않습니다. 따라서 일반적인 사용자 환경에서는 상태 정보를 서버로 보내기 위해 전체 페이지를 새로 고쳐야 합니다. 사용자가 페이지에 입력한 내용은 서버에서 처리된 후 HTML 형식으로 복원되어 브라우저로 다시 보내집니다.

ASP.NET에서는 이러한 프로세스를 화면에 보이지 않는 폼 필드를 사용하여 관리합니다. 페이지의 일부만 업데이트되더라도 전체 페이지의 HTML이 전송되므로 브라우저 화면은 일시적으로 비어 있게 됩니다. 또한 페이지의 내용을 새로 고치기 위해 브라우저에서 새 내용을 받아 렌더링하는 동안에는 사용자가 웹 페이지와 상호 작용할 수 없습니다. AJAX는 이러한 환경을 개선하기 위해 전체 페이지를 새로 고치지 않고도 서버를 호출하여 웹 서비스를 실행할 수 있는 XMLHttpRequest 개체를 사용합니다. 이 개체를 사용하면 수신한 XML을 기반으로 페이지의 업데이트할 부분을 JavaScript에서 직접 수정할 수 있습니다. 사용자는 페이지가 업데이트되고 있는지 알아챌 수 없으며, 작업은 대역 외 방식으로 백그라운드에서 비동기로 실행되므로 그 동안에도 계속해서 웹 페이지를 읽거나 상호 작용할 수 있습니다.

Atlas란?

ASP.NET의 Atlas 기능을 단지 클라이언트 중심의 웹 응용 프로그램을 만들기 위한 또 하나의 AJAX 스크립트 라이브러리로 생각해서는 안 됩니다. Atlas는 .NET Framework 2.0을 기반으로 만들어졌으며 클라이언트 쪽 JavaScript와 XMLHttpRequest 개체의 기능을 보다 잘 활용할 수 있도록 지원합니다. 또한 기존 ASP.NET 응용 프로그램뿐 아니라 Atlas 컨트롤과 서비스에서 사용하는 클라이언트 스크립트 라이브러리도 손쉽게 향상시킬 수 있는 서버 기반 기능을 갖추고 있습니다. 그림 1의 Atlas 아키텍처 다이어그램은 Atlas가 클라이언트와 서버 모두를 확장하며, 기능이 더욱 다양하고 응답이 보다 빠른 브라우저 간 웹 응용 프로그램을 만들 수 있는 광범위한 개발 기술로 간주되어야 함을 보여 줍니다.


그림 1 ASP.NET Atlas 아키텍처

Atlas를 활용할 수 있는 시나리오는 JavaScript를 비동기식으로 호출하여 웹 페이지의 일부를 업데이트하는 것에 한정되지 않으며 그 동안 구현하기 힘들었던 다양한 클라이언트 환경을 만드는 데 사용할 수 있습니다. 예를 들어 영화 관련 데이터로 구성된 웹 응용 프로그램이 있다고 가정해 보겠습니다. 사용자가 이 응용 프로그램을 사용하여 특정 배우를 검색할 수도 있습니다. 모든 배우 이름을 하나의 드롭다운 목록으로 만들어 제공하는 것은 비현실적이므로 선택의 폭을 좁힐 수 있는 다른 방법을 찾아야 합니다. 예를 들어 배우 이름의 첫 번째 글자를 선택하도록 사용자에게 요청하고 이 글자로 서버에 요청하여 해당 글자가 포함된 목록을 제공하면 처리가 보다 수월해지지만 사용자 입장에서는 귀찮은 일이 됩니다. 또 다른 방법으로, 배우의 이름 중 일부만 입력하여 검색할 수 있는 입력란을 제공할 수도 있습니다. 그러면 서버에서 사용자가 입력한 데이터를 기준으로 검색의 범위를 좁힐 수 있을 것입니다. 첫 번째 방법보다는 낫지만, 이 방법 역시 여전히 개선의 여지가 있습니다. Atlas를 사용하면 사용자의 입력에 따라 동적으로 반응하는 입력란을 제공하여 브라우저에서 전체 페이지를 새로 고칠 때까지 기다리지 않고도 검색 범위를 좁힐 수 있습니다. 그림 2는 Atlas를 사용하여 사용자의 입력에 따라 피드백을 제공하는 자동 완성 동작을 추가한 모습을 보여 줍니다.


그림 2 필터링 콤보 상자

Atlas CTP는 atlas.asp.net (영문)에서 다운로드할 수 있습니다. Atlas CTP를 설치하면 C# .NET 및 Visual Basic .NET용 추가 웹 사이트 템플릿이 Microsoft Visual Web Developer™에 추가되므로 Visual Web Developer에서 파일, 새로 만들기, 웹 사이트를 차례로 클릭하여 웹 사이트 프로젝트를 새로 만들 때 그림 3과 같은 대화 상자가 표시됩니다. Atlas 기반의 ASP.NET 기능을 사용하기 위해, Atlas 웹 사이트에는 웹 응용 프로그램을 구성하는 업데이트된 web.config 파일과 Microsoft.Web.Atlas.dll이 포함됩니다. 현재 릴리스의 경우 Microsoft.Web.Atlas.dll은 응용 프로그램 전체에서 사용할 수 있는 로컬 어셈블리로 응용 프로그램의 bin 디렉터리에 배치됩니다.


그림 3 Atlas 웹 사이트 만들기

Atlas 기반 응용 프로그램은 Atlas를 별도로 설치할 필요 없이 개발 컴퓨터에 있는 파일을 ASP.NET 2.0이 있는 서버로 복사하여 쉽게 배포할 수 있습니다. Atlas는 시스템 수준이 아니라 응용 프로그램 수준에서 설치되므로 다음 버전의 CTP가 제공되면서 기능이 발전하고 변경될 경우 이전 버전의 Atlas가 있는 컴퓨터에서 새 버전을 사용할 수 있습니다. 따라서 시스템 수준으로 설치하는 경우보다 더욱 유연하게 마이그레이션할 수 있습니다.

Atlas 아키텍처

그림 1의 Atlas 아키텍처 다이어그램에서 가장 먼저 주목해야 할 사실은 Atlas가 클라이언트와 서버 양쪽 모두에 적용된다는 점입니다. ASP.NET 2.0에 몇 가지 클라이언트 기능이 추가되었지만 이는 Atlas의 적용 범위에 미치지 못합니다. 아키텍처 다이어그램의 오른쪽을 보면 Atlas의 서버 기능이 ASP.NET 2.0을 기반으로 이를 확장하고 있음을 알 수 있습니다. Atlas는 브라우저에서 서버 기반 데이터와 서비스에 액세스할 수 있는 새로운 기능뿐만 아니라 새로운 서버 컨트롤 집합도 제공합니다.


그림 4 일반적인 클라이언트-서버 상호 작용

다이어그램 왼쪽에는 서버 기능과는 별도로 클라이언트 중심의 JavaScript를 만들 때 사용할 수 있는 포괄적인 클라이언트 스크립트 라이브러리가 나와 있습니다. 이 라이브러리는 향상된 클라이언트-서버 상호 작용이 가능한 풍부한 기능의 응용 프로그램을 개발하기 위해 새 Atlas 기능에서 많이 사용하는 클라이언트의 기반 구조라 할 수 있습니다.

그림 4는 웹 응용 프로그램의 일반적인 클라이언트-서버 상호 작용 방식을 보여 줍니다. 먼저 브라우저에서 웹 페이지를 요청하고 사용자는 이 페이지와 상호 작용합니다. 사용자의 작업을 위해 서버의 데이터가 필요하면 전체 페이지가 새로 고쳐지며


그림 5 Atlas의 클라이언트-서버 상호 작용

사용자 입력에 따라 페이지의 일부를 업데이트합니다. 그러나 이 방식에서는 페이지가 업데이트되는 동안 사용자가 페이지와의 상호 작용을 계속할 수 없습니다. 즉, 사용자는 웹 응용 프로그램에서 작업하는 동안 계속해서 작업을 일시 중단해야 합니다.

그림 5는 전체 페이지를 새로 고칠 필요가 없는 Atlas의 클라이언트-서버 상호 작용 방식을 보여 줍니다. 이 방식에서는 처음 HTML을 가져온 후 서버를 다시 호출할 경우 XML, JSON(JavaScript Object Notation) 또는 HTML 조각으로 업데이트된 데이터를 가져와 페이지를 증분 업데이트합니다. 웹 서비스를 호출하거나 페이지 변경 내용을 가져오기 위한 호출이 비동기식으로 백그라운드에서 수행되므로 사용자는 작업을 일시 중단할 필요가 없습니다. 이러한 비동기식 호출에서는 서버로의 다음 번 다시 게시 작업을 위해 업데이트된 보기 상태 정보를 관리하므로 페이지를 완전히 새로 고쳐야 할 경우 페이지의 정확한 상태를 서버에 전달합니다.

클라이언트 스크립트 핵심 라이브러리

Atlas의 클라이언트 스크립트 라이브러리는 분리된 몇 개의 고유한 조각으로 브라우저에 전달됩니다. 스크립트 핵심은 나머지 라이브러리의 기반이 되는 하위 계층을 구성하며 최하단에는 브라우저 호환성 계층이 자리잡게 됩니다. Atlas의 주요 특징은 AJAX의 주요 요소를 지원하는 최신 브라우저에서만 Atlas가 실행된다는 점입니다. CTP 빌드를 지원하는 브라우저로는 Mozilla Firefox, Apple Safari 및 Microsoft Internet Explorer가 있습니다. 브라우저 호환성 계층은 브라우저의 차이에 신경 쓰지 않고 보다 편리하게 스크립트를 작성할 수 있게 해 주는 추상화 계층입니다. 이 계층을 통해 브라우저의 구현 방식에 의한 차이는 감춰지며 브라우저의 업데이트나 새 버전 발표에 맞춰 Atlas 지원도 쉽게 확장될 수 있습니다. 요청하는 브라우저의 종류에 따라 호환성 계층의 브라우저별 부분 중 어떤 부분이 사용될지 자동으로 결정되며 추상화 계층에 상위 수준의 코드가 미리 작성되어 있으므로 브라우저 구현에 따라 다르게 코딩할 필요가 없습니다.

호환성 계층의 최상단에는 핵심 형식 시스템이 있습니다. 형식 시스템은 JavaScript에서 개체 지향 방식을 사용하기 위한 것으로, JavaScript 개발자는 이 시스템을 사용하여 네임스페이스를 만들고 이 네임스페이스에 클래스를 추가할 수 있으며 개체 상속도 시뮬레이트할 수 있습니다. 또한 인터페이스, 대리자, 열거형을 지원하므로 C# 같은 개체 지향 프로그래밍 언어를 사용하는 서버에서의 코드 개발과 JavaScript를 사용하는 클라이언트 코드 작성 간의 전환이 수월합니다.

Type.registerNamespace(‘AtlasSample’);

AtlasSample.Movie = function(title, genre) {
  var _title = title;
  var _genre = genre;
      
  this.get_title = function() {
       return _title;
  }
  this.get_genre = function() {
       return _genre;
  }
  this.toString = function() {
       return this.get_title() + " : " + this.get_genre();
  }
  AtlasSample.Movie.registerBaseMethod(this, ‘toString’);
}   
AtlasSample.Movie.registerClass(‘AtlasSample.Movie’);

AtlasSample.Drama = function(title, year) {
  AtlasSample.Drama.initializeBase(this, [title, ‘drama’]);
 
  var _year = year;
 
  this.get_year = function() {
       return _year;
  }
  this.toString = function() {       
       return AtlasSample.Drama.callBaseMethod(this, ‘toString’) +
           ‘ : ‘ + this.get_year();
  }
  AtlasSample.Drama.registerBaseMethod(this, ‘toString’);
}
AtlasSample.Drama.registerClass(‘AtlasSample.Drama’, AtlasSample.Movie);
그림 6 Using the Atlas Type System

형식 시스템을 기반으로 구축된 기본 클래스 라이브러리 계층은 클라이언트 스크립트 라이브러리의 핵심을 완성합니다. 개념 자체가 .NET Framework를 모방한 것이므로 일부 형식은 이미 사용자에게 친숙할 것입니다. 예를 들어 JavaScript에서 자연스럽게 이벤트를 멀티캐스팅할 수 있도록 지원하는 Event 개체가 있으며 StringBuilder 개체도 있습니다. 또한 JSON과 XML 데이터에 대한 지원뿐 아니라 개체의 직렬화(serialization)도 지원합니다. 기본 클래스 라이브러리에는 브라우저의 XMLHttpRequest 개체에 대한 추상화를 제공하는 WebRequest와 WebResponse 클래스도 있습니다. 이는 .NET Framework의 System.Net 네임스페이스에 있는 개체와 상당히 유사합니다. 그림 6의 코드는 Atlas 스크립트 핵심을 사용하여 JavaScript로 두 개의 간단한 형식을 만드는 방법을 보여 줍니다. 첫 번째 형식인 Movie는 영화의 제목과 장르를 보여주는 두 속성, 그리고 결과를 문자열로 반환하는 toString 메서드를 지원합니다. 두 번째 형식인 Drama는 Movie 형식을 확장하고 toString 메서드를 다시 정의합니다.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
  <title>Atlas Type System</title>
</head>
<body>
  <form id="form1" runat="server">
       <atlas:ScriptManager runat="server" ID="scriptManager">
           <Scripts>
               <atlas:ScriptReference Path="drama.js"
                    ScriptName="Custom" />
           </Scripts>
       </atlas:ScriptManager>
       <div>
           <input id="btn1" value="Show Movie Data" type="button"
            onclick="return Click_ShowMovie()" />
           <input id="btn2" value="Show Drama Data" type="button"
            onclick="return Click_ShowDrama()" />
       </div>   
  </form>
  <script type="text/JavaScript" language="JavaScript">
       function Click_ShowMovie() {
           var aMovie = new AtlasSample.Movie(‘Ray’, ‘drama’);
           alert(aMovie.toString());
       }
       function Click_ShowDrama() {
           var aDrama = new AtlasSample.Drama(‘Ray’, ‘2005’);
           alert(aDrama.toString());       
       }
  </script>
</body>
</html>
그림 7 Inheritance Demonstration

Movie와 Drama 형식을 사용하는 페이지는 그림 7에서 볼 수 있습니다. 이 페이지는 먼저 형식이 정의되어 있는 .js 파일을 참조합니다. 이 파일은 Atlas ScriptManager 컨트롤에 포함되어 있습니다. 그런 다음 Click 처리기에서 Movie와 Drama 형식의 인스턴스를 만들고 이 인스턴스의 toString 메서드를 호출합니다. 사용되는 코드는 동적 JavaScript이지만 상속 동작은 개체 지향 프로그래밍 언어를 사용할 때와 동일합니다. 현재 발표된 Atlas의 또 다른 장점은 클라이언트 스크립트 라이브러리의 디버그 버전이 포함되어 디버깅과 문제 해결이 더 쉽다는 것입니다. JavaScript 디버깅은 항상 번거로운 작업이었으므로 이러한 지원은 많은 수고를 덜어줄 수 있습니다.

클라이언트 스크립트 컨트롤과 구성 요소

AppDomain은 관리 코드의 하위 프로세스를 격리합니다. 이것은 각 AppDomain에 고유의 상태 집합이 있다는 의미입니다. 하나의 AppDomain에 있는 확인할 수 있는 코드는 호스팅 환경에서 작성된 인터페이스가 상호 작용을 허용하지 않는한 다른 AppDomain에 있는 코드나 데이터를 손상시키지 않습니다. 이것이 어떤 방식으로 작동하는지 알아 봅시다. C# 및 Visual Basic .NET 컴파일러에서 기본적으로 작성되는 형식이 안전한 확인할 수 있는 코드는 어떤 방법으로도 메모리에 액세스할 수 없습니다. 각 명령은 형식이 안전한 방식으로 메모리에 액세스하도록 확인 규칙 집합을 사용하여 런타임으로 검사됩니다. 따라서 런타임 검사을 통해 확인할 수 있는 코드를 실행할 때 AppDomain의 격리를 보장하며 확인할 수 없는 코드의 실행을 막을 수 잇습니다.

호스트는 이러한 격리를 통해 코드를 동일한 프로세스에서 다른 신뢰 수준으로 안전하게 실행할 수 있습니다. 낮은 신뢰 코드는 신뢰할 수 있는 호스트 코드 또는 다른 낮은 신뢰 코드와는 별도로 AppDomain에서 실행할 수 있습니다. 낮은 신뢰 코드의 호스트에 필요한 AppDomain 수는 해당 호스트의 격리 의미에 따라 다릅니다. 예를 들어 Internet Explorer는 관리 컨트롤에 대해 사이트당 하나의 AppDomain을 작성합니다. 한 사이트의 컨트롤은 동일한 AppDomain에서 상호 작용할 수 있지만 다른 사이트의 컨트롤을 간섭하거나 악의적으로 이용할 수 없습니다.

Atlas 아키텍처의 클라이언트 스크립트 핵심을 구성하는 계층 위에는 구성 요소 모델과 컨트롤 계층이 있습니다. 스크립트 라이브러리의 이 계층은 하부의 스크립트 핵심을 기반으로 구축되지만 클라이언트에서는 별도로 렌더링됩니다. 스크립트를 작성할 때 구성 요소 계층을 포함하지 않고 JavaScript 형식 시스템과 기본 클래스 라이브러리를 직접 사용할 수도 있지만, 이렇게 하면 Atlas에서 제공되는 클라이언트 구성 요소에 액세스할 수 없으며 브라우저로 보내지는 페이지 마크업에 포함되는 새로운 선언적 요소 집합인 xml-script를 사용할 수 없게 됩니다. xml-script 요소는 다음과 같은 새로운 형식 값을 사용하는 스크립트 태그 안에 포함됩니다.

<script type="text/xml-script">

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
  <title>Hover Example</title>
  <atlas:ScriptManager ID="ScriptManager1" runat="server" />   
</head>
<body>
  <form id="form1" runat="server">
  <div>   
  <h2>Movie of the Year</h2>
  <div id="popup2004" width="30">2004
  <span id="movieName2004" style="visibility: hidden;
    display: none; border: solid 1px black;
    background: cyan;">Million Dollar Baby</span>
  </div>   
  </form>
  <script type="text/xml-script">
  <page xmlns:script="http://schemas.microsoft.com/xml-script/2005"
    xmlns:samples="samples">
     <components>
       <control id="movieName2004">
         <behaviors>
           <popupBehavior id="popupBehavior2"
            parentElement="popup2004"
            positioningMode="BottomLeft" />
         </behaviors>
       </control>
       <control id="popup2004">
         <behaviors>
           <hoverBehavior hoverElement="movieName2004">
             <hover>
               <invokeMethod target="popupBehavior2"
                method="show" />
             </hover>
             <unhover>
               <invokeMethod target="popupBehavior2"
                method="hide" />
             </unhover>
           </hoverBehavior>
         </behaviors>
       </control>
     </components>
  </page>
  </script>
</body>
</html>
그림 8 Page Showcasing Pop-Up Behavior

마크업에서 추가적인 요소 집합을 사용하기 위한 가장 기본적인 방법은 스크립트 태그를 사용하는 것입니다. 브라우저는 스크립트 요소를 인식하지만 text/xml-script 형식을 처리할 수는 없습니다. 스크립트 태그 자체에 포함된 요소는 Atlas 스크립트 라이브러리에서 처리될 수 있으며 마크업은 클라이언트 스크립트 라이브러리의 구성 요소 계층에서 처리됩니다. xml-script는 클라이언트에서 구문 분석되어 구성 요소와 컨트롤의 인스턴스를 만듭니다. xml-script에는 정의하는 구성 요소와 컨트롤에 대한 속성 설정이 포함될 수 있으며 페이지의 다른 부분에 있는 HTML 요소와의 바인딩도 선언될 수 있습니다. 또한 xml-script 요소에서 웹 서비스 리소스를 선언한 다음 마크업의 다른 부분에서 이를 데이터 소스로 참조할 수도 있습니다. 그림 8의 예제 페이지는 xml-script를 사용하여 마우스 포인터로 연도를 가리킬 때 해당 연도와 관련된 영화 제목이 팝업 요소로 표시되도록 선언적으로 설정하고 있습니다.

그림 8의 페이지에서는 연도를 표시하기 위해 DIV 요소를, 영화 제목을 표시하기 위해 SPAN 요소를 사용하지만 숨겨지도록 선언했습니다. xml-script의 popupBehavior는 영화 제목과 연결되어 있으며 해당 연도와 연결된 hoverBehavior에 의해 실행됩니다. popupBehavior에 대한 코드는 Atlas 스크립트 라이브러리의 컨트롤과 구성 요소 계층에 있습니다. xml-script는 일반적으로 페이지에 포함되는 JavaScript에 비해 더 쉽게 분석할 수 있으며, 특히 다수의 브라우저 구현을 처리하기 위해 코드에서 팩터링을 시작할 때 이러한 특징이 두드러지게 나타납니다. 그림 8의 xml-script와 같은 선언적 구문은 개발 도구를 사용하여 쉽게 만들고 사용할 수 있으며 풍부한 기능의 사용자 환경을 가능하게 하는 xml-script는 페이지가 실행될 때 Atlas 서버 컨트롤에 의해 만들어집니다. Atlas 응용 프로그램에서 사용되는 대다수의 xml-script는 .aspx 파일에 전혀 존재하지 않으며 개발자가 직접 코딩할 필요도 거의 없습니다.

Atlas CTP에서 제공하는 다양한 동작은 사용자 환경을 개선하는 데 사용됩니다. 진행률 동작은 백그라운드에 보류되어 있는 작업에 대한 정보를 제공할 수 있으며 클릭, 가리키기, 팝업 동작은 다양한 사용자 상호 작용을 가능하게 합니다. 이러한 동작은 xml-script를 사용하여 선언적 방식으로 페이지의 HTML 요소와 쉽게 연결할 수 있습니다. 동작 자체는 JavaScript로 구현되기 때문에 더 복잡한 동작도 가능하지만, 페이지에서 동작을 사용할 때는 xml-script를 사용할 수 있습니다.

서버 컨트롤

Atlas CTP에 포함된 서버 컨트롤을 사용하면 페이지를 다시 게시할 때 일시적으로 상호 작용이 중단되는 것을 방지할 수 있습니다. 즉, 서버 컨트롤이 백그라운드에서 렌더링을 업데이트하는 동안 사용자는 웹 페이지와 계속 상호 작용할 수 있습니다. 이러한 기능의 주역은 함께 작동하는 두 개의 서버 컨트롤입니다. 이러한 두 서버 컨트롤을 기존 페이지에 추가하면 상당한 기능 향상을 얻을 수 있습니다. 첫 번째 컨트롤인 ScriptManager 컨트롤은 클라이언트에서 서버로의 다시 게시 동작을 변경하며 두 번째 컨트롤인 UpdatePanel 컨트롤은 변경 작업을 위해 서버에서 페이지의 수명 주기를 관리합니다.

ScriptManager 컨트롤은 Atlas 기능을 사용할 모든 페이지에 포함되어야 합니다. 이 컨트롤은 클라이언트로 보내는 JavaScript를 제어하는 역할을 하는데, 서버 컨트롤은 클라이언트에 JavaScript를 제공할 수 있으며 이 동작을 제어하기 위해 ScriptManager 컨트롤을 이용합니다. ScriptManager 컨트롤은 구현을 위해 새로운 IScriptComponent 인터페이스를 이용하며 xml-script 요소와 연결되는 구성 요소 스크립트 라이브러리도 지원합니다.

다음과 같이 ScriptManager 컨트롤의 EnablePartialRendering 속성을 true로 설정하면 클라이언트에서 서버로의 다시 게시 동작이 새롭게 변경됩니다.

<atlas:ScriptManager EnablePartialRendering="true" runat="server" />

이 코드를 통해 사용자 환경을 중단하지 않고도 서버로 다시 게시를 요청할 수 있게 됩니다. 요청 간의 제어 정보를 보존하는 데 필요한 보기 상태 정보는 부분적 렌더링 요청을 위해 유지됩니다. 그리고 새로 고쳐지거나 수정되는 부분에 대한 HTML은 브라우저 DOM(Document Object Model)과 상호 작용하는 JavaScript에서 업데이트됩니다. ASP.NET 페이지에서 부분 업데이트를 허용해야 하는 영역은 UpdatePanel 컨트롤을 사용하여 지정됩니다.

UpdatePanel 컨트롤은 페이지의 나머지 부분과는 별개로 업데이트되어야 하는 영역을 ScriptManager 컨트롤에 알리는 역할을 합니다. 사용자가 브라우저에서 수행한 작업으로 인해 페이지의 일부를 다시 게시해야 할 경우, 폼 데이터가 게시되고 페이지 수명 주기가 서버에서 실행되기 시작합니다. 이때 스크립트는 백그라운드에서 비동기식으로 다시 게시 작업을 초기화하므로 페이지가 계속 사용자에게 표시됩니다. 서버에서는 클라이언트에서 게시한 보기 상태 데이터를 바탕으로 컨트롤 상태를 복원합니다. 렌더링 단계에 접어들면 ScriptManager 컨트롤은 UpdatePanel 영역을 새로 고쳐 브라우저로 돌려 보내기 위해 이 영역에 대한 렌더링을 격리시킵니다. 또한 페이지의 보기 상태 데이터가 수집되고 응답의 일부로 HTML이 전송됩니다. 그런 다음 브라우저의 스크립트에서 UpdatePanel의 이전 내용을 해당하는 새 HTML로 바꿉니다.

UpdatePanel 컨트롤에는 다음과 같이 Triggers와 ContentTemplate에 대한 요소가 포함될 수 있습니다.

<atlas:updatepanel id="UpdatePanel1" runat="server">
<triggers>
...
</triggers>
<contenttemplate>
...
</contenttemplate>
</atlas:updatepanel>

ContentTemplate 내의 영역은 ScriptManager 컨트롤이 비동기식으로 요청된 다시 게시 작업을 처리할 때 새로 고쳐집니다. Triggers 요소에는 ControlEventTrigger 및 ControlValueTrigger 요소가 포함될 수 있으며, 웹 페이지 개발자는 Triggers 요소를 사용하여 어떤 내용이 변경되었을 때 영역이 업데이트될 것인지 지정할 수 있습니다. 이렇게 하면 UpdatePanel 컨트롤에 직접 포함되지 않은 외부 컨트롤에 의해서도 변경이 이루어지도록 할 수 있습니다. 또한 간단한 선언을 사용하여 페이지의 동작과 UpdatePanel 컨트롤을 제어하고 가져온 새 데이터를 표시할 수 있습니다.

한 페이지에 여러 개의 UpdatePanel 컨트롤을 배치하고, 각 컨트롤을 업데이트하도록 하는 여러 Triggers를 추가할 수 있습니다. UpdatePanel 컨트롤의 내용은 특정 사용자 입력에 응답하는 데 필요한 최소한의 범위로 축소할 수 있습니다. UpdatePanel 컨트롤을 사용하면 기존의 ASP.NET 페이지를 크게 변경하지 않고도 사용자의 입력에 보다 빠르게 응답하도록 만들 수 있습니다.

웹 서비스

웹 응용 프로그램은 서비스 지향 아키텍처를 중심으로 만들어집니다. 대화형 응용 프로그램을 가능하게 하는 핵심은 브라우저에서 서비스에 액세스할 수 있는 기능으로, Atlas에서 제공하는 서비스에는 두 가지 종류가 있습니다. ScriptManager 컨트롤은 다음과 같이 웹 서비스 참조를 위해 자동으로 생성되는 프록시를 사용합니다.

<atlas:ScriptManager EnablePartialRendering="true" runat="server">
  <Services>
       <atlas:ServiceReference GenerateProxy="true"
        Path="~/nominees.aspx" Type="Custom"
  </Services>
</atlas:ScriptManager>

이렇게 하면 클라이언트 쪽 구성 요소가 스크립트에서 웹 서비스를 직접 호출할 수 있습니다. 웹 서비스는 컨트롤에 바인딩되어 더욱 풍부한 동작을 지원합니다. 예를 들어 웹 서비스를 사용하여 관련된 정보를 검색하도록 xml-script에서 AutoCompleteBehavior를 정의할 수 있습니다(그림 9 참조).

<script type="text/xml-script">
  <page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
  <components>
     <textBox id="suggestTextBox">
       <behaviors>
         <autoComplete
          completionList="completionList" serviceURL="suggest.asmx"
          serviceMethod="GetSuggestions" minimumPrefixLength="1"
          completionInterval="500" completionSetCount="15" />
       </behaviors>
     </textBox>
  </components>
  </page>
</script>
그림 9 AutoCompleteBehavior in xml-script

동작은 페이지의 요소에 연결되어 해당 요소의 동작을 확장하며 .aspx 마크업에 설정되면 extender로 참조됩니다. AutoCompleteBehavior는 AutoCompleteExtender 컨트롤을 통해 요소와 연결될 수 있습니다. xml-script를 직접 작성하는 대신 extender가 서버의 컨트롤과 연결됩니다. 그런 다음 적절한 xml-script를 렌더링하여 클라이언트 쪽 동작을 가져옴으로써 컨트롤 동작이 확장됩니다. 웹 서비스를 호출할 때는 호출과 반환에 대한 결과가 대개 XML로 전달됩니다. 그러나 Atlas는 웹 서비스의 데이터를 JSON 형식으로 serialize할 수 있는 기능도 제공하므로 이를 통해 XML 사용으로 인한 일부 오버헤드를 줄일 수 있습니다. JSON 데이터는 브라우저에서 JavaScript 개체로 직접 deserialize될 수 있습니다. 이 밖에도 Atlas는 서버에서 사용되는 보다 복잡한 .NET의 관리되는 형식을 브라우저에서 JavaScript 개체로 serialize할 수 있는 기능을 지원하므로 브라우저에서 웹 서비스에 액세스하는 작업이 간단해집니다.

브라우저에서 사용할 수 있는 웹 서비스는 응용 프로그램의 일부인 사용자 지정 웹 서비스뿐만 아니라 ASP.NET 응용 프로그램 서비스까지 다양합니다. Atlas는 다음과 같이 JavaScript에서 폼 인증 서비스를 직접 사용할 수 있는 기능도 제공합니다.

Sys.Services.AuthenticationService.login(
   username, password, completionFunction);

사용자가 로그인 자격 증명을 제공할 때 HTML이 동적으로 변경될 수 있으므로 사용자는 로그인 페이지로 리디렉션된 후 원래 페이지로 되돌아가지 않아도 됩니다. .aspx 페이지에서 사용되는 프로필 데이터는 웹 서비스 호출을 통해서도 사용할 수 있으며 JavaScript 개체를 통해 서버에 저장된 프로필 데이터를 저장하거나 가져오는 기능도 제공됩니다.

응용 프로그램에서 사용하는 웹 서비스가 언제나 같은 호스트 서버에 있는 것은 아닙니다. 호스트 서버뿐 아니라 실제로 웹 서비스가 같은 도메인에 있어야 한다는 규칙도 없습니다. 그러나 브라우저는 페이지의 원래 출처가 아닌 도메인에 대해서는 XmlHttpRequest를 사용하는 호출을 차단합니다. 자식 요청을 시작하는 숨겨진 IFrame 개체를 사용하여 이 제한을 피하는 몇 가지 교묘한 방법이 있기는 하지만 실행하기가 상당히 번거롭습니다. Atlas에서는 웹 서비스 브리징을 제공하여 이 문제를 해결합니다. 웹 서비스 브리징을 사용하면 클라이언트가 다른 도메인으로 보내는 웹 서비스 호출을 시작할 수 있습니다. 이 호출은 Atlas 응용 프로그램으로 보내지며, 여기서 대상 서버로 요청을 프록시한 후 serialize하여 클라이언트로 결과를 돌려 보냅니다. 물론 Atlas는 IFrame 기술을 사용하여 다른 도메인과 직접 통신하는 기능도 제공합니다.

결론

Atlas는 풍부한 기능을 갖춘 웹 응용 프로그램을 만들기 위한 다양한 기능을 제공합니다. 클라이언트 스크립트 라이브러리는 JavaScript를 작성하는 작업을 간소화하며 JavaScript를 작성할 때 개체 지향적 접근 방식을 사용하기 위한 구문을 제공합니다. 웹 서비스 기능을 사용하면 원격 및 로컬 서비스에 쉽게 액세스할 수 있습니다. 또한 복잡한 형식을 serialize하여 클라이언트와 서버에서 다양한 형식을 쉽게 이용할 수 있습니다. 서버 컨트롤 역시 클라이언트 스크립트 라이브러리를 이용하며 이를 통해 기존 응용 프로그램과 새 응용 프로그램에서 일반적으로 발생하는 '사용 대기를 위한 일시 중지'를 상당히 줄일 수 있습니다.

CTP 빌드는 업데이트, 변경, 새 기능 추가 등이 이루어져 거의 두 달에 한 번씩 새로 발표되고 있습니다. 궁극적으로 Atlas는 Visual Studio의 디자인 환경에서 사용할 수 있는 기능을 포함하여 다음 릴리스의 .NET Framework에 통합될 예정입니다. 근래에 Microsoft는 현재 사용 중인 사이트에 Atlas를 배포하여 웹 응용 프로그램에서 이용할 수 있는, 사용권이 제한된 버전의 Atlas를 발표했습니다. 자세한 내용을 보거나 최신 Atlas CTP를 다운로드하려면 atlas.asp.net (영문)을 참조하십시오.

출처 : MSDN
Posted by 상현넘™

댓글을 달아 주세요