잊지 않겠습니다.

어제 만들던  log 파일 분석기에서 Text를 처리하는 것이 문제가 아닌, 대용량 파일 처리에 대해서 생각하지 못했던 문제가 발생되었다. 

가장 큰 문제는 File를 한번에 얻어서 byte를 얻어낼 경우에 StringBuilder의 최대 사이즈를 넘어가기 때문에 사용할 수 없다는 점. 그리고 FileStream.Read 에서 읽어낼 수 있는 양 자체도 시스템에 따른 특정 사이즈에 결정이 되어있다는 것이 대용량 처리에서 가장 큰 문제가 되었다. 

이 경우에 전에 C++에서는 가상 메모리 또는 File Buffer를 사용해서 일정 크기로 잘라서 읽었던 경험이 있는데. .NET에서는 어떤 방법이 좋을지 다양한 방법으로 실험을 해봤다.;

* 제약 사항
: byte 단위로 읽어야 된다. 
: IIS에서 사용되고 있는 log file이기 때문에 공유 형태로 읽어야지 된다. 
: 크기에 제약이 없이 처리가 가능해야지 된다. 

생각했던 방법들은 다음과 같다. 

1. StringBuilder를 이용한 byte 값 저장. 
: 가장 처음에 생각한 방법이다. 이 방법의 경우에 파일의 크기가 커질 경우에 StringBuilder에서 OutOfMemory Exception을 발생한다. 큰 파일을 열어야지 되기 때문에 바로 퇴장. 
참고로 개발 시스템에서는 2147483647 character만이 string builder의 capacity로 사용 가능. 

2. 1Byte 씩 계속해서 읽어가면서 문자열을 만들어간다. 
: 매우 안정적이지만, 엄청나게 느리다. 
FileStrema.ReadByte() 를 계속해서 호출하기 때문에 느리게 되는데. 상당히 비효율적인 코드로 보인다.;

3. Divide and Conquer
: 거의 방법은 첫번째 방법과 비슷하지만, 특정 BufferSize를 잡아서 그 BufferSize만큼 반복해서 읽어준다. FileStream.Read() 를 호출하는 것으로 약 4096 * 100 byte 씩 한번에 읽어서 처리하는 경우에 약 130 Mbyte의 text를 처리하는데 7sec 도 안걸리게 된다. (속도에 놀라버렸다. 같은 크기의 2번째 방법은 40여분이 걸려도 끝나지 않았다.; )
File의 끝까지 크기를 잡아주기 위해서 언제나 File의 Position과 BufferSize의 비교를 통해서 안정되게 문자열을 뽑아오는데 성공했다. T-T

List<string> messages = new List<string>();
using(FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    Console.WriteLine("Read Text");
    byte[] btDatas = new byte[BufferSize];
    List<byte> readBytes = new List<byte>();
    for(long index = readStartPosition; index < readEndPosition; )
    {
        fs.Position = index;
        int readLength = Math.Min(BufferSize, (int)(readEndPosition - index));
        int readCount = fs.Read(btDatas, 0, readLength);
        index += readCount;
 
       // 가장 불만인 코드. 한글자씩 얻어와서 찾는 방법 이외에는 없을까.. 라는 생각이 무척 많이 든다.
        for(int i = 0 ; i < readCount ; i++)
        {
            Byte readByte = btDatas[i];
            if(readByte == (byte)'\n')
            {
                string message = Encoding.UTF8.GetString(readBytes.ToArray());
                messages.Add(message);
                readBytes.Clear();
            }
            else
            {
                readBytes.Add(readByte);
            }
        }
    }
}
Posted by Y2K
,
IIS 나 log4net으로 만들어진 log file에 대해서 monitoring을 해야지 되는 경우에, 파일 로그의 특성상 가장 최근의 데이터는 언제나 파일의 끝부분에 위치하게 된다. 파일 자체도 끝부분이기도 하지만, 어디까지 파일을 읽어야지 되는지를 잘 파악하기 힘들게 된다. 

잘 생각을 해보니.. 파일의 끝부분부터 모아서 string 변화를 시키고, 자신이 마지막에 읽었던 string 값과 비교해서 찾는 것이 방법을 사용해봤다. 

