잊지 않겠습니다.

복불복이나 사다리타기 같은 프로그램을 간단히 만들어보자라는 생각으로 잡았다가 은근히 일이 커져버렸다.;
Random으로 어떻게하면 뽑아낼 수 있을지, 요즘 고민되고 있는 Domain-Driven 방법을 어떻게하면 적용할 수 있을지를 고민하다가,
별것도 아닌것에 시간을 다 잡아먹어버린것 같다.;

ASP .NET MVC에서 AJAX도 사용해보고, Castle container도 사용해보고.. 이것저것 사용해보면서 재미를 느낀 짬짬이 취미생활.;

Domain Model 구성은 다음과 같다. 데이터를 따로 제공하지 않아도 되어서 Repository는 비어있는 상태


핵심이된 List Shuffle method.
    static class ShuffledList
    {
        public static List Shuffle(this List list)
        {
            string[] shuffledList = new string[list.Count];
            List freeIndex = new List();
            Random rand = new Random();

            for(int i = 0 ; i < list.Count ; i++)
            {
                freeIndex.Add(i);
            }

            foreach(string item in list)
            {
                int indexOfNewIdx = rand.Next(freeIndex.Count);
                int newIdxOfCard = freeIndex[indexOfNewIdx];

                shuffledList[newIdxOfCard] = item;
                freeIndex.Remove(newIdxOfCard);
            }
            return shuffledList.ToList();
        }
    }



Domain-Driven으로 3tier로 작성해주면서.. 참 이렇게 편하고 좋은 방법을 내가 돌아가고 있었구나.. 하는 후회가 엄청나게 된다.
많은 것을 더 배워야지 되는데. 나태해진 내 자신도 반성이 되고 말이야.;
Posted by Y2K
,

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
,
Mockup class를 이용한 Routing test code
URL 테스트는 Rest 및 개발에서 가장 중요한 사항중 하나인데. 테스트코드보다 직접 실행해서 한번 테스트해보는 경우가 더 많은 것 같다.;



private string GetOutboundUrl(object routeValues)
{
    // Get route configuration and mock request context
    RouteCollection routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    var mockHttpContext = new Moq.Mock();
    var mockRequest = new Moq.Mock();
    var fakeResponse = new FakeResponse();
    mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
    mockHttpContext.Setup(x => x.Response).Returns(fakeResponse);
    mockRequest.Setup(x => x.ApplicationPath).Returns("/");
    // Generate the outbound URL
    var ctx = new RequestContext(mockHttpContext.Object, new RouteData());
    return routes.GetVirtualPath(ctx, new RouteValueDictionary(routeValues)).VirtualPath;
}

이 code를 이용한 test code는 다음과 같이 적을 수 있다.
[Test]
public void Football_Page1_Is_At_Slash_Football()
{
    Assert.AreEqual("/Football", GetOutboundUrl(new
    {
        controller = "Product",
        action = "List",
        category = "Football",
        page = 1
    }));
}

URL의 테스트가 꼭 필요하긴 한데.. 요즘들어 일을 이런쪽으로 전혀 안하고 있다는 아쉬움이 많이 들고 있는듯.;
Posted by Y2K
,
Model 객체의 경우에는 데이터의 Display class로만 주로 사용하고 있었는데. 재미있는 특징들이 이제서야 많이 보이는 것 같다.

특히 IDataError interface의 상속으로 View에서의 데이터 에러를 검증하고, 그 에러를 View에서 표시하는데 사용할 수 있는 여력이 많이 보인다.
여러가지로 재미있다고 해야지 될까나.



public class GuestResponse : IDataErrorInfo
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public bool? WillAttend { get; set; }
    public string Error { get { return null; } } // Not required for this example
    public string this[string propName]
   {
       get {
               if((propName == "Name") && string.IsNullOrEmpty(Name))
                    return "Please enter your name";
               if ((propName == "Email") && !Regex.IsMatch(Email, ".+\\@.+\\..+"))
                    return "Please enter a valid email address";
               if ((propName == "Phone") && string.IsNullOrEmpty(Phone))
                    return "Please enter your phone number";
               if ((propName == "WillAttend") && !WillAttend.HasValue)
                    return "Please specify whether you'll attend";
               return null;
             }
    }
}

