달력

42024  이전 다음

  • 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

기본 설정을 어케 바꾸는지 몰라서..

aspx 파일 하나 생성하구... DOCTYPE 을 바꿔준다는 ㅎㅎㅎ

 

하여간에 비쥬얼 스튜디오에서 웹폼 생성하면 HTML 부분이 이렇게 아래처럼 된다...

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
    </div>
    </form>
</body>
</html>

여기서 DOCTYPE 이랑 HTML 부분을 4.0 스타일로 바꿔야

vs 에서 문법 체크를 안한다...

 

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

 

<html>

...

 

 

이렇게 말이다.. 흐흐

 

 

Posted by tornado
|

.NET Framework V2.0 Obsolete API List

These lists identify all of the APIs which are obsolete in the .NET Framework V2.0. These lists are meant to serve as a simple reference for obsoleted members and types. Where applicable, an alternate or improved version of the API that provides a more robust set of functionality is listed. If no alternative is suggested then there is no replacement for the API. It is perfectly valid to continue to use and consume a type/member which is obsoleted as a warning. For information on making APIs obsolete, guidance is available in this Obsolete FAQ.

In each list, there are two separate counts specified for each assembly or namespace:

  • The Obsoleted Types count is the number of obsoleted types found in the assembly/namespace.
  • The Obsoleted Members count is the number of obsoleted members not contained in an obsoleted type. If a type is obsolete, it's members are not counted in this value.

Note: only assemblies that contain obsoleted types or members are listed.

Obsolete List: By Assembly

Obsolete List: By Namespace
  

Posted by tornado
|

http://haacked.com/archive/2005/03/07/2317.aspx

 

Quick and Dirty Guide to Configuring Log4Net For Web Applications

Looking around, I noticed a lot of people struggling with getting Log4Net to work with their web applications (ASP.NET 1.1). I'm not going to spend a lot of time digging into Log4Net here, as you can do a Google search for that. But I will give you a quick and dirty guide to quickly getting it set up for a website. Bar of soap not included.

Using a Separate Config File
Although you can put your Log4Net configuration settings within the web.config file, I prefer to use a separate configuration file. Log4Net is a bit of an elitist. It won't dare put a FileSystemWatcher on web.config nor App.config. However, if you tell it to use its own config file, it will gladly monitor that log file and update its settings on the fly when the file changes.

Specifying the Log4Net Config File
If you use a separate config file, a quick and easy (and dirty) way to have your application find it is to place the config file in the webroot and add the following attribute to your AssemblyInfo.cs file.

[assembly: log4net.Config.DOMConfigurator( ConfigFile="Log4Net.config",Watch=true )]

Declaring the Logger
At the top of each class that I plan to use logging in, I declare a logger like so:

private static readonly ILog Log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType);
The reason I place a logger in each class is to scope it to that class. If you read the log4Net docs, you'll see what I mean by this.

Using the Logger
Once you've declared the logger, you can call one its logging methods. Each method is named for the logging level. For example:

Log.Debug("This is a DEBUG level message.  It's typically your most VERBOSE level."); Now whether that message shows up in your logs depends on how you've configured your appenders and the logging level you've set. Don't understand what that means? Read the Log4Net introduction.

Sample Web Solution
In order to make all this discussion very concrete, I've gone ahead and did all your homework for you by creating a simple ASP.NET 1.1 web solution (Log4NetSampleSolution.zip ) using Visual Studio.NET 2003. After unzipping this solution, you should be able to build it and then view the default.aspx web page. This page will log a few very interesting messages of varying levels to three appenders.

Of special note is the use of the RollingFileAppender as seen in this snippet.

<appender name="RollingLogFileAppender"        type="log4net.Appender.RollingFileAppender">    <file value="..\\Logs\\CurrentLog" />    <appendToFile value="true" />    <datePattern value="yyyyMMdd" />    <rollingStyle value="Date" />    <filter type="log4net.Filter.LevelRangeFilter">        <acceptOnMatch value="true" />        <levelMin value="INFO" />        <levelMax value="FATAL" />    </filter>    <layout type="log4net.Layout.PatternLayout">        <conversionPattern         value="%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n" />    </layout></appender>

Note that the file value (with backslashes escaped) points to ..\Logs\CurrentLog. This specifies that Log4Net will log to a file in a directory named "Logs" parallel to the webroot. You need to give the ASPNET user write permission to this directory, which is why it is generally a good idea to leave it out of the webroot. Not to mention the potential for an IIS misconfiguration that allows the average Joe to snoop through your logs.

[Listening to: Envy / Faith - Deep Dish - Global Underground 021 - Moscow CD2 (4:51)]

Feedback

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Exactly what I was trying to accomplish. Thanks.
3/12/2005 8:42 AM | sam

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Great info - thanks. This works for a website and the classes (Page) objects in the website. What about when the website uses different class libraries? How do I configure the class libraries so that they, too, will log to the same location as the website code does? Can I have the class libraries point to the log4net config file that is used by the website?

Thanks in advance.
3/17/2005 7:36 AM | bags

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

That's the beauty of it. If your classes are declaring a logger, they will log to wherever the executing application is configured to log to.

If you think of your application as an assembly graph, you need to configure your assembly and all the rest pick up that setting.

Typically your root assembly is your website or your EXE.

Hope that helps.
3/17/2005 8:07 AM | Haacked

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Thanks a lot. You don't believe I have been looking for it since 41/2 hours, most of the examples out there: either they are difficult to configure or difficult to understand.
Good work, keep it up
4/1/2005 12:17 PM | coolguy

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Glad it helped! Thanks for the compliment.
4/1/2005 12:51 PM | Haacked

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Hello,

We are evaluating log4net for one of our customers.

Our application consists of Asp.Net application which uses remoting.
We have included log4net in both web application and remoting application.
but the log4net logs the debug info only for the web application and not in=
the remoting application.
It doesn't even creat the log file in the remoting application directory.
we have given necessary permissions for the folders.

Help on the same is much appriciated. Thanks!

Regards,
Rohit

The following is mentioned in web.config of remoting application
<configSections>
<section name=3D"log4net" type=3D"log4net.Config.Log4NetConfigurationSect=
ionHandler,log4net" />
</configSections>

<log4net>
<appender name=3D"FileAppender" type=3D"log4net.Appender.RollingFileAppen=
der">
<param name=3D"File" value=3D"Test.log" />
<param name=3D"AppendToFile" value=3D"true" />
<param name=3D"RollingStyle" value=3D"Date" />
<param name=3D"MaxSizeRollBackups" value=3D"30" />
<param name=3D"DatePattern" value=3D"yyyyMMdd" />
<layout type=3D"log4net.Layout.PatternLayout">
<param name=3D"ConversionPattern" value=3D"%d [%t] %-5p %c [%x] - %m%n"=
/>
</layout>
<filter type=3D"log4net.Filter.LevelRangeFilter">
<levelMin value=3D"DEBUG" />
<levelMax value=3D"WARN" />
</filter>
</appender>
<appender name=3D"EventLogAppender" type=3D"log4net.Appender.EventLogAppe=
nder">
<applicationName value=3D"ApplicationName" />
<layout type=3D"log4net.Layout.PatternLayout">
<conversionPattern value=3D"%date [%thread] %-5level %logger [%ndc] - %=
message%newline" />
</layout>
<filter type=3D"log4net.Filter.LevelRangeFilter">
<levelMin value=3D"ERROR" />
<levelMax value=3D"FATAL" />
</filter>
</appender>
<root>
<level value=3D"DEBUG" />
<appender-ref ref=3D"FileAppender" />
<appender-ref ref=3D"EventLogAppender" />
</root>
</log4net>

Also in the AssemblyInfo file we have added this line
[assembly: log4net.Config.DOMConfigurator(Watch=3Dtrue)]=
4/9/2005 4:26 AM | Rohit

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Rohit, which version of Log4Net are you using. The config sample you sent me uses a different syntax than what I used.
4/9/2005 10:15 AM | Haacked

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Thanks for the reply, we are using .Net framework version 1.1 and log4net version log4net-1.2.0-beta8. Please let me know where we are going wrong.
I also noticed that log.IsDebugEnabled(also any other IsxxxEnabled) function always returns false. I tried with the same configuration with other test projects i created it works but it doesn't work with my actual application. [It doesn't work with the Remoting application but same configuration works with web application]
Thanks again fro the reply...
4/12/2005 4:24 AM | Rohit

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

I'm using version 1.2.0.30714 and the only thing I noticed is that your configuration file was very different from mine in how you declared the appenders.
4/14/2005 8:28 AM | Haacked

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Hi all,

I need help to set the log4net to delete log files automatically after 10days.
4/21/2005 1:24 PM | AspNet

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Hi

I'm simply trying to get NUnit to run some tests that log, with the Test project being the one that starts the application logging. In fact, ive seen this before and this is what im trying todo: im using a configuration class (that will utlimately read from the registry or db) that says where the configuration path should be, and that in turn tells to write to file, ill write out the TestFixture first and then the config file. I dont get any errors but neither do i get a file created or written to... thanks for any help!

[TestFixture]
public class TestLogging
{

static TestLogging()
{
string configFile = Configuration.LogConfigFilePath;
log4net.Config.DOMConfigurator.Configure(new System.IO.FileInfo(configFile));
ILog log = LogManager.GetLogger(typeof(TestLogging));
log.Info("Starting Logging");
}

[Test]
public void Test()
{
ILog logger = LogManager.GetLogger("my logger");
logger.Info("Logging the Test() method");
}
}





<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="c:\logs\log-file.txt" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
5/17/2005 10:11 AM | Al

# re: Quick and Dirty Guide to Configuring Log4Net For Web Applications

Great, just what I needed. Been pounding my head against the wall for last couple of hours, trying to get it work, and this was excactly what I needed to get starting.

I've made 2 slight chances though

1. I put the assembly line in the global.asax instead of the assembly.cs. As far as I can tell it makes no difference and it just seems a bit more 'logical' place to me - purely a question of taste (and of where I suspect my collegues will be able to find it :)

2. I've used
[assembly: log4net.Config.XmlConfigurator( ConfigFile="Log4Net.config",Watch=true )]

Although it compiled just fine, I got a warning saying that the DOMConfigurator was obsolete and that the XmlConfigurator should be used instead - so I did :)


/emil@obey.your.compiler.or.die.com
5/20/2005 7:20 PM | Emil
Posted by tornado
|

http://www.devintelligence.com/Log4NetViewer/

 

 

Introduction

Log4Net Viewer is a GUI log viewer and filter for the Log4Net library. By
default it listens for LoggingEvent objects sent using the UdpAppender and
displays them in a table.

The events can be filtered based on:
  • Level

  • Logger

  • Message

All the details for each event can be displayed by selecting the event in the
table.

Screen shot

Log4Net Viewer

Getting Log4Net Viewer

System requirements

To run Log4Net Viewer you will need:

Setup

Extract the ZIP archive into a directory.

Configuring Log4Net

You will need to configure Log4Net to send logging events to Log4Net Viewer.

The following example shows how to configure the UdpAppender to send events to a RemoteAddress on the specified RemotePort.

<appender name="UdpAppender" type="log4net.Appender.UdpAppender">    <param name="RemoteAddress" value="127.0.0.1" />    <param name="RemotePort" value="8080" />    <layout type="log4net.Layout.XmlLayout">        <param name="Prefix" value="" />     </layout></appender>				

Configuring Log4Net Viewer

You will need to configure Log4Net Viewer to recieve logging events.

End User License Agreement (Disclaimer)

SOFTWARE : Log4Net Viewer - GUI log viewer and filter for the Log4Net library
AUTHOR : Naumtsev Taras

The SOFTWARE, information and documentation is provided "as is" without warranty
of any kind, either express or implied.

In no event the AUTHOR shall be liable for any damages whatsoever including
direct, indirect, incidental, consequential, loss of business profits or
special damages, even if the AUTHOR has been advised of the possibility of such
damages.

This include, but not limited to merchantibility and fitness for a particular
function. The entire risk related to the SOFTWARE performance or quality is
assumed by the user.

It is prohibited to change any part of this SOFTWARE, including changes or
eliminations of messages or windows.

Reverse engineer (decompile) or disassemble of this SOFTWARE is prohibited.

It is prohibited to add any other copyright information (e.g. With bbs-adds)
then listed either in this license or within the SOFTWARE.

The archive and the SOFTWARE may be freely distributed provided that it is not
modified in any form and the original archive remains intact with all files.

All rights reserved by the AUTHOR.

If you do not agree with the terms of this license you have to remove the
SOFTWARE from your storage devices and do not use the SOFTWARE.

Contact information

email: admin at devintelligence.com
url: http://www.devintelligence.com
Posted by tornado
|

http://www.l4ndash.com/Home/tabid/36/Default.aspx

 

 

Log4Net Dashboard / Log4Net Viewer

  • Easy and powerful access to Log4net log table
  • Web application for easy access to the log4net log, by several users.
  • Suitable both during development and production.
  • Highly configurable


Log4Net is widely used as a logging framework, ported from the excellent Log4J logging framework and rewritten by the Apache organization.

Log4Net has proven it value in several projects and is today the number one choice in the .NET community.

The Log4Net Dashboard / Log4Net Viewer (L4NDash) is a web application that uses the Log4Net log table in a SqlServer database, and gives the user features to view and analyze the content of the log4net log table. L4NDash is aimed to fulfill the needs both during development of an application, and later when that application goes in to production.

 

L4NDash extends the benefit of using Log4Net logging framework, your customers can now have a powerful tool to view and track the logging form your application.

 

The main page in L4NDash is the dashboard page, which gives the user instant information on the logging status, on a top level, through a log summary pivot table.


L4NDash Summary table:
Log4Net Dashboard / Log4Net Viewer summary table

 

From the summary table the user can drill down to view collections of log rows and to view all details about a single log row. The content of the summary table can be configured in the config file.

 

L4NDash also offers a page to view the news log rows. Both in the Dashboard page and the New log events page, the user can fine grain the view through defining filters.

 

In addition, L4NDash offers a page to clean out logging events from the database (Clean up log). The page give a status on number of rows, database size etc, and the possibilities to specify which logging evens you need to delete.


L4NDash status table:
Log4Net Dashboard / Log4Net Viewer status table



L4NDash is highly configurable.

  • The summary pivot table can be customized to show the periods you are interested in.

  • Add custom columns, in addition to the standard columns in log4net. Many Log4Net users extend log4net beyond the standard columns (DateTime, Level, Logger, Message and Exception) and add custom columns. L4NDash can be configured use the custom columns, both in viewing and filtering (available in version 2.1)

  • The user interface is defined through extensive use of CSS style sheets. This gives the user the possibility to adjust the look and feel in accordance with personal preferences. In the settings page, the user can choose among several predefined style sheets. If you like to future customized the look of L4NDash, new style sheets can be added to the style sheet directory.

  • L4NDash can be configured to use several data sources bye defining several connection strings in the config file, the user can choos among these different data sources in the settings page.


 

Technology

  • L4NDash  is written in C#, and are using Microsoft ASP.NET.  Version 1.1 of L4NDash uses ASP.NET 1.1, and version 2.0 is based on ASP.NET version 2.0.
     
  • If you use ASP.NET 1.1 it is still possible to use L4NDash version 2.x by configuration Internet Information Server to us different .NET frameworks for different virtual directories.
     
  • L4NDash can be used with Microsoft SQL Server 2000 or Microsoft SQL Server 2005.
     
  • L4NDash uses (off course) Log4net to log events and can be configured to log to all the appenders that Log4Net offers.
     
  • L4NDash uses CSS (cascading style sheet) to define the look and feel of the application.
     
  • L4NDash does not require any third-party plug-ins or components. The user interface is rendered trough plain html and java-script.
     
  • L4NDash comes with an msi installer, making it easy to install. The installer will create a virtual directory (of your choice) in IIS (Internet Information Server).
Posted by tornado
|
Posted by tornado
|

[msdn 펌] AOP...

.NET 2005. 11. 29. 11:58

자바에서 쓰던거 전부 넘어오는거 아닌지 모르겠네..

스프링까지 넘어와서.. VS.NET 툴에 통합되면 무지 편리하겠는데..

 

AOP(Aspect-Oriented Programming)

 

Matthew Deiters
ThoughtWorks

2005년 9월

적용 대상:
Microsoft Visual Studio
Microsoft Visual Basic

요약: 웹 서비스 클라이언트 응용 프로그램의 동작을 동적으로 확장하는 방법을 시연하여 AOP(Aspect-Oriented Programming)에 대한 실용적인 정보를 확인합니다(11페이지/인쇄 페이지 기준).

이 기사의 코드 샘플을 다운로드하려면 여기를 클릭하십시오.

목차

소개
Aspect란?
AOP의 장단점
Trace.WriteLine을 넘어서
Mixin의 배후 구현
동작 위빙(Weaving)
정리
결론

소개

AOP(Aspect Oriented Programming)는 개발된 지 오래되었지만 최근에야 Microsoft .NET 개발 커뮤니티에서 주목을 받기 시작했습니다. 새로 채택되는 모든 기술이 그렇듯이 기술과 기술의 사용 방법에 대해서는 많은 오해가 발생하기 마련이며 AOP도 예외는 아닙니다. 이 기사와 다음 코드 샘플에서는 AOP의 실제적인 응용 방법과 AOP에서 처리할 수 있는 몇 가지 일반적인 문제를 설명하며 AOP에 대해 집중적으로 다룰 예정입니다. 또한 웹 서비스를 사용하는 응용 프로그램을 살펴보고, AOP 프레임워크를 사용하여 웹 서비스에서 반환하는 개체에 새로운 aspect를 적용함으로써 개체의 기능을 확장해 보겠습니다. 이러한 aspect는 WSDL로 생성된 개체 모델과는 별개로 이 기능만을 위한 것입니다.

Aspect란?

개체와 다른 개체 간의 관계를 떠올리면 대개 상속의 관점에서 생각하게 됩니다. 여기서는 Dog라는 추상 클래스를 예로 들어 보겠습니다. 저마다 고유한 동작을 가지는 유사한 클래스를 정의할 때는 대개 상속을 사용하여 기능을 확장하게 됩니다. 예를 들어 Poodle을 정의하는 경우 PoodleDog이므로 PoodleDog를 상속한다고 할 수 있을 것입니다. 지금까지는 괜찮지만 나중에 Obedient Dog라는 이름으로 또 다른 고유한 동작을 정의하면 어떻게 될까요? 당연히 모든 Dog가 고분고분한(obedient) 것은 아니므로 Dog 클래스에는 obedience 동작이 포함될 수 없습니다. 또한 Dog에서 상속된 Obedient Dog 클래스를 만드는 경우 Poodle은 이 계층 구조에서 어디에 위치할까요? PoodleDog입니다. 그러나 Poodle고분고분(obedient)하거나 그렇지 않을 수 있습니다. 그렇다면 PoodleDog에서 상속되는 것일까요, 아니면 Obedient Dog에서 상속되는 것일까요? obedience를 Dog 계층 구조에 부적절하게 강제로 적용하는 대신 고분고분한 모든 유형의 Dog에 적용할 aspect로 볼 수도 있습니다.

소프트웨어 측면에서 볼 때, AOP(Aspect-Oriented Programming)를 사용하면 상속 계층 구조와 별개로 클래스나 개체의 동작을 변경하는 aspect를 적용할 수 있습니다. 그러면 이러한 aspect를 런타임이나 컴파일 시에 적용하게 됩니다. AOP는 설명하는 것보다 예제를 통해 보여 주는 편이 더 간단합니다. 시작하기 전에 반복해서 사용할 네 개의 주요 AOP 용어를 정의해야 합니다.

  • Joinpoint—식별 가능하며 제대로 정의된 코드 지점
  • Pointcut—구성 또는 코드를 사용하여 joinpoint를 지정하는 방법
  • Advice—발생해야 하는 크로스 커팅(Cross Cutting) 작업을 표현하는 방법
  • Mixin—새 동작을 적용할 클래스의 대상 인스턴스와 혼합될 클래스의 인스턴스

이들 용어에 대한 이해를 돕기 위해 joinpoint를 프로그램의 흐름에 정의된 지점으로 생각해 봅시다. 코드에서 메서드를 호출할 때 해당 호출이 발생하는 지점이 joinpoint의 좋은 예입니다. pointcut을 사용하면 프로그램 흐름을 차단할 joinpoint를 지정하거나 정의할 수 있습니다. 또한 pointcut에는 joinpoint에 도달할 경우 발생하는 advice가 들어 있습니다. 따라서 호출 중인 특정 메서드에 pointcut을 정의하는 경우, 호출이 발생하거나 joinpoint가 호출되면 AOP 프레임워크에 의해 프로그램의 흐름이 중단되고 pointcut의 advice가 실행됩니다. advice는 여러 가지일 수 있지만 호출할 또 다른 메서드로 간주하는 것이 가장 일반적입니다. 그러므로 pointcut이 있는 메서드를 호출하는 경우 실행할 advice는 호출할 또 다른 메서드가 됩니다. 이 advice 또는 호출할 메서드는 메서드가 차단된 개체에 있거나 여기서 혼합한 다른 개체에 있을 수 있습니다. mixin에 대해서는 나중에 좀 더 자세히 설명하겠습니다.

AOP의 장단점

흔히들 AOP를 차단으로 오해하지만 사실은 그렇지 않습니다. AOP는 차단 기능의 힘을 빌어 advice를 적용하거나 동작을 함께 위빙(weaving)할 뿐입니다. 현재 ContextBoundObject를 사용하여 AOP를 연상하게 하는 스타일로 차단하는 방법을 보여 주는 다양한 .NET 코드 샘플을 확인할 수 있습니다. 하지만 이 접근 방식에서는 사전 요구 사항에 따라 차단을 허용해야 하는 모든 클래스가 ContextBoundObject에서 상속되어야 하므로 ContextBoundObject는 이 작업에 적합하지 않습니다. ContextBoundObject 같은 사전 요구 사항이 있는 AOP에 대한 접근 방식은 요구 사항으로 인해 좋지 않은 영향이 발생하므로 복잡한 접근 방식으로 간주될 수 있으며 가능한 한 사용하지 않도록 합니다. 복잡한 접근 방식은 시스템에서 많은 공간을 차지하여 잠재적으로 모든 클래스에 영향을 미치므로 향후 시스템을 변경하거나 수정하는 능력을 떨어뜨릴 수 있습니다.

필자는 Encase라는 간단한 프레임워크를 만들었습니다. 여기서 "간단한"이라는 용어는 시스템 전체에 전혀 영향을 주지 않는다는 의미로 사용한 것입니다. AOP를 사용하면 한 시스템의 서로 다른 부분이 영향을 받게 되지만 간단한 프레임워크를 선택하고 뛰어난 프로그래밍 방식을 적용하면 부정적인 문제를 대부분 완화할 수 있습니다. Encase 프레임워크는 pointcut, mixin 및 aspect 위빙을 단순화하도록 설계되었습니다. 개발자는 대부분의 다른 간단한 AOP 프레임워크에 사용되는 XML 같은 구성 파일 대신 Encase가 있는 코드를 통해 aspect를 적용할 수 있습니다.

AOP가 널리 채택되지 않는 이유로는 복잡한 프레임워크를 꼽을 수 있겠지만, 가장 큰 원인은 현재 사용되고 있는 AOP 예제에서 찾을 수 있습니다. 이들 예제는 거의 모두가 실행 전에 메서드를 차단하고 Trace.WriteLine("Method entered.")을 실행하는 aspect를 적용하도록 구성되어 있습니다. 이러한 일반적인 인식과는 달리 AOP는 로깅, 보안, 계측 등을 제외한 문제를 해결하는 데 유용하게 사용할 수 있습니다.

Trace.WriteLine을 넘어서

AOP에 대한 보다 실제적인 접근 방식을 설명하기 위해 ContactService.Service라는 웹 서비스에서 사람 개체 컬렉션을 수신하는 응용 프로그램을 만들어 보겠습니다. 현재 .NET 개발에 웹 서비스를 사용하는 가장 일반적인 방법은 XML을 반환하는 웹 서비스를 호출하고 프레임워크에서 자동으로 이 XML을 개체로 deserialize하는 것입니다. 이러한 개체에는 데이터만 있고 동작은 들어 있지 않습니다. .NET Framework 2.0에서는 partial 키워드를 사용하고 동작을 만들어 이러한 자동 코드 생성 개체에 기능을 추가할 수 있습니다. 그러나 다양한 웹 서비스(또는 프록시) 개체에서 일부 특정 동작을 다시 사용하려고 하면 여전히 문제가 발생하게 됩니다. 앞서 언급했듯이 대부분의 경우 공유하는 공통 동작은 추상 클래스에 있고 다른 모든 클래스는 이 클래스에서 상속됩니다. 그러나 웹 서비스 개체를 사용하면 개체에서 기능을 상속할 수 없습니다. 이 문제는 AOP가 얼마나 강력한지 보여 줄 수 있는 좋은 기회로 활용할 수 있습니다.

여기서 소개하는 응용 프로그램의 용도는 연락처 정보를 표시하는 것입니다. 본래는 정보를 표시하는 것이 목적이었지만 이제는 여기에 몇 가지 동작을 추가해야 합니다. 코드 샘플을 살펴보기 위해 TheAgileDeveloper.ContactService라는 가상 디렉터리를 만들어야 합니다. 이 디렉터리는 로컬 시스템에서 TheAgileDeveloper.ContactService 프로젝트가 위치한 폴더를 가리켜야 합니다.

참고   이 프로젝트는 http://localhost/TheAgileDeveloper.ContactService를 통해 액세스할 수 있어야 합니다.

그림 1. 응용 프로그램 스크린 샷

응용 프로그램에 있는 단일 뷰는 MainForm이라는 WinForm이며, 이 뷰에는 웹 서비스에서 반환된 연락처 개체가 ListView의 왼쪽에 표시됩니다. 연락처를 선택하면 오른쪽에 있는 텍스트 상자에 성과 이름, 그리고 웹 페이지가 표시됩니다. MainForm이 로드되면 ServiceManager 클래스를 호출하여 연락처 정보를 가져옵니다. 다음 ServiceManager 클래스는 언뜻 보면 폼과 웹 서비스 사이의 또 다른 계층을 제외하고는 값을 추가하지 않는 것처럼 보입니다. 그러나 이 클래스는 코드를 복제하지 않고도 웹 서비스에 새 기능을 추가할 수 있는 하나의 공간을 제공하므로 매우 중요한 역할을 수행합니다. 또 다른 이점으로는 전체 응용 프로그램에서 웹 서비스의 자취를 추상화하여 제거한다는 점을 들 수 있습니다.

Public Class ServiceManager    Public Shared Function GetAllContacts() As ContactService.Contact()        Dim service As ContactService.Service = New ContactService.Service        Dim contacts() As ContactService.Contact = service.GetAllContacts        Return contacts    End Function    Public Shared Sub SaveContact(ByVal contact As ContactService.Contact)        Dim service As ContactService.Service = New ContactService.Service        service.SaveContact(contact)    End SubEnd Class

이제 Reference.vb 파일의 TheAgileDeveloper.Client 프로젝트를 살펴보겠습니다. 이 프로젝트는 ContactService의 웹 참조를 가져올 때 wsdl.exe에 의해 만들어진 것입니다. 또한 이 프로젝트는 WSDL에서 다음 Contact 클래스를 자동으로 생성합니다.

'<remarks/>    <System.Xml.Serialization.XmlTypeAttribute(_  [Namespace]:=http://tempuri.org/TheAgileDeveloper.ContactService/Service1 _ )>  _    Public Class Contact                '<remarks/>        Public Id As Integer                '<remarks/>        Public FirstName As String                '<remarks/>        Public LastName As String                '<remarks/>        Public WebSite As String    End Class

위의 코드에서 현재 Contact 개체는 데이터만 처리합니다. 또한 이 코드는 wsdl.exe에 의해 자동으로 생성되며 다음에 다시 생성하면 모든 변경 내용을 잃어버리게 되므로 편집하지 않습니다. 여기서는 Save라는 메서드를 호출하여 개체를 저장하는 동작을 소개하겠습니다. 이 동작은 mixin을 사용하여 손쉽게 수행할 수 있습니다. mixin은 인터페이스 구현만 혼합할 수 있는 등의 제한 사항을 제외하면 다중 상속을 연상하게 합니다. 여기서 사용하는 Encase 프레임워크에는 개체를 가져와 래핑하는 Encaser 클래스가 들어 있습니다. 개체를 래핑하면 실제로 새 개체가 만들어지며(이 경우 새 Contact 개체) 여기에는 구성된 mixin과 pointcut이 포함됩니다.

mixin을 만들어 Contact 개체에서 Save 메서드를 호출하려면 인터페이스를 지정해야 하는데, 여기서는 이를 ISavable이라고 하겠습니다. 실제로 개체에 혼합될 것이 바로 이 ISavable 인터페이스입니다. 이 인터페이스는 ContactSave라는 또 다른 새로운 클래스에 구현해야 합니다.

Public Interface ISaveable    Sub Save()End InterfacePublic Class ContactSave    Implements ISavable    Public Contact As ContactService.Contact    Public Sub Save() Implements ISavable.Save        ServiceManager.SaveContact(Me.Contact)    End SubEnd Class

이 응용 프로그램에서 Contact 개체에 ContactSave 구현을 혼합하기에 가장 적합한 위치는 ServiceManager입니다. 이 동작을 혼합할 수는 있지만 클라이언트 코드, 즉 MainForm을 변경할 필요는 없습니다. 이는 mixin을 적용한 후에도 ContactContactSave가 통합된 새 Contact 개체가 여전히 본래의 Contact 형식을 유지하기 때문입니다. 다음은 이를 처리할 ServiceManager의 변경된 GetAllContacts 메서드입니다.

Public Shared Function GetAllContacts() As ContactService.Contact()        Dim service As ContactService.Service = New ContactService.Service        Dim contacts() As ContactService.Contact = service.GetAllContacts        '//각 contact 개체를 래핑합니다.        For i As Integer = 0 To contacts.Length-1            '//개체를 래핑하는            '//encaser의 새 인스턴스를 만듭니다.            Dim encaser As encaser = New encaser            '//ContactSave의 mixin 인스턴스를 추가합니다.            Dim saver As ContactSave = New ContactSave            encaser.AddMixin(saver)            '//Contact 및 ContactSave 구현을 사용하여            '//새 개체를 만듭니다.            Dim wrappedObject As Object = encaser.Wrap(contacts(i))            '//새로 래핑된 contact 개체를            '//이전 contact 개체에 할당합니다.            contacts(i) = DirectCast(wrappedObject, _              ContactService.Contact)             '//래핑된 개체의 형식이 여전히 동일한지 확인합니다.            '//새로 래핑된 Contact 개체를            '//혼합된 ContactSave의 대상 필드에 할당합니다.            saver.Target = contacts(i)        Next        Return contacts    End Function(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

Mixin의 배후 구현

프레임워크에서 pointcut과 advice 또는 aspect를 적용하는 방법은 프레임워크별로 다르지만 적용 목적과 개념은 같습니다. 이 기사에서 Encaser가 개체를 래핑하면 실제로는 System.Reflection.Emit 네임스페이스의 클래스를 사용하여 MSIL 코드를 내보내 즉석에서 새 Contact 형식이 만들어집니다. 새 Contact 형식은 Contact 클래스에서 파생되므로 계속해서 형식을 공유하지만 새로 래핑된 개체에도 여기서 혼합한 ContactSave 개체에 대한 참조가 유지됩니다. ISavable.Save 메서드는 새 Contact 개체에 구현되므로 Save를 호출하면 실제로는 호출을 혼합된 ContactSave 개체에 위임하게 됩니다. 여기서의 이점은 새 Contact 개체를 혼합된 개체에 구현된 모든 인터페이스에 캐스팅할 수 있다는 점입니다.

그림 2. 래핑된 개체의 UML 다이어그램

