lecture Home > ASP.NET > .NET Board

.NET 게시판 강좌 - 김연진님 제공

   강좌 최초 작성일 : 2001년 09월 10일
   강좌 최종 수정일 : 2001년 09월 26일

   작성자 : Cassatt(김 연진)
   편집자 : Taeyo(김 태영)

   강좌 제목 : 드뎌.. 글쓰기 : write.aspx

write.aspx

이제 글쓰기란부터, 실제 웹 페이지를 만들어 보겠습니다. 우선 /bin 디렉터리에, 파일을 하나 더 만듭니다. 이름은 cstMessageBoard.cs로 합니다.

cstMessageBoard.cs:
using System;
using System.Data;
using System.Data.SqlClient;

namespace cstDB {
  
   public class MessageBoard : OrderedTableReader {

     public MessageBoard( String _tableName ) 
                   : base( _tableName, "seq" ) {}

     public bool AddMessage( 
                   String writer,
                   String email,
                   String title,
                   String pwd,
                   int mode,
                   String content )
     {
        String sql;
        bool ret=false;

        sql = "insert into "+tableName
            + "( writer,email,title,pwd,ip,writeDate,readed,mode,content) "
            + "values( @writer,@email,@title,@pwd,@ip,"
            + "getdate(), 0, @mode, @content )";

        SqlCommand cmd= new SqlCommand(sql, dbConnection );

        cmd.Parameters.Add(new SqlParameter("@writer",
                           SqlDbType.VarChar,20)).Value=writer;
        cmd.Parameters.Add(new SqlParameter("@email",
                           SqlDbType.VarChar,100)).Value=email;
        cmd.Parameters.Add(new SqlParameter("@title",
                           SqlDbType.VarChar,200)).Value=title;
        cmd.Parameters.Add(new SqlParameter("@pwd",
                           SqlDbType.VarChar,20)).Value=pwd;
        cmd.Parameters.Add(new SqlParameter("@ip",
                           SqlDbType.Int)).Value=cstUtil.GetClientIP();
        cmd.Parameters.Add(new SqlParameter("@mode",
                           SqlDbType.TinyInt)).Value=mode;
        cmd.Parameters.Add(new SqlParameter("@content",
                           SqlDbType.Text)).Value=content;

        try {
          cmd.ExecuteNonQuery();
          ret=true;
        }
        catch(Exception) {}
        return ret;
      }
   }
}

OrderedTableReader를 상속 받아서 MessageBoard 라는 클래스를 만드는 것이죠. 굳이 OrderedTableReader 라는 테이블을 끼워 넣은 것은, 그 클래스에 다른데서도 쓰일 몇개의 메서드를 넣을 예정이기 때문인데요, 다음 강좌에서 설명하게 됩니다.

MessageBoard에, 지금은 AddMessage 라는 메서드 밖에 없지만, DeleteMessage ( 글 삭제 ), ModifyMessage ( 글 수정 ), ReadPage ( 글 목록 ) 등의 메서드가 추가될 것입니다.

그리고 몇가지 자주 쓰일 함수를 넣을 cstUtil.cs 라는 파일을 만듭니다.

cstUtil.cs
using System;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;

namespace cstDB {
    
    public class cstUtil {
      public static void Die_DoScript( string msg,string script )
      {
        string umsg="<"+"script language="javascript" "+">"
               +"alert(\""+msg+"\");"
               +script
               +"<"+"/script"+">";
        HttpContext.Current.Response.Write(umsg);
        HttpContext.Current.Response.End();
      }

      public static void Die( string msg )
      {
        Die_DoScript( msg, "history.back()" );
      }

      public static void Die_Redirect( string msg, string url )
      {
        Die_DoScript( msg, "location.replace('"+url+"')" );
      }

      public static string Int32ToIP(Int32 ip) 
      {
        IPAddress ipa = new IPAddress(ip);
        return ipa.ToString();
      }

      public static Int32 IPToInt32(string ip)
      {
         return IPAddress.Parse(ip).Address;
      }

      public static Int32 GetClientIP()
      {
        return IPToInt32( HttpContext.Current.Request.UserHostAddress );
      }

      public static string RegExReplace(string str, 
                                        string regexpr, 
                                        string replacement)
      {
        Regex rex=new Regex(regexpr);
        return rex.Replace(str, replacement );
      }

      public static string TagReplace(string src, params string[] tags)
      {
        for( int i=0; i<tags.Length; i++) 
          src=RegExReplace( src, "(\\<\\/?\\s*)("+tags[i]+"[^\\>]*\\>)", "$1_$2" );
        return src;
      }

      public static string LinkURL(string str,string target)
      {
        return RegExReplace(str, 
                 "http\\:\\/\\/\\S+", 
                 "<a href='$0' target='"+target+"'>$0</a>" );
      }

      public static string LinkEmail(string str)
      {
        return RegExReplace( str, 
                 "\\S+\\@\\S+", 
                 "<a href='mailto:$0'>$0</a>" );
      }

      public static string Nl2Br (string src)
      {
        return src.Replace("\n","\n<br>");
      }
    };

};

이 파일을 컴파일해야 겠는데요, 앞 강좌에서 처럼 cmd를 실행시켜서 /bin 폴더로 이동한 후,

