수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1620526
  • Today | 388
  • Yesterday | 670

26 Articles, Search for 'Programming/.NET'

  1. 2007/07/23 Windows Workflow Foundation 호스팅에 대한 소개
  2. 2007/07/23 Windows Workflow Foundation 규칙 엔진 소개
  3. 2007/06/15 Introduction to Windows Communication Foundation
  4. 2007/04/29 High-Performance .NET Application Development & Architecture
  5. 2007/01/11 데이터 바인딩 어플리케이션 만들기 기초
  6. 2007/01/11 개체를 이용한 데이터 바인딩 어플리케이션 만들기
  7. 2007/01/11 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로 배포하는 방법
  8. 2007/01/11 Windows Form : 윈도우 폼 꾸미기
  9. 2007/01/11 Windows Form 기반의 프로그램에서, 폼에서 컨트롤의 키 입력을 가로채기
  10. 2007/01/11 .NET의 Enterprise Service(COM+) 이해
«Prev  1 2 3  Next»
Programming/.NET2007/07/23 09:41

Windows Workflow Foundation 호스팅에 대한 소개

Moustafa Khalil Ahmed
프로그램 관리자
Microsoft Corporation

2006년 8월

적용 대상:
Windows Workflow Foundation
Microsoft .NET Framework 2.0
Microsoft .NET Framework 3.0

요약: Windows WF(Workflow Foundation)를 호스팅하는 응용 프로그램이 실행 중인 워크플로를 관리 및 모니터링하는 방법과 런타임 서비스 및 기본 구현 방법에 대한 개요를 제공합니다. 이 기사는 Microsoft .NET Framework, C# 및 WF 프로그래밍 모델에 대한 지식이 있는 사용자를 대상으로 합니다.

목차

소개
워크플로 인스턴스 수명 주기 관리
관리 효율 및 모니터링
안정성 및 고가용성
기본 런타임 서비스
결론
추가 정보

소개

이 기사는 Windows WF(Workflow Foundation)를 사용하는 개발자가 실행 중인 워크플로 인스턴스를 관리 및 모니터링하는 데 사용할 수 있는 응용 프로그램의 다양한 옵션을 쉽게 이해하도록 돕기 위한 것입니다. 따라서 이 기사는 독자가 Microsoft .NET Framework, C# 및 WF에 대한 기본 지식이 있다는 가정 하에 작성되었습니다.

WF는 작업 라이브러리 및 프레임워크, 런타임 엔진 및 런타임 서비스 구성 요소로 구성되어 있으며, 이러한 구성 요소는 호스트 응용 프로그램 프로세스 내에서 실행되어야 합니다. 워크플로는 런타임 엔진에서 실행하는 일련의 작업으로 구성되어 있으며, 런타임 엔진은 호스트 응용 프로그램 프로세스 내에서 실행되어야 합니다. 다음 그림에서는 워크플로, 작업 및 워크플로 런타임 엔진을 호스트 응용 프로그램 프로세스에서 호스팅하는 방법을 보여 줍니다.

그림 1. Windows Workflow Foundation 호스트 프로세스

WF는 워크플로 실행 및 상태 관리를 담당하는 런타임 엔진을 제공합니다. WF 런타임은 ASP.NET, Windows 서비스, 콘솔 응용 프로그램 및 Windows Forms 응용 프로그램을 포함한 모든 .NET 프로세스에서 호스팅할 수 있습니다. 워크플로를 사용하는 응용 프로그램을 빌드할 때 개발자가 이러한 호스트 프로세스를 작성해야 합니다. 런타임 서비스는 호스트 프로세스에서 실행되어 런타임 엔진이 워크플로 실행을 관리할 때 런타임 엔진에 추가 기능을 제공합니다.

호스트 응용 프로그램을 WF에 맞게 구현할 경우 여러 가지 사항을 고려해야 합니다. 이 기사에서는 호스트 응용 프로그램이 워크플로를 관리 및 모니터링하는 방법에 대한 개요를 제공하며 기본 런타임 서비스 및 기본 구현 방법에 대해 간략하게 설명합니다.

워크플로 인스턴스 수명 주기 관리

WF는 WorkflowInstance 클래스에서 기본 작업 및 제어 작업 메서드를 제공하여 워크플로 상태 및 수명 주기를 관리합니다. 워크플로 인스턴스 수명 주기 관리 섹션에서는 다양한 워크플로 인스턴스 관련 런타임 이벤트, 이러한 이벤트 간의 전환 및 워크플로 상태 관계에 대해 설명합니다.

보존 지점

워크플로가 장시간 실행되는 빈도가 많아져서 사실상 유휴 시간이 길어지면 사용자나 다른 시스템이 입력 작업에 대해 대기하는 시간이 길어집니다. 메모리에 유휴 워크플로를 두는 것은 실용적이지 못하므로 워크플로가 대기 중인 이벤트를 수신할 때까지 워크플로 인스턴스 상태를 저장 매체에 보존하는 것이 좋습니다. 또한 워크플로 인스턴스 상태를 저장해 두면 이후 프로세스에서 오류가 발생할 경우 저장했던 지점부터 워크플로를 다시 시작할 수 있습니다.

그림 2에서는 보존 지점을 사용하여 실행 중인 워크플로 인스턴스를 다시 시작하는 방법을 보여 줍니다.

그림 2. 보존 지점을 사용하여 실행 중인 워크플로 인스턴스 다시 시작

워크플로 인스턴스 상태가 B 지점에서 보존되고 C 지점에서 오류가 발생한 경우 A 지점과 B 지점 사이에 완료된 작업이 손실되지 않고 워크플로 인스턴스를 B 지점부터 다시 시작할 수 있습니다. 하지만 보존 서비스를 사용할 수 없거나 워크플로 인스턴스 상태가 보존되지 않은 경우에는 A 지점과 B 지점 사이에 완료된 작업을 잃게 됩니다.

WorkflowPersistenceService가 있는 경우 즉, WorkflowRuntime 인스턴스에 추가된 경우 워크플로 런타임 엔진이 이 서비스를 사용하여 워크플로 인스턴스 상태를 저장 매체에 보존합니다. 이러한 작업은 다음 지점에서 수행될 수 있습니다.

  • PersistOnCloseAttribute로 표시된 작업 완료 시(예: 트랜잭션 범위 작업)
  • 워크플로 인스턴스 완료 전
  • 워크플로 인스턴스 종료 전
  • 워크플로가 유휴 상태로 되는 경우
  • WorkflowInstance.Unload 또는 WorkflowInstance.TryUnload가 호출된 경우

WF 런타임 엔진은 WorkflowPersistenceService에서 SaveWorkflowInstanceState() 메서드를 호출하여 워크플로 인스턴스 상태를 저장하며 필요한 경우 LoadWorkflowInstanceState() 메서드를 호출하여 워크플로 인스턴스 보존 상태를 검색합니다. 워크플로 런타임은 보존 수행 시기에 관한 모든 구문을 처리하고, 보존 서비스는 워크플로 인스턴스에 대한 실제 저장 및 로드를 담당하며, 작업 상태 및 워크플로 인스턴스 ID는 직렬화되어 보존 저장소에 저장됩니다. 또한 워크플로 인스턴스 실행을 다시 시작하는 데 필요한 다른 모든 정보(예: 대기열)도 직렬화되어 저장됩니다.

워크플로 인스턴스 이벤트

워크플로 인스턴스의 상태는 5가지, 즉 Created, Running, Suspended, Completed, Terminated 중 하나가 될 수 있습니다. 워크플로 인스턴스가 실행되는 동안 워크플로에는 13개의 이벤트가 발생할 수 있는데, 이러한 이벤트는 다른 상태로의 전환을 나타낼 수 있습니다. 예를 들어 WorkflowCompleted 이벤트는 인스턴스가 Running 상태에서 Completed 상태로 전환되었음을 나타냅니다. 일부 이벤트는 인스턴스가 다른 상태로 전환되었음을 나타내지 않기도 합니다. 예를 들어 WorkflowPersisted 이벤트는 인스턴스가 Persisted 상태이지만 여전히 Running 상태임을 나타냅니다. 13개의 이벤트 중 11개는 런타임 이벤트 및 워크플로 추적 이벤트를 통해 호스트 응용 프로그램과 통신하지만 Changed 이벤트와 Exception 이벤트는 워크플로 추적 이벤트를 통해서만 호스트 응용 프로그램과 통신할 수 있습니다.

호스트 응용 프로그램은 WF가 WorkflowInstance 클래스에 제공하는 제어 작업 메서드를 사용하여 워크플로 수명 주기를 관리할 수 있습니다. 또한 워크플로 수명 주기는 정책 설정을 통해서도 관리할 수 있습니다. 예를 들어 응용 프로그램은 언로드 정책을 사용하여 WF 엔진이 워크플로 인스턴스를 언로드하도록 지시할 수 있습니다. WF는 워크플로 인스턴스 상태에 영향을 줄 수 있는 기본 작업을 제공합니다. 예를 들어 SuspendActivity 및 TerminateActivity 작업을 사용하여 워크플로 인스턴스를 각각 일시 중단하거나 종료할 수 있습니다. 다음 섹션에서는 워크플로 인스턴스의 상태 전환 및 워크플로 인스턴스 이벤트와 통신하기 위해 워크플로 런타임이 발생시키는 다양한 워크플로 인스턴스 관련 이벤트에 대해 설명합니다.

WorkflowAborted

워크플로 런타임 엔진이 메모리 내 인스턴스를 제거하면 워크플로 인스턴스가 중단됩니다. 호스트 응용 프로그램은 WorkflowInstance.Abort()를 호출하여 워크플로 인스턴스를 중단할 수 있습니다. 중단된 워크플로 인스턴스는 WorkflowInstance.Resume()을 호출하여 마지막 보존 지점부터 다시 시작할 수 있습니다. 워크플로 인스턴스 중단은 응용 프로그램이 마지막 보존 지점부터 WorkflowInstance.Abort()가 호출되기 직전까지 완료된 모든 작업을 취소하기로 결정하는 경우처럼 극단적인 상황에 사용됩니다.

WorkflowCompleted

워크플로 인스턴스가 실행을 끝마치면 해당 인스턴스가 완료됩니다. 이때 호스트 응용 프로그램은 워크플로 인스턴스가 소비하지 않은 다른 이벤트 및 메시지 대기열을 검사할 수 있습니다.

WorkflowCreated

워크플로는 인스턴스 생성을 마친 후 작업 실행이 시작되기 전에 생성됩니다. 워크플로 인스턴스는 여러 개의 오버로드된 WorkflowRuntime.CreateWorkflow() 메서드 중 하나를 호출하여 생성할 수 있습니다.

WorkflowIdled

외부 이벤트(예: 타이머, 메시지 또는 기타 사용자 지정 이벤트)가 실행되는 동안 기다릴 때 워크플로 인스턴스는 유휴 상태가 됩니다. 시스템 리소스를 절약하기 위해 응용 프로그램은 언로드 정책을 설정하여 유휴 상태인 워크플로 인스턴스를 메모리에서 언로드할 수 있습니다. 호스트 응용 프로그램이 기본 SqlWorkflowPersistenceService를 사용하는 경우 응용 프로그램 구성 파일에 UnloadOnIdle 플래그를 설정하여 인스턴스가 유휴 상태일 때 WF 런타임 엔진이 워크플로 상태를 보존하도록 지시할 수 있습니다.

WorkflowLoaded

인스턴스 상태를 보존 저장소에서 메모리로 로드하는 경우 WorkflowLoaded 이벤트가 발생합니다.

WorkflowPersisted

WorkflowRuntime 인스턴스에 기본 SqlWorkflowPersistenceService 또는 사용자 지정 보존 서비스가 추가된 경우 워크플로 인스턴스 상태를 보존 저장소에 저장하면 워크플로 인스턴스가 보존됩니다.

WorkflowResumed

중단 또는 일시 중단된 워크플로 인스턴스에서 WorkflowInstance.Resume()을 호출하면 해당 워크플로 인스턴스가 다시 시작됩니다.

WorkflowStarted

WorkflowInstance.Start()를 호출하면 WorkflowStarted 이벤트가 발생합니다. WorkflowStarted 이벤트는 워크플로 런타임 엔진이 워크플로 작업을 실행하기 전에 발생합니다.

WorkflowSuspended

WorkflowInstance.Suspend()를 호출하거나 SuspendActivity 작업을 실행하면 워크플로 인스턴스가 일시 중단됩니다. 그 결과 워크플로 인스턴스는 일시 중단 상태가 됩니다.

WorkflowTerminated

WorkflowInstance.Terminate()를 호출하거나, TerminateActivity 작업을 실행하거나, 실행 중인 워크플로 인스턴스에서 처리되지 않는 예외가 발생하면 워크플로 인스턴스가 종료됩니다. 이 이벤트가 발생하면 워크플로 인스턴스는 종료 상태가 됩니다.

WorkflowUnloaded

워크플로 인스턴스가 메모리에서 보존 저장소로 언로드되는 경우 WorkflowUnloaded 이벤트가 발생하며, 이는 보존 정책에 따라 수행되거나 WorkflowInstance.Unload() 또는 WorkflowInstance.TryUnload() 호출을 통해 수행됩니다.

워크플로 인스턴스 이벤트 전환

워크플로 인스턴스 이벤트는 워크플로 런타임 이벤트와 워크플로 추적 이벤트를 통해 호스트와 통신합니다. 호스트 응용 프로그램은 런타임 이벤트를 구독하거나 추적 서비스를 사용하여 알림을 수신할 수 있습니다. Exception 및 Changed 이벤트는 추적 서비스를 통해서만 호스트 응용 프로그램과 통신합니다. Exception 이벤트는 워크플로 인스턴스 실행 중에 예외가 발생했음을 나타내며 Changed 이벤트는 실행 중에 워크플로 인스턴스가 동적으로 업데이트되었음을 나타냅니다.

그림 3에서는 다양한 워크플로 이벤트 간의 전환과 워크플로 상태를 보여 줍니다.

더 큰 이미지를 보려면 여기를 클릭하십시오.

그림 3. 워크플로 이벤트 간 전환 및 상태(더 큰 이미지를 보려면 이미지를 클릭하십시오.)

보존 서비스를 사용할 수 있다면 그림 3에 나타나 있듯이 보존 지점이 생성됩니다. WorkflowPersisted, WorkflowUnloaded 및 WorkflowLoaded 이벤트를 사용할 수 있는 경우 호스트 응용 프로그램이 이러한 이벤트를 확인할 수 있어야 합니다. 워크플로 인스턴스가 메모리에 없을 때 보존 서비스를 사용하면 워크플로 인스턴스가 먼저 로드된 후 인스턴스에서 유효한 작업(Resume, Abort, Terminate 등)이 수행됩니다. 예를 들어, 워크플로 인스턴스가 일시 중단된 채 언로드된 경우 Resume을 호출하면 다이어그램에 나타나 있듯이 워크플로 인스턴스가 먼저 로드된 후 Resumed 이벤트가 발생합니다.

워크플로 인스턴스 작업

앞에서도 설명했듯이 WorkflowInstance 클래스는 워크플로 인스턴스의 수명 주기를 제어하기 위해 메서드를 사용합니다. 이 섹션에서는 이러한 메서드에 대해 설명합니다.

WorkflowInstance.Start()

생성된 워크플로 인스턴스를 실행합니다. WorkflowInstance.Start()가 실행되면 워크플로 런타임이 WorkflowStarted 이벤트를 발생시키며 워크플로 인스턴스는 Running 상태가 됩니다. 이미 시작된 워크플로 인스턴스에 대해 Start()를 호출하면 InvalidOperationException이 발생합니다.

WorkflowInstance.Abort()

워크플로 인스턴스를 중단합니다. 성공적으로 중단되면 워크플로 런타임이 WorkflowAborted 이벤트를 발생시킵니다.

WorkflowInstance.Load()

언로드된 워크플로 인스턴스를 보존 저장소에서 메모리로 로드합니다. 그 다음 이 인스턴스는 언로드되기 전에 있었던 상태부터 실행됩니다. 성공적으로 로드되면 워크플로 런타임이 WorkflowLoaded 이벤트를 발생시킵니다.

WorkflowInstance.Resume()

중단 또는 일시 중단된 워크플로 인스턴스를 다시 시작하고 실행합니다. 워크플로 런타임은 워크플로 인스턴스 실행이 다시 시작되기 바로 전에 WorkflowResumed 이벤트를 발생시킵니다.

WorkflowInstance.Suspend()

워크플로 인스턴스 실행을 일시 중단합니다. WorkflowInstance.Suspend()를 성공적으로 호출하면 워크플로 런타임이 WorkflowSuspended 이벤트를 발생시킵니다.

WorkflowInstance.Terminate()

워크플로 인스턴스를 종료하고 메모리 내 워크플로 인스턴스를 지웁니다. 워크플로 런타임은 워크플로 인스턴스가 메모리에서 지워졌음을 등록된 보존 서비스에 알립니다. SqlWorkflowPersistenceService의 경우 종료 시에 해당 워크플로 인스턴스에 대한 모든 상태 정보가 데이터베이스에서 삭제되므로 이전에 저장한 보존 지점부터 워크플로 인스턴스를 다시 로드할 수 없습니다. WorkflowInstance.Terminate()에 성공하면 워크플로 런타임이 WorkflowTerminated 이벤트를 발생시킵니다.

WorkflowInstance.Unload()

워크플로 인스턴스를 메모리에서 보존 저장소로 언로드합니다. WorkflowInstance.Unload()는 동기식이므로 현재 예약된 작업을 끝마칠 때까지 또는 트랜잭션 범위의 끝부분에 도달할 때까지 다른 작업을 차단하여 언로드 작업이 성공적으로 수행될 수 있도록 합니다. WorkflowInstance.Unload()에 성공하면 워크플로 런타임이 WorkflowUnloaded 이벤트를 발생시킵니다. 등록된 보존 서비스가 없는 경우 Unload()를 호출하면 InvalidOperationException이 발생합니다.

WorkflowInstance.TryUnload()

WorkflowInstance.TryUnload()는 WorkflowInstance.Unload()와 달리 워크플로가 언로드될 때까지 다른 작업을 차단하지 않습니다. WorkflowInstance.TryUnload()는 워크플로 인스턴스를 메모리에서 보존 저장소로 언로드하며 인스턴스가 일시 중단 또는 유휴 상태인 경우 true를 반환하고 그렇지 않은 경우에는 false를 반환합니다. 등록된 보존 서비스가 없는 경우 TryUnload()를 호출하면 InvalidOperationException이 발생합니다.

WorkflowInstance의 다양한 제어 메서드에 대한 자세한 내용은 Windows Foundation SDK를 참조하십시오.

관리 효율 및 모니터링

워크플로를 호스팅하는 응용 프로그램은 호스팅되어 실행되는 워크플로를 관리 및 모니터링해야 합니다. WF에서는 다양한 관리 효율 및 모니터링 도구를 지원합니다. 예를 들어 WF에서는 하위 수준 디버깅에 사용할 수 있는 종단 간 추적 기능과 워크플로 데이터 추출 및 모니터링을 위한 추적 인프라를 제공합니다.

이 섹션에서는 관리 효율 및 모니터링 인프라 그리고 호스트 응용 프로그램에서 이를 사용하는 방법에 대해 설명합니다.

추적

WF에서는 워크플로 인스턴스가 실행되는 동안 워크플로, 작업 및 사용자 이벤트와 데이터를 캡처하기 위한 추적 인프라를 제공합니다. 모든 워크플로 런타임 인스턴스가 등록된 추적 서비스를 여러 개 사용하거나 전혀 사용하지 않을 수 있습니다. 추적 정보는 등록된 추적 서비스로 전송되며 호스트 응용 프로그램의 요구에 따라 추적 서비스가 이 정보를 저장 및 처리합니다. 또한 WF에서는 호스트 응용 프로그램이 사용할 수 있는 기본 SQL 기반 추적 서비스(SqlTrackingService)를 제공하며, 호스트 응용 프로그램 개발자가 자체적으로 사용자 지정 추적 서비스를 작성하여 호스트 응용 프로그램에 사용할 수도 있습니다.

추적 기능을 사용하여 워크플로 인스턴스의 실행 기록을 검사할 수 있으며 사용자 시스템에서 실행 중인 워크플로 인스턴스의 현재 상태를 확인할 수 있습니다. 또한 추적 기능을 사용하면 함께 사용 가능한 정보를 워크플로 정의에 제공하여 시스템에서 실행 중인 워크플로 인스턴스의 향후 예상 실행 경로를 예측할 수 있습니다. WF에서 제공하는 응용 프로그램 샘플(Workflow Monitor Sample)은 기본 SqlTrackingService를 사용하며 워크플로 디자이너는 현재 실행 중인 워크플로와 완료된 워크플로에 대한 워크플로 및 작업 상태 정보가 표시되도록 이 샘플을 제어합니다.

추적 기능을 사용한 워크플로 모니터링에 대한 자세한 내용은 Applications Samples/Workflow Monitor Sample의 Workflow Monitor SDK Tool 섹션을 참조하십시오. 사용자 지정 추적 서비스를 빌드하는 방법에 대한 예제를 보려면 Technology Samples/Tracking의 ConsoleTrackingService Sample 및 File Tracking Service and Query Sample 섹션을 참조하십시오. 기본 SqlTrackingService를 사용하는 방법에 대한 예제를 보려면 Technology Samples/Tracking out-of-box의 Simple Tracking Sample 및 Query Using SQLTracking Service Sample 섹션을 참조하십시오.

추적 및 종단 간 추적

추적 기능을 사용하면 응용 프로그램 상태를 모니터링한 후 실행 중인 시스템을 방해하지 않고 문제를 분리하여 수정할 수 있습니다. WF는 System.Diagnostics API를 사용하여 워크플로 런타임 및 워크플로 인스턴스 실행에 대한 정보를 추적합니다(규칙 집합 평가 정보 포함). 기본적으로 추적은 OFF로 설정되어 있지만 원하는 경우 ON으로 전환할 수 있습니다.

WF는 종단 간 추적에도 참여합니다. 종단 간 추적 기능을 사용하면 추적 뷰어에서 다양한 구성 요소 간의 연속 추적 정보 및 이러한 구성 요소 간의 전환 상태를 확인할 수 있습니다. 따라서 종단 간 디버깅이 수월해집니다.

응용 프로그램 구성 파일을 사용하는 경우 다음 구문을 추가하면 여러 WF 네임스페이스에 대한 추적을 로깅할 수 있습니다.

<system.diagnostics>
<switches>
<add name="System.Workflow LogToTraceListeners" value="1" />
<add name="System.Workflow.Runtime" value="All" />
<add name="System.Workflow.Runtime.Hosting" value="All" />
<add name="System.Workflow.Runtime.Tracking" value="All" />
<add name="System.Workflow.Activities" value="All" />
<add name="System.Workflow.Activities.Rules" value="All" />
</switches>
</system.diagnostics>

LogToTraceListeners가 사용되는 경우 WF는 호스트 응용 프로그램에서 만든 각 TraceListener를 열거하며 모든 로깅 정보를 여기에 전송합니다. 예제의 나머지 행에서는 네임스페이스를 지정하여 로깅 정보 및 추적된 정보의 양을 캡처할 수 있습니다. 값 특성에는 All, Off, Critical, Error, Warning, Information 및 Verbose를 사용할 수 있습니다. 값 특성 사용에 대한 자세한 내용은 WF SDK를 참조하십시오.

워크플로 런타임 이벤트

런타임 이벤트는 워크플로 런타임에 의해 발생되며, 워크플로 런타임 및 워크플로 인스턴스의 수명 주기를 관리하기 위한 수단을 호스트 응용 프로그램에 제공합니다. 이벤트 처리기는 WorkflowRuntime 클래스에 정의되므로 이를 사용하려면 호스트 응용 프로그램이 이러한 이벤트를 구독해야 합니다.

런타임 이벤트는 호스트 응용 프로그램이 쿼리를 위해 이벤트 및 관련 데이터를 저장하는 대신 특정 이벤트에 대해 영향을 줄 필요가 있을 때 간단한 알림 시스템의 역할을 합니다. 전자의 경우에는 추적 인프라를 사용하는 것이 좋습니다.

WorkflowRuntime 인스턴스는 여러 개의 워크플로 인스턴스를 실행할 수 있으며, 각 워크플로 인스턴스는 자체 수명 주기를 가집니다. 따라서 워크플로 인스턴스 이벤트의 이벤트 인수에는 워크플로 인스턴스 ID 및 다른 정보가 포함됩니다. 이 정보를 사용하면 해당 이벤트와 워크플로 런타임이 이 이벤트를 발생시키게 하는 워크플로 인스턴스를 연결할 수 있습니다.

다음 섹션에서는 사용 가능한 워크플로 런타임 이벤트에 대해 설명합니다.

WorkflowRuntime.ServiceExceptionNotHandled

이 이벤트는 서비스 소유 스레드가 예외를 생성하는 경우에 발생합니다. WorkflowRuntimeService 클래스에서 파생된 서비스는 RaiseServicesExceptionNotHandledEvent() 메서드를 호출하여 구독자에게 ServicesExceptionNotHandled 이벤트에 대해 알려 줍니다. 이 이벤트는 실행 중에 예외를 발생시키는데, 이 예외는 처리할 수 없습니다. 기본 서비스는 이러한 조건에서 이 이벤트를 발생시킵니다. 호스트 응용 프로그램은 이 이벤트를 구독하여 복구 메커니즘을 구현할 수 있습니다. 이 이벤트와 관련된 이벤트 인수는 ServicesExceptionNotHandledEventArgs입니다.

WorkflowRuntime.Started

이 이벤트는 WorkflowRuntime의 지정된 인스턴스가 시작되는 경우에 발생합니다. 이 이벤트와 관련된 이벤트 인수는 WorkflowRuntimeEventArgs입니다.

WorkflowRuntime.Stopped

이 이벤트는 WorkflowRuntime의 지정된 인스턴스가 중지되는 경우에 발생합니다. 이 이벤트와 관련된 이벤트 인수는 WorkflowRuntimeEventArgs입니다.

표 1. WorkflowInstanceEvents

이벤트 설명 이벤트 인수
WorkflowAborted 워크플로 인스턴스가 중단되면 발생합니다. WorkflowEventArgs
WorkflowCompleted 워크플로 인스턴스가 완료되면 발생합니다. WorkflowCompletedEventArgs
WorkflowCreated 워크플로 인스턴스가 생성된 후 작업이 처리되기 전(즉, 워크플로가 실행되기 전)에 발생합니다. WorkflowEventArgs
WorkflowIdled 워크플로 인스턴스가 유휴 상태로 들어설 때 다시 말해 외부 이벤트(예: 타이머, 메시지 등)가 실행되는 동안 기다릴 때 발생합니다. WorkflowEventArgs
WorkflowLoaded 워크플로 인스턴스가 일반적으로 보존 저장소에서 메모리로 로드될 때 발생합니다. WorkflowEventArgs
WorkflowPersisted 워크플로 인스턴스가 보존되면 발생합니다. WorkflowEventArgs
WorkflowResumed 워크플로 인스턴스가 일시 중단 상태나 중단 상태에서 다시 시작되는 경우 발생합니다. WorkflowEventArgs
WorkflowStarted 워크플로 인스턴스가 실행되면 발생합니다. WorkflowEventArgs
WorkflowSuspended 워크플로 인스턴스가 일시 중단되면 발생합니다. WorkflowSuspendedEventArgs
WorkflowTerminated 워크플로 인스턴스가 종료되면 발생합니다. WorkflowTerminatedEventArgs
WorkflowUnloaded 워크플로 인스턴스가 메모리에서 보존 저장소로 언로드될 때 발생합니다. WorkflowEventArgs

다양한 이벤트 및 워크플로의 상태 전환 방법에 대한 자세한 내용은 이 기사의 앞부분에 있는 "워크플로 인스턴스 수명 주기 관리" 섹션을 참조하십시오.

다양한 워크플로 런타임 이벤트의 사용 방법에 대한 자세한 내용은 Windows Workflow Foundation SDK Samples를 참조하십시오.

성능 카운터

Windows 성능 도구를 사용하여 워크플로 성능을 모니터링할 수 있습니다. 성능 도구는 시스템 모니터와 성능 로그 및 경고의 두 부분으로 구성되어 있습니다. 성능 로그 및 경고를 통해 성능 카운터를 구성하여 성능 데이터를 기록할 수 있으며, 특정 카운터 값이 정의된 임계값을 넘거나 아래로 내려갈 때 사용자에게 알려 주도록 시스템 경고를 설정할 수 있습니다.

WF에서는 워크플로 성능을 추적하는 데 사용할 수 있는 WF 성능 개체를 성능 카운터 집합에 제공합니다. 성능 카운터의 전체 목록을 보려면 WF SDK의 Workflow Performance Counters 섹션을 참조하십시오.

성능 도구에 성능 카운터를 추가하는 방법에 대한 자세한 내용은 Microsoft TechNet 웹 사이트를 참조하십시오.

언로드 정책

시스템에서는 지정된 시간에 수천 개의 워크플로가 동시에 실행되기도 합니다. 따라서 모든 워크플로를 메모리에 두는 것은 비효율적입니다. 시스템 리소스를 보다 잘 관리하려면 워크플로 상태를 보존한 후 메모리에서 언로드하도록 언로드 정책을 설정하는 것이 좋습니다.

기본 보존 서비스를 사용하는 경우 WF는 유휴 시 언로드 정책을 제공합니다. 이 정책은 SqlWorkflowPersistenceService 클래스에서 UnloadOnIdle 속성을 설정한 경우 활성화되어 유휴 상태일 때 런타임 엔진이 워크플로를 언로드하도록 합니다. 호스트 응용 프로그램이 구성 파일을 통해 SqlWorkflowPersistenceService를 사용할 수 있는 경우 UnloadOnIdle 플래그를 true로 설정하여 이 작업을 수행하면 됩니다. SqlWorkflowPersistenceService가 생성되어 코드를 통해 사용할 수 있는 경우에는 호스트 응용 프로그램이 SqlWorkflowPersistenceService(String, Boolean, TimeSpan, TimeSpan) 생성자를 사용하여 생성하면 됩니다. 호스트 응용 프로그램은 다른 복잡한 언로드 정책도 구현할 수 있습니다.

또한 WorkflowInstance.Unload() 메서드를 호출하여 이러한 특정 워크플로 인스턴스를 메모리에서 언로드한 후 상태를 보존하도록 요청할 수 있습니다. 호스트 응용 프로그램은 나중에 이 인스턴스에 대해 Load() 메서드를 호출하여 마지막 보존 지점부터 인스턴스를 계속 실행할 수 있습니다. 워크플로 인스턴스가 언로드되면 런타임 이벤트인 WorkflowUnloaded 이벤트가 발생합니다.

안정성 및 고가용성

WF에서는 다음 사항을 지원하여 호스트의 안정성 및 고가용성을 지원합니다.

SQL 클러스터링

기본 SQL 기반 서비스는 클러스터링 설치를 지원합니다. WF의 기본 SQL 기반 서비스는 일괄 처리를 SQL Server로 커밋할 때 재시도를 허용합니다. 따라서 장애 조치 시나리오 또는 일시적으로 액세스할 수 없는 SQL 서버를 지원할 수 있습니다. 재시도 논리는 다음 서비스 조합에서 설정할 수 있습니다.

  • DefaultWorkflowCommitWorkBatchService
  • SharedConnectionWorkflowCommitWorkBatchService
  • SqlTrackingService
  • SqlWorkflowPersistenceService

기본적으로 재시도 논리는 OFF로 설정되어 있습니다. 호스트 응용 프로그램이 이 기능을 사용하려면 재시도 논리를 명시적으로 ON으로 설정해야 합니다. 응용 프로그램은 재시도를 서비스 단위로 수행할지 아니면 앞에서 언급된 모든 서비스에 대해 수행할지를 선택할 수 있습니다. 이 작업은 서비스 클래스에서 EnableRetries 속성을 설정하거나 구성 파일을 통해 수행할 수 있습니다. 구성 파일을 사용하는 경우 호스트 응용 프로그램은 공유 플래그인 EnableRetries를 사용하여 영향을 받는 모든 서비스에 대한 재시도 사용 여부를 ON 또는 OFF로 설정할 수 있습니다. 서비스에 EnableRetries 속성을 설정한 경우에는 이 값이 EnableRetries 공유 플래그 값을 덮어쓰게 됩니다. WF의 기본 SQL 기반 서비스는 일정 횟수만큼 재시도를 수행하며, 이 재시도 횟수는 구성할 수 없습니다. 그러나 서비스의 연결 문자열에서 연결 시간 제한을 조정하여 재시도 간격을 부분적으로 조정할 수는 있습니다.

