Click here to Skip to main content
11,705,763 members (58,044 online)
Click here to Skip to main content

HtmBuilder

, 30 Jun 2003 CPOL 50.1K 1.2K 45
Rate this:
Please Sign up or sign in to vote.
An article on the idea of bulding html pages from templates with .Net windows applications

Introduction

Every Web site seems to have a large amount of static material. This is usually communication material from business to customer and/or potential customers. There are sometimes legitimate arguments to have the pages in .asp/.aspx and query a data source depending on the user’s interaction. These information pages can get complicated in the process of attempting to amalgamate the sporadic material and the on-the-fly approach of collecting and caching information sometimes does not work.

My proposal is to collect the information from a series of databases, xml files, or documents (doc, pdfs). Then build html pages from a .NET windows application that queries these data sources and pushes the information into a template. This is more of a reporting approach for content that can be classified as non interactive and static. The application amalgamating the content of the page and building it has nothing to do with the web server. It is a nice thought to leveraging .NET, SQL Server, and even word documents to build pages for an Apache web server.

The Advantages are:

  1. Internal or external search engines can crawl and index the pages. If content is in a data source, google will not see it.
  2. Pages can be built for any platform. The template can be in htm, jsp, php, or whatever. If the web server is Microsoft the template can be aspx, htm, or asp -- asp for this demo.
  3. Many database driven pages seem to get the same information from the database every hit. Instead of fancy caching, just build the page and push the static page to a deployment area.
  4. Static pages have a simple security model.

Using the code

Introduction II

If we can agree that templating has a time and place, I would like to zoom in on 3 programmatic methodologies.

  1. Starting the application in different ways from different sources.
  2. Loading and reading XML docs as config files or data sources.
  3. Letting the dataset column names determine the place holders in the template -- where the data needs to be loaded.

Quick start: running the demo with a start up switch of "fullRun" will build 6 asp files in the articles directory. To put a switch in Visual Studios, right click the project and go: properties -> configuration Properties -> debugging -> start options -> command line argument. Or, no start up switch will load the form and then click "process articles" to do the same. When finished, make sure you click, "Show all files."

Starting the Application

The Windows C# .Net application has a UI mode and a non UI mode. The non UI mode can be set off from defined switches, “fullrun” for the demo. UI mode (triggered by no switches) loads a form and then the form loads the appconfig.config file.

A known switch starting the app will override the value of the startmode that is also defined in the config file. So any switch will start non UI mode, but the startmode will then be determined by the appconfig.config.

Here is the entry point:

static void Main(string[] args)
{
  appMain m;
  string s_mode = "";
  if (args.Length > 0)
    s_mode = args[0].ToString();
  m = new appMain();
  if (s_mode == m.cFULL_RUN){
    m.g_startOverRide = true;
    m.g_startMode = s_mode;
    m.runFromConfig();}
  else if (s_mode.Length >0){
    m.g_startOverRide = false;
    m.runFromConfig();}
  else{
    m = null;
    Application.Run(new UI());}
}

Upon running in UI mode, the form updates the config file through o_XML.saveDoc(); and then fires off a thread -- calling the same function non UI mode would to start the application. At this point, the app is the same for any mode except delegates communicate back to the form to update the number of files built and when finished. This type of architecture leaves the UI totally separate from functionality. This app can be run from a scheduled job, an intranet web page, or the form provided.

The father of this app actually only allows one instance to run because of this. Anyway here is the save method from the form (see UI.cs): The only thing that should be going on here is a save to the config file and starting the app -- o_app.runFromConfig

private void bSaveRun_Click(object sender, System.EventArgs e)
{
  m_DelegateThreadFinished = new DelegateThreadFinished(this.ThreadFinished);
  m_DelegateThreadGoing = new DelegateThreadGoing(this.ThreadGoing);

  //Updates vals to appconfig
  saveConfig();

  appMain o_app = new appMain(this);
  //** check to see if thread was finished
  Thread th_refresh = new Thread(new ThreadStart(o_app.runFromConfig));
  th_refresh.Name = "Thread";
  this.p_run.Visible=true;
  this.bSaveRun.Enabled = false;
  th_refresh.Start();
}

Whatever started it, the App is going

