잊지 않겠습니다.

Castle IoC Component

.NET Framework 2009. 12. 27. 21:29
Domain-Driven Model의 구현에 있어서, Repository와 Service를 각기 달리 구현하는 것은 매우 중요한 문제이다.
Service를 구현한 Repository가 변경되어도 언제나 동일한 서비스가 구현이 가능해야지 되며, 이는 데이터의 저장소나 서비스의 구현을
달리 나눈다는 점에서 테스트의 유용성에도 많은 영향을 미치게 된다. 무엇보다 이 둘이 결합되어 있으면 테스트를 행하는 것이 거의 불가능하다.

테스트를 어떻게 하면 좋게 할 수 있을지에 대한 꽤나 좋은 솔루션으로 castle을 찾게 되었다.

castle은 web.config나 xml 파일로 지정된 component를 기반으로 그 프로젝트에서 사용될 service를 정의하고, 제공해주는 역활을 하고 있으며,
이를 이용해서 단순히 config 파일만 변경하는 것으로 각기 다른 Repository가 제공되는 프로젝트를 만드는 것이 가능하다.

예를 들어 Interface가 다음과 같이 정의된 Repository들이 있을 때에,

namespace DomainModel.Abstract
{
    public interface IProductRepository
    {
        IQueryable Products { get; }
    }
    public interface IShippingDetailRepository
    {
        IQueryable ShippingDetails { get;}
        bool AddShippingDetail(ShippingDetail shippingDetail);
    }
}

이를 이용한 Service Repository는 castle에서 다음과 같이 선언될 수 있다.

	
		
			
				
					Data Source=HOME-PC\SQLEXPRESS;Initial Catalog=SportsStore;Integrated Security=True;Pooling=False
				
			
      
        
      
      
        
          Data Source=HOME-PC\SQLEXPRESS;Initial Catalog=SportsStore;Integrated Security=True;Pooling=False
        
      
			
		
	

위와 같은 Domain-Driven model에서는 가장 중요한 것이 서비스와 데이터간의 완벽한 분리이다.
어떤 식으로 구현해주는 것이 좋은지에 대해서 좀더 많은 생각과 좀더 많은 공부가 필요하다. -_-;;
Posted by Y2K
,
일주일에 두번이상은 가는 asp.net 에서 배신을 당했다. ㅋㅋ 
18일날 대강 상황을 본 기억인데. 그날 바로 Release가 나오다니.;

일단 검토해본 결과 Release version과 RC1, RC2 버젼간의 차이는 Bug Fix 이외에는 발견하지 못했다. 

MS의 전통상... 
RC가 나오면 개발을 하더라도 변경이 아애 없는 경우가 많아서, RC 버젼이 나왔을때 대처하는 것이 좀더 나았던 경험들이 많은 것 같다. 

일단, 개발자들을 좀더 즐겁게 해줄 수 있는 ASP .NET MVC를 환영하고.. ^^
장고같은 REST service framework로도 발전할 수 있기를. ^^


Posted by Y2K
,
ASP .NET에서 각각 상태를 저장할 수 있는 방법이지만, 각기 사용 방법에 유의점이 많이 보이는 방법들. 

Cache
IIS 전역에 저장시키는 값으로, 전체 유저들이 공용으로 이용될 수 있는 값이다. 
Cache.Add 에서 보이듯이, Cache가 추가되고, 그 Cache가 Expired가 되는 시간을 항시 정해줘서 Cache를 관리하게 된다. 

Session
사용자에 따라 각기 다른 값을 갖는 상태변수. 
Cache값과 차이를 갖는것은 사용자 연결 Connection에 따라 각기 다른 값을 가지고 있는 차이를 가지고 있다. 그러나, IIS 6.0에서의 사용자 Session은 out-of-process와 in-of-process 차이에 따라서 갱신되는 시간 및 Process의 값의 차이가 생기게 된다. 

Cookie
사용자에 따라 각기 다른 값을 갖게 할 수 있는 client 단에 저장되는 상태 변수
일반적으로 browser에서 저장되는 상태변수값. 1024 byte까지 저장할 수 있고, 사용자가 변경을 해서 서버단에 해킹을 가할 수 있기 때문에 cookie 값을 사용하는데에 있어서 항시 보안을 고려해서 사용해줘야지 된다.


Posted by Y2K
,
  ASP .NET에서의 Session의 생명주기는 In-of-Process 단위로 움직이게 된다. 그런데, 이 Session의 처리주기가 In-of-Process 단위로 움직이는 경우에 IIS 6에서 사용되는 process recycling에 의해서 session이 끊기게 된다. Session을 이용한 상태의 저장시에 치명적인 오류를 가지게 되는데. 이러한 에러상황을 피하기 위해서는 다음과 같은 방법들이 사용 가능하다. 