csc /t:library /out:cstDB.dll /debug+ /optimize+ cstDB.cs cstMessageBoard.cs cstUtil.cs

이렇게 컴파일하면 cstDB.dll 이 다시 만들어질 겁니다.

1. cstMessageBoard.cs

여기선 MessageBoard라는 객체가 정의되었고, Constructor(생성자)와 메서드 하나가 추가 되었습니다.

   public MessageBoard( String _tableName ) 
                 : base( _tableName, "seq" ) {}

앞 강좌에서 잠시 나왔지만, 객체 이름과 같은 메서드는 Constructor 혹은 생성자 라고 합니다. 이 메서드는 객체를 생성시킬때 실행하게 되죠. 위 처럼 정의했다면, MessageBoard mb = new MessageBoard("cstBoard") 식으로 생성하고 mb.AddMessage(...) 식으로 쓰게 됩니다. 위 생성자는 상위 개체( base, 즉 OrderedTableReader)의 생성자를 호출할 뿐입니다. MessageBoard 라는 개체를 만들려면 위처럼 테이블 이름을 정하도록 해놨는데요, 이것은 나중에, 이 소스 하나로 여러개의 게시판( 소위, Multi-게시판 )을 만들기 위해서 입니다.

     public bool AddMessage( 
                   String writer,
                   String email,
                   String title,
                   String pwd,
                   int mode,
                   String content )
    {
    ...
    }

두번째로 만든 메서드는 이번 강좌에서 쓰이게 될 AddMessage란 메서드인데요, 글쓴이, 메일주소, 제목, 암호, 형식( Html 태그 허용 여부 ), 내용등을 인수로 주면 그 값을 이용해서 DB에 저장하게 됩니다.

        String sql;
        bool ret=false;

        sql = "insert into "+tableName
            + "( writer,email,title,pwd,ip,writeDate,readed,mode,content) "            
            + "values( @writer,@email,@title,@pwd,@ip,"
            + "getdate(), 0, @mode, @content )";

        SqlCommand cmd= new SqlCommand(sql, dbConnection );

insert 문 사용해서 저장하게 되는데요, 입력일인 writeDate는 getdate() 함수로 입력 시간을 넣고, 조회 수인 readed는 0으로 초기값을 줍니다. 그 외의 @writer 처럼 @으로 시작하는 것은 다음 처럼 파라메터를 지정해서 값을 넣어주게 됩니다.

        cmd.Parameters.Add(new SqlParameter("@writer",
                           SqlDbType.VarChar,20)).Value=writer;
        cmd.Parameters.Add(new SqlParameter("@email",
                           SqlDbType.VarChar,100)).Value=email;
        cmd.Parameters.Add(new SqlParameter("@title",
                           SqlDbType.VarChar,200)).Value=title;
        cmd.Parameters.Add(new SqlParameter("@pwd",
                           SqlDbType.VarChar,20)).Value=pwd;
        cmd.Parameters.Add(new SqlParameter("@ip",
                           SqlDbType.Int)).Value=cstUtil.GetClientIP();
        cmd.Parameters.Add(new SqlParameter("@mode",
                           SqlDbType.TinyInt)).Value=mode;
        cmd.Parameters.Add(new SqlParameter("@content",
                           SqlDbType.Text)).Value=content;

이전보다 약간 복잡해졌네요. SqlCommand 개체의 Parameters 에 하나씩 추가하는 것인데요,

        cmd.Parameters.Add(new SqlParameter("@writer",
                           SqlDbType.VarChar,20)).Value=writer;

이건 좀더 길게 쓰면,

        param = new SqlParameter("@writer",SqlDbType.VarChar,20);
        param.Value=writer;
        cmd.Parameters.Add(param);

이렇게도 쓸 수 있습니다. 넘겨줄 값(파라메터)의 형식( 위에서 SqlDbType.VarChar ), 길이(위에서 20)등도 정하게 됩니다. IP 값은 다음에 설명할 cstUtil 개체에 정의해둔 GetClientIP() 라는 메서드로 현 사용자의 IP값을 넣습니다.

그리고 쿼리문을 실행합니다.

        try {
          cmd.ExecuteNonQuery();
          ret=true;
        }
        catch(Exception) {}
        return ret;

SqlCommand의 ExecuteNonQuery() 메서드로 실행합니다. 앞 강좌에서 설명했지만, ExecuteNonQuery는 결과 값이 없는 쿼리문 실행할때 쓰입니다.
여기 c#에서 종종 쓰일 에러 처리 구문인 try/catch 문이 쓰였는데요, 우선 쿼리문을 실행해 보고( try ), 에러가 나면 그 에러를 잡게(catch) 됩니다. catch에는 아무 내용이 없는데요, 이 메서드 맨 앞에 ret 값을 false로 해놓았으니, 에러가 나면 false가 리턴되게 됩니다. 제대로 실행되면 true가 리턴되고요. 사용할때는 if(AddMessage(...)) { /*성공처리*/ } else { /*실패처리*/ } 식으로 쓰면 되겠죠.

* 디버깅할땐, try - catch 없이 하시는 것이 나을 겁니다. 다른 에러도 catch 되어 버려서요. ^^;

