달력

022012  이전 다음

  •  
  •  
  •  
  • 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
  •  
  •  
  •  

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.1 버전으로 개발된 웹사이트를 운영중인 서버에 ASP.NET 2.0 버전으로 개발된 웹사이트를 추가로 올려서 운영을 하게되면 1.1 버전으로 운영중인 사이트가 죽게되고 1.1 버전으로 개발된 웹사이트를 다시 살리면 2.0으로 개발된 웹사이트가 죽게된다.
이러한 문제점을 해결하고 1.1과 2.0 버전으로 개발된 웹사이를 동시에 운영할 수 있는 방법을 알아보자.



1. 제어판 - 관리 도구 - 인터넷 정보 서비스(IIS) 관리를 실행시킨다.

2. 새로운 응용 프로그램 풀을 추가한다.


3. ASP.NET 2.0 버전으로 개발된 웹사이트의 속성창을 연다.

4. 속성창 탭중에서 [홈 드렉토리]로 이동한 후 응용 프로그램 풀에서 새로 생성한 응용 프로그램 풀을 선택한다.

5. 모든 설정이 끝났으니 이젠 1.1과 2.0 버전으로 개발된 웹사이를 각각 테스트 해보기 바란다.

작성 : 상현넘™ [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 추가로 가능하네요 ^^; 똑같은 방법인지 몰랐습니다.

저 자 : 이시환
출판사 : 한빛미디어

도서특징
반복적인 코딩 작업은 이제 그만! 코드량을 최대 70%까지 확~줄여줍니다.
ASP.NET 2.0의 개념부터 상세하고 친절하게 알려주어 웹 프로그래밍 경험이 없는 독자도 쉽게 웹 프로그래밍을 시작할 수 있다. ASP.NET 2.0이 지향하는 Codeless(코드량을 혁신적으로 줄인 개발 방법)에 맞춘 예제들은 생산성을 높이는 방법을 일깨워준다. 매번 웹 사이트를 구현할 때마다 똑같은 삽질을 하고 있다는 자괴감에 빠진 개발자에게는 더욱 안성맞춤이다. 중요한 대목에서는 예제만 살펴보고 끝내지 않는다. 호기심을 자극하는 ‘비타민 퀴즈’, 머리 속이 깔끔하게 정리되는 ‘실습문제’를 낸다. 이 책을 읽다 보면 개발자의 수고를 획기적으로 덜어주는 '친절한' ASP.NET 2.0을 만나게 될 것이다.



저 자 : Dave Crane , Eric Pascarello , Darren James
역 자 : 강철구
출판사 : 에이콘

도서특징
웹 사이트에 접속하는 사용자들은 전통적인 웹 사이트 구성 방법에 이미 식상해 있다. 웹 페이지를 보다 보면 스크롤 위치가 자꾸 바뀌는 통에 짜증이 나고, 페이지를 새로 받아오는 동안 멍하니 기다리기도 하며, 새로 받아온 페이지에 눈을 적응시키느라 스트레스를 받는다. 불편한 점이 이만저만이 아니다. 자바스크립트와 XML을 사용해 비동기적으로 통신하는 방법, 즉 Ajax라고 알려진 방법을 적용하면 웹 사이트에 접속하는 사용자에게 훨씬 훌륭한 기능과 인터페이스를 제공할 수 있다. 일단 한번 Ajax 인터페이스에 맛을 들인 사용자는 입맛을 바꾸기가 쉽지 않다. Ajax는 웹상에서 물 흐르듯 직관적인 인터페이스를 사용자에게 제공할 수 있는 새로운 개념이다.

Ajax 인 액션은 이렇듯 새로운 개념을 직접 구현하는 데 꼭 필요한 내용을 담고 있다. 애플리케이션 프로그램의 전체적인 기능을 그대로 유지하면서 서버와 클라이언트 부분으로 어떻게 분리하는지를 설명한다. 또한 유연하고 유지보수가 용이한 상태로 애플리케이션을 관리하는 방법도 알려주며, 프로그램을 적절하게 잘 설계해 브라우저 호환성 등의 문제에도 얼마나 잘 대응할 수 있는지를 설명한다. 이 책을 읽기 전에 전통적인 웹 사이트에서 주로 사용하던 낡은 프로그래밍 기법은 버려야 한다. 독자는 프로그램 실행 코드의 상당 부분을 웹 브라우저로 옮겼을 때 어떤 장점이 있는지를 몸소 체험할 수 있다. 이 책은 지금 웹 사이트를 개발하거나 개발해 본 경험이 있는 웹 개발자의 필독서이다.
Posted by 상현넘™

댓글을 달아 주세요

  1. 나루  댓글주소 수정/삭제 댓글쓰기 2006/07/25 23:35

    뇌를자극하는 asp.net 2.0 나도 저거샀는데. ㅋ
    Ajax 좀 많이 알려줘라 ~~