'htmlhelper'에 해당되는 글 1건

  1. 2009.04.08 [ASP.NET MVC Tip] Implementing Custom HtmlHelper Merthods

안녕하세요? 웹지니입니다.

무척이나 오랫만에 기술 관련 포스팅을 하는 것 같습니다. 게다가 영어로 된 제목이라니!!! (역시나 간지에 치중하는 모습을 버리지 못하는 지니입니다 -ㅅ-;;) 이번 포스트에서는 ASP.NET MVC의 HtmlHelper 클래스와 요 녀석이 제공하는 메서드들에 대해 간략하게 살펴보고 내게 필요한 Custom HtmlHelper 메서드를 구현하는 방법에 대해 알아보겠습니다.

예제 소스 다운로드: MvcApplication1.zip (289.72 kb)

Prologue

최근 웹지니는 ASP.NET MVC Framework 1.0 기반의 프로젝트를 진행하고 있습니다. 처음에 공부하는 시점에서는 요것이 과연 유용할 것인가에 대해 고민을 많이 했었지만 실제 프로젝트에 도입한 후로는 제법 쓸만한 녀석이라고 느껴집니다. 다만 모든 면에서 100% 만족할 수 있는 수준은 아니었고 특히 HtmlHelper, 요 녀석에게 불만(?)이 조금 있습니다. 오늘 포스팅의 목적도 바로 이 부분을 해소하기 위한 것이라 할 수 있어요.

HtmlHelper Class

다들 아시다시피 HtmlHelper 클래스는 ASP.NET MVC 프로젝트에서 View를 통해 HTML UI 요소들을 렌더링하기 위한 메서드들을 제공하는 클래스입니다. 그런데 요 녀석이 제공하는 메서드는 그렇게 많지 않습니다. 실제로 HTML UI 요소들을 렌더링 하기 위한 대부분의 메서드들은 확장 메서드 (Extension Methods)로 구현되어 있기 때문입니다. HtmlHelper 클래스의 멤버들은 여기서 확인하실 수 있어요. 이 중 HTML UI 요소들과 관련되어 자주 사용되는 메서드를 살펴보면 다음과 같습니다.

string ActionLink(...);    // 지정된 Controller 클래스의 Action 메서드를 호출하는 <A> 요소를 렌더링합니다.
string CheckBox(...);      // <input type="checkbox"> 요소를 렌더링합니다.
string DropDownList(...);  // <select> 요소를 드롭다운 목록을 렌더링합니다.
string Hidden(...);        // <input type="hidden"> 요소를 렌더링합니다.
string ListBox(...);       // <select> 요소를 이용하여 리스트 상자를 렌더링합니다.
string Password(...);      // <input type="password"> 요소를 렌더링합니다.
string RadioButton(...);   // <input type="radio"> 요소를 렌더링합니다.
string RouteLink(...);     // 특정 라우팅 규칙에 해당하는 URL로 이동하는 <A> 요소를 렌더링합니다.
string TextArea(...);      // <textarea> 요소를 렌더링합니다.
string TextBox(...);       // <input type="text"> 요소를 렌더링합니다.

어예~ 제법 많은 메서드들이 제공되고 있습니다!! 요것들만 있으면 어떤 HTML 문서도 만들어낼 수 있겠군요!!!! 된장... 절대 그렇지 않습니다. -ㅅ-;; 그 자주 사용되는 <img> 태그를 렌더링하기 위한 메서드도 없네요 췟...

So, What's the Problem?

우리는 어떤 컨트롤러의 액션 메서드를 호출하기 위한 링크를 생성할 때 ActionLink 메서드를 사용하게 됩니다. 예를 들어 다음의 코드를 살펴보죠.

<div>
    <%= Html.ActionLink("로그인", "Login", "Account"); %>
</div>

위의 코드는 다음과 같은 <A> 태그를 렌더링하게 됩니다.

<div>
    <a href="/Account/Login">로그인</a>
</div>

문제가 뭐냐구요? 음... 뭐 이 부분이 굳이 문제라고 생각되진 않습니다. 그러면 다음의 코드는 어떻게 만들어 낼 수 있을까요?