* 컴포넌트 dll 디버깅할때, Response.Write를 못써서 저는 throw new Exception("lalala"); 같은 문장을 실행시킵니다. 위에서 sql 문 보고 싶으면 throw new Exception(sql) 이 되겠죠. 결과는 해보시면 압니다. - Response.Write와 비슷한 효과가 납니다. 이외에, 아직은 잘 몰라 설명못드립니다만, Trace.Write() 이용하는 방법이 있으니까요, Trace 도 찾아보시기 바랍니다.

여기서 SqlParameter로, 쿼리문 파라메터를 하나하나 지정하도록 해놓았는데요, 전처럼, sql = "insert into .... "values( '"+writer+"','"+email+"'....)" 이렇게, 직접 쿼리문에 작은 따옴표로 둘러쌓아서 값을 넣어도 됩니다. 다만 이렇게 SqlParameter 개체를 하나하나 추가하는 방식은, 소스는 약간 길어지지만 몇가지 장점이 있습니다.

  1. varchar형 값 안에 작은따옴표(') 가 있어도 제대로 변환됩니다. 즉, 전처럼 replace로 작은따옴표 하나를 작은따옴표 두개로 바꾸지 않아도 됩니다.
  2. 만약 정해진 길이보다 길면, 그 길이에서 잘리기 때문에 에러가 안납니다.
예를들어 DB에 varchar(2)으로 정해놨는데, 'aaa' 처럼 세글자면 "String or Binary data would be truncated" 라는 에러가 나고 종료되죠. 그런데 위에서 처럼 SqlParameter에 길이를 정해두면 그 길이에서 자동으로 잘립니다. ( 즉, 'aa'만 들어갑니다 )

그런데 이게, 한글 문제는 여전히 남아있습니다. SqlDbType.VarChar에 길이 10으로 해도, 한글 10자면 20byte가 되어 에러가 나게 됩니다. VarChar 대신 NVarChar로 하면 됩니다만, 낭비하는 것 같아서 사용하기 좀 그렇더군요(영어도 2byte로 들어갑니다).
제 경우 보통, 테이블은 필드길이를 두배로 잡아서, VarChar(20)으로 잡았으면, SqlParameter에는 길이를 10으로 합니다. 그럼 에러는 안납니다만, 영문도 10자에서 잘리는 문제가 있죠. 한글/영문따라 실제 바이트수 구해서 잘라넣을 수 있겠습니다만, 복잡하기만 한것 같아 다루지 않습니다.

cstUtil.cs

여기에는 몇가지 종종 쓰이게될 함수들을 넣어놨습니다. 모두 static 함수들인데요, static이라서 new로 생성 안하고, cstUtil.Die("...") 식으로 쓰게 됩니다. 앞 강좌에서 설명드렸듯, 예전의 전역 함수/변수 쓰고 싶으시면 원하는 개체에 몰아넣고, 선언할때 static 붙이면 됩니다.

* 닷넷 도움말 보시다가, 클래스 멤버란에, Public static (shared) methods/properties 로 분류 되어 있는 것은 클래스이름.메서드이름 으로 호출하게 됩니다. 한편 Public Instance methods/properties 로 분류되어 있는 것들은 new로 생성한 후, 생성한개체이름.메서드이름으로 호출하게 됩니다. 도움말 보실때 참고하세요.
* MessageBoard mb = new MessageBoard("cstBoard") 식으로 생성하면, MessageBoard는 개체고, mb 는 그 개체의 인스턴스Instance라고 부릅니다. 저도 좀 헷갈리게 사용하고 있고, 헷갈립니만, 일단 Instance 뜻은 그렇습니다.
이경우 static 메서드는 MessageBoard.xxx() 식으로 호출할테고, Instance 메서드는 mb.xxx() 식으로 호출하겠죠

세부 설명은 생략하고요, 어떤 함수들인지만 설명하고 넘어가겠습니다.

      public static void Die( string msg )
      public static void Die_DoScript( string msg, string script )
      public static void Die_Redirect( string msg, string url )

Die는 PHP die함수에서 따온 이름인데요, msg를 alert 창으로 띄우고, history.back()으로 이전페이지로 돌아가는 기능을 합니다. Die_DoScript( msg, script )는 msg를 alert 창으로 띄우고, script를 실행, Die_Redirect(msg, url)은 msg를 alert창으로 띄우고 url 로 이동합니다. 페이지 에러 처리를 위한 메서드 들입니다.

      public static string Int32ToIP(Int32 ip) 
      public static Int32 IPToInt32(string ip)

Int32ToIP는 정수로된 ip 값을 문자열로, IPToInt32는 문자열로 된 ip를 정수로 바꿉니다. 아이피는 아시다시피, 127.0.0.1 처럼, 0 ~ 255의 숫자 네개가 점으로 구분되어 있어서, 정수 4byte 로 바꿀 수 있습니다. DB 저장할때 몇byte라도 줄여보고자 만든 함수입니다.

* 이거 원랜 좀 긴 함수였는데, 강좌 쓰다 찾아보니 IPAddress라는 간단한 클래스가 있더군요. ^^; 뭐든 생각나면 우선 MSDN을 찾아봐야겠습니다.

      public static Int32 GetClientIP()
      {
        return IPToInt32( HttpContext.Current.Request.UserHostAddress );
      }

GetClientIP() 는 현재 사용자의 IP를 구하는 메서드인데요, AddMessage 메서드에서 아이피 넣을때 쓰입니다. 전처럼 쓰자면, Request.ServerVariables["REMOTE_ADDR"] 이 되겠는데, 찾아보니 UserHostAddress란 프로퍼티를 따로 제공하네요. 지금 이 소스가 페이지가 아니라 컴포넌트(dll) 이기 때문에, 현재 사용자의 아이피를 알아내기 위해서 앞에 HttpContext.Current.을 붙였습니다.

 public static string RegExReplace(string str, string regexpr, string replacement)

이것은 정규식(RegularExpression)을 이용해서 문자열을 치환(Replace) 하는 함수인데요, 이 자체보다는 다음에 설명할 메서드에서 호출되어 쓰이게 됩니다.

      public static string TagReplace(string src, params string[] tags)
      public static string LinkURL(string str,string target)
      public static string LinkEmail(string str)
      public static string Nl2Br (string src)

이것은 글 보기(view.aspx) 에 쓰일 메서드들입니다.

TagReplace는 지정한 태그를 약간 바꿔서 못쓰게(?) 합니다. 예를들어, s = cstUtil.TagReplace( "<xmp> lalala", "xmp") 이렇게 하면 s에 "<_xmp> lalala" 로, 태그 앞에 _ 가 들어가서, 태그가 실행이 안됩니다. xmp 같은 요주의(?) 태그를 써도 실행 안되도록 하기 위해 만든 메서드입니다. 태그가 여러개면 s = cstUtil.TagReplace( s, "xmp", "pre", "table", "tr", "td" ) 식으로 여러개 써도 됩니다.

LinkURL, LinkEmail 은 글 내용중에 http://www.corebiz.co.kr , cassatt@hanmir.com 처럼 url이나 메일주소에 링크를 걸기 위해 만든 메서드입니다. Nl2Br은 PHP에서 따온 이름인데요, 리턴 코드를 <br>로 바꾸는 작용( New Line to <BR> ) 을 합니다. ( 어디에 쓰일지는 아시겠죠? )


이제 글쓰기 - write.aspx, 실제 페이지를 작성할 차례입니다.

write.aspx:
<%@ Page Language="C#" EnableSessionState="false" Debug="true" %>
<%@ Import Namespace="cstDB" %>
<%@ Register TagPrefix="cst" TagName="header" Src="pagelet/cstHeader.ascx" %>
<%@ Register TagPrefix="cst" TagName="footer" Src="pagelet/cstfooter.ascx" %>

<script runat=server language="C#">

  void SubmitBtn_Click( Object src, EventArgs e ) {
     if( Page.IsValid ) {
        using( MessageBoard cmb=new MessageBoard("cstBoard") )
        {
            bool r=
            cmb.AddMessage( txtWriter.Text,
                            txtEmail.Text,
                            txtTitle.Text,
                            txtPassword.Text,
                            rdoMode.SelectedIndex,
                            txtContent.Text );
          }
        }
        Response.Redirect("list.aspx");
     }
  }