재시도 설정

다음 예제에서는 공용 매개 변수인 EnableRetries를 추가한 후 해당 값을 True로 설정하여 EnableRetries를 모든 기본 서비스에 대해 설정하는 방법을 보여 줍니다. 또한 SqlWorkflowPersistenceService에 대한 EnableRetries 플래그를 추가한 후 이 값을 False로 설정하여 이 서비스에 대해 EnableRetries를 사용하지 않는 방법도 보여 줍니다.

<WorkflowRuntime Name="SampleApplication" UnloadOnIdle="false">
<CommonParameters>
<add name="ConnectionString" value="Initial Catalog=WorkflowStore;Data Source=localhost;Integrated Security=SSPI;" />
<add name="EnableRetries" value="True" />
</CommonParameters>
<Services>
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" EnableRetries="False" />
</Services>
</WorkflowRuntime>

일반적으로 모든 서비스에 대해 재시도를 ON 또는 OFF로 설정하는 것이 좋습니다.

WorkflowCommitWorkBatchService 클래스는 TransactionScopeActivity가 아닌 다른 모든 작업 관련 일괄 처리 커밋(보존 지점)을 모두 재시도해야 합니다. WorkflowCommitBatchService 클래스는 TransactionScopeActivity 작업에 대한 작업 일괄 처리 커밋을 재시도할 수 없습니다. 이는 트랜잭션를 시작하지 못하여 소유하지 못했기 때문으로, 이런 경우에는 TransactionScopeActivity 작업에 대한 작업 일괄 처리 커밋 재시도를 워크플로로 모델링해야 합니다. 이 작업은 일반적으로 TransactionScopeActivity 외부의 예외 처리기 및 while 루프 형식으로 이루어집니다.

기본 SqlTrackingService 재시도(비트랜잭션 모드에서 실행되는 경우) 및 기본 SqlWorkflowPersistenceService는 작업 일괄 처리 커밋과 관련 없는 SQL 관련 작업을 제어합니다. 여기에는 만료된 타이머에 대한 확인 작업 및 워크플로 인스턴스 로드 작업 등이 포함됩니다.

부하 분산 및 프런트 엔드 크기 조정

WF를 호스팅하는 응용 프로그램은 부하 분산 시나리오 및 프런트 엔드 크기 조정 시나리오를 관리해야 합니다. WF에서는 서로 다른 호스트 응용 프로그램 인스턴스가 동일한 보존 또는 추적 SQL 데이터베이스를 가리킬 수 있도록 돕는 기본 SQL 기반 서비스 및 엔진을 지원합니다.

여러 개의 호스트 응용 프로그램이 동일한 보존 저장소에 연결되어 있는 경우 이 중 어느 응용 프로그램이든지 데이터베이스로부터 모든 형식의 워크플로 인스턴스를 로드할 수 있습니다. WF의 기본 SqlWorkflowPersistenceService에 서는 호스트가 동일한 보존 저장소를 공유하더라도 특정 호스트에 로드될 워크플로 형식이나 인스턴스를 할당할 수 없습니다. 이 동작이 사용자의 호스트 응용 프로그램 요구에 맞지 않는 경우에는 사용자 지정 보존 서비스를 구현해야 합니다.

여러 개의 호스트 응용 프로그램이 동일한 보존 저장소를 사용하는 경우 WF 런타임 엔진이 잠금 구문을 제공하여 프런트 엔드 크기 조정 시나리오를 지원합니다. 잠금 구문을 사용하면 한 응용 프로그램에서 이미 로드한 워크플로 인스턴스를 다른 응용 프로그램이 로드할 수 없습니다. WorkflowPersistenceService 클래스를 사용하면 SaveWorkflowInstanceState() 메서드에 매개 변수를 제공하여 데이터 저장소에 있는 워크플로 인스턴스의 상태 정보 잠금 해제 여부를 지정하고, UnlockWorkflowInstanceState() 메서드를 통해 이전에 잠겨 있던 워크플로 상태 정보의 잠금을 해제하여 워크플로 런타임 엔진 기능을 지원할 수 있습니다. 잠금을 구현하는 보존 서비스에서 LoadWorkflowInstanceState() 메서드를 호출하면 워크플로 인스턴스의 상태 정보가 잠깁니다.

워크플로 인스턴스 잠금 구문에 대한 자세한 내용은 WF Programming Guide의 Windows Workflow Persistence Services 및 Creating Custom Persistence Services 섹션을 참조하십시오.

기본 런타임 서비스

WF 런타임 엔진은 런타임 서비스를 사용하여 워크플로를 실행합니다. 런타임 서비스 모델을 통해 호스트 응용 프로그램은 융통성 있게 WF 런타임 엔진에 다양한 서비스를 제공할 수 있습니다. 이 섹션에서는 WF가 제공하는 런타임 서비스 및 이러한 서비스의 기본 구현 방법에 대해 설명합니다.

런타임 서비스 구현에 대한 자세한 내용은 WF Programming Guide의 Windows Workflow Foundation Services 및 Developing Windows Workflow Foundation Services 섹션을 참조하십시오.

WF 런타임은 4개의 서비스를 제공하며, 이러한 서비스는 기본 형태로 구현할 수도 있고 호스트 응용 프로그램이 자체 서비스를 구현하여 워크플로 런타임에 제공할 수도 있습니다.

워크플로 트랜잭션 서비스

Windows 워크플로 트랜잭션 서비스(WorkflowCommitWorkBatchService)는 '보존 지점'이라고 하는 작업 일괄 처리 커밋에 관한 사용자 지정 논리를 사용하기 위한 것입니다. 작업 일괄 처리가 커밋되면 런타임이 현재 트랜잭션 서비스를 호출하고 실제 작업 일괄 처리 커밋을 담당할 대리자를 호출에 전달합니다. 커밋 수행은 여전히 런타임에서 담당하지만 서비스를 프로세스에 삽입하여 커밋 프로세스를 사용자 지정할 수 있습니다.

WF 프레임워크에서는 사용자 소유의 트랜잭션을 외부에서 워크플로 인스턴스로 가져오는 기능을 지원하지 않습니다. WorkflowCommitWorkBatchService가 지원하는 앰비언트 트랜잭션 형식은 워크플로 인스턴스가 생성한 트랜잭션뿐입니다. 워크플로 런타임을 실행하는 호스트 응용 프로그램이 생성한 앰비언트 트랜잭션은 현재 스레드에서 일시적으로 제거되므로 부작용이 줄어듭니다. 워크플로가 유휴 상태를 벗어나면 원본 앰비언트 트랜잭션을 포함하는 호스트 응용 프로그램이 다시 스레드로 돌아옵니다.

WF에서는 트랜잭션 서비스에 대한 두 가지 기본 구현 방법(DefaultWorkflowCommitWorkBatchService와 SharedConnectionWorkflowCommitWorkBatchService)을 제공합니다.

WF 런타임 엔진에는 워크플로 트랜잭션 서비스가 필요합니다. 기본적으로 DefaultWorkflowCommitWorkBatchService가 사용됩니다. 호스트 응용 프로그램은 DefaultWorkflowSchedulerService를 SharedConnectionDefaultWorkflowCommitWorkBatchService 또는 사용자 지정 서비스로 바꿀 수 있습니다.

DefaultWorkflowCommitWorkBatchService

워크플로 런타임 엔진이 시작되면 DefaultWorkflowCommitWorkBatchService 인스턴스가 생성되어 WorkflowRuntime에 추가됩니다(다른 트랜잭션 서비스가 추가되지 않은 경우). 이 서비스는 각 데이터베이스 연결에 대해 .NET Framework 트랜잭션을 생성합니다. 예를 들어 SQL 추적 서비스와 SQL 보존 서비스 간의 연결은 공유되지 않습니다. 사용자의 워크플로에서 이 서비스를 사용하면 데이터 무결성에 필요한 트랜잭션을 지원할 수 있습니다.

SharedConnectionWorkflowCommitWorkBatchService

이 서비스는 여러 개체 간에 공유되는 연결을 사용하는 데이터베이스 트랜잭션에 사용됩니다. 호스트 응용 프로그램이 이 서비스를 사용하려면 AddService 메서드를 사용하거나 구성 파일을 통해 WorkflowRuntime에 서비스를 추가해야 합니다.

워크플로 스케줄러 서비스

워크플로 인스턴스가 비동기 모드에서 처리되는지 아니면 수동 동기 모드에서 처리되는지에 관계 없이 워크플로 스케줄러 서비스는 워크플로 런타임 엔진에서 워크플로 인스턴스를 예약하는 방법을 관리합니다. WF에서는 WorkflowSchedulerService에 대해 두 가지 기본 구현 방법(DefaultWorkflowSchedulerService와 ManualWorkflowSchedulerService)을 제공합니다.

워크플로를 실행하기 위해 WF 런타임 엔진에는 워크플로 스케줄러 서비스가 필요합니다. 기본적으로 DefaultWorkflowSchedulerService가 사용됩니다. 호스트 응용 프로그램은 DefaultWorkflowSchedulerService를 ManualWorkflowSchedulerService 또는 사용자 지정 서비스로 바꿀 수 있습니다.

DefaultWorkflowSchedulerService

DefaultWorkflowSchedulerService는 워크플로 런타임 엔진에서 워크플로 인스턴스를 비동기 방식으로 실행하는 스레드를 생성 및 관리하며 기본적으로 런타임 스레드 풀에 여러 개의 워크플로 인스턴스가 대기할 수 있도록 지원합니다. WorkflowRuntime에 다른 워크플로 스케줄러 서비스 인스턴스가 추가되지 않은 경우 기본적으로 DefaultWorkflowSchedulerService가 사용됩니다.

ManualWorkflowSchedulerService

ManualWorkflowSchedulerService는 워크플로 인스턴스의 동기식 실행에 사용됩니다. 이 서비스를 사용하는 경우 워크플로 인스턴스는 호스트 응용 프로그램의 호출 스레드에서 실행되므로 워크플로 인스턴스가 유휴 상태로 될 때까지 호스트 응용 프로그램의 실행이 차단됩니다.

워크플로 보존 서비스

보존 서비스는 워크플로 인스턴스 상태의 저장 및 검색(로드 및 언로드)을 담당합니다. WF에서는 보존 서비스에 대한 기본 SQL 기반 구현 방법(SqlWorkflowPersistenceService)을 제공합니다.

SqlWorkflowPersistenceService

이러한 기본 구현은 상태 및 타이머 정보를 SQL Server/MSDE에 저장합니다. SqlWorkflowPersistenceService는 워크플로 트랜잭션에 참여하여 액세스 잠금을 구현합니다. 또한 SqlWorkflowPersistenceService는 SQL 서버를 사용할 수 없는 경우 재시도할 수 있는 기능을 제공합니다. 이 기능은 서비스에서 EnableRetries 속성을 설정하여 제어할 수 있습니다. SqlWorkflowPersistenceService에 대한 자세한 내용은 WF Programming Guide를 참조하십시오.

워크플로 추적 서비스

추적 서비스는 추적 프로필과 추적 정보 저장소를 관리합니다. WF에서는 SqlTrackingService 클래스에서 구현되는 추적 서비스에 대한 기본 SQL 기반 구현 방법을 제공합니다.

SqlTrackingService

이 구현에서는 추적 프로필 및 데이터를 SQL Server/MSDE에 저장합니다. 이 서비스는 다음을 지원합니다.

  • 기본적으로 워크플로 트랜잭션에 참여합니다. 이 동작은 SqlTrackingService.IsTransactional 속성이 제어합니다.
  • 모든 형식에 기본 추적 프로필을 사용하거나 워크플로 형식 또는 워크플로 인스턴스에 따라 추적 프로필을 다르게 연결하는 메커니즘을 제공합니다.
  • 라이브 및 주문형 분할 기능을 제공하여 데이터 유지 관리를 지원합니다.

    데이터 유지 관리에 대한 자세한 내용은 WF Programming Guide의 Data Maintenance with SqlTrackingService 섹션을 참조하십시오.

또한 WF에서는 SqlTrackingService에 저장된 추적 데이터를 쿼리하는 데 사용할 수 있는 SqlTrackingQuery API를 제공합니다. SqlTrackingService에 대한 자세한 내용은 WF Programming Guide를 참조하십시오.

결론

WF에서는 워크플로 실행 및 워크플로 상태 관리를 위한 런타임 엔진과 서비스를 제공합니다. 호스트 응용 프로그램 또는 프로세스는 WF 워크플로를 호스팅할 수 있도록 작성되어야 하며, 이 호스트 응용 프로그램은 워크플로 런타임 엔진에 다양한 런타임 서비스를 제공해야 합니다. WF의 기본 런타임 서비스는 일반적인 시나리오를 따르도록 되어 있지만, 기본 서비스가 호스트 응용 프로그램의 요구에 맞지 않는 경우 호스트 응용 프로그램은 사용자 지정 서비스를 구현하여 워크플로 런타임에 제공해야 합니다.

또한 WF에서는 실행 중인 워크플로 인스턴스를 관리 및 모니터링하고 호스트 응용 프로그램의 안정성 및 고가용성을 지원하기 위한 인프라를 제공합니다. 호스트 응용 프로그램은 호스트 관련 시나리오를 기반으로 하는 다양한 옵션의 사용 방법을 지정해야 합니다.

추가 정보

워크플로 기술을 처음 접하는 개발자들의 경우 다음 리소스와 함께 이 기사를 참조하면 이 기술을 배운 후 활용하여 생산성을 빠르게 향상시킬 수 있을 것입니다.

  • MSDN 워크플로 개발자 센터(영문)
    • 웹 캐스트 및 실습에 대한 문서와 링크가 수록되어 있습니다.
  • 커뮤니티 사이트 샘플(영문)
  • Windows SDK 문서(영문)
    • Windows Foundation SDK가 없으면 Windows SDK 사이트 (영문)에서 다운로드하십시오. Windows Workflow SDK (영문)는 Windows SDK의 일부입니다. 여기에는 WF Programming Guide 참조, API 참조 및 다양한 기술 및 응용 프로그램 샘플 (영문)이 수록되어 있습니다.
  • MSDN 워크플로 포럼(영문)
    • WF 런타임 호스팅이나 WF에 대한 일반적인 질문이 있는 경우 이 토론 포럼을 방문하십시오.


저자 소개

Moustafa Khalil Ahmed는 Microsoft Corp.(WA, Redmond)의 WF 팀 프로그램 관리자입니다. 2000년 Microsoft에 입사한 이후 다양한 서버 구성 요소 개발을 담당하고 있으며 Microsoft BizTalk Server를 포함한 4개의 서버 제품 출시에 참여하였습니다. Microsoft에 입사하기 전 Moustafa는 ITWorx(Egypt, Cairo)에서 소프트웨어 엔지니어, 비즈니스 분석가 및 고객 관리자로 일했습니다.

".NET" 카테고리의 다른 글
  • Windows Workflow Foundation 호스팅에 대한 소개 (0)2007/07/23
  • Windows Workflow Foundation 규칙 엔진 소개 (0)2007/07/23
  • Introduction to Windows Communication Foundation (0)2007/06/15
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
2007/07/23 09:41 2007/07/23 09:41
Posted by webdizen
Tags WF, Windows Workflow Foundation
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3087

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/07/23 09:37

Windows Workflow Foundation 규칙 엔진 소개

Jurgen Willis
Microsoft Corporation

업데이트한 날짜: 2006년 2월

적용 대상:
Windows WF(Workflow Foundation) 베타 2
Visual Studio 2005

요약: 이 기사에서는 Windows WF(Workflow Foundation)의 규칙 엔진 기능에 대한 개요를 제공합니다. WF에서 조건과 규칙 집합이 어떻게 사용되는지와 전달 체인, 추적 및 트레이싱을 비롯한 규칙 모음의 동작에 대해서 설명합니다(18페이지/인쇄 페이지 기준).

참고 이 기사는 베타 2를 기준으로 작성되었으므로 여기 설명된 작업을 다음 버전의 Windows Workflow Foundation에서 수행하려면 변경이 필요할 수 있습니다.

목차

소개
Windows Workflow Foundation의 규칙 개요
규칙 평가
전달 체인
전달 체인 제어
추가 모델링 설명
추적 및 트레이싱
결론
추가 정보

소개

Microsoft는 Windows WF(Workflow Foundation)의 출시에 맞춰 WinFX 개발자 플랫폼에 새로운 규칙 기능을 도입했습니다. 새로 도입된 이 기능은 작업 실행 동작을 제어하는 간단한 조건에서부터 모든 기능을 갖춘 전달 체인 규칙 엔진에 의해 실행되는 복잡한 규칙 집합에 이르기까지 다양합니다.

규칙 기능을 사용하면 전체 비즈니스 프로세스 범위 내에서 응용 프로그램 논리 단위의 선언적 모델링이 가능합니다. 규칙 엔진 기술에 대한 샘플 시나리오로 순서 검증, 가격 계산, 홍보 강화, 예외 프로세스 관리, 클래임 판결 및 관리를 들 수 있습니다.

이 기술을 개발한 주요 목적은 워크플로와 규칙이 완전하게 통합된 환경을 제공하기 위해서입니다. 일반적으로 업계 전체에서 규칙과 워크플로는 전혀 다른 기술로 취급되고 있으며 대개 서로 다른 공급업체에 의해 제공됩니다. 타사 규칙 엔진이 워크플로 및 BPM(비즈니스 프로세스 관리) 공급자로부터 기본 제공되거나 통합되는 경우도 종종 있지만 개발자나 관리자 환경이 복잡하고 어려운 것은 확실합니다.

Microsoft는 개발자가 워크플로의 모든 지점에서 규칙을 쉽게 통합할 수 있도록 WF에서 워크플로와 규칙 모델링 간 원활한 연계가 가능한 개발자 환경을 제공하는 데 주력했습니다. 이제 개발자들은 자신의 결정이 통합에 미칠 영향을 걱정하지 않고 워크플로 모델, 규칙, 코드 등 논리를 모델링할 대상을 결정할 수 있습니다. 그러면서도 워크플로 범위 밖에서 규칙을 실행할 수 있는 기능은 그대로 유지됩니다.

두 번째 주요 목적은 개발자들이 규칙 정의에 사용할 수 있는 손쉬운 모델을 제공하기 위한 것입니다. 지금까지 규칙 기술은 뛰어난 전문성을 갖춘 규칙 전문가 그룹에서만 사용하는 틈새 기술이었습니다. 도구의 개선을 통해 규칙 사용자의 범위가 넓어지긴 했지만 모델에 따라 기반 엔진의 구현 기술에 대한 전문가급 지식이 필요한 경우도 종종 있습니다. Microsoft는 플랫폼에서 규칙 기능을 제공함으로써 대다수 .NET 개발자 커뮤니티는 물론 궁극적으로 비개발자들까지도 사용할 수 있는 모델을 채택했습니다. 어떤 기술이든 해당 분야에 대한 일정 수준의 전문 지식이 필요하겠지만 Microsoft의 목표는 모든 .NET 개발자들이 규칙 모델을 빠르게 습득하여 이를 자신의 응용 프로그램에 쉽게 통합할 수 있도록 하는 것입니다.

WF는 사용이 편리한 모델을 제공할 뿐 아니라 전달 체인 평가와 정확한 평가 제어가 필요한 복잡한 규칙 시나리오를 지원하는 강력한 평가 엔진도 제공합니다. 이러한 기능은 다양하게 확장이 가능한 형태로 제공되기 때문에 개발자들은 Microsoft 플랫폼을 기반으로 다양한 시나리오를 지원할 수 있는 규칙 기능을 제공할 수 있습니다.

이 문서에서는 WF에서 제공하는 규칙 기능의 기술적 측면을 소개하고 사용 가능한 기능 및 이러한 기능의 사용법에 대해서 설명합니다. 이 문서의 마지막에는 WF의 규칙 기능을 알아보는 데 참조할 수 있는 추가적인 자료 목록이 나와 있습니다.

Windows Workflow Foundation의 규칙 개요

규칙 기술은 WF에서 작업에 대한 조건과 정책 작업의 전달 체인 규칙 집합이라는 두 가지 주요 방식으로 사용됩니다. 전달 체인에 대해서는 이 문서의 뒷부분에서 설명하겠지만 간단히 말하자면 전달 체인이란 한 규칙의 작업이 다른 종속 규칙을 다시 평가하도록 만드는 기능입니다.

작업 조건

조건은 WF에 제공하는 다음과 같은 네 가지 작업에서 사용됩니다.

  • IfElseBranch
  • While
  • 복제기
  • CAG(ConditionedActivityGroup)

조건은 작업의 실행 동작을 제어하는 데 사용됩니다. 예를 들어 지정된 IfElseBranch의 실행 여부를 결정하는 데 사용될 수 있습니다. 조건은 코드 내에 구성된 처리기가 있는 CodeConditions나 RuleConditionReference의 형태로 지정할 수 있습니다. RuleConditionReference는 워크플로 프로젝트의 워크플로와 연결된 .rules 파일의 RuleCondition 정의를 가리킵니다. 그림 1에서는 Visual Studio에서의 사용 예를 보여 줍니다.

그림 1. WF 규칙 조건 작성

규칙 조건 추가

IfElse 작업 내부의 IfElseBranch 작업에 규칙 조건을 추가하는 단계는 다음과 같습니다.

  1. IfElseBranch와 같이 조건이 있는 작업을 선택하면 Properties 표에 Condition 속성이 표시됩니다.
  2. Condition 속성에 대한 드롭다운에서 CodeCondition 또는 RuleConditionReference를 선택할 수 있습니다.
  3. RuleConditionReference를 선택할 경우 ConditionName 및 Expression 속성이 표시되도록 Condition 속성을 확장할 수 있습니다.
  4. 조건 이름을 입력한 후 Expression 속성의 줄임표를 클릭하여 Rule Condition Editor를 시작합니다. 이 편집기에서는 개발자가 Intellisense와 같은 지원을 통해 텍스트 형태로 조건을 입력할 수 있습니다. 입력한 텍스트는 RuleCondition의 연결된 개체 모델 표현으로 구문 분석됩니다.
  5. OK를 선택하면 RuleCondition 정의가 프로젝트에 추가되는 <WorkflowName>.rules 파일로 serialize됩니다.

편집기에 this.를 입력하면 워크플로의 필드 또는 속성을 참조할 수 있습니다. 점을 입력하면 워크플로의 멤버를 선택할 수 있는 Intellisense와 비슷한 메뉴가 나타납니다. 멤버를 직접 입력할 수도 있습니다. this.order.Total과 같은 중첩 호출도 가능합니다. 코드에서처럼 클래스 이름 다음에 메서드 이름을 입력하여 참조된 형식에 대해 static 메서드를 호출할 수 있습니다.

식에 사용할 수 있는 관계 연산자는 다음과 같습니다.

  • 같음("==" 또는 "=")
  • 보다 큼(">")
  • 크거나 같음(">=")
  • 보다 작음("<")
  • 작거나 같음("<=")

또한 다음과 같은 산술 연산자를 사용할 수 있습니다.

  • 더하기("+")
  • 빼기("-")
  • 곱하기("*")
  • 나누기("/")
  • 나머지("MOD")

다음과 같은 연산자를 사용하여 식을 결합하거나 부정할 수 있습니다.

  • AND("AND", "&&")
  • OR("OR", "||")
  • NOT("NOT", "!")
  • 비트 AND("&")
  • 비트 OR("|")

개발자가 코드 조건 대신 규칙 조건을 사용하는 가장 큰 이유는 규칙 조건은 모델의 일부가 되고 워크플로 인스턴스 실행 시 런타임에 동적으로 업데이트할 수 있기 때문입니다. 규칙 조건의 두 번째 이점은 모델의 일부이기 때문에 모델을 기반으로 정교한 도구를 구축할 수 있어 추가적인 작성 환경을 제공하고 종속성 관리, 조건 간 분석 등의 작업을 지원할 수 있다는 것입니다.

정책 작업

정책 작업은 규칙 집합의 정의와 실행을 캡슐화합니다. 규칙 집합이란 실행 가능한 기능 집합을 갖춘 규칙의 모음이며 규칙은 워크플로 멤버에서 작동하는 If-Then-Else 식입니다. 이에 대한 설명은 그림 2에 나와 있으며 규칙 조건에서 설명한 내용과 유사합니다.

그림 2. WF 규칙 집합 작성

정책 작업 추가

정책 작업에 대한 규칙 집합을 구성하기 위한 단계는 다음과 같습니다.

  1. 도구 상자에서 워크플로 디자이너로 정책 작업을 끕니다.
  2. RuleSetReference에서 규칙 집합의 이름을 입력합니다.
  3. RuleSet Definition 필드에서 줄임표를 선택하여 Rule Set Editor를 시작합니다.
  4. 편집기의 목록 상자에 규칙 모음이 나타납니다.
  5. 규칙을 선택하면 조건, Then 작업, Else 작업 및 추가 속성 집합이 표시됩니다.
  6. Rule Condition Editor에서처럼 텍스트로 조건, Then 작업 및 Else 작업을 입력하여 관련 개체 모델 표현으로 구문 분석합니다. 규칙은 워크플로의 멤버에 대해 작성됩니다.
  7. OK를 선택하면 워크플로와 연결된 .rules 파일로 규칙 집합이 serialize됩니다.

RuleSetReference 속성에서 줄임표를 선택하면 그림 3과 같이 기존 규칙 집합을 선택하거나 규칙 집합을 추가 또는 제거하거나 이름을 바꿀 수 있는 편집기가 시작됩니다.

그림 3. WF 규칙 집합 브라우저

규칙 평가

규칙 집합의 각 규칙에는 기본값이 0인 우선 순위 값이 있습니다. 규칙 집합의 규칙은 우선 순위 값에 따라 정렬된 컬렉션으로 간주할 수 있습니다. WF 규칙 평가기는 규칙을 개별적으로 평가하고 규칙의 조건 평가 결과에 따라 규칙의 작업을 실행합니다.

평가 방식은 다음과 같은 절차를 통해 개념적으로 설명할 수 있습니다.

  1. 활성 규칙의 목록을 대상으로 평가가 시작됩니다.
  2. 가장 높은 우선 순위를 가진 규칙을 찾습니다.
  3. 규칙을 평가하고 Then/Else 작업을 적절히 실행합니다.
  4. 규칙의 작업이 목록에 있는 우선 순위가 높은 이전 규칙에서 사용한 필드/속성을 업데이트할 경우 이전 규칙을 다시 평가하고 Then/Else 작업을 적절히 실행합니다. 이때 현재 규칙과 종속성이 있는 규칙만 다시 평가됩니다.
  5. 규칙 집합의 모든 규칙을 평가할 때까지 프로세스를 계속합니다.

간단한 예제를 살펴보도록 합시다. 다음과 같은 규칙 집합이 있다고 가정하는데 여기서 A와 B 등의 알파벳은 워크플로의 데이터를 나타냅니다.

규칙4(우선 순위=4)

IF A = 15
THEN B = 5

규칙3(우선 순위=3)

IF C = 5
THEN B = 10

규칙2(우선 순위=2)

IF D = 2
THEN A = 15

규칙1(우선 순위=1)

IF B = 5
THEN E = 7

다음과 같은 입력 데이터가 있다고 가정해 봅시다.

  • A = 0
  • B = 0
  • C = 5
  • D = 2
  • E = 0

평가는 다음과 같이 진행됩니다.

  • 규칙4를 평가합니다. 결과는 false이며 Else 작업이 없기 때문에 더 이상 작업이 실행되지 않습니다.
  • 규칙3은 true로 평가되므로 B = 10으로 설정하는 작업이 실행됩니다. 규칙4는 B의 값에 종속적이지 않으므로 규칙2 평가로 넘어갑니다.
  • 규칙2가 true로 평가되어 A = 15로 설정하는 작업이 실행됩니다.
  • 규칙4는 조건에 A의 값을 사용하므로 A의 값이 다시 평가됩니다. 결과는 true이며 B = 5로 설정하는 작업이 실행됩니다. 규칙3과 규칙2는 조건이 A의 값에 종속적이지 않으므로 다시 평가되지 않습니다. 앞의 어떤 규칙도 B의 값에 종속적이지 않으므로 규칙1 평가로 넘어갑니다.
  • 규칙1이 true로 평가되고 E = 7로 설정하는 작업이 실행됩니다.

결과 데이터 집합은 다음과 같습니다.

  • A = 15
  • B = 5
  • C = 5
  • D = 2
  • E = 7
전달 체인

앞서 살펴보았듯이 체인은 규칙 간 식별된 종속성을 기준으로 합니다. 보다 구체적으로 말하자면 한 규칙의 작업과 다른 규칙의 조건 간의 종속성을 기준으로 한다고 할 수 있습니다. 이러한 종속성은 다음 세 가지 방법 중 하나로 식별하거나 선언할 수 있습니다.

  • 암시적
  • 특성 기반
  • 명시적
암시적

암시적 종속성은 엔진에 의해 자동으로 식별됩니다. 규칙 집합이 처음 실행되면 규칙의 조건에서 읽거나 작업에 쓰는 필드/속성을 평가하여 각 규칙을 분석합니다. 이러한 분석은 조건의 식과 작업의 문을 순서에 따라 평가하는 방식으로 수행됩니다. 예를 들어 다음과 같은 규칙이 있다고 가정해 봅시다.

규칙1

IF this.subtotal > 10000
THEN this.discount = .05

규칙2

IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal

엔진은 규칙을 평가하여 규칙1이 subtotal 필드를 읽고 discount 필드를 쓴다는 사실을 확인합니다. 규칙2는 discount 및 subtotal 필드를 읽고 total 필드를 씁니다. 따라서 규칙2는 규칙1에 종속되므로 엔진에서는 규칙1이 Then 작업을 실행할 때마다 규칙2가 평가 또는 다시 평가되도록 합니다.

종속성은 리프 노드 수준에서 식별됩니다. 다음 규칙 집합을 예로 들어 보겠습니다.

규칙1

IF this.order.Subtotal > 10000
THEN this.order.Discount= .05

규칙2

IF this.order.Discount > 0
THEN this.order.Total = (1-this.order.Discount) * this.order.Subtotal

규칙3

IF this.order.CustomerType = "Residential"
THEN ...

여전히 규칙1과 2 사이에는 종속성이 있지만 규칙1 또는 2 중 어느 것도 CustomerType 속성을 업데이트하지 않으므로 규칙3은 이러한 규칙에 종속되지 않습니다. 즉, 종속성은 Order 개체 자체가 아닌 CustomerType 속성 수준에서 식별됩니다.

WF는 암시적 체인을 통해 사용자에게 필요한 체인 작업 중 대부분을 처리하므로 사용자는 명시적으로 모델을 업데이트할 필요가 없고 사실상 대부분의 경우 체인이나 체인의 필요성에 대해 알 필요가 없습니다. 암시적 체인은 복잡한 규칙 집합의 모델링을 보다 용이하게 하며 더욱 강력하면서도 대부분의 작업을 보이지 않게 수행하는 엔진의 기능을 담당하게 됩니다. 그러나 보다 복잡하고 구체적인 시나리오에 사용할 수 있도록 특성 기반 체인과 명시적 체인을 제어하는 두 가지 다른 방법도 제공됩니다.

특성 기반

규칙 내 메서드 호출의 경우 수행되는 읽기 및 쓰기를 명확하게 평가하는 것이 더욱 어렵습니다. 이 문제를 해결하기 위해 WF에서는 메서드에 적용하여 각 작업의 종류를 표시할 수 있는 다음과 같은 세 가지 특성을 제공합니다.

  • RuleRead
  • RuleWrite
  • RuleInvoke

메서드에 RuleRead 특성을 지정하면 메서드가 표시된 속성을 읽음을 나타냅니다. 마찬가지로 RuleWrite 특성은 메서드가 특정 필드 또는 속성을 업데이트함을 표시하는 데 사용할 수 있습니다. 암시적 종속성에서 사용한 첫 번째 두 규칙이 다음과 같이 다시 작성되었다고 가정해 봅시다.

규칙1

IF this.subtotal > 10000
THEN this.SetDiscount(.05)

규칙2

IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal

SetDiscount 메서드는 다음과 같이 특성을 지정할 수 있으며, 이렇게 하면 규칙2가 discount 필드를 사용하므로 규칙1에 종속되어 있음을 엔진에서 식별할 수 있게 됩니다.

