잊지 않겠습니다.

DomainModel은 모두다 구성이 마쳐진 상태로 가정하고 MVC에 구성하는 방법에 대해서 알아본다.

먼저, web.config에 configSections에 castle속성과 IRepository에 대한 속성을 등록하고, httpModules에 Lifestyle에 대한 module을 등록한다.


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

다음, WindsorControllerFactory를 생성한다. WindsorControllerFactory는 DefaultControllerFactory를 상속하며, ControllerInstance를 저장하고 반환하는데 사용된다.

public class WindsorControllerFactory : DefaultControllerFactory
{
    private WindsorContainer container;

    public WindsorControllerFactory()
    {
        container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
        var controllerTypes = from t in Assembly.GetExecutingAssembly().GetTypes() 
                              where typeof(IController).IsAssignableFrom(t) select t;
        foreach(Type t in controllerTypes)
        {
            container.AddComponentWithLifestyle(t.FullName, t, LifestyleType.Transient);
        }
    }

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        return (IController)container.Resolve(controllerType);
    }
}
마지막으로 Controller의 생성자를 이용해서 사용될 DomainModel의 interface를 등록하는 것으로 완료할 수 있다.
public class ProductController : Controller
{
    private IProductRepository _productRepository;
    public ProductController(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }


    public ViewResult List()
    {
        return View(_productRepository.Products.ToList());
    }
}

상당히 간단한데 왜 외워지지가 않는 것일까.; Castle에 대해서는 두번째 포스트인데 포스트 내용이 비스무리하다. -_-
Posted by Y2K
,
.NET Framework 4.0Beta2 로 작성된 application들은 NUnit에서 테스트가 불가능하다. NUnit는 .NET 2.x대의 base assembly만이 테스트가 가능하도록 작성되어 있기 때문이다.

그래서 MS의 xUnit test 를 이용해서 RouteData Test Code를 작성해보면 다음과 같다.
먼저, 가장 기본적인 RouteCollection을 생성하고, 테스트할 MVC application의 Route를 등록시킨다.
그리고 RouteCollection.GetRouteData(HttpBaseContext)를 이용해서 들어온 HttpBaseContext에 대한 RouteData를 확인한다.


[TestMethod]
public void TestMethod1()
{
    RouteCollection routeConfig = new RouteCollection();
    RouteApp.MvcApplication.RegisterRoutes(routeConfig);    
    RouteData routeData = routeConfig.GetRouteData(HttpBaseContext);
}


문제는 HttpBaseContext를 생성하는 것인데. Mock을 이용하면 간단하게 해결 할 수 있다.
다음 코드는 Mock을 이용해서 HttpBaseContext를 생성하는 코드이다.


private static Mock MakeMockHttpContext(string url)
{
    var mockHttpContext = new Mock();

    var mockRequest = new Mock();
    //HttpContext에 Request 설정
    mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
    //HttpRequest의 relative url로 사용자 지정 url 설정
    mockRequest.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);

    var mockResponse = new Mock();
    //HttpContext에 Response 설정
    mockHttpContext.Setup(x => x.Response).Returns(mockResponse.Object);
    //HttpResponse에서 사용자 Url을 그냥 pass시키도록 ApplyAppPathModifier 수정
    mockResponse.Setup(x => x.ApplyAppPathModifier(It.IsAny())).Returns(x => x);            

    return mockHttpContext;
}


최종적으로 완성된 코드는 다음과 같다.

[TestMethod]
public void TestMethod1()
{
    RouteCollection routeConfig = new RouteCollection();
    RouteApp.MvcApplication.RegisterRoutes(routeConfig);

    var mockHttpContext = MakeMockHttpContext("~/");
    RouteData routeData = routeConfig.GetRouteData(mockHttpContext.Object);

    Assert.IsNotNull(routeData.Route);            
    Console.WriteLine(routeData.Values.ToString());
}
Posted by Y2K
,
1. URL을 깔끔하고 사용자 친화적으로 만들자.
URL은 폰트나 이미지 못지 않게 중요한 UI의 일부라는 것을 인식하자.
  • URL이 응용 프로그램의 일부가 아닌 그가 담고 있는 내용을 서술하도록 변경하자.
  • ID나 숫자보다는 본문 제목을 표시하도록 하자.
  • 가능하다면 HTML 페이지에 대하여 파일명 확장자를 사용하지 말라. (.aspx와 같은) 하지만 특수한 형식들 jpg, pdf, zip에 대해서는 사용하는 것이 좋다. 사용자가 예상하는 파일명 확장자에 대해서는 그것을 사용하는 것이 훨씬 좋다.
  • 계측적인 느낌을 만들자(/Product/Menswear/Shirts/Red와 같이)
  • 대소문자를 구별하지 말자
  • 기술적인 기호나 코드, 연속문자를 피하라. 만일 단어의 분리를 원한다면 -를 사용해서 연걸해라
  • URL을 변경하지 말아라. 깨져버린 링크는 사업의 손실과 같다. URL을 변경하는 경우 301 영구적 재전송을 통해 가능한 길게 기존 URL 스키마를 유지해야지 된다.
  • URL은 짧아야 하고, 입력하기 쉬워야지 하며, 편집하기 용의하고 지속적이여야지 된다. 그리고 사이트 구조를 투영하고 있어야지 된다