그런데, 예전부터 사용되는 C에서의 파일처리와 비슷하게 해줘야지 되는 것 같아서.. 조금 애매하긴 하다. 
차라리.. 기존에 읽었던 Byte의 Position을 기억해서, 그 부분부터 끝부분까지 읽는 방법은 어떨지 고민해봐야지 될 것 같다. 
    
            DateTime dtNow = DateTime.Now;
            string fileName = string.Format("ex{0}.log", dtNow.ToString("yyMMdd"));
            string lastReadLine = "7015289892;01051410960;1051410960;NB00000380;20081220095101";
            using(FileStream fs = File.OpenRead("C:\\zordbs0200511-s06-호스트웨이.dat"))
            {
                long fileEndPosition = fs.Seek(0, SeekOrigin.End);
                
                long currentPosition = fileEndPosition - 2;
                fs.Position = fileEndPosition;
                List<string> gettingStrings = new List<string>();
                bool findOldText = false;
                do
                {
                    bool crlfFind = false;
                    List<byte> btDatas = new List<byte>();
                    string text;

                    do
                    {
                        //파일을 뒤에서부터 읽는 루틴
                        fs.Position = currentPosition;
                        btDatas.Add((byte) fs.ReadByte());

                        List<byte> btDatas2 = new List<byte>();
                        btDatas2.AddRange(btDatas);
                        btDatas2.Reverse();
                        text = Encoding.UTF8.GetString(btDatas2.ToArray());
                        crlfFind = text.Contains("\n");
                        currentPosition--;
                    } while (!crlfFind);

                    if(text.Contains(lastReadLine))
                    {
                        findOldText = true;
                    }
                    else
                    {
                        gettingStrings.Add(text);
                    }
                } while (!findOldText);


                foreach(string text in gettingStrings)
                {
                    Console.WriteLine(text);
                }
                Console.ReadLine();
            }

Posted by 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 Y2K
,

ASP .NET MVC RC1

.NET Framework 2009. 1. 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 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 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 Y2K
,
ActionResult의 Type중 ContentResult를 이용한다. 

먼저, Response 에 Buffer 설정과 Header 설정을 한다.
            Response.ClearContent();
            Response.AppendHeader("content-disposition", attachment);
            Response.Buffer = true;

다음 ContentResult의 결과를 넣어준다. 
<table>로 표현하는 경우에는 Excel에서 표로 읽는 것이 가능하다. 

            StringBuilder sb = new StringBuilder();
            sb.Append("<table>");
            sb.Append("<tr>");
            sb.Append("<td>Date</td>");
            sb.Append("<td>Comment</td>");
            sb.Append("</tr>");

            foreach(JoinOrLeaveReason joinOrLeaveReason in joinOrLeaveReasons)
            {
                sb.Append("<tr>");
                sb.AppendFormat("<td>{0:yyyy-MM-dd HH:mm:ss}</td>", joinOrLeaveReason.CreateDate);
                sb.AppendFormat("<td>{0}</td>", joinOrLeaveReason.Comment);
                sb.Append("</tr>");
            }
            sb.Append("</table>");

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

            return contentResult;

사용될 수 있는 Content-Type의 종류는 다음과 같다. 

1) Multipart Related MIME 타입
  - Content-Type : Multipart/related(기본형태)
  - Content-Type : Application/X-FixedRecord
  - Content-Type: Text/x-Okie; charset=iso-8859-1;

2) XML Media의 타입
 - Content-Type : text/xml
 - Content-Type : Application/xml
 - Content-Type : Application/xml-external-parsed-entity
 - Content-Type : Application/xml-dtd
 - Content-Type : Application/mathtml+xml
 - Content-Type : Application/xslt+xml

3) Application의 타입 
 - Content-Type : Application/EDI-X12:  Defined in RFC 1767 
 - Content-Type : Application/EDIFACT:  Defined in RFC 1767 
 - Content-Type : Application/javascript: Defined in RFC 4329 
 - Content-Type : Application/octet-stream: <-- 디폴트 미디어 타입은 운영체제 종종 실행파일, 다운로드를 의미
 - Content-Type : Application/ogg: Defined in RFC 3534 
 - Content-Type : Application/x-shockwave-flash: Adobe Flash files
 - Content-Type : Application/json: JavaScript Object Notation JSON; Defined in RFC 4627 
 - Content-Type : Application/x-www-form-urlencode <-- HTML Form 형태 