참 여러가지로 사용되는 Interface가 많은 것 같다. 무언가 class를 생성할 때, 언제나 먼저 생각해줄 것이.
일단 Framework에서 지원되는 class가 있는지. 내가 사용할 action이 정의되어있는 interface는 존재하는지에 대한
생각은 항시 필요한 것 같다.

공부할 내용이 너무나 많아. --;

그리고, asp.net에서 SmtpClient를 사용할 때, 개발 환경이나 개인 환경에서 SMTP 서버를 사용할 수 없을 때, File에 지정할 수 있는 방법이 있다
web.config에 다음과 같은 설정을 넣어주면 된다.



	
		
			
				
			
		
	
Posted by Y2K
,

AD Update source

.NET Framework 2009. 11. 16. 09:44
AD와 같은 분산 시스템에서의 업데이트는 개발자뿐 아니라 시스템 관리자들에게 매우 중요한 일이 된다.
특히 Exchange나 Domain에서의 권한문제 등을 다룰때, AD의 값을 어떻게 변경하느냐에 따라서 각 시스템의 운영자체가 달라지기 때문에 특히 주의를 해야지 된다. 그리고, 시스템측에서는 이러한 AD의 일괄 업데이트를 Powershell이 나오기 전에는 할 방법이 마땅치 않아서 곤란해지는 경우가 많다. PowerShell의 경우에는 매우 쉽게 이런 일을 할 수 있다.

먼저, AD는 모두 DirectoryEntry로 접근을 한다. 각 DirectoryEntry를 통해서 AD Query를 이용한 결과값을 차례대로 업데이트를 하는 것이 가장 유용한 방법이다. 

string ldap = String.Format("LDAP://{0}", LdapPath);  //AD Path 설정

