관리 메뉴

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

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

ⓟrogramming/asp.net 게시판

닷넷 게시판 만들기 Part 43

가이브 2011.06.09 18:03


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
2011/06/01 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 41
2011/06/08 - [ⓟrogramming/asp.net 게시판] - 닷넷 게시판 만들기 Part 42

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



지난 시간에 이어 글삭제 기능을 같이 해보자. 삭제 기능은 말 그대로 현재의 글을 쓴 사람이나 관리자가 글을 삭제하는 기능이다. 구현은 DB로부터 자료를 완전히 날려버리는 방법이 가장 간단하겠다. 간혹 "이 글은 삭제된 글입니다"라는 문구에 클릭이 안되는 형식의 게시판도 있긴하다. 필자 생각에는 컬럼 1개를 더 두어 글 삭제시 이 컬럼의 값을 조작하는 것 같다. 그러니까 삭제하면 그저 날려버리는게 아니라 자료는 놔두고 특정 컬럼의 값을 조작하는 것이다. 그리고 그 값에 따라 메시지를 뿌려주고 링크를 없애버리는 방법으로 구현할 수 있겠다. 프로그래밍 대부분은 값을 어떻게 가지고 놀건지 고민하는데에 시간을 많이 들인다.

글일기, 글수정처럼 특정 글에 대한 기능이므로 작성된 글의 고유번호로 삭제하자. 쿼리문은 DELETE를 사용하면 될 듯하다.
그리고 댓글도 함께 삭제한다. 댓글을 먼저 삭제하면 의미상 더 좋겠다. 지금은 별 의미없는 말이긴 하나, 글이 없는 상태에서 댓글이 존재할 수는 없으니까..

언제나 기능을 구현하기 전에 상수값을 예로 넣어서 쿼리를 미리 만들어본 후 (혹은 DB관리툴에서 실제 수행까지 해본 후) 실제 프로그램 코드에 적용시키는 버릇을 들이도록 하자.



1. 댓글삭제
DELETE FROM board_comment WHERE board_id=1

2. 글삭제
DELETE FROM board WHERE category='test' board_id=1



Board.cs 에 글 삭제용으로 Remove(..) 이름의 메서드를 만들자. 글삭제와 마찬가지로 2개의 필요한 인수를 받는다. 리턴은 없으며, 2개의 쿼리를 실행하는 기능이다.






그리고 앞서 만들었던 board_view.aspx 의 글삭제 버튼 클릭 이벤트에 적용하도록 하자.
삭제후에는 가차없이 리스트로 이동하기로 한다.





우리는 게시판을 만들기 위해 제일 처음에 DB서버와 통신하기 위한 Database 라이브러리를 만들었다. 그리고 게시판 라이브러리 Board 에서 실제 자료 입출력을 위한 쿼리문을 만들고 Database 라이브러리를 통해 원하는 자료를 넣거나 빼거나 삭제, 수정하였다. 

필자가 처음 asp.net 으로 만든 게시판은 이렇게 라이브러리를 컴파일하여 사용하지 않았다. 기억으로는, 지금 작업중인 홈페이지의 구조에서 사용한 포함페이지인 top.ascx, bottom.ascx 처럼 사용자 정의 컨트롤에 각각의 기능을 하는 메서드들을 모두 넣었다.  그리고 그 메서드들은 다들 각각 DB를 열고 닫는 작업을 했다. 처음에는 어떤 방법이 더 쉽고 편하게 하는지 알 수가 없다. 누군가가 알려주는 사람이 없이 MSDN만 보고 C#부터 시작해서 서버 컨트롤들을 보고 익혔으니.. (필자는 닷넷 첫 코딩을 위해 3개월간 흰색 바탕의 파란색 네임스페이스, 클래스를 클릭한 기억밖에 없다. 까막눈으로 그들 만의 언어를 문서로만 배운다는건 참 어려운 일이다)

많은 사람들이 웹 프로그래밍을 학습하면서 게시판 만들기부터 시작한다. 학습 효과에서 중요한 것은 결과물이 아니라 무언가를 하는 방법인데, 게시판을 완벽히 만든 사람들이고 이해를 했다고 하더라도 특정 기능을 추가하라면 매우 고민한다.

예를 들어 첨부파일을 지금 1개에서 5개로 늘여달라라던가, 자동 글 등록 방지 기능 넣어달라라고 할 때, 다뤄보지 않았다는 이유로 매우 힘들어하는 경우가 많다. 


