잊지 않겠습니다.

모든 입력은 위조될 수 있다. - 사용자의 입력을 신뢰하지 말아라.

변조 가능한 모든 데이터들:
  • GET QueryString
  • POST로 전송 가능한 Data
  • Cookie
  • HTTP Header에 저장된 데이터
XSS(Cross-site scripting)
: Web Application에 피해를 입히는 가장 유명하고 널리 알려진 방법
Input Message를 이용해서 Web Page에 공격자의 Form 또는 Javascript를 삽입하여 공격하는 방법

* 대응책 : 사용자가 제공한 모든 데이터는 인코딩을 통해 출력한다.

* Html.Encode(string message)를 통해 ASP .NET MVC에서 가능하다. MVC2에서는 <%: %>으로 간단히 표현 가능하다.
* ASP .NET에서 제공되는 ValidationRequest를 이용하는 방법
 - HTML, Javascript와 유사한 모든 입력을 차단한다.
 - 사용자의 입력이 매우 제한되기 때문에 추천되지 않는 방법이다.

Session Hijacking
: Session ID cookie(ASP.NET_ SessionID로 만들어진다.)를 저장하고, 자체 브라우저를 이용해서 신원을 위장 가능하다.

* 대응책
  - Client IP를 Session의 값에 같이 저장해서, Session을 발급한 Client를 재확인하는 절차를 거친다.
  - HttpOnly flag를 설정한다. 이 경우, Cookie를 javascript를 이용해서 Hijacking 하는 것이 불가능하게 된다.

CSRF
: 사용자가 정상적인 로그인을 거친 이후에 다른 사이트에 Session이 유지된 상태로 이동한 이후 타 사이트에서 값을 넘기는 것으로 사용자의 정보를 훔쳐갈 수 있다.

* 대응책
  - 사용자에게 특화된 토큰이 보안적인 요청안에 포함되도록 한다. ASP .NET MVC에서는 이러한 기법을 이미 사용하고 있다.

  <%using(Html.BeginForm()) { %>
      <%= Html.AntiForgeryToken() %>
  <%}%>

  [AcceptVerbs(HttpVerbs.Post)][ValidateAntiForgeryToken]
  public ActionResult SubmitUpdate()

SQL Injection
: SQL Query문을 Query String 또는 POST 데이터에 넣어서 데이터를 위변조한다.

* 대응책
  - 입력을 Encoding해서 방어
  - 매개변수를 사용하는 Query를 이용해서 방어
  - ORM Mapping을 이용해서 방어 (LINQ SQL, ASP .NET Entity Framework, NHibernate etc..)


저작자 표시 비영리 변경 금지
신고
Posted by xyzlast Y2K
1. IIS
* IIS의 pipe line mode
> ISAPI mode : class mode라고도 불리우며, 이는 file의 확장자를 기준으로 ISAPI 확장에 의하여 처리된다.
> Integrated mode : .NET이 pipe line의 일부이기 때문에 특정 file의 확장자와 관련된 어떠한 ISAPI mode가 필요하지 않는다.

System.Web.Routing.UrlRoutingModule 에 의하여 Url의 Routing이 처리가 된다.
이는 Url의 모든 파일이나 내용이 .NET에 의하여 관리되는 것을 의미한다.

2. Routing
System.Web.Routing.UrlRoutingModule은 요청을 받는 즉시 System.Web.Routing을 실행한다.
Routing의 역활은 먼저, Disk의 파일과의 연관성을 찾아내고 파일과의 연관성이 없는 경우, URL 패턴을 인식하고 분석하여 내부 Component가 사용할 수 있는 Request context 데이터 구조를 구성한다. 파일과의 연관성이 발견되는 경우, 파일에 대하여 실행이 되게 된다. 정적 resource 파일들(*.jpg와 같은 그림 파일이나 html 파일들)에 대하여 제공 및 aspx 파일에 대한 WebForm 실행이 이루어진다.
Routing 구성은 System.Web.Routing.RouteTable이라는 static collection안에 놓여진다. 그 Collection안의 entry는 서로 다른 URL pattern을 표현하는데 이 부분의 경우에는 Global.asax 파일안에 RegisterRoutes() method로 사용자가 구성이 가능하다.