<div>
    <a href="/Account/Login"><img src="login.jpg" /></a>
</div>

오호 저 따위 코드는 HtmlHelper 클래스의 ActionLink 메서드로 만들 수 있다구! 이렇게 말이야!

<div>
    <%= Html.ActionLink("<img src=\"login.jpg\" />", "Login", "Account") %>
</div>

흐헤헤헤헤! 쉽잖아!

그러나 위의 메서드는 다음과 같은 결과를 렌더링하게 됩니다.

<div>
    <a href="/Account/Login">&gt;img src=&guot;login.jpg&quot; /&lt;</a>
</div>

ASP.NET MVC의 소스 코드를 뒤집어 보면 아시겠지만 ActionLink 메서드는 첫 번째 인자로 전달되는 linkText 변수에 전달된 문자열을 HTML 인코딩 해버립니다. 이런 된장...

Implementing the "Image" Method

다행히 HtmlHelper 클래스가 제공하는 대부분의 메서드는 확장 메서드이며 따라서 우리도 마음만 먹으면 얼마든지 확장 메서드를 구현하여 HtmlHelper 클래스에 추가할 수 있습니다. 그렇다면 위와 같은 <IMG> 태그를 렌더링하기 위한 메서드를 우리가 직접 구현할 수도 있겠습니다. 그러려면 다음의 클래스와 메서드에 대해 우선 알아야 합니다.

// 컨트롤러의 액션 메서드를 호출하거나
// 지정된 라우팅 규칙에 해당하는 URL을 생성해 주는 클래스입니다.
public class UrlHelper {
    // 지정된 컨트롤러의 액션 메서드를 호출하는 URL을 생성합니다.
    public string Action(...);
    // 지정된 라우팅 규칙에 해당하는 URL을 생성합니다.
    public string RouteUrl(...);
}

그렇습니다. 오늘 포스트의 핵심은 이 UrlHelper 클래스와 Action, RouteUrl 메서드 형제입니다. 요 녀석들을 이용하면 우리도 얼마든지 액션 메서드를 호출하는 요소들을 메서드 형태로 구현할 수 있다 이겁니다! 그러면 앞서 액션 메서드를 호출하는 Image 메서드를 한 번 구현해 볼까요? 우선 Visual Studio 2008에서 새로운 ASP.NET MVC Application 프로젝트를 생성하고 MyLinkExtension이라는 이름의 클래스를 추가한 후 다음과 같이 코드를 작성합니다.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Web;
  5: using System.Web.Mvc;
  6: 
  7: namespace MvcApplication1
  8: {
  9: 	public static class MyLinkExtension
 10: 	{
 11: 		public static string Image(
 12: 			this HtmlHelper helper, 
 13: 			string imageSource, 
 14: 			string actionName)
 15: 		{
 16: 			// 아래 구현된 Image 메서드를 호출합니다.
 17: 			return Image(helper, imageSource, actionName, null);
 18: 		}
 19: 
 20: 		public static string Image(
 21: 			this HtmlHelper helper, 
 22: 			string imageSource, 
 23: 			string actionName, 
 24: 			string controllerName)
 25: 		{
 26: 			// UrlHelper 클래스의 인스턴스를 생성합니다.
 27: 			UrlHelper urlHelper = new UrlHelper(
 28: 				helper.ViewContext.RequestContext
 29: 			);
 30: 
 31: 			// 컨트롤러와 액션 메서드의 이름을 지정하여 URL을
 32: 			// 생성합니다.
 33: 			string url = urlHelper.Action(actionName, controllerName);
 34: 
 35: 			// <A> 요소를 렌더링할 TagBuilder 클래스의
 36: 			// 인스턴스를 생성합니다.
 37: 			TagBuilder anchorBuilder = new TagBuilder("a");
 38: 
 39: 			// 이동할 URL을 지정할 href 특성을 추가합니다.
 40: 			anchorBuilder.MergeAttribute("href", url);
 41: 
 42: 			// <IMG> 요소를 렌더링할 TagBuilder 클래스의
 43: 			// 인스턴스를 생성합니다.
 44: 			TagBuilder imgBuilder = new TagBuilder("img");
 45: 			
 46: 			// 이미지 경로를 지정할 src 특성을 추가합니다.
 47: 			imgBuilder.MergeAttribute("src", imageSource);
 48: 
 49: 			// <A> 요소에 <IMG> 요소를 렌더링합니다.
 50: 			anchorBuilder.InnerHtml = imgBuilder.ToString();
 51: 			
 52: 			// <A> 요소를 렌더링한 결과를 리턴합니다.
 53: 			return anchorBuilder.ToString();
 54: 		}
 55: 	}
 56: }
 57: 

