요즘은 시간이 많아서 그런지 자주 강좌를 올리네요 ㅋㅋ



 

이번에는 웹사이트의 핵심인 Core(코어) 모듈을 구성해 보는 시간을 가져봅니다.





잘 구성된 모듈은 안정된 시스템 그리고 코딩의 작업 속도와 바로 직결됩니다. 





모듈의 구성 방법은 개발자마다, 그리고 시스템 마다 각이 각색이겠지만, 






오늘 구성해 볼 모듈은 대부분의 웹사이트에서 적용할 수 있는 코어 구성 첫 번째




시간을 갖도록 합니다.
 




본 강의는 객체지향적인 문법을 공부하거나 소화한 분을 대상으로 합니다.
 





우리가 구성할 코어는 XML 로 구성된 사이트 맵이 필요합니다.



 
<?xmlversion="1.0"encoding="utf-8" ?>
<AdminID="ADMIN1" Path="WebAdmin">
    <Title>블로그 관리자</Title>
    <ModuleGroupID="MG1" Path="01Blog">
        <Title>기본 설정 관리</Title>
        <ModuleID="UBI1"ViewControl="/WebAdmin/01Blog/BlogBaseInfo.ascx">
            <Title>블로그 기본 정보</Title>
            <InitParams>
                <ParamName="umc">Umc</Param>
                <ParamName="umc1">Umc1</Param>
            </InitParams>
        </Module>
        <ModuleID="UBI2"ViewControl="/WebAdmin/01Blog/ProfileInfo.ascx">
            <Title>프로필 기본정보 관리</Title>
        </Module>
    </ModuleGroup>
    <ModuleGroupID="MG2"Path="02Privacy">
        <Title>프라이버시 관리</Title>
        <ModuleID="UP1"ViewControl="/WebAdmin/02Privacy/Privacy.ascx">
            <Title>프라이버시 관리</Title>
        </Module>
    </ModuleGroup>
    <ModuleGroupID="MG3"Path="02Bbs">
        <Title>게시판 관리자</Title>
        <ModuleID="Bbs1"ViewControl="/WebAdmin/02Privacy/Privacy.ascx">
            <Title>자유게시판</Title>
            <InitParams>
                <ParamName="BoardNo">1</Param>
                <ParamName="Template.Bbs.List">/WebAdmin/02Bbs/ListTemplate.ascx</Param>
                <ParamName="Template.Bbs.Write">/WebAdmin/02Bbs/WriteTemplate.ascx</Param>
                <ParamName="Template.Bbs.View">/WebAdmin/02Bbs/ViewTemplate.ascx</Param>
            </InitParams>
        </Module>
    </ModuleGroup>
</Admin>




ModuleGruop
섹션은 여러 개의 Module 로 구성되어 있습니다. 이 사이트맵을 기준으로






메뉴 컨트롤을 만들때나 이 Module의 부모가 누구인지가 필요할 때 유용합니다.




Module
섹션은 ViewControl 이라는 속성이 있습니다. 사용자 정의 콘트롤로 만들어진





이 컨트롤은 PageTemplate.ascx 이라는 마스터페이지와 같은 역할을 하는 PageTemplate의





PlaceHolder
컨트롤에 들어가게 될것입니다.




Module
섹션안에는 InitParams 라는 섹션이 또 존재하게 되는데,예를 들어 QueryString으로




넘겨야할 BoardNo 등의 파라메터들을 여기에 모두다 집어 넣을 수 있습니다.
 



이제 이 XML 을 사이트에 이용할 수 있도록 Parsing 해야 합니다.




로직은 “C# 디자인 패턴” 책의 Composite 패턴을 확장해 보았습니다. Composite 패턴에




