관리 메뉴

새로운 시작, GuyV's lIfe sTyle.

닷넷 게시판 만들기 Part 41 본문

ⓟrogramming/asp.net 게시판

닷넷 게시판 만들기 Part 41

가이브 2011.06.01 16:38


2011/05/13 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 31
2011/05/16 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 32
2011/05/18 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 33
2011/05/19 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 34
2011/05/23 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 35
2011/05/25 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 36
2011/05/26 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 37
2011/05/27 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 38
2011/05/30 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 39
2011/05/31 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 40



1. 닷넷 개발환경 준비, 테스트
2. 닷넷 알아보기 [7/7]
3. asp.net 컨트롤 [10/10]
4. 데이터베이스(DB) [7/7]
5. 닷넷 게시판을 만들어보기 전에.. [4/4]
6. 게시판 만들기 [11/..]


6개의 Label 컨트롤을 배치하고 자료를 넣어주면 다음처럼 원하는 결과물이 출력되겠다.




여기서 잠시 쉬어가도록 하자. ^^

지금까지 신경써서 해보신 분이라면 결국 게시판 만들기는 'DB 입출력'과 '결과로 출력'하는 것이 분리되어 있다는 느낌을 받으시리라 생각한다. 만약에 친구랑 같이 게시판을 만든다고 가정하면 한명은 자료처리(DB설계, 라이브러리)를 맡고, 한명은 이쁘게 꾸며지는 aspx 디자인에 친구가 만들어준 라이브러리 명세서를 가지고 자료를 끼워맞춰주면 되겠다.

이 웹(Web)이라는게 페이지가 열리면 그 자체로 모든 것이 끝이 난다고 생각하면 되는데, 끝난 녀석을 계속 유지하기 위해서 GET(또는 POST) 으로 넘기는 변수들(우리 게시판에서처럼 카테고리 c, 글의 고유번호 n과 같은..)이 존재하고 이런 것들은 모두 사용자에게 이미 감추려고 해도 감추지 못하는 것을 알 수 있다. 회원 로그인에 사용된 세션변수(또는 어플리케이션 변수)와 같은 것들은 서버측에 자료를 계속해서 유지해야 되기 때문에 남용할 수도 없다. 
또한 이른바 말하는 "해킹"이라는 것을 막기 위해서는 개발자가 일어날 수 있는 문제점을 모두 알아야 한다는 것이다. 사실 이렇게 무언가를 만든다는 것은 어디서 참고를 하고 라이브러리를 가져와서 사용하는 등의 수단을 이용해 쉽게 할 수 있겠지만 중요한 것은 그 뒤에 일어날 수 있는 오류나 예외에 관련한 지식이 없다면 계속 프로그램을 사용하기가 어려울 것이다. 결국 만든 사람은 자신이 만든 프로그램에 대해 완벽하게 이해를 하고 있어야 한다.

게시판에는 자료를 넣고, 빼고, 수정하고, 삭제하는 "자료 처리 방법"에 대한 모든 기능들이 들어가 있는, 학습용으로 매우 좋은 주제가 될 수 있다. 만들어보면 끝이 아니라, 필자는 여러분들이 게시판을 이해한다면 웹에서(뿐만 아니라) DB를 이용한 프로그램을 전반적으로 만들 수 있게 된다고 자신한다. 그러므로 자기 것으로 꼭 만들길 바란다. 그리고 프로그래밍의 결과는 토시하나 틀림없이 같을 수는 있어도 과정은 결코 정답이 있을 수 없다. 다른 사람의 것과 비교할 필요가 없으며, 언제나 자신의 프로그램에 대해 만족하지말고 계속해서 변화해주며 더욱 완벽한 프로그램을 만들기 위해 노력하길 바란다. 절대 완벽한 프로그램은 존재하지 않는다. (몇 억짜리 소프트웨어도 오류 패치를 내놓고 기능이 보안된 업그레이드를 내놓는다)



이제 댓글 부분을 처리하도록 하자. 댓글 쓰기 기능이 먼저 이루어져야 하단에 있는 리스트가 나올 수 있겠다.




#댓글작성자# 라고 되어있는 것은 임시로 보여주기 위해 넣은 표기이다. 댓글 작성자는 아이디를 넣어도 되고, 닉네임을 넣어도 될 것 같다. 필자는 닉네임을 넣도록 하겠다. 로그인 한 사용자가 누구인지는 모르겠지만 로그인 했다면 세션 값은 가지고 있을 수 밖에 없다. 

