본문 바로가기

Programming/.NET

[ASP.NET] AJAX

 1. UpdatePanel

 

일반적으로 웹 환경은 클라이언트가 서버에 어떠한 처리를 요청하면 서버는 요청을 받아들이고 해당 작업을 수행한 뒤 페이지에 대한 전체적인 데이터를 클라이언트에게 넘겨줍니다. 그러면 클라이언트(웹브라우저)는 수신된 페이지 내용을 표시하게 되는 것입니다.

 

이는 페이지 전체가 아닌 일부분만을 갱신할때 매우 비효휼적인 처리방식입니다. 그래서 AJAX는 클라이언트의 Javascrpt를 통해 페이지 전체중 필요한 특정 부분만을 갱신하기 위한 비동기 처리를 요청합니다. 이 후 서버로의 응답을 수신해 필요한 정보만을 화면에 재표시하게 됩니다.

 

<form id="form1" runat="server">

<div>

    <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>

    <asp:UpdatePanel ID="UpdatePanel1" runat="server">

        <ContentTemplate>

            <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>

            <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />

        </ContentTemplate>

    </asp:UpdatePanel>

</div>

</form>

 

protected void Button1_Click(object sender, EventArgs e)

{

    Label1.Text = DateTime.Now.ToString();

}

 

위 예제는 ASP.NET에서 구현가능한 AJAX의 간단한 기능을 보여주고 있습니다. 페이지에서 사용된 ScriptManager는 AJAX를 위한 서버컨트롤로서 AJAX구현에 필요한 자바스크립트(Javascript)를 알아서 로딩하여 관리하고 서버에서 응답해 오는 데이터를 마샬링(SOAP, JSON등을 활용)하여 클라이언트에서 필요한 처리를 수행할 수 있도록 해주는 역활을 수행합니다.

 

특정 웹 사이트가 마스터페이지를 사용하는 형태라면 ScriptManager는 마스터페이지에 배치해 둘 수 있습니다. 그러면 마스터페이지안에 포함되는 하위페이지의 경우 개별적으로 ScriptManager를 둘 필요없이 마스터페이지의 ScriptManager를 대체하여 사용할 수 있습니다. 만약 마스터페이지안에 ScriptManager가 존재하는데 하위페이지에서 또 다시 ScriptManager를 포함시키려 하면 오류가 발생할 것입니다. 왜냐하면 ScriptManager는 페이지당 하나의 ScriptManager컨트롤만 가질 수 있기 때문입니다.

 

하지만 하위페이지에서 해당 페이지에만 필요한 스크립트 처리를 위해 ScriptManager의 형태를 변경해야 한다면 ScriptManager대신 ScriptManagerProxy컨트롤을 사용할 수 있습니다. 아래 예제는 마스터페이지의 ScriptManager에 스크립트참조를 추가하도록 합니다.

<asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="aaa.js" />
    </Scripts>
</asp:ScriptManagerProxy>

UpdatePanel은 ContentTemplate내부에서 부분 PostBack을 발생시키기 위한 필요한 영역을 정의합니다. 이 안에 웹페이지의 작동에 필요한 컨트롤을 배치하고 해당 컨트롤에서 PostBack을 발생시키면 페이지 전체가 아닌 UpdatePanel로 둘러진 영역만 부분 PostBack이 발생하게 되는 것입니다.

 

 2. Triggers

 

그런데 여기서는 한가지 문제가 있습니다. UpdatePanel의 ContentTemplate안에는 버튼도 있는데 실제 갱신이 필요한것은 Label이지 Button이 아닙니다. 버튼은 그저 클릭에 대한 이벤트만 발생시킬뿐 AJAX처리 대상이 아닌데 불필요 하게 서버측 전송데이터에 포함되는 것입니다. 따라서 Label과 Button을 분리시켜야할 필요가 있습니다.

 

<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Button1" />
    </Triggers>
</asp:UpdatePanel>

<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />

 

이럴 경우 UpdatePanel의 ContentTemplate요소에는 PostBack전송에 필요한 요소만 추가하고 이벤트를 발생시키는 요소는 UpdatePanel 밖에서 정의합니다. 그리고 Triggers섹션 하위에 AsyncPostBackTrigger로 이벤트를 발생시키는 컨트롤을 ControlID를 통해 지정하면 됩니다.

 

AsyncPostBackTrigger에는 ControlID외에 EventName속성도 지정할 수 있는데 EventName은 이벤트를 발생시키는 요소가 비동기포스트백 호출을 위해 호출하는 이벤트이름입니다.

 

참고로 만약 UpdatePanel을 2개 이상 사용하고 있는 경우 Triggers로 잡혀있는 컨트롤에서 이벤트를 발생시키면 모든 UpdatePanel이 포스트백을 일으키게 됩니다.

 

<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
    </Triggers>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server">
    <ContentTemplate>
        <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
    </ContentTemplate>
</asp:UpdatePanel>

<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />

 

예를 들어 위와 같이 구성된 페이지에서 Button1_Click 이벤트에 다음과 같이 코드를 작성하고

 

protected void Button1_Click(object sender, EventArgs e)
{
    Label1.Text = DateTime.Now.ToString();
    Label2.Text = DateTime.Now.ToString();
}

 

페이지를 실행한뒤 버튼을 클릭하면 각 Panel에 있는 모든 Label의 Text값이 변경됩니다. Button컨트롤은 분명 UpdatePanel1에 묶어 있지만 이벤트를 발생시키면 모든 UpdatePanel의 요소가 포스트백 대상이 되기 때문입니다.

 

