반응형
반응형
반응형
반응형

Cookie vs Session

 

 

Cookie 는 Client, PC 에서 운용되고 Session 은 서버에서 운용 된다. 적절히 조율 하여 최적 설계하는게 좋은데 의외로 MS 버그인지 생각되로 잘되질 않는다, 한글 처리와 삭제 ..

 

 

Web.config

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.web>
    <globalization 
       requestEncoding="ks_c_5601-1987" 
       responseEncoding="ks_c_5601-1987" 
    />
 </system.web>

</configuration>

 

// 이쁘게 구성 할때 선언

using System.Collections.Specialized;

 

// 자주 쓰이는 Class 와 멤버

HttpCookieCollection MyCookieColl = Request.Cookies;
HttpCookie MyCookie = MyCookieColl["USER"];
String[] arr1 = MyCookieColl.AllKeys;

NameValueCollection Nvc = Request.Cookies["USER"].Values;
 

// 한항목에 서너개 변수를 줄때 Name "USER" 서버 항목 생일(BDAY),성(SEX),이름(NAME),등록일

    MyCookie.Values["BDAY"] = "20001010";
    MyCookie.Values["SEX"]  = HttpUtility.UrlEncode("남");
    MyCookie.Values["NAME"] = HttpUtility.UrlEncode("홍길동");
    MyCookie.Values["DATE"] = DateTime.Now.ToString("u");

혹은

    MyCookie.Values.Add("BDAY","20001010");
    MyCookie.Values.Add("SEX", HttpUtility.UrlEncode("남"));
    MyCookie.Values.Add("NAME", HttpUtility.UrlEncode("홍길동"));
    MyCookie.Values.Add("DATE", DateTime.Now.ToString("u"));

 

실제 기억될떄는 각각 항목을 가지지 않고 구분자(&)로 한 항목에 다 들어 간다

 

BDAY=20001010&SEX=남&NAME=홍길동&DATE=20101010 153024

 

MyCookie.Value 는 구분자 포함한 전체 문장을 가져 오고

MyCookie.Values["BDAY"] 는 생일(20001010) 만 가져 온다 

MyCookie["BDAY"] 도 같은것으로 생일(20001010) 만 가져 온다

 

 

Cookie 삭제

 

private void Cookie_Delete()
{
   HttpCookie aCookie;
   string cookieName;
   int limit = Request.Cookies.Count;


   for (int i = 0; i < limit; i++)
   {
    cookieName = Request.Cookies[i].Name;
    aCookie = new HttpCookie(cookieName);
    aCookie.Expires = DateTime.Now.AddDays(-1);
    Response.Cookies.Add(aCookie);
   }
}

 

삭제에 Add 함수라 상식을 초월하는데 Add시 기존 자료를 삭제하고 들어간다는 의미인듯

 

 

 

 

 http://blog.naver.com/icehuk/50027337179  2008/01/30 09:01

 


한글 쿠키 깨짐

 

A와 B사이트가 있습니다.
iis 밑 web.config 환경 똑같구요...
Form인증을 사용합니다.
인증처리는 아래와 같이 했습니다.
FormsAuthentication.SetAuthCookie(ID, false, "/");
   
   HttpCookie Cookie;
   string Name = ci.Name.Trim();
   Cookie = new HttpCookie("Name", Name);
   Cookie.Path = "/";
   Response.Cookie!s.Add(Cookie);


 

똑같은 환경인데.. A라는 사이트는 한글 쿠키가 이상없이 사용이되고.. B라는 사이트는 한글이 깨지네요


동시에 한글을 쿠키값이 들어가면. Form인증 또한 영향을 가서..  
Response.Write(Page.User.Identity.IsAuthenticated + "<br>"); 라고 찍으면 값이 "false"가 나옵니다.


쿠키값을 출력하면
"?좊??? .AuthCIC=C7D27C82206120830DEDE2F490B69.......A"
이렇게 출력됩니다.


흠..여기서 심사장님이 HttpUtility.UrlDecode 사용해보라고 하셨는데.. 역쉬나 깨지더군요...


 

근데 쿠키 적용시 HttpUtility.UrlEncode를 사용해서 저장하면 
인증도. 쿠키도 깔끔하게 출력이 되네요...ㅠ,ㅠ...

 

아래와 같이 말이죠

쿠키값 저장시
string Name = HttpUtility.UrlEncode(ci.Name.Trim());
Cookie = new HttpCookie("Name", Name);

쿠키값 사용시
string temp =  (Request.Cookie!s["Name"] == null) ? "" :HttpUtility.UrlDecode(Request.Cookie!s["Name"].Value.ToString());

 

이것때문에 어제 하루 날려버리고..--;;;.. 이번에도 심사장님에 도움으로 해결 합니다. ㅋㅋㅋㅋ
근데 이걸 MS버그라고 해야 하나요??? 아니면... 프로토콜때문에 생기는 문제라고 해야 하나요??
흠 어렵네.. 쩝...
 
출처  : http://sindream.tistory.com/entry/한글-쿠키-깨짐

 

 

 

 

http://blog.naver.com/tjdjd/90041950196   2009/02/08 20:10

 


[ASP] 쿠기 사용하기

 


웹에서 서버가 클라이언트 브라우저에 작은 텍스트 파일을 저장할 수 있는데 이 파일을 "쿠키" 라고 한다.
쿠키에는 사용자의 ID,Passwd 등의 정보를 저장해 두었다가 서버에 연결할 때 쿠키를 이용하여 사용자를 파악할 수 있다.

 

***************************************
- 쿠키에 저장할 때 : Response 객체
- 쿠키의 값을 읽어올때 : Request 객체
EX) 
→ Response.cookies(“user”) = “nhs”
→ Request.cookies(“user”)
***************************************

 

형식 : Request.Cookies(쿠키변수)[(하위쿠기변수)|.속성]
 
<html>
<head><title> Cookie 컬렉션 </title></head>
<body>
<p>
<h3> Cookie 컬렉션을 출력 </h3>
<%
       Response.Cookies("myname") = “홍길동”  ' 홍길동이라고 텍스트파일에 저장한다.
       Response.write     Request.Cookies(“myname”)  '홍길동이라고 저장된 데이타를 읽어온다.
%>
</body>
</html>
 
-----------------------------------------------------------------------------------
(보충) 하위쿠키예제
- "myinfo"라는 쿠키의 모든 하위 쿠키를 출력하는 예제 : for 문의 사용법을 눈여겨 봐둔다.
<html>
<head><title> Cookie 컬렉션 </title></head>
<body>
<p>
<h3> Cookie 컬렉션을 출력 </h3>
<%
       Response.Cookies(“myinfo”)(“name”) = “홍길동”
       Response.Cookies(“myinfo”)(“age”) = “20”
       Response.Cookies(“myinfo”)("tel”) = “02-555-6666”
 
'쿠기를 2009년 01월27일까지 유지하되 날짜가 지나면 쿠키를 삭제하라는 것이다.
'즉 지정해준 날짜가 지나면 쿠기는 삭제된다는 것이다.
Response.Cookies(“myInfo”).Expires = #27/01/2009 00:00:00# 
 
'쿠기를 읽어와서 화면에 출력한다.
Response.write Request.Cookies(“myInfo”)
 
'strkey 라는 변수에 값을 담아서 myinfo에 있는 모든 값을 돌려준다.
'즉 순서대로 돌려주기 때문에 처음에 name 그다음 age 그다음 tel 값을 돌려준다.
       For each strkey in Request.Cookies(“myinfo”)                
               Response.write       Request.Cookies(“myinfo”)(strkey) & “<br>”
       next
%>
</body>
</html>
-----------------------------------------------------------------------------------

 

 

네이버 지식iN

 

쿠키가 아닌 Session의 경우 서브 도메인간 공유가 가능한지 궁굼해서 질문 올립니다.
얼핏 생각해보면 Session정보는 서버에 저장하기 때문에 서브 도메인으로 들어가면 다른 서버로 인식을 해서 SessionID를 다른 것을 받고 있는 것 같은데요.
같은 서버에서의 서브 도메인이라면 혹시 가능 하지 않을까 싶어서 질문 올립니다.
참고 - 검색해보니 P3P규약이라는 것이 있는데 쿠키만 되는 것인지 사용을 잘못한 것인지 잘 되지 않는 상태입니다.

 

----------------------------------

 

이부분은 쿠키로 하지 않고는 풀기 힘듭니다.
브라우저 창을 새로 열때마다 새로운 세션을 부여받기 때문에 쿠키가 아니고선 페이징을 여러번 처리해야 합니다.
 
1. 로그인을 했을 때나 웹페이지를 열 때 다른 서브 도메인까지 열도록 하는 방법
2. 세션 값을 쿠키에 저장하는 방법
 
1.번은 뭐 잘 아시리라...<iframe> 쓰시면 되구요.
2.번은 현재 제가 쓰는겁니다.


using System.Collections.Specialized;
 
Dim iSessionID


IF Request.Cookies("SSID") = "" THEN
    Response.Cookies("SSID").domain  = Request.ServerVariables["SERVER_NAME"]
    Response.Cookies("SSID").path    = "/"
    Response.Cookies("SSID")         = Session.SessionID
    iSessionID = Request.Cookies("SSID")
ELSE
    iSessionID = Request.Cookies("SSID")
END IF

 

 

 

 

http://blog.naver.com/artfile/130092855606 2010/08/27 20:02
  
//오늘 본 상품목록의 쿠키저장할부분
//Response.Cookies["userInfo"]["userName"] = "patrick";
//Response.Cookies["userInfo"]["lastVisit"] = DateTime.Now.ToString();
//Response.Cookies["userInfo"].Expires = DateTime.Now.AddDays(1);--> -1로하면 쿠키삭제

 

Response.Cookies["TodayPD"][PD_Idx] = PD_Idx;
Response.Cookies["TodayPD"][PD_Thum_img] = 
"/Admin/Products/Images/" + PD_Thum_img.ToString();

 

우선 쿠키를 생성하는 페이지부터 보자, ["TodayPD"] 라는 컬렉션에 담는 형식의 [] 배열구조로 저장한다. 이는 향후에 따로따로 값을 받아오는 불편함을 없애기 위함이다.
 
그리고 ASP.NET에서 어떤 값을 설정할 때는 Response를 사용함을 상기하자.
 
if (Request.Cookies["TodayPD"] != null)
{
     NameValueCollection nvc = Request.Cookies["TodayPD"].Values;

     int PDCount = nvc.Count;
     for (int i = 0; i < PDCount; i++)
     {
         Label1.Text += nvc[i];
         i++;
         Image1.ImageUrl += nvc[i];
     }
}
 
NameValueCollection 이라는 컬렉션을 이용하여, 쿠키에 담겨진 정보 전부를 저장시킨다.


단, 쿠키를 Request로 받아올때의 주의점이 있다.


변수값을 지정해주지 않은 Request.Cookies는 Session의 의미도 포함하므로 이전에 Session 변수를 사용하였다면, 반드시 위에서 처럼 Cookies의 ["변수값"]까지 명확히 적어주어야 한다.


아니면 쿠키를 받아야하는 자리에 생뚱맞게 Session이 자리 잡고서 주인행세를 할테니 그건 보기 안좋을뿐더러 주객전도된 경우라고 할 수있다.
따라서 값이 증가함에 따라 해당 쿠키에 + 된 값을 받아오는 로직을 구사할수있다.
 
이작업은 왜하는가?
 
쿠키가 웹브라우저에서 존재하는 동안 내가 본 상품목록을 우측상단에 표시하고자할 때 쓰인다.
이 쿠키는 브라우저의 메모리상에만 존재하는 쿠키로서 브라우저를 닫는 순간 사라진다.
오늘 본 상품목록의 재현을 하는 것으로 이를 응용해 더 기능적인 상품목록을 생성시켜 보자.

 

 

Response.Cookies["TodayPD"].Expires = DateTime.Now.AddDays(183);

닫는 순간 사라 진다..??  만기시간을 두면 계속 PC 에 남아 있지 않나 ??

 


 

http://blog.naver.com/tjdjd/90041962184   2009/02/08 23:21

 

 

SessionID값을 부여하여 쿠키에 저장되어진 값과 비교해서 같은 접속자인지 확인하는 소스(같은 계정확인)

 

- 클라이언트에서 서버의 ASP 페이지를 처음 요청할 때 ASP에서는 SessionID를 생성
- SessionID는 각 클라이언트에게 고유한 숫자를 부여하고 브라우저에 쿠기로 저장
- SessionID를 쿠키로 저장하고 있기 때문에 다른 ASP 파일을 요청해도 동일한 쿠키값으로 
- 사용자의 세션을 추적할 수 있습니다.
- 사용자가 세션 시간 제한을 넘겨 다시 세션을 요구하거나 세션을 사용자가 일부러 중단하고 
- 다시 요청할 때도 같은 쿠키 값으로 새로운 세션을 시작합니다.
- SessionID는 서버를 재시작해서 서버의 메모리에 있는 SessionID가 지워진 경우와 사용자가 
- 웹 브라우저를 재시작하는 경우에만 새로운 SessionID를 받습니다. 
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
 <HEAD>
  <TITLE> New Document </TITLE>
  <META NAME="Generator" CONTENT="EditPlus">
  <META NAME="Author" CONTENT="">
  <META NAME="Keywords" CONTENT="">
  <META NAME="Description" CONTENT="">
 </HEAD>
 <BODY>
  <%
Response.write "당신이 부여받은 SessionID는 "
Response.write Session.SessionID & "입니다. <br>"
' 세션의 유지시간을 30분으로 설정
Session.Timeout = 30
'쿠키값을 텍스트파일에 저장한다.
Response.cookies("sessionid")=Session.SessionID
Dim qqq
'저장된 쿠키값을 qqq변수에 담는다.
qqq = Request.cookies("sessionid")
'Response.write qqq & "<br>"
'만약에 저장된 세션아이디가 쿠키와 같다면 
If Session.SessionID = qqq Then
 Response.write "난 좀 짱인듯!!!!!!!"
End if
%> 
 </BODY>
</HTML>
  
 
   
.

.

.

 


반응형
반응형

안녕하세요.

오늘 말해볼 사항은, 새로고침을 죽어라 하는 등의 공격이 비정상적일 때 제한을 주는 로직입니다.

여러명이 동시에 이러한 공격을 하면 제대로 막지 못합니다. 밴리스트 자체가 많아지고, 예전에 중국발

수백명짜리 DDOS 같은 것은 꿈도 못꾸죠.

하지만 소규모 서버에서 최소한으로 탑재 시켜놓은면 적은양의 공격은 막을 수 있다더군요.

가장 좋은 방법은 웹 방화벽 등에서 네트워크타고 들어오기전에 차단하는 것이지만,

원리는 아주 간단합니다.

웹캐시를 이용해 지정된 시간동안 몇 회 이상 접근시 막는 것이죠~, 기존에 제가 사용하는 제 라이브러리에 있던 것을 복사해봤습니다.



public static class ActionValidation
{
private const int DURATION = 10; //10 min period
public const int CountAction = 100;
/*
* Type of actions and their maximum value per period
*
*/

private class HitInfo
{
public int Hits;
private DateTime _ExpiresAt = DateTime.Now.AddMinutes(DURATION);
public DateTime ExpiresAt { get { return _ExpiresAt; } set { _ExpiresAt = value; } }
}

public static bool IsValid()
{
HttpContext context = HttpContext.Current;
if (context.Request.Browser.Crawler) return false;

string key = context.Request.UserHostAddress;

HitInfo hit = (HitInfo)(context.Cache[key] ?? new HitInfo());

if (hit.Hits > (int)CountAction)
{
hit.Hits++;
hit.ExpiresAt = DateTime.Now.AddMinutes(DURATION);

context.Cache.Remove(key);
context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(DURATION),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);

return false;
}
else
hit.Hits++;

if (hit.Hits == 1)
context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(DURATION),
System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);

return true;
}
}

이것을 ControllerBase 와 같이 설정해서 컨트롤러 밑에 깔아줬었습니다.

로그남기거나, 화면을 Redirect 해주는 등의 구현이 추가로 들어가면 되겠습니다.

.NET 원폼인 경우 글로벌에 그냥 넣어주면 되겠습니다.


public class ControllerBase : Controller
{
private JHFrk.Com.Security.JHSession jhs;
public JHFrk.Com.Security.JHSession JHS
{
get
{
if (jhs == null)
{
jhs = new JHFrk.Com.Security.JHSession();
}
return jhs;
}
}

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!JHFrk.Com.Util.ActionValidator.IsValid())
{
Response.Write("DDOS");
Response.End();
Response.Close();
}
base.OnActionExecuting(filterContext);
}
}
반응형
반응형


1. 명령프롬프트 실행

2. cd C:\windows\Microsoft.NET\Framework64\v4.0.30319

3. aspnet_regiis -i  실행


반응형
반응형
반응형
반응형
반응형
반응형

 

Cross-Site Scripting

 

Cross-site scripting (XSS) attacks exploit vulnerabilities in Web page validation by injecting client-side script code. Common vulnerabilities that make your Web applications susceptible to cross-site scripting attacks include failing to properly validate input, failing to encode output, and trusting the data retrieved from a shared database. To protect your application against cross-site scripting attacks, assume that all input is malicious. Constrain and validate all input. Encode all output that could, potentially, include HTML characters. This includes data read from files and databases.

 

게시판이나 웹 메일 등에 악의적인 스크립트를 삽입하여 비정상적인 페이지가 보이게 해 타 사용자의 사용을 방해하거나 쿠키 및 기타 개인정보를 특정 사이트로 전송하는 등의 공격

Ref: http://msdn.microsoft.com/en-us/library/ms998274.aspx

 

XSS는 다음과 같은 공격을 가능 하게 한다.

- 쿠키 절도, 계정 하이재킹으로 이어질 수 있는 세션 쿠키의 절도를 포함

- 피해 웹 사이트 / 어플리케이션에 대한 키 입력의 감시

- 웹사이트에서 특정 사용자인 것처럼 행동. 가령 Window Live Mail에 대한 한 XSS 공격은 공격자가 이메일을 읽고 전달할 수 있으며, 새로운 일정을 설정할 수 있습니다.

 

IE 8 자체 XSS 필터

XSS 필터 - 동작 원리

XSS 필터는 브라우저를 통하는 모든 요청 / 응답을 들여다 볼 수 있는 IE8 구성 요소로서 동작합니다. 필터가 크로스-사이트 요청에서 XSS처럼 보이는 것을 발견한 경우, 필터는 그 공격을 확인하여 만일 서버의 응답에서 그것이 다시 발견되면 무력화합니다. 사용자에게 물어보거나 하지는 않습니다. IE는 단순히 악의적인 스크립트의 실행을 차단합니다.

 

브라우저에 다음 처럼 주소를 입력 한다.

http://www.google.co.kr/search?complete=1&hl=ko&q=xss&btnG=Google+%EA%B2%80%EC%83%89&lr=&aq=<script>alert('hello');</script>

 

아래 IE 8 자체 XSS 필터가 적용 된 화면이다.

 

 

Ref: http://blogs.msdn.com/ie8kr/archive/2009/03/17/ie8-4-xss.aspx

 

MSDN에도 소개 되어 있듯이 XSS를 방지 하는 가장 중요한 대응 책은 다음 두 가지 이다.

  • Constrain input. (입력 값 검사)
  • Encode output. (출력 값 Encode)

 

How To: Prevent Cross-Site Scripting in ASP.NET(http://msdn.microsoft.com/en-us/library/ms998274.aspx) 를 살펴 보면 아래와 같이 Cross-Site Scripting을 방지 하는 다섯 가지 Step을 소개 한다.

  • Step 1. Check that ASP.NET request validation is enabled.
  • Step 2. Review ASP.NET code that generates HTML output.
  • Step 3. Determine whether HTML output includes input parameters.
  • Step 4. Review potentially dangerous HTML tags and attributes.
  • Step 5. Evaluate countermeasures.

 

1.     Check That ASP.NET Request Validation Is Enabled

( ASP.NET Request 검사가 활성화 되어 있는지 확인 하라 )

예제 페이지: RequestValidationEnabled.aspx

Web.config

<pages validateRequest="false" enableSessionState="false">

<pages validateRequest="true" enableSessionState="false">

 

필요 시 개별 페이지에 ValidateRequest 속성을 false로 줍니다.

개별 페이지

<%@ Page Language="C#" ValidateRequest="false" %>

 

2.     Determine Whether HTML Output Includes Input Parameters

(입력 파라미터에 HTML 출력 값이 포함 될지 여부를 결정 하라)

예제 페이지: HTMLOutputIncludesInputParameters.aspx

 

3.     Review Potentially Dangerous HTML Tags and Attributes

HtmlEncode to ensure the inserted text is safe.

(삽입 될 텍스트의 안전함이 보장 될 수 있도록 잠재적으로 위험한 HTML 태그나 속성을 HTML Encode 하는 것을 확인 하라.)

예제 페이지: DangerousHTMLTagsAndAttributes.aspx

-      Potentially Dangerous HTML Tags

(잠재적으로 위험한 HTML 태그)

<applet>  <body>  <embed>  <frame> <script> <frameset> <html> <iframe> <img> <style> <layer> <link>  <ilayer> <meta>  <object>

 

이러한 태그를 통하여 다음처럼 공격에 이용된다.

<img src="javascript:alert('hello');">

<img src="java&#010;script:alert('hello');">

<img src="java&#X0A;script:alert('hello');">

<style TYPE="text/javascript">

  alert('hello');

</style>

 

4.     Use the HttpOnly Cookie Option

(HttoOnly 쿠키 옵션을 사용 하라)

Internet Explorer 6 Service Pack 1 and later supports an HttpOnly cookie attribute, which prevents client-side scripts from accessing a cookie from the document.cookie property.

Instead, the script returns an empty string. The cookie is still sent to the server whenever the user browses to a Web site in the current domain.

The System.Net.Cookie class in Microsoft .NET Framework version 2.0 supports an HttpOnly property. The HttpOnly property is always set to true by Forms authentication.

Earlier versions of the .NET Framework (versions 1.0 and 1.1) require that you add code similar to the following to the Application_EndRequest event handler in your application Global.asax file to explicitly set the HttpOnly attribute.

( 인터넷 익스플로러 서비스팩 1 이후 버전 부터 HttpOnly 속성을 지원한다. 이 속성은 document.cookie 속성과 같이 클라이언트 단 스크립트로 쿠키에 접근 하는 것을 방지 한다. 이 속성을 사용 하면 스크립트는 쿠키 값 대신 공백 문자열을 반환 한다. 쿠키는현재 도메인 하에  여전히 사용자 브라우저에서 서버로 전송 된다.  .NET Framework  2.0부터 System.Net.Cookie 클래스는 HttpOnly 속성을 지원한다. 또한 폼 인증 쿠키는 항상 HttpOnly 속성 값이 true로 설정 된다. 1.0, 1.1과 같은 초기 버전의 .NET Framework에서는 Global.asax 파일의 Application_EndRequest 이벤트 핸들러에 유사한 코드를 추가 함으로써 명시적으로  HttpOnly 속성을 지정 할수가 있었다. 코드는 아래와 같다.)

 

protected void Application_EndRequest(Object sender, EventArgs e)

    {

        //.NET 2.0은 기본적으로 속성 True

        // 아래 코드는 1.1 이하 코드에서

        string authCookie = FormsAuthentication.FormsCookieName;

        foreach (string sCookie in Response.Cookies)

        {

            if (sCookie.Equals(authCookie))

            {

                Response.Cookies[sCookie].Path += ";HttpOnly";

            }

        }

    }

 

 

또한 사용자 데이터를 쿠키에 담고 싶다면 다음 처럼 FormsAuthenticationTicket 을 생성 할때 사용자 데이터를 넘겨 인증 쿠키에 추가 한다. 이렇게 처리 하면 기본적으로 HttpOnly 속성이 쿠키에 할당 될 뿐 아니라 개발자가 해당 데이터를 암호화 할 필요가 없다.

 

 

  StringBuilder userData = new StringBuilder();

  userData  = 필요한 데이터를 할당 한다.;

 

            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, member.MemberGuid, DateTime.Now, DateTime.Now.AddMinutes(300), false, userData.ToString());

 

            string encTicket = FormsAuthentication.Encrypt(ticket);

 

            HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

  

 

