달력

42024  이전 다음

  • 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

리플렉션을 사용하여 비즈니스 개체를 ASP.NET 폼 컨트롤에 바인딩

John Dyer
Dallas Theological Seminary

적용 대상:
   Microsoft Visual Studio 2005 및 이전 버전
   ASP.NET 1.1
   C# 프로그래밍 언어
   Visual Basic 프로그래밍 언어

요약: 리플렉션을 사용하여 단 한 줄의 코드로 비즈니스 개체를 ASP.NET Web Forms에 바인딩하면 복잡한 작업이 단순해지고 오류가 줄어듭니다.

MSDFormBinding.msi 샘플 파일을 다운로드하십시오.

목차

소개
양식 코드 단순화 및 단축
시작하기: 리플렉션에서 속성 목록 검색
개체 속성 값을 컨트롤에 바인딩
알려진 속성을 사용하여 알려지지 않는 컨트롤 값 설정
프로세스를 반대로 수행: BindControlsToObject
성능 및 FormBinding 스키마 확장
결론

소개

웹 개발자가 반복적으로 수행하는 가장 일상적인 작업 중 하나는 데이터베이스 테이블을 업데이트하는 간단한 폼을 작성하는 것입니다. 웹 개발자는 테이블의 레코드를 표시하는 목록 페이지와 각 데이터베이스 필드에 대한 적절한 폼 컨트롤이 있는 폼 페이지를 만듭니다. 또한 대부분의 개발자는 데이터베이스 테이블을 나타내는 비즈니스 개체를 사용하여 코드를 다계층 디자인으로 구성합니다. 비즈니스 개체(Document)가 데이터베이스 테이블(Documents)을 나타내는 경우 대부분의 폼은 아래 코드와 같습니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