따라서 Trigger로 연결되어 있는 컨트롤의 UpdatePanel만 포스트백을 처리하려면 해당 UpdatePanel에서 UpdateMode속성을 Conditional로 설정해야 합니다.

 

<asp:ScriptManager ID="ScriptManager1" runat="server" ></asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
    </Triggers>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
    </ContentTemplate>
</asp:UpdatePanel>

<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />

 

그러면 이벤트 발생시 연결되어 있는 UpdatePanel의 요소만 포스트백 처리될것입니다.

 

 3. UpdateProgress

 

페이지에서 포스트백이 발생할때 이에 대한 처리시간이 오래걸리는 경우 사용자에게 어떤 식으로는 처리중이라는 메세지를 표시해야하는 경우가 있습니다. UpdateProgress는 이런 경우를 위한 컨트롤입니다.

 

<asp:UpdateProgress ID="UpdateProgress1" runat="server">
    <ProgressTemplate>
        처리중...
    </ProgressTemplate>
</asp:UpdateProgress>

 

UpdateProgress 컨트롤을 원하는 위치에 배치하고 ProgressTemplate안에 적당한 내용을 넣어둡니다. ASP.NET의 대부분의 Template이 그러하듯이 여기에도 단순텍스트나 HTML요소, 컨트롤등이 들어갈 수 있습니다.

 

protected void Button1_Click(object sender, EventArgs e)
{
    Thread.Sleep(10000);
    Label1.Text = DateTime.Now.ToString();
}

 

클릭이벤트에는 일부로 시간을 지연시키기 위해 Sleep를 주었습니다.

 

실제 버튼을 클릭하면 UpdateProgress가 배치된 위치에 '처리중...'이라는 메세지가 뜨고 Sleep시간이 지나면 시간이 표시될 것입니다. 참고로 DisplayAfter속성을 이용하면 지정된 시간 이후에 메세지를 표시합니다. 이 기능은 처리시간이 빠르게 진행될때 메세지를 굳이 표시할 필요가 없는 경우 유용하게 사용될 수 있습니다.

 

 4. Script combining (스크립트 결함)

 

ASP.NET의 AJAX처리는 필요에 따라 여러 스크립트를 다운로드하도록 합니다. 문제는 수많은 스크립트의 다운로드를 위한 요청이 너무 많으면 성능상 좋지 못하기 때문에 이들 스크립트를 하나로 묶어주는 방법을 써야 합니다.

 

이 방법은 현재 페이지에 AJAX처리를 위해 필요한 스크립트를 ScriptManager에 모두 명시함으로서 가능한 것인데 이때 무슨 스크립트가 얼마나 필요한지를 어떻게 알아낼까 하는 의문이 생길 수 있습니다. 이 문제는 ScriptReferenceProfiler 라는 서버컨트롤을 사용하면 아주 간단히 해결될 수 있습니다.

 

해당 컨트롤은 Visual Studio의 도구모음에 포함되어 있지 않습니다. 아래 사이트에 접속해 내려받으시기 바랍니다.

 

http://aspnet.codeplex.com/releases/view/13356

 

파일(Binary)을 내려받고 압축을 풀면 dll파일 하나만 있는데 이 파일을 도구모음에 포함시키면 다음과 같이 ScriptReferenceProfiler컨트롤이 생성되는 것을 볼 수 있습니다.

 

 

 

이 컨트롤을 aspx디자인모드에서 원하는 곳에 끌어다 놓으십시오.

 

<%@ Register Assembly="ScriptReferenceProfiler" Namespace="ScriptReferenceProfiler" TagPrefix="cc1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
                <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
            </ContentTemplate>
        </asp:UpdatePanel>
        <cc1:scriptreferenceprofiler runat="server">
        </cc1:scriptreferenceprofiler>
    </div>
    </form>
</body>
</html>

 

그러면 Register 지시자와 함께 컨트롤이 배치될 것입니다. 이 상태에서 그대로 페이지를 실행시켜 보면

 

위와 같이 필요한 스크립트를 표시하게 됩니다. 필요한 경우 별도로 스크립트를 다운받을 수도 있군요.

 

어쨌건 위에 표시된 목록을 그대로 복사해서 ScriptManager에 CompositeScript로 추가시키면 스크립트 결합이 완료됩니다.

 

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <CompositeScript>
        <Scripts>
            <asp:ScriptReference name="MicrosoftAjax.js"/>
            <asp:ScriptReference name="MicrosoftAjaxWebForms.js"/>
        </Scripts>
    </CompositeScript>
</asp:ScriptManager>

 

예제에서는 어차피 필요한 스크립트가 2개밖에 안되므로 별 차이가 없지만 많은 수의 스크립트를 필요로 하는 경우 해당 스크립트에 대한 서버요청수를 획기적으로 줄일 수 있으므로 성능에 분명 도움이 될것입니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET] 서버 컨트롤 - 6 (컨트롤)  (0) 2017.02.08
[ASP.NET] HttpModule, HttpHandler  (0) 2017.02.02
[ASP.NET] AJAX  (0) 2017.01.24
[ASP.NET] 캐시 - 2  (0) 2017.01.17
[ASP.NET] ExecuteXmlReader  (0) 2017.01.13
[ASP.NET] SiteMapDataSource  (0) 2017.01.03