인증 쿠키에 할당 된 사용자 데이터는 다음 처럼 가져 온다.

 

 

 if (HttpContext.Current.Request.IsAuthenticated)

 {

            FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;

            FormsAuthenticationTicket ticket = id.Ticket;

            string memberData = ticket.UserData.;

 

 

5.     Use the <frame> Security Attribute (frame에 Security 속성을 사용 하라)

Internet Explorer 6 and later support a new security attribute for the <frame> and <iframe> elements. You can use the security attribute to apply the user's Restricted Sites Internet Explorer security zone settings to an individual frame or iframe. By default, the Restricted Sites zone does not support script execution.

If you use the security attribute, it must be set to "restricted" as shown in the following.

(IE 6을 포함 한 이후 버전에는 frameiframe 개체에 security 속성을 지원 한다. 이 security 속성을 사용 하여 각각의 frame 과 iframe에 사용자 제한 적인 보안 영역을 설정 할 수 있다. 기본값으로 이 제한된 사이트 영역은 스크립트 실행을 제한한다.  security 속성을 사용 하기 위해서는 아래 처럼  속성 값을 "restricted" 로 설정 하여야 한다.)

<frame security="restricted" src="http://www.somesite.com/somepage.htm"></frame>

 

 

AntiXssLibrary 적용

http://msdn.microsoft.com/en-us/library/aa973813.aspx

 

The Microsoft Security Development Lifecycle (SDL) 팀에서는 모든 관리 코드 온라인 서비스에서 Anti-XSS 라이브러리 사용을 의무화하고 있습니다.

 

Benefits of the Microsoft SDL

Reducing the number of software vulnerabilities

The SDL has played a critical role in embedding security and privacy into Microsoft software and culture, leading to measurable and widely recognized security improvements in flagship products such as Windows Vista and SQL Server.

Reducing the total cost of development

The SDL reduces the “total cost of development” by finding and eliminating vulnerabilities early. According to the National Institute of Standards and Technology (NIST), eliminating vulnerabilities in the design stage can cost 30 times less than fixing them post release.

 

첨부된 파일이나 아래 참조 사이트를 확인 하여 보면 Microsoft.Security.Application.AntiXss.HtmlEncode(string) 처럼 사용 하는 것을 알 수 있는데 .NET Framework에서 제공 하는 Encode 방식과 차이점은 잘 모르겠다.

 

단. JavaScriptEncode와 같은 추가 적인 Encode 방식도 지원 한다.

 

Ref: http://msdn.microsoft.com/en-us/security/cc448177.aspx

 

예제 페이지:

Default.aspx

HtmlEncode_Example1.aspx

HtmlEncode_Example3.aspx

HtmlAttributeEncode_Example1.aspx

JavaScriptEncode_Example1.aspx

UrlEncode_Example1.aspx

 

첨부 된  SampleApplication.zip 파일에 예제 페이지 및 Anti-XSS 라이브러리 샘플이 포함 되어 있다.

 

반응형
반응형
비호환 목록 사이트 정보에 포함시키면 된다.
IE8에서 깨져보인다면 메타태그쪽에
<meta http-equiv="X-UA-Compatible" content="IE=8" /> 
를 추가시키면 짜잔~ IE7과 같은 것으로 변신~
반응형
반응형

다음은 두개의 데이터 베이스에 있는 내용을 TreeView에 Binding하는 소스입니다..
천 번째 데이터 베이스에는 학교의 분류와 그에 해당하는 key값이 들어있고,
두 번째 데이터 베이스에는 해당 key값에 정의된 학교 이름이 있습니다..

SqlDataAdapter adp1 = new SqlDataAdapter("select * from school_category",con);
            
            DataSet ds1 = new DataSet();
            adp1.Fill(ds1);

            string ca = "";

for(int i = 0;i<ds1.Tables[0].Rows.Count;i++)//첫번째 데이터의 rows만큼 반복
            {
                Microsoft.Web.UI.WebControls.TreeNode item = new Microsoft.Web.UI.WebControls.TreeNode();        
item.Text = "ds1.Tables[0].Rows[i][1].ToString()";//Root노드의 텍스트 입력
                item.CheckBox = true;
                this.TreeView1.Nodes.Add(item);//Root 노드 추가

                ca = ds1.Tables[0].Rows[i][0].ToString();//Root노드의 Key값 가져오기

                SqlDataAdapter adp2 = new SqlDataAdapter("select * from school where category="+ca+"",con);
        
                DataSet ds2 = new DataSet();
                adp2.Fill(ds2);

                for(int j = 0;j<ds2.Tables[0].Rows.Count;j++)//두 번째 데이터의 rows만큼 반복
                {
                    Microsoft.Web.UI.WebControls.TreeNode item2 = new Microsoft.Web.UI.WebControls.TreeNode();                               item2.Text = ds2.Tables[0].Rows[j][1].ToString();//자식 노드의 텍스트 입력
                    item2.CheckBox = true;
                    this.TreeView1.Nodes[i].Nodes.Add(item2);//자식 노드 추가
                    //여기서 주의 할 점은 this.TreeView1.Nodes[i].Nodes.Add(item2);에서 j 가 아니라 i의 노드에 추가 한다는 것입니다..만약 j로 입력하면 에러가 발생합니다..주의하시길..
                }
            }

 

<출처 : Devpia>

반응형
반응형

.Net Framowork 3.5에 Addon 된 MSChart를 이용하여 그래프 형태의 통계 웹파트 또는 페이지를 구현하고자 할 때 아래와 같이 Web.config에 추가 해 주어야 한다. 

1. AppSettings 에 추가 (MSChart Sample의 구성섹션에는 Storage=file 이므로 이부분만 주의 하면 된다.)
<add key="ChartImageHandler" value="Storage=memory;Timeout=20;Url=~/tempImages/;"
/>

2. System.Web의 httpHandlers 에 추가
<add path="ChartImg.axd" verb="GET,HEAD" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>

3. System.WebServer의 handlers 에 추가

<
remove name="ChartImageHandler"/>

<add name="ChartImageHandler" preCondition="integratedMode" verb="GET,HEAD" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

반응형
반응형

SqlServer2005 
SqlBulkCopy() 사용하기 
----------------------- 
pubic void BulkInsert() 
{ 
  SqlBulkCopy bcp = new SqlBulkCopy("Data Source=localhost...ConnectionString...."); 
  bcp.DestinationTableName = "테이블명"; //sqlserver2005에 미리 테이블이 있어야 함. 
  bcp.BulkCopyTimeout = 600; //무한정 입력시도 방지 
  bcp.WriteToServer(ds.Tables[0]); 
} 
--------------------------------------------------- 
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Data; 
using System.Data.SqlClient; 

namespace SqlBulkCopyDemo 
{ 
   class Program 
   { 
      private static string connectionString = "Server=(local);Database=Test;Integrated Security=SSPI"; 

      static void Main(string[] args) 
      { 
         using (SqlConnection firstConnection = new SqlConnection(connectionString)) 
         { 
            SqlCommand cmdAnimals = firstConnection.CreateCommand(); 
            cmdAnimals.CommandText = "Select * from Animals"; 
            firstConnection.Open(); 
            SqlDataReader dr = cmdAnimals.ExecuteReader(); 

            using (SqlConnection secondConnection = new SqlConnection(connectionString)) 
            { 
               SqlBulkCopy bc = new SqlBulkCopy(secondConnection); 
               bc.DestinationTableName = "AnimalsCopy"; 
               bc.WriteToServer(dr); 
               bc.Close(); 
               dr.Close(); 
            } 
         } 
      } 
   } 
} 
-------------------------------------------------------------- 
using System; 
using System.Data; 
using System.Configuration; 
using System.Collections; 
using System.Web; 
using System.Web.Security; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using System.Web.UI.WebControls.WebParts; 
using System.Web.UI.HtmlControls; 
using System.Data.SqlClient; 

public partial class _Default : System.Web.UI.Page 
{ 
    protected void Page_Load(object sender, EventArgs e) 
    { 

        string connectionString = ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString; 
        SqlConnection myConnection = new SqlConnection(connectionString); 
        SqlCommand myCommand = new SqlCommand("SELECT * FROM Person", myConnection); 
        myConnection.Open(); 
        SqlDataReader dr = myCommand.ExecuteReader(); 

        SqlConnection myNewConnection = new SqlConnection(connectionString); 
        myNewConnection.Open(); 
        SqlBulkCopy bulk = new SqlBulkCopy(myNewConnection); 
        bulk.DestinationTableName = "[Person2]"; 

        bulk.ColumnMappings.Add("Name", "LastName"); 
        bulk.ColumnMappings.Add("Email", "Email"); 
        bulk.ColumnMappings.Add("Picture", "Picture"); 


       try 
        { 
            bulk.WriteToServer(dr); 
        } 
        catch (Exception ex) 
        { 
            Response.Write(ex.Message); 
        } 
        finally 
        { 
            myNewConnection.Close(); 
            dr.Close(); 
            myConnection.Close(); 
            bulk.Close(); 
        } 

    } 

}

 

 

 

  [닷넷 솔루션 ASP.NET 전문교육기관 ]

반응형
반응형

IIS7 에러 해결하기

실버라이트를 돌리자

IIS7를 처음으로 사용을 해보면서 여러가지 에러들이 발생을 하게 되었습니다...많은 시행착오를 거쳤는데요.. 나온지 얼마 되지 않은 프로그램이이서 그런지 여러 에러에 대한 해결방법이 많지는 않았습니다. 그래서 저는 이번에 IIS7에서 응용프로그램을 만들어 보고 기본설정에서  설정테스트를 해보면은 권한부여에 대해서 에러가 나는 경우가 있습니다. 그 에러를 해결하는 방법을 알려드리겠습니다.

IIS7의 시작화면,

IIS7를 처음 시작하면 보여주는 화면입니다. 제가 사용하는 응용프로그램과 여러가지 설정을 할수 있는 아이콘들이 있습니다. 저는 많은것은 모르고 조금만 알고 있구요..지금도 공부를 하고 있는 상태입니다.

IIS7 권한부여 에러 페이지 

왼쪽의 트리뷰에서 자신이 원하는 응용프로그램을 선택을 하고 나서 기본설정을 가면 설정테스트를 할 수가 있습니다. 그리고 설정 테스트를 선택을 하시면 밑의 그림과 같이 권한부여에서 !표가 뜨는 것을 확인 할 수 있습니다. 에러는 아니지만 약간은 찜찜한 기분을 들게 하고 있습니다. 이 에러를 해결 하는 방법은 간단합니다.

연결계정을 설정을 해봅시다

연결계정을 클릭을 하시면 그림처럼 되어있을 겁니다. 여기서 사용자 지정을 클릭을 하고 설정을 누르세요

 

자격 증명설정

설정을 누르면 옆의 그림처럼 나오는데요.. 비스타 로그인할때 쓰는 아이디와 비밀번호를 써주시고 확인을 눌러주세요

 

 

바뀐 연결 계정 설정

예전의 연결 계정 설정과는 다른 모습을 볼 수가 있습니다. 이렇게 하면은 과연 느낌표가 사라져 있을지 확인을 해볼게요

 

바뀌었습니다.

 

이게 과연 얼마나 도움이 될지는 모르지만 찜찜한 느낌은 사라질 거 같습니다.

 

 

 

 

Posted by 마야울
반응형
반응형
반응형

'Program > ASP.NET' 카테고리의 다른 글

닷넷(.NET) ADO.NET 2.0 SqlBulkCopy 사용예제  (0) 2010.01.20
IIS7에서 권한부여 에러 해결하기  (0) 2009.12.28
Blog Source  (0) 2009.07.19
Wiki source  (0) 2009.07.19
처리되지 않은 예외 처리방안  (0) 2009.06.16
반응형
반응형
반응형
반응형
반응형

Page_Error 이벤트 메서드

ASPX 페이지가 실행되다가 어떤 에러가 발생한다면, 틀림없이 틀림없이 생겨난다! Page_Error 이벤트. 그렇습니다. 만일, 여러분이 Page_Error 이벤트 메서드를 작성해 두었다면, 웹 페이지(aspx)가 실행되다가 예외가 발생할 경우, 무조건 Page_Error 이벤트가 호출됩니다. 그렇기에, 이 이벤트 메서드를 작성해두면 예기치 않은 예외가 발생하는 경우에 여러분이 원하는 처리를 수행할 수 있습니다. 예를 들면, 예외 메시지를 로깅 한다거나, 관리자에게 메일을 보낸다거나, 사용자에게 부드러운 안내 메시지를 제공한다거나 하는 것들을 말이죠. 일반적으로 현업에서는 이러한 처리 모두를 수행하곤 하죠.

만일, 여러분이 공통 부모 페이지 클래스로서 PageBase라는 클래스를 만들어, 모든 웹 페이지가 이 클래스를 상속하도록 애플리케이션을 설계했다면, PageBase 클래스의 Page_Error 이벤트 메서드를 사용하여 모든 페이지에서 발생하는 예외를 중앙집중적으로 관리할 수도 있습니다. 그리고, 대부분의 웹 애플리케이션에서는 이 방법이 권장되기도 합니다. 다음은 이러한 구조로 설계된 코드 샘플입니다.

PageBase.cs의 소스
using System;
using System.Web.UI;

namespace MyWebApp.Common
{
    public class PageBase : Page
    {
        protected void Page_Error(object sender, EventArgs e)
         {
            //페이지 수준에서 처리해야 할 예외들을 추출해서
            //예외를 데이터베이스나 파일에 로깅한다
             //혹은, 관리자에게 예러 내용을 메일로 보낸다.
        }
    }
}

ASPX 페이지들의 코드 비하인드

using System;
using MyWebApp.Common;

namespace MyWebApp
{
    public partial class MyPage1 : PageBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            //작업
         }
    }
}

다만, Page_Error 이벤트는 이름에서 보여지는 대로 웹 페이지(Page)의 라이프 사이클 내에서 발생하는 예외만을 잡을 수 있다는 단점(?)이 있습니다. 즉, 페이지 레벨에서의 예외만을 처리할 수 있고, 애플리케이션 레벨의 예외는 잡을 수 없다는 것이죠. 중, 소규모의 웹 사이트는 별도의 모듈이나 외부 컴포넌트를 사용하지 않는 단순한 구조이기에 사실상 페이지 수준의 예외만 잡아도 충분합니다. 하지만, 대규모의 사이트에서는 애플리케이션이 상당히 복잡하며, 수 많은 모듈들(HttpModule), 추가 처리기(HttpHandler), 외부 라이브러리, 컴포넌트 등이 사용될 수 있습니다. 그리고, 이들 중 일부는 사실상 페이지 라이프 사이클 외에 속해 있기에 Page_Error 이벤트만으로는 모든 예외 상황에 대비할 수가 없습니다. 예를 들면, HttpModule로 제작된 모듈에서 발생되는 예외는 Page 레벨에서 잡을 수가 없다는 것이죠.

해서, ASP.NET은 Application_Error라는 더욱 광범위한 예외 처리를 위한 장소를 제공합니다.

Application_Error

웹 페이지를 포함하여 현재의 ASP.NET 애플리케이션(페이지, 모듈, 핸들러 등을 포함)이 구동되다가 에러가 발생하게 되면 Application_Error 이벤트가 발생하게 됩니다. 이는 일반적인 에러부터 처리되지 않은 에러까지 모두가 피할 수 없이 거쳐가는 이벤트 메서드이기에 애플리케이션 수준에서 예외를 처리하고자 할 경우 대단히 유용한 처리 장소를 제공합니다. 다음 그림을 한번 참고해 보시기 바랍니다(참고로, 이 그림은 IIS 6에서의 요청 파이프라인을 보여주고 있습니다)

모든 요청은 우선 HttpRuntime을 거쳐 HttpApplication에게로 넘겨지며, HttpApplication 내부에 속해 있는 다양한 HttpModule을 수행한 다음, HttpHandler인 ASP.NET 처리기에게로 넘겨지게 됩니다. 페이지 수준의 예외 처리(Page_Error)는 HttpHandler 내부에서 발생하는 예외를 처리할 수 있는 반면, 지금 이야기하는 애플리케이션 수준의 예외 처리(Application_Error)는 페이지를 포함한 Application 전체 파이프 라인에서 발생하는 모든 예외를 처리할 수 있게 합니다(이에 대한 자세한 이야기는 http://taeyo.net/Columns/View.aspx?SEQ=97&PSEQ=8&IDX=0 를 참고하세요).

고로, 이 이벤트 메서드가 모든 예외의 중앙 관문이자, 예외 로깅을 위한 최적의 장소이기도 합니다. 그렇기에, "예외고 뭐고 난 잘 모르겠다. 그냥 다 필요 없고 한 줄 결론만 말해줘" 하시는 분들에게는 "코드에서는 예외 처리를 아무것도 하지 마시고, Global.asax에 있는 Application_Error 메서드에 필요한 예외 처리 코드를 넣으세요" 라고 말씀드리곤 합니다.

Application_Error 이벤트는 페이지에서 예외가 발생하던, 애플리케이션에서 예외가 발생하던 모든 경우에 호출되므로, 폭넓게 사용할 수 있습니다. 해서, 중,소 규모의 애플리케이션이라면 아예 Page_Error 이벤트 메서드는 작성하지 않고, Application_Error 이벤트 메서드에서 모든 예외 처리를 수행하기도 하죠.

그렇다면, 한번 예제를 통해서 Application_Error 이벤트가 얼마나 유용한지 확인해 보도록 하겠습니다. 우선, 현재의 프로젝트에 Global.asax 페이지를 하나 만들고, 비하인드 페이지에 다음과 같이 데모용 로깅 코드를 작성해 보도록 해요.

using System;
using System.IO;
using System.Web;

namespace MyWebApp
{
    public class Global : System.Web.HttpApplication
    {

        // .. 기타 이벤트 코드들

        protected void Application_Error(object sender, EventArgs e)
        {
            //예외를 데이터베이스나 파일에 로깅한다
            Exception ex = HttpContext.Current.Server.GetLastError();
            string msg = this.GetErrorMessage(ex);

            msg = "\r\n-------------------------------------------------------" +
                "\r\n예외 발생 일자 : " + DateTime.Now.ToString() + msg;

            StreamWriter tw = File.AppendText(@"D:\temp\log.txt");
            tw.WriteLine(msg);
            tw.Close();
        }

        private string GetErrorMessage(Exception ex)
        {
            string err = "\r\n 에러 발생 원인 : " + ex.Source +
                "\r\n 에러 메시지 :" + ex.Message +
                "\r\n Stack Trace : " + ex.StackTrace.ToString();

            if (ex.InnerException != null)
            {
                err += GetErrorMessage(ex.InnerException);
            }

            return err;
        }

        // .. 기타 이벤트 코드들

    }
}

코드를 보면 대략적인 내용을 이해하실 수 있겠죠? 그렇습니다. 예외가 발생할 경우, 현재 발생한 예외(내부 예외가 있다면 그들까지 모두)의 모든 StackTrace 정보를 문자열로 구성하여 텍스트 파일에 저장하는 것입니다. 즉, 예외의 세부적인 내용을 로깅하는 것이죠.

HttpContext.Current.Server.GetLastError 라는 코드를 통해서 최종적으로 발생한 예외 개체를 얻어낼 수 있다는 것과 순환 함수로서 작성된 GetErrorMessage(..) 함수(네. 명칭은 맘에 안 드네요)를 사용하여 예외 정보를 적절한 문자열로 구성하는 것은 기억을 하시는 것이 좋겠네요.

그리고, 위의 예제 코드에서는 작성되지 않았습니다만, Application_Error 이벤트 구역 내의 코드(즉, 로깅코드)는 try..catch로 둘러싸는 것이 좋습니다. 로깅하다가 에러가 발생되면 다시 또 Applicarion_Error 이벤트가 호출되는 무한반복의 문제가 발생할 수 있으니까요(다음 강좌에서 이 부분에 대해서는 좀 더 설명드릴 예정입니다)

자. 코드가 준비되었다면 이제 강제로 예외를 일으켜서 우리의 예외 처리 방안이 잘 동작하는 지 확인해 보도록 하죠. ThrowError.aspx 라는 페이지를 하나 생성하고, 다음과 같이 강제적으로 예외를 일으키는 코드를 Page_Load에 넣어보도록 하겠습니다.

using System;
using System.Web.UI;

namespace MyWebApp
{
    public partial class ThrowError : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            throw new Exception("강제로 예외를 어이쿠 발생시켰습니다");
        }
    }
}

그리고, 이 페이지를 실행(브라우저로 요청)해 보도록 하죠.

참고. 개발용 웹 애플리케이션은 반드시 가상 디렉토리로 구성합니다

물론, 여러분도 현재의 프로젝트를 가상 디렉토리로 설정하셨겠죠? 개발 시에는 반드시 그렇게 하는 것이 좋습니다. 비록 VS 가 기본적으로는 가상 IIS를 지원한다고 해도 말이죠. 그렇지 않으면, 가끔씩 IIS로 구동시킬 경우와는 다른 오동작(?)을 하는 경우가 있으므로, 꼭 가상 디렉토리 설정을 하시기 바랍니다.

혹시 아직 하지 않으셨다면, 프로젝트의 [속성] 창에 가셔서 다음과 같이 [Web] 탭에서 가상 디렉토리를 구성해 주시면 됩니다.(제 VS는 영문이라 영문 설정이 나오는 부분은 미리 죄송합니다)

페이지는 당연히 예외를 발생시킬 것입니다. 그리고, 여러분의 web.config 설정( 구역의 설정)에 따라 자세한 예외 메시지가 페이지에 나타날 수도 있고(개발 시에는 이렇게 설정해야겠죠?), 혹은 단순히 에러가 났다는 메시지만 나타날 수도 있습니다(실제 서버에서는 이렇게 나오게 해야겠죠?).

저는 실제 서버 기준으로 설정해 두었기에(즉, 으로 설정해 두었기에) 다음처럼 에러 페이지가 출력되고, 자세한 에러의 정황은 나타나지 않고 있는 것을 볼 수 있습니다.

에러가 났다면, 서버의 D:\temp\log.txt경로로 가서, 로그 파일에 자세한 에러 정보가 기록되어 있는지 확인해 보도록 합니다.

에러가 발생한 구체적인 정보가 로깅되어 있는 것을 확인할 수가 있습니다.