대한 선수 학습이 필요합니다.





 
먼저 ISitemap 이라는 Interface를 만들어 보겠습니다.
namespace Umc.Core.WebAdmin.Sitemap
{
         public interface ISitemap
         {
                  string ID { get; set; }
                  string Path { get; set; }
                  string Title { get; set; }
                  string ViewControl { get; set; }
                  int Count { get; }
                  IEnumerator GetEnumerator();
                  ISitemap Parent { get; }
                  void Add(ISitemap sitemap);
                  bool HasChild { get; }
                  void ReadXml(XmlReader reader);
         }
}
 



Sitemap
에 필요한 모든 클래스는 위 Interface를 상속 받게 되고 가장 핵심이 되는




ReadXml
메서드가 바로 XML 을 해독하게 될 것입니다.




 
이 Interface는 SitemapModule가 상속받습니다.


namespace Umc.Core.WebAdmin.Sitemap
{
         public class SItemapModule : ISitemap
         {
                  protected ArrayList sitemap;
                  protected ISitemap parent;
                  protected string _id;
                  protected string _path;
                  protected string _title;
 
                  // InitParams 섹션
                  protected StringDictionary _initParam = new StringDictionary();
 
                  public SItemapModule(ISitemap _parent)
                  {
                           this.parent       = _parent;
                           this.sitemap      = new ArrayList();
                  }
                  #region ISitemap 멤버
 
                  public string ID
                  {
                           get { return _id; }
                           set { _id = value; }
                  }
 
                  public string Path
                  {
                           get { return _path; }
                           set { _path = value; }
                  }
 
                  public string Title
                  {
                           get { return _title; }
                           set { _title = value; }
                  }
 
                  public virtual string ViewControl
                  {
                           get { return null; }
                           set { throw new Exception("The method or operation is not implemented."); }
                  }
 
                  public int Count
                  {
                           get { return sitemap.Count; }
                  }
 
                  public System.Collections.IEnumerator GetEnumerator()
                  {
                           return sitemap.GetEnumerator();
                  }
 
                  public ISitemap Parent
                  {
                           get { return this.parent; }
                  }
 
                  public virtual void Add(ISitemap sitemap)
                  {
                           this.sitemap.Add( sitemap );
                  }
 
                  public bool HasChild
                  {
                           get { return sitemap.Count>0; }
                  }
 
                  public virtual void ReadXml(XmlReader reader)
                  {
                           throw new Exception("The method or operation is not implemented.");
                  }
                  #endregion
 
                  public void ReadInitParams(XmlReader reader)
                  {
                           while (reader.Read())
                           {
                                   if(reader.IsEmptyElement) return;
 
                                   if (reader.NodeType == XmlNodeType.Element)
                                   {
                                            if (reader.Name == Sitemap.NODE_PARAM)
                                            {
                                                     string _key       = reader["Name"];
                                                     string _value     = reader.ReadElementString();
                                                     _initParam.Add( _key, _value );
                                            }
                                   }
                                   if(reader.Name==Sitemap.NODE_INIT_PARAMS &&
                                            reader.NodeType==XmlNodeType.EndElement)
                                            return;
                           }
                  }
 
                  public StringDictionary GetInitParam
                  {
                           get { return _initParam; }
                  }
         }
}




기본이 되는 각각 메서드와 프로퍼티의 역할을 정의해 놓았습니다. 각각 쓰임새에 따라





특히 ReadXml 은 virtual 로 선언된 것을 볼 수 있습니다.





이 클래스는 각각 섹션을 처리할 클래스들의 뼈다가 되는 클래스로, 다음과 같은




다이어그램을 볼 수 있습니다.
 





 



차례대로 Sitemap 클래스부터 살펴보겠습니다.



