현재 착수하고 있는 새로운 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"클래스를 만들어 처리했다.
위 클래스가 응용프로그램에 추가되면 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의 코드비하인드에 포함된다.
ASP.NET 응용프로그램 클래스에 의해 개발자는 응용프로그램 시작/종료, 및 글로벌 오류처리를 할 수 있게 된다.
기본적으로 ASP.NET MVC 프로젝트의 템플릿은 자동적으로 Application_Start메소드를 클래스에 추가하여 2개의 URL 라우팅 규칙을 함께 등록한다.
위 최초 라우팅 규칙은 어느 컨트롤러 클래스를 초기화하여 어느 액션 메소드를 실행시킬지를 결정할 때, ASP.NET MVC 프레임웍은 "[controller]/[action]/[id]"의 포멧을 사용하고 기본적으로 URL을 컨트롤러에 매핑해야하는 것인지를 나타낸다.
이 기본 라우팅 규칙에 의해 파트1에 있던 전자거래 검색 예제 /Products/Detail/3에 대한 URL의 요청이 자동적으로 ProductsController클래스내에 Detail메소드를 호출시켜 ID메소드의 인수값으로서 3이 넘어온다.
위 2번째 라이팅 규칙은 응용프로그램으로 특별한 경우인 루트의 Default.aspx(이것은 응용프로그램의 루트URL에 대한 요청을 처리할 때 "/"대신에 웹서버로부터 가끔보내진다) URL에 추가된다. 이 규칙에서는 응용프로그램의 루트 "/Default.aspx" 또는 "/"의 어느 쪽에 대한 요청도 "HomeController"클래스(Visual Studio가 자동적으로 새 응용프로그램을 "ASP.NET MVC 웹응용프로그램"템플릿을 사용하면 자동 추가된다) 위 "Index()" 엑션에 의해 처리도록 한다.
루트 인스턴스 이해하기라우팅 규칙은 루트 인스턴스를 System.Web.Mvc.RouteTable의 Routes콜렉션에 추가하면 등록된다.
Route클래스에서 맵핑규칙의 구성에 사용하는 수많은 속성이 정의되어 있고 속성은 "일반적인" .NET 2.0속성설정을 사용하여 설정할 수 있다.
또한, 새로운
객체 초기화(object initializer) 기능을 VS2008의 C# 및 VB컴파일러로 활용하여 보다 간결하게 속성을 설정할 수도 있다.
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클래스를 프로젝트에 추가한다.
이후, SearchController클래스에서 2개의 액션메소드를 정의한다. Index()액션메소드는 사용자가 입력하여 검색대상을 송신하는데 사용하는 TextBox가 있는 검색페이지를 표시하기 위해 사용된다. Result()액션은 그곳으로부터 폼송신을 처리하여 데이터베이스에서 검색한 다음 그 결과를ㄹ 표시하기 위해 사용된다.
기본적으로 /[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] 루트 정의가 추가되었기 때문이다.
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루트매핑 규칙을 추가하여 깔끔한 검색결과를 보일 수 있다.
처음 2개의 규칙에서는 /Search/ URL에 대해서 컨트롤러나 액션인수를 현재 명시적으로 지정하고 있다. "/Search"는 항상 SearchController에서 "Index"액션으로 처리되는 것이 당연한 것으로 나타내고 있다. 현재 하위 URL 계층을 가지는 URL은 모두(/Serach/Foo나 /Serach/Bar 등), 항상 SearchController상에서 "Results"액션으로 처리되고 있다.
위 2번째 라우팅 규칙은 /Search/프리픽스를 넘는 것은 모두 "[query]"로 불리는 인수로서 처리되어 SearchController의 Results액션에 메소드 인수로서 보내지는 것을 나타낸다.
대부분의 경우(검색결과가 10이상 표시되는 경우), 검색결과를 페이지 번호를 나타내고 싶어한다고 생각한다. 이것은 쿼리 스트링 인수를 통해 할지 또는 옵션으로서 URL일부에 페이지번호(/Search/Beverages/2)를 처리한다. 이것은 다음 옵션으로서 지원하기 위해 추가 인수를 2번째 라우팅 규칙에 추가해둔다. :
위에서 확인할 수 있는 것으로 새 URL 규칙을 합쳐 현재 "Search/[query]/[page]"가 되어 잇다. 또한, 기본페이지번호는 URL에 포함되지 않도록 하는 경우를 위해 1로 설정한다. (이것은 "Defaults"속성으로서 처리된
익명형을 통해서)
이후, SearchController.Results액션 메소드를 업데이트하고 이 페이지 인수를 메소드 인수로서 받는다.
이렇게 하여 사이트에 대해서 깔끔한 URL로 검색할 수 있다.
라우팅 규칙에 대한 전체컨디션 평가이 글에 대해서는 앞에서도 말했지만, Route클래스는 "Validation"속성을 가지고 있어 루트규칙이 합쳐지면 True가 되지 않으면 안되는 전제조건 규칙을 검증할 수 있도록 추가되었다. ASP.NET MVC 프레임웍은 정규식을 사용하여 URL의 각 인수를 평가할 수 있어 HTTP헤더 평가리를 하는 일도 할 수 있다.
이하는 사용자지정 평가규칙으로 "/Products/Detail/43"등과 같은 URL에 대한 유효화를 할 수 있다. 이는 ID인수가 수치로 1-8문자가 아니면 안되는 설정이다.
응용프로그램에 /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를 호출한:
자동적으로 이 글 앞쪽에서 구성한 특정 Search결과의 루트 규칙과 이것을 반양하여 자동적으로 생성한 "href"속성을 선택한다.
특히, 어떻게 Html.ActionLink에 대한 2번째 호출이 자동적ㅇ로 "page"인수를 URL일부로서 매핑되어 있지 않을 까 확인하세요. (또한, 서버사이트에서 기본값이 제공되는 것을 알았기 때문에, 1번째 호출로 페이지 인수값이 생략되는 모습을 확인한다)
Url.Action
Html.ActionLink의 사용을 추가형 ASP.NET MVC에는 Url.Action()뷰 헬퍼 메소드도 있다. 이것은 문자열 URL을 생성한다. 이것을 사용하고 싶다면 아래와 같이 코드 스닙펫:
URL 라우팅 시스템을 사용하고 아래의 URL을 리턴한다.(<a href="">요소에는 랩핑되어 있지 않다)
Controller.RedirectToActionASP.NET MVC는 Controller.RedirectToAction() 헬퍼 메소드를 지원하고 있어, 그것은 컨트롤러에서 사용하고 (URL이 URL 라우팅 시스템을 사용하여 계산되었을 경우) 리다이렉트를 실행할 수 있다.
예를 들면, 아래 코드가 컨트롤러내에서 실행되었을 때,
내부에서 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 호출:
또는 다음과 같이 쓸 수 있다.
조금 간결하게 쓸 수 있는 것을 추가형 2번째 옵션을 형 세이프인 장점을 가진다. 즉, 식의 컴파일시 체크와 Visual Studio 코드 인텔리 센스(리펙토링도 사용할 수 있다)를 사용할 수 있다.
위에서 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"액션 메소드를 실행하지 않는다.
이 HTML의 하이퍼링크를 리턴하는 대신에
최종 사용자가 이 하이퍼링크를 클릭했을 때, HTTP요청이 서버에 보내져 SearchController의 Results액션메소드를 실행시킨다.
루트의 전체 테스트ASP.NET MVC 프레임웍의 코어 설계의 원칙하나가 테스트를 많이 지원할 수 있도록 하는 것이다. 나머지 MVC프레임웍과 같이 쉽게 루트나 루트를 합치는 규칙의 전체 테스트를 할 수 있다. MVC의 라우팅 시스템은 ASP.NET으로부터 도립하여 초기화하여 실행할 수 있다. 즉, 어떤 전체 테스트 라이브러리(웹서버를 시작할 필요가 없다) 안에서도 어떤 전체 테스트 프레임웍(NUint, MBUnit, MSTest등)을 사용해도 루트패턴을 읽어 전체테스트할 수 있다.
여러개의 전체 테스트 중에서 직접 ASP.NET MVC응용프로그램의 글로벌 RouteTable매핑 콜렉션을 전체 테스트할 수 있지만, 일반적으로 전체 테스트를 변경하거나 글로벌 상황에 의지하는 것은 좋은 생각은 아니다. 좋은 패턴은 아래와 같이 RegisterRoutes() 헬퍼 메소드에 루트 등록 놀리를 구조화하는 것이다. 이것은 인수로서 보내지는 RouteCollection에 대해서 실행된다. (주: 아마 이것을 다음 프리뷰 업데이트에서 기본 VS템플릿패턴으로 할 것으로 생각된다)
고유의 RouteCollection인스턴스를 만들어 루트 규칙을 응용프로그램안에서 등록하기 위해 응용프로그램의 RegisterRoutes()헬퍼를 호출하는 전체 테스트를 쓸 수 있다. 그러면, 응용프로그램에 대한 요청을 레이트하고 그것들에 대한 올바른 컨트롤러나 액션이 등록되어 있는지 어떤 문제가 있을지 걱정하는 일없이 확인할 수 있다.
정리
이번 글에 의해 ASP.NET MVC 라우팅 아키텍쳐의 동작과 이를 사용하여 ASP.NET MVC응용프로그램내 발생하는 URL구조나 레이아웃을 사용자지정으로하는 방법에 대해서 상세히 다루어서 다행이다.
기본적인 새 ASP.NET MVC 웹응용프로그램을 만들 때, 기본적으로 /[constroller]/[action]/[id] 라우팅 규칙이 정의된다. 이것에 의해 스스로 고유의 사용자지정으로 라우팅 규칙을 등록할 필요없이 많은 응용프로그램을 구축할 수 있게 되지만, 앞에서 소개한 것에 의해 URL포멧을 사용자지정으로 하고 싶은 경우 간단하게 만들수 있어 이것을 실행할 때 MVC프레임웍이 큰 파워와 유연성을 줄 수 있어 기쁘게 생각한다.
Hope this helps.
Scott