3. Controller & Action
기본적으로 MvcRouteHandler라는 Routing 처리기를 이용해서 구성이 되며, Routing 결과에 따라 ControllerFactory에서 Controller를 생성하게 된다. 이 과정에서 MvcRouterHandler는 MVC Controller를 찾아내고, Action method의 매개변수를 제공한다. 이 과정은 Assembly reflection을 이용하고 있으며 모든 IController interface를 가지고 이름이 *Controller로 끝이 나는 모든 public class를 ControllerFactory에 등록하고, 들어온 URL 결과에 따라 특정 Controller를 실행하고, 그에 맞는 Action method를 실행한다. 따라서, Controller에서 직접 Response를 제어할 수 있으나, MVC에서는 이를 권장하지 않는다. MVC에서는 계획된 출력을 의미하는 ActionResult 객체를 반환하는 것이 권장된다.

4. ActionResult & View
Controller에서 제공된 ActionResult를 이용하여 View를 Render하는 과정이다. 기본적으로 View의 Render는 IViewEngine을 구현하는 .NET class를 통해서 구현되는데 기본적으로 WebFormViewEngine을 사용한다. 이름을 보면 알 수 있듯이 이는 기존의 WebForm이 Render 될 때 사용되는 View Engine과 동일하다. 그러나 MVC의 '관계의 분리' 개념에 따라 View는 HTML을 생성하는 것 이외에는 어떠한 일을 하지 않는다.
 

저작자 표시 비영리 변경 금지
신고
Posted by xyzlast Y2K
복불복이나 사다리타기 같은 프로그램을 간단히 만들어보자라는 생각으로 잡았다가 은근히 일이 커져버렸다.;
Random으로 어떻게하면 뽑아낼 수 있을지, 요즘 고민되고 있는 Domain-Driven 방법을 어떻게하면 적용할 수 있을지를 고민하다가,
별것도 아닌것에 시간을 다 잡아먹어버린것 같다.;

ASP .NET MVC에서 AJAX도 사용해보고, Castle container도 사용해보고.. 이것저것 사용해보면서 재미를 느낀 짬짬이 취미생활.;

Domain Model 구성은 다음과 같다. 데이터를 따로 제공하지 않아도 되어서 Repository는 비어있는 상태


핵심이된 List Shuffle method.
    static class ShuffledList
    {
        public static List Shuffle(this List list)
        {
            string[] shuffledList = new string[list.Count];
            List freeIndex = new List();
            Random rand = new Random();

            for(int i = 0 ; i < list.Count ; i++)
            {
                freeIndex.Add(i);
            }

            foreach(string item in list)
            {
                int indexOfNewIdx = rand.Next(freeIndex.Count);
                int newIdxOfCard = freeIndex[indexOfNewIdx];

                shuffledList[newIdxOfCard] = item;
                freeIndex.Remove(newIdxOfCard);
            }
            return shuffledList.ToList();
        }
    }



Domain-Driven으로 3tier로 작성해주면서.. 참 이렇게 편하고 좋은 방법을 내가 돌아가고 있었구나.. 하는 후회가 엄청나게 된다.
많은 것을 더 배워야지 되는데. 나태해진 내 자신도 반성이 되고 말이야.;
저작자 표시 비영리 변경 금지
신고
Posted by xyzlast Y2K
게시판 작업을 하나 할 것이 있어서.. 기존의 TextArea를 사용하는 것보다 좀 더 나은 방법을 사용하고 싶은 생각을 가지고 있던 차에 집에서 끄적대다가 발견. 

1. FCKEditor 설치

ASP .NET MVC 모델로 Application을 작성하고, 자신이 생각하면 좋은 위치로 카피.
(여기서는 Scripts/fckeditor/ 폴더로 지정.)




설치가 다 되면 (설치위치)/_samples/default.html 로 들어가면 Sample web page가 다음과 같이 나오면 모든 설치는 완료. 



2. ASP .NET MVC에서 FCKeditor의 사용
먼저 간단히 Editor를 표시해보기 위해서 다양한 방법들이 존재하는데, 기본적으로 ASP .NET MVC는 Page의 Fire & Forget 형태이기 때문에 javascript를 이용한 방법이 가장 좋다. (asp, jsp 등의 다양한 방법들이 있지만, MVC에서 사용하기에는 안좋아 보인다.)