</script>

<html>
<head>
<title>게시판</title>
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
<meta name="Author" content="cassatt">
<link rel="stylesheet" type="text/css" href="stylesheets/cstBoard.css" >
<link rel="stylesheet" type="text/css" href="stylesheets/write.css" >
</head>
<body bgcolor="#ffe0e0">
<cst:header title="자유게시판" runat="server"  />

<form id=writeForm runat="server" >

<table width=600 border=0 cellspacing=0 cellpadding=0 align=center>
  <tr>
    <td class="cwTitle" >  글쓰기</td>
  </tr>
</table>

<!-- main table start -->

<table width=600 border=0 cellspacing=0 cellpadding=4 align=center class="cwMainBox">
  <tr>
    <td class="cwName" nowrap>이름</td>
    <td nowrap>
      <asp:RequiredFieldValidator 
         id="txtWriterValidator" 
         runat="server"
         ControlToValidate="txtWriter" 
         ErrorMessage="이름"
         Display="Static" 
         Text="*" />
  </td>
  <td class="cwField" width=1000>
    <ASP:TextBox 
         id=txtWriter 
         name=txtWriter 
         size=10 maxlength=10 
         class="cwTextBox" 
         style="width:200" 
         runat="server" />
    </td>
  </tr>
  <tr>
    <td class="cwName">메일</td>
    <td nowrap>
      <asp:RegularExpressionValidator 
         id="txtEmailValidator" 
         runat="server"
         ControlToValidate="txtEmail"
         ValidationExpression="^\s*\S+\@\S+\s*$"
         ErrorMessage="메일주소"
         Display="Static" 
         Text="*" />
  </td>
  <td class="cwField">
    <ASP:TextBox 
         id=txtEmail 
         name=txtEmail 
         size=10 
         class="cwTextBox" 
         style="width:200" 
         runat="server" />
  </td>
  </tr>
  <tr>
    <td class="cwName">제목</td>
    <td nowrap>
      <asp:RequiredFieldValidator 
         id="txtTitleValidator" 
         runat="server"
         ControlToValidate="txtTitle" 
         ErrorMessage="제목"
         Display="Static" 
         Text="*" />
  </td>
  <td class="cwField">
    <ASP:TextBox 
         id=txtTitle 
         name=txtTitle 
         size=50 
         runat="server" 
         class="cwTextBox" 
         style="width:100%" />
  </td>
  </tr>
  <tr>
    <td class="cwName">내용</td>
    <td nowrap> </td>
  <td class="cwField">
    <asp:TextBox 
         TextMode="multiline" 
         id=txtContent 
         class="cwTextBox" 
         rows=15 cols=50 
         runat="server" 
         style="width:100%; overflow:hidden;" />
  </td>
  </tr>
  <tr>
    <td class="cwName">형식</td>
    <td> </td>
  <td class=cwField width=1000>
    <asp:RadioButtonList 
      id="rdoMode" 
      RepeatLayout="flow"
      RepeatDirection="Horizontal"
      class="cwField" 
      runat="server">
      <asp:ListItem>텍스트</asp:ListItem>
      <asp:ListItem>HTML</asp:ListItem>
    </asp:RadioButtonList>
  </td>
  </tr>
  <tr>
    <td class="cwName" nowrap>비밀번호</td>
    <td nowrap>
      <asp:RequiredFieldValidator 
         id="txtPasswordValidator" 
         runat="server"
         ControlToValidate="txtPassword" 
         ErrorMessage="비밀번호"
         Display="Static" 
         Text="*" />
  </td>
  <td class="cwField">
    <asp:TextBox 
         TextMode="Password" 
         id=txtPassword 
         size=10 maxlength=10 
         class="cwTextBox" 
         style="width:200" 
         runat="server"/>
  </td>
  </tr>
  <tr>
    <td colspan=2> </td>
    <td class="cwName">
      <asp:ValidationSummary
        id="valSummary"
        HeaderText="<font color=darkred>다음을 정확히 입력해주세요</font> : "
        DisplayMode="SingleParagraph"
        runat="server"/>
    </td>
  </tr>