public class Sitemap : SItemapModule
         {
                  public const string NODE_ADMIN              = "Admin";
                  public const string NODE_MODULE_GROUP      = "ModuleGroup";
                  public const string NODE_MODULE             = "Module";
                  public const string NODE_INIT_PARAMS       = "InitParams";
                  public const string NODE_PARAM              = "Param";
                  public const string NODE_TITLE              = "Title";
 
                  public Sitemap(ISitemap _parent)
                           : base(_parent)
                  {
                  }
                  public Sitemap() : base(null)
                  {
                  }
 
                  public override void Add(ISitemap _sitemap)
                  {
                           sitemap.Add( _sitemap );
                  }
 
                  public override void ReadXml(XmlReader reader)
                  {
                           while (reader.Read())
                           {
                                   if (reader.NodeType == XmlNodeType.Element)
                                   {
                                            if (reader.Name == NODE_ADMIN)
                                            {
                                                     this.ID = reader["ID"];
                                                     this.Path         = reader["Path"];
                                            }
                                            if (reader.Name == NODE_TITLE)
                                            {
                                                     this.Title        = reader.ReadElementString();
                                            }
                                            if (reader.Name == NODE_MODULE_GROUP)
                                            {
                                                     ModuleGroup moduleGroup = new ModuleGroup(this);
                                                     moduleGroup.ID = reader["ID"];
                                                     moduleGroup.Path = reader["Path"];
                                                     moduleGroup.ReadXml(reader);
 
                                                     this.Add(moduleGroup);
                                            }
                                   }
                                   if(reader.Name==NODE_ADMIN &&
                                            reader.NodeType==XmlNodeType.EndElement)
                                            break;
                           }
                  }
 
                  public static ISitemap ReadSitemap(string path)
                  {
                           XmlTextReader reader       = null;
                           try
                           {
                                   reader            = new XmlTextReader(path);
                                   ISitemap root     = new Sitemap();
 
                                   root.ReadXml( reader );
 
                                   return root;
                           }
                           catch(Exception ex)
                           {
                                   throw ex;
                           }
                           finally
                           {
                                   reader.Close();
                           }
                  }
         }




ReadSitemap
이라는 static 클래스가 시발점으로 각각의 노드를 탐색 할 수 있습니다.




클래스의 상단에는 XML 의 섹션들을 상수로 정의 해 놓았습니다.





ISitemap root = new Sitemap()
과 같이 최상단의 부모는 Null 이라는걸 알 수 있습니다.




ModuleGroup moduleGroup = new ModuleGroup(this);
구문이 의아해 하실겁니다.




XML
의 ModuleGroup 섹션을 만나면 ModuleGroup 내의 요소들을 처리할 또 다른 처리기가





필요합니다. 여기의 this 인자는 ModuleGroup 내에서는 이 this 가 부모 클래스가 되는




것입니다.
 
public class ModuleGroup : SItemapModule
         {
                  public ModuleGroup(ISitemap _parent)
                           : base(_parent)
                  {
                  }
 
                  public override void ReadXml(System.Xml.XmlReader reader)
                  {
                           int depth         = reader.Depth;
 
                           while (reader.Read())
                           {
                                   if (reader.NodeType == XmlNodeType.Element)
                                   {
                                            if (reader.Name == Sitemap.NODE_TITLE)
                                            {
                                                     this.Title        = reader.ReadElementString();
                                            }
                                            if (reader.Name == Sitemap.NODE_MODULE)
                                            {
                                                     ModuleInfo moduleInfo      = new ModuleInfo(this);
                                                     moduleInfo.ID              = reader["ID"];
                                                     moduleInfo.ViewControl     = reader["ViewControl"];
                                                     moduleInfo.ReadXml( reader );
 
                                                     this.Add(moduleInfo);
                                            }
                                   }
                                   if(reader.Depth <= depth ) return;
                           }
                  }
         }




여기도 별반 다를게 없네요~






ModuleGroup
을 탐색하다 Module 섹션을 만나면 this 클래스를 인자로 ModuleInfo