[RuleWrite("discount")] 
void SetDiscount(double requestedDiscount)
{
...//discount 필드를 업데이트하는 코드
}(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

RuleInvoke 특성은 연결된 메서드 호출로 인한 종속성을 나타내는 데 사용할 수 있습니다. 예를 들어 규칙과 메서드를 다음과 같이 수정한다고 가정해 봅시다.

규칙1

IF this.subtotal > 10000
THEN this.SetDiscountWrapper(.05)

규칙2

IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal
[RuleInvoke("SetDiscount")]
void SetDiscountWrapper(double requestedDiscount)
{
...
SetDiscount(requestedDiscount);
...
}

[RuleWrite("discount")]
void SetDiscount(double requestedDiscount)
{
}

규칙2의 조건은 SetDiscountWrapper를 호출하고 이 메서드는 discount 필드에 값을 쓰는 SetDiscount를 호출합니다. RuleInvoke 특성을 사용하면 이러한 간접 쓰기를 선언하여 엔진에서 감지하도록 할 수 있습니다.

특성 경로에서 참조되는 필드나 특성은 메서드와 동일한 클래스의 필드나 특성을 참조한다는 점이 중요합니다. 실행을 위해 규칙 집합에 반드시 루트 개체를 전달할 필요는 없기 때문입니다. 예를 들어 Order의 특성을 다음과 같이 지정할 수 있습니다.

public class Order
{
private double discount;

public double Discount
{
get { return discount;}
set { discount = value;}
}

[RuleWrite("Discount")]
void CalculateDiscount(double requestedDiscount, double weighting)
{
...//discount 필드를 업데이트하는 코드
}
}

그런 다음 워크플로에서 이 클래스를 다음과 같이 사용할 수 있습니다.

public class Workflow1 :  SequentialWorkflowActivity
{
private Order discount;

...
}

규칙2를 실행하면 규칙1이 다시 평가됩니다.

규칙1

IF this.order.Discount > 5
THEN ...

규칙2

IF ...
THEN this.order.CalculateDiscount( 5.0, .7)

특성 사용에 관해 알아두어야 할 몇 가지 추가적인 사항은 다음과 같습니다.

  • 특성은 메서드 호출에 대한 매개 변수가 아닌 Owner 클래스의 필드/속성을 가리킵니다.
  • 규칙 특성에 와일드카드도 사용할 수 있습니다. 예를 들어 RuleWrite("order/*")를 사용하여 "order" 필드에서 참조하는 개체의 모든 필드가 이 메서드에 의해 수정됨을 나타낼 수 있습니다. 이 예에서는 메서드가 Order 클래스가 아닌 워크플로 자체에 있습니다. 그러나 와일드카드는 경로의 끝에서만 사용할 수 있습니다. RuleWrite("*/Discount")와 같은 특성은 올바르지 않습니다.
  • RuleWrite("order")와 같은 특성을 참조 형식과 함께 사용하여 참조가 변경되었음을 나타낼 수 있습니다. 예를 들어 변수가 이제부터는 다른 Order 인스턴스를 가리킴을 나타낼 수 있습니다. IF this.order == this.order2와 같이 인스턴스 참조 자체를 테스트하는 모든 규칙뿐 아니라 변수의 필드/속성을 사용하는 모든 규칙은 영향을 받는 것으로 가정합니다.
  • 메서드 특성을 지정하지 않는 경우 기본 동작은 메서드 호출에서 대상 개체, 즉 메서드가 호출된 개체의 모든 필드/속성을 읽기는 하지만 이러한 필드/속성 중 어느 것에도 쓰지는 않는 것으로 가정합니다. 또한 메서드는 전달된 매개 변수를 읽지만 이러한 매개 변수 중 어느 것에도 쓰지는 않는 것으로 가정합니다. Out 및 ref 매개 변수는 규칙 작업에 사용될 경우 데이터가 기록되는 것으로 가정합니다.
명시적

필드/속성 종속성을 나타내는 마지막 방법은 Update 문을 사용하는 것입니다. Update 문은 필드/속성의 경로를 나타내는 문자열이나 필드/속성 액세스를 나타내는 식을 인수로 취합니다. 예를 들어 다음 두 문 중 하나를 규칙 집합 편집기에 입력하면 워크플로의 Customer 인스턴스의 Name 속성에 Update 문을 생성할 수 있습니다.

Update("this/customer/Name") 

또는

Update(this.customer.Name)

Update 문은 규칙이 표시된 필드/속성에 값을 기록함을 나타냅니다. 따라서 Update 문은 규칙의 필드/속성에 직접 값을 설정하거나 필드/속성에 대해 RuleWrite 특성을 사용하는 메서드를 호출하는 것과 결과가 동일합니다.

Update 명령 역시 와일드카드 사용을 지원합니다. 예를 들어 규칙에 다음 Update 명령을 추가할 수 있습니다.

Update("this/customer/*")

그러면 조건에 Customer 인스턴스의 속성을 사용하는 모든 규칙이 다시 평가됩니다. 즉, 다음과 같은 두 개의 규칙은 각각 다시 평가됩니다.

IF this.customer.ZipCode == 98052
THEN ...

IF this.customer.CreditScore < 600
THEN ...

일반적으로 대부분의 상황에서 사용자가 명시적으로 Update 문을 모델링할 필요는 없습니다. 대개 필요한 종속성 분석과 체인 동작은 암시적 체인을 통해 제공되고 암시적 체인에서 종속성을 식별할 수 없는 경우에는 메서드 특성 지정이 주로 사용됩니다. 일반적으로 메서드에서 종속성을 한 번 식별하고 해당 메서드를 사용하는 많은 규칙에서 그 종속성을 활용할 수 있기 때문에 메서드 특성 지정이 Update 문을 사용하는 것보다 종속성을 나타내는 데 선호되는 방법입니다. 또한 규칙 작성자와 메서드를 구현한 사람이 다르거나 다른 시간에 작업한 경우 메서드 특성 지정을 사용하면 코드에 대해 가장 잘 아는 메서드 작성자가 메서드의 종속성을 식별할 수 있습니다.

그러나 다음 코드에서처럼 워크플로 작성자가 제어하지 않기 때문에 특성을 지정할 수 없는 클래스의 메서드로 필드/속성이 전달되는 경우와 같이 Update 문이 더 적합한 경우도 있습니다.

IF ...
THEN this.customer.UpdateCreditScore(this.currentCreditScore)
Update(this.currentCreditScore)
전달 체인 제어

전달 체인은 규칙 간 종속성에 대한 정의 또는 종속성에 대한 지식 없이도 개별 규칙을 규칙 집합으로 그룹화할 수 있는 매우 강력한 개념입니다.

그러나 일부 시나리오에서는 규칙 작성자가 체인 동작을 보다 잘 제어할 수 있는 기능, 특히 체인 연결을 제한하는 기능이 필요할 수 있습니다. 이 기능을 사용하면 규칙을 모델링하는 사람이 다음과 같은 작업을 수행할 수 있습니다.

  • 잘못된 결과를 초래할 수 있는 규칙의 반복 실행 제한
  • 성능 향상
  • 무한 루프 방지

이러한 고급 제어는 WF 규칙에서 다음과 같은 두 가지 속성에 의해 가능합니다.

  • 규칙 집합의 Chaining Behavior 속성
  • 각 규칙의 Reevaluation Behavior 속성

이러한 속성값은 RuleSet Editor에서 설정할 수 있습니다.

Chaining Behavior 속성

규칙 집합의 Chaining Behavior 속성에 사용할 수 있는 값은 다음 세 가지입니다.

  • Full Chaining
  • Explicit Chaining
  • Sequential

Full Chaining 옵션은 기본값이며 지금까지 설명한 방식에 해당합니다. Explicit Chaining 옵션을 선택하면 암시적 체인과 특성 기반 체인이 해제되고 명시적 Update 문에 대해서만 체인이 연결됩니다. 이렇게 하면 규칙 작성자가 어떤 규칙에 의해 다시 평가 과정이 실행될지를 완벽하게 제어할 수 있습니다. 일반적으로 이 옵션은 과도하게 또는 무한적으로 규칙을 다시 평가하게 되는 순환 종속성을 방지하거나 규칙 집합의 기능적 완전성을 제공하는 데 필요하지 않은 규칙 다시 평가 과정을 제거함으로써 성능을 향상시키기 위해 사용합니다.

마지막 옵션은 Sequential입니다. 이 옵션을 선택하면 엔진이 엄격하게 순차적 방식으로 규칙을 다시 평가하게 됩니다. 각 규칙은 우선 순위에 따라 한 번씩만 평가되며 우선 순위가 높은 규칙은 우선 순위가 낮은 규칙에 영향을 미칠 수 있으나 반대 방향으로는 체인이 연결되지 않기 때문에 영향을 미치지 않습니다. 따라서 이 옵션은 규칙 간 종속성이 있는 경우 명시적 우선 순위를 지정하여 사용해야 합니다.

Reevaluation Behavior 속성

규칙의 Reevaluation Behavior 속성에 사용할 수 있는 값은 다음 두 가지입니다.

  • Always
  • Never

Always는 기본값으로 지금까지 설명한 방식에 해당합니다. 즉, 체인 연결에 따라 다른 규칙의 작업으로 인해 규칙이 항상 다시 평가됩니다. Never는 이름에서 알 수 있듯이 이러한 다시 평가 기능을 해제합니다. 규칙은 한 번만 평가되며 앞서 작업을 수행한 경우에도 다시 평가되지 않습니다. 즉, 규칙이 이전에 평가되고 그 결과로 Then 또는 Else 작업이 수행된 경우 이 규칙은 다시 평가되지 않습니다. 그러나 Then 또는 Else 작업에서 아무 것도 실행하지 않은 경우에는 규칙이 실행된 것으로 표시되지 않습니다.

일반적으로 이 속성은 규칙이 자체 작업이나 다른 규칙에 대해 가지는 종속성으로 인해 무한 루프가 발생하는 것을 방지하기 위해 규칙 수준에서 사용됩니다. 예를 들어 다음 규칙은 자체 무한 루프를 생성하며 이 규칙의 기능적 요구 사항을 충족시키기 위해 다시 평가할 필요가 없습니다.

IF this.shippingCharge < 2.5 AND this.orderValue > 100
THEN this.shippingCharge = 0

또한 규칙을 다시 평가하더라도 OrderValue 필드만 변경된 경우 사용자는 명시적 Update 문에 대해서만 체인을 연결하고 이 Update 문을 관련 규칙 작업에 추가하도록 규칙 집합의 체인 동작을 설정할 수 있습니다. 물론 이 규칙에 ShippingCost의 값이 이미 0이 아닌지 확인하는 조건을 추가할 수도 있지만 체인 제어를 사용하면 업무 요구 사항과는 달리 평가 세부 정보를 기준으로 규칙을 정의해야 할 필요성을 없애 줍니다.

Halt 함수

마지막 제어 방법으로 Halt 함수를 규칙 작업으로 추가하는 방법을 들 수 있습니다. 편집기의 Then 또는 Else 작업 부분에 "Halt"만 입력하면 됩니다. 그러면 규칙 집합의 실행이 즉시 중지되고 호출 코드로 제어권이 되돌아갑니다. 물론 이 함수는 체인 제어 이외의 경우에도 사용할 수 있습니다. 한 예로 특정 기능을 위한 규칙 집합에서 Halt 함수를 사용하여 목표가 달성되면 실행 순환 주기를 단축할 수 있습니다.

추가 모델링 설명 우선 순위 기반 실행

앞서 설명했듯이 사용자가 규칙 집합이나 해당 규칙 집합에 있는 규칙의 하위 집합에 특정 실행 순서를 적용하려는 경우 규칙에 우선 순위 필드를 사용하여 이 순서를 정확하게 정의할 수 있습니다. 이렇게 하면 체인을 사용할 필요가 없어지는 경우가 종종 있으므로 체인 기능을 해제할 수도 있습니다. 많은 경우 명시적 실행 순서를 제공하는 것만으로도 규칙 종속성이 충족되는 것으로 나타났습니다.

그러나 중요한 것은 WF의 전달 실행 방식을 통해 사용자가 실행 순서를 정의할 수 있으나 반드시 정의해야 하는 것이 아니라는 점입니다. 대부분의 경우 규칙 종속성이 충족되도록 엔진에 의해 관계가 자동으로 관리되므로 전달 체인 동작이 수행되면 규칙 우선 순위를 지정하지 않아도 정확한 규칙 집합 결과를 얻을 수 있습니다.

다음과 같은 규칙을 예로 들어 봅시다.

규칙1

IF this.Weather.Temperature < 50
THEN this.Drink.Style = "Latte"

규칙2

IF this.Drink.Style == "Latte"
THEN this.Snack.Style = "Scone"
ELSE this.Snack.Style = "Muffin"

WF에서는 규칙1이 먼저 실행되도록 더 높은 우선 순위를 지정할 수 있습니다. 이렇게 함으로써 규칙2가 평가되기 전에 Drink.Style이 설정됩니다.

그러나 원하는 결과를 얻기 위해 반드시 순서를 지정할 필요는 없습니다. 규칙2가 먼저 평가되었다고 가정해 봅시다. 이 경우 Drink.Style은 null이거나 다른 스타일일 수 있습니다. 그러면 Snack.Style이 Muffin으로 설정됩니다. 그러나 규칙1이 실행되고 Drink.Style을 Latte로 설정하면 규칙2가 다시 평가되므로 Snack.Style은 Scone으로 설정됩니다. 즉, 기본적으로 사용자는 순서를 지정할 수 있으나 그럴 필요가 없는 경우가 많습니다.

컬렉션 처리

어떤 경우에는 컬렉션에 있는 모든 항목에 대해 규칙을 평가해야 할 수 있습니다. 컬렉션에 대한 반복은 여러 가지 방법으로 수행될 수 있으나 그 중 한 가지 방법은 다음과 같은 규칙 패턴을 사용하는 것입니다.

규칙1(우선 순위=2)

//항상 이 규칙을 한 번 실행하여 열거자 생성
IF 1==1
THEN this.enumerator = this.myCollection.GetEnumerator()

규칙2(우선 순위=1)

IF this.enumerator.MoveNext()
THEN this.currentInstance = this.enumerator.Current

규칙3-N(우선 순위=0)

.... //this.currentInstance에 대해 작성된 추가적인 규칙

규칙N+1(우선 순위=-1)

// 항상 평가되는 조건
// this.currentInstance는 매번 평가됩니다.
//this.currentInstance는 변경되지만
// "1==1"은 한 번만 평가됩니다
IF this.currentInstance == this.currentInstance

THEN ...
Update("this/enumerator") //이로 인해 규칙2가 다시 평가됩니다
ELSE ...
Update("this/enumerator")
추적 및 트레이싱 추적

호스트에서 추적 프로파일에 UserTrackPoint를 추가하여 추적 이벤트를 등록하면 규칙 집합 실행 시 호스트에 구성된 추적 서비스로 추적 이벤트가 전송됩니다. 전송되는 RuleActionTrackingEvent는 평가되는 규칙 이름뿐 아니라 조건 평가 결과(true/false)를 제공합니다. 이에 대한 예는 SDK의 RuleActionTrackingEvent 샘플을 참조하십시오.

작업에 대한 규칙 조건의 평가 결과는 추적 작업 실행을 통해 간접적으로 추적할 수 있습니다.

트레이싱

응용 프로그램 구성 파일에 다음과 같은 항목을 추가하면 추가적인 규칙 집합 평가 정보를 로그 파일로 전송할 수 있습니다.

<configuration>
<system.diagnostics>
<switches>
<add name="Rules" value="Information"/>
</switches>
</system.diagnostics>
</configuration>

다음과 같은 정보가 로그 파일로 전송됩니다.

  • 조건 종속성 정보:
    • 예: Rule "ReturnNumberOfStops" Condition dependency: "this/currentFlight/OutboundFlight/NumberOfStops/"
  • 파생 작업에 대한 정보:
    • 예: Rule "ReturnNumberOfStops" THEN side-effect: "this/currentFlight/Score/"
  • 체인 관계:
    • 예: Rule "ReturnNumberOfStops" THEN actions trigger rule "ApprovedFlights"
  • 작업 집합 실행:
    • 예: Rules: "Executing RuleSet FlightRuleSet"
  • 조건 평가:
    • 예: Rules: Evaluating condition on rule "SetDefaultScore"
  • 조건 평가 결과:
    • 예: Rules: Condition evaluated to True
  • 작업 실행:
    • 예: Rules: Evaluation THEN actions for rule"SetDefaultScore"

모든 트레이싱 메시지는 현재 "정보" 수준으로 정의되어 있으므로 규칙 트레이싱을 보려면 구성 파일에서 정보의 수준이나 세부 내용 표시 기준을 조정해야 합니다.

결론

WF는 여러 가지 방법으로 활용하여 다양한 시나리오를 지원할 수 있는 유연한 규칙 기능을 제공합니다. 간단한 작업 조건에서 복잡한 전달 체인 규칙 집합에 이르기까지 이 기술을 사용하면 규칙에 대한 지원을 워크플로에 원활히 통합할 수 있습니다. 또한 모든 .NET 응용 프로그램에 규칙 기능을 제공하기 위해 워크플로 외부에서 규칙 엔진을 사용할 수도 있습니다.

이러한 기능을 통해 사용하면 고급 규칙 집합 및 응용 프로그램 시나리오를 지원할 수 있는 다양성과 확장성은 그대로 유지하면서도 신규 개발자들이 워크플로에 간단한 규칙을 쉽게 통합할 수 있습니다. 다음 섹션에서 소개하는 자료와 함께 이 문서를 참조함으로써 WF 규칙을 처음 접하는 개발자들은 이 기술을 배우고 활용하며 빠르게 생산성을 향상시킬 수 있을 것입니다.

추가 정보
  • MSDN 워크플로 사이트(영문)

    웹캐스트 및 실습에 대한 다양한 문서 및 링크 수록

  • 커뮤니티 사이트 샘플(영문)
    • 외부 규칙 집합 툴킷 워크플로 어셈블리의 외부로 규칙을 분리하는 방법에 대한 예제를 제공합니다.
    • 규칙 집합 분석 규칙 집합에서 규칙 간 그리고 규칙과 데이터 간 관계를 분석할 수 있는 도구입니다.
    • Excel의 규칙 워크플로와 상관없이 규칙을 독립적으로 사용하는 방법에 대한 예제를 제공합니다. Excel의 결정 테이블을 통해 작성된 규칙을 프로그래밍 방식으로 만드는 방법도 설명합니다.
  • SDK 샘플
    • IfElseWithRules IfElse 작업에서 RuleCondition을 사용하는 방법을 보여 줍니다(\Technologies\RulesAndConditions).
    • DynamicUpdateChangingRules 동적 업데이트 API를 사용하여 실행 중인 워크플로 인스턴스에서 RuleCondition을 변경하는 방법을 보여 줍니다(\Technologies\RulesAndConditions).
    • SimplePolicy API를 사용하여 간단한 규칙 집합과 정책 작업을 정의하는 방법을 보여 줍니다(\Technologies\Activities\Policy).
    • AdvancedPolicy 보다 복잡한 규칙 집합을 정의합니다(\Technologies\Activities\Policy).
    • RuleActionTrackingEventSample 추적 공급자에서 규칙 평가 결과를 수집하는 방법을 보여 줍니다(\Technologies\Tracking).
  • MSDN 워크플로 포럼(영문)
    • WF 규칙이나 WF의 전반적인 부분에 대한 질문이 있는 경우 이 토론 포럼을 방문하십시오.

저자 소개

Jurgen Willis는 Windows Workflow Foundation 팀의 프로그램 관리자로 규칙 엔진 기술과 규칙 기반 작업을 책임지고 있습니다. Microsoft에 합류하기 전 Jurgen은 Fortune지 선정 500대 기업에 대한 통합 및 프로세스 관리 솔루션을 설계 및 구현했습니다.

".NET" 카테고리의 다른 글
  • Windows Workflow Foundation 호스팅에 대한 소개 (0)2007/07/23
  • Windows Workflow Foundation 규칙 엔진 소개 (0)2007/07/23
  • Introduction to Windows Communication Foundation (0)2007/06/15
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
2007/07/23 09:37 2007/07/23 09:37
Posted by webdizen
Tags WF, Windows Workflow Foundation
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3085

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/06/15 15:30

Introduction to Windows Communication Foundation

Introduction.

Windows Communication Foundation (WCF) is Microsoft's unified programming model for building service-oriented applications.  It enables developers to build secure, reliable, transacted solutions that interoperate with applications in different platforms.

Interoperability is the fundamental characteristic of WCF. The fundamental communication mechanism is based on Web Services specifications such as SOAP, XML, XSD, WSDL and newly established standards including the WS-* protocols. These specifications address several areas, including basic messaging, security, reliability, transactions, and working with a service's metadata.

WCF relies on WS-Policy and WS-Metadata Exchange to discover information about the communications partners. Reliable communication is essential for most situations (no duplicates messages), and WS-Reliable Messaging would be used to interact with many of the other applications in this scenario. WS-Security and the related specifications might also be used for establishing a secure channel. The specifications support the main security services such as authentication, integrity and confidentiality. WS-Atomic Transaction is very important for managing transactional context involving several transactional resources.

The key point is that WCF implements interoperable Web services, complete with cross-platform security, reliability, transactions, and other services. It also is transport neutral, protocol neutral, and format neutral. For example, services are free to make use of HTTP, TCP, named pipes, and any other transport mechanisms for which there is an implementation. It is possible the WCF-to-WCF communication to be optimized, but all other communication uses standard Web services protocols.

The architecture.

The architecture is based on layers. See on Figure 1.

사용자 삽입 이미지

Figure 1.

This architecture permits programmers to work at several levels. High-level programming leverages the service runtime and doesn't require developers to directly work with messages at all, unless they want to. At the other end of the spectrum, developers can program to the messaging layer and take charge of communication activities.

Contracts

Contracts define various aspects of the message system. The data contract describes how every business object to be on the wire is going to be persisted using XML. The message contract defines specific message parts (header and body) using SOAP protocols, and allows finer-grained control over parts of the message. The service contract specifies the actual method signatures of the service. The policy and binding contracts enable you to specify transport, security details, and other aspects that must be met in order to communicate with a service.

Service Runtime

The service runtime manages the execution of services. It is possible to be specified the run-time behaviors through attributes or configuration settings. For example, a service can indicate that its instances are not thread-safe. Throttling behavior allows you to put limits on the number of connections, sessions, and threads. Error handling behaviors control how errors are handled and reported to clients. Metadata behavior governs how and whether metadata is made available to the outside world. Transaction behavior enables the rollback of transacted operations if a failure occurs. Dispatch behavior is the control of how a message is processed by the WCF infrastructure. Parameter filtering allows preset actions to occur based on filters acting on message headers.

Messaging

The messaging layer illustrates the possible formats and exchange patterns of the data. WS Security channel is an implementation of the WS-Security specification enabling security at the message layer. The WS Reliable Messaging channel enables the guarantee of message delivery. The encoders present a variety of encodings that can be used to suit the needs of the message. The HTTP channel specifies that the HyperText Transport Protocol is used for message delivery. The TCP channel similarly specifies the TCP protocol. The Transaction Flow channel governs transacted message patterns. The Named Pipe channel enables inter-process communication. The MSMQ channel enables interoperation with MSMQ applications.

Activation and Hosting

The specific method by which a service is started is determined by its activation options. And services can be either self-hosted or hosted in the context of another application. The Windows Activation Service (WAS) enables WCF applications to be activated automatically when deployed on a computer running the WAS. Services can also be manually run as executables (.EXE files). A service can also be run automatically as a Windows service. And a service can also be run as a COM+ application.

Services from the Outside.

From the outside, a service is collection of endpoints and a service description. The description is the information necessary for interchange messages with the service. It is used WSDL, XML Schemas, WS-Policy (description of non-functional requirements such as security mechanisms, schedules availability, and quality of service), WS-Metadata Exchange (WS-MEX) for asking the service to describe itself.

The endpoint is a basic unit of communication in Windows Communication Foundation (WCF). It is the physical port. You interchange messages with a service through the endpoints. Each endpoint is made up of three elements: an address, a binding, and a contract. The endpoint's ABC, A stands for address, B stands for binding and C stands for contract.

The address is basically a URI. The format of the address is scheme://myservice.mydomain.com:port/path. For example for accessing a service using http protocol you must write the following address down http://myservice.mydomain.com:8080/myservice/financialservice.svc, or for accessing a service using tcp protocol by the port 2525 you must write the following address down

net.tcp://myservice.mydomain.com:2525/myservice/financialservice.svc.

The binding describes how a service communicates with the partners. It is a collection of details such as transport method, encoding format, reliability requirements, and security requirements. WCF creates a channel stack to satisfy the using binding.

The contract is a collection of operations that specifies what the endpoint communicates to the outside world. Each operation is a simple message exchange, for example a request message and an associated reply message that form a request/reply message exchange.

Conclusion.

This paper is intended to present the fundamental concepts of Windows Communication Foundation as the new programming model for service-oriented development. WCF implements new specifications adopted in standardization organizations such as OASIS, W3C, etc. And it is a good object model that really represents the concepts of the Service-Oriented Architecture.

".NET" 카테고리의 다른 글
  • Windows Workflow Foundation 호스팅에 대한 소개 (0)2007/07/23
  • Windows Workflow Foundation 규칙 엔진 소개 (0)2007/07/23
  • Introduction to Windows Communication Foundation (0)2007/06/15
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
2007/06/15 15:30 2007/06/15 15:30
Posted by webdizen
Tags .NET, WCF, Windows Communication Foundation
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3046

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/04/29 11:52

High-Performance .NET Application Development & Architecture

Introduction

It has always been a goal of project architects to plan an effective strategy from the ground up in regards to an new application. All relevant factors are taken into consideration with respect to the application, from its design and layout to a functional website infrastructure. Pre-.NET strategies and design guidelines still effective now were developed with Microsoft's DNA (Distributed interNet Application) platform. This model successfully served the purpose of architecting N(any number of)-Tier (levels) applications. In its basic sense, as in most robust, distributed applications, you'll architect 3 main layers or Tiers: presentation, business rules and data access.

Now, each independent layer within itself, is another issue altogether. Each one has its own methodologies regarding its perspective best practices. In other words, many factors will determine its proper design, efficient data access and visually pleasing presentation. That is why each Tier, with respect to the overall success and health of your application, should be maximally optimized. So in light of such, we'll go through each Tier and dependent of your situation, offer the very best methods resulting in one robust, secure and high-performance distributed application.

One issue that is slightly bemused is that of Performance and Scalability. However similar these may sound in end-result, none could be further from the truth. Performance is a matter of responsiveness, how well a application performs, as our General .NET Best Practices section below will help you capitalize on. Scalability is another subject slightly paralleling performance only within the confines that it has to be high-performance for all users and not affect anyone else nor the tie up the server for that matter. Highly-scalable applications do their best to absolutely minimize network traffic, and server-side database interaction. By these measures then, could scalability be in collaboration with performance.

At any rate, as aforementioned, architecting a robust application involves the time-tested, structurally sound methodology in having an application made up of separate, easily maintainable components that would require modification only once to effect site-wide changes. This, among other things, creates harmony among IT teams, guaranteeing no one will interfere with or break someone else's work. The parts individually, in this sense, are on par to the sum total. To digress, how many audiophiles would buy a all-in-one stereo unit, rather than various individual high performance components? Right, I think none would.

Anyway, before proceeding on, this article assumes you have some basic .NET and or development experience. Still, it's important to realize that this article is a codified manual of tips and optimization techniques for the more or less experienced developer and, or expert looking for more ways in maximizing an application. Beginners may find it hard in following some sections. Nevertheless, I have made ample effort to direct beginners to any outside sources where more in-depth information is available, should one require it, and recommended.

In any case, we'll begin.

Planning

Planning is the first stage all developers embark on - what to do, how to do it or the best way to do it. Proper planning is key in determining that all aspects of the application are architected correctly and efficiently, and the right direction is chosen. Once this is established, the actual development begins to take place. From this point on you'll begin to deal with the application at all levels and its individual components, and how each requires individual optimization. Thus as a whole it becomes one solid, cohesive application.

Good robust, application architecture insists on it being functional, responsive, secure and user friendly. I'll briefly point out that when presenting an finished application, it must be faultless. People like a speedy application with complementing visually appealing elements. Most users will never fully appreciate all the sophistication it took behind the scenes to make the application work, they initially focus on its design and layout. Although, I won't demonstrate web site and graphics design, I felt it a point needing attention, even thought this article will not demonstrate such. So keep this in mind, that for a fully robust, cohesive application, design and back end do go hand-in-hand, not productively but ultimately. So all parts must be on equal level in making it an cohesive application. Again, even thought this article is really more for .NET project architects, it's a fact that some developers out there simply do it all. For some more info of project planning read - Web Application Development - A Guide to Success.

That aside, prior to dealing with the three archetypal levels inherent to "N-Tier," we'll be discussing some conventional information regarding application and server security. After that, we'll dive right into offering general .NET Best Practices. Then move on to error handling, and further deal with some common .NET errors you may encounter, and finally look into performance testing.

As a final note, .NET's documentation offers an absolutely exhaustive collection of information which is the reason why you'll notice numerous links throughout this article directing you to more in-depth information where applicable and necessary.

Now let's get started with .NET security, and rest assured that our application is being built on a secured platform, and that all security precautions were taken and are intact.

Application/Server Security

Security in .NET, and really in any application, is and should be of the highest importance. It is critical that at all times your application be unbreakable and tamperproof, as much as possible, from anything. Anywhere from someone fiddling with your query string to server / data access authorization to preventing SQL Injection attacks.


As the topic of security is really too vast and complex to be dealt with here in any great detail, we'll offer some generalized pointers and tips to set you off in the right direction with a good overview of the various security methods available. Oh, btw, work closely with your Network Admin, make sure you're both on the same page.

Security in .NET works with two types of concepts:

  1. Authentication : Confirms users identity and credentials in allowing them access, through either valid Windows Accounts or IIS, that include methods such as certificate (SSL), Windows (NLTM or Kerberos), Forms, and Passport authentication.
  2. Authorization : Allows or denies file or url access to a given user based on certain criteria, or on ACL (Access Control List) settings in Windows. Authorization parallels .NET's users and roles settings within its web.config file's <authorization> element node. Note all authorization is always done after authentication.

With which introduces us to the following kinds of security implementations.:

IIS Security

IIS (Internet Information Services) is the first line of defense for application security, since all requests must go through here first. This method of security rests solely on setting certain elements found in IIS's Directory Security / Anonymous Access and Authentication Control tab. This verifies IP/Domains and user accounts via ACLs, that are preset Windows or Active Directory User Accounts. Incidentally, ACL access is verified whether or not anonymous access is enabled. Disabling Anonymous Access will bring up the logon dialog box, that would require you a valid Windows account, unless you're on the same domain.

Generally, for any fully public sites you should leave all default settings to manage Anonymous Access . Windows (Windows Integrated Security) handles this, whereby it assigns its own IUSR_computername account the privilege, and all users of this site fall under the Guest group account.

Moreover, you could also disable anonymous access and integrate Basic Authentication (with disabled Windows Integrated Security) that presents the user with the common dialog-driven box to enter in their credentials. Basic security mode further allows control via a domain controller, though this should not be the sole method in doing such. At any rate, Basic Authentication is the most frequently used security measure because this works well among all browsers, and is commonly used in concert with SSL (Secure Sockets Layer) technology for added security.

Another IIS security measure, though not widely used since all browsers do not support it, is Digest Authentication . This was Microsoft's way of remedying one issue that has befallen Basic Authentication which was sending passwords in plain text, and the solution here sends a digest (Hash encryption) in place of the actual password.

Finally, is Integrated Windows Authentication or NTLM challenge/response protocol. This increased security method, since it doesn't work with Netscape, is not really suited for the Internet, but rather a fully IE-inherent Intranet.

Now that we've got a grasp on the IIS's security options, IIS has many more options within its properties that are adjustable for not only additional security measures, but even performance.

.NET/IIS Permissions

Moving on now, .NET allows programmatic determination of NT group permissions as well, using two .NET (principal objects) classes : WindowsPrincipal (automatically created when using Windows authentication) or GenericPrincipal (customizable event using the WindowsAuthentication_OnAuthenticate event (within the Global.asax file) that occurs during authentication, used with None Authentication mode in web.config):

// With users being members of a Windows account group on a domain: 
if (User.IsInRole ("Domain\\Group")) {
...
// Or via Windows built-in account groups users get added to:
if (User.IsInRole ("BUILTIN\\Administrators")) {
...

Or through user identity and role, including web.config's case-sensitive credential information, as discussed in the next section containing Forms Authentication, and through Windows Authentication:

using System.Security.Principal;
if ((User.Identity.Name == "authorizedUsername") && (User.Identity.IsAuthenticated == true)) {
AllowAccess();
}

Finally, you could also implement the WindowsIdentity Class to validate a Windows user account, using the GetCurrent method:

using System.Security.Principal; 

WindowsIdentity User = WindowsIdentity.GetCurrent();

if ((User.Name == "Domain\\User") && (User.IsAuthenticated == true)) {
AllowAccess();
}

Note: Anonymous Access, as well as impersonation, should be disabled to initiate these kinds of Role-Based Security inquiries, and Authentication Mode should be set to Windows in your web.config file. Otherwise, you'll retrieve .NET's IUSR_machinename user account info - "Domain\ASPNET", or simply nothing at all, due to the enabled impersonation.

Moreover, employing this works as well for applying file authorization through the file's Security Permissions - right-click Properties settings, and URL Authorization.

So now that we've got a good overview on identifying permissions, and since we're on the topic of IIS, we'll briefly discuss some general IIS optimizations that will help our server's overall efficiency and performance.

General IIS Optimizations

  • Make sure Mappings under Home Directory-->Configuration-->Application Configuration has Cache ISAPI Applications enabled, as well as Buffering and other Cache Options under Options
  • Also enable HTTP compression found under the Service tab
  • Adjust the Performance Tuning of your server to an amount of anticipated site activity or hits you may expect to get, and enable Process Throttling in preventing your site from overusing CPU processing, which may indicate an external attack of some kind. Both these adjustments can be made under the Performance tab, within the Web Site's Master Properties
  • Finally, enable performance settings for all your sites under the Default Web Site Properties-->Server Extensions tab, and adjust the performance list box for the appropriate amount of pages that you may have on your site

Web.Config or ASP.NET Security

.NET incorporates this configuration file that is capable of doing a great deal when it comes to security, among other things. For starters, it's the perfect place to store all your appsettings or database connection strings that'll be accessed in your application. Furthermore, everything regarding your application's security can be stored here within its perspective nodes, from allowing specific users and groups access to a particular file location path or multiple location paths to storing account credentials, examine specific HTTP access, and standard Authentication.

A typical web.config configuration file

<configuration> 
      <appSettings> 
        <add key="myDataBase" value=id; _=pw; Data=myDS; _=myCat; Enlist=false;" /> 
      </appSettings> 
      <location path="securePage.aspx"> 
        <system.web="None | Windows | Forms | Passport"/> 
          <forms forms="securepage" loginUrl="login.aspx" /> 
          <credentials passwordFormat="Clear | MD5 | SHA1"> 
            <user name="Jimmy" password="hashed Password"/> 
          </credentials> 
          </authentication> 
          <authorization> 
            <!-- General application authorization -->
            <allow verbs="POST" users="Jimmy, Lumi" roles="Administrators" /> 
            <allow verbs="GET" users="Peter" roles="Debugger Users" /> 
			<!-- URL Authorization --> 
            <allow users="domain1\user, domain2\group" /> 
            <!--Deny anonymous or All Users--> 
            <deny users="? | *" /> 
          </authorization> 
        </system.web> 
      </location> 
      <!--For a second secure page--> 
      <location path="securePage2.aspx"> 
        <!-- Other configuration settings--> 
      </location> 
<configuration> 

As seen, the web.config file is capable of many security measures, from public access to Passport authentication. We'll now discuss them in turn:

<authentication mode="None | Passport | Windows | Forms "/>

Impersonation

.NET allows general, public access through impersonation, where IIS uses its own IUSR_computername account process token to control authentication and authorization. As far as strict security is concerned, this is not really it. That is why it best serves all public sites, and is set in the web.config file like so:

<system.web="true" /> 
</system.web>

The identity key element holding the attribute impersonate, set to true is the equivalent of IIS's Anonymous access, but directly affects your .NET application here more, since it helps overcome any " Access Is Denied " problems with .NET on a public site.

And due to this, your SQL connection string would be this below, providing both your application and SQL Server share the same domain or trusted domains. Otherwise you'll receive the Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON' error.

"Data Source=myDS; Initial Catalog=myCat; Enlist=false; Trusted_Connection=yes"

For more info on .NET Impersonation, readImplementing Impersonation in an ASP.NET Application.

Default/None (Custom) Authentication (IIS's Anonymous Authentication)

The default setting of no authentication or None mode , is typically for public sites (impersonation). When included for this purpose in your configuration file, does two things, helps performance since no extra runtime processing is needed and could allow customized authentication to take effect, whether application wide via the global.asax or HTTP modules, working with the AuthenticateRequest event.

Passport Authentication

As noticed in the authentication key element above, one such technique, different among the others as I'm sure most have seen is .NET Passport authentication mode . This service offered by Microsoft, is a somewhat of a one-stop shopping methodology, enabling you a unified access across many applications.

Windows Authentication (IIS's Integrated Windows / Digest Authentication)

Next, as aforementioned in the IIS section above, is Windows Mode , the default security method, as it indeed relies on Windows Authentication in IIS for its authentication, prior to .NET gaining control. Furthermore, because of its nature it is best suited for Intranet sites.

In the web.config file, the authentication node is set for ACL Windows authorization, and is also based on the elements contained within the authorization nodes for which users, roles or groups are allowed or denied access, or URL authorization. Additionally, within these nodes you may even specify what type of HTTP action is allowed, using the verbs key, and setting it for either POST, GET, HEAD, or DEBUG.

Here we've set Role-Based Security within the following web.config's <allow> element key setting, and in this instance it'll allow only Admins and Debuggers access, and deny everyone else:

<configuration> 
<system.web="Windows" /> 
  <authorization> 
	<allow roles="Administrators, Debugger Users"/> 
	<deny users="*"/> 
  </authorization> 
</system.web> 
</configuration>

Forms Authentication

And finally, there is Forms (Cookie based) Authentication Mode , where your dealing with security based upon criteria captured through a form on your web page. This could be Forms-based (using configuration credentials) or Windows/Role-Based, with account credentials set in place and stored in a database, XML file, text file, or even a remote Web Service, rather than solely in an ACL list. Incidentally, this mode enables good flexibility for creating more personalized sites.

Forms Authentication is your best bet for Internet security-based applications such as an E-Commerce site, since it supports all browsers, is fully customizable, and works well with other security additions such as SSL. Incidentally, cookie size limit with IE5 and higher browsers, has no limit.

Here is an example of Forms-Based Authentication using the FormsAuthentication Class ,

if (FormsAuthentication.Authenticate (userName, userPassword)) { 
//Redirect user back to original URL once logged in or do whatever
FormsAuthentication.RedirectFromLoginPage (userName, boolean);
} else {
Response.Redirect ("login.aspx");
}

with the web.config file holding the user's credentials and authorization:

<authentication mode="Forms"> 
	<forms name=".ASPXAUTH" loginUrl="/login.aspx"> 
	  <credentials passwordFormat="userName" password="encryptedPassword"/> 
	  </credentials> 
	</forms> 
</authentication> 
<authorization> 
	<!--Deny all unauthenticated users-->
	<deny users="?"/> 
</authorization> 

Forms authorization as seen, is controlled via cookies (name attribute) that you name within the forms element key, and the loginUrl or login page to use. This is useful for any pages requiring strict security measures, whereby all unauthorized users get sent to your login page to enter their credentials in a form, and must match the Forms Authentication Credentials before access is granted.

Alternatively, you can utilize the same FormsAuthentication class technique above using a database housing users and roles, as well as utilizing the FormsAuthenticationTicket Class. This method of course has its advantages since a database could store as many credentials as you need. Additionally, if you so choose, you could even store credentials within an XML file, providing you set ACL permissions on its location.

Now, as far as encrypting passwords is concerned, its significance cannot be really stressed enough. It's fortunate however, that it is incredibly easy to do for the above credentials using the (get ready for a long one) FormsAuthentication.HashPasswordForStoringInConfigFile Method. This method uses two parameters, one for the password itself, and the other for the type of hash algorithm you require, SHA1 or MD5:

string encyptedPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Text, "encryptionMethod");

Furthermore, within the web.config file you could add any specific authenticated page and allow certain user's access, based on specific HTTP method and deny access to all all or specific non-authenticated user's. Subsequently, by choosing Forms Authentication as a custom security measure, then bear in mind that Anonymous Authentication should be enabled in IIS.

Read Forms Authentication in .NET's documentation for more info.

Miscellaneous

Now aside from these aforementioned security measures, I'll discuss a few miscellaneous encryption-enabling configuration settings helpful in further securing your application.

If you're concerned about the security of any viewstate data being examined or manipulated in any way, then set enableViewStateMac (MAC stands for message authentication code) property to true, as demonstrated below, and individually within each .aspx page's @Page Directive - EnableViewStateMac="true" as well. This property hash encrypts the viewstate and ensures that it has not been altered in any way. It stores this information in the machine.config file's <machineKey> node element. Luckily, this is already the default in .NET, phew!

Further encryption of any viewstate, or forms authentication cookie data could be physically set within the web.config by adding - <machineKey validation="3DES" /> , .NET's Triple-DES (3DES) encryption or any other encryption method you prefer.

As for sites dealing with file uploading, we both know limits have to be set. Users obviously can't upload to their hearts content, and bog down the server. So setting .NET's HTTP runtime maxRequestLength parameter, for 2 megabytes as demonstrated below, ensures that any larger files are declined.

<system.web="true" enableViewStateMac="true" /> 
	<machineKey validationKey="autogenerate | value" decryptionKey="autogenerate | value" 
		validation="SHA1 | MD5 | 3DES" /> 
	<httpRuntime maxRequestLength="2048" /> 
</system.web>

ADO.NET Security

In all database applications you're dealing with a form of some kind that will be accessed by a user who will enter information to be verified against a database or other means, before any access or data is allowed. Protecting information or access in this situation isn't too difficult a concept, it's simply a matter of storing (preferably encrypted info) into a database, then validating this on login. For more info check out - Using MD5 to Encrypt Passwords in a Database. Almost certainly your site will also include a searchable form for users to enter search criteria in obtaining data.

Aside from these somewhat innocent scenarios, and by virtue of creating forms that enables users the ability to submit information to your application, you're left vulnerable to a malevolent technique exploited by hackers known as SQL Injection . This is a security threat whereby SQL statements or other malicious code, through form fields, are injected in addition to your code and executed, and can cause great damage to your data, whether they retrieve, delete confidential information or simply cause havoc among it!

To prevent SQL Injections you should:

  1. Validate all entered criteria via regular expressions for any mischievous keywords or text. Furthermore, apply HTML Encoding to any inputted text. Fortunately, version 1.1 of the .NET framework, offers superior validation, whereby if any user enters certain un-encoded HTML syntax or characters, .NET will raise an error. Read Request Validation - Preventing Script Attacks for more info.
  2. Use parameterized Stored Procedures (Sprocs) for your queries
  3. Create custom error pages so no one could inadvertently retrieve any data or server information from non-custom error pages.

Some added security measures when working with ADO.NET are:

  1. If caching data, store critical info server-side via session state or with the Cache object. I discussed this in light of a real-world example in my article Drilldown Datagrid Searching with ASP.NET, and read more about the various method for storing state.
  2. SQL server access should be performed through Windows Integrated Security, not "mixed mode," and never leave the default "sa" username and blank password as is, set a good username and password instead, which lead us to number 3.
  3. If possible create a trusted connection to SQL with the Integrated Security set to SSPI (Security Support Provider Interface), as this eliminates the need in storing any user id's and passwords.
    "Data Source=myDS; Integrated Security=SSPI; Enlist=false; Initial Catalog=myCtlg" 

    However, you may encounter - Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON,' where remedying this is to have your Windows Account be a Domain Account and share the same or trusted domains with IIS and SQL Server.
  4. Encrypt your connection strings in your web.config file, or other critical data elsewhere, and then later decrypt them, via the CryptoStream class in your page code, when you need to use them. Also dependent on the size of your data, whether large or small will determine Symmetric or Asymmetric Encryption.
  5. Finally, never hard-code anything, like any of the aforementioned, in your client-side pages.

Code Security

This security measure deals with setting permissions on projects, files, code and resources from anything unauthenticated. The CodeAccessPermission class comes in to play in allowing administrators the power to delegate certain types of access to various resources, thus ensuring authorized access only. It works alongside the trust element key within the web.config file in setting code access security (Full is the default):

<trust level="Full | High | Medium | Low | Minimal" originUrl="url"/>

Read Secure Coding Guidelines for the .NET Framework and Secure Coding Guidelines for more info .

Furthermore, as this also applies to all aspects in this article, if you really, really need the utmost security for very critical data, you do have a couple of nice options:

  1. Retrieve sensitive information and settings using serialization , that converts data to bytes for any type of transmission, that later gets deserialized on the other end.
  2. Directly reference an object's (dll/assembly) metadata (binary info about your dll) and other information or invoke methods contained within it during run time via Reflection using the System.Reflection namespace

Look at ASP.NET Security for more info and if you got the time read Microsoft's 608 page - Building Secure ASP .NET Applications treatise. Furthermore, peruse ASP.NET Architecture as well.

Now that we've dealt with security as an good overview, we now can move on to the actual components involved in making our application's architecture robust and high-performance. We'll first begin looking at some best practices that will determine general application health, before we discuss the best methods with each Tier.


General .NET Best Practices

The best practices to be discussed stem from real-world .NET application use, and have proven themselves very viable and sound in producing very scalable, high-performance applications. Since I'll be dealing with .NET programming a la hand coding and web forms, those interested in strict Visual Basic.NET optimizations should see - Performance Optimization in Visual Basic .NET for more info. Nevertheless, the best practices discussed here, generally apply to VB.NET as well. Moreover, all the tips offered here are general application page optimizations.

  • Right at the top of your .aspx page's @ Page directive, it is always important to disable any features you won't need or use. For example, set EnableSessionState to false on any pages not requiring use of session state, as well as Debug, Trace and EnableViewState. For strongly-typed error-free compilation, Strict and Explicit should be set to true to enforce early binding and variable declaration. Moreover, always set the page's Buffer to true for nice performance boosts, and application-wide within the web.config file's system.web nodes as well - <pages buffer="true"/> .
    <%@ Page Language="C#" Debug="false" Strict="true" Explicit="true"
        Trace="false" EnableSessionState="false" EnableViewState="false"
        Buffer="true" %>

Note, all of the above attributes are applicable to an .ascx user-control's @ Control directive, except for Buffer, Trace and EnableSessionState.

  • On Page_Load set Response.BufferOutput to true and Response.Flush at some later key point in your code.
    void Page_Load(Object sender, EventArgs e) { 
    
        Response.BufferOutput = true; 
    
        //...... 
    Response.Flush(); }
  • Utilize Page caching by incorporating the @ OutputCache directive to speed up page display by storing this page is memory. You start by specifying, in seconds, the duration for the cache to remain in memory, your particular request scenario and its location. Other customizable request attribute settings include VaryByCustom, VaryByHeader, VaryByParam and VaryByControl , and for the Location attribute (not supported in User Controls or .ascx files) you're allowed - "Any | Client | Downstream | Server | None."
    <%@ OutputCache Duration="7200" VaryByParam="value" Location="Any" Shared="True" %>
    Output caching could also be enabled with the HttpCachePolicy Class. Not only that, but the OutputCache directive include-syntax also fully applies to Fragment Caching or caching server user controls (.ascx) files separately as well, and works along the exact same lines. However, one distinct advantage this has over standard Page output caching, is that the user control file supports the Shared attribute setting, whereas the .NET Web Forms page (.aspx) does not.

    Therefore, by setting your server user control's Shared attribute to true, you in effect allow this control to be shared among all your pages, as opposed to creating separate cached versions of it. As you can probably guess, you'll end up with more available memory and even better performance!

    Now that's not all, caching your code-behind's source file is very different from what we've seen. To cache your code-behind control you'd declaratively implement the duration attribute in your source file's metadata like so:

    [C#]
    [PartialCaching(7200)] 
    
    public class myClass : UserControl { 
    
       //Your code here 
    }

    [VB]
    <PartialCaching(7200)> _ 
    
    Public myClass : Inherits UserControl 
    
       'Your code here 
    End Class
    Reference PartialCachingAttribute Classfor more info.
  • Aside from Page Caching, Data Caching is extremely important, and what you tend to do once you present data to the client. How have you developed the page in question in light of scalabilty and performance? If the user is returned 10,000 records that will assuredly require paging, will your application keep re-hitting the database each time or does it take advantage of .NET's data caching methods?

    I have written two articles that cover such topics in much greater detail .NET Data Caching and Drilldown Datagrid Searching with ASP.NET. I'll refer you to these two articles for all the information you'll need. As for more generalized data access, we'll be discussing this in greater detail in our Data Layer section, and reveal different scenarios and the best data retrieval method to be used for the best performance.

  • Check for Postback on Page_Load to avoid unnecessarily hitting the server when the page is posting back to itself:
    void Page_Load(Object sender, EventArgs e) { 
    
       if ( !IsPostBack ) { 
    
         //...rest of code here 
    } }
  • Don't unnecessarily use server-side web server controls for simple tasks where standard HTML controls would suffice.
  • Use .NET's StringBuilder class for all your string writing/creation and manipulation needs.
  • When it comes to your code and values try to minimize boxing and unboxing of values, as this can cause a performance hit.
  • Be very paranoid when it comes to any text you'll be accepting through forms, or query strings. Unless imperative and not practical although easier, utilize POST primarily instead of GET, for any data requests or passing among pages.
  • Use <%@ Page AspCompat="true" %> only if it's an absolute must where you require pre-.NET apartment-threaded VB6 COM object compatibility. It truth, it's best that you simply make the effort to fully migrate your application completely to .NET, as this is better in the long run.
  • Particular settings within your configuration file - web.config , prove useful in improving application performance. First of all, set all pages to buffer, as mentioned. Second, if your site is fully public, setting authentication to None mode further helps, as well as disabling site wide debugging. No public pages or applications should ever have debug mode enabled.
    <system.web="None" /> 
       <pages buffer="true" /> 
       <compilation debug="false" /> 
    </system.web> 
  • Take advantage of .NET's machine.config file processModel attributes. They have much to do with the robustness of the application as it's the server and how well it lives that will further epitomize your application. The old days of rebooting IIS for component registration or recycling and so forth are long gone with all of .NET's way cool abilities contained within this one file. Heck, this file even helps with automatic crash recovery! Read Configuring the ASP.NET Worker Process through the <machine.config> file for some good tips.
  • When compiling any classes in a code behind scheme or as a data object, outside of VS.NET, use /optimize thus assuring smaller, more efficient runtime code:
    (csc/vbc).exe /t:library /out:c:\inetpub\wwwroot\bin\sf.dll sf.(cs/vb) /optimize

Within VS.NET, you could enable optimizations through your Project Solutions properties, under Configuration Properties-->Optimizations.

Furthermore, you could utilize .NET's Native Image Generator (Ngen.exe) to cache your components or applications (.exe) within the GAC (Global Assembly Cache) for faster start-up times:

ngen myComponent.dll/exe
  • When possible and applicable, utilize Asynchronous Calls for added scalability to your application, anywhere from I/O actions to ASP.NET Web Forms.
  • Always, always remember to close, null and dispose of all your objects. Even though .NET's garbage collection scheme is magnificent, do it as well nevertheless.
  • For maximum readability, always properly indent your code.
  • And finally, add comments to your code. .NET comment tags are <%-- Code --%> , whereas dependent on the language you've chosen, VB uses an apostrophe ' to comment out code and C# uses slashes // . This practice ensures not forgetting certain logic or program flow when the time arises. We don't always remember why we coded what we did or how, do we?

Now that we've gotten some great performance enhancing tips for our standard .NET pages, we'll proceed to the N-Tier portions, and individually deal with each one.

Directory Structure

This should be a simple enough concept, separate all key components in their own folders: directories like: includes, images, bin, info, documents, stylesheets, scripts, etc. You get the idea, what a nightmare to have your entire application in one folder, yikes! Furthermore, for stronger security measures, never place your site's folder in your server's root folder.

Also working with a source code control software such a Microsoft's Visual Source Safe goes a long way in protecting code. Many editors available now from HomeSite to Dreamweaver integrate this. Furthermore, Source Safe promotes versioning as well, just like .NET.

Presentation / Business Layer

N-TIER

The presentation layer is just that the presentation of the application via a User Interface (UI). It encapsulates our HTML code and any controls or components that make up our entire application. In classic ASP this page would look like a unpleasant interspersion of code, or spaghetti code as it's known. Simply put, this means server, client and HTML code all mixed together without a care in the world.

This approach is messy, problem prone and doesn't follow .NET's Object-Oriented approach. Our .NET page in comparison will be cleanly laid out, and lightly coded. Why? Well, again because of .NET Platform model. How? With .NET's code-behind . Code-Behind is, as its name implies, the method where all your commonly used code is placed in another file, "behind" the page, in a dll or vb/cs source file, that get's inherited via your page's @ Page Directive. Alongside this benefit, there are performance advantages of writing code this way, since .NET automatically pre-compiles these controls!

The page's @ Page Directive inheriting code-behind is thus:

<%@ Page Inherits="MyNamespace.ClassName" Src="source file" %>

The source file would look something like this:

          

// Page inherits code-behind source file

using System;
using System.Diagnostics; //For Tracing, Performance Monitoring
using System.Drawing; // For Graphics, Datagrid Colors
using System.Web; // For HttpContext
using System.Web.UI; //For Literal Controls
using System.Web.UI.WebControls; // For Datagrid

namespace MyNamespace {

// All your variables, properties here

public class ClassName : Page { //Inherits Page Class

// All your code here }

}

Above we imported any necessary libraries we might need. Next we created our namespace, and within this our class, which is what our Page's @ Page Directive will inherit for code-behind to work. However, if you prefer for whatever reason, you could simply create a standalone class in your code-behind file, whereby the Page Directive's Inherits attribute is the class name alone, instead in our example where it is "MyNamespace.ClassName."

Also notice that our class inherits the Page class, because this is what constitutes our code-behind interaction with our caller page.

Alternatively, if you compile your source code into a dll,

(csc/vbc).exe /t:library /out:c:\inetpub\wwwroot\bin\sf.dll sf.(cs/vb) /optimize

you would then import it and instantiate it, like so:

          

// Import It

<%@ Import Namespace ="MyNamespace" %>

// Then Instantiate It

[C#]

ClassName variableName = new ClassName();

[VB]

Dim variableName As New ClassName()

Another method that implement's code-behind methodology are pagelets or .NET's new improved include files/ Web Server User Controls , or .ascx files . These files expand on the content (UI)/code separation by including commonly used code, in nicely stored controls, again just like include files in classic ASP, but certainly more powerful. How? Well, they for one they can assign and pass parameters, not to mention handle events!

Apart from the more conventional application of user controls, and normal code-behind modules, yet another instance is in creating reusable, Custom Server Controls . Basically these are new, custom built, super-duper controls, acquiring their functionality from inheriting its target control's base class, and expanding on it. Creating one works along the same lines as the above source code, except instead of a Page code-behind file inheriting the Page Class in that example, you'd inherit your particular control's base class. Take for example creating a custom datagrid control:

          

namespace MyNamespace {

// All your variables, properties here

public class ClassName : DataGrid { //Inherits DataGrid Class

// All your code here

}

}

After you compile your source file, you'd then register your assembly for use in your page like so:

          

<%@ Register TagPrefix="myControl" Namespace="myNamespace" Assembly="myDll Name" %>

Then implementing it is as easy as:

          

<myControl:myNamespace ID="MyControl ID" runat="server" property1 = "false" _

property2 = "Red" OnClick="Click Method Name" />

Notice how (providing your source code includes this) your custom control could accept and set, parameters and or properties, and handle events, as we've mentioned. Moreover, this could also apply to standard code-behind user controls as well! Custom control creation tends to get more advanced, and the coding skill requirements jump up a few notches. At any rate, a user controls' more common usage stems, more or less, in reusable site code, i.e. a page's header and footer for instance.

Below demonstrates a typical, albeit, bare bones .aspx page. It has a header and footer, whereby the respective code is separated between two web server controls or modules - header.ascx and footer.ascx. All your page would need to do is register them, and place them in the page accordingly:

          

// The control's first get registered at the top of the page


<%@ Register TagPrefix="Header" TagName="Top" Src="header.ascx" %>
<%@ Register TagPrefix="Footer" TagName="Bottom" Src="footer.ascx" %>

// Then get placed in your page where needed

<body>

<Header:Top ID="Header" runat="server" />

<Footer:Bottom ID="Footer" runat="server" />

</body>

Man, .NET rocks! Now that's a nice, clean presentation I think! But wait, that's not all, your web server controls themselves could also further inherit code-behind code! In this case the inherit/src members are implement this time via the control's @ Control directive.

// Header.ascx
          

<% @ Control inherits="ClassName" src="source file" %>

// Your code here

And your control's code-behind file is identical to your page's code behind file, except the control doesn't inherit the Page class, but rather the UserControl class, since it's a control that's calling this file.

          

// User Control's code-behind source file


using System;
using System.Web.UI;
using System.Web.UI.WebControls;
public class ClassName : UserControl {

// Your code here

}

Additionally, if you compile your code-behind file as a dll with a namespace that looks like this:

          

namespace ClassName {

public class myClass : UserControl {

// Your code here

}

}

Then there are now two ways for including it for use in your page. You could inherit it as follows:

          

// Inherit it

<%@ Control inherits="ClassName.myClass" src="source file" %>

Or you could import it and instantiate it as demonstrated earlier. Still yet another cool feature in .NET is the ability for the global.asax file to inherit code-behind as well!

<%@ Application Inherits="MyApp" %> 

For more info on the fine art of OOP Programming, it'll be worth your while in taking the time to thoroughly read - The Quick and Dirty .NET Guide to C#/VB Object-Oriented Programming.

As a final afterthought, since a majority of your initial code within your pages and source file will interact on your code's Page_Load method, you may find that aside from this common function, you may to need to process something even before this or something else as well. Well then have a look at these six additional Page methods, that are contiguous to your Page's event handlers:

  • Page_Init - for pre-Viewstate processing
  • Page_PreRender - for pre-Page rendering tasks.
  • Page_DataBind - for when the Page binds data
  • Page_Dispose - for when the Page get's disposed
  • Page_Error - for any errors on the Page
  • Page_Unload - for code to run on Page unloading or exit

So the above are simply just something to keep in mind. Now you should have a real good idea on creating a clean Presentation Layer with the use of web server controls.

Business Rules/Logic Layer:

This layer is quite simple, as it involves all specific and particular business rules and requirements to any given company. It enforces rules pertaining to data that is retrieved from a database, i.e. mathematical, monetary, formatting, etc.

These rules are stored as methods in this layer separately from other components, thus any changes effective to this app would take place only once. Again, these are functions or methods capable of anything required by your application, to effect all particular business practices. Therefore, in light of efficient N-Tier design, you really do want to avoid placing any business logic in your presentation layer, and here is where it would all go.

Data Access Layer

The data layer, on the other hand, is where we'll be covering various scenarios, and the best methods to use for the best performance and results. This layer encapsulates and compartmentalizes all our data access code within nice, clean components, that interact with our database. Also, keep in mind that the following tips apply to non-Tier standalone applications as well :-)

Incidentally, take the time and prep yourself with An Introduction to ADO.NET for a good look at ADO.NET, with v1.1 features as well, prior to diving into this section. Furthermore, although this section is named Data Access, it could nevertheless include XML as a viable data source. Therefore, read Reading, Storing and Transforming XML Data in .NET for a good look into this methodology.

Now to start, some quick tips off the top dealing with both aspects of data, SQL and ADO.NET. We'll divide these here solely to offer optimal means in each for prime data access. This involves both efficient database design and queries, and employing proficient ADO.NET techniques in retrieving already polished data.

SQL

  • Normalize your data for maximum efficiency and faster performance.

  • When creating new SQL tables always consider the best data types to employ with the type of column data you'll be storing.

  • Index your tables using SQL's Create Index command and or Query Analyzer's Index Tuning Wizard, assuring that your databases are finely tuned.

  • Use SQL Stored Procedures for all your data access, as SQL Server compiles them for all future uses. Furthermore, in SQL, straight and narrow is the best method in writing SQL Stored Procedures. Never overcomplicate your procedures with unnecessary or excessive temporary tables, server-side cursors , as these do create performance bottlenecks. You're better off creating subqueries off a base query and or with a join (avoiding left joins).

  • Never name your stored procedures with a "sp_" prefix, as SQL will interpret this as a system procedure.

  • Create SQL Views for added clarity and security.

  • Include SET NOCOUNT ON in all your stored procedures, as this diminishes network traffic by eliminating the need for SQL in always returning how many rows the procedure affected.

  • Use sp_executesql to execute any standard non-Sproc SQL statements in your code, gaining Stored Procedure-type benefits and reducing overhead:

    sp_executesql N'Select * from table' 

    and even within a Stored Procedure, instead of strictly EXECing any query strings:

    DECLARE @sqlQuery nvarchar (100)

    SET @sqlQuery = N'SELECT * FROM database.dbo.table'

    EXECUTE sp_executesql @sqlQuery



  • Finally, optimize SQL Server itself to maximize performance. Read SQL Server Settings Optimization Tips for more info.



    ADO.NET


  • Create all your database access routines as generic, versatile objects, rather than client-side repeated-code methods. All your UI should do is interact with these components, and not have to work out any details. The methods aforementioned in the Presentation Layer apply here as well in creating components and controls.

  • For all intents and purposes, the golden rule for data access is as follows: If you want to page data or provide your application with functionality use a Dataset as the preferred method of disconnected data, otherwise use a Datareader for all your data retrieval. For XML users this would translate to an XmlReader , and StreamReader for text files, both equivalent as that of a DataReader for their respective file types.

  • Use the correct managed data provider for your particular database, ex. System.Data.SqlClient for SQL Server , System.Data.OleDb for Access, System.Data.OracleClient for Oracle , etc.

  • Use Strongly-Typed Datasets over the standard, common un-Typed ones when possible, as this yields better performance. A generated typed Dataset is basically an early bound class inheriting the Dataset base class, as opposed to the typical late bound, non-inheritable Dataset. Thus, it allows for greater flexibility in dealing with your data. In turn, you'll be dealing with easier to read code, whereby you can access fields and tables by customizable names, instead of the conventional collection-based way. There's a good article here on DNJ that you should definitely read called Using Typed DataSets . Here you'll find all you need to know to get you going.

  • As mentioned briefly in the General .NET Best Practices, take full, and I mean full advantage of .NET Caching, as this will significantly boost performance and greatly diminish any persistent database interaction. However, and an important however is, if you decide on caching your data from within your data object, then conventional caching methods won't apply. Within the confines of components, and its interaction with the caller page, these requests happen via an HTTP request. Therefore, caching within your component can only be implemented by using the HttpContext.Cache Class property, part of .NET's Page class.

  • Use parameterized Stored Procedures along side .NET's Command Class Prepare() method, that caches your query for all future SQL Server uses.

    objCommand.Prepare()

  • Make chunky calls to your database rather than smaller, chatty calls. It's better to group all similar, associated calls in one SQL Server access. In other words, use one solid connection to retrieve as much as you can, as opposed to multiple ones.

  • Avoid using Universal Data Link ( UDL ) files for OleDb connections as these can cause potential performance hits. .NET's SQLClient managed data provider does not support this, as prior versions of SQL Server did.

  • Take advantage of connection pooling (whereby all your connection strings are identical) by storing all your connection strings in your web.config file. Additionally, if your not enlisting any transactional procedures, include enlist=false ; to your database's connection string for added performance.

    Example:

    <configuration>

    <appSettings>

    <add key="myDatabase" value="User ID=id; Password=pw; Data Source=datasrc; _
    Initial Catalog=dbCat; Enlist=false;"
    />

    </appSettings>

    <configuration>

and call it from your page, code-behind source file or component like so:

            

[C#]

ConfigurationSettings.AppSettings["myDatabase"].ToString();

[VB]

ConfigurationSettings.AppSettings("myDatabase").ToString()

  • Finally, remember to close, clear and dispose of all your data objects no matter what. If you would like to further confirm that your database connection is indeed closed, you would write:

    if (dbConnection.State != ConnectionState.Closed) { dbConnection.Close(); }

Common ADO.NET Scenarios

  1. You want to retrieve one single value or item:

    Use the command class's ExecuteScalar method.

    object dbValue = objCommand.ExecuteScalar();

    Remember, this method always returns an object that you have to convert/cast to your specific type.

  2. You need to retrieve just a single row of data instead:

    Use the ExecuteReader's SingleRow Command Behavior

  3.               

    objDataReader = objCommand.ExecuteReader(CommandBehavior.SingleRow);



  4. You want to retrieve multiple database rows and automatically close your connection:

    Use a data reader.

    objDataReader = objCommand.ExecuteReader(CommandBehavior.CloseConnection);


    Not fast enough, then read all data sequentially as a data stream via ExecuteReader's SequentialAccess Command Behavior. Note that although obvious, I should point out all fields are to be read and access in sequential order starting from 0, dependent on your SQL query columns order.


    objDataReader = objCommand.ExecuteReader(CommandBehavior.SequentialAccess); 


    Still not enough? Read all fields by index position or strongly-typed accessor method, instead of field name


    string value = objDataReader(0);

    or try

    string value = objDataReader.GetString(0);



    *The GetString is one of the Datareaders type specific members to read columns thus reducing any runtime conversions.

    And get your results with your preferred method of datareader field reads:


    while (objDataReader.Read() == true)
    {

    Response.Write (objDataReader(0));

    }



  5. You'd like to open a database connection, read your data, then close it all pretty quickly without too much code

    This can be accomplished only in C#, with the using statement.

    using (SqlConnection string) {


    // Do database work


    } // Now connection is automatically closed



  6. Best method to retrieve values from a Stored Procedure, instead of looping through rows with a datareader:

    objCommand.ExecuteNonQuery();


  7. Want even better functionality when working with SQL Server? Then use Microsoft's new Data Access Application Block (DAAB) 2.0 . .NET introduces the SqlHelper class that dramatically cut's down development time by allowing you to execute commands against a SQL database, with Sprocs, all within a few lines of code (about 75% less code than you would typically need)!

    The example below demonstrates how incredibly easy it is to implement a Datareader to connect to and query a SQL database, whether using simple SQL syntax or a SQL Parameter, and all with one line of code!

    <%@ Import Namespace="System.Data" %> 
    <%@ Import Namespace="System.Data.SqlClient" %>
    <%@ Import Namespace="Microsoft.ApplicationBlocks.Data" %>

    <script language="C#" runat="server">


    void Page_Load (Object Source, EventArgs E) {

    string strConn = "server=(local);uid=sa;pwd=;database=northwind;";
    int categoryID = 1;


    //Using a SQL Query
    //Connection String, Command Type (Text), and SQL Query

    SqlDataReader objDataReader = SqlHelper.ExecuteReader_
    (strConn, CommandType.Text, "SELECT * FROM Products");



    //Using a Sproc
    //Connection String, Command Type (Stored Procedure), and Parameter
    SqlDataReader objDataReader = SqlHelper.ExecuteReader_
    (strConn, "getProductsByCategory", categoryID);


    while (objDataReader.Read() == true) {

    Response.Write (objDataReader[1].ToString() + "<BR>");

    }


    //Close and clear our object
    objDataReader.Close();
    objDataReader = null;

    }

    </script>



    That's amazing, considering this would usually be accomplished with roughly more or less 10 lines of code!

    The SQLHelper class used here provides the same amazing robustness for the Dataset. Not only that, but you're allowed transactional and Dataset database updating, as well as exception management!

    Now to implement this class into your project, you can do this one of two ways. One, as a private assembly by simply xcopying the Microsoft.ApplicationBlocks.Data.dll into your project's bin folder, or two, as a Shared Assembly whereby you install the dll into the Global Assembly Cache using .NET's gacutil.exe command line utility while noting that it has to be assigned a Strong Name. This can be accommodated using the Assembly Linker (Al.exe) utility program. Finally, within Visual Studio.NET, it's all a matter of simply referencing the DLL, by importing into your project.

    This sure is a cool new class, huh? I think so too. Download the Data Access Application Block for .NET v2 here.

So there you have most ADO.NET data access scenarios that you'll run into while creating your application. Pretty sure you've got enough now to get you moving.

Now that we've gotten our Tiers taken care of, we'll discuss methods that'll ensure us from having our application blow up in our faces - Error Trapping and Handling.

Error Trapping & Handling

Error handling is an essential consideration in all applications. You always want to make sure users aren't scared half to death with some unintelligible error message. Furthermore, you never, ever want any server-side errors ever shown in any way to the client, ever!

Not only that, but you'd want to catch any errors that may in effect break your application. Usually employing typical if-then conditionals are the best way to go in dealing with standard errors and controlling program flow. Still, at times prior to critical code execution, you may need further protection, and better overall error handling. However, overusing try/catch exceptions can decrease system performance!

.NET offers its try/catch error handling , where you'd first issue "try" to run the following code, capture any errors that may occur, and display the exception message, then take any appropriate steps to remedy it. Moreover, this error code block further attaches a finally to itself for any final code to execute:

          

try {

myDataGrid.Databind()

} catch (Exception e) {

Response.Write ("The following error occurred :" + e.Message);

} finally {

myDataGrid.CurrentPageIndex = 0

}

Of course, using finally is not mandatory, and doesn't require it being included. For more info read - Handling and Throwing Exceptions and Best Practices for Handling Exceptions.

Further VB error objects available is the On Error Statement, capable of resuming next and going to a preset error handling label via Goto.

Alternatively to assigning custom error pages through IIS's Custom Errors tab, another such method involves our famous web.config file. Invoking the try/catch error blocks do well for code errors, but server errors? Nope, not these. Here we'll use web.config's custom errors nodes :

          

<system.web>

<customErrors mode="On" defaultRedirect="errorpage.aspx">

<error statusCode="403" redirect="AccessIsNotAllowed.aspx"/>
<error statusCode="404" redirect="PageNotFound.aspx"/>
<error statusCode="500" redirect="InternalServerError.aspx"/>

</customErrors>

</system.web>

Now all errors are redirected to more friendly error pages, and if you prefer, why not pull in additional error information, and e-mail yourself or the appropriate party when the error occurs.

Don't forget that you may also implement global error handling not dealt with our try/catch code blocks, via your global.asax file's Application_Error method using .NET's System Error codes:

          

protected void Application_Error (Object sender, EventArgs e) {

Server.Transfer ("error.aspx?error="+Server.GetLastError().Message);

}

Now error handling in .NET, can and should at times be given over to Stored Procedures via the two good methods found in SQL:

  1. @@ERROR :

    DECLARE @RetValue int

    UPDATE database
    SET database.dbfield = 'Jimmy'
    WHERE dbfield = 'Dimitrios'

    IF @@ERROR <> 0

    BEGIN

    PRINT 'An error occurred'

    SET @RetValue = @@ERROR

    END

    ELSE

    BEGIN

    PRINT 'All OK'

    SET @RetValue = '0'

    END

    RETURN @RetValue



    With the @@Error function you declare a variable for the error, return this value at the end and read this client side or print the error through SQL itself. Note an error return of zero (0) indicates no errors at all.

  2. RAISERROR:

    Hard-coded error messages:

    RAISERROR ('Error Occurred in : %s', 16, 1)

    Or utilizing the sp_addmessage system stored procedure to produce error messages using the msg_id, as long as the msg_id is over 50000.

    RAISERROR (50001, 16, 1, @columnId)

Debugging

Debugging is the art of program flow error handling, where you track down errors that has prevented your code from fully executing. .NET has much improved the classic ASP way of Response.Writing to find and trap program errors and flow. To employ debugging you could do this:

  1. Application wide in your web.config file:

    <system.web>

    <compilation debug="true"/>

    </system.web>


  2. Or locally and easier is by setting Debug to true in your page's @ Page Directive and also your user-control's @ Control Directive

Tracing

Tracing in .NET is another means of debugging, but allows you to insert custom statements added to your page's output, whereby you can "trace" the flow of your code and determine the order of things at run time. Tracing, contrary to debug mode, presents you with a lot of information about your page. It further can help much in ascertaining any performance issues concerning your code. In any event, like debugging, tracing could be implement the same two ways:

  1. Application wide via the web.config file:

    <system.web>

    <trace enabled="true"/>

    </system.web>


  2. Or locally within each page's @ Page Directive:


    <%@ Page Trace="true" TraceMode="SortByTime | SortByCategory" %>


    And within your code you would trace by writing:

    Trace.Write("Tracing has started")

    Trace.Warn("Examine more closely here")

    Trace.Write("Tracing has ended")


    Trace. Write produces standard output, whereas using Trace.Warn would output your text in red for added notice. And bear in mind that the best place to Trace is usually before and after any major code sections, i.e. Datagrid binding, editing, etc.

With all this error handling now under our belt, what happens now when an error does occur? How do we fix it? Well, next we'll look at some common, though not basic, .NET system errors.

Common .NET Errors

  • CS0019: Operator '&' cannot be applied to operands of type 'type' and 'type'

    This happens when you are trying to use the wrong operator for the specific operation.

  • CS0029: Cannot implicitly convert type 'object' to 'string'

    This error occurs when you are typically trying to present an object as a string. The solution here is to append ".ToString()," that now represents the object as a string.

  • CS0103: The name 'whatever' does not exist in the class or namespace / CS0246: The type or namespace name 'whatever' could not be found (are you missing a using directive or an assembly reference?)

    This will usually occur when you have not declared a variable globally and or it's missing altogether. CS0246 error also occurs when you are improperly declaring a object or variable, as well as it not existing in your code.

  • CS0117: 'object' does not contain a definition for 'Length'

    Again this has to do with improperly trying to convert a value. To remedy this properly cast or box the value in question.

  • CS0118: 'System.Configuration.ConfigurationSettings.AppSettings' denotes a 'property' where a 'method' was expected

    This error occurs when your AppSettings key is accessed with parentheses in VB, instead of brackets in C# or vice-versa.

  • CS0119: 'whatever' denotes a 'class' which is not valid in the given context / Object reference not set to an instance of an object

    One cause of this error is to improperly call an object without the "new" keyword in C# and "As New" in VB

  • CS0161: 'method name': not all code paths return a value

    This will happen when you are trying to make a function method behave like a subroutine or vice-versa. Use the proper method keyword to remedy this. Void is for methods that do not return a value, although you could response write the output. Whereas, applying a keyword value like string, int, works alongside a return value at the end of the method.

  • CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement

    This error is produced by a number of sources.

    1. If you call an object that requires () after the command, you'll get this error. Ex. database.Open() not database.Open
    2. Using an assignment operator as opposes to an equality operator


  • CS1001: Identifier expected / BC30183: Keyword is not valid as an identifier.

    Both these similar errors occur when you forget to identify a variable without a keyword, or you were inadvertently forcing a keyword to behave as a variable or type.

  • BC30451: Name 'whatever' is not declared.

    This happens when you are referencing a web form control or variable that either doesn't exist or is named different. Also by chance the control in question may be missing the mandatory runat="server" option.

  • BC30512: Option Strict On disallows implicit conversions from 'type' to 'type'.

    Improper type conversions produce this error. Convert the value or method to the proper cast to fix this.

  • BC30554/BC30560: 'object' is ambiguous.

    This error occurs when you have more than one similarly named dll in your bin folder when compiling or it matched your control inherits call. Furthermore, more than one dll root namespace or class within are named the same. Your page or control are trying to access one source file where a source file and dll may simultaneously exist.

  • BC30574: Option Strict On disallows late binding

    This can happen from any number of reasons in your code. Some common ones are:

    1. Not setting the object's proper data type
    2. An expression that binds data was incorrectly parsed against the object at runtime In Datagrids, for instance, this occurs when omitting the DataBinder.Eval method on a data field
  • BC30684: 'name' is a type and cannot be used as an expression

    This can happen usually when you forget to use the new keyword when instantiating an object or invoking a constructor.

Phew, hope this all helps in fixing those pesky errors you may run into when coding. Don't think for a minute that they're all here. Yeah, right, take a look at a complete list of Compiler Errors CS0001 Through CS9999. Here I simply showed a number of the more common ones you'll probably run into, with some quick ways in figuring them out and remedying them.

Performance Testing

I really won't dwell too much on this topic, as whole books are available on this topics alone! Performance testing takes time and patience to fully realize before and after results. To monitor your site's performance, use the Admin Tools' performance monitors and take the time to add the appropriate counters to the system monitor and take it from there.

Not only that, but .NET gives you ability to programmatically do the same things, alongside accessing system logs and system processes with its System.Diagnostics namespace. Read Displaying Performance Monitor Information through an ASP.NET Web Page for more info.

At any rate, read the book in the above link, and check out the article. Subsequently, Microsoft also offers a software tool that pushes your app to its limits, its WAS Tool. This tool and other useful information can be found here - Performance Optimizing Tools.

Conclusion

Well you have alot to think about and take into consideration regarding the creation of a finely-tuned application machine, both scalable and secure, not to mention lightning fast. At least it'll give you pride in the finished result, when you present a robust, high-performance application, with little or no down time. In the world of Internet development and deployment, that speaks volumes!

Until Next Time. Happy .NETing </>

[Originally published on dotnetjunkies.com - 6/03]

".NET" 카테고리의 다른 글
  • Windows Workflow Foundation 규칙 엔진 소개 (0)2007/07/23
  • Introduction to Windows Communication Foundation (0)2007/06/15
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
  • 개체를 이용한 데이터 바인딩 어플리케이션 만들기 (0)2007/01/11
2007/04/29 11:52 2007/04/29 11:52
Posted by webdizen
Tags .NET, Application, Architecture, Development, High-Performance
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2866

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:51

데이터 바인딩 어플리케이션 만들기 기초

고수닷넷 - 빵이님

Visual Studio 2005를 설치하면 몇가지 생소한 컨트롤이 눈에 띕니다. 데이터 그룹의 컨트롤이 더욱 그러한데, 이전 버전에 있던 것들과 겹치는 것은 데이터셋(DataSet) 컨트롤 밖에 없습니다. 컨르롤 명만 봐도 대단히 쉬워졌다는 느낌이 옵니다. 이전버전도 쉬웠지만 Visual Studio 2005는 더 편리한 방법으로 더 강력한 기능을 제공합니다. 이를 이용해 간단한 데이터 바운드 윈도우 폼 응용프로그램을 만들어 보겠습니다.


먼저, Visual Studio 2005 또는 Visual C# 2005 가 설치되어 있어야 합니다. 윈도우 응용프로그램(Windows Application) 프로젝트를 생성합니다. 프로젝트명은 BindingApplication 으로 하겠습니다.


사용자 삽입 이미지

[그림1] 윈도우 응용프로그램(Windows Application) 프로젝트 생성

솔루션 탐색기에서 Form1.cs를 BindingForm.cs로 바꾸어 줍니다. 그리고 폼을 조금더 보기 좋게 하기위해 폼의 Text속성을 데이터바인딩 연습으로 고치고 (Name)속성도 frmBinding으로 고치겠습니다.


그 다음엔 데이터소스로부터 응용프로그램으로 데이터를 끌어올 수 있는 기능을 붙여주는 것입니다. IDE의 메뉴에 Data > Add New Data Source 를 선택합니다. 데이터베이스, 웹 서비스, 객체 중 하나를 선택하라는 창이 나타납니다. 저는 Access 를 설치하면 기본으로 제공되는 Northwind.mdb 를 이용할 것이므로 데이터베이스를 선택하겠습니다.


사용자 삽입 이미지

[그림2] 데이터 소스 타입 선택

여기부터는 제가 보여드리는 프로세스 그대로 안나타 날 수 있습니다. 나타나지는 창 모양대로 그때 그때 대처하시면 됩니다.


데이터베이스를 선택하고 다음으로 넘어가면 연결문자열(connection string)을 선택하는 화면이 보입니다.


사용자 삽입 이미지

[그림3] 데이터베이스 연결문자열 선택

이미 원하는 연결문자열이 있다면 선택하면 되지만 없다면 New Connection 버튼을 눌러 연결정보를 입력합니다.

사용자 삽입 이미지

[그림4] 연결정보 입력창

각자 원하는 데이터소스를 선택하신 후 연결정보를 입력하시면 됩니다. Northwind 데이터베이스는 SQL Server 2000 에서도 기본으로 제공하기때문에 SqlClient 를 선택하셔도 상관 없습니다.


사용자 삽입 이미지

[그림5] 데이터 소스 변경

연결정보를 입력하신 후 다음으로 넘어가시면, 연결스트링의 키를 입력하는 화면이 다오는데 적절히 입력후 넘어갑니다. 참고로, Northwind.mdb 파일은 "C:\Program Files\Microsoft Office\OFFICE11\SAMPLES" 와 비슷한 위치에 있을 것입니다.


사용자 삽입 이미지

[그림6] 연결 문자열 저장

다음으로 넘어가면, 연결된 데이터베이스에 포함된 개체가 보입니다. 테이블항목의 Employees 테이블을 선택하겠습니다. 데이터셋의 이름은 기본인 NorthwindDataSet으로 하겠습니다.


사용자 삽입 이미지

[그림7] 테이블선택

마침(Finish) 버튼을 누르면 서버탐색기(Server Explorer)에 NorthwindDataSet 이 추가 된것을 볼수 있습니다.


사용자 삽입 이미지

[그림8] 서버탐색기

서버탐색기의 Employees 테이블을 그대로 드래그 해서 폼위로 끌어다 놓으면 데이터셋, 바인딩소스, 데이터테이블어뎁터 컴포넌트와 데이터그리드뷰, 바인팅네비게이터가 추가되는 것을 볼 수 있습니다. 보기좋게 하기위해 데이터 그리드뷰는 Dock 속성은 "사방"으로 해주시면 작업이 완료 됩니다.


이제 실행 시켜 보십시오. ASP.NET 2.0과 비교해서 미안하지만, 코드한줄 적지않고 ASP.NET 2.0 보다 더 막강한 기능을 구현 했습니다. + 버튼과 x 버튼을 이용해 단절된 상태에서 레코드를 추가, 삭제 할 수 있습니다. 물론 편집도 가능합니다. 수정동작을 완료한 뒤 저장버튼을 누르면 데이터베이스에 적용이 됩니다.


정리


WinForm 2.0 에서 데이터 바운드 응용프로그램을 만들어 보았습니다. 실제의 응용프로그램은 이보다는 복잡하지만 기본 방법은 비슷합니다. Visual Studio 2005는 기존의 속칭 "노가다"를 많이 줄여주었습니다. 제 개인적인 생각으로는 놀랍습니다^o^ 개발자는 코드를 작성하는 시간보다 창의적인 생각을 할 수 있는 시간을 더 벌게 되었습니다.


다음에는 좀더 실세계에 접근한 응용프로그램을 작성해 보도록하겠습니다.


P.S 첨부한 프로젝트에서 Northwind.mdb는 라이센스가 문제될 것 같아 제외하였습니다.

".NET" 카테고리의 다른 글
  • Introduction to Windows Communication Foundation (0)2007/06/15
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
  • 개체를 이용한 데이터 바인딩 어플리케이션 만들기 (0)2007/01/11
  • 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로... (0)2007/01/11
2007/01/11 09:51 2007/01/11 09:51
Posted by webdizen
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2579

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:45

개체를 이용한 데이터 바인딩 어플리케이션 만들기

고수닷넷 - 빵이님

Visual Studio 2005는 개발자의 생산성 향상에 가장 중점을 둔 개발툴로 거듭난 것 같습니다. 이번에는 객체를 데이터 소스로 이용해 보겠습니다. 어설프게나마 어느정도 MVC 패턴을 따라가게 되었습니다.


시나리오

최근에 시나리오로 많이 쓰이는 어드벤쳐 웍스 사이클스의 데이터베이스를 이용하려다 제가 SQL Server 2005 를 설치할때 깜박한 관계로, Office 에서 제공하는 Northwind 를 이용하도록 하겠습니다. Northwind의 Order - Order Detail 테이블을 이용합니다. SQL Server 2000에서도 제공하므로 어렵지 않게 접근 할 수 있을 것입니다.


프로젝트 생성

프로젝트는, 데이터 컨트롤 프로젝트 - Controller, 데이터 객체 프로젝트 - Model, 유저 인터페이스 프로젝트 - View 를 만듭니다. Controller, Model 은 클래스 라이브러리이고, View는 윈도우 응용프로그램입니다.


MVC 패턴을 원래의 취지대로 적용하여 프로그램을 작성하기 위해서는 MVC패턴 자체에 대한 깊은 성찰이 필요한 것 같습니다. 그렇게 하기에는 좀 지루하므로 막연하게나마 마이크로소프트의 Patterns & practices 에서 제공하는 개발 스타일대로 따라해 보도록 하겠습니다.


모델

모델은 데이터에 의존적인 오퍼레이션과 비즈니스와 관련된 오퍼레이션을 담습니다. 수많은 형태로 모델이 작성되지만, 어떤것이 가장 좋은 형태인지에 대한 대답을 얻기는 쉽지 않습니다. 때에 따라 다를수 있고, 개발자의 견해에 따라 다를수 있습니다. 분명한것은 한 패턴이 정해지면, 모든 부분에서 동일한 패턴으로 프로그램이 작성되어야 한다는 것입니다.


이번 프로그램은, 각 테이블과 대응되는 컨테이너를 작성하고, 데이터 베이스에서 데이터를 불러와 컨테이너에 저장하는 형태로 가도록 하겠습니다. 관련된 데이터베이스의 테이블은 Orders와 OrderDetails가 있으므로 클래스 역시 Orders와 OrderDetails를 작성합니다. Model 프로젝트에 Orders.cs 파일과 OrderDetails 파일을 추가합니다. 각 클래스의 코드는 다음과 같습니다.


[Orders.cs]using System;

using System.Collections.Generic;

using System.Text;

using System.Data;

using System.Data.OleDb;

using System.Configuration;


namespace Model

{

   public class Orders

   {

       public static List<Orders> GetOrders()

       {

           OleDbDataAdapter dataAdapter;

           DataSet dataSet;

           List<Orders> orders;


           orders = new List<Orders>();

           dataSet = new DataSet();

           dataAdapter = new OleDbDataAdapter("SELECT * FROM Orders", _connectionString);

           dataAdapter.Fill(dataSet, "Orders");

           dataAdapter = new OleDbDataAdapter("SELECT * FROM [Order Details]", _connectionString);

           dataAdapter.Fill(dataSet, "OrderDetails");


           foreach (DataRow drOrder in dataSet.Tables[0].Rows)

           {

               Orders order = new Orders();

               order.OrderID = Convert.ToInt32(drOrder["OrderID"]);

               order.CustomerID = Convert.ToString(drOrder["CustomerID"]);

               order.EmployeeID = Convert.ToInt32(drOrder["EmployeeID"]);

               order.OrderDate = Convert.ToString(drOrder["OrderDate"]);

               order.RequireDate = Convert.ToString(drOrder["RequiredDate"]);

               order.ShippedDate = Convert.ToString(drOrder["ShippedDate"]);

               order.ShipVia = Convert.ToInt32(drOrder["ShipVia"]);

               order.Freight = Convert.ToDouble(drOrder["Freight"]);

               order.ShipName = Convert.ToString(drOrder["ShipName"]);

               order.ShipAddress = Convert.ToString(drOrder["ShipAddress"]);

               order.ShipCity = Convert.ToString(drOrder["ShipCity"]);

               order.ShipRegion = Convert.ToString(drOrder["ShipRegion"]);

               order.ShipPostalCode = Convert.ToString(drOrder["ShipPostalCode"]);

               order.ShipCountry = Convert.ToString(drOrder["ShipCountry"]);


               DataRow[] orderDetails = dataSet.Tables[1].Select(String.Format("OrderID={0}", drOrder["OrderID"]));

               foreach (DataRow drOrderDetail in orderDetails)

               {

                   OrderDetail orderDetail = new OrderDetail();

                   orderDetail.OrderID = Convert.ToInt32(drOrderDetail["OrderID"]);

                   orderDetail.ProductID = Convert.ToInt32(drOrderDetail["ProductID"]);

                   orderDetail.UnitPrice = Convert.ToDouble(drOrderDetail["UnitPrice"]);

                   orderDetail.Quantity = Convert.ToInt32(drOrderDetail["Quantity"]);

                   orderDetail.Discount = Convert.ToInt32(drOrderDetail["Discount"]);

                   

                   order.OrderDetail.Add(orderDetail);

               }


               orders.Add(order);

           }


           return orders;

       }


       public static int UpdateOrders(Orders orders)

       {

           OleDbConnection con;

           OleDbCommand cmd;

           int retVal;


           con = new OleDbConnection(_connectionString);

           cmd = con.CreateCommand();


           try

           {

               cmd.CommandText = "UPDATE Orders SET CustomerID=@CustomerID, EmployeeID=@EmployeeID,"

                   + "OrderDate=@OrderDate, RequireDate=@RequireDate, ShippedDate=@ShippedDate, "

                   + "ShipVia=@ShipVia, Freight=@Freight, ShipName=@ShipName, ShipAddress=@ShipAddress, "

                   + "ShipCity=@ShipCity, ShipRegion=@ShipRegion, ShipPostalCode=@ShipPostalCode, "

                   + "ShipCountry=@ShipCountry WHERE OrderID=@OrderID";


               .

               .

               .

               cmd.Parameters.Add(new OleDbParameter("@ShipName", OleDbType.VarChar));

               .

               .

               cmd.Parameters.Add(new OleDbParameter("@OrderID", OleDbType.Integer));

               .

               .

               .

               cmd.Parameters["@ShipName"].Value = orders.ShipName;

               .

               .

               cmd.Parameters["@OrderID"].Value = orders.OrderID;

               .

               .



               con.Open();

               retVal = cmd.ExecuteNonQuery();

           }

           catch (OleDbException ex)

           {

               throw new Exception(String.Format("데이터베이스 오류 : {0}\n", ex.Message));

           }

           catch (Exception ex)

           {

               throw new Exception(String.Format("일반 오류 : {0}\n", ex.Message));

           }

           finally

           {

               if (con.State == ConnectionState.Open) con.Close();


               cmd.Parameters.Clear();

               cmd = null;

               con = null;

           }


           return retVal;

       }


       private List<OrderDetail> _orderDetail = new List<OrderDetail>();


       public List<OrderDetail> OrderDetail

       {

           get { return _orderDetail; }

       }


       private int _OrderID;

       private string _CustomerID;

       private int _EmployeeID;

       private string _OrderDate;

       private string _RequiredDate;

       private string _ShippedDate;

       private int _ShipVia;

       private double _Freight;

       private string _ShipName;

       private string _ShipAddress;

       private string _ShipCity;

       private string _ShipRegion;

       private string _ShipPostalCode;

       private string _ShipCountry;


       public int OrderID

       {

           get { return _OrderID; }

           set { _OrderID = value; }

       }


       public string CustomerID

       {

           get { return _CustomerID; }

           set { _CustomerID = value; }

       }


       public int EmployeeID

       {

           get { return _EmployeeID; }

           set { _EmployeeID = value; }

       }


       public string OrderDate

       {

           get { return _OrderDate; }

           set { _OrderDate = value; }

       }


       public string RequireDate

       {

           get { return _RequiredDate; }

           set { _RequiredDate = value; }

       }


       public string ShippedDate

       {

           get { return _ShippedDate; }

           set { _ShippedDate = value; }

       }


       public int ShipVia

       {

           get { return _ShipVia; }

           set { _ShipVia = value; }

       }


       public double Freight

       {

           get { return _Freight; }

           set { _Freight = value; }

       }


       public string ShipName

       {

           get { return _ShipName; }

           set { _ShipName = value; }

       }


       public string ShipAddress

       {

           get { return _ShipAddress; }

           set { _ShipAddress = value; }

       }


       public string ShipCity

       {

           get { return _ShipCity; }

           set { _ShipCity = value; }

       }


       public string ShipRegion

       {

           get { return _ShipRegion; }

           set { _ShipRegion = value; }

       }


       public string ShipPostalCode

       {

           get { return _ShipPostalCode; }

           set { _ShipPostalCode = value; }

       }


       public string ShipCountry

       {

           get { return _ShipCountry; }

           set { _ShipCountry = value; }

       }

   }

}


[OrderDetails.cs]

using System;

using System.Collections.Generic;

using System.Text;


namespace Model

{

   public class OrderDetail

   {

       public static int UpdateOrderDetail(OrderDetail orderDetail)

       {

               //업데이트 로직

               .          

               .

               .

               .

               .

       }


       public int _OrderID;

       public int _ProductID;

       public double _UnitPrice;

       public int _Quantity;

       public int _Discount;


       public int OrderID

       {

           get { return _OrderID; }

           set { _OrderID = value; }

       }


       public int ProductID

       {

           get { return _ProductID; }

           set { _ProductID = value; }

       }


       public double UnitPrice

       {

           get { return _UnitPrice; }

           set { _UnitPrice = value; }

       }


       public int Quantity

       {

           get { return _Quantity; }

           set { _Quantity = value; }

       }


       public int Discount

       {

           get { return _Discount; }

           set { _Discount = value; }

       }

   }

}


좀 유심히 봐야하는 부분은 Orders의 List<OrderDetails> 타입의 OrderDetails 멤버입니다. 테이블에서 마스터-디테일 관계에 있기때문에, OrderDetails가 Orders의 멤버로 두었습니다. 이렇게 함으로서 나중에 보실 View 의 개발시 생산성의 향상을 느낄 수 있게됩니다.


사실, 위의 코드는 수동적이든 능동적이든 코드를 자동생성 할 수 있는 부분입니다. 그리고, 가능하다면 반드시 자동생성해서 사용하기를 바랍니다.


Orders 에서는 오퍼레이션을 담당하는 static 형태의 메서드가 있는데, 이 메서드에서 실제로 컨테이너를 채우고, 채워진 컨테이너를 반환하는 역할을 합니다.


(중간에 업데이트관련코드는 왜 저모양이냐! 바쁜일정에 쫓겨서 ㅠㅜ)


컨트롤러

컨트롤러는 View와 관련되지만 분리될 수 있는 동작을 담습니다. 간단히 말해서 뷰와 모델의 다리역할을 하는 개체라고나 할까요? 모델의 데이터를 불러온다거나 업데이트를 한다거나 하는 동작을 여기에 담습니다. 보통의 어플리케이션은 MVC 패턴만으로 개발하지는 않다는 걸 여러분도 잘 알고 계실 것입니다. 업데이트시 옵저버를 이용해 다른 클라이언트에 바뀐 상태를 전달하기도 합니다. 그 외 여러가지 방법으로 많은 기능을 구현하지만 여기에서는 컨트롤러가 모든것을 다 수행하는 것으로 하겠습니다.


다음은 소스코드는 컨트롤러에 해당하는 코드입니다.


[Controller.cs]

using System;

using System.Collections.Generic;

using System.Text;


using Model;


namespace Controller

{

   public class Controller

   {

       public static object GetOrders()

       {

           return (object)Orders.GetOrders();

       }


       public static bool UpdateOrders(Orders orders)

       {

           return Orders.UpdateOrders(orders) != 0;

       }


       public static bool UpdateOrderDetail(OrderDetail orderDetail)

       {

           return OrderDetail.UpdateOrderDetail(orderDetail) != 0;

       }

   }

}


가끔, 뷰에 들어갈 코드를 컨트롤러에 넣는 경우가 있는데, 좋은 습관은 아닌것 같습니다. 컨트롤러는 뷰와 독립되게 만들어야 활용도가 높아집니다. 즉, 결합도를 낮추는 것이지요.  실제 뷰와관련이 없는 오퍼레이션을 여기에 담는 것이 좋다는게 저의 생각입니다.


뷰

실제 고객과 인터액트하는 부분이기때문에 가장 신경을 쓴것 처럼 만들어야 하는 부분입니다. 윈폼의 경우 개발자가 Anchor 속성을 설정하는 것만 잊게되도 사용자는 크게 불편을 느낍니다. 신경을 써야 하는게 아니라 쓰는 것처럼 보이라는 이유는 실제 비즈니스 로직에 더 신경을 쓰고 구현을 해야 전체 프로젝트가 산으로 안가지만, 고객은 값만 정확히 맞는다면 이에 대해서는 관심을 크게 안가지므로, 이에대해 고객에게 떠들어봐야 "얘가 많이 힘들구나, 불쌍하다" 란 생각밖에 안하므로, 뷰부분을 신경쓰는 것 처럼 작업합니다.


다행히 Visual Studio 2005 는 이부분에서 대단한 생상성 향상을 보여줍니다. 먼저 뷰 프로젝트에 모델 프로젝트와 컨트롤러 프로젝트를 참조 추가합니다. 그 다음 뷰 프로젝트에 포커스를 맞추고, 메뉴에서 "데이터 > 새 데이터 소스 추가" 를 실행합니다.


사용자 삽입 이미지

그림 1. 새 데이터 소스 추가

그림1과 같은 다이얼로그 창이 나타나는데 "개체"를 선택하고 다음을 누릅니다.


사용자 삽입 이미지

그림 2. 바인딩할 개체 선택

그러면, 그림2와 같이 바인딩할 개체를 선택하는 화면이 나오는데, 프로젝트 참조를 하셨다면 모델 프로젝트가 보일 것입니다. 여기서 Model 네임스페이스의 Orders 클래스를 선택합니다. OrderDetail은 선택할 필요가 없는 것이 어짜피 Orders가 OrderDetail 클래스를 포함하기 때문에 따로 선택할 필요는 없습니다.


다음을 누러 데이터 소스 추가를 완료합니다. 그러면 데이터 소스창에 보면, Orders 개체가 추가된것이 보입니다. 데이터 소스창이 보이지 않는다면, Shift + Alt + D 키를 눌러 화면에 표시합니다.


그럼 데이터 소스창에는 다음과 같은 화면이 보입니다.


사용자 삽입 이미지

그림3. 데이터소스 추가

Orders 테이블이 보이고, OrderDetail 테이블이 보입니다. 실은 Orders 클래스와 OrderDetail 클래스를 가리킵니다. VS2005 가 알아서 어울리는 컨트롤로 맵핑을 했지만, 개발자가 어울리는 컨트롤을 지정할 수 있습니다.


일단을 각 필드를 폼위에 끌어다 놓습니다. 그럼, 도구상자에서 컨트롤을 끌어다 놓는 것과 같이 폼위에 컨트롤이 그려지게 되는데, 다른점이 있다면, 이렇게 하면 각 컨트롤은 자동으로 데이터 소스와 바운딩이 됩니다.


다음은 그 화면입니다. 시간상 SplitContainer 컨트롤에 (데이터 소스의)데이터 그리드 두개, FlowLayoutPanel 두개 올려놓았습니다. FlowLayoutPanel 에는 상세보기를 위해 각 필드를 올려 놓습니다.


다음은 그 화면입니다.


사용자 삽입 이미지

그림 4. 폼에 컨트롤을 올려놓은 모습

조금 잘리긴했지만, 충분히 이해하시라 생각합니다. 잘 보셔야 할 것은 밑의 바운딩 소스입니다. Orders, OrderDetail 이 관계된 바운딩 소스인데  이 둘은 연결이 되어있습니다. 그래서 Orders 에만 데이터 소스를 대입하면 OrderDetail에까지 데이터가 연결 됩니다.


다음은 뷰에 해당하는 코드입니다.


[View.cs]

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;


using Model;


namespace View

{

   public partial class View : Form

   {

       public Form1()

       {

           InitializeComponent();

       }


       private void Form1_Load(object sender, EventArgs e)

       {

           ordersBindingSource.DataSource = Controller.Controller.GetOrders();

       }


       private void btnSave_Click(object sender, EventArgs e)

       {

           ordersBindingSource.EndEdit();

           if (!Controller.Controller.UpdateOrders((Orders)ordersBindingSource.Current))

               MessageBox.Show("업데이트 실패!");

       }


       private void btnDetailSave_Click(object sender, EventArgs e)

       {

           orderDetailBindingSource.EndEdit();

           if (!Controller.Controller.UpdateOrderDetail((OrderDetail)orderDetailBindingSource.Current))

               MessageBox.Show("업데이트 실패!");

       }

   }

}


저장 방법이 내가 데이터를 다루는 동안에는 다른 사람이 데이터를 다루지 않는다고 가정하는 낙관정 동시 접근 방법을 사용하기 때문에 좀 위험한 부분도 있지만 아주 간단히 기본적인(아주 부족한) 어플리케이션이 완성 되었습니다.


실행시켜 보시면 데이터 그리드에서 데이터를 마우스로 선택하면 관련된 컨트롤의 데이터까지 해당하는 데이터로 채워진다는 것입니다. 우리는 어떤 코드도 입력 안했는데 말이죠. 뭐, 당연한 건가요^^


정리

Visual C# 1.1 에서는 데이터메니저를 클래스 형태로 제공하긴했지만, 바운딩 소스 컴포넌트가 더 편한 것 같습니다.  생산성을 극대화 시켜주는 느낌입니다. 다음에는 좀더 정교하게 컨트롤에 되는 어플리케이션을 다루어 보겠습니다.

".NET" 카테고리의 다른 글
  • High-Performance .NET Application Development &... (0)2007/04/29
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
  • 개체를 이용한 데이터 바인딩 어플리케이션 만들기 (0)2007/01/11
  • 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로... (0)2007/01/11
  • Windows Form : 윈도우 폼 꾸미기 (0)2007/01/11
2007/01/11 09:45 2007/01/11 09:45
Posted by webdizen
Tags Data Binding
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2578

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:39

닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로 배포하는 방법

고수닷넷 - 대니님

[개요]


Microsoft Visual Studio .NET 2003 Bootstrapper Plug-In을 사용하면 응용 프로그램과 함께 .NET Framework 1.1 및/또는 MDAC 2.7을 재배포하는 설치 관리자를 쉽게 만들 수 있습니다.

.NET Framework 응용 프로그램이 특정 컴퓨터에서 실행되도록 하려면 해당 컴퓨터에 .NET Framework가 설치되어 있어야 합니다. ADO.NET을 사용하여 데이터베이스에 연결하는 모든 .NET Framework 응용 프로그램은 MDAC 2.7을 요구합니다. 많은 컴퓨터에 이미 이러한 구성 요소가 설치되어 있지만 응용 프로그램이 확실하게 실행되도록 하려면 응용 프로그램과 함께 이 구성 요소를 재배포해야 합니다.

Visual Studio .NET 2003 Bootstrapper는 Visual Studio .NET 설치 프로그램 및 배포 프로젝트와 통합됩니다. 이 Bootstrapper를 사용하여 대상 컴퓨터에 요구되는 필수 구성 요소가 있는지 검사하여 필요한 경우 자동으로 해당 요소를 설치하는 단일 설치 관리자를 만들 수 있습니다. 컴퓨터를 다시 시작해야 하는 구성 요소가 있는 경우, 설치 관리자는 이후에 자동으로 작업을 다시 진행합니다. 응용 프로그램 사용자는 단지 하나의 설치 프로그램을 실행하여 필수 구성 요소와 응용 프로그램을 설치하면 됩니다.

[한번의 인스톨로 닷넷 환경을 완벽하게 갖추자.]


왜 부트스트래퍼가 필요한지가 궁금할것이다...

먼저 사용자 환경은 내 프로그램이 필요로 하는 기본 요구사항들을 만족하고 있는지 알수없다.

실행해보기 전까지는 말이다.


정작 자신이 개발한 프로그램을 배포해보면 정말 이유도 가지가지다.

안되는 이유말이다.


이 이유들중 가장 중요한 이유 두가지를 부트스트래퍼를 이용해서 처리해 보도록 하겠다.


먼저 아래의 url 로 이동해서 MS에서 제공하는 Microsoft Visual Studio .NET 2003 Bootstrapper Plug-In 을 이용해보도록 하겠다.


http://www.microsoft.com/downloads/deta ··· ang%3Dko


간단하게 PluginInstaller.msi 파일을 설치하는것만으로도 셋팅은 끝난다.

정말이지 쉽고 간편하다.

몰라서 사용하지 못하는것 뿐이지 어려워서 사용하지 못하는건 적어도 아니다.

사용자 삽입 이미지

설치하고나면 이렇게 부트스트래퍼가 생긴다.

이 프로그램이 설치되어있는 컴퓨터에서 배포프로젝트를 만들어서 배포하게되면 자기가 개발한 용량보다 약 25메가정도가 크다는것을 금방알수가 있을것이다.


왜냐하면 이렇게 배포된 인스톨 프로그램에는 기본적으로 프레임웍 1.1 과 언어팩, 그리고 mdac 등이 기본적으로 포함되어있으며 인스톨시에 사용자 컴퓨터에 설치되지 않았을경우 바로 인스톨 화면을 제공해주기 때문이다.


실제 적용해보면 아주 부드럽게 화면과 화면이 이어지며 프래임워크와 mdac 와 언어팩들을 설치해주는 화면을 볼수있을것이다.


기존에는 배포하는 프로그램설치하랴.. 프래임웍 설치하랴.. mdac 설치하랴..언어팩설치하랴... 문제도 많고 말도 많았는데 이런 기능을 이용하면 한번 설치로 상쾌하게 인스톨 시킬수 있을것이다.


정리

이 이외에도 MSDN에서는 아주 유용한 기능들을 많이 제공한다.

".NET" 카테고리의 다른 글
  • 데이터 바인딩 어플리케이션 만들기 기초 (0)2007/01/11
  • 개체를 이용한 데이터 바인딩 어플리케이션 만들기 (0)2007/01/11
  • 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로... (0)2007/01/11
  • Windows Form : 윈도우 폼 꾸미기 (0)2007/01/11
  • Windows Form 기반의 프로그램에서, 폼에서 컨트롤... (0)2007/01/11
2007/01/11 09:39 2007/01/11 09:39
Posted by webdizen
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2577

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:35

Windows Form : 윈도우 폼 꾸미기

고수닷넷 - 빵이님

XP의 테마적용하기

별 생각없이 윈도우 프로그래밍을 했는데, 이사한 점을 발견한 경험이 있습니다. 운영체제는 XP으로 기본 스타일의 테마를 적용하여 사용했는데 작성한 프로그램의 스타일은 테마가 적용되지 않은 일반적인 스타일의 프로그램의 모양입니다. 알고보니, 테마는 프로그래미컬하게 구현 해줘야 한다는 것을 알게 되었습니다.


//폼이 실행되기 전에 테마적용을 가능하도록 설정합니다.

Application.EnableVisualStyles();

Application.Run(new frmTheme());


.NET 1.1 에서부터만 가능하며 .NET 1.0은 Application.EnableVisualStyle() 대신 .manifest 파일을 추가해 줘야합니다. 아직 프로그램에는 아무런 변화가 없습니다. XP의 테마를 적용시켜주기 위해서는, 각 컨트롤에 적용을 해주어야 합니다.


//버튼 컨트롤에 XP의 테마를 적용합니다.
button2.FlatStyle = FlatStyle.System;

FlatStyle 속성은 모든 컨트롤이 가지고 있는 속성이 아님을 주의하셔야 합니다. 폼의 자식컨트롤의 컬렉션을 foreach로 반복하여 스타일을 적용하는 것도 가능합니다. 이렇게하면, 나중에 어떤 컨트롤이 추가되더라도 추가 코딩이 없게됩니다.


평범하지 않은 폼모양

저는 동영상 플레이어로 곰플레이어를 사용합니다. 곰플레이어의 폼모양을 보면 일반 윈도우 프로그램과는 모양이 많이 다릅니다. 폼 자체의 모양을 일일히 그려 줬겠지만, 일반 윈도우 프로그램보다는 친근감 있습니다. 곰플레이어는 닷넷으로 만들어 진것 같지는 않으나, 닷넷에서도 이런 폼은 쉽게 만들수 있도록 합니다.


먼저 해줘야 할 작업은 폼의 프레임과 백그라운드를 투명하게 해줘야 합니다.


this.TransparencyKey = this.BackColor;  //폼의 배경색과 같은색은 모두 투명처리한다.

this.FormBorderStyle = FormBorderStyle.None; //프레임을 안보이게 한다.


그리고 사각형의 영역설정을 해줍니다. 이 작업은 폼로드시 실행해주면 됩니다.


Rectangle rect = this.ClientRectangle;

using(GraphicsPath path = new GraphicsPath())

{

   path.AddEllipse(rect);

   this.Region = new Region(path);

}


만약, 폼의 크기를 변경가능하게 한다면, SizeChanged 이벤트에도 같은 처리를 해줍니다.


그 후에, OnPaintBackground() 메서드를 재구현합니다.


protected override void OnPaintBackground(PaintEventArgs pevent)

{

   pevent.Graphics.FillRectangle(Brushes.Violet, this.ClientRectangle);

}


이제 실행 시켜보면 이쁘진 않지만 일반폼과는 다른 폼 모양이 그려지게 됩니다.


문제점이야 많겠지만, 당장 이 프로그램을 불편하게 하는 문제점은 제목표시줄이 없기때문에 폼을 옮길 수 가 없다는 것입니다. 해결책은 폼 몸체를 드래그 할 수 있도록 하는 것입니다. 다음을 각 이벤트에 등록하면 이 문제점은 해결 됩니다. 참고로 pointClick의 타입은 Point 입니다.


private void frmTheme_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)

{

   if(e.Button != MouseButtons.Left) return;

   pointClick = new Point(e.X, e.Y);

}


private void frmTheme_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)

{

   if(pointClick == Point.Empty) return;

   Point location = new Point(this.Left + e.X, this.Top + e.Y - pointClick.Y);

   this.Location = location;

}


private void frmTheme_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)

{

   if(e.Button != MouseButtons.Left) return;

   pointClick = Point.Empty;

}


이제, 어설프게나마 폼을 이동시킬 수 있게 되었습니다.


정리

윈도우 폼을 좀더 화려하게 꾸밀수 있는 방법을 알아보았습니다. 이를 응용하면 사용자가 좀더 친숙한 환경을 꾸밀수 있습니다.

".NET" 카테고리의 다른 글
  • 개체를 이용한 데이터 바인딩 어플리케이션 만들기 (0)2007/01/11
  • 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로... (0)2007/01/11
  • Windows Form : 윈도우 폼 꾸미기 (0)2007/01/11
  • Windows Form 기반의 프로그램에서, 폼에서 컨트롤... (0)2007/01/11
  • .NET의 Enterprise Service(COM+) 이해 (0)2007/01/11
2007/01/11 09:35 2007/01/11 09:35
Posted by webdizen
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2576

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:31

Windows Form 기반의 프로그램에서, 폼에서 컨트롤의 키 입력을 가로채기

고수닷넷 - Kenial님

:)

사실 뭐 소개하고 할 내용도 없는 것 같군요. 곧바로 들어가겠습니다아~



키보드 메시지는 어디로 갔는가?


Kenial은 아직 예전 Win32 API나 MFC에서 윈도 메시지를 가지고 이런 저런 기능을 만들어내는데에 익숙하다. 최근 들어서는 개발속도의 이점 및 배포시의 편리함 때문에 닷넷 기반의 프로그램을 많이 만들어내고 있는데, 이번에 만들어야 하는 프로그램은 이런 기능이 필요했다 :


 Alt + F4로 프로그램이 종료되지 않아야 한다.


이 문장을 보고 거의 반사적으로 WM_KEYDOWN을 떠올리는 것이 Kenial에게는 당연한 일인 것이지만, 닷넷에는 닷넷의 방법이 있다.


자. Alt + F4를 막아보도록 하자.



KeyPress 이벤트, KeyPreview 속성


닷넷 프레임워크의 이벤트에 익숙해 있다면, KeyPress 이벤트로 키보드의 입력을 받을 수 있다는 것을 알고 있을 것이다. 먼저 다음과 같은 간단한 폼을 만들어 보자 :

사용자 삽입 이미지

그리고 다음과 같이 폼의 KeyPress 이벤트 핸들러를 코딩해보자 :


private void Form1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) {

           System.Diagnostics.Debug.WriteLine("Form1_KeyPress");

}


프로그램을 실행하고 뭔가 키보드 입력을 해 보면 출력창에 "Form1_KeyPress"라는 내용이 나와야 할텐데, 출력되지 않는다. 어째서일까? 간단하게 설명하자면, 키보드 메시지를 텍스트 박스가 먼저 받아서 처리해 버려서 폼의 이벤트 핸들러는 해당 메시지를 받지 못하기 때문이다.


이는 KeyPreview 속성을 true로 설정함으로써, 해당 폼의 이벤트 핸들러가 해당 메시지를 먼저 처리하도록 해서 해결할 수 있다. 앞의 프로그램에서 KeyPreview 속성을 true로 설정하면, 텍스트박스에 글자가 입력되면서 출력창에 "Form1_KeyPress"가 출력되는 것을 확인할 수 있다.



KeyDown 이벤트


하지만 KeyPress 만으로는 단지 눌려진 키의 char 코드만 받을 수 있을 뿐이고, Alt, Ctrl 등의 조합키의 입력까지 알아내려면 KeyDown 이벤트를 사용해야 한다. 다음과 같이 이벤트 핸들러를 만들자 :


private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)

{

   // 폼에서 종료 가능한 키 조합을 막아버리자

   if(e.KeyData == (Keys.Alt | Keys.F4))

       e.Handled = true;

}


KeyData 속성은 금방 이해된다. 그럼 Handled 속성은?

이 속성은 아까 위에서 설명한 '이 키보드 메시지가 처리되었음'을 나타내는 속성이다. false로 설정되어 있으면 Form1에서 포커스를 갖고 있는 자식 컨트롤(여기서는 텍스트박스)에 해당 키보드 메시지를 전달해 주겠지만, true로 설정하면 키보드 메시지를 텍스트박스에도 전달하지 않고 자신의 부모(=System.Windows.Forms.Form, 그리고 그 부모 클래스)에게도 전달하지 않는다.


결과적으로, Alt + F4 키 입력으로 프로그램이 종료되지 않게 된다.



정리


사실 키보드 입력을 막는 방법은 이것으로 끝이 아닙니다. 최상위 윈도우가 받는 키보드 메시지(ctrl + alt + del, alt + tab) 등은 이것만으로는 막을 수 없고, 키보드 입력과 관련된 dll을 후킹해야 가능한 일입니다.


하지만 Kenial은 아직 닷넷 기반에서의 후킹은 해본적이 없어서 ;

사실 VC++에서 구현하는 것과 별다른 차이는 없을 거라고 생각하긴 합니다만, 뭐 천천히 적어나가도록 하겠습니다 : )


그럼 : )