닷넷이든, php든, asp든.. 뭐든.. 웹을 표현하는 녀석은 HTML이다. 즉, 이들의 결과물은 HTML임에 집중한다.


이 강의 Part 1의 첫 문장이다. HTML이 결과물인 웹프로그래밍에서, 내부적으로 처리를 어떻게 하는지는 그 누구도 알 수 없다. 소스를 보기 전까지는 말이다. 예로 게시판 리스트에서 DataGrid 를 썼는지, 우리처럼 쌩짜 <table>태그를 썼는지, 아니면 Table 웹폼을 썼는지, HtmlTable 서버컨트롤을 썼는지, 소스보기에는 HTML결과물이 출력될 뿐이다. 물론 웹폼 특성상 소스보기를 하면 태그의 모습을 보고 짐작은 가능하다.

사실 게시판 리스트를 우리처럼 Repeater 대신에 글 리스트를 Page_Load() 이벤트에 for(;;) 를 이용해서 루프로 만들어도 100% 토시 하나 틀리지 않게 구현이 가능하다. 심지어 소스보기에서 하드 코딩된 탭문자, 공백 등등 특정 문자열까지 하나하나 똑같이 맞추어 주면 된다.

필자가 이런 말을 하는 이유는 지금 이 강의의 게시판을 따라하면서 '재밌게 즐긴(?)' 것은 이제 시작일 뿐이라는 것이다.
아무리 베스트셀러 책이라도 제각기 공부하는 스타일에 맞출 수 없다. 이 글 역시 여러분들에게 절반 정도만 맞아도 다행이라 생각한다. 아마 받아들이는 방법은 모두 다 다르기 때문일 것이다.

고수의 기준은 무엇일까? 필자의 생각으로는 자기 분야에서 만큼은 새로운 것을 할 준비가 되어 있는 사람이라고 말하고 싶다. 그리고 그들은 우리보다 키보드를 한번이상 더 두드려본 사람이고 우리보다 한번이상 더 고민한 사람일 뿐이다. 조금 늦게 시작했을 뿐이니 너무 서두르지 말고 재미있게 앞으로 만날 오류를 즐기시기 바란다.




이렇게 글삭제까지 만들어서 작성하거나 삭제한 후 리스트를 보면 맨 첫 컬럼인 게시물 번호가 꼬인 것을 볼 수 있다. 중간에 듬성듬성 삭제된 것도 있다. 실제 Database에 기록되어있는 글 고유 번호를 그대로 출력했기 때문인데, 의미상으로는 사실 8개의 글이 8,7,6,...,2,1 이렇게 내림차순으로 차례로 출력되는게 맞는 것 같다. 뭐, 그렇다고 글 번호로 글을 찾는 경우는 없긴하지만 말이다.

현재의 board_list.aspx 에 맨 윗줄에 Trace를 걸어보자. 예전에 닷넷 컨트롤을 다루는 파트에서 해본듯 하다.


<%@ Page Trace="true" %>


페이지의 하단에 페이지의 정보가 뜬다. 여기서 "컨트롤 트리(Control Tree)"항목을 보자.




board_list.aspx 인 "__Page" 는 크게 ctl00 (top.ascx), rptList, ctl01 (bottom.ascx) 이렇게 3개의 하위 항목을 가지고 있다. 그리고 예전에 게시물 리스트를 가져오기 위해 rptList 에 DataSource 속성과 DataBind() 메서드를 이용했었다.

컨트롤 UniquaID 는 닷넷에서 알아서 관리하는 해당 고유 컨트롤의 ID 이다. "$"표시가 붙은 컨트롤은 Page__(board_list.aspx) 에서 바로 접근이 불가능하다. 즉, 게시판 리스트에 사용한 Repeater 컨트롤, rptList 에 감싸진 녀석들은 모두 접근이 불가능하다고 생각하면 되겠다. 이것들은 그림처럼 Repeater(rptList) 의 하위에 있는 개념이므로, Repeater 에서 접근할 수 있다.

테스트를 해보기 위해, board_list.aspx 의 Repeater 컨트롤에 단 1개 있는 <tr>태그에 id를 아무렇게나 주고 서버컨트롤로 만들자.