클래스에게 탐색을 넘깁니다.



 
public class ModuleInfo : SItemapModule
         {
                  private string _viewControl                 = null;
                  public ModuleInfo(ISitemap _parent)
                           : base(_parent)
                  {
                  }
 
                  public override string ViewControl
                  {
                           get { return _viewControl; }
                           set { _viewControl = value; }
                  }
 
                  public override void ReadXml(XmlReader reader)
                  {
                           int depth         = reader.Depth;
 
                           while (reader.Read())
                           {
                                   if (reader.NodeType == XmlNodeType.Element)
                                   {
                                            if (reader.Name == Sitemap.NODE_TITLE)
                                            {
                                                     this.Title        = reader.ReadElementString();
                                            }
                                            if (reader.Name == Sitemap.NODE_INIT_PARAMS)
                                            {
                                                     ReadInitParams( reader );
                                            }
                                   }
 
                                   if(reader.Name==Sitemap.NODE_MODULE &&
                                            reader.NodeType==XmlNodeType.EndElement)
                                            return;
                           }
                  }
         }




ViewControl
은 Module 섹션이 갖는 Attribute 이므로 여기에서 override 되었습니다.




그리고 ReadInitParams() 는 이미 부모 클래스에서 정의 해 놓았습니다.
 




이제 이 클래스를 통제시키는 SitemapManager 를 살펴봅니다.