p.s:이번 아티클은 분량도 적고.. 캡처할게 별로 없다보니 너무 편하네요 ;


p.s2:그리고, 이벤트에 익숙하지 않으신 분이라면 차라리 KeyDown 이벤트보다는 ProcessCmdKey 함수를 오버라이딩해서 사용하시는 편이 더 나을듯도 합니다 : )

".NET" 카테고리의 다른 글
  • 닷넷 윈폼 배포시 Framework, mdac 등 병합모듈로... (0)2007/01/11
  • Windows Form : 윈도우 폼 꾸미기 (0)2007/01/11
  • Windows Form 기반의 프로그램에서, 폼에서 컨트롤... (0)2007/01/11
  • .NET의 Enterprise Service(COM+) 이해 (0)2007/01/11
  • Microsoft Application Blocks for .NET (0)2007/01/11
2007/01/11 09:31 2007/01/11 09:31
Posted by webdizen
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2575

Leave your greetings.

[로그인][오픈아이디란?]

Programming/.NET2007/01/11 09:24

.NET의 Enterprise Service(COM+) 이해

고수닷넷 - 방랑자님

Shannon Pahl
Microsoft Corporation

요약: Microsoft .NET과 COM+ 서비스 통합의 이면에 있는 기술적 정보를 제공하고 관리되는 코드에 사용할 수 있는 서비스에 대해 설명합니다(26페이지/인쇄 페이지 기준).