개념적으로는 tr1 서버컨트롤(HtmlTableRow) 을 Page_Load() 에서 사용할 수 있을 것 같다.
확인해보기 위해 Page_Load() 맨 밑에 Response.Write() 로 출력해보자. 알고 있겠지만 해당 서버컨트롤을 출력하면 그 컨트롤의 클래스가 출력되게된다.






그러나 오류가 발생한다. 오류메시지는 'tr1이 뭐니?'라며 전혀 모른다는 반응이다. 확인했으니 해당 코드를 다시 삭제한다.


Repeater 컨트롤은 DataSource 속성에 지정된 데이터를 DataBind() 메서드가 호출될 때 차례대로 하나씩 자료를 루프를 돌리며 넣어준다고 하였다. 이렇게 하나씩 루프가 돌아갈 때, 발생하는 이벤트가 있는데 오랜만에 MSDN에서 같이 찾아보자.




이벤트 목록을 살펴본다. (맨 하단에 있다)
"Controls 에서 상속됨" 이라고 되어 있는것은 모두 무시한다. 뒤집어 생각하면 Repeater 고유 이벤트가 아니라는 말이다.




앞서 필자가 말한 녀석이 ItemDataBound 라는 이벤트이다. 클릭하면 자세한 사용법을 알 수 있을 것이다. 클릭해서 쭈욱 읽어보자. 예제를 봐도 어떻게 사용하는지 대충 알 수 있다. asp.net 에서 디자인 단에 이벤트를 걸려면 "On이벤트명" 이라고 지정하는 것을 알고 있을 것이다. Click은 OnClick, SelectChanged 는 OnSelectChanged 처럼 말이다.

무조건 부딪혀보자. 먼저 <ASP:Repeater ..> 에 다음처럼 해당 이벤트를 걸자.






이제 rptList_Bound 메서드를 만들어야하는데, Button 클릭이벤트의 속성과는 조금 다르다. MSDN의 예제에 나와있듯이 object 와 RepeaterItemEventArgs 인수를 받으면 된다. 일반적으로 이벤트의 속성은 이벤트를 발생한 객체를 가리키는 object 하나와 이벤트가 발생될 때 함께 전달되는 정보를 가지고 있는 (...)EventArgs 이렇게 2개를 가진다. EventArgs는 앞에 컨트롤에 따라 접두사가 붙을 수도 있음에 유의하자.

여튼, 버튼 이벤트를 지정하는 방법과 같은 방법으로 Page_Load() 와 동일선상으로 rptList_Bound 이벤트 메서드를 지정하자.





뭐든지 확인해보는데에는 Response.Write() 가 최고이다.
이벤트에 대한 정보를 가지고 있는 e 값을 찍어보자. 그리고 몇개가 찍히는지 구분하기 위해 "<br>"태그를 붙여준다.


 // Repeater 이벤트
 void rptList_Bound(object sender, RepeaterItemEventArgs e)
 {
  Response.Write(e + "<br>"); 
 }



필자가 실행한 결과는 다음처럼 출력되었다.





게시물이 총 8개가 있으니 8번 찍히는 것을 볼 수 있다. 그렇다면 'e' 로 사용되는 RepeaterItemEventArgs 에는 어떤 속성들이 있을까? MSDN 에서 신나게 찾아보자. 

▶ RepeaterItemEventArgs 생성자
http://msdn.microsoft.com/ko-kr/library/system.web.ui.webcontrols.repeateritemeventargs.repeateritemeventargs(v=VS.80).aspx


클래스들을 추적하는 자체가 좀 복잡하긴 하지만, Item 이라는 녀석이 돌고 있는 것을 알 수 있다. 그러니까 RepeaterItem(클래스 멤버 링크)을 'e'라는 이름에 'e.Item' 이라는 이름으로 사용해야한다. 이것은 하나하나 각각 자료가 들어갈 때 마다 이벤트에서 사용할 수 있다. 확인해보자.

// Repeater 이벤트
void rptList_Bound(object sender, RepeaterItemEventArgs e)
{
Response.Write(e.Item + "<br>");
}



오류가 발생하지 않고, 상단에 e.Item 의 클래스가 찍히는 것을 볼 수 있다. 이제 우리는 여기서 RepeaterItem 의 속성을 마음대로 사용할 수 있게 되었다. RepeaterItem 속성중에 우리가 원하는 기능을 구현하기 위해 순번을 리턴하는 ItemIndex 속성을 찍어보자.

 
// Repeater 이벤트
void rptList_Bound(object sender, RepeaterItemEventArgs e)
{
Response.Write(e.Item.ItemIndex + "<br>");
}