* x-www-form-urlencode와 multipart/form-data은 둘다 폼 형태이지만 x-www-form-urlencode은 대용량 바이너리 테이터를 전송하기에 비능률적이기 때문에 대부분 첨부파일은 multipart/form-data를 사용하게 된다. 

4) 오디오 타입
- Content-Type : Type audio: Audio 
- Content-Type : audio/mpeg: MP3 or other MPEG audio
- Content-Type : audio/x-ms-wma: Windows Media Audio;
- Content-Type : audio/vnd.rn-realaudio: RealAudio;  등등 
 

5) Multipart 타입(아카이브 또는 개체) 
- Content-Type : multipart/mixed: MIME E-mail; 
- Content-Type : multipart/alternative: MIME E-mail;
- Content-Type : multipart/related: MIME E-mail; Defined in RFC 2387 and used by MHTML(HTML mail) 
- Content-Type : multipart/formed-data : <-- 파일 첨부

6) TEXT 타입 
- Content-Type : text/css:  
- Content-Type : text/html:
- Content-Type : text/javascript 
- Content-Type : text/plain: 
- Content-Type : text/xml: 


7) 기타 MIMERPC 예제들 
가) HTTP with x/www-form-urlencoded 일반요청 
POST /some/resource HTTP/1.1
Content-type: application/x-www-form-urlencoded
0=example.getStateName&1=10023


[응답]
HTTP/1.1 200 OK
Content-type: text/plain
New York


나) HTTP x/www-form-urlencoded namedArgs getTeam
POST /some/resource HTTP/1.1
Content-type: application/x-www-form-urlencoded
0=example.getTeam&state=New York&sport=Baseball


[응답]
HTTP/1.1 200 OK
Content-type: multipart/mixed, boundary=B
--BYankees

--BMets

--B


다) HTTP x/www-form-urlencoded unicode addUser
POST /some/resource HTTP/1.1
Content-type: application/x-www-form-urlencoded
0=example.addUser&fname=Igna%ACio&lname=Sanchez


라) HTTP with multipart/form-data 요청
POST /some/resource HTTP/1.1
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"


Joe Blow


--AaB03x  
content-disposition: form-data; name="pics"; filename="file1.gif"
Content-type: image/gif
Content-Transfer-Encoding: binary
...contents of file1.gif...


--AaB03x--

[응답] 
HTTP/1.0 200 OK
Content-type: text/plain
OK


마) Uploading multiple files with unicode

POST /foo HTTP/1.0
Content-type: multipart/form-data, boundary=AaB03x


--AaB03x
content-disposition: form-data; name="field1"
Joe Blow


--AaB03x  <-- 여러개의 파일을 첨부할 때 
content-disposition: form-data; name="pics"
Content-type: multipart/mixed, boundary=BbC04y

--BbC04y  <-- 첫번째 첨부파일은 텍스트
Content-disposition: attachment; filename="file1.txt"
Content-Type: text/plain; charset=UNICODE-1-1
Content-Transfer-Encoding: binary
... contents of some unicode file.txt ...


--BbC04y <-- 두번째 첨부파일은 이미지
Content-disposition: attachment; filename="file2.gif"
Content-type: image/gifContent-Transfer-Encoding: binary
...contents of file2.gif...

--BbC04y


----AaB03x--


바) XML and EMail 요청
HTP Request 
POST /x/foo/bar HTTP/1.0
reply-to-url: callback@domain.com
message-id: abc123
aynch: required0=getAuthorization&1="bobjones"


[응답] 
HTTP/1.0 200 OK
delivered-to: callback@domain.com
Content-length: 0
Mail/SMTP Response 
To: callback@domain.comFrom: mimeRPC@otherplace.com
in-reply-to: abc123
content-type: text/xml
<?xml version="1.0"?><this><is /><xml /></this>

Posted by Y2K
,