1. IIS의 process recycling을 중지 시킨다.

1) 인터넷 정보 서비스 관리자를 띄운다 
2) 응용 프로그램 풀을 선택하고 새로만들기-응용프로그램 풀을 선택하여 새로운 응용 프로그램 풀을 만들어 준다. 
3) 새 응용프로그램의 등록정보를 열고 재생 탭의 항목을 모두 언체크 한다. 
4) 성능 탭의 웹 가든을 1로 맞춰준다 
5) 인터넷 정보 서비스 관리자에서 세션오류가 난 사이트의 등록정보를 연다 
6) 홈 디렉터리 탭의 응용 프로그램 설정 부분에 있는 응용 프로그램 풀을 2번 과정에서 만든 풀을 선택해준다. 

이 과정을 거치면 IIS 6에서도 세션 사용이 가능하다. 하지만 IIS 6의 강점인 자동 리사이클링이 동작하지 않게 세팅하는 것이므로 적용을 최소화 할 수 있도록 한다. 


2. Session 관리 방법을 변경한다. 

ASP.NET에서는 다양한 Session의 관리 방법을 제공한다. 기본적으로 제공되는 In-of-Process 단위의 Session 뿐 아니라, Out-of-Process 단위의 Session 역시 제공한다. Out-of-Process 단위의 Session의 사용은 ASP .NET State Service를 사용하는 방법과 MS-SQL을 이용한 Session의 저장 방법이 있다. 

1) ASP .NET Session State Service 
a. Windows Service에서 ASP .NET Session State Service를 활성화시킨다. (기본적으로 사용하지 않음으로 stop 되어있는 서비스이다.) 

b. web.config에 다음과 같은 항목을 추가한다. 
<sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424">
</sessionState>

* 주의할점은 dual core process의 경우에는 session과 working process간에 dead-lock이 일어날 수 있다. 이 경우에는 webgarden 속성이 반드시 false여야지 된다. 이에 대한 자세한 내용은 이곳에서..

2) MS-SQL을 이용한 Session 저장
DB를 이용한 Session의 저장 역시 가능하다. DB를 이용하게 되면 일단 속도는 떨어질 수 있지만, 다른 어떤 방법보다 안전한 Session의 이용이 가능하게 된다. (SQL Agent를 이용하기 때문에 SQL Express 버젼에서는 사용이 애매해진다. 시간이 오래된 Session의 자동정리 등 기능을 많이 사용할 수 없게 된다.)

a. command를 이용해서 ASP .NET Session Database 작성
aspnet_regsql.exe -S <machine> -E -ssadd -sstype p

b. web.config에 다음과 같은 항목을 추가한다.
<sessionState mode="SQLServer" sqlConnectionString="server=localhost; uid=*******; pwd=********* ">
</sessionState>

* 주의할 점은 반드시 Serialize 되는 항목만 Session에 저장 가능하다. 


Posted by Y2K
,

MVC Model은 매우 멋진 Web Model이고, View에서 표현하는 방법이 표준 Html을 기본적으로 사용하고 있기 때문에 매우 만족스러울 뿐 아니라, 디자이너와의 협업에서 뛰어난 적응도를 보여준다.

기존의 ASP .NET에서는 수 많은 서버 control들이 있었고, 이러한 서버 control들은 각각의 사용법 및 세부적인 디자인을 변경시키는 데에 있어서 많은 어려움을 가지고 있었던 것이 사실이다. (내가 잘 못하는 것도 있겠지만. –-;;)

MVC 모델을 사용하다가 자주 만드는 것 중 하나가 table인데, 각각의 데이터에 따라서 매번 다른 table을 반복작업을 하는 것이 싫어서 하나의 MVC user control을 만들었다.

먼저 LINQ를 이용한 데이터의 pagination을 제공하고, table에 각각 필요한 데이터를 Model로 선언한다. 그리고 table의 pagination은 AJAX call을 기반으로 한다. 