Once the application is going it needs to reload the appconfig.config to understand what to do. There are a set of predefined global variables that have the node name (from the config) as the value they should be loaded with. So the vars are simply reloaded with the corect vals -- not a feature, laziness: declared as private string g_pathXML = "pathToXmlData"; then, see below.

public void runFromConfig()
{
  //load defaults and set properties of this class
  quickXML o_XML = new quickXML();
  if(o_XML.openAdminXML(g_pathtoXML))
  {
    //is there a switch        
    if(!(g_startOverRide))
      g_startMode = o_XML.openNode("descendant::" + g_startMode);

    g_pathXML = o_XML.openNode("descendant::" + g_pathXML);
    g_pathTemplate = o_XML.openNode("descendant::" + g_pathTemplate);
    g_pathSave = o_XML.openNode("descendant::" + g_pathSave);
    o_XML = null;

    processFiles();
  }
  else
    allExceptions(o_XML.getException);
}

Getting the XML Config File and the XML Data Source

So an Xpath query is run to get the global vars loaded. The xml object comes with the app (see generic\quickXML.cs) and is made as simple as possible. Here is the o_XML.openNode function.

public string openNode(string s_xPath)
{
  XmlNode o_node = g_xml.SelectSingleNode(s_xPath);
  return o_node.InnerText.ToString();
}

I removed all the sql and the database queries and replaced the data source with a few products from the Northwind database that have been put in an XML file through a URL query – the query is in the config file for those interested.

The XML is put in a dataset and has 3 tables but is still a flat relationship because Tables[0].Rows[0][0] corresponds to Tables[1].Rows[0][0]. A one-to-many relation makes the dynamic template loading a little harder, but I have done that as well.

Below is the simple flat 3 loop example.

for (int i=0;i < g_recordsProcessed;i++)   //o_dataSet.Tables[0].Rows.Count
{  
  if(b_noError)
  {
    if(g_form != null)
      g_form.Invoke(g_form.m_DelegateThreadGoing);

      b_noError = loadToTemplate( ref o_dataSetKB, i);  
    
    ... then in the function loadToTemplate
    
    for (j=0; j< o_set.Tables.Count; j++)
    {
      for (i=0; i< o_set.Tables[j].Columns.Count; i++)
      {
        s_find = o_set.Tables[j].Columns[i].ColumnName.ToString();
                 
        s_find = "%" +  s_find + "%";
        s_final = o_set.Tables[j].Rows[i_row][i].ToString();

Notice above that the first loop simply sets up the correct row and the next loop loads the column name and the value that needs to be loaded in the template. So s_find is the place holder name and s_final is the value to be loaded in the place holder.

Loading the template

As long as the column names are the same as the place holder names (see %anyThing% in template\articletemplate.asp) everything will go well. The final part of this app is the most barbaric. If someone has better solutions for the good old find-and-replace, I would love to see it.

 s_final = s_final.Replace("\"",""");
      s_final = s_final.Replace("<","&lt;");
      s_final = s_final.Replace(">","&gt;");
      s_final = s_final.Replace("\r","<br>");

      if(s_final == "N/A")
        s_final = "";
      //s_newArt is the article template loaded in a string
      s_newArt = s_newArt.Replace(s_find, s_final);
    }
  }
  try
  {
    FileInfo fi = new FileInfo(@g_pathSave +  i_row + ".asp");
    StreamWriter sw =  fi.CreateText();
    sw.Write(s_newArt);
    sw.Close();
    fi = null;
    sw = null;
  }

And there is it. The template has been opened, copied to a string, rebuilt and saved to a new location. The template is called articletemplate.asp in the template folder. Again, The program flow of the demo is in appMain.cs and goes:main() -> runFromConfig() -> processFiles() -> loadToTemplate().

Points of Interest

I find this app extremely fast for what it does (file IO operations). It can build 10,000 pages in about 2 hours and the father of the demo does a lot more parsing and even Active link building.

I have downloaded so much code from this site I was starting to get an imbalance in programmatic Karma. I hope this brings a balance.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

pburns
Web Developer
Canada Canada
Biography in progress.

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150819.1 | Last Updated 30 Jun 2003
Article Copyright 2003 by pburns
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid