Introduction
Wouldn't it be nice to write what you want and have it be available right away on the intranet for your pals? There are solutions, yes, but what about just two ASPX pages and a few tricks on writing your information in XML which to the end is just text, right?
What I did:
- I looked at XML blocks as nodes reflecting hierarchies, node attributes having node related information
- I found simple recursive code to translate XML nodes into tree nodes
- I created the XmlReadOnlyView.aspx page which is Explorer like with a tree control on the left, table on the right
- For each XML node, the tree will capture the "
display
" attribute and the rest of the attributes for the selected node will be displayed in the table on the right - Established the following rules (if you think of XML as nodes expressing hierarchies and attributes qualifying the parent node, the rules are natural):
- Supported XML-s will have a
MainTree
node, child of the root node - Each XML node child of
MainTree
will have a mandatory display
attribute - Remaining attributes for the selected tree node will be displayed name, value in the table on the right of the screen
- Added root child
IgnoreAttributeList
to be able to filter out attributes you simply want to ignore in the table on the right (one being "display
")
The simplest way to try the sample is to unzip the source, use File -> Open -> Web Site... menu path in Visual Studio 2010 and after loading, run Debug. Inspect, alter the XML, look at the code... To deploy, you need to add an application with IIS Manager, either you know how to or you can Google...
The code in this presentation is deliberately stripped down to the backbone. It was not intended to be production quality, the scope was to keep focus on what needs to happen. These being said, all you have to do for sharing information is to edit some XML files to create nodes and attributes at your will, just respecting the conventions above. Myself, I went deeper. In my elaborated solution, the XMLs are created from different applications - not only logs - and I can later update the XMLs through both application and web (adding/removing nodes and attributes without breaking the application). I added an Updates
node in the XML file root children collection where I keep track of each intranet user update on the XML file, either through application or the browser. And sure, I have more than one deployment for the browser access, each dedicated (one for logs, many for different config files for my employer's applications). Think of this as an initial information portal created by somebody deep into the problem then, through web, many consumers able to do punctual updates. But that will be part two if people will find this article worth of 'To be continued ...'
Code
Default.aspx.cs has nothing fancy, enumerates the XML files in the current directory and loads the table, one row per file. The button handlers just redirect the request to the page xmlReadOnlyViewer.aspx:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
List<string> files = Directory.EnumerateFiles(
Path.GetDirectoryName(Request.PhysicalPath), "*.xml",
SearchOption.TopDirectoryOnly).ToList();
foreach (string f in files)
{
try
{
XDocument xDoc = XDocument.Load(f);
XElement xmain = xDoc.Root.Element("MainTree");
if (xmain != null)
{
TableRow tr = new TableRow();
TableCell tcb = new TableCell();
tcb.BorderWidth = 1;
Button btn = new Button();
btn.CommandArgument = Path.GetFileName(f);
btn.Text = "Open";
btn.Click += new EventHandler(btn_Click);
tcb.Controls.Add(btn);
TableCell tcDesc = new TableCell();
tcDesc.Text = xmain.Attribute("display").Value;
tcDesc.BorderWidth = 1;
tcDesc.Wrap = true;
TableCell tcName = new TableCell();
tcName.Text = f;
tcName.BorderWidth = 1;
TableFiles.Rows.Add(tr);
tr.Cells.Add(tcb);
tr.Cells.Add(tcDesc);
tr.Cells.Add(tcName);
}
}
catch (Exception ex)
{
Trace.Write(ex.Message);
}
}
if (TableFiles.Rows.Count == 0)
{
TableRow tr = new TableRow();
TableCell tc = new TableCell();
tc.Text = "No suitable xml found.";
tc.ColumnSpan = 3;
tc.HorizontalAlign = HorizontalAlign.Center;
tc.VerticalAlign = VerticalAlign.Middle;
TableFiles.Rows.Add(tr);
return;
}
}
void btn_Click(object sender, EventArgs e)
{
Response.Redirect("XmlViewer.aspx?file="+((Button)sender).CommandArgument);
}
}
XmlReadOnlyViewer.aspx.cs - The selected XML file is displayed in the tree control on the left. The attributes for the selected node in the tree are displayed in the table on the right.
Page_load Event
protected void Page_Load(object sender, EventArgs e)
{
string selNodeStr = "";
if (Session["selected_tree_node"] != null)
selNodeStr = Session["selected_tree_node"].ToString();
loadTree(selNodeStr);
updateRightPannel();
}
loadTree
method - note the call to the recursive method building the tree node and the recursive meted to highlight the selected node.
private void loadTree(string selectedNode)
{
string file = Request["file"];
if (file == null || file.Length == 0)
return;
string fileFullPath = Path.GetDirectoryName(Request.PhysicalPath) + "\\" + file;
XDocument xDoc = XDocument.Load(fileFullPath);
if (Session["selected_tree_node"] == null)
Session["selected_tree_node"] = xDoc.Root.Element("MainTree").Attribute("display").Value;
TreeNode root = new TreeNode(xDoc.Root.Element("MainTree").Attribute("display").Value);
XElement xMainTree = xDoc.Root.Element("MainTree");
buildNodes(root, xMainTree);
treeViewlayout.Nodes.Clear();
treeViewlayout.Nodes.Add(root);
treeViewlayout.ExpandAll();
if (selectedNode.Length > 0)
{
selectNode(root, selectedNode);
}
else
{
root.Select();
}
}
buildNodes
method - Builds the tree view recursively using the display
attribute value for node text.
private void buildNodes(TreeNode treeNode, XElement element)
{
foreach (XElement child in element.Elements())
{
if (child.Name.LocalName == "Link")
continue;
TreeNode childTreeNode = new TreeNode(child.Attribute("display").Value);
childTreeNode.SelectAction = TreeNodeSelectAction.SelectExpand;
treeNode.ChildNodes.Add(childTreeNode);
buildNodes(childTreeNode, child);
}
}
selectNode
method - Recursive, highlights the node with focus.
private bool selectNode(TreeNode treeNode, string nodeText)
{
if (treeNode.Text == nodeText)
{
treeNode.Select();
return true;
}
foreach (TreeNode child in treeNode.ChildNodes)
{
if (selectNode(child, nodeText))
return true;
}
return false;
}
updateRightPannel
method - Displays the selected XML node attributes in the table on the right side of the page.
private void updateRightPannel()
{
try
{
if (treeViewlayout.SelectedNode == null)
return;
string selectedNodeDisplayString = (Session["selected_tree_node"] != null) ?
Session["selected_tree_node"].ToString() : "";
string file = Request["file"];
if (file == null || file.Length == 0)
return;
string fileFullPath = Path.GetDirectoryName(Request.PhysicalPath) + "\\" + file;
if (selectedNodeDisplayString != null && selectedNodeDisplayString.Length > 0)
{
XDocument xDoc = XDocument.Load(fileFullPath);
XElement xSelNode = xDoc.Root.Element("MainTree"); ;
string display = selectedNodeDisplayString;
var xNodes = from xNode in xDoc.Root.Element("MainTree").Descendants()
where xNode.Attribute("display").Value == display
select xNode;
if (xNodes.Count() > 0)
{
xSelNode = xNodes.First();
}
var ignoreAttributeList = from xAttNode in xDoc.Root.Element
("IgnoreAttributeList").Elements("Attribute")
select xAttNode.Attribute("name").Value;
var attrs = from xAtr in xSelNode.Attributes()
where !ignoreAttributeList.Contains(xAtr.Name.LocalName)
select xAtr;
tableAttributes.Rows.Clear();
if (attrs.Count() > 0)
{
foreach (XAttribute xAttr in attrs)
{
TableRow tr = new TableRow();
TableCell tcc = new TableCell();
tcc.Text = xAttr.Name.LocalName;
tcc.BorderWidth = 1;
tr.Cells.Add(tcc);
TableCell tcv = new TableCell();
tcv.Text = xAttr.Value;
tcv.BorderWidth = 1;
tr.Cells.Add(tcv);
tableAttributes.Rows.Add(tr);
}
}
}
}
catch (Exception ex)
{
Trace.Write(ex.Message);
}
}
treeViewlayout_SelectedNodeChanged
method - the new selection is passed through the Session
variable.
protected void treeViewlayout_SelectedNodeChanged(object sender, EventArgs e)
{
if (treeViewlayout.SelectedNode != null)
{
Session["selected_tree_node"] = treeViewlayout.SelectedNode.Text;
updateRightPannel();
}
}
XmlLog.cs - Sample class that would offer an interface for building XML logs compatible with the display. Use with caution, the class was not intended to be used in a multithreaded environment, it is just a sample. Through the interface, log nodes are added to the MainTree
node. However, XmlDoc
is publicly accessible, meaning you can build whatever hierarchy you want. The node Captions
is part of the extended functionality I mentioned. I preferred to leave it in the code to not need to change this class for the follow-up article.
public static class XmlLog
{
public static XDocument XmlDoc
{
get;
}
public static string FileName
{
get;
}
public static void CreateXmlDoc(string root)
{
XmlDoc = new XDocument();
XElement xRoot = new XElement(root);
DateTime currentDate = DateTime.Now;
XElement xMain = new XElement("MainTree");
xMain.Add(new XAttribute("display", root + " Log " +
currentDate.ToShortDateString() + " " + currentDate.ToShortTimeString()));
xRoot.Add(xMain);
xMain.Add(new XAttribute("start", string.Format("{0}:{1}:{2}:{3}",
currentDate.Hour, currentDate.Minute, currentDate.Second, currentDate.Millisecond)));
XmlDoc.Add(xRoot);
FileName = string.Format("{0}_{1}_{2}_{3}_{4}_{5}_{6}_{7}.xml",
xRoot.Name,
currentDate.Year,
currentDate.Month,
currentDate.Day,
currentDate.Hour,
currentDate.Minute,
currentDate.Second,
currentDate.Millisecond);
xRoot.Add(new XElement("Captions"));
XElement xI = new XElement("IgnoreAttributeList");
XElement xa = new XElement("Attribute");
xa.Add(new XAttribute("name", "display"));
xI.Add(xa);
xRoot.Add(xI);
}
public static void LogInfo(string display, string description)
{
XElement xL = new XElement("Log");
xL.Add(new XAttribute("display", display));
xL.Add(new XAttribute("description", description));
xL.Add("date_time", string.Format("{0} {1}",
DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()));
XmlDoc.Root.Element("MainTree").Add(xL);
}
public static void Save()
{
using (Stream logFile = File.Create(FileName))
{
using (XmlTextWriter tr = new XmlTextWriter(logFile, Encoding.Unicode))
{
DateTime now = DateTime.Now;
XmlLog.XmlDoc.Root.Element("MainTree").Add(new XAttribute(
"end", string.Format("{0}:{1}:{2}:{3}",
now.Hour, now.Minute, now.Second, now.Millisecond)));
tr.Formatting = Formatting.Indented;
XmlDoc.WriteTo(tr);
}
}
}
public static void LogException(Exception ex, string function)
{
XElement xex = new XElement("Exception");
xex.Add(new XAttribute ("display","Exception"));
xex.Add(new XAttribute("function", function));
xex.Add(new XAttribute("message", ex.Message));
xex.Add(new XAttribute("callstack", ex.StackTrace));
if (ex.InnerException != null)
{
XElement ixex = new XElement("InnerException");
ixex.Add(new XAttribute ("display","InnerException"));
ixex.Add("message", ex.InnerException.Message);
xex.Add(ixex);
}
XmlDoc.Root.Element("MainTree").Add(xex);
}
}
XML file sample:
="1.0"="utf-8"
<NightlyDataCheckLog>
<MainTree display="Nightly data check for 08/16/2012 3:30 AM"
start="08/16/2012 3:30 AM" result="OK" duration="200ms">
<Log display="data inspection start"
description="Inspection started at 08/12/2012 03:00:15:455"/>
<Log display="Inspection Resultt"
description="All inspected points are valid,
end time 08/12/2012 03:00:25:777"/>
</MainTree>
<IgnoreAttributeList>
<Attribute name="display"/>
</IgnoreAttributeList>
</NightlyDataCheckLog>
Background
The article is aimed at C# programmers having collateral contact with ASP.NET, but being familiar with LINQ to the extent the syntax is not a mystery.
Using the Code
Download the code, extract into a directory of your choice, then start Visual Studio 2010 and use the 'Open Existing Web' menu path. You can run as it is or you may review the references - be sure you have .NET 4.0 which does not come with Visual Studio 2010, you need to install it. The code should work with 3.5, I did not bother to check.
Points of Interest
I went into this playing with LINQ, trying to do ASP.NET without the auto generated code (that is part two), and exploiting XML as info storage and info handling. The log came into attention because I had some data content utilities running once a week and I needed a way to expose the XML logs through intranet for our customer support web interface.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.