MVC Model은 매우 멋진 Web Model이고, View에서 표현하는 방법이 표준 Html을 기본적으로 사용하고 있기 때문에 매우 만족스러울 뿐 아니라, 디자이너와의 협업에서 뛰어난 적응도를 보여준다.

기존의 ASP .NET에서는 수 많은 서버 control들이 있었고, 이러한 서버 control들은 각각의 사용법 및 세부적인 디자인을 변경시키는 데에 있어서 많은 어려움을 가지고 있었던 것이 사실이다. (내가 잘 못하는 것도 있겠지만. –-;;)

MVC 모델을 사용하다가 자주 만드는 것 중 하나가 table인데, 각각의 데이터에 따라서 매번 다른 table을 반복작업을 하는 것이 싫어서 하나의 MVC user control을 만들었다.

먼저 LINQ를 이용한 데이터의 pagination을 제공하고, table에 각각 필요한 데이터를 Model로 선언한다. 그리고 table의 pagination은 AJAX call을 기반으로 한다. 

LINQ를 이용한 PagedList : 

   public interface IPagedList
    {
        int TotalCount { get; set; }
        int PageIndex { get; set; }
        int PageSize { get; set; }

        bool IsPreviousPage { get;  }
        bool IsNextPage { get;  }

        int TotalPageCount { get; set; }
    }

    public class PagedList<T> : List <T>, IPagedList
    {
        public PagedList(IQueryable<T> source, int index, int pageSize)
        {
            TotalCount = source.Count();
            PageSize = pageSize;
            PageIndex = index;
            TotalPageCount = TotalCount/PageSize;
            AddRange(source.Skip(index*pageSize).Take(PageSize).ToList());
        }

        public PagedList(List<T> source, int index, int pageSize)
        {
            TotalCount = source.Count();
            PageSize = pageSize;
            PageIndex = index;
            TotalPageCount = TotalCount / PageSize;
            AddRange(source.Skip(index * pageSize).Take(PageSize).ToList());
        }

        #region IPagedList Members

        public int TotalCount
        {
            get;
            set;
        }

        public int PageIndex
        {
            get;
            set;
        }

        public int PageSize
        {
            get;
            set;
        }

        public bool IsPreviousPage
        {
            get
            {
                return PageIndex > 0;
            }
        }

        public bool IsNextPage
        {
            get
            {
                return ( (PageIndex + 1) * PageSize ) < TotalCount;
            }
        }

        public int TotalPageCount { get; set; }

        #endregion
    }

    public static class Pagiation
    {
        public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
        {
            return new PagedList<T>(source, index, pageSize);
        }

        public static PagedList<T> ToPagedList<T>(this List<T> source, int index, int pageSize)
        {
            return new PagedList<T>(source, index, pageSize);
        }
    }

PagedTableData : Table에 필요한 데이터들
    public class PagedTableData
    {
        public List<string> ColumnHeaders { get; set; }     //Column Headers
        public string TableId { get; set; }                 //Html Id
        public PagedList<string[]> PagedList { get; set; }  //Page<tbody> datas
        public string ActionName { get; set; }              //Ajax call Action data
    }

PagedTable : MVC User Control 
PagedTable.ascx >>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PagedTable.ascx.cs" Inherits="SYNCmailAdmin.Views.Shared.PagedTable" %>
<div id="<%=ViewData.Model.TableId %>">
  <table class="mainTable" width="100%">
    <tr>
      <%foreach(string column in ViewData.Model.ColumnHeaders) { %>
      <th><%=column %></th>
      <%} %>
    </tr>
    
    <%foreach(string[] datas in ViewData.Model.PagedList) { %>
      <tr>
        <%foreach(string data in datas) { %>
          <td><%=data%></td>
        <%} %>
      </tr>
    <%} %>  
  </table>
  <div align="center">
    <%
      int pageIndex = ViewData.Model.PagedList.PageIndex;
      int startIndex = Math.Max(0, pageIndex - 5);
      int endIndex = Math.Min(pageIndex + 5, ViewData.Model.PagedList.TotalPageCount);
    %>
    <%if(ViewData.Model.PagedList.IsPreviousPage) { %>    
      <%for(int i = startIndex ; i < pageIndex ; i++){ %>
      <%=Ajax.ActionLink(i.ToString(), ViewData.Model.ActionName, new {pageIndex = i}, new AjaxOptions() { UpdateTargetId = ViewData.Model.TableId}) %>
      <%} %>
    <%} %>
    <strong><%=pageIndex%></strong>
    <%if(ViewData.Model.PagedList.IsNextPage) { %>
      <%for(int i = pageIndex + 1 ; i <= endIndex ; i++) { %>
      <%=Ajax.ActionLink(i.ToString(), ViewData.Model.ActionName, new {pageIndex = i}, new AjaxOptions() {UpdateTargetId = ViewData.Model.TableId}) %>
      <%} %>
    <%} %>    
  </div>