참고 자료 : http://www.w3.org/Provider/Style/Url

2. HTTP 관습을 따르자
  • GET과 POST를 정확히 선택하라. 기본적인 지침에 따르면 GET 요청은 모든 readonly 정보 검색에 사용되어야지 되며 POST 요청은 서버상에서 상태를 변경하는 write 작업에 사용되어야 한다는 것이다. 표준에 입각한 관점에서 보면 GET요청은 안전한 상호요청을 위한 것이며 POST 요청은 안전하지 않은 상호요청에 의한 것이다.
  • GET 요청은 접근이 가능해야지 된다. 모든 정보는 URL에 포함되어 있으며 그렇기 때문에 주소를 링크하거나 즐겨찾기가 가능해야지 된다.
  • 상태를 변경하는 작업에 GET을 사용하면 안된다.
  • 질의 문자열의 사용에 유의하라. 질의 문자열은 사용자 친화성을 고려하지 않는 곳이나 링크하지 않을 곳을 찾아서 사용하는 것이 좋다. 또한 알고리즘에 값을 넣어서 결과를 얻어오는 때에도 사용할 수 있다.

3. HTTP 재전송의 정확한 유형을 사용하자.
HTTP 재전송의 경우 301과 302로 정의되어 있는데, 둘다 모두 브라우저로 하여금 GET 요청을 통해 새로운 URL로 이동하게 된다. 그러나 301과 302는 검색엔진에서 차이가 있으며 이는 다음과 같다.

301 : 영구적인 이동. 이는 이 URL이 영원히 사용되지 않으며, 다시는 요쳥되지 않아야지 함을 의미한다. 그리고 모든 Inbound 링크는 새로운 URL로 변경되어야지 되는 것을 의미한다. 검색엔진에서는 새로운 URL하에서 Context처리를 한다. URL Schema가 변경되었을 때 사용되어야지 된다.
302 : 임시적인 이동. 클라이언트는 이번 요청만을 위해서 제공하는 대체 URL을 사용한다. 검색엔진은 기존의 URL을 유지한다.

4. 검색엔진 최적화
검색 엔진의 종류에 따라 각기 다른 엔진 순위를 가지고 있으나, 많은 Blogger들이 실험으로 증명한 사항들은 다음과 같다.
  • URL에 적절한 키워드를 사용하라.
  • 질의 문자열 매개변수를 최소화하고 단어구분자로 밑줄을 사용하지 말라
  • 컨탠츠의 각 부분에 대해 단일 URL, 즉 정규 URL을 주도록 하라. HTTP 301 재전송을 통해서 현재의 정규 URL을 유지하라.
  • 모든 Contents는 접근 가능해야지 된다. Contents가 javascript, flash, silverlight의 탐색에 의하여 종속적이지 않아야지 된다. (불행하게도 우리나라에서 가장 안지켜지는 부분인것 같다. 모든 사이트가 Flash toolbar로 떡칠이 되어있으니.;;)




Posted by Y2K
,
: RouteBase, Route, RouteCollection

응용프로그램은 하나의 System.Web.Routing.RouteTable.Routes를 가지며, 응용프로그램의
실제 동작하는 Routing 구성 설정을 갖는다.

ASP.NET MVC에서 구성되는 Global.asax에서 application_start()로 구현이 된다.


Route의 동작

1. Url 요청
2. UrlRoutingModule module 호출(System.Web.Routing)
3. System.Web.Routing.RouteTable.Routes 안에서 요청과 일치하는 RouteBase 객체 반환
4. RouteData 구조체 확인
   1) RouteHandler : 요청을 처리할 객체 (ASP .NET MVC에서는 MvcRouteHandler의 instance가 된다.)
   2) RouteValueDictionary : 요청에서 추출된 중괄호 매개변수의 이름과 값의 dictionary
   3) Route : RouteBase에 대한 참조 객체
   4) DataTokens : Routing Entry에 의해 제공되는 모든 추가적인 구성 설정 옵션의 Dictionary
5. RouteData의 RouteHandler 호출
   : RouteHandler에 RouteData, Http Header, Cookie, Auth, Query string, post data를 포함한 모든 HttpContextBase전달

* Route Entry의 순서는 매우 중요하다. System.Web.Routing.RouteTable.Routes는 정렬된 목록이 제공되며,
  이는 Global.asxs 파일안에 정의되어있는 순서대로 구성이 되게 된다. 보다 명확한 매치에 해당되는 Route를
  먼저 등록시켜야지 된다.

* Server의 HDD에 있는 경우에는 언제나 그 정적인 파일이 우선적으로 호출된다. Routing 구성 설정보다
  우선권을 갖게 된다.


