이번 글에서는 UpdateProgress 컨트롤에 대해 살펴보고자 한다.
● UpdateProgress 컨트롤 바로 사용해보기
이 컨트롤도 ASP.NET AJAX 의 확정 컨트롤 형태로 제공되는 넘이다. 긴 시간이 필요한 작업을 비동기 포스트백으로 수행한다고 보자. 작업 처리 동안 사용자가 볼 수 있는 것은 아무것도 없고, 사용자는 작업이 진행중인지 여부도 알 길이 없다. 차라리 일반 포스트백이라면 하단에 화면 갱신 표시기가 움직이므로 사용자 입장에서는 더 나은 UI가 될 것이다.
이런 문제를 해결하기 위해 UpdateProgress 컨트롤을 사용하는데, 즉 긴 작업 처리 시간 동안 사용자로 하여금 "아..현재 작업이 진행 중이구나.."라고 느낄 수 있도록 UI를 제공하기 위한 컨트롤이다.
아래 처럼 간단하게 사용할 수 있다.
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdateProgress ID="UpdateProgress1" runat="server">
<ProgressTemplate>
현재 업데이트 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="Label1" runat="server"></asp:Label>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<br />
<br />
<asp:Button ID="Button1" runat="server" Text="긴 작업하기" OnClick="Button1_Click" />
</div>
</form>
긴 작업을 하는 서버 측 코드는 아래와 같다. 무려 10초나 걸리는 엄청난 작업이다.
protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(10000);
Label1.Text = "작업 종료 시간 : " + DateTime.Now.ToString();
}

○ 예쁜 이미지로 진행 상태 표시하기
위 예제의 텍스트 대신에 아래와 같이 이미지 컨트롤을 추가하여 예쁜 이미지로서 진행과정을 표시할 수도 있다.
<asp:UpdateProgress ID="UpdateProgress1" runat="server">
<ProgressTemplate>
<asp:Image ID="Image1" runat="server" ImageUrl="spinningwheel.gif" />
</ProgressTemplate>
</asp:UpdateProgress>
● 여러 개의 UpdatePanel에 대한 UpdateProgress 컨트롤
자.. 그럼 다시 여러 개의 UpdatePanel이 있다면? AssociatedUpdatePanelID 속성을 통해 UpdatePanel 마다 전용의 UpdateProgress 컨트롤을 지정할 수 있다.
이 속성을 지정하지 않으면 해당 UpdateProgress 컨트롤은 페이지 내 어떤 UpdatePanel 컨트롤에서든 비동기 포스트백이 발생할 때마다 표시된다.
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdateProgress ID="UpdateProgress1" runat="server"
AssociatedUpdatePanelID="UpdatePanel1" DynamicLayout="False">
<ProgressTemplate>
현재 작업 1 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="Label1" runat="server"></asp:Label>
<asp:Button ID="Button1" runat="server" Text="긴 작업하기" OnClick="Button1_Click" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress2" runat="server"
AssociatedUpdatePanelID="UpdatePanel2" DynamicLayout="False">
<ProgressTemplate>
현재 작업 2 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="Label2" runat="server"></asp:Label>
<asp:Button ID="Button2" runat="server" Text="긴 작업하기2" OnClick="Button2_Click" />
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
결과 화면은 아래와 같이 UpdatePanel 각각에 대해 다른 진행 메시지를 표시할 수 있다.