목차

소개
트랜잭션
배포
서비스되는 구성 요소
개체 수명
보안
원격 구성 요소
결론

소개

이 기사에서는 Microsoft.NET Framework와 COM+ 서비스를 잘 알고 있어야 합니다. Enterprise Services를 반드시 알고 있을 필요는 없지만 알고 있으면 도움이 됩니다. 이 항목에 대한 자세한 배경은 다음을 참조하십시오.

  • Derek Beyer의 저서, "C# COM+ Programming"(Professional Mindware, 2001)

COM은 구성 요소 기반 응용 프로그램을 작성하는 한 가지 방법을 제공합니다. COM 구성 요소를 작성하는 데 필요한 측정 작업이 중요하고 반복된다는 사실은 잘 알려져 있습니다. COM+는 COM의 새로운 버전이 아닙니다. COM+는 구성 요소에 대한 서비스 인프라를 제공합니다. 배포가 쉽고 처리량이 많은 확장 가능한 서버 응용 프로그램을 작성하려면 구성 요소를 작성한 다음 COM+ 응용 프로그램에 설치합니다. 구성 요소에서 서비스를 사용할 필요가 없을 경우 해당 구성 요소를 COM+ 응용 프로그램에 설치할 필요가 없습니다. 응용 프로그램을 처음부터 트랜잭션, 개체 풀링, 동작 구문 등을 사용하도록 디자인하면 확장성 및 처리량을 얻을 수 있습니다.

