使用 Linq 创建动态 ASP.Net 网站导航菜单,并自动为您生成静态 Web.sitemap 文件。






4.75/5 (5投票s)
通过在所有网站中实施此代码,避免创建额外的冗余工作。
引言
使用 Linq 创建动态 ASP.Net 网站导航菜单,并自动为您生成静态 Web.sitemap 文件。
问题:当您编写 ASP.net 网站时,添加新内容页面的任务包括将导航信息添加到 web.sitmap xml 文件中。 此任务在一个网站上重复多次,并且可能变得复杂,因为 web.sitemap 文件不使用 IntelliSense,并且可能发生许多运行时错误。 通过在所有网站中实施此代码,避免创建额外的冗余工作。 我搜索了互联网,找到了这个难题的两个部分。 参考资料如下。
背景
为了提出这个例子,我从两个网站借用了代码
- https://codeproject.org.cn/Articles/12716/Navigation-using-dynamic-SiteMapPath-file
- http://www.dotnetcurry.com/showarticle.aspx?ID=281
使用代码
网站要求
- 必须有一个 Site.master 模板文件,所有内容页面都继承自该文件。
- Site.master 必须有一个 Menu 控件。
- Site.master 必须有一个 SiteMapDataSource。
- Site.master 必须有一个 ScriptManager。
- Site.master 应该有一个创建 Web.sitemap xml 文件的方法(如果它不存在)。 这使系统能够在创建动态 SiteMapProvider 的同时创建更新的 Web.sitmap xml 文件,以确保您的网站在没有 Web.sitemap xml 文件的情况下正常运行。 系统会为您创建一个文件,并动态引用它。
- 需要一个带有 SiteMapProvider 配置元素的 Web.config 文件。 此元素设置为 DefaultProvider,但可以切换为使用您将创建的 DynamicSiteMapProvider,以便您可以直接对其进行测试。 测试 DynamicSiteMapProvider 后,您可以删除该 provider 元素。
- 您的网站应该有组织良好的文件夹,其中包含您要包含在顶部菜单节点中的内容页面。 顶部菜单节点将是文件夹名称。
您添加到您的站点的内容
- 您将需要创建 DynamicSiteMapProvider 类。 可能需要针对您的特定站点进行一些调整。
- 您需要在 Site.master.cs 文件中添加并调用 Page_Init() 方法。
- 您需要将 SiteMapProvider 元素添加到 Web.config 文件。
- 您需要创建一个继承自 Site.master 的测试内容页面,以添加一个按钮和按钮事件,以便您可以实例化 DynamicSiteMapProvider 类的一个对象,并调用 RebuildSiteMap() 和 GenerateWebDotSiteMapXMLFile() 方法。 您需要 FindControl Site.master Menu 和 SiteMapDataSource 控件,以便您可以数据绑定菜单。 这将使系统立即使用更新的 SiteMapProvider 节点。
下载 zip 以获得更完整的实现。
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Security.Permissions;
/// <summary>
/// Summary description for DynamicSiteMapProvider
/// Creates a dynamic SiteMapProvider
/// Dynamically creates the Web.sitemap xml file
/// </summary>
public class DynamicSiteMapProvider : StaticSiteMapProvider
{
#region Variables
/// <summary>
/// Add or remove Excluded folders as needed
/// </summary>
private const string ExcludedFolders = "(App_Data)|(obj)|(Scripts)|(App_Code)|(bin)|(fonts)|(Content)";
/// <summary>
/// Add or remove Excluded files as needed.
/// Warning: Do not have but one default.aspx file in your root. All others should be excluded as to not have duplicates and throw a duplicate exception.
/// </summary>
private const string ExcludedFiles = "(Default.aspx)";
/// <summary>
/// Add or remove Excluded files by file extension.
/// Use this to exclude all files that are not aspx content pages.
/// </summary>
private const string ExcludedFilesByExtension = "(.cs)|(.exclude)|(.config)|(.master)|(master.cs)|(.rdlc)|(.ico)|(.ascx)|(.asax)|(.webinfo)|(.sitemap)";
private SiteMapNode parentNode = null;
#endregion END Variables
#region Accessors Mutators
/// <summary>
/// The parent node
/// Use the parent node to access the child nodes.
/// </summary>
public SiteMapNode ParentNode
{
get { return parentNode; }
set { parentNode = value; }
}
#endregion END Accessors Mutators
#region overridded methods
/// <summary>
/// Returns the ParentNode
/// Microsoft says: When you inherit from the SiteMapProvider class, you must override the following members: GetRootNodeCore, FindSiteMapNode, GetChildNodes, and GetParentNode. Apparently I didn't need to.
/// </summary>
/// <returns>SiteMapNode</returns>
protected override SiteMapNode GetRootNodeCore()
{
return BuildSiteMap();
}
/// <summary>
/// Builds the sitemap and saves it in cache
/// </summary>
/// <returns>SiteMapNode</returns>
public override SiteMapNode BuildSiteMap()
{
lock (this)
{
parentNode = HttpContext.Current.Cache["SiteMap"] as SiteMapNode;
if (parentNode == null)
{
base.Clear();
parentNode = new SiteMapNode(this,
HttpRuntime.AppDomainAppVirtualPath,
"Home");
// Make sure that you have a Default.aspx file in your root directory. This serves as your main Home page.
SiteMapNode fileNode = new SiteMapNode(this, HttpRuntime.AppDomainAppVirtualPath, HttpRuntime.AppDomainAppVirtualPath + "Default.aspx", "Home", "Home");
AddNode(fileNode, parentNode);
AddFiles(parentNode);
AddFolders(parentNode);
HttpContext.Current.Cache.Insert("SiteMap", parentNode);
}
return parentNode;
}
}
#endregion END Overridden Methods
#region Methods
/// <summary>
/// Rebuilds the SiteMap
/// Call this method when you want to update the sitemap.
/// You need to update the sitemap when you add a content page.
/// Clears the cache first
/// </summary>
public void RebuildSiteMap()
{
lock (this)
{
HttpContext.Current.Cache.Remove("SiteMap");
}
BuildSiteMap();
}
/// <summary>
/// Finds all the web site folders, parses out the folders you don't want included and creates top menu nodes under the parent node and child nodes under the top menu nodes.
/// </summary>
/// <param name="parentNode">SiteMapNode</param>
private void AddFolders(SiteMapNode parentNode)
{
var folders = from o in Directory.GetDirectories(HttpContext.Current.Server.MapPath(parentNode.Key))
let dir = new DirectoryInfo(o)
where !Regex.Match(dir.Name, ExcludedFolders).Success
select new
{
DirectoryName = dir.Name
};
foreach (var item in folders)
{
string folderUrl = parentNode.Key + item.DirectoryName;
SiteMapNode folderNode = new SiteMapNode(this,
folderUrl,
null,
item.DirectoryName,
item.DirectoryName);
// call base class method to add nodes
AddNode(folderNode, parentNode);
AddFiles(folderNode);
}
}
/// <summary>
/// Takes in a top menu node, parses out the files you do not want included then adds the children (content pages) as SiteMapNodes.
/// </summary>
/// <param name="folderNode">SiteMapNode</param>
private void AddFiles(SiteMapNode folderNode)
{
var files = from o in Directory.GetFiles(HttpContext.Current.Server.MapPath(folderNode.Key))
let fileName = new FileInfo(o)
where ((!Regex.Match(fileName.Name, ExcludedFiles).Success) && (!Regex.Match(fileName.Extension, ExcludedFilesByExtension).Success))
select new
{
FileName = fileName.Name
};
foreach (var item in files)
{
SiteMapNode fileNode = new SiteMapNode(this,
item.FileName,
folderNode.Key + "/" + item.FileName,
item.FileName.Replace(".aspx", ""));
AddNode(fileNode, folderNode);
}
}
#endregion END methods
#region Create Web.Sitemap file locally
/// <summary>
/// Guess what this method does? Here is an example of the use of the Microsoft's C# codeing naming standard.
/// </summary>
/// <param name="sFileName">string</param>
public void GenerateWebDotSiteMapXMLFile(string sFileName = "Web.sitemap")
{
SiteMapNodeCollection myCollection;
if (ParentNode == null)
{
ParentNode = GetRootNodeCore();
}
myCollection = ParentNode.ChildNodes;
//Create the Web.sitemap XML file
Encoding enc = Encoding.UTF8;
XmlTextWriter myXmlTextWriter = new XmlTextWriter(HttpRuntime.AppDomainAppPath + sFileName, enc);
myXmlTextWriter.WriteStartDocument();//xml document open
// Create the Top level (Parent element)
myXmlTextWriter.WriteStartElement("siteMap");
myXmlTextWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/AspNet/SiteMap-File-1.0");
// Create the first Node of the Menu
myXmlTextWriter.WriteStartElement("siteMapNode");
//Title attribute set
myXmlTextWriter.WriteAttributeString("title", "Home");
myXmlTextWriter.WriteAttributeString("description",
"This is home");//Description attribute set
myXmlTextWriter.WriteAttributeString("url",
"");//URL attribute set
//Loop and create the main Menu nodes that are represented by folders that were included
foreach (SiteMapNode node in ParentNode.ChildNodes)
{
myXmlTextWriter.WriteStartElement("siteMapNode");
myXmlTextWriter.WriteAttributeString("title",
node.Title);
myXmlTextWriter.WriteAttributeString("description",
node.Description);
myXmlTextWriter.WriteAttributeString("url",
node.Url);
//Loop and create child nodes for the Main Menu nodes
foreach (SiteMapNode childNode in node.ChildNodes)
{
myXmlTextWriter.WriteStartElement("siteMapNode");
myXmlTextWriter.WriteAttributeString("title",
childNode.Title);
myXmlTextWriter.WriteAttributeString("description",
childNode.Description);
myXmlTextWriter.WriteAttributeString("url",
childNode.Url);
myXmlTextWriter.WriteEndElement();//Close the first siteMapNode
}
myXmlTextWriter.WriteEndElement();//Close the siteMapNode
}
myXmlTextWriter.WriteEndDocument();//xml document closed
// clean up and release xml resources
myXmlTextWriter.Flush();
myXmlTextWriter.Close();
return;
}
#endregion END Create Web.Sitemap file locally
}
Site.Master.cs
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class SiteMaster : MasterPage
{
private const string AntiXsrfTokenKey = "__AntiXsrfToken";
private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
private string _antiXsrfTokenValue;
protected void Page_Init(object sender, EventArgs e)
{
// rem this code if you change the System.Web.XmlSiteMapProvider defaultProvider in the Web.config file to use the DynamicSiteMapProvider
if (!System.IO.File.Exists(Server.MapPath("~") + "Web.sitemap"))
{
DynamicSiteMapProvider myCustomSiteMap = new DynamicSiteMapProvider();
myCustomSiteMap.RebuildSiteMap();
myCustomSiteMap.GenerateWebDotSiteMapXMLFile();
}
// The code below helps to protect against XSRF attacks
var requestCookie = Request.Cookies[AntiXsrfTokenKey];
Guid requestCookieGuidValue;
if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
{
// Use the Anti-XSRF token from the cookie
_antiXsrfTokenValue = requestCookie.Value;
Page.ViewStateUserKey = _antiXsrfTokenValue;
}
else
{
// Generate a new Anti-XSRF token and save to the cookie
_antiXsrfTokenValue = Guid.NewGuid().ToString("N");
Page.ViewStateUserKey = _antiXsrfTokenValue;
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
HttpOnly = true,
Value = _antiXsrfTokenValue
};
if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
{
responseCookie.Secure = true;
}
Response.Cookies.Set(responseCookie);
}
Page.PreLoad += master_Page_PreLoad;
}
protected void master_Page_PreLoad(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Set Anti-XSRF token
ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
}
else
{
// Validate the Anti-XSRF token
if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
|| (string)ViewState[AntiXsrfUserNameKey] != (Context.User.Identity.Name ?? String.Empty))
{
throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Unnamed_LoggingOut(object sender, LoginCancelEventArgs e)
{
Context.GetOwinContext().Authentication.SignOut();
}
}
<!--Web.config--> <system.web> <siteMap defaultProvider="DefaultProvider"> <!--change the defaultProvider attribute to main to switch--> <providers> <clear /> <add name="DefaultProvider" type="System.Web.XmlSiteMapProvider" siteMapFile="Web.sitemap" /> <add name="DynamicSiteMapProvider" type="DynamicSiteMapProvider" siteMapFile="Web.sitemap" /> </providers> </siteMap>
关注点
我将这两个想法和一些来自参考资料的代码合并成一个非常有用的完整包。
如何测试和运行
- 您可以删除 Web.sitemap xml 文件。 系统将为您生成一个更新的 Web.sitemap 文件,并根据您将添加的 Site.master 中的 protected void Page_Init(object sender, eventArgs e) 方法使用它。 这可能是添加内容页面的最佳方法。 因此,每当您向文件夹添加内容页面时,只需删除 Web.sitemap xml 文件并运行该站点即可。
- 您可以向任何内容页面(管理页面?)添加一个按钮,该按钮直接调用 DynamicSiteMapProvider 类方法,它将更新现有的 Web.sitemap xml 文件。
- 删除或删除 Web.sitemap 文件,并在 Web.config 文件中设置 defaultProvider =" DynamicSiteMapProvider",以便不需要 Web.sitemap 文件,并且专门使用 DynamicSiteMapProvider 类。