● UpdatePanel 트리거 지정 시 UdpateProgress 컨트롤 표시
한가지 더 확인할게 남아 있다. 앞선 글에서 공부한 UpdatePanel에 트리거를 지정한 경우에도 잘 동작하겠지? 당연히 그래야 한다고 보지만….그렇지 않다.
아래와 같이 트리거를 지정하도록 변경한 경우,
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="Label1" runat="server"></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"></asp:Label>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button2" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1" DynamicLayout="true">
<ProgressTemplate>
현재 작업 1 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdateProgress ID="UpdateProgress2" runat="server" AssociatedUpdatePanelID="UpdatePanel2" DynamicLayout="true">
<ProgressTemplate>
현재 작업 2 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:Button ID="Button1" runat="server" Text="긴 작업하기" OnClick="Button1_Click" />
<asp:Button ID="Button2" runat="server" Text="긴 작업하기2" OnClick="Button2_Click" />
버튼을 클릭하더라도 UpdateProgress 내용은 보이지 않는다.(확인해 보기 바란다)
그 이유는, AssociatedUpdatePanelID 속성을 설정한 UpdateProgress 컨트롤은 UpdatePanel 안에 존재하는 컨트롤에 의해 발생한 비동기 포스트백일 경우만 표시되기 때문이다. 즉 UpdatePanel 외부에 존재하는 트리거(버튼 컨트롤)는 UpdateProgress를 표시할 수 없다는 의미다.
AssociatedUpdatePanelID 속성을 제거한 UpdataProgress 컨트롤은 이런 경우에도 보이긴 하지만 UpdatePanel 별로 다른 진행 메시지를 보여주려는 목적은 달성할 수 없게 된다. 즉 솔루션이 아니다.
이 문제를 해결하기 위해서는 클라이언트 측 코드의 도움을 받아야 한다. 아래와 같이 수동으로 UpdateProgress 컨트롤의 표시를 조정해야 한다.
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<script language="javascript" type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(InitializeRequest);
prm.add_endRequest(EndRequest);
var postBackElement;
function InitializeRequest(sender, args) {
if (prm.get_isInAsyncPostBack()) {
// 동시에 여러 비동기 포스트백은 처리가 되지 않기때문에, 이 경우에는 나중에 발생한 요청을 명시적으로 취소해버린다
args.set_cancel(true);
}
postBackElement = args.get_postBackElement();
if (postBackElement.id == 'Button1') {
$get('UpdateProgress1').style.display = 'block';
$get('UpdateProgress1').style.visibility = 'visible';
}
else if (postBackElement.id == 'Button2') {
$get('UpdateProgress2').style.display = 'block';
$get('UpdateProgress2').style.visibility = 'visible';
}
}
function EndRequest(sender, args) {
if (postBackElement.id == 'Button1') {
$get('UpdateProgress1').style.display = 'none';
$get('UpdateProgress1').style.visibility = 'hidden';
}
if (postBackElement.id == 'Button2') {
$get('UpdateProgress2').style.display = 'none';
$get('UpdateProgress2').style.visibility = 'hidden';
}
}
</script>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="Label1" runat="server"></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"></asp:Label>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button2" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1" DynamicLayout="false">
<ProgressTemplate>
현재 작업 1 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdateProgress ID="UpdateProgress2" runat="server" AssociatedUpdatePanelID="UpdatePanel2" DynamicLayout="false">
<ProgressTemplate>
현재 작업 2 하는 중입니다...다른거 건들지 마셈...
</ProgressTemplate>
</asp:UpdateProgress>
<asp:Button ID="Button1" runat="server" Text="긴 작업하기" OnClick="Button1_Click" />
<asp:Button ID="Button2" runat="server" Text="긴 작업하기2" OnClick="Button2_Click" />
</div>
</form>

** 위 소스를 완벽하게 파악하려면, Microsoft AJAX Library(클라이언트 라이브러리)에 대한 이해가 필요하며, 이에 대해서는 ASP.NET AJAX 시리즈의 글에 포함될 예정이다.
● 한 페이지 내에서 두 개의 작업을 동시에 실행(?)
위 예제를 실행해 보면 알겠지만, 두 버튼을 연속해서 누른다고 해서 두 작업이 모두 실행되지 않는다.
위 예제의 경우 첫 번째 요청만 수행될 것이고, 아래 구문을 제거하고 실행하면, 마지막 요청만이 처리되어 결과가 표시될 것이다.
if (prm.get_isInAsyncPostBack()) {
// 동시에 여러 비동기 포스트백은 처리가 되지 않기때문에, 이 경우에는 나중에 발생한 요청을 명시적으로 취소해버린다
args.set_cancel(true);
}
하나의 웹 페이지 내에서 여러 작업을 동시에 실행하고 싶은 분들은 실망했을 것이다.
엔심플 ASP.NET AJAX 시리즈의 후반부 Microsoft AJAX Library 관련 내용에서도 언급하겠지만, 기본적으로 PageRequestManager 객체는 한번에 하나의 비동기 포스트백만을 처리하도록 작동한다. 즉 여러 개의 비동기 포스트백이 발생하면 가장 최근에 발생한 넘(가장 마지막 비동기 포스트백) 만을 처리하고 나머지는 요청을 취소해 버린다. 따라서 ASP.NET AJAX에서는 여러 개의 비동기 포스트백으로 여러 작업을 동시에 실행하는 것이 기본적으로 지원되지 않는다.
사용자가 버튼을 여러 번 클릭하여 동시에 여러 비동기 요청이 발생할 때, 이를 실제로 동시(병렬) 처리할 수는 없어도, 요청된 순서대로 모든 작업이 (동기적으로) 수행되게 할 수는 있다.
이에 대한 좋은 예시가 있어서 소개한다.
http://disturbedbuddha.wordpress.com/2007/12/12/handling-multiple-asynchronous-postbacks/
이 양반이 소개하는 방법은 아래와 같이 큐를 이용하여 요청을 쌓아두었다가 요청이 완료될 때마다 큐의 다음 요청을 수동으로 포스트백하는 것이다.
<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(InitializeRequestHandler);
prm.add_endRequest(EndRequestHandler);
var pbQueue = new Array();
var argsQueue = new Array();
function InitializeRequestHandler(sender, args) {
if (prm.get_isInAsyncPostBack()) {
args.set_cancel(true);
pbQueue.push(args.get_postBackElement().id);
argsQueue.push(document.forms[0].__EVENTARGUMENT.value);
}
}
function EndRequestHandler(sender, args) {
if (pbQueue.length > 0) {
__doPostBack(pbQueue.shift(), argsQueue.shift());
}
}
</script>
결과적으로 아래처럼 사용자가 연속해서 누른 모든 요청에 대해서 처리를 수행할 수 있다.
(실제 내부적으로 큐를 이용하여 동기적으로 작업이 진행된다>)

오늘은 여기까지 하고, 다음 글에서는 사용자의 작업 요청이 발생한 후에 연속적으로 다른 작업을 요청할 수 없도록 방지하는 기법을 다뤄본다.