필자가 작성중인 소스는 댓글 쓰기 부분과 댓글 목록 부분을 <table> 태그로 나누어 놓았다. 혹시나 로그인하지 않은 사람이 있다면 댓글을 작성할 수 없다. 출력 하는 방식을 정하는건 우리 마음이니, 결국 세션값 유무에 따라 자유롭게 처리하도록 하
자. 필자는 댓글 작성 부분을 아예 없애버리겠다.

board_view.aspx 페이지에서,

1. 댓글 쓰기의 테이블 전체를 프로그램 코드로 제어하기 위해 id를 주고 runat="server"를 붙여주어 서버컨트롤로 만들자. (혹은 태그가 렌더링 되지 않는 PlaceHolder 컨트롤 감싸주어도 될 것 같다)

2. 로그인 한 사용자에게는 댓글쓰기 테이블이 보여져야 한다. 그리고 '#댓글작성자#'에 해당 사용자의 닉네임 세션값이 보여져야 되니, Label 컨트롤로 id를 lblWriter 를 지정하여 넣어주자.

3. 미리 만든 서버컨트롤 버튼에 이벤트를 주도록 한다. 이벤트명은 btnCommentWrite_Click 으로 한다. 당연히 메서드도 정의해야 오류 없이 실행할 수 있다.


다음 소스처럼 변경되었다.

(...)


<!--------------- 댓글쓰기 -------------------->
<table width="600" id='tblCommentWrite" runat="server">
 <tr>
  <td width="150">
   <ASP:Label id="lblWriter" runat="server" />
  </td>
  <td width="450">
   <ASP:TextBox id="txtComment" textmode="multiline" width="450" height="80" runat="server" />
   <ASP:Button
    id="btnCommentWrite"
    text="댓글쓰기"
    onclick="btnCommentWrite_Click"
    runat="server" />   
   
  </td>
 </tr>

</table>


(...)


이제 상단의 Page_Load() 메서드 쪽으로 넘어와서 로그인 체크를 하여(세션 변수의 유무) 없다면 tblCommentWrite 의 Visible 속성을 False 값으로 준다. 기본값은 True 이다.

확인해보면 현재 로그인 된 상태이기 때문에 "테스터"라고 잘 붙어있는 것을 알 수 있다. 로그인을 임시로 없애주고 비회원인 상태를 보고 싶다면 로그인 체크 상단에 간단하게 로그오프 기능의 코드처럼 Session.Abandon() 을 실행하면 된다. 예상대로 다음 그림처럼 실행시 댓글 쓰기는 아예 렌더링 조차 되지 않는다.



이런 방법도 있지만 글쓰기 버튼만 없애고 텍스트박스에 "댓글 작성은 로그인이 필요합니다"라는 메시지로 표시하는 등등 만드는 사람이 마음대로 방식을 정하면 되겠다. 중요한 포인트가 "필요한 세션변수가 null인지 체크"하는 것일 뿐.

이제 다시 로그인 상태로 돌려서 댓글 쓰기를 처리하는 이벤트(btnCommentWrite_Click) 에 기능을 넣어주자. Page_Load() 동급으로 이벤트 메서드를 정의하고, 거기에 게시판 글쓰기 기능처럼 구현한다. 댓글의 내용은 비어있으면 의미가 없으니 빈 값은 등록하지 못하도록 막자. 어느 게시판에서는 장난치듯 쓰는 댓글도 막기 위해 글자수가 n자 이상일 때만 등록되는 그런 기능도 있으니 이러한 기능 구현도 (여러분들이 스스로 ^^) 해보기로 하자. 

체크 사항에 문제가 없다면 라이브러리에 만들어 놓은 CommentWrite(........) 메서드를 호출해서 완료한다.

필자가 가지고 있는 소스의 댓글쓰기 텍스트박스의 id는 txtComment 로 지정했다. 




위의 그림은 필자가 작성한 코드이다. 내용이 비어있으면 (MESSAGE 문자열 변수에) 출력할 오류 메시지만 넣고, 댓글이 있다면 라이브러리의 댓글쓰기 메서드를 호출해서 댓글을 쓰고 출력할 메시지를 넣어준다.