</table>

<table width=600 border=0 cellspacing=0 cellpadding=4 align=center>
  <tr>
    <td>
    <input type=reset class="cwResetBtn" value="다시쓰기" />
  </td>
  <td align=right>
    <asp:Button 
         class="cwSubmitBtn" 
         Text="글 쓰 기" 
         OnClick="SubmitBtn_Click" 
         runat="server" />
  </td>
  </tr>
</table>

<!-- main table end -->

</form>

<cst:footer runat="server" />

</body>
</html>

디자인은 모두 css 파일로 빼놓았습니다. /pagelet/Header.ascx 나 /pagelet/Footer.ascx 는 다운받은 소스를 참조하시기 바랍니다. ( 보시면 이해가 되실 겁니다 )

<form runat="server">

    <ASP:TextBox 
         id=txtWriter 
         name=txtWriter 
         size=10 
         maxlength=10 
         class="cwTextBox" 
         style="width:200" 
         runat="server" />
...
</form>

우선, 서버에서 실행되는 폼( <form runat="server"> ) 이 사용되고요, <input type=text > 대신 위처럼, ASP:TextBox라는 다른 이름의 컨트롤이 쓰이고 있습니다. 마지막에 붙은 runat="server" 라는 것이 이 태그가, 서버에서 실행된다는 것을 말해주죠. 이처럼, html 태그 외에도 asp.net에서 제공하는 서버 컨트롤들이 여럿 존재합니다. 거의 모든 html 컨트롤은 다른 이름의 서버 컨트롤 들을 가지고 있습니다.

<input type="text">     -> <ASP:TextBox runat="server" />
<input type="password"> -> <ASP:TextBox TextMode="Password" runat="server"/>
<textarea></textarea>   -> <ASP:TextBox TextMode="MultiLine" runat="server"/>
<input type="button">   -> <ASP:Button runat="server"/>
<input type="radio">    -> <ASP:RadioButton runat="server"/>
                      혹은 <ASP:RadioButtonList runat="server">...</ASP:RadioButtonList>

등 입니다. 이것은 html 태그의 단순 대체 정도입니다만, 여기에 더해서, 다양한 서버 컨트롤 들이 존재합니다.

* <form runat="server">에서 보듯, <input type="text" runat="server" /> 처럼 다른 html 폼 컨트롤도 runat="server" 만 붙여줘도 서버 컨트롤이 됩니다. 사용법이 약간 다르고요( Text 대신 Value가 사용된다거나,...), 큰 장점이나 단점은 ... 아직 잘 모르겠습니다. -.-

* <ASP:TextBox runat="server" /> 여기서 태그가 /> 로 끝나는데요, </ASP:TextBox> 식으로, 닫는 태그가 없이 태그하나일땐 이처럼 꼭 /> 로 끝내야 합니다. xml 형태인데, 서버 컨트롤은 이 룰을 지켜야 합니다.

아무튼, "서버" 컨트롤, 여기서 "서버"는 무엇을 뜻하는 것인가? 간단히 말해서, 서버에서 실행되는 소스에서 txtWriter.Text 처럼 해당 컨트롤의 값을 그냥 가져올 수도 있고,

<asp:Button ... OnClick="SubmitBtn_Click" runat="server" />

이처럼 Button을 클릭했을때( OnClick 이벤트 시에 ) 서버에서 무슨 일을 하는 것이 가능하다는 얘기입니다. 서버에서 실행되니까요.