</div>


PagedTable.ascx.cs
    public partial class PagedTable : System.Web.Mvc.ViewUserControl<PagedTableData>
    {
    }


사용법은 다음과 같다. 
        public ActionResult Index()
        {
            ViewData["Title"] = "Home Page";
            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            HomeIndexDataModel dataModel = new HomeIndexDataModel() {PagedTableData = GetPagedData(null)};
            return View(dataModel);
        }

        public ActionResult GetPagedDataModel(int? pageIndex)
        {
            return View("PagedTable", GetPagedData(pageIndex));
        }

        private PagedTableData GetPagedData(int? pageIndex)
        {
            PagedTableData pageData = new PagedTableData();
            pageData.ColumnHeaders = new List<string>() {"FirstData", "SecondData"};
            pageData.TableId = "TableId";
            pageData.PagedList = TableTempDataGenerator.GetDatas()
                .ToPagedList(pageIndex == null ? 0 : pageIndex.Value, 10);
            pageData.ActionName = "GetPagedDataModel";

            return pageData;
        }

각 Column Header와 Table의 ActionResult 값을 얻어오는 함수명을 넣어주는 것으로 쉽게 해결 가능하다. 조금 제약사항이 되는 것이, GetPagedData에서 반드시 pageIndex라는 변수명으로 적어줘야지 되는 제약사항이 걸리게 된다. 

실행 결과는 다음과 같다.

Sample Source도 같이 올립니다. 필요하신 분들은 얼마든지 퍼가세요.
(이곳에 오시는 분들이 있으시다면.. ㅋㅋ ^^)
Posted by Y2K
,

어제 책을 읽다가 기존에 사용하고 있던 일반적인 text 형태의 Error reporter를 조금 예쁘게 만들어보고 싶다는 생각이 들어서 급조한 소스. 

xslt에 대한 포멧을 아직은 정확히 알지를 못해서 좀더 공부를 해볼 필요가 있을것 같다.
무엇보다 모든 일에 대한 표준화를 제공해줄수 있다는 점에서 가장 큰 장점을 가지고 있는듯. 

Class Source
    using System.Net.Mail;
    using System.Xml;
    using System.Xml.Linq;
    using System.IO;
    using System.Xml.Xsl;

    public class ExceptionReporter
    {
        private readonly Exception reportedException;
        public ExceptionReporter(Exception ex)
        {
            reportedException = ex;
        }

        public string ConvertXml()
        {
            XDocument xDocument = new XDocument();
            XElement rootElement = new XElement("Exceptions");
            XElement element = new XElement("ExceptionInfo");
            
            rootElement.Add(element);
            element.Add(new XElement("ExceptionType", reportedException.GetType().ToString()));
            element.Add(new XElement("Message", reportedException.Message));
            element.Add(new XElement("Source", reportedException.Source));
            element.Add(new XElement("TargetSite", reportedException.TargetSite));
            element.Add(new XElement("StackTrace", reportedException.StackTrace));
            xDocument.Add(rootElement);
            
            string xsl = File.ReadAllText("ErrorReporter.xslt");
            XDocument transformedDoc = new XDocument();
            using(XmlWriter xmlWriter = transformedDoc.CreateWriter())
            {
                XslCompiledTransform transform = new XslCompiledTransform();
                transform.Load(XmlReader.Create(new StringReader(xsl)));
                transform.Transform(xDocument.CreateReader(), xmlWriter);
            }
            return transformedDoc.ToString();
        }

        public bool SendEmailToManager()
        {
            SmtpClient client = new SmtpClient("smtpAddress");
            MailMessage mailMessage = new MailMessage();
            mailMessage.Subject = "Subject";
            mailMessage.IsBodyHtml = true;
            mailMessage.Body = ConvertXml();
            mailMessage.From = new MailAddress("fromMailAddress");
            mailMessage.To.Add(new MailAddress("toMailAddress"));
            client.Send(mailMessage);

            return true;
        }
    }