첫번째인 0부터 7까지 출력되는 것을 볼 수 있다. 우리가 원하는 결과는 위에서 부터 8개니까 8,7,6...,2,1 을 출력해야되니, 이를 뒤집어야한다. Item은 총 Repeater의 총 8개 중 현재의 RepeaterItem 이다. 그러므로, 현재 리스트의 전체 갯수에서 이벤트로 들어오는 현재의 e.Item.ItemIndex를 빼주면 우리가 원하는 순번을 구할 수 있겠다.


전체 - e.Item.ItemIndex = [원하는값]
8 - 0 = 8
8 - 1 = 7
..
..
8 - 7 = 1



그렇다면 전체의 글 수는 어디서 가져올 수 있을까?
당장 생각나는대로 int 클래스 변수를 지정해서 Page_Load()에 할당한 후 사용하자.




이제 rptList_Bound() 이벤트에서 전체 글 수를 사용할 수 있다. 이것을 적용시키면 다음처럼 되고 결과를 확인하면 8부터 1까지 숫자가 차례대로 출력되는 것을 볼 수 있다.

 
// Repeater 이벤트
void rptList_Bound(object sender, RepeaterItemEventArgs e)
{
  Response.Write((TOTAL_COUNT - e.Item.ItemIndex) + "<br>");  
}



원하는 값을 구했으니, 원하는 위치에 넣으면 되겠다. <ItemTemplate>...</ItemTemplate> 에서 글번호를 출력하는 구문인 <%# Eval("board_id") %> 를 웹폼 Label 로 변경해주자. 이 컨트롤의 ID는 lblNum 이라고 준다. 이 컨트롤은 이벤트에서 찾아낸 후 사용 가능하다.


// Repeater 이벤트
void rptList_Bound(object sender, RepeaterItemEventArgs e)
{
  int VIRTUAL_NUM = TOTAL_COUNT - e.Item.ItemIndex;  
  lblNum.Text = VIRTUAL_NUM.ToString();
}



이렇게 VIRTUAL_NUM 이라는 변수로 하나 지정하고 이것을 그대로 출력해보자. 오류가 발생하는 것을 알 수 있다. 조금 까다롭게도 Repeater 컨트롤 내에 있으므로, e.Item 에서 찾아야 한다.


e.Item.lblNum.Text
▶ 없음

e.Item.FindControl("lblNum").Text
▶ 오류 : Control 에 Text 속성이 없음


((Label)e.Item.FindControl("lblNum")).Text
▶ 성공



e.Item 이 현재의 항목이라고 하였다. 간단하게 <ItemTemplate>이 총 8개니까 8번 도는데, 각각 돌때마다 e.Item 이 각각의 항목이라 생각하자. e.Item 에는 분명 lblNum 이 존재한다. 그러나 바로 접근은 할 수 없다.(UniquaID 가 바뀌기 때문) 그러므로 모든 서버컨트롤에서 사용가능한 Control.FindControl(string 컨트롤ID) 메서드를 이용해서 잡아내야한다.

object Control.FindControl(string 컨트롤ID);

이 메서드의 리턴은 분명히 모든 컨트롤을 담을 수 있는 object 이다. 우리는 우리가 배치한 컨트롤이 어떤 형태인지 알고 있으므로, Label 로 변환하여 .Text 속성을 준다.


// Repeater 이벤트
void rptList_Bound(object sender, RepeaterItemEventArgs e)
{
  int VIRTUAL_NUM = TOTAL_COUNT - e.Item.ItemIndex;  
  ((Label)e.Item.FindControl("lblNum")).Text = VIRTUAL_NUM.ToString();
}



이제 우리가 원하는 위치에 출력이 가능해졌다.




무언가를 해결했다 하더라도 또 다른 문제가 발생할 수가 있음을 언제나 상기하자. 긴장할 필요 없이 차분하게 이유를 발견하고 해결하면 그만이다. 오류가 발생하면 빨리 발견되었기 때문에 더 좋아하시길 바란다.


다음시간에도 계속해서 게시판을 좀 더 그럴듯하게 다듬어보자. 압축 파일은 여기까지 진행한 전체 소스파일이다.




0 Comments
댓글쓰기 폼