사용하는 입장에선, 버튼 클릭시에 (OnClick) 해당 컨트롤의 값으로( txtWriter.Text ... ) 무엇을 하면 그만입니다. 위 소스가 복잡해 보일지 몰라도 줄기는 간단합니다. 버튼 OnClick 시에 SubmitBtn_Click 이란 메서드 실행하고, 그 메서드에서 해당 컨트롤 값으로 AddMessage를 호출하는 것이죠. 마치 javascript 쓰듯, 서버스크립트를 쓰게 된 것입니다.

* 그렇다 해도, 클라이언트/서버가 동시에 실행되는 게 아니라는 것, 서버에서 html 페이지를 만들어서 클라이언트로 전송하고, 클라이언트에서 입력받아 서버로 전송되는 로직을 이해할 필요가 있다는 생각이 듭니다.

정말로 javascript처럼, OnClick 즉시 무엇이 실행되고, 텍스트 박스에 입력된 값을 직접 가져오고 하는 것이 아닙니다. 실행해서 소스보기 해보시면 아시겠지만, hidden 필드에 원래 입력한 값을 인코딩해서 저장하고, 그 값을 다시 서버로 전송하는 것을 반복하는, 약간 복잡한 로직에 의해 이루어집니다. 이걸 완전히 잊고 코딩해도 될만큼이면 상관없겠지만, 나중에 Page.IsPostBack 같은 프로퍼티 할때 어차피 다루게 됩니다.

게시판 답변할때, ASP에서 alert 창을 못띄우는가, 혹은 자바스크립트 변수를 asp에서 못쓰는 가 하는 질문을 의외로 여러번 보았습니다. 그때마다 ASP는 html/javascript 만드는 기계다, ASP가 보기에 자바스크립트는 그저 text 일 뿐이다, 라는 식으로 답을 했었는데요, asp.net에서 마치 OnClick 시에 서버스크립트 메서드가 실행되는 식으로 되어서, 초보자는 더 이해하기 어렵게 된 것 아닌가 하는 생각이 드네요.

Form Validation - 폼 유효성검사

서버 컨트롤에는 여러가지가 있습니다. 그중에, 글쓰기란에서, 유용하게 쓰이는 것이 Validator Control이라는 것입니다.

      <asp:RequiredFieldValidator 
         id="txtWriterValidator" 
         runat="server"
         ControlToValidate="txtWriter" 
         ErrorMessage="이름"
         Display="Static" 
         Text="*" />

RequiredFieldValidator 란, ControlToValidate 에서 지정한 id에 해당하는 컨트롤에 값을 넣었는지 체크하는 컨트롤입니다. 글쓰기란에선 이름, 제목, 비밀번호가 필수 사항이기 때문에 이 컨트롤을 사용하게 되죠. 만약 값을 넣지않고 버튼을 누르면 Text 에 지정한 내용(위에서 *)이 그 위치에 출력됩니다. ErrorMessage는 나중에 설명할 ValidationSummary 위치에 출력되게 됩니다.

어느 필드가 필수 입력항목일때, 전엔 submit 버튼 클릭시 ( 보통 form 태그의 onsubmit 이벤트 이용 ) 자바스크립트에서 입력했는지 체크했었죠. 그 체크하는 루틴을 asp.net에선 서버컨트롤로 제공하는 겁니다. 어느 필드를 필수 입력하도록 하기 위해선 위처럼, 필요한 validator 컨트롤(위에서 RequiredFieldValidator ) 를 만들어두고, ControlToValidate에 해당 컨트롤의 id 값을 넣어주면 끝입니다. ( asp.net에선 name 대신 id로 컨트롤을 구분합니다 ) 이 컨트롤은 클라이언트 뿐 아니라 서버에서도 체크하기 때문에, 전처럼 서버 스크립트에도 같은 내용을 써줄 필요가 없습니다. ( html 직접 만들어서 submit 해도 안된다는 얘기입니다 ) 그리고 VS.net을 쓴다면 단순 Drag-drop만으로도 처리가 됩니다.

      <asp:RegularExpressionValidator 
         id="txtEmailValidator" 
         runat="server"
         ControlToValidate="txtEmail"
         ValidationExpression="^\s*\S+\@\S+\s*$"
         ErrorMessage="메일주소"
         Display="Static" 
         Text="*" />