기본 Routing 작성


Route defaultRoute = new Route("{controller}/{action}/{id}", new MvcRouteHandler())
{                
    Defaults = new RouteValueDictionary(new { controller = "Home", action = "index", id = "" })
};
routes.Add("default", defaultRoute);

routes.Add(new Route("Catalog/{color}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(
        new { controller = "Products", action = "List", color=(string)null }
        )                
});


제약사항이 있는 Routing 작성
: Constraint를 이용. 제약 사항을 추가적으로 만들어주기 위해서는 System.Web.Routing.IRouteConstraint를
  상속받는 Class를 제공해서 RouteValueDictionary에 넣어주면 된다.


routes.Add(new Route("Article/{id}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(new { controller = "Articles", action = "Show" }),
    Constraints = new RouteValueDictionary(
            new
            {
                id = @"\d{1,6}", //ID 값이 항시 정규표현식 안에 일치하는지 제약조건 강제화.
                httpMethod = new HttpMethodConstraint("GET"), //항시 GET method로만 call이 가능
                userAgent = new UserAgentConstraint("iPhone") //iPhone web agent로만 접근 가능 
            }
        )
});

public class UserAgentConstraint : IRouteConstraint
{
    private string _requestSubString;
    public UserAgentConstraint(string requestSubString)
    {
        _requestSubString = requestSubString;
    }

    #region IRouteConstraint Members

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if(httpContext.Request.UserAgent == null)
            return false;
        return httpContext.Request.UserAgent.Contains(_requestSubString);
    }

    #endregion
}


* 현재 사용의 매개변수는 재사용될 수 있다는 점을 염두해둬야지 된다.이 때 고려될 것은 매개변수보다
  앞쪽에 나오는 매개변수에 대한 값들만이 재사용된다.
  routes.MapRoute(null, "{controller}/{action}/{color}/{page}");  의 경우에,
  <%=Html.ActionLink("ClickMe", "List", "Catalog", new {page=789}, null)%>로 구성이 될 경우에는 color
  값은 ActionLink가 호출이 되는 현재 요청의 color 값이 사용이 된다. 그러나,
  <%=Html.ActionLink("ClickMe", "List", "Catalog", new {color="black"},null%>로 구성이 될 경우에는
  page값이 재사용되지 못하고 route entry는 일치되지 못한다.


Posted by Y2K
,
Client 소스는 좀더 간단하다. Server의 경우에는 Bind와 Listen의 절차를 거치고, Listen에서 BeginListen, EndListen으로
AsyncCallback을 통해야지 되지만, Client의 경우에는 간단히 Connect만으로 가능하고, 하나의 Client에서는 역시 하나의
연결정도만 신경을 쓰면 되기 때문에 더욱더 편한 개발이 가능하다.

솔찍한 생각으로 예제 소스이긴 하지만, UI단만 조금 붙여버리면 거의 일반 프로그램들과 큰 차이가 안보인다.
Socket의 Close시에 Shutdown으로 Socket을 정리하는 부분에 신경을 쓰면 될 것 같다.


/****************************** Module Header ******************************\
* Module Name:	Program.cs
* Project:		CSSocketClient
* Copyright (c) Microsoft Corporation.
* 
* Sockets are an application programming interface(API) in an operating system
* used for in inter-process communication. Sockets constitute a mechanism for
* delivering incoming data packets to the appropriate application process or
* thread, based on a combination of local and remote IP addresses and port
* numbers. Each socket is mapped by the operational system to a communicating
* application process or thread.
*
*
* .NET supplies a Socket class which implements the Berkeley sockets interface.
* It provides a rich set of methods and properties for network communications. 
* Socket class allows you to perform both synchronous and asynchronous data
* transfer using any of the communication protocols listed in the ProtocolType
* enumeration. It supplies the following types of socket:
*
* Stream: Supports reliable, two-way, connection-based byte streams without 
* the duplication of data and without preservation of boundaries.
*
* Dgram:Supports datagrams, which are connectionless, unreliable messages of
* a fixed (typically small) maximum length. 
*
* Raw: Supports access to the underlying transport protocol.Using the 
* SocketTypeRaw, you can communicate using protocols like Internet Control 
* Message Protocol (Icmp) and Internet Group Management Protocol (Igmp). 
*
* Rdm: Supports connectionless, message-oriented, reliably delivered messages, 
* and preserves message boundaries in data. 
*
* Seqpacket:Provides connection-oriented and reliable two-way transfer of 
* ordered byte streams across a network.
*
* Unknown:Specifies an unknown Socket type.
* 
* This source is subject to the Microsoft Public License.
* See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
* All other rights reserved.
* 
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\***************************************************************************/

#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Security.Permissions;
#endregion


public class Program
{
    static void Main(string[] args)
    {
        // Receiving byte array 
        byte[] bytes = new byte[1024];
        try
        {
            // Create one SocketPermission for socket access restrictions
            SocketPermission permission = new SocketPermission(
                NetworkAccess.Connect,    // Connection permission
                TransportType.Tcp,        // Defines transport types
                "",                       // Gets the IP addresses
                SocketPermission.AllPorts // All ports
                );

            // Ensures the code to have permission to access a Socket
            permission.Demand();

            // Resolves a host name to an IPHostEntry instance           
            IPHostEntry ipHost = Dns.GetHostEntry("");

            // Gets first IP address associated with a localhost
            IPAddress ipAddr = ipHost.AddressList[0];

            // Creates a network endpoint
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 6000);

            // Create one Socket object to setup Tcp connection
            Socket sender = new Socket(
                ipAddr.AddressFamily,// Specifies the addressing scheme
                SocketType.Stream,   // The type of socket 
                ProtocolType.Tcp     // Specifies the protocols 
                );

            sender.NoDelay = true;   // Using the Nagle algorithm

            // Establishes a connection to a remote host
            sender.Connect(ipEndPoint);
            Console.WriteLine("Socket connected to {0}",
                sender.RemoteEndPoint.ToString());

            // Sending message
            // is the sign for end of data
            string theMessage = "Hello World!";
            byte[] msg = 
                Encoding.Unicode.GetBytes(theMessage +"");

            // Sends data to a connected Socket.
            int bytesSend = sender.Send(msg);

            // Receives data from a bound Socket.
            int bytesRec = sender.Receive(bytes);

            // Converts byte array to string
            theMessage = Encoding.Unicode.GetString(bytes, 0, bytesRec);

            // Continues to read the data till data isn't available
            while (sender.Available > 0)
            {
                bytesRec = sender.Receive(bytes);
                theMessage += Encoding.Unicode.GetString(bytes, 0, bytesRec);
            }
            Console.WriteLine("The server reply: {0}", theMessage);

            // Disables sends and receives on a Socket.
            sender.Shutdown(SocketShutdown.Both);

            //Closes the Socket connection and releases all resources
            sender.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.ToString());
        }
    }
}
Posted by Y2K
,
Socket을 사용해서 TCP/IP 서버를 만들게 되면 개발에서 가장 신경쓰게 되는 것이 Multi-Thread 문제이다.
이부분에 대해서 .NET에서 내놓은 최고의 Solution은 IAsyncCallback interface를 이용해서 BeginXXX 와
EndXXX를 이용하는 방법이다. 이 방법은 WebService client, Stream, Socket 등 데이터를 읽고, 쓰는데에
시간이 소요가 되어 Multi-Thread를 구현해야지 되는 거의 모든 경우에서 사용이 가능하다.