주석을 표시해 두었으니 아마 코드를 이해하는데 큰 무리는 없을 것입니다. 코드를 작성했으면 아래와 같이 Home/Index.aspx 페이지를 열고 메서드를 시험해 봅시다.

  1: <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
  2: 
  3: <%@ Import Namespace="MvcApplication1" %> <!-- 요거 중요!! -->
  4: 
  5: <!-- 나머지 코드 생략 -->
  6: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
  7:     <%= Html.Image("/content/webgenie.png", "LogOn", "Account") %>
  8: </asp:Content>
  9: 

앞서 우리가 정의한 Image 메서드를 사용하려면 이 메서드가 구현된 클래스의 네임스페이스를 Import 해야 합니다. web.config 파일에 네임스페이스 참조를 추가하면 모든 페이지에서 손쉽게 사용할 수 있겠지만 예제에서는 3번 라인에서와 같이 페이지 수준에서 네임스페이스를 Import했습니다. 그리고 실제로 메서드를 사용하는 부분은 7번 라인의 코드입니다. 이제 이 페이지가 동작하는 모습을 살펴볼까요?

img1

그림에서 보듯이 이미지가 올바르게 표시되는 것을 볼 수 있으며 이미지 위에 마우스 포인터를 가져가면 우리가 지정한 대로 Account 컨트롤러의 LogOn 메서드로의 링크가 설정되어 있음을 볼 수 있습니다. 유후~ 간단하군요!

Extending "Image" Method

오랫만의 포스팅이라 이대로 끝을 내기는 살짝 아쉬워서 한 가지 팁을 더 소개할까 합니다. HtmlHelper 클래스의 대부분의 메서드들은 object 타입 혹은 RouteValueDictionary 타입을 이용하여 렌더링되는 HTML UI 요소에 적용될 HTML 특성들을 지정할 수 있습니다. 바로 다음과 같이 말이지요.

  1: <div>
  2: <%= Html.ActionLink("실행!", "Execute", new { style = "color:#EA0000;" }) %>
  3: </div>

위의 코드는 ActionLink 메서드가 렌더링하는 <A> 태그에 style 특성을 지정합니다. 즉, 다음과 같은 결과가 렌더링됩니다.

  1: <div>
  2:     <a href="/Controller/Execute" style="color:#EA0000;">실행!</a>
  3: </div>