RegularExpressionValidator는 Unix에서 전통적으로 쓰였던, 정규식 ( Regular Expression ) 을 이용해서, 컨트롤의 내용을 제대로 입력했는지 체크하는 컨트롤입니다. 사용법은 RequiredFieldValidator와 거의 같으며, ValidationExpression에 입력 규칙(정규식)을 지정하게 됩니다. 위에 쓰인 것은 메일주소용 정규식인데요, ^\s*\S+\@\S+\s*$ 이 뜻을 말로 설명하면,
문자열시작(^) 후, 공백(\s)이 0개이상(*) 나오고, 공백아닌글자(\S)가 1개 이상(+) 나오고, @ (\@) 이 나오고, 공백아닌글자(\S)가 1개이상(+) 나오고, 공백글자(\s)가 0개 이상나오고 끝나는($) 문자열
을 말합니다. ^^; 그러니까, xx@xx, xx@xx.com x@x 같은 것은 맞고, xx, xx@, @xx 는 틀리게 됩니다. 중간에 @기호만 있으면 정확한 메일주소로 간주하죠. 정규식은 이처럼, 문자열이 어떤식으로 구성되는가, 하는 규칙을 나타내는 식인데요, 여기에는 간단히 쓰였습니다만, 다양한 방식으로 응용할 수 있기 때문에, 거의 모든 형태에 적용할 수 있다 해도 과언이 아닙니다. 예를들어,

  • 전화번호 : 0\d{2,3}\-\d{2,4}\-\d{3,4}
  • 휴대폰번호 : 01[16789]\-\d{2,4}\-\d{3,4}
  • 주민등록번호: \d{6}\-\d{7}
  • domain : http\:\/\/[\w\-]+(\.\[\w\-]+)+
이런 식으로, 응용하기 나름이죠. 아마도 RequiredFieldValidator 다음으로 많이 쓰이게 될 Validator 컨트롤이 아닐까 합니다. 정규식은 약간 어렵지만, 익숙해지면 참 유용합니다. 여기 뿐 아니라, 앞에 cstUtil 에서 잠시 보았지만 문자열 처리할때도 자주 쓰이게 됩니다.

* 메일주소는 필수가 아니라서, RequiredFieldValiator를 쓰지 않았습니다만, 만약 메일주소도 필수라면, RequiredFieldValidator를 하나 더 써주면 됩니다. 모든 필수 입력 컨트롤에는 RequiredFieldValidator를 써줘야 합니다.

이외에도, 여기 다루지 않았습니다만, 두 값 비교할때 쓰이는 CompareValidator 는 비밀번호와 비밀번호 확인 값 비교할때 유용하게 쓰이고요, RangeValidator 를 이용해서 값이 지정한 범위 내인지 체크할 수 있습니다. 또한, 그외 너무나(?) 특이한 것은 CustomValidator로 정의해서 할 수 있습니다. CustomValidator는 나중에 글지우기(delete.aspx)에서 다루겠습니다.

  <asp:ValidationSummary
    id="valSummary"
    HeaderText="<font color=darkred>다음을 정확히 입력해주세요</font> : "
    DisplayMode="SingleParagraph"
    runat="server"/>

ValidationSummary는 Validator 컨트롤로 체크한 내용을 한곳에 목록으로 출력할때 쓰입니다. 앞에서 보았지만, <asp:RequiredFieldValidator ... ErrorMessage="메일주소" Text="*" /> 여기서 Text와 ErrorMessage가 쓰였는데요, 입력한 내용에 잘못이 있을때, Text는 컨트롤이 있는 그 자리에 찍히고, ErrorMessage는 ValidationSummary 자리에 모두 모여서(?) 출력됩니다. ShowMessageBox="true" ShowSummary="false" 값을 주면 화면에 표시하는 대신 메시지 박스로 출력할 수도 있습니다.

Validator 컨트롤 들은 써보시면 아시겠지만, 아주 간편합니다. 장점은 스크립트를 작성할 필요가 없다, 클라이언트 뿐아니라 서버에서도 체크하므로 확실하다, VS.NET 쓰면 Drag-Drop으로 된다, 브라우저가 무엇인지는 신경 안써도 된다는 등이 되겠습니다.

다만, 제 생각에 약간의 단점이 있는데요, 첫번째로, 내용을 체크하는 것이, IE 5 이상에서는 클라이언트에서도 되지만, 다른 브라우저에서는 서버에서만 됩니다. 즉, 버튼 클릭하면 서버까지 자료가 전송되었다가, 잘못이 있으면 다시 클라이언트로 재전송 되어야 한다는 것이죠. 모뎀 쓰는 NN 사용자는 좀 귀찮을것 같습니다. 어차피 javascript 쓸거, NN이나 IE4에서도 되었으면 좋겠네요. 그리고 두번째로, 이것이 매우 확장성 있기는 한데, 주민등록번호 같은 특이한 것 하려니 좀 어렵더군요. (앞자리 뒷자리를 나누면 - 깔끔한 방법이 잘 생각이 안납니다 ) 쉬운 한편, 특이한 것 하려니 좀 어렵다는 겁니다.

* 이것 쓰려면 클라이언트 스크립트가 웹사이트에 있어야 합니다. /aspnet_client 란 폴더에 저장되어 있습니다. 닷넷 깔면 자동으로 설치되지만, 새로 웹사이트 만들면 수동으로 설치해야 합니다. 그러려면 cmd 상에서 aspnet_regiis -c 를 실행하시거나, 루트 폴더에 있는 aspnet_client 폴더를 해당 사이트 루트로 복사하시면 될겁니다.

* 여기서는 체크 안합니다만, <asp:TextBox TextMode="multiline" > 인경우, 즉 html에서 <textarea></textarea>로 표현되는 것을 RequiredFieldValidator이용해서 체크하려하면, 체크가 잘 안될겁니다. 이건 닷넷 beta 2의 버그라 생각되는데요, 굳이 하셔야 할 상황이면, 제경우 /aspnet_client/system_web/1_0_2914_16/WebUIValidation.js 에서 ValidatorTrim 함수를