예제 소스는 다음과 같다. IPV6를 사용하는 소스를 IPV4로만 변경시킨 예제이다.


/****************************** Module Header ******************************\
* Module Name:	Program.cs
* Project:		CSSocketServer
* Copyright (c) Microsoft Corporation.
* 
* Sockets are an application programming interface(API) in an operating system
* used for in inter-process communication. Sockets constitute a mechanism for
* delivering incoming data packets to the appropriate application process or
* thread, based on a combination of local and remote IP addresses and port
* numbers. Each socket is mapped by the operational system to a communicating
* application process or thread.
*
*
* .NET supplies a Socket class which implements the Berkeley sockets interface.
* It provides a rich set of methods and properties for network communications. 
* Socket class allows you to perform both synchronous and asynchronous data
* transfer using any of the communication protocols listed in the ProtocolType
* enumeration. It supplies the following types of socket:
*
* Stream: Supports reliable, two-way, connection-based byte streams without 
* the duplication of data and without preservation of boundaries.
*
* Dgram:Supports datagrams, which are connectionless, unreliable messages of
* a fixed (typically small) maximum length. 
*
* Raw: Supports access to the underlying transport protocol.Using the 
* SocketTypeRaw, you can communicate using protocols like Internet Control 
* Message Protocol (Icmp) and Internet Group Management Protocol (Igmp). 
*
* Rdm: Supports connectionless, message-oriented, reliably delivered messages, 
* and preserves message boundaries in data. 
*
* Seqpacket:Provides connection-oriented and reliable two-way transfer of 
* ordered byte streams across a network.
*
* Unknown:Specifies an unknown Socket type.
* 
* This source is subject to the Microsoft Public License.
* See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
* All other rights reserved.
* 
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\***************************************************************************/

#region Using Directive
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Security;
using System.Security.Permissions;
#endregion


