공지사항 메인 컨트롤인 Announcements.ascx의 Page_Load를 살펴보자. 이 부분이 실제 모듈이 웹 페이지에서 보여지는 모양을 결정하는 부분이다. 특히 데이터를 읽어오는 과정에서 닷넷누크가 사용하는 데이터 추상화에 대한 내용을 주의 깊게 살펴본다.
먼저 코드부터 보자.
Private
Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesMyBase.Load
Try
' Obtain announcement information from Announcements table
' and bind to the datalist control
Dim announcements AsNew AnnouncementsController
' DataBind Announcements to DataList Control
lstAnnouncements.DataSource = announcements.GetAnnouncements(ModuleId)
lstAnnouncements.DataBind()
Catch exc As Exception 'Module failed to load
ProcessModuleLoadException(Me, exc)
End
Try
End
Sub
매우 간단하다. 공지사항을 DB에서 읽어 와서 DataList에 뿌려주는 것이다. 결국 모든 일는 GetAnnouncements가 하게 되므로 이 부분을 살펴보자. 이 함수는 AnnouncementsController.cs에 들어 있는데 다음과 같은 코드로 구성되어 있다.
Public
Function GetAnnouncements(ByVal ModuleId AsInteger) As ArrayList
Return CBO.FillCollection(DataProvider.Instance().GetAnnouncements(ModuleId), GetType(AnnouncementInfo))
End
Function
우선 이 함수가 파라메터로 ModuleId를 받는 다는 점을 눈 여겨 볼 필요가 있다. 즉 닷넷누크의 모듈은 사이트 페이지의 어떤 곳에서 던지 위치할 수 있는데 그 모듈의 특성이 각 모듈별로 다른 데이터를 가진다면 항상 DB에 저장할 때 모듈 ID를 같이 저장해야 한다. 그렇지 않고 사이트의 어떤 페이지에 존재하던 같은 내용을 보여 주는 것이라면 모듈ID를 가질 필요가 없게 된다. 모듈 ID에 기초하여 데이터를 저장하는 경우 모듈이 삭제되면 데이터도 같이 잃게 되는 것이다.
이 함수가 직접 DB를 접근하지 않고 CBO.FillCollection이라는 함수를 사용하고 있다. 이 함수는 닷넷누크 코어에서 지원하는 함수인데 그 내용이 복잡하므로 간단히 하는 일만 설명하고 넘어가기로 하자.(자세히 알고 싶은 분들은 언제라도 소스를 읽어 보길…) FillCollection은 결국 DataProvider내에 존재하는 GetAnnoucements함수를 불러서 데이터를 채우게 된다. 이 함수는 DataProvider.vb에 있다.
Public
MustOverride
Function GetAnnouncements(ByVal ModuleId AsInteger) As IDataReader
그런데 결국 여기에도 내용은 없다. MustOverride이므로 어디선가 따로 구현을 하고 있는 것이다. 어디에서 하는가? 같은 파일 안에 있는 다음 함수가 한다.
' dynamically create provider
Private
Shared
Sub CreateProvider()
objProvider = CType(Framework.Reflection.CreateObject("data", "DotNetNuke.Modules.Announcements", "DotNetNuke.Modules.Announcements"), DataProvider)
End
Sub
여기서 objProvier를 생성하는데 이 과정도 다소 내부는 복잡하므로 여기서는 공지사항 모듈의 DataProvider 개체를 생성한다는 정도만 이해해 두도록 하자. 공지사항의 DataProvider는 DotNetNuke.Announcements.SqlDataProvider라는 프로젝트에서 생성되는데 이 프로젝트는 Annoucements\Provider\DataProviders\SqlDataprovider 에 들어 있다.
여기서 잠깐 생각해 보자. 그냥 DB에서 간단히 레코드 몇 개 가지고 오는데 도데체 왜 이렇게 복잡한 과정을 만들어 놨을까? 닷넷누크의 설계 기본에는 데이터베이스를 철저하게 추상화하는 것을 목표로 하고 있다. 즉 기본 데이터베이스를 MSSql을 사용하고 있지만 코어의 수정없이 다른 데이터베이스를 지원할 수 있도록 Data Provider라는 개념을 도입한 것이다. 즉 새로운 데이터베이스 용의 Provider를 만들기만 하면 모듈 소스에 대한 아무런 수정없이 돌아가는 것을 목표로 하고 있는 것이다. 현재 상용이기는 하지만 오라클 Data Provider도 개발되어 있다.
다시 코드로 돌아가자. 결국 마지막으로 불리는 함수는 SqlDataProvider.vb에 들어있다.
Public
Overrides
Function GetAnnouncements(ByVal ModuleId AsInteger) As IDataReader
Return
CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & ObjectQualifier & "GetAnnouncements", ModuleId), IDataReader)
End
Function
여기서도 직접 Sql데이터를 읽는 코드는 없다. SqlHelper.ExecuteReader를 실행한다. 이 함수는 Microsoft.ApplicationBlock.Data라는 어셈블리에 들어 있는 함수 인데 MS에서 제공하는 SQL데이터를 처리할 때 사용하는 라이브러리이다. MS는 .NET에서 구현하고 있지 않으나 필연적으로 많이 사용하게 되는 코드를 따로 만들어 보급하고 있는데 그것이 바로 Microsoft .NET application block이라고 불리는 것이다. 이것의 자세한 사용법은 아래 코드프로젝트의 아티클을 참고 하기 바란다.
http://www.codeproject.com/cs/database/MS_Application_Blocks.asp
추상화가 지나쳐서 너무 뜬구름 잡는 식이 되고 있으므로 바닥으로 가서 데이터 베이스를 살펴보자.
CREATE TABLE [dbo].[Announcements] (
[ItemID] [int] IDENTITY (0, 1) NOT NULL ,
[ModuleID] [int] NOT NULL ,
[CreatedByUser] [nvarchar] (100) COLLATE Korean_Wansung_CI_AS NULL ,
[CreatedDate] [datetime] NULL ,
[Title] [nvarchar] (150) COLLATE Korean_Wansung_CI_AS NULL ,
[URL] [nvarchar] (150) COLLATE Korean_Wansung_CI_AS NULL ,
[ExpireDate] [datetime] NULL ,
[Description] [nvarchar] (2000) COLLATE Korean_Wansung_CI_AS NULL ,
[ViewOrder] [int] NULL
) ON [PRIMARY]
공지사항 모듈은 단 한 개의 테이블을 사용하고 있다. 설명이 필요 없을 만큼 단순하다. 단순한 게시판 DB와 거의 유사하다. 틀린 점이 있다면 ModuleID가 있다는 정도이다. 이 테이블에서 현재 보여줘야 할 공지사항을 가져오는 것은 저장프로시저가 한다.
CREATE procedure dbo.GetAnnouncements
@ModuleId int
as
select Announcements.ItemId,
Announcements.ModuleID,
Announcements.CreatedByUser,
Announcements.CreatedDate,
Announcements.Title,
'URL' = case when Files.FileName is null then Announcements.URL else Files.Folder + Files.FileName end,
Announcements.ExpireDate,
Announcements.Description,
Announcements.ViewOrder,
UrlTracking.TrackClicks,
UrlTracking.NewWindow
from Announcements
left outer join UrlTracking on Announcements.URL = UrlTracking.Url and UrlTracking.ModuleId = @ModuleID left outer join Files on Announcements.URL = 'fileid=' +
convert(varchar,Files.FileID)
where Announcements.ModuleId = @ModuleId
and (Announcements.ExpireDate > GetDate() or Announcements.ExpireDate is null)
order by Announcements.ViewOrder asc, Announcements.CreatedDate desc
GO
별다른 내용은 없고 URLTracking이라는 표를 조인을 하고 있는데 이 표는 공지사항의 각 항목의 클릭 수를 저장하는 표이므로 그리 중요한 요소는 아니다. 닷넷누크는 클릭 수 추적이 필요한 URL들을 하나로 모아서 관리하고 있다는 정도만 이해해 두자. 결국 맨 앞에서 나온
lstAnnouncements.DataSource = announcements.GetAnnouncements(ModuleId)
라는 문장은 4~5단계의 추상화를 거쳐 최종 DB의 데이터를 꺼내오게 되는 것이다. 물론 처음 모듈을 개발하면서부터 이러한 추상화 과정을 다 구현하는 것은 다소 개발과정에 부담스러운 일이다. 개발과정에서는 DB의 수정이 빈번할 수 있는데 이러한 경우에는 이러한 다단계의 추상화를 할 필요가 없다. 이러한 추상화는 개발이 완료단계에 이르러 더 이상 DB에 대한 수정이 없다고 판단될 경우에 하는 것이 바람직하다. 또는 이러한 Data Provider를 자동으로 생성해주는 도구도 있는데 단점이라면 유료이다. 과히 비싸지는 않으므로 소개를 해둔다.
http://www.snowcovered.com/snowcovered2/Default.aspx?tabid=166&CatalogItemID=835