LINQ를 이용한 PagedList : 

   public interface IPagedList
    {
        int TotalCount { get; set; }
        int PageIndex { get; set; }
        int PageSize { get; set; }

        bool IsPreviousPage { get;  }
        bool IsNextPage { get;  }

        int TotalPageCount { get; set; }
    }

    public class PagedList<T> : List <T>, IPagedList
    {
        public PagedList(IQueryable<T> source, int index, int pageSize)
        {
            TotalCount = source.Count();
            PageSize = pageSize;
            PageIndex = index;
            TotalPageCount = TotalCount/PageSize;
            AddRange(source.Skip(index*pageSize).Take(PageSize).ToList());
        }

        public PagedList(List<T> source, int index, int pageSize)
        {
            TotalCount = source.Count();
            PageSize = pageSize;
            PageIndex = index;
            TotalPageCount = TotalCount / PageSize;
            AddRange(source.Skip(index * pageSize).Take(PageSize).ToList());
        }

        #region IPagedList Members

        public int TotalCount
        {
            get;
            set;
        }

        public int PageIndex
        {
            get;
            set;
        }

        public int PageSize
        {
            get;
            set;
        }

        public bool IsPreviousPage
        {
            get
            {
                return PageIndex > 0;
            }
        }

        public bool IsNextPage
        {
            get
            {
                return ( (PageIndex + 1) * PageSize ) < TotalCount;
            }
        }

        public int TotalPageCount { get; set; }

        #endregion
    }

    public static class Pagiation
    {
        public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
        {
            return new PagedList<T>(source, index, pageSize);
        }

        public static PagedList<T> ToPagedList<T>(this List<T> source, int index, int pageSize)
        {
            return new PagedList<T>(source, index, pageSize);
        }
    }

PagedTableData : Table에 필요한 데이터들
    public class PagedTableData
    {
        public List<string> ColumnHeaders { get; set; }     //Column Headers
        public string TableId { get; set; }                 //Html Id
        public PagedList<string[]> PagedList { get; set; }  //Page<tbody> datas
        public string ActionName { get; set; }              //Ajax call Action data
    }

PagedTable : MVC User Control 
PagedTable.ascx >>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PagedTable.ascx.cs" Inherits="SYNCmailAdmin.Views.Shared.PagedTable" %>
<div id="<%=ViewData.Model.TableId %>">
  <table class="mainTable" width="100%">
    <tr>
      <%foreach(string column in ViewData.Model.ColumnHeaders) { %>
      <th><%=column %></th>
      <%} %>
    </tr>
    
    <%foreach(string[] datas in ViewData.Model.PagedList) { %>
      <tr>
        <%foreach(string data in datas) { %>
          <td><%=data%></td>
        <%} %>
      </tr>
    <%} %>  
  </table>
  <div align="center">
    <%
      int pageIndex = ViewData.Model.PagedList.PageIndex;
      int startIndex = Math.Max(0, pageIndex - 5);
      int endIndex = Math.Min(pageIndex + 5, ViewData.Model.PagedList.TotalPageCount);
    %>
    <%if(ViewData.Model.PagedList.IsPreviousPage) { %>    
      <%for(int i = startIndex ; i < pageIndex ; i++){ %>
      <%=Ajax.ActionLink(i.ToString(), ViewData.Model.ActionName, new {pageIndex = i}, new AjaxOptions() { UpdateTargetId = ViewData.Model.TableId}) %>
      <%} %>
    <%} %>
    <strong><%=pageIndex%></strong>
    <%if(ViewData.Model.PagedList.IsNextPage) { %>
      <%for(int i = pageIndex + 1 ; i <= endIndex ; i++) { %>
      <%=Ajax.ActionLink(i.ToString(), ViewData.Model.ActionName, new {pageIndex = i}, new AjaxOptions() {UpdateTargetId = ViewData.Model.TableId}) %>
      <%} %>
    <%} %>    
  </div>
</div>


PagedTable.ascx.cs
    public partial class PagedTable : System.Web.Mvc.ViewUserControl<PagedTableData>
    {
    }


사용법은 다음과 같다. 
        public ActionResult Index()
        {
            ViewData["Title"] = "Home Page";
            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            HomeIndexDataModel dataModel = new HomeIndexDataModel() {PagedTableData = GetPagedData(null)};
            return View(dataModel);
        }

        public ActionResult GetPagedDataModel(int? pageIndex)
        {
            return View("PagedTable", GetPagedData(pageIndex));
        }

        private PagedTableData GetPagedData(int? pageIndex)
        {
            PagedTableData pageData = new PagedTableData();
            pageData.ColumnHeaders = new List<string>() {"FirstData", "SecondData"};
            pageData.TableId = "TableId";
            pageData.PagedList = TableTempDataGenerator.GetDatas()
                .ToPagedList(pageIndex == null ? 0 : pageIndex.Value, 10);
            pageData.ActionName = "GetPagedDataModel";

            return pageData;
        }

각 Column Header와 Table의 ActionResult 값을 얻어오는 함수명을 넣어주는 것으로 쉽게 해결 가능하다. 조금 제약사항이 되는 것이, GetPagedData에서 반드시 pageIndex라는 변수명으로 적어줘야지 되는 제약사항이 걸리게 된다. 

실행 결과는 다음과 같다.

Sample Source도 같이 올립니다. 필요하신 분들은 얼마든지 퍼가세요.
(이곳에 오시는 분들이 있으시다면.. ㅋㅋ ^^)
Posted by Y2K
,