public class Program
{
    static void Main(string[] args)
    {
        // Creates one SocketPermission object for access restrictions
        SocketPermission permission = new SocketPermission(
            NetworkAccess.Accept,     // Allowed to accept connections
            TransportType.Tcp,        // Defines transport types
            "",                       // The IP addresses of local host
            SocketPermission.AllPorts // Specifies all ports
            );

        // Listening Socket object
        Socket sListener = null;

        try
        {
            // Ensures the code to have permission to access a Socket
            permission.Demand();

            // Resolves a host name to an IPHostEntry instance
            IPHostEntry ipHost = Dns.GetHostEntry("");

            ///Get First IPV4 Address
            ///아직까지는 IPV4로 만들지 않으면 외부에서 접근을 못하는 경우가 많다.;
            IPAddress ipAddr = ipHost.AddressList.Where(a => a.AddressFamily != AddressFamily.InterNetworkV6).First();

            // Creates a network endpoint
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 6000);

            // Create one Socket object to listen the incoming connection
            sListener = new Socket(
                ipAddr.AddressFamily,
                SocketType.Stream,
                ProtocolType.Tcp
                );

            // Associates a Socket with a local endpoint
            sListener.Bind(ipEndPoint);

            // Places a Socket in a listening state and specifies the maximum
            // Length of the pending connections queue
            sListener.Listen(10);

            Console.WriteLine("Waiting for a connection on port {0}",
                ipEndPoint);

            // Begins an asynchronous operation to accept an attempt
            AsyncCallback aCallback = new AsyncCallback(AcceptCallback);
            sListener.BeginAccept(aCallback, sListener);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.ToString());
            return;
        }

        Console.WriteLine("Press the Enter key to exit ...");
        Console.ReadLine();
        
        if (sListener.Connected)
        {
            sListener.Shutdown(SocketShutdown.Receive);
            sListener.Close();
        }
    }


    /// 
    /// Asynchronously accepts an incoming connection attempt and creates
    /// a new Socket to handle remote host communication.
    ///      
    /// the status of an asynchronous operation
    ///  
    public static void AcceptCallback(IAsyncResult ar)
    {
        Socket listener = null;

        // A new Socket to handle remote host communication
        Socket handler = null;
        try
        {
            // Receiving byte array
            byte[] buffer = new byte[1024];
            // Get Listening Socket object
            listener = (Socket)ar.AsyncState;
            // Create a new socket
            handler = listener.EndAccept(ar);

            // Using the Nagle algorithm
            handler.NoDelay = true;

            // Creates one object array for passing data
            object[] obj = new object[2];
            obj[0] = buffer;
            obj[1] = handler;

            // Begins to asynchronously receive data
            handler.BeginReceive(
                buffer,        // An array of type Byt for received data
                0,             // The zero-based position in the buffer 
                buffer.Length, // The number of bytes to receive
                SocketFlags.None,// Specifies send and receive behaviors
                new AsyncCallback(ReceiveCallback),//An AsyncCallback delegate
                obj            // Specifies infomation for receive operation
                );

            // Begins an asynchronous operation to accept an attempt
            AsyncCallback aCallback = new AsyncCallback(AcceptCallback);
            listener.BeginAccept(aCallback, listener);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.ToString());
        }
    }

    /// 
    /// Asynchronously receive data from a connected Socket.
    /// 
    /// 
    /// the status of an asynchronous operation
    ///  
    public static void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            // Fetch a user-defined object that contains information
            object[] obj = new object[2];
            obj = (object[])ar.AsyncState;

            // Received byte array
            byte[] buffer = (byte[])obj[0];

            // A Socket to handle remote host communication.
            Socket handler = (Socket)obj[1];

            // Received message
            string content = string.Empty;

            // The number of bytes received.
            int bytesRead = handler.EndReceive(ar);

            if (bytesRead > 0)
            {
                content += Encoding.Unicode.GetString(buffer, 0,
                    bytesRead);
                // If message contains "", finish receiving
                if (content.IndexOf("") > -1)
                {
                    // Convert byte array to string
                    string str =
                        content.TrimEnd(("").ToCharArray());
                    Console.WriteLine(
                        "Read {0} bytes from client.\n Data: {1}",
                        str.Length, str);

                    // Prepare the reply message
                    byte[] byteData =
                        Encoding.Unicode.GetBytes(str);

                    // Sends data asynchronously to a connected Socket
                    handler.BeginSend(byteData, 0, byteData.Length, 0,
                        new AsyncCallback(SendCallback), handler);
                }
                else
                {
                    // Continues to asynchronously receive data
                    byte[] buffernew = new byte[1024];
                    obj[0] = buffernew;
                    obj[1] = handler;
                    handler.BeginReceive(buffernew, 0, buffernew.Length,
                        SocketFlags.None,
                        new AsyncCallback(ReceiveCallback), obj);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.ToString());
        }
    }

    /// 
    /// Sends data asynchronously to a connected Socket.
    /// 
    /// 
    /// The status of an asynchronous operation
    ///  
    public static void SendCallback(IAsyncResult ar)
    {
        try
        {
            // A Socket which has sent the data to remote host
            Socket handler = (Socket)ar.AsyncState;

            // The number of bytes sent to the Socket
            int bytesSend = handler.EndSend(ar);
            Console.WriteLine(
                "Sent {0} bytes to Client",bytesSend);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.ToString());
        }
    }
}
Posted by Y2K
,

0008. CSWindowsHook