현재는 로그 파일명을 log.txt로 고정시켜 두었습니다만, 파일명은 날짜로 지정하여 각 날짜마다 각각의 로그 파일이 생성되도록 할 수도 있을 것입니다. 그리고, 관리자는 매일 매일 해당 날짜의 로그 파일을 열어서 예외가 발생한 내역이 있는지, 있다면 어떤 예외였는지를 파악하여 문제점을 보완할 수 있겠죠? 예외 파일이 전혀 생성되지 않는 날(로그 파일이 없으면 예외가 발생한 것이 없다는 의미이기에)을 꿈꾸며…

그리고, 좀 더 능력있는 개발자라면 저 로그를 Xml 형태로 기록한다거나, 특정 데이터베이스에 저장하도록 처리하고, 예외 로그 뷰어 같은 것을 제작한다면 보다 쉽게 예외를 관리하고 상부에 보고할 수 있을 듯도 하네요.

그렇다면, 예외 처리는 이 정도로 충분한가?

큰 줄기는 이걸로 충분하다고 봅니다만, 세세하게는 아직 다루어야 할 이야기가 조금 남아있습니다. 예를 들면, 위와 같이 중앙집중적으로 예외를 관리할 수 있게 되었다 하더라도, 여전히 try.. catch 구문을 사용할 일이 있다는 것인데요.

DAL(데이터 액세스 레이어)에서 트랜잭션을 처리할 경우 등등 에서 올바른 커밋이나 롤백을 위해 try.. catch 구문이 요구되기도 합니다. 그를 통해, 예외에 대비하는 방어적인 코드를 작성할 필요가 있다는 것이죠.

다만, 그러한 경우에도 코딩 위치에서 필요한 처리를 수행한 뒤에는 가급적 상위로 예외를 전달해주는 것이 좋습니다. 만일, 해당 부분에서 발생한 예외를 위로 전달하지 않으면, 현재의 try… catch를 거치면서 에러가 사라지게 되어 어떤 문제가 생겼는 지를 올바르게 로깅할 수 없는 상황이 연출될 수 있기 때문입니다. 모든 예외 발생 상황은 로깅이 되어야 하며, 그래야 사후 대응을 할 수 있습니다. 코드를 통한 로직으로 예외에 대비했다고 하더라도 그것이 문제 요소를 해결한 것은 아니기에 반드시 로깅을 하고, 사후에 예외가 발생하게 된 원인을 분석하여 문제 요소를 근원부터 해결할 필요가 있습니다. 사실, 이러한 이유로 런타임 예외를 로깅하는 것이죠.

또한, 지금처럼 예외 대응 코드를 global.asax 파일에 넣어두는 부분도 사실은 그다지 깔끔하다고 볼 수 없는데요. Global.asax 파일은 이 외에도 다양한 코드로 인해 충분히 복잡해질 수 있기 때문입니다. 해서, 예외 대응 코드는 별도의 HttpModule로 분리 제작하여 각 애플리케이션에서 재활용할 수 있도록 처리하는 것이 훨씬 엘레강쓰 합니다.

authored by Taeyo


강좌 잘 보았습니다. 한가지 조심해야 할점은 Application_Error 는
Application_Error 안에서의 에러도 잡아냅니다. 만약 Application_Error 에서 에러
가 나거나 할경우 무한루프에 걸립니다.(수차례 경험....ㅠ.ㅠ) 그리고 예제 코드에
는 없지만, MSDN 에 따르면...
After handling an error, you must clear it by calling the ClearError method of
the Server object (HttpServerUtility class).

이라고 나와있습니다. Application_Error 마지막 부분에
HttpContext.Current.Server.ClearError() 요놈도 넣어주는게

반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

태그 목록 페이지(Tag.aspx) 페이지 구현

마소 3월호에서 관리자가 포스트를 작성할 경우 블로그에 관련된 태그(블로그 태그는 작성한 포스트가 가지는 특징적인 키워드를 말한다고 생각할 수 있다.)를 입력하여 데이터베이스에 저장할 수 있도록 하였다. 이 때 저장되는 태그들의 목록을 볼 수 있는 태그 목록 페이지를 만드는 방법에 대해서 설명하도록 하겠다. 우선, 태그 정보를 "BLOG_TAGS" 테이블에 추가/삭제하는 "dbo.usp_SetTagInfo" 저장 프로시저의 코드는 <리스트 5>와 같이 작성되어 있다.

CREATE PROCEDURE [dbo].[usp_SetTagInfo]
(
    @command    VARCHAR(6)
,   @tag        VARCHAR(20)
)
AS
BEGIN
    SET @tag = LTRIM(@tag)
 
    IF @command = 'create'
    BEGIN
        -- 기존에 입력된 태그 정보가 없으면
        IF (0 = (SELECT COUNT(TagName) FROM dbo.BLOG_TAGS 
            WHERE UPPER(tagname) = UPPER(@tag)))
        BEGIN
            INSERT INTO dbo.BLOG_TAGS VALUES (@tag, 1)
        END
        ELSE
        BEGIN
            UPDATE dbo.BLOG_TAGS SET tagcount = tagcount + 1
            WHERE UPPER(tagname) = UPPER(@tag)
        END
    END
    ELSE IF @command = 'delete'
    BEGIN
        UPDATE dbo.BLOG_TAGS SET tagcount = tagcount - 1
        WHERE UPPER(tagname) = UPPER(@tag)
 
        -- 현재 태그의 개수가 0이면 삭제
        IF (0 = (SELECT COUNT(TagName) FROM dbo.BLOG_TAGS 
            WHERE UPPER(tagname) = UPPER(@tag)))
        BEGIN
            DELETE FROM dbo.BLOG_TAGS 
            WHERE UPPER(tagname) = UPPER(@tag)
        END
    END
END
<리스트 5> dbo.usp_SetTagInfo 저장 프로시저의 코드

<리스트 5>에서 보는 것과 같이 "dbo.usp_SetTagInfo" 저장 프로시저에 대해 간략하게 설명하자면, @command 매개 변수에 "create"라는 값이 들어오게 되면, 우선 "BLOG_TAGS" 테이블에 @tag 매개 변수에 입력되는 태그 값에 관련된 정보가 있는지 없는지를 확인한다. 만약 해당 태그 정보가 있으면, "BLOG_TAGS" 테이블의 TagCount 필드의 값을 1 증가시켜주고, 태그 정보가 없는 경우에는 태그 정보를 테이블에 추가하게 된다. 마찬가지로, @command 매개 변수에 "delete"라는 값이 들어오게 되면, 해당 태그의 TagCount 필드의 값을 1 감소시킨 후, 만약 해당 태그의 TagCount 필드의 값이 0이 되면 해당 행을 삭제하게 된다. <리스트 5>에 사용된 코드를 이용하여, "dbo.usp_SetTagInfo" 저장 프로시저를 생성후에 “App_Code" 폴더 안에 이미 생성되어 있는 ”BlogDataClasses.dbml“ 파일의 디자이너 화면의 우측 영역에 이 저장 프로시저를 추가한다. 그 후에, "Admin.aspx.cs" 코드 비하인드 파일을 열어서 포스트의 작성/수정/삭제 시에 사용되는 각 이벤트에서 "dbo.usp_SetTagInfo" 저장 프로시저를 이용하여 "BLOG_TAGS" 테이블에 값을 추가/수정/삭제하는 SetTagList() 메소드를 호출하는 코드를 추가한다. <리스트 6>은 추가된 "Admin.aspx.cs" 코드 비하인드 파일의 소스 코드 및 SetTagList() 메소드의 코드를 보여준다.

    // 포스트 쓰기 탭에서 작성 버튼 클릭 시
    protected void btnPostWrite_Click(object sender, EventArgs e)
    {
        try
        {
            if (chkNotice.Checked)
            {
                ... <코드 생략> ...
            }
            else
            {
                ... <코드 생략> ...
                SetTagList(string.Empty, txtPostTag.Text, "create");
            }
        }
        catch (Exception ee)
        {
            ... <코드 생략> ...
        }
    }
 
    // 포스트 수정 탭에서 수정 버튼 클릭 시
    protected void btnPostModify_Click(object sender, EventArgs e)
    {
        try
        {
            if (!string.IsNullOrEmpty(strPostID))
            {
                if (chkModifyNotice.Checked)
                {
                    ... <코드 생략> ...
                }
                else
                {
                    ... <코드 생략> ...
                    string strOldTag = blogPost.tag;
 
                    ... <코드 생략> ...
                    SetTagList(strOldTag, txtModifyPostTag.Text, "modify");
                }
            }
        }
        catch (Exception ee)
        {
            ... <코드 생략> ...
        }
    }
 
    // 포스트 수정 탭에서 삭제 버튼 클릭 시
    protected void btnPostDelete_Click(object sender, EventArgs e)
    {
        try
        {
            if (!string.IsNullOrEmpty(strPostID))
            {
                if (chkModifyNotice.Checked)
                {
                    ... <코드 생략> ...
                }
                else
                {
                    ... <코드 생략> ...
                    string strOldTag = blogPost.tag;
 
                    ... <코드 생략> ...
                    SetTagList(strOldTag, string.Empty, "delete");
                }
            }
        }
        catch (Exception ee)
        {
            ... <코드 생략> ...
        }
    }
 
    private void SetTagList(string strOldTag, string strNewTag, string strCommand)
    {
        string[] strOldTagList = null;
        string[] strNewTagList = null;
 
        if (string.Compare(strCommand, "create", true) == 0)
        {
            strNewTagList = strNewTag.Split(',');
 
            // 신규 태그 정보 입력
            foreach (string strTag in strNewTagList)
            {
                int iTag = blogDataContext.usp_SetTagInfo(strCommand, strTag);
            }
        }
        else if (string.Compare(strCommand, "modify", true) == 0)
        {
            strOldTagList = strOldTag.Split(',');
            strNewTagList = strNewTag.Split(',');
 
            // 기존 태그 정보 삭제
            foreach (string strTag in strOldTagList)
            {
                int iOldTag = blogDataContext.usp_SetTagInfo("delete", strTag);
            }
 
            // 신규 태그 정보 입력
            foreach (string strTag in strNewTagList)
            {
                int iNewTag = blogDataContext.usp_SetTagInfo("create", strTag);
            }
        }
        else if (string.Compare(strCommand, "delete", true) == 0)
        {
            strOldTagList = strOldTag.Split(',');
 
            // 기존 태그 정보 삭제
            foreach (string strTag in strOldTagList)
            {
                int iTag = blogDataContext.usp_SetTagInfo(strCommand, strTag);
            }
        }
    }
<리스트 6> SetTagList() 메소드의 코드

<리스트 6>에서 보는 것과 같이 SetTagList() 메소드를 이용하여 "BLOG_TAGS" 테이블에 태그 정보와 사용된 태그 개수 정보를 제어하는 부분을 추가하였다. 이제 태그 목록(Tag.aspx) 페이지에서 "BLOG_TAGS" 테이블에 저장된 정보를 가져와서 화면에 출력하는 부분 및 포스트 조회 화면이나 기타 다른 화면에서 작성된 포스트의 태그 정보를 클릭시에 선택한 태그가 포함되어 있는 포스트의 목록 정보를 출력하는 부분에 대한 설명을 하도록 하겠다.

태그 목록(Tag.aspx) 페이지는 두 개의 ListView 컨트롤로 구성되어 있으며, 첫번째 ListView 컨트롤은 "BLOG_TAGS" 테이블에 들어있는 정보를 화면에 출력하기 위해 사용되고, 두번째 ListView 컨트롤은 "BLOG_POSTS" 테이블에 저장된 정보 중 Tag 필드의 값이 선택한 태그(쿼리스트링 혹은 폼을 통해 전달되는 tag값)의 값과 일치하는 포스트 목록을 출력하기 위해 사용된다. <화면 8>은 등록되어 있는 전체 태그 목록이 출력된 화면을 보여준다.


<화면 8> 등록된 전체 태그 목록 출력 화면

두번째 ListView 컨트롤은 데이터 소스 컨트롤로써 SqlDataSource 데이터 소스 컨트롤을 사용하며, SqlDataSource 데이터 소스 컨트롤에서 사용할 저장 프로시져는 "dbo.usp_GetPostsInfoByTag"로, 이 저장 프로시저는 <리스트 7>에서 보여지는 코드와 같이 구성되어 있으며, 저장 프로시저를 생성한 후에 SqlDataSource 데이터 소스 컨트롤의 데이터 소스를 <화면 2>부터 <화면 6>에서 보여지는 것과 똑같이 구성하면 된다.

CREATE PROCEDURE [dbo].[usp_GetPostsInfoByTag]
(
    @tag    VARCHAR(20)
)
AS
BEGIN
    SELECT PostID, PostName, posts.CreateDate, PostContent
    , Tag, ViewCount, CommentCount, CategoryName
    FROM dbo.BLOG_POSTS posts
    INNER JOIN dbo.BLOG_CATEGORIES category
    ON posts.categoryid = category.categoryid
    WHERE posts.Tag LIKE '%' + @tag + '%'
    ORDER BY posts.CreateDate DESC
END
<리스트 7> dbo.usp_GetPostsInfoByTag 저장 프로시저의 코드

"Tags.aspx.cs" 코드 비하인드 파일의 Page_Load 이벤트에서는, 쿼리스트링 혹은 폼을 통해 전달되는 값 중 "tag"값이 있는지를 검사하여, "tag"값이 있는 경우에는 "Tag.aspx" 페이지에 정의된 SqlDataSource 데이터 소스 컨트롤의 SelectParameter에 "tag"의 값을 전달하여 ListView 컨트롤을 데이터바인딩시키고, "tag"의 값이 없는 경우에는 두번째의 ListView 컨트롤이 포함된 영역을 보이지 않도록 Div 컨트롤의 스타일 속성 중, display 속성을 "none"으로 할당시킨다. <화면 9>는 "tag"값으로 "LINQ"라는 값이 전달되었을 때의 태그 목록 페이지의 화면을 보여준다.

<화면 9> "LINQ"라는 태그가 포함된 포스트 목록 출력 화면

포스트 작성시에 입력한 태그 정보를 쉼표(,)를 구분자로 하여 각각의 태그를 선택할 수 있게 하기 위해서, 코드 비하인드 파일에 DisplayTagList()라는 이름의 메소드를 정의한다. 이 메소드는 "Tag.aspx" 페이지의 "LvwTagList"라는 ListView 컨트롤의 ItemTemplate 안에 "lblPostTag"라는 Label 태그의 Text 속성에서 사용되며, 태그 정보를 쉼표(,)를 구분자로 하여 하이퍼링크 형태로 화면에 출력하게 한다. <리스트 8>은 DisplayTagList() 메소드의 코드를 보여준다.

public string DisplayTagList(string strTags)
{
    string[] strArrayTag = strTags.Split(',');
    StringBuilder sbTag = new StringBuilder();
 
    foreach (string strTag in strArrayTag)
    {
        sbTag.AppendFormat
            ("<a href='/Tag/{0}.aspx' target='_self'>{0}</a>, ", strTag);
    }
 
    return sbTag.ToString().Substring(0, sbTag.ToString().Length - 2);
}
<리스트 8> DisplayTagList() 메소드 코드

<리스트 8>에 정의된 DisplayTagList() 메소드의 코드를 바탕으로 포스트의 목록 정보가 출력되는 "Archive.aspx", "Category.aspx", "PostView.aspx", "Search.aspx" 페이지의 코드 비하인드 파일에 각각 DisplayTagList() 메소드를 정의하고 각 페이지에 사용된 ListView 컨트롤에서 태그 정보가 출력되는 곳에서 DisplayTagList() 메소드를 호출하도록 이달의 디스켓에 들어있는 소스를 참고하여 코드를 변경한다.

관리자 기능의 추가 - 카테고리 관리, 즐겨찾기 관리

블로그의 우측 메뉴에서 보이는 카테고리 목록과 즐겨찾기 목록을 관리하는 기능에 대해서 설명하도록 하겠다. “Admin.aspx" 페이지를 열어, 기존에 정의한 TabPanel 컨트롤을 2개 추가한 후, 첫 번째 TabPanel 컨트롤의 HeaderTemplate에는 카테고리 목록, 두 번째 TabPanel 컨트롤의 HeaderTemplate에는 즐겨찾기 목록이라고 입력한다. 또한, TabPanel의 ContentTemplate 안에 UpdatePanel 컨트롤을 각각 위치시킨 후, UpdatePanel 컨트롤의 ContentTemplate 템플릿 안에 ListView 컨트롤을 추가한다. 그 후에, 카테고리 목록을 추가/수정/삭제/조회하는데에 사용될 SqlDataSource 데이터 소스 컨트롤을 ”Admin.aspx" 웹 페이지로 추가한 후, 스마트 태그의 “데이터 소스 구성(Configurate Data Source...) 메뉴를 눌러 데이터 소스 구성 마법사를 실행한다. <화면 2>를 참고하여 데이터 연결 선택 화면을 진행하고, <화면 3>에서 ”테이블 또는 뷰의 열 지정(Specify column from a table or view)“을 선택한 후, <화면 10>과 같은 SELECT 구문이 나올 수 있도록 WHERE 버튼과 ORDER BY 버튼을 이용하여 구성하도록 한다.


<화면 10> Select 구문을 구성하는 화면

또한, <화면 10>에서 보이는 우측의 “고급(Advanced...)" 버튼을 클릭하여, <화면 11>과 같이 ”Insert, Update 및 Delete 문 생성(Generate INSERT, UPDATE, and DELETE statements)“을 체크한 후, ”확인(OK)" 버튼을 클릭하고, “다음(Next)" 버튼을 클릭하여, <화면 6>에서 보이는 쿼리 테스트 화면의 ”종료(Finish)" 버튼을 클릭하여 데이터 소스 구성 마법사를 종료하면, 자동적으로 SqlDataSource 데이터 소스 컨트롤에 4개의 Command 속성(SelectCommand, InsertCommand, UpdateCommand, DeleteCommand)이 추가된다.


<화면 11> 고급 SQL 등록 옵션 구성 화면