XSLT File
<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:output method='html'/>
  <xsl:template match='/'>
    <body>
      <xsl:apply-templates/>
    </body>    
  </xsl:template>

  <xsl:template match='ExceptionInfo'>
    <table width="100%" bgcolor="#d1d9e4" cellspacing="1" cellpadding="3">
      <tr>
        <td bgcolor="#666666" colspan="2">
          <font face="Arial" color="white">
            <b>Error Information</b>
          </font>
        </td>
      </tr>
      <tr bgcolor="#d0d0d0">
        <th>Parameter</th>
        <th>Data</th>
      </tr>
    <xsl:apply-templates />      
    </table>
  </xsl:template>
  <xsl:template match='ExceptionType'>
    <tr valign="top" bgcolor="white">
      <td>Exception Type</td>
      <td><xsl:apply-templates/></td>
    </tr>
  </xsl:template>  
  <xsl:template match='Message'>
    <tr valign="top" bgcolor="#f4f4f4">
      <td>Message</td>
      <td>
        <xsl:apply-templates/>
      </td>
    </tr>
  </xsl:template>
  <xsl:template match='Source'>
    <tr valign="top" bgcolor="white">
      <td>Source</td>
      <td><xsl:apply-templates/></td>
    </tr>
  </xsl:template>
  <xsl:template match="TargetSite">
    <tr valign="top" bgcolor="#f4f4f4">
      <td>TargetSite</td>
      <td><xsl:apply-templates/></td>
    </tr>    
  </xsl:template>
  <xsl:template match="StackTrace">
    <tr valign="top" bgcolor="white">
      <td>Stack Trace</td>
      <td><xsl:apply-imports/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>


사용 예제 : 
            try
            {
                File.ReadAllText("C:\\afwqerqwer.txt");
            }
            catch(Exception ex)
            {
                ExceptionReporter reporter = new ExceptionReporter(ex);
                reporter.SendEmailToManager();
                //Console.WriteLine(reporter.ConvertXml());
            }


결과:




Posted by Y2K
,
XElement를 이용하면 XmlDocument를 이용한 XmlDocument의 생성보다 편하고 직관적인 방법을 제공할 수 있다.

XElement - Element 생성
XComment - Comments 생성
XDeclaration - Declaration 생성

    public class XmlLinq2
    {
        public void CreateXmlDocument()
        {
            XNamespace nameSpace = "http://www.hostway.co.kr";
            XElement xBookParticipant = new XElement(nameSpace + "BookParticipant");
            XAttribute bookAttribute = new XAttribute("type", "Author");
            XElement xFirstname = new XElement(nameSpace + "FirstName", "Joe");
            XElement xLastName = new XElement(nameSpace + "LastName", "Rattz");
            XComment xComment = new XComment("This is XComment");

            xBookParticipant.Add(bookAttribute);
            xBookParticipant.Add(xComment);
            xBookParticipant.Add(xFirstname);
            xBookParticipant.Add(xLastName);

            Console.WriteLine(xBookParticipant);
        }

        public void FindDatas()
        {
            XNamespace nameSpace = "http://www.hostway.co.kr";
            XElement xBookParticipant = new XElement(nameSpace + "BookParticipant", 
                                                     new XElement(nameSpace + "FirstName", "Joe"),
                                                     new XElement(nameSpace + "LastName", "Rattz"));
            Console.WriteLine(xBookParticipant.ToString());
            Console.WriteLine(xBookParticipant);
        }
    }

: 기초책을 볼때마다 느끼는 거지만.. 언제나 기본인 내용을 모르고 있는 내 모습이 많이 부끄러워질때가 많은 것 같다. 


Posted by Y2K
,