출처 : http://warkyman.tistory.com/146

Flex 에서 MXML로 정의한 버튼이 다음과 같이 있다고 가정합니다.

 <mx:Button id="btnAlert"  label="Alert!" />


이 버튼을 클릭할 때 어떠한 동작을 하고자 하면 해당 버튼에 다음과 같이 클릭 이벤트 핸들러를 등록시켜주면 됩니다.

Script ---

public function alert(evt:MouseEvent):void
{
  Alert.show("Hello, Flex!", "Alert");
}

MXML ---

 <mx:Button id="btnAlert" label="Alert!" click="alert(event)" />


만약, 실제 버튼을 클릭하지 않고서 버튼을 클릭한 것과 같은 이벤트를 수동으로 발생시키려면 어떻게 해야할까요?
이럴때 필요한 것이 dispatchEvent 입니다.

dispatchEvent 의 구조는 다음과 같습니다.
objectInstance. dispatchEvent(event:Event)

자세한 설명은 수동에 의한 이벤트의 송출(Dispatch)  에서 참고하도록 하고 본 포스트에서는 생략하겠습니다.


아래에 보여드릴 소스는 캔버스를 MXML로 정의하여 이 캔버스를 클릭하였을때 버튼을 클릭한 것 과 같이 수동으로 이벤트를 발생시킴으로써 버튼을 클릭하였을 때와 같이 팝업이 뜨도록 하고 있습니다.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300">

    <mx:Script>
        <![CDATA[
   
        import mx.controls.Alert;

        public function alert(evt:MouseEvent):void
        {
            Alert.show("Hello, Flex!", "Alert");
        }

        public function canvasClick(evt:MouseEvent):void
        {
            btnAlert.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
        }

        ]]>
    </mx:Script>

    <mx:Canvas x="44" y="79" width="200" height="153" backgroundColor="#FF0000" borderStyle="solid"
            cornerRadius="20" click="canvasClick(event)">
        <mx:Label text="Click Here!" horizontalCenter="0" verticalCenter="0"/>
    </mx:Canvas>

    <mx:Button id="btnAlert" x="271" y="137" label="Alert!" click="alert(event)"/>

</mx:Application>

dispatchEvent 를 이용하여 마치 버튼을 클릭한 것 처럼 MouseEvent.CLICK 이벤트를 버튼에 dispatch 함으로써 버튼에서 클릭 이벤트 핸들러로 등록해놓은 alert() 메소드가 실행되게 됩니다.

[Flash] http://warkyman.tistory.com/attachment/fk170000000001.swf


물론 위의 예제에서는 단순히 클릭시에 Alert 을 띄우는 작업으로 그쳤기에 문제가 없었지만, 실제 클릭이벤트와 관련된 작업이 필요할 경우 새로 생성해준 마우스이벤트 (new MouseEvent(MouseEvent.CLICK)) 의 상세한 값들을 제어할 필요가 있습니다.

MouseEvent (type:String , bubbles:Boolean = true, cancelable:Boolean = false, localX:Number , localY:Number , relatedObject:InteractiveObject = null, ctrlKey:Boolean = false, altKey:Boolean = false, shiftKey:Boolean = false, buttonDown:Boolean = false, delta:int = 0)

와.. 많다.;;;


Posted by 나비:D
:

[Flash] http://blog.jidolstar.com/attachment/1285711347.swf




위의 소스는 FlexComponent 카페에서 시난님이 만든 "XML의 자식을 가지고 있는 마지막 노드 알아내기(http://cafe.naver.com/flexcomponent/3755)" 글을 보고 아이디어를 얻었다. 시난님이 이것을 만들게 된 계기는 카페 주인장이신 브라이언 님이 WithFlex.com에서 운영할 Flex로 만든 트리메뉴를 같이 만들자고 제안을 했기 때문이다. 그렇지 않아도 Tree메뉴가 필요할 것 같아서 만들려고 하던 참이였는데 동지를 만난것 같아 넘 좋다.

동작은 아직 미비한 편이지만 http://blog.jidolstar.com/18 에서 만든 것에 기능을 개선시켰다.
개선된 기능은 다음과 같다.


  1. 전체펼치기/접기 기능 추가
  2. 선택된 노드 펼치기/접기 기능추가
  3. 찾을 노드 id를 입력하여 해당 노드를 펼치고 선택하기 기능 추가

프로그램은 아직 실용성은 없으며 계속 보완해가고 있는 중이다. 최종 결과물은 각 노드의 위치를 바꾸고 서버에 비동기적으로 통신하여 수정할 수 있게 만들어갈 예정이다.

사용한 XML 파일은 다음과 같다.


<?xml version="1.0" encoding="utf-8" ?>
<root>
    <msg>ok</msg>
    <nodes>
        <node id="a" label="홈">
            <node id="b-1" label="제품">
                <node id="c-1" label="플렉스" />
                <node id="c-2" label="플래시" />
            </node>
            <node id="b-2" label="도움말">
                <node id="c-3" label="플렉스 라이브 문서" />
                <node id="c-4" label="플래시 라이브 문서" />
            </node>
            <node id="b-3" label="커뮤니티">
                <node id="c-5" label="플렉스 커뮤니티">
                    <node id="d-1" label="플렉스컴포넌트 카페" />
                    <node id="d-2" label="어도비유저그룹" />
                </node>
                <node id="c-6" label="플래시 커뮤니티" />
            </node>
        </node>
    </nodes>
</root>

           
프로그램 소스는 http://blog.jidolstar.com/18 에서 올린 것과 다르게 Tree를 상속받아서 사용했다. 나중에 나만의 컴퍼넌트를 만들어 아무때나 사용하기 위해서이다.

파일명 : TreeTest.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml " layout="absolute"
  initialize="System.useCodePage=true"
  xmlns:js="com.jidolstar.components.*" creationComplete="init()">
 <mx:Script>
  <![CDATA[
   private function init():void
   {
    myTreeXMLMenu.setCrossDomain("crossdomain.xml 파일 url");
    myTreeXMLMenu.request("XML파일 url");  
   }
  ]]>
 </mx:Script>
 <mx:Style>
  Application
  {
   fontSize:12pt;
  }
 </mx:Style>
 <mx:Panel width="500" height="300"
  layout="absolute" title="동적으로 바인드되는 XML Tree 메뉴+Node찾아펼치기-jidolstar.com">
  <mx:VBox width="100%" height="100%">
   <js:TreeXMLMenu id="myTreeXMLMenu" width="100%"/>
   <mx:HBox>
    <mx:Label text="찾을 노드 ID"/>
    <mx:TextInput width="100" id="findValue" text="d-2"/>
    <mx:Button label="찾기" click="myTreeXMLMenu.find(findValue.text)"/>
   </mx:HBox>
   <mx:HBox>
    <mx:Button label="전체펼치기" click="myTreeXMLMenu.expandAll(true)"/>
    <mx:Button label="전체접기" click="myTreeXMLMenu.expandAll(false)"/>
    <mx:Button label="선택된노트 접기/펼치기" click="myTreeXMLMenu.expand()"/>
   </mx:HBox>
  </mx:VBox>
 </mx:Panel>
</mx:Application>



파일명 : com.jidolstar.componet.TreeXMLMenu.mxml
 


<?xml version="1.0" encoding="utf-8"?>
<mx:Tree xmlns:mx="http://www.adobe.com/2006/mxml "    
    labelField="@label"
    dataProvider="{xlcData}"
    showRoot="false" creationComplete="init()">
 <mx:Script>
  <![CDATA[
   import mx.collections.*;
   import mx.controls.Alert;
   import mx.rpc.events.FaultEvent;
   import mx.rpc.events.ResultEvent;
   import mx.rpc.http.HTTPService;
   import flash.system.Security;

   public var httpServ:HTTPService;
   
   [Bindable]
   public var xlcData:XMLListCollection;
   
   ////////////////////////////////////////////
   // 초기화
   // HTTPSerivce를 생성
   ////////////////////////////////////////////
   public function init():void
   {
    httpServ = new HTTPService();
   }  
   
   ////////////////////////////////////////////
   // CrossDomain 설정
   ////////////////////////////////////////////  
   public function setCrossDomain(url:String):void 
   {
    Security.loadPolicyFile(url);
   }
   
   ////////////////////////////////////////////
   // XML 요청
   // 1. POST방식으로 요청하고  결과물을 E4X형태로 한다.
   // 2. 결과를 받기 위해 Listener Handler함수를 선언
   ////////////////////////////////////////////
   public function request(url:String , params:Object=null):void
   {
    this.httpServ.url = url;
    this.httpServ.method = "POST";
    this.httpServ.resultFormat="e4x";
    this.httpServ.addEventListener("result", resultHandler);
    this.httpServ.addEventListener("fault", faultHandler);
    this.httpServ.send(params);
   }

   ////////////////////////////////////////////
   // XML 결과를 받았을 경우
   // 1. XMLList에 결과를 임시로 저장
   // 2. 적합한 데이타인지 확인
   // 3. nodes요소만 XMLListCollection으로 생성
   // 이는 동적으로 Tree에 바인드[Bindable]됨
   ////////////////////////////////////////////  
   private function resultHandler(e:ResultEvent):void
   {
    var xlData:XMLList = new XMLList(e.result);
    if(xlData.elements("msg").toString()!="ok")
    {
     mx.controls.Alert.show(xlData.elements("msg").toString(),"Error");
    }
    else
    {
     xlcData = new XMLListCollection(xlData.elements("nodes"));
    }       
   }
   
   ////////////////////////////////////////////
   // XML 데이타 요청에 대한 실패가 있을 경우 호출됨
   ////////////////////////////////////////////  
   private function faultHandler(e:FaultEvent):void
   {
    mx.controls.Alert.show("XML정보 읽어오기 실패\n"+e.fault.faultString);
   }
   
   ////////////////////////////////////////////
   // Tree 전체를 접고 펼침
   ////////////////////////////////////////////
   public function expandAll(open:Boolean):void
   {
    var xlc:XMLListCollection = this.dataProvider as XMLListCollection;
    var nodeList:XMLList = xlc.descendants();
    trace(nodeList.length());
    for(var i:int=0; i<nodeList.length(); i++)
    {
     trace(nodeList[i]);
     this.expandItem(nodeList[i], open, false);
    }
   }
   
   ////////////////////////////////////////////
   // 선택한 Node 접고 펼침
   ////////////////////////////////////////////  
   public function expand():void
   {
    var selectedNode:Object=this.selectedItem;
    trace(selectedNode);
    if(this.isItemOpen(selectedNode)==true)
    {
     this.expandItem(selectedNode, false);    
     trace("close");
    }
    else
    {
     this.expandItem(selectedNode, true);    
     trace("open");
    }
   }
   
   ////////////////////////////////////////////
   // 인자로 넘어온 id값을 찾는다.
   ////////////////////////////////////////////  
   public function find(id:String):Boolean
   {
    var xlc:XMLListCollection = this.dataProvider as XMLListCollection;
    var nodeList:XMLList = xlc.descendants();
    trace(nodeList.length());
    for(var i:int=0; i<nodeList.length(); i++)
    {
     trace(nodeList[i].@id.toString());
     if(nodeList[i].@id.toString() == id)
     {
      trace(i);
      break;
     }
    }
    if(i == nodeList.length())
    {
     trace("id='"+id+"'는 없음");
     return false;
    }
    expandParents(nodeList[i]);
    this.selectedItem = nodeList[i];
    return true;
   }
   
   ////////////////////////////////////////////
   // 인자로 넘어온 node의 부모를 전부 펼친다.
   ////////////////////////////////////////////
   protected function expandParents(xmlNode:XML):void
   {
    if(xmlNode == null)
    {
     return;
    }
    while (xmlNode.parent() != null && xmlNode.localName().toString() == "node")
    {
     xmlNode = xmlNode.parent();
     this.expandItem(xmlNode, true, false);
    }
   }

  ]]>
 </mx:Script>
 
</mx:Tree>



