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



 

이번에는 웹사이트의 핵심인 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 ) 관리자 부분을 보시면 위의 패턴으로 구현이 되어있으니, 참고 하시기 바랍니다.


저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 땡초 POWERUMC

댓글을 달아 주세요