.NET Framework 2.0의 부분 클래스 언어 기능을 사용하면 또 다른 partial 클래스에 Save 동작을 추가할 수 있는 것으로 생각할 수도 있습니다. 이는 가능한 일이지만 필자는 코드가 이전 버전의 .NET Framework 1.x와 호환되도록 하기 위해 이러한 방식을 피하고 있습니다. 이제 부분 언어 기능이 있으므로 이전 샘플에서도 일반적인 경우에는 mixin이 필요 없을 것입니다. 하지만 mixin은 개발자가 다른 비관련 개체의 계층 구조에 속한 개체의 다시 사용 가능한 동작을 혼합하도록 하여 partial 클래스보다 더 많은 작업을 수행할 수 있으므로 여전히 중요한 역할을 합니다. partial 키워드를 사용할 때 추가하는 코드는 클래스나 형식이 동일하며 물리적인 위치만 다릅니다. 다음에 소개할 mixin에서는 Contact 클래스에 국한되지 않고 대신 FieldUndoer라는 다시 사용 가능한 클래스인 동작을 추가하는 작업에 대해 설명하겠습니다. FieldUndoerIUndoable 인터페이스를 구현하며 수정된 개체를 원래 상태로 복원할 수 있도록 해 줍니다.

    Public Interface IUndoable        ReadOnly Property HasChanges() As Boolean        Sub Undo()        Sub AcceptChanges()    End Interface

HasChanges 속성은 변경 사항이 발생했음을 나타내며 Undo는 개체를 원래 상태로 되돌리고, AcceptChanges는 개체의 현재 변경 사항을 적용합니다. 따라서 나중에 Undo를 호출하면 마지막으로 변경 사항이 적용된 상태로 복원됩니다. 이 인터페이스가 부분 클래스에 구현된 경우 이 세 개의 메서드는 이 동작을 포함시킬 클래스마다 반복해서 복제되도록 구현되어야 합니다. 필자는 실용주의적인 성향의 프로그래머로 "한 번 코딩하여 한 번만 사용한다"는 원칙을 고수하려 노력하므로 코드를 복제하지 않을 뿐 아니라 복사하여 붙여넣는 경우도 상당히 적습니다. mixin을 사용하면 IUndoable을 구현하는 FieldUndoer 개체를 다시 사용할 수 있습니다. 필자는 이 새로운 기능을 ServiceManager에 다시 혼합하는 방법을 채택하고 있습니다. 모든 클라이언트 코드는 여전히 새로운 mixin을 인식하지 않으며 IUndoable 인터페이스를 사용해야 할 경우가 아니면 변경할 필요도 없습니다. MainFormContact 개체를 변경한 다음 undo를 클릭하여 이 동작을 테스트해 보십시오.

Public Shared Function GetAllContacts() As ContactService.Contact()        Dim service As ContactService.Service = New ContactService.Service        Dim contacts() As ContactService.Contact = service.GetAllContacts        '//각 contact 개체를 래핑합니다.        For i As Integer = 0 To contacts.Length-1            '//개체를 래핑하는            '//encaser의 새 인스턴스를 만듭니다.            Dim encaser As encaser = New encaser            '//ContactSave의 mixin 인스턴스를 추가합니다.            Dim saver As ContactSave = New ContactSave            encaser.AddMixin(saver)            '//FieldUndoer의 mixin 인스턴스를 추가합니다.            Dim undoer As FieldUndoer = New FieldUndoer            encaser.AddMixin(undoer)            '//Contact 및 ContactSave 구현을 사용하여            '//새 개체를 만듭니다.            Dim wrappedObject As Object = encaser.Wrap(contacts(i))            '//새로 래핑된 contact 개체를            '//이전 contact 개체에 할당합니다.            contacts(i) = DirectCast(wrappedObject, _              ContactService.Contact)             '//래핑된 개체의 형식이 여전히 동일한지 확인합니다.            '//새로 래핑된 Contact 개체를 대상 필드에 할당합니다.            saver.Target = contacts(i)            undoer.Target = contacts(i)        Next        Return contactsEnd Function

동작 위빙(Weaving)

mixin은 전체의 작은 부분에 지나지 않습니다. AOP가 진정한 인정을 받는 부분은 혼합된 동작이 함께 위빙될 때 나타납니다. 예를 들어 새 Contact 개체를 사용하는 경우 ISavable.Save 메서드를 호출하면 클라이언트 코드에서는 다음에 IUndoable.Undo를 호출할 때 마지막으로 저장된 변경 내용을 사용하도록 IUndoable.AcceptChanges 메서드를 호출해야 합니다. 이를 검토하여 작은 MainForm에 추가하는 것은 간단하지만 이 규칙을 단일 사용자 인터페이스보다 큰 시스템에 코딩하는 일은 상당한 노력이 요구되는 작업입니다. 이렇게 하려면 Save 메서드가 호출된 모든 항목을 찾은 다음 AcceptChanges를 추가로 호출해야 합니다. 또한 새 코드가 만들어지면 개발자는 Save를 호출할 때마다 이 기능을 잊지 않고 추가해야 합니다. 이는 곧바로 계단식 효과로 이어져 시스템을 순식간에 불안정한 상태로 만들고 추적하기 힘든 다양한 버그를 만들어 낼 수 있습니다. 하지만 AOP(Aspect-Oriented Programming)를 사용하면 이러한 메서드를 함께 위빙할 수 있습니다. 메서드를 위빙하려면 Save 메서드가 호출될 때 Contact 개체가 백그라운드에서 자동으로 AcceptChanges를 호출하도록 pointcut과 advice를 지정합니다.

응용 프로그램에 위빙을 구현하려면 ServiceManager에 코드 한 줄을 추가해야 합니다. 이 코드는 FieldUndoer mixin을 추가한 후에 추가합니다.

'//joinpoint를 저장한 다음 AcceptChanges 메서드를 실행합니다.encaser.AddPointcut("Save", "AcceptChanges") 

AddPointcut 메서드는 서로 다른 여러 개의 서명과 함께 오버로드되어 pointcut을 상당히 자유롭게 지정할 수 있도록 해 줍니다. 호출되는 AddPointcutSave 메서드에 해당하는 문자열을 joinpoint 이름으로, AcceptChanges라는 메서드를 실행할 advice로 사용합니다. 실행을 확인하려면 FieldUndoer.AcceptChanges 메서드와 ContactSave.Save 메서드에 각각 중단점을 설정합니다. MainFormSave 단추를 클릭하면 joinpoint가 차단되어 먼저 AcceptChanges 메서드인 advice를 실행하게 됩니다. advice의 실행이 완료되면 계속해서 Save 메서드를 실행합니다.

이 간단한 예제에서는 전체 응용 프로그램에 걸쳐 새 동작을 추가하는 매우 강력한 기능을 보여 주고 있습니다. AOP는 단순히 기능을 효과적으로 추가하는 방법이 아니라 든든한 조력자의 역할을 담당합니다. AOP는 코드를 다시 사용하고 시스템 관리 용이성을 향상시키는 등의 다양한 이점을 통해 새로운 요구 사항에 발맞추어 손쉽게 발전시켜 나갈 수 있도록 해줍니다. 그러나 이와 동시에 AOP를 잘못 사용하면 시스템 유지 관리에 매우 심각한 영향을 미칠 수 있으므로 AOP의 사용 시기와 방법을 적절히 알아 두는 것이 중요합니다.

정리

AOP는 아직 대규모 또는 중요한 프로덕션 시스템에 사용할 만큼 발전하지는 못했지만 언어에 대한 지원이 늘어나면 보다 널리 채택될 것으로 예상됩니다. 또한 AOP를 활용하는 소프트웨어 팩토리 같은 새로운 소프트웨어 개발 패러다임은 이러한 지원을 촉진하는 사례로 볼 수 있습니다. 현재 .NET 영역에는 다양한 AOP 프레임워크가 있으며 이들은 각각 고유한 접근 방식과 장단점을 보유하고 있습니다.

  • Encase (영문)—이 코드 샘플에 포함된 Encase 프레임워크는 AOP를 신속하게 실행하여 AOP의 기반이 되는 개념을 이해하는 수단으로 사용하기 위한 것입니다. Encase는 런타임에 개체에 개별적으로 추가할 수 있는 aspect를 적용합니다.
  • Aspect# (영문)—aspect를 선언 및 구성하는 언어를 기본적으로 제공하는 CLI를 위한 AOP 지원 호환 프레임워크입니다.
  • RAIL (영문)—RAIL 프레임워크는 가상 시스템에서 클래스를 JIT 컴파일 중일 때 aspect를 적용합니다.
  • Spring.NET (영문)—현재 널리 사용되는 Java Spring 프레임워크의 .NET 버전으로 향후 릴리스에 AOP와 함께 구현될 예정입니다.
  • Eos (영문)—C#을 위한 aspect 지향 확장입니다.

결론

이 기사의 목적은 기존의 로깅 또는 보안 예제에 비해 새롭고 보다 실제적인 AOP 적용 방식을 보여 주는 데 있습니다. AOP를 올바르게 사용하면 많은 이점이 있을 뿐 아니라 기존 프로그래밍 옵션으로는 불가능한 결과를 손쉽게 얻을 수 있습니다. 아울러 AOP의 적용 시기와 방법에 대한 결정을 내릴 때는 가능한 한 인터넷에 나와 있는 다양한 리소스를 검색하여 도움을 얻도록 합니다.

 


저자 소개

Matthew Deiters는 소프트웨어 개발에 깊은 관심을 갖고 있으며 ThoughtWorks에서 컨설턴트로 활동하고 있습니다. 또한 .NET Framework를 사용한 금융 및 보험 업계의 다양한 엔터프라이즈 시스템 개발 업무에 참여했습니다. 그는 XP 프로그래밍과 TTD 방법론을 높이 평가하며 인간이 겪는 문제의 대부분은 디자인 패턴이나 뛰어난 단위 테스트를 통해 해결할 수 있다고 생각하고 있습니다. Matthew의 개인 웹 페이지는 http://www.theagiledeveloper.com/ (영문)입니다.

Posted by tornado
|

ASP.NET 웹 서비스, Enterprise Service 및 .NET Remoting의 성능

 

Ingo Rammer
thinktecture.

Richard Turner
Program Manager
Microsoft Distributed Systems Group

2005년 8월

요약: ASP.NET 웹 서비스, .NET Enterprise Service 구성 요소 및 .NET Remoting 구성 요소의 성능 특성을 실제 상황에서 비교 및 대조하고, 이들 기술의 활용도를 극대화하는 방법에 대한 권장 사항을 확인하십시오(34페이지/인쇄 페이지 기준).

관련 코드 샘플인 ASMXvsEnterpriseServicesvsRemotingPerformanceTests.msi를 다운로드하십시오.

목차

소개
목적
   벤치마크 이상의 목표
   테스트
결과
   테스트 1: 주문 만들기 및 저장
   테스트 2: Northwind 제품 데이터 검색
   테스트 3: 고객 정보 검색
.NET Framework 2.0 성능
결론
   권장 사항
부록 A: 종합 기준 - 빈 메서드 테스트
부록 B - 세부 테스트 결과
   주문을 개체로 저장
   주문을 데이터 집합으로 저장
   제품을 개체로 로드
   제품을 데이터 집합으로 로드
   고객을 개체로 로드
   고객을 데이터 집합으로 로드
   빈 메시지(크로스 프로세스)
   빈 메시지(크로스 컴퓨터)
부록 C: 테스트 응용 프로그램 정보
   테스트 응용 프로그램 실행
   흥미로운 사실 - 기술 독립적인 테스트 응용 프로그램
부록 D: 소프트웨어 및 하드웨어 설치
   응용 프로그램 환경
   인프라 환경

소개

절대 성능은 여러 가지 기술 영역(장치, 하드웨어 컨트롤러, 생명 및 보건 서비스, 특정 재무 시스템)에서 최고의 관심사 중 하나지만 경시되는 경향이 있습니다. 대부분의 업무용 응용 프로그램의 1차적 목표는 "정확성", "배달 시간", 그리고 필요한 만큼 빠른 속도를 갖추는 것입니다. 절대 성능이 극대화된 엔지니어링 응용 프로그램을 만들려면 엄청난 비용과 노력이 필요할 수 있을 뿐 아니라, 업무 시스템에는 대개 이러한 성능 최적화를 위해 막대한 시간과 기술을 투입할 필요가 없는 경우가 많습니다. 그러나 절대 성능을 최적화하려는 노력이 과도한 경우도 있지만 투자 수익을 극대화하려는 대다수 기업에서는 여전히 전체 시스템 성능을 높은 수준으로 유지하고자 합니다.

이 백서에서는 .NET에서 사용할 수 있는 다음 세 개의 분산 구성 요소/서비스 기술 내에서 각각 호스트되는 실제 구성 요소/서비스의 상대 성능을 비교 분석하겠습니다.

  • COM+에서 호스트되는 .NET ES(Enterprise Service)
  • IIS에서 호스트되는 ASMX(ASP.NET 웹 서비스)
  • IIS 및 사용자 지정 호스트에서 호스트되는 .NET Remoting
(참고 System.Messaging과 MSMQ COM API의 성능 비교는 System.Messaging Performance(영문) 백서에서 다룰 예정입니다.)

목적

"어떤 Microsoft 분산 응용 프로그램 기술이 가장 빠른가?"에 관한 논란이나 "특정 기술은 사용하기에 너무 느리다" 같은 의견은 끊임없이 제기되고 있습니다. 본 백서의 주된 목적은 Microsoft 분산 기술의 성능과 관련된 많은 문제, 오해, 정확하지 않은 부분, 그리고 잘못된 정보를 바로잡고 이를 명확히 설명하는 데 있습니다.

또한 이 백서를 통해 Microsoft의 각 분산 구성 요소/서비스 기술의 상대 성능 특성에 대해 현재 알려진 오해를 해결하는 것은 물론, 설명이 추가된 명확한 테스트 및 테스트 결과를 비롯하여 고객의 요구에 가장 적합한 기술을 선택하는 데 도움이 될 만한 간단한 지침을 제시하고자 합니다.

본 백서의 목적을 요약하면 다음과 같습니다.

  1. 대부분의 업무용 응용 프로그램에서 나타나는 이들 세 기술 간의 상대 성능 차이 확인
  2. 현재 알려진 기술 상호 간 성능 저하 문제에 대한 몇 가지 오해 정정
  3. 각 기술의 활용도를 최적화하는 상황, 시기 및 방법을 손쉽게 결정하도록 유도
  4. 이러한 테스트를 각자의 시스템과 환경에서 실행할 수 있도록 테스트 응용 프로그램 제공 가급적 이 테스트 환경을 직접 빌드하고 실행하여 이들 기술의 성능 특성을 확인 및 분석하는 것이 좋습니다. 이렇게 하면 분산 시스템의 성능에 영향을 미치는 많은 요소를 완벽하게 이해할 수 있습니다.

벤치마크 이상의 목표

이 백서에 나와 있는 테스트는 테스트 대상인 특정 기술들 사이에 일관된 비교 결과를 제공하기 위해 명시적으로 디자인되었습니다. 이러한 테스트는 각 기술의 최적화된 절대 성능을 측정하기 위한 것이 아닙니다.

테스트 드라이버(클라이언트) 응용 프로그램은 단일 스레드 응용 프로그램이므로 호출한 서비스가 응답하는 즉시 연속해서 동기식으로 호출합니다. 이 디자인의 경우 서버에서 CPU를 사용하는 과정에 병목 현상이 항상 발생하지는 않습니다.

여러 클라이언트 즉, 멀티스레드 클라이언트를 사용하면 서버측 처리에서 초당 호출 수가 늘어날 수 있습니다. CPU를 많이 사용하는 서버 응용 프로그램 테스트의 경우 여러 클라이언트를 사용해도 테스트하는 기술의 절대 또는 상대 성능이 크게 변화하지 않습니다.

여러 클라이언트를 사용하면 상당히 높은(2배) 집계 서버 처리량을 얻어 테스트를 간소화할 수 있으며 다양한 기술의 상대 성능이 어느 정도 변화하는 효과도 얻을 수 있습니다.

단일 클라이언트와 여러 클라이언트 중 어느 쪽이 더 현실적인지는 웹 서비스를 배포하는 상황과 방법에 따라 다릅니다. 단일 클라이언트로 측정하려는 결정이 본 백서의 결론에 영향을 미치지는 않습니다.

테스트

다음 성능 테스트에서는 종합 기준과 실제 시나리오를 모두 살펴보고 있습니다. 여기서 검토할 일반적인 질문은 다음과 같습니다.

  1. .NET Remoting이 ASMX보다 빠릅니까?
  2. ES는 .NET Remoting보다 느립니까?
  3. ASMX가 많이 느려 실제 시나리오에서는 사용할 수 없습니까?
  • 이러한 의견을 검토하기 위해 다양한 테스트를 수행했습니다. 첫 번째 테스트에서는 대규모의 요청을 받아들여 많은 작업을 수행하거나 많은 작업을 수행하여 소규모 또는 대규모의 결과 집합을 반환하도록 요청 받는 작업에 대한 성능을 검토합니다. 이러한 테스트를 수행하는 목적은 시스템을 빌드하면서 경험할 수 있는 일반적인 업무 시나리오의 성능을 설명하는 데 있습니다.

본 백서의 모든 테스트는 아래 나와 있는 각 기술의 프로토콜에 대해 실행되었습니다.

  • enterprise service(Just-In-Time 활성화 사용)
    • 인증 없음
    • 호출 수준 인증 및 역할 액세스 검사 적용
  • ASP.NET 웹 서비스
    • 인증 없음
    • 사용자 이름 및 암호 인증
    • 통합 인증
  • .NET Remoting
    • TCP/Binary(보안되지 않음)
    • HTTP/Binary(보안되지 않음)
    • HTTP/SOAP(보안되지 않음)
    • IIS의 HTTP/SOAP(보안되지 않음)
    • IIS의 HTTP/Binary(보안되지 않음)
    • IIS의 HTTP/SOAP(HTTP 기본 인증)
    • IIS의 HTTP/Binary(HTTP 기본 인증)
    • IIS의 HTTP/SOAP(통합 인증)
    • IIS의 HTTP/Binary(통합 인증)

다음 테스트는 모두 서버측 메서드를 반복해서 호출하는 단일 클라이언트 응용 프로그램을 기반으로 하고 있습니다. 여기서는 집합의 각 테스트에 대해 초당 평균 원격 호출/메서드 수를 계산하고 전체 집합을 10번 반복하여 전체 다단계 테스트 실행에 대한 평균 및 표준 편차를 구했습니다.

결과

테스트 1: 주문 만들기 및 저장

이 첫 번째 테스트는 호출되는 작업으로 많은 작업을 수행하는 대부분의 최적화된 상황에서 기술별로 나타나는 성능 특성을 시뮬레이트하도록 디자인되었습니다. 호출되는 작업은 데이터베이스에 주문을 저장하고 트랜잭션 내의 두 테이블에 레코드를 작성하여 많은 작업을 수행합니다.

호출자는 다음과 같은 Order 클래스의 인스턴스를 만듭니다.

[Serializable]public class Order{   public int OrderID;   public String CustomerID;   public int EmployeeID;   public DateTime OrderDate;   public Address ShippingAddress;   public Address BillingAddress;   public int ShipVia;   public decimal Freight;   public LineItem[] LineItems;}

Order 클래스에는 Address 형식의 자식 개체 하나와 LineItem 20개로 구성된 배열이 포함되어 있습니다.

[Serializable]public class Address{   public String Name;   public String Street;   public string City;   public string Region;   public string PostalCode;   public string Country;}[Serializable]public class LineItem {   public int ProductID;   public double UnitPrice;   public short Quantity;   public double Discount;}

이 데이터 개체는 메서드를 호출하는 동안 호출자에게서 서버로 전달되며 테스트 대상 기술에 의해 네트워크에서 serialize됩니다.

서버측 구현은 다음과 같습니다.

SqlConnection _conn;private void InitializeComponent(){   // ... 생성된 코드 대부분 생략    _conn = new SqlConnection();   // ... 생성된 코드 대부분 생략}public void StoreOrder(Order o){   using (_conn)   {      _conn.Open();      using (SqlTransaction tx = _conn.BeginTransaction())      {         using (SqlCommand cmd = new SqlCommand(@"INSERT INTO Orders(EmployeeID, " +                       "CustomerID, OrderDate, RequiredDate, ShipVia, ShippedDate, " +                       "ShipName, Freight, ShipAddress, ShipCity, ShipRegion, " +                       "ShipCountry, ShipPostalCode) VALUES (@EmployeeID, @CustomerID, " +                      "@OrderDate, @RequiredDate, @ShipVia, @ShippedDate, @ShipName, " +                        "@Freight, @ShipAddress, @ShipCity, @ShipRegion, @ShipCountry, " +                       "@ShipPostalCode); SELECT @@IDENTITY", _conn,tx))         {            cmd.Parameters.Add("@EmployeeID", o.EmployeeID);            cmd.Parameters.Add("@CustomerID",o.CustomerID);            cmd.Parameters.Add("@OrderDate",o.OrderDate);            cmd.Parameters.Add("@RequiredDate",o.OrderDate.AddDays(10));            cmd.Parameters.Add("@ShipVia",o.ShipVia);            cmd.Parameters.Add("@ShippedDate",o.OrderDate.AddDays(5));            cmd.Parameters.Add("@ShipName",o.ShippingAddress.Name);            cmd.Parameters.Add("@Freight",o.Freight);            cmd.Parameters.Add("@ShipAddress",o.ShippingAddress.Street);            cmd.Parameters.Add("@ShipCity",o.ShippingAddress.City);            cmd.Parameters.Add("@ShipRegion",o.ShippingAddress.Region);            cmd.Parameters.Add("@ShipCountry",o.ShippingAddress.Country);            cmd.Parameters.Add("@ShipPostalCode",o.ShippingAddress.PostalCode);                        decimal orderID = (decimal) cmd.ExecuteScalar();            o.OrderID = (int) orderID;         }         using (SqlCommand cmd = new SqlCommand(@"INSERT INTO [Order Details] (OrderID, " +                      "ProductID, UnitPrice, Quantity, Discount) VALUES (@OrderID, " +                       "@ProductID, @UnitPrice, @Quantity, @Discount)", _conn,tx))         {            cmd.Parameters.Add("@OrderID",SqlDbType.Int);            cmd.Parameters.Add("@ProductID",SqlDbType.Int);            cmd.Parameters.Add("@UnitPrice",SqlDbType.Money);            cmd.Parameters.Add("@Quantity",SqlDbType.SmallInt);            cmd.Parameters.Add("@Discount",SqlDbType.Real);            foreach (LineItem itm in o.LineItems)            {               cmd.Parameters["@OrderID"].Value = o.OrderID;               cmd.Parameters["@ProductID"].Value = itm.ProductID;               cmd.Parameters["@UnitPrice"].Value = itm.UnitPrice;               cmd.Parameters["@Quantity"].Value = itm.Quantity;               cmd.Parameters["@Discount"].Value = itm.Discount;               cmd.ExecuteNonQuery();            }         }                  tx.Commit();      }      _conn.Close();(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)   }}

테스트 실행이 완료되면 서버측 논리에서는 두 번째 트랜잭션을 사용하여 삽입된 레코드를 삭제하고 테스트의 실행 횟수와 관계없는 "동등한 조건의 환경"을 제공합니다.

그림 1. 실제 서버측 구현으로 프로세스 간에 "주문" 전달

그림 1의 테스트 결과를 보면 73 CPS인 ES와 63 CPS인 ASP.NET 웹 서비스의 차이는 14%에 불과하며 최고 속도 프로토콜과 최저 속도 프로토콜의 최대 차이는 45%라는 것을 알 수 있습니다. 이 결과는 수행하는 작업량이 데이터를 전송하는 데 걸리는 시간에 비례하여 증가하면 다른 전송에 대한 특정 전송의 이점이 줄어든다는 것을 분명하게 보여 줍니다.

또한 그림 2에 정의된 DataTable이 들어 있는 형식화된 DataSet을 빌드하여 동일한 조건의 ADO.NET DataSet 테스트를 수행했습니다.

그림 2. 이 DataSet은 구매 주문서를 나타냅니다.

이 DataSet은 생성된 SqlDataAdapter와 함께 사용되며 여기에는 위의 코드 조각에 나타난 것과 유사한 SqlCommand가 들어 있습니다.

주문이 호출자에서 서버로 DataSet 형태로 전달된 후 저장되면 그림 3과 같은 결과를 얻게 됩니다.

그림 3. 구매 주문서를 DataSet으로 저장

여기서 DataSet을 사용하면 성능에 큰 영향을 미치게 되며(50% 속도 감소) 프로토콜 간의 차이는 계속해서 추가로 줄어든다는 점에 유의하십시오. 또한 데이터 개체를 전달하여 얻는 최저 속도의 결과가 ADO.NET DataSet을 전달할 때의 최고 성능을 뛰어넘는다는 점을 기억해야 합니다.

이 결과는 시스템 계층 사이에 ADO.NET DataSet을 전달하면 성능이 크게 저하된다는 사실을 분명하게 보여 줍니다.

테스트 2: Northwind 제품 데이터 검색

이 테스트의 목적은 많은 작업을 수행하지 않는 서비스에서 비교적 많은 분량의 데이터가 반환되는 경우의 기술별 성능 특성을 설명하는 데 있습니다.

이 시나리오를 모델링하기 위해 잘 알려진 SQL Server "Northwind" 샘플 데이터베이스에서 "product" 레코드 목록을 검색하여 반환하는 메서드를 빌드했습니다.

이 테스트에서는 이진 serialization 기술(ES/COM+ 및 Remoting Binary/TCP)의 성능이 가장 뛰어날 것으로 예상됩니다. SOAP 기반 기술은 많은 양의 데이터를 전송하는 데다 serialization 및 deserialization 메커니즘이 복잡하기 때문에 낮은 성능 수준을 나타낼 것으로 보고 있습니다.

첫 번째 테스트에서는 XML Serializer와 호환되는 공용 멤버가 포함된 [Serializable] 클래스를 정의했습니다.

[Serializable]public class Product{   public int ProductID;   public string ProductName;   public int SupplierID;   public int CategoryID;   public string QuantityPerUnit;   public decimal UnitPrice;   public short UnitsInStock;   public short UnitsOnOrder;   public short ReorderLevel;   public bool Discontinued;}

서버측 구현에서는 SQL Server에 대한 데이터베이스 연결을 열고 데이터베이스에 대해 쿼리를 실행한 다음, ADO.NET SqlDataReader를 사용하여 이 클래스의 Product 인스턴스 77개가 포함된 ArrayList를 채웠습니다. 그런 다음 ArrayList는 호출자로 반환되었습니다.

결과는 그림 4에 나와 있습니다.

그림 4. Northwind 제품 카탈로그를 개체로 검색

이 결과는 Enterprise Service(DCOM 사용)와 TCP 기반의 이진 Remoting이 보안되지 않는 호출에 대해 동일한 수준의 성능을 제공한다는 것을 보여 줍니다. 또한 ASP.NET 웹 서비스의 성능이 Enterprise Service의 62% 수준이며 최저 속도 프로토콜은 최고 속도에 비해 초당 호출 수를 17%밖에 사용할 수 없다는 사실도 알 수 있습니다.

이 테스트와 이어지는 모든 테스트에서는 일반적으로 SOAP 서비스를 빌드하는 데 .NET Remoting SoapFormatter를 사용하는 것이 바람직하지 않은 이유가 ASP.NET Web Service 스택의 속도가 세 배 정도 빠르기 때문이라는 점을 분명하게 보여 줍니다.

이 테스트의 두 번째 접근 방식에서는 요청된 데이터를 원본으로 사용하는 테이블 레이아웃에 직접 기반을 둔 ADO.NET DataSet 개체에 반환하고 SqlDataAdapter를 사용하여 DataSet의 DataTable을 채우는 메서드를 구현했습니다. 테이블 정의는 그림 5에 설명되어 있습니다.

그림 5. 제품 카탈로그에 액세스하는 형식화된 DataSet

그림 6에는 다음과 같은 결과가 설명되어 있습니다.

그림 6. Northwind 제품 카탈로그를 DataSet으로 검색

이 테스트 결과는 서비스에서 호출자로 DataSet을 반환하면 성능이 크게 떨어진다는(이진 전송의 경우 약 75%) 것을 분명하게 보여 줍니다. 또한 네 개의 최고 속도 프로토콜 성능이 완전히 같다는 것은 네 기술의 성능이 모두 DataSet의 serialization, deserialization 및 전송에 의해 좌우됨을 나타냅니다. 게다가 최고 속도(ASMX : 39 cps)와 최저 속도(Remoting HTTP/SOAP(IIS) 통합 보안 : 25 cps) 사이의 성능 범위는 14 cps로 구분되며 이는 30%에 불과한 하강 수준을 나타냅니다.

이 결과를 통해 DataSet을 사용하여 응용 프로그램 계층 사이에 정보를 전달하면 편리하지만 그 과정이 성능에 큰 영향을 미치게 된다는 것을 확실히 알 수 있습니다. 이에 대해서는 나중에 다시 살펴보겠습니다.

또한 단순히 ADO.NET DataSet을 호출자에게 반환하기보다는 ADO.NET DataSet을 반복하고 데이터 개체 컬렉션을 만들어 이 컬렉션을 serialize하는 것이 더 빠른 방법이라는 점도 알아 두어야 합니다. 이것이 서버와 호출자 사이에 DataSet을 전달하기 위해 구현된 실제 시스템이었다면 작은 양의 작업으로도 이 부분의 시스템 성능을 4배 가까이 끌어올릴 수 있었을 것입니다.

테스트 3: 고객 정보 검색

다음 테스트에서는 "Northwind" SQL Server 샘플 데이터베이스에서 단일 고객 레코드를 검색하여 서버에서 클라이언트로 매우 작은 양의 데이터를 전송하는 경우의 기술별 상대 성능 특성을 살펴보기로 했습니다.

검색된 데이터를 다시 클라이언트에서 서버로 돌려보내기 위해 다음과 같은 [Serializable] Customer 클래스를 만들었습니다.

[Serializable]public class Customer{   public String CustomerID;   public String CompanyName;   public String ContactName;    public String ContactTitle;   public string Address;    public string City;   public string Region;   public string PostalCode;    public string Country;   public string Phone;   public string Fax;}

다음은 SqlDataReader를 사용하여 Customer 클래스의 새 인스턴스를 채우는 서버측 구현의 일부입니다.

public Customer GetCustomer(string id){   using (SqlConnection conn = new SqlConnection(...))   {      conn.Open();      String sql = "SELECT CustomerID, CompanyName, ContactName, "                   "ContactTitle, Address, City, Region, " +                   "PostalCode, Phone, Fax, Country FROM " +                    "Customers WHERE (CustomerID = @CustomerID)";      using (SqlCommand cmd = new SqlCommand(sql,conn))      {         cmd.Parameters.Add("@CustomerID", id);         SqlDataReader rdr = cmd.ExecuteReader();         if (rdr.Read())         {            Customer c = new Customer();            c.CustomerID = (string) rdr[0];            c.CompanyName = (String) rdr[1];            c.ContactName = (String) rdr[2];            c.ContactTitle = (String) rdr[3];            c.Address = (String) rdr[4];            c.City = (String) rdr[5];            c.Region = rdr.IsDBNull(6) ? "" : rdr[6] as string;            c.PostalCode = (String) rdr[7];            c.Phone = (String) rdr[8];            c.Fax = (String) rdr[9];            c.Country = (String) rdr[10];            return c;         }         else         {            return null;         }      }   }}

성능 비교 결과는 그림 7에 나와 있습니다.

그림 7. 고객 데이터를 개체로 검색

이 결과는 각 기본 기술 간의 성능 차이를 좀 더 확실하게 보여 줍니다. 이 테스트에서는 전체 호출 비용에서 단일 레코드를 검색하고 반환하기 위해 수행 중인 작업이 차지하는 비중이 훨씬 낮으며 그에 따라 전송 비용이 보다 중요한 역할을 하기 때문에 성능 차이가 이처럼 두드러지게 나타나는 것입니다. 따라서 SOAP/HTTP는 Binary/DCOM보다 전송 비용이 높기 때문에 SOAP 전송은 이진 메커니즘보다 처리량 수준이 훨씬 낮습니다.

다음에는 그림 8에서처럼 형식화된 DataSet을 반환하는 동일한 작업을 테스트했습니다.

그림 8. 고객 정보에 액세스하는 형식화된 DataSet

이 DataSet은 다음과 같은 서버측 구현을 사용하여 채웁니다.

SqlDataAdapter _customerAdapter;SqlCommand _customerSelect;SqlConnection _conn;private void InitializeComponent(){   // ... 생성된 코드 대부분 생략    _conn = new SqlConnection();    _customerAdapter = SqlDataAdapter();    _customerSelect = SqlCommand();    _customerSelect.CommandText = "SELECT CustomerID, CompanyName, " +               "ContactName, ContactTitle, Address, City, Region, " +               "PostalCode, Phone, Fax, Country FROM Customers WHERE " +                "(CustomerID = @CustomerID)";_customerSelect.Connection = _conn;_customerSelect.Parameters.Add(new SqlParameter("@CustomerID",                SqlDbType.NVarChar, 5, "CustomerID"));    _customerAdapter.SelectCommand = this.sqlSelectCommand3;    _customerAdapter.TableMappings.AddRange(new DataTableMapping[] {           new DataTableMapping("Table", "Customers", new DataColumnMapping[] {                  new DataColumnMapping("CustomerID", "CustomerID"),                  new DataColumnMapping("CompanyName", "CompanyName"),                  new DataColumnMapping("ContactName", "ContactName"),                  new DataColumnMapping("ContactTitle", "ContactTitle"),                  new DataColumnMapping("Address", "Address"),                  new DataColumnMapping("City", "City"),                  new DataColumnMapping("Region", "Region"),                  new DataColumnMapping("PostalCode", "PostalCode"),                  new DataColumnMapping("Phone", "Phone"),                  new DataColumnMapping("Fax", "Fax"),                  new DataColumnMapping("Country", "Country")})});   // ... 생성된 코드 대부분 생략}public PerfTestDataSet GetCustomerAsDataset(string id){   using (_conn)   {      _conn.Open();      customerAdapter.SelectCommand.Parameters["@CustomerID"].Value = id;            PerfTestDataSet ds = new PerfTestDataSet();      _customerAdapter.Fill(ds,"Customers");      return ds;   }}

이 코드를 테스트 프레임워크 내에서 실행하자 그림 9와 같은 결과가 나타났습니다.

그림 9. 고객 데이터를 DataSet으로 검색

앞의 테스트와 마찬가지로 ADO.NET DataSet을 교환하면 성능에 좋지 않은 영향을 미칠 뿐 아니라, 모든 기술의 처리량 차이도 ADO.NET DataSet을 통해 처리량이 조절되고 있음을 나타내는 serialize된 개체를 반환하는 경우보다 줄어든다는 것을 확실히 알 수 있습니다.

.NET Framework 2.0 성능

이 백서를 작성할 당시 Microsoft는 다음과 같은 수많은 개선 및 향상된 기능과 새로운 기능이 포함된 .NET Framework 2.0(이전 코드 이름: "Whidbey")을 선보이고 있었습니다.

  • x64 및 IA64 기반 시스템 완벽 지원
  • System.Transactions를 통한 새로운 트랜잭션 지원
  • 연결 인식 및 프록시 지원 같은 System.Net의 향상된 기능
  • Windows Form 및 Web Form의 대폭 향상된 기능
  • "ClickOnce" 자동 응용 프로그램 배포 인프라

.NET Framework 2.0는 이처럼 향상된 새로운 기능이 추가된 것 외에도 많은 개선 작업이 이루어져 BCL, XML Serialization, 네트워킹 처리량, ADO.NET, ASP.NET 등과 같은 많은 영역에서 성능이 대폭 개선되고 메모리 사용이 향상되었습니다.

이러한 성능 및 처리량 개선 부분에 대한 초기 예는 다음 백서에서 확인할 수 있습니다.

.NET Framework 2.0이 계속해서 RTM(Release To Manufacturing) 쪽으로 옮겨감에 따라 향후 몇 개월 동안 성능 분석 및 비교와 관련된 백서가 추가로 게시될 예정입니다. 항상 최신 백서를 확인하려면 다음 온라인 리소스를 참조하십시오.

결론

위의 실제 테스트 결과는 표 1에 나와 있습니다.

표 1. 결과 요약

초당 호출 수
(반올림)
enterprise serviceASP.NET 웹 서비스.NET Remoting TCP Binary
테스트 1: 주문 저장
a) serialize된 데이터
b) DataSet