Sample Codes 2010. 1. 5. 16:26
Windows Hooking의 경우에는 Windows message queue에 대한 이해가 좀 필요하다.
Windows는 Windows Message Queue를 통해서 각각의 Message를 전달하고, 그 Message에 따라 각기 다른 Action을 행한다.
WM_PAINT의 경우에는 화면을 그리라는 Message가 되고, WM_MOUSE의 경우에는 Mouse의 움직임에 따라 발생되는 Windows
Message이다.

Hooking은 이런 WIndows Message Queue에 Hook을 걸어서, 원래 처리되어야지 되는 함수가 아닌, 사용자 지정 Method에서
처리하게 하는 방법이다. 이것을 이용하면 Ctrl+Alt+Del의 기능도 막아버릴 수 있으며, 마우스를 움직여서 다른 동작을 하는 것도
막는 것이 가능하다. 또한 Mouse나 Keyboard의 모든 기록들을 저장할 수 있기 때문에 Windows에서 Cracking을 하는 경우에
왕왕 사용되는 방법이다.

먼저, WIN32 함수인 SetWindowsHookEx 함수를 DllImport시켜서 가지고 온다. 필요한 Native method의 선언은 다음과 같다.

internal delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

internal class NativeMethods
{
    /// 
    /// Hooking WIN32 API
    /// dwThreadId가 0인 경우에는 전역 hook
    /// 특정 handler안에서 Hooking을 하는 경우에는 Handle 값을 넣어준다.
    /// 
    /// 
    /// Hooking을 한 이후에 받을 method의 주소값 delegate된 method를 넣어줄수 있다.
    /// 
    /// 
    /// 
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc callback, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
}

internal static class HookCodes
{
    public const int HC_ACTION = 0;
    public const int HC_GETNEXT = 1;
    public const int HC_SKIP = 2;
    public const int HC_NOREMOVE = 3;
    public const int HC_NOREM = HC_NOREMOVE;
    public const int HC_SYSMODALON = 4;
    public const int HC_SYSMODALOFF = 5;
}

internal enum HookType
{
    WH_KEYBOARD = 2,
    WH_MOUSE = 7,
    WH_KEYBOARD_LL = 13,
    WH_MOUSE_LL = 14
}

[StructLayout(LayoutKind.Sequential)]
internal class POINT
{
    public int x;
    public int y;
}

/// 
/// The MSLLHOOKSTRUCT structure contains information about a low-level keyboard 
/// input event. 
/// 
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEHOOKSTRUCT
{
    public POINT pt;        // The x and y coordinates in screen coordinates
    public int hwnd;        // Handle to the window that'll receive the mouse message
    public int wHitTestCode;
    public int dwExtraInfo;
}

/// 
/// The MOUSEHOOKSTRUCT structure contains information about a mouse event passed 
/// to a WH_MOUSE hook procedure, MouseProc. 
/// 
[StructLayout(LayoutKind.Sequential)]
internal struct MSLLHOOKSTRUCT
{
    public POINT pt;        // The x and y coordinates in screen coordinates. 
    public int mouseData;   // The mouse wheel and button info.
    public int flags;
    public int time;        // Specifies the time stamp for this message. 
    public IntPtr dwExtraInfo;
}

internal enum MouseMessage
{
    WM_MOUSEMOVE = 0x0200,
    WM_LBUTTONDOWN = 0x0201,
    WM_LBUTTONUP = 0x0202,
    WM_LBUTTONDBLCLK = 0x0203,
    WM_RBUTTONDOWN = 0x0204,
    WM_RBUTTONUP = 0x0205,
    WM_RBUTTONDBLCLK = 0x0206,
    WM_MBUTTONDOWN = 0x0207,
    WM_MBUTTONUP = 0x0208,
    WM_MBUTTONDBLCLK = 0x0209,

    WM_MOUSEWHEEL = 0x020A,
    WM_MOUSEHWHEEL = 0x020E,

    WM_NCMOUSEMOVE = 0x00A0,
    WM_NCLBUTTONDOWN = 0x00A1,
    WM_NCLBUTTONUP = 0x00A2,
    WM_NCLBUTTONDBLCLK = 0x00A3,
    WM_NCRBUTTONDOWN = 0x00A4,
    WM_NCRBUTTONUP = 0x00A5,
    WM_NCRBUTTONDBLCLK = 0x00A6,
    WM_NCMBUTTONDOWN = 0x00A7,
    WM_NCMBUTTONUP = 0x00A8,
    WM_NCMBUTTONDBLCLK = 0x00A9
}

/// 
/// The structure contains information about a low-level keyboard input event. 
/// 
[StructLayout(LayoutKind.Sequential)]
internal struct KBDLLHOOKSTRUCT
{
    public int vkCode;      // Specifies a virtual-key code
    public int scanCode;    // Specifies a hardware scan code for the key
    public int flags;
    public int time;        // Specifies the time stamp for this message
    public int dwExtraInfo;
}

internal enum KeyboardMessage
{
    WM_KEYDOWN = 0x0100,
    WM_KEYUP = 0x0101,
    WM_SYSKEYDOWN = 0x0104,
    WM_SYSKEYUP = 0x0105
}