function ValidatorTrim(s) {
   return s.replace(/(^\s+)|(\s+$)/g,"");
}
이렇게 바꿔서 임시로 해결해서 쓰니 참고하세요.

이제 Validator 컨트롤로 체크했으니, 버튼 클릭시 실행될 코드를 보도록 하겠습니다.

<asp:Button 
     class="cwSubmitBtn" 
     Text="글 쓰 기" 
     OnClick="SubmitBtn_Click" 
     runat="server" />

우선 asp:Button OnClick에 실행될 함수 이름(위에서 SubmitBtn_Click)을 주고, 그 함수 이름으로 코드를 적으면 됩니다. 함수 이름은 어떤 것이든 상관없습니다.

  void SubmitBtn_Click( Object src, EventArgs e ) {
     if( Page.IsValid ) {
        using( MessageBoard cmb=new MessageBoard("cstBoard") )
        {
            bool r=
            cmb.AddMessage( txtWriter.Text,
                            txtEmail.Text,
                            txtTitle.Text,
                            txtPassword.Text,
                            rdoMode.SelectedIndex,
                            txtContent.Text );
          }
        }
        Response.Redirect("list.aspx");
     }
  }

Validator 컨트롤은 앞서 잠깐 말했듯, 클라이언트에서 뿐 아니라 서버에서도 체크합니다. 만약 체크해서 제대로 입력했으면 Page.IsValid 가 true 값을 갖게 됩니다. 위의 if문은 그러한 의미입니다. 체크해서 값이 정확할 때만 (즉, 이름,제목, 비밀번호 모두 입력하고, 메일주소를 입력한 경우, 값이 정확했다면 ) 글을 DB에 저장하는 것이죠.

using은 강좌4에서 잠시 설명했습니다만, {} 블럭을 빠져나갈때 using 안에서 생성한 개체 ( 위에서 cmb ) 의 Dispose() 메서드를 자동으로 호출해줍니다. MessageBoard의 상위 개체인 DBConnectedObject에 정의되어 있듯, 이경우엔 DB Connection을 Close() 하게 됩니다.

그외에는 서버컨트롤 값으로 앞에서 정의한 MessageBoard.AddMessage 메서드를 호출하는 것 밖에 없습니다. rdoMode.SelectedIndex 는, 설명 안하고 넘어갔습니다만( ^^; )소스에서 RadioButtonList 가 쓰였는데요, 그 컨트롤은 라디오버튼을 그룹지어 사용할때 쓰는데, 속성중 SelectedIndex는 몇번째가 선택되었는지를 나타냅니다. javascript에서 select 박스 쓰는 것과 비슷한 방식입니다.
그리고 AddMessage 호출 후, Response.Redirect로 앞으로 만들게 될 list.aspx 로 이동합니다.

우선은 이렇게 만들어 실행해서, Query Analyser에서 확인했을때 테이블에 값이 들어가 있으면 성공입니다.

이 write.aspx는 소스를 약간 바꿔서, 글 수정에도 사용하게 됩니다. 그 내용은 다음에 하도록 하겠습니다.

제가 설명을 잘하고 있나 모르겠네요. 페이지의 로직을 다시한번 요약해보죠.

  • <asp:TextBox ... runat="server">, <asp:RadioButtonList ... runat="server"> 등의 서버컨트롤을 이용해서 내용을 입력받는데,
  • <asp:RequiredFieldValidator ... runat="server">, <asp:RegularExpressionValidator ... runat="server"> 등의 Validator 컨트롤을 이용해서 내용을 정확히 입력했는지 체크합니다.
  • 입력된 내용이 모두 정확하면 Page.IsValid가 true가 됩니다.
  • 입력된 내용이 정확하지 않으면 해당 Validator 컨트롤 위치엔 그 컨트롤의 Text값이 나오고, <asp:ValidationSummary ... runat="server"> 컨트롤 위치에 ErrorMessage 값이 모여서 출력됩니다.
  • <asp:Button ... runat="server"> 의 OnClick 이벤트를 정의해서, 버튼 클릭시, SubmitBtn_Click이란 메서드를 호출하도록합니다.
  • SumitBtn_Click 함수에선 우선 입력한 내용이 정확한지 확인해서(Page.IsValid가 true인지 확인) 정확하면 데이터를 저장한 후 글 목록페이지(list.aspx) 로 이동합니다.
이번 강좌에서 제가 중요하게 생각하는 부분은,
  • 쿼리문 실행시 Parameter 값 설정하기
  • 서버컨트롤, 특히 Validator 컨트롤 사용하기
  • asp:Button의 OnClick으로 이벤트 정의하기
  • Page.IsValid
등이 되겠습니다.

이렇게 만들어서, css를 적용하면 화면이 다음과 같습니다. 아무 내용도 입력안하고 "글쓰기" 버튼 클릭한 화면인데요, 이름, 제목, 비밀번호가 필수라서 옆에 빨간색 별표(Validator컨트롤의 Text에 지정한 내용)가 표시되어 있습니다. 그리고 ValidationSummary 위치에 에러 내용(Validator컨트롤의 ErrorMessage)이 정리되어 출력됩니다.

다음엔 글목록 보여주는 부분( list.aspx)을 하겠습니다.

 

Back