<script runat="server">protected void Page_Load(Object Src, EventArgs E) {if (!IsPostBack) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   Title.Text = document.Title;   Active.Checked = document.Active;   CreatedDate.Text = document.CreatedDate.ToString();   AuthorID.FindByValue(document.AuthorID.ToString()).Selected =         true;   // ... 기타 등등   HtmlBody.Text = document.HtmlBody;}}protected void SaveButton_Click(Object Src, EventArgs E) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   document.Title = Title.Text;   document.Active = Active.Checked;   document.CreatedDate = Convert.ToDateTime(CreatedDate.Text);   document.AuthorID = Convert.ToInt32(AuthorID.SelectedItem.Value);   // ... 기타 등등   document.HtmlBody = HtmlBody.Text;   Documents.Update(document);}</script>(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

양식 코드 단순화 및 단축

위 코드에서 각 컨트롤은 명시적으로 캐스팅되어 폼 컨트롤의 올바른 속성으로 설정됩니다. 속성 및 폼 컨트롤 수에 따라서 이 코드 부분은 길이가 길어져 관리하기가 어려울 수 있습니다. 이 코드는 또한 형식 변환과 ListControl을 위한 오류 수정을 포함해야 하므로 더욱 복잡해집니다. 코드 생성 도구(예: Eric J. Smith의 뛰어난 CodeSmith )를 사용하여 폼을 만들어도 사용자 지정 논리가 필요한 경우에는 오류가 포함되기 쉽습니다.

리플렉션을 사용하면 코드를 한 줄만 사용하여 비즈니스 개체의 모든 속성을 해당 폼 컨트롤에 바인딩할 수 있으므로, 코드의 줄 수가 줄어들고 가독성이 향상됩니다. 리플렉션 시스템 빌드가 끝나면 위 코드는 다음과 같이 줄어듭니다.

protected void Page_Load(Object Src, EventArgs E) {   if (!IsPostBack) {      Document document =      Documents.GetDocument(Request.QueryString["DocumentID"]);      FormBinding.BindObjectToControls(document);   }}protected void Save_Click(Object Src, EventArgs E) {   Document document =         Documents.GetDocument(Request.QueryString["DocumentID"]);   FormBinding.BindControlsToObject(document);   Documents.Update(document);}

이 코드는 모든 표준 ASP.NET 컨트롤(TextBox, DropDownList, CheckBox 등)과 대부분의 타사 컨트롤(예: Free TextBox Calendar Popup )에서 작동합니다. 비즈니스 개체 속성과 폼 컨트롤의 수에 상관 없이, 폼 컨트롤 ID가 비즈니스 개체 속성 이름과 일치할 경우 동일한 한 줄의 코드가 모든 필수 기능을 처리합니다.

시작하기: 리플렉션에서 속성 목록 검색

가장 먼저 해야 할 일은 비즈니스 개체의 속성을 검사하고 비즈니스 개체 속성 이름과 같은 ID를 가지는 ASP.NET 컨트롤을 찾는 것입니다. 아래 코드는 기본적인 바인딩 조회를 구성합니다.

public class FormBinding {   public static void BindObjectToControls(object obj,         Control container) {      if (obj == null) return;      Type objType = obj.GetType();      PropertyInfo[] objPropertiesArray =               objType.GetProperties();      foreach (PropertyInfo objProperty in objPropertiesArray) {         Control control =                      container.FindControl(objProperty.Name);         if (control != null) {             // 컨트롤을 처리합니다.         }      }   }}

위 코드에서 BindObjectsToControls 메서드는 비즈니스 개체 obj 및 컨테이너 컨트롤을 받아들입니다. 컨테이너 컨트롤은 일반적으로 현재 WebForm의 Page 개체입니다. 런타임에 컨트롤의 중첩 순서를 변경하는 ASP.NET 1.x MasterPages 구현을 사용하는 중이면 폼 컨트롤이 상주하는 Content 컨트롤을 지정해야 합니다. 그 이유는 FindControl 메서드가 ASP.NET 1.x에서 중첩 컨트롤 및 명명 컨테이너와 작동하는 방식 때문입니다.

위 코드에서 비즈니스 개체의 Type을 가져오고 해당 Type을 사용하여 PropertyInfo 개체의 배열을 가져옵니다. 각 PropertyInfo 개체는 비즈니스 개체 속성에 대한 정보뿐 아니라 비즈니스 개체에서 값을 가져와 설정하는 기능도 포함합니다. foreach 루프를 사용하여 비즈니스 개체 속성 이름(PropertyInfo.Name)에 해당하는 ID 속성이 있는 ASP.NET 컨트롤의 컨테이너를 확인합니다. 컨트롤을 찾으면 속성 값을 컨트롤에 바인딩합니다.

개체 속성 값을 컨트롤에 바인딩

대부분의 처리 작업은 이 단계에서 수행합니다. 발견된 Control을 개체의 속성 값으로 채워야 합니다. 이렇게 하는 방법 중 하나는 각 컨트롤 형식에 대해 if ... else 문을 만드는 것입니다. ListControl(DropDownList, RadioButtonList, CheckBoxListListBox)에서 파생되는 모든 컨트롤에는 일관되게 액세스할 수 있는 공용 인터페이스가 있으므로, 이러한 컨트롤을 그룹화할 수 있습니다. 발견된 컨트롤이 ListControl인 경우 컨트롤을 ListControl로 캐스팅한 다음 선택한 항목을 설정할 수 있습니다.

Control control = container.FindControl(objProperty.Name);if (control != null) {   if (control is ListControl) {      ListControl listControl = (ListControl) control;      string propertyValue = objProperty.GetValue(obj,               null).ToString();      ListItem listItem =              listControl.Items.FindByValue(propertyValue);      if (listItem != null) listItem.Selected = true;   }else {      // 다른 컨트롤 형식을 처리합니다.   }}

애석하게도 다른 컨트롤 형식은 부모 클래스에서 파생되지 않습니다. 여러 일반 컨트롤(TextBox, LiteralLabel)에는 모두 .Text 문자열 속성이 있지만, 속성이 공용 부모 클래스로부터 파생되지 않으므로 각 컨트롤 형식을 개별적으로 캐스팅해야 합니다. 또한 Calendar 컨트롤과 같은 다른 컨트롤 형식을 캐스팅해야 하는데 이는 적절한 속성(Calendar의 경우에는 SelectedDate 속성)을 사용하기 위해 필요합니다. 모든 표준 ASP.NET 폼 컨트롤을 포함하고 폼 컨트롤의 올바른 속성에 액세스하는 데 굉장히 많은 코드 줄이 필요한 것은 아닙니다.

if (control is ListControl) {   ListControl listControl = (ListControl) control;   string propertyValue = objProperty.GetValue(obj,         null).ToString();   ListItem listItem = listControl.Items.FindByValue(propertyValue);   if (listItem != null) listItem.Selected = true;} else if (control is CheckBox) {   if (objProperty.PropertyType == typeof(bool))      ((CheckBox) control).Checked = (bool)               objProperty.GetValue(obj, null);} else if (control is Calendar) {   if (objProperty.PropertyType == typeof(DateTime))      ((Calendar) control).SelectedDate = (DateTime)                objProperty.GetValue(obj, null);} else if (control is TextBox) {   ((TextBox) control).Text = objProperty.GetValue(obj,         null).ToString();} else if (control is Literal)(   //... 기타 등등(레이블용 등)}

이 방식에서는 표준 ASP.NET 1.x 컨트롤을 적절하게 다루고 있습니다. 이제 완전한 기능을 하는 BindObjectToControls 메서드를 사용할 수 있습니다. 그러나 이 방법은 기본 제공 ASP.NET 1.x 컨트롤만을 고려하므로 적용에 있어 제한적입니다. 새 ASP.NET 2.0 컨트롤을 지원하거나 타사 컨트롤을 사용하려면 FormBinding 프로젝트에서 컨트롤의 어셈블리를 참조하고 if ... else 목록에 컨트롤 형식을 추가해야 합니다.

이 문제에 대한 해결 방법은 또 다른 리플렉션을 사용하여 각 컨트롤의 속성을 검색하고, 비즈니스 개체 속성의 유형에 해당하는 속성 유형이 컨트롤에 있는지 확인하는 것입니다.

알려진 속성을 사용하여 알려지지 않는 컨트롤 값 설정

위에서 언급한 것처럼 여러 컨트롤은 문자열 속성 .Text를 공유하며 대부분의 폼 컨트롤은 이 속성을 기본적으로 동일한 방식으로 사용합니다. 즉, 사용자가 입력하는 데이터를 가져와 설정하는 데 사용하는 것입니다. 그 외에도 다양한 컨트롤에 사용되는 기타 일반 속성 및 속성 유형이 있습니다. 이러한 속성 중 몇 가지를 예로 들면, 여러 일정 및 날짜 선택 컨트롤에서 사용되는 .SelectedDate라는 DateTime 속성, 부울 컨트롤에서 사용되는 .Checked라는 부울 속성, 숨겨진 컨트롤에서 일반적으로 사용되는 .Value라는 문자열 속성 등이 있습니다. 이러한 네 개의 속성(string Text, string Value, bool CheckedDateTime SelectedDate)은 가장 일반적인 컨트롤 속성입니다. 컨트롤 형식에 상관 없이 이러한 속성에 바인딩되는 시스템을 디자인할 수 있다면 Binding 메서드는 이러한 네 개의 속성을 사용하는 모든 컨트롤에 적용됩니다.

아래 코드에서는 일반적으로 사용되는 속성을 포함하는지 확인하기 위해 리플렉션을 다시 사용합니다. 그러나 이번에는 비즈니스 개체가 아닌 폼 컨트롤에서 사용합니다. 이러한 속성이 포함된 경우 비즈니스 개체의 속성 값을 컨트롤의 속성으로 설정합니다. 예를 들어 PropertyInfo 배열을 반복하여 .Text라는 문자열 속성을 찾습니다. 컨트롤에 이 속성이 있으면 비즈니스 개체에서 해당 컨트롤의 속성으로 데이터를 보냅니다.

if (control is ListControl) {   // ...}else {   // 컨트롤의 형식과 속성을 가져옵니다.   //   Type controlType = control.GetType();   PropertyInfo[] controlPropertiesArray =         controlType.GetProperties();   // .Text 속성을 찾습니다.   //   foreach (PropertyInfo controlProperty         in controlPropertiesArray) {      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 .Text 속성을 설정합니다.         //         controlProperty.SetValue(control,                    (String) objProperty.GetValue(obj, null), null);      }   }}

.Text가 발견되면 PropertyInfo 클래스의 GetValue 메서드를 사용하여 비즈니스 개체의 속성에서 값을 검색합니다. 그런 다음 컨트롤의 .Text 속성에 대한 SetValue 메서드를 사용합니다. 여기서는 또한 컨트롤의 속성을 typeof(String)로서 형식 검사하고 (String) 표기법을 사용하여 속성에서 값을 명시적으로 캐스팅합니다.

BindObjectToControls 메서드를 완성하기 위해 다른 일반 속성인 .Checked, .SelectedDate.Value도 처리해야 합니다. 아래 코드에서는 코드를 단순화하기 위해 컨트롤 속성 검색을 FindAndSetControlProperty라는 도우미 메서드에 래핑합니다.

if (control is ListControl) {   // ...}else {   // 컨트롤의 속성을 가져옵니다.   //   Type controlType = control.GetType();   PropertyInfo[] controlPropertiesArray =         controlType.GetProperties();   bool success = false;   success = FindAndSetControlProperty(obj,         objProperty, control, controlPropertiesArray,         "Checked", typeof(bool) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "SelectedDate", typeof(DateTime) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "Value", typeof(String) );   if (!success)      success = FindAndSetControlProperty(obj,               objProperty, control, controlPropertiesArray,               "Text", typeof(String) );}private static void FindAndSetControlProperty(object obj,   PropertyInfo objProperty, Control control,   PropertyInfo[] controlPropertiesArray, string propertyName,   Type type) {   // 컨트롤 속성을 반복합니다.   foreach (PropertyInfo controlProperty in         controlPropertiesArray) {      // 일치하는 이름과 형식을 확인합니다.      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 속성을 비즈니스 개체                  // 속성 값으로 설정합니다.         controlProperty.SetValue(control,                    Convert.ChangeType(                    objProperty.GetValue(obj, null), type) , null);         return true;      }   }   return false;}

일부 컨트롤에는 위 속성 중 둘 이상이 있을 수 있지만 여기서는 하나의 속성만 설정할 것이므로 위 속성 검사의 순서가 중요합니다. 예를 들어 CheckBox 컨트롤에는 .Text.Checked 속성이 모두 있습니다. 이 경우에는 .Checked 속성만 사용하고 .Text 속성을 사용하지 않을 것이므로 속성 검색 순서에서 .Checked를 앞에 둡니다. 각 경우에서 이름과 형식이 올바른 컨트롤 속성을 찾으면 컨트롤의 속성을 비즈니스 개체 속성의 값으로 설정합니다.

이 시점에서 BindObjectToControls 메서드는 완전하게 기능을 수행합니다. 이 메서드는 임의의 클래스와 컨트롤 조합을 사용하여 ASPX 폼 위의 어떤 위치에서나 호출할 수 있습니다. 이제 폼을 제출할 때 이와 반대의 기능을 수행하는 메서드를 만들어야 합니다. 컨트롤 속성의 값을 비즈니스 개체 값으로 설정하는 대신에 컨트롤에서 사용자의 입력을 나타내는 새 값을 검색해야 합니다.

프로세스를 반대로 수행: BindControlsToObject

BindControlsToObject 메서드에서는 앞서 수행한 것과 동일한 방식으로 비즈니스 개체에서 속성 목록을 검색하고 FindControl 메서드를 사용하여 개체 속성과 일치하는 ID를 가진 컨트롤을 찾습니다. 해당 컨트롤을 찾으면 값을 검색하여 비즈니스 개체에 반환합니다. 이 섹션에는 또한 ListControl에 대한 별도의 코드도 포함됩니다. ListControl에는 공용 인터페이스가 있기 때문입니다. 이 컨트롤에서 값을 검색하여 비즈니스 개체에 반환하기 위해 또 다른 도우미 메서드를 사용합니다.

public static void BindControlsToObject(object obj,   Control container) {   Type objType = obj.GetType();   PropertyInfo[] objPropertiesArray = objType.GetProperties();   foreach (PropertyInfo objProperty in objPropertiesArray) {      if (control is ListControl) {         ListControl listControl = (ListControl) control;         if (listControl.SelectedItem != null)            objProperty.SetValue(obj,                           Convert.ChangeType(list.SelectedItem.Value,                          objProperty.PropertyType), null);      }else {         // 컨트롤의 속성을 가져옵니다.         //         Type controlType = control.GetType();         PropertyInfo[] controlPropertiesArray =                      controlType.GetProperties();         bool success = false;         success = FindAndGetControlProperty(obj,                     objProperty, control, controlPropertiesArray,                     "Checked", typeof(bool) );         if (!success)            success = FindAndGetControlProperty(obj,                          objProperty, control, controlPropertiesArray,                          "SelectedDate", typeof(DateTime) );         if (!success)            success = FindAndGetControlProperty(obj,                           objProperty, control, controlPropertiesArray,                           "Value", typeof(String) );         if (!success)            success = FindAndGetControlProperty(obj,                          objProperty, control, controlPropertiesArray,                          "Text", typeof(String) );      }   }}private static void FindAndGetControlProperty(object obj,   PropertyInfo objProperty, Control control, PropertyInfo[]   controlPropertiesArray, string propertyName, Type type) {   // 컨트롤 속성을 반복합니다.   foreach (PropertyInfo controlProperty in         controlPropertiesArray) {      // 일치하는 이름과 형식을 확인합니다.      if (controlPropertiesArray.Name == "Text" &&               controlPropertiesArray.PropertyType == typeof(String)) {         // 컨트롤의 속성을 비즈니스 개체                  // 속성 값으로 설정합니다.         try {            objProperty.SetValue(obj,                           Convert.ChangeType(                           controlProperty.GetValue(control, null),                           objProperty.PropertyType) , null);            return true;         } catch {            // 폼 컨트롤의 데이터를                        // objProperty.PropertyType으로                        // 변환할 수 없습니다.            return false;         }      }   }   return true;}

이러한 두 개의 메서드가 완성되면 양식 코드 단순화 및 단축에서 설명한 것처럼 폼 구문이 간단해집니다. 모든 속성과 컨트롤에 대한 형식 변환 및 오류 수정은 자동으로 처리됩니다. BindObjectToControlsBindControlsToObject의 두 메서드를 사용하면 개발자는 매우 유연하게 폼을 작성할 수 있습니다. 또한 다음과 같은 몇 가지 일반적인 시나리오가 처리됩니다.

  • 새 속성이 비즈니스 개체에 추가되고 해당 속성을 폼에서 액세스해야 하는 경우에는 해당 ID가 새 속성의 이름으로 설정된 컨트롤을 페이지에 추가하면 됩니다. 나머지 작업은 FormBinding 메서드가 처리합니다.
  • 특정 속성에 사용되는 컨트롤의 형식을 변경해야 하는 경우(예: TextBox에서 타사 HTML 편집기 컨트롤로)에는 새 컨트롤에 .Text 등 앞에서 나온 속성 중 하나가 있는지 확인하면 됩니다. 그러면 폼은 이전과 마찬가지로 제대로 작동합니다.
  • 모든 TextBox 컨트롤을 사용하여 폼을 빠르게 생성할 수도 있습니다. 이 경우에도 입력은 올바른 비즈니스 개체 속성 유형으로 변환됩니다. 예를 들어 Calendar 컨트롤이나 타사 날짜 선택 컨트롤 대신에 TextBox 컨트롤을 사용할 수 있습니다. 사용자가 값 DateTime 문자열을 입력하는 한, Calendar 컨트롤의 .SelectedDate 속성에서와 마찬가지로 TextBox.Text 속성에 있는 값은 DateTime으로 변환됩니다. TextBox가 나중에 날짜 선택 컨트롤로 바뀌어도 논리는 이전과 동일하게 유지됩니다.
  • 모든 컨트롤을 Literal 컨트롤로 변경하여 "보기" 페이지를 빠르게 만들 수도 있습니다. TextBox에서와 마찬가지로 Literal.Text 속성은 비즈니스 개체 속성의 값으로 설정됩니다.
  • 실제 시나리오에서는 다른 데이터 형식과 사용자 지정 구성도 폼에 포함됩니다. 이러한 특수한 작업을 처리하기 위한 코드를 BindObjectToControlsBindControlsToObject의 호출 뒤에 배치할 수 있습니다.

성능 및 FormBinding 스키마 확장

성능 저하가 발생한다는 사실을 고려할 때 일부 개발자는 리플렉션을 사용하는 것이 그만한 가치가 있는지 궁금해할 것입니다. 저는 int DocumentID, bool Active, DateTime Created, int CategoryID, String Title, string Author 및 String htmlText의 7개 속성이 있는 개체를 사용하여 테스트한 적이 있는데 이 테스트에서 BindObjectToControls는 약 1/3밀리초가 걸렸고 BindControlsToObject는 약 1밀리초가 걸렸습니다. 이러한 값은 루프에서 BindObjectToControlsBindControlsToObject 메서드를 1000번 실행한 결과로 얻어진 것입니다. 일반적인 폼 추가 및 편집 시나리오에서 이 정도의 성능 저하는 심각한 문제를 야기하지 않습니다. 개발 속도와 유연성이라는 확실한 이점의 대가로는 충분한 것이죠.

이 메서드는 거의 모든 폼에서 작동하지만 경우에 따라서는 위 코드를 수정해야 할 수도 있습니다. 일부 시나리오에서 개발자는 위 속성 중 하나를 기본 인터페이스로 사용하지 않는 컨트롤을 사용할 수 있습니다. 이 경우에는 해당 속성과 유형을 포함하도록 FormBinding 메서드를 업데이트해야 합니다.

결론

두 개의 FormBinding 메서드인 BindObjectToControlsBindControlsToObject를 사용하면 양식 코드를 매우 단순하게 만들 수 있으며 최대한 유연하게 ASP.NET 폼을 개발할 수 있습니다. 이러한 사실이 저에게 도움이 된 것처럼 여러분께도 도움이 되기를 바랍니다.

관련 서적

 


저자 소개

John Dyer는 Dallas Theological Seminary 의 수석 웹 개발자로 일하면서 Telligent Systems 의 Community Server에 구축되어 있는 저명한 온라인 교육 프로그램을 관리하고 있습니다. 또한 널리 사용되고 있으며 모든 사람에게 무료로 제공되는 FreeTextBox  ASP.NET HTML 편집기를 만들었습니다. 여가 시간에는 DTS에서 신학 석사 과정을 공부하고 있으며, 2005년 1월 1일에 결혼할 예정입니다.

Posted by tornado
|