여기에서 구성된 Hooking method들은 다음과 같다.

private bool SetGlobalLLMouseHook()
{
    // Create an instance of HookProc.
    globalLLMouseHookCallback = new HookProc(this.LowLevelMouseProc);

    hGlobalLLMouseHook = NativeMethods.SetWindowsHookEx(
        HookType.WH_MOUSE_LL,  // Must be LL for the global hook
        globalLLMouseHookCallback,
        // Get the handle of the current module
        Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
        // The hook procedure is associated with all existing threads running 
        // in the same desktop as the calling thread.
        0);
    return hGlobalLLMouseHook != IntPtr.Zero;
}

public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if(nCode >= 0)
    {
        // Marshal the MSLLHOOKSTRUCT data from the callback lParam
        MSLLHOOKSTRUCT mouseLLHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

        // Get the mouse WM from the wParam parameter
        MouseMessage wmMouse = (MouseMessage)wParam;

        // Display the current mouse coordinates and the message
        String log = String.Format("X = {0} Y = {1}  ({2})\r\n", mouseLLHookStruct.pt.x, mouseLLHookStruct.pt.y, wmMouse);
        tbLog.AppendText(log);
    }

    // Pass the hook information to the next hook procedure in chain
    return NativeMethods.CallNextHookEx(hGlobalLLMouseHook, nCode, wParam, lParam);
}
Posted by Y2K
,
별다를 것이 없는 코드.
Visual Studio에서 Generate되는 code만을 간단히 이용하지만, Windows EventLog를 사용하는 법이 나온 부분만 체크해보면 될 것 같다.
또 하나가 있다면, ProjectInstaller 역시 살펴 볼 내용.

Windows Service를 등록하기 위해서는 InstallUtil을 이용해서 Service를 등록시켜야지 되는데 이때 등록할 ServiceName과 Service의
이름을 일치시켜주는 것이 필요하다.


private void InitializeComponent()
{
    ///서비스 등록 Account와 Password, UserName을 명시해준다.
    this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
    this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
    // 
    // serviceProcessInstaller1
    // 
    this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalService;
    this.serviceProcessInstaller1.Password = null;
    this.serviceProcessInstaller1.Username = null;
    // 
    // serviceInstaller1
    // 
    ///서비스에 대한 내용을 기술한다. ServiceName의 경우에는 작성한 ServiceName과 반드시 일치해야지 된다.
    this.serviceInstaller1.Description = "All-In-One Code Framework Windows Service example";
    this.serviceInstaller1.DisplayName = "CSWindowsService Sample Service";
    this.serviceInstaller1.ServiceName = "CSWindowsService";
    // 
    // ProjectInstaller
    // 
    this.Installers.AddRange(new System.Configuration.Install.Installer[] {
                                this.serviceProcessInstaller1,
                                this.serviceInstaller1});
}

/// Required method for Designer support - do not modify 
/// the contents of this method with the code editor.
/// Windows EventLog에 대한 초기화를 행한다. 
private System.Diagnostics.EventLog eventLog1;
private void InitializeComponent()
{
    this.eventLog1 = new System.Diagnostics.EventLog();
    ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).BeginInit();
    // 
    // eventLog1
    // 
    this.eventLog1.Log = "Application";
    this.eventLog1.Source = "CSWindowsService";
    // 
    // MyService
    // 
    this.ServiceName = "CSWindowsService";
    ((System.ComponentModel.ISupportInitialize)(this.eventLog1)).EndInit();
}
Posted by Y2K
,
WPF를 이용한 Image animation

System.Windows.Media.Animation.StoryBoard controller를 이용해서 Image Animation을 할 수 있다.
Image Animation 형태는 StoryBoard Controller의 Property들을 변경시키면 가능하다.



int nextImageIndex;
List images = new List();

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // initialize the images collection
    images.Add(new BitmapImage(new Uri("Images/image1.jpg", UriKind.Relative)));
    images.Add(new BitmapImage(new Uri("Images/image2.jpg", UriKind.Relative)));
    images.Add(new BitmapImage(new Uri("Images/image3.jpg", UriKind.Relative)));
    images.Add(new BitmapImage(new Uri("Images/image4.jpg", UriKind.Relative)));

    nextImageIndex = 2;
}
       
private void VisbleToInvisible_Completed(object sender, EventArgs e)
{
    // change the source of the myImage1 to the next image to be shown 
    // and increase the nextImageIndex
    this.myImage1.Source = images[nextImageIndex++];

    // if the nextImageIndex exceeds the top bound of the ocllection, 
    // get it to 0 so as to show the first image next time
    if (nextImageIndex == images.Count)
    {
        nextImageIndex = 0;
    }

    // get the InvisibleToVisible storyboard and start it
    Storyboard sb = FindResource("InvisibleToVisible") as Storyboard;
    if(sb != null) 
        sb.Begin(this);

}

