'소프트웨어/C# & ASP.NET'에 해당되는 글 85건

  1. 2010.02.18 Xml to Json
  2. 2010.02.10 JQGrid using MVC, Json and Datatable.
  3. 2010.01.12 C# 강좌
  4. 2009.12.15 C# 4.0의 새로운 기능
  5. 2009.12.11 잉여들을 위한 클래스설계 이야기 2/4
  6. 2009.12.11 잉여들을 위한 클래스설계 이야기 1/4
  7. 2009.12.09 Data Access Application Block
  8. 2009.12.09 문자열 비교 - 팁
  9. 2009.12.03 C# 프로그래밍 도구
  10. 2009.11.26 Windows Mobile in C# 강좌 Lab 2 - GPS 사용법(2)
2010.02.18 10:48

Xml to Json

How to convert XML to JSON in ASP.NET C#


Last modified: 18 July 2006.   Any comments or suggestions - please fill in form below. Chris Cant.
No download provided - cut and paste to try out. The following code is provided as-is without any warranty of any kind.
  • Code to convert XML to JSON in ASP.NET C#
  • Example of how to insert JSON code into a page when the page is generated
  • Please acknowledge PHD Computer Consultants Ltd (PHDCC) if you use this code
  • Introduction

    JSON is a lightweight data-interchange format. It is particularly useful because it can be 'decoded' easily by web page JavaScript into object form.

    AJAX-based web pages use XmlHttpRequest to receive data from a server in response to a user action. While the returned data is normally in XML format, it can also be returned in JSON string format and processed more easily in JavaScript.

    Many applications may store information in XML format. However they may want to send data to a client using JSON. To achieve this, they must convert their XML data into JSON format. The ASP.NET C# code below does this job.

    Code Description

    The code provides a method private static string XmlToJSON(XmlDocument xmlDoc) that converts an XmlDocument into a JSON string. The code iterates through each XML element, its attributes and children, creating the corresponding JSON objects.
    • The code never generates number or boolean values.
    • The XML documentElement is always reported as member:object even if it could be shortened by the following rules.
    • Element attributes are converted into member "attr_name":"attr_value".
      XML JSON
      <xx yy='nn'></xx> { "xx": {"yy":"nn"} }
      <xx yy=''></xx> { "xx": {"yy":""} }
    • Element children with no attributes, children or text are converted into member "child_name":null
      XML JSON
      <xx/> { "xx":null }
    • Element children with no attributes or children but contain text are converted into "child_name":"child_text"
      XML JSON
      <xx>yyy</xx> { "xx":"yyy" }
    • Other element attributes and children are converted into "child_name":object or an array "child_name":[elements] as appropriate, with element text converted into a member with name "value"
      XML JSON
      <xx yy='nn'><mm>zzz</mm></xx> { "xx": {"yy":"nn", "mm":"zzz"} }
      <xx yy='nn'><mm>zzz</mm><mm>aaa</mm></xx> { "xx": {"yy":"nn", "mm": [ "zzz", "aaa" ] } }
      <xx><mm>zzz</mm>some text</xx> { "xx": {"mm":"zzz", "value":"some text"} }
      <xx value='yyy'>some text<mm>zzz</mm>more text</xx> { "xx": {"mm":"zzz", "value": [ "yyy", "some text", "more text" ] } }
    • Characters are made safe for conversion into JSON. Note that this does not protect your JavaScript from attack if any of the source XML comes from an unsafe source, eg user input.
      XML JSON
      <aa>/z'z"z\yyy<aa>< {"aa": "\/z\u0027z\"z\\yyy" }

    In some special circumstances, such as in the example below, you may need to escape the backslash characters again, eg:

    string JSON = XmlToJSON(doc);
    JSON = JSON.Replace(@"\", @"\\");

    Note that there may be security implications for web pages using unchecked XML contents.

    Example

    The examples on this page come from my Space Browse site.

    XML Input:

    <space name="Cake Collage">
    <frame>
      <photo img="cakecollage1.jpg" />
      <text string="Browse my cake space" />
      <rule type="F" img="cakecollage9.jpg" x="150" y="0" w="300" h="250" />
      <rule type="F" img="cakecollage2.jpg" x="0" y="0" w="150" h="220" />
    </frame>
    <frame>
      <photo img="cakecollage2.jpg" />
      <rule type="B" img="cakecollage1.jpg" />
      <rule type="L" img="cakecollage3.jpg" />
    </frame>
    </space>
    

    JSON Output (re-formatted):

    { "space":
      { "name": "Cake Collage",
        "frame": [ {"photo": { "img": "cakecollage1.jpg" },
                    "rule": [ { "type": "F",
                                "img": "cakecollage9.jpg",
                                "x": "150",
                                "y": "0",
                                "w": "300",
                                "h": "250"
                              }, 
                              { "type": "F",
                                "img": "cakecollage2.jpg",
                                "x": "0",  
                                "y": "0",  
                                "w": "150",  
                                "h": "220" 
                              }
                            ],
                    "text": { "string": "Browse my cake space" }
                   },
                   {"photo": { "img": "cakecollage2.jpg" },
                    "rule": [ { "type": "B", "img": "cakecollage1.jpg" },
                              { "type": "L",  "img": "cakecollage3.jpg" }
                            ]
                   }
                 ]
      }
    }
    

    Once the JSON has been converted into a JavaScript object, eg called space_DOM, the following objects are available:

    • space_DOM.space.name
    • space_DOM.space.frame.length
    • space_DOM.space.frame[0].text.string
    • space_DOM.space.frame[0].rule[0].type

    Your JavaScript code should be flexible to cope with members not existing, members existing as a single value, or members existing as an array. I find it useful to have a JavaScript function ObjectToArray which converts all these cases into an Array of length 0, 1 or greater.

    function ObjectToArray( obj)
    {
        if( !obj) return new Array();
        if( !obj.length) return new Array(obj);
        return obj;
    }
    
    space_DOM.space.frame = ObjectToArray(space_DOM.space.frame);
    

    XmlToJSON C# code

    You may wish to use some of the updates suggsted in the comments below.
    private static string XmlToJSON(XmlDocument xmlDoc)
    {
        StringBuilder sbJSON = new StringBuilder();
        sbJSON.Append("{ ");
        XmlToJSONnode(sbJSON, xmlDoc.DocumentElement, true);
        sbJSON.Append("}");
        return sbJSON.ToString();
    }
    
    //  XmlToJSONnode:  Output an XmlElement, possibly as part of a higher array
    private static void XmlToJSONnode(StringBuilder sbJSON, XmlElement node, bool showNodeName)
    {
        if (showNodeName)
            sbJSON.Append("\"" + SafeJSON(node.Name) + "\": ");
        sbJSON.Append("{");
        // Build a sorted list of key-value pairs
        //  where   key is case-sensitive nodeName
        //          value is an ArrayList of string or XmlElement
        //  so that we know whether the nodeName is an array or not.
        SortedList childNodeNames = new SortedList();
    
        //  Add in all node attributes
        if( node.Attributes!=null)
            foreach (XmlAttribute attr in node.Attributes)
                StoreChildNode(childNodeNames,attr.Name,attr.InnerText);
    
        //  Add in all nodes
        foreach (XmlNode cnode in node.ChildNodes)
        {
            if (cnode is XmlText)
                StoreChildNode(childNodeNames, "value", cnode.InnerText);
            else if (cnode is XmlElement)
                StoreChildNode(childNodeNames, cnode.Name, cnode);
        }
    
        // Now output all stored info
        foreach (string childname in childNodeNames.Keys)
        {
            ArrayList alChild = (ArrayList)childNodeNames[childname];
            if (alChild.Count == 1)
                OutputNode(childname, alChild[0], sbJSON, true);
            else
            {
                sbJSON.Append(" \"" + SafeJSON(childname) + "\": [ ");
                foreach (object Child in alChild)
                    OutputNode(childname, Child, sbJSON, false);
                sbJSON.Remove(sbJSON.Length - 2, 2);
                sbJSON.Append(" ], ");
            }
        }
        sbJSON.Remove(sbJSON.Length - 2, 2);
        sbJSON.Append(" }");
    }
    
    //  StoreChildNode: Store data associated with each nodeName
    //                  so that we know whether the nodeName is an array or not.
    private static void StoreChildNode(SortedList childNodeNames, string nodeName, object nodeValue)
    {
    	// Pre-process contraction of XmlElement-s
        if (nodeValue is XmlElement)
        {
            // Convert  <aa></aa> into "aa":null
            //          <aa>xx</aa> into "aa":"xx"
            XmlNode cnode = (XmlNode)nodeValue;
            if( cnode.Attributes.Count == 0)
            {
                XmlNodeList children = cnode.ChildNodes;
                if( children.Count==0)
                    nodeValue = null;
                else if (children.Count == 1 && (children[0] is XmlText))
                    nodeValue = ((XmlText)(children[0])).InnerText;
            }
        }
        // Add nodeValue to ArrayList associated with each nodeName
        // If nodeName doesn't exist then add it
        object oValuesAL = childNodeNames[nodeName];
        ArrayList ValuesAL;
        if (oValuesAL == null)
        {
            ValuesAL = new ArrayList();
            childNodeNames[nodeName] = ValuesAL;
        }
        else
            ValuesAL = (ArrayList)oValuesAL;
        ValuesAL.Add(nodeValue);
    }
    
    private static void OutputNode(string childname, object alChild, StringBuilder sbJSON, bool showNodeName)
    {
        if (alChild == null)
        {
            if (showNodeName)
                sbJSON.Append("\"" + SafeJSON(childname) + "\": ");
            sbJSON.Append("null");
        }
        else if (alChild is string)
        {
            if (showNodeName)
                sbJSON.Append("\"" + SafeJSON(childname) + "\": ");
            string sChild = (string)alChild;
            sChild = sChild.Trim();
            sbJSON.Append("\"" + SafeJSON(sChild) + "\"");
        }
        else
            XmlToJSONnode(sbJSON, (XmlElement)alChild, showNodeName);
        sbJSON.Append(", ");
    }
    
    // Make a string safe for JSON
    private static string SafeJSON(string sIn)
    {
        StringBuilder sbOut = new StringBuilder(sIn.Length);
        foreach (char ch in sIn)
        {
            if (Char.IsControl(ch) || ch == '\'')
            {
                int ich = (int)ch;
                sbOut.Append(@"\u" + ich.ToString("x4"));
                continue;
            }
            else if (ch == '\"' || ch == '\\' || ch == '/')
            {
                sbOut.Append('\\');
            }
            sbOut.Append(ch);
        }
        return sbOut.ToString();
    }
    

    Using XmlToJSON

    The following code shows how to use XmlToJSON() when an ASP.NET 2 page loads. It then uses the ASP.NET2 ClientScriptManager to insert code containing the JSON string into the web page. See the following section for an example of JavaScript space_processJSON().

    protected void Page_Load(object sender, EventArgs e)
    {
        XmlDocument doc = new XmlDocument();
        try
        {
            string path = Server.MapPath(".");
            doc.Load(path+"whatever.xml");
        }
        catch (Exception ex)
        {
            lblError.Text = ex.ToString();
            return;
        }
    
        // Convert XML to a JSON string
        string JSON = XmlToJSON(doc);
        
        // Replace \ with \\ because string is being decoded twice
        JSON = JSON.Replace(@"\", @"\\");
        
        // Insert code to process JSON at end of page
        ClientScriptManager cs = Page.ClientScript;
        cs.RegisterStartupScript(GetType(), "SpaceJSON", "space_processJSON('" + JSON + "');", true);
    }
    

    Client-side code

    <script src="space/json.js" type="text/javascript"></script>
    
    <script type="text/javascript">
    function space_processJSON( JSON)
    {
        space_DOM = JSON.parseJSON();
        if( !space_DOM)
        {
            alert("JSON decode error");
            return;
        }
        space_DOM.space.frame = ObjectToArray(space_DOM.space.frame);
        space_frameCount = space_DOM.space.frame.length;
        .. or whatever
    }
    </script>
    

    Comments:

    Michael, Mon, 19 Jun 2006 16:46:05 (GMT)
    See this implementation: http://groups.google.de/group/ajaxpro/browse_thread/thread/219f830011e5ca6f/9e72c85fcf802a84#9e72c85fcf802a84

    Damon Carr, Thu, 07 Sep 2006 12:24:31 (GMT)
    Excellent work.

    Alex Egg, Sun, 31 Dec 2006 06:31:43 (GMT)
    Question: Why is XmlToJSON private? Wouldn't it be more appropriate for this method to be declared as public? Also, I think you should change the XmlDocument parameter of XmlToJSON to an XmlNode, it would be much more versatile.
    Also, I have discovered you code does not produce correct JSON when the xml contains cdata blocks

    Eric Walker, Mon, 8 Oct 2007 08:01:45 -0700
    I found that an empty xml node was not being decoded properly (an extra } was being added). So for example <foo /> would be translated as {"foo" : }}
    By tracking whether or not a child was added, I was able to work arround this issue:
    //  XmlToJSONnode:  Output an XmlElement, possibly as part of a higher array
            public static void XmlToJSONnode(StringBuilder sbJSON, XmlElement node, bool showNodeName)
            {
                bool childAdded = false;
                if (showNodeName)
                    sbJSON.Append("\"" + SafeJSON(node.Name) + "\": ");
                sbJSON.Append("{");
                // Build a sorted list of key-value pairs
                //  where   key is case-sensitive nodeName
                //          value is an ArrayList of string or XmlElement
                //  so that we know whether the nodeName is an array or not.
                SortedList childNodeNames = new SortedList();
    
                //  Add in all node attributes
                if (node.Attributes != null)
                    foreach (XmlAttribute attr in node.Attributes)
                        StoreChildNode(childNodeNames, attr.Name, attr.InnerText);
    
                //  Add in all nodes
                foreach (XmlNode cnode in node.ChildNodes)
                {
                    childAdded = true;
                    if (cnode is XmlText)
                        StoreChildNode(childNodeNames, "value", cnode.InnerText);
                    else if (cnode is XmlElement)
                        StoreChildNode(childNodeNames, cnode.Name, cnode);
                }
    
                // Now output all stored info
                foreach (string childname in childNodeNames.Keys)
                {
                    childAdded = true;
                    ArrayList alChild = (ArrayList)childNodeNames[childname];
                    if (alChild.Count == 1)
                        OutputNode(childname, alChild[0], sbJSON, true);
                    else
                    {
                        sbJSON.Append(" \"" + SafeJSON(childname) + "\": [ ");
                        foreach (object Child in alChild)
                            OutputNode(childname, Child, sbJSON, false);
                        sbJSON.Remove(sbJSON.Length - 2, 2);
                        sbJSON.Append(" ], ");
                    }
                }
                sbJSON.Remove(sbJSON.Length - 2, 2);
                if (childAdded)
                {
                    sbJSON.Append(" }");
                }
                else
                {
                    sbJSON.Append(" null");
                }
            }
    
    I hope this is helpful.

    Leon, Mon, 22 Oct 2007 09:20:55 -0700
    Another way to do it (DataContractJsonSerializer):
    http://blogs.msdn.com/kaevans/archive/2007/09/04/use-linq-and-net-3-5-to-convert-rss-to-json.aspx

    Mark Brito, Tue, 19 Feb 2008 21:16:32 GMT
    In spirit of helping .. I found a bug where there is only one element in the xml, it would make it a single element instead of an array.. simply change the following line of code..
    if (alChild.Count == 1)
    to
    if (alChild.Count == 1 && (alChild[0] is string))
    Below is the entire function.
    public static void XmlToJSONnode(StringBuilder sbJSON, XmlElement node, bool showNodeName)
    {
        bool childAdded = false;
        if (showNodeName)
            sbJSON.Append("\"" + SafeJSON(node.Name) + "\": ");
        sbJSON.Append("{");
        // Build a sorted list of key-value pairs
        //  where   key is case-sensitive nodeName
        //          value is an ArrayList of string or XmlElement
        //  so that we know whether the nodeName is an array or not.
        SortedList childNodeNames = new SortedList();
    
        //  Add in all node attributes
        if (node.Attributes != null)
            foreach (XmlAttribute attr in node.Attributes)
                StoreChildNode(childNodeNames, attr.Name, attr.InnerText);
    
        //  Add in all nodes
        foreach (XmlNode cnode in node.ChildNodes)
        {
            childAdded = true;
            if (cnode is XmlText)
                StoreChildNode(childNodeNames, "value", cnode.InnerText);
            else if (cnode is XmlElement)
                StoreChildNode(childNodeNames, cnode.Name, cnode);
        }
    
        // Now output all stored info
        foreach (string childname in childNodeNames.Keys)
        {
            childAdded = true;
            ArrayList alChild = (ArrayList)childNodeNames[childname];
            bool bFlag = false;
            foreach (object oChild in alChild) bFlag = true;
            if (alChild.Count == 1 && (alChild[0] is string))
                OutputNode(childname, alChild[0], sbJSON, true);
            else
            {
                sbJSON.Append(" \"" + SafeJSON(childname) + "\": [ ");
                foreach (object Child in alChild)
                    OutputNode(childname, Child, sbJSON, false);
                sbJSON.Remove(sbJSON.Length - 2, 2);
                sbJSON.Append(" ], ");
            }
        }
        sbJSON.Remove(sbJSON.Length - 2, 2);
        if (childAdded)
        {
            sbJSON.Append(" }");
        }
        else
        {
            sbJSON.Append(" null");
        }
    }

    Milind Amin, Fri, 25 Jul 2008 06:52:56 GMT
    Thanks. Very Good Article.

    Paul Chu, Thu, 16 Oct 2008 01:52:44 GMT
    Thank you and all the other contributors for this excellent article.
    Does the last post contain all the suggested enhancements ?

    Answer: I haven't tested the suggestions but they look good!

    Chris, Thu, 13 Nov 2008 04:47:25 GMT
    I could see where this would come in handy. Hats off to you for that. Overall, I would prefer to build JSON from real objects which gives me the ability to serialize to XML, JSON, or whatever.

    noone, Mon, 06 Apr 2009 12:55:55 GMT
    Use the .NET 3.5 JavaScript Serializer: System.Web.Script.Serialization.JavaScriptSerializer

    Michele Costabile, Wed, 20 May 2009 13:33:31 GMT
    I had a problem with an extra brace at the end of the file. I solved it checking that I really had to remove two characters from the end of the string buffer in XmlToJSONnode. The following is my version of the function. Maybe further inspection of the code would be in order for finding a more elegant solution, but this is what I managed to do in a short time.
    //  XmlToJSONnode:  Output an XmlElement, possibly as part of a higher array
    private static void XmlToJSONnode(StringBuilder sbJSON, XmlElement node, bool showNodeName)
    {
        if (showNodeName)
            sbJSON.Append("\"" + SafeJSON(node.Name) + "\": ");
        sbJSON.Append("{");
        // Build a sorted list of key-value pairs
        //  where   key is case-sensitive nodeName
        //          value is an ArrayList of string or XmlElement
        //  so that we know whether the nodeName is an array or not.
        SortedList childNodeNames = new SortedList();
    
        //  Add in all node attributes
        if (node.Attributes != null)
            foreach (XmlAttribute attr in node.Attributes)
                StoreChildNode(childNodeNames, attr.Name, attr.InnerText);
    
        //  Add in all nodes
        foreach (XmlNode cnode in node.ChildNodes)
        {
            if (cnode is XmlText)
                StoreChildNode(childNodeNames, "value", cnode.InnerText);
            else if (cnode is XmlElement)
                StoreChildNode(childNodeNames, cnode.Name, cnode);
        }
    
        // Now output all stored info
        bool hasAddedChild = false;
        foreach (string childname in childNodeNames.Keys)
        {
            ArrayList alChild = (ArrayList)childNodeNames[childname];
            if (alChild.Count == 1 && (alChild[0] is string))
            {
                hasAddedChild = true;
                OutputNode(childname, alChild[0], sbJSON, true);
            }
            else
            {
                sbJSON.Append(" \"" + SafeJSON(childname) + "\": [ ");
                foreach (object Child in alChild)
                {
                    hasAddedChild = true;
                    OutputNode(childname, Child, sbJSON, false);
                }
                if (hasAddedChild)
                    sbJSON.Remove(sbJSON.Length - 2, 2);
                sbJSON.Append(" ], ");
            }
        }
        if (hasAddedChild)
            sbJSON.Remove(sbJSON.Length - 2, 2);
        sbJSON.Append(" }");
    }
    

    Overide, Thu, 09 Jul 2009 11:09:49 GMT
    ObjectToArray(obj): function is incorrect if obj is String. Maybe better:
    function ObjectToArray( obj)
    {
        if (!obj) return new Array();
        if (!(obj instanceof Array)) return new Array(obj);
        return obj;
    }
    

    Override, Fri, 10 Jul 2009 08:39:11 GMT
    and also it conflicts with JQuery. Line: v = f(v); - not enough memory

    lucky, Fri, 21 Aug 2009 01:29:39 GMT
    Thank

    Karl, Thu, 15 Oct 2009 23:46:49 GMT
    I added a check for numeric values, to optionally display the quotes.
    In OutputNode() change the one line with the quotes to:
    Double temp;
    if (Double.TryParse(sChild, out temp))
     sbJSON.Append(SafeJSON(sChild));
    else
     sbJSON.Append("\"" + SafeJSON(sChild) + "\"");

    출처 : http://www.phdcc.com/xml2json.htm

    '소프트웨어 > C# & ASP.NET' 카테고리의 다른 글

    Xml to Json  (0) 2010.02.18
    JQGrid using MVC, Json and Datatable.  (0) 2010.02.10
    C# 강좌  (0) 2010.01.12
    C# 4.0의 새로운 기능  (0) 2009.12.15
    잉여들을 위한 클래스설계 이야기 2/4  (0) 2009.12.11
    잉여들을 위한 클래스설계 이야기 1/4  (0) 2009.12.11
    Trackback 3 Comment 0
    2010.02.10 15:10

    JQGrid using MVC, Json and Datatable.

    Last couple of days i have been trying to make my sample JQGrid working ASP.NET MVC and DataTable. If you google it with this two terms MVC,JQGrid you will find lot of samples using Linq, but if you work with Databases like Oracle or any other databases which does not have a LINQ provider(atleast at the time of writing this article) your alternate choice is to go with DataSet/DataTable. So i thought of putting this example together to help others who are on the same boat like myself.

    I have given a fully working sample of ASP.NET MVC with JQgrid using Datatable(See below for the download link).

    I am not going to cover the basics of MVC in this article, for which you can refer to other blogs such as this one.

    These are the features i have implemented in this sample,


    • Themes
    • Refresh Grid
    • Server side Paging
    • Sorting
    • JSON based


    I will cover other features of JQGrid in my future articles.

    Here are the steps to get started,

    1. Download JQGrid from here

    2. Create an MVC Application using the Visual Studio 2008 template( if you want a detailed explanation for creating an MVC application VS Template refer here).

    3. Now move the downloaded JQGrid files into the <project>/scripts folders.

    4. Usually with MVC application people tend to put all the themes under Content folder, if you do that here you will have to modify the js files for paging button's images.So i wouldn't recommend moving themes folder.

    4. Open the Site.Master inside <project>/Shared/Site.Master and add links to the following files,
    ../../Scripts/themes/steel/grid.css
    ../../Scripts/themes/jqModal.css
    ../../Scripts/jquery.jqGrid.js
    ../../Scripts/js/jqModal.js
    ../../Scripts/js/jqDnR.js

    5. If you don't like steel themes there 4 other themes( basic,coffee,green and sand) available inside themes folder.

    6. Now you site.master will look similar to this.

    1. <%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>  
    2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  
    3. <html xmlns="http://www.w3.org/1999/xhtml">  
    4. <head runat="server">  
    5.     <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>  
    6.     <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />  
    7.     <script src="/Scripts/jquery-1.3.2.js" type="text/javascript"></script>      
    8.     <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>  
    9.     <link rel="stylesheet" type="text/css" href="../../Scripts/themes/steel/grid.css" title="steel"  
    10.         media="screen" />  
    11.     <link href="../../Scripts/themes/jqModal.css" rel="stylesheet" type="text/css" />  
    12.     <script src="../../Scripts/jquery.jqGrid.js" type="text/javascript"></script>  
    13.     <script src="../../Scripts/js/jqModal.js" type="text/javascript"></script>  
    14.     <script src="../../Scripts/js/jqDnR.js" type="text/javascript"></script>  
    15.       
    16.     <asp:ContentPlaceHolder ID="HeadContent" runat="server" />      
    17. </head>  
    18. <body>  
    19.     <div class="page">  
    20.         <div id="header">  
    21.             <div id="title">  
    22.                 <h1>Sample from arahuman.blogspot.com</h1>  
    23.             </div>  
    24.             <div id="logindisplay">  
    25.                 <% Html.RenderPartial("LogOnUserControl"); %>  
    26.             </div>   
    27.             <div id="menucontainer">  
    28.                 <ul id="menu">                
    29.                     <li><%= Html.ActionLink("Home", "Index", "Home")%></li>  
    30.                     <li><%= Html.ActionLink("About", "About", "Home")%></li>  
    31.                 </ul>  
    32.             </div>  
    33.         </div>  
    34.         <div id="main">  
    35.             <asp:ContentPlaceHolder ID="MainContent" runat="server" />  
    36.             <div id="footer">  
    37.             </div>  
    38.         </div>  
    39.     </div>  
    40. </body>  
    41. </html>  



    7. Create a folder named Helper under the <project>/Helper folder and add the following Helper method to convert a Datatable into the JSON format.

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Web;  
    5. using System.Data;  
    6. using Newtonsoft.Json;  
    7. using System.Text;  
    8. using System.IO;  
    9.   
    10. namespace JQGridMVCDemo.Helper {  
    11.     public class JsonHelper {  
    12.         public static string JsonForJqgrid(DataTable dt, int pageSize, int totalRecords,int page) {  
    13.             int totalPages = (int)Math.Ceiling((float)totalRecords / (float)pageSize);  
    14.             StringBuilder jsonBuilder = new StringBuilder();  
    15.             jsonBuilder.Append("{");  
    16.             jsonBuilder.Append("\"total\":" + totalPages + ",\"page\":" + page + ",\"records\":" + (totalRecords) + ",\"rows\"");  
    17.             jsonBuilder.Append(":[");  
    18.             for (int i = 0; i < dt.Rows.Count; i++) {  
    19.                 jsonBuilder.Append("{\"i\":"+ (i) +",\"cell\":[");  
    20.                 for (int j = 0; j < dt.Columns.Count; j++) {  
    21.                     jsonBuilder.Append("\"");  
    22.                     jsonBuilder.Append(dt.Rows[i][j].ToString());  
    23.                     jsonBuilder.Append("\",");  
    24.                 }  
    25.                 jsonBuilder.Remove(jsonBuilder.Length - 1, 1);  
    26.                 jsonBuilder.Append("]},");  
    27.             }  
    28.             jsonBuilder.Remove(jsonBuilder.Length - 1, 1);  
    29.             jsonBuilder.Append("]");  
    30.             jsonBuilder.Append("}");  
    31.             return jsonBuilder.ToString();  
    32.         }  
    33.     }  
    34. }  



    8. Now open the index page under <project>/SViews/Home/Index.aspx and add the following code,

    1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
    2. <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">  
    3.     Home Page  
    4. </asp:Content>  
    5. <asp:Content ID="indexContent" ContentPlaceHolderID="HeadContent" runat="server">  
    6.     <script type="text/javascript">  
    7.         jQuery(document).ready(function() {  
    8.             jQuery("#list").jqGrid({  
    9.             url: '/Home/GetGridData/',  
    10.                 datatype: 'json',  
    11.                 mtype: 'GET',  
    12.                 colNames: ['Customer ID', 'Contact Name', 'Address', 'City', 'Postal Code'],  
    13.                 colModel: [  
    14.                   { name: 'CustomerID', index: 'CustomerID', width: 100, align: 'left' },  
    15.                   { name: 'ContactName', index: 'ContactName', width: 150, align: 'left' },  
    16.                   { name: 'Address', index: 'Address', width: 300, align: 'left' },  
    17.                   { name: 'City', index: 'City', width: 150, align: 'left' },  
    18.                   { name: 'PostalCode', index: 'PostalCode', width: 100, align: 'left' }  
    19.                 ],  
    20.                 pager: jQuery('#pager'),  
    21.                 rowNum: 10,  
    22.                 rowList: [5, 10, 20, 50],  
    23.                 sortname: 'CustomerID',  
    24.                 sortorder: "asc",  
    25.                 viewrecords: true,  
    26.                 imgpath: '/scripts/themes/steel/images',  
    27.                 caption: 'Northwind Customer Information'  
    28.             }).navGrid(pager, { edit: false, add: false, del: false, refresh: true, search: false });  
    29.         });  
    30.     </script>  
    31.   
    32. </asp:Content>  
    33. <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">  
    34.     <h2>  
    35.         Customers List</h2>  
    36.     <table id="list" class="scroll" cellpadding="0" cellspacing="0" width="100%">  
    37.     </table>  
    38.     <div id="pager" class="scroll" style="text-align: center;">  
    39.     </div>  
    40. </asp:Content>  



    the id (#list) links the html table with the jquery to inject the grid ui's code at runtime.
    it makes an ajax calls using the url(/Home/GetGridData/) provided.
    datatype: json refers to the output from the above call returns the JSON type results.

    9. Now open the home controller page to add the GetGridDataMethod under <project>/Controller/HomeController.cs. Add the following code to it.

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Web;  
    5. using System.Web.Mvc;  
    6. using System.Data.SqlClient;  
    7. using System.Configuration;  
    8. using System.Data;  
    9. using JQGridMVCDemo.Helper;  
    10.   
    11. namespace MvcApplication1.Controllers {  
    12.     [HandleError]  
    13.     public class HomeController : Controller {  
    14.         public ActionResult Index() {  
    15.             ViewData["Message"] = "Welcome to ASP.NET MVC!";  
    16.             return View();  
    17.         }  
    18.   
    19.         public ActionResult About() {  
    20.             return View();  
    21.         }  
    22.   
    23.         public ActionResult GetGridData(string sidx, string sord, int page, int rows) {  
    24.             return Content(JsonHelper.JsonForJqgrid(GetDataTable(sidx,sord,page,rows), rows, GetTotalCount(), page), "application/json");  
    25.         }  
    26.   
    27.         public DataTable GetDataTable(string sidx, string sord, int page, int pageSize) {  
    28.             int startIndex = (page-1) * pageSize;  
    29.             int endIndex = page * pageSize;  
    30.             string sql = @"WITH PAGED_CUSTOMERS  AS  
    31.                         (  
    32.                          SELECT  CustomerID, ContactName, Address, City, PostalCode,  
    33.                            ROW_NUMBER() OVER (ORDER BY " + sidx + @" " + sord + @") AS RowNumber  
    34.                          FROM CUSTOMERS  
    35.                         )  
    36.                         SELECT CustomerID, ContactName, Address, City, PostalCode  
    37.                         FROM PAGED_CUSTOMERS  
    38.                         WHERE RowNumber BETWEEN " + startIndex + @" AND " + endIndex + @";";  
    39.                           
    40.             DataTable dt = new DataTable();  
    41.             SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["mainConnection"].ConnectionString);  
    42.             SqlDataAdapter adap = new SqlDataAdapter(sql,conn);  
    43.             var rows=adap.Fill(dt);  
    44.             return dt;  
    45.         }  
    46.   
    47.         public int GetTotalCount() {  
    48.             string sql = @"SELECT COUNT(*) FROM Customers";  
    49.             SqlConnection conn=null;  
    50.             try {  
    51.                  conn= new SqlConnection(ConfigurationManager.ConnectionStrings["mainConnection"].ConnectionString);  
    52.                 SqlCommand comm = new SqlCommand(sql, conn);  
    53.                 conn.Open();  
    54.                 return (int)comm.ExecuteScalar();  
    55.             } catch {  
    56.             } finally {  
    57.                 try {  
    58.                     if (ConnectionState.Closed != conn.State) {  
    59.                         conn.Close();  
    60.                     }  
    61.                 }catch {  
    62.                 }  
    63.             }  
    64.             return -1;  
    65.         }  
    66.     }  
    67. }  




    I have declared four paramters here which will be passed by the JQuery. To help us understand better i have named it same like the JGrid where sidx refers to Sort Index name, sord refers to Sort Direction, page refers to page being invoked and rows refers to rows per page.

    That's it. You can download the fully functional source code here. Enjoy and leave me a comment if you like it.

     

    출처 :ㅣ http://arahuman.blogspot.com/2009/06/jqgrid-using-mvc-json-and-datatable.html

    '소프트웨어 > C# & ASP.NET' 카테고리의 다른 글

    Xml to Json  (0) 2010.02.18
    JQGrid using MVC, Json and Datatable.  (0) 2010.02.10
    C# 강좌  (0) 2010.01.12
    C# 4.0의 새로운 기능  (0) 2009.12.15
    잉여들을 위한 클래스설계 이야기 2/4  (0) 2009.12.11
    잉여들을 위한 클래스설계 이야기 1/4  (0) 2009.12.11
    Trackback 9 Comment 0
    2010.01.12 08:35

    C# 강좌

    C# 강좌

    '소프트웨어 > C# & ASP.NET' 카테고리의 다른 글

    Xml to Json  (0) 2010.02.18
    JQGrid using MVC, Json and Datatable.  (0) 2010.02.10
    C# 강좌  (0) 2010.01.12
    C# 4.0의 새로운 기능  (0) 2009.12.15
    잉여들을 위한 클래스설계 이야기 2/4  (0) 2009.12.11
    잉여들을 위한 클래스설계 이야기 1/4  (0) 2009.12.11
    Trackback 6 Comment 0
    2009.12.15 20:38

    C# 4.0의 새로운 기능

    Written by 안재우(Jaewoo Ahn), 닷넷엑스퍼트(.netXpert)

     

    이미 많은 분들이 알고 계시겠지만, 드디어 C#의 버전이 4.0이 되었습니다. Microsoft의 버전 업그레이드질(?)에 지치셨을 분들에게 큰 압박이 되겠군요. -.-; 우선 4.0을 돌아보기 전에 그 과거부터 돌아봅시다.

     

    C# 1.0

    C# 1.0은 .NET 1.0 및 Visual Studio 2002와 함께 출시되었습니다. 한참 다른 언어(예: Java, C++)와 C#을 비교하는 얘기들이 많던 시절이었죠. 일부 사람들에게는 C계열 언어의 사생아(?)라는 혹평을 듣기도 했으나, 컴파일러/프로그래밍 언어의 귀재인 Anders Hajelsburg의 존재를 무시할 수는 없었을 겁니다.

     

    2002년~2003년 지음, Anders Hajelsburg는 C# 언어가 추구하는 방향을 'Most Modernized Programming Language'라는 개념으로 간단하게 정리했습니다. 말 그대로 현대에 나오는 프로그래밍 언어에 대한 개념 중 바람직한 것이 있다면 어느 언어보다도 빨리 적극적으로 흡수하는 프로그래밍 언어가 될 것이다라는 뜻입니다. (이 때부터 고생문이 훤하겠다 싶었습니다.)

     

    추억을 돌아보고 싶으신 분들은 MSDN에서 다음 페이지를 보시죠. 이 시절에는 C# 언어 사양을 온라인 버전으로도 볼 수 있었습니다.

    Visual C# Language (C#) - 영문

    Visual C# 언어(C#) - 한글

     

    C# 2.0

    C# 2.0은 .NET 2.0 및 Visual Studio 2005와 함께 출시되었습니다. C# 1.0 시절까지만 해도 프로그래밍 언어와 개발도구의 경계가 좀 애매하던 시절이었긴 하지만, 2.0에 와서야 언어와 개발도구를 명시적으로 분리를 하기 시작했습니다.

     

    C# 2.0에서 새로운 기능들은 다음 항목에 잘 정리되어 있습니다.

    What's New in the C# 2.0 Language and Compiler - 영문

    C# 2.0 언어 및 컴파일러의 새로운 기능 - 한글

     

    제 블로그에서도 C# 2.0에 대한 내용을 소개한 적이 있습니다.

    C# 2.0 및 VB.NET 2005의 새로운 기능 1 - Generic

    C# 2.0 및 VB.NET 2005의 새로운 기능 2

    C# 2.0 및 VB.NET 2005의 새로운 기능 3

     

    여러가지 변화가 있긴 하지만, C# 2.0에서의 가장 핵심적인 변화사항은 역시 Generic과 Partial Type이라고 봅니다. 이 두 가지로 인해 이후 C# 언어, 개발도구, 프레임워크 라이브러리 등에 많은 변화가 일어나게 되니까요. 아, 물론 Nullable TypeIterator,  Anonymous 메서드, Static 클래스Friend 어셈블리 등도 중요합니다.

     

    C# 3.0

    C# 3.0은 .NET 3.5 및 Visual Studio 2008과 함께 출시되었습니다. 무엇보다 C# 3.0에서의 가장 큰 변화는 PDC에서 소개되었던 LINQ의 도입과 var 키워드의 지원입니다. 물론 그 외에도 많은 변화가 있었습니다. 자세한 건 다음 내용을 읽어보시기 바랍니다.

    What's New in Visual C# - 영문

    Visual C#의 새로운 기능 - 한글

     

    LINQ나 var 키워드 외에 나머지 항목들이 흥미로운 것은 완전히 새롭게 등장한 개념이라기 보다는 기존 C#에서의 연장 선상을 보여주는 항목들이 많습니다. 또한 미래를 예측해볼 수도 있죠. ^^

     

    예를 들어, partial type (C# 2.0)에 이어 partial method가 등장했습니다. 현재 C# 4.0에는 포함되어 있지 않지만, 언젠가는 partial property도 등장하지 않을까 추측이 됩니다. (저 뿐만 아니라 여러 C# MVP들이 요청하는 기능 중 하나이죠)

    delegate -> anonymous method -> lambda expression으로의 발전 역시 흥미로운 항목입니다. 친절하게도 MSDN에서는 다음 예제를 통해 C# 1.0에서 C# 3.0까지의 대리자(delegate) 발전 과정을 한 눈에 보여줍니다. ^^

     

    class Test
    {
        delegate void TestDelegate(string s);
        static void M(string s)
        {
            Console.WriteLine(s);
        }

        static void Main(string[] args)
        {
            // Original delegate syntax required 
            // initialization with a named method.
            TestDelegate testdelA = new TestDelegate(M);

            // C# 2.0: A delegate can be initialized with
            // inline code, called an "anonymous method." This
            // method takes a string as an input parameter.
            TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

            // C# 3.0. A delegate can be initialized with
            // a lambda expression. The lambda also takes a string
            // as an input parameter (x). The type of x is inferred by the compiler.
            TestDelegate testDelC = (x) => { Console.WriteLine(x); };

            // Invoke the delegates.
            testdelA("Hello. My name is M and I write lines.");
            testDelB("That's nothing. I'm anonymous and ");
            testDelC("I'm a famous author.");

            // Keep console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        } 
    }
    /* Output:
        Hello. My name is M and I write lines.
        That's nothing. I'm anonymous and
        I'm a famous author.
        Press any key to exit.
    */

    static 멤버 -> static 클래스 -> extension(확장) 메서드 역시도 그 연장 선상에 있습니다. 특히 프레임워크 라이브러리나 유틸리티 작업을 많이 하는 사람들에게 extension 메서드는 이제 거의 필수입니다.

     

    C# 4.0

    도대체 C# 4.0은 언제할건데? 자, 이제 시작합니다. -.-;;

    사실 이제와서 하는 얘기입니다만, 이 포스트를 쓸까 말까를 무척이나 망설였습니다. 왜냐하면, 사실 굳이 쓰지 않더라도 C# 언어 PM인 Mads Torgersen이 쓴 New Features in C# 4.0에 너무나 잘 정리되어 있기 때문입니다. 그러나, 영어에 약하신 분들을 위해 간단하게 정리해보도록 하겠습니다. ^^

     

    C# 4.0은 .NET 4.0 및 Visual Studio 2010과 함께 출시될 예정입니다. C# 4.0의 주요 테마는 이미 Dynamic in C# 4.0 포스트에서 소개했다시피 바로 dynamic입니다. 역시 얘기되었던 대로 C#은 여전히 static type 언어입니다만, dynamic type들과 상호 작용을 할 수 있는 기능들이 추가됩니다.

    사실 C# 4.0에서 dynamic 외에 Visual Basic과의 co-evolution이라는 테마를 가지고 있긴 한데, Microsoft 내부적으로는 의미가 있겠지만 외부적으로는 별 의미가 없는 것 같습니다. VB 사용자 분들에게는 죄송하지만, 최근 몇 년간 VB는 C#의 발전을 기를 쓰고 따라 잡기에 급급한 모습인 듯 합니다. 사실 C# 입장에서는 말이 co-evolution이지, '님하 살살 좀'을 외치는 VB도 따라 와 줄 수 있기에 '당분간 천천히 가줄께'라는 것 같거든요.

     

    New Features in C# 4.0에서는 C# 4.0의 새 기능들을 크게 4개 덩어리로 나누고 있습니다.

    - Dynamic lookup

    - Named and optional parameters

    - COM specific interop features

    - Variance

     

    Dynamic lookup

    Dynamic lookup에 대해서는 이미 Dynamic in C# 4.0에서 기본적인 내용을 소개했으니, 따로 설명하지 않겠습니다. 하지만 자세한 내용을 원하시는 분들은 New Features in C# 4.0에서 해당 부분을 반드시 읽어보시기 바랍니다. 특히 Open issues 절에서는 C# 4.0에서 dynamic과 관련한 제한사항들을 다루고 있습니다. DLR 관련 내용은 제쳐두더라도 extension 메서드를 사용할 수 없다거나, anonymous 함수를 메서드 호출 인수로 사용할 수 없어 LINQ 쿼리의 사용이 제한된다는 내용들은 주의할 항목입니다. 아쉽게도 이 제한사항들은 C# 4.0에서는 개선될 계획이 없습니다.

     

    Named and optional parameters

    이건 예제를 드는 것이 가장 설명이 쉬울 듯 합니다. 예를 들어, 다음과 같은 메서드가 있다고 가정합시다.

     

    public void M(int x, bool option1, string option2);

     

    그런데, 대부분 x 값만 넘겨주고, option1과 option2는 정해진 값만 넘기는 경우가 많다고 가정합시다.

     

    M(100, false, null);

     

    사실 이 경우, 다음과 같은 게 있으면 편할 겁니다.

     

    M(100); // option1 = false, option2 = null;

     

    하지만 현재는 이를 하기 우해서는 M 메서드를 일일이 다 오버로딩하는 수 밖에 없죠.

     

    public void M(int x) { M(x, false, null); }

     

    C# 4.0에서는 Optional Parameter가 도입되어 다음과 같이 처리가 가능합니다.

     

    public void M(int x, bool option1 = false, string option2 = null);

     

    이제 M 메서드는 다음과 같은 3가지 모두로 호출이 가능합니다.

     

    M(100, false, "Hello")

    M(100); // option1 = false, option2 = null;

    M(100, true); // option 2 = null;

     

    그런데 만약 중간에 있는 option1만 생략하고 싶다면 어떻게 해야 할까요?

    전통적인 방법이라면 다음과 같이 될 것입니다.

     

    M(100, , "Hello");

     

    하지만 C# 팀은 이 경우 가독성이 저하된다고 판단하고, named parameter라는 개념을 도입했습니다.

    M(1, option2: "Hello");

     

    named parameter를 쓸 경우, 메서드 인수의 순서를 바꿀 수도 있습니다.

     

    M(option2: "Hello", x: 100);

     

    COM specific interop features

    Dynamic과 named/optional parameter 덕에 COM API를 사용하는 것이 좀 더 편리해졌습니다. 그리고 COM에 국한된 몇 가지 interop 관련 기능이 추가되었습니다.

     

    먼저 COM에서는 Variant 타입을 쓰는 경우가 많은데, Variant의 경우 PIA(Primary Interop Assembly)에서는 object로 표현됩니다. 따라서 항상 object 타입을 자신이 사용하려는 타입으로 변환해야 하는 경우가 많았습니다. C# 4.0에서는 object 대신 dynamic을 사용해서 variant를 표현하는 것이 가능합니다. 예를 들자면 다음과 같죠.

     

    ((Excel.Range)excel.Cells[1, 1]).Value2 = "Hello";  // 기존 방식(cast 필요)

    excel.Cells[1, 1].Value2 = "Hello";  // C# 4.0(cast 필요없음)

     

    Optional Parameter의 지원으로, Default Parameter 등의 처리도 쉬워졌습니다. Missing.Value 노가다를 해보신 분들은 무슨 의미인지 이해하실 듯.. ^^

     

    excel.Workbooks.Add(Missing.Value); // 기존 방식

    excel.Workbooks.Add(); // C# 4.0

     

    또한 no-PIA라는 기능이 생겼습니다. 예전에는 COM 인터페이스를 strong type으로 interop하기 위해서는 PIA 어셈블리가 필요했는데, 덩치도 크다는 것도 있고 PIA는 별도로 배포되는 것이라 버전 관련 문제가 발생한다는 문제도 있었습니다. 그런데, no-PIA 기능은 PIA에서 실제로 프로그램에서 사용하는 부분만 떼서 별도 어셈블리가 아닌 호출 어셈블리 안에 넣어버리는 기능입니다. 따라서 이제 런타임 시에 더 이상 덩치 큰 PIA를 로드할 필요가 없어졌습니다.

     

    매개변수에서의 ref 키워드 문제도 있었는데, 이제 더 이상 COM API를 사용할 때 일일이 ref를 선언하지 않아도 됩니다. 컴파일러가 자동적으로 해당 작업을 처리해준다는군요.

     

    Variance

    통상적으로 Covariance는 원래 지정된 Type대신 그 Type에서 파생된 자식 Type을 사용할 수 있게 해주는 것을 말하며, Contravariance는 그 반대 개념, 즉 지정된 Type 대신 그 Type의 상위 부모 Type을 사용할 수 있게 해주는 것을 말합니다. (양쪽 다 별도의 명시적인 Casting이 없는 경우를 말합니다)

     

    예를 들어 다음과 같은 상속 구조가 있다고 가정합시다.

    class Animal;

    class Person : Animal;

    class Man : Person;

     

    어딘가에서 Person 타입을 요구한다고 가정합시다.

    이 때, Person 대신 Man을 사용할 수 있게 해주는 경우가 Covariance입니다.

    반대로 Person 대신 Animal을 사용할 수 있게 해주는 경우가 Contravariance가 되는거죠.

     

    그리고 만약 Covariance와 Contravariance가 둘 다 되지 않는 경우, 즉 위의 예제에서 무조건 Person 타입만 써야 하는 경우를 Invariant라고 합니다.

     

    C# 3.0까지는 Generic에 대해서는 Invariant였습니다. 그런데, C# 4.0에서는 Generic에 대해서 Covariance 및 Contravariance가 지원됩니다. 보다 자세한 내용은 New Features in C# 4.0의 내용이나, Eric Lippert의 블로그를 참조하시기 바랍니다. 

     

    C# 5.0?

    헉, 끔찍하시죠? ^^

    사실 C# 4.0이 논의될 시점에 C# MVP들과 Microsoft C# 팀 간에 별도의 뉴스 그룹이 만들어져서 매우 긴 토론이 있었습니다. C# Future나 Connect, Forum 등에서도 다양한 의견과 논의가 제시되었습니다.

     

    그 중 많이 얘기된 내용이 typedef의 지원이라거나, partial property, 언어/컴파일러 레벨에서의 AOP 지원, auto-implemented property에서 get이나 set 중 하나만 설정할 수 있게 해달라거나 backing store에 접근할 수 있게 해달라거나.. 다양한 요청이 있었습니다.

    위에서 일부 기능들의 경우, C# 4.0에 반영이 되지 않았을까 싶었는데, 결국 현재 C# 4.0은 위에서 소개한 기능들로 사양이 정해진 것 같습니다.

     

    프로그래밍 언어 얘기가 때로 재밌기도 한데, 어떻게 보면 너무 academic해보기도 하네요. C# 언어에 대한 얘기는 당분간 여기서 끝을 맺도록 하겠습니다. ^^


    출처 : http://blog.naver.com/saltynut/120097115880

    Trackback 5 Comment 0
    2009.12.11 09:05

    잉여들을 위한 클래스설계 이야기 2/4

    본론으로 가기전 여기에서 MFC 클래스 구조도를 함 봅니다~*

    적절한가?

    적절한가?


    Rhea君을 포함한 우리 잉여들에게, MFC는 참 많은 것을 이야기해준다.
    그중 하나가 클래스 구조이다.
    잘만든 상용 C++ 클래스 설계를 어디가면 볼수 있을까? 파랑새는 1px옆에 있다, 바로 MFC가 그것이다.

    MFC는 좆뉴비들이 착각하듯, 게임 스프라이트 툴 만드는 도구가 아니다.
    그속에서 적절히 훔쳐와야 할 것이 무궁무진하다.
    니 친구가 만든 듣보잡 3D 엔진의 구조를 파악하기보다는 차라리 MFC의 구조를 파악하고 숨겨진 의미를 알아내는게 프력증강에 도움이 된다.

    일단 여기에서 훔쳐올 것은 최상단 클래스와 파생 클래스들의 관계이다. 우리가 잘쓰는 CView, CFrameWnd등은 아래와 같이 상속을 받았다.
    파워포인터까지 동원한 포스트...

    파워포인터까지 동원한 포스트...

    왜 이렇게 나누었을까?
    또 CView 이전에 클래스는 무슨 역활을 하나?

    CObject : 거의 모든 MFC 클래스의 기반 클래스로 직렬화(Serialization), 런타임 클래스 정보(Runtime class information), 객체 진단 출력(Object Diagnostic Output) 기능을 제공한다.
    CCmdTarget : 명령 메시지를 받는 기능을 갖고 있다.
    CWinApp : 프로그램을 구동하는 기능
    CDocument : 데이터를 저장하는 기능
    CWnd : 눈에 보이는 속성을 지닌 객체에 관련한 모든 기능
    CFrameWnd : 윈도우 프레임 와꾸을 관리하는 기능
    CView : DC를 포함하여 데이터를 보여주는 윈도우를 관리하는 기능

    이중 가장 유명한 것이 바로 CWnd일 것이다. 300개 이상의 멤버 함수를 갖고 있는 이 클래스는 눈에 보이는 모든 윈도우 객체들이 CWnd를 상속받는다. CView는 물론이고 CDialog나 CContolBar, CEdit, CButton 등 친숙한 각종 클래스들을 낳고 낳은 인기 최고, MFC의 퀸 에일리언, 컨트롤 클래스의 여왕벌 정도 되시는 클래스겠다.

    그리고 CObject는 최상단임에도 불구하고 잘 알려지지 않고 있다. 오죽했으면 CWnd를 최상단 클래스라고 믿는 뉴비들이 많을 지경이니까. 아마 직접 사용하는 일이 없기에 그럴지도 모르겠다. 그러나 DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE, DECLARE_SERIAL/IMPLEMENT_SERIAL등의 매크로를 이용하여 클래스 런타임시 클래스 정보와 객체 생성 여부, 그리고 직렬화를 사용할 수 있게 해준다.
    굳히 기존의 자신의 MFC 프로젝트를 뒤져보지 않더라도 MSDN에서 http://msdn.microsoft.com/ko-kr/library/38z04tfa.aspx를 살펴보면 CObject에서 직접 상속받아 객체를 관리하는 모습을 볼수 있다.

    이렇듯 MFC에서 1차적으로 배울수 있었던 것 하나는 가장 최상단 클래스는 직접 눈에 보이거나 입출력을 하는 일을 하지 않는다 하더라도 객체 관리를 위한 작업 준비를 해둔 것임을 알수 있다.

    이 싯점에서 지난 시간 만들다 멈춘 클래스를 좀더 작업해보자. 지난 시간, 우리는 CCharacter에서 CGameCharacter를 상속받은후, CAmazon과 CAssasion과 CSorceress를 각각 상속받았다. CObject 와 같은 일을 해줄 클래스를 CGObject라는 이름으로 하나 만들었다. 특별히 'G'를 더 추가한 이유는 MFC의 CObject와 혼선을 막기 위해서이다.
    또한 CGameCharacter는 저절로 MFC의 CWnd와 같이 눈에 보이는 것을 다루기 위한 클래스가 되었음을 잊지말자.

    객체 관리를 위한 클래스가 최상단으로.

    객체 관리를 위한 클래스가 최상단으로.

    다시 게임으로 되돌아가보자. 아마존, 어새신같은 직업을 나타나는 클래스가 있지만 아직 이 녀석들에게서 직접 객체를 선언하는 것은 안된다. 지난 시간에 언급한 것처럼 적이라도 아마존이나 어세신, 소서리스같은 특성을 나타낼수 있기 때문이다. 비단 적뿐만이 아니다. 아이템을 주고 도움을 주거나 경비를 서주는 NPC 역시 직업을 가질 수 있다. 

    "얼래? 제가 만들 게임에서는 NPC 직업은 상인인데요, 플레이어는 절대로 상인이 되지 못하거든요?"
    라는 질문이 있을수 있다. 그럴 경우 "상인"이라는 클래스 역시 CChraracter나 CGameCharacter의 파생 클래스로 넣으면 된다.

    또한 싱글 게임이 아니라 네트워크 혹은 MMO 게임이라면 상대편 역시 이들 클래스에서 파생된다. 따라서 아직도 CAmazon같은 직업 클래스는 추상 클래스로 나타내주어야 한다. 그럼 필요한 것은 아마존, 어세신같은 직업을 상속받은 클래스가 실제 플레이어인지, NPC인지, 적인지를 구분할수 있어야 한다.

    다시 말해 플레이어, NPC, 적이란 것도 각각의 클래스도 나타내야 한다는 것이다. MO를 위한 "다른 플레이어"도 클래스로 나타낼수 있으나 이 강좌에서는 일단 싱글 플레이용이라 간주하자.
    아 복잡해진다 ㅠㅠ

    아 복잡해진다 ㅠㅠ


    이제 실제 눈에 보이는 캐릭터(인스턴스가 존재하는 진짜 객체!)를 위한 마지막 파생 클래스가 필요하다.
    이때 다중 상속을 하면 된다. 오호~ 다중 상속 구현 숙제는 이렇게 구현이 되었다.

    사실 Rhea君은 극단적인 다중상속 반대주의자다. 그렇다고 다중상속을 100% 안한다는 이야기가 아니라,
    엔진과 같은 UI부분과 데이터 처리를 위한 패턴 구현으로 인해 결국 다중상속을 받게 되게 되므로
    가급적 설계단계에서는 논리적인 다중상속은 피하자는다는 이야기이다.
    결국 편하게 가자는 이야기인데 이건 다음 시간에 마법 속성이 들어가며 다시 논하겠다.

    예컨데 CPlayer와 CAssassin을 다중 상속 받게되면 플레이어를 위한 어세신 캐릭터가 만들어진다.
    CNPC와 CMerchant를 다중 상속 받게되면 CPU가 움직여주는 상인 NPC가 만들어진다.
    CEnemy와 CSorceress를 다중 상속 받게되면 마법사 적이 만들어진다.

    어세신 하악하악

    어세신 하악하악



    그런데 어느날, 기획자가 달려와 고민이 하나 생길 수도 있다.

    쵝오의 먼치킨 급 캐릭터인데요, 아마존과 소서리스, 혹은 어세신과 아마존의 능력치를 동시에 쓸수 있는 새 캐릭터를 하나 넣기로 하지요, 아, 물론 현질 해야하는 유료 캐릭터로요!

    라는 막장으로 가는 기획이 끼어들수 있다.

    막장에 대해 좀더 자세히 알고 싶다면
    이 문서(http://ko.uncyclopedia.info/wiki/%EC%95%84%EB%82%B4%EC%9D%98_%EC%9C%A0%ED%98%B9) 를 추천한다.

    아마 분명 상속을 써먹을 좋을 기회라 판단한 뉴비 개발자는 CPlayer + CAssassion + CAmazon을 다중 상속 받아 CPlayerAssassinAmazon 클래스를 만들 것이다. 그림으로 보면,

    이른바 죽음의 다이아몬드!

    이른바 죽음의 다이아몬드!

    CPlayer를 배제하더라도 CPlayerAssassinAmamzon은 이른바 죽음의 다이아몬드(DOD, Diamond of Death. 게임프로그래머를 위한 C++ 2장 참조)라는 다중 상속의 폐해, 즉 모호성 문제를 고스란히 떠안게 된다.

    그럼 CPlyaerAssassin과 CPlayerAmazon을 상속받으면 어떻게 될까?
    엎어치나 메치나~란 단어가 적절하다.

    엎어치나 메치나~란 단어가 적절하다.

    이렇게 해본들, 다이아몬드가 길어질 뿐, 나아지진 않는다.
    의도하지 않게 CGameCharacter는 CPlayerAssassinAmazon의 부모 클래스가 되어 예측하지 못한 결과를 초래하는 결과를 낳는다. 이건 좆망하는 실패 사례로 가는 지름길이며 KGC에서 우린 이렇게 망했어요~라며 작년처럼 울부짖을수 있는 아이템이 된다.

    이를 해결하는 방법으로 가상 상속(vitual inheritance), 추상 인터페이스의 사용(AddRef(), Release()), 플러그인 기법 등이 있지만 지나치게 복잡도을 증가시키게 되며 배보다 배꼽이 더 커질 수도 있다.
    여고생치킨 : IT전문 용어로 배보다 배꼽이 더 큰 경우를 가르킨다. 그래도 강남에서 12만원이면 아주 적절한거다(응?).

    여고생치킨 : IT전문 용어로 배보다 배꼽이 더 큰 경우를 가르킨다. 그래도 강남에서 12만원이면 아주 적절한거다(응?).

    따라서 Rhea君 기준으로 가장 좋은 방법은 이 구조를 피하는 것이다!
    이처럼 CAssassin과 CAmazon이 동시에 필요한 경우에는 CAssAmazon(어? 뜻이 좀 -_-;;;)라는 클래스를 아예 하나 만드는 것이다. 어차피 똥꼬아마존AssAmazon은 하나의 캐릭터 클래스이기 때문이다. 마치 C같네~, 멤버가 겹치네~, 같은 함수를 사용할 수 있네~라는 유혹이 뒤따른다. 하지만 그 개발자스런 유혹을 벗어나야 유지보수 단계가 편해진다.
    이 단계에서 이런 일이 발생한다.

    이 단계에서 이런 일이 발생한다.

    사실 방금의 사례는 실제 개발자 혼자 개발하는 기간에도 자주 일어난다.
    흔히 보아온 사례중 하나가 CRecordset 같은 DB 테이블을 하나의 클래스로 생성시킨 경우이다.
    두가지 이상의 CRecordset 파생 클래스(의 객체)를 갖고 놀다가 어느 순간 두 클래스를 하나의 클래스로 다중 상속 시켜 작업을 하는 사례가 있다. 무엇 때문에 어떤 설계 방침을 갖고 그런 구조가 나오게 되었는지 모르겠지만... 상상력 하나만큼은 끝내준다.
    이 이외에도 소켓 객체를 따로따로 갖고 놀다가 하나로 합치거나 아주 기가막힌 것을 볼때가 있는데 이런 경우 대부분, "왠지 그렇게 하면 될것 같은" 유혹에 빠져서가 아닐까 생각해본다.

    또다른 다중상속의 슬픈 예

    또다른 다중상속의 슬픈 예


    아뭏든 본론으로 돌아와보면,
    이게 정답이다.

    이게 정답이다.


    상기와 같은 클래스가 정답이다.
    이는 IS-A, HAS-A 공식과도 맞아 떨어진다.

    갑자기 나온 IS-A, HAS-A? 이게 뭘까?
    이 이야기는 다음 시간 아이템을 다루며 상속과 포함이야기에서 다시 언급하겠다.

    탐구생활 :
    Rhea君은 귀찮아서 클래스 관계만 기술했을뿐, 각각의 멤버 함수와 멤버 변수를 적지 않았다.
    또한 public과 protected, private 도 명시하지 않았다.
    이제까지를 설명한 아래 소스에서 각자가 생각하는 멤버들을 채워보자.
    또한 인스턴스를 직접 생성할 수 없도록 특정 클래스는 추상 클래스로 만들어보자.

    class CGObject
    {
    };

    class CCharacter : public CGObject
    {
    };

    class CGameCharacter: public CCharacter
    {
    };

    class CNPC: public CCharacter
    {
    };

    class CPlayer: public CCharacter
    {
    };

    class CEnemy: public CCharacter
    {
    };

    class CAmazon: public CGameCharacter
    {
    };

    class CAssassin: public CGameCharacter
    {
    };

    class CSorceress: public CGameCharacter
    {

    };

    class CAssassinAmzon: public CGameCharacter
    {
    };

    class CMerchant: public CGameCharacter
    {
    };

    class CPlayerAssassin : public CPlayer, public CAssassin
    {
    };

    class CPlayerAmazon : public CPlayer, public CAmazon
    {
    };

    class CNPCMerchant : public CNPC, public CMerchant
    {
    };

    class CEnemySorceress : public CPlayer, public CSorceress
    {
    };



    '소프트웨어 > C# & ASP.NET' 카테고리의 다른 글

    C# 강좌  (0) 2010.01.12
    C# 4.0의 새로운 기능  (0) 2009.12.15
    잉여들을 위한 클래스설계 이야기 2/4  (0) 2009.12.11
    잉여들을 위한 클래스설계 이야기 1/4  (0) 2009.12.11
    Data Access Application Block  (0) 2009.12.09
    문자열 비교 - 팁  (0) 2009.12.09
    Trackback 8 Comment 0
    2009.12.11 09:03

    잉여들을 위한 클래스설계 이야기 1/4

    며칠전, 한 뉴비 5년차(뉴비 3년이면 이미 잉여이다.)에게서 C++ 클래스 설계 과제에 대한 도움을 해주게 되었다.
    보편적인 OOP 숙제답게 언제나처럼 상속 + 다중상속 + 은닉(캡슐화) + 다형성(폴리모피즘)이 구현되어야 하는 숙제.

    생각해보면 OOP나 C++ 수업에는 이런 경우가 상당히 많은 것 같고
    한번쯤 클래스 설계에 대한 포스팅을 하고 싶기도 했기에
    다른 뉴비와 잉여들을 위해 클래스 설계 이야기를 포스팅해본다.

    원래 구조적  프로그래밍 언어의 대명사였던 C가 있었고 80년대에 들어 OOP, 즉 객체 지향 프로그래밍이 고개를 들었다.

    어떤 언어에 객체 지향 개념을 주입할까 고민하던중,
    가장 인기 있던 언어인 C에 객체 지향을 주입하였고 우리는 이것을 C++이라 부른다.

    물론 C++만 객체 지향이 아니다.
    GW-BASIC은 Visual Basic으로 변모하고 클래스와 객체 개념이 생겨났고
    90년대 초반까지 C와 쌍벽을 이루던 Pascal 역시 OOP의 영향아래 Object Pascal, 즉  Delphi가 생겨났다.

    그리고 사람들이 그 정체를 모르는 객체 지향의 사생아(?) 라이브 스크립트는,
    지가 낳은 자식을 넷스케이프에 의해 자바스크립트라 불리게 되는 기가 막힌 팔자를 겪었으며,
    새로 새끼친 자식까지 Adobe에 의해 액션 스크립트(플래쉬!)로 불리게 된다.

    C 역시 C++뿐만 아니라  Objective-C란게 있다는 것을 기억하자.
    C++은 OOP를 첨가한 가장 성공적인 언어일 뿐이다.

    레포트 자료 찾아서 들어온 좆뉴비분들, 베낄 소스는 안나오고 참 지겨우시죠~~?

    레포트 자료 찾아서 들어온 좆뉴비분들, 베낄 소스는 안나오고 참 지겨우시죠~~?

    그런데 바로 여기에서 문제가 발생한다.
    C++은 구조적 프로그래밍 언어인 C에 OOP를 강제주입(?) 하며,
    C의 모든 사양을 그대로 수용했기에 아직도 "보다 객체 지향 다운 C++"에 대한 토론이 끝날 줄 모르게 되기도 한 것이다.

    더우기 OOP의 이상과 구현이라는 현실에서 서로 맞지 않는 부분이 꽤 많다!
    (그래서 C++은 코딩 트랜드가 꽤나 자주 바뀐다. STL이나 TR1같은 새로운 라이브러리도 추가되어 주시고.)
    정말로 이것 때문에 현재에도, 회사 내에도 개발자들끼리 머리털 붙잡고 치고 박는다. -_-;;;;

    인터페이스 사용 여부를 두고 싸우는 세명의 개발자. - 이전에 다른 포스트에서 썼다고 따지지 말것. 짤방이 부족합니다.

    인터페이스 사용 여부를 두고 싸우는 세명의 개발자. - 이전에 다른 포스트에서 썼다고 따지지 말것. 짤방이 부족합니다.

    아, 그런데 이 시점에서 오래된 낚시글에는 떡밥을 물지 않길 바란다.
    아주 유명한 뻥이거든.
    http://www.okjsp.pe.kr/seq/54602

    낚기면 10년차도 잉여취급한다.

    낚기면 10년차도 잉여취급한다.

    사실 어떤 C++책에서도 이상과 현실이 맞지 않는다고 명시한 책은 없지만,
    가장 유력한 증거가 바로 C#의 탄생이다(이 이야기는 또 나~~중에.).

    즉, Anders Hejlsberg 아저씨가 OOP를 위해 기존 C를 버리고
    ADA같은 좆망한 잊혀진 언어 스펙까지 들고온후 언어 스펙을 완전히 재작성한 것이다.
    (Anders Hejlsberg 에 대해 더 잘 알고 싶으면 http://www.dreamsvc.com/?p=10 으로 ㄱㄱㅆ~)

    예컨데 C#이나 JAVA에서는 다중상속이 아예 불가능하며 심지어 포인터조차 없다.
    C++을 대표하는 기능중 하나인 다중상속과 C를 대표하는 기능인 포인터가 현대적인 환경에는 맞지 않을수도 있다는 이야기이다.
    물론 명시적인 스펙상 불가능하지만 언제나 뒷길이 있다. 하지만 이 포스트는 C++이야기니까 일단은 여기까지만.

    자, 역사 수업은 여기서 마치고 C++ 수업이란 두가지 목표를 가진다.
    하나는 OOP 개념을 잡자는 것이고
    두번째는 C++ 언어자체를 배우는 것이다.
    하긴 머, 요즘은 교수가 컴맹이라 JAVA 따위로 새로운 시대를 맞아 JAVA로 OOP를 배우는 불쌍한 학생들도 있다고 카더라~
    따라서 거의 대부분의 C++ 수업의 중간고사는 클래스 설계가 중심인 과제가 나오게 된다.

    그럼 클래스 설계를 해보기로 하자.
    래셔날 로즈같은 툴을 생각하는 분도 있겠지만 사실 별 쓸모가 없다.
    지난 달 달력 뒤장과 네임펜이 궁극의 클래스 설계 도구이다.

    단, 상기의 언급은 클라이언트 환경에 국한된다.
    서버는 UI가 없어서 래셔날 로즈나 UML로 제법 그럴싸하게 만들어 낼수 있다.
    그래서 서버팀 문서가 클라이언트팀 문서보다 항상 있어 보인다.

    클래스 설계는 어떻게 하면 잘할 수 있을까?
    우선 OOP의 마음 가짐 한가지를 기억하자.
    OOP는 현실세계를 투영한다는 철학이다.
    따라서 평소 철학책도 좀 읽어주시고, 깊이있는 사고를 할수 있는 능력이 있어야 한다.
    개발자라도 니체의 "짜라투스트라는 이렇게 말했다" 나 카뮈의 이방인, 페스트 정도는 읽어주는게 좋다.

    서론 너무너무 길었다,
    Rhea君은 클래스 설계라는 초딩용과제를 맞이하여 어떤 게임을 생각하고 클래스다이어그램을 그려보았다.
    여기서 어떤 게임이란 아마존, 소서, 어세신이라는 미소녀 1명과 아줌마 2명이 데커드 케인이란 노인을 납치한후, 메피스토, 디아블로, 바알을 쥐어패 삥을 뜯는다는 사회 폭력에 관한 게임이다.
    남자도 등장했던 것 같은데 남자로는 한번도 안해봐서 잘 모르겠다.

    사진설명 : 기둥 뒤에 숨어 디아블로에게 삥을 뜯을려는 아마존과 소서리스. 지들 힘으로 안될 것 같아 아는 오빠를 부르고 있다.

    사진설명 : 기둥 뒤에 숨어 디아블로에게 삥을 뜯을려는 아마존과 소서리스. 지들 힘으로 안될 것 같아 아는 오빠를 부르고 있다.

    기본 구조는 다음과 같다.
    모든 플레이 가능한 캐릭터와 몬스터는 다 같은 캐릭터란 클래스에서 출발한다.
    캐릭터는 기본적으로 HP, MP가 있을 것이고 능력치를 가질 것이다. 공격도 하고 속성도 가지고 머 암튼 생각해보면 엄청 비슷하다. 그래서 CCharacter에서 CMonster와 CPlayerCharacter가 파생되었다.

    시작!

    시작!

    그런데... 뭔가 아리까지한 것이 생긴다.
    잘 생각해보면, 또한 ACT1의 용병 Rogue나
    엉덩이 탱탱한 비키니 입고선 활쏘고 죽창들고 달리던 언냐들, 사실 아마존이 하는 짓이랑 똑같지 않은가?


    이거 적은 적이되, 속성은 플레이어와 비슷하거나 똑같다는 사실을 느끼게 된다.
    이거 아주 중요한 발견이다. 즉, 단순히 몬스터와 플레이어를 나눠선 안되겠단 생각이 든다.

    개조하자~

    개조하자~

    자...그런데 이넘의 아마존, 어세신, 소서리스는 적이 될수도 있고 플레이어가 될 수도 있겠다.
    그리고 게임의 가치를 더하는 아이템은 어떻게 처리할 것인가?
    최상단 클래스는 CCharacter가 굳건히 자리를 지킬 것인가?

    포스트가 너무 길게 쓰니 립흘을 안달아줘  독자들의 집중력을 위해 오늘은 여기까지!

    다음 시간에는 캐릭터와 아이템에 도전해보자!

    Trackback 0 Comment 0
    2009.12.09 09:53

    Data Access Application Block

    Data Access Application Block
    http://www.taeyo.net/Columns/View.aspx?SEQ=85&PSEQ=8&IDX=0
    http://poco.egloos.com/328457
    http://dncblog.tistory.com/43
    http://dncblog.tistory.com/1
    http://nowpds.tistory.com/11
    http://jaribium.tistory.com/46
    http://o2tree.tistory.com/56
    http://www.gosu.net/GosuWeb/ArticleBList.aspx?CateCode=0104000


    Simplest code to convert an ADO.NET DataTable to an ADODB.Recordset
    http://forums.asp.net/t/982507.aspx
    http://www.codeproject.com/KB/database/DataTableToRecordset.aspx

    Trackback 4 Comment 0
    2009.12.09 09:51

    문자열 비교 - 팁

    유경상님에 문자열 비교 팁

    Tip about String Compare

    문자열 비교에서 팁 하나를 소개하고자 한다. 문자열을 비교할 때 효율성을 고려해야 할 것이 한두 가지가 아닐 것이지만 흔히 지나치는 것 중 하나가 대소문자 구별 없이 비교를 하는 경우이다. 기본적으로 String.Equals 메소드는 대소문자를 구별하여 비교를 수행한다. 그리고 VB.NET 에서 = 연산자를 이용하여 문자열 비교를 하는 경우도 마찬가지다. 이 때문에 대소문자에 관계 없이 문자열을 비교하고자 할 때 흔히 사용하는 방법은 비교 대상을 모두 대문자로 바꾸거나 모두 소문자로 바꾸어 비교하곤 한다.

    // 대소문자에 관계없는 문자열 비교 C# 코드 예제
    if (s1.ToUpper() == s2.ToUpper()) {
        // 동일한 문자열
    }
    else {
        // 동일하지 않은 문자열
    }

    위 코드는 잘 작동하지만 효율적이라고 볼 수 없다. 왜냐면 ToUpper() 메쏘드 호출은 대문자로 변경된 새로운 문자열 객체를 반환한다는 점이다. 문자열은 변경되지 않는 immutable 임을  잊지 말자. 따라서 위 코드는 불필요하게 문자열 객체 2개를 만들어 내며 이 문자열 객체는 곧바로 GC 의 대상이 되는 쓰레기로 전락한다.

    보다 효율적인 것은 String.Compare 메쏘드를 사용하는 것이다. 이 메쏘드는 비교 옵션을 매개변수로 취할 수 있다. 이 옵션 중에 IgnoreCase 옵션을 사용하면 대문자/소문자에 관계 없이 비교를 해준다. 물론 앞서의 예제처럼 불필요한 문자열 객체를 생성하는 일은 없다.

    // 보다 효율적인 코드 예제
    if (String.Compare(s1,s2, CompareOptions.IgnoreCase) == 0) {
        // 동일한 문자열
    }
    else {
        // 동일하지 않은 문자열
    }

    출처 : http://www.simpleisbest.net/archive/2005/06/08/158.aspx
    Trackback 1 Comment 0
    2009.12.03 08:33

    C# 프로그래밍 도구

    C# 프로그래밍 도구

    C# 팀이 웹에서 다른 CLI 구현, 멋진 개체 브라우저, IDE 트릭, 추가 기능, Obfuscator를 비롯하여 보다 신속한 작업 수행을 지원하는 기타 유용한 도구와 유틸리티를 찾았습니다.

    코드 생성 도구를 포함하여 유용한 도구, 프레임워크 및 클래스 라이브러리에 대한 추가 링크를 보려면 클래스 및 라이브러리 페이지를 확인하십시오.


    주요 도구

    Microsoft® .NET 응용 프로그램을 위한 IMSL™ C# Numerical Library

    C# Numerical Library는 30년 전부터 시작된 선도적인 수학 및 통계 라이브러리입니다.

    Extreme Optimization Mathematics Library for .NET
    Extreme Optimization Mathematics Library for .NET에는 복소수, 곡선, 방정식, 수치 적분, 수치 미분, 벡터, 행렬 및 다양한 특수 함수를 위한 클래스가 포함되어 있습니다.
    devAdvantage : Visual Studio .NET을 위한 C# 소스 코드 분석기

    코드에서 초기에 문제를 확인하면 개발 과정 후반의 코드 검토, 테스트 및 버그 수정에 소요되는 시간을 줄여 개발 주기를 단축하고 프로젝트 비용을 낮출 수 있습니다. devAdvantage는 C# 소스 코드에 버그 및 문제가 있는지 분석하여 코드 검토를 자동화하는 Microsoft Visual Studio .NET 추가 기능입니다.

    라이브러리 및 기타 자료

    C# 컴파일러 및 프레임워크

    Obfuscator

    디컴파일러

    프로파일러/최적화 도구

    리팩터링

    개체 브라우저

    편집기

    IDE(개발 환경)

    빌드 도구

    표준 검증 도구

    테스트

    설명서

    데이터베이스

    정규식

    그래픽, 게임 및 그리기


    출처 : http://msdn.microsoft.com/ko-kr/vcsharp/aa336818.aspx

    '소프트웨어 > C# & ASP.NET' 카테고리의 다른 글

    Data Access Application Block  (0) 2009.12.09
    문자열 비교 - 팁  (0) 2009.12.09
    C# 프로그래밍 도구  (0) 2009.12.03
    Windows Mobile in C# 강좌 Lab 2 - GPS 사용법(2)  (0) 2009.11.26
    c# 개념잡기 - Generic  (0) 2009.11.25
    get/set 메소드와 public 멤버변수  (0) 2009.11.25
    Trackback 3 Comment 0
    2009.11.26 16:14

    Windows Mobile in C# 강좌 Lab 2 - GPS 사용법(2)


    Windows Mobile in C#
    • 본 강좌는 Windows Mobile 6 세미나의 자료를 기반으로 제작 하였습니다.
    • Microsoft Visual Studio 2008 C# / Windows Mobile 6 Professional SDK


    안녕하세요^^ Windows Mobile in C# 세번째 강좌 시간 입니다.

    연재강좌.. 어때요?? 열심히 한다고 만들기는 했는데.. 도움이 좀 되시나요?? :)

    도움이 되었길 기도하면서... 지난시간에 이어 GPS 사용법에 대해 이번 시간을 체워 볼까 합니다.

    지난 강좌에서 가상 GPS 기능인 FakeGPS를 연결하는 방법과 간단한 GPS 좌표를 출력하는 법을 배웠습니다.

    오늘은 좀더 구체적인 GPS 사용법에 대해 알아 볼건데요.. 이해하기 쉽게 설명 하도록 노력해 보겠습니다..^^;;

    1 Form 생성

    1.1 FormGpsInfo

    • 지난 시간에 FormGpsInfo 폼에 TextBox와 Button을 이용하여 GPS 좌표를 출력 하였는데요.. 이번 시간에는 TextBox만을 사용하여 가상 GPS에서 보내는 실시간 정보를 출력 해보도록 하겠습니다.

    • 우선 FormGpsInfo 폼에 도구상자의 TextBox 컨트롤을 더블클릭하여 삽입 합니다.
    • 그리고 TextBox의 Name 속성을 "txtInfo"로 설정하고, Dock 속성을 "Fill", Multiline 속성을 "True"로 설정 합니다.

      1.JPG



    2 참조 추가

    2.1 GPSSingleton 추가

    • GPS Device를 사용하기 위해서 Windows Mobile 6 SDK의 라이브러리를 추가하시면 된다고 전 강좌에서 설명 했습니다. 모르시는 분들은 Lab2 강좌에서 확인 하시면 됩니다. ^^
    • 본 강좌에서는 GPS Operation을 access 하는 독립적인 클래스를 만들어서 사용할 것입니다. 즉, GPS 관련된 모든일을 이 클래스에 정의 하는 것이죠. 표현력 부족..ㅠ.ㅜ
    • 그럼 GPSSingleton.cs 를 새로 생성 합니다. 방법은 GPS 폴더에서 마우스 오른쪽을 클릭하여 "새 항목"을 추가 합니다. 템플릿은 "클래스"이고 파일 이름은 "GPSSingleton.cs" 로 설정 하시면 됩니다.

      2.JPG


    • 자.. 그럼 GPSSingleton.cs 클래스의 기능을 구현해 보도록 하겠습니다. 아래는 GPSSingleton.cs의 전체 코드 입니다.

    public sealed class GPSSingleton : IDisposable
        {
            #region Fields
            private GpsDeviceState _LastDeviceState = null;               // GPS 디바이스 객체 생성 및 초기화
             private GpsPosition _LastValidPosition = null;                // GPS 위치 정보 객체 생성 및 초기화
             private DateTime _LastValidPositionTime = DateTime.MinValue;  // 시간 정보 객체 생성 및 초기화
             private bool _HasValidData = false; 
            private Gps _Gps = new Gps();                        
            private static volatile GPSSingleton _Instance;
            private static object _SyncRoot = new Object();
            #endregion Fields
    
            #region Properties
            public GpsServiceState LastDeviceState
            {
                get { return _LastDeviceState.DeviceState; }    //디바이스 상태
            }
    
            public GpsServiceState LastServiceState
            {
                get { return _LastDeviceState.ServiceState; }   //서비스 상태.
            }
    
            public String DriverFriendlyName
            {
                get { return _LastDeviceState.FriendlyName; }   //디바이스 장치명.
            }
    
            public GpsPosition LastValidPosition
            {
                get { return _LastValidPosition; }              //위치정보.
            }
    
            public DateTime LastValidPositionTime
            {
                get { return _LastValidPositionTime; }          //GPS로 받아본 국제 표준 시간.
            }
    
            public bool HasValidData
            {
                get { return _HasValidData; }
            }
            #endregion
    
            #region Private Constructor for Singleton
    
            private GPSSingleton()
            {
                _Gps.DeviceStateChanged += new DeviceStateChangedEventHandler(gps_DeviceStateChanged);
                _Gps.LocationChanged += new LocationChangedEventHandler(gps_LocationChanged);
                _Gps.Open();
            }
            #endregion
    
            #region Singleton
            public static GPSSingleton Instance
            {
                get
                {
                    if (_Instance == null)
                    {
                        lock (_SyncRoot)
                        {
                            if (_Instance == null)
                            {
                                _Instance = new GPSSingleton();
                            }
                        }
                    }
    
                    return _Instance;
                }
            }
            #endregion Singleton
    
            #region EventHandlers
            //EventArgs 클래스 상속 및 구현
            private void gps_LocationChanged(object sender, LocationChangedEventArgs args)
            {
                _LastValidPosition = args.Position;
    
                if (_LastValidPosition != null && _LastValidPosition.LatitudeValid == true 
                    && _LastValidPosition.LongitudeValid == true)
                {
                    _HasValidData = true;
                    _LastValidPositionTime = DateTime.Now;
                }
            }
    
            //EventArgs 클래스 상속 및 구현
            private void gps_DeviceStateChanged(object sender, DeviceStateChangedEventArgs args)   
            {
                _LastDeviceState = args.DeviceState;
    
                if (_LastDeviceState != null && _LastDeviceState.DeviceState == GpsServiceState.Off 
                    || _LastDeviceState.DeviceState == GpsServiceState.ShuttingDown 
                    || _LastDeviceState.DeviceState == GpsServiceState.Unknown 
                    || _LastDeviceState.DeviceState == GpsServiceState.Unloading)
                {
                    _HasValidData = false;
                }
            }
            #endregion EventHandlers
    
            #region IDisposable Members
    
            public void Dispose()
            {
                if (_Gps.Opened)
                {
                    _Gps.Close();    // GPS Close() 메소드.
                    _Gps = null;
                }
            }
    
            #endregion
        }
    

    • 이제 코드가 점점 길어지기 시작하죠?? ㅎ 천천히 하나씩 살펴 볼 텐데요.. 아직 제가 실력이 많이 모자라서 틀린 부분이 있을 지도 모르겠네요.. 그래도 조금 양해 부탁 드리면서 진행을 시작 하겠습니다. 내용을 보시면 #region 을 사용하여 코드를 깔끔하게 정리 하였습니다. 저도 C#은 처음 접해보는 거라 #region가 뭔지 몰랐었는데.. 찾아보니 금방 나오네요..ㅎ #region에 대해서 궁금해 하시는 분들은 http://msdn.microsoft.com/ko-kr/library/ed8yd1ha(VS.80).aspx 이곳에서 확인 하시기 바랍니다..^^

    • #region Fields 부분을 보시면 사용 되어지는 변수들을 생성하고 초기화 시키는 코드를 보실 수있습니다.
    • #region Properties 은 get블럭으로 외부 각 필드값을 반환합니다. 소스를 보시면 대~~~충 어떤 값들인지 예상은 할 수 있겠죠?? 디바이스와 서비스 상태 및 디바이스 장치명, 위치, 시간 등에 대한 내용 입니다.

    • 아래의 코드는 이벤트 처리기 입니다. GPS 상태와 위치가 변경 되었을 때, GPS 객체에서 갱신 시켜주는 역할을 합니다. 그리고 GPS를 Open 시켜 줍니다. 여기서 쎈스가 있는 사람들은 눈치 채셨을지 모르겠네요.. Open() 메소드가 있다면 반드시 Close() 메소드가 존재 하겠죠?? 종료시에 Close() 해주 셔야 합니다^^

    private GPSSingleton()
      {
          _Gps.DeviceStateChanged += new DeviceStateChangedEventHandler(gps_DeviceStateChanged);
          _Gps.LocationChanged += new LocationChangedEventHandler(gps_LocationChanged);
          _Gps.Open();
      }
    

    • GpsSingleton 클래스를 살펴 보았습니다. 어딘가 모르게 설명의 어색한 부분이 있는것 같은데...ㅡㅡ ㅎ

    3 Source Code

    3.1 FormGpsInfo

    • 이제 Form Code를 작성해 봐야겠죠?? 아래는 FormGpsInfo의 전체 Code 입니다.
    namespace MobilityMetro2009
    {
        public partial class FormGpsInfo : Form
        {
            GPSSingleton _gps = GPSSingleton.Instance;
            StringBuilder _info = new StringBuilder();
    
            public FormGpsInfo()
            {
                InitializeComponent();
                GpsUpdateTimer.Enabled = true;   // Timer 작동
            }
    
            void UpdateGPSInfo()
            {
                _info.Remove(0, _info.Length);
    
                try
                {
                    // 디바이스 장치명, 디바이스 상태, 서비스 상태
                    _info.AppendLine(_gps.DriverFriendlyName + " " + _gps.LastServiceState + ",
                    " + _gps.LastDeviceState);
    
                    // HasValidData는 사용할 수 있는 GPS Data 유무
                    if (_gps.HasValidData == true)
                    {
                        _info.AppendLine("Lat  (DD): " + _gps.LastValidPosition.Latitude);   // GPS 위도
                        _info.AppendLine("Long (DD): " + _gps.LastValidPosition.Longitude);  // GPS 경도
    
                        if (_gps.LastValidPosition.SatellitesInSolutionValid 
                        && _gps.LastValidPosition.SatellitesInViewValid 
                        && _gps.LastValidPosition.SatelliteCountValid)
                        {
                            // 사용중인 위성의 갯수, 탐지된 위성의 갯수
                            _info.Append("Satellite Count: "
                            + _gps.LastValidPosition.GetSatellitesInSolution().Length + "/" 
                            + _gps.LastValidPosition.GetSatellitesInView().Length + " (" 
                            + _gps.LastValidPosition.SatelliteCount + ")");
    
                            // Heading은 방향을 나타 냄.
                            if (_gps.LastValidPosition.HeadingValid == true)
                            {
                                _info.AppendLine(" - Heading: " + _gps.LastValidPosition.Heading.ToString());
                            }
                            else
                            {
                                _info.AppendLine(" - Heading: ");
                            }
                        }
    
                        // 속도 (단위는 knots 노트 이므로 km로 변경해서 사용)
                        if (_gps.LastValidPosition.SpeedValid == true)
                        {
                            _info.AppendLine("Speed: " + _gps.LastValidPosition.Speed.ToString("0.00") 
                            + " knots - " + (_gps.LastValidPosition.Speed * 1.85325).ToString("0.00") + "Km/h");
                        }
    
                        // 해수면 고도
                        if (_gps.LastValidPosition.SeaLevelAltitudeValid == true)
                        {
                            _info.AppendLine("Sea Level Altitude: " 
                            + _gps.LastValidPosition.SeaLevelAltitude.ToString() + " Meters");
                        }
    
                        string Inf = string.Empty;
                        string Separator = string.Empty;
                        
                        // fixType는 위치 계산 방법
                        if (_gps.LastValidPosition.fixType == FixType.XyD)
                        {
                            Inf += Separator + "Fix: 2D";
                            Separator = " - ";
                        }
    
                        else if(_gps.LastValidPosition.fixType == FixType.XyzD)
                        {
                            Inf += Separator + "Fix: 3D";
                            Separator = " - ";
                        }
    
                        // PDOP는 데이터의 신뢰도
                        if (_gps.LastValidPosition.PositionDilutionOfPrecisionValid == true)
                        {
                            Inf += Separator + "PDOP: " + 
                            ((_gps.LastValidPosition.PositionDilutionOfPrecision * 100) / 50).ToString("0")
                            + "%";
                            Separator = " - ";
                        }
    
                        // HDOP는 수평오차
                        if (_gps.LastValidPosition.HorizontalDilutionOfPrecisionValid == true)
                        {
                            Inf += Separator + "HDOP: " + 
                            ((_gps.LastValidPosition.HorizontalDilutionOfPrecision * 100) / 50).ToString("0")
                            + "%";
                            Separator = " - ";
                        }
    
                        // VDOP는 수직오차
                        if (_gps.LastValidPosition.VerticalDilutionOfPrecisionValid == true)
                        {
                            Inf += Separator + "VDOP: " + 
                            ((_gps.LastValidPosition.VerticalDilutionOfPrecision * 100) / 50).ToString("0")
                            + "%";
                            Separator = " - ";
                        }
    
                        _info.AppendLine(Inf);
    
                        // 
                        if (_gps.LastValidPosition.TimeValid)
                        {
                            _info.AppendLine("Time: " + _gps.LastValidPosition.Time.ToLocalTime().ToString());
                        }
                    }
                    else
                    {
                        _info.AppendLine("No GPS Data Available.");
    
                        if (_gps.LastValidPosition.SatellitesInSolutionValid
                            && _gps.LastValidPosition.SatellitesInViewValid
                            && _gps.LastValidPosition.SatelliteCountValid)
                        {
                            _info.AppendLine("Satellite Count: " + 
                            _gps.LastValidPosition.GetSatellitesInSolution().Length + "/" +
                            _gps.LastValidPosition.GetSatellitesInView().Length + " (" +
                            _gps.LastValidPosition.SatelliteCount + ")");
                        }
    
                    }
    
                    txtInfo.Text = _info.ToString();
                }
    
                catch
                {
                }
            }
    
            // Timer 작동
            private void GpsUpdateTimer_Tick(object sender, EventArgs e)
            {
                UpdateGPSInfo();
            }
    
            // 종료 처리
            private void MnuBack_Click(object sender, EventArgs e)
            {
                GpsUpdateTimer.Enabled = false;
                this.Close();
            }
        }
    }
    

    • 간단하게 Code에 주석으로 작성 하였습니다. GPS에 관해 사용 할 수 있는 정보들이 많이 있는데요. 저도 강좌를 위해 공부 하면서 어려운 점이 있네요. ㅠ.ㅜ 이보다 더 자세히 설명 하고 싶지만.. 너무 깊게 들어 가버리게 될까봐.. 간략하게 설명만 주석으로 추가 하였습니다^^;; 자 그럼 작성을 완료 하였으니 에뮬레이터로 실행 시켜 봐야겠죠??

    4 실행 결과

    • 에뮬레이터 실행 방법은 이제 어느정도 아실 거라 생각 됩니다. 그럼 에뮬레이터 실행 결과 화면을 보시죠.
      6.JPG


    • 에뮬레이터로 테스트 해 보셨나요?? 위와 같은 내용을 확인 하셨으면 제대로 하셨습니다. 계속해서 GPS Data가 변하는 것을 보실 수 있을 거에요..

    • 다음 Lab 3 강좌는 SMS 문자 전송에 대한 강좌 입니다. 흠... GPS랑 전혀 연관이 없는데.. ㅋ SMS는 지금까지 만들어온 예제의 "연락처 추가" 기능을 통해 문자 전송하는 방법을 위해 Lab 3에서 배우는 것입니다. 그러니 연관성이 있겠죠?? 그럼 다음 강좌 시간에 뵙겠습니다~ 좋은 하루 보내세요~~ 감사합니다.


    출처 : http://dforge.devpia.com/projects/studymobile/wiki/Lab2-1
    http://dforge.devpia.com/projects/studymobile/wiki/
    Trackback 1 Comment 0