Web-MA ASP.Net MWPSK Sitemap Editor

Current Version: 1.0.6 relesead on 10/27/2008

Some code changes for the SiteMap editor and related file to let your MWPSK copy support additional information

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

The GNU license to which this software is referering is available at: http://www.gnu.org/licenses/gpl.txt

If you want, you can add a link to www.web-ma.com, making it easy for others to recognize the author of this script.

Why these changes?

Finding an open source CMS (hence free) written in C # 2.0, which doesn't need a database to store data, it's not an easy task. On ASP.Net platform, there aren't so much solutions like per Linux platform, and Microsoft guy did a great job when they released the MWPSK also continuing the development with the 1.2.x version.

To be honest these changes I'm proposing was originally written for the previous version, but today, with a bit of spare time I've decided to download the new version and see the changes replacing the current CMS version with the new one. This also meant integrate all the changes I wrote into the other version.

Migration process is always a nightmare, but at least it gave me the possibility to focus on the changes I did and write down this page also for my personal future reference.

But what are the changes I'm talking about?

Well guys, you know, everybody of us comes with differents necessity, and code programming is a leverage to accomplish these tasks.
In my case I felt the necessity to extend the Sitemap editor with a bunch of characteristics to improve the following:

  • The date of last change xml tag; this is usefull if you attach an automatic sitemap.xml generator to give out the correct date when you doc has been changed last time;
  • A RootLevel xml tag, to know from the root at which level the current node is nested; this is usefull since I do an extensive use of css and throught a modified CSS Friendly adapter I actually display my menu with different color according to the level it belongs.
  • A Parent xml tag; this contain the guid of the level to which the current one is nested. Basically it's used for the same purpose of RootLevel;
  • A ContentFrom xml tag; this addition comes from a customer exigency. He requested me to have the same page displayed twice or more (virtual page), with different name. Due to the logic of MWPSK, the best solution I've found is to create a new page with the internal reference of the original Guid and during page load show the content from the original page accordingly to GUID contained into this tag. This tag is supposed to be very usefull in a SEO scenario.

To support the changes above, it has been necessary also to make changes to additional file like WebPage.cs, Persistable.cs, and ~/Administration/Navigation.aspx and .cs and ~/Default.aspx.cs.
The changes in the files just announced are minimal; the biggest work has been done in SiteMapEditor.cs in the App_Code folder.

Why implement thess changes?

Well you aren't obliged to use this; furthermore you don't need at all, expecially if you (or your customer) don't need your to have the above characteristics.

How to implement these changes?

To implement this code, you need a bit of knowledge of ASP.Net programming. Code has been written and tested for version 1.1.4, but as I said porting it to the new release - 1.2.x - it seems working fine.
A the end of the page, I will provide a zip file with all the changes described here, but since the base code may changes without notice, it would be possible in a (near) future that the zip file won't be valid.