여기서 눈여겨 볼 점이 83번째 줄에서 txtComment.Text 의 값을 공백으로 처리한다는건데, 83번째 줄이 없으면 댓글을 작성 후 버튼을 누르면 PostBack 이 일어나므로 사용자가 입력한 값은 그대로 남아있게 된다. 게시판 글쓰기에서는 board_write.aspx 에서 글을 쓰고 성공하면 처리한 후 다른 페이지인 board_list.aspx로 이동했었다. 그러나 댓글은 현재의 페이지에 계속 머물러야 한다. 만약 결과 메시지(오류 또는 성공)를 보여주지 않아도 될 것 같으면 Response.Redirect() 를 이용해서 아예 새로 현재의 페이지를 다시 열어버려도 되겠다.

그리고 음영처리된 88번째 줄을 보자.
게시판 글쓰기에서는 Label 컨트롤을 하나 따로 두어서
 메시지를 담아둔 변수를 뿌려주었었다. 필자가 약간 다른 방법으로 처리를 해보았는데, tblCommentWrite 는 <table>태그의 서버컨트롤이다. asp.net 의 특징을 기억하시겠는가? 우리가 하고 있는 asp.net 은 별 볼일 없는 태그에 runat="server"를 붙여주면 슈퍼컨트롤(?!)이 되는 어마어마한 기능이 있다.(는 좀 오버고..) 즉, 프로그래밍 방식으로 접근 가능하게 된다. HtmlControl 과 WebControl 이렇게 2가지가 있다고 예~전에 닷넷 서버컨트롤을 3장에서 간단하게 다루었었다.

지금 설명과는 Part 15 가 가장 밀접한 관계가 있을 것 같은데, 모든 서버컨트롤은 엄마격인 System.Web.UI.Control 이라는 클래스에서 상속된다. 그리고 Control 은 ControlCollection 이라는 Controls 이름의 컬렉션 형식 속성이 있다.

그렇기 때문에 모든 서버컨트롤은 자기가 가지고 있는 것들을 컬렉션 형식으로 사용이 가능하다.


댓글쓰기 테이블은 runat="server" 를 줬기 때문에 HtmlTable 이라는 서버컨트롤로 변환되었다. MSDN을 뒤져보면 모두 다 드러나겠지만, <table>태그는 <tr>태그가 1개이상 존재한다. 여기서 <tr>태그 역시 서버컨트롤이 되는데, HtmlTableRow
라는 이름이고, 이것은 HtmlTable.Rows 컬렉션으로 사용가능하다. 그리고 <tr>태그 역시 <td> 태그가 1개 이상 존재할 수 있기 때문에 서버컨트롤인 HtmlTableCell 이름의 <td>태그에 해당하는 컬렉션이 존재하고, HtmlTable.Rows.Cells 컬렉션으로 사용가능하다.

즉, 지금 tblCommentWrite 서버컨트롤은 1개의 <tr>이 존재하니 첫번째 <tr>인 tblCommentWrite.Rows[0] 밖에 없다. 그리고 여기에 2개의 <td>가 존재하니 tblCommentWrite.Rows[0].Cells[0] 과 tblCommentWrite.Rows[0].Cells[1] 이렇게 각각 접근할 수 있다.

이렇기 때문에 원하는 <td>, 즉 서버컨트롤에 접근해서 .Controls 속성(컬렉션)을 이용해 무언가를 끼워넣어 준다는 것이다. 그게 바로 Add() 메서드이고, 이는 컬렉션이라는 녀석에 Remove() 메서드, Count 속성 등 과 함께 기본적으로 들어가 있는 기능이다.

  tblCommentWrite.Rows[0].Cells[1].Controls.Add(new LiteralControl(MESSAGE));

LiteralControl 은 렌더링되는 태그의 결과가 없는 컨트롤이다. 여기에는 Label도 넣을 수 있고, 당연히 웹폼도 넣을 수 있다. 별별게 다 들어갈 수 있겠다.
결과를 보면 댓글을 작성한 후 소스보기로 보면 다음처럼 그냥 MESSAGE 변수의 텍스트만 들어가 있음을 확인할 수 있다.