73
35

63
34

72
34
테스트 2: 제품 검색
a) serialize된 데이터
b) DataSet

147
39

93
39

149
39
테스트 3: 고객 검색
a) serialize된 데이터
b) DataSet

618
90

289
83

682
91

위의 결과는 여러분이 실제 응용 프로그램에서 예상할 수 있는 성능 특성을 보여 주며, 이를 통해 실제 응용 프로그램에서는 웹 서비스(일반적으로 매우 느린 것으로 파악된)와 DCOM(이 테스트에서 가장 빠른 분산 응용 프로그램 기술 중 하나로 확인된) 간의 차이가 매우 작다는 것을 알 수 있습니다.

테스트 대상 기술에 대한 Microsoft의 견해는 다음 섹션에 보다 자세히 나와 있으며 요약하자면 다음과 같습니다.

  • ASMX가 현재 Microsoft에서 제공하는 기술 중 가장 빠른 것은 아니지만 그 성능은 대부분의 업무 시나리오에 매우 적합한 수준입니다. ASMX 서비스는 확장이 용이할 뿐 아니라 상호 운용성 및 향후 호환성 같은 다양한 이점을 제공하므로 오늘날 서비스를 빌드하는 데 선택할 수 있는 가장 적합한 기술은 ASMX입니다.
  • 절대 성능이 주된 관심사인 경우 enterprise service 구성 요소를 사용하여 시스템에서 성능이 가장 중요한 부분을 빌드해야 합니다. COM+는 전체적으로 가장 뛰어난 성능을 나타냈으며 분산 구성 요소를 위한 안전하고 안정적인 보안 호스팅 환경으로 사용할 수 있습니다. 또한 ES/COM+는 Indigo 서비스와 완벽하게 통합되며 ES 구성 요소를 Indigo 서비스로 변환하는 과정도 비교적 간단히 수행할 수 있을 것입니다.
  • .NET Remoting은 TCP상에서 이진 serialization을 사용할 때 가장 성능이 뛰어나지만 Remoting 구성 요소를 IIS에서 호스트하거나 SOAP 메시지를 주고 받으면 성능이 저하됩니다. .NET Remoting 구성 요소는 .NET Remoting 끝점을 제외한 다른 기술과는 상호 운용할 수 없으므로 가능하면 Remoting보다는 ASMX나 ES를 고려하는 것이 좋습니다.

이 결과에서 도출한 중요한 견해 한 가지는 서버에서 수행되는 작업량이 각 호출의 전체 지속 시간에서 큰 비중을 차지하지 않는 경우 전송 시간에서 호출 시간이 상당 부분을 차지하게 된다는 점입니다. COM+ 기술에서의 DCOM 같은 "고속" 전송에서 ASMX 기술에서의 SOAP/HTTP 같은 "비용이 많이 드는" 전송으로 옮겨갈 경우 성능에 큰 영향을 미칠 수 있습니다. 이를 통해 메서드를 호출할 때마다 서버에서 가능한 많은 작업을 수행하고 불필요한 네트워크 이동을 피해야 한다는 점을 더욱 분명히 알 수 있습니다.

각 기술의 상대적인 성능 이점은 호출된 서비스에서 수행하는 작업량이 줄어듦에 따라 감소하게 됩니다. 이러한 견해는 .NET Enterprise Services Performance (영문)를 비롯한 많은 기사에서 확인할 수 있습니다.

또한 이번 테스트를 통해 이들 기술 간의 성능 차이는 DataSet 또는 serialize된 구조를 사용하는 것 같은 응용 프로그램 관련 결정 간의 차이보다 작다는 것이 분명해졌습니다.

이 백서에서는 성능 수치에 큰 중점을 두었지만 개발 프로젝트를 계획할 때 고려해야 할 요소가 성능만 있는 것은 아닙니다. 보안, 확장성, 관리, 개발자 생산성, 상호 운용성, 그리고 Microsoft 분산 기술의 향방 같은 다른 요소도 모두 고려해야 합니다. Developing Distributed Services Today (영문)에는 각 기술의 활용 시기와 상황을 가장 효율적으로 선택하는 방법과 그 발전 가능성을 담은 규범적인 지침이 나와 있습니다.

권장 사항

이번 테스트 결과를 토대로 향후 기술의 방향과 최적의 방법에 따라 다음 권장 사항을 준수하면 성능 문제에 취약하지 않은 시스템을 빌드할 수 있을 것입니다.

가능한 한 ASMX를 사용합니다.

서비스 범위를 극대화하려면 가능한 한 ASMX를 사용하여 서비스를 게시하는 방법을 고려하십시오. ASMX를 사용하면 시스템에서 실행되거나 구현될 수 있는 플랫폼, 장치 또는 언어에 관계없이 모든 시스템에서 HTTP를 통해 SOAP를 전달하여 서비스를 호출할 수 있게 됩니다. 단일 스레드 응용 프로그램에서 호출하는 단일 프로세스 시스템의 경우에도 ASMX는 초당 63회의 많은 작업을 처리할 수 있으며, 이는 동일한 ES 구성 요소보다 초당 호출 수가 10회 적은 수준에 불과합니다. 프로덕션 환경의 경우 단일 시스템에서 ASMX 서비스를 호스트하면 훨씬 뛰어난 성능을 기대할 수 있으므로 ASMX를 채택하도록 유도하는 가장 큰 요인은 무엇보다도 성능이라 할 수 있습니다.

웹 서비스에는 다음을 포함하여 여러 가지 유용한 특징과 기능이 있습니다.

  • 확장성-Windows 네트워크 로드 균형 조정 같은 로드 균형 조정 기술이나 Cisco 및 F5 같은 공급업체의 하드웨어 장치를 사용하면 웹 서비스를 매우 손쉽게 확장할 수 있습니다. 자세한 내용은 다음과 같습니다.
  • 가용성-ASMX 웹 서비스는 로드 균형 조정 같은 기술과 함께 Windows 2003 Server에 내장된 IIS6 인프라의 강력한 기능(예: 실패한 서비스의 자동 재활용 및 다시 시작)을 사용하여 높은 가용성을 갖추도록 구성할 수 있습니다.
  • 상호 운용성-웹 서비스는 개별적인 이종 시스템 간에 자유롭게 통신할 수 있도록 하는 상호 운용 가능한 안전하고 트랜잭션 처리된 통신 프로토콜을 표준화하는 움직임의 핵심입니다.
  • 생산성-대부분의 개발자는 몇 가지 특성을 신중하게 사용하고 일부 권장 사항을 준수하여 ASMX 웹 서비스를 매우 손쉽게 빌드할 수 있습니다.

웹 서비스에 ASMX를 사용합니다.

prescriptive guidance (영문)에는 ASMX가 서비스를 빌드 및 배포하는 데 뛰어난 플랫폼인 다양한 이유가 나와 있으며, 본 백서의 결과를 통해서도 ASMX는 일반적으로 SOAP를 전달할 때 Remoting보다 뛰어난 성능을 발휘한다는 것을 알 수 있습니다.

.NET Remoting도 SoapFormatter 및 HttpChannel을 통해 SOAP/HTTP를 지원할 수 있지만 Remoting을 사용하여 웹 서비스를 노출하는 방법은 가급적 피하는 것이 좋습니다. .NET Remoting SoapFormatter는 더 이상 SOAP의 표준 인코딩 방식이 아닌 RPC-Encoding(대부분의 공급업체 기술 및 플랫폼에서 사용될 미래의 표준 인코딩 메커니즘은 문서-리터럴임)을 사용하므로 Remoting 서비스와의 상호 운용성이 매우 제한적입니다.

확장성을 고려하여 웹 서비스를 선택합니다.

위에서 언급하지 않았지만 이번 설명과 매우 관련성이 높은 내용으로, 웹 서비스는 주로 로드 균형 조정이 간단한 상태 비저장 통신 기술인 HTTP를 통해 통신합니다. 따라서 TCP 또는 HTTP(즉, IIS 내에서 호스트되는)를 사용해도 Remoting 구성 요소의 로드 균형 조정을 수행할 수 있지만 세션 상태가 필요한 경우 복잡해질 뿐 아니라 문제를 일으킬 수 있습니다.

ES/COM+ 구성 요소의 로드 균형 조정을 수행하려면 Application Center 2000의 기능인 CLB(구성 요소 로드 균형 조정)를 사용해야 합니다. 그러나 아쉽게도 AppCenter 2000은 2006년 7월 지원이 확장되며 2011년이면 지원이 종료됩니다. 따라서 더 이상 CLB에 의존하지 않기 위해 2011년 전에 시스템을 다시 설계하는 수고를 피하려면 새로운 개발에 이 기술을 사용하지 않고 가능한 한 빠른 시일 내에 다른 전략으로 마이그레이션해야 합니다. 효과적인 마이그레이션 전략은 현재 COM+ 구성 요소에 ASMX 웹 서비스를 연결하는 것입니다. 이렇게 하면 COM+ 구성 요소로 직접 호출을 전달하고 위에 설명된 것처럼 웹 서비스의 로드 균형 조정을 수행할 수 있습니다.

Enterprise Service는 서비스 내에서만 사용합니다.

.NET Enterprise Service는 COM+에서 제공하는 다양한 서비스에 대한 액세스 및 지원을 제공하는 .NET Framework 계층입니다. 여러 가지 개체/서비스, 미세 조정 보안, JITA 및 개체 풀링을 포괄하는 분산 트랜잭션 같은 다양한 COM+ 서비스가 필요한 경우에는 Enterprise Service를 선택해야 합니다. Enterprise Service와 COM+는 적절한 테스트를 거쳐 높은 수준으로 최적화된 기술로, 매우 빠르고 대기 시간이 낮은 크로스 프로세스 및 크로스 컴퓨터 호출을 제공합니다.

그러나 Enterprise Service 구성 요소를 광범위하게 노출해서는 안 되며 서비스 경계 내에서만 이 기술을 사용해야 합니다. 서비스에 대한 액세스를 제공하려면 ASMX를 사용한 웹 서비스를 통해 서비스의 기능을 노출하는 것이 좋습니다. ES/COM+ 코드 기반을 설정한 경우에는 가급적 COM+ 서비스와 ASMX 웹 서비스를 연결하는 것이 좋습니다.

직접 빌드하기보다는 호스트되는 인프라를 사용합니다.

위에서 설명한 것처럼 ASP.NET은 상호 운용성이 뛰어날 뿐 아니라 IIS(특히, Windows 2003 Server의 IIS6)에서 제공하는 뛰어난 호스팅 기능을 통해 이점이 발휘되는 탁월한 성능의 웹 서비스를 제공합니다.

COM+는 enterprise service 및 COM+ 구성 요소에 신뢰할 수 있고 안전하며 안정적인 호스팅 환경을 지원하고 Microsoft 플랫폼에 가장 빠른 구성 요소 성능을 제공합니다. 또한 분산 트랜잭션 지원, 보안(인증 및 암호화), 안정성, 가용성, 관리 서비스 등을 제공하므로 서비스 내에서 복잡한 구성 요소 기반 시스템을 호스트하는 데 매우 적합합니다.

TCP상에서 이진 serialization을 사용하는 .NET Remoting도 성능이 뛰어나지만 호스팅 환경, 보안, 안정성, 가용성 또는 "즉시 적용할 수 있는" 관리 기능은 제공하지 않습니다. TCP를 사용하는 Remoting 구성 요소를 호스트하려면 호스팅 응용 프로그램을 직접(보안, 확장성, 안정성) 작성해야 하는데, 이는 그리 간단한 작업이 아닙니다. HTTP상에서 Remoting을 사용하려는 경우 IIS를 호스트로 사용할 수도 있지만, 앞서 살펴보았듯이 IIS에서 Remoting 구성 요소를 호스트하여 SOAP/HTTP 통신을 수행하면 Remoting 성능이 크게 떨어지며 대개는 ASMX를 사용하는 것보다 못하게 됩니다!

따라서 Remoting을 사용하기보다는 IIS에서 호스트되는 ASMX나 COM+ 내의 enterprise service 구성 요소에 서비스를 전달하는 것이 바람직합니다.

서비스 간 DataSet을 전달하지 않습니다.

이번 테스트에서 서비스와 호출자의 데이터 교환하는 방법(DataSet 또는 serialize된 구조로)을 선택한 결과 특정 분산 시스템 기술을 서로 비교하여 선택하는 것보다 성능에 훨씬 더 많은 영향을 미쳤습니다.

성능 측면에서 보면 DataSet을 데이터 전송 메커니즘으로 사용하는 것은 응용 프로그램에서 꼭 필요한 부분으로 제한하고 가능한 [Serializable] 구조를 선택하는 것이 좋습니다.

ADO.NET DataSet에서는 데이터를 검색, 조작, 정렬하고 구조를 지정하며 오프라인에서 사용하도록 로컬에 저장하고 변경 내용을 중앙 데이터베이스와 다시 동기화하는 뛰어난 방식을 제공합니다. 이 방식이 응용 프로그램의 요구 사항이라면 당연히 선택해야 합니다. .NET Framework 1.x의 DataSet 값은 항상 XML로 serialize되고(이 값을 이진으로 serialize하거나 ES 또는 Remoting과 함께 사용하길 원하더라도) 데이터를 설명하는 XSD가 포함되어 있는데 이 역시 성능에 영향을 미칩니다.

또한 DataSet은 .NET 관련 기술이고 다른 플랫폼에서 serialize된 DataSet이 포함된 데이터를 반드시 serialize 및 구문 분석할 필요는 없으므로 상호 운용성을 크게 제한합니다.

대신 serialize된 데이터 구조를 교환하면 성능을 크게 개선하는 효과를 얻을 수 있습니다. 이 작업은 위의 테스트에서처럼 기본 제공된 런타임 서식 지정 및 XML serialization 프레임워크를 사용하여 간단히 수행할 수 있습니다.

인증된 연결 공유를 사용합니다.

통합 보안과 함께 IIS(Internet Information Server)를 호스트로 사용하는 모든 테스트는 ASP.NET 클라이언트 및 .NET Remoting에 사용할 수 있는 useAuthenticatedConnectionSharing 및 useUnsafeAuthenticatedConnectionSharing을 옵션으로 실행하도록 구성되어 있습니다. 이 설정을 사용하면 클라이언트와 서버에서 기존 NTLM 인증 연결을 다시 사용할 수 있습니다.

처음에 그림 1(구매 주문서를 개체로 저장)에서 소개한 테스트를 수행하면서 그림 10의 이 옵션을 설정한 경우 나타나는 성능 결과를 확인할 수 있습니다. 이 설정은 IIS를 서버로 사용하고 가상 디렉터리 구성에서 "Windows In Security"를 지정하는 경우에만 적용된다는 점에 유의하십시오.

MSDN Magazine 2004년 9월호의 Web Q&A: Caching Transforms, Connection Sharing, and More (영문) 칼럼에서는 이러한 상호 균형에 대해 설명하고 있습니다.

그림 10. UnsafeAuthenticatedConnectionSharing의 영향

이 설정은 다음과 같이 ASP.NET 웹 서비스 내에서 Windows의 통합 보안과 함께 사용할 수 있습니다.

MyWebService svc = new MyWebService();svc.Credentials = System.Net.CredentialCache.DefaultCredentials;svc.UnsafeAuthenticatedConnectionSharing = true;

또한 .NET Remoting과는 다음과 같이 사용합니다.

IMyRemote rem = (IMyRemote) Activator.GetObject(typeof(IMyRemote), "HTTP://...");IDictionary snkprops = ChannelServices.GetChannelSinkProperties(rem);snkprops["unsafeauthenticatedconnectionsharing"] = true;

부록 A: 종합 기준 - 빈 메서드 테스트

이 테스트는 검토 대상인 기본으로 포함되는 기술들 간의 성능 차이에 대한 기준이 됩니다. 이러한 테스트에서는 매개 변수를 받아들이지 않고 작업을 수행하지 않으며 결과를 반환하지 않는 작업에 대해 호출합니다. 서버측 구현은 다음과 같이 간단합니다.

public void TransferEmpty(){   // 명령을 입력하지 마십시오.}
참고 이러한 종합 기준 수치는 검토 대상인 각 기술의 통신 인프라에 대한 상대 성능 수준을 나타내기 위해서만 제공되며, 실제 시스템에서 예상되는 성능 특성을 반영하지는 않습니다.

그림 11. 프로세스 간 매개 변수 없이 메서드 호출(종합 기준)

그림 11의 테스트 결과는 COM+ 및 DCOM을 비롯하여 고성능 LPC(Lightweight Procedure Call) 시스템상 통신 인프라를 사용하는 ES가 훨씬 최적의 성능을 제공한다는 일반적인 가정에 매우 근접하게 일치했습니다. .NET Remoting 통신 프로토콜은 2위를 차지했습니다. ASP.NET 웹 서비스는 3위를 차지했지만 여전히 IIS에서 호스트되는 .NET Remoting 구성 요소보다는 빠릅니다.

두 번째 단계로 이 테스트를 크로스 컴퓨터 환경에서 실행했습니다. 여기서는 "Url" 속성을 설정하여 생성된 ASP.NET을 리디렉션하고 COM+/enterprise service 구성 요소에 대해 응용 프로그램 프록시를 내보냈으며 이 테스트의 .NET Remoting 부분에 Activator.GetObject()를 사용했습니다. 그 결과 이전 테스트와 매우 유사했지만 네트워크 이동이 포함된 탓에 초당 호출 수가 약간 적었습니다.

그림 12. 컴퓨터 간 매개 변수 없이 빈 메서드 호출(종합 기준)

컴퓨터 간 네트워크 이동은 본래 가장 빠른 전송(ES 및 Remoting Binary/TCP)을 조절하는 한편 보다 느린 전송에는 좀처럼 영향을 미치지 않습니다. 이 결과는 주로 네트워크 트래픽 서비스를 위해 빌드된 기술(즉, IIS 및 ASMX)과 비교해 COM+의 크로스 프로세스 통신 메커니즘이 얼마나 효과적인지 분명하게 보여 줍니다.

또한 앞에서 언급한 것처럼 이 테스트는 매우 인위적이며 전송 성능을 설명하기 위한 것이라는 점에 유의합니다.

부록 B - 세부 테스트 결과

주문을 개체로 저장

테스트평균 초당 호출 수표준 편차
enterprise service730.39
enterprise service(인증)730.34
Remoting TCP/Binary722.71
Remoting HTTP/Binary680.86
Remoting HTTP/Binary(IIS)660.31
ASP.NET 웹 서비스632.71
Remoting HTTP/Binary(IIS) 암호630.39
Remoting TCP/SOAP561.97
Remoting HTTP/SOAP530.57
Remoting HTTP/Binary(IIS) 통합510.28
Remoting HTTP/SOAP(IIS)500.16
ASP.NET 웹 서비스 - 통합500.30
Remoting HTTP/SOAP(IIS) 암호490.29
Remoting HTTP/SOAP(IIS) 통합400.84

주문을 DataSet으로 저장

테스트평균 초당 호출 수표준 편차
enterprise service350.54
enterprise service(인증)350.51
ASP.NET 웹 서비스340.43
Remoting TCP/Binary340.85
Remoting HTTP/Binary320.77
Remoting HTTP/Binary(IIS)321.10
Remoting TCP/SOAP320.77
Remoting HTTP/Binary(IIS) 암호311.47
Remoting HTTP/SOAP300.68
Remoting HTTP/SOAP(IIS)300.48
Remoting HTTP/SOAP(IIS) 암호300.46
ASP.NET 웹 서비스 - 통합290.37
Remoting HTTP/Binary(IIS) 통합280.37
Remoting HTTP/SOAP(IIS) 통합260.31

제품을 개체로 로드

테스트평균 초당 호출 수표준 편차
Remoting TCP/Binary1493.05
enterprise service1472.29
enterprise service(인증)1462.49
Remoting HTTP/Binary1182.13
Remoting HTTP/Binary(IIS)1140.63
Remoting HTTP/Binary(IIS) 암호1061.19
ASP.NET 웹 서비스931.04
Remoting HTTP/Binary(IIS) 통합760.81
ASP.NET 웹 서비스 - 통합670.35
Remoting TCP/SOAP330.34
Remoting HTTP/SOAP300.32
Remoting HTTP/SOAP(IIS)300.25
Remoting HTTP/SOAP(IIS) 암호290.16
Remoting HTTP/SOAP(IIS) 통합260.14

제품을 DataSet으로 로드

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

테스트평균 초당 호출 수표준 편차
ASP.NET 웹 서비스390.12
enterprise service390.26
enterprise service(인증)390.21
Remoting TCP/Binary391.16
Remoting HTTP/Binary(IIS)360.24
Remoting HTTP/Binary351.10
Remoting HTTP/Binary(IIS) 암호350.17
Microsoft patterns & practices Home...  (0) 2005.12.25
[msdn 펌] AOP...  (0) 2005.11.29
[펌]Repearter 사용시 이벤트..  (0) 2005.10.28
[C#] Reqeust.Form 은 짜증나 -.-  (0) 2005.10.12
[펌] ASP.NET 마스터하기: 사용자 지정 엔터티 클래스 소개  (0) 2005.10.12
Posted by tornado
|

출처 : 데브피아 게시판...  (이런것도 문제 되려나?)

 

onItemDataBound 이벤트의 인자인 RepeaterItemEventArgs 에서는

잠시 테스트 해봤는데 값 속성이 읽기전용 이더군요. 쓰기가능

프로퍼티는 못찾아서 다음과 같은 편법으로 변경할 수 있더군요.

 

Label 웹폼으로 감싸는 겁니다. 참고하세요.

 

( 참고 : ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1042/cpref/html/frlrfSystemWebUIWebControlsRepeaterClassOnItemDataBoundTopic.htm )

 

 

[ 디자인 ]

 

<ASP:Repeater

    id="a"

    runat=server

    onItemDataBound="abc"

    >

    

    <ItemTemplate>

        <%# DataBinder.Eval(Container.DataItem, "idx") %>

 

            ------

 

        <ASP:Label

            id="lbl"

            text=<%# DataBinder.Eval(Container.DataItem, "m") %>

            runat="server"

            />

        <br>

    </ItemTemplate>

 

</ASP:Repeater>

 

[ 코드 ]

    void abc(Object o, RepeaterItemEventArgs e)

    {

        if (e.Item.ItemIndex == 3)

            ((Label)e.Item.FindControl("lbl")).Text = "<font color='red'>aaa</font>";       

    }

 

 

 [ 결과 ]

 


 

 

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

똑같지는 않지만 FindControl 로 Object 찾은 후 visible 속성으로 조절해서 했음.

Posted by tornado
|

입력폼이 조금 많은데, 자바처럼 프라퍼티 카피 하는걸 몰라서리..

msdn, 닷넷책, 웹페이지 뒤져보니 System.Reflection 이라는 네임 스페이스가 있었고,

자바에 객체 내부 프라퍼티 복사하듯이 할 수 있었당..

메소드 호출도 잘 되고.. 필드값도 잘 대입됨..

 

사용은 걍 aspx 에서  대입될 객체 생성하고 메서드에 전달해 주면 된다.

 

MyCustomProperty dest = new MyCusotmProperty();

CopyProperty(Reqeust.Form, dest);

와 같이 사용하면 됨..

string , int double, boolean 만 대입 가능함.

 

 

public static void CopyProperty(NameValueCollection src, object dest)
  {
   if(src == null) throw new Exception("src is null..");
   if(dest == null) throw new Exception("dest obj is null..");

   Type t = dest.GetType();
   
   FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

   string key = "";
   for(int i = 0; i < src.Count; i++)
   {
    key = src.GetKey(i);

    for(int j = 0; j < f.Length; j++)
    {
     if(key.Equals(f[j].Name))
     {
      if("System.Int32".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, Int32.Parse(src.Get(key)));
      }
      else if("System.Single".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, Single.Parse(src.Get(key)));
      }
      else if("System.Double".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, Double.Parse(src.Get(key)));
      }
      else if("System.Boolean".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, Boolean.Parse(src.Get(key)));
      }
      else if("System.Decimal".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, Decimal.Parse(src.Get(key)));
      }
      else if("System.String".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, src.Get(key));
      }
      else if("System.Object".Equals(f[j].FieldType.ToString()))
      {
       f[j].SetValue(dest, src.Get(key));
      }       
     }     
    }    
   }
  } // end CopyProperty..

Posted by tornado
|

 

출처 : MSDN

ASP.NET 마스터하기: 사용자 지정 엔터티 클래스 소개

 

Karl Seguin
Microsoft Corporation

요약: 경우에 따라 형식화되지 않은 DataSet가 데이터 조작을 위한 최선의 솔루션이 되지 못하는 상황이 있을 수 있습니다. 이 가이드에서는 사용자 지정 엔터티 및 컬렉션이라는 DataSet의 대안을 살펴도록 하겠습니다.

목차

소개
DataSet의 문제
사용자 지정 엔터티 클래스
개체 관련 매핑
사용자 지정 컬렉션
관계 관리
중급 단계
결론

소개

ADODB.RecordSet과 곧잘 잊혀지던 MoveNext의 시대는 가고 이제는 그 자리를 Microsoft ADO.NET의 강력하고 유연한 기능이 대신하게 되었습니다. Microsoft에서 내놓은 새로운 방법은 탁월한 속도의 DataReader와 풍부한 기능의 DataSet를 갖추고 뛰어난 개체 지향 모델에 패키지화되는 System.Data 네임스페이스입니다. 이러한 도구를 마음대로 사용할 수 있는 상황은 충분히 예견된 일입니다. 모든 3 계층 아키텍처는 강력한 DAL(데이터 액세스 계층)을 사용하여 데이터 계층을 비즈니스 계층에 안정적으로 연결합니다. 양질의 DAL은 코드 재사용률을 높이고 뛰어난 성능을 위한 핵심 역할을 수행할 뿐 아니라 완전히 투명하게 나타납니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

도구가 발전을 거듭함에 따라 일정한 개발 패턴을 갖게 되었습니다. MoveNext와 작별을 고한 것은 성가신 구문으로부터 벗어난 수준을 뛰어넘어 연결이 끊어진 데이터에 눈을 돌리게 한 것은 물론 응용 프로그램을 빌드하는 방법에도 막대한 영향을 미쳤습니다.

DataReader에 익숙해지자(RecordSet과 유사하게 작동) 얼마 지나지 않아 DataAdapter, DataSet, DataTableDataView에도 과감히 달려들어 살펴보게 되었습니다. 이는 개발 방식에 변화를 주는 새 개체를 활용하는 능력이 향상된 것으로 볼 수 있습니다. 연결이 끊어진 데이터를 사용하면 새로운 캐싱 기법을 활용할 수 있어 응용 프로그램의 성능이 크게 향상됩니다. 게다가 이러한 클래스 기능을 통해 보다 세련되고 강력한 함수를 작성할 수 있게 된 동시에 때로는 일반적인 작업에 필요한 코드의 양을 눈에 띌 만큼 줄이게 되었습니다.

DataSet가 특히 적합한 상황은 프로토타입, 소형 시스템 및 지원 유틸리티를 비롯하여 다양합니다. 하지만 출시 시간보다 유지 관리의 편의성이 중요한 엔터프라이즈 시스템에 사용하면 최상의 효과를 발휘하지 못할 수도 있습니다. 이 가이드의 목표는 이러한 작업 유형을 위해 조정된 DataSet를 대신할 사용자 지정 엔터티 및 컬렉션이라는 대안을 살펴보는 것입니다. 다른 대안도 있기는 하지만 기능이 동일하지 않거나 지원 수준이 떨어집니다. 가장 먼저 할 일은 DataSet의 단점을 확인하고 해결할 문제를 이해하는 것입니다.

모든 솔루션은 저마다 장단점이 있으므로 사용자 지정 엔터티의 단점(이후 설명 참조)보다 DataSet의 단점에 더 친숙해질 수 있습니다. 따라서 여러분과 팀 구성원은 해당 프로젝트에 보다 적합한 솔루션을 결정해야만 합니다. 또한 변경할 요구 사항의 특성 및 실제 코드 개발보다 생산 후에 더 많은 시간이 소요될 가능성을 비롯하여 총 솔루션 비용을 반드시 고려해야 합니다. 마지막으로, 여기서 언급하는 DataSet는 형식화되지 않은 DataSet의 일부 단점을 해결한 형식화된 DataSet를 말하는 것이 아님을 유의하십시오.

DataSet의 문제

추상화의 부재