마찬가지로 즐겨찾기 목록의 조회와 즐겨찾기 정보를 추가/수정/삭제하기 위한 SqlDataSource 컨트롤을 웹 폼에 추가한 후, 동일한 방법으로 데이터 소스 구성 마법사를 실행한다. 이러한 방법으로 정의된 SqlDataSource 컨트롤을 ListView 컨트롤과 결합한 화면은 <화면 12>와 같다. (즐겨찾기 목록 정보도 카테고리 목록 정보와 동일하게 추가/수정/삭제/조회가 가능하며, 정의된 SqlDataSource 컨트롤과 ListView 컨트롤과의 결합 정보는 본 필자가 태오사이트에 강좌로 등록한, ListView 컨트롤 Part2(http://www.taeyo.pe.kr/Lecture/NET/AspNet35_listView02.asp)를 참고하도록 한다.)


<화면 12> 카테고리 목록 구성 화면

설명대로 구성되어진 카테고리 목록에 새로운 카테고리를 추가하면, 블로그 우측 메뉴에 추가한 카테고리 목록이 나타나게 되며, 새로운 포스트를 작성하고자 할 때에도 카테고리의 목록이 나타나게 된다. 이와 동일하게 즐겨찾기 목록에 새로운 즐겨찾는 사이트 또는 블로그에 대한 정보를 추가하도록 한다. 그리고, 블로그의 마스터 페이지인 “BlogMasterPage.master.cs" 파일에 즐겨찾기 목록의 정보를 표시하는 GetFavoriteListInfo() 메소드를 추가한다. GetFavoriteListInfo() 메소드의 코드는 <리스트 9>에 정의되어 있으며, 블로그에 표시되는 즐겨찾기 목록은 <화면 13>과 같이 보여지게 된다.

    // 즐겨찾기 정보 가져오기
    private void GetFavoriteListInfo() 
    {
        BlogDataClassesDataContext blogDataContext 
            = new BlogDataClassesDataContext();
 
        List<BLOG_LINKS> blogLinkList =
                            (from c in blogDataContext.BLOG_LINKS
                             orderby c.sortkey ascending
                             select c).ToList();
 
        LvwFavoriteList.DataSource = blogLinkList;
        LvwFavoriteList.DataBind();
 
        blogDataContext = null;
    }
<리스트 9> GetFavoriteListInfo() 메소드 코드


<화면 13> 즐겨찾기 목록이 표시된 블로그 화면

마치며...

이로써, 4개월간의 Visual Studio 2008을 이용한 블로그 프로그래밍에 관련된 연재를 끝내고자 한다. 지면의 한계상 블로그 프로그래밍에서의 방명록에 관련된 기능과 트랙백에 관련된 기능 구현을 설명하지는 못했지만, 필자의 블로그(http://www.neostyx.net)에 “방명록 만들기”라는 포스트를 참조하여 방명록의 기능을 구현하였으면 한다. 또한, 트랙백에 관련된 포스트도 마소의 독자들을 위해서 올릴 예정이니 트랙백의 기능 구현도 필자의 블로그를 참고하면 좋겠다. 필자가 처음 글을 집필할 때의 의도처럼, 필자의 글을 바탕으로 Visual Studio를 이용해서 블로그를 만들려고 결심한 개발자 또는 독자가 많이 생겼으면 하며, 필자가 설명한 것보다 더 훌륭하고 멋진, 그리고 좋은 기능을 가진 블로그가 많이 나왔으면 하는 게 필자의 바램이다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

포스트 작성 기록 페이지(Archive.aspx) 페이지 구현

관리자에 의해 작성된 포스트를 “년도-달”의 형식으로 우측 메뉴 영역에서 볼 수 있도록 하는 기능은 지난 호에서 설명하였다. 이 목록을 선택할 경우에 선택한 달에 작성된 포스트의 목록을 볼 수 있는 페이지인 “Archive.aspx" 페이지를 만들어보도록 하자. 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, "Archive.aspx"라는 이름으로 새로운 웹 페이지를 만들도록 한다. “Archive.aspx" 페이지 역시, 지난 호에서 설명한 카테고리에 속한 포스트 목록 페이지를 보여주는 "Category.aspx" 페이지와 구현 방법은 거의 동일하므로 "Category.aspx"에서 사용한 코드를 거의 그대로 사용하도록 한다.

그리고 "Archive.aspx.cs" 파일의 Page_Load 이벤트에서 쿼리스트링으로 받은 archiveid값을 바탕으로 포스트 작성 기록 정보와 개수를 가져오기 위한 GetArchiveInfo() 메소드를 호출한다. 또한, 포스트 작성 기록 목록을 표현하기 위해 LinqDataSource 컨트롤을 사용하며, LinqDataSource 컨트롤의 OnSelecting 이벤트가 발생할 때 작성일의 년-월 정보가 쿼리스트링으로 받은 archivesid값과 같은 포스트의 목록을 가져오는 저장 프로시져를 호출하여 반환된 데이터 정보를 ListView 컨트롤을 이용하여 화면에 출력하는 방식을 사용하고 있다. 이 때 호출되는 저장 프로시져명은 “dbo.usp_GetArchivePostsInfo"이며, <리스트 1>과 같은 코드로 되어 있다.

CREATE PROCEDURE [dbo].[usp_GetArchivePostsInfo]
(
    @archiveid    VARCHAR(7)
)
AS
BEGIN
    SELECT PostID, PostName, CreateDate, PostContent, Tag, ViewCount, CommentCount
    FROM dbo.BLOG_POSTS
    WHERE CONVERT(VARCHAR(7), CreateDate, 120) = @archiveid AND DeleteDate IS NULL
    ORDER BY CreateDate DESC
END
<리스트 1> usp_GetArchivesPostsInfo 저장 프로시져의 코드

<리스트 1>의 코드를 이용하여 저장 프로시저를 생성한 후, “App_Code" 폴더 안에 이미 생성되어 있는 ”BlogDataClasses.dbml“ 파일의 디자이너 화면의 우측 영역에 이 저장 프로시저를 추가하기 위해, 기존에 연결시켜 놓은 "서버 탐색기(Server Explorer)"의 연결 정보를 새로고침하여, 추가된 ”dbo.usp_GetArchivePostsInfo“ 저장 프로시저를 검색한 후, ”BlogDataClasses.dbml“ 파일의 디자이너 화면의 우측 영역으로 저장 프로시저를 드래그 앤 드랍하여, 저장 프로시저를 추가한다. ”BlogDataClasses.dbml“ 파일의 디자이너 영역에 저장 프로시저가 추가된 것을 확인한 후에 선택한 "년도-달"에 속하는 포스트의 목록을 화면에 출력하기 위해 ListView 컨트롤과 LinqDataSource 데이터 소스 컨트롤을 이용하도록 한다. 여기에서 "Category.aspx"에서 사용된 LinqDataSource 데이터 소스 컨트롤과는 달리 LinqDataSource 컨트롤에는 Select, TableName, OrderBy 등의 속성을 별도로 정의하지 않으며, LinqDataSource 데이터 소스 컨트롤이 가지고 있는 OnSelecting 이벤트를 통해서 OnSelecting 이벤트가 발생할 때, ListView 컨트롤에 바인딩할 데이터의 값을 ”dbo.usp_GetArchivePostsInfo“ 저장 프로시저를 이용해서 가져오도록 하겠다. 웹 폼과 코드 비하인드 파일에서 정의되는 LinqDataSource 데이터 소스 컨트롤과 컨트롤의 이벤트에 관련된 코드는 <리스트 2>와 같다.

-- Archive.aspx의 코드
<asp:LinqDataSource ID="LinqDataSourcePostList" runat="server" 
            ContextTypeName="BlogDataClassesDataContext"
            OnSelecting="LinqDataSourcePostList_Selecting">
</asp:LinqDataSource>
 
-- Archive.aspx.cs의 코드
protected void LinqDataSourcePostList_Selecting(object sender
    , LinqDataSourceSelectEventArgs e) 
{
    blogDataContext = new BlogDataClassesDataContext();
    e.Result = blogDataContext.usp_GetArchivePostsInfo(_archiveID).ToList();
    blogDataContext = null;
}
<리스트 2> 정의된 LinqDataSource 데이터 소스 컨트롤의 코드

<리스트 2>에서 보는 것과 같이 저장 프로시저에 매개변수 값으로 _archiveID를 넣어주면, <리스트 1>에서 정의된 저장 프로시저의 구문에 의해서 결과가 반환되게 된다. 이 결과값을 LinqDataSourceSelectEventArgs가 가지고 있는 Result라는 객체에 할당하면, LinqDataSource 데이터 소스 컨트롤에 데이터가 추가되게 되며, 이 데이터를 바탕으로 ListView 컨트롤에 결과값이 표시되게 되는 것이다. 또한, "Category.aspx"에서 사용한 방법과 동일하게, ListView 컨트롤의 OnDataBinding 이벤트 발생시에 현재 화면에 표시되는 목록에 대한 세부 포스트 정보를 보여주기 위해서, GetPostListInCategory()라는 메소드를 호출하게 되며, 이 메소드는 DataPager 컨트롤의 StartRowIndex 속성값과 현재의 페이지 인덱스를 지정하는 전역변수인 _pageIndex, 그리고 화면에 출력할 페이지의 개수를 정의하는 전역변수인 _pageCount를 가지고 화면에 보여줄 포스트의 정보를 가져오게 된다. GetPostListInCategory() 메소드의 코드는 <리스트 3>에 정의되어 있다.

    private void GetPostListInCategory()
    {
        blogDataContext = new BlogDataClassesDataContext();
        _pageIndex = DpPostList.StartRowIndex / _pageCount;
 
        List<usp_GetArchivePostsInfoResult> blogPostList = 
            blogDataContext.usp_GetArchivePostsInfo(_archiveID).Skip(
            _pageIndex * _pageCount).Take(_pageCount).ToList();
 
        LvwPostInCategory.DataSource = blogPostList;
        LvwPostInCategory.DataBind();
 
        blogDataContext = null;
    }
<리스트 3> GetPostListInCategory() 메소드 코드

지금까지 설명한 것을 바탕으로 구현된 "Archive.aspx" 페이지의 화면은 <화면 1>과 같다.


<화면 1> 포스트 글 보관함 페이지 화면

검색 페이지(Search.aspx) 페이지 구현

다음으로는 검색 페이지를 구현하는 방법에 대해서 알아보도록 하겠다. 검색 페이지는 "Search.aspx" 파일에 정의되며, 검색어를 입력하게 되면, 포스트의 제목이나 포스트 본문의 내용, 태그 등을 검색하는 코드가 정의된 "dbo.usp_GetSearchPostsInfo" 저장 프로시저를 사용하여 검색어가 들어있는 포스트의 목록 정보를 반환하는 결과를 SqlDataSource 데이터 소스 컨트롤을 이용하여, ListView 컨트롤을 통해 화면에 출력하게 된다. 검색 페이지에서 사용되는 "dbo.usp_GetSearchPostsInfo" 저장 프로시저의 코드는 <리스트 4>와 같다.

CREATE PROCEDURE [dbo].[usp_GetSearchPostsInfo]
(
    @searchtext    VARCHAR(30)
)
AS
BEGIN
    IF @searchtext <> ''
    BEGIN
        SELECT PostID, PostName, posts.CreateDate, PostContent
        , Tag, ViewCount, CommentCount, CategoryName
        FROM dbo.BLOG_POSTS posts
        INNER JOIN dbo.BLOG_CATEGORIES category
        ON posts.categoryid = category.categoryid
        WHERE (PostName LIKE '%' + @searchtext + '%' 
            OR PostContent LIKE '%' + @searchtext + '%'
            OR Tag LIKE '%' + @searchtext + '%') 
            AND posts.DeleteDate IS NULL
        ORDER BY posts.CreateDate DESC
    END
END
<리스트 4> dbo.usp_GetSearchPostsInfo 저장 프로시저의 코드

<리스트 4>의 코드를 이용하여 저장 프로시저를 생성한 후, "Search.aspx"에 SqlDataSource 데이터 소스 컨트롤을 추가한다. 그리고 SqlDataSource 데이터 소스 컨트롤의 스마트 태그 메뉴의 "데이터 소스 구성(Configurate Data Source...)" 메뉴를 클릭하여, 데이터 소스 구성 마법사를 실행한다. <화면 2>와 같이 연결 문자열을 구성하는 화면이 나오게 되면, 기존에 Web.Config 파일에 연결 문자열이 정의되어 있으므로, 이 연결 문자열을 선택한 후, "다음(Next)" 버튼을 클릭한다.


<화면 2> 연결 문자열 정의 화면

다음으로는 Select 구문을 구성하는 화면이 나오게 된다. 여기에서는 <리스트 4>에서 정의된 "dbo.usp_GetSearchPostsInfo" 저장 프로시저를 사용할 것이므로, <화면 3>과 같이 "사용자 지정 SQL 문 또는 저장 프로시저 구성(Specify a custom SQL statement or stored procedure)"를 선택한 후 "다음(Next)" 버튼을 클릭한다.


<화면 3> Select 문 구성 화면

그러면, <화면 4>와 같은 사용자 지정 문 또는 저장 프로시져 정의 화면이 나오게 된다. 저장 프로시저로 "dbo.usp_GetSearchPostsInfo" 저장 프로시저를 사용할 것이므로, 화면 하단의 저장 프로시저 부분을 클릭한 후, "dbo.usp_GetSearchPostsInfo" 저장 프로시저를 선택 후, "다음(Next)" 버튼을 클릭한다.


<화면 4> 사용자 지정 문 또는 저장 프로시져 정의 화면

다음으로는 저장 프로시저에 정의된 @searchtext 매개 변수에 할당할 매개 변수 소스를 정의하는 화면인 매개 변수 정의 화면이 나오게 된다. "Search.aspx"에서는 txtSearch라는 TextBox 컨트롤에 입력되는 값을 통하여 검색을 할 예정이므로, 매개 변수 소스(Parameter Source)를 Control로 선택하고, ControlID값을 txtSearch로 선택하면, <화면 5>와 같게 된다. "다음(Next)" 버튼을 클릭한다.


<화면 5> 매개 변수 정의 화면

마지막으로 쿼리를 테스트할 수 있는 쿼리 테스트 창이 <화면 6>과 같이 나타나게 된다. "쿼리 테스트(Test Query)" 버튼을 통해서 정상적으로 값이 출력되는 지를 확인할 수 있다. "마침(Finish)" 버튼을 클릭하여, 데이터 소스 구성 마법사를 종료한다.


<화면 6> 쿼리 테스트 화면

이달의 디스켓에 포함되어 있는 소스를 참고하여, "Search.aspx"와 "Search.aspx.cs" 페이지의 코드를 구성한 후 웹 브라우저를 실행시켜, 블로그 메뉴 상단의 "Search" 메뉴를 클릭하면 검색 화면이 나타나게 되며, TextBox 컨트롤에 검색할 단어를 입력 후에 "Search" Button 컨트롤을 클릭하면, <화면 7>과 같이 검색하는 단어가 포함되는 포스트의 목록 정보가 화면에 출력되게 된다.


<화면 7> 검색어가 포함된 포스트 목록 정보


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

덧글 수정 및 삭제 페이지 구현

포스트 조회 화면에서 보이는 덧글 목록의 “[Modify]", "[Delete]" 버튼을 클릭하게 되면, 방문자는 입력한 덧글에 대한 수정 및 삭제를 할 수 있게 된다. 덧글의 수정 및 삭제를 위한 ”CommentMenu.aspx" 페이지를 웹 사이트에 새롭게 추가한 후, MultiView 컨트롤과 View 컨트롤을 이용하여 화면을 구성한다. 덧글 목록의 “[Delete]" 버튼을 클릭하게 되면, 덧글을 작성할 때 입력한 비밀번호를 입력하는 화면이 보이게 된다. 비밀번호를 입력 후 ”확인“ 버튼을 클릭할 때, 정확한 비밀번호를 입력하였으면 포스트 조회 화면이 새로고침되게 되며, 틀린 비밀번호를 입력하였으면, 비밀번호가 틀렸다는 메시지 창이 뜨게 된다. ”[Modify]" 버튼을 클릭시에도 "[Delete]" 버튼과 동일한 화면이 보여지게 되지만, 정확한 비밀번호를 입력하게 되면 덧글을 수정할 수 있는 화면이 나타나게 된다. <화면 6>은 ”[Modify]" 버튼을 클릭시 표시되는 화면과, 정확한 비밀번호를 입력하였을 때 표시되는 수정 화면을 보여주고 있다.


<화면 6> 덧글 수정시에 표시되는 화면

블로그 우측 메뉴의 구성

블로그 우측 메뉴에서 최근 포스트 목록 및 최근 덧글 목록 정보를 표시하기 위해, 몇 개의 포스트 및 덧글을 작성하도록 한다. 몇 개의 데이터를 입력한 뒤에 블로그 우측 메뉴에서 보여지는 최근 포스트 목록과 최근 덧글 목록 정보를 조회하여 출력하는 코드를 블로그의 마스터 페이지인 “BlogMasterPage.master" 및 ”BlogMasterPage.master.cs"에 추가하도록 한다. 최근 포스트 목록 정보와 최근 덧글 목록 정보를 조회하는 코드는 동일하며, 단지 테이블명만 다를 뿐이기에 최근 포스트 목록 정보를 가져오는 코드에 대해서만 설명하도록 하겠다. 최근 포스트 목록 정보를 가져오는 GetRecentlyPostInfo() 메소드를 Page_Load 이벤트에 정의한 후, <리스트 6>과 같이 코드를 작성한다.

    // 최근 포스트 목록 정보 가져오기
    private void GetRecentlyPostInfo()
    {
        BlogDataClassesDataContext blogDataContext 
            = new BlogDataClassesDataContext();
 
        List<BLOG_POSTS> blogPostList =
                            (from c in blogDataContext.BLOG_POSTS
                                where c.deletedate == null
                                orderby c.createdate descending
                                select c).Skip(0).Take(5).ToList();
 
        LvwLicentlyPost.DataSource = blogPostList;
        LvwLicentlyPost.DataBind();
 
        blogDataContext = null;
    }
<리스트 6> 최근 포스트 목록 정보를 가져오는 코드

최근 포스트 목록 정보와 최근 덧글 목록 정보는 화면에 5개씩만을 보여줄 것이므로, 등록일 순으로 정렬하여 최근 5개의 목록만을 가져온다. 최근 포스트 목록 정보를 조회하여, “BlogMasterPage.master"에 이미 정의된 ListView 컨트롤에 데이터를 표시한다. 최근 포스트 목록 정보가 표시될 때 포스트의 제목을 클릭 시 이동하기 위해서(또는, 최근 덧글 목록 정보가 표시될 때 덧글의 내용을 클릭 시 이동하기 위해서), ListView 컨트롤에 데이터가 바인딩되면서 발생하는 이벤트인 ”OnItemDataBound" 이벤트를 이용하여 ListView 컨트롤에 포함되어 있는 HyperLink 컨트롤의 NavigateUrl 속성에 할당된 값을 재정의하게 된다. ListView 컨트롤의 ”OnItemDataBound" 이벤트를 이용한 코드는 <리스트 7>과 같다.

    protected void LvwLicentlyPost_ItemDataBound
        (object sender, ListViewItemEventArgs e)
    {
        if (e.Item.ItemType == ListViewItemType.DataItem)
        {
            HyperLink hlnkPost 
                = (HyperLink)e.Item.FindControl("hlnkRecentlyPost");
            hlnkPost.NavigateUrl 
                = "/Post/" + hlnkPost.NavigateUrl.ToString() + ".aspx";
        }
    }
<리스트 7> ListView 컨트롤의 OnItemDataBound 이벤트를 이용한 코드

블로그의 우측 메뉴 화면에서 보여지는 공지사항이나 Archives 목록, 그리고 카테고리 정보를 표시하기 위한 코드도 <리스트 6>과 <리스트 7>에 보여지는 코드와 동일하게 구성을 하였으며, 이에 대한 코드는 이달의 디스켓을 참고하도록 한다. 이와 같이 구성된 블로그의 우측 메뉴 화면은 <화면 7>과 같이 보여지게 된다.

<화면 7> 블로그 우측 메뉴 화면

블로그의 우측 메뉴에서 보여지는 Archives 목록은 "BLOG_ARCHIVES" 테이블에서 조회하게 된다. 그러므로, 포스트를 작성할 때와 삭제할 때, "BLOG_ARCHIVES" 테이블에 필요한 값을 추가, 제거해야 하지만, 마소 3월호에는 필자가 그 부분을 제외하였기 때문에 "BLOG_ARCHIVES" 테이블에 값을 입력하는 코드를 추가해야 한다.

“Admin.aspx.cs"의 ”btnPostWrite_Click" 이벤트에 정의된 코드의 가장 아래에 SetArchivesInfo(1)이라는 메소드 호출 코드를 추가하고, ”btnPostDelete_Click" 이벤트에 정의된 코드의 가장 아래에 SetArchivesInfo(-1)이라는 메소드 호출 코드를 추가한다. SetArchivesInfo() 메소드 안의 인자는 "BLOG_ARCHIVES" 테이블에 등록된 ArchivesDate 필드와 일치하는 ArchivesCount 필드의 값을 메소드 안의 인자만큼 증가시키거나 감소시키기 위한 값이 들어가게 된다. SetArchivesInfo 메소드는 저장 프로시져인 “dbo.usp_SetArchivesCount"를 호출하게 되며, 이 저장 프로시져는 <리스트 8>과 같은 코드로 되어 있다.

CREATE PROCEDURE [dbo].[usp_SetArchivesCount]
(
    @archivesdate    VARCHAR(7)
,    @archivescount    INT
)
AS
BEGIN
IF EXISTS 
(SELECT ArchivesDate FROM BLOG_ARCHIVES WHERE ArchivesDate = @archivesdate)
BEGIN
    UPDATE BLOG_ARCHIVES SET ArchivesCount = ArchivesCount + @archivescount
    WHERE ArchivesDate = @archivesdate
 
    IF 0 = 
    (SELECT ArchivesCount FROM BLOG_ARCHIVES WHERE ArchivesDate = @archivesdate)
    BEGIN
        DELETE FROM BLOG_ARCHIVES WHERE ArchivesDate = @archivesdate
    END
END
ELSE
BEGIN
    INSERT INTO BLOG_ARCHIVES VALUES (@archivesdate, @archivescount)
END

<리스트 8> usp_SetArchivesCount 저장 프로시져의 코드


RSS 페이지의 구성

필자가 마소 1월호에 기고한 “ASP.NET AJAX로 만드는 RSS 뷰어”에서 RSS에 대해서 간략하게 다룬 적이 있고, RSS에 관련해서는 인터넷 검색을 통하여 정보를 쉽게 얻을 수 있기 때문에 RSS에 대한 설명 및 구성 원리에 대한 설명은 생략하도록 한다. 블로그 상단 메뉴의 RSS 메뉴를 클릭하면 “Rss.aspx" 페이지로 이동되게 되며, ”Rss.aspx.cs" 페이지에서 RSS 정보를 내보내기 위해 블로그 및 포스트의 정보를 Xml 형식으로 구성하게 된다.
RSS 페이지를 만드는 방법은 XmlDocument 클래스를 이용하여 XmlNode를 생성하여 RSS 형식에 맞는 태그로 구성하는 방법이 일반적이지만, 필자의 경우는 좀 더 간편하게 Xml 형식을 구성하기 위해서 StringBuilder 클래스를 사용하였으며, XmlDocument 클래스의 LoadXml() 메소드를 통해, 구성된 StringBuilder 클래스의 정보를 추가하는 것으로 RSS 페이지를 구성하였다. RSS 페이지에 관련된 소스는 이달의 디스켓을 참고하도록 하며, 상단 메뉴의 RSS 메뉴를 클릭하였을 때 표시되는 화면은 <화면 8>과 같다.


<화면 8> RSS가 표시된 화면

카테고리에 속한 포스트 목록 페이지의 구현

블로그 우측 메뉴 영역의 Categories 아래에 표현되는 카테고리를 누르거나, 포스트 조회 페이지에서 카테고리 항목을 누를 경우에는 선택한 카테고리에 등록되어 있는 포스트의 제목이 나타나는 목록 및 포스트를 한꺼번에 볼 수 있는 포스트 목록 페이지로 이동하게 된다. 이렇게 선택한 카테고리에 등록되어 있는 포스트의 목록을 구현하기 위해서, 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, "Category.aspx"라는 이름의 페이지를 추가한다. "Category.aspx"에서는 "BLOG_POSTS" 테이블의 CategoryID 필드와 쿼리스트링으로 CategoryID값이 동일한 포스트의 목록을 조회하여 ListView 컨트롤 및 DataPager 컨트롤을 이용하여 화면에 정보를 출력하게 한다. 먼저 Web.Config의 <rewriter> 섹션에 Category.aspx 페이지로 URL을 재작성하기 위한 <rewrite> 요소를 추가한다.

<rewrite url="/Category/(.+).aspx" to="/Category.aspx?categoryid=$1" />

그리고 "Category.aspx.cs" 파일의 Page_Load 이벤트에서 쿼리스트링으로 받은 CategoryID값을 바탕으로 카테고리명과, 현재 카테고리에 등록되어 있는 전체 포스트의 개수를 검색하기 위한 GetCategoryInfo() 메소드를 호출한다. GetCategoryInfo() 메소드의 코드는 <리스트 9>와 같다.

    // 카테고리 정보 가져오기
    private void GetCategoryInfo()
    {
        BlogDataClassesDataContext blogDataContext 
            = new BlogDataClassesDataContext();
 
        List<BLOG_CATEGORIES> blogCategory =
                            (from c in blogDataContext.BLOG_CATEGORIES
                             where c.deletedate == null
                             orderby c.sortkey ascending
                             select c).ToList();
 
        LvwCategory.DataSource = blogCategory;
        LvwCategory.DataBind();
 
        blogDataContext = null;
    }
<리스트 9> GetCategoryInfo() 메소드의 코드

또한 필자가 마소 2월달에 만든 “VIEW_POSTLIST" 뷰를 사용하는 LinqDataSource 컨트롤을 추가하여, 카테고리에 속한 포스트의 목록을 구현하는 데이터 소스 컨트롤로 사용하도록 한다. 이 데이터 소스 컨트롤을 포스트의 목록을 출력하기 위한 ListView 컨트롤과 연결시키도록 하며, ListView 컨트롤의 페이징을 위해 DataPager 컨트롤을 사용하며, DataPager 컨트롤의 PagedControlID 속성을 ListView 컨트롤의 ID로 설정함으로써, ListView 컨트롤의 페이징을 구현하도록 한다. 지금까지 설명한 내용을 바탕으로 한 코드는 <리스트 10>과 같다.

<div class="list">
    <div class="sub">
        "<asp:Label ID="lblCategoryName" runat="server"></asp:Label>"에 해당되는 글 
        <asp:Label ID="lblCategoryCount" runat="server"></asp:Label>
        <hr />
        <asp:ListView ID="LvwPostList" runat="server" 
            DataSourceID="LinqDataSourcePostList"
            onitemdatabound="LvwPostList_ItemDataBound" 
            ondatabinding="LvwPostList_DataBinding">
            <LayoutTemplate>
                <ul>
                    <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
                </ul>
            </LayoutTemplate>
            <ItemTemplate>
                <li><span>
                    <%#Convert.ToDateTime(Eval("CreateDate").ToString()).ToShortDateString()%>
                </span>
                <asp:HyperLink ID="hlnkPost" runat="server" NavigateUrl='<%#Eval("PostID")%>' 
                Text='<%#Eval("PostName")%>'></asp:HyperLink></li>
            </ItemTemplate>
        </asp:ListView>
        <div style="text-align:center">
            <asp:DataPager ID="DpPostList" runat="server" PagedControlID="LvwPostList" 
                PageSize="4">
                <Fields>
                    <asp:NumericPagerField ButtonType="Link" ButtonCount="5" />
                </Fields>
            </asp:DataPager>
        </div>
    </div>
</div><br />
 
<asp:LinqDataSource ID="LinqDataSourcePostList" runat="server" 
        ContextTypeName="BlogDataClassesDataContext" 
        Select="new (postid, postname, createdate, categoryid)" 
        TableName="VIEW_POSTLISTs" OrderBy="createdate desc" 
    Where="categoryid == @categoryid">
    <WhereParameters>
        <asp:ControlParameter ControlID="hidCategoryID" Name="categoryid" 
            PropertyName="Value" Type="Int32" />
    </WhereParameters>
</asp:LinqDataSource>
<리스트 10> 포스트 목록 페이지의 구현 코드

<리스트 10>에서 작성된 코드를 통해 표시되는 화면은 <화면 9>와 같다.

<화면 9> 포스트 목록이 표시된 화면

<리스트 10>에서 보여지는 코드 중에 ListView 컨트롤에서 사용되는 OnDataBounding 이벤트를 사용하는 이유는 DataPager 컨트롤을 사용할 경우에 선택한 해당 페이지의 포스트의 목록 화면 아래에 보여지게 되는 포스트의 정보를 별도로 LINQ 쿼리를 통하여 보여주기 위해서 사용하는 것이며, OnDataBounding 이벤트에서는 포스트의 정보를 보여주는 GetPostInCategoryList() 메소드를 호출하게 된다. GetPostInCategoryList() 메소드의 코드는 <리스트 11>과 같다.

    private void GetPostListInCategory()
    {
        blogDataContext = new BlogDataClassesDataContext();
        _pageIndex = DpPostList.StartRowIndex / _pageCount;
 
        List<BLOG_POSTS> blogPostList =
            (from c in blogDataContext.BLOG_POSTS
             where c.categoryid == int.Parse(_categoryID) && c.deletedate == null
             orderby c.createdate descending
             select c).Skip(_pageIndex * _pageCount).Take(_pageCount).ToList();
 
        LvwPostInCategory.DataSource = blogPostList;
        LvwPostInCategory.DataBind();
 
        blogDataContext = null;
    }
<리스트 11> 선택한 페이지의 포스트 목록 출력 화면 코드

<리스트 11>에서 보는 것과 같이 _pageIndex라는 전역 변수를 선언한 후, DataPager 컨트롤의 StartRowIndex 속성을 통해 DataPager 컨트롤의 시작행을 알아온 후에, 한 화면에서 보여지는 _pageCount(필자가 설명하는 블로그에서는 한 화면에서 보여줄 개수를 4로 지정하였다.)로 나눈 값을 _pageIndex 변수에 할당하도록 한다. 그러므로, DataPager 컨트롤의 1페이지를 선택할 경우 _pageIndex값에는 0값이, 3페이지를 선택할 경우에는 _pageIndex값에 2의 값이 들어가게 된다. 이렇게 정의되는 _pageIndex값과 _pageCount 값을 가지고, Skip() 메소드와 Take() 메소드를 이용하여 화면에 출력해야 하는 포스트의 정보를 조회한 후 ListView 컨트롤을 이용하여 화면에 출력하게 된다. 화면에 출력하는 코드는 <리스트 12>와 같다.

<asp:ListView ID="LvwPostInCategory" runat="server" 
    onitemdatabound="LvwPostInCategory_ItemDataBound">
    <LayoutTemplate>
        <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate>
        <h1><asp:HyperLink ID="hlnkSubject" runat="server" 
            NavigateUrl='<%#Eval("PostID")%>' 
            Text='<%#Eval("PostName")%>'></asp:HyperLink></h1>
        <h5>작성일 : <asp:Label ID="lblCreateDate" runat="server" 
            Text='<%#Eval("CreateDate")%>'></asp:Label> 
        | 조회수 : <asp:Label ID="lblViewCount" runat="server" 
            Text='<%#Eval("ViewCount")%>'></asp:Label>
        </h5><br />                    
        <asp:Label ID="lblPostContent" runat="server" 
            Text='<%#Eval("PostContent").ToString().Replace("\r\n", "<br />")%>'>
            </asp:Label><br /><br />
        <div class="taginfo">
            태그 : <asp:Label ID="lblPostTag" runat="server" 
            Text='<%#DisplayTagList(Eval("Tag").ToString())%>'></asp:Label>
        </div>
        <div class="otherinfo">
            트랙백 : 0개, 덧글 : <asp:Label ID="lblCommentCount" runat="server" 
            Text='<%#Eval("CommentCount")%>'></asp:Label>
        </div>
        <br /><br /><br />
    </ItemTemplate>
</asp:ListView>
<리스트 12> 선택한 페이지의 포스트 정보 출력 코드

이상으로, 선택한 카테고리에 등록된 포스트의 목록 및 포스트의 목록에서 보여지게 되는 포스트의 정보가 출력되는 “Category.aspx" 페이지에 대한 설명을 마치도록 하겠다. 완성된 ”Category.aspx" 페이지의 화면은 <화면 10>과 같다.


<화면 10> “Category.aspx" 페이지의 전체 화면

이로써 포스트 조회화면에 대한 설명과 덧글의 목록 정보 및 덧글의 추가/수정/삭제, 그리고 블로그 우측 메뉴의 정보 출력 및 RSS 페이지를 구성하는 방법에 대해서 설명하였다. 다음 호는 4개월동안 연재되는 블로그 프로그래밍의 마지막 회로써, 구현하지 못한 검색과 약간의 관리자가 사용할 수 있는 기능에 대해서 설명하도록 하겠다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

포스트 조회 페이지의 구성

이제, 포스트 조회 페이지를 구성하는 방법에 대해서 알아보도록 하자. 먼저 포스트를 조회하기 위한 “PostView.aspx" 파일을 만들기 위해, 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, "PostView.aspx"라는 이름의 페이지를 추가한다.
먼저, 작성한 포스트에 대한 정보를 화면에 출력하기 위한 코드를 "PostView.aspx" 파일에 추가하도록 하며, 이 코드는 이달의 디스켓을 참조하도록 한다.

“PostView.aspx" 파일에 포스트의 정보를 출력하는 코드를 추가한 후, “PostView.aspx.cs"에서 포스트의 정보를 가져오는 코드를 작성한다. 우선, 전역변수로 _strPostID를 선언한 후에, 쿼리스트링으로 전달되는 PostID값을 할당한다. 그 후, Page_Load 이벤트에서 현재 포스트의 정보를 가져오는 GetPostData() 메소드를 호출한다. 현재 포스트의 정보를 가져오는 GetPostData() 메소드의 코드는 <리스트 3>과 같다.

    // 현재 조회하려는 블로그 데이터 가져오기
    private void GetPostData()
    {
        // 해당 블로그 데이터 조회
        BLOG_POSTS blogPost
            = blogDataContext.BLOG_POSTS.Single(q => q.postid == _strPostID);
 
        // 조회수를 1증가시킨다.
        blogPost.viewcount = blogPost.viewcount + 1;
        blogDataContext.SubmitChanges();
 
        // 조회된 데이터를 출력
        lblPostSubject.Text = blogPost.postname;
        lblCreateDate.Text = blogPost.createdate.ToString();
        lblViewCount.Text = blogPost.viewcount.ToString();
        lblCommentCount.Text = blogPost.commentcount.ToString();
        lblPostContent.Text
            = Server.HtmlDecode(blogPost.postcontent.ToString().Replace("\r\n", "<br />"));
        hlnkCategory.Text = blogPost.BLOG_CATEGORIES.categoryname;
        hlnkCategory.NavigateUrl = "/Category/" + blogPost.categoryid.ToString() + ".aspx";
        lblTrackBackUrl.Text
            = Request.ServerVariables["HTTP_HOST"].ToString() + "/TrackBack/" + _strPostID + ".aspx";
        lblTrackBackUrl.Attributes.Add("onclick", "fnCopyUrl('" + lblTrackBackUrl.Text + "')");
 
        _categoryID = blogPost.categoryid.ToString();
        this.Master.Page.Title = lblPostSubject.Text;
    }
<리스트 3> GetPostData() 메소드의 코드

GetPostData() 메소드는 쿼리스트링으로 전달받은 PostID값을 가지고 BLOG_POST 테이블에서 PostID값이 일치하는 “BLOG_POST" 엔티티를 찾은 후, 화면에 필요한 정보만을 표시하도록 코드가 구성되어 있다. 또한 <필자메모>에서 설명한 것과 같이, GetPostData() 메소드에서도, 카테고리를 클릭할 경우 카테고리 목록 화면으로 이동하기 위한 URL인 “/Category/카테고리ID.aspx"와 트랙백 정보를 받기위한 ”/TrackBack/포스트ID.aspx"를 정의하고 있다. GetPostData() 메소드를 통해 구성된 "PostView.aspx" 파일의 화면은 <화면 3>과 같다.


<화면 3> 기본 정보가 표시된 포스트 조회 화면

다음으로는, 현재 조회하고 있는 포스트의 이전/다음 포스트 정보를 구성하는 코드에 대해서 설명하도록 하겠다. 우선 현재 포스트의 이전/다음 포스트 정보를 가져오는 쿼리는 저장 프로시져를 사용하여 구성하며, 사용되는 저장 프로시져명은 “dbo.usp_GetPrevNextPost”이고, 이 저장 프로시져는 <리스트 4>와 같이 구성되어 있다.

CREATE PROCEDURE [dbo].[usp_GetPrevNextPost]
(
    @postid            NVARCHAR(50)
,    @categoryid        INT
,    @prevpostid        NVARCHAR(50)     OUTPUT
,    @nextpostid        NVARCHAR(50)    OUTPUT
,    @prevpostname    NVARCHAR(100)    OUTPUT
,    @nextpostname    NVARCHAR(100)    OUTPUT
)
AS
BEGIN
    DECLARE @nowpostdate        DATETIME
 
    SELECT @nowpostdate 
        = (SELECT CreateDate FROM BLOG_POSTS WHERE PostID = @postid)
    SELECT @prevpostid 
        = (SELECT TOP 1 ISNULL(PostID, '') AS PrevPostID 
            FROM BLOG_POSTS 
            WHERE CategoryID = @categoryid AND CreateDate < @nowpostdate 
            AND DeleteDate IS NULL 
            ORDER BY CreateDate DESC)
    SELECT @nextpostid 
        = (SELECT TOP 1 ISNULL(PostID, '') AS NextPostID 
            FROM BLOG_POSTS 
            WHERE CategoryID = @categoryid AND CreateDate > @nowpostdate 
            AND DeleteDate IS NULL 
            ORDER BY CreateDate ASC)
    SELECT @prevpostname 
        = (SELECT PostName FROM BLOG_POSTS WHERE PostID = @prevpostid)
    SELECT @nextpostname 
        = (SELECT PostName FROM BLOG_POSTS WHERE PostID = @nextpostid)
END
<리스트 4> usp_GetPrevNextPost 저장 프로시져의 코드

<리스트 4>과 같이 저장 프로시져를 생성한 후, “App_Code" 폴더 안에 이미 생성되어 있는 ”BlogDataClasses.dbml“ 파일의 디자이너 화면의 우측 영역에 이 저장 프로시져를 추가하기 위해, 기존에 연결시켜 놓은 "서버 탐색기(Server Explorer)"의 연결 정보를 새로고침하여, 추가된 ”dbo.usp_GetPrevNextPost“ 저장 프로시져를 검색한 후, ”BlogDataClasses.dbml“ 파일의 디자이너 화면의 우측 영역으로 저장 프로시져를 드래그 앤 드랍하여, 저장 프로시져를 추가한다.

그 후에, Page_Load 이벤트에서 이 저장 프로시져를 사용하기 위한 메소드인 GetPrevNextPostInfo() 메소드를 호출한다. GetPrevNextPostInfo() 메소드의 코드는 <리스트 5>와 같다.

    private void GetPrevNextPostInfo()
    {
        string strPrevPostID = string.Empty;
        string strNextPostID = string.Empty;
        string strPrevPostName = string.Empty;
        string strNextPostName = string.Empty;
 
        int iReturn 
            = blogDataContext.usp_GetPrevNextPost(_strPostID, int.Parse(_categoryID)
            , ref strPrevPostID, ref strNextPostID, ref strPrevPostName, ref strNextPostName);
 
        // 이전 포스트가 있을 경우
        if (! string.IsNullOrEmpty(strPrevPostID))
        {
            hlnkPrevPost.Text = strPrevPostName;
            hlnkPrevPost.NavigateUrl = "/Post/" + strPrevPostID + ".aspx";
        }
        else
        {
            hlnkPrevPost.Text = "이전 포스트가 없습니다.";
        }
 
        // 다음 포스트가 있을 경우
        if (!string.IsNullOrEmpty(strNextPostID))
        {
            hlnkNextPost.Text = strNextPostName;
            hlnkNextPost.NavigateUrl = "/Post/" + strNextPostID + ".aspx";
        }
        else
        {
            hlnkNextPost.Text = "다음 포스트가 없습니다.";
        }
    }
<리스트 5> GetPrevNextPostInfo() 메소드의 코드

<리스트 5>와 같이 현재 포스트의 이전/다음 포스트 정보를 가져오는 메소드를 호출하게 되면, <화면 4>와 같은 화면을 볼 수 있게 된다.


<화면 4> 포스트의 이전/다음 정보가 표시된 화면

다음으로는 포스트에 작성된 덧글 정보를 표시하는 코드에 대해서 설명하도록 하겠다. Page_Load 이벤트에, 덧글 정보를 가져오는 GetPostCommentInfo() 메소드를 추가한 후, 포스트에 관련된 덧글 정보를 가져오는 코드를 작성한다. GetPostCommentInfo() 메소드에 관련된 코드는 이달의 디스켓을 참고하도록 하며, 조회된 덧글 정보는 “PostView.aspx" 파일에 이미 정의되어 있는 ListView 컨트롤에 의해서 화면에 보여지게 된다. 등록되어 있는 덧글 정보가 있다면, <화면 5>와 같이 화면에 표시되게 된다.


<화면 5> 포스트 조회 화면에서 보이는 덧글 목록 화면

<화면 5>에서 보이는 것처럼, 포스트를 조회하는 방문자가 포스트에 대한 덧글 정보를 입력한 후, “덧글 입력” 버튼을 클릭하게 되면, 방문자가 입력한 덧글 정보가 "BLOG_COMMENTS" 테이블에 입력되게 된다. 이러한 로직은 “PostView.aspx.cs" 파일의 ”btnWriteComment_Click" 이벤트에 정의되어 있다. “덧글 입력” 버튼을 클릭하면, 방문자가 입력한 덧글 정보를 테이블에 저장한 후, “BLOG_POSTS" 테이블에서 현재 포스트의 Comment 개수를 1 증가시키게 되며, 다시 덧글 정보 목록을 조회하여 화면에 보여주게 된다. 덧글 정보를 입력하는 부분과 덧글 정보를 보여주는 부분은 ASP.NET AJAX를 이용하기 위해서 UpdatePanel 컨트롤의 ContentTemplate 템플릿 안에 구현되어 있으며, UpdatePanel 컨트롤을 사용하기 위해서, ScriptManager 컨트롤의 선언 구문에 페이지 상단에 추가되어 있다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_ARCHIVES] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[BLOG_ARCHIVES](
[archivesdate] [varchar](7) NOT NULL,
[archivescount] [int] NOT NULL
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_CATEGORIES] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[BLOG_CATEGORIES](
[categoryid] [int] IDENTITY(1,1) NOT NULL,
[categoryname] [nvarchar](50) NOT NULL,
[sortkey] [smallint] NOT NULL,
[createdate] [datetime] NOT NULL,
[deletedate] [datetime] NULL,
CONSTRAINT [PK_BLOG_CATEGORIES] PRIMARY KEY CLUSTERED
(
[categoryid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_COMMENTS] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[BLOG_COMMENTS](
[commentid] [int] IDENTITY(1,1) NOT NULL,
[postid] [nvarchar](100) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[password] [varchar](50) NOT NULL,
[homepage] [varchar](100) NULL,
[comment] [ntext] NOT NULL,
[createdate] [datetime] NOT NULL,
CONSTRAINT [PK_BLOG_COMMENTS] PRIMARY KEY CLUSTERED
(
[commentid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_COUNTS] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[BLOG_COUNTS](
[IP] [varchar](30) NOT NULL,
[VisitDate] [datetime] NOT NULL,
CONSTRAINT [PK_BLOG_COUNTS] PRIMARY KEY CLUSTERED
(
[IP] ASC,
[VisitDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_LINKS] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[BLOG_LINKS](
[linkid] [int] IDENTITY(1,1) NOT NULL,
[linkname] [nvarchar](50) NOT NULL,
[linkurl] [varchar](100) NOT NULL,
[sortkey] [smallint] NOT NULL,
[createdate] [datetime] NOT NULL,
[deletedate] [datetime] NULL,
CONSTRAINT [PK_BLOG_LINKS] PRIMARY KEY CLUSTERED
(
[linkid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_NOTICES] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[BLOG_NOTICES](
[noticeid] [int] NOT NULL,
[noticename] [nvarchar](100) NOT NULL,
[noticecontent] [ntext] NOT NULL,
[createdate] [datetime] NOT NULL,
[deletedate] [datetime] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_POSTS] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[BLOG_POSTS](
[postid] [nvarchar](100) NOT NULL,
[categoryid] [int] NOT NULL,
[postname] [nvarchar](100) NOT NULL,
[postcontent] [ntext] NOT NULL,
[createdate] [datetime] NOT NULL,
[deletedate] [datetime] NULL,
[viewcount] [int] NOT NULL,
[commentcount] [int] NOT NULL,
[tag] [nvarchar](300) NOT NULL,
CONSTRAINT [PK_BLOG_POSTS] PRIMARY KEY CLUSTERED
(
[postid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_TAGS] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[BLOG_TAGS](
[tagname] [nvarchar](50) NOT NULL,
[tagcount] [int] NOT NULL
) ON [PRIMARY]

GO

USE [BlogDataBase]
GO

/****** Object: Table [dbo].[BLOG_USERINFO] Script Date: 03/09/2009 09:09:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[BLOG_USERINFO](
[AdminID] [varchar](50) NOT NULL,
[AdminPWD] [varchar](30) NOT NULL
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[BLOG_CATEGORIES] WITH CHECK ADD CONSTRAINT [FK_BLOG_CATEGORIES_BLOG_CATEGORIES] FOREIGN KEY([categoryid])
REFERENCES [dbo].[BLOG_CATEGORIES] ([categoryid])
GO

ALTER TABLE [dbo].[BLOG_CATEGORIES] CHECK CONSTRAINT [FK_BLOG_CATEGORIES_BLOG_CATEGORIES]
GO

ALTER TABLE [dbo].[BLOG_COMMENTS] WITH CHECK ADD CONSTRAINT [FK_BLOG_COMMENTS_BLOG_POSTS] FOREIGN KEY([postid])
REFERENCES [dbo].[BLOG_POSTS] ([postid])
GO

ALTER TABLE [dbo].[BLOG_COMMENTS] CHECK CONSTRAINT [FK_BLOG_COMMENTS_BLOG_POSTS]
GO

ALTER TABLE [dbo].[BLOG_POSTS] WITH CHECK ADD CONSTRAINT [FK_BLOG_POSTS_BLOG_CATEGORIES] FOREIGN KEY([categoryid])
REFERENCES [dbo].[BLOG_CATEGORIES] ([categoryid])
GO

ALTER TABLE [dbo].[BLOG_POSTS] CHECK CONSTRAINT [FK_BLOG_POSTS_BLOG_CATEGORIES]
GO
반응형
반응형

이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

Default.aspx 페이지의 수정

필자는 지난 호에서 블로그의 첫 화면인 “Default.aspx”에 가장 최근에 등록한 3개의 포스트의 목록을 보여주는 코드를 설명했었다. 지난 호에서 설명한 그 코드를 약간 수정하여 포스트의 제목을 클릭할 때에, 선택한 포스트의 내용을 조회할 수 있는 화면으로 이동하도록 하기 위해서, “Default.aspx”에 정의된 ListView 컨트롤의 구문을 <코드 1>과 같이 변경한다.

<asp:ListView ID="LvwRecentPostList" runat="server" ItemPlaceholderID="phRecentPostList">
    <LayoutTemplate>
        <asp:PlaceHolder ID="phRecentPostList" runat="server"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate>
        <h1><a href="/post/<%#Eval("PostID")%>.aspx" target="_self">
        <%#Eval("PostName")%></a></h1>
        <h5>작성일 : <%#Eval("CreateDate")%> | 
        조회수 : <%#Eval("ViewCount")%> | 댓글수 : <%#Eval("CommentCount")%></h5><br />
        <%#ChangePostContent(Eval("PostContent").ToString())%>
        <div style="height:15px;width:100%"><hr /></div>
    </ItemTemplate>
    <EmptyDataTemplate>
        <div style="width:100%">
            <center><h3>등록된 포스트가 없습니다.</h3></center>
        </div>        
    </EmptyDataTemplate>
</asp:ListView>
<리스트 1> ListView 컨트롤의 소스 코드

ListView 컨트롤의 ItemTemplate 템플릿 안에 정의된 포스트의 제목을 클릭할 경우, 웹 페이지는 “/Post/포스트ID.aspx" 페이지로 이동하게 된다. 그렇다면, 각각의 포스트가 만들어질때마다 개별 aspx 페이지를 만들어야 하는가? 그건 그렇지 않다. 이렇게 ”포스트ID.aspx"의 경로와 같이 직관적으로 사용자에게 보이기 위해 각 포스트는 고유링크(퍼마링크, Permalink)를 가지고 있으며, 사용자에게 표시되는 URL 또한 이러한 식으로 표현되지만, 실제로는 ”PostView.aspx?postid=포스트ID"와 같이 PostView.aspx 페이지에 쿼리스트링값으로 postid값을 전달하는 방식을 취한다. 이것을 Url ReWriting이라고 하며, 이 부분에 대해서는 <필자메모>에서 좀 더 자세하게 설명하였다.
<리스트 1>과 같이 코드를 수정한 후의 “Default.aspx” 화면은 <화면 1>과 같이 변경된다.


<화면 1> 수정된 Default.aspx 화면

----------------------------------------------------------------------------------------------------------------
<필자메모>

일반적으로 잘 알려져 있는 블로그의 포스트를 조회 시에 표시되는 URL은 <화면 2>과 같다.


<화면 2> 각 블로그의 URL 정보

블로그는 각 포스트마다 고유링크(퍼마링크, Permalink)를 가지게 된다. 게시판 또는 사이트의 경우는 "게시판URL.aspx?boardID=1&categoryID=1"과 같은 식인 쿼리스트링 값이 그대로 URL에 노출되지만, 블로그의 경우는 <화면 2>에서 보는 것과 같이 포스트마다 고유한 링크를 가진 것처럼 사용자에게 인식되도록 한다. 그러한 퍼마링크는 <화면 2>와 같이 글 고유의 ID값을 가질 수도 있고, 또는 제목을 그대로(MSDN 및 기타 해외 블로그 등) 퍼마링크로 사용할 수도 있다. 필자가 설명하고 있는 VS 2008을 이용한 블로그 프로그래밍도 이러한 퍼마링크를 이용하기 위해서 UrlReWriter.net에서 제공하는 어셈블리를 이용해서 URL을 Rewriting하는 방식을 사용하고 있으며, 필자가 구현하는 블로그에서는 “/Post/포스트ID.aspx"의 형태로 표현되게 된다. URL Rewriting을 사용하기 위해서는, 먼저 http://urlrewriter.net에서 제공하는 소스를 다운로드 받은 후, 웹 사이트의 참조 추가 메뉴를 이용하여 소스에 포함된 Intelligencia.UrlRewriter.dll 어셈블리를 참조한다. 그 후에 Web.Config 파일을 <리스트 2>와 같이 추가한 어셈블리를 사용할 수 있도록 코드를 추가한다.

<?xml version="1.0"?>
<configuration>
    <configSections>
        ...
        <section name="rewriter" requirePermission="false" 
                 type="Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler
                 , Intelligencia.UrlRewriter" />
    </configSections>
    <appSettings/>
    <connectionStrings>
        <add name="BlogDataBaseConnectionString" providerName="System.Data.SqlClient"
             connectionString="Data Source=neostyx\neostyx;Initial Catalog=BlogDataBase;User ID=sa;Password=1"
          />
    </connectionStrings>
    <system.web>
        ...
        <httpModules>
            <add name="ScriptModule" type="System.Web.Handlers.ScriptModule
                 , System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            <add name="UrlRoutingModule" type="System.Web.Mvc.UrlRoutingModule
                 , System.Web.Extensions, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        </httpModules>
    </system.web>
    <rewriter>
        <rewrite url="/Post/(.+).aspx" to="/PostView.aspx?postid=$1" />
        <rewrite url="/Category/(.+).aspx" to="/Category.aspx?categoryid=$1" />
    </rewriter>
    ...
</configuration>
<리스트 2>Web.Config 파일에 추가된 URL Rewriting 코드

Web.Config 파일의 <rewriter>섹션에 있는 <rewrite>요소에 정의된 것처럼 “/Post/포스트ID.aspx"로 요청되는 URL은 이 어셈블리를 통해서 ”PostView.aspx" 페이지에 쿼리스트링값으로써 포스트ID값을 전달해주고 있다. 그러므로 “PostView.aspx" 페이지의 코드 비하인드에서 Request.QueryString으로써 포스트ID값을 전달받을 수 있는 것이다. 다음 호에서 설명할 트랙백이나 태그, 그리고 검색의 경우도 이 URLRewriter를 사용하여 URL Rewriting 기능을 구현할 것이다.
또한, 필자는 보다 원활한 블로그 구성을 위해서 IIS(인터넷 정보 서비스)에서 8080포트를 가지는 별도의 웹 사이트를 구성하였다. 이 글을 보시는 독자분들도 보다 나은 프로그래밍을 위해 별도의 웹 사이트를 구성하여 개발 및 테스트를 하기를 권장한다.
----------------------------------------------------------------------------------------------------------------


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

블로그의 포스트 목록 기능 구현

<화면 11>에서와 같이 작성된 포스트의 목록을 보여줄 때, 포스트의 정보가 저장되는 BLOG_POSTS 테이블만으로는 분류(카테고리) 정보를 알아올 수 없으므로, BLOG_POSTS 테이블과 BLOG_CATEGORIES 테이블을 조인하는 뷰를 만들어 포스트의 목록을 보여주도록 한다. "서버 탐색기(Server Explorer)"에서 사용 중인 연결 정보를 확장하여 나타나는 View 항목에 우측 마우스를 클릭하여 "새 뷰 추가(Add New View)" 메뉴를 선택한다. 그러면, View에 사용될 테이블을 추가하는 화면이 <화면 12>와 같이 나타나게 된다.


<화면 12> View에 사용할 테이블을 추가하는 화면

여기에서 BLOG_CATEGORIES 테이블과 BLOG_POSTS 테이블을 선택한 후 "추가(Add)" 버튼을 클릭한 후, "닫기(Close)" 버튼을 클릭하여 테이블 추가 화면을 닫는다. 그리고, <화면 13>과 같이 View를 구성한 후, 새로운 View명을 "VIEW_POSTLIST"라고 변경한 후 저장한다.


<화면 13> "VIEW_POSTLIST" 뷰 구성

새로 생성한 "VIEW_POSTLIST" 뷰의 쿼리문은 다음과 같다.

SELECT dbo.BLOG_CATEGORIES.categoryid, dbo.BLOG_CATEGORIES.categoryname,
    dbo.BLOG_POSTS.postid, dbo.BLOG_POSTS.postname, dbo.BLOG_POSTS.postcontent,
    dbo.BLOG_POSTS.createdate, dbo.BLOG_POSTS.viewcount,
    dbo.BLOG_POSTS.commentcount, dbo.BLOG_POSTS.tag
FROM dbo.BLOG_CATEGORIES
INNER JOIN dbo.BLOG_POSTS
ON dbo.BLOG_CATEGORIES.categoryid = dbo.BLOG_POSTS.categoryid
WHERE (dbo.BLOG_POSTS.deletedate IS NULL)


새로 생성한 "VIEW_POSTLIST" 뷰를 "BlogDataClasses.dbml" 파일 디자이너의 좌측 영역으로 드래그 앤 드랍하여, 뷰의 스키마를 추가한다. 포스트 목록과 공지사항 목록은 Visual Studio 2008에서 새롭게 추가된 LinqDataSource 컨트롤을 사용하고 있으며, LinqDataSouce 컨트롤을 데이터 소스로 하여 ListView 컨트롤에 데이터를 출력하게 하고 있다. "BlogDataClasses.dbml" 파일에 추가된 "VIEW_POSTLIST" 뷰 스키마는 포스트 목록에서 사용되는 LinqDataSource 컨트롤에서 사용할 것이다. 또한, DataPager 컨트롤을 ListView 컨트롤과 연결하여 DataPager 컨트롤로 하여금 페이징 기능을 구현하도록 하였다. 포스트의 목록에 관련된 소스 코드는 이달의 디스켓을 참고하기를 바라며, 공지사항의 목록 구현도 포스트의 목록 구현과 동일한 방식을 취하고 있으므로 이에 대한 설명은 생략하도록 한다.

포스트 목록과 공지사항 목록을 보여주기 위해 ListView 컨트롤을 사용하고 있다. 목록에서 사용되는 ListView 컨트롤은 3개의 템플릿을 가지고 있으며, 각 템플릿의 기능은 다음과 같다.

● LayoutTemplate : 포스트 및 공지사항의 목록을 보여주는 전체적인 테이블의 레이아웃을 정의하는 템플릿으로, 목록의 헤더 부분과 DataPager 컨트롤을 사용한 페이징 부분의 코드가 정의되어 있다.
● ItemTemplate : 반환되는 포스트 및 공지사항의 데이터를 LayoutTemplate에서 정의한 레이아웃에 맞게 구성하는 템플릿으로, ItemTemplate에 정의되는 각 아이템들은 ListView 컨트롤의 ItemPlaceholderID 속성에 정의된 PlaceHolder 컨트롤의 위치에 렌더링시 포함되게 된다.
● EmptyDataTemplate : 반환되는 포스트 및 공지사항의 데이터가 없을 경우, 화면에 보여지고자 하는 영역을 정의하는 템플릿이다.

따라서, 포스트 및 공지사항의 데이터가 있을 경우는 LayoutTemplate 템플릿과 ItemTemplate 템플릿에 정의된 코드로 렌더링되어 화면에 보여지게 되고, 만약 데이터가 없을 경우는 <화면 14>처럼 EmptyDataTemplate 템플릿에 정의된 코드가 화면에 보여지게 된다.


<화면 14> 데이터가 없을 경우의 화면

또한, 목록에 관련된 페이징을 위한 DataPager 컨트롤을 ListView 컨트롤과 연결시키기 위해서는 DataPager 컨트롤의 PagedControlID 속성을 이용하여 ListView 컨트롤의 ID를 지정하면, DataPager 컨트롤을 이용한 페이징을 구현할 수 있다. DataPager 컨트롤에서 이용할 수 있는 페이저 필드는 3개가 있으며, 각각의 페이저 필드와 그에 대한 설명은 다음과 같다.

● NextPreviousPagerField : 이전 페이지, 다음 페이지 그리고 첫 페이지 또는 마지막 페이지로 이동할 수 있는 페이징 기능을 제공한다.
● NumericPagerField : 페이지 번호가 표시되어 페이지 번호를 클릭할 시에 클릭한 페이지로 이동할 수 있는 페이징 기능을 제공한다.
● TemplatePagerField : 개발자가 임의로 페이징의 기능을 구현할 수 있는 페이저 필드이다.

필자는 NumericPagerField 페이저 필드를 이용해서 페이징을 구현하였으며, NextPreviousPagerField 페이저 필드를 사용하여 페이징을 구현하는 경우의 화면은 <화면 15>와 같다.


<화면 15> NextPreviousPagerField 페이저를 사용한 페이징 화면


블로그의 포스트 수정 및 삭제 기능 구현

포스트 목록 탭에 있는 포스트의 목록에서 작성된 포스트의 제목을 클릭하면, fnMoveModify()라는 이름의 자바스크립트 함수를 호출하며, 이 함수는 <리스트 6>에 보여지는 페이지 메소드를 호출하며, 페이지 메소드에서 반환된 테이블의 엔티티 정보를 포스트 수정 탭에 있는 항목에 표시하게 된다.

    // 포스트 정보를 가져온다.
    [System.Web.Services.WebMethod]
    public static VIEW_POSTLIST GetPostInfo(string strPostID)
    {
        blogDataStaticContext = new BlogDataClassesDataContext();
        VIEW_POSTLIST blogPostList = 
            blogDataStaticContext.VIEW_POSTLIST.Single(q => q.postid == strPostID);
        blogPostList.postcontent = HttpUtility.HtmlDecode(blogPostList.postcontent);
        blogDataStaticContext = null;
        return blogPostList;
    }
<리스트 6> 페이지 메소드의 코드

수정할 항목을 수정한 후, 화면 하단의 수정 버튼을 클릭하면 변경된 제목 및 본문, 그리고 태그 정보가 반영되며 포스트 목록 탭으로 이동하게 된다. 포스트 목록 탭에서는 수정된 정보가 반영된 포스트 목록이 나타나게 된다. 화면 하단의 삭제 버튼을 클릭하면 <리스트 7>에서 보여지는 코드가 실행되어, 해당 포스트의 정보가 삭제된다. (실제 데이터 삭제가 아닌, BLOG_POSTS 테이블의 deletedate 필드에 현재 날짜 정보가 입력되게 된다.) 그리고, 포스트 목록 탭으로 이동하게 되며 포스트 목록 탭에는 삭제한 포스트의 정보가 나타나지 않게 된다.

    // 포스트 수정 탭에서 삭제 버튼 클릭 시
    protected void btnPostDelete_Click(object sender, EventArgs e)
    {
        try
        {
            string strPostID = hidPostID.Value;
 
            if (!string.IsNullOrEmpty(strPostID))
            {
                // 공지사항 CheckBox가 체크되어 있으면 공지사항 삭제
                if (chkModifyNotice.Checked)
                {
                    // DeleteDate 필드를 현재의 시간으로 업데이트
                    BLOG_NOTICES blogNotice =
                        blogDataContext.BLOG_NOTICES.Single
                        (q => q.noticeid == int.Parse(strPostID));
                    blogNotice.deletedate = DateTime.Now;
                    blogDataContext.SubmitChanges();
                    SetNoticeListDataBound();
                    ScriptManager.RegisterClientScriptBlock(
                        this, this.GetType(), "setindex", "fnSetTablIndex('3')", true);
                }
                else
                {
                    // DeleteDate 필드를 현재의 시간으로 업데이트
                    BLOG_POSTS blogPost =
                        blogDataContext.BLOG_POSTS.Single(q => q.postid == strPostID);
                    blogPost.deletedate = DateTime.Now;
                    blogDataContext.SubmitChanges();
                    SetPostListDataBound();
                    ScriptManager.RegisterClientScriptBlock(
                        this, this.GetType(), "setindex", "fnSetTablIndex('2')", true);
                }
 
                SetInitForm();
            }
        }
        catch (Exception ee)
        {
            ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "error"
                , "alert('" + ee.Message + "::" + ee.Source + "::" + ee.InnerException + "')", true);
        }
    }
<리스트 7> 삭제 버튼 클릭 시의 소스 코드

최신 포스트 목록 출력하기

자신의 블로그를 방문하는 사용자에게 작성된 포스트 중에서 최신 포스트의 제목 및 간단한 정보를 보여주게 하기 위해서 "Default.aspx" 페이지에, 작성된 포스트 중에서 날짜순으로 정렬하여 3개의 최신 포스트 정보를 가져오는 코드를 <리스트 8>과 같이 추가한다.

    // 최근 포스트 목록 가져오기
    private void GetRecentPostList() 
    {
        BlogDataClassesDataContext blogDataContext = new BlogDataClassesDataContext();
 
        // 포스트의 목록 중 최근의 3개의 목록만을 가져온다.
        List<VIEW_POSTLIST> recentlyPost 
            = (from c in blogDataContext.VIEW_POSTLIST
                orderby c.createdate descending
                select c).Skip(0).Take(3).ToList();
 
        LvwRecentPostList.DataSource = recentlyPost;
        LvwRecentPostList.DataBind();
 
        blogDataContext = null;
    }
<리스트 8> 최신 포스트 정보를 가져오는 소스 코드

"Default.aspx" 페이지의 소스를 수정한 후, 블로그를 방문하는 사용자가 보게 될 초기 화면은 <화면 16>과 같이 된다.

<화면 16> 블로그의 초기 화면

이상으로, 포스트를 작성, 수정 그리고 삭제하는 기능 및 Visual Studio 2008에서 새롭게 추가된 컨트롤인 ListView 컨트롤과 DataPager 컨트롤, 그리고 LinqDataSource 컨트롤을 사용하여 작성된 포스트의 목록을 화면에 출력하는 방법에 대해서 알아보았다. 다음 호에서는 포스트 조회 화면 및 댓글의 작성/수정/삭제의 기능, 그리고 RSS 기능 구현 등 블로그에 필요한 기능을 구현하는 방법에 대해서 설명하도록 하겠다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

관리자 페이지(Admin.aspx)의 구성

포스트 및 공지사항의 작성과 수정, 그리고 삭제의 기능과 작성된 포스트의 목록 및 공지사항의 목록을 볼 수 있는 화면은 모두 지난 호에서 만든 "Admin.aspx" 페이지를 이용할 것이다. 우선 완성된 "Admin.aspx"의 페이지 디자인을 보면 <화면 9>와 같다.


<화면 9> "Admin.aspx" 페이지의 화면
<화면 9>에서 보는 것과 같이 "Admin.aspx" 페이지는 총 4개의 탭으로 구성되어 있으며, 화면에 표시되는 3개의 탭(포스트 쓰기, 포스트 목록, 공지사항 목록)과, 화면에 표시되지 않는 1개의 숨겨진 탭(포스트 수정)이 있다. 각각의 탭에서 사용하게 되는 기능은 다음과 같다.

● 포스트 쓰기 탭 : 블로그에 등록하는 포스트를 작성할 수 있는 기능을 제공하는 탭으로, 공지사항이라는 체크박스에 체크를 하게 되면 공지사항으로 등록이 되며, 체크박스에 체크를 하지 않게 되면 포스트로써 등록이 된다.
● 포스트 목록 탭 : 작성된 포스트에 대한 목록이 보여지는 탭으로, 작성된 포스트의 제목을 클릭하게 되면, 클릭한 포스트를 수정 및 삭제할 수 있는 포스트 수정 탭 화면으로 이동하게 된다.
● 공지사항 목록 탭 : 작성된 공지사항에 대한 목록이 보여지는 탭으로, 작성된 공지사항의 제목을 클릭하게 되면, 클릭한 공지사항을 수정 및 삭제할 수 있는 포스트 수정 탭 화면으로 이동하게 된다.
● 포스트 수정 탭 : 포스트 목록 탭 또는 공지사항 목록 탭에 있는 목록에서 제목을 클릭하게 되면 활성화되는 탭으로, 클릭한 포스트 또는 공지사항의 정보를 수정할 수 있는 기능과 삭제를 할 수 있는 기능을 제공한다.

"Admin.aspx" 페이지에서 사용되는 탭 컨트롤은 AJAX Control ToolKit에서 제공하는 TabContainer 컨트롤을 사용하고 있다. TabContainer 컨트롤의 스타일은 기본적으로 AJAX Control Toolkit에서 제공되는 스타일이 아닌 사용자 정의 스타일로 정의했으며 TabContainer 컨트롤의 스타일에 관련된 내용은 참고 자료에 나오는 URL을 참고하기 바란다. TabContainer 컨트롤은 4개의 TabPanel 컨트롤을 포함하고 있으며, 이들 TabPanel 컨트롤들은 각기 UpdatePanel 컨트롤을 포함하고 있다. 물론, AJAX 기능을 사용하기 위해서 "Admin.aspx" 페이지의 상단에는 ScriptManager 컨트롤을 추가하였다. 또한, 포스트 또는 공지사항의 수정에 관련된 내용을 가져오기 위해서 페이지 메소드(Page Method)를 사용하였고, "Admin.aspx" 페이지에서 페이지 메소드를 사용가능하게 하기 위해서 ScriptManager 컨트롤의 EnablePageMethods 속성을 true로 설정하였다. "Admin.aspx" 페이지의 소스 코드는 이달의 디스켓을 참고하기 바란다.

블로그의 포스트 작성 기능 구현

이제 블로그 프로그래밍에 있어 핵심적인 기능이라 할 수 있는 포스트를 작성하는 기능에 대해서 설명하도록 하겠다. <화면 9>에서 보는 것처럼, 포스트 쓰기 탭에서는 작성할 포스트의 제목과 본문, 그리고 태그(공지사항을 작성할 경우에는 입력을 하지 않아도 된다.)를 입력하는 TextBox 컨트롤과 공지사항으로 등록할 것인지 일반 포스트로 등록할 것인지를 결정하게 되는 CheckBox 컨트롤, 그리고 포스트로 등록할 경우에 어떤 카테고리로 등록할 것인지를 선택할 수 있는 DropDownList 컨트롤로 구성되어 있다. 카테고리 정보를 가져와서 카테고리 정보를 보여주는 DropDownList 컨트롤로 바인딩하는 코드는 <리스트 4>와 같다.

    // Category 정보를 가져온다.
    private void GetCategoryInfo()
    {
        var categoryInfo = from c in blogDataContext.BLOG_CATEGORIES select c;
 
        foreach (BLOG_CATEGORIES blogCategory in categoryInfo)
        {
            // 포스트 작성 탭의 카테고리 정보에 바인딩
            ddlCategory.Items.Add(new ListItem(
                blogCategory.categoryname.ToString(), blogCategory.categoryid.ToString()));
            // 포스트 수정 탭의 카테고리 정보에 바인딩
            ddlModifyCategory.Items.Add(new ListItem(
                blogCategory.categoryname.ToString(), blogCategory.categoryid.ToString()));
        }
    }
<리스트 4> 카테고리 정보를 가져오는 메소드

제목과 본문 그리고 태그 정보를 입력한 후, 화면 하단에 있는 미리보기 버튼을 클릭하면 작성한 포스트가 화면에 어떻게 보여질 것인가를 확인할 수 있는 미리보기 창이 <화면 10>과 같이 뜨게 된다.


<화면 10> 작성한 포스트에 대한 미리보기 창

화면 하단에 있는 등록 버튼을 클릭하면, <리스트 5>에 보여지는 것과 같이 등록 버튼 클릭에 관련된 코드가 실행되며 정상적으로 등록이 되면 공지사항 체크박스의 체크 유무에 따라 포스트 목록 탭 또는 공지사항 목록 탭에 <화면 11>과 같이 작성한 포스트 또는 공지사항의 정보가 목록으로 보여지게 된다.

// 포스트 쓰기 탭에서 작성 버튼 클릭 시
    protected void btnPostWrite_Click(object sender, EventArgs e)
    {
        try
        {
            // 공지사항 CheckBox가 체크되어 있으면 공지사항으로 저장
            if (chkNotice.Checked)
            {
                BLOG_NOTICES blogNotice = new BLOG_NOTICES();
                blogNotice.noticename = txtPostSubject.Text;
                blogNotice.noticecontent = Server.HtmlEncode(txtPostContent.Text);
                blogNotice.createdate = DateTime.Now;
 
                blogDataContext.BLOG_NOTICES.InsertOnSubmit(blogNotice);
                blogDataContext.SubmitChanges();
                SetNoticeListDataBound();
                ScriptManager.RegisterClientScriptBlock(
                    this, this.GetType(), "setindex", "fnSetTablIndex('3')", true);
            }
            else
            {
                BLOG_POSTS blogPost = new BLOG_POSTS();
                blogPost.postid = Guid.NewGuid().ToString().ToUpper();
                blogPost.postname = txtPostSubject.Text;
                blogPost.postcontent = Server.HtmlEncode(txtPostContent.Text);
                blogPost.categoryid = int.Parse(ddlCategory.SelectedItem.Value.ToString());
                blogPost.tag = txtPostTag.Text;
                blogPost.createdate = DateTime.Now;
                blogPost.viewcount = 0;
                blogPost.commentcount = 0;
 
                blogDataContext.BLOG_POSTS.InsertOnSubmit(blogPost);
                blogDataContext.SubmitChanges();
                SetPostListDataBound();
                ScriptManager.RegisterClientScriptBlock(
                    this, this.GetType(), "setindex", "fnSetTablIndex('2')", true);
            }
 
            SetInitForm();
        }
        catch (Exception ee)
        {
            ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "error"
                , "alert('" + ee.Message + "::" +  ee.Source + "::" + ee.InnerException + "')", true);
        }
    }
<리스트 5> 등록 버튼 클릭 시의 코드


<화면 11> 작성한 포스트의 목록 정보


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

블로그 관리자의 로그인/로그아웃 기능 구현

이제부터, 블로그를 관리하는 역할을 가진 관리자의 로그인 기능을 구현해보도록 하겠다. 우선 관리자 로그인이 된 경우와 로그인이 되지 않은 경우(Session값을 가지고 구분하게 된다), 메뉴 부분을 다르게 보여주기 위해서 마스터 페이지(BlogMasterPage.master)에 정의된 메뉴 부분을 <리스트 1>과 같이 변경한다.

        <div class="nav">
            <a href="Default.aspx">HOME</a> 
            <a href="About.aspx">ABOUT</a> 
            <a href="Tag.aspx">TAG</a>
            <a href="Rss.aspx">RSS</a> 
            <a href="Search.aspx">SEARCH</a> 
            <a href="GuestBook.aspx">GUESTBOOK</a>
            <asp:HyperLink ID="hlnkLogin" runat="server" 
                NavigateUrl="~/Login.aspx" Text="LOGIN"></asp:HyperLink>
            <asp:HyperLink ID="hlnkLogout" runat="server" 
                NavigateUrl="~/Logout.aspx" Text="LOGOUT"></asp:HyperLink>
            <asp:HyperLink ID="hlnkAdmin" runat="server" 
                NavigateUrl="~/Admin.aspx" Text="ADMIN"></asp:HyperLink>
            <div class="clearer">
                <span></span>
            </div>
        </div>
<리스트 1> BlogMasterPage.master 페이지의 소스 코드

또한, 블로그 마스터 페이지의 코드 비하인드 페이지에서는 Session값 중 "Admin"값을 확인하여, <리스트 1>에서 재구성한 HyperLink 컨트롤의 Visible 속성을 <리스트 2>와 같이 정의하게 된다.

    protected void Page_Load(object sender, EventArgs e)
    {
        // Session의 값이 없을 경우에는,
        // Logout과 Admin링크를 보여주지 않도록 한다.
        if (Session["Admin"] == null)
        {
            hlnkLogout.Visible = false;
            hlnkAdmin.Visible = false;
        }
        else
        {
            hlnkLogin.Visible = false;
        }
    }
<리스트 2> BlogMasterPage.master.cs 페이지의 소스 코드

<리스트 1>과 <리스트 2>의 코드로 인해, 관리자로 로그인했을 경우와, 관리자로 로그인하지 않은 경우의 메뉴 구조는 각각 <화면 6>, <화면 7>과 같게 된다.


<화면 6> 관리자로 로그인이 되지 않은 경우의 메뉴 구성


<화면 7> 관리자로 로그인이 된 경우의 메뉴 구성

이제, 메뉴의 "Login"을 클릭할 경우, 관리자 로그인을 하기 위한 페이지인 Login.aspx 페이지를 구성해보도록 하자. <리스트 3-1>과 <리스트 3-2>는 Login.aspx 페이지의 소스 코드 및 Login 버튼을 클릭했을 경우에 실행되는 소스 코드를 보여준다.

<%@ Page Language="C#" MasterPageFile="~/BlogMasterPage.master" AutoEventWireup="true" 
CodeFile="Login.aspx.cs" Inherits="Login" Title="Untitled Page" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <script language="javascript" type="text/javascript">
        // ID, PWD 입력상자 유효성 검사
        function fnLoginValidation() {
            var txtAdminID = document.getElementById("<%=txtAdminID.ClientID%>");
            var txtAdminPassword = document.getElementById("<%=txtAdminPassword.ClientID%>");
 
            if (txtAdminID.value == "") {
                alert("ID를 입력하세요.");
                return false;
            }
 
            if (txtAdminPassword.value == "") {
                alert("PASSWORD를 입력하세요.");
                return false;
            }
 
            return true;
        }
 
        // ID, PWD 입력상자 초기화
        function fnClearInputText() {
            var txtAdminID = document.getElementById("<%=txtAdminID.ClientID%>");
            var txtAdminPassword = document.getElementById("<%=txtAdminPassword.ClientID%>");
 
            txtAdminID.value = "";
            txtAdminPassword.value = "";
 
            return false;
        }
    </script>
    <div class="left">
        <div class="content">
            <h1>LOGIN</h1>
            <div style="width:100%;text-align:center">
                <table width="80%" cellpadding="2" cellspacing="0" border="0">
                    <tr valign="middle" style="height:26px;">
                        <td class="td_left_input_width30">ID : </td>
                        <td class="td_right_input_width70">
                            <asp:TextBox ID="txtAdminID" runat="server" MaxLength="10" 
                                CssClass="txtCommon"></asp:TextBox>
                        </td>
                    </tr>
                    <tr valign="middle" style="height:26px;">
                        <td class="td_left_input_width30">PASSWORD : </td>
                        <td class="td_right_input_width70">
                            <asp:TextBox ID="txtAdminPassword" runat="server" MaxLength="10" 
                                TextMode="Password" CssClass="txtCommon"></asp:TextBox>
                        </td>
                    </tr>
                    <tr style="height:40px;" valign="middle">
                        <td style="width:100%" colspan="2" align="center">
                            <asp:Button ID="btnLogin" runat="server" Text="Login" 
                                OnClientClick="return fnLoginValidation()" OnClick="btnLogin_Click" />&nbsp;
                            <asp:Button ID="btnCancel" runat="server" Text="Clear" 
                                OnClientClick="return fnClearInputText()" />&nbsp;
                        </td>
                    </tr>
                </table>
            </div>
        </div>
    </div>
</asp:Content>
<리스트 3-1> Login.aspx 페이지의 소스 코드

    protected void btnLogin_Click(object sender, EventArgs e) 
    {
        BlogDataClassesDataContext blogContext 
            = new BlogDataClassesDataContext();
 
        var adminInfo = from USER in blogContext.BLOG_USERINFO select USER;
 
        string strAdminID = string.Empty;
        string strAdminPassword = string.Empty;
 
        // 관리자 정보가 있을 경우
        if (adminInfo != null)
        {
            foreach (BLOG_USERINFO userInfo in adminInfo)
            {
                strAdminID = userInfo.AdminID;
                strAdminPassword = userInfo.AdminPWD;
            }
 
            // 관리자 정보와 입력한 정보가 일치할 경우
            if (string.Compare(strAdminID, txtAdminID.Text.Trim(), true) == 0 && 
                string.Compare(strAdminPassword, txtAdminPassword.Text.Trim(), true) == 0)
            {
                Session["Admin"] = "admin";
                Response.Redirect("Default.aspx");
            }
            else 
            {
                string strNotCorrect 
                    = "alert('관리자 ID 또는 비밀번호가 틀립니다.');";
                Page.ClientScript.RegisterStartupScript(
                    this.GetType(), "notcorrect", strNotCorrect, true);
            }
        }
        else 
        {
            string strNotData = "alert('관리자 정보가 없습니다.');";
            Page.ClientScript.RegisterStartupScript(
                this.GetType(), "notdata", strNotData, true);
        }
    }
<리스트 3-2> Login 버튼을 클릭시의 소스 코드

<리스트 3>에서 보여지는 코드에 대해서 잠깐 설명하도록 하겠다. Login 버튼을 클릭하게 되면, BLOG_USERINFO 테이블에 저장되어 있는 관리자 아이디 정보와 비밀번호 정보를 LINQ 쿼리로 가져와서 변수에 할당한 후, 사용자가 입력한 값과 일치하는 지를 확인한다. 사용자가 입력한 값과 일치하게 되면 Session값 중 "Admin"값에 값을 할당한 후, 블로그의 초기 화면으로 이동시키게 되며, 일치하지 않을 경우는 아이디 또는 비밀번호가 틀렸다는 경고창을 보여주게 된다. "CTRL+F5키" 또는 디버그 메뉴의 "디버깅하지 않고 시작 메뉴"를 선택하여, 웹 브라우저로 지금까지 적용한 화면을 보도록 한다. 메뉴의 "Login"을 눌러 로그인 페이지로 이동 후에 아이디와 비밀번호에 임의의 글자를 입력한 후 로그인 버튼을 클릭하면 <화면 8>과 같은 경고창이 뜨게 되며, 정확하게 아이디와 비밀번호를 입력하게 되면 Default.aspx 페이지로 이동하게 되며 상단의 메뉴 구조가 <화면 7>과 같이 변경되게 된다.


<화면 8> 로그인이 실패했을 경우의 화면

로그인 부분이 완성되었으므로, 로그아웃 부분에 대해서 설명하도록 하겠다. 로그아웃은 별도의 페이지인 "Logout.aspx"를 사용하여 로그아웃 기능을 구현하도록 하며, 별다른 기능 없이 Session 값 중 Admin에 할당된 값을 초기화시키는 코드를 가지게 된다. 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, "Logout.aspx"라는 이름의 페이지를 추가한다. 추가한 "Logout.aspx.cs" 페이지에 다음과 같은 코드를 추가한다.

    protected void Page_Load(object sender, EventArgs e)
    {
        Session["Admin"] = null;
        Response.Redirect("Login.aspx");
    }


관리자로 로그인했을 경우, 상단 메뉴의 "LogOut" 메뉴를 클릭하면, 세션값 중 Admin값은 null로 변경되며, 페이지는 "Login.aspx" 페이지로 이동하게 된다.

로그아웃 기능에 대해서 별도로 페이지를 만든 것에 대해서 의문점을 가지고 있는 독자들이 있을 줄로 안다. 물론, Visual Studio 2008부터는 기본적으로 ASP.NET AJAX 기능이 포함되어 있기 때문에 AJAX가 제공하는 기능 중 페이지 메소드나 웹 서비스를 이용하여 Session값에 대한 처리를 할 수도 있지만, 로그아웃에 관련된 별도의 페이지를 만들어 Session값에 대한 처리를 하는 것이 좀 더 편하기 때문에 필자는 이와 같이 로그아웃 페이지를 별도로 만들게 되었다.

또한, 관리자 아이디와 비밀번호를 저장하고 있는 BLOG_USERINFO 테이블 또한, AdminID와 AdminPWD 2개의 필드만을 염두에 두고 있지는 않았던 테이블이다. 블로그를 방문하는 방문자 중에서 블로그에 회원 가입을 하는 사용자들의 정보를 저장하기 위한 테이블로 구상하였으나, 지면상 회원 가입 부분에 관련되어서 설명을 별도로 드릴 수 없을 것 같기 때문에 AdminID와 AdminPWD, 이와 같이 2개의 필드로만 구성하였고 관리자의 아이디 정보와 비밀번호 정보를 담게 하였다. 회원 가입 부분에 관련되어서는 여러 ASP.NET 도서나 블로그 및 사이트를 참조하기 바란다.



Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

LINQ to SQL Classes의 생성

필자가 설명하는 블로그에서 데이터베이스에 포함된 각 테이블과의 연결은 Visual Studio 2008에 새로 추가된 템플릿 중의 하나인 "LINQ to SQL Classes"를 사용하도록 하겠다. "LINQ to SQL Classes"은 .NET Framework 3.5에 내장된 O/RM(object relational mapper)으로, .NET 클래스들을 사용하여 관계가 있는 데이터베이스로의 연결을 가능하게 해주는 템플릿이다. 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여 <화면 1>과 같이 "LINQ to SQL Classes" 템플릿을 선택하고, 템플릿의 이름을 "BlogDataClasses.dbml"로 변경한 후 "추가(Add)" 버튼을 클릭하여 "LINQ to SQL Classes" 템플릿을 추가한다. App_Code 폴더가 없으므로, App_Code 폴더로 추가할 것이냐는 알림 창이 뜨게 될 것이다. 확인 버튼을 클릭하면 App_Code 폴더가 생성되며, "BlogDataClasses.dbml" 파일이 App_Code 폴더 아래로 추가되게 된다.


<화면 1> "LINQ to SQL Classes" 템플릿 추가

"LINQ to SQL Classes" 템플릿을 추가하게 되면, 템플릿에 데이터베이스에 있는 테이블, 뷰, 저장 프로시저, 사용자 정의 함수 등을 추가할 수 있는 디자이너 화면이 나타나게 된다. 디자이너 화면은 2개의 영역으로 구분되어 있으며, 좌측의 영역에는 테이블 및 뷰를 추가할 수 있고 우측의 영역에는 저장 프로시저 및 사용자 정의 함수 등을 추가할 수 있다. 이제, 생성한 "BlogDataClasses.dbml" 파일로 사용할 테이블의 정보를 추가하기 위해서, "서버 탐색기(Server Explorer)"의 "데이터 연결(DataConnection)"에서 우측 마우스를 클릭하여, "연결 추가(Add Connection)" 메뉴를 선택한다. 그러면, 데이터 소스를 선택하는 <화면 2>가 보이게 된다.


<화면 2> 데이터 소스 선택 화면

데이터 소스 선택 화면에서 "Microsoft SQL Server"를 선택한 후, "계속(Continue)" 버튼을 클릭하면, <화면 3>과 같은 연결 추가 화면이 나타나게 된다.


<화면 3> 연결 추가 화면

연결 추가 화면에서 서버명(Server Name)은 SQL Server가 위치한 서버명을 입력하면 되고, 서버로의 로그온 방식(Log on to the server)는 윈도우 인증 또는 SQL Server 인증 방식 중 하나를 선택하면 되겠다. SQL Server 인증 방식을 사용할 경우에는 로그온할 사용자명과 비밀번호를 입력하여야 하며, 로그온 방식을 선택한 후에는 연결이 정확한 지를 확인하기 위해 화면 좌측 하단의 연결 테스트(Test Connection)을 눌러 연결이 정확하게 되는지를 확인한다. 연결이 정확한 것을 확인하였으면, 연결할 데이터베이스명을 선택 또는 입력한 후 "확인(OK)" 버튼을 클릭하여 연결 추가 화면을 종료하도록 한다. 서버 탐색기(Server Explorer)에 방금 추가한 연결 정보가 추가된 것을 <화면 4>와 같이 확인할 수 있으며, 지난 호에서 생성한 테이블들이 포함되어 있는 것을 확인할 수 있다.


<화면 4> 추가된 연결 정보 화면

<화면 4>에서 보여지고 있는 테이블들을 "BlogDataClasses.dbml" 파일 디자이너의 좌측 영역으로 드래그 앤 드랍하면, <화면 5>와 같이 "BlogDataClasses.dbml" 파일로 테이블의 스키마가 추가되게 된다. 이 때, 각 테이블간의 관계 등도 같이 표시되게 되며 필자는 "BlogDataClasses.dbml" 파일로 추가된 테이블 스키마 정보를 바탕으로 LINQ 쿼리를 사용하여, 블로그의 데이터 정보를 관리할 것이다.


<화면 5> "LINQ to SQL Classes" 템플릿 디자이너에 테이블이 추가된 화면

블로그 프로그래밍에 관련된 기본적인 정보를 2개의 테이블에 입력하도록 한다. 먼저, 관리자로 로그인하기 위한 정보를 입력하기 위해, 다음과 같은 쿼리를 실행하여 관리자 정보를 BLOG_USERINFO 테이블에 데이터를 입력한다.

INSERT INTO BLOG_USERINFO VALUES ('neostyx', 'neostyx')

또한, 포스트를 작성하는 경우 CategoryID값이 반드시 필요하게 되므로, 임의로 BLOG_CATEGORIES 테이블에 다음과 같은 쿼리를 실행하여 데이터를 입력한다.

INSERT INTO BLOG_CATEGORIES VALUES ('ASP.NET', 1, GETDATE(), NULL)



Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

블로그를 위한 데이터베이스의 생성 및 구성

이제부터는, 블로그에 관련된 데이터베이스 및 테이블들을 만들어보도록 하겠다. 우선 SQL Server Management Studio를 실행시킨 후, 새 데이터베이스 생성 메뉴를 통하여 “BlogDataBase"라는 새로운 데이터베이스를 만든다. 새로운 데이터베이스를 생성하였으면, 새로운 테이블들을 만들어보도록 하겠다. 우선, 관리자가 지정한 카테고리들의 목록이 저장되는 BLOG_CATEGORIES 테이블의 구성은 <표 2>와 같다.



다음으로는, 관리자가 작성한 이전 포스트의 목록을 작성일자(년-월)별로 표시하기 위한 BLOG_ARCHIVES 테이블의 구성은 <표 3>과 같다.



관리자가 지정한 즐겨찾는 사이트 및 블로그의 목록이 표시되는 BLOG_LINKS 테이블의 구성은 <표 4>와 같다.



작성한 포스트의 정보가 입력되는 BLOG_POSTS 테이블의 구성은 <표 5>와 같다.



블로그의 공지 사항을 저장하는 테이블인 BLOG_NOTICES 테이블의 구성은 <표 6>과 같다. BLOG_NOTICES 테이블의 구조는 BLOG_POSTS 테이블의 구성과 유사하다.



포스트에 대하여 사용자들이 등록하는 덧글 정보를 저장하는 BLOG_COMMENTS 테이블의 구성은 <표 7>과 같다.



포스트 작성시에 등록되는 태그들의 정보를 저장하는 BLOG_TAGS 테이블의 구성은 <표 8>과 같다.



<표 2>에서 <표 8>까지 설명한 테이블의 정보는 <화면 8>과 같다.


<화면 8> BlogDataBase에 속한 테이블들의 구성 정보

블로그 관리자 정보 페이지 구성 (About.aspx)

블로그를 관리하고 있는 관리자에 대한 정보를 사용자들이 조회하는 웹 페이지를 만들어보도록 하겠다. 특별하게 데이터베이스에서 쿼리를 하는 부분이 없는, 단순한 웹 페이지라고 할 수 있겠다. 앞에서 만든 웹 페이지 중 “About.aspx”에 <리스트 3>과 같은 코드를 추가한다.

<%@ Page Title="" Language="C#" MasterPageFile="~/BlogMasterPage.master" 
    AutoEventWireup="true" CodeFile="About.aspx.cs" Inherits="About" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <div class="left">
        <div class="content">
            <h1>Neostyx는?</h1>
            <p><a href="http://www.neostyx.net" target="_blank">neostyx.net</a> 
            사이트를 운영하고 있는 닷넷 개발자입니다<br />
            2007년 Microsoft ASP.NET MVP로 활동중에 있으며 2008년부터 
            마이크로소프트웨어에 글을 개제하고 있습니다<br />
            앞으로도 많은 관심과 격려를 부탁드리겠습니다</p>
            <center>
                <img src="http://pds6.egloos.com/logo/200709/13/66/c0052366.png" alt="mvp 로고 />
            </center><br />
            <h1>Neostyx의 관심사</h1>
            <ul>
                <li>ASP.NET AJAX</li>
                <li>ASP.NET MVC Framework</li>
                <li>LINQ</li>
                <li>Microsoft Seminar</li>
                <li>Microsoft MVP</li>
        </ul>
        </div>
    </div>
</asp:Content>

<리스트 3> About.aspx 페이지의 소스 코드

또한 웹 브라우저의 타이틀을 변경하기 위하여, "About.aspx.cs" 코드 파일의 페이지 로드 이벤트에 다음과 같이 마스터 페이지의 타이틀을 변경하는 코드를 추가한다.

    protected void Page_Load(object sender, EventArgs e)
    {
        this.Master.Page.Title = "About Me";
    }


“About.aspx” 웹 페이지에 적용되어 있는 디자인을 보기 위하여, CTRL+F5키 또는 디버그 메뉴의 디버깅하지 않고 시작 메뉴를 선택한다. “About.aspx” 웹 페이지의 화면은 <화면 9>과 같다.


<화면 9> About.aspx의 웹 페이지 화면

이상으로, 블로그 정보를 저장하는 데이터베이스를 생성하고, 블로그를 구성하는 데에 사용되는 웹 사이트를 만들어보았다. 다음회에서는 로그인에 관련된 로직의 구성과 포스트 작성, 작성된 포스트의 목록을 조회하는 화면 등 블로그의 주요 기능들에 대한 설명을 하도록 하겠다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

 
이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

블로그 웹 사이트 구성

블로그에 적용할 디자인을 선택하였으므로, 이제부터 실제적으로 블로그를 만들어 보도록 하겠다. Visual Studio 2008을 실행시킨 후, 새 웹사이트 생성 메뉴를 이용하여 <화면 3>과 같이 “BlogProgramming"라는 이름의 새로운 웹 사이트를 만든다.


<화면 3> 새로운 웹 사이트의 생성

새로운 웹 사이트를 생성하였으면, 블로그에 사용할 스타일시트 파일 및 이미지 파일을 웹 사이트로 추가하여야 한다. 우선, "Img"라는 이름의 폴더를 새로 웹 사이트에 추가 후, 웹 사이트 이름에서 우측 마우스를 클릭하여 "기존 항목 추가(Add Existing Item...)" 메뉴를 선택하여, 이전에 다운로드 받은 블로그 디자인에 사용되는 Shades of Gray 폴더의 Img 폴더 아래에 위치한 이미지 파일들을 웹 사이트로 추가한다. 또한, 같은 방법으로 Shades of Gray 폴더의 default.css 스타일시트 파일을 웹 사이트로 추가한다. 그 후 default.css 스타일 시트 파일명을 “BlogStyleSheet.css”로 변경한다. <화면 4>는 현재까지 작업한 “BlogProgramming" 웹 사이트의 구조이다.


<화면 4> “BlogProgramming" 웹 사이트의 구조

<그림 1>에서 설명한 것과 같이 필자가 만드려고 하는 블로그는 4단 분류를 채택하고 있다. 이 중, <그림 2>와 같이 ① 상단 메뉴 영역, ② 우측 메뉴 영역, 그리고 ④ 하단 컨텐츠 영역은 모든 페이지에 공통적으로 적용되는 부분이므로, 마스터 페이지(MasterPage)를 이용하는 것이 블로그를 만드는데에 있어 효과적이라고 할 수 있겠다. 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, 블로그에서 사용할 ”BlogMasterPage.master“라는 이름의 마스터 페이지를 생성하도록 한다.


<그림 2> 마스터 페이지의 적용 영역

생성된 ”BlogMasterPage.master“ 마스터 페이지에 블로그 디자인에 사용되는 Shades of Gray 폴더에 포함되어 있는 Index.html 파일의 소스 중 필요한 부분을 복사하도록 한다. <리스트 1>은 ”BlogMasterPage.master“ 마스터 페이지의 전체 소스이다.

<%@ Master Language="C#" AutoEventWireup="true" 
CodeFile="BlogMasterPage.master.cs" Inherits="BlogMasterPage" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="BlogStyleSheet.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder id="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body style="margin: 0 12%">
    <form id="form1" runat="server">
    <div class="container">
        <div class="header">
            <a href="Default.aspx"><span>Neostyx's Blog</span></a>
        </div>
        <div class="stripes"><span></span></div>
        <div class="nav">
            <a href="Default.aspx">HOME</a>
            <a href="About.aspx">ABOUT</a>
            <a href="Tag.aspx">TAG</a>
            <a href="Rss.aspx">RSS</a>
            <a href="Search.aspx">SEARCH</a>
            <a href="GuestBook.aspx">GUESTBOOK</a>
            <a href="Login.aspx">LOGIN</a>
            <a href="Admin.aspx">ADMIN</a>
            <div class="clearer"><span></span></div>
        </div>
        <div class="stripes"><span></span></div>
        <div class="main">
            <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
            </asp:ContentPlaceHolder>
            <div class="right">
                <div class="subnav">
                    <h1>Notices</h1>
                    <ul>
                        <li><a href="#">공지</a></li>
                    </ul>
                    <h1>Categories</h1>
                    <ul>
                        <li><a href="#">카테고리</a></li>
                    </ul>
                    <h1>Archives</h1>
                    <ul>
                        <li><a href="#">2008-02</a></li>
                    </ul>
                    <h1>Licently Posts</h1>
                    <ul>
                        <li><a href="#">최근포스트</a></li>
                    </ul>
                    <h1>Licently Comments</h1>
                    <ul>
                        <li><a href="#">최근댓글</a></li>
                    </ul>
                    <h1>Favorite Links</h1>
                    <ul>
                        <li><a href="#">즐겨찾기</a></li>
                    </ul>
                </div>
            </div>
        <div class="clearer"><span></span></div>
        </div>
        <div class="footer">
            <div class="bottom">
                <span class="left">&copy; 2008 <a href
                 ="http://www.neostyx.net">Neostyx.net</a>.</span>
               <span class="right">Template design by 
                    <a href="http://templates.arcsin.se">Arcsin</a></span>
                <div class="clearer"><span></span></div>
            </div>
        </div>
    </div>
    </form>
</body>
</html>

<리스트 1> BlogMasterPage.master 마스터 페이지의 소스

블로그 디자인의 수정을 위해, 앞에서 생성한 “BlogStyleSheet.css” 스타일 시트 파일에 정의된 스타일 중 <리스트 2>에 표시된 스타일들을 수정한다.

.header {
    background: #111;
    border-bottom: 1px solid #333;
    font: normal 2em sans-serif;
    height: 70px;
}
.header a {
    color: #888;
    display: block;
    line-height: 60px;
    text-decoration: none;
    width: 100%;
}
.subnav h1 {
    padding-top: 8px;
    background: url(img/bgh1.gif) repeat-x left bottom;
    margin-bottom: 10px;
    padding: 10px 0 4px;
}
.footer {
    background: #191919;
    border-top: 1px solid #444;
    color: #999;
    padding: 3% 3% 3%;
}


<리스트 2> 스타일의 수정

마스터 페이지만으로는 별도의 디자인을 웹에서 볼 수 없으므로, 웹 사이트 생성시에 기본적으로 생성되는 “Default.aspx" 웹 페이지를 삭제 후, 웹 사이트 이름에서 우측 마우스를 클릭하여 “새 항목 추가(Add New Item...)" 메뉴를 선택하여, 새로운 웹 폼을 추가하도록 한다. 새로운 웹 폼명은 ”Default.aspx"이며, 새로운 웹 폼을 생성 시 “마스터 페이지 선택” 항목을 체크하여, <화면 5>와 같이 마스터 페이지 선택 시 “BlogMasterPage.master”를 선택하도록 한다.


<화면 5> 마스터 페이지 선택 화면

새로운 웹 폼이 생성되었으면, “BlogMasterPage.master” 마스터 페이지에 적용되어 있는 디자인을 보기 위하여, CTRL+F5키 또는 디버그 메뉴의 디버깅하지 않고 시작 메뉴를 선택한다. “BlogMasterPage.master” 마스터 페이지의 화면은 <화면 6>과 같다.


<화면 6> “BlogMasterPage.master” 마스터 페이지의 화면

이제, 각 메뉴를 클릭 시에 이동할 페이지들을 미리 생성한다, 기본 페이지인 “Default.aspx" 페이지는 이미 생성했으므로, ”Default.aspx" 페이지를 생성할 때와 마찬가지의 방법으로 “About.aspx", "Tag.aspx", "Rss.aspx", "Search.aspx", "GuestBook.aspx", "Login.aspx", "Admin.aspx" 등의 7개 웹 페이지를 생성하도록 한다. <화면 7>은 현재까지 작업한 웹 사이트의 구조를 보여준다.


<화면 7> 웹 사이트의 구조


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

이 포스트는 월간 마이크로소프트웨어에 기고한 원고를 재편집한 포스트입니다. 그러므로, 본의 아니게 반말로 진행되고 있습니다. 원래 싸가지가 없어서 그런 것이 아니니 무한 용서를... ^^;;
또한, .NET Framework 3.5 SP1이 적용되기 이전의 소스이므로, 현재의 개발 환경과 다를 수 있습니다. 마소에 제출한 블로그 소스는 블로그 소스 다운로드에서 다운로드하실 수있습니다.

(기획 의도) 웹 2.0의 시대를 이끌고 있는 가장 선도적인 주자는 블로그가 아닐 듯 싶다. 개발자라면 누구나 자신이 스스로 블로그를 만들어보고 싶은 욕구가 있을 것이라고 생각하지만, 블로그 프로그래밍에 관련된 정보를 찾을 수 없는 가장 큰 이유 때문에 자신만의 블로그를 만들지 못하고 있다고 생각한다. 이러한 이유로 자신만의 블로그를 만들지 못하고 있는 개발자들에게 약간의 도움을 주고자 블로그 프로그래밍에 관련되어 연재를 하고자 한다.

---------------------------------------------------------------------------------------------------

웹 2.0의 시대를 이끌고 있는 가장 선도적인 주자는 블로그가 아닐 듯 싶다. 개발자라면 누구나 자신이 스스로 블로그를 만들어보고 싶은 욕구가 있을 것이라고 생각하지만, 시간이 없거나 블로그 프로그래밍에 관련된 정보를 찾을 수 없는 등의 이유가 있기 때문에 대중화된 블로그를 제공하는 서비스를 이용하고 있는 것으로 생각한다. 혹시라도, 블로그 프로그래밍에 관련된 정보가 많이 없어 자신만의 블로그를 만드는 것을 미루고 있는 독자라면, 지금부터 시작되는 블로그 프로그래밍에 관련된 연재를 참고하길 바라며, 연재가 끝날 무렵에는 실제적으로 사용할 수 있는 블로그가 완성된 것을 확인할 수 있을 것이다.

블로그에 관련된 용어를 알고 있다면 블로그 프로그래밍을 하는 데에 있어 조금이나마 도움이 될 것이다.

● 블로거(blogger) : 블로그를 운영하는 사람을 의미한다.
● 블로깅(blogging) : 블로그를 보고, 덧글 및 방명록을 작성하는 행위를 의미한다.
● 포스팅(posting) : 블로그에 글을 작성하는 행위를 의미한다.
● 엔트리(entry, 포스트, post) : 블로그에 작성한 글을 뜻한다. 엔트리와 포스트 모두 동일한 말로써 블로그에 등록한 문서, 글, 게시물, 자료등이 엔트리 혹은 포스트를 의미하게 된다.
● 코멘트(comment, 덧글) : 엔트리 혹은 포스트에 몇 줄로 간단하게 작성된 사용자 의견을 의미한다.
● 트랙백(trackback) : 다른 사람의 글을 읽고 그 글에 직접 덧글을 올리는 대신에, 자신의 블로그에 글을 올리고 글의 일정 부분을 다른 사람의 블로그에 보이도록 하는 것을 의미한다.


블로그의 구조 이해

블로그 프로그래밍을 하기 전에 우선, 블로그는 어떤 구조로 되어 있는지를 확인해보도록 하겠다. 필자가 현재 운영하고 있는 블로그는 <화면 1>과 같이 구성되어 있다.


<화면 1> 필자가 운영하고 있는 블로그

<화면 1>에서 보는 것과 같이 블로그는 디자인에 따라 다르겠지만 크게 2단 분류와 3단 분류, 그리고 4단 분류로 나눌수 있으며, 일반적으로는 3단으로 분류된 디자인을 많이 사용하고 있다. 3단 분류는 크게 상단 메뉴 영역, 좌측(혹은 우측) 메뉴 영역, 우측(혹은 좌측) 컨텐츠 영역으로 나눌 수 있으며 각각의 분류는 <표 1>과 같은 메뉴 또는 컨텐츠가 들어가게 된다.



이와 같은 구조로 대부분의 블로그들이 이루어져 있으며, 기타 각 블로그 서비스마다 약간씩 다른 메뉴들을 제공하고 있다. 필자는 4단 분류를 사용하는 블로그를 만들 예정이며, 4단 분류는 3단 분류에 하단 컨텐츠 영역이 추가된 것으로 생각하면 된다. <그림 1>은 필자가 만들 블로그의 각 영역을 표시하고 있다.


<그림 1> 블로그의 각 영역

블로그의 디자인 선택

블로그를 최초로 사용자가 방문할 때 가장 먼저 접하게 되는 것이 블로그의 디자인이다. 보기 좋은 떡이 먹기도 좋다는 옛말처럼, 사용자의 관심을 끄는 디자인이 적용된 블로그라면 블로그를 재방문하도록 하는 효과를 얻을 수 있겠다. 블로그의 디자인은 블로그를 서비스하고 있는 업체에서 제공하는 각종 스킨 중 마음에 드는 스킨을 가지고 블로그를 구성하거나, 또는 저작권이 없는 공개된 블로그 디자인을 제공하는 사이트에서 제공하는 소스를 가지고 구성하면 된다. 필자는 Open Source Web Design(http://www.oswd.org/)에서 제공하는 무료 디자인 중 Shades of Gray(http://www.oswd.org/design/preview/id/3627) 디자인을 가지고 블로그를 구성하도록 하겠다. Shades of Gray의 화면은 <화면 2>와 같다.


<화면 2> Shades of Gray 화면

블로그 메뉴 구성

앞에서 설명한 대로 4단 분류의 틀을 유지하는 블로그를 만들 것이다. 먼저 상단 메뉴 영역에는 다음과 같은 메뉴들로 구성될 것이다.

● HOME : 블로그의 첫 화면으로 이동하기 위한 메뉴
● ABOUT : 블로그 관리자에 대한 정보를 보기 위해 이동하는 메뉴 (about.aspx로 이동)
● TAG : 포스트에 같이 등록된 태그들의 목록을 보기 위해 이동하는 메뉴 (tag.aspx로 이동)
● RSS : 포스트에 대한 RSS 정보를 보기 위한 메뉴 (rss.aspx로 이동 - 팝업 형식)
● SEARCH : 포스트를 검색하기 위해 이동하는 메뉴 (search.aspx로 이동)
● GUESTBOOK : 블로그에 방문하는 사용자들이 간단한 인사말을 남길 수 있는 방명록으로 이동하는 메뉴 (guestbook.aspx로 이동)
● LOGIN : 관리자가 로그인을 하기 위해 이동하는 메뉴 (login.aspx로 이동)
● ADMIN : 관리자 로그인이 성공했을 경우, 블로그를 관리하기 위한 관리자 화면으로 이동하는 메뉴 (admin.aspx로 이동)

또한, 우측 메뉴 영역은 다음과 같은 항목들로 구성될 것이다.

● Notices : 관리자가 작성한 공지사항의 목록이 표시된다.
● Categories : 관리자가 지정한 카테고리들의 목록이 표시된다.
● Archives : 관리자가 이전에 작성한 포스트의 목록이 작성일자(년-월)별로 표시된다.
● Licently Posts : 관리자가 작성한 포스트의 작성일자순으로 정렬된 최신 포스트의 목록이 표시된다.
● Licently Comments : 포스트에 대한 덧글이 작성일자순으로 정렬된 최신 덧글의 목록이 표시된다.
● Favorite Links : 관리자가 지정한 즐겨찾는 사이트 및 블로그의 목록이 표시된다.


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형
반응형

이 글은 월간 마이크로소프트웨어(일명 마소) 2008년 01월호 실전 강의실에 기고한 글입니다. 요즘 도통 포스팅을 할 수 없는 관계로 인해 대체합니다. ㅡ_ㅡ;; (하는 거 없이 바빠요~~~)


ASP.NET AJAX 코드와의 결합

CommonUtility 클래스의 코드까지 입력이 완료되었다면, 웹 폼에 ASP.NET AJAX 코드를 추가하도록 하겠다. 위에서, 웹 폼에는 divAddRss, divRssList, divPostList라는 id를 가진 3개의 <div>가 있다고 설명하였다. 우선, “AddUpdatePanel”, “RSSUpdatePanel”, “PostUpdatePanel”라는 id를 가진 UpdatePanel 컨트롤을 웹 폼에 생성한 후 UpdatePanel 컨트롤의 UpdateMode를 Conditional로 설정한다. 그리고, 이 3개의 <div>를 각각의 UpdatePanel 컨트롤의 하위로 이동시킨다. 또한, "AddUpdatePanel" UpdatePanel 컨트롤에는 “AddUpdateProgress"라는 id를 가지는 UpdateProgress 컨트롤을 추가하도록 한다. 새로운 UpdatePanel 컨트롤과, UpdateProgress 컨트롤을 추가한 후에 변경된 웹 폼의 소스는 <리스트 10>과 같다.

-------------------------------------------------------------------------------------------------------------------
<form id="RSSForm" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div id="content">
<asp:UpdatePanel ID="AddUpdatePanel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<div id="divAddRss" style="text-align:center;padding-bottom:20px;">
... ...
</div>
<asp:UpdateProgress ID="AddUpdateProgress" runat="server" AssociatedUpdatePanelID="AddUpdatePanel">
<ProgressTemplate>
<div style="text-align:center;padding-top:20px;padding-bottom:20px;">
<h5>처리중입니다</h5>
</div>
</ProgressTemplate>
</asp:UpdateProgress>
</ContentTemplate>
</asp:UpdatePanel>
<hr />
<asp:UpdatePanel ID="RSSUpdatePanel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<div id="divRssList" style="text-align:center;padding-bottom:20px;">
... ...
</div>
</ContentTemplate>
</asp:UpdatePanel>
<hr />
<asp:UpdatePanel ID="PostUpdatePanel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<div id="divPostList" style="text-align:center;padding-bottom:20px;">
... ...
</div>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
-------------------------------------------------------------------------------------------------------------------
<리스트 10> 변경된 웹 폼의 소스 코드

코드 비하인드에서의 소스 코드 작성

마지막 작업으로, RssView.aspx의 코드 비하인드 파일인 RssView.aspx.cs 파일에 코드를 추가하도록 하겠다. using 선언부에 "using System.Xml;" 선언을 추가한 후, Page_Load 이벤트 핸들러에 현재 등록된 RSS 주소의 카운트를 가져오는 코드와, RSS 주소의 카운트가 0 이상일 경우에 주소 목록과 포스트 목록을 가져오는 <리스트 11>의 코드를 추가한다.

-------------------------------------------------------------------------------------------------------------------
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
lblCount.Text = CommonUtility.GetRssAddressCount();

if (int.Parse(lblCount.Text) > 0)
{
DisplayRssAddressList();
DisplayRssItemList("");
}
}
else
{
lblInValid.Visible = false
lblInValid.Text = string.Empty;
}
}
-------------------------------------------------------------------------------------------------------------------
<리스트 11> Page_Load 이벤트 핸들러 코드

다음으로는, 등록된 RSS 주소의 목록과 등록된 포스트(Item)의 목록을 가져오는 메소드를 <리스트 12>와 같이 정의한다.

-------------------------------------------------------------------------------------------------------------------
private void DisplayRssAddressList()
{
DataTable dtAddressList = CommonUtility.GetRssAddressList();

if (dtAddressList != null && dtAddressList.Rows.Count > 0)
{
gvRssList.DataSource = dtAddressList.DefaultView;
gvRssList.DataBind();
}

RSSUpdatePanel.Update();
}

private void DisplayRssItemList(string strLink)
{
DataTable dtItemList = CommonUtility.GetRssItemList(strLink);

if (dtItemList != null && dtItemList.Rows.Count > 0)
{
gvPostList.DataSource = dtItemList.DefaultView;
gvPostList.DataBind();
}

PostUpdatePanel.Update();
}
-------------------------------------------------------------------------------------------------------------------
<리스트 11> Page_Load 이벤트 핸들러 코드

다음으로는 가장 중요한, 추가 버튼을 클릭했을 때인 btnAddRssAddress_Click 이벤트 핸들러의 코드를 추가해보도록 한다. 우선 추가하고자 하는 RSS 정보에 대한 XmlDocument 정보를 CommonUtility 클래스의 GetRssData()메소드를 통해 얻어온다.

-------------------------------------------------------------------------------------------------------------------
XmlDocument xmlDoc = CommonUtility.GetRssData(txtRssAddress.Text.Trim());
-------------------------------------------------------------------------------------------------------------------

XmlDocument가 Null이 아닐 경우에는, 반환된 XmlDocument에서 channel 노드를 찾아서 xChannel 변수명을 가진 XmlNode에 값을 할당한 후, xChannel의 하위 노드 중에서 item 노드를 찾는다. item 노드가 없을 경우에는 lblInValid 변수명을 가진 Label 컨트롤에 Item이 없다는 메시지를 출력해준다.

-------------------------------------------------------------------------------------------------------------------
if (xmlDoc != null && xmlDoc.ChildNodes.Count > 0)
{
XmlNode xChannel = xmlDoc.SelectSingleNode("//channel");
XmlNodeList xItems = xChannel.SelectNodes("item");

if (xItems == null || xItems.Count == 0)
{
lblInValid.Visible = true;
lblInValid.Text = "추가하려는 RSS에 Item이 없습니다.<br /><br />"
}
}
-------------------------------------------------------------------------------------------------------------------

Item이 있을 경우에는, RSS에 대한 정보를 추가한다. 이 때, 이미 추가된 RSS 정보를 추가하려고 하면, lblInValid 변수명을 가진 Label 컨트롤에 입력된 RSS가 이미 존재한다는 메시지를 출력하도록 하고, 그렇지 않은 경우에는 Item 노드의 pubDate 노드의 날짜 정보를 변경 후에, Item 정보를 입력한다.

-------------------------------------------------------------------------------------------------------------------
string strAddChannel = CommonUtility.InsertRssAddress(strChannelTitle, strChannelLink, strChannelDesc);

if (string.Compare(strAddChannel, "EXIST", true) == 0)
{
lblInValid.Visible = true;
lblInValid.Text = "입력한 RSS 주소가 이미 존재합니다.<br /><br />"
}
else
{
xChannel = CommonUtility.ConvertToFitDateTime(xChannel);
string strAddItem = CommonUtility.InsertRssItems(strChannelLink, xChannel);
}

-------------------------------------------------------------------------------------------------------------------

정상적으로 데이터베이스에 데이터들이 입력이 되었으면, 등록된 RSS의 개수를 나타내는 lblCount 변수명을 가진 Label 컨트롤의 값을 +1 해주고, 추가된 RSS 정보에 대한 주소 목록과 포스트 목록을 다시 가져온다.

-------------------------------------------------------------------------------------------------------------------
txtRssAddress.Text = string.Empty;
lblCount.Text = (int.Parse(lblCount.Text) + 1).ToString();

DisplayRssAddressList();
DisplayRssItemList(strChannelLink);
-------------------------------------------------------------------------------------------------------------------

지금까지 설명한, btnAddRssAddress_Click 이벤트 핸들러의 코드의 전체 코드는 <리스트 12>와 같다.

-------------------------------------------------------------------------------------------------------------------
protected void btnAddRssAddress_Click(object sender, EventArgs e)
{
XmlDocument xmlDoc = CommonUtility.GetRssData(txtRssAddress.Text.Trim());

try
{
if (xmlDoc != null && xmlDoc.ChildNodes.Count > 0)
{
XmlNode xChannel = xmlDoc.SelectSingleNode("//channel");

string strChannelTitle = xChannel.SelectSingleNode("title").InnerText;
string strChannelLink = xChannel.SelectSingleNode("link").InnerText;
string strChannelDesc = xChannel.SelectSingleNode("description").InnerText;
XmlNodeList xItems = xChannel.SelectNodes("item");

if (xItems == null || xItems.Count == 0)
{
lblInValid.Visible = true
lblInValid.Text = "추가하려는 RSS에 Item이 없습니다.<br /><br />"
}
else
{
string strAddChannel = CommonUtility.InsertRssAddress(strChannelTitle, strChannelLink, strChannelDesc);

if (string.Compare(strAddChannel, "EXIST", true) == 0)
{
lblInValid.Visible = true
lblInValid.Text = "입력한 RSS 주소가 이미 존재합니다.<br /><br />"
}
else
{
xChannel = CommonUtility.ConvertToFitDateTime(xChannel);
string strAddItem = CommonUtility.InsertRssItems(strChannelLink, xChannel);

if (string.Compare(strAddItem, "OK", true) != 0)
{
lblInValid.Visible = true
lblInValid.Text = "Item 값을 입력하는 중 오류가 발생하였습니다.<br /><br />"
}
else
{
txtRssAddress.Text = string.Empty;
lblCount.Text = (int.Parse(lblCount.Text) + 1).ToString();

DisplayRssAddressList();
DisplayRssItemList(strChannelLink);
}
}
}
}
else
{
lblInValid.Visible = true
lblInValid.Text = "정상적인 RSS 주소값을 받지 못했습니다.<br /><br />"
}
}
catch (Exception ee)
{
lblInValid.Visible = true
lblInValid.Text = ee.Message + "<br />" + ee.InnerException + "<br /><br />"
}
}

-------------------------------------------------------------------------------------------------------------------
<리스트 12> btnAddRssAddress_Click 이벤트 핸들러 코드

마지막 코드인, 등록된 RSS 주소 목록을 선택할 경우, 포스트 목록이 변경되기 위한, gvRssList_RowCommand 이벤트 핸들러 코드는 다음과 같이 구성된다.

-------------------------------------------------------------------------------------------------------------------
string strChannelLink = e.CommandName;

DisplayRssItemList(strChannelLink);
-------------------------------------------------------------------------------------------------------------------

RSS Viewer 실행하기

Ctrl+F5 또는 디버그 메뉴의 디버깅하지 않고 시작을 선택하여, 브라우저에 Rss Viewer 화면을 실행시킨다.. 그리고, 새로운 RSS 주소인 ASP.NET News(http://www.asp.net/news/rss.ashx)를 입력한 후, 추가 버튼을 클릭하면, 등록된 RSS 주소 목록과 포스트 목록에 동시에 ASP.NET News에서 제공하는 정보가 나타나게 된다.


<화면 8> 새로운 RSS 정보가 추가된 화면

새로운 RSS 주소를 추가할 때, “처리중입니다...”라는 글자가 잠시 나타났다가 사라지는 것을 볼 수 있다. 이것은 btnAddRssAddress 변수명을 가진 ImageButton 컨트롤의 Click이벤트로 인하여 비동기 포스트백을 진행하고 있는 동안, AddUpdateProgress 변수명을 가진 UpdateProgress 컨트롤에 의해서 나타나게 되는 글자이다. UpdateProgress 컨트롤의 ProgressTemplate 안에 사용자에게 보여주고자 하는 글자 또는 이미지를 넣으면, 비동기 포스트백을 진행하고 있는 동안 사용자에게 보여지게 된다.


<화면 9> UpdateProgress 컨트롤에 의해 나타나는 진행 표시

계속적으로 ASP.NET Team Blogs(http://weblogs.asp.net/aspnet-team/rss.aspx)와 ASP.NET Weblogs(http://weblogs.asp.net/MainFeed.aspx?GroupID=4)를 추가한다. 그리고, 등록된 RSS 주소 목록의 목록에서 등록된 RSS 주소를 선택하면, <화면 10>과 같이 포스트 목록이 선택된 RSS 주소에 등록된 포스트의 목록으로 화면의 깜빡임 없이 변경되게 된다.


<화면 10> 선택된 RSS에 대해 포스트 목록이 변경되는 화면

포스트 정보 보기 기능 추가

RSS Viewer가 정상적으로 잘 동작하는 것을 볼 수 있다. 하지만, 포스트 목록에 나와있는 포스트를 누르면, 아무런 동작도 하지 않는다. 포스트 목록에 나와있는 포스트를 누를 경우 포스트에 대한 정보를 보여주도록 ASP.NET AJAX 컨트롤 툴킷 중 ModalPopUp Extender 컨트롤을 사용하도록 하겠다. 웹 폼에서, “panPopUp" 변수명을 가진 Panel 컨트롤을 찾은 다음에, 도구 상자의 AJAX Control ToolKit 탭에서 ModalPopUp Extender를 선택한 후, Panel 컨트롤 하단으로 추가시킨다. 그리고, 다음과 같이 소스를 변경한다.

-------------------------------------------------------------------------------------------------------------------
<cc1:ModalPopupExtender ID="modalItemInfo" runat="server"
TargetControlID="lblItemTitle" PopupControlID="panPopUp"
BackgroundCssClass="modalBackGround" CancelControlID="btnCancel">
</cc1:ModalPopupExtender>
-------------------------------------------------------------------------------------------------------------------
웹 폼을 저장하고, 다시 Ctrl+5 또는 디버그 메뉴의 디버깅하지 않고 시작 메뉴를 선택한 후, 포스트 목록에 나와있는 포스트명을 클릭하면 선택한 포스트에 대한 정보가 나타나게 된다.



<화면 11> 선택한 포스트에 대한 정보가 나타나는 화면

RSS Viewer의 활용

이것으로써, ASP.NET과 ASP.NET AJAX를 이용하여, RSS Viewer를 만드는 것에 대한 설명은 모두 끝났다. 만드는 프로그램 자체가 너무 단순하다는 생각을 지울 수는 없지만, ASP.NET과 ASP.NET AJAX를 사용하면 개발자가 많은 코딩 없이, 사용자에게 효과적인 인터페이스를 제공한다는 것을 보여주고자 했던 것인만큼, 이 글을 읽는 여러분이 ASP.NET AJAX를 사용해봐야겠다라는 불씨를 지핀 것으로만 만족하고자 한다. 앞으로도 기회가 된다면, ASP.NET AJAX를 이용한 실무에 도움되는 글을 여러분에게 제공한다는 말을 남기고 이만 줄이고자 한다. 혹, 이 글을 읽고 궁금한 점이 있거나, 좋은 아이디어가 있으신 분들은, 필자의 블로그(http://www.neostyx.net)으로 와서 글을 남겨주기 바란다.

-------------------------------------------------------------------------------------------------------------------
참고 자료
http://cyber.law.harvard.edu/rss/rss.html
http://www.asp.net/AJAX/Documentation/Live/tutorials/ConsumingWebServicesWithAJAXTutorial.aspx
-------------------------------------------------------------------------------------------------------------------


Creative Commons License
저작물크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
반응형

+ Recent posts

반응형