닷넷 서버컨트롤은 충분히 공부할 가치가 있는 녀석이다. 여러분들이 직접 만들지는 못하더라도 닷넷에서 제공하는 것들을 마음대로 요리할 수 있는 수준까지는 올려놓도록 하자. 특정 컨트롤에 넣어도 보고, 빼보기도 하고 몇개가 들어앉아 있는지 확인하기도 하고, 그 중 원하는 컨트롤에 접근하기도 하고, 넣어보고 빼보자. 알고보면 컬렉션으로 가지고 노는 수준이다.


이제 댓글 리스트를 완성해보자. 이미 거의 동일한 기능인 게시판 리스트를 완성하지 않았었던가.
Repeater 의 이름은 rptComment 라고 하기로 하자.




빨간색 네모는 처음 디자인했을 때 없었던 추가된 코드이다. 전체 댓글 수를 표시하기 위해 한줄을 넣었다. 이 코드에 대해서는 별 설명이 필요 없을 것 같다.

이제 Page_Load() 에 rptComment 에 데이터를 라이브러리로부터 가져와 뿌려주자.
그리고 방금 새로 추가된 lblCommentCount 이름의 Label 서버컨트롤에는 댓글 갯수를 알리는 텍스트를 넣자.

 



주석으로 설명을 붙여 놓았는데, 리스트와 조금 다르게 처리해보았다.
Repeater.DataSource 는 데이터소스에 맞는 형식을 요구한다. 그 중에 우리가 쓰고 있는 DataTable 인데, 이 형태로 들어가게 되면 Repeater.DataSource 형식이 DataTable 이 되어버린다. 간단하게 DataTable 을 따로 선언하지 않고 바로 넣는 방법도 이렇게 구현할 수 있다고 생각하자.

이제 댓글리스트가 잘 나올 것이다.




그런데, 이 상태에서 댓글을 한번 남겨보자. 제대로 남겨졌다는 말이 나오지만 방금 쓴 댓글은 목록에 나오지 않는다.
왜 그럴까?

먼저, 댓글 리스트는 Page_Load() 에서 나오게 했었다. 이 상태에서, 댓글을 남기면(버튼을 클릭하면, PostBack이 발생하면) 어떻게 될까?

PostBack 이라는 말은 현재의 페이지(board_view.aspx) 에 <form>이 runat="server" 일 때에는 ViewState라는 상태관리 값과 함께 한번 더 현재의 페이지에 POST로 요청을 보낸다는 말이다. 그래서 닷넷은 ViewState 값을 가지고 PostBack 인지, 아니면 그냥 생짜 주소로(다이렉트로) 열었는지를 알 수 있는 것이다. 예전에도 한번 언급한 적이 있지만, 현재 상태가 PostBack인지를 확인하는 변수는 Page.IsPostBack (bool형) 이라고 했다.

board_view.aspx 의 상태를 처음부터의 과정을 통해 한번 살펴보자.


1. 게시판 리스트에서 제목 링크를 눌러 board_view.aspx?c=test&n=# 를 연다.
2. board_view.aspx 에서 현재의 "n"값에 대한 댓글리스트를 가져와서 리피터에 뿌린다. (IsPostBack = false)
3. 사용자가 댓글을 남기기 위해 텍스트박스에 글을 쓰고 댓글쓰기 버튼을 누른다.
4. <form>을 통해 POST 로 board_view.aspx 페이지에 값들이 넘어간다. (IsPostBack = true)
5. Page_Load() 가 실행된다. (IsPostBack = true)
6. '2번'과 동일하게 댓글리스트를 뿌린다. (아직 방금 쓴 댓글은 저장하지 않은 상태이다)
7. 댓글쓰기 버튼 이벤트가 (이제서야) 실행되어 댓글이 DB에 저장된다.
--------- 종료 -----------


문제는 6번이다. 방금 쓴 댓글을 저장하기 위해 버튼이벤트인 btnCommentWrite_Click() 메서드가 먼저 실행되지 않고, Page_Load() 가 먼저 실행되기 때문에 일어나는 현상이다. 이해가 되시는가?

이런 현상을 해결하려면 어떻게 해야할까?