DataSet의 대안을 고려해야 할 첫 번째이자 가장 확실한 이유는 코드와 데이터베이스 구조를 분리할 수 없다는 점에 있습니다. DataAdapter는 기본 데이터베이스 공급업체(Microsoft, Oracle, IBM 등) 종류에 관계 없이 코드를 작성하는 데는 효과적이지만 테이블, 열 및 관계 같은 핵심 데이터베이스 구성 요소를 추상화하지는 못합니다. 이러한 핵심 데이터베이스 구성 요소는 DataSet의 핵심 구성 요소이기도 합니다. DataSet와 데이터베이스는 일반적인 구성 요소 이상의 것을 공유하며 아쉽게도 스키마까지 공유합니다. 다음과 같은 Select 문이 있다고 가정합시다.

SELECT UserId, FirstName, LastName   FROM Users

다들 알겠지만 값은 DataSet 내의 UserId, FirstNameLastName DataColumn에 있습니다.

이것이 어째서 문제가 되는 것일까요? 기본적인 일반 예제를 살펴봅시다. 먼저 다음과 같이 간단한 DAL 함수를 만듭니다.

'Visual Basic .NETPublic Function GetAllUsers() As DataSet Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As SqlCommand = New SqlCommand("GetUsers", connection) command.CommandType = CommandType.StoredProcedure Dim da As SqlDataAdapter = New SqlDataAdapter(command) Try  Dim ds As DataSet = New DataSet  da.Fill(ds)  Return ds Finally  connection.Dispose()  command.Dispose()  da.Dispose() End TryEnd Function//C#public DataSet GetAllUsers() { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUsers", connection); command.CommandType = CommandType.StoredProcedure; SqlDataAdapter da = new SqlDataAdapter(command); try {  DataSet ds = new DataSet();  da.Fill(ds);  return ds; }finally {  connection.Dispose();  command.Dispose();  da.Dispose(); }            }

그런 다음 아래와 같이 모든 사용자의 이름을 표시하는 반복기가 있는 페이지를 만듭니다.

<HTML> <body>   <form id="Form1" method="post" runat="server">     <asp:Repeater ID="users" Runat="server">        <ItemTemplate>           <%# DataBinder.Eval(Container.DataItem, "FirstName") %>           <br />        </ItemTemplate>     </asp:Repeater>   </form> </body></HTML><script runat="server">  public sub page_load     users.DataSource = GetAllUsers()     users.DataBind()  end sub</script>

위에 나온 것처럼 ASPX 페이지는 반복기의 DataSource에 대해 DAL 함수 GetAllUsers를 사용합니다. 어떠한 이유로든(예: 성능 향상을 위한 비정규화, 명확도 향상을 위한 정규화, 요구 사항의 변화) 데이터베이스 스키마가 변경되면 변경 내용은 항상 "FirstName" 열 이름을 사용하는 ASPX 즉, Databinder.Eval 줄로 전달됩니다. 이렇게 되면 즉시 '데이터베이스 스키마의 변경 내용이 항상 ASPX 코드로 전달될까?'와 같은 위험한 의문이 머리 속에 떠오르게 됩니다. N 계층의 장점이 무색해지는 대목입니다.

해야 할 작업이 간단한 열 이름 바꾸기 뿐이라면 이 예제에서의 변경 작업은 간단하게 이루어집니다. 그러나 GetAllUsers를 수많은 위치에 사용하거나 설상가상으로 웹 서비스로 노출하여 수없이 많은 소비자에게 공급한다면 어떻게 될까요? 얼마나 쉽게 또는 안전하게 변경 내용을 전파할 수 있을까요? 이 기본 예제에서는 저장 프로시저가 추상화 계층 역할을 수행하는 것으로 충분하지만 가장 기본적인 보호의 용도 이외에 모든 부분에서 저장 프로시저에 의존하면 향후 더 큰 문제가 발생하게 됩니다. 그러면 이러한 형태를 하드 코딩이라고 가정해 봅시다. 본질적으로 DataSet를 사용하면 데이터베이스 스키마(열 이름을 사용하든 순서를 사용하든 관계없이)와 응용 프로그램/비즈니스 계층 사이에 긴밀한 연결을 만들게 됩니다. 이전의 경험(또는 논리)을 통해 하드 코딩이 유지 관리 및 향후 개발에 미치는 악영향을 알고 있을 것입니다.

DataSet가 적절한 추상화를 제공하지 못하는 또 다른 이유는 개발자가 기본 스키마를 알고 있어야 하기 때문입니다. 여기서 말하는 스키마란 기본 지식을 의미하는 것이 아니라 열 이름, 형식 및 관계에 대한 전체 지식을 의미하는 것입니다. 이러한 요구 사항을 없애면 위에서처럼 코드가 잘못될 위험이 줄어들 뿐 아니라 작성 및 유지 관리도 용이해집니다. 간단히 나타내면 다음과 같습니다.

Convert.ToInt32(ds.Tables[0].Rows[i]["userId"]);

위의 코드는 읽기 어려울 뿐만 아니라 열 이름 및 해당 형식에 대해 자세히 알고 있어야 합니다. 이상적인 경우라면 비즈니스 계층에서는 기본 데이터베이스, 데이터베이스 스키마 또는 SQL에 대해 전혀 알 필요가 없습니다. DataSet를 이전 코드 문자열에 나타난 대로 사용하면(CodeBehind를 사용해도 효과가 없음) 비즈니스 계층이 매우 얇아질 수 있습니다.

약한 형식

DataSet는 오류가 자주 발생하여 개발 노력에 영향을 줄 수 있는 약한 형식입니다. 다시 말해 DataSet에서 값을 검색할 때마다 System.Object 형식으로 반환되므로 이를 변환해야 합니다. 여기서 직면하는 위험은 변환에 실패하는 상황입니다. 안타깝게도 이러한 실패 상황은 컴파일 타임이 아닌 런타임에 발생합니다. 또한 Microsoft VS.NET(Visual Studio.NET) 같은 도구는 개발자가 약한 형식의 개체를 작업하는 데 있어 그다지 많은 도움이 되지 못합니다. 바로 이러한 이유 때문에 앞에서 스키마에 대해 풍부한 지식을 갖추고 있어야 한다고 언급한 것입니다. 다음은 매우 일반적인 예제입니다.

'Visual Basic.NETDim userId As Integer =        Convert.ToInt32(ds.Tables(0).Rows(0)("UserId"))Dim userId As Integer = CInt(ds.Tables(0).Rows(0)("UserId"))Dim userId As Integer = CInt(ds.Tables(0).Rows(0)(0))//C#int userId = Convert.ToInt32(ds.Tables[0].Rows[0]("UserId"));

이 코드는 DataSet에서 값을 검색할 수 있는 방법을 나타내며 아마도 수 많은 위치에 이 코드가 있을 것입니다(변환을 수행하지 않고 현재 Visual Basic .NET을 사용하는 경우 Option Strict를 비활성화했을 것이며 이 경우 문제는 훨씬 심각해집니다).

아쉽게도 위의 각 코드 줄은 다음과 같은 수많은 런타임 오류를 발생시킬 수 있습니다.

  1. 변환에 실패하는 원인은 다음과 같습니다.
    • 값이 null일 수 없습니다.
    • 개발자가 기본 데이터 형식을 잘못 알고 있을 수 있습니다(데이터베이스 스키마에 대해 자세히 알고 있어야 함).
    • 순서 값을 사용하는 경우, X 위치에 어떤 열이 있는지 알 수 없습니다.
  2. ds.Tables(0)가 null 참조를 반환할 수 있습니다(DAL 메서드 또는 저장 프로시저의 일부가 실패한 경우).
  3. "UserId"의 열 이름이 올바르지 않은 원인은 다음과 같습니다.
    • 이름이 변경되었을 수 있습니다.
    • 저장 프로시저에 의해 반환되지 않을 수 있습니다.
    • 오타가 있을 수 있습니다.

null/nothing에 대한 확인과 try/catch를 변환 과정에 추가하는 방식으로 코드를 수정하여 좀 더 방어적으로 작성할 수 있더라도 개발자에게는 도움이 되지 않습니다.

가장 나쁜 상황은 앞에서 언급했듯이 추상화되지 않는다는 점입니다. 이렇게 되면 DataSet에서 userId를 제거할 때마다 앞에서 언급한 위험을 겪게 되거나 동일한 방어 단계를 다시 프로그래밍해야 합니다(이 문제를 완화하는 데는 유틸리티 함수가 도움이 됨). 약한 형식의 개체는 오류를 항상 자동으로 발견하여 손쉽게 수정하는 디자인 타임이나 컴파일 타임에서 위험이 생산 단계에 노출되어 잡아내기가 어려운 런타임으로 옮깁니다.

비개체 지향

DataSet가 개체이고 C# 및 Visual Basic .NET이 OO(개체 지향) 언어라고 해서 이를 사용할 때 개체 지향이 자동으로 이루어지는 것은 아닙니다. OO 프로그래밍의 "Hello World"는 일반적으로 Person 클래스의 하위 클래스인 Employee 클래스 입니다. 그러나 DataSet는 이러한 상속 유형이나 대부분의 다른 OO 기법을 가능한(최소한 자연스럽게/직관적으로) 만들지 않습니다. 클래스 엔터티의 열렬한 지지자인 Scott Hanselman은 이를 다음과 같이 잘 설명하고 있습니다.

"DataSet는 물론 개체입니다. 그러나 도메인 개체도 아니고 'Apple' 또는 'Orange'도 아닌 'DataSet' 형식의 개체입니다. DataSet는 일종의 그릇입니다(백업 데이터 저장소에 대한 정보가 있는). DataSet는 행과 열을 저장하는 방법을 알고 있는 개체이기도 합니다. 또한 이 개체는 데이터베이스에 대해서도 많은 부분을 알고 있습니다. 그러나 저는 그릇은 반환하고 싶지 않으며 'Apples' 같은 도메인 개체를 반환하고 싶습니다."1

DataSet는 데이터를 관계 형식으로 유지하므로 강력한 특성을 나타내고 관계형 데이터베이스와 함께 사용하기 편리합니다. 하지만 아쉽게도 이렇게 되면 OO의 이점을 놓치게 됩니다.

DataSet는 도메인 개체 역할을 할 수 없으므로 기능을 추가할 수 없습니다. 일반적으로 개체에는 클래스 인스턴스에 대해 동작하는 필드, 속성 및 메서드가 있습니다. 예를 들어 Promote 또는 CalcuateOvertimePay 함수가 someUser.Promote() 또는 someUser.CalculateOverTimePay()를 통해 명확하게 호출할 수 있는 User 개체와 연결되어 있을 수 있습니다. DataSet에 메서드를 추가할 수 없으므로 유틸리티 함수를 사용하고 약한 형식의 개체를 처리하며 하드 코딩된 값의 인스턴스를 코드 전체에 추가로 분배해야 합니다. 또한 기본적으로 절차 코드로 마무리하여 DataSet에서 계속 데이터를 제거하거나 이를 로컬 변수에 저장하여 전달합니다. 두 메서드 모두 단점은 있지만 어느 쪽도 이점은 없습니다.

DataSet 사례

데이터 액세스 계층이 DataSet를 반환하기 위한 것이라는 생각을 가지고 있으면 몇 가지 중요한 이점을 놓칠 수 있습니다. 한 가지 이유는 특히 추상화 능력을 제한하는 얇거나 존재하지 않는 비즈니스 계층을 사용할 수 있기 때문입니다. 또한 미리 빌드된 일반적인 솔루션을 사용하기 때문에 OO 기법을 사용하기가 어렵습니다. 마지막으로 Visual Studio.NET 같은 도구는 DataSet 같은 약한 형식의 개체를 사용하는 개발자의 능률을 손쉽게 끌어올리지 못해 생산성을 떨어뜨리고 버그의 발생 가능성을 높이게 됩니다.

이러한 모든 요소가 이런 저런 방식으로 코드의 관리 용이성에 직접적으로 영향을 미칩니다. 추상화를 수행하지 않으면 기능 변경 및 버그 수정의 복잡성과 위험이 높아집니다. 또한 코드 재사용이나 OO에서 제공하는 향상된 가독성을 완전히 활용할 수 없게 됩니다. 게다가 개발자는 비즈니스 논리를 작업하든 프레젠테이션 논리를 작업하든 간에 기본 데이터 구조에 대해 자세히 알고 있어야 합니다.

사용자 지정 엔터티 클래스

DataSet와 관련된 대부분의 문제는 효율적으로 정의된 비즈니스 계층 내에 OO 프로그래밍의 풍부한 기능을 활용하여 해결할 수 있습니다. 일단 기본적으로 필요한 것은 관계에 따라 구성된 데이터(데이터베이스)를 얻어 개체(코드)에서 사용하는 것입니다. 개념적인 측면에서 보면 자동차에 대한 정보를 저장하는 DataTable을 가지는 대신에 실제로 자동차 개체(사용자 지정 엔터티 또는 도메인 개체라고 함)를 가지는 것입니다.

사용자 지정 엔터티를 살펴보기 전에 먼저 당면한 과제를 짚고 넘어가겠습니다. 가장 분명하게 드러나는 부분은 필요한 코드의 양입니다. 데이터를 가져와 DataSet를 자동으로 채우는 대신 데이터를 가져와 먼저 만들어야 하는 사용자 지정 엔터티에 수동으로 매핑합니다. 이렇게 되면 반복 작업을 수행하게 되므로 코드 생성 도구 또는 O/R 매퍼를 사용하여 이를 줄여야 합니다. 이에 대해서는 나중에 자세히 다룰 것입니다. 보다 큰 문제는 데이터를 관계 영역에서 개체 영역으로 매핑하는 실제 프로세스입니다. 단순한 시스템에서는 매핑이 가장 간단한 작업이지만 시스템이 복잡해지면 두 영역 간의 차이가 벌어져 문제가 발생할 수 있습니다. 예를 들어 개체 영역에서 코드 재사용 및 관리 용이성에 도움이 되는 주요 기법에는 상속이 있습니다. 하지만 아쉽게도 상속은 관계형 데이터베이스에서 낯선 개념입니다. 이러한 차이점의 또 다른 예는 개체 영역은 개별 개체에 대한 참조를 관리하고, 관계 영역은 외래 키를 사용한다는 점입니다.

이렇게 하면 마치 이 접근 방식이 코드의 양이 많고 관계형 데이터와 개체 간의 불일치로 인해 복잡한 시스템에는 적합하지 않는 것처럼 들리지만 실제로는 정반대입니다. 복잡한 시스템은 단일 계층에서 격리하는 데(매핑 프로세스) 어려움이 있으므로 이 접근 방식이 도움이 됩니다(자동화 가능). 또한 이 접근 방식은 이미 상당히 널리 사용되고 있으므로 추가되는 복잡성을 명확하게 처리할 수 있는 다양한 디자인 패턴이 나와 있습니다. 앞에서 복잡한 시스템의 단점과 함께 다룬 DataSet의 단점을 좀 더 자세히 살펴보면 결국 시스템을 빌드하는 데 따르는 어려움은 변경 불가능한 특성만 뛰어넘는 수준으로 마무리 될 것입니다.

사용자 지정 엔터티의 정의

사용자 지정 엔터티는 비즈니스 도메인을 나타내는 개체로 비즈니스 계층의 기초가 됩니다. 사용자 인증 구성 요소(이 가이드 전체에서 사용할 예제)가 있다면 아마도 UserRole 개체가 있을 것입니다. 또한 전자 상거래 시스템이라면 SupplierMerchandise 개체가, 부동산 회사에는 Houses, RoomsAddresses가 있을 수 있습니다. 사용자 지정 엔터티는 코드 내에서 단순한 클래스입니다(엔터티와 클래스는 OO 프로그래밍에 사용될 때 상당히 밀접한 상관 관계를 가짐). 일반적인 User 클래스는 다음과 같습니다.

'Visual Basic .NETPublic Class User#Region "Fields and Properties" Private _userId As Integer Private _userName As String Private _password As String Public Property UserId() As Integer  Get   Return _userId  End Get  Set(ByVal Value As Integer)    _userId = Value  End Set End Property Public Property UserName() As String  Get   Return _userName  End Get  Set(ByVal Value As String)   _userName = Value  End Set End Property Public Property Password() As String  Get   Return _password  End Get  Set(ByVal Value As String)   _password = Value  End Set End Property#End Region#Region "Constructors" Public Sub New() End Sub Public Sub New(id As Integer, name As String, password As String)  Me.UserId = id  Me.UserName = name  Me.Password = password End Sub#End RegionEnd Class//C#public class User {#region "Fields and Properties" private int userId; private string userName; private string password; public int UserId {  get { return userId; }  set { userId = value; }  } public string UserName {  get { return userName; }  set { userName = value; } } public string Password {  get { return password; }  set { password = value; } }#endregion#region "Constructors" public User() {} public User(int id, string name, string password) {  this.UserId = id;  this.UserName = name;  this.Password = password; }#endregion}

이점 세부 사항

사용자 지정 엔터티를 통해 얻게 되는 중요한 이점은 컨트롤에서는 완전히 개체라는 단순한 사실에서 비롯됩니다. 즉, 사용자 지정 엔터티를 사용하면 다음을 수행할 수 있습니다.

  • 상속 및 캡슐화 같은 OO 기법을 사용합니다.
  • 사용자 지정 동작을 추가합니다.

예를 들어 User 클래스는 클래스에 UpdatePassword 함수를 추가하여 효과적으로 사용할 수 있습니다(외부/유틸리티 함수를 사용하면 DataSet로도 가능하지만 가독성과 관리 용이성이 희생됨). 또한 강력한 형식이므로 IntelliSense가 지원됩니다.

그림 1. User 클래스의 IntelliSense

마지막으로 사용자 지정 엔터티는 강력한 형식이므로 다음과 같이 오류에 취약한 캐스트가 덜 필요합니다.

Dim userId As Integer = user.UserId'vsDim userId As Integer =          Convert.ToInt32(ds.Tables("users").Rows(0)("UserId"))

개체 관련 매핑

앞에서 언급했듯이 이 접근 방식의 한 가지 큰 난제는 관계형 데이터와 개체 간의 차이를 처리하는 것입니다. 관계형 데이터베이스에는 데이터가 영구적으로 저장되기 때문에 두 영역을 연결하는 것 외에 다른 선택은 없습니다. 앞의 User 예제에서 예상되는 데이터베이스의 사용자 테이블 모양은 다음과 같습니다.

그림 2. User의 데이터 뷰

이 관계형 스키마에서 사용자 지정 엔터티로 매핑하는 작업은 다음과 같이 매우 간단하게 이루어집니다.

'Visual Basic .NETPublic Function GetUser(ByVal userId As Integer) As User Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetUserById", connection) command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId Dim dr As SqlDataReader = Nothing Try  connection.Open()  dr = command.ExecuteReader(CommandBehavior.SingleRow)  If dr.Read Then   Dim user As New User   user.UserId = Convert.ToInt32(dr("UserId"))   user.UserName = Convert.ToString(dr("UserName"))   user.Password = Convert.ToString(dr("Password"))   Return user  End If  Return Nothing Finally  If Not dr is Nothing AndAlso Not dr.IsClosed Then   dr.Close()  End If  connection.Dispose()  command.Dispose()  End TryEnd Function//C#public User GetUser(int userId) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUserById", connection); command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId; SqlDataReader dr = null; try {  connection.Open();  dr = command.ExecuteReader(CommandBehavior.SingleRow);  if (dr.Read()){   User user = new User();   user.UserId = Convert.ToInt32(dr["UserId"]);   user.UserName = Convert.ToString(dr["UserName"]);   user.Password = Convert.ToString(dr["Password"]);   return user;              }  return null; }finally{  if (dr != null && !dr.IsClosed){   dr.Close();  }  connection.Dispose();  command.Dispose(); }}

connection 및 command 개체는 여전히 평소와 마찬가지로 설정하지만 User 클래스의 새로운 인스턴스를 만들고 DataReader에서 이를 채웁니다. 또한 이 함수 내에서 계속해서 DataSet를 사용하여 이를 사용자 지정 엔터티에 매핑할 수 있지만 DataReader에 대한 DataSet의 주된 이점은 연결이 끊어진 데이터 뷰를 제공한다는 것입니다. 이 경우 User 인스턴스는 이처럼 연결이 끊어진 뷰를 제공하여 DataReader의 속도를 활용할 수 있게 해줍니다.

잠깐, 아직 아무 것도 해결되지 않았습니다!

주의 깊은 독자라면 DataSet에 대해 지적한 문제 중 한 가지가 강력한 형식이 아닌 관계로 생산성이 떨어지고 런타임 오류 발생 가능성이 높은 점이라는 것을 알 수 있습니다. 또한 개발자들은 기본 데이터 구조에 대해 세부적인 지식을 갖추고 있어야 합니다. 앞의 코드를 보면 이와 똑같은 함정이 숨어 있음을 알 수 있을 것입니다. 그러나 이러한 문제들은 완전히 격리된 코드 영역에 캡슐화되어 있으므로 클래스 엔터티(웹 인터페이스, 웹 서비스 소비자 및 Windows Form)의 소비자는 이러한 문제를 완전히 알 수 없다는 점을 고려해야 합니다. 이와 반대로 DataSet를 사용하면 코드 전체에 이러한 문제가 확산됩니다.

향상 부분

앞의 코드는 매핑 개념을 설명하기 위한 것으로, 두 가지 주요 부분을 향상시켜 이를 개선할 수 있습니다. 첫째, 채우기 코드를 자체 함수로 끌어내어 재사용이 쉽도록 합니다.

'Visual Basic .NETPublic Function PopulateUser(ByVal dr As IDataRecord) As User Dim user As New User user.UserId = Convert.ToInt32(dr("UserId")) 'NULL 검사 예제 If Not dr("UserName") Is DBNull.Value Then  user.UserName = Convert.ToString(dr("UserName")) End If user.Password = Convert.ToString(dr("Password")) Return userEnd Function//C#public User PopulateUser(IDataRecord dr) { User user = new User(); user.UserId = Convert.ToInt32(dr["UserId"]); //NULL 검사 예제 if (dr["UserName"] != DBNull.Value){  user.UserName = Convert.ToString(dr["UserName"]);    } user.Password = Convert.ToString(dr["Password"]); return user;}(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는
 설명을 위해 번역문으로 제공됩니다.)

두 번째로 확인할 사항은 매핑 함수에 SqlDataReader를 사용하는 대신 IDataRecord를 사용했다는 점입니다. 이것은 모든 DataReader가 구현하는 인터페이스입니다. IDataRecord를 사용하면 매핑 프로세스를 공급업체와 무관하게 실행할 수 있습니다. 즉, 앞의 함수가 OleDbDataReader를 사용하더라도 이를 통해 Access 데이터베이스에서 User를 매핑할 수 있습니다. 이러한 특정 접근 방식과 Provider Model Design Pattern(링크 1 , 링크 2 )을 조합하면 서로 다른 데이터베이스 공급업체에 대해 손쉽게 사용할 수 있는 코드를 얻게 됩니다.

마지막으로 위의 코드는 캡슐화가 얼마나 강력한지를 보여 줍니다. DataSetNULL을 처리하기가 쉽지 않은 이유는 값을 추출할 때마다 NULL인지 확인해야 하기 때문입니다. 우리는 위의 채우기 메서드를 사용해 이를 단일 위치에서 편리하게 관리하여 소비자가 이를 직접 처리해야 하는 수고를 덜어주었습니다.

매핑 위치

이러한 데이터 액세스 및 매핑 함수가 개별 클래스의 일부인지 아니면 해당 사용자 엔터티의 일부인지에 대한 논란이 일부에서 제기되고 있습니다. 모든 사용자 관련 작업(데이터 얻기, 업데이트 및 매핑)을 User 사용자 지정 엔터티의 일부로 사용하면 확실한 이점을 얻을 수 있습니다. 이러한 특성은 데이터베이스 스키마가 사용자 지정 엔터티와 매우 비슷한 경우에 확실한 효과를 나타냅니다(이 예제에서처럼). 시스템의 복잡성이 늘어나면서 두 영역 간의 차이가 드러나기 시작함에 따라 데이터 계층과 비즈니스 계층을 명확하게 구분하면 유지 관리를 단순화하는 데 큰 도움이 될 수 있습니다(이를 데이터 액세스 계층이라고 함). 자체 계층인 DAL 내에 액세스 및 매핑 코드를 두었을 때 얻어지는 부수적인 효과는 다음과 같은 명확한 계층 분리를 위한 훌륭한 규칙을 제공한다는 점입니다.

"System.Data에서 클래스를 반환하거나 DAL에서 자식 네임스페이스를 반환해서는 안 됩니다."

사용자 지정 컬렉션

지금까지는 개별 엔터티를 처리하는 부분만 살펴보았지만 단일 개체를 둘 이상 처리해야 하는 경우도 비일비재할 것입니다. 이를 위한 단순한 솔루션은 Arraylist 같은 일반적인 컬렉션 내에 여러 값을 저장하는 것입니다. 하지만 DataSet에 대해 겪었던 다음과 같은 몇 가지 문제를 다시 유발하므로 이상적인 솔루션이라고 하기에는 부족합니다.

  • 강력한 형식이 아니며
  • 사용자 지정 동작을 추가할 수 없습니다.

여기서의 요구에 가장 적합한 솔루션은 사용자 지정 컬렉션을 만드는 것입니다. 다행히도 Microsoft .NET Framework는 다음과 같이 이를 위해 상속하도록 설계된 클래스인 CollectionBase를 제공합니다. CollectionBase는 전용 Arraylists 내에 모든 개체 형식을 저장하지만 User 개체 같은 특정 형식만 사용하는 메서드를 통해 이들 전용 컬렉션에 대한 액세스를 노출하는 방식으로 작동합니다. 즉, 약한 형식의 코드가 강력한 형식의 API 내에 캡슐화되는 것입니다.

사용자 지정 컬렉션은 코드가 많은 것처럼 보이지만 대부분은 코드 생성 또는 잘라내기 및 붙여넣기를 쉽게 수행할 수 있으며 찾아서 바꾸기는 한 번만 수행하면 되는 경우가 많습니다. 다음과 같이 User 클래스의 사용자 지정 컬렉션을 구성하는 다양한 부분을 살펴보겠습니다.

'Visual Basic .NETPublic Class UserCollection   Inherits CollectionBase Default Public Property Item(ByVal index As Integer) As User  Get   Return CType(List(index), User)  End Get  Set   List(index) = value  End Set End Property Public Function Add(ByVal value As User) As Integer  Return (List.Add(value)) End Function Public Function IndexOf(ByVal value As User) As Integer  Return (List.IndexOf(value)) End Function Public Sub Insert(ByVal index As Integer, ByVal value As User)  List.Insert(index, value) End Sub Public Sub Remove(ByVal value As User)  List.Remove(value) End Sub Public Function Contains(ByVal value As User) As Boolean  Return (List.Contains(value)) End FunctionEnd Class//C#public class UserCollection : CollectionBase { public User this[int index] {  get {return (User)List[index];}  set {List[index] = value;} } public int Add(User value) {  return (List.Add(value)); } public int IndexOf(User value) {  return (List.IndexOf(value)); } public void Insert(int index, User value) {  List.Insert(index, value); } public void Remove(User value) {  List.Remove(value); } public bool Contains(User value) {  return (List.Contains(value)); }}

CollectionBase를 구현하면 더 많은 작업을 수행할 수 있지만 여기서는 사용자 지정 컬렉션에 필요한 핵심 기능만을 나열했습니다. Add 함수를 살펴보면 User 개체만 허용되는 함수에서 List.Add(Arraylist)에 대한 호출을 어떤 방식으로 간단히 래핑하는지 알 수 있습니다.

사용자 지정 컬렉션 매핑

관계형 데이터를 사용자 지정 컬렉션에 매핑하는 프로세스는 사용자 지정 엔터티에 대해 살펴본 프로세스와 매우 유사합니다. 단일 엔터티를 만들어 반환하는 대신 컬렉션에 엔터티를 추가하고 다음 항목으로 반복합니다.

'Visual Basic .NETPublic Function GetAllUsers() As UserCollection Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetAllUsers", connection) Dim dr As SqlDataReader = Nothing Try  connection.Open()  dr = command.ExecuteReader(CommandBehavior.SingleResult)  Dim users As New UserCollection  While dr.Read()   users.Add(PopulateUser(dr))  End While  Return users Finally  If Not dr Is Nothing AndAlso Not dr.IsClosed Then   dr.Close()  End If  connection.Dispose()  command.Dispose() End TryEnd Function//C#public UserCollection GetAllUsers() { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command =new SqlCommand("GetAllUsers", connection); SqlDataReader dr = null; try {  connection.Open();  dr = command.ExecuteReader(CommandBehavior.SingleResult);  UserCollection users = new UserCollection();  while (dr.Read()){   users.Add(PopulateUser(dr));  }  return users; }finally{  if (dr != null && !dr.IsClosed){   dr.Close();  }  connection.Dispose();  command.Dispose(); }}

여기서는 데이터베이스에서 데이터를 가져오고 사용자 지정 컬렉션을 만들며 결과를 순환하여 각 User 개체를 만들고 이를 컬렉션에 추가합니다. 또한 PopulateUser 매핑 함수를 어떻게 재사용하는지 확인해 보십시오.

사용자 지정 동작 추가

사용자 지정 엔터티에 대해 설명할 때 사용자 지정 동작을 클래스에 추가하는 기능에 대해서는 피상적으로만 언급했습니다. 엔터티에 추가할 기능의 유형은 주로 구현하는 비즈니스 논리의 유형에 따라 달라지지만 몇 가지 일반 기능을 사용자 지정 컬렉션에 구현해야 할 수 있습니다. 이에 대한 한 가지 예는 일정한 키를 토대로 단일 엔터티를 반환하는 것인데 예를 들어 userId를 기반으로 사용자를 반환할 수 있습니다.

'Visual Basic .NETPublic Function FindUserById(ByVal userId As Integer) As User For Each user As User In List  If user.UserId = userId Then   Return user  End If Next Return NothingEnd Function//C#public User FindUserById(int userId) { foreach (User user in List) {  if (user.UserId == userId){   return user;  } } return null;}

또 다른 예는 다음과 같이 부분 사용자 이름 등의 특정 기준을 토대로 사용자 하위 집합을 반환하는 것입니다.

'Visual Basic .NETPublic Function FindMatchingUsers(ByVal search As String) 
As UserCollection If search Is Nothing Then  Throw New ArgumentNullException("search cannot be null") End If Dim matchingUsers As New UserCollection For Each user As User In List  Dim userName As String = user.UserName  If Not userName Is Nothing And userName.StartsWith(search) Then   matchingUsers.Add(user)  End If Next Return matchingUsersEnd Function//C#public UserCollection FindMatchingUsers(string search) { if (search == null){  throw new ArgumentNullException("search cannot be null"); } UserCollection matchingUsers = new UserCollection(); foreach (User user in List) {  string userName = user.UserName;  if (userName != null && userName.StartsWith(search)){   matchingUsers.Add(user);  } } return matchingUsers;}

DataSet를 사용하면 DataTable.Select로도 동일한 방법을 수행할 수 있습니다. 자신의 기능을 만들면 코드를 완전히 제어할 수 있으며 Select 메서드는 매우 편리하고 자유로운 코딩 방식으로 이 기능을 제공합니다. 한편 Select는 강력한 형식이 아니므로 이를 사용하려면 개발자가 기본 데이터베이스에 대해 알고 있어야 합니다.

사용자 지정 컬렉션 바인딩

우리가 살펴본 첫 번째 예제는 DataSet를 ASP.NET 컨트롤에 바인딩한 것이었습니다. 이 작업이 상당히 자주 이루어진다는 점을 고려한다면 사용자 지정 컬렉션이 그만큼 쉽게 바인딩된다는 사실에 반가움을 느낄 것입니다(이는 CollectionBase가 바인딩에 사용되는 Ilist를 구현하기 때문임). 다음과 같이 사용자 지정 컬렉션은 이를 노출하는 모든 컨트롤에 대해 DataSource 역할을 수행할 수 있으며 DataBinder.EvalDataSet에서처럼 사용할 수 있습니다.

'Visual Basic .NETDim users as UserCollection = DAL.GetallUsers()repeater.DataSource = usersrepeater.DataBind()//C#UserCollection users = DAL.GetAllUsers();repeater.DataSource = users;repeater.DataBind();<!-- HTML --><asp:Repeater onItemDataBound="r_IDB" ID="repeater" Runat="server"> <ItemTemplate>  <asp:Label ID="userName" Runat="server">   <%# DataBinder.Eval(Container.DataItem, "UserName") %><br />  </asp:Label> </ItemTemplate></asp:Repeater>

열 이름을 DataBinder.Eval의 두 번째 매개 변수로 사용하는 대신 표시할 속성 이름을 지정하며 이 경우에는 UserName입니다.