Unfortunately these changes weren't applyable as a section, providing in this way an easy install way, so turn up your shirt sleeve and start opening your Visual Studio!

  1. Firt changes we will do is to add a bunch of code to the SiteMapEditor.cs located into the ~/AppCode/ folder. At the end of that file, add the following:

    #region Additional method from Web-MA
    private int GetRootLevel(string pageId)
     XPathNavigator x = _sitemap.CreateNavigator();

     if (x != null)
      return Convert.ToInt32((double)x.Evaluate(string.Format("count(//sm:siteMapNode[@pageId='{0}']/ancestor::*)", pageId), _nsManager)) - 1;

     return 0;

    private void SetAttribute(XmlNode node, string attributeName, object value)
     XmlAttribute attribute = node.Attributes[attributeName];

     if (attribute == null)
      goto start;

     attribute.Value = value.ToString();

    private void DeleteAttribute(XmlNode node, string attributeName)
     XmlAttribute attribute = node.Attributes[attributeName];

     if (attribute != null)

    public void UpdateLastWriteTime(string page)
     XmlNode node = FindNodeByVirtualPath(page);
      if (node != null)
      XmlAttribute attribute = node.Attributes["visible"];

      if (attribute != null)
       SetAttribute(node, "lastmod", CurrentFormattedDate());

    private void UpdateLastWriteTime(XmlNode node)
      SetAttribute(node, "lastmod", CurrentFormattedDate());

    private string CurrentFormattedDate()
     DateTime dt = DateTime.Now;
     String format = "mm/dd/yyyy hh:mm:ss";

     return dt.ToString(format, DateTimeFormatInfo.InvariantInfo);

    private XmlNode FindNodeByPageId(string pageId)
     return _sitemap.SelectSingleNode(string.Format("//sm:siteMapNode[@pageId='{0}']", pageId), _nsManager);

    public string GetAttributeValue(string pageId, string attributeName)
     if (string.IsNullOrEmpty(pageId) || string.IsNullOrEmpty(attributeName))
      return "";

      XmlNode node = FindNodeByPageId(pageId);

      return node != null ? node.Attributes[attributeName].Value : "";
    #endregion Additional method from Web-MA

    Now, continues to change the current file, modifying the MoveLevelUp and MoveLevelDown methods. Add in both files the following code into the condition's brackets:

    int level = GetRootLevel(pageId);

    SetAttribute(node, "RootLevel", level);
    SetParentNode(node, pageId, level, true);

    Move to the methos InserPages. At the end of this method thers is a condition that start with a comment: "add the new node ...". We need to add some lines. I report all the code piece and make bold the changes in this case.

    //add the new node right after the selected page
    if (previousPageId != string.Empty)
     XmlNode previousNode = _sitemap.DocumentElement.SelectSingleNode(string.Format("//sm:siteMapNode[@pageId='{0}']", previousPageId), _nsManager);
     if (previousNode != null)
      previousNode.ParentNode.InsertAfter(node, previousNode);

     int level = GetRootLevel(previousPageId);
     SetAttribute(node, "RootLevel", level);
     SetParentNode(node, previousPageId, level, false);
     SetAttribute(node, "RootLevel", 0);

  2. Change the ~/App_Code/WebPage.cs code and add the following code into the class WebPage:

    // Changes
    /// <summary>
    /// The page id from which this page gets content.
    /// </summary>
    public string ContentFrom
     get { return _data.ContentFrom; }
     set { _data.ContentFrom = value; }

    and this code, on the same file into the class WebPageData

    public string ContentFrom;

  3. Change the ~/Administration/Persistable.cs file.Look for SaveData method and add this code at the end:

    #region Changes made by Web-MA
    // Changes to support lastMod change date
    SiteMapNode smn = SiteMap.Provider.CurrentNode;

    if (smn != null)
     SitemapEditor editor = new SitemapEditor();
     string path = smn.Url;

     //Trim the path
     if (path.Contains("?"))
      path = path.Remove(path.IndexOf('?'));

     editor.UpdateLastWriteTime(VirtualPathUtility.ToAppRelative(path, "/"));
    #endregion Changes made by Web-MA

  4. Finally the last change. Modify the ~/Administration/Navigation.aspx page.
    Look for the new page button and just below add the following:

    <asp:Button runat="server" ID="btnCopyPage" OnClick="btnCopyPage_Click" Text="Copy" />

    Find a place suitable for you and add the following:

     <td class="fieldlabel">
      <asp:Localize ID="Localize5" runat="server" Text="<%$ Resources:stringsRes, adm_Navigation_VirtualPage%>" /><
    <td class="field">
     <asp:TextBox runat="server" ID="txtVirtual" Enabled="false" /></td>
    Now move into the code behind file and add the following method wherever you want:

    protected void btnCopyPage_Click(object sender, EventArgs e)
     if (lstPages.SelectedIndex < 0)
     string PageId = lstPages.SelectedValue;
     WebPage originalPage = new WebPage(PageId);
     WebPage page = new WebPage();
     page.NavigationName += originalPage.NavigationName + " - Copy";
     page.VirtualPath += VirtualPathUtility.GetFileName(originalPage.VirtualPath) + "Copy";
     page.ContentFrom = originalPage.PageId;
     page.AllowAnonymousAccess = true;
     page.Visible = true;
     SitemapEditor editor = new SitemapEditor();
     editor.InsertPage(page, PageId);
     Response.Redirect(string.Format("{0}?sel={1}", Request.Url.AbsolutePath, page.PageId));

    Add the following code into the lstPages_SelectIndexChanged method:

    #region Changes by Web-MA
    if (lstPages.SelectedIndex > -1)
     btnCopyPage.Visible = true;

     btnCopyPage.Visible = false;  #endregion Changes by Web-MA

    and this statement into the Load method:

    if (IsPostBack || Request.QueryString["sel"] != null)
     btnCopyPage.Visible = true;

     btnCopyPage.Visible = false;

    Add the following code line into the ShowPageDetails method to show the virtual page url from which a page is inheriting:

    txtVirtual.Text = editor.GetAttributeValue(page.ContentFrom, "url");

  5. Do the following changes into the ~/Default.aspx.cs to definitely support the Copy mode. Add a variable declaration on the top of the class:

    private bool isInheriting;
    Change the Page_Init method adding the following declaration immediately after the _page variable has been valorized:

    // 2008-03-05 If ContentFrom page attribute exist this mean that I need to load content
    // from the specified page.
    if (!string.IsNullOrEmpty(_page.ContentFrom))
     isInheriting = true;
     _page = new WebPage(_page.ContentFrom);

    Move down until you will find the if (User.Identity.IsAuthenticated && PowerUserExt.HasEditRights(Page.User, _page)) occurence and change it to:

    if (!isInheriting && User.Identity.IsAuthenticated && PowerUserExt.HasEditRights(Page.User, _page))

  6. Modify your ~/App_GlobalResources/StringRes.resx file adding an item form the label adm_Navigation_VirtualPage that will be used by the Navigation.aspx page.

  7. You have finished! Saves all the changes made and run your code. Starting from now you are able to have your MWPSK with a sitemap file containing the last date of change of the doc, a RootLevel, the Parent ID and a contentFrom tag for virtual pages.


For your best convenience I've provided a zip file with the MWPSK SiteMapEditor Extension changes made on the original 1.1.4 version. You can use it without problem, but don't forget to backup your original file, in case you want to revert back or if you did changes that aren't in the version available in CodePlex code.

Back to Open Source Utility list or take a look at the Embed Resource section or Static Content section.


1.0.0 - 2008-01-21 - First release made to support Parent Attribute;
1.0.1 - 2008-03-05 - Supporting ContentFrom Attribute;
1.0.2 - 2008-03-31 - Supporting lastmod Attribute for sitemap.xml generation;
1.0.3 - 2008-08-20 - Fix - The copy button didn't correctly displayed after a page was saved;
1.0.4 - 2008-08-21 - Added a textbox field into Navigation.aspx page that is filled up when the node selected is a virtual page. Textbox display the original path from which the content comes from. Added two additional methods into the SiteMapEditor to support this functionality.
1.0.5 - 2008-09-06 - Fix - A small fix about the lastmod field and how to return current DateTime. Not it uses the new method CurrentFormattedDate(); 1.0.6 - 2008-10-27 - Fix - A small fix about the lastmod field and default.aspx page. In case of default.apx page, with no virtual path, when adding a new section to the page the virtualUtility method was unable to resolve the path and so an exception was raised.;

Copyright © 2008 http://www.web-ma.com. All rights reserved.