public class SitemapManager
         {
                  private static SitemapManager _instance              = null;
                  private ISitemap _sitemap;
 
                  public ISitemap SiteInfo
                  {
                           get { return _sitemap; }
                  }
 
                  private SitemapManager()
                  {
                  }
 
                  public static SitemapManager GetInstance()
                  {
                           lock (typeof(SitemapManager))
                           {
                                   if (_instance == null)
                                   {
                                            _instance = new SitemapManager();
                                            _instance.Init();
                                   }
                           }
 
                           return _instance;
                  }
 
                  public void Init()
                  {
                           ReConfig();
                           string path       = Utility.GetAbsolutePath( Umc.Core.UmcConfiguration.Core["AdminXmlPath"]);
                           _sitemap          = Sitemap.ReadSitemap(path);
                  }
 
                  public void ReConfig()
                  {
                           _sitemap          = null;
                  }
 
                  public string GetNavigateString(ISitemap _map)
                  {
                           ISitemap _tempSitemap      = _map;
                           StringBuilder naviString   = new StringBuilder();
 
                           naviString.Insert(0, _tempSitemap.Title);
 
                           while (_tempSitemap.Parent != null)
                           {
                                   naviString.Insert(0, _tempSitemap.Parent.Title + " > " );
                                   _tempSitemap      = _tempSitemap.Parent;
                           }
 
                           return naviString.ToString();
                  }




여기에서 Singleton 패턴이 사용되었습니다.




생성자가 private 로 선언되어있습니다. 이 객체를 접근 하기 위해서 GetInstance() 라는




단 한 개의 통로만 열려있습니다.





이로 인한 이점은 객체의 정보를 소멸 시키지 않고 언제든 참조할 수 있습니다.




때문에 언제 어디서든 SiteInfo 프로퍼티를 호출하면 사이트맵의 정보를 다시 읽지 않고




고스란이 참조 할 수 있게 되는것입니다.




또한, GetNavigateString(ISitemap _map) 으로 부모의 Sitemap 까지 찾아 올라가




네비게이트 문자열을 뽑아 낼 수 있습니다.

 



여기의 Init는 언제 호출 될 것이냐가 의문이네요… 간단합니다..




Global.asax
에 첫 어플케이션이 시작될 때 초기화 되면 됩니다.



private SitemapManager _sitemapManager   = null;
        
    void Application_Start(object sender, EventArgs e)
    {
         _sitemapManager            = SitemapManager.GetInstance();
    }
 




이제 이 Sitemap 이 TreeView 컨트롤에 바인딩 될 수 있도록 Sitemap_Node 클래스를






만들어봅니다.



public class Sitemap_Node : System.Web.UI.WebControls.TreeNode
         {
                  ISitemap sitemap;
 
                  public Sitemap_Node(ISitemap _sitemap)
                           : base(_sitemap.Title)
                  {
                           this.sitemap               = _sitemap;
                           base.Value                 = _sitemap.ID;
                  }
 
                  public ISitemap Sitemap
                  {
                           get { return sitemap; }
                  }
         }




이와 같이 TreeView 에 Sitemap의 Title 프로퍼티가 바인딩 되고, TreeView의 Value 에는




Sitemap.ID
가 바인딩 됩니다.
 




그럼 우리가 구성한 Sitemap 은 TreeView 컨트롤과 거의 흡사한 형식으로 메모리에




저장됩니다. Sitemap 은 지금 내가 누구인지, 내 부모가 누구인지, 그리고 Root 가 



누구인지 까지 알아낼 수 있습니다.




 
마지막으로 재귀호출을 이용하여 TreeView 컨트롤에 Sitemap 노드들을 바인딩 해봅니다.



public partial class WebAdmin_Default : Umc.Core.WebAdmin.WebAdminTemplate
{
         private ISitemap sitemap;
         protected void Page_Load(object sender, EventArgs e)
         {
                  // Postback 또는 UpdatePanel 안의 클릭후 Sitemap_Node 로 캐스팅이 안되므로
                  // 매번 다시 바인딩
                  BuildTree();
         }
 
         private void BuildTree()
         {
                  treeMenu.Nodes.Clear();
                  sitemap = SitemapManager.GetInstance().SiteInfo;
                  Sitemap_Node node = new Sitemap_Node(sitemap);
 
                  treeMenu.Nodes.Add(node);
 
                  AddNodes(sitemap, node);
 
                  treeMenu.ExpandAll();
         }
 
         private void AddNodes(ISitemap sitemap, Sitemap_Node node)
         {
                  IEnumerator currentSitemap = sitemap.GetEnumerator();
 
                  while (currentSitemap.MoveNext())
                  {
                           ISitemap newSitemap                = (ISitemap)currentSitemap.Current;
                           Sitemap_Node newNode       = new Sitemap_Node(newSitemap);
                           node.ChildNodes.Add( newNode );
                           AddNodes( newSitemap, newNode );
                  }
         }
         protected void treeMenu_SelectedNodeChanged(object sender, EventArgs e)
         {
                  ISitemap _sitemap = (ISitemap)((Sitemap_Node)treeMenu.SelectedNode).Sitemap;
 
                  lblNavigate.Text = SitemapManager.GetInstance().GetNavigateString(_sitemap);
 
                  CurrentSitemapInfo         = _sitemap;
 
                  if (_sitemap.ViewControl != null)
                  {
                           Control template = LoadControl(_sitemap.ViewControl);
                           MainPlace.Controls.Clear();
                           MainPlace.Controls.Add(template);
                  }
         }
}
 









실행결과 화면은 다음과 같습니다.







이런 방식의 Sitemap 을 구성함으로써 다음과 같은 장점이 있습니다.






1.    
MasterPage 가 필요없음
2.     SiteMap 컨트롤 / SiteMapPath 컨트롤이 필요없음.

3.     불필요한 QueryString 이 필요없음

4.     Menu 컨트롤 등이 필요없음

5.     Sitemap 을 Dom 과 같은 형태로 탐색이 가능

6.     Atlas/Ajax 등을 활용하여 한 페이지로 동적인 사이트가 완성 가능


(사실 뭐좀 준비하고 있어요^^ㅋ)

 


이외에도 많은 장점을 가지고 있지만, 결정적인 단점은 직접 만들어야 한다는거~~ oTL
 




하지만 오늘 구성한 사이트맵이 가장 핵심이 되는 부분이므로, 핵심이 완성된다면 다른




부분은 쉽게 구현 가능 할 것입니다.



 
훔… 내가 쓰고 봐도 재미가 없네요 ㅋ




그럼 다가오는 크리스마스는 행복하게 보내시기 바랍니다^^//







※ UmcBlog 소스 ( http://umc.pe.kr/article_107.aspx ) 관리자 부분을 보시면 위의 패턴으로 구현이 되어있으니, 참고 하시기 바랍니다.


저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by 땡초 POWERUMC

댓글을 달아 주세요