많은 데이터 바인딩된 컨트롤에 의해 노출되는 OnItemDataBound 또는 OnItemCreated에서 처리를 수행하는 경우 e.Item.DataItemDataRowView로 캐스팅할 수 있습니다. 다음과 같이 사용자 지정 컬렉션에 바인딩하는 경우 e.Item.DataItem은 대신 사용자 지정 엔터티로 캐스팅하며 이 예제에서는 User 클래스입니다.

'Visual Basic .NETProtected Sub r_ItemDataBound (s As Object, 
e As RepeaterItemEventArgs) Dim type As ListItemType = e.Item.ItemType If type = ListItemType.AlternatingItem OrElse    type = ListItemType.Item Then  Dim u As Label = CType(e.Item.FindControl("userName"), Label)  Dim currentUser As User = CType(e.Item.DataItem, User)  If Not PasswordUtility.PasswordIsSecure(currentUser.Password) Then   ul.ForeColor = Drawing.Color.Red  End If End IfEnd Sub//C#protected void r_ItemDataBound(object sender, 
RepeaterItemEventArgs e) { ListItemType type = e.Item.ItemType; if (type == ListItemType.AlternatingItem ||      type == ListItemType.Item){  Label ul = (Label)e.Item.FindControl("userName");  User currentUser = (User)e.Item.DataItem;  if (!PasswordUtility.PasswordIsSecure(currentUser.Password)){   ul.ForeColor = Color.Red;  } }}

관계 관리

아무리 단순한 시스템이라도 엔터티 간에 관계가 존재하기 마련입니다. 관계형 데이터베이스의 관계는 외래 키를 통해 관리되며 개체를 사용하는 경우 관계는 다른 개체에 대한 참조에 해당합니다. 예를 들어 앞의 예제를 기반으로 설명하면 User 개체에 다음과 같은 Role이 만들어질 것으로 예측할 수 있습니다.

'Visual Basic .NETPublic Class User Private _role As Role Public Property Role() As Role  Get   Return _role  End Get  Set(ByVal Value As Role)   _role = Value  End Set End PropertyEnd Class//C#public class User { private Role role; public Role Role {  get {return role;}  set {role = value;} }}

또는 다음과 같은 Role의 컬렉션일 수도 있습니다.

'Visual Basic .NETPublic Class User Private _roles As RoleCollection Public ReadOnly Property Roles() As RoleCollection  Get   If _roles Is Nothing Then    _roles = New RoleCollection   End If   Return _roles  End Get End PropertyEnd Class//C#public class User { private RoleCollection roles; public RoleCollection Roles {  get {   if (roles == null){    roles = new RoleCollection();   }   return roles;  } }}

위의 두 예제에 사용된 Role 클래스 또는 RoleCollection 클래스는 가상의 것으로, 이는 UserUserCollection 클래스와 같이 사용자 지정 엔터티 또는 컬렉션 클래스의 하나 입니다.

관계 매핑

실질적인 문제는 관계를 매핑하는 방법에 있습니다. 간단한 예제를 살펴보고 역할과 함께 userId를 기반으로 사용자를 검색하겠습니다. 먼저, 다음과 같은 관계형 모델을 살펴봅니다.

그림 3. Users 및 Roles 간의 관계

이제 Users 테이블과 Roles 테이블 모두 간단한 방식으로 사용자 지정 엔터티에 매핑할 수 있는지 확인해 보겠습니다. 여기에는 UsersRoles 사이에 다대다 관계를 나타내는 UserRoleJoin 테이블도 있습니다.

그런 다음 아래와 같이 저장 프로시저를 사용하여 두 개의 개별 결과를 가져오는데 다음과 같이 첫 번째는 User용이고 두 번째는 사용자의 Role을 위한 것입니다.

CREATE PROCEDURE GetUserById(  @UserId INT)ASSELECT UserId, UserName, [Password]  FROM Users  WHERE UserId = @UserIDSELECT R.RoleId, R.[Name], R.Code  FROM Roles R INNER JOIN     UserRoleJoin URJ ON R.RoleId = URJ.RoleId  WHERE  URJ.UserId = @UserId

마지막으로 다음과 같이 관계형 모델에서 개체 모델로 매핑합니다.

'Visual Basic .NETPublic Function GetUserById(ByVal userId As Integer) As User Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("GetUserById", connection) command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId Dim dr As SqlDataReader = Nothing Try  connection.Open()  dr = command.ExecuteReader()  Dim user As User = Nothing  If dr.Read() Then   user = PopulateUser(dr)   dr.NextResult()   While dr.Read()    user.Roles.Add(PopulateRole(dr))   End While  End If  Return user Finally  If Not dr Is Nothing AndAlso Not dr.IsClosed Then   dr.Close()  End If  connection.Dispose()  command.Dispose() End TryEnd Function//C#public User GetUserById(int userId) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("GetUserById", connection); command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId; SqlDataReader dr = null; try {  connection.Open();  dr = command.ExecuteReader();  User user = null;  if (dr.Read()){   user = PopulateUser(dr);   dr.NextResult();   while(dr.Read()){    user.Roles.Add(PopulateRole(dr));   }              }  return user; }finally{  if (dr != null && !dr.IsClosed){   dr.Close();  }  connection.Dispose();  command.Dispose(); }}

User 인스턴스가 만들어져 채워지면 다음 결과로 이동하고 선택 및 순환하여 Roles를 채우고 이를 User 클래스의 RolesCollection 속성에 추가합니다.

중급 단계

이 가이드의 목적은 사용자 지정 엔터티 및 컬렉션의 개념과 사용 방법을 소개하는 것입니다. 사용자 지정 엔터티의 사용은 업계에서 널리 사용되는 방식이며 그로 인해 다양한 시나리오를 처리하는 수많은 패턴이 문서화되어 있습니다. 디자인 패턴이 유용한 이유는 다양합니다. 첫째, 특정 상황을 처리하는 데 있어 아마 주어진 문제를 처음 겪지는 않을 것입니다. 디자인 패턴을 사용하면 이미 시도된, 그리고 테스트된 솔루션을 주어진 문제에 다시 사용할 수 있습니다(설계 패턴을 완전히 잘라내어 붙여넣을 수는 없지만 대개 솔루션을 위한 훌륭한 기초가 됩니다). 또한 널리 사용되는 접근 방식이고 체계적으로 문서화되어 있으므로 시스템을 복잡성의 정도에 따라 확장할 수 있다는 안정감을 느끼게 해줍니다. 디자인 패턴은 또한 일반적인 어휘를 제공하여 정보의 전달 및 교육이 매우 용이하게 이뤄질 수 있습니다.

물론 디자인 패턴은 사용자 지정 엔터티에만 적용되는 것이 아니며 실제로 다양한 분야에 사용됩니다. 하지만 사용자 지정 엔터티와 매핑 프로세스에 적용할 수 있는 문서화된 패턴이 얼마나 되는지 확인하면 깜짝 놀라게 될 것입니다.

이 마지막 섹션은 보다 크고 복잡한 시스템을 실행할 수 있는 일부 고급 시나리오를 설명하기 위한 것입니다. 대부분의 항목은 개별 가이드만으로 충분할 수 있지만 여기서는 최소한 몇 가지 시작 리소스를 제공할 예정입니다.

처음에 활용하기 좋은 자료로는 Martin Fowler의 Patterns of Enterprise Application Architecture 가 있는데 일반적인 디자인 패턴을 위한 효과적인 참조(자세한 설명과 많은 샘플 코드가 있는) 역할 밖에 못하지만 처음 100페이지를 잘 읽어보면 전체적인 개념을 이해하는 데 많은 도움이 됩니다. 또한 Fowler의 온라인 catalog of patterns 는 이미 개념에 친숙하지만 간단한 참조가 필요한 사람에게 유용합니다.

동시성

앞에서 소개한 예제들은 모두 데이터베이스에서 데이터를 가져오고 이 데이터에서 개체를 만드는 부분을 다루고 있습니다. 또한 대부분의 경우 데이터의 업데이트, 삭제 및 삽입이 간단히 이루어집니다. 여기서 소개한 비즈니스 계층은 개체를 만들고 이를 데이터 액세스 계층으로 전달하며 관계 영역에 대한 매핑을 처리합니다. 예를 들면 다음과 같습니다.

'Visual Basic .NETPublic sub UpdateUser(ByVal user As User) Dim connection As New SqlConnection(CONNECTION_STRING) Dim command As New SqlCommand("UpdateUser", connection) '역방향 맵핑을 위한 재사용 함수도 만들 수 있음 command.Parameters.Add("@UserId", SqlDbType.Int) command.Parameters(0).Value = user.UserId command.Parameters.Add("@Password", SqlDbType.VarChar, 64) command.Parameters(1).Value = user.Password command.Parameters.Add("@UserName", SqlDbType.VarChar, 128) command.Parameters(2).Value = user.UserName Try  connection.Open()  command.ExecuteNonQuery() Finally  connection.Dispose()  command.Dispose() End TryEnd Sub//C#public void UpdateUser(User user) { SqlConnection connection = new SqlConnection(CONNECTION_STRING); SqlCommand command = new SqlCommand("UpdateUser", connection); //역방향 맵핑을 위한 재사용 함수도 만들 수 있음 command.Parameters.Add("@UserId", SqlDbType.Int); command.Parameters[0].Value = user.UserId; command.Parameters.Add("@Password", SqlDbType.VarChar, 64); command.Parameters[1].Value = user.Password;  command.Parameters.Add("@UserName", SqlDbType.VarChar, 128); command.Parameters[2].Value = user.UserName; try {  connection.Open();  command.ExecuteNonQuery(); }finally{  connection.Dispose();  command.Dispose(); }}

그러나 동시성을 처리하는 경우는 간단하지 않습니다. 즉, 두 명의 사용자가 동시에 동일한 데이터를 업데이트하면 어떤 일이 발생할까요? 기본 동작(아무 것도 하지 않는 경우)은 데이터를 마지막으로 커밋한 사람이 이전의 모든 작업을 덮어쓰는 것입니다. 이러한 동작은 사용자 한 명의 작업을 자동으로 덮어쓰게 되므로 이상적이지는 않을 수 있습니다. 충돌을 완전히 피하는 한 가지 방법은 비관적 동시성을 사용하는 것이지만 이 방식을 사용하려면 확장 가능한 방식으로 구현하기 힘든 특정 유형의 잠금 메커니즘을 사용해야 합니다. 이에 대한 대안은 낙관적 동시성 기법을 사용하는 것입니다. 첫 번째 커밋에 우선 순위를 부여하고 이후의 사용자에게 알리는 것은 일반적으로 보다 순조롭고 사용자 친화적인 접근 방식입니다. 이를 위해 타임스탬프 같은 특정 유형의 행 버전 관리를 사용합니다.

참고 자료

성능

적절한 유연성 및 성능에 대한 염려와는 달리 사소한 성능 차이에 대해 걱정하는 경우가 너무나도 많습니다. 성능은 물론 중요하지만 가장 간단한 솔루션을 제외한 모든 부분에 대해 일반화된 지침을 제공하기란 어렵기 마련입니다. 사용자 지정 컬렉션과 DataSet를 예로 들어봅시다. 어느 쪽이 더 빠를까요? 사용자 지정 컬렉션을 사용하면 DataReader를 많이 사용하여 데이터베이스에서 데이터를 보다 신속하게 가져올 수 있습니다. 하지만 여기서 주의할 점은 이를 어떤 데이터 형식과 함께 어떻게 사용하느냐에 따라 해답이 달라지므로 포괄적인 설명은 아무런 소용이 없다는 것입니다. 보다 중요한 사항은 절감할 수 있는 처리 시간이 어느 정도이든 관계없이 관리 용이성과의 차이에 비해 그다지 많지 않을 것이라는 사실입니다.

물론 관리하기 용이한 고성능 솔루션을 가질 수 없다는 말은 아닙니다. 이를 사용하는 방법에 크게 좌우된다고 다시 말하지만 여기에는 성능을 극대화할 수 있는 몇 가지 패턴이 있습니다. 먼저 사용자 지정 엔터티 및 컬렉션 캐시와 DataSetHttpCache 같은 동일한 메커니즘을 사용할 수 있다는 점을 알아야 합니다. DataSet의 한 가지 이점은 Select 문을 작성하여 필요한 정보만 포함시킬 수 있는 기능에 있습니다. 사용자 지정 엔터티를 사용하면 전체 엔터티와 자식 엔터티까지 모두 채워야 한다는 느낌을 받는 경우가 많습니다. 예를 들어 DataSet를 사용하여 Organization 목록을 표시하려면 OganizationId, NameAddress를 가져와 이를 반복기에 바인딩할 것입니다. 필자의 경우 사용자 지정 엔터티를 사용할 때 다른 모든 Organization 정보를 가져와야 할 것 같은 느낌까지 듭니다. 또한 이러한 정보에는 ISO 인증 여부, 모든 직원 컬렉션, 추가 연락처 정보 등이 포함될 수 있습니다. 다른 사람은 이러한 고민거리를 공유하지 않을 수도 있지만 다행히도 우리는 원하는 경우 사용자 지정 엔터티를 세부적으로 제어할 수 있습니다. 가장 일반적인 접근 방식은 처음 필요할 때만 정보를 가져오는 레이지 로드(lazy-load) 패턴 형식을 사용하는 것입니다(속성에 효과적으로 캡슐화할 수 있음). 개별 속성을 이런 방식으로 제어하면 다른 방식으로는 얻기 힘든 엄청난 유연성을 발휘하게 됩니다(DataColumn 수준에서 유사한 작업을 수행한다고 가정해 보십시오).

참고 자료

정렬 및 필터링

DataView의 기본 정렬 및 필터링 지원은 SQL 및 기본 데이터 구조에 대해 알아야 한다는 단점은 있지만 편리한 기능이며 사용자 지정 컬렉션에는 없는 기능이기도 합니다. 정렬 및 필터링은 계속 수행할 수 있지만 이렇게 하려면 기능을 작성해야 합니다. 고급 기법이라고 할 수는 없지만 전체 데모 코드는 이 섹션의 범위를 벗어납니다. 하지만 필터 클래스로 필터링하거나 비교 클래스로 정렬하는 것 같은 대부분의 기법은 전과 상당히 비슷하며 분명 방법이 있습니다. 다음 리소스를 참조하십시오.

코드 생성

개념적인 문제를 지나쳤다면 사용자 지정 엔터티 및 컬렉션의 중요한 단점은 이러한 모든 유연성, 추상화 및 낮은 유지 관리 비용을 제공하는 추가 코드의 양을 들 수 있습니다. 실제로 지금까지 언급한 줄어든 유지 관리 비용과 버그보다 추가 코드가 더 부담스러울 수도 있습니다. 어떤 솔루션도 완벽하지는 않으므로 이것은 분명히 올바른 지적이지만 디자인 패턴 및 CSLA.NET 같은 프레임워크가 장기적으로 이러한 문제를 점차 완화하고 있습니다. 또한 패턴 및 프레임워크와는 별도로 코드 생성 도구가 실제로 작성하는 데 필요한 코드의 양을 현저히 줄여줄 수 있습니다. 이 가이드는 처음에 무료로 널리 사용되는 CodeSmith 같은 코드 생성 도구를 자세히 설명하려고 했지만 필자의 지식 범위를 넘어서는 너무 많은 리소스가 있어 제외하였습니다.

코드 생성이 마치 꿈 같은 일로 들릴 수도 있습니다. 그러나 적절히 사용하고 이해하면 사용자 지정 엔터티 뿐만 아니라 다른 분야에서도 강력한 무기가 될 수 있습니다. 코드 생성이 사용자 지정 엔터티에만 적용되는 것은 아니지만 대부분 이러한 목적으로만 조정되어 있습니다. 이유는 간단합니다. 사용자 지정 엔터티를 사용하려면 많은 양의 반복 코드가 필요하기 때문입니다.

간단히 말해 코드 생성은 어떤 식으로 작동할까요? 이러한 개념은 진로를 한참 벗어났거나 역효과를 나타내는 것처럼 들리겠지만 기본적으로는 코드(템플릿)를 작성하여 코드를 생성하게 됩니다. 예를 들어 CodeSmith는 다음과 같이 데이터베이스를 활용하

Posted by tornado
|
Posted by tornado
|

http://ko.gotdotnet.com/quickstart/default.aspx

 

한글로 정리 잘 되어있음...

Posted by tornado
|

http://tortoisesvn.tigris.org/download.html 

요기서 다운로드 받아서 해결함 .

관건은 .svn 을 _svn 으로 바꿔줘야 하는데 ㅡㅡ;

절라게 헤맬뻔하다가.. 찾음

 

Special version for Win2k/XP: (We provide NO support for this!) uses _svn folders instead of .svn to work around the VS.NET bug with web projects. If you don't use web projects then please use the official version. Note: working copies created by this version are incompatible with other Subversion clients!Download

 

Posted by tornado
|

원본글 : http://www.microsoft.com/korea/msdn/library/ko-kr/dev/dotnet/entsvcperf.aspx

죄다 깨지냐 ㅡㅡ;

 

 

.NET Enterprise Services 성능

Richard Turner, 프로그램 관리자, XML Enterprise Services
Larry Buerk, 프로그램 관리자, XML Enterprise Services
Dave Driver, 소프트웨어 디자인 엔지니어, XML Enterprise Services

Microsoft Corporation

2004년 3월

적용 대상:
   COM+ 구성 요소
   Microsoft .NET Enterprise Services

요약: 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능을 확인합니다. .NET Enterprise Services 구성 요소를 C++의 COM+ 구성 요소처럼 빨리 실행하기 위한 지침과 함께 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항을 살펴봅니다(45페이지/인쇄 페이지 기준).

관련 EnterpriseServicesPerf.exe 코드 샘플을 다운로드하십시오.

목차

소개
관리되는 코드로 마이그레이션해야 하는 이유
코드 변경 정도 지정
.NET Enterprise Services에 COM+ 연결
.NET Enterprise Services와 COM+ 성능 비교
테스트 결과 및 분석
결론
부록 1: 성능 권장 사항
부록 2: "Indigo" 및 .NET의 동향
부록 3: 분산 트랜잭션이 성능에 미치는 영향
부록 4: 참고 자료
부록 5: 성능 테스트 소스 코드
부록 6: 테스트 결과

소개

COM+ 코드를 "원시" Visual C++ 또는 Visual Basic 6에서 관리되는 .NET Enterprise Services 구성 요소로 이동할 것을 고려 중인 개발자는 다음과 같은 고민 사항이 있을 수 있습니다.

  • 왜 관리되는 코드로 전환해야 하는가?
  • 코드를 얼마나 변경해야 하는가?
  • Enterprise Services 구성 요소는 어떻게 수행되는가?
  • COM+ 및 .NET Enterprise Services가 제시하는 미래는 어떠한가?

이 문서에서는 특히 성능에 대한 질문을 위주로 위 주제를 다룰 것입니다. 이러한 주제에 대한 자세한 내용은 부록 4: 참고 자료에 있는 리소스를 참조하십시오.

이 문서는 COM+ 구성 요소를 개발하고 코드를 .NET Enterprise Services로 마이그레이션할 것을 고려 중인 개발자와 설계자를 대상으로 합니다.

관리되는 코드로 마이그레이션해야 하는 이유

개발자가 .NET에서 코드를 개발해야 하는 이유는 여러 가지입니다. 다음은 코드 개발의 몇 가지 이점입니다.

  • 향상된 개발자 생산성: 개발자들은 종종 .NET을 사용하여 개발할 때 작성해야 하는 "통로 코드"가 훨씬 적기 때문에 응용 프로그램 논리를 작성하는 데 더 많은 신경을 쓸 수 있습니다. 또한 대부분의 개발자는 .NET에서 제공하는 명확하고 일관되게 구성된 풍부한 리소스 라이브러리 덕분에 다른 기술과 비교하여 훨씬 빨리 배울 수 있습니다.
  • 향상된 코드 안정성 및 보안: 개발자는 원시 코드보다는 .NET을 사용할 때 안정적이고 보안된 코드를 더욱 쉽게 작성할 수 있습니다. 그 이유는 코드 액세스 보안 및 CLR(공용 언어 런타임) 같은 기능 때문입니다. 이러한 기능은 NET 코드가 다른 실행 중인 코드에 의도하지 않은 영향을 미치는 것을 방지하고 해커가 .NET 코드를 사용하여 환경을 방해하거나 제어할 수 있는 기회를 줄이는 데 도움이 됩니다.
  • 향상된 성능 및 확장성: 개발자는 .NET으로 마이그레이션할 때 코드의 성능과 확장성이 향상되는 것을 알 수 있습니다. 모든 .NET 언어가 다중 스레딩 같은 기능을 지원하고 활용하기 때문입니다.
  • XCOPY 배포: 대부분의 .NET 응용 프로그램에서는 필요한 파일을 하드 드라이브의 폴더로 복사하고 공유 구성 요소를 운영 체제에 선택적으로 등록하기만 하면 배포가 이루어집니다. 이는 다른 응용 프로그램에서 일반적으로 사용하는 것보다 훨씬 명쾌한 배포 전략입니다.

개발자가 .NET으로 마이그레이션하기를 원하는 10가지 이유에 대해서는 Top 10 Reasons for Developers to Use the .NET Framework 1.1 을 참조하십시오.

.NET은 시스템 관리자에게도 이점을 제공합니다. 관리자가 .NET으로 이동하기를 원하는 10가지 이유가 기록된 목록을 보려면 Top 10 Reasons for Systems Administrators to Use the .NET Framework 1.1 을 참조하십시오.

일반 C++ 코드를 관리되는 C++로 마이그레이션하는 방법에 대한 소개는 Managed Extensions for C++ Migration Guide의 Introduction to Wrapping C++ Classes 를 참조하십시오.

코드 변경 정도 지정

대부분의 경우 COM+ 코드를 .NET Enterprise Services에 연결하려면 COM+ 구성 요소가 어떠한 언어로 개발되었는지를 비롯한 다수의 요인에 따라 몇 가지 수동 작업이 필요합니다. 예를 들어, Visual Basic 6 개발자는 앞으로 나올 Visual Studio 2005의 도구에서 서브루틴 및 함수의 본문을 수정하지 않고도 클래스 정의 및 메서드 서명을 변환할 수 있는 몇 가지 지원을 받게 됩니다(다른 변환된 메서드에 대한 호출은 제외).

C++ 개발자는 COM+ 코드에서 .NET 코드로 변환하는 작업을 대부분 수동으로 수행해야 하지만, 대부분의 통로가 ATL(Active Template Library) 같은 클래스 라이브러리 대신 CLR에 구현되기 때문에 변환된 코드가 좀 더 간결해지는 것을 확인할 수 있습니다. C++ 개발자는 응용 프로그램을 연결할 언어를 선택할 수도 있습니다. 예를 들어, 기존 코드 기반을 최대한 활용하기 위해 관리되는 C++를 선택하거나, 더욱 간결한 코드를 위해 C#을 선택할 수 있습니다.

.NET Enterprise Services에 COM+ 구성 요소 연결

기존 COM+ 코드를 Visual Basic 6 및 Visual C++ 같은 원시 프로그래밍 언어와 도구로부터 마이그레이션하는 개발자는 .NET 코드로 완벽하게 변환하기 위해 몇 가지 기존 코드를 수정해야 합니다. 여기에 동반되는 작업 양은 사용할 수 있는 기존 코드 기반 및 도구에 따라 다릅니다. 다음 표에는 코드를 .NET으로 마이그레이션할 때 고려할 옵션이 요약되어 있습니다.

변환하기 전의 언어변환한 후의 언어코드 변환 도구 사용 가능 여부필요한 코드 변환 작업
Visual Basic 6Visual Basic .NET예(Visual Studio 2005)대부분의 Visual Basic 6 코드는 Visual Basic .NET에 직접 연결됩니다. Visual Studio 2005의 Visual Basic 6 코드 마이그레이션 도구는 대부분의 클래스와 메서드 선언 및 형식을 Visual Basic .NET 구문으로 변환합니다.
Visual C++Visual C++ .NET아니요C++ .NET은 특히 원시 코드와 .NET 사이에서 상호 작동하는 코드를 작성하는 데 유용합니다.
Visual C++Visual C#아니요C# 구문은 많은 면에서 C++와 비슷합니다. 변환을 수행하려면 개발자의 특정 작업이 필요합니다.

.NET 특성

COM+ 구성 요소를 설치한 후에는 구성 요소 서비스 스냅인 도구를 사용하여 수동으로 구성하거나 스크립트 또는 코드를 통해 구성해야 합니다.

예를 들어, 구성 요소에 트랜잭션 지원이 필요하다고 표시하고 각 구성 요소의 AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션을 자동으로 커밋하도록 지정하는 상황을 가정합니다. 그러기 위해서는 구성 요소 및 필요한 메서드의 속성을 COM+ 구성 요소 서비스 관리 콘솔에서 수동으로 구성해야 합니다.

  1. 구성 요소 서비스 관리 콘솔을 열고 올바른 응용 프로그램 및 구성 요소를 탐색합니다.

  2. 이 구성 요소의 속성을 열고 트랜잭션 탭을 클릭한 다음 필수를 클릭합니다.

  3. 확인을 클릭하고 구성 요소 탐색기를 통해 ATLPerfTests 개체의 AddSale() 메서드를 찾습니다.

  4. AddSale() 메서드를 마우스 오른쪽 단추로 클릭하고 메서드의 속성을 연 다음 이 메서드가 반환하면 자동으로 이 개체 비활성화 확인란을 선택합니다.

관리자는 일반적으로 보안 및 ID 설정 같은 배포 지향 설정이나 역할 구성원 및 재활용 같은 런타임 지향 설정을 구성합니다. 개발자는 트랜잭션 지원과 같은 구성 요소의 개발 지향 기능을 구성합니다. 그러나 COM+에서는 개발자가 코드 내에서 구성 요소의 구성 방법을 지정하기가 어렵습니다. Visual Basic 개발자는 구성 요소에 필요한 트랜잭션 지원을 어느 정도 지정할 수 있지만 C++ 개발자는 그렇지 못합니다. COM+ 구성 요소를 안정적이면서 반복적으로 설치하기 위해서는 스크립트, 설치 관리자 응용 프로그램 또는 설치 지침을 작성해야 합니다.

.NET은 개발자가 구성 요소의 코드 내에서 구성 요소에 필요한 서비스와 해당 서비스의 구성 방법을 지정할 수 있도록 함으로써 구성 요소 구성을 단순화합니다. 구성 요소가 설치되면 플랫폼에서 구성 설정을 자동으로 구성하지만, 이러한 구성 설정은 설치 후에 변경할 수 있습니다.

COM+에서는 설치 후에 구성 설정을 변경할 수 있기 때문에 이러한 속성을 다수 변경할 때는 매우 주의를 기울여야 합니다. 예를 들어, 보안 설정을 변경하면 개체를 인스턴스화하고 개체의 메서드를 호출할 수 있는지의 권한 지정에 영향을 미칠 수 있습니다. 반면에 트랜잭션 지원을 제거하면 구성 요소가 안정적이지 못하고 예측 불가능해질 수 있으며 구성 요소의 데이터가 손실되거나 손상될 수도 있습니다.