.NET Framework는 구성 요소 기반 응용 프로그램을 작성하는 다른 방법을 제공하며, 보다 나은 도구를 지원하는 COM 프로그래밍 모델, 공용 언어 런타임(CLR), 훨씬 쉬워진 코딩 구문보다 이점들을 가지고 있습니다. COM+ 서비스 인프라는 관리되는 코드와 관리되지 않는 코드 모두에서 액세스될 수 있습니다. 관리되지 않는 코드의 서비스를 COM+ 서비스라 합니다. .NET에서는 이 서비스를 Enterprise Services라 합니다. ServicedComponent에서 클래스가 파생될 경우 한 구성 요소를 위해 서비스가 필요하다는 것을 나타냅니다. 구성 요소에서 서비스를 사용할 필요가 없을 경우 ServicedComponent에서 해당 구성 요소를 파생할 필요가 없습니다. 프로그래머들이 서버 기반 응용 프로그램을 작성할 수 있도록 도구 지원을 향상시켰지만, 우수한 프로그래밍 영역에서는 여전히 확장성 및 처리량 문제가 존재합니다. 서비스의 이면에 있는 기본적인 생각은 처음부터 처리량과 확장성을 고려해서 디자인하고 Enterprise Services를 사용하여 해당 위치에서 디자인 패턴을 쉽게 구현하게 하는 것입니다.

서비스 인프라 디자인은 COM 또는 구성 요소와 거의 관련이 없다고 주장할 수도 있습니다. 그러나, COM+ services는 이제 COM 구성 요소와 .NET 구성 요소에 적용될 수 있으며, 심지어는 ASP 페이지, 임의의 코드 블록 등과 같은 구성 요소가 아닌 엔티티에도 적용될 수 있습니다(Microsoft Windows XP의 Components COM+ 기능 없는 서비스 참조).

현재 사용할 수 있는 모든 COM+ 서비스는 .NET 및 COM 개체에 사용될 수 있습니다. 이러한 서비스로는 트랜잭션, 개체 풀링 및 작업 문자열, JIT, 동기화, 역할 기반 보안, CRM, BYOT 등이 있습니다. Microsoft Windows 2000의 전체 서비스 목록은 Platform SDK의 COM+에 제공되는 서비스를 참조하십시오. Microsoft Windows XP에는 .NET 구성 요소와 함께 사용될 수 있는 추가 서비스가 있는 COM+ 1.5라는 새로운 COM+ 버전이 포함되어 있습니다.

트랜잭션

서비스를 사용하는 관리되는 응용 프로그램을 작성하려면 서비스를 필요로 하는 클래스를 ServicedComponent에서 파생하고 다양한 사용자 지정 특성을 사용하여 실제로 필요한 서비스를 지정해야 합니다. 이 절에서는 서비스의 개념과 서비스가 관리되는 코드 작성에 어떤 영향을 미치는지에 대해 설명합니다. 자세한 설명은 이후의 절들에 제공됩니다.

Account라는 클래스(실제 코드는 뒤에 나열됨)가 작성되어 BankComponent 어셈블리에 있다고 가정합니다. 이 클래스는 다음과 같이 사용될 수 있습니다.

BankComponent Client

using system;
using BankComponent;
namespace BankComponentClient
{
      class Client
      {
        public static int Main() 
        {
          Account act = new Account();
          act.Post(5, 100);
          act.Dispose();
          return 0;
        }
      }
}

클라이언트를 구축하려면 BankComponent 네임스페이스에 참조를 추가해야 합니다. 또한, BankComponentClient 네임스페이스에 System.EnterpriseServices 어셈블리에 대한 참조를 추가해야, 클라이언트가 Dispose() 및 ServicedComponent 생성자를 호출합니다. 이 생성자는 BankComponent가 포함된 어셈블리가 아니라 System.EnterpriseServices에 정의되는 메서드입니다. 다음은 파생 클래스가 모든 기본 클래스 메서드를 재정의하지 않는 일반적인 .NET 요구 사항입니다.

BankComponent Server 코드는 .NET에서 트랜잭션을 사용하는 Account 클래스 구현을 표시합니다. Account 클래스는 System.EnterpriseServices.ServicedComponent 클래스에서 파생됩니다. Transaction 특성은 클래스에 트랜잭션이 필요하다고 표시합니다. Transaction 특성이 사용되기 때문에 Synchronization 및 JIT 서비스는 자동으로 구성됩니다. AutoComplete 특성은 메서드를 실행하는 동안 처리되지 않은 예외가 throw될 경우 런타임에 트랜잭션을 위한 SetAbort 함수가 자동으로 호출되고, 그렇지 않을 경우 SetComplete 함수가 호출되도록 지정하는 데 사용됩니다. ApplicationName 특성은 이 응용 프로그램의 서비스 구성 데이터를 저장하는 COM+ 응용 프로그램에 이 어셈블리를 연결합니다. 이 클래스에 필요한 추가 수정은 코드에서 강조 표시됩니다.

BankComponent Server

using System.EnterpriseServices;
[assembly: ApplicationName("BankComponent")]
[assembly: AssemblyKeyFileAttribute("Demos.snk")]

namespace BankComponentServer
{
      [Transaction(TransactionOption.Required)]
      public class Account : ServicedComponent
      {
            [AutoComplete]
            public bool Post(int accountNum, double amount)
            {
            // SetComplete를 호출하지 않고 데이터베이스를 업데이트합니다. 
            // 예외가 throw되지 않으면 SetComplete가 자동으로 호출됩니다. 
            }
      }
}

BankComponent Server 네임스페이스에 있는 코드는 .NET에서 COM+ 서비스를 얼마나 쉽게 사용할 수 있는지를 보여 줍니다. 아래 목록은 코딩에서 배포까지의 전체 프로세스에 대한 요약입니다.

  1. 서버 어셈블리를 작성합니다.
  2. 어셈블리 작성:
    1. 어셈블리에 서명합니다. 키 파일은 프로젝트마다 한 번만 생성될 수 있으므로 컴파일할 때마다 생성할 필요가 없습니다. 키는 Microsoft .NET 명령 프롬프트 및 sn.exe를 다음과 같이 사용하여 생성될 수 있습니다.
      sn ?k Demos.snk
      
    2. 코드를 컴파일합니다. System.EnterpriseServices에 대한 참조가 추가되어야 합니다.
  3. 응용 프로그램을 배포합니다.

    서비스되는 구성 요소를 사용하는 어셈블리는 COM+ 카탈로그에 등록되어야 합니다. ServicedComponent 클래스와 사용자 지정 특성은 관리되는 코드에서 COM+ 서비스에 액세스하는 두 가지 핵심 개념입니다. 서비스의 구성은 COM+ 카탈로그에 저장됩니다. 개체는 CLR 내부에서 존재하고 실행됩니다. 관리되는 개체와 관련된 COM+ 컨텍스트는 그림 1에 표시되어 있으며 다음 두 절에서 자세히 설명합니다.

    사용자 삽입 이미지

    그림 1. 관리되는 구성 요소와 연결된 서비스

    COM+ 구성 요소에서는 카탈로그를 수동으로 구성해야 하지만, 서비스되는 구성 요소에서는 카탈로그가 코드에 있는 특성을 기반으로 업데이트될 수 있습니다. 명령줄 도구 regsvcs.exe를 사용하거나 관리되는 API를 액세스하는 스크립트를 작성하여 어셈블리를 명시적으로 등록할 수 있습니다. 자세한 내용은 아래의 배포 정보 절을 참조하십시오. 개발하는 동안 편리한 때에 어셈블리를 응용 프로그램 디렉터리에 간단하게 복사하여 배포하는 XCopy 배포 기능이 제공됩니다. 클라이언트 응용 프로그램이 ServicedComponent에서 파생되는 클래스의 인스턴스를 만들 때마다 런타임에서 COM+ 응용 프로그램의 어셈블리가 이미 등록되어 있는지 여부를 검색합니다. 등록되어 있지 않을 경우 로컬 디렉터리에서 어셈블리를 검색하여, 어셈블리가 있으면 해당 어셈블리에 있는 모든 서비스된 구성 요소를 COM+ 응용 프로그램에 등록하여 활성화될 수 있는 상태로 만듭니다. 이 등록을 지연 등록이라 하지만, 모든 시나리오에 적용되지는 않습니다. 예를 들어, COM+ 서버 응용 프로그램으로 표시되는 어셈블리는 모두 명시적으로 등록해야 합니다(아래 참조). 지연 등록은 관리되는 서비스 구성 요소를 호출하는 관리되지 않는 클라이언트에는 적용되지 않습니다. 지연 등록은 개발 기간 동안에는 유용하지만, 그 외에는 스크립트, 코드 또는 RegSvcs를 사용하여 어셈블리를 등록합니다.

  4. 어셈블리를 GAC에 저장합니다. 자세한 내용은 배포 절을 참조하십시오.
  5. 클라이언트를 실행합니다.

배포

사용자 지정 특성은 관리되는 코드에서 COM+ 서비스를 액세스하는 두 가지 핵심 개념 중 하나입니다. 사용자 지정 특성은 이전 코드 목록의 Transaction 사용자 지정 특성과 같은 필수 서비스를 지정하는 데 사용됩니다. 이 특성은 어셈블리 메타데이터에 있는 서비스에 대한 구성 옵션을 저장합니다. 사용자 지정 특성은 일부 코드가 어셈블리를 로드하고 반사를 사용하여 특성 인스턴스를 만든 다음 해당 특성에 대한 메서드를 호출하여 특성에 저장된 서비스 구성을 추출하는 방식으로 사용됩니다. 그런 다음 COM+ 카탈로그에 정보를 기록할 수 있습니다. 모든 단계를 실행하는 코드는 EnterpriseServices.RegistrationHelper에 포함됩니다. 등록 프로세스를 보다 쉽게 만들기 위해 모든 등록 양식에서 EnterpriseServices.RegistrationHelper 구성 요소를 사용합니다. 이 구성 요소는 COM 개체 뿐만 아니라 관리되는 클래스로도 액세스될 수 있습니다.

사용자 삽입 이미지


그림 2. 서비스되는 구성 요소 등록

개념적으로 RegistrationHelper는 다음 단계를 실행합니다.

  • RegistrationServices.RegisterAssembly를 사용하여 레지스트리에 어셈블리를 등록합니다. 따라서, 클래스는 레지스트리에 관리되는 코드로 작성된 COM 구성 요소로 표시되며 mscoree.dll을 가리키는 InprocServer32 키를 가집니다. 관리되는 클래스가 인터페이스를 구현하지 않을 경우 ClassInterfaceAttribute가 사용되지 않으면 해당 클래스의 공개 메서드가 COM+ 카탈로그에 표시되지 않습니다. 이것은 메서드 수준에 연결된 서비스 구성이 카탈로그에 저장될 수 없음을 의미합니다. 그러나, 일부 COM+ 서비스는 메서드 수준에서 구성될 수 있으며 인터페이스를 COM+ 카탈로그에 표시된대로 노출하려면 구성 요소가 있어야 합니다. 예를 들어, 메서드 수준의 COM+ 역할 기반 보안에서는 서비스를 구성하기 위한 인터페이스를 구현하는 구성 요소가 필요합니다. 이 문제에 대해서는 보안 절에 자세히 설명되어 있습니다.
  • TypeLibConverter를 사용하여 어셈블리에서 COM 형식 라이브러리를 생성합니다. ConvertAssemblyToTypeLib.
  • 형식 라이브러리를 등록합니다. 지금까지는 RegAsm.exe /tlb와 거의 동일합니다.
  • COM+ 응용 프로그램을 찾거나 만듭니다. 이름은 ApplicationName 특성, 어셈블리 이름, 제공된 응용 프로그램 이름/GUID 등에서 추출됩니다.
  • 형식 라이브러리에서 COM+ 관리 API를 사용하여 COM+ 응용 프로그램을 구성합니다.
  • 사용자 지정 특성을 모두 진행한 다음 IConfigurationAttribute를 사용하여 COM+ 카탈로그에 특정 서비스에 대한 구성 데이터를 기록합니다.