그렇다면 우리가 만든 Image 메서드도 저런 것을 지원하면 좋겠습니다! 그리고 이제와 하는 말이지만 앞서 렌더링 된 이미지는 테두리가 쳐져 있어서 살짝 보기가 안좋군요! HTML 특성에 border="0"을 지정할 수 있다면 매우 깔쌈하게 이미지를 출력할 수 있을텐데 말이지요. 해서 Image 메서드를 다음과 같이 확장해 보았습니다.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Web;
  5: using System.Web.Mvc;
  6: using System.Web.Routing;
  7: 
  8: namespace MvcApplication1
  9: {
 10: 	public static class MyLinkExtension
 11: 	{
 12: 		public static string Image(
 13: 			this HtmlHelper helper, 
 14: 			string imageSource, 
 15: 			string actionName)
 16: 		{
 17: 			// 바로 아래 구현된 Image 메서드를 호출합니다.
 18: 			return Image(helper, imageSource, actionName, null);
 19: 		}
 20: 
 21: 		public static string Image(
 22: 			this HtmlHelper helper,
 23: 			string imageSource,
 24: 			string actionName,
 25: 			string controllerName)
 26: 		{
 27: 			// 요것도 바로 아래의 Image 메서드를 호출합니다.
 28: 			return Image(helper, imageSource, actionName, controllerName, null);
 29: 		}
 30: 
 31: 		public static string Image(
 32: 			this HtmlHelper helper, 
 33: 			string imageSource,
 34: 			string actionName, 
 35: 			string controllerName,
 36: 			object htmlAttributes)
 37: 		{
 38: 			// UrlHelper 클래스의 인스턴스를 생성합니다.
 39: 			UrlHelper urlHelper = new UrlHelper(
 40: 				helper.ViewContext.RequestContext
 41: 			);
 42: 
 43: 			// 컨트롤러와 액션 메서드의 이름을 지정하여 URL을
 44: 			// 생성합니다.
 45: 			string url = urlHelper.Action(actionName, controllerName);
 46: 
 47: 			// <A> 요소를 렌더링할 TagBuilder 클래스의
 48: 			// 인스턴스를 생성합니다.
 49: 			TagBuilder anchorBuilder = new TagBuilder("a");
 50: 
 51: 			// 이동할 URL을 지정할 href 특성을 추가합니다.
 52: 			anchorBuilder.MergeAttribute("href", url);
 53: 
 54: 			// <IMG> 요소를 렌더링할 TagBuilder 클래스의
 55: 			// 인스턴스를 생성합니다.
 56: 			TagBuilder imgBuilder = new TagBuilder("img");
 57: 			
 58: 			// htmlAttribute 매개 변수에 전달된 특성들을 병합합니다.
 59: 			if (htmlAttributes != null)
 60: 			{
 61: 				imgBuilder.MergeAttributes<String, Object>(
 62: 					new RouteValueDictionary(htmlAttributes)
 63: 				);
 64: 			}
 65: 
 66: 			// 이미지 경로를 지정할 src 특성을 추가합니다.
 67: 			imgBuilder.MergeAttribute("src", imageSource);
 68: 
 69: 			// <A> 요소에 <IMG> 요소를 렌더링합니다.
 70: 			anchorBuilder.InnerHtml = imgBuilder.ToString();
 71: 			
 72: 			// <A> 요소를 렌더링한 결과를 리턴합니다.
 73: 			return anchorBuilder.ToString();
 74: 		}
 75: 	}
 76: }
 77: 

36번 라인과 같이 object 타입의 htmlAttribute 매개 변수를 추가하고 58번 라인부터 64번 라인까지의 코드와 같이 이 매개 변수에 null 이 아닌 값이 전달되면 이를 RouteValueDictionary 객체로 변환하여 TagBuilder 클래스의 MergeAttributes 메서드를 통해 <IMG> 태그에 병합하기만 하면 끝입니다! 이제 Index.aspx 페이지의 코드를 다음과 같이 수정해 볼까요?

  1: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
  2:    <%= Html.Image(
  3:        "/content/webgenie.png", 
  4:        "LogOn", 
  5:        "Account", 
  6:        new { border = 0 })%>
  7: </asp:Content>

6번 라인과 같이 new { border = 0 } 구문을 이용하여 <IMG> 태그에 border 특성을 지정했으며 그 결과는 아래 그림과 같습니다.

img2

어예~ <IMG> 태그에 border="0" 특성이 적용되어 이미지 테두리가 사라졌군요! 이제서야 뭔가 좀 만들어 낸 것 같은 느낌이 듭니다.

Epilogue

이상으로 간단하게 HtmlHelper 클래스를 확장하는 방법에 대한 소개를 마치고자 합니다. 오늘 포스트에서 소개해 드린 내용은 사실 간단하기 그지 없는 내용이지만 이 내용을 토대로 여러분이 원하는 다양한 확장 메서드들을 구현하여 보다 편리하게 HTML 요소들을 활용하실 수 있게 되기를 바랍니다. (그런데 과연 이 내용이 필요한 분들이 있을까요 -ㅅ-;;)

다음엔 조금 더 유용한 (쿨럭..) 포스트로 찾아뵙겠습니다. 즐거운 수요일 되세요!!

Posted by 웹지니 트랙백 0 : 댓글 0