개발자는 특성(C# 및 C++에서는 [attribute]와 같이 대괄호 안에 표시되고, Visual Basic .NET에서는 <attribute>와 같이 꺾쇠괄호 안에 표시됨)을 사용하여 어셈블리, 응용 프로그램, 구성 요소 또는 메서드의 요소를 구성합니다. 예를 들어, 아래 코드에서는 SimpleTest 구성 요소에 트랜잭션이 필요하며, AddSale() 메서드가 오류 없이 완료될 경우 트랜잭션이 자동으로 완료된다는 것을 보여줍니다.

C#

 [Transaction(TransactionOption.RequiresNew)] public class SimpleTest: ServicedComponent { ... [AutoComplete] public void AddSale(int orderNumber, int storeID, int titleID, int qty) { ... } ... }

Visual Basic .NET

<Transaction(TransactionOption.RequiresNew)> _ Public Class VBTestObject : Inherits ServicedComponent ... <AutoComplete> _ Public Function Sum(ByVal number1 As Integer, ByVal number2 As Integer) As Integer ... End Function ... End Class

다음 표에는 Enterprise Services 구성 요소에 적용할 수 있는 주로 사용되는 특성이 들어 있고, 설치 후에 특성을 안전하게 변경할 수 있는지 여부가 표시되어 있습니다.

특성범위기본 소유자실행 중에 안전하게 수정 가능한지의 여부
ApplicationAccessControl 어셈블리개발자아니요.
개발자가 응용 프로그램의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ApplicationActivation 어셈블리관리자예. 그러나 주의해야 합니다.
라이브러리 또는 서비스로 변경하거나 이로부터 변경하면 성능에 영향을 미칠 수 있으며 대기 중인 구성 요소가 손상될 수 있습니다.
ApplicationID 어셈블리개발자예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationName 어셈블리개발자예. 그러나 주의해야 합니다.
이 특성을 변경하면 하드 코딩된 구성 요소 등록 도구에 영향을 미칠 수 있습니다.
ApplicationQueuing 어셈블리개발자아니요.
AutoComplete 메서드개발자아니요.
아래의 "Transaction"을 참조하십시오.
ComponentAccessControl 클래스개발자아니요.
개발자는 구성 요소의 보안 방법에 대한 하드 종속성 또는 묵시적 종속성을 사용하여 코드를 작성했을 수 있습니다.

액세스 제어를 낮추면 시스템 보안이 손상될 수 있습니다.

ConstructionEnabled 클래스관리자예.
생성자 문자열을 변경할 수는 있지만, 설정/해제 상태를 전환하지는 마십시오.
Description 어셈블리
클래스
메서드
인터페이스
관리자예.
EventTrackingEnabled 클래스관리자예.
InterfaceQueuing 클래스
인터페이스
개발자아니요.
JustInTimeActivation 클래스개발자아니요.
MustRunInClientContext 클래스개발자아니요.
구성 요소가 클라이언트의 컨텍스트와 호환되지 않을 수도 있습니다.
ObjectPooling 클래스개발자예. 풀 설정은 변경할 수 있습니다.
구성 요소가 손상될 수 있기 때문에 개체 풀링 설정/해제 상태를 전환하지는 마십시오.
PrivateComponent 클래스개발자아니요.
이 구성 요소를 공개적으로 호출할 수 없도록 개발자가 특별히 지정한 것입니다. 구성 요소가 알 수 없는 사용 패턴을 지원하는지에 대한 테스트를 받지 않았을 수도 있습니다. 공개되는 경우 심각한 보안 위협을 초래할 수 있습니다.
SecurityRole - 역할 이름  어셈블리
클래스
인터페이스
개발자아니요. 역할을 제거하지 마십시오.
개발자가 지정된 역할에 대한 명시적 종속성 또는 역할 존재에 대한 묵시적 예외를 사용하여 코드를 작성했을 수 있습니다.
SecurityRole - 역할 구성원  어셈블리
클래스
인터페이스
관리자예. 그러나 주의해야 합니다.
액세스 제어를 너무 광범위하게 공개하면 시스템의 보안이 손상되고, 너무 제한되게 공개하면 액세스가 과도하게 제한될 수 있습니다.
Synchronization  클래스개발자아니요.
Transaction 클래스개발자아니요.
구성 요소의 트랜잭션 지원을 수정하면 시스템의 안정성과 무결성이 손상될 수 있습니다.

위 표는 구성 요소 개발자가 기본적으로 소유하는 구성 요소와 관리자가 기본적으로 소유하는 구성 요소도 보여 줍니다. 코드에 미치는 영향을 자세히 알고 있는 경우가 아니라면 개발자의 설정을 변경하지 않는 것이 좋습니다. 개발자가 지정한 보안 관련 특성을 관리자가 변경할 수는 있지만, 이 경우에는 구성 요소 또는 응용 프로그램에 대한 액세스를 충분히 제한하되 너무 과도하게 제한하지는 않도록 주의를 기울여 보안을 구성해야 합니다.

특성을 통해 개발자는 자신의 구성 요소에 대한 구성 요구 사항을 간단하고 효율적으로 지정하면서 설치 후에 관리자가 구성 설정을 변경할 수 있도록 합니다. Enterprise Services가 제공하는 특성에 대한 자세한 내용은 .NET Framework Class Library 를 참조하십시오.

.NET Enterprise Services와 COM+ 성능 비교

Enterprise Services의 성능을 COM+와 비교하여 측정하기 위해 다음 언어로 구성 요소를 만들었습니다.

  • Visual C++ .NET 및 ATL COM+
  • Visual Basic 6 COM+
  • C# 및 .NET Framework 1.1 Enterprise Services
  • Visual Basic .NET 및 .NET Framework 1.1 Enterprise Services

각 구성 요소에는 두 개의 공용 메서드가 포함되어 있습니다.

  • Sum(): 이 간단한 메서드는 두 숫자를 합해 디스크 또는 데이터베이스 액세스 작업을 수행하지 않는 간단한 작업을 시뮬레이트합니다.
  • AddSale(): 이 일반적인 메서드는 트랜잭션되며, 테이블에 레코드를 삽입하고 반환 전에 트랜잭션을 완료하는 개인 메서드인 InsertSale()을 호출합니다. 이 메서드는 일반적인 비즈니스 응용 프로그램 작업을 수행하는 "실제" 메서드의 성능 특징을 보여 줍니다.

그 다음에는 각 구성 요소에 대해 다음 테스트를 수행한 테스트 프로그램을 만들었습니다.

  • 반복적인 만들기/호출/릴리스: 이 테스트는 개체를 만들고 호출하고 릴리스하는 과정을 반복적으로 수행합니다.
  • 만들기/반복 호출/릴리스: 이 테스트는 개체를 인스턴스화하여 수천 번 호출한 다음 마지막에 개체를 릴리스합니다.

테스트 프로그램에서는 각 구성 요소에 대해 두 가지 테스트를 모두 실행했으며 결과를 쉼표로 분리된 파일에 작성하는 고해상도 타이머를 사용하여 각 테스트를 수행하는 데 걸린 시간을 측정했습니다. 이 파일의 결과를 Microsoft Excel로 가져와서 분석했습니다. 각 구성 요소에 대한 코드 목록은 부록 5: 성능 테스트 소스 코드를 참조하십시오.

테스트는 다음 표에 표시된 구성으로 설정된 컴퓨터에서 실행되었습니다.

 컴퓨터 1: 서버 컴퓨터컴퓨터 2: 클라이언트 컴퓨터/단일 컴퓨터
CPUDual Pentium 4 Xeon 3.06GHzDual Pentium 4 Xeon 2.8GHz
RAM1GB1GB
디스크로컬 SCSI로컬 SCSI
네트워크기가비트 이더넷기가비트 이더넷
OS 및 .NETWindows Server™ 2003
.NET Framework 1.1
Windows Server 2003
.NET Framework 1.1

다른 하드웨어에서 테스트 응용 프로그램을 실행할 때 나타나는 특정 결과는 아래에 보고된 결과는 물론, 서로 간에도 다를 수 있습니다. 그러나 각 결과는 여기에 소개된 결과와 비례해야 합니다.

테스트 결과 및 분석

다음 절에서는 이전에 논의한 코드에 대해 실행한 성능 테스트의 결과를 분석합니다. 이 결과는 위에 나열된 하드웨어와 소프트웨어에서 테스트를 실행하여 얻은 것입니다. 모든 결과의 목록은 부록 6: 테스트 결과에 포함되어 있습니다.

결과를 보여 주는 다음 차트에서 차트 막대 길이가 길거나 숫자가 클수록 성능이 뛰어난 것입니다.

개체 활성화 및 삭제 성능

먼저 C++ 및 Visual Basic 6을 사용하여 개발한 원시 COM+ 구성 요소의 성능을 통해 COM+ 인프라가 어떻게 수행되는지를 살펴보겠습니다. 아래 차트는 개체 만들기, 간단한 메서드 호출 및 개체 릴리스를 반복적으로 수행함으로써 얻은 초당 호출 수를 보여 줍니다.

MTS(Microsoft Transaction Server) 1.0의 원래 디자이너는 이와 비슷한 데이터를 보고서 호출을 배달하는 데 필요한 인프라, 즉 프록시, DCOM(또는 프로세스간) 채널, 스텁 및 컨텍스트를 설정함으로써 프로세스 간 및 컴퓨터 간 활성화 시간에 큰 영향을 미칠 수 있다는 것을 깨달았습니다. 이 점이 다음을 수행하는 JIT(Just In Time) 활성화를 디자인하게 된 기본적인 동기였습니다.

  • 서버 구성 요소가 반환 이전에 SetComplete() 또는 SetAbort()를 호출하여 자체 수명 주기를 제어할 수 있도록 합니다.
  • 여러 메서드 호출을 사용하여 DCOM 통로를 설정하는 부담을 줄입니다.

다음 차트는 JIT 활성화를 활용하는 수정된 테스트를 단일 개체를 만들고 두 숫자를 합하는 간단한 메서드를 반복적으로 호출한 다음 마지막에 개체를 릴리스하는 방법으로 실행할 경우 발생하는 상황을 보여 줍니다.

이 결과는 JIT 활성화를 사용할 때 초당 호출 수 면에서 성능이 크게 향상되는 것을 보여 줍니다. JIT 활성화 및 Visual Basic 6을 사용하면 JIT 활성화를 사용하지 않는 C++보다 거의 33배 빠른 결과를 얻을 수 있습니다. JIT가 활성화된 Visual Basic 6에서는 초당 호출 수가 약 8600인 반면 JIT가 활성화되지 않은 Visual C++에서는 초당 호출 수가 약 261입니다.

작업을 수행하는 데 필요한 통로를 설정한 이후 컴퓨터 간 호출을 수행하면 네트워크가 성능에 큰 영향을 미치게 됩니다. 이 경우 Visual Basic 6 및 C++는 성능이 거의 비슷하지만, 그래도 Visual Basic 6가 C++보다 88% 빨리 수행됩니다.

Enterprise Services에서는 필요한 통로를 설정하는 추가 작업이 수행됩니다. 특히 개체를 생성하고 릴리스하기 위한 추가 호출이 필요합니다. 따라서 개체에서 아무런 작업을 수행하지 않고 단순히 개체를 만들었다 제거하는 경우를 비교할 때 이러한 추가 왕복 부담이 성능 비교에 큰 영향을 미치게 됩니다. Visual Studio 2005에서는 Enterprise Services가 향상되어 이러한 활성화 왕복 중 하나가 제거되었기 때문에 "활성화/단일 호출/릴리스" 패턴을 사용할 때 .NET Framework 1.1과 비교하여 성능이 20-30% 향상됩니다. 그러나 가능하면 이러한 패턴은 사용하지 않는 것이 좋습니다.

JIT 활성화를 사용할 때 C++ 및 Visual Basic 6의 성능이 비슷하다는 것을 고려하면 C# 및 Visual Basic .NET을 사용하는 Enterprise Services도 대략 같은 성능을 보일 것으로 예상할 수 있습니다. 아래 그림은 간단한 메서드를 호출하는 위 테스트를 실행한 결과를 보여 줍니다.

이 데이터는 단순히 두 정수를 합하고, 개체의 컨텍스트에 SetComplete()를 호출하고, 결과를 반환하는 간단한 메서드에 대해 수행한 초당 호출 수를 보여 줍니다. 개체를 활성화하고 릴리스하는 부담은 거의 사라졌지만 버퍼를 마샬링하고 호출 스택으로 변환하는 등의 작업으로 인해 호출을 수행하는 부담은 아직 남아 있습니다.

이처럼 매우 간단한 메서드에서도 프로세스 간 호출의 경우 Enterprise Services는 Visual Basic 6과 성능이 거의 비슷합니다. 컴퓨터간에 호출할 경우에는 모든 언어의 성능이 거의 비슷합니다.

그러나 일반적인 비즈니스 응용 프로그램은 메서드에서 이보다 더 복잡한 작업을 수행합니다. 다음 차트는 일반적인 메서드를 호출하여 분산 트랜잭션 내에서 데이터베이스 연결을 열고 간단한 SQL 문을 실행하는 동일한 응용 프로그램을 네 가지 언어로 작성했을 때 각각의 상대적인 성능을 보여 줍니다.

앞의 결과는 메서드 내에서 많은 작업을 수행할 경우 모든 언어의 결과가 실험 오차 범위 내에서 동등하다는 것을 보여 줍니다. ADO를 사용하는 C++ 및 Visual Basic 6을 통해 작성한 COM+ 원시 응용 프로그램은 Enterprise Services를 사용하는 C# 또는 Visual Basic .NET 응용 프로그램과 동일한 속도로 수행됩니다. 프로세스 간 작업을 실행하거나 컴퓨터 간 작업을 실행하는 경우 성능 면에서는 거의 차이가 없습니다.

결과 요약

위 결과는 구성 요소를 최대한 효율적으로 수행하는 데 있어서 JIT 활성화 및 "만들기/반복 호출/릴리스" 패턴이 매우 중요하다는 것을 보여 줍니다.

결론

코드를 .NET으로 마이그레이션하는 것이 유리한 몇 가지 주요 이유를 설명했습니다. 다른 활성화 및 호출 패턴에 적용될 때의 원시 COM+ 및 .NET Enterprise Services 구성 요소 성능에 대해서도 논의했습니다. 또한 지침을 따라가면서 .NET Enterprise Services 구성 요소가 C++ COM+ 구성 요소만큼 빨리 실행된다는 것을 보여 주었습니다. 부록 1: 성능 권장 사항에는 고성능 .NET Enterprise Service 구성 요소를 만드는 데 도움이 되는 주요 권장 사항이 나와 있습니다.

여기에 설명된 기술을 일관되게 적용하면 기존 COM+ 코드를 .NET Enterprise Service 구성 요소로 바로 변환하고 아무런 성능 저하 없이 .NET Framework의 사용성, 보안 및 개발자 생산성 등의 이점을 누릴 수 있습니다.

지금 COM+ 구성 요소를 Enterprise Services 구성 요소로 변환해 놓으면 나중에 코드를 "Indigo"로 더욱 쉽게 마이그레이션할 수 있다는 점도 중요합니다. 부록 2: "Indigo" 및 .NET의 동향에서는 이 주제에 대해 간략히 논의합니다.

부록 1: 성능 권장 사항

다음 절에서는 높은 수준의 성능을 제공하는 빠른 COM+ 및 Enterprise Services 구성 요소를 만드는 방법에 대한 팁과 안내를 제공합니다. 대부분의 제안은 .NET Enterprise Services 구성 요소와 원시 COM+ 구성 요소에 동일하게 적용됩니다.

해당하는 경우 개체 풀링 및 JIT 활성화 사용

위의 테스트 결과가 보여주듯이 메서드 호출이 구성 요소 활성화보다 빠르며 관리되지 않는 구성 요소의 활성화가 Enterprise Services 구성 요소의 활성화보다 빠릅니다. 따라서 구성 요소 기반 응용 프로그램의 속도를 최대한 높이려면 코드에서 구성 요소 활성화 및 삭제의 수를 최소화하는 것이 중요합니다.

COM+에서는 개체 활성화를 최소화할 수 있는 두 가지 서비스를 제공합니다.

  1. 첫 번째는 앞에서 설명했듯이 호출자가 개체에 대한 활성 참조를 보관하고 있는 동안 해당 개체를 원활하게 비활성화할 수 있는 COM+ 서비스인 JIT(Just-in-time) 활성화입니다. 클라이언트가 개체에 메서드를 호출하기만 하면 COM+가 개체의 할당을 동적으로 관리하여 요청을 처리합니다.
  2. 두 번째는 해당 형식의 구성 요소 인스턴스를 요청하는 클라이언트가 바로 사용할 수 있도록 개체를 풀에서 활성 상태로 유지하는 개체 풀링입니다. COM+는 풀을 자동으로 관리하며, 개체 활성화 정보를 처리하고 사용자가 지정한 기준(예: 풀 크기)에 따라 다시 사용합니다.

풀링된 구성 요소 및 JIT 활성화된 구성 요소에 대한 참조를 보관하고 다시 사용함으로써 구성 요소 활성화 및 삭제를 최소화하고 높은 수준의 성능을 얻을 수 있습니다.

COM+ JIT 활성화 및 개체 풀링에 대한 자세한 내용은 Platform SDK: COM+ (Component Services) 설명서 를 참조하십시오.

왕복 회피

COM+ 구성 요소의 성능을 최적화하려면 호출자와 구성 요소 사이에 수행되는 프로세스 간 또는 컴퓨터 간 호출의 수를 최소화하는 것이 중요합니다. COM+ 구성 요소에서 만들어진 모든 메서드는 프로세스 간 호출 전환은 물론 컴퓨터 간 호출 전환도 초래하며, 매번 전환할 때마다 시간이 걸립니다. 따라서 COM+ 개체에서 만들어지는 메서드 호출을 최소한으로 유지하는 것이 필수적입니다. 그러기 위해 단일 호출에서 최대한 많은 작업을 수행하는 메서드를 사용하여 COM+ 구성 요소를 디자인하는 것도 좋은 방법입니다. 단, 이 경우에는 순수 아키텍처에서 변형된 구성 요소도 디자인해야 합니다.

COM+ 서비스 사용 최적화

COM+가 중요한 서비스를 다수 제공하기는 하지만 이러한 서비스를 현명하게 사용하는 것이 중요합니다. COM+가 구성 요소에 필요한 서비스를 제공하는 경우에는 대개 이 서비스가 가장 높은 성능을 제공하므로 반드시 사용하도록 하십시오. 그러나 서비스가 필요하지 않은 경우에는 구성 요소가 불필요한 작업을 수행하여 실행 속도도 느려질 수 있으므로 해당 서비스를 사용할 필요가 없습니다.

COM 마샬링 가능 매개 변수 사용

호출자가 데이터를 전달할 때 사용하는 매개 변수를 Enterprise Services 구성 요소의 메서드가 받아들이는 경우 다음과 같이 COM과 .NET 사이에 쉽게 마샬링할 수 있는 형식을 사용할 것을 매우 강력히 제안합니다.

  • Boolean
  • Byte, SByte
  • Char
  • DateTime
  • Decimal
  • Single, Double
  • Guid
  • Int16, UInt16, Int32, UInt32, Int64, UInt64
  • IntPtr, UIntPtr
  • String

이러한 형식만 사용하고 다른 복잡한 형식(예: 구조 또는 배열)은 전달하지 않으면 .NET serializer가 호출 처리 스택을 최적화하고 호출을 유선(RPC의 경우) 또는 가상 유선(LRPC의 경우)에 일렬로 직렬화할 수 있습니다. 그러면 호출이 더욱 빨리 실행됩니다. 그러나 메서드에 복잡한 형식이 필요한 경우에는 코드가 일반 DCOM 호출 스택을 통해 호출되므로 추가 처리 과정이 발생합니다.

파이널라이저 사용하지 않기

Enterprise Services 구성 요소에서 파이널라이저(C# 및 C++의 ~Classname() 소멸자)를 구현하지 마십시오. 파이널라이제이션은 가비지 수집기의 단일 스레드 작업입니다. Enterprise 구성 요소를 파이널라이제이션할 경우 완료되는 데 상당한 시간이 걸리므로 가비지 수집기 성능이 저하됩니다. 가비지 수집기 엔진이 실행 중인 동안에는 응용 프로그램에서 다른 작업을 수행할 수 없으므로 가비지 수집기가 완료되는 데 오랜 시간이 걸리면 전체 응용 프로그램의 성능이 저하됩니다.

대신 개체에서 Dispose(bool)을 재정의하고 Dispose(true)가 호출될 때 파이널라이제이션 형식 작업을 수행하는 것을 고려해 보십시오. 또한 이러한 종료 코드를 최대한 정돈되고, 안전하고, 간단하게 유지하도록 하십시오.

단일 스레드 COM+ 구성 요소 만들지 않기

여러 스레드의 동시 액세스를 지원하지 않는 개체는 STA(단일 스레드 아파트) 기능을 지원하는 것으로 표시되어 있습니다. 여러 스레드가 같은 인스턴스에 동시 액세스할 수 있도록 지원하는 구성 요소는 MTA(다중 스레드 아파트) 인식으로 표시되어 있습니다.

.NET Enterprise Services 구성 요소는 항상 STA 및 MTA를 모두 지원하는 것으로 표시되어 있기 때문에 더 이상 이 논의에 포함시키지 않겠습니다.

모든 Visual Basic 6 COM+ 구성 요소는 STA입니다. C++ COM+ 개발자는 구성 요소를 STA, MTA 또는 둘 다로 표시할 수 있습니다.

STA COM+ 구성 요소의 잠재적인 문제는 개체가 단일 스레드에서만 실행될 수 있으므로 해당 스레드가 개체의 메서드를 실행하는 유일한 스레드라는 것입니다. 이 직렬화를 통해 개발자는 STA 구성 요소를 더욱 쉽게 작성할 수 있지만, 도메인 간 마샬링이 종종 필요해서 성능이 저하되고 STA에서 한 스레드만 계속 실행됨으로 인해 확장성이 떨어지는 것은 피할 수 없습니다.

가능하면 STA 구성 요소를 만들거나 사용하지 않을 것을 제안합니다. 특히 확장성이 중요한 곳에서는 더욱 그렇습니다. 구성 요소가 다른 COM+ 구성 요소를 호출하는 경우 특히 STA 스레딩을 피해야 합니다. 이러한 호출에는 종종 스레드 전환이 필요한데, 스레드 전환은 해당 아파트에 있는 다른 모든 COM+ 구성 요소를 차단합니다.

설상가상으로 가비지 수집기의 파이널라이저도 개체를 소유한 STA 스레드를 호출할 때 차단됩니다. 그러면 파이널라이제이션 프로세스가 단일 스레드에 직렬화되며, 이는 이전 주제에서 설명했듯이 시스템 성능을 크게 저하시킬 수 있습니다.

부록 2: "Indigo" 및 .NET의 동향

Microsoft에서 현재 개발 중인 연결된 응용 프로그램을 위한 새로운 플랫폼, 코드 이름 "Indigo"에 대해 들어 보셨을 것입니다. 그렇다면 "Indigo"란 과연 어떤 제품일까요?

"Indigo"는 서비스 지향 연결 응용 프로그램을 위한 Microsoft의 전략적 기술 플랫폼으로서, 로스앤젤레스에서 개최된 2003 PDC(Professional Developers Conference)에서 소개되었습니다.

"Indigo"는 다음 기술의 개념, 특징 및 기능을 하나의 기술 스택으로 통합합니다.

  • COM
  • DCOM
  • COM+/Enterprise Services
  • ASMX/Web Services
  • .NET Remoting
  • 향상된 웹 서비스
  • MSMQ의 요소

"Indigo"는 서비스를 호출자에게 노출하는 데 필요한 프로토콜 및 전송으로부터 서비스의 개념을 추상화하는 다계층 플랫폼입니다. 최대한 많은 시스템과 상호 작동하기 위해 "Indigo"는 HTTP, TCP 및 IPC를 통한 고급 웹 서비스(WS-*)를 완벽하게 지원합니다. 현재 Microsoft는 코드 이름이 "Longhorn"인 Microsoft Windows의 출시 시기에 맞춰 "Indigo"를 배포할 예정입니다. Windows XP 및 Windows Server 2003에 대한 "Indigo" 지원도 함께 배포될 것입니다.

향후 "Indigo"가 발표된다고 하니 .NET Enterprise Services(또는 이 경우 ASMX 및 Remoting) 같은 기존 기술이 오늘날의 연결된 응용 프로그램을 개발하는 데 아직 유효한 것인지 걱정할 수도 있습니다. ASMX & WSE, Enterprise Services, Remoting 및 MSMQ는 오늘날의 엔터프라이즈 수준 솔루션에 가장 적합한 기술입니다. 적절하게 사용하면 "Indigo"가 릴리스되고 널리 사용될 때까지 훌륭한 응용 프로그램 개발 플랫폼을 제공해 줄 것입니다.

.NET을 사용하여 새 응용 프로그램을 작성하고 기존 응용 프로그램을 .NET으로 마이그레이션하면 향상된 보안, 안정성, 관리 및 확장성의 혜택을 누릴 수 있습니다. 또한 "Indigo"로 업그레이드하는 것이 원시 코드보다 훨씬 쉬워질 것입니다. 응용 프로그램의 "Indigo" 업그레이드를 준비하는 방법과 기존 기술을 사용하여 "Indigo"와 상호 작동하는 방법에 대한 자세한 내용은 나중에 MSDN을 참조하십시오.

"Indigo"에 대한 자세한 소개는 MSDN Magazine 기사, Code Name Indigo: A Guide to Developing and Running Connected Systems with Indigo 를 참조하십시오. 이 기사에 제공된 아키텍처 개요를 통해 이후의 Microsoft 응용 프로그램 플랫폼을 미리 살펴볼 수 있습니다.

"Indigo"에 대한 일반적인 내용은 Microsoft "Indigo" Frequently Asked Questions 를 참조하십시오.

부록 3: 분산 트랜잭션이 성능에 미치는 영향

위 테스트를 진행하면서 "COM+ 분산 트랜잭션이 이러한 구성 요소에 얼마나 많은 영향을 미칠까?"라는 질문이 생길 수도 있습니다. 이 질문의 답을 찾기 위해 각 구성 요소에 대해 COM+에서 "트랜잭션 필요" 설정을 끄고 테스트를 다시 실행해 보았습니다. 그 결과는 다음 차트에 표시되어 있습니다.

위 차트에서 볼 수 있듯이 COM+ 트랜잭션 지원이 없는 구성 요소의 성능과 트랜잭션이 켜져 있는 구성 요소의 성능은 거의 동일합니다. 이 결과는 테스트에서 COM+ 트랜잭션의 영향을 무시해도 상관없다는 것을 분명하게 보여 줍니다.

부록 4: 참고 자료

Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET 

Visual Basic 6.0 응용 프로그램을 Visual Basic .NET으로 업그레이드하는 내용을 다루며 프로그래밍 팁, 요령 및 단계별 코드 비교 내용을 함께 제공합니다.

Programming with Managed Extensions for Microsoft Visual C++ .NET 

Visual C++ .NET 2003에 맞춰 업데이트된 이 책은 개발자를 위해 컴파일러의 새 기능과 언어에 대한 링커 확장을 심층적이고 전문적으로 다룹니다.

.NET Enterprise Services and COM+ 1.5 Architecture 

Microsoft .NET 및 Enterprise Services가 어떻게 조화를 이루는지 보여 주고 COM+/Enterprise Services 구성 요소를 빌드, 제어, 관리 및 보안하는 방법을 설명합니다.

.NET Framework Developer Center의 Performance 페이지 

고성능 코드를 작성하는 방법과 발생하는 문제를 진단하는 방법에 대해 심도있게 다루는 여러 링크와 리소스가 제공됩니다.

Performance Tips and Tricks in .NET Applications 

.NET 응용 프로그램이 제대로 수행되도록 하기 위한 팁과 힌트를 모아 놓았습니다.

Writing Faster Managed Code: Know What Things Cost 

.NET 코드에서 다양한 작업이 시스템에 어떤 부담을 주는지 자세히 분석한 내용입니다.

Garbage Collector Basics and Performance Hints 

가비지 수집기의 작동 방법, 가비지 수집기가 코드에 미치는 영향, 가비지 수집의 영향을 최소화하도록 코드를 작성하는 방법 등에 대해 설명합니다.

Performance Considerations for Run-Time Technologies in the .NET Framework 

가비지 수집 및 메모리 사용, JIT, 스레딩, .NET Remoting, 보안 등의 주제를 다룹니다.

부록 5: 성능 테스트 소스 코드

C++\ATL 구성 요소

헤더 파일

// ATLPerfTestObj.h : CATLPerfTestObj의 선언 #pragma once #include "ATLPerfTests.h" #include "resource.h" // 주 기호입니다. #include <comsvcs.h> #include <mtxattr.h> // CATLPerfTestObj class ATL_NO_VTABLE CATLPerfTestObj : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CATLPerfTestObj, &CLSID_ATLPerfTestObj>, public IDispatchImpl<IPerfTestObj, &IID_IPerfTestObj, &LIBID_ATLPerfTestsLib, /*wMajor =*/ 1, /*wMinor =*/ 0> { public : CATLPerfTestObj() { } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } DECLARE_REGISTRY_RESOURCEID(IDR_ATLPERFTESTOBJ) DECLARE_NOT_AGGREGATABLE(CATLPerfTestObj) BEGIN_COM_MAP(CATLPerfTestObj) COM_INTERFACE_ENTRY(IPerfTestObj) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // IPerfTestObj public : STDMETHOD(Sum)(LONG number1, LONG number2, LONG* result); STDMETHOD(AddSale)(LONG orderNumber, LONG storeID, LONG titleID, LONG qty); private : HRESULT InsertSaleRecord(LONG orderNumber, LONG storeID, LONG titleID, LONG qty); }; OBJECT_ENTRY_AUTO(__uuidof(ATLPerfTestObj), CATLPerfTestObj)(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

소스 파일

// ATLPerfTestObj.cpp : CATLPerfTestObj의 구현 #include "stdafx.h" #include "ATLPerfTestObj.h" #include ".\atlperftestobj.h" #include "atlstr.h" #import "c:\Program Files\Common Files\System\ADO\msado15.dll" rename_namespace("ADO") rename("EOF", "EndOfFile") using namespace ADO; // CATLPerfTestObj // 여기에서 간단한 작업을 수행하여 간단한 메서드를 시뮬레이트합니다. STDMETHODIMP CATLPerfTestObj::Sum(LONG number1, LONG number2, LONG* result) { // 개체 컨텍스트를 가져옵니다. IObjectContext* ctx = NULL; HRESULT hr = CoGetObjectContext(IID_IObjectContext, (LPVOID*)&ctx); if(SUCCEEDED(hr)) { // 계산을 수행합니다. *result = number1 + number2; // 트랜잭션을 커밋합니다. ctx->SetComplete(); ctx->Release(); } return hr; } STDMETHODIMP CATLPerfTestObj::AddSale(LONG orderNumber, LONG storeID, LONG titleID, LONG qty) { // COM+ 개체 컨텍스트를 가져옵니다. IObjectContext* ctx = NULL; HRESULT hr = CoGetObjectContext(IID_IObjectContext, (LPVOID*)&ctx); // COM+ 컨텍스트가 있는지 확인합니다. if(SUCCEEDED(hr)) { // 기본 작업은 중단하는 것입니다. ctx->SetAbort(); // 데이터베이스에 레코드를 삽입합니다. hr = InsertSaleRecord(orderNumber, storeID, titleID, qty); // 삽입 작업의 결과를 확인합니다. if(SUCCEEDED(hr)) { // 모든 것이 올바르면 트랜잭션을 완료로 // 표시합니다. ctx->SetComplete(); } // 컨텍스트 포인터를 정리합니다. ctx->Release(); ctx = NULL; } // 전체 결과를 반환합니다. 삽입 작업이 오류 없이 // 실행되는 경우에만 결과가 S_OK입니다. return hr; } // 데이터베이스에 판매 레코드를 삽입합니다. HRESULT CATLPerfTestObj::InsertSaleRecord(LONG orderNumber, LONG storeID, LONG titleID, LONG qty) { // 기본 결과는 실패를 반환하는 것입니다. HRESULT hr = E_FAIL; try { // 서버에서 실행할 SQL을 포맷합니다. CString str; str.Format("insert into sales (order_no, store_id, \ title_id, order_date, qty) values (%i, %i, %i, \ GetDate(), %i)", orderNumber, storeID, titleID, qty); // 데이터베이스에 대한 연결을 엽니다. _ConnectionPtr cn("ADODB.Connection"); cn->Open("Provider=SQLOLEDB;SERVER=localhost;Integrated \ Security=SSPI;DATABASE=ESPERFTESTDB", "", "", adConnectUnspecified); // 명령을 실행합니다. _variant_t rs; rs = cn->Execute(_bstr_t(str), &rs, adCmdText); hr = S_OK; // 연결을 명시적으로 닫습니다. cn->Close(); } catch(_com_error e) { // 문제가 있으면 오류가 반환됩니다. hr = e.Error(); } return hr; }

Visual Basic 6 구성 요소

Private Function IPerfTestObj_Sum(ByVal nA As Long, ByVal nB As Long) As Long IPerfTestObj_Sum = nA + nB GetObjectContext.SetComplete End Function Sub IPerfTestObj_AddSale(ByVal OrderNumber As Long, ByVal StoreID As Long, ByVal TitleID As Long, ByVal Qty As Long) ' 기본값은 오류가 throw될 경우 중단하는 것입니다. GetObjectContext.SetAbort Call InsertSaleRecord(OrderNumber, StoreID, TitleID, Qty) ' 실패하지 않았기 때문에 트랜잭션을 완료할 수 있습니다. GetObjectContext.SetComplete End Sub Sub InsertSaleRecord(ByVal OrderNumber As Long, ByVal StoreID As Long, ByVal TitleID As Long, ByVal Qty As Long) Dim command As String connDB.Open ("Provider=SQLOLEDB;SERVER=localhost;Integrated" + _ "Security=SSPI;DATABASE=ESPERFTESTDB") command = "insert into sales (store_id, order_no, order_date, qty, title_id) values (" & StoreID & ", " & OrderNumber & ", GetDate(), " & Qty & ", " & TitleID & ")" connDB.Execute (command) connDB.Close End Sub 

C# 구성 요소

using System; using System.EnterpriseServices; using System.Data.SqlClient; using System.Reflection; using System.Runtime.InteropServices; using perftestsinterop; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyKeyFile("..\\..\\sign.key")] [assembly: ApplicationName("PerfTest")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] namespace CSPerfTests { [Guid("0BA5534E-8544-42e2-A909-3265105BEA09")] [Transaction(TransactionOption.Required)] public class CSPerfTestObj : ServicedComponent, IPerfTestObj { public CSPerfTestObj() { } // 여기에서 간단한 작업을 수행하여 간단한 메서드를 // 시뮬레이트합니다. public int Sum(int number1, int number2) { int result = 0; // 계산을 수행합니다. result = number1 + number2; // 트랜잭션을 커밋합니다. ContextUtil.SetComplete(); return result; } // 트랜잭션에 새 판매 항목을 추가합니다. public void AddSale(int orderNumber, int storeID, int titleID, int qty) { try { // 데이터베이스에 레코드를 삽입합니다. InsertSaleRecord(orderNumber, storeID, titleID, qty); // 모든 것이 올바르면 트랜잭션을 완료로 // 표시합니다. ContextUtil.SetComplete(); } catch(Exception) { ContextUtil.SetAbort(); throw; } } // 데이터베이스에 판매 레코
            




                    
Posted by tornado
|

출처 : http://network.hanbitbook.co.kr/view.php?bi_id=1090

 

저자: 한동훈 / traxacun@gmail.com

[지난기사보기]
ASP.NET 가이드 4. 통합 예외처리
ASP.NET 가이드 3. UI 향상 및 사용자 템플릿 만들기
ASP.NET 가이드 2. 숫자/문자 입력 텍스트 박스 만들기
ASP.NET 가이드 1. 자바 스크립트 사용하기


프로젝트 진행시에 겪게되는 어려움 중에 하나는 코드 문서화에 대한 것이다. 거의 모든 회사들은 일정이 빠듯하다고 여기며 코딩이 끝난 구현물을 토대로 문서화를 만들게 된다.

[코딩후 문서화]는 여러가지 문제점을 갖고 있다. 개발자가 구현시에 고려했던 내용들을 문서화하는 시점에서는 잊혀지기 때문에 문서화에서 누락되는 경우가 발생한다. 나중에 문제가 발생한 경우 문서만으로 문제를 해결하지 못하고, 원 코드 제작자에게 문의하여 해결하게 되는 것도 이런 탓이다.

코딩후 문서화가 갖는 다른 문제는 개발 문서, 사용자 설명서, 사용자 시나리오 문서를 구분하지 못한채 문서화되는 경우가 많다는 점이다. 대체로 개발 문서에서 코드만 제거하고 사용자 설명서가 되는 경우도 많다.

여기서는 코딩을 하면서 바로바로 문서화할 수 있는 것을 도와주는 NDoc에 대해 살펴볼 것이다. Visual Studio .NET에는 [도구] → [주석 웹페이지 빌드]를 통해 XML 문서화된 내용을 도움말로 빌드해주는 기능을 갖고 있지만 부족한 부분이 많다. 웹 페이지 뿐만 아니라 윈도우 도움말(.CHM, .HLP)까지 함께 빌드해 줄 수 있는 NDoc 사용법을 살펴볼 것이다.

오픈소스 문서화 도구중에 DoxyGen은 C#의 XML 주석을 이용하지 않고 자체 태그를 사용하고 있기 때문에 .NET 코드 문서화에는 사용하지 않기 때문에 다루지 않는다. 관심있는 분은 DoxyGen 사이트를 참고하기 바란다.

XML 주석 작성하기

.NET에서는 XML 문서화를 지원하고 있으며 사용할 수 있는 태그목록은 다음과 같다. 보다 자세한 사항은
Recommeded Tags for Documentation Comments를 참고하기 바란다.




[표1] XML 문서화 태그 목록


XML 태그 이름에서 알 수 있는 것처럼 대충의 사용 용도는 알 수 있으며 Visual Studio.NET에서는 해당 태그를 입력하면 자동으로 태그의 형식을 입력해준다. Visual Studio .NET이 완성해주는 XML 문서화 태그 입력이 부족하다고 여겨진다면 GhostDoc을 사용하자. 영어로 문서화를 해야하는 경우엔 유용하게 사용할 수 있다. 다음은 BasePage에 필자가 BR 태그를 개행문자로 변환하기 위해 만든 메서드와 그에 대한 주석이다.




[그림1] XML 문서화 사용 예


SUMMARY 태그는 해당 클래스나 메서드의 내용을 요약해서 보여주기 위해 사용한다. REMARKS 태그는 그에 대한 간단한 설명을 덧붙이기 위해 사용하며 EXAMPLE 태그는 예제설명이 시작이라는 것을 나타내며, 예제 코드에 대한 제목 역할을 한다. CODE 태그는 다른 사람이 참고할 수 있는 예제코드를 작성하는 부분이다. PARAM 태그는 각 메서드의 매개변수에 대한 설명이며, RETURNS는 반환값에 대한 주석을 작성할 수 있다. 이렇게 작성된 코드는 컴파일시에 XML 부분만 별도로 작성되는데 이를 위해서는 프로젝트 설정을 해야한다. 명령줄에서 csc.exe를 사용하여 직접 컴파일시에는 /doc 옵션으로 지정할 수 있다.

XML 문서화 옵션 지정

Visual Studio.NET에서 [프로젝트] - [BasePage 속성(P)]를 클릭한다.




[그림2] XML 문서화 설정하기


[XML 문서 파일]에 그림2와 같이 입력한다. 이제 [빌드] - [솔루션 다시 빌드]를 선택하면 전체 프로젝트를 다시 빌드하면서 XML 문서를 생성한다. 개발환경에서 빌드시에는 소스 코드 전체를 빌드하는 대신 수정한 부분만 다시 빌드한다. 이러한 부분 빌드시에는 XML 문서 파일이 갱신되지 않기 때문에 위와 같이 다시 빌드를 해야한다.
이렇게 생성한 XML 문서 파일과 NDoc을 이용해서 문서파일을 자동으로 생성해보자.

NDoc 문서화

NDoc을 받아서 설치했다고 가정하고 NDoc 사용법을 바로 살펴보자.




[그림3] NDoc 시작화면


[Add] 버튼을 클릭한다.




[그림4] 어셈블리와 XML 문서 추가


BasePage.dll과 앞에서 생성한 XML 문서, Mona.Web.UI.BasePage.xml을 추가하고 [OK]를 클릭한다.

다시 NDoc 메인화면으로 돌아오면 화면 중간에 다양한 속성을 설정할 수 있는 부분이 있다. 이 설정을 제대로 해야만 한글이 깨지지 않고 문서화가 된다. NDoc에서 설정할 필요가 있는 속성들만 다음에 나열하였다.




[표2] NDoc 속성 설정


언어 설정을 위해서는 SdkDocLanguage와 LangID를 변경하면 되며, DocumentInherited로 되어 있는 속성들은 기본값이 True인데 False로 변경하였다. 그렇지 않으면 .NET Framework의 기본 멤버들이 모두 문서화되어 여러분이 작성한 메서드보다 프레임워크에서 상속한 메서드에 대한 설명이 더 많아지게 된다. 실제로, .NET Framework 멤버에 대한 설명은 MSDN을 참고해야할 부분이지 여러분이 작성할 문서에서 참고할 것은 아니다.

Internals와 Privates는 클래스내의 내부 데이터나 메서드에 대한 참조용으로 문서화를 하기 위해 True로 하였다. 만약, 다른 업체를 위한 외부용 문서라면 이 부분은 False로 설정하기 바란다.

NDoc 프로젝트도 BasePage 프로젝트 디렉터리와 같은 곳에 저장하고, VS.NET에서도 BasePage.ndoc 파일을 추가한다. ndoc 확장자를 NDoc 프로그램과 연결해두면 언제든지 VS.NET에서 NDoc을 불러서 설정할 수 있다. 여기까지 설정을 모두 마쳤으면 NDoc에서 [Documentation] - [Build]를 선택해서 문서를 빌드한다. 최종 컴파일이 끝나면 doc 디렉터리에 생성된 .CHM 도움말을 볼 수 있으며, NDoc에서는 [Documentation] - [View]를 선택해서 볼 수 있다.




[그림5] BasePage 도움말


[그림5]에서 볼 수 있는 것처럼 Br2Nl 메서드에 대한 설명이 잘 정리되어 나타나는 것을 볼 수 있다. [그림1]과 [그림5]를 비교하면 XML 문서태그의 어떤 부분이 어떤식으로 보여지는지 알 수 있다.
NDoc에서 문서화 옵션을 HTML & CHM으로 선택했기 때문에 웹 사이트에 바로 올려놓고 참조할 수 있는 HTML 페이지도 doc 디렉터리에 작성되어 있다. doc\index.html 파일을 열어보면 HTML 형식의 도움말이 어떻게 작성되었는지 살펴볼 수 있다.

NDoc을 이용한 문서화에서 한가지 걸리는 부분이 있는데, 클래스나 메서드 설명에서 공백문자가 ?로 표시되는 부분이다.

이를 위해서는 처음 NDoc을 실행해서 CHM 파일의 원본이 되는 HTML 파일을 만들고, 모든 HTML 파일에서 ?를 공백문자로 변환하고, 다시 NDoc에서 CHM 파일을 작성하는 편법을 이용한다.

? 제거하기

윈도우 환경에서는 mreplace와 같은 유틸리티를 이용하여 해결한다. Visual Studio .NET 2003에도 텍스트 파일의 내용을 한 번에 변경하는 기능을 제공하지만 프로젝트에 포함된 파일에 대해서만 사용할 수 있다. 파일안의 텍스트를 변경하기 위해 엄청나게 많은 수의 HTML 파일을 프로젝트에 등록하는 것은 번거롭다. 따라서 윈도우 환경에서는 mreplace와 같은 유틸리티를 쓰거나 필자와 같이 UNIX 계열 운영체제 명령어에 대한 배경지식이 있는 사람은 Cygwin을 이용할 수 있다. 또는 Mono를 사용하는 분들도 sed를 이용해서 간단하게 변경할 수 있다.




[그림6] mreplace 유틸리티


?를 공백문자로 변경해야 하는데 HTML 형식으로 변경하기 때문에 변경할 문자열에  를 입력한다. HTML 파일에 대해서만 적용할 것이므로 File mask에는 *.html을 입력한다. [OK]를 클릭하면 모든 파일의 내용이 변경된다. Cygwin이나 UNIX 계열에서 이와 같은 작업을 하려면 다음과 같이 하면 된다.


root@ns2:~/doc# find *.html -print |
> while read file
> do  sed -e 's/\?/\ /g' "$file"  > "$file.new"
>     mv "$file.new" "$file"
> done


HTML 파일을 정리했으면 doc 디렉터리로 이동한다. 여기에 보면 Mona.Web.UI.BasePage.hhp 파일이 있다. HTML Help Workshop의 프로젝트 파일이며, NDoc에서 자동으로 생성한 것이다. HTML Help Workshop을 설치했다면 이 파일을 클릭해서 열 수 있다.




[그림7] HTML Help Workshop 실행화면


[File] - [Compile]을 선택해서 CHM 도움말을 빌드하면 ?등이 사라진 깨끗한 도움말을 볼 수 있다.

Br2Nl과 Nl2Br 함수

사용자가 웹에서 여러 줄을 입력할 때 각 줄의 끝에는 개행문자가 붙지만, HTML 웹 페이지를 볼 때 줄 바꿈을 하려면 개행문자가 아니라 BR 태그를 사용해야 한다. BR 태그는 각 줄을 잘라내는 BReak를 뜻한다.
반대로 BR 태그가 들어간 문서를 수정할 때는 BR 태그가 아닌 개행문자로 보여주어야만 화면에 여러줄로 제대로 표시된다.


String.Replace( "\r\n", "" );
String.Replace( "", "\r\n" );


위와 같은 코드를 사용하면 Br2Nl이나 Nl2Br 함수를 만들 수 있다고 생각하기 쉽지만 실제로 웹 페이지는 <BR>, <br>, <BR/>, <BR  />, <Br>, <br  />, <br       />과 같이 얼마든지 다양한 형태로 쓰일 수 있다. 따라서 문자열 형태의 치환이 아닌 정규식을 사용하여 제대로 치환하여야 한다.
마찬가지로 윈도우 환경에서는 개행문자를 \r\n으로 사용하지만 비윈도우 환경에서는 개행문자를 \n으로 사용한다. Linux 환경의 Apache에서 mod_mono를 이용하거나 xsp를 이용하는 경우와 윈도우 IIS 환경에서 동작하는 경우에도 문제없이 변환하려면 \r\n이나 \n 어느 한쪽으로만 변환해서는 안된다. 이런 경우에는 Environment.NewLine 상수를 사용해야 한다.

Replace 함수

Br2Nl과 Nl2Br 함수에서는 Replace를 사용했는데 이는 System.Text.RegularExpressions.RegEx 클래스를 이용해서 만든 함수다.




[그림8] Replace 함수


Replace 함수는 위 코드처럼 모두 5개의 매개변수로 되어 있으며, 마지막 2개는 몇 개나 일치시킬 것인가를 나타내는 count와 정규식 패턴 매치를 시작할 위치를 나타내는 startPos로 되어 있다.

5회까지 걸쳐 작성된 전체 소스는 이곳에서 다운받기 바란다.

참고자료

HTML Help Workshop
  MS에서 제공하는 무료 도움말 제작도구이며 CHM 형식의 도움말을 제작하거나 해제할 수 있다.

• 유닉스 파워 풀(UNIX POWER TOOLS), 9.9 찾아낸 파일에 대해 원하는 명령을 실행하는 방법
  UNIX 환경에서 파일의 내용을 치환하는 스크립트 작성에 사용했다.

• C# Cookbook, O'Reilly, Recipe 8.5 - Replacing Characters or Words in a String
  정규식을 이용한 손쉬운 문자열 치환을 수행하는 Replace 함수는 Recipe 8.5에서 인용했다.

Ndoc
  .NET의 XML 문서화를 자동화하기 위한 도구로 Java의 XML 문서화를 자동화하는 JDoc과 같은 이름을 따르는 문서화 도구다.

DoxyGen
  C/C++을 위한 문서화 도구로 개발되었고, 현재는 Java, PHP, C#과 같은 다양한 언어들을 지원합니다. C/C++ 문서화 도구가 필요하다면 DoxyGen을 권합니다.

GhostDoc
  VS.NET 환경에서 개발언어에 관계없이 XML 문서화를 자동화해주는 도구입니다. 특히, 메서드 이름을 잘 지은 경우에는 그것을 토대로 요약까지 자동으로 작성합니다. 메서드 이름 위에서 오른쪽 클릭해서 선택하면 자동으로 필요한 XML 태그를 작성해줍니다.

mreplace
  Modula-2용으로 만든 유틸리티로 여기서는 파일 텍스트의 내용을 일괄적으로 변경하기 위해 사용했습니다. 윈도우 사용자가 사용하기에 가장 간단하고 편리합니다. 마지막에 사용한 설정을 기억하므로 프로젝트 작업시에 반복하는 일은 많지 않습니다.
Posted by tornado
|
 

ASP.NET의 Visual SourceSafe 사용에 관한 모든 것

Paul Sheriff, Michael Krasowski
PDSA, Inc.

2003년 12월

요약: Microsoft Visual SourceSafe를 사용하여 ASP.NET 프로젝트를 관리하는 전체 프로세스를 안내합니다(17페이지/인쇄 페이지 기준).

적용 대상:
Microsoft ASP.NET
Microsoft Visual SourceSafe

목차

소스 코드 컨트롤을 사용해야 하는 이유
격리 모드와 비격리 모드 비교
SourceSafe 데이터베이스 설정
VSS에 ASP.NET 솔루션 추가
VSS로 파일 조작
파일 기록 추적
소프트웨어 버전에 레이블 사용
Visual Studio .NET에서 솔루션 가져오기
결론

 

일부 개발자들은 소스 코드 컨트롤을 반드시 사용해야 하지만 매우 번거로운 것으로 생각합니다. 하지만 소스 코드 컨트롤은 소프트웨어 개발 프로세스를 지원하는 안전한 업무 관례입니다. 이 문서에서는 실제로 Microsoft Visual SourceSafe를 소스 코드 컨트롤 메커니즘으로 유용하게 사용하는 단계별 방법을 보여 줍니다. 새 SourceSafe 데이터베이스를 만드는 방법, 파일을 체크 인하고 체크 아웃하는 방법, 레이블을 사용하여 릴리스를 만드는 방법을 볼 수 있습니다.

소스 코드 컨트롤을 사용해야 하는 이유

단순하게 말하자면 VSS(Visual SourceSafe) 같은 SCM(Software Configuration Management) 제품은 프로젝트를 구성하는 문서의 중앙 라이브러리(데이터베이스)입니다. Visual SourceSafe에는 프로젝트 계획, 사양 설명서, 데이터베이스 개체, 소스 코드 등의 비트 스트림과 프로젝트의 기타 모든 항목을 저장할 수 있습니다. 최상의 방법은 소스 코드뿐만 아니라 모든 프로젝트 항목을 Visual SourceSafe 데이터베이스에 포함시키는 것입니다. 그러면 액세스 및 팀 구성원 간 공유가 용이해지며, 무엇보다도 버전 제어가 용이해집니다.

여느 라이브러리에서처럼 사용할 파일을 "체크 아웃"하는 기능이 필요합니다. 사용자는 파일을 체크 아웃한 후 편집할 수 있습니다. 일반적으로 한 번에 한 명의 사용자만 파일을 체크 아웃하여 편집할 수 있습니다. 언제나 한 명의 사용자만 파일을 체크 아웃할 수 있도록 하는 것이 가장 좋습니다. 간혹, 같은 파일을 여러 사용자가 체크 아웃할 수 있도록 할 것을 권장하는 Visual SourceSafe 사용 시나리오를 소개하는 백서도 있습니다. 이러한 백서에서는 나중에 모든 변경 사항을 함께 병합할 수 있도록 할 것을 권장합니다. 그러나 VSS에 기본 제공되는 도구를 사용하면 쉽게 병합할 수 있을 것 같지만 실제로는 여러 가지 단점이 있습니다. 항목을 다시 체크 인하는 데 시간이 더 오래 걸리고, 병합 프로세스에서 충돌을 수동으로 점검해야 할 경우가 발생할 수 있으며, 데이터베이스, Microsoft Word 문서 등의 이진 항목에는 대개 사용할 수 없습니다. 또한 업데이트된 항목과 업데이트한 사람 및 시간에 대한 정확한 기록을 반영하지 못하는 경우가 있습니다.

VSS에서는 라이브러리 관리자가 액세스 제어를 정의할 수 있습니다. 사용자에게는 액세스 ID 및 암호, 그리고 액세스 권한이 주어집니다. 액세스 권한은 읽기 또는 읽기/쓰기 기능처럼 단순할 수도 있고, 기능 권한과 같이 복잡할 수도 있습니다. 예를 들어 파일을 삭제하는 기능도 하나의 기능 권한입니다.

모든 개별 파일(소스 코드, 프로젝트 계획, 요구 사항 등)에 대해 각각의 파일 주기 동안 어떠한 변화가 있었는지 파악하는 것은 매우 중요합니다. VSS는 파일을 만든 시간과 만든 사람, 해당 파일에 대한 각 수정, 해당 파일에 대한 메모 또는 주석, 그리고 해당 문서의 주기를 추적하는 데 도움이 되는 기타 정보 등 모든 작업 기록을 보관합니다.

위에서 언급한 VSS의 기능과 그 외 여러 기능을 통해 잘 관리된 구조적인 방식으로 개발, 빌드 및 유지 관리 프로세스를 효율적으로 관리할 수 있습니다. 이 외에도, VSS를 사용하여 소프트웨어 개발의 생산성을 높일 수 있는 이유에는 여러 가지가 있습니다.

격리 모드와 비격리 모드 비교

팀 환경에서 웹 응용 프로그램을 개발할 경우 두 가지 모델 중에서 하나를 선택할 수 있습니다. 첫 번째 방식인 비격리 모델에서는 모든 개발자가 중앙 서버에서 모든 파일을 만들고 수정합니다. 비격리 개발 모델에서는 중앙 공유 컴퓨터에서 하나의 Microsoft IIS(Internet Information Services) 서버를 사용해야 하며, 응용 프로그램의 모든 파일이 해당 서버의 가상 디렉터리에 상주해야 합니다. 모든 개발자가 VSS에서 파일을 체크 아웃하며, 체크 아웃된 파일은 중앙 IIS 서버의 가상 디렉터리로 이동합니다.

두 번째 웹 개발 방식인 격리 모델에서는 각 개발자가 자신의 개발 컴퓨터에서 실행 중인 IIS 안에 가상 디렉터리를 만듭니다. 격리 방식에서는 각 개발자가 중앙 VSS 데이터베이스에서 로컬 컴퓨터로 파일을 가져오거나 체크 아웃합니다. 개발자는 로컬 컴퓨터에서 모든 내용을 편집, 디버그 및 테스트하고 모든 내용이 정상적으로 작동하면 파일을 중앙 위치로 다시 체크 인할 수 있습니다. 체크 인된 파일은 다른 개발자가 가져올 수 있습니다.

두 가지 개발 유형 모두 장단점이 있습니다. 각각의 장점과 단점을 살펴보겠습니다.

비격리 개발의 장점

  • 개발자의 로컬 컴퓨터에서 IIS를 실행하고 있지 않아도 됩니다.
  • 모든 소스 코드가 서로 다른 개발자의 컴퓨터에 흩어져 있는 것이 아니라 한 위치에 모여 있습니다. 소스 코드가 저장된 컴퓨터에 문제가 발생할 경우 변경 내용을 SourceSafe에 체크 인하지 않았으면 해당 변경 내용을 잃을 수도 있습니다.

비격리 개발의 단점

  • 다른 개발자의 작업에 의도하지 않은 영향을 미치기 쉽습니다.
  • 한 개발자가 디버깅을 위해 응용 프로그램을 실행하는 동안에는 프로세스가 잠기므로 다른 개발자들이 응용 프로그램을 디버그할 수 없습니다.
  • 여러 개발자가 같은 파일을 작업하는 경우에는 마지막에 체크 인한 파일이 최종 파일이 됩니다.
  • 소스 제어 기능이 제한되어 있습니다.
  • 한 개발자가 일부 코드를 수정하여 해당 코드가 작동하지 않을 경우 다른 모든 개발자가 더 이상 프로젝트의 해당 부분을 실행할 수 없게 됩니다.

격리 개발의 장점

  • 다른 개발자를 부주의하게 방해하지 않고 응용 프로그램을 개발 및 디버그할 수 있습니다.
  • 다른 개발자에게 영향을 미치지 않고 로컬에서 변경 내용을 테스트할 수 있습니다.
  • 소스 코드 컨트롤에 대한 지원이 뛰어납니다.
  • 개발자는 네트워크에 연결하지 않아도 프로젝트를 다른 컴퓨터로 이동하거나 가지고 다니면서 사용자를 표시할 수 있습니다.

격리 개발의 단점

  • 각 개발자가 자신의 로컬 컴퓨터에 IIS를 설정해야 웹 응용 프로그램을 개발할 수 있습니다.
  • VSS 라이브러리가 백업 프로세스에 포함되는 경우 각 개발자는 파일이 백업되도록 퇴근하기 전에 모든 파일을 다시 체크 인해야 합니다.

선택할 모델

격리 개발 모델을 사용할 것을 권장합니다. 물론 이를 위해서는 각 사용자의 컴퓨터에 IIS가 있어야 하므로 일부 조직에서는 제약이 따를 수 있습니다. 하지만 격리 모델은 가장 유연한 소스 코드 컨트롤을 위한 최상의 모델입니다.

격리 개발을 위한 Visual Studio .NET 설정

격리 모델을 사용할 수 있도록 Microsoft Visual Studio .NET에서 올바른 옵션을 설정했는지 확인하십시오. Visual Studio .NET에서 도구 | 옵션 탭으로 이동하고 Microsoft FrontPage Extensions 옵션이 아닌 파일 공유를 클릭합니다. FrontPage Extensions 옵션은 모든 파일이 중앙 서버에 위치한 비격리 방식을 사용할 경우 선택하는 옵션입니다.

SourceSafe 데이터베이스 설정

지금까지는 Visual SourceSafe의 개요 및 사용 이점에 대해 알아보았습니다. 이제 Visual SourceSafe를 시작하는 방법을 살펴보겠습니다. 첫 번째 단계는 중앙 VSS 데이터베이스의 위치를 찾는 것입니다. 이 데이터베이스는 엄밀한 의미에서 데이터베이스라기 보다는 하드 드라이브의 폴더입니다. 이 폴더는 모든 개발자가 찾을 수 있는 네트워크 공유에 배치되어야 합니다. 개발자가 한 명뿐일 경우에는 개발자의 로컬 하드 드라이브에 배치할 수도 있습니다.

VSS 관리 도구를 아직 설치하지 않았으면 VSS CD에서 설치합니다. 사용자 지정 설치를 통해 이 옵션을 선택해야 합니다. CDD에서는 일반적으로 ACMBoot.exe를 실행하여 관리 도구를 설치합니다. 사용자 지정 설치 옵션에서 Administrative ProgramsCreate SourceSafe Database 옵션을 선택해야 합니다.

관리 프로그램을 설치한 후에는 시작 메뉴로 이동하여 프로그램 | Microsoft Visual SourceSafe | Visual SourceSafe 6.0 Admin을 클릭합니다. 새로 시작되는 인터페이스에서 Tools | Create Database...를 클릭합니다 . 그러면 그림 1과 같은 대화 상자가 표시됩니다. 이 새 SourceSafe 데이터베이스를 만들 위치를 "D:\MyVSSDB" 또는 \\SharedDrive\MyVSSDB와 같이 입력합니다.

그림 1. 모든 SourceSafe 파일에 사용할 공유 폴더의 위치를 지정합니다.

데이터베이스를 만들고 나면 그림 2와 같은 대화 상자가 표시됩니다. 이 대화 상자는 이 SourceSafe 데이터베이스를 만들 때 만들어지는 Admin 사용자에 관련 암호가 없다는 것을 나타내는 경고일 뿐입니다. Admin 사용자를 클릭하고 메뉴에서 Users | Change Password...를 클릭하여 Admin 사용자에 암호를 할당하십시오.

그림 2. Admin 사용자에 암호를 할당하여 관리 도구를 보안합니다.

데이터베이스를 만들고 나면 Visual SourceSafe Administrator 도구(그림 3)가 나타납니다. 이 도구에서는 한 번에 하나의 VSS 데이터베이스에만 연결할 수 있습니다. 이 데이터베이스에서 파일을 체크 아웃하고 체크 인할 수 있도록 할 모든 사용자를 이 데이터베이스 안에서 만들어야 합니다. 만드는 각 데이터베이스마다 해당 사용자를 추가해야 합니다. 이 기능은 설정된 사용자만 이 데이터베이스를 사용할 수 있도록 하므로 유용합니다. 그러나 모든 사용자에게 액세스를 허용할 VSS 데이터베이스가 여러 개 있는 경우 각 데이터베이스에서 각 사용자를 개별적으로 설정해야 합니다.

그림 3. Administrator 도구에서는 새 사용자를 만들고, 데이터베이스를 만들고, 데이터베이스를 잠그며, SourceSafe에 대한 기타 시스템 관리 기능을 수행할 수 있습니다.

이제 이 데이터베이스에 자기 자신을 새 사용자로 추가해야 합니다. SourceSafe에서 도메인 로그온 ID(도메인 이름 제외)를 사용자 이름으로 사용하고 있는지 확인하십시오. SourceSafe는 아무런 메시지를 표시하지 않고 도메인 로그온 ID를 사용하여 로그온을 시도합니다. 이 VSS 데이터베이스에 암호를 할당한 경우에는 해당 암호가 도메인 암호와 일치하는지도 확인하십시오.

이 새 데이터베이스의 위치를 기억해야 합니다. 데이터베이스에 사용자를 도메인 이름과 함께 추가한 후에는 사용자가 VSS 클라이언트 유틸리티를 처음 설정할 때 데이터베이스를 찾을 수 있도록 모든 사용자에게 데이터베이스 위치를 알려야 합니다.

VSS에 ASP.NET 솔루션 추가

새로 만든 VSS 데이터베이스에 프로젝트 및 프로젝트 항목을 추가할 수 있습니다. 두 가지 방법을 통해 이 VSS 데이터베이스를 조작할 수 있습니다. 즉, "다른 프로젝트 항목"을 데이터베이스에 추가하는 데 유용한 VSS Explorer 도구를 사용하거나 Visual Studio .NET 내에서 VSS를 사용할 수 있습니다. 사실 VSS에 새 프로젝트를 추가하는 경우에는 Visual Studio .NET을 사용하여 추가하는 것이 좋습니다. Visual Studio .NET 인터페이스를 사용하면 .SLN 파일에 몇 가지 바인딩 정보가 추가되므로 개발자가 VSS에서 솔루션을 가져올 때 VSS에 자동으로 연결될 수 있습니다.

참고 이 문서의 예제에서는 ASP.NET  사이트에서 다운로드할 수 있는 Microsoft ASP.NET Portal Starter Kit을 사용하여 파일을 체크 인하고 체크 아웃했습니다. 원하는 다른 프로젝트를 사용할 수도 있습니다.

ASP.NET Portal Starter Kit을 다운로드하여 설치했다고 가정하고 VSS에 이 솔루션을 추가하는 방법을 설명하겠습니다. Visual Studio .NET에서 솔루션을 열고 그림 4와 같이 메뉴에서 File | Source Control | Add Solution to Source Control...을 선택합니다.

그림 4. Visual Studio .NET에서 기본 제공하는 메뉴를 사용하여 솔루션을 SourceSafe에 추가합니다.

이 메뉴 항목을 선택한 후에는 그림 5와 같은 대화 상자가 표시될 것입니다. 격리 개발 모드를 사용하는 경우 이 대화 상자는 FrontPage Server Extensions 대신 일반 파일 URL을 사용하여 모든 파일을 참조한다는 의미일 뿐입니다. Don't' show this dialog box again (Always allow addition of Web projects using File Share access to source control) 확인란을 클릭하고 Continue를 클릭하십시오.

그림 5. FrontPage Server Extensions에서 파일 공유 액세스 사용으로 전환

이제 VSS 데이터베이스를 설정할 때 만든 로그온 ID와 암호(그림 6 참고)를 입력할 차례입니다. 설정한 ID(예: JohnD)와 암호를 입력합니다. 그런 다음 Browse...를 클릭하여 VSS 데이터베이스를 만든 특정 폴더를 찾습니다. 작업을 마쳤으면 OK를 클릭합니다.

그림 6. VSS 로그온

데이터베이스에서 만들 프로젝트 이름을 입력하는 대화 상자가 나타납니다. 첫 번째 대화 상자인 "Add to SourceSafe Project"(그림 7)는 솔루션 파일이 있는 Visual Studio .NET 프로젝트를 나타냅니다. 그림의 "ASP.NET Portal Starter Kit (VBVS)"와 같이 이 프로젝트의 이름을 입력합니다.

그림 7. 본인이나 다른 개발자가 나중에 쉽게 찾을 수 있도록 프로젝트에 이름을 지정합니다.

솔루션에 있는 모든 개별 Visual Studio .NET 프로젝트를 입력하는 대화 상자가 나타납니다. 그림 8과 같이 VSS는 각 Visual Studio .NET 프로젝트 이름을 "Add to SourceSafe Project" 대화 상자의 입력란에 자동으로 추가합니다. 이 경우 두 번째로 입력되는 내용은 PortalVBVS입니다. "ASP.NET Portal Starter Kit (VBVS)" 폴더를 클릭하여 이 프로젝트를 이 솔루션 아래에 배치해야 합니다.

그림 8. 각각의 새 프로젝트를 VSS에 개별적으로 추가합니다.

솔루션 파일이 프로젝트와 동일한 폴더에 있다는 경고 메시지가 표시됩니다. 의도적으로 같은 폴더에 배치한 것이므로 확인란을 클릭하여 계속 진행합니다. 그림 9와 같은 대화 상자가 나타날 수도 있고 나타나지 않을 수도 있습니다. 이 대화 상자가 나타나면 확인란을 선택하고 OK를 클릭합니다. 이 경우 다른 항목은 프로젝트 파일의 일부분이 아닌 폴더에 있기 때문에 나중에 VSS Explorer 도구를 통해 수동으로 추가해야 합니다. 폴더 안에 있지만 프로젝트 파일로 새로 지정된 문서 또는 .SQL 파일이 이러한 항목에 해당할 수도 있습니다.

그림 9. 프로젝트 파일에서 참조하지 않는 추가 파일이나 폴더가 있으면 VSS에서 이를 사용자에게 알립니다.

솔루션과 프로젝트를 SourceSafe 제어 아래에 배치하고 나면 Visual Studio .NET이 그림 10과 같이 특수 아이콘을 사용하여 파일이 잠겼는지 아니면 체크 인되었는지 표시합니다. 소스 코드 컨트롤 아래의 각 파일 옆에는 자물쇠 아이콘이 표시됩니다. 자신이 체크 아웃한 파일 옆에는 확인 표시가 나타나고, 다른 사용자가 체크 아웃한 파일에는 원형 아이콘이 나타납니다.

그림 10. Visual Studio .NET은 소스 코드 컨트롤 아래에 각 파일의 상태를 표시합니다.

VSS 데이터베이스 내의 전체 프로젝트를 보려면 운영 체제 메뉴에서 시작 | 모든 프로그램| Microsoft Visual SourceSafe| Microsoft Visual SourceSafe 6.0을 선택하십시오. 다시 로그온해야 할 수도 있습니다. 로그온 이름과 암호를 입력하십시오. VSS Explorer(그림 11 참고)를 처음 실행하는 경우 데이터베이스를 만든 폴더를 탐색하여 VSS 데이터베이스도 찾아야 합니다.

그림 11. Visual SourceSafe Explorer에서는 전체 프로젝트 및 소스 코드 컨트롤 아래에 배치된 모든 파일을 볼 수 있습니다.

VSS로 파일 조작

프로젝트 파일을 VSS 데이터베이스에 배치하고 나면 프로젝트의 모든 파일이 디스크에서 읽기 전용으로 설정됩니다. 체크 아웃되지 않은 파일로 솔루션을 실행할 수도 있습니다. 그러나 Visual Studio .NET 내에서 파일을 작업하려면 체크 아웃해야 합니다. 현재의 작업과 비교하면 한 단계가 추가된 것이지만 대신 이전 버전으로 다시 돌아갈 수 있으며 다른 개발자가 수정 중인 파일을 사용자가 동시에 수정할 수 없도록 합니다.

파일 체크 아웃

파일 작업을 위해 체크 아웃해야 할 때는 Solution Explorer 창에서 해당 파일을 마우스 오른쪽 단추로 클릭하고 상황에 맞는 메뉴에서 Check Out...을 클릭하기만 하면 됩니다. 예를 들어 Portal Starter Kit에서 Default.aspx 파일을 클릭하고 마우스 오른쪽 단추를 클릭한 다음 Check Out...을 클릭합니다. 그림 12와 같은 대화 상자가 나타납니다. Check Out 단추를 클릭하면 .ASPX 파일뿐만 아니라 .ASPX.resx 및 .ASPX.VB 파일도 체크 아웃됩니다. 이제 해당 파일을 작업할 수 있으며 다른 사용자에게 파일이 체크 아웃된 것으로 표시됩니다.

그림 12. Check Out 대화 상자에서는 프로젝트 파일을 하나 또는 여러 개 가져와서 하드 드라이브에 쓸 수 있는 상태로 설정할 수 있습니다.

파일 체크 인

체크 아웃한 파일에서 원하는 내용을 모두 수정한 후에는 SourceSafe에 다시 체크 인해야 합니다. 파일을 체크 인할 경우 두 가지 사항을 염두에 두어야 합니다. 첫째, 프로젝트의 페이지 또는 클래스에서 변경한 내용이 컴파일되는지 확인해야 합니다. 그렇지 않으면 SourceSafe 데이터베이스에서 최신 변경 사항을 가져오는 다른 개발자의 프로젝트에서 오류가 발생하는 곤란한 상황이 발생합니다. 둘째, 매일 일과가 끝나면 파일을 모두 체크 인해야 합니다. 그러면 파일이 단지 하드 드라이브에 저장되는 것이 아니라 다른 위치에 백업됩니다. 따라서 하드 드라이브에 문제가 발생할 경우에도 변경한 내용을 모두 보존할 수 있습니다. 일과 후에도 소스 코드가 아직 컴파일되지 않은 경우에는 문제가 되는 코드에 주석을 달고 체크 인하면 됩니다.

최신 버전 가져오기

팀 환경에서 작업하는 경우 프로젝트 내에서 다른 파일을 수정하는 다른 개발자도 있습니다. 특정 시점에서는 VSS 데이터베이스에 있는 모든 최신 변경 사항을 프로젝트와 동기화할 수 있습니다. 그러기 위해서는 Visual Studio .NET Solution Explorer 창에서 프로젝트를 클릭하고 마우스 오른쪽 단추를 클릭한 다음 Get Latest Version (Recursive)를 클릭합니다. 그러면 VSS 데이터베이스로 이동하여 변경된 모든 파일을 검색하고 해당 파일을 프로젝트로 가져옵니다. 이제 로컬 컴퓨터에서 프로젝트를 실행하고 나면 다른 개발자가 변경한 내용을 모두 볼 수 있습니다.

파일 기록 추적

특정 시점에서 개발 팀은 "빌드", "버전" 또는 "릴리스"를 만들 수도 있습니다. VSS는 버전 번호를 사용하여 파일 및 프로젝트에 대한 모든 변경 사항을 추적합니다. 따라서 파일 또는 프로젝트의 모든 버전을 검색할 수 있습니다. VSS는 내부 버전 번호, 날짜 및 사용자 정의 레이블의 세 가지 항목을 기준으로 이전 버전을 추적합니다. 버전을 자체적으로 지정하는 경우에는 VSS에서 할당한 내부 버전 번호가 아니라 사용자 정의 레이블을 사용합니다.

버전 번호

VSS는 체크 인된 각 파일에 대해 내부 버전 번호를 유지합니다. 파일을 체크 아웃하고 변경한 다음 VSS에 다시 체크 인할 때마다 해당 파일 버전에 대한 새 번호가 만들어집니다. VSS에서 History 대화 상자를 사용하여 파일의 전체 기록을 볼 수 있습니다. History 대화 상자는 Visual Studio .NET 또는 VSS Explorer 도구를 통해 볼 수 있습니다.

Visual Studio .NET에서 기록을 볼 파일(예: default.aspx)을 클릭한 다음 Visual Studio .NET 메뉴 시스템에서 File | Source Code Control | History를 클릭합니다. 그러면 그림 13과 같은 대화 상자가 나타납니다. default.aspx 파일을 아직 변경하지 않은 경우에는 첫 번째 버전 이외의 다른 버전이 없습니다.

VSS Explorer 도구를 사용하는 경우 Explorer에서 특정 파일을 찾아서 마우스 오른쪽 단추로 클릭한 다음 Show History... 메뉴 항목을 클릭하여 동일한 대화 상자를 표시합니다.

그림 13. VSS Explorer에 파일의 전체 기록이 표시됩니다.

참고 내부 VSS 버전 번호는 단순히 참조용이며 빌드 또는 버전 번호와 직접적인 관련이 없습니다. 빌드 또는 버전 번호에는 레이블(아래 참고)을 사용합니다.

소프트웨어 버전에 레이블 사용

SourceSafe에서 파일에 할당하는 내부 버전 번호를 사용하는 대신 소프트웨어 릴리스를 정의하는 자신만의 코드 집합용 "레이블"을 만들 수도 있습니다. 릴리스는 첫 번째 베타 버전, 제품의 첫 번째 버전, 증분 릴리스, 제품의 두 번째 또는 세 번째 릴리스일 수 있습니다.
각 파일에는 자체 내부 버전 번호가 지정되며, 파일 수정 빈도에 따라 이 번호는 전체 프로젝트에서 전혀 일치하지 않게 됩니다. 따라서 내부 버전 번호 대신 자신만의 레이블을 전체 프로젝트에 적용하여 이 레이블을 만든 특정 시점에서 체크 인된 모든 파일을 식별할 수 있습니다.

레이블(최대 31자)을 만들 때 "1.0," "2.01b," "Final Beta" 또는 "Approved for QA" 같은 텍스트를 사용할 수 있습니다. 레이블을 적용한 후에는 기록 대화 상자에서 이 레이블과 연결된 모든 파일을 검색할 수 있습니다. 개별 파일에 레이블을 할당할 수도 있지만 대개 프로젝트 수준에서 레이블을 적용합니다. 프로젝트에 설명 문자열이 있는 레이블을 할당하면 해당 프로젝트의 모든 파일과 하위 프로젝트가 그 레이블을 사용합니다.

개발 주기의 어느 시점에서나 프로젝트에 레이블을 할당할 수 있습니다. 예를 들어 제품의 각 "릴리스"마다 알파, 베타 또는 최종 생산 코드에 관계없이 해당 시점에서 모든 프로젝트 항목에 레이블을 할당할 수 있습니다. 개발을 진행하다 베타 1.0의 소스 코드가 필요할 경우 그냥 가져오면 됩니다. 원하면 원본 파일에 아무런 영향을 미치지 않고 레이블의 이름을 바꿀 수 있습니다.

레이블을 만들려면 VSS Explorer 도구에서 레이블을 할당할 Project 폴더를 클릭하십시오. 메뉴에서 File | Label...을 클릭하면 그림 14와 같은 대화 상자가 나타납니다. 설명이 포함된 레이블 이름과 이 레이블의 사용 용도를 알려 주는 주석을 입력하고 OK를 클릭하여 레이블을 이 프로젝트와 이 프로젝트 아래의 모든 파일 및 하위 폴더에 적용합니다.

그림 14. VSS Explorer 도구를 사용하여 레이블 만들기

VSS Explorer에서 프로젝트를 다시 선택하고 마우스 오른쪽 단추로 클릭한 다음 Show History... 메뉴 항목을 클릭하여 History 대화 상자를 표시하면 그림 15와 같이 레이블이 표시됩니다.

그림 15. VSS Explorer의 History 대화 상자에서 적용한 여러 가지 레이블을 볼 수 있습니다.

기존 레이블에 파일 추가

버전에 레이블을 할당했는데 나중에 레이블이 할당된 버전에 포함되었어야 할 파일을 빠뜨린 것을 발견하는 경우도 있을 수 있습니다. 파일을 레이블의 일부분으로 추가하려면 파일을 프로젝트에 추가하기만 하면 됩니다. VSS Explorer에서 해당 파일을 클릭한 다음 File | Label...을 클릭하고 프로젝트에 할당한 것과 동일한 레이블을 할당합니다. 레이블을 기준으로 파일을 가져올 경우 레이블 이름이 동일하기 때문에 이 파일도 함께 가져옵니다. 레이블 이름을 정확히 입력했는지 확인하십시오.

레이블에 할당된 모든 파일 가져오기

특정 레이블이 할당된 파일을 모두 가져오려면 해당 파일에 대해 "가져오기(get)" 작업을 수행하면 됩니다. 즉, 특정 레이블 아래의 파일을 체크 아웃할 수는 없지만 가져올 수는 있습니다. 그러기 위해서는 VSS Explorer에서 내용을 가져올 프로젝트를 마우스 오른쪽 단추로 클릭하고 Show History...를 클릭합니다. Project History Options 대화 상자(그림 16 참고)가 나타나면 Labels Only 확인란을 선택하고 OK를 클릭합니다.

그림 16. 프로젝트의 레이블 가져오기

선택한 프로젝트에 할당한 모든 레이블이 표시됩니다. 가져올 레이블을 클릭한 다음 화면(그림 15 참고) 오른쪽에서 Get을 클릭합니다. 그러면 이 레이블이 있는 모든 파일이 이 프로젝트에 할당된 작업 디렉터리로 복사됩니다. 이미 언급한 대로 레이블이 할당된 릴리스에서는 파일을 체크 아웃할 수 없습니다. 따라서 릴리스된 파일 집합은 아무도 무단으로 변경할 수 없습니다.

Visual Studio .NET에서 솔루션 가져오기

사용자가 빌드 중인 응용 프로그램에 대해 프로젝트 책임자가 초기 솔루션을 만들면 다른 개발자가 이 솔루션을 가져와서 각자의 컴퓨터에 설정할 수 있도록 해야 합니다. 이때 각 개발자가 자신의 컴퓨터에 가상 디렉터리를 다시 만들고 모든 파일을 올바른 위치로 가져왔는지 확인하게 할 필요는 없습니다. 다행히도 VSS 및 Visual Studio .NET에서는 이를 자동으로 처리합니다.

VSS와 통합된 Visual Studio .NET은 하드 드라이브에 적절한 폴더를 자동으로 만들고, 새 가상 디렉터리를 만들며, VSS에서 새 폴더로 파일을 자동 복사합니다. 항상 VSS가 아닌 Visual Studio .NET을 통해 이 프로세스를 수행해야 합니다. 그렇지 않으면 IIS를 수동으로 구성하고 VSS 데이터베이스에 대한 참조를 직접 설정해야 합니다.

다음 단계를 수행하려면 다른 컴퓨터를 사용하거나 앞에서 만든 가상 디렉터리를 지워야 합니다. Visual Studio .NET의 새 인스턴스를 엽니다. File | Source Control | Open from Source Control...을 클릭하면 그림 17과 같은 대화 상자가 나타납니다. 창에서 PortalVBVS 프로젝트를 클릭합니다. Create a new project in the Folder 입력란에 다른 폴더 이름을 입력하고 OK를 클릭합니다.

그림 17. 새 폴더로 SourceSafe 프로젝트 가져오기

드라이브에 없는 폴더를 선택하면 폴더를 만들라는 메시지가 표시됩니다. 이 예제의 경우에는 폴더가 드라이버에 없어야 정상입니다. Yes All을 클릭하여 이 프로젝트에 필요한 모든 폴더를 만듭니다.

그림 18과 같이 이 프로젝트를 배치할 가상 디렉터리를 입력하는 대화 상자가 나타납니다.

그림 18. 프로젝트에 가상 디렉터리 할당

SourceSafe 내의 프로젝트 및 솔루션 레이아웃 방법에 따라 열려는 솔루션 파일을 입력해야 할 수도 있습니다. 그럴 경우 대화 상자에서 .SLN 파일을 선택합니다. Portal 솔루션에서는 .SLN 파일이 분리된 자체 폴더 안에 있으므로 SourceSafe에서 아무런 대화 상자도 표시되지 않습니다.

다음으로는 이 가상 디렉터리를 만들 IIS의 위치를 입력하는 대화 상자가 나타납니다. 웹 서버 이름과 가상 디렉터리 이름을 입력하고 OK를 클릭합니다.

그림 19. 웹 서버에 프로젝트 할당

이제 Visual Studio .NET에서 이 프로젝트의 모든 파일을 가져오기 시작합니다. 이 문서의 예제에서는 D:\PortalVBVS를 사용했습니다. 즉, 솔루션이 이 폴더에 저장된다는 의미입니다. 이 프로젝트의 다른 모든 파일은 기본 웹 사이트가 가리키는 폴더에 배치됩니다. 일반적으로 이 폴더는 c:\inetpub\wwwroot입니다. 이로써 프로젝트가 다른 개발자의 컴퓨터에 설정되었으며 사용 준비가 끝났습니다. 사이트의 시작 페이지를 선택하고 F5를 누르기만 하면 응용 프로그램이 실행됩니다. 이제 파일을 체크 아웃하여 작업하고 다시 체크 인할 수 있습니다. 이 모든 작업이 Visual Studio .NET 내에서 가능합니다.

결론

모든 개발자는 일상적인 작업에서 Visual SourceSafe를 사용해야 하며, 모든 개발 관리자는 팀에서 Visual SourceSafe를 사용하도록 해야 합니다. 개발자가 한 명뿐일 경우에도 이 도구를 효율적으로 사용하면 소스 코드를 다른 컴퓨터에 백업하고 소스 코드의 이전 버전으로 돌아갈 수 있습니다. VSS를 만들고 사용하는 것은 간단하고 쉽습니다. 사용 방법을 조금만 배우면 됩니다. 소스 코드 컨트롤이 뛰어나면 개발 프로세스가 향상되고 소프트웨어 구성 관리의 다양한 이점을 모두 활용할 수 있습니다.

BIO

Paul D. Sheriff는 SDLC 문서 및 아키텍처 프레임워크를 비롯한 .NET 컨설팅, 제품 및 서비스를 제공하는 PDSA, Inc.의 사장입니다(http://www.pdsa.com/ 을 참고하십시오). 또한 남부 캘리포니아의 Microsoft 지역 책임자입니다. .NET 저서로는 ASP.NET Developer's Jumpstart(Addison-Wesley: 영문)와 PDSA 웹 사이트에서 구할 수 있는 여러 eBook이 있습니다. 전자 메일 주소는 PSheriff@pdsa.com입니다.

Michael Krasowski는 PDSA, Inc의 개발부 부사장입니다. 이전에는 The Boeing Company, Long Beach Division에서 선임 IT 관리자 직책을 역임했습니다. IT 분야에서 27년이 넘는 경험을 보유하고 있으며 캘리포니아 대학의 Irvine 사회 교육 과정에서 .NET에 대해 강의하고 있습니다. 전자 메일 주소는 Michaelk@pdsa.com입니다.

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

[msdn 펌] .NET Enterprise Services 성능  (0) 2005.07.22
[펌] ASP.NET 가이드 5. NDoc을 통한 코드 문서화  (0) 2005.07.14
asp 트리 ...  (0) 2005.07.07
닷넷용 하이버네이트  (0) 2005.06.29
[msdn펌] ASP.NET 업로드  (0) 2005.06.27
Posted by tornado
|

asp 트리 ...

.NET 2005. 7. 7. 18:37

http://www.obout.com/t2/eMSDN_DL_deep.aspx 

 

쓸만한거 많음 ㅎㅎㅎ

 

Posted by tornado
|

닷넷용 하이버네이트

.NET 2005. 6. 29. 12:18
Posted by tornado
|

[msdn펌] ASP.NET 업로드

.NET 2005. 6. 27. 15:48

Uploading Files Using the File Field Control

 

By Bill Evjen
Reuters

Feburary 2004

Applies to:
    Microsoft® ASP.NET

Summary: Learn how to use the Microsoft ASP.NET File Field control to allow your end-users to upload one or more files to your server. (16 printed pages)

Contents

Introduction
Looking Closely at the File Field Control
   Working Around File Size Limitations
   Controlling Uploaded File Types
   Uploading Multiple Files at the Same Time
Conclusion
Related Books

Introduction

Microsoft® ASP.NET Web Forms are all about communicating with an end-user to abstract what information you need to from them depending on the service administered. In many cases with Web Forms, it is usually simply textual data that is collected and stored. However, there are many cases where you are going to need more than simple textual data; you may want your users to upload files to the server.

For instance, your application can have a document library that is shared with all users. There are plenty of examples, but invariably you will need this ability to upload any type of document for storage, or to have your application take action upon the document once it is uploaded.

In any case, ASP.NET provides you with a simple ability to upload documents to the server with just a little work on your part. This article describes how to use the File Field control for uploading documents to your Web server.

Looking Closely at the File Field Control

The File Field control uses the HtmlInputFile class and is a cool control simply because it does things that were difficult to do before ASP.NET (that is, without buying a third-party component to do it for you). The File Field control uploads files to the server from the client's machine.

It is important to note that this control is called the File Field control in all versions of Microsoft Visual Studio® .NET, while in the ASP.NET Web Matrix, this control is called the FileUpload control. In both cases, you will find the control in the HTML section of the Toolbox.

The File Field control allows access to program the HTML <input type="file"> tag. This tag is used to work with file data within an HTML form. In the past when using classic ASP (ASP 3.0 or earlier), many programmers worked with third-party components to upload files from the client to the server. Now, with .NET and this new control, uploading is taken care of for you—and it couldn't be simpler. Listing 1 shows you how to use the File Field control to upload files to the server.

Note   The sample code is provided in both Microsoft Visual Basic® (VB) and C#.

Listing 1. Uploading files to the server using the File Field control

VB

<%@ Page Language="VB" %><script runat="server">    Sub SubmitButton_Click(Source As Object, e As EventArgs)       If Not (File1.PostedFile Is Nothing) Then          Try             File1.PostedFile.SaveAs("C:\Uploads\uploadedfile.txt")             Span1.InnerHtml = "Upload Successful!"          Catch ex As Exception             Span1.InnerHtml = "Error saving file <b>C:\\" & _                File1.Value & "</b><br>" & ex.ToString()          End Try       End If    End Sub</script><html><head></head><body>    <form runat="server" enctype="multipart/form-data">        Select a file to upload:<br />        <input type="file" id="File1" runat="Server">        <p>        <input type="submit" id="Submit1" runat="Server"          value="Upload File" OnServerClick="SubmitButton_Click">        <p>        <span id="Span1" runat="Server" />    </form></body></html>

C#

<%@ Page Language="C#" %><script runat="server">    void SubmitButton_Click(Object sender, EventArgs e) {       if (File1.PostedFile != null) {          try {             File1.PostedFile.SaveAs("C:\\Uploads\\uploadedfile.txt");             Span1.InnerHtml = "Upload Successful!";          }          catch (Exception ex) {             Span1.InnerHtml = "Error saving file <b>C:\\" +                File1.Value + "</b><br>" + ex.ToString();          }       }    } </script><html><head></head><body>    <form runat="server" enctype="multipart/form-data">        Select a file to upload:<br />        <input type="file" id="File1" runat="Server">        <p>        <input type="submit" id="Submit1" runat="Server"          value="Upload File" OnServerClick="SubmitButton_Click">        <p>        <span id="Span1" runat="Server" />    </form></body></html>

When the page from Listing 1 is run, you can select a file and upload it to the server by clicking the Upload File button on the page. This is a great feature that ASP 3.0 programmers always wished for. Now it is here in .NET! With only a few lines of code, it is easy to upload any type of files to the server.

There are some important items we should go over for this example so you understand all the needed pieces to make this work. First, for the example in Listing 1 to work, you have to make the destination folder on the server writeable for the account used by ASP.NET so the file can be saved to the specified folder.

If you think your ASP.NET account is not enabled to write to the folder you want, simply open up Microsoft Windows® Explorer and navigate to the folder to which you want to add this permission. Right-click on the folder (in this case, the Uploads folder), and then select Properties. In the Properties dialog box, click on the Security tab and make sure the ASP.NET Machine Account is included in the list and has the proper permissions to write to disk (see Figure 1).

Figure 1. Looking at the Security tab of the Uploads folder

If you don't see the ASP.NET Machine Account under the Security tab, you can add it by clicking the Add button and entering ASPNET (without the period) in the text area, as illustrated in Figure 2.

Figure 2. Adding the ASP.NET Machine Account to the folder security definition

Click OK to add the ASP.NET Machine Account to the list. From here, make sure you give this account the proper permissions; then click OK and you are ready to go.

Looking at the code from Listing 1, you might notice some really important things right away. First, the <form> tag has been altered by the addition of another attribute. To upload files to a server, your <form> tag must have the attribute enctype="multipart/form-data". Without this attribute, the Web form cannot upload the file.

The Submit button on the page causes an OnServerClick event to occur. This event uploads the file and then displays a message telling you if the upload was successful. If it was unsuccessful, the page displays an error message describing why the upload failed.

By using the <input type="file"> tag, the browser automatically places a Browse button next to the text field on the ASP.NET page. You don't have to program anything else for this to occur. When the end user clicks the Browse button, he can navigate through the local file system to find the file to be uploaded to the server. This is shown in Figure 3. Clicking Open will place that filename and the file's path within the text field.

Figure 3. Choosing a file

Working Around File Size Limitations

You may not realize it, but there is a limit to the size of a file that can be uploaded using this technique. By default, the maximum size of a file to be uploaded to the server using the File Field control is around 4MB. You cannot upload anything that is larger than this limit.

One of the great things about .NET, however, is that it usually provides a way around limitations. You can usually change the default settings that are in place. To change this size limit, you make some changes in either the machine.config or web.config file.

In the machine.config file, find a node called <httpRuntime> that looks like the following:

<httpRuntime  executionTimeout="90"  maxRequestLength="4096" useFullyQualifiedRedirectUrl="false"  minFreeThreads="8"  minLocalRequestFreeThreads="4" appRequestQueueLimit="100"/>

A lot is going on in this single node, but the setting that takes care of the size of the files to be uploaded is the maxRequestLength attribute. By default, this is set to 4096 kilobytes (KB). Simply change this value to increase the size of the files that you can upload to the server. If you want to allow 10 megabyte (MB) files to be uploaded to the server, set the maxRequestLength value to 11264, meaning that the application allows files that are up to 11000 KB to be uploaded to the server.

Making this change in the machine.config file applies this setting to all the applications that are on the server. If you want to apply this to only the application you are working with, then apply this node to the web.config file of your application, overriding any setting that is in the machine.config file. Make sure this node resides between the <system.web> nodes in the configuration file.

Another setting involved in the size limitation of files to be uploaded is the value given to the executionTimeout attribute in the <httpRuntime> node.

The value given the executionTimeout attribute is the number of seconds the upload is allowed to occur before being shut down by ASP.NET. If you are going to allow large files to be uploaded to the server, you are also going to want to increase this value along with the maxRequestLength value.

One negative with increasing the size of a file that can be uploaded is that there are hackers out there who attack servers by throwing a large number of requests at them. To guard against this, you can actually decrease the size of the files that are allowed to be uploaded; otherwise, you may find hundreds or even thousands of 10 MB requests hitting your server.

Controlling Uploaded File Types

There are a several methods you can use to control the types of files that are uploaded to the server. Unfortunately, there is no bullet-proof method to protect you from someone uploading files that would be considered malicious. You can take a few steps, however, to make this process of allowing end users to upload files a little more manageable.

One nice method you can employ is to use the ASP.NET validation controls that are provided for free with ASP.NET. These controls enable you to do a regular-expression check upon the file that is being uploaded to see if the extension of the file is one you permit to be uploaded.

This is ideal for browsers that allow client-side use of the validation controls because it forces the checking to be done on the client; the file is not uploaded to the server if the signature isn't one you allow. Listing 2 shows you an example of using validation controls to accomplish this task.

Note   The use of validation controls is not explained here. Take a look at Validating ASP.NET Server Controls for a complete explanation of validation controls and how to use them in your ASP.NET pages.

Listing 2: Using validation controls to restrict the types of files uploaded to the server

VB

<%@ Page Language="VB" %><script runat="server">    Sub SubmitButton_Click(Source As Object, e As EventArgs)       If Not (File1.PostedFile Is Nothing) Then          Try             File1.PostedFile.SaveAs("C:\Uploads\uploadedfile.txt")             Span1.InnerHtml = "Upload Successful!"          Catch ex As Exception             Span1.InnerHtml = "Error saving file <b>C:\\" & _                File1.Value & "</b><br>" & ex.ToString()          End Try       End If    End Sub</script><html><head></head><body>    <form enctype="multipart/form-data" runat="server">        <input id="File1" type="file" runat="Server" />        <p>            <input id="Submit1" type="submit" value="Upload File"              runat="Server" onserverclick="SubmitButton_Click" />        </p>        <span id="Span1" runat="Server" />         <p>            <asp:RegularExpressionValidator              id="RegularExpressionValidator1" runat="server"              ErrorMessage="Only mp3, m3u or mpeg files are              allowed!" ValidationExpression="^(([a-zA-             Z]:)|(\\{2}\w+)\$?)(\\(\w[\w             ].*))+(.mp3|.MP3|.mpeg|.MPEG|.m3u|.M3U)$"              ControlToValidate="File1">            </asp:RegularExpressionValidator>            <br />            <asp:RequiredFieldValidator              id="RequiredFieldValidator1" runat="server"              ErrorMessage="This is a required field!"              ControlToValidate="File1">            </asp:RequiredFieldValidator>        </p>            </form></body></html>

C#

<%@ Page Language="C#" %><script runat="server">    void SubmitButton_Click(Object sender, EventArgs e) {       if (File1.PostedFile != null) {          try {             File1.PostedFile.SaveAs("C:\\Uploads\\uploadedfile.txt");             Span1.InnerHtml = "Upload Successful!";          }          catch (Exception ex) {             Span1.InnerHtml = "Error saving file <b>C:\\" +                File1.Value + "</b><br>" + ex.ToString();          }       }    }</script><html><head></head><body>    <form enctype="multipart/form-data" runat="server">        <input id="File1" type="file" runat="Server" />        <p>            <input id="Submit1" type="submit" value="Upload File"              runat="Server" onserverclick="SubmitButton_Click" />        </p>        <span id="Span1" runat="Server" />         <p>            <asp:RegularExpressionValidator              id="RegularExpressionValidator1" runat="server"              ErrorMessage="Only mp3, m3u or mpeg files are              allowed!" ValidationExpression="^(([a-zA-             Z]:)|(\\{2}\w+)\$?)(\\(\w[\w             ].*))+(.mp3|.MP3|.mpeg|.MPEG|.m3u|.M3U)$"              ControlToValidate="File1">            </asp:RegularExpressionValidator>            <br />            <asp:RequiredFieldValidator              id="RequiredFieldValidator1" runat="server"              ErrorMessage="This is a required field!"              ControlToValidate="File1">            </asp:RequiredFieldValidator>        </p>            </form></body></html>

This simple ASP.NET page uses validation controls so that the end user can only upload .mp3, .mpeg, or .m3u files to the server. If the file type is not one these three choices, a Validation control throws an exception onto the screen. This is shown in Figure 4.

Figure 4. Validating the file type using validation controls

Using validation controls is not a foolproof way of controlling the files that are uploaded to the server. It wouldn't be too hard for someone to change the file extension of a file so it would be accepted and uploaded to the server, thereby bypassing this simple security model.

Uploading Multiple Files at the Same Time

So far, you have seen some good examples of how to upload a file to the server without much hassle. Now let's take a look at how to upload multiple files to the server from a single page.

No built-in capabilities in the Microsoft .NET Framework enable you to upload multiple files from a single ASP.NET page. With a little work, however, you can easily accomplish this task.

One trick is to import the System.IO class into your ASP.NET page, and to then use the HttpFileCollection class to capture all the files that are sent in with the Request object. This approach enables you to upload as many files as you want from a single page.

For this example, you can build an ASP.NET page that has four file-input boxes and one Submit button. After the user clicks the Submit button and the files are posted to the server, the code behind takes the files and saves them to a specific location on the server. After the files are saved, the file information that was posted is displayed in the ASP.NET page (see Listing 3).

Listing 3: Uploading multiple files to the server

VB

<%@ Import Namespace="System.IO" %><%@ Page Language="VB" %><script runat="server">    Sub SubmitButton_Click(Source As Object, e As EventArgs)       Dim filepath As String = "C:\Uploads"       Dim uploadedFiles As HttpFileCollection = Request.Files       Dim i As Integer = 0              Do Until i = uploadedFiles.Count          Dim userPostedFile As HttpPostedFile = uploadedFiles(i)                    Try             If (userPostedFile.ContentLength > 0) Then                Span1.InnerHtml += "<u>File #" & (i+1) & "</u><br>"                Span1.InnerHtml += "File Content Type: " & _                   userPostedFile.ContentType & "<br>"                Span1.InnerHtml += "File Size: " & _                   userPostedFile.ContentLength & "kb<br>"                Span1.InnerHtml += "File Name: " & _                   userPostedFile.FileName & "<br>"                    userPostedFile.SaveAs(filepath & "\" & _                   Path.GetFileName(userPostedFile.FileName))                    Span1.InnerHtml += "Location where saved: " & _                   filepath & "\" & _                   Path.GetFileName(userPostedFile.FileName) & _                   "<p>"             End If          Catch ex As Exception             Span1.InnerHtml += "Error:<br>" & ex.Message          End Try          i += 1       Loop           End Sub</script><html><head></head><body>    <form enctype="multipart/form-data" runat="server">        <p>            Select File1:<br />            <input id="File1" type="file" runat="Server" />            <br />            Select File2:<br />            <input id="File2" type="file" runat="Server" />            <br />            Select File3:<br />            <input id="File3" type="file" runat="Server" />            <br />            Select File4:<br />            <input id="File4" type="file" runat="Server" />        </p>        <p>            <input id="Submit1" type="submit" value="Upload Files"              runat="Server" onserverclick="SubmitButton_Click" />            <br />        </p>        <span id="Span1" runat="Server"></span>    </form></body></html>

C#

<%@ Import Namespace="System.IO" %><%@ Page Language="C#" %><script runat="server">    protected void SubmitButton_Click(Object sender, EventArgs e){       string filepath = "C:\\Uploads";       HttpFileCollection uploadedFiles = Request.Files;           for (int i = 0; i < uploadedFiles.Count; i++)       {              HttpPostedFile userPostedFile = uploadedFiles[i];              try          {                 if (userPostedFile.ContentLength > 0 )             {                Span1.InnerHtml += "<u>File #" + (i+1) +                    "</u><br>";                Span1.InnerHtml += "File Content Type: " +                    userPostedFile.ContentType + "<br>";                Span1.InnerHtml += "File Size: " +                    userPostedFile.ContentLength + "kb<br>";                Span1.InnerHtml += "File Name: " +                    userPostedFile.FileName + "<br>";                    userPostedFile.SaveAs(filepath + "\\" +                    Path.GetFileName(userPostedFile.FileName));                    Span1.InnerHtml += "Location where saved: " +                    filepath + "\\" +                    Path.GetFileName(userPostedFile.FileName) +                    "<p>";             }              }           catch (Exception Ex)          {                 Span1.InnerText += "Error: <br>" + Ex.Message;              }           }        }</script><html><head></head><body>    <form enctype="multipart/form-data" runat="server">        <p>            Select File1:<br />            <input id="File1" type="file" runat="Server" />            <br />            Select File2:<br />            <input id="File2" type="file" runat="Server" />            <br />            Select File3:<br />            <input id="File3" type="file" runat="Server" />            <br />            Select File4:<br />            <input id="File4" type="file" runat="Server" />        </p>        <p>            <input id="Submit1" type="submit" value="Upload Files"              runat="Server" onserverclick="SubmitButton_Click" />            <br />        </p>        <span id="Span1" runat="Server"></span>    </form></body></html>

The end user can select up to four files and click the Upload Files button, which initializes the SubmitButton_Click event. Using the HttpFileCollection class with the Request.Files property lets you gain control over all the files that are uploaded from the page. When the files are in this state, you can do whatever you want with them. In this case, the files' properties are examined and written to the screen. In the end, the files are saved to the Uploads folder in the root directory of the server. The result of this action is illustrated in Figure 5.

Figure 5. Uploading four files at once to the server from a single ASP.NET page

As you may have noticed, one interesting point about this example is that the states of the file input text boxes are not saved with the postback. You can see this in Figure 5. In ASP.NET, the state of the file-input text boxes cannot be saved because doing so might pose a security risk.

Conclusion

The File Field control provided by ASP.NET is a powerful control that was quite difficult to achieve in the days of Active Server Pages 3.0. This new capability allows your end-users to upload one or more files to your server. Remember, you can control the size of the files that are uploaded by working with settings in either the machine.config or web.config file.

Related Books

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

asp 트리 ...  (0) 2005.07.07
닷넷용 하이버네이트  (0) 2005.06.29
헉.. ASP.NET 인데... 자바스크립트가 안먹어 ㅡㅡ;  (0) 2005.06.27
[C#] 트랜잭션 사용  (0) 2005.06.15
[링크] 닷넷용 웹메뉴....  (0) 2005.06.10
Posted by tornado
|