RegistrationHelper는 .NET를 설치할 때 만들어지는 COM+ 응용 프로그램에 있는 RegistrationHelperTx 클래스를 사용하여 트랜잭션 내부에서 이 단계를 실행하려고 시도합니다. 따라서, 등록에 실패하면 COM+ 카탈로그와 레지스트리가 원래의 상태로 복원됩니다. 그러나, 현재 생성된 형식 라이브러리는 디스크에(어셈블리가 GAC에 있을 경우 GAC에) 그대로 유지됩니다. 등록 중인 어셈블리가 또한 COM+ 서비스를 사용하는 다른 어셈블리를 참조할 경우 종속성 그래프에 있는 모든 어셈블리는 위에 나열된 단계를 실행합니다.

RegistrationHelper는 COM+ 카탈로그를 액세스하기 때문에 시스템에 관리되지 않는 코드 사용 권한 및 관리자 권한이 있어야 합니다. 클라이언트의 RegistrationHelper(지연 등록, RegSvcs, 스크립트/코드)의 경우에도 마찬가지입니다. 이것은 인터넷에서 다운로드했거나 네트워크 공유에 저장된 코드는 등록될 수 없음을 의미합니다.

트랜잭션 필요, 동기화를 사용 안 함으로 설정과 같이 호환되지 않는 특성 조합을 코딩할 수 있습니다. 이러한 조합은 현재 컴파일 기간이 아니라 COM+ 카탈로그에 기록하는 등록 기간 동안 검색됩니다. 일부 특성은 다른 특성에 암시적으로 종속됩니다. 예를 들어, Transaction 특성만 사용해도 Transaction, JustInTimeActivation 및 Synchronization 특성을 사용하는 것과 동일합니다. 관리되는 구성 요소가 등록되면 특성을 사용하여 '구성되지 않은' 기본값을 덮어쓰지 않는 한 COM+ 카탈로그 기본값이 사용됩니다. 예를 들어, 구성 요소를 등록하고 Transaction 특성을 지정하지 않으면 카탈로그의 트랜잭션 설정에 대한 구성되지 않은 기본값이 TransactionOption.Disabled로 설정됩니다. 개발자는 이 접근법을 사용하여 구성 요소에 어느 특성이 더 이상 필요하지 않을 경우 코드에서 해당 특성을 제거할 수 있습니다. 트랜잭션의 카탈로그 항목은 어셈블리가 다시 등록될 때 적절하게 다시 설정됩니다. 구성되지 않은 기본값에 대한 자세한 내용은 온라인 설명서를 참조하십시오. 기본 구성 값은 특성 매개 변수의 기본 값입니다. 예를 들어, [Transaction] 특성을 사용하는 것은 TransactionOption.Required를 나타냅니다.

관리되는 클래스의 서비스에 대한 구성 데이터는 COM+ 카탈로그에 저장되기 때문에 어셈블리가 등록된 후 특정 카탈로그 항목이 관리상 수정될 수도 있습니다. 일부 서비스는 이런 방식으로 수정되어서는 안됩니다. 예를 들어, 카탈로그에서 트랜잭션 서비스를 비활성화하면 코드가 올바르게 작동되지 않을 수 있습니다. 개체 생성 문자열, 보안 역할 등과 같은 배포 관련 설정은 등록 후에도 조작될 수 있습니다. 사후 등록 설정을 만들 경우에는 서비스된 구성 요소가 포함된 어셈블리를 XCopy 배포만으로 완전히 배포할 수 없을 수도 있습니다. COM+ 응용 프로그램의 가져오기/내보내기 기능은 응용 프로그램을 현재 상태로 분배하도록 도와 줍니다. 가져오기 및 내보내기에 대한 자세한 내용은 원격 절에 제공됩니다.

카탈로그가 구성 데이터에서는 참조되지 않고 어셈블리 메타데이터에서만 추출되는 경우도 있습니다. AutoComplete, JIT, 개체 풀링(풀 크기는 카탈로그에서 추출됨), 보안 메서드 특성 등의 경우가 그렇습니다. 이 문제에 대한 자세한 내용은 서비스를 설명하는 해당 절을 참조하십시오.

어셈블리를 등록하는 동안 COM+에 필요한 GUID가 자동으로 생성됩니다. 어셈블리가 서명되지 않을 경우 GUID는 형식 이름과 네임스페이스 이름만을 기반으로 하여 생성됩니다. 따라서, 어셈블리가 서명되지 않으면 고유하지 않은 GUID가 생성될 수 있습니다. COM+ 서비스를 사용하지 않지만 고유한 형식 이름이 필요한 .NET 어셈블리의 경우에도 마찬가지입니다. 따라서, COM+ 서비스를 사용하는 어셈블리는 서명되어야 합니다. 어셈블리가 서명되지 않으면 등록되지 않습니다. 등록은 COM+ 서비스를 사용하는 .NET 클래스에 글로벌 구성 데이터 저장소가 하나 있다는 의미를 내포합니다. 전용 어셈블리를 여러 응용 프로그램 디렉터리에 복사할 수 있더라도 궁극적으로는 그런 응용 프로그램들도 모두 서비스되는 구성 요소에 대해 하나의 구성 데이터를 참조합니다. 따라서, COM+ 카탈로그에서 구성 데이터를 변경하면 해당 클래스를 사용하는 모든 응용 프로그램이 영향을 받습니다. 이것은 서비스되는 구성 요소를 사용하는 동일한 어셈블리 사본을 구성하는 Microsoft ASP.NET 구성에 여러 vroot가 있다는 증거입니다. 동일한 응용 프로그램이 여러 구성을 갖게 하려면 Microsoft Windows .NET에서 COM+ 파티션을 사용합니다. .NET에서 COM+ 파티션을 사용하려면 ApplicationID 특성을 사용하지 않고 동일한 구성 요소를 여러 파티션에 설치하십시오. COM+에는 고유한 응용 프로그램 ID가 필요합니다.

일반적으로 클라이언트가 클라이언트 응용 프로그램 디렉터리가 아닌 다른 디렉터리에 있는 어셈블리에 액세스해야 하거나 어셈블리가 클라이언트 디렉터리에 없는 다른 프로세스에 로드되는 경우 GAC가 사용됩니다. 개념적으로, 서비스되는 구성 요소를 사용하는 전용 어셈블리는 실제로 공유되는 구성 데이터를 사용하는 공유 어셈블리입니다. ApplicationActivationOption이 라이브러리에 설정되면 어셈블리 내의 클래스에서 트랜잭션을 사용할 수 있습니다. 모든 어셈블리가 동일한 디렉터리에서 로드될 경우 하나의 클라이언트에서 해당 어셈블리를 사용할 수 있습니다. ApplicationActivationOption을 사용하는 어셈블리가 서버에 설정되어 있으면, 해당 어셈블리는 dllhost.exe에 의해 로드되며, 대체로 클라이언트와 동일한 디렉터리에 존재하지 않습니다. COM+ 서버 응용 프로그램에서 서비스되는 구성 요소를 사용하는 어셈블리는 GAC에 배치되어야 합니다. COM+ 라이브러리 응용 프로그램에서 서비스되는 구성 요소를 사용하는 어셈블리는 서로 다른 디렉터리에 있지 않는 한 GAC에 배치되지 않아도 됩니다. ASP.NET에서 호스팅될 경우에만 예외입니다. 섀도 복사를 사용하여 올바르게 작업하려면 어셈블리를 GAC에 배치하지 않아야 합니다.

서비스되는 구성 요소를 사용하는 .NET 응용 프로그램을 제거하려면 GAC에서 해당 어셈블리를 제거(GAC에 등록되어 있을 경우)하고, regsvcs.exe를 사용하여 COM+에서 어셈블리를 다시 등록한 다음 해당 어셈블리와 연결된 형식 라이브러리를 삭제합니다.

버전 지정

GUID 특성을 사용하여 COM+에 필요한 GUID를 고정시킬 수 있습니다. 그러나, GUID를 명시적으로 사용하는 대신 버전 지정을 사용하는 것이 좋습니다. 클래스가 서로 다른 서비스 특성으로 데코레이팅되거나 새로운 메서드 서명이 만들어질 때마다 어셈블리의 주 버전 번호 또는 부 버전 번호가 증분되어야 합니다. 등록은 버전마다 한 번만 실행하면 됩니다. 새로운 어셈블리 버전을 등록하면 해당 버전에 대한 새로운 GUID가 생성되고 구성 요소가 동일한 구성 요소 이름을 사용하여 동일한 COM+ 응용 프로그램에 등록됩니다. 따라서 구성 요소는 COM+ 응용 프로그램에 여러 번 표시됩니다. 그러나, 각 구성 요소에는 GUID에 의해 지정되는 고유한 ID가 있습니다. 각 인스턴스는 구성 요소의 특정 버전을 참조합니다. Microsoft Visual Studio .NET을 사용하여 .NET 응용 프로그램을 작성할 때 통지되는 경우도 있습니다. 이 환경에서는 프로젝트에 [assembly: AssemblyVersion("1.0.*")] 특성이 추가됩니다. 프로젝트는 빌드마다 새로운 빌드 번호가 생성되기 때문에 어셈블리가 다시 등록되면 GUID가 새로 생성됩니다. 따라서, 가능하면 빌드 번호를 수동으로 증분하는 것이 좋습니다. 클라이언트는 CLR 버전 정책을 사용하여 어셈블리에 바인딩하기 때문에 COM+ 응용 프로그램에서 올바른 클래스 버전이 사용됩니다. 다음은 서비스되는 구성 요소를 사용하는 어셈블리를 작성하는 side-by-side 시나리오입니다. 아래에 사용된 일부 활성화 항목은 다음 절에 설명되어 있습니다.

  • 관리되는 클라이언트, 관리되는 서버, 고정되지 않은 GUID가 어셈블리에서 사용됩니다.
  • 클라이언트는 버전 정책에 지정된 어셈블리를 로드합니다.
  • 관리되는 클라이언트, 관리되는 서버, 고정 GUID가 사용됩니다.
  • 클라이언트가 클래스를 활성화하고 버전 정책을 사용하여 이전 어셈블리 버전으로 이동하면, 활성화하는 동안 코드에 있는 고정 GUID를 사용하여 카탈로그에서 서비스 정보를 추출합니다. 따라서, 이 GUID를 사용하여 마지막으로 등록된 어셈블리에서 추출한 정보를 사용하여 개체를 만듭니다. 실제로 개체가 최신 버전인 경우도 있기 때문에 만들어진 개체(v2)에서 코드에 있는 참조(v1)로 캐스트하려고 시도하면 형식 캐스트 예외가 발생될 수 있습니다.
  • 관리되는 클라이언트, 관리되는 서버, 고정되지 않은 GUID, 빌드 번호만 변경합니다.
  • 새로운 GUID가 생성되더라도 형식 라이브러리는 하나의 버전에 대해 두 개의 번호만 가질 수 있기 때문에 여전히 동일한 버전 번호를 갖습니다. 작동은 되지만, 버전 2를 버전 1 위에 설치하면 버전 1이 제거되고, 버전 2의 형식 라이브러리 등록이 취소됩니다. 해결 방법 1: 다음에 릴리스되는 .NET Framework(V1.1)에서는 형식 라이브러리를 사용하여 어셈블리 버전을 독립적으로 지정할 수 있게 하여 이 문제를 해결했습니다. 이것은 어셈블리 버전 번호를 변경하면 형식 라이브러리 버전도 함께 변경되어야 함을 의미합니다. 해결 방법 2: 주 버전 번호와 부 버전 번호만 사용합니다.
  • 관리되지 않는 클라이언트, 관리되는 서버, 고정되지 않은 GUID가 사용됩니다.
    • 클라이언트는 GUID를 사용하여 구성 요소를 만듭니다. Interop은 GUID에서 이름을 확인한 다음 버전 정책을 적용합니다. 동일한 어셈블리의 버전 1과 버전 2가 동일한 시스템에 있고 정책을 사용하여 버전 2로 이동하면 버전 2가 관리되지 않는 클라이언트가 됩니다.
    • 버전 1을 설치하고, 버전 2를 설치한 다음 버전 1을 제거합니다. 이제 버전 2로 리디렉션하는 버전 정책이 없으면 클라이언트가 구성 요소를 만들 수 없습니다. 또한, 버전 1 등록 정보에 대한 레지스트리 항목이 있어야 합니다. 제거된 버전 1에 대한 레지스트리 정보를 만드는 한 가지 방법은 Windows XP의 COM+ 별칭 기능을 사용하는 것입니다.

버전 지정은 동일한 COM+ 응용 프로그램에 있는 모든 구성 요소에 적용됩니다. 즉, 개별 응용 프로그램의 버전을 자동으로 지정할 수 있는 방법은 없습니다. 예를 들어, 버전 정책을 사용하여 응용 프로그램에 있는 역할의 버전을 지정할 수 없습니다. 응용 프로그램 이름 특성을 사용하여 응용 프로그램 버전을 지정합니다.

서비스되는 구성 요소

활성화

Enterprise Services 인프라는 컨텍스트의 개념을 기초로 합니다. 컨텍스트는 비슷한 실행 요구 사항을 갖는 개체를 위한 환경입니다. 서비스는 활성화하는 동안 및/또는 메서드 호출을 차단하는 동안 적용될 수 있습니다. COM+ 서비스는 관리되지 않는 코드로 작성되지만 .NET의 COM interop 기술을 사용할 때보다 .NET과 COM+ 서비스가 훨씬 깊게 통합됩니다. ServicedComponent에서 파생하지 않을 경우 등록 프로세스에서 원하는 효과를 얻을 수 없습니다.

서비스되는 구성 요소는 다양한 조합으로 활성화 및 호스트될 수 있습니다. 그림 3에서 설명한 것처럼 이 토론에서는 in-process(동일한 app-domain), 상호 app-domain(동일한 process), 상호 프로세스 활성화의 세 가지 경우를 참조합니다. 세 경우의 중요한 점은 구성 요소를 호출할 때 교차되는 범위입니다. in-process 활성화는 잠정적인 상호 컨텍스트 범위를 증가시키고, 상호 app-domain의 경우에는 상호 컨텍스트 범위와 상호 응용 프로그램 도메인 범위가 모두 있습니다. 반면에 상호 프로세스의 경우에는 상호 시스템/상호 프로세스와 상호 컨텍스트 범위가 함께 처리됩니다.

사용자 삽입 이미지


그림 3. 서비스되는 구성 요소의 활성화 호스트

서비스되는 구성 요소는 원격 .NET에 따라 구현되어 관리되지 않는 코드 또는 관리되는 코드로 작성된 서비스에 연결할 수 있는 포괄적인 메카니즘을 제공합니다. 서비스되는 구성 요소는 ContextBoundObject에서 파생되어 IDisposable와 같은 다양한 인터페이스를 구현합니다. ProxyAttribute 파생 사용자 지정 특성을 사용하면 CLR의 활성화 체인을 사용자에 맞게 쉽게 사용자 지정할 수 있습니다. 사용자 지정 실제 프록시를 작성하여 차단을 사용자에 맞게 설정할 수 있습니다. 새롭게 서비스되는 구성 요소 파생 클래스가 필요할 경우 활성화 호출이 CoCreateInstance에 대한 관리되는 C++ 래퍼를 실제로 호출하도록 활성화 체인을 사용자에 맞게 설정합니다. 이렇게 하면 COM+에서 이전에 등록된 어셈블리의 COM+ 카탈로그에 저장된 정보를 기반으로 하는 관리되지 않는 컨텍스트와 서비스를 설정할 수 있습니다. 이 단계는 지연 등록이 구현되는 단계이기도 합니다. 어셈블리를 등록하는 동안 InprocServer32 키는 mscoree.dll을 가리키므로, COM+ CreateInstance를 리디렉션하면 런타임으로 돌아와 실제 관리되는 개체가 만들어집니다. 따라서, 활성화하는 동안에 사용자 정의 실제 프록시 개체가 만들어집니다. 이 프록시의 in-process 버전을 서비스되는 구성 요소 프록시 또는 SCP라 합니다. 그림 4에 설명되어 있습니다.

사용자 삽입 이미지


그림 4. 활성화 경로

활성화 호출의 반환 경로에서는 관리되지 않는 COM+를 통해 관리되는 코드로부터 관리되는 참조를 마샬링한 다음 관리되는 코드로 돌아옵니다(그림 4, 라인 1의 역경로). 실제 개체가 만들어진 위치에 따라 클라이언트쪽에서 관련 양식으로 참조를 역마샬링합니다. in-process 활성화에서 그림 5는 참조가 투명한 프록시(TP)에 직접 참조로 역마샬링됨을 나타냅니다. 상호 app-domain 참조는 .NET 원격 프록시로 역마샬링됩니다. 상호 프로세스 또는 상호 시스템 참조(그림 6)에서는 추가 역마샬링이 필요합니다. 즉, COM interop은 활성화 및 역마샬링이 실행되는 동안 ServicedComponent에 의해 구현된 IManagedObject를 호출합니다. 원격 서비스되는 구성 요소 프록시(RSCP)는 활성화하는 동안 IServicedComponentInfo를 호출하여 서버 개체의 URI를 가져옵니다. 이것은 활성화하는 동안 두 개의 원격 호출이 만들어짐을 의미합니다. 메서드 수준에서 COM+ 역할 기반 보안이 필요할 경우 인터페이스를 역할에 연결해야 인프라가 해당 인터페이스를 호출할 때 역마샬링할 수 있습니다. 보안 절에서는 역할 기반 보안 구성에서 교차 프로세스 활성화와 마샬링이 갖는 함축된 의미에 대해 설명합니다.

사용자 삽입 이미지


그림 5. in-process 호출 인프라

사용자 삽입 이미지


그림 6. out of process 호출 인프라

사용자 지정 실제 프록시(차단용)를 만들고 관리되지 않는 컨텍스트를 만들기 위해 차단 서비스 구문을 실행하는 데 필요한 컨텍스트 인프라만 COM+에 남겨 두고 활성화 체인을 사용자에 맞게 설정했습니다. COM+ 컨텍스트는 이제 COM 개체가 아니라 관리되는 개체에 연결됩니다.

차단

그림 7에서는 in-process 메서드 호출 인프라를 보여 줍니다. 사용자 지정 프록시(SCP)를 사용하여 관리되는 호출을 차단할 수 있습니다. COM+ 컨텍스트 ID는 활성화하는 동안 SCP에 저장됩니다. 관리되는 개체가 서비스되는 구성 요소를 호출하면 대상 SCP에 저장된 컨텍스트 ID를 현재 컨텍스트의 컨텍스트 ID와 비교하여 두 컨텍스트 ID가 동일할 경우 실제 개체에서 호출이 직접 실행됩니다. 컨텍스트 ID가 서로 다르면 SCP가 COM+에 대한 호출을 만들어 컨텍스트를 전환한 다음 메서드 호출을 받을 서비스를 렌더링합니다. in-process 호출의 경우에는 AppDomain이 COM+가 되는 점을 제외하고는 AppDomain.DoCallBack과 비슷합니다. 'DoCallBack' 함수가 COM+(그림 7의 2단계)에 전달되어 컨텍스트를 전환하고 서비스를 렌더링한 다음 콜백 함수가 SCP를 호출합니다. SCP는 데이터 마샬링을 실행하고 실제 개체에서 메서드를 호출합니다. 메서드가 종료되면 반환 경로를 통해 COM+가 메서드 호출을 빠져나올 수 있는 구문을 렌더링할 수 있습니다(그림 7의 5단계). COM+는 서비스를 렌더링하는 데만 사용됩니다. 데이터 마샬링과 메서드 호출은 .NET 런타임에서 실행되므로, 메서드를 호출할 때 유형을 변환(예: String에서 BSTR로)할 필요가 없습니다. COM interop를 in-process 호출에 사용한 경우 데이터를 마샬링해야 합니다. in-process 호출의 경우 관리되지 않는 코드의 서비스 렌더링 요청은 COM interop 호출이 아닙니다.

사용자 삽입 이미지


그림 7. in-process 호출 인프라

정적 메서드에 대한 호출은 투명하고 실질적인 프록시에 전달되지 않습니다. 따라서, 정적 메서드에서는 차단 서비스를 사용할 수 없습니다. 정적 메서드는 클라이언트의 컨텍스트 내부에서 호출됩니다. 내부 메서드는 올바른 컨텍스트에서 호출됩니다. 이것은 새 트랜잭션을 위해 구성된 개체에 대한 내부 메서드를 호출하는 클라이언트가 새 트랜잭션에 포함됨을 의미합니다. 그러나, 메서드 수준 서비스는 COM+ 카탈로그(자세한 내용은 이 항목 다음에 있는 보안 항목 참조)에 인터페이스가 있어야 하기 때문에 내부 메서드는 메서드 수준 서비스를 위해 구성될 수 없습니다. 서비스는 속성에 적용될 수 있지만 메서드 수준 속성(예: AutoComplete)은 getter/setter 메서드에 개별적으로 배치되어야 합니다.

AutoComplete 특성을 활용하면 코드를 작성하지 않고 트랜잭션을 사용하여 서비스에 편리하게 액세스할 수 있습니다. ContextUtil.SetAbort 또는 ContextUtil.SetComplete를 사용할 수도 있습니다. 메서드의 속성에 있는 확인란 하나를 설정하여 COM+ 탐색기에서 이 서비스를 구성할 수 있습니다. 그러나, 관리되는 개체는 인터페이스를 구현할 필요가 없습니다. 서비스되는 구성 요소의 경우에도 마찬가지입니다. 인터페이스에서 메서드가 선언되지 않으면 등록 시 카탈로그에 메서드 수준 서비스를 구성할 수 없으므로, 구성이 메타데이터에만 저장될 수 있습니다. 메서드에 대한 인터페이스가 없으면 AutoComplete 특성이 있을 경우 IRemoteDispatch.RemoteDispatchAutoDone에 저장된 구성 정보를 사용하여 SCP에서 컨텍스트 전환이 실행됩니다. AutoComplete가 없을 경우 IRemoteDispatch.RemoteDispatchNotAutoDone이 사용됩니다. IRemoteDispatch는 ServicedComponent에 의해 구현되는 인터페이스입니다. 관리되지 않는 클라이언트는 IDispatch(런타임에 바인딩)를 사용하여 인터페이스가 없는 서비스된 구성 요소만 호출할 수 있으므로 이 경우 실제 프록시가 없기 때문에 AutoComplete 구문을 사용할 수 없습니다. 인터페이스가 사용되는 경우에도 AutoComplete의 구성은 관리되는 클라이언트의 매타데이터에 의해 제어됩니다. DCOM 메서드 호출은 out of process의 경우에만 RemoteDispatchAutoDone에 만들어집니다. Out-of-process 구성 요소는 DoCallBack 메카니즘을 사용하지 않는 대신 DCOM을 사용하여 호출을 배달하고 서비스를 렌더링합니다. 인터페이스에 메서드가 있으면 원격 서비스되는 구성 요소의 인터페이스 메서드는 DCOM을 사용하여 호출됩니다. 메서드가 없으면 호출이 ServicedComponent에 있는 IRemoteDispatch 인터페이스에 발송됩니다. 이것은 Dispose()와 같은 호출이 DCOM을 통해 호출됨을 의미합니다. 이것의 의미에 대해서는 나중에 설명합니다.

컨텍스트

ContextUtil 클래스를 사용하여 관련된 COM+ 개체 컨텍스트와 해당 속성을 액세스합니다. 이 클래스는 관리되지 않는 코드에서 CoGetObjectContext에 의해 반환되는 개체와 비슷한 기능을 제공합니다. 서비스되는 구성 요소와 연결된 관리되는 개체 컨텍스트는 관련있는 관리되지 않는 개체 컨텍스트와 다른 목적으로 사용됩니다. 이 클래스는 트랜잭션이 필요한 개체 하나(루트 역할)와 서비스되는 구성 요소에서 파생되지 않는 두 개체(컨텍스트 관리되는 개체를 빠르게 예시하는 자식 개체 역할)의 관리되는 세 개체를 작성하여 매니페스트됩니다. 서비스되지 않는 구성 요소는 Transactions이 지원하여 서비스되는 구성 요소처럼 동작합니다. 즉, 리소스 관리자를 호출하고 필요한 경우 ContextUtil.SetAbort를 사용할 수 있습니다. 루트 개체가 만들어지면 관련된 관리되지 않는 컨텍스트가 만들어진 다음 현재 스레드에 연결됩니다. 자식 개체가 호출되면 해당 개체는 관리되지 않는 컨텍스트와 관련이 없기 때문에 COM+ 컨텍스트를 변경할 필요가 없습니다. 따라서, 스레드는 루트의 관리되지 않는 컨텍스트 ID를 계속해서 유지합니다. 자식 개체가 리소스 관리자를 호출하면 리소스 관리자는 해당 자식 개체를 실행하는 스레드에서 관리되지 않는 컨텍스트를 추출합니다. 이 컨텍스트는 루트 개체의 관리되지 않는 컨텍스트입니다. 관리되지 않는 컨텍스트에 의존하는 것은 매우 위험하므로 이후 버전에서는 관리되지 않는 컨텍스트를 관리되는 컨텍스트에 병합하여 자식 개체를 잠정적으로 다른 관리되는 컨텍스트에 연결할 것입니다. 리소스 관리자는 루트 개체의 컨텍스트를 선택할 수 없습니다. 따라서, 새로운 버전의 .NET으로 업그레이드하면 이런 유형의 동작에 의존하는 코드가 중단될 수 있습니다.

성능 결과

이 절에서는 관리되는 클라이언트/관리되는 서버 서비스된 구성 요소 솔루션의 성능을 관리되지 않는 클라이언트/서버 솔루션과 비교합니다. in-process 메서드에 대해서는 다음 표에서 설명합니다. 트랜잭션에 대해 구성되는 ServicedComponent가 단지 번호만을 추가하는 단일 메서드를 사용하여 C#로 작성되었습니다. 비교를 위해 해당 C++ 구현을 사용했습니다. 이 비교에서는 실제로 작업하지 않고 관리되는 솔루션과 관리되지 않는 솔루션 사이의 차이점을 보여 줍니다. In-process 활성화 속도는 관리되는 솔루션에서 약 3.5배 더 느리고 메서드 호출 비용은 컨텍스트 스위치가 있을 때 약 2배 가량 더 비쌉니다. 그러나, 컨텍스트 전환이 필요한 서비스되는 구성 요소 메서드와 컨텍스트 전환이 필요하지 않는 서비스되는 구성 요소 메서드를 비교해 보면, in-process 서비스되는 구성 요소 차단 인프라의 성공을 나타내는 약 3등급의 차이가 발생합니다. out of process 솔루션의 경우 활성화 비용이 약 2배 더 비싸고, 상호 컨텍스트 메서드 호출 비용은 약 3배 가량 더 비쌉니다.

표 1에서는 관리되는 솔루션과 관리되지 않는 솔루션을 사용할 때의 in-process 활성화와 메서드 호출의 조정되는 시간을 비교하여 보여 줍니다.

표 1. In-process 활성화 및 메서드 호출

관리되는 솔루션 관리되지 않는 솔루션
활성화 35 10
Cross-context-do-nothing 메서드 호출 2 1
Cross-context-do-work 메서드 호출 200 100

활성화는 'do-nothing' 메서드에 대한 메서드 호출보다 약 한 등급 정도 비용이 비쌉니다. 일부 작업에 추가하면 활성화 및 메서드 호출 시와 동일한 등급의 관련이 없는 DTC 트랜잭션만 가져옵니다. 메서드 호출이 풀링된 데이터베이스 연결을 열면 메서드 호출 작업의 등급이 활성화와 'do-nothing' 메서드 호출을 합한 것보다 한 등급 더 커지므로, 서비스되는 구성 요소 인프라의 오버헤드가 실제 작업이 실험에 추가될 때 이론적으로 일치됩니다.

개체 수명

Just-In-Time 활성화

JIT(just-in-time) 서비스는 격리에는 일반적으로 사용되지 않습니다. 트랜잭션 서비스 및 종종 개체 풀링과 함께 암시적으로 사용됩니다. 다음 예는 일부 관심 항목을 강조 표시하는 데 도움이 됩니다. 아래 코드에서 .NET 클래스는 JIT 서비스만을 사용하여 작성됩니다.

using System;
using System.EnterpriseServices;
[assembly: AssemblyKeyFile("Demos.snk")]
[assembly: ApplicationName("JITDemo")]

namespace Demos
{
    [JustInTimeActivation]
    public class TestJIT : ServicedComponent
    {
       public TestJIT()
       {  // 호출되기 전에 
       }
       [AutoComplete]
       public void DoWork ()
       {       // 다음을 사용하여 결과 표시 .. 
                  // 1. 자동 완성 특성 또는
                  // 2. ContextUtil.DeactivateOnReturn = true 또는
                  // 3. ContextUtil.SetComplete();
       } 
       public override void Dispose(bool b)
{      // 이 메서드를 선택적으로 재지정하고 사용자에 맞게 설정할 수 있는
// 삭제 논리를 실행합니다. b==true이고, Dispose()가 
// 클라이언트에서 호출되었고 false이면 GC는 해당 개체를 정리합니다. 
}
    }
}

클래스는 ServicedComponent에서 파생되고 JIT 특성을 사용하여 필요한 특정 서비스를 나타냅니다. 관리되지 않는 코드에서 Activate 및 Deactivate 메서드를 재정의하려면 클래스에서 IObjectControl 인터페이스를 구현해야 합니다. ServicedComponent 클래스는 Activate 및 Deactivate 이벤트를 처리하기 위해 재정의될 수 있는 가상 메서드를 갖습니다. 그러나, ServicedComponent와 실제 프록시인 SCP 모두 IObjectControl을 구현하지 않습니다. 대신 SCP는 COM+에서 IObjectControl 인터페이스를 요청하면 프록시를 해체합니다. 해체시 COM+의 호출은 ServicedComponent의 가상 메서드에 전달됩니다. 메서드의 AutoComplete 특성을 사용하거나, ContextUtil.SetComplete(), ContextUtil.SetAbort()을 호출하거나 ContextUtil.DeactivateOnReturn을 설정하여 DeactivateOnReturn 비트를 설정합니다. 메서드가 호출될 때마다 DeactivateOnReturn 비트가 설정된다고 가정할 경우 메서드는 클래스 생성자, Activate, 실제 메서드 호출, Deactivate, Dispose(true), 클래스 종료자(있을 경우)의 순으로 호출됩니다. 다른 메서드가 호출되면 동일한 시퀀스가 반복됩니다. 우수한 디자인을 위해서는 Activate 및 Deactivate 메서드만 무시하여 개체를 제거한 다음 개체 풀로 다시 놓을 때를 잘 알고 있어야 합니다. Activate 및 Deactivate의 나머지 논리는 클래스 생성자와 Dispose(bool) 메서드에 놓여야 합니다. DeactivateOnReturn 비트는 다음과 같은 방법으로 설정될 수 있습니다.

  1. 클라이언트는 단일 메서드 호출에 대해서만 개체 상태를 사용합니다. 메서드에서 시작할 때 새로운 실제 개체가 만들어져 SCP에 첨부됩니다. 메서드가 종료될 때 Dispose(true)와 실제 개체 종료자(있을 경우)가 차례로 호출되어 실제 개체가 비활성화됩니다. 그러나, 연결된 COM+ 컨텍스트, SCP 및 TP는 활성 상태로 유지됩니다. 클라이언트 코드는 실제 개체라고 판단되는 참조를 그대로 유지합니다(투명한 프록시). 클라이언트가 동일한 참조에 대해 다음 메서드 호출을 만들면 새로운 실제 개체를 만들어 SCP에 첨부하여 메서드 호출 서비스를 제공합니다. 새 개체 만들기 요구사항을 제거하려면 개체 풀링 절을 참조하십시오. 실제 개체를 비활성화하려면 메서드 호출이 종료될 때 실제 개체가 완료 표시되어야 합니다. 이것은 다음을 사용하면 가능합니다.
    1. 클래스 메서드의 AutoComplete 특성
    2. ContextUtil class, DeactivateOnReturn 또는 SetComplete에 대한 두 메서드 호출 중 하나
  2. 클라이언트는 메서드를 종료하기 전에 완료 비트를 false로 설정하여 각 메서드 호출 후에 개체를 비활성화하지 않고 동일한 개체에서 여러 메서드를 호출합니다. 예를 들어, 양식 수준에서 JIT를 사용하는 서비스되는 구성 요소의 범위를 지정하고 메서드의 완료 비트를 false로 명시적으로 설정하여 두 양식 단추를 사용하여 동일한 개체 인스턴스에서 메서드를 호출합니다. 완료 비트가 true로 설정되어야 하는 경우도 있습니다. 이 접근 방법은 계약이 클라이언트와 개체 사이에 있다는 것을 나타냅니다. 이는 클라이언트에 의해 명시적 또는 암시적으로 실행될 수 있습니다.
    1. 클라이언트는 완료된 개체에서 특정 메서드를 호출하여 해당 개체를 비활성화하는 방법을 알고 있어야 합니다. 메서드 구현에서는 옵션 1의 아이디어를 사용합니다. 개체 참조는 동일한 호출 시퀀스를 사용하여 다시 호출될 수 있으며 이는 실제 개체를 새로 만들다는 것을 의미합니다.
    2. 개체는 클라이언트가 해당 개체에서 Dispose() 메서드를 호출하면 명시적으로 삭제됩니다. Dispose()는 ServicedComponent에 정의된 메서드이며, Dispose(true)와 클래스 종료자(있는 경우)를 차례로 호출한 다음 관련된 COM+ 컨텍스트를 해체합니다. 이 경우 개체 참조에 대한 추가 메서드 호출은 더 이상 만들어질 수 없습니다. 추가 메서드 호출을 시도하면 예외가 throw됩니다. 많은 클라이언트에서 동일한 개체를 사용할 경우 마지막 클라이언트에서 개체 작업이 완료되면 Dispose() 호출이 완료되어야 합니다. 그러나, JIT 개체의 상태 비저장 특성으로 인해 클라이언트 모델별로 단일 인스턴스를 사용하는 디자인 연습을 해야 합니다.
    3. 개체는 완료 비트를 절대 true로 설정하지 않고 클라이언트는 절대 Dispose()를 호출하지 않습니다. 실제 개체, 프록시 및 컨텍스트는 가비지 수집이 발생되면 삭제됩니다. GC에 의해 초기화되는 메서드 호출 순서는 Deactivate, Dispose(false), 클래스 종료자(있을 경우)입니다.

