Introduction
When I started learning programming with C#, I searched detailed articles about making complex systems step by step. I can't say that I found lot of them. I think this type of article can be used by beginner and advanced programmers (last skip some simple steps ;)). In this case, I planned a detailed description of developing my next project. The target of this project is to make a Log system with these features:
- Manage messages from 'end users' of different programs, used in complex network data storage, analysis and transmit systems, to developers
- Report errors from 'end users' and testers to developers
- Manage 'ToDo' events
I will post all steps of developing progress in the next part of this article (but may be, I'll combine some steps in part one).
And now, we begin.
Part I
System Structure
The systems uses Client-Server architecture, where server-side is based on ASP.NET, and client side is a Class Library containing client-side forms and functions.
Starting (Step 1)
Server-Side
Server-Side is implemented using XmlWebService
(for now only one, but maybe in future ...).
To start, we need only 2 methods on the server:
GetLog()
- returns posted messages in XML format PostMessage(string message)
- posts a new message on server
GetLog Function
public XmlNode GetLog()
{
string dpath = this.GetType().Assembly.CodeBase;
dpath = dpath.Substring(dpath.IndexOf(":///")+4);
dpath = dpath.Substring(0, dpath.LastIndexOf('/'));
dpath = dpath.Substring(0, dpath.LastIndexOf('/')+1);
XmlDocument xdoc = new XmlDocument();
try
{
xdoc.Load(dpath+"todo_data.xml");
}
catch (Exception)
{
XmlNode root = xdoc.CreateElement("log");
xdoc.AppendChild(root);
}
return xdoc.SelectSingleNode("/log");
}
in this code:
Determine Path to Storage Data File
dpath = dpath.Substring(dpath.IndexOf(":///")+4);
dpath = dpath.Substring(0, dpath.LastIndexOf('/'));
dpath = dpath.Substring(0, dpath.LastIndexOf('/')+1);
In case the Service
is invoked in Windows System directory, we must determine its real storage directory (this is bin subdirectory in Web application directory). Path is returned in URI format with protocol name. On this step, we assume that protocol returned by stored path is always file:///. Crop it from path using String.Substring
and String.IndexOf
methods.
In one of the next steps, we must implement detection and cropping of other protocols.
In the result, path string
become of the form [Drive:/Path to WEBApp root/bin/AssemblyName]. From that string
, we crop AssemblyName and /bin (see source). In the result, we have path in the form [Drive:/Path to WEBApp root/].
Maybe this is not the best way to do this, but it is working :). If you know a better method, post it in comments.
Loading Stored Data, or Creating New Empty Data Storage
XmlDocument xdoc = new XmlDocument();
try
{
xdoc.Load(dpath+"todo_data.xml");
}
catch (Exception)
{
XmlNode root = xdoc.CreateElement("log");
xdoc.AppendChild(root);
}
Why using try
...catch
block? XmlDocument.Load
method throws exception if file is not found or if it finds errors in XML file while parsing. In this step, we don't worry about corrupt stored file (simply return empty result).
Returning Storage Data to Client
return xdoc.SelectSingleNode("/log");
XML file that stores data, has this structure:
="1.0"
<log>
<message posted="<!-- when posted -->"></message>
................
................
<message posted="<!-- when posted -->"></message>
</log>
In result, to return messages to client, all we need is to find <log>
node and return it as result.
PostMessage Function
public void PostMessage(string message)
{
string dpath = this.GetType().Assembly.CodeBase;
dpath = dpath.Substring(dpath.IndexOf(":///")+4);
dpath = dpath.Substring(0, dpath.LastIndexOf('/'));
dpath = dpath.Substring(0, dpath.LastIndexOf('/')+1);
XmlDocument xdoc = new XmlDocument();
try
{
xdoc.Load(dpath+"todo_data.xml");
}
catch (Exception)
{
xdoc.AppendChild(xdoc.CreateElement("log"));
}
XmlNode root = xdoc.SelectSingleNode("/log");
XmlNode msg = xdoc.CreateElement("message");
XmlAttribute msg_attr = xdoc.CreateAttribute("posted");
msg_attr.InnerText = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
msg.Attributes.Append(msg_attr);
msg.InnerText = message;
root.AppendChild(msg);
xdoc.Save(dpath+"todo_data.xml");
}
in this code:
Determining storage path, loading to create data storage and finding root node for message making as in the previous function.
Creating New 'Message' Element and Saving Data
XmlAttribute msg_attr = xdoc.CreateAttribute("posted");
msg_attr.InnerText = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
msg.Attributes.Append(msg_attr);
msg.InnerText = message;
root.AppendChild(msg);
xdoc.Save(dpath+"todo_data.xml");
I think, this part of code doesn't need comments. (If needed, see articles about working with XML data in .NET and C#.)
Install and Use Code
This is all for this step. In the result, we have a simple WebService
that allows us to store and retrieve messages.
To test this code, download the demo project and build it.
Copy code library for Web application located in web_bin directory to bin directory of your Web application. Create log.asmx file in root folder of your Web application. log.asmx file must contain only this row:
<%@ WebService language="C#" class="devel.Log" %>
In the next part, I will discuss how to make a simple client for this service, in C#.