1) FCKeditor의 javascript를 추가한다. 
<script type="text/javascript" src="fckeditor/fckeditor.js"></script>

2) FCKeditor의 인스턴스를 생성한다. 
<script type="text/javascript"> window.onload = function() { var oFCKeditor = new FCKeditor( 'MyTextarea' ) ; oFCKeditor.BasePath = "/fckeditor/" ; oFCKeditor.ReplaceTextarea() ; } </script>
3) textarea를 넣어주고, FCKeditor로 만들어준다. 
<textarea id="MyTextarea" name="MyTextarea">This is <b>the</b> initial value.</textarea>

3. FCKeditor의 값 얻어오기
MVC에서 주로 사용되는 Form을 이용한 POST 방식으로 값을 얻어오는 것이 안되기 때문에 javascript를 이용해서 값을 얻어와야지 된다. POST를 이용해서 FormCollection을 이용하면 값이 넘어오지 않고 Action이 실행되지도 않는다. 
<script type = "text/javascript" language = "javascript">"
    var fckValue = FCKeditorAPI.GetInstance('name').GetHTML();
</script>

주로 사용되는 것이 FCKeditor의 Instance를 생성하고, 그 값을 얻어오는 반복작업을 줄이기 위해서 HtmlHelper class를 작성.  
   public static class FckEditorHelper
    {
        public static string FckTextBoxBase = "../../Scripts/fckeditor/";
        public static string FckTextBox(this HtmlHelper u, string name)
        {
            return u.FckTextBox(name, null);
        }

        public static string FckTextBox(this HtmlHelper u, string name, object value)
        {
            return u.FckTextBox(name, value.ToString());
        }

        public static string FckTextBox(this HtmlHelper u, string name, string value)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat(
                "<textarea name=\"{0}\" id=\"{0}\" rows=\"50\" cols=\"80\" style=\"width:100%; height: 600px\">{1}</textarea>",
                name, value);
            sb.Append("<script type=\"text/javascript\" language=\"javascript\">");
            sb.AppendFormat("var oFCKeditor = new FCKeditor('{0}');", name);
            sb.AppendFormat("oFCKeditor.BasePath    = '{0}';", FckTextBoxBase);
            sb.Append("oFCKeditor.Height=400;");
            sb.Append("oFCKeditor.ReplaceTextarea();");
            sb.Append("</script>");
            return sb.ToString();
        }

        public static string PrepareFckTextBox(this HtmlHelper u, string name)
        {
            StringBuilder sb = new StringBuilder("<script type = \"text/javascript\" language = \"javascript\">");
            sb.AppendFormat("var fckValue = FCKeditorAPI.GetInstance('{0}').GetHTML();", name);
            sb.Append("</script>");
            
            return sb.ToString();
        }
    }

사용법은 다음과 같이 사용해주면 된다. 
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <%=Html.FckTextBox("fckTextbox")%>
</asp:Content>

결과는 다음과 같다.

저작자 표시 비영리
신고
Posted by xyzlast Y2K

ASP .NET MVC RC1

.NET Framework 2009.01.28 13:28
ASP .NET MVC Framework가 1월 27일부로 RC 버젼이 나왔다. 
ActionResult의 추가와 ViewPage의 선언의 변화, 그리고 Visual Studio와의 UI 통합등 다양한 기능들이 포함되어서 개발자들의 편의성을 보다 더 높인 것 같다. 

기존의 Beta에서 RC로의 upgrade를 위해서 변경해야지 될 사항은 다음과 같다. 

1. web.config의 변경
기존의 Reference 를 변경시켜주기 위해서 assembly 선언을 변경시켜줘야지 된다. 
기존의 complation/assemblies의 항목을 다음과 같이 변경

    <compilation debug="false">
      <assemblies>
        <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
      </assemblies>
    </compilation>

그리고 sectiongroup 도 다음과 같이 변경
    <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
        <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
        <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
          <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
          <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
          <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
          <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
        </sectionGroup>
      </sectionGroup>

마지막으로, View 폴더에 있는 각 View Page에 대한 web.config를 다음과 같이 변경시켜주면 web.config의 변경은 모두 끝나게 된다. 

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpHandlers>
      <add path="*" verb="*"
          type="System.Web.HttpNotFoundHandler"/>
    </httpHandlers>
    <pages
        validateRequest="false"
        pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
        userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <controls>
        <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
      </controls>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <handlers>
      <remove name="BlockViewHandler"/>
      <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
    </handlers>
  </system.webServer>