글쓴이 : 지돌스타 (http://blog.jidolstar.com/18  )
Posted by 나비:D
:

ASP.NET MVC Framework는 URL을 controller 클래스에 매핑합니다. Controller는 들어오는 요청을 처리하고, 사용자 입력과 상호작용을 다루며, 적당한 응용프로그램 로직을 수행합니다. Controller는 요청에 대한 HTML을 생성하기 위해 view 컴포넌트를 호출합니다.


모든 controller 클래스는 Controller 클래스를 상속받으며 IController, IActionFilter, IDisposable 인터페이스를 구현합니다.


모든 controller는 Controller라고 하는 접미사를 사용해서 이름을 지어야 합니다. 다음 예제를 보면 HomeController라고 되어 있는데요, action 메소드를 포함하고 있으며 view 페이지를 렌더링하기 위한 메소드를 제공합니다.


public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewData["Title"] = "Home Page";
        ViewData["Message"] = "Welcome to ASP.NET MVC!";

        return View();
    }

    public ActionResult About()
    {
        ViewData["Title"] = "About Page";

        return View();
    }
}


Action 메소드


MVC Framework를 사용하지 않는 ASP.NET 응용프로그램에서는 사용자와의 상호작용이 페이지를 통해 이루어지지만, ASP.NET MVC 응용프로그램에서는 controller와 action을 통해 이루어집니다. controller 클래스는 action 메소드를 정의하며 원하는 만큼의 action 메소드를 포함할 수 있습니다.


Action 메소드는 보통 사용자와의 상호작용과 1대 1로 매핑이 됩니다. 예를 들면, 브라우저에 URL을 입력하거나 링크를 클릭하거나 폼을 전송하는 것들이 이에 해당됩니다. 이런 동작들은 서버에 대해 요청을 하게 되고 URL은 MVC Framework이 Action을 호출할 수 있는 정보를 포함하게 됩니다.


예를 들어, 사용자가 브라우저에 URL을 입력하면 MVC 응용프로그램은 Global.asax에 정의된 라우팅 규칙을 적용하여 URL을 해석하여 적용할 controller를 결정하고 controller는 수행할 action을 결정합니다.


기본적으로 URL은 controller 다음에 action 이 오는 하위 경로 구조를 하고 있습니다. 예를 들어 URL이 http://contoso.com/MyWebSite/Products/Categories 라면, 하위 경로는 /Products/Categories가 되고 "Products" 가 controller의 이름이며 "Categories" 가 action 의 이름이 됩니다.


만약 URL이 /Products/Detail/5 라고 하면 "Detail" 이 Action이 되고 5 는 Detail Action 메소드에 전달하는 매개변수 값이 될 것입니다.


ActionResult Return Type


모든 action 메소드는 ActionResult 에서 파생된 클래스의 인스턴스를 반환합니다.  모든 action 결과에 대한 base는 ActionResult이지만 action에 따라 여러가지 타입의 action result type이 존재할 수 있습니다.


다음은 내장 action result type입니다.

  • ViewResult : View 메소드에 의해 반환됩니다.
  • RedirectToRouteResult : RedirectToAction 과 RedirectToRoute 메소드에 의해 반환됩니다.
  • RedirectResult : Redirect 메소드에 의해 반환됩니다.
  • ContentResult : Content 메소드에 의해 반환됩니다.
  • JsonResult : Json 메소드에 의해 반환됩니다.
  • EmptyResult : action 메소드가 null을 반환해야 할 경우 반환합니다.

Action 메소드 매개변수


controller 클래스 내에서 URL 매개변수를 참조하는 방법은 여러가지가 있습니다. 기본적으로는 Request와 Response 객체를 사용하여 접근할 수 있습니다.


public void Detail()
{
    int id = Convert.ToInt32(Request["id"]);
}

기존에 사용하던 방법과 크게 다르지 않습니다.


하지만, 좀 더 편리하게 매개변수를 참조할 수 있는 방법이 있습니다.


Action 메소드 매개변수 자동 매핑


ASP.NET MVC Framework는 URL 매개변수 값을 매개변수에 자동으로 연결할 수 있는 방법을 제공하고 있습니다. 이 방법을 사용하게 되면 위에서 살펴보았던 매개변수 값을 받는 문장을 사용하지 않아도 됩니다.


public ResultAction Detail(int id)
{
    ViewData["DetailInfo"] = id;
    return View("Detail");
}

이렇게 하면 매개변수 id에 요청한 내용중 id라는 이름으로 된 값이 자동으로 연결이 됩니다. 별도의 Request 구문을 작성하지 않아도 되는 것이죠.


또한 Query String 대신 매개변수 값을 URL의 일부로 포함시키는 것이 가능해집니다. 예를 들어 /Products/Detail?id=3 와 같은 URL이 있다고 하면, 이 것은 /Products/Detail/3 처럼 바꿀 수 있습니다.

 

기본적인 라우팅 규칙은 /{controller}/{action}/{id} 와 같은 형태를 하고 있습니다. id 에 해당하는 값이 action 메소드의 매개변수로 자동으로 전달이 됩니다.

 

이번 내용은 거의 번역 수준이 되었네요... ^^

Posted by 나비:D
:

출처 : http://blog.naver.com/process3?Redirect=Log&logNo=20050651724

Visual Studio 2008 서비스 팩1과 .net framework 3.5 서비스 팩1 이 나왔습니다.


한글판 서비스팩은 아직 나오지 않았습니다. 영문 Visual Studio2008에만 서비스팩이 설치 됩니다.



[Visual C++ 2008 관련 업데이트 사항]

VS 2008 SP1 beta does include VC 2008 Feature Pack and TR1.

 

VC++ 2008 Express SP1 beta does not include VC 2008 feature pack

VC++ 2008 Express SP1 beta does include TR1.


[변경 사항]

.NET 3.5 SP1 과 VS 2008 SP1은 제품 발표후에 보고된 버그를 수정했고 성능을 향상 시켰고 기능을 추가해서 .net application을 좀더 잘 만들수 있도록 했다. 이번 여름에 정식버전이 출시 될 예정이다.


.NET 3.5 SP1 and VS 2008 SP1 provide a bunch of bug fixes, performance improvements, and additional feature enhancements that make building all types of .NET applications better.  It will be a fully compatible service pack release. 

We plan to ship the final release of both .NET 3.5 SP1 and VS 2008 SP1 this summer as free updates.


각각 기능별 수정 사항을 알고 싶은신 분들은 Scott Guthrie 의 블로그를 방문하시면 됩니다.

http://weblogs.asp.net/scottgu/archive/2008/05/12/visual-studio-2008-and-net-framework-3-5-service-pack-1-beta.aspx


필요하신 분들은 아래 사이트에서 다운받으세요


Downloads
VS 2008 SP1 : http://download.microsoft.com/download/7/3/8/7382EA08-4DD6-4134-9B92-8585A5B07973/VS90sp1-KB945140-ENU.exe
 
.NET 3.5 SP1 : http://download.microsoft.com/download/8/f/c/8fc1fe13-55de-4bf5-b43e-375daf01452e/dotNetFx35setup.exe
 
Express 2008 with SP1:
http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vbsetup.exe
 
http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vcsetup.exe
 
http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vcssetup.exe
 
http://download.microsoft.com/download/F/E/7/FE754BA4-140B-413C-933F-8D35FB150F12/vnssetup.exe
 
TFS 2008 SP1: http://download.microsoft.com/download/a/e/2/ae2eb0ff-e687-4221-9c3e-9165a942bc1c/TFS90sp1-KB949786.exe


출처 : 다년간의 프로그램밍 경험 및 http://weblogs.asp.net/scottgu/archive/2008/05/12/visual-studio-2008-and-net-framework-3-5-service-pack-1-beta.aspx

Posted by 나비:D
:

ASP.NET에서 MVC(Model-View-Controller) 구현

? Data Column: select for more on pattern organization Application Column: select for more on pattern organization Deployment Column: select for more on pattern organization Infrastructure Column: select for more on pattern organization
Architecture Row: select for more on pattern organization Data Architecture: select for more on pattern organization Application Architecture: select for more on pattern organization Deployment Architecture: select for more on pattern organization Infrastructure Architecture: select for more on pattern organization
Design Row: select for more on pattern organization Data Design: select for more on pattern organization Application Design: select for more on pattern organization Deployment Design: select for more on pattern organization Infrastructure Design: select for more on pattern organization
Implementation Row Data Implementation: select for more on pattern organization Application Implementation: select for more on pattern organization Deployment Implementation: select for more on pattern organization Infrastructure Implementation: select for more on pattern organization
? Complete List of patterns & practices Complete List of patterns & practices Complete List of patterns & practices Complete List of patterns & practices

버전 1.0.1

이 패턴에 대한 공동 작업을 위한 DotDotNet 커뮤니티

patterns & practives 전체 목록 (영문)

상황

Microsoft ASP.NET에서 웹 응용 프로그램을 구축 중인데 응용 프로그램이 복잡하기 때문에 코드 중복을 줄이고 변경 사항이 전파되는 것을 제한하려면 프로그램의 서로 다른 기능을 분리해야 합니다.

구현 전략

소프트웨어의 모델, 뷰 및 컨트롤러 역할을 분리하여 제공된 값 및 ASP.NET에서 MVC(Model-View-Controller)패턴을 구현하는 방법을 설명하기 위해, 다음 예에서는 이 세 역할을 분리하지 않은 단일 페이지 솔루션을 이러한 역할을 분리한 솔루션으로 리팩토링합니다. 이 예제 응용 프로그램은 드롭다운 목록이 있는 단일한 웹 페이지(그림 1)로서, 데이터베이스에 저장되어 있는 기록을 표시합니다.

그림 1: 예제 웹 페이지

사용자는 드롭다운 목록에서 특정 기록을 선택한 다음 전송(Submit) 단추를 클릭합니다. 그러면 응용 프로그램이 데이터베이스의 이 기록에서 모든 트랙의 목록을 검색하여 그 결과를 표로 표시합니다. 이 패턴에 설명된 세 솔루션 모두 정확하게 동일한 기능을 구현합니다.

단일 ASP.NET 페이지

ASP.NET에서 이 페이지를 구현하는 방법은 많습니다. 가장 간단하고 단순한 방법은 다음 코드 예에서 볼 수 있는 것처럼 모든 것을 "Solution.aspx"라고 하는 한 파일에 모두 넣는 방법입니다.

 

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
   <head>
      <title>start</title>
      <script language="c#" runat="server">
         void Page_Load(object sender, System.EventArgs e)
         {
            String selectCmd = "select * from Recording";

            SqlConnection myConnection = 
               new SqlConnection(
                  "server=(local);database=recordings;Trusted_Connection=yes");
            SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, 
               myConnection);

            DataSet ds = new DataSet();
            myCommand.Fill(ds, "Recording");
            
            recordingSelect.DataSource = ds;
            recordingSelect.DataTextField = "title";
            recordingSelect.DataValueField = "id";
            recordingSelect.DataBind();
         }
         
         void SubmitBtn_Click(Object sender, EventArgs e) 
         {   
            String selectCmd = 
               String.Format(
               "select * from Track where recordingId = {0} order by id",
               (string)recordingSelect.SelectedItem.Value);

            SqlConnection myConnection = 
               new SqlConnection(
                  "server=(local);database=recordings;Trusted_Connection=yes");

            SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd,
               myConnection);

            DataSet ds = new DataSet();
            myCommand.Fill(ds, "Track");

            MyDataGrid.DataSource = ds;
            MyDataGrid.DataBind();
         }
      </script>
   </head>
   <body>
      <form id="start" method="post" runat="server">
         <h3>Recordings</h3>
         Select a Recording:<br />
         <asp:dropdownlist id="recordingSelect" runat="server" />
         <asp:button runat="server" text="Submit" OnClick="SubmitBtn_Click" />
         <p/>
         <asp:datagrid id="MyDataGrid" runat="server" width="700" 
               backcolor="#ccccff" bordercolor="black" showfooter="false" 
               cellpadding="3" cellspacing="0" font-name="Verdana" 
               font-size="8pt" headerstyle-backcolor="#aaaadd" 
               enableviewstate="false" />
      </form>
   </body>