using(DirectoryEntry entry = new DirectoryEntry(ldap, UserName, Password, AuthenticationTypes.Sealing))
{
    //AD Search query
    using(DirectorySearcher searcher = new DirectorySearcher(entry, filter, null, SearchScope.Subtree))
    {
        //Update 할 item의 limit 갯수 설정, 너무나 많은 값들을 업데이트 하는 경우에, 실수라도 하게 되면 큰일도 날수도 있고.;
        searcher.SizeLimit = Buffer;
        SearchResultCollection results = searcher.FindAll();

        foreach(SearchResult result in results)
        {
            try
            {
                //각 Entry의 Directory 값들을 모두 가지고 온다. AD에서의 하나의 Item은 여러개의 Property로 구성이 되기 때문에, 각 Property의 값들의 변경을 행할때 유용하다.
                DirectoryEntry objectEntry = result.GetDirectoryEntry();
                //integer값으로 변경시켜주는 상태이다. 각 값의 type에 따라서 변경하게 해주는 것은 좀더 연구가 필요하다.
                int changeValue = int.Parse(value);
                objectEntry.Properties[propertyName].Value = changeValue;
                objectEntry.CommitChanges();
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}
Posted by Y2K
,

IIS Configuration

.NET Framework 2009. 10. 9. 15:22
IIS를 직접 제어해서 WebSite를 구성해주는 Setup 파일이라던지, 아니면 자신의 요구대로 IIS의 설정을 변경할 수 있는 방법을 제공

참고 : 
http://www.iis.net/ConfigReference

Microsoft.Web.Administration를 사용하면 IIS 7.0에서부터 사용되는 모든 설정의 Config를 변경시킬 수 있다. 기본적으로 복사되는 Machine.config의 세부 설정을 변경시켜줄 수 있게 된다. 간단히 Wrapping시킨 IisSite class의 코드는 다음과 같다. 




public class IisSite
  {
      public string SiteName { get; private set; }
      public string LastErrorMessage { get; private set; }

      public IisSite(string siteName)
      {
          SiteName = siteName;
      }

      public bool AddApplication(string applicationPool, string applicationName, string path)
      {
          try
          {
              using (ServerManager mgr = new ServerManager())
              {
                  Site site = mgr.Sites[SiteName];
                  if (site != null)
                  {
                      string appName;
                      if(applicationName.StartsWith("/"))
                      {
                          appName = applicationName;
                      }
                      else
                      {
                          appName = "/" + applicationName;
                      }

                      site.Applications.Add(appName, path);
                      Application app = site.Applications[appName];
                      app.ApplicationPoolName = applicationPool;
                      mgr.CommitChanges();
                  }

                  return true;
              }
          }
          catch(Exception ex)
          {
              LastErrorMessage = ex.StackTrace;
              return false;
          }
      }

      public bool SetAuthicationForAnonymous(string applicationName, bool enable)
      {
          return SetAuthicationEnable(applicationName, IisConfigConstant.AnonymousAuth, enable);
      }

      public bool SetAuthicationForBasic(string applicationName, bool enable)
      {
          return SetAuthicationEnable(applicationName, IisConfigConstant.BasicAuth, enable);
      }

      private bool SetAuthicationEnable(string applicationName, string authType, bool enable)
      {
          try
          {
              string appPath = string.Format("{0}/{1}", SiteName, applicationName);
              using (ServerManager mgr = new ServerManager())
              {
                  Configuration config = mgr.GetApplicationHostConfiguration();
                  ConfigurationSection basicAuthenticationSection = config.GetSection(authType, appPath);
                  basicAuthenticationSection[IisConfigConstant.Enabled] = enable;
                  mgr.CommitChanges();

                  return true;
              }
          }
          catch(Exception ex)
          {
              LastErrorMessage = ex.Message;
              return false;
          }
      }

      public bool SetDefaultDomainForBasic(string applicationName, string defaultDomain)
      {
          return SetAuthDefaultDomain(applicationName, IisConfigConstant.BasicAuth, defaultDomain);
      }

      private bool SetAuthDefaultDomain(string applicationName, string authType, string defaultDomainName)
      {
          try
          {
              using (ServerManager mgr = new ServerManager())
              {
                  string appPath = string.Format("{0}/{1}", SiteName, applicationName);

                  //NOTE : Set Auth
                  Configuration config = mgr.GetApplicationHostConfiguration();
                  ConfigurationSection basicAuthenticationSection = config.GetSection(authType, appPath);
                  basicAuthenticationSection[IisConfigConstant.DefaultDomain] = defaultDomainName;
                  mgr.CommitChanges();

                  return true;
              }
          }
          catch(Exception ex)
          {
              LastErrorMessage = ex.Message;
              return false;
          }
      }
Posted by Y2K
,
web.config 및 app.config에서 수많은 xml을 보다보면 무엇을 보고 사용해야지 될지 모르게 될 때가 많다. -_-

지금까지는 조금 어색하게 사용하고 있었던 application config section에 대한 내용을 한번 정리해보자. 

Section group에 대한 예시는 다음과 같다. 
  
    
        
        
  
section group의 name을 정할 수 있고, 그렇게 되면 configuration에 section group의 이름이 들어갈 수 있다.
configSections에 section group이 지정되어있지 않으면, section not found라는 web.config error가 나타나게 된다. 

여기에서 section name과 permission의 경우에는 일반적인 type에 따라서 정해주면 되는데, 그것보다 더 중요한 것이 뒤에 나오는 type값이다.
class full name, assembly name 으로 구성이 되어 있는 것을 볼 수 있다. 이는 사용자 section을 만들었을 때에 중요한 항목이 되는데, 자신이 만들어준 ConfigurationSection을 상속받은 ConfigurationSection class를 이용해서 handling할 class를 지정해주어야지 된다. 

예를 들어 주로 사용하는 web service url group을 만들었을 때에는 다음과 같다. 

  
    
      http://localhost/MPSWS/HostedActiveDirectory/Service.asmx
      http://localhost/MPSWS/Hostedsharepoint2007/Service.asmx
      http://localhost/mpsws/hostedemail2007/service.asmx
      http://localhost/mpsws/hostedsignup/service.asmx
      http://localhost/mpsws/managedplans/service.asmx
      http://localhost/mpsws/Exchange2007ResourceManager/service.asmx
      http://localhost/mpsws/HostedMobility2007/service.asmx
      http://localhost/mpsws/ManagedEmail2007/service.asmx
      http://localhost/mpsws/managedplans/service.asmx
    
    
      http://localhost/MPSWS/HostedActiveDirectory/Service.asmx
      http://localhost/MPSWS/Hostedsharepoint2007/Service.asmx
      http://localhost/mpsws/hostedemail2007/service.asmx
      http://localhost/mpsws/hostedsignup/service.asmx
      http://localhost/mpsws/managedplans/service.asmx
      http://localhost/mpsws/Exchange2007ResourceManager/service.asmx
      http://localhost/mpsws/HostedMobility2007/service.asmx
      http://localhost/mpsws/ManagedEmail2007/service.asmx
      http://localhost/mpsws/managedplans/service.asmx
    
  
public sealed class WebServiceUrlCollection : ConfigurationSection
{
    private readonly XmlDocument xmlDoc = new XmlDocument();
    public Dictionary MapTable { get; private set; }

    public string GetValue(string key)
    {
        if (MapTable == null)
        {
            Create();
        }
        if(MapTable.ContainsKey((key)))
        {
            return MapTable[key.ToString()];    
        }
        else
        {
            return string.Empty;
        }
    }

    private void Create()
    {
        MapTable = new Dictionary();
        foreach (XmlNode node in xmlDoc.ChildNodes[0].ChildNodes)
        {
            MapTable.Add(node.Name, node.InnerText); 
        }
    }
    protected override void DeserializeSection(XmlReader reader)
    {
        xmlDoc.Load(reader);
        Create();
    }
}
Posted by Y2K
,

MS SQL Paging query

.NET Framework 2009. 8. 19. 11:52
MS SQL 2005부터 사용가능한 ROW_NUMBER()를 이용한 Paging query


DECLARE @startRow INT
DECLARE @endRow INT
SELECT * FROM (SELECT *, ROW_NUMBER() OVER(ORDER BY LogonName) AS rownum FROM Product) ProductOrdered WHERE rownum > @startRow AND @endRow > rownum
간단히 사용해보면 시작 위치와 마지막 위치만을 지정해주고, Order되는 Column을 설정해주면 된다. 
일단 MS SQL 2005에서부터 사용할 수 있는 함수라서 단점을 가지고 있긴 하지만, 그래도 깔끔하고 무엇보다 속도가 매우 빠르다.
예전에 사용하던 SELECT TOP * FROM ~ 쿼리의 경우에는 뒤로 가면 갈 수록 query가 느려지는 단점을 가지고 있는데, 이를 전혀 신경 쓰지 않아도 되는 좋은 함수이다. 
Posted by Y2K
,
1의 경우에서 Strategy Pattern을 사용해서 각각 Marin, Firebet, Medic을 구현을 다 해놓고 나니, Zerg Unit를 다른 개발자가 개발해서 들고 왔다.
그런데, 각 unit의 method가 다르기 때문에 사용할 수가 객체가 어떤 형태인지에 따라 접근 방법을 달리 해줘야지 되는 문제가 발생되었다. 이럴 때에, Method의 Interface를 한번 덮어줘서 사용하고자 하는 interface로 변경해주는 것만으로, 이런 문제를 해결 할 수 가 있다. 이것이 Adapter Pattern이다. 

기본적으로 Zerg Unit는 MoveTo를 갖지만, Attack이 아닌 Bite Method를 갖는다. 
그렇지만, 본질적으로 Bite, RunTo Method는 BaseUnit의 Attack, MoveTo와 유사 동작을 하기 때문에
Public으로 선언된 각 Method를 변경시켜주면 된다. 

public abstract class ZergUnitBase
{
    //Terran 의 Unit과는 다른 Bite와 Run Method를 가지고 있다. 
    public abstract String Bite();
    public abstract String Run(String direction);
}

따라서, BaseUnit과의 호환성을 맞추어주기 위해서 ZergUnitToBaseUnit이라는 Adapter를 구성해준다.

public class ZergUnitToBaseUnit : BaseUnit
{
    private readonly ZergUnitBase zergUnitBase;

    public ZergUnitToBaseUnit(ZergUnitBase zergUnitBase)
    {
        this.zergUnitBase = zergUnitBase;
    }

    public override string Attack()
    {
        return zergUnitBase.Bite();
    }

    public override string MoveTo(string direction)
    {
        return zergUnitBase.Run(direction);
    }
}

구성된 zerg unit을 이용해서 terran과 zerg의 난타전을 테스트하면 다음과 같다.

BaseUnit unit1 = new ZergUnitToBaseUnit(new Zergling());
BaseUnit unit2 = new ZergUnitToBaseUnit(new Hydralisk());

Console.WriteLine(unit1.Attack());
Console.WriteLine(unit2.MoveTo("Terran"));
보면, Zerg와 Terran의 Unit을 다루는 법이 동일하게 변경되었다. 다른 인터페이스와 동일하나, class의 변경은 이루어지지 않는다.

Posted by Y2K
,
전에 한번 정리한 소스를 한번 정리해보는 내용으로...

Marin과 Medic, FIrebet.. 이 3개의 unit가 존재를 하고, 이 unit은 Attack과 Move라는 기본 동작을 갖는다.
Move라는 동작은 모든 Unit이 동일한 동작을 할 수 있지만, Attack 방법은 각기 다르고, 기본적으로 Attack이라는 Method를
갖지만 내부 구현이 다른 동작이 되게 된다. 

따라서, 각기 다른 동작을 구현하는 Class를 따로 뽑아내서 구현하는 Strategy Pattern이 사용 가능하다. 

먼저, 각기 다른 Attack에 대한 동작을 구현하는 Rifle, Firebazuka, Heal Class를 구현한다.

class Rifle : IAttack
{
    #region IAttack Members

    public string Attack()
    {
        return "dddd...";
    }

    #endregion
}

public class Firebazuka : IAttack
{
    #region IAttack Members

    public string Attack()
    {
        return "Fire!!...............";
    }

    #endregion
}

public class Heal : IAttack
{
    #region IAttack Members

    public string Attack()
    {
        return "Heal... ";
    }

    #endregion
}
그리고, 이 Attack Method의 IAttack을 인자로 갖는 BaseUnit class를 만들어준다. 
BaseUnit은 각 Unit의 parent class가 된다. 

class Runner : IMove
{
    #region IMove Members

    public string MoveTo(string direction)
    {
        return String.Format("To :{0}, Move it!", direction);
    }

    #endregion
}

public abstract class BaseUnit
{
    protected IAttack attackMethod;
    protected IMove moveMethod;

    protected BaseUnit()
    {
        
    }

    protected BaseUnit(IAttack attackMethod, IMove moveMethod)
    {
        this.attackMethod = attackMethod;
        this.moveMethod = moveMethod;
    }

    public virtual String Attack()
    {
        return attackMethod.Attack();
    }

    public virtual String MoveTo(String direction)
    {
        return moveMethod.MoveTo(direction);
    }
}
이렇게 구성된 BaseUnit을 이용해서 Marin, Firebet, Medic을 구현해준다. 구현된 코드는 다음과 같다.


public class Marin : BaseUnit
{
    public Marin() : base(new Rifle(), new Runner())
    {
        
    }
}
public class Firebet : BaseUnit
{
    public Firebet() : base(new Firebazuka(), new Runner())
    {
        
    }
}
public class Medic : BaseUnit
{
    public Medic() : base(new Heal(), new Runner())
    {
        
    }
}
구현된 Class를 이용해서 Test Code를 작성하면 다음과 같다. 

//Marin, Medic, Firebet의 class 생성
BaseUnit unit1 = new Marin();
BaseUnit unit2 = new Marin();
BaseUnit unit3 = new Medic();
BaseUnit unit4 = new Firebet();

//BaseUnit으로 Attack, MoveTo를 모두 구현
Console.WriteLine(unit1.Attack());
Console.WriteLine(unit2.Attack());
Console.WriteLine(unit3.Attack());
Console.WriteLine(unit4.Attack());

Console.WriteLine(unit1.MoveTo("Direction"));
Console.WriteLine(unit2.MoveTo("Direction"));
Console.WriteLine(unit3.MoveTo("Direction"));
Console.WriteLine(unit4.MoveTo("Direction"));
Strategy Pattern은 같은 Interface에서 각각 다른 동작을 하는 class들을 구현하는 방법으로, 
Method의 주체가 되는 Rifle, Firebazuk, Heal class의 구성이 관건이다. 다른 것을 객체화하라. 라는 
Design pattern의 성경같은 구절이 생각난다. 
Posted by Y2K
,