</configuration>


2. MasterPage의 Page 선언 변경
MVC RC의 가장 큰 변화라고 하면, ViewPage에서의 code behind 파일이 모두 제거가 되었다는 점이다. 이 사항은 MasterPage 역시 마찬가지로, MasterPage의 code behind 파일을 모두 제거해주고, MasterPage의 선언을 다음과 같이 변경시켜주면 된다.

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>


3. ViewPage, UserControl의 Page 선언 변경
MasterPage와 마찬가지로, code behind 파일을 모두 제거하고 각 Model에 맞는 Generic Template 형식으로 모든 View의 선언을 해준다. 이때 code behind 파일을 모두 제거하지 않으면 assembly에서 적당한 class를 찾지 못하는 dll hall과 비슷한 에러가 나타나게 된다. 반드시 project에서 모두 제거시켜줘야지 된다.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>


4. ViewData.Model에서 Model로 변경(Optional)
마지막으로, 좀더 코드를 깔끔하게 해주기 위해서 비록 지금 둘다 사용이 가능하지만 ViewData.Model에서 Model만 적어주는 것으로 Replace를 시켜주면 좀더 코드가 깔끔하게 보이게 된다. 

새로운 기능들이 너무나도 많이 추가가 되었다. Preview2에서 3, 4, 5 갈수록 기능이 많아진다는 느낌을 받고 있었는데, 이제는 정말 생각하지도 못했던 기능들 뿐 아니라 코드가 간결해지고 통일성이 생긴다는 느낌이 들게하는 RC 1의 발표로 ASP .NET MVC 모델이 더욱더 멋져진 느낌이다. ^^


저작자 표시 비영리
신고
Posted by xyzlast Y2K
* 아래 사항은 ScottGu's Blog에서 발췌한 내용으로 실제 변경사항과는 차이가 있을 수 있습니다. 

1. View 에서의 Code-Behind page의 삭제
  생각해보면 너무나 당연한 내용이 드디어 구현되었다. Model-View-Control 에서 View에서 code-behind로 무언가 작업을 해야지 될 일을 만드는 것 자체가 문제라고 생각이 된다. 작업이 필요한 것은 모두 Control이 담당하고, 보여주는 방법에 대한 것을 제공하는 것을 모두 View가 담당하게 된다. 
  기존 코드를 Googling하다보면, View에서 Page_Load 측에서 무언가 일을 해서 download 등 여러 일을 하게 되는 코드를 보게 되는데, 기존 버젼에서도 역시 Controller에서 Response를 변경하게 되면 View에서의 Code-Behind page는 다 비워둔 상태로 작업이 가능하게 된다. 


  View페이지에서의 Model의 선언. 전과는 다른 코드로 만들어진다. 기존 코드에서 View.Model로 시작되던 모든 코드를 Model로 해줘야지 된다는 코드 변경에서 귀찮은 점은 있을 것 같다.


2. HTML/AJAX Helper의 확장 가능
  기존 버젼에서는 HtmlHelper와 AjaxHelper에서 사용되는 중요 기능들 (Html attribute 적용)등이 모두 private 또는 internal로 선언되어 있었다. 그래서 몇몇 코드에서의 확장을 위해 이와 같은 코드를 직접 소스를 다운 받아서 자신의 소스에서 사용하는 방법밖에 없었는데, 이제는 지원 가능하다. 

3. Scaffolding 지원
  UI에서 auto-scaffolding이 지원 가능하다. Rails에서 부러웠던 기능인데, Model과 Controller의 ActionName을 입력받아서 View가 바로 생성 가능하다. Edit, Create, List, Details, Empty와 같은 5가지 기본 scaffolding이 아래와 같은 멋진 UI로 지원된다. ^^ 



4. 다양한 ActionResult : FileResult, JavaScriptResult
 이게 뭐지? 라는 생각을 하다가.. 잘 생각해보니 Preview 2에서 지원하고 있던 FileResult를 재지원한다. 파일 다운로드 또는 MVC.AJAX와 연결시켜서 Image의 다양한 표현을 할 수 있을 것이라고 생각된다. 그리고 JavaScriptResult가 지원이 된다. 이제는 Web UI에서 사용되는 JavaScript 사용이 매우 편리해질 것 같다.