</html>
 

이 파일은 이 패턴의 세 역할을 모두 구현하지만 이 역할을 서로 다른 파일이나 클래스로 구분하지는 않습니다. 뷰 역할은 HTML 고유의 렌더링 코드에 의해 표시됩니다. 이 페이지는 바운드 데이터 제어 구현을 사용하여 데이터베이스에서 반환된 DataSet 개체를 표시합니다. 한편 모델 역할은 Page_Load 및 SubmitBtn_Click 함수에서 구현됩니다. 컨트롤러 역할은 직접 표시되지 않으며 ASP.NET에 내재되어 있습니다. 이에 대해서는 페이지 컨트롤러를 참조하십시오. 페이지는 사용자가 요청하면 업데이트됩니다. MVC(Model-View-Controller)는 이것을 수동적인 컨트롤러로 설명합니다. ASP.NET가 컨트롤러 역할을 구현하지만 그 컨트롤러가 응답하는 이벤트에 작업을 연결하는 책임은 프로그래머에게 있기 때문입니다. 이 예에서는 컨트롤러가 Page_Load 함수를 호출하면 페이지가 로드됩니다. 그리고 사용자가 전송(Submit) 단추를 클릭하면 컨트롤러가 SubmitBtn_Click 기능을 호출합니다.

이 페이지는 아주 단순하며 독립적입니다. 이 구현은 유용하며, 응용 프로그램이 작고 자주 변경되지 않는 경우 사용하기 좋습니다. 하지만 개발 중 다음과 같은 상황이 발생하는 경우에는 이 방법을 변경하는 방안을 고려해 보아야 합니다.

  • 병렬 처리를 늘리고 오류 가능성을 줄이고자 할 경우. 뷰 코드와 모델 코드를 서로 다른 사람이 작업하여 병렬 처리 양을 늘리고 오류 발생 가능성을 제한하고자 할 수 있습니다. 예를 들어, 모든 코드가 한 페이지에 있는 경우 개발자는 DataGrid 서식을 변경하고 데이터베이스를 액세스하는 원본 코드 일부를 무심코 변경할 수 있습니다. 하지만 페이지는 다시 보기를 해야만 컴파일되므로 페이지를 다시 보기 전까지는 오류를 발견할 수 없습니다.

  • 여러 페이지에 데이터베이스 액세스 코드를 다시 사용하고자 할 경우. 이 구현에서는 코드를 중복하지 않고는 다른 페이지에 코드를 다시 사용할 수 있는 방법이 없습니다. 중복 코드를 사용하면 데이터베이스 코드가 변경될 경우 그 데이터베이스를 액세스하는 모든 페이지를 수정해야 하므로 유지 관리가 어렵습니다.

    이러한 문제를 처리하기 위해 ASP.NET 구현자들은 코드 비하인드 기능을 도입했습니다.

    코드 비하인드 리팩토링

    Microsoft Visual Studio .NET 개발 시스템의 코드 비하인드 기능을 사용하면 프레젠테이션(뷰) 코드를 모델 컨트롤러 코드에서 쉽게 분리할 수 있습니다. 개별 ASP.NET 페이지에는 그 페이지에서 호출된 메서드가 별도의 클래스에서 구현되도록 하는 메커니즘이 있습니다. 이 메커니즘은 Visual Studio .NET에 의해 활성화되며 Microsoft IntelliSense 기술과 같은 많은 이점이 있습니다. 코드 비하인드 기능을 사용하여 페이지를 구현할 때에는 IntelliSense를 사용하여 해당 페이지 뒤의 코드에서 사용하고 있는 개체의 사용 가능한 메서드 목록을 표시할 수 있습니다. IntelliSense는 .aspx 페이지에서는 작동되지 않습니다.

    다음은 동일한 예로, 이번에는 코드 비하인드 기능을 사용하여 ASP.NET을 구현합니다.

    이 프레젠테이션 코드는 이제 Solution.aspx라고 하는 별도의 파일에 들어 있습니다.

     
    
    <%@ Page language="c#" Codebehind="Solution.aspx.cs" 
       AutoEventWireup="false" Inherits="Solution" %>
    <html>
       <head>
          <title>Solution</title>
       </head>
       <body>
          <form id="Solution" method="post" runat="server">
             <h3>Recordings</h3>
             Select a Recording:<br/>
             <asp:dropdownlist id="recordingSelect" runat="server" />
             <asp:button id="submit" runat="server" text="Submit" 
                enableviewstate="False" />
             <p/>
             <asp:datagrid id="MyDataGrid" runat="server" width="700"
                   backcolor="#ccccff" bordercolor="black" showfooter="false"
                   cellpadding="3" cellspacing="0" font-name="Verdana" font-size="8pt"
                   headerstyle-backcolor="#aaaadd" enableviewstate="false" />
          </form>
       </body>
    </html>
     

    이 코드의 대부분은 첫 번째 구현에서 사용된 코드와 비슷하며 가장 큰 차이점은 첫 번째 줄입니다.

     
    
    <%@ Page language="c#" Codebehind="Solution.aspx.cs" 
       AutoEventWireup="false" Inherits="Solution" %>
     

    이 줄은 코드 비하인드 클래스가 이 페이지에서 참조되는 메서드를 구현하는 ASP.NET 환경을 나타냅니다. 이 페이지에는 데이터베이스를 액세스하는 코드가 없으므로, 데이터베이스가 코드 변경 사항을 액세스하더라도 이 페이지를 수정해야 할 필요가 없습니다. 사용자 인터페이스 디자인에 익숙한 사람은 데이터베이스 액세스 코드에 오류를 유발하지 않고도 이 코드를 수정할 수 있습니다.

    모델 컨트롤러

    이 솔루션의 두 번째 부분은 다음과 같은 코드 비하인드 페이지입니다.

     
    
    using System;
    using System.Data;
    using System.Data.SqlClient;
    
    public class Solution : System.Web.UI.Page
    {
       protected System.Web.UI.WebControls.Button submit;
       protected System.Web.UI.WebControls.DataGrid MyDataGrid;
       protected System.Web.UI.WebControls.DropDownList recordingSelect;
       
       private void Page_Load(object sender, System.EventArgs e)
       {
          if(!IsPostBack)
          {
             String selectCmd = "select * from Recording";
    
             SqlConnection myConnection = 
                new SqlConnection(
                   "server=(local);database=recordings;Trusted_Connection=yes");
             SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
    
             DataSet ds = new DataSet();
             myCommand.Fill(ds, "Recording");
    
             recordingSelect.DataSource = ds;
             recordingSelect.DataTextField = "title";
             recordingSelect.DataValueField = "id";
             recordingSelect.DataBind();
          }
       }
    
       void SubmitBtn_Click(Object sender, EventArgs e) 
       {   
          String selectCmd = 
             String.Format(
             "select * from Track where recordingId = {0} order by id",
             (string)recordingSelect.SelectedItem.Value);
    
          SqlConnection myConnection = 
             new SqlConnection(
                "server=(local);database=recordings;Trusted_Connection=yes");
          SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
    
          DataSet ds = new DataSet();
          myCommand.Fill(ds, "Track");
    
          MyDataGrid.DataSource = ds;
          MyDataGrid.DataBind();
       }
    
       #region Web Form Designer generated code
       override protected void OnInit(EventArgs e)
       {
          //
          // CODEGEN: This call is required by the ASP.NET Web Form Designer.
          //
          InitializeComponent();
          base.OnInit(e);
       }
          
       /// <summary>
       /// Required method for Designer support - do not modify
       /// the contents of this method with the code editor.
       /// </summary>
       private void InitializeComponent()
       {    
          this.submit.Click += new System.EventHandler(this.SubmitBtn_Click);
          this.Load += new System.EventHandler(this.Page_Load);
    
       }
       #endregion
    }
     

    이 코드는 단일 ASP.NET 페이지에서 자체 파일로 옮겨졌습니다. 이 두 엔터티를 하나로 연결하려면 몇몇 구문을 변경해야 합니다. 이 클래스에 정의된 구성원 변수는 Solution.aspx 파일에 참조된 이름과 동일한 이름을 공유합니다. 명시적으로 정의되어야 하는 또 다른 부분은, 이 컨트롤러가 반드시 실행되어야 하는 작업이 발생한 이벤트에 어떻게 연결하는가 입니다. 이 예에서는 InitializeComponent 메서드가 이 두 이벤트를 연결합니다. 첫 번째 이벤트는 Load로, Page_Load 함수로 연결됩니다. 그리고 두 번째 이벤트는 Click으로, 전송(Submit) 단추를 클릭하면 SubmitBtn_Click 함수가 실행되도록 만듭니다.

    코드 비하인드 기능은 뷰 역할을 모델 및 컨트롤러 역할에서 분리하는 훌륭한 메커니즘입니다. 코드 비하인드 기능은 코드 비하인드 클래스에 있는 코드를 다른 페이지에 재사용해야 하는 경우 불충분해질 수 있습니다. 코드 비하인드 페이지에서 코드를 재사용하는 것이 기술적으로는 가능하지만 코드 비하인드 클래스를 공유하는 모든 페이지의 결합이 증가하기 때문에 바람직하지 않습니다.

    MVC(Model-View-Controller) 리팩토링

    마지막 문제를 해결하기 위해서는 컨트롤러에서 모델 코드를 분리해야 합니다. 뷰 코드는 이전 구현에서 사용한 코드와 동일합니다.

    모델

    다음 코드 예는 모델을 표시한 것으로, 데이터베이스에만 의존합니다. 따라서 뷰 의존 코드(ASP.NET 종속성 코드)는 포함되어 있지 않습니다.

     
    
    using System;
    using System.Collections;
    using System.Data;
    using System.Data.SqlClient;
    
    public class DatabaseGateway
    {
       public static DataSet GetRecordings()
       {
          String selectCmd = "select * from Recording";
    
          SqlConnection myConnection = 
             new SqlConnection(
                "server=(local);database=recordings;Trusted_Connection=yes");
          SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
    
          DataSet ds = new DataSet();
          myCommand.Fill(ds, "Recording");
          return ds;
       }
    
       public static DataSet GetTracks(string recordingId)
       {
          String selectCmd = 
             String.Format(
             "select * from Track where recordingId = {0} order by id",
             recordingId);
    
          SqlConnection myConnection = 
             new SqlConnection(
                "server=(local);database=recordings;Trusted_Connection=yes");
          SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection);
    
          DataSet ds = new DataSet();
          myCommand.Fill(ds, "Track");
          return ds;
       }
     

    이제 이 파일이 데이터베이스에 의존하는 유일한 파일입니다. 이 클래스는 테이블 데이터 게이트웨이의 훌륭한 예입니다. 테이블 데이터 게이트웨이에는 단일 테이블 또는 뷰를 액세스하는 모든 SQL 코드(선택, 삽입, 업데이트, 삭제)가 있습니다. 다른 코드는 이 데이터베이스와의 모든 상호 작용을 위해 자체 메서드를 호출합니다. [Fowler03]

    컨트롤러

    이 리팩토링은 코드 비하인드 기능을 사용하여 그 페이지에 있는 데이터 제어에 모델 코드를 적용하고 컨트롤러가 전달하는 이벤트를 특정 작업 메서드로 매핑합니다. 이 모델은 여기에서 DataSet 개체를 반환하기 때문에 작업이 단순합니다. 이 코드는 뷰 코드와 마찬가지로 데이터베이스에서 데이터가 검색되는 방법에 의존하지 않습니다.

     
    
    using System;
    using System.Data;
    using System.Collections;
    using System.Web.UI.WebControls;
    
    public class Solution : System.Web.UI.Page
    {
       protected System.Web.UI.WebControls.Button submit;
       protected System.Web.UI.WebControls.DataGrid MyDataGrid;
       protected System.Web.UI.WebControls.DropDownList recordingSelect;
       
       private void Page_Load(object sender, System.EventArgs e)
       {
          if(!IsPostBack)
          {
             DataSet ds = DatabaseGateway.GetRecordings();
             recordingSelect.DataSource = ds;
             recordingSelect.DataTextField = "title";
             recordingSelect.DataValueField = "id";
             recordingSelect.DataBind();
          }
       }
    
       void SubmitBtn_Click(Object sender, EventArgs e) 
       {   
          DataSet ds = 
             DatabaseGateway.GetTracks(
             (string)recordingSelect.SelectedItem.Value);
    
          MyDataGrid.DataSource = ds;
          MyDataGrid.DataBind();
       }
    
       #region Web Form Designer generated code
       override protected void OnInit(EventArgs e)
       {
          //
          // CODEGEN: This call is required by the ASP.NET Web Form Designer.
          //
          InitializeComponent();
          base.OnInit(e);
       }
          
       /// <summary>
       /// Required method for Designer support - do not modify
       /// the contents of this method with the code editor.
       /// </summary>
       private void InitializeComponent()
       {    
          this.submit.Click += new System.EventHandler(this.SubmitBtn_Click);
          this.Load += new System.EventHandler(this.Page_Load);
    
       }
       #endregion
    }
     

    테스트

    ASP.NET 환경에서 모델을 분리하면 모델 코드를 테스트하기가 더 쉬워집니다. ASP.NET 환경 내에서 이 코드를 테스트하려면 이 프로세스의 출력을 테스트해야 합니다. 이는, HTML을 읽고 정확한지를 판단해야 하는 것을 의미하는데 그 작업은 아주 지루하고 오류가 발생하기 쉽습니다. 모델을 분리하여 ASP.NET 없이 실행할 수 있게 되면 이러한 지루한 작업을 피할 수 있으며 코드만 분리하여 테스트할 수 있습니다. 다음은 NUnit (http://nunit.org)의 모델 코드 샘플 단위 테스트입니다.

     
    
    using System;
    
    using NUnit.Framework;
    using System.Collections;
    using System.Data;
    using System.Data.SqlClient;
    
    [TestFixture]
    public class GatewayFixture
    {
       [Test]
       public void Tracks1234Query()
       {
    
          DataSet ds = DatabaseGateway.GetTracks("1234");
          Assertion.AssertEquals(10, ds.Tables["Track"].Rows.Count);
       }
    
       [Test]
       public void Tracks2345Query()
       {
          DataSet ds = DatabaseGateway.GetTracks("2345");
          Assertion.AssertEquals(3, ds.Tables["Track"].Rows.Count);
       }
    
       [Test]
       public void Recordings()
       {
          DataSet ds = DatabaseGateway.GetRecordings();
          Assertion.AssertEquals(4, ds.Tables["Recording"].Rows.Count);
    
          DataTable recording = ds.Tables["Recording"];
          Assertion.AssertEquals(4, recording.Rows.Count);
    
          DataRow firstRow = recording.Rows[0];
          string title = (string)firstRow["title"];
          Assertion.AssertEquals("Up", title.Trim());
       }
    }
      

    결과

    ASP.NET에서 MVC를 구현하면 다음과 같은 이점과 단점이 있습니다.

    장점

  • 종속성 감소.ASP.NET 페이지는 프로그래머가 페이지 내에서 메서드를 구현할 수 있도록 해줍니다. 단일 ASP.NET 페이지에서 볼 수 있는 것처럼 이 페이지는 프로토타입 및 작고 수명이 짧은 웹 응용 프로그램에 유용할 수 있습니다. 하지만 페이지가 복잡하고 페이지 간 코드 공유 필요성이 증가하면 코드의 일부를 분리하는 것이 더욱 유용합니다.

  • 코드 중복 감소. DatabaseGateway 클래스의 GetRecordingsGetTracks 메서드를 이제 다른 페이지에서도 사용할 수 있습니다. 따라서 메서드를 여러 뷰로 복사할 필요가 없습니다.

  • 작업 및 문제점 분리. ASP.NET 페이지를 수정하는 데 필요한 기술은 데이터베이스를 액세스하는 코드 작성에 필요한 기술과 다릅니다. 앞서 설명한 것처럼 뷰와 모델을 분리하면 각 분야의 전문가가 동시에 작업할 수 있습니다.

  • 기회의 최적화. 앞서 설명한 것처럼 책임을 특정 클래스로 분리하면 최적화를 위한 기회가 증가됩니다. 앞에서 설명한 예에서, 요청이 있을 때마다 데이터베이스에서 데이터가 로드됩니다. 특정 상황에서 데이터를 캐싱하는 것이 가능하므로 응용 프로그램의 전체적인 성능이 향상될 수 있습니다. 하지만 이는 코드를 분리하지 않으면 어렵거나 불가능합니다.

  • 테스트 가능. 뷰에서 모델을 격리하면 ASP.NET 환경 밖에서 모델을 테스트할 수 있습니다.

    단점

    추가 코드 및 복잡성. 앞에 제시된 예는 더 많은 파일과 코드를 추가하므로 세 역할 모두를 변경해야 할 경우 코드 유지 관리 비용이 높아집니다. 어떤 경우에는 한 파일을 변경하는 것이 변경 사항을 여러 파일로 분리하는 것보다 더 쉬울 수 있습니다. 이 추가 비용은 코드 분리 이유에 불리하게 작용합니다. 작은 응용 프로그램의 경우 이러한 비용을 들일 만한 가치가 없을 수도 있습니다.

    관련 패턴

    자세한 정보는 다음 관련 패턴을 참조하십시오.

  • 테이블 데이터 게이트웨이. 이 패턴은 데이터베이스 테이블에 대해 게이트웨이 역할을 하는 개체입니다. 한 인스턴스가 한 테이블의 모든 역할을 처리합니다. [Fowler03]

  • 바운드 데이터 제어. 이 패턴은 데이터 원본에 바인딩된 사용자 인터페이스 구성 요소로서 화면 또는 페이지에 렌더링할 수 있습니다.

    참고 자료

    [Fowler03] Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.

    Patterns Practices

  • Posted by 나비:D
    :
    현재 착수하고 있는 새로운 ASP.NET MVC프레임웍을 커버하는 블로그 시리즈의 1번째를 포스팅했다. 이 시리즈의 1번째글에서는 간단하게 전자거래의 제품 목록표시나 검색사이트를 구축했다. 그곳에서는 MVC뒤에 있는 고급레벨 개념을 커버하고 있어, ASP.NET MVC프로젝트를 아무것도 없는 상태에서 만들어 제품목록표시기능을 만들고 테스트하는 방법을 소개했다.

    오늘 블로그에서는 ASP.NET MVC 프레임웍의 URL 라우팅 아키텍쳐에 대해서 깊히 알아볼 것이고 응용프로그램으로 보다 고급 시나리오로 사용하는 방법을 몇가지 이야기하겠다.

    파트1의 간단한 리뷰
    이 시리즈의 파트1에서는 3개의 타입 URL을 공개하는 전자거래 사이트를 만들었다.

     URL 포멧  동작  URL 예
     /Products/Categories  모든 Product Categories를 검색  /Products/Categories
     /Products/List/Category  Category내 Products을 목록화  /Products/List/Beverages
     /Products/Detail/ProductID  특정 Product의 상세표시  /Products/Detail/34

    이런 URL을 아래와 같이 :ProductsController"클래스를 만들어 처리했다.

    step11.jpg

    위 클래스가 응용프로그램에 추가되면 ASP.NET MVC프레임웍은 컨트롤러에서 처리를 하는데 적절한 액션 메소드에 자동적으로 들어온 URL을 라우팅처리한다.

    오늘 블로그 글은 확실하게 이 URL 매핑이 어떻게 하는지 알아보는 것과 동시에 ASP.NET MVC프레임웍으로 활용할 수 있는 고급 시나리오를 알아보자. 또한, URL라우팅의 시나리오를 간단하게 테스트하는 방법도 소개하겠다.

    ASP.NET MVC URL 라우팅 시스템은 무엇을 실행하는가?
    ASP.NET MVC 프레임웍에서는 유연한 URL 라우팅 시스템이 있어 응용프로그램으로 URL매핑 규칙을 정의할 수 있다. 이 라우팅 시스템에는 주로 2가지 목적이 있다.

    1. 들어오는 URL을 응용프로그램에 맵 및 그런 루트의 결정에 의해 알맞은 컨트롤러와 액션메소드가 처리를 실행한다.
    2. 컨트롤러/액션(예로 폼의 포스트 <a href="">링크 AJAX  호출)의 콜백에 사용되는 외부로 향한 URL을 구축

    내부/외부로 향한 URL시나리오의 양쪽 모두를 처리하는 URL매핑규칙을 사용할 수 있게 되면, 응용프로그램 코드에 큰 유연성을 줄 수 있다. 응용프로그램의 URL구조를 다음에 변경하고 싶은 경우, 응용프로그램 레벨로 매핑규칙의 1식을 수정하는 것으로 처리할 수 있어, 컨트롤러 또는 뷰의 템플릿내에서 코드를 변경할 필요가 없다.

    기본 ASP.NET MVC URL 라우팅 규칙
    기본적으로 Visual Studio로 "ASP.NET MVC 웹응용프로그램" 템플릿을 사용하여 새 프로젝트를 만들 때, ASP.NET 응용프로그램 클래스를 프로젝트에 추가한다. 이것은 Global.asax의 코드비하인드에 포함된다.

    step1.jpg

    ASP.NET 응용프로그램 클래스에 의해 개발자는 응용프로그램 시작/종료, 및 글로벌 오류처리를 할 수 있게 된다.
    기본적으로 ASP.NET MVC 프로젝트의 템플릿은 자동적으로 Application_Start메소드를 클래스에 추가하여 2개의 URL 라우팅 규칙을 함께 등록한다.

    step2.jpg

    위 최초 라우팅 규칙은 어느 컨트롤러 클래스를 초기화하여 어느 액션 메소드를 실행시킬지를 결정할 때, ASP.NET MVC 프레임웍은 "[controller]/[action]/[id]"의 포멧을 사용하고 기본적으로 URL을 컨트롤러에 매핑해야하는 것인지를 나타낸다.

    이 기본 라우팅 규칙에 의해 파트1에 있던 전자거래 검색 예제 /Products/Detail/3에 대한 URL의 요청이 자동적으로 ProductsController클래스내에 Detail메소드를 호출시켜 ID메소드의 인수값으로서 3이 넘어온다.

    step10.jpg

    위 2번째 라이팅 규칙은 응용프로그램으로 특별한 경우인 루트의 Default.aspx(이것은 응용프로그램의 루트URL에 대한 요청을 처리할 때 "/"대신에 웹서버로부터 가끔보내진다) URL에 추가된다. 이 규칙에서는 응용프로그램의 루트 "/Default.aspx" 또는 "/"의 어느 쪽에 대한 요청도 "HomeController"클래스(Visual Studio가 자동적으로 새 응용프로그램을 "ASP.NET MVC 웹응용프로그램"템플릿을 사용하면 자동 추가된다) 위 "Index()" 엑션에 의해 처리도록 한다.

    루트 인스턴스 이해하기
    라우팅 규칙은 루트 인스턴스를 System.Web.Mvc.RouteTable의 Routes콜렉션에 추가하면 등록된다.
    Route클래스에서 맵핑규칙의 구성에 사용하는 수많은 속성이 정의되어 있고 속성은 "일반적인" .NET 2.0속성설정을 사용하여 설정할 수 있다.

    step4.jpg

    또한, 새로운 객체 초기화(object  initializer) 기능을 VS2008의 C# 및 VB컴파일러로 활용하여 보다 간결하게 속성을 설정할 수도 있다.

    step3.jpg

    Route클래스의 "Url"속성은 URl의 매칭 규칙을 정의한다. 이것은 루트 규칙이 특정 내부 요청에 적용할지를 평가하기 위해서 사용되는 것이다. 이것은 또한 인수에 대한 URL의 토큰화 메소드도 정의한다. URL로 변경하여 넣은 인수는 [ParamName]문장구조법을 사용하여 정의된다. 다음을 보면 "well known(잘알려진)"인수명에만 한전되는 일이 없이, URL로 사용하고 싶은 임의의 인수를 임의 수만큼 가질 수 있다. 예를 들어, 블로그 글에 대해서 들어온 URL을 토큰화하기 위해 "/Blogs/[Username]/Archive/[Year]/[Month]/[Day]/[Title]"의 Url규칙을 사용하여 자동적으로 MVC프레임웍에 UserName, Year, Month, Day, Title인수를 컨트롤러의 액션 메소드에 넘겨 처리되도록 한다.

    Route클래스의 "Defaults"속성에는 들어오는 URL에 특정 인수값이 1개도 포함되지 않았던 경우에 사용하는 기본값의 사전을 정의한다. 예를 들면, 위 URL매핑예로 2개의 기본 URL인수값, "[action]"과 "[id]"에 대해서 정의한다. 즉, /Products/에 대한 URL가 응용프로그램에 수신되었을 경우, 라우팅시스템은 ProductsController상에서 실행되는 기본 액션명으로 "Index"를 사용한다. 같은 /Products/List/"가 특정되었을 경우 "ID"인수에 대한 Null문자열값이 사용된다.

    Route클래스의 "RouterHandler"속성에서는 URL이 토큰화되어 사용되는 알맞은 라우팅 규칙이 결정된 후에 요청을 처리하기 위해 사용하는 IRouteHandler인스턴스를 정의한다, 이 여분의 스텝이 있는 이유는 URL 라우팅 시스템이 MVC와 MVC가 아닌 요청 모두에 대해서 확실하게 사용할 수 있도록 하기 위해서이다. 이 IRouteHandler인터페이스를 갖는다라고 하는 것은 MVC가 아닌 요청(예로 표준 웹폼, Astoria REST 지원등)에 대해서도 깔끔하게 사용할 수 있는 것이다.

    또한, Route 클래스에는 "Validation"속성이 있는데 이것은 나중에 따로 본다. 이 속성에는 미리 만든 프리컨디션 제품에 특정할 수 있다. 특정한 라우팅 규칙에 합치키기 위해 필요하게 된다. 예를 들면, 특정 HTTP verb(REST명령어에 의해 간단하게 매핑할 수 있음)에 대해서만 적용하는 라우팅 규칙을 나타내거나 라우팅 규칙에 대해서 필더를 하기 위해 인수에 정규식을 사용하거나 할 수 있다.

    주: 최초 공개 MVC 프리뷰에서는 Route클래스는 확장이 가능하지 않다. (데이터 클래스가 됨), 다음 프리뷰 릴리즈에서 이것을 확장가능하게 하고 개발자가 한층 더 시멘틱스한 기능을 깔끔하게 추가하기 위해 시나리오의 특정 루트클래스(예로 RestRoute하위클래스)를 추가할 수 있도록 하고 싶다.

    루트 규칙 평가
    들어오는 URL을 ASP.NET MVC 웹응용프로그램이 수신했을 때, MVC 프레임웍은 RouteTable.Routes컬렉션으로 라우팅규칙을 평가하여 그 요청을 처리하는 알맞은 컨트롤러를 결정한다.

    MVC 프레임웍은 등록된 순서로 RouteTable규칙을 평가하는 것으로 사용하는 컨트롤러를 선택한다. 들어오는 URL은 각 루트 규칙에 합치고 있는지 확인한다. 만약, 루트 규칙이 합쳐지는 경우, 그 규직(관련된 RouteHandler)가 요청을 처리하는데 사용된다. 즉, 일반적인 경우, "상세"순서로 라우팅 규칙을 구조화해 두면 좋다고 생각한다.

    라우팅 시나리오: 사용자지정 검색 URL
    몇개인지 사용자지정의 라우팅 규칙을 실제 시나리오로 사용해보자. 이것은 전자거래 사이트의 검색 기능이다.
    우선 새 SearchController클래스를 프로젝트에 추가한다.

    step5.jpg

    이후, SearchController클래스에서 2개의 액션메소드를 정의한다. Index()액션메소드는 사용자가 입력하여 검색대상을 송신하는데 사용하는 TextBox가 있는 검색페이지를 표시하기 위해 사용된다. Result()액션은 그곳으로부터 폼송신을 처리하여 데이터베이스에서 검색한 다음 그 결과를ㄹ 표시하기 위해 사용된다.

    step6.jpg

    기본적으로 /[controller]/[action]/[id] URL루트 매핑 규칙을 사용하여 아래와 같이 "설정이 끝난 상태"의 URL을 SearchController 액션 실해엥 사용할 수 있다. actions:

     시나리오  URL  액션 메소드
     검색폼  /Search/  Index
     검색결과  /Search/Results?query=Beverages  결과
       /Search/Results?query=ASP.NET  결과

    Index()액션 메소드에 대해 기본적으로 루트의 /Search URL이 맵핑되고 있는 이유는 Visual Studio가 새 프로젝트를 만들 떄("Defaults"속성을 통해) 컨트롤러에서 기본 액션으로서 "Index"를 설정했을 때 기본적으로 /[countroller]/[action]/[id] 루트 정의가 추가되었기 때문이다.

    step3.jpg

    URL이 /Search/Results?query=Beverages등의 경우 완벽하게 가능하지만, 조금 깔끔한 URL을 검색결과에 보여주고 싶다. 특히 Results액션명을 URL로부터 삭제하고 QueryString인수를 사용하는 대신에 URL일부로서 검색 쿼리를 보내고 싶은 경우 있을 것이라고 생각한다. 예를 들면,

     시나리오  URL  액션 메소드
     검색폼  /Search/  Index
     검색결과  /Search/Beverages  Results
       /Search/ASP.NET  Results

    아래와 같이, 기본 /[controller]/[action]/[id]규칙 전에 2개의 사용자지정 URL루트매핑 규칙을 추가하여 깔끔한 검색결과를 보일 수 있다.

    step7.jpg

    처음 2개의 규칙에서는 /Search/ URL에 대해서 컨트롤러나 액션인수를 현재 명시적으로 지정하고 있다. "/Search"는 항상 SearchController에서 "Index"액션으로 처리되는 것이 당연한 것으로 나타내고 있다. 현재 하위 URL 계층을 가지는 URL은 모두(/Serach/Foo나 /Serach/Bar 등), 항상 SearchController상에서 "Results"액션으로 처리되고 있다.

    위 2번째 라우팅 규칙은 /Search/프리픽스를 넘는 것은 모두 "[query]"로 불리는 인수로서 처리되어 SearchController의 Results액션에 메소드 인수로서 보내지는 것을 나타낸다.

    step8.jpg

    대부분의 경우(검색결과가 10이상 표시되는 경우), 검색결과를 페이지 번호를 나타내고 싶어한다고 생각한다. 이것은 쿼리 스트링 인수를 통해 할지 또는 옵션으로서 URL일부에 페이지번호(/Search/Beverages/2)를 처리한다. 이것은 다음 옵션으로서 지원하기 위해 추가 인수를 2번째 라우팅 규칙에 추가해둔다. :

    step9.jpg

    위에서 확인할 수 있는 것으로 새 URL 규칙을 합쳐 현재 "Search/[query]/[page]"가 되어 잇다. 또한, 기본페이지번호는 URL에 포함되지 않도록 하는 경우를 위해 1로 설정한다. (이것은 "Defaults"속성으로서 처리된 익명형을 통해서)
    이후, SearchController.Results액션 메소드를 업데이트하고 이 페이지 인수를 메소드 인수로서 받는다.

    step10.jpg

    이렇게 하여 사이트에 대해서 깔끔한 URL로 검색할 수 있다.

    라우팅 규칙에 대한 전체컨디션 평가
    이 글에 대해서는 앞에서도 말했지만, Route클래스는 "Validation"속성을 가지고 있어 루트규칙이 합쳐지면 True가 되지 않으면 안되는 전제조건 규칙을 검증할 수 있도록 추가되었다. ASP.NET MVC 프레임웍은 정규식을 사용하여 URL의 각 인수를 평가할 수 있어 HTTP헤더 평가리를 하는 일도 할 수 있다.

    이하는 사용자지정 평가규칙으로 "/Products/Detail/43"등과 같은 URL에 대한 유효화를 할 수 있다. 이는 ID인수가 수치로 1-8문자가 아니면 안되는 설정이다.

    step11.jpg

    응용프로그램에 /Products/Detail/12와 같은 URL로 처리할 경우, 위 라우팅 규칙은 유효하게 되지만, /Products/Detail/abc 또는 /Products/Detail/2323232323과 합치지 않는다.

    라우팅 시스켐으로부터 밖으로 향한 URL구축
    이 글의 앞에서 말했지만, ASP.NET MVC 프레임웍의 URL 라우팅 시스템은 2개를 가지고 있다.

    1. 처리하기 위해 들어온 URL을 Controllers/Actrions에 매핑
    2. Controllers/Actions에 다음 콜백에 사용가능성이 외부로 향한 URL구축을 지원(예를 들면, 폼, <a href="">링크, AJAX호출)

    URL의 라우팅시스템에는 많은 헬퍼 메소드나 클래스가 있어 이를 간단하게 실행시 URL을 동적으로 검색 및 구축할 수 있다.(RouteTable Route컬렉션을 집적조작하는 것으로 URL을 검색할 수 있다)

    Html.ActionLink
    이 시리즈의 파트1에서 간단하게 Html.ActionLink() 뷰 헬퍼 메소드에 대해서 말했지만, 그것은 뷰내에서 사용할 수 있어 동적으로 <a href="">하이퍼링크를 생성할 수 있다. 장점은 MVC라우팅 시스템으로 정의된 URL매핑 규칙을 사용하여 이런 URL을 생성한다. 예를 들면 아래  2개의 Html.ActionLink를 호출한:

    step12.jpg

    자동적으로 이 글 앞쪽에서 구성한 특정 Search결과의 루트 규칙과 이것을 반양하여 자동적으로 생성한 "href"속성을 선택한다.

    step13.jpg

    특히, 어떻게 Html.ActionLink에 대한 2번째 호출이 자동적ㅇ로 "page"인수를 URL일부로서 매핑되어 있지 않을 까 확인하세요. (또한, 서버사이트에서 기본값이 제공되는 것을 알았기 때문에, 1번째 호출로 페이지 인수값이 생략되는 모습을 확인한다)

    Url.Action
    Html.ActionLink의 사용을 추가형 ASP.NET MVC에는 Url.Action()뷰 헬퍼 메소드도 있다. 이것은 문자열 URL을 생성한다. 이것을 사용하고 싶다면 아래와 같이 코드 스닙펫:

    step14.jpg

    URL 라우팅 시스템을 사용하고 아래의 URL을 리턴한다.(<a href="">요소에는 랩핑되어 있지 않다)

    step15.jpg

    Controller.RedirectToAction
    ASP.NET MVC는 Controller.RedirectToAction() 헬퍼 메소드를 지원하고 있어, 그것은 컨트롤러에서 사용하고 (URL이 URL 라우팅 시스템을 사용하여 계산되었을 경우) 리다이렉트를 실행할 수 있다.
    예를 들면, 아래 코드가 컨트롤러내에서 실행되었을 때,

    step16.jpg

    내부에서 Response.Redirect("/Search/Beverages") 호출을 생성한다.

    DRY
    위 모든 헬퍼메소드는 컨트롤러나 뷰의 논리내에서 URL경로에 하드코드할 필요가 없는 일이다. 만약, 다음에 "/Search/[query]/[page]"로부터 "/Search/Results/[query]/[page]" 또는 /Search/Results?query=[query]&page=[page]"에 검색 URL의 루트 매핑 규칙을 변경하려고 결정하면, 1개의 장소(루트 등록코드)에서 편집하는 것으로 간단하게 할 수 있다. 새로운 URL(이것은 "DRY principle"을 보관유지한다)를 선택하기 위해 뷰 또는 컨트롤러에서 코드를 변경할 필요가 없다.

    라우팅 시스템으로부터 외부로 나가는 URL을 구축(Lambda식 사용)
    전에 URL헬퍼 예는 VS2008으로 지원되고 있는 VB와 C#에 있는 새로운 익명형을 활용하고 있다. 위 에에서는 익명형을 사용하여 효과적으로 일련의 이름과 값이 맞추어 URL의 매핑을 보조하고 있다. (이것을 사전으로 깔끔하게 생성하는 방법이라고 생각할 수 있다)

    익명형을 사용한 동적인 방법으로 인수를 보내는 것을 추가하여 ASP.NET MVC 프레임웍은 강력한 메커니즘을 사용하여 액션루트를 만드는 기능도 지원하고 있어 이를 컴파일시 체크와 URL헬더에 대한 인텔리센스가 제공된다. Generic형과 Lambda식에 대한 새로운 VB와 C#이 지원하고 있어 이를 사용한다.

    예를 들면 아래의 익명형을 ActionLink 호출:

    step17.jpg

    또는 다음과 같이 쓸 수 있다.

    step18.jpg

    조금 간결하게 쓸 수 있는 것을 추가형 2번째 옵션을 형 세이프인 장점을 가진다. 즉, 식의 컴파일시 체크와 Visual Studio 코드 인텔리 센스(리펙토링도 사용할 수 있다)를 사용할 수 있다.

    step19.jpg

    위에서 SearchController에서 액션 메소드를 선택하기 위해서 인텔리센스를 어떻게 사용하는지 인수가 어떻게 강한 형이 되는지 확인한다. 생성된 URL은 모두 ASP.NET MVC URL라이팅 시스템으로부터 없앤다.

    이것은 어떻게 실행되는지? 생각할지도 모른다. 8개월전 Lambda식에 대한 글을 포스팅했지만,  거기서는 코드 Delegate 및 Lambda식을 분석하기 위한 실행시 사용가능한 expression tree object의 어딘가에 Lambda식을 컴파일할 수 있는 것에 대해 이야기햇다. Html.ActionLink<T> 헬퍼 메소드로 이 expression tree object을 사용하여 실행시 Lambda식을 분석하여 실행시키는 액션 메소드와  식안에 특정 인수형태, 이름, 값을 검색한다. 이것들을 MVC url라우팅 시스템으로 사용하여 적절한 URL과 관련된 HTML을 리턴할 수 있다.

    중요: 이 Lambda식을 사용할 때, 컨트롤러 액션은 전혀 실행하지 않는다. 예를 들면 아래 코드는 SearchController에서 "Results"액션 메소드를 실행하지 않는다.

    step18.jpg

    이 HTML의 하이퍼링크를 리턴하는 대신에

    step20.jpg

    최종 사용자가 이 하이퍼링크를 클릭했을 때, HTTP요청이 서버에 보내져 SearchController의 Results액션메소드를 실행시킨다.

    루트의 전체 테스트
    ASP.NET MVC 프레임웍의 코어 설계의 원칙하나가 테스트를 많이 지원할 수 있도록 하는 것이다. 나머지 MVC프레임웍과 같이 쉽게 루트나 루트를 합치는 규칙의 전체 테스트를 할 수 있다. MVC의 라우팅 시스템은 ASP.NET으로부터 도립하여 초기화하여 실행할 수 있다. 즉, 어떤 전체 테스트 라이브러리(웹서버를 시작할 필요가 없다) 안에서도 어떤 전체 테스트 프레임웍(NUint, MBUnit, MSTest등)을 사용해도 루트패턴을 읽어 전체테스트할 수 있다.

    여러개의 전체 테스트 중에서 직접 ASP.NET MVC응용프로그램의 글로벌 RouteTable매핑 콜렉션을 전체 테스트할 수 있지만, 일반적으로 전체 테스트를 변경하거나 글로벌 상황에 의지하는 것은 좋은 생각은 아니다. 좋은 패턴은 아래와 같이 RegisterRoutes() 헬퍼 메소드에 루트 등록 놀리를 구조화하는 것이다. 이것은 인수로서 보내지는 RouteCollection에 대해서 실행된다. (주: 아마 이것을 다음 프리뷰 업데이트에서 기본 VS템플릿패턴으로 할 것으로 생각된다)

    step21.jpg

    고유의 RouteCollection인스턴스를 만들어 루트 규칙을 응용프로그램안에서 등록하기 위해 응용프로그램의 RegisterRoutes()헬퍼를 호출하는 전체 테스트를 쓸 수 있다. 그러면, 응용프로그램에 대한 요청을 레이트하고 그것들에 대한 올바른 컨트롤러나 액션이 등록되어 있는지 어떤 문제가 있을지 걱정하는 일없이 확인할 수 있다.

    step22.jpg

    정리
    이번 글에 의해 ASP.NET MVC 라우팅 아키텍쳐의 동작과 이를 사용하여 ASP.NET MVC응용프로그램내 발생하는 URL구조나 레이아웃을 사용자지정으로하는 방법에 대해서 상세히 다루어서 다행이다.

    기본적인 새 ASP.NET MVC 웹응용프로그램을 만들 때, 기본적으로 /[constroller]/[action]/[id] 라우팅 규칙이 정의된다. 이것에 의해 스스로 고유의 사용자지정으로 라우팅 규칙을 등록할 필요없이 많은 응용프로그램을 구축할 수 있게 되지만, 앞에서 소개한 것에 의해 URL포멧을 사용자지정으로 하고 싶은 경우 간단하게 만들수 있어 이것을 실행할 때 MVC프레임웍이 큰 파워와 유연성을 줄 수 있어 기쁘게 생각한다.

    Hope this helps.
    Scott
    Posted by 나비:D
    :

    [SOAP 모니터링 사용]


    TCP Monitor(윈도APP)

    D:/work>java org.apache.axis.utils.tcpmon

    위와 같이 하면 자바 위도APP가 실행되는데, 여기서 로컬포트를 지정하는 곳이 있다.

    일종의 터널링이라 할 수 있는데, 클라이언트가 이 포트를 통해 요청하면 프락시 역활을 하여 서버에 요청하고 그 결과를 다시 클라이언트에 전송한다.

    이 과정에서 TCP Monitor는 클라이언트/서버간 주고 받는 메시지를 파악하게 되는 것이다.

    사용자가 할 일은 기존 클라이언트가 서비스를 제공받기 위해 접속하던 주소를 변경해주면 된다.

    즉, 아래와 같이 해주면 된다.


    //public final static String ENDPOINT = http://127.0.0.1:8080/FOO/services/MyService;

    :원래 접속할 서버

    public final static String ENDPONT = "http://127.0.0.1:1234/FOO/services/MyService;

    :TCP Monitor용 연결


    처음 실행 화면, 여기서 리스터 포트는 1234로 설정하고 추가 버튼을 클릭한다.

    사용자 삽입 이미지
     

    로컬의 1234포트로 클라이언트의 연결을 기다리고 있다.

    사용자 삽입 이미지


    클라이언트가 접속한 예.

    사용자 삽입 이미지


    [SOAP Monitor(애플릿)]

    http://localhost:8080/axis/SOAPMonitor


    [SOAP Monitor 활성화하기]

    활성화하리 위해 2과정이 필요하다


    1. SOAPMonitorApplet.java 애플릿 컴파일

    2. deploy-monitor.wsdd를 통한 SOAP Monitor deploy


    1.SOAPMonitorApplet.java 애플릿 컴파일

    E:/>type d:/util/axis.bat

    @echo off

    set AXIS_HOME=D:util.axis-1_4

    set AXIS_LIB=%AXIS_HOME%/lib

    set AXISCLASSPATH=%AXIS_LIB%/axis.jar;%AXIS_LIB%/jaxrpc.jar;%AXIS_LIB%/saaj.jar;

    %AXIS_LIB%/log4j-1.2.8.jar;%AXIS_LIB%/commons=discovery-0.2.jar;%AXIS_LIB%/commons-logging-1.0.4.jar;%AXIS_LIB%/wsdl4j-1.5.1.jar

    set CLASSPATH=%CLASSPATH%;%AXISCLASSPATH%

    E:/>axis.bat

    E:/>cd "%CATALINA_HOME%/webapps/axis"

    E:/>Program Files/Apache Software Foundation/Tomcat 5.5/webapps/axis>javac SOAPMonitorApplet.java

    Note : SOAPMonitorApplet.java.uses or overrides a deprecated API.

    Note : Recompile with -Xlint : deprecation for details.

    Note : SOAPMonitorApplet.java uses unchecked or unsafe operations.

    Note : Recompile with -Xlint : unchecked for details.


    2. deploy-monitor.wsdd를 통한 SOAP Monitor deploy

    D:/work>java org.apache.axis.client.AdminClient deploy-monitor.wsdd

    log4j:WARN No appenders coulde be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    Processing file deploy-monitor.wsdd

    <Admin>Done processing</Admin>


    [deploy-monitor.wsdd]

    <deployment xmlns=http://xml.apache.org/axis/wsdd/

                       xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

        <handler name="soapmonitor"

                     type="java:org.apache.axis.handlers.SOAPMonitorHandler">

            <parameter name="wsdlURL"

                             value="/aixs/SOAPMonitorService-impl.wsdl" />

            <parameter name="namespace"

                             value=http://tempuri.org/wsdl/2001/12/SOAPMonitorService-impl.wsdl />

            <parameter name="serviceName" value="SOAPMonitorService" />

            <parameter name="portName" value="Demo" />

        </handler>

        <service name="SOAPMonitorService" provider="java:RPC">

            <parameter name="allowedMethods" value="publishMessage" />

            <parameter name="className" value="org.apache.axis.monitor.SOAPMonitorService" />

            <parameter name="scope" value="Application" />

        </service>

    </deployment>


    [브라우저에서 SOAP 모니터를 실행]

    사용자 삽입 이미지


    [SOAP Monitor 사용예]

    deploy.wsdd작성시 requestFlow, responseFlow를 추가해주면 해당 웹서비스가 주고받는 SOAP메시지를 실시간으로 볼 수 있다.

    개발과정에서 아주 요긴하게 사용될 수 있다.

    2회때 사용한 예제에서 deploy.wsdd파일에 다음을 추가해주기만 하면된다.

    [deploy,wsdd]

    <deployment xmlns=http://xml.apache.org.axis.wsdd/ xmlns:java="http://xml.apache.org.axis/wsdd/providers/java">

        <service name="MyService" provider="java:RPC">

            <parameter name="className" value="MyService" />

            <parameter name="allowMethods" value="*">

            <requestFlow><handler type="soapmonitor" /></requestFlow>

            <responseFlow><handler type="soapmonitor" /></responseFlow>

        </service>

    </deployment>


    D:/work>java Client "안녕, 세상아!"

    log4j:WARN No appenders coulde be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    You typed : 안녕, 세상아!

    D:work>


    브라우저에 모니터링된 SOAP,메시지는 그림과 같다.

     

    사용자 삽입 이미지
    Posted by 나비:D
    :

    [메시지 스타일 서비스]

    자바코드로의 매핑을 빼고 XML데이터 자체를 주고받는 서비스다.


    D:/work>type MessageService.java

    import org.w3c.dom.Element;

    import javax.xml.soap.*;

    public class MessageService{

        public Element[] echoElements(Element[] elems){

            return elems;

        }

        public void Process(SOAPEnvelope req, SOAPEnvelope res) throws javax.xml.soap.SOAPException{

            SOAPBody body = res.getBody();

            Name ns0 = res.createName("TestNS0", "ns0", http://aa.com);

            Name ns1 = res.createName("TestNS1", "ns1", http://aa.com);

            SOAPElement bodyElement = body.addBodyElement(ns0);

            SOAPElement el = bodyElement.addChildElement(ns1);

            el.addTextNode("TEST RESPONSE");

        }

    }

    D:/work>javac MessageService.java

    D:/work>copy MessageService.class "%CATALINA_HOME%/webapps/axis/WEB-INF/classes"

    1개 파일이 복사되었습니다.

    D:/work>type deploy.wsdd

    <deployment name="test" xmlns=http://xml.apache.org/axis/wsdd/

                       xmlns:java=http://xml.apache.org/axis/wsdd/providers/java

                       xmlns:xsi="http://www.w3c.org/2000/10/XMLSchema-instance">

    //주석 : note that either style="message" OR provider="java:MSG" both work

        <service name="MessageService" style="message">

            <parameter name="className" value="MessageService" />

            <parameter name="allowedMethods" value="echoElements" />

        </service>

        <service name="MessageService2" style="message">

            <parameter name="className" value="MessageService" />

            <parameter name="allowedMethods" value="process" />

        </service>

    </deployment>

    D:/work>type undeploy.wsdd

    <undeploytment name="test" xmlns="http://xml.apache.org/axis/wsdd/">

        <service name="MessageService" />

    </undeployment>

    D:/work>java org.apache.axis.client.AdminClient deploy.wsdd

    log4j : WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j : WARN Please initialize the log4j system property.

    Processing file deploy.wsdd

    <Admin>Done processing</Admin>

    D:/work>java org.apache.axis.client.AdminClient list

    D:/work>jaba org.apache.axis.client.AdminClient list

    log4j : WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j : WARN Please initialize the log4j system property.

    <ns1:deployment xmlns=http://xml.apache.org/axis/wsdd/

                             xmlns:java=http://xml.apache.org/axis/wsdd/providers/java

                             xmlns:ns1="http://xml.apache.org/axis/wsdd/">

        <ns1:globalConfiguration>

            <ns1:parameter name="sendMultiRefs" value="true" />

            <ns1:parameter name="disablePrettyXML" value="true" />

            <ns1:parameter name="adminPassword" value="admin" />

            <ns1:parameter name="attachments.Directory" value="E:/Program Files/Apache Software Foundation.Tomcat5.5/webapps/axis/WEB-INF/arrachments" />

            <ns1:parameter name="dotNetSoapEncFix" value="true" />

            <ns1:parameter name="enableNamespacePrefixOptimization" value="false" />

            <ns1:parameter name="sendXsiTypes" value="true" />

            <ns1:parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl" />

            <ns1:requestFlow>

                <ns1:handler type="java:org.apache.axis.handlers.JWSHandler">

                    <ns1:parameter name="scope" value="session" />

                </ns1:handler>

                <ns1:handler type="java:org.apache.axis.handlers.JWSHandler">

                    <ns1:parameter name="scope" value="request" />

                    <ns1:parameter name="extension" value=".jwr" />

                </ns1:handler>

            </ns1:requestFlow>

        </ns1:globalConfiguration>

        <ns1:handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder" />

        <ns1:handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper" />

        <ns1:handler name="Authenticate" type"java:org.apache.axis.handlers.SimpleAuthenticationHAndler" />

        <ns1:service name="MessageService2" provider="java:MSG" style="message" use="literal">

            <ns1:parameter name="allowedMethods" value="process" />

            <ns1:parameter name="className" value="MessageService" />

            <ns1:parameter name="sendXsiTypes" value="false" />

            <ns1:parameter name="sendMultiRefs" value="false" />

        </ns1:service>

        <ns1:service name="MessageService" provider="java:MSG" style="message" use="literal">

            <ns1:parameter name="allowedMethods" value="echoElements" />

            <ns1:parameter name="className" value="MessageService" />

            <ns1:parameter name="sendXsiTypes" value="false" />

            <ns1:parameter name="sendMultiRefs" value="false" />

        </ns1:service>

        <ns1:service name="AdminService" provider="java:MSG">

            <ns1:parameter name="allowedMethods" value="AdminService" />

            <ns1:parameter name="enableRemoteAdmin" value="false" />

            <ns1:parameter name="className" value="org.apache.axis.utils.Admin" />

            <ns1:namespace>http://xml.apache.org/axis/wsdd/</ns1:namespace>

        </ns1:service>

        <ns1:service name="Version" provider="java:RPC">

            <ns1:parameter name="allowedMethods" value="getVersion" />

            <ns1:parameter name="className" value="org.apache.axis.Version" />

        </ns1:service>

        <ns1:transport name="http">

            <ns1:requestFlow>

                <ns1:handler type="URLMapper" />

                <ns1:handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler" />

            </ns1:requestFlow>

            <ns1:parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler" />

            <ns1:parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler" />

            <ns1:parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler" />

            <ns1:parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler" />

            <ns1:parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler" />

            <ns1:parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler" />

        </ns1:transport>

        <ns1:transport name="local">

            <ns1:responseFlow>

                <ns1:handler type="LocalResponder" />

            </ns1:responseFlow>

        </ns1:transport>

    </ns1:deployment>


    [클라이언트로 서비스 테스트]

    D:/work>javac TestMsg.java

    D:/work>java TestMsg

    log4j : WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j : WARN Please initialize the log4j system property.

    D:/work>type TestMsg.java


    import org.apache.axis.client.Call;

    import org.apache.axis.client.Service;

    import org.apache.axis.message.SOAPBodyElement;

    import org.apache.axis.utils.Options;

    import org.apache.axis.Utils.XMLUtils;

    import org.w3c.dom.CDATASection;

    import org.w3c.dom.Document;

    import org.w3c.dom.Element;

    import javax.xml.parsers.DocumentBuilder;

    import javax.xml.parsers.DocumentBuilderFactory;

    import javax.xml.soap.MessageFactory;

    import javax.xml.soap.MimeHeaders;

    import javax.xml.soap.SOAPConnection;

    import javax.xml.soap.SOAPConnectionFactory;

    import javax.xml.soap.SOAPEnvelope;

    import javax.xml.soap.SOAPMessage;

    import javax.xml.soap.SOAPPart;

    import java.io.ByteArrayInputStream;

    import java.net.URL;

    import java.util.Vector;


    //주석 : Simple test driver for our message service.


    public class TestMsg{

        public String doit(String[] args) throws Exception{

            Optionsopts = new Options(args);

            opts.setDefaultURL(http://localhost:8080/axis/service/MessageService);

            Service service = new Service();

            Call call = (Call) service.createCall();

            call.setTargetEndpointAddress(new URL(opts.getURL()));

            SOAPBodyElement[] input = new SOAPBodyElement[3];

            input[0] = new SOAPBodyElement(XMLUtils.,StringToElement("urn:foo", "e1", "Hello"));

            input[1] = new SOAPBodyElement(XMLUtils.,StringToElement("urn:foo", "e1", "World"));

            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocunebtBuilder();

            Document doc = builder.newDocument();

            Element cdataElem = doc.createElementNS("urn:foo", "e3");

            CDATASection cdata = doc.createCDATASection("Text with/n/t Important <b> witespace</b> and tags!");

            cdataElem.appendChild(cdata);

            input[2] = new SOAPBodyElement(cdataElem);

            Vector elems = (Cevtor)call.invoke(input);

            SOAPBodyElement elem = null;

            Element e = null;

            elem = (SOAPBodyElement) elems.get(0);

            e = elem.getAsDOM();

            String srt = "Reselem[0]=" + XMLUtils.ElementToString(e);

            elem = (SOAPBodyElement) elems.get(1);

            e = elem.getAsDOM();

            str = str + "Reselem[1]=" + XMLUtils.ElementToString(e);

            elem = (SOAPBodyElement) elems.get(2);

            e = elem.getAsDOM();

            str = str + "Reselem[2]=" + XMLUtils.ElementToString(e);

            return(str);

        }

        public void testEnvelope(String[] args) throws Exception{

            String xmlString =

             "<?xml version=/"1.0/" encoding=/"UTF-8/"?>/n"+

             "<soapenv:Envelope xmlns:soapenv=/"http://schemas.xmlsoap.org/soap/envelope//"/n+

             "xmlns:xsd=/"http://www.w3.org/2001/XMLSchema/"/n"+

             "xmlns:xsi=/"http://www.w3.org/2001/XMLSchema-instance/">/n"+

             "<soapenv:Header>/n"+

             "<shw:Hello xmlns:shw=/"http://localhost:8080/axis/services/MessageService/">/n"+

             "<shw:Myname>Tony</shw:Myname>/n"+

             "</shw:Hello>/n"+

             "</soapenv:Header>/n"+

             "<soapenv:Body>/n"+

             "<shw:process xmlns:shw=/"http://message.samples/">/n"+
             "<shw:City>GENT</shw:City>/n"+
             "</shw:process>/n" +
             "</soapenv:Body>/n"+
             "</soapenv:Envelope>/n"+
             MessageFactory mf = MessageFactory.newInstance();
             SOAPMessage smsg =
    mf.createMessage(new MimeHeaders(), new ByteArrayInputStream(xmlString.getBytes()));
             SOAPPart sp = smsg.getSOAPPart();
             SOAPEnvelope se = (SOAPEnvelope)sp.getEnvelope();
             SOAPConnection conn = SOAPConnectionFactory.newInstance().createConnection();        
             SOAPMessage response = conn.call(smsg, http://localhost:8080/axis/services/MessageService2);
        }
        public static void main(String[] args) throws Exception{
            TestMsg testMsg = new TestMsg();
            testMsg.doit(args);
            testMsg.testEnvelope(args);
        }
    }

    Posted by 나비:D
    :

    [WSDD를 이용한 웹서비스 개발]

    D:work>type MyService.java

    public class MyService{

        public String serviceMEthod(String arg){

            return arg;

        }

    }

    D:/work>type deploy.wsdd

    <deplyment xmlns=http://xml.apache.org/axis/wsdd/ xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

        <service name="MyService" provider="java:RPC">

            <parameter name="className" value="MyService"/>

            <parameter name="allowMethods" value="*"/>

        </service>

    </deployment>


    D:work>type undeploy.wsdd

    <undeployment xmlns="htt://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

        <service name="MyService" />

    </undeployment>


    [톰캣 시동후, 자것ㅇ한 웹서비스를 deploy]

    D:/work>set AXIS_HOME=D:util/axis-1_4

    D:/work>set AXIS_LIB=%AXIS_HOME%/lib

    D:/work>setAXISCLASSPATH=%AXIS_LIB%/axis.jar;%AXIS_LIB%/jaxrpc.jar;%AXIS_LIB%/saaj.jar;%AXIS_LIB%/log4j-1.2.8.jar;%AXIS_LIB%/commons-discovery-0.2.jar;%AXIS_LIB%/commons-logging-1.0.4.jar;%AXIS_LIB%/wsdl4j-1.5.1.jar

    D:/work>set CLASSPATH=%CLASSPATH%;%AXISCLASSPATH%

    D:/work>javac MyService.java

    D:/work>copy MyService.class "%CATALINA_HOME%/webapps/axis/WEB-INF.classes"

    1개 파일이 복사되었습니다.


    AdminClient를 이용해 웹서비스를 deploy

    D:/work>java org.apache.axis.client.AdminClient deploy.wsdd

    log4j:WARN No appenders could ve found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    Processing file deploy.wsdd

    <Admin>Done processing</Admin>

    D:/wrok>


    *undeploy하려면 다음과 같다.

    D:/work>java org.apache.axis.client.AdminClient undeploy.wsdd

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN please initialize the log4j system property.

    Processing file undeploy.wsdd

    <Admin>Done processing<Admin>


    [클라이언트 접속 테스트]

    D:/work>type Client.java

    import org.apache.axis.client.Call;

    import org.apache.axis.client.Service;

    import org.apache.axis.encoding.XMLType;

    import org.apache.axis.utile.Options;

    import javax.xml.namespace.QName;

    import javax.xml.rpc.ParameterMode;

    public class Client{

        public static void main(String[] args){

            try{

                Options options = new Options(args);

                String endpointURL = http://localhost:8080/axis/services/MyService;

                String textToSend;

                args = options.getRemainingArgs();

                if((args == null) || (args.length < 1)){

                    textToSend = "<nothing>";

                }else{

                    textToSend = args[0];

                }

                Service service = new Service();

                Call call = (Call) service.createCall();

                call.setTargetEndpointAddress(new java.net.URL(endpointURL));

                call.setOperationName(new QName("ServiceMethod"));

                call.addParameter("arg1", XMLType.XSD_STRING, ParameterMode.IN);

                call.setReturnType(org.apache.axis.encoding.XMLType.XSD_STRING);

                String ret = (String) call.invoke(new Object[] {textTosend} );

                System.out.println("You typed : " +ret);

            } catch(Exception e){

                System.err.println(e.toString());

            }

        }

    }


    D:/wrok>javac Client.java

    D:/work>java Client "안녕, 세상아!"

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.projectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    You typed : 안녕, 세상아!



    * 웹서비스 deploy되었는지 확인하려면 AdminClient의 list 명령을 사용하면 된다.

    이 명령으로 출력된 결과는 AXIS의 설정파일인 WEB-INF/server-config.xml의 내용이다.

    이 파일은 명시적으로 생성하지 않으면 axis가 시작할 때 자동으로 만들게 된다.


    D:/work>java org.apache.axis.client.AdminClient list

    D:/work>java org.apache.axis.client.AdminClient deploy.wsdd

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    Processing file deploy.wsdd

    <Admin>Done processing</Admin>


    D:/work>java org.apache.axis.client.AdminClient list

    log4j:WARN No appenders could ve found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.


    <ns1:deployment xmlns="http://xml.apache.org/axis.wsdd/" xmlns:java="htt://xml.apache.org/axis/wsdd/providers/java" xmlns:ns1="http://xml.apache.org/axis/wsdd/">

        <ns1:globalConfiguration>

            <ns1:parameter name="sendMultiRefs" value="true" />

            <ns1:parameter name="disablePrettyXML" value="true" />

            <ns1:parameter name="adminPassword" value="admin" />

            <ns1:parameter name="attachements.Directory" value="E:/Program Files/Apache Software Foundation.Tomcat 5.5/webapps/axis/WEB-INF/attachements" />

            <ns1:parameter name="dotNetSoapEncFix" value="true" />

            <ns1:parameter name="enableNamespacePrefixOptimization" value="false" />

            <ns1:parameter name="sendXMLDeclaration" value="true" />

            <ns1:parameter name="sendMultiRefs" value="true" />

            <ns1:parameter name="disablePrettyXML" value="true" />

            <ns1:parameter name="arrachments.implementation" value="org.apache.axis.attachments.ArrachmentsImpl" />

            <ns1:parameter name="sendXsiTypes" value="true" />

            <ns1:requestFlow>

                <ns1:handler type="java:org.apache.axis.handlers.JWSHandler">

                    <ns1:parameter name="scope" value="session" />

                </ns1:handler>

                <ns1:handler type="java:org.apache.axis.handlers.JWSHandler">

                    <ns1:parameter name="scope" value="request" />

                    <ns1:parameter name="extension" value=".jwr" />

                </ns1:handler>

            </ns1:requestFlow>

        </ns1:globalConfiguration>

        <ns1:handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder" />

        <ns1:handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper" />

        <ns1:handler name="Authenticate" type="java:org.apache.axis.handlers.SimploAuthenticationHandler" />

        <ns1:service name="AdminService" provider="java:MSG">

            <ns1:parameter name="allowedMethods" value="AdminService" />

            <ns1:parameter name="enableRemoteAdmin value="false" />

            <ns1:parameter name="className" value="org.apache.axis.utiles.Admin"/>

            <ns1:namesapce>http://xml.apache.org/axis/wsdd/</ns1:namespace>

        </ns1:service>

        <ns1:service name="Version" provider="java:RPC">

            <ns1:parameter name="allowedMEthods" value="getVersion" />

            <ns1:parameter name="className" value="org.apache.axis.Version" />

        </ns1:service>

        <ns1:service name="MyService" provider="java:RPC">

            <ns1:parameter name="className" value="MyService" />

            <ns1:parameter name="allowMethods" value="*"/>

        </ns1:service>

        <ns1:transport name="http">

            <ns1:requestFlow>

                <ns1:handler type="URLMapper" />

                <ns1:handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler" />

            </ns1:requestFlow>

            <ns1:parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler" />

            <ns1:parameter name="qs:wsdl" value="org.apache.axis.transport.http.SQWSDLHandler" />

            <ns1:parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler" />

            <ns1:parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler" />

            <ns1:parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler" />

            <ns1:parameter name="qs:wsdl" value="org.apache.axis.transport.http.SQWSDLHandler" />

        </ns1:transport>

        <ns1:transport name="local">

            <ns1:responseFlow>

                <ns1:handler type="LocalREsponder" />

            </ns1:responseFlow>

        </ns1:reansport>

    </ns1:deployment>


    D:/work>


    D:/work>java org.apache.axis.client.AdminClient undeploy.wsdd

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    Processing file undeployt.wsdd

    <Admin>Done processing</Admin>

    D:/work>java Client "안녕, 세상아!"

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system.property.

    The AXIS engine could not find a target service to invoke! targetService is MyService

    D:/work>

    Posted by 나비:D
    :

    AXIS 설치

    1. Axis압축파일(axis-bin-1_4.zip)를 다운받아 압축파일내에 있는 webapps/axis 폴더를 %CATALINA_HOME%/webapps에 푼다

    2. 톰캣을 실행하고 브라우저에서 http://localhost:8080/axis/주소로 연결되면 성공


    [Trouble Shooting]

    위의 주소로 연결후 CAll이라고 되어 있는 링크를 클릭하면 아래와 같은 에러가 발생할 수 있다.

    Call -Call a local endpoint that list's the caller's http headers

    에러 : java.lang.RuntimeException: No compiler found in your classpath!(you may need to add 'tools.jar')


    이는 톰캣 설치시 사용할 런타임 JRE 위치를 JDK가 아닌 JRE로 했기 때문입니다.

    이럴 경우 톰캣을 다시 설치하여 JDK쪽의 경로로 변경해주면 된다. 그럴 수 없다면 JDK의 tool.jar를 %CATALINA_HOME%/common/lib로 복사해주면 된다.


    JAVA_HOME은 E:/Program Files/Java/jdk1.5.0_09/lib/tool.jar

    D:/work>copy "%JAVA_HOME%/lib/tools.jar" "%CATALINA_HOME%/common/lib"

    1개 파일이 복사되었습니다.


    Administer Axis, SOAPMonitor는 디폴트로 사용할 수 없게 되어 있으므로 지금은 무시한다.

    Administer Axis는 web.xml에서 해당 부분을 주석해제하면 사용할 수 있고, SOAPMonitor는 4부에서 설명하겠다.


     [부가기능 설치]

    3. JavaMail(javamail-1_4.zip)을 다운받아 압축파일내 mail.jar를 CLASSPATH에 등록

    http://java.sun.com/products/javamail/downloads/index.html

    4. ㅓㅁㅍ므먀ㅣsms ㅓㅁㄹ vozlwl(jaf-1_1-fr.zip)를 필요로 하므로 이 또한 설치(activation.jar)

    http://java.sun.com/beans/glasgow/jaf.html

    * 즉, mail.jar와 activation.jar를 %CATALINA_HOME%/common.lib에 넣어주면 된다.


    [웹서비스 작성과 테스트(서버측)]

    소스파일의 확장자를 jws로 변경하여 올리면 알아서 웹서비스를 수행한다.

    jws확장자를 가지면 AXIS는 이 파일이 SOAP기능을 수행할 수 있게 자동으로 모든 과정을 처리해준다.

    간편하게 테스트용으로 사용할 수 있겠으나 내부적으로 어떻게 돌아가는지 파악이 되지 않으므로 이런자동구성보다는 수동으로 제어(WSDD파일을 이용)하는 것이 바람직할 것이다.


    D:work>type Calculator.java

    public class Calculator{

        public int add(int a, int b){

            return a+b;

        }

        public int subtract(int a, int b){

            return a-b;

        }

    }


    D:work>copy Calculator.java "%CATALINA_HOME%webappsaxisCalculator.jws"

    1개 파일이 복사되었습니다.


    * .jws 확장자를 가지면 웹서비스로 인식하게끔 web.xml에 설정되어 있다.

    위처럼 작업한 뒤, 브라우저에서 http://localhost:8080/axis/Calculator.jws로 접속하면 아래와 같다.


    [브라우저 접속화면]

    위처럼 확장자를 jws로 만들어 deploy하며느 AXIS는 자동으로 웹서비스가 제공될 수 있도록 처리해준다.

    사용자 삽입 이미지


    WSDL도 자동으로 만들어 주므로 편하다.


    사용자 삽입 이미지


    [콘솔에서 테스트 클라이언트(클라이언트측)]

    D:work> type CalcClient.java


    import org.apache.axis.client.Call;

    import org.apache.axis.client.Service;

    import org.apache.axis.encording.XMLType;

    import org.apache.axis.utils.Options;

    import javax.xml.rpc.ParameterMode;

    public class CalcClient{

        public static void main(String[] args) throws Exception{

            Options options = new Options(args);

            String endpoint = http://localhost: + options.getPort() + "/axis/Calculator.jws";

            args = options.getRemainingArgs();

            if ( args == null || args.length != 3){

                System.err.println("Usage: CalcClient <add|subtract> arg1 arg2");

                return;

            }

            String method = args[0];

            if ( !(method.equals("add") || method.equals("subtract"))){

                System.err.println("Usage: CalcClient <add|subtract> arg1 arg2");

                return;

            }

            Integer i1 = new Integer(args[1]);

            Integer i2 = new Integer(args[2]);

            Service service = new Service();

            Call Call = (Call) service.createCall();

            call.setTargetEndpointAddress( new java.net.URL(endpoint));

            call.setOperationName(method);

            call.addParameter("op1", XMLType.XSD_INT, ParameterMode.IN);

            call.addParameter("op2", XMLType.XSD_INT, ParameterMode.IN);

            call.setReturnType(XMLType.XSD_INT);

            Integer ret = (Integer) call.invoke(new Object [] {i1, i2});

            System.out.println("Got result : " + ret);

        }

    }


    D:/work>set AXIS_HOME=D:/util/axis-1_4

    D:/work>set AXIS_LIB=%AXIS_HOME%/lib

    D:/work>set AXISCLASSPATH=%AXIS_LIB%/axis.jar;%AXIS_LIB%/jaxrpc.jar;%AXIS_LIB%/saaj.jar;%AXIS_LIB%/log4j-1.2.8.jar;%AXIS_LIB%/common-discovery-0.2.jar;%AXIS_LIB%/commons-logging-1.0.4.jar;%AXIS_LIB%/wsdl4j-1.5.1.jar

    D:/work>set CLASSPATH=%CLASSPATH%;%AXISCLASSPATH%

    D:/work>javac CalcClient.java

    D:/work>java CalcClient add 1 2

    log4j:WARN No appenders could be found for logger (org.apache.axis.i18n.ProjectResourceBundle).

    log4k:WARN please initialize the log4j system. properly.

    Got result : 3

    D:work>java CalcClient subtract 3 1

    log4j:WARN No appenders could be found for logger(org.apache.axis.i18n.ProjectResourceBundle).

    log4j:WARN Please initialize the log4j system property.

    Got result : 2


    D:/work>

    Posted by 나비:D
    :

    BLOG main image
    by 나비:D

    공지사항

    카테고리

    분류 전체보기 (278)
    Programming? (0)
    ---------------------------.. (0)
    나비의삽질 (5)
    Application (177)
    SQL (51)
    Web (27)
    etc. (14)
    Omnia (0)
    ---------------------------.. (0)

    최근에 올라온 글

    최근에 달린 댓글

    최근에 받은 트랙백

    글 보관함

    달력

    «   2024/12   »
    1 2 3 4 5 6 7
    8 9 10 11 12 13 14
    15 16 17 18 19 20 21
    22 23 24 25 26 27 28
    29 30 31
    Total :
    Today : Yesterday :