1. 게시판 리스트에서 제목 링크를 눌러 board_view.aspx?c=test&n=# 를 연다.
2. board_view.aspx 에서 현재의 "n"값에 대한 댓글리스트를 가져와서 리피터에 뿌린다. (IsPostBack = false)
3. 사용자가 댓글을 남기기 위해 텍스트박스에 글을 쓰고 댓글쓰기 버튼을 누른다.
4. <form>을 통해 POST 로 board_view.aspx 페이지에 값들이 넘어간다. (IsPostBack = true)
5. Page_Load() 가 실행된다. (IsPostBack = true)
6. '2번'과 동일하게 댓글리스트를 뿌린다. (아직 방금 쓴 댓글은 저장하지 않은 상태이다)
7. 댓글쓰기 버튼 이벤트가 (이제서야) 실행되어 댓글이 DB에 저장된다.
8. '2번'과 동일하게 댓글리스트를 뿌린다.
--------- 종료 -----------


이렇게 빨간색처럼 한번 더 뿌려버리면 해결 가능할 것이다.

그런데.. 꼭 두번을 뿌릴 필요는 없지 않는가? 데이터가 많으면 속도면에서나 서버 과부하문제에서 더 문제가 될 수 있다. (물론 우리 초보는 이런거 신경쓰지 마시라고 했지만... 이제 슬슬 초보에서 탈출할 때가....? ^^)

그래서~ 다음처럼 한번 생각해보자.


1. 게시판 리스트에서 제목 링크를 눌러 board_view.aspx?c=test&n=# 를 연다.
2. [IsPostBack]이 아니면  board_view.aspx 에서 현재의 "n"값에 대한 댓글리스트를 가져와서 리피터에 뿌린다. (IsPostBack = false)
3. 사용자가 댓글을 남기기 위해 텍스트박스에 글을 쓰고 댓글쓰기 버튼을 누른다.
4. <form>을 통해 POST 로 board_view.aspx 페이지에 값들이 넘어간다. (IsPostBack = true)
5. Page_Load() 가 실행된다. (IsPostBack = true)
6. [IsPostBack=true] 이기 때문에 리스트를 뿌리지 않는다.
7. 댓글쓰기 버튼 이벤트가 (이제서야) 실행되어 댓글이 DB에 저장된다.
8. 댓글리스트를 뿌린다.
--------- 종료 -----------


그러니까, 처음 열릴 때는 한번 무조건 뿌려주고, 댓글을 남길 때만 Page_Load() 에서는 뿌려주지 말고, 댓글 이벤트에서 뿌려주는 기능을 넣자. 라는 말이다. 그러니까, 댓글 리스트를 뿌려주는 기능은 Page_Load(), btnCommentWrite_Click() 이렇게 두 군대에서 실행된다.

중복코드를 없애기 위해 따로 메서드를 만들자. 단지 실행하는 기능이니 리턴은 필요없다.
메서드명은 CommentList() 라고 한다.





이 메서드를 Page_Load() 에는 IsPostBack 값이 false 일때만 호출하자.




그리고 댓글을 쓴 후에는 위의 메서드가 호출되지 않으므로 댓글 작성 후에 호출해준다.




닷넷에서 포스트백(PostBack)이 될 때, 실행되는 이렇듯 이벤트의 순서를 알아둘 필요가 있다. 오류는 발생하지 않아도 원하는 기능이 잘 작동하지 않을 때는 대부분 이러한 이유가 많다. 필자가 제시한 방법이 답이 아니고, '이렇게 처리도 하는구나'라고 생각하자. asp.net 에서 자기 나름대로 돌아가는 형태를 개발자 입장에서는 따라줘야 할 수 밖에 없다.

이렇게 Part 1~11 까지 게시판의 설계, 회원가입, 게시판 글쓰기, 게시판 리스트, 게시판 글 읽기까지 진행해보았다.
각각의 기능별로 문제가 발생할 사항이 많다고 했고, 자잘한 기능은 현재 구현하지 않은 상태이다. 일단은 여기까지를 기준으로 하고 더 필요한 기능을 추가해보거나 발생하는 문제점을 해결하는 방법을 직접 고민해보자.

방법을 알면 자신의 원하는 것을 모두 만들어낼 수 있다. 프로그램 100개를 만들어도 이해하지 못하면 101개째 프로그램은 만들 수 없다. 5개만 만들어도 몇백개를 만들 수 있는 공부 방법을 택하시길 바란다.

다음 첨부파일은 이번 Part 에서 진행한 board_view.aspx 의 최종 소스이다.



그럼 다음시간에..




강좌 내용에 대해서는 블로그에 댓글 또는 http://cafe.naver.com/guyv 에 카페가 있으니 언제나 편하게 피드백/질문하시면 됩니다. 

0 Comments
댓글쓰기 폼