너무나 기대되는 업데이트가 많다. 일단 가장 기대되는 것은 드디어 완벽한 MVC가 되어간다는 생각이 든다. 왜 View에서 Controller가 해야지 되는 Server code가 들어가게 되는지가 가장 큰 불만이였는데, 그 불만도 해결이 되고 JavaScriptResult와 Scaffolding의 경우에는 나오기만 하면 바로 사용해보고 싶은 생각이 든다. 정말 Happy Programming이다. ^^

PS : MS 내부 사이트의 경우에는 MVC를 이용해서 작성된 site가 상당수 있는 것 같다. Windows Mobile live Messenger 홈페이지의 경우에는 Home.Mvc/Index로 접근이 된다. (초기 IIS 6.0에서 MVC를 사용하는 기본 방법이였다.) 뒤지다보면 꽤나 나올 것 같은데, 일단 외부로 상당히 많은 사용자들에게 노출이 되는 live messenger 페이지가 아직 Beta 밖에 안나온 MVC로 만들어져 있다는 것은 놀라웠다. 

저작자 표시 비영리
신고
Posted by xyzlast Y2K
public ContentResult ConvertJoinOrLeaveReasonsToContentResult(IQueryable<JoinOrLeaveReason> joinOrLeaveReasons, string fileName)
{
    string attachment = string.Format("attachment; filename={0}", fileName);

    Response.ClearContent();
    Response.AppendHeader("content-disposition", attachment);
    Response.Buffer = true;
    Response.Charset = string.Empty;

    TableStyle tblStyle = new TableStyle();
    tblStyle.BorderStyle = BorderStyle.Solid;
    tblStyle.BorderColor = Color.Black;
    tblStyle.BorderWidth = Unit.Parse("1px");

    TableItemStyle headerStyle = new TableItemStyle();
    headerStyle.BackColor = Color.LightGray;

    StringWriter sw = new StringWriter();
    HtmlTextWriter tw = new HtmlTextWriter(sw);

    tblStyle.AddAttributesToRender(tw);
    tw.RenderBeginTag(HtmlTextWriterTag.Table);

    tw.RenderBeginTag(HtmlTextWriterTag.Thead);
    {
        headerStyle.AddAttributesToRender(tw);
        tw.RenderBeginTag(HtmlTextWriterTag.Th);
        tw.Write("Date");
        tw.RenderEndTag();

        headerStyle.AddAttributesToRender(tw);
        tw.RenderBeginTag(HtmlTextWriterTag.Th);
        tw.Write("Comment");
        tw.RenderEndTag();
    }
    tw.RenderEndTag();

    Encoding encoding = sw.Encoding;
    tw.RenderBeginTag(HtmlTextWriterTag.Tbody);
    foreach(JoinOrLeaveReason joinOrLeaveReason in joinOrLeaveReasons)
    {
        tw.RenderBeginTag(HtmlTextWriterTag.Tr);
        {
            tw.RenderBeginTag(HtmlTextWriterTag.Td);
            tw.Write(string.Format("{0:yyyy-MM-dd HH:mm:ss}", joinOrLeaveReason.CreateDate));
            tw.RenderEndTag();

            tw.RenderBeginTag(HtmlTextWriterTag.Td);
            tw.Write(string.Format("{0}", joinOrLeaveReason.Comment));
            tw.RenderEndTag();
        }
        tw.RenderEndTag();
    }
    tw.RenderEndTag();

    tw.RenderEndTag();

    ContentResult contentResult = new ContentResult();
    contentResult.ContentType = "application/ms-excel";
    contentResult.Content = sw.ToString();
    contentResult.ContentEncoding = Encoding.UTF8;

    return contentResult;
}

1. Response 설정 : Header, Buffer 설정
2. ContextResult의 데이터 설정
 - HtmlTextWriter를 이용한 Table Build
 - Table의 속성은 Attribute를 이용해서 설정해준다. 
3. 최종적으로 Context를 TextWriter를 이용해서 얻어준다.
저작자 표시 비영리
신고
Posted by xyzlast Y2K


티스토리 툴바