모든 서비스되는 구성 요소에는 SCP(원격의 경우 RSCP)에 참조로 저장되는 관련된 COM+ 컨텍스트가 있습니다. 참조는 GC가 발생되거나 클라이언트가 Dispose()를 호출하는 경우에만 릴리스됩니다. GC에 의존하여 컨텍스트를 정리하지 않는 것이 좋습니다. COM+ 컨텍스트는 하나의 OS 핸들을 잡고 있으며 일부 메모리에서는 GC가 발생할 때까지 핸들의 해제가 지연될 수 있습니다. 또한, ServicedComponent에 종료자가 없더라도 SCP는 종료자를 구현합니다. 즉, COM+ 컨텍스트 참조는 첫 번째 수집 단계에서 정확히 수집됨을 의미합니다. 실제로, SCP에서 종료자가 호출될 때 컨텍스트는 종료자 스레드에 의해 삭제되지 않고, 종료자 스레드로부터 컨텍스트 삭제 작업을 제거하여 내부 대기열에 놓습니다. 서비스되는 구성 요소의 생성, 사용 및 삭제가 빠르게 진행되는 업무가 가중되는 환경에서는 작업에 의해 종료자 스레드가 소비될 수 있기 때문에 이 작업이 수행됩니다. 대신, 내부 스레드가 대기열에 서비스를 제공하고 이전 컨텍스트는 삭제됩니다. 또한, 새 ServicedComponent를 만드는 응용 프로그램 스레드는 대기열에서 항목을 제거하려고 시도한 다음 이전 컨텍스트를 삭제합니다. 따라서, 클라이언트에서 Dispose()를 호출하면 클라이언트 스레드를 사용하여 COM+ 컨텍스트를 바로 해체하여 컨텍스트가 사용하는 핸들과 메모리 리소스를 해제합니다. Dispose()를 호출하면 예외가 throw되는 경우도 있습니다. 한 가지 경우는 개체가 루트가 아닌 중단된 트랜잭션 컨텍스트에서 활성화되는 경우입니다. 이 경우 Dispose()를 호출하면 CONTEXT_E_ABORTED 예외가 발생될 수 있습니다. 다른 경우에 대해서는 개체 풀링에 설명되어 있습니다.

성능의 관점에서는 ServicedComponent 파생 클래스에서 종료자를 구현하는 대신 이 논리를 Dispose(bool) 메서드에 놓는 것이 더 좋습니다. SCP는 종료자를 구현하지만 실제 개체의 종료자는 반사를 통해 호출됩니다.

다음은 JIT 사용에 대한 좋은 디자인 사례입니다.

  • 생성자 및 Dispose(bool) 메서드에 사용자 지정 활성 및 종료 코드를 배치하여 종료자를 구현하지 않고 메서드에 있는 AutoComplete 특성을 사용하여 종료를 나타내는 단일 호출 패턴을 사용합니다.
  • 클라이언트에서 개체 작업이 완료되면 클라이언트에서 Dispose()를 호출합니다.

이 토론에서는 클라이언트가 관리되고 구성 요소가 in-process인 것으로 가정합니다. 구성 요소가 out-of-process인 경우:(자세한 내용은 원격 절 참조)

  • GC는 클라이언트 활성 개체에 대한 .NET 원격 임대 기간이 만료되면 해당 개체를 정리합니다.
  • 앞에서 설명한 것처럼 out-of-process 구성 요소에서 메서드를 호출하면 DCOM을 사용하여 컨텍스트를 전환한 다음 메서드 호출을 전달합니다. 구성 요소가 JIT에 의해 비활성화된 다음 Dispose()가 호출되면 서버 컨텍스트가 입력되고 실제 개체가 다시 만들어져 DCOM을 호출한 다음 다시 비활성화됩니다. in-process 구성 요소의 경우 실제 개체가 비활성화되면 구성 요소를 재활성화하는 Dispose() 호출이 서비스되기 전에는 올바른 컨텍스트로 전환하려는 시도 없이 해당 컨텍스트만 삭제됩니다.

개체 풀링

개체 풀링의 기본 전제는 개체 재사용입니다. 개체 풀링은 대부분 JIT와 함께 사용됩니다. 풀링된 COM 구성 요소와 풀링된 .NET 구성 요소의 경우에도 마찬가지입니다.

using System;
using System.EnterpriseServices;
[assembly: AssemblyKeyFile("Demos.snk")]
[assembly: ApplicationName("OPDemo")]

namespace Demos
{
[ObjectPooling(MinPoolSize=2, MaxPoolSize=50, CreationTimeOut=20)]
[JustInTimeActivation]
public class DbAccount : ServicedComponent
{
   [AutoComplete]
   public bool Perform ()
   {      // 작업을 실행합니다.
   }
   public override void Activate()
   {   // .. 활성화 메시지를 처리합니다.
   }
   public override void Deactivate()
   {   // .. 비활성화 메시지를 처리합니다.
   }
   public override bool CanBePooled()
   {  // .. CanBe Pooled 메시지를 처리합니다.
      // 기본 구현은 false를 반환합니다.
      return true;
   }
}
}

JIT를 사용하는 경우처럼 개체 풀링은 다음과 같은 두 가지 방법 중 하나로 사용될 수 있습니다.

  1. 단일 호출 패턴. 코드에서 개체는 클라이언트가 메서드를 호출하려고 시도할 때 풀로부터 검색된 다음 JIT가 개체 풀링과 함께 사용되고 메서드를 호출하는 동안 완료 비트가 true로 설정되는 단일 메서드 호출이 종료되면 풀로 다시 반환됩니다. JIT 사용과 동일한 단일 호출 접근법이 여기서도 적용됩니다. 생성자는 개체가 만들어져 풀에 배치될 때 한 번만 호출됩니다. JIT 및 풀링된 개체를 사용할 때 메서드 호출 순서는 활성화, 메서드 호출, 비활성화, CanBePooled입니다. CanBePooled가 true를 반환하면 개체가 풀에 다시 놓입니다. 컨텍스트는 앞에서 설명한 것처럼 활성 상태로 유지됩니다. 서비스되는 구성 요소는 매개 변수화된 생성자를 사용할 수 없기 때문에 임의의 개체가 풀로부터 추출된 후의 후속 메서드 호출에서는 생성자를 다시 호출하지 않고 동일한 메서드 호출 순서가 반복됩니다. 마지막으로, 클라이언트가 풀링된 개체에서 Dispose()를 호출하면 in-process 경우에서 컨텍스트만 삭제됩니다. out-of-process의 경우에는 앞에서 설명한 것처럼 Dispose()를 호출하여 개체를 다시 활성화할 수 있습니다. 개체가 풀링되면 해당 풀에서 개체를 가져와야 합니다. 즉, Dispose()는 CO_E_ACTIVATION_TIMEOUT과 함께 예외를 throw할 수 있습니다.
  2. 다중 호출 패턴. 비슷한 여러 메서드 호출 접근법을 사용하여 JIT 서비스를 강조 표시하면 많은 개체에 대한 많은 메서드 호출이 있은 후에만 풀로 다시 배치될 수 있습니다. 그러나, 클라이언트가 Dispose를 호출하지 않고 JIT가 사용되지 않으면 GC에 의해 개체가 풀에 다시 놓일 때 종료해야 하는 풀링된 개체의 자식 개체가 부활되는지 확인할 수 없습니다. 풀링된 개체가 가비지 수집되면 구성원이 여전히 유효한 비활성화가 보장되지 않습니다. 다음에 릴리스되는 .NET Framework(V1.1)에서는 canBePooled 및 Deactivate가 호출되지 않고 개체가 풀로 다시 놓이지 않습니다. 이 접근법에는 보다 일관성 있는 모델이 있습니다. Deactivate에서 자식 개체가 활성화되고, Dispose()에서는 자식 개체의 활성 상태가 보장되지 않습니다. 따라서, JIT를 사용하지 않는 풀링된 개체에 대해 Dispose()를 호출해야 합니다. 그렇지 않으면 개체가 풀로 반환되지 않습니다.

관리자는 어셈블리가 배포 및 등록된 후에 풀 크기와 시간 초과를 수정할 수 있습니다. 풀 크기 변경은 프로세스가 다시 시작될 때 적용됩니다. Windows XP 이상에서 풀 크기는 프로세스 내에 있는 각 응용 프로그램 도메인에 적용됩니다. Windows 2000에서 풀 크기는 기본 응용 프로그램 도메인에 존재하는 풀링된 개체의 프로세스 너비입니다. 동일한 프로세스 내의 다른 응용 프로그램 도메인에서 풀링된 개체를 필요로 할 경우 클라이언트는 응용 프로그램 도메인과 풀링된 개체 사이에서 효과적으로 통신합니다. 이것을 실현하는 방법은 각 IIS vroot가 개별 응용 프로그램 도메인에 하우징되는 ASP.NET 응용 프로그램 내부에서 COM+ 라이브러리 응용 프로그램에 정의된 풀링된 .NET 개체를 사용하는 것입니다.

서비스되는 구성 요소는 매개 변수화된 생성자를 사용할 수 없습니다.

보안

CAS(코드 액세스 보안)

.NET Framework 보안을 사용하면 실행 권한이 있는 경우에만 코드로 리소스를 액세스할 수 있습니다. 이것을 설명하기 위해 .NET Framework는 보호되는 리소스를 액세스할 수 있는 코드의 권한을 의미하는 사용 권한의 개념을 사용합니다. 코드는 필요한 사용 권한을 요청합니다. .NET Framework에는 코드 액세스 권한 클래스가 제공됩니다. 또한, 사용자 지정 권한 클래스를 작성할 수 있습니다. 이 사용 권한을 사용하면 .NET Framework에서 작업을 허용할 때 코드에 필요한 내용 및 코드 호출자에 필요한 권한을 나타낼 수 있습니다. System.EnterpriseServices를 통과하는 모든 코드 경로에는 관리되지 않는 코드 권한이 필요합니다.

.NET의 코드 액세스 보안은 웹으로부터 코드를 다운로드하여 작성자를 완전히 신뢰할 수 없는 응용 프로그램에서 가장 유용합니다. 일반적으로 서비스되는 구성 요소를 사용하는 응용 프로그램은 완전히 신뢰할 수 있고, 여러 프로세스 간에 적용되는 보안을 필요로 하며, 배포시에 역할 구성을 가능하게 합니다. 이 기능은 COM+ 역할 기반 보안에 노출되는 기능입니다.

System.EnterpriseServices를 통과하는 모든 코드 경로에는 관리되지 않는 코드 권한이 필요합니다. 이것은 다음과 같은 의미를 내포합니다.

  • 관리되지 않는 코드 사용 권한은 서비스되는 구성 요소에 대한 상호 컨텍스트 호출을 활성화 및 실행하는 데 필요합니다.
  • 서비스되는 구성 요소에 대한 참조가 신뢰할 수 없는 코드에 전달될 경우 신뢰할 수 없는 코드로부터 ServicedComponent에 정의된 메서드를 호출할 수 없습니다. 그러나, 일부 환경에서는 ServicedComponent에서 파생된 클래스에 정의된 사용자 지정 메서드를 신뢰할 수 없는 코드로부터 호출할 수 있습니다. 컨텍스트 전환, 차단 서비스 등이 필요하지 않거나 메서드 구현에서 System.EnterpriseServices의 구성원을 호출할 수 없는 경우에는 신뢰할 수 없는 코드로부터 호출을 만들 수 있습니다.

또한, .NET 버전 1에서는 스레드가 전환될 때 보안 스택이 복사되지 않기 때문에 서비스되는 구성 요소에서 사용자 지정 보안 권한을 사용할 수 없습니다.

RBS(약할 기반 보안)

System.EnterpriseServices에서는 COM+ 보안 메카니즘의 기능을 미러하는 .NET 개체에 보안 서비스를 제공합니다. COM+ 서버 응용 프로그램을 사용하여 구성 요소를 호스트할 경우 RBS는 DCOM 전송 프로토콜을 사용하여 원격 클라이언트에서 구성 요소를 활성화해야 합니다. 원격에 대한 자세한 내용은 다음 절을 참조하십시오. COM+의 보안 호출 컨텍스트와 ID는 관리되는 코드에서 사용할 수 있습니다. 또한, CoImpersonateClient, CoInitializeSecurity 및 CoRevertClient는 서버쪽에서 일반적으로 사용되는 익숙한 호출인 반면, CoSetProxyBlanket는 클라이언트쪽에서 일반적으로 사용됩니다.

특성 사용, 역할에 사용자 추가, 프로세스 보안 ID 설정 등과 같은 특정 보안 설정은 메타데이터에 저장되지 않습니다. 그러나, 어셈블리 수준 특성을 사용하여 COM+ 서버 응용 프로그램의 COM+ 탐색기의 보안 탭에 표시되는 내용을 구성할 수 있습니다.

  • 응용 프로그램에 대해 인증 사용
    (ApplicationAccessControlAttribute(bool)). 이 탭에서는 RBS를 실제로 지원해야 합니다.
  • 보안 수준 (ApplicationAccessControlAttribute(AccessChecksLevelOption)). AccessChecksLevelOption.Application으로 설정되면 응용 프로그램에서 역할에 지정된 사용자는 프로세스 보안 설명자에 추가되고 구성 요소, 메서드 및 인터페이스 수준에서의 미세 조정 검사 기능이 해제됩니다. 보안 검사는 응용 프로그램 수준에서만 실행되며 라이브러리 응용 프로그램의 프로세스 수준 보안은 호스트 프로세스에 따라 달라집니다. 특성이 AccessChecksLevelOption.ApplicationComponent로 설정되면 응용 프로그램에서 역할에 지정된 사용자가 프로세스 보안 설명자에 추가되고 응용 프로그램에서 역할 기반 보안 검사가 실행됩니다. 또한, ComponentAccessControl 특성을 클래스에 적용하여 RBS를 필요로 하는 모든 구성 요소에서 액세스 검사를 실행해야 합니다. 라이브러리 응용 프로그램에서 역할 기반 보안 검사는 서버 응용 프로그램처럼 실행됩니다. 보안 속성은 응용 프로그램 내에 있는 모든 개체의 컨텍스트에 포함되며 보안 호출 컨텍스트를 사용할 수 있습니다. 개체에 작성자의 컨텍스트와 호환되지 않는 구성이 있으면 자체 컨텍스트로 활성화됩니다. 프로그래밍 역할 기반 보안은 보안 호출 컨텍스트의 사용 가능성에 따라 달라집니다.

    의미 있는 액세스 검사를 위해 COM+ 라이브러리 응용 프로그램에서 작업하려면 프로세스 및 구성 요소 수준에서 액세스 검사를 실행하도록 선택합니다.

  • 가장 및 인증 선택 항목은 ApplicationAccessControl 특성의 ImpersonationLevel 및 Authentication 속성과 해당합니다.

    SecurityRole 특성은 어셈블리, 클래스, 또는 메서드 수준에 적용될 수 있습니다. 어셈블리 수준에 적용될 경우 해당 역할에 있는 사용자는 응용 프로그램에 있는 모든 구성 요소를 활성화할 수 있습니다. 클래스 수준에 적용될 경우 해당 역할에 있는 사용자는 해당 구성 요소에 있는 모든 메서드를 호출할 수 있습니다. 응용 프로그램 및 클래스 수준 역할은 메타데이터에 구성되거나, COM+ 카탈로그에 액세스하여 관리 방식으로 구성될 수 있습니다.

    메타데이터를 사용하여 어셈블리 수준으로 RBS 구성:

    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    // 이 역할에 NTAuthority\everyone을 추가합니다. 
    [assembly:SecurityRole("TestRole1",true)]
    // 관리를 위해 역할에 사용자를 추가합니다.
    [assembly:SecurityRole("TestRole2")]
    

    RBS를 메타데이터에 클래스 수준으로 구성:

    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    ?[ComponentAccessControl()]
    [SecurityRole("TestRole2")]
    public class Foo : ServicedComponent
    {
    public void Method1() {}
    }
    

    어셈블리 수준 또는 클래스 수준의 RBS는 어셈블리가 등록된 후 COM+ 카탈로그에 존재하므로 관리 방식으로 구성될 수 있습니다. 그러나, 앞에서 설명한 것처럼 클래스 메서드는 COM+ 카탈로그에 표시되지 않습니다. 메서드에서 RBS를 구성하려면 클래스가 인터페이스의 메서드를 구현하고 클래스 수준에서 SecureMethod 특성을 사용하거나, 메서드 수준에서 SecureMethod 또는 SecurityRole 특성을 사용해야 합니다. 또한, 특성이 인터페이스 정의의 인터페이스 메서드가 아니라 클래스 메서드 구현에 나타나야 합니다.

  • 메서드에서 RBS를 사용하는 가장 쉬운 방법은 SecureMethod 특성을 클래스 수준에 적용한 다음 역할을 구성(SecurityRole 특성을 메서드에 배치하거나 관리 방식으로)하는 것입니다.
    [assembly: ApplicationAccessControl(true,
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    Interface IFoo
    {
    void Method1();
    void Method2();
    }
    [ComponentAccessControl()] 
    [SecureMethod]
    public class Foo : ServicedComponent, IFoo
    {
    // 관리를 위해 이 메서드에 역할을 추가합니다.
    public void Method1() {} 
    // "RoleX"가 이 메서드용 카탈로그에 추가됩니다.
    SecurityRole("RoleX")
    public void Method2() {}
    }
    

    클래스 수준에서 SecureMethod를 사용하면 클래스의 모든 인터페이스에 있는 모든 메서드가 COM+ 카탈로그의 역할에 관리 방식으로 구성될 수 있습니다. 클래스에서 이름이 같은 두 인터페이스를 구현하고 역할이 관리 방식으로 구성될 경우(클래스가 IFooMethod1과 같은 특정 메서드를 구현하지 않는 한) COM+ 카탈로그에 해당 역할이 표시되는 두 가지 메서드에 역할이 구성되어야 합니다. 그러나, 클래스 메서드에서 SecurityRole 특성이 사용될 경우에는 어셈블리가 등록될 때 이름이 같은 모든 메서드가 해당 역할에 자동으로 구성됩니다.

  • SecureMethod 특성은 메서드 레벨에도 배치될 수 있습니다.
    [assembly: ApplicationAccessControl(true, 
    AccessCheckLevel=AccessChecksLevelOption.ApplicationComponent)]
    Interface IFoo
    {
       void Method1();
       void Method2();
    }
    [ComponentAccessControl()] 
    public class Foo : ServicedComponent, IFoo
    {
    // 관리를 위해 이 메서드에 역할을 추가합니다.
    [SecureMethod]  // 또는 SecurityRole(SecureMethod++로 변환)을 사용합니다.
       public void Method1() {}
       public void Method2() {}
    }
    

    예에서 IFoo와 두 메서드는 COM+ 카탈로그에 표시되므로 관리상 두 메서드 모두에서 역할을 구성할 수 있지만, 메서드 수준 RBS는 Method1에만 적용됩니다. 메서드 수준 RBS에 참가해야 하는 모든 메서드에서 SecureMethod 또는 SecurityRole을 사용하거나 앞에서 설명한 것처럼 SecureMethod를 클래스 수준에 배치합니다.

RBS가 메서드 수준에 구성될 때마다 Marshaller 역할이 필요합니다. 메서드가 호출될 때 메서드에 RBS가 없을 경우 서비스되는 구성 요소 인프라가 IRemoteDispatch를 호출합니다. 메서드가 호출되고 RBS가 메서드에 구성되어 있을 경우(SecureMethod 특성이 있을 경우) 해당 메서드와 연결된 인터페이스를 사용하는 DCOM을 사용하여 메서드 호출이 만들어집니다. 따라서, DCOM은 RBS가 메서드 수준에 적용됨을 보장합니다. 그러나, 활성화 및 차단 절에서 설명한 것처럼 COM interop 및 RSCP는 IManagedObject(원격 활성기가 자체 공간으로 참조를 마샬링) 및 IServicedComponentInfo(원격 개체를 쿼리)를 호출합니다. 이들 인터페이스는 서비스되는 구성 요소에 연결됩니다. 구성 요소는 메서드 수준 검사를 실행하도록 구성되기 때문에 인프라가 성공적으로 호출하게 하려면 이들 인터페이스에 역할을 연결해야 합니다.

따라서, 어셈블리가 등록될 때 응용 프로그램에 Marshaller 역할이 추가된 다음 사용자를 이 역할에 추가해야 합니다. 대부분의 경우 모든 응용 프로그램 사용자가 이 역할에 추가됩니다. 이것은 메서드에 RBS를 구성하여 이 추가 구성 단계가 필요하지 않는 관리되지 않는 COM+의 경우와는 다소 차이가 있습니다. 등록하는 동안 이 역할에 '모든 사람'이 자동으로 추가되면 모든 사람이 활성화 권한 없이 구성 요소를 활성화할 수 있기 때문에(호출은 불가능) 보안 구멍이 생길 수 있습니다. 클라이언트가 개체를 삭제할 수 있도록 Marshaller 역할이 IDisposable 인터페이스에 추가됩니다. 사용자는 Marshaller 역할 대신 앞에서 설명한 세 인터페이스 각각에 대한 적절한 역할을 직접 추가하는 것입니다.

원격 구성 요소

ServicedComponent 클래스는 상속 트리에 MarshalByRefObject를 포함하고 있기 때문에 원격 클라이언트에서 액세스될 수 있습니다. 서비스되는 구성 요소를 원격으로 노출하는 방법은 여러 가지가 있습니다. 서비스되는 구성 요소를 원격으로 액세스하려면 다음과 같은 방법을 사용합니다.

  • ASP.NET에 기록되거나 ASP.NET에서 호출된 서비스되는 구성 요소가 있는 HTTP 채널은 높은 확장성 및 성능과 함께 우수한 보안 및 암호 옵션을 제공합니다. SOAP에서 사용될 경우 더 많은 상호 운용성 옵션이 있습니다. 서비스되는 구성 요소는 IIS/ASP.NET에 COM+ 라이브러리 응용 프로그램으로 호스트될 수 있습니다. COM+ 서버 응용 프로그램을 사용할 경우 IIS/ASP.NET 호스트는 DCOM을 사용하여 구성 요소에 액세스할 수 있습니다.
  • 서비스되는 구성 요소가 Dllhost에 호스트될 경우의 DCOM. 이 옵션은 최적의 성능과 보안, 시스템 간에 서비스 컨텍스트를 전달하는 기능 등을 제공합니다. 원격 기술을 선택할 때의 주요 디자인 문제는 서비스가 시스템 간에 제공되어야 하는지 여부입니다. 예를 들어, 한 시스템에서 트랜잭션을 만들고 해당 트랜잭션을 다른 시스템에서 지속해야 하는 서버 그룹 내에서 DCOM은 이 목적으로 사용될 수 있는 유일한 프로토콜입니다. 그러나, 클라이언트가 원격 ServicedComponent만 호출하면 될 경우 HTTP 채널 또는 SOAP 종점 접근법을 사용해도 좋습니다.
  • .NET 원격 채널(예: TCP, 사용자 지정 채널). TCP 채널을 사용하려면 소켓에서 프로세스에 대해 수신 대기해야 합니다. 일반적으로 사용자 지정 프로세스는 소켓에서 수신 대기한 다음 서비스되는 구성 요소를 COM+ 라이브러리 또는 서버 응용 프로그램으로 호스트하는 데 사용됩니다. Dllhost를 수신기로 사용할 수도 있습니다. 두 접근법 모두 증명된 성능, 확장성 및 보안을 갖는 사용자 지정 소켓 수신기를 작성해야 합니다. 따라서, ASP.NET 또는 DCOM 솔루션은 대부분의 프로젝트에 가장 알맞은 접근법입니다.

서비스되는 구성 요소를 DCOM을 사용하여 원격으로 액세스하여 Dllhost에 호스트하려면 먼저 어셈블리가 COM+ 서버 응용 프로그램에 등록되고 서버 시스템의 GAC에 배치되는지 확인합니다. 그런 다음 COM+ 응용 프로그램 내보내기 기능을 사용하여 응용 프로그램 프록시에 대한 MSI 파일을 만듭니다. 클라이언트에 응용 프로그램 프록시를 설치합니다. 관리되는 어셈블리가 응용 프로그램 프록시에 포함됩니다. 설치 관리자가 어셈블리를 등록하고 클라이언트 시스템의 GAC에 배치합니다. 따라서:

  • .NET Framework는 클라이언트와 서버에 설치되어야 합니다. 관리되지 않는 클라이언트만 원격 서비스되는 구성 요소에 액세스하는 경우에도 클라이언트 시스템에 설치되어야 합니다. Windows 2000 플랫폼에서는 서비스 팩 3도 필요합니다.
  • 프록시를 제거하면 해당 어셈블리도 GAC에서 제거되어야 합니다.

그림 6에서는 서버 구성 요소가 클라이언트쪽에서 관리되는 코드로 활성화된 후의 인프라를 보여 줍니다.

DCOM을 사용하는 것은 CLR이 Dllhost에 호스트된다는 의미를 함축하고 있으며, 응용 프로그램 구성 파일인 dllhost.exe.config가 system32 디렉터리에 있다는 의미입니다. 또한 구성 파일이 시스템에 있는 모든 Dllhost 프로세스에 적용됨을 의미합니다. 다음에 릴리스되는 .NET Framework(V1.1)에서는 COM+ 응용 프로그램 루트 디렉터리를 COM+ 응용 프로그램에 설정하여 해당 응용 프로그램의 구성 파일과 어셈블리를 찾는 데 사용할 수 있습니다.

클라이언트 활성 개체의 경우 개체의 URI가 요청될 때마다 해당 개체에 대한 수명 임대가 만들어집니다. 활성화 절에서 이미 설명한 것처럼 URI는 원격 서비스되는 구성 요소 프록시에 의해 요청됩니다. 이것은 기존의 in-process 서비스되는 구성 요소가 원격 프로세스에 마샬링되는 경우에도 발생할 수 있습니다. URI는 응용 프로그램 도메인 외부에서 .NET가 MBR 개체를 마샬링할 때마다 요청됩니다. URI는 .NET에 있는 개체 ID가 고유함을 보장하고 프록시 체인을 금지하는 데 사용됩니다. 따라서, 관리되는 클라이언트가 원격 서비스되는 구성 요소를 활성화하면 서버 개체에 임대 시간이 사용됩니다. 관리되지 않는 클라이언트는 클라이언트쪽에 원격 서비스되는 구성 요소 프록시가 없기 때문에 개체의 URI를 요청하지 않는다는 사실에 주의하십시오. 대신에 관리되지 않는 클라이언트는 DCOM을 사용하여 개체 ID를 확인합니다. 따라서, 관리되지 않는 클라이언트에서 활성화될 경우에는 서비스되는 구성 요소의 임대 시간이 사용되지 않습니다.

임대 시간이 서비스되는 구성 요소에 포함될 경우 InitialLeaseTime 및 RenewOnCallTime 시간 초과 값을 작은 값(예: 10초)으로 설정하는 것이 좋습니다. 서비스되는 구성 요소를 삭제하려면 Dispose()를 사용하거나 GC에서 개체를 정리합니다. Dispose()가 호출되면 원격 서비스되는 구성 요소 프록시는 DCOM 프록시에 있는 참조를 해제한 후 다음 GC에서 사용 가능하게 만듭니다. 서버 개체는 Dispose 호출을 처리하고 (또는 원격 호출을 Dispose()에 서비스하는 새로운 서버 개체를 만듬), 관련된 COM+ 컨텍스트를 삭제한 다음 임대 시간이 초과된 경우에만 다음 GC에서 사용 가능하게 만듭니다. 클라이언트가 Dispose()를 호출하지 않을 경우 서버는 클라이언트쪽 GC가 DCOM 프록시에 대한 참조를 해제하는 동안 대기한 다음 임대 시간이 만료된 후 다음 GC에서 해당 서버와 COM+ 컨텍스트를 사용할 수 있게 만듭니다. 따라서, Dispose()를 호출하고 기본 임대 시간을 축소합니다. 클라이언트가 활성 상태인 경우에는 임대 시간이 만료되더라도 해당 서버 개체에 대한 DCOM 참조는 서버 개체를 활성 상태로 유지합니다. 그러나, DCOM 참조를 사용해도 서비스되는 구성 요소가 항상 활성 상태를 유지하는 것은 아닙니다. 클라이언트가 CLR 원격 채널 또는 COM+ SOAP 서비스를 통해 개체에 액세스할 경우 임대가 만료되는 강력한 참조만 서비스되는 구성 요소를 활성 상태로 유지합니다.

결론

이 기사에서는 관리되는 코드에 사용할 수 있는 서비스 중 일부에 대해서만 설명했습니다. 관리되는 코드에서는 트랜잭션 격리 수준, 프로세스 초기화, 구성 요소 없는 서비스, 프로세스 재활용 등과 같은 모든 COM+ 서비스를 사용할 수 있습니다. 이제 .NET Framework에서도 모든 COM+ 서비스를 일관성 있고 논리적인 방법으로 액세스할 수 있습니다. 게다가, ASP.NET, Microsoft ADO.NET 및 Messaging과 같은 .NET Framework의 많은 혁신적인 부분들이 .NET Enterprise Services에 통합되어 트랜잭션 및 개체 풀링과 같은 서비스를 사용할 수 있게 되었습니다. 이 통합은 일관성 있는 아키텍처 및 프로그래밍 모델을 위해 제공됩니다. System.EnterpriseServices 네임스페이스는 관리되는 클래스에 서비스를 추가할 수 있는 프로그래밍 모델을 제공됩니다.


[이 자료는 MSDN Library에서 가져왔습니다.]

".NET" 카테고리의 다른 글
  • Windows Form : 윈도우 폼 꾸미기 (0)2007/01/11
  • Windows Form 기반의 프로그램에서, 폼에서 컨트롤... (0)2007/01/11
  • .NET의 Enterprise Service(COM+) 이해 (0)2007/01/11
  • Microsoft Application Blocks for .NET (0)2007/01/11
  • 가비지 수집기 기본 및 성능 힌트 (0)2007/01/11
2007/01/11 09:24 2007/01/11 09:24
Posted by webdizen
Tags COM+
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2574

Leave your greetings.

[로그인][오픈아이디란?]

«Prev  1 2 3  Next»

RSS HanRSS
Blog Image
webdizen
이 곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (2998)
Webdizen (134)
Life (6)
Diary (16)
Blog (9)
IDEA (1)
Travel (10)
Book (14)
Photo (7)
Movie (7)
Music (13)
Leisure Sports (10)
Funny (5)
Hardware (119)
Software (120)
Windows (5)
Unix & Linux (119)
Installation (4)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (805)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)