private void InvisibleToVisible_Completed(object sender, EventArgs e)
{
    // change the source of the myImage2 to the next image to be shown
    // and increase the nextImageIndex
    this.myImage2.Source = images[nextImageIndex++];

    // if the nextImageIndex exceeds the top bound of the ocllection, 
    // get it to 0 so as to show the first image next time
    if (nextImageIndex == images.Count)
    {
        nextImageIndex = 0;
    }

    // get the VisibleToInvisible storyboard and start it
    Storyboard sb = this.FindResource("VisibleToInvisible") as Storyboard;
    if(sb != null) 
        sb.Begin(this);
}   
Posted by Y2K
,
RSAServiceProvider를 이용해서 xml 파일에 서명 및 Decryption 하는 과정
RSAServiceProvider를 제공된 public key를 이용해서 얻어내는 것이 이 코드의 핵심.



private String publicOnlyKey;
private void btnSign_Click(object sender, EventArgs e)
{
    try
    {
        /////////////////////////////////////////////////////////////////
        // Create a new RSA signing key and export public key for 
        // verification.
        
        RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
        
        //Public Key exported
        //Public Key 뿐 아니라 Private Key도 얻어내는 것이 가능하다. 사용자에게 Key를 보내거나 저장할 때 이용 가능
        publicOnlyKey = rsaKey.ToXmlString(false);
        tbxRSAParameters.Text = publicOnlyKey;
        
        /////////////////////////////////////////////////////////////////
        // Sign the XML document.
        // 

        SignXml(xmlDoc, rsaKey);
        MessageBox.Show("XML file signed.");


        /////////////////////////////////////////////////////////////////
        // Save and display the signed document.
        // 

        xmlDoc.Save("test1.xml");
        tbxDigitalSignature.Text = xmlDoc.OuterXml;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

///XML파일을 RSA를 이용해서 서명. SignedXml class가 있는 것을 기억하고 있을것!
public static void SignXml(XmlDocument Doc, RSA Key)
{
    // Check arguments.
    if (Doc == null)
        throw new ArgumentException("Doc");
    if (Key == null)
        throw new ArgumentException("Key");

    try
    {
        // Create a SignedXml object to generate signature.
        SignedXml signedXml = new SignedXml(Doc);

        // Add the key to the SignedXml document
        signedXml.SigningKey = Key;

        // Create a reference to be signed
        Reference reference = new Reference();
        reference.Uri = "";

        // Add an enveloped transformation to the reference
        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
        reference.AddTransform(env);

        // Add the reference to the SignedXml object
        signedXml.AddReference(reference);

        // Compute the signature
        signedXml.ComputeSignature();

        // Get the XML representation of the signature and save
        // it to an XmlElement object.
        XmlElement xmlDigitalSignature = signedXml.GetXml();

        // Append the element to the XML document.
        Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

private void btnVerify_Click(object sender, EventArgs e)
{
    /////////////////////////////////////////////////////////////////////
    // Create a new RSA signing key and import public key for 
    // verification.
    //

    //NOTE:Public RSA Key를 이용해서 RSACryptoService Generate
    RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
    rsaKey.FromXmlString(publicOnlyKey);
    /////////////////////////////////////////////////////////////////////
    // Load the signed XML, and call VerifyXml to verify the signature of 
    // the signed XML.
    // 

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.Load("test1.xml");

    bool result = VerifyXml(xmlDoc, rsaKey);

    if (result)
    {
        MessageBox.Show("The XML signature is valid.");
    }
    else
    {
        MessageBox.Show("The XML signature is not valid.");
    }
}

public static Boolean VerifyXml(XmlDocument Doc, RSA Key)
{
    // Check arguments.
    if (Doc == null)
        throw new ArgumentException("Doc");
    if (Key == null)
        throw new ArgumentException("Key");


    /////////////////////////////////////////////////////////////////////
    // Create a SignedXml object to verify the signature
    //

    SignedXml signedXml = new SignedXml(Doc);

    // Find Signature node and create a new XmlNodeList object
    XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");

    // Throw an exception if no signature was found.
    if (nodeList.Count <= 0)
    {
        throw new CryptographicException(
            "Verification failed:" +
            " No Signature was found in the document.");
    }

    // This example only supports one signature for entire XML document
    if (nodeList.Count >= 2)
    {
        throw new CryptographicException(
            "Verification failed: More that one signature was found.");
    }

    // Load the first  node.  
    signedXml.LoadXml((XmlElement)nodeList[0]);

    // Check the signature and return the result.
    return signedXml.CheckSignature(Key);
}

private void btnChangeXML_Click(object sender, EventArgs e)
{
    // Modify the value of the Xml document for test. 

    XDocument xDoc = XDocument.Load("test1.xml");

    if (xDoc != null)
    {
        xDoc.Element("invoice").Element("items").
            Element("creditcard").Element("number").SetValue("19834210");

        xDoc.Save("test1.xml");

        tbxModifiedMessage.Text = xDoc.ToString();
    }
}
Posted by Y2K
,