
在WebForms中,大家应该都体会过SiteMapPath给开发带来的便利,而今格式各样的导航栏、导航菜单已经成了网站不可缺少的一部分,接下去大家会看到一个在MVC下使用的,并且符合MVC设计规范的导航栏“插件”,以在MVC中取代之前SiteMapPath的应用。
首先我们还是明确一下这个插件的意义和需要完成的基本功能:
问:既然有SiteMapPath,为什么还要重复开发一个同样功能的导航栏?
答:没错,SiteMapPath服务器控件在MVC(以下无特别说明都专指ASP.NET MVC)中仍然可以很好地“显示”,但是显然无法很好满足C-V结构的分离,SiteMapPath控件依赖于aspx页面,而在MVC中,早在aspx页面执行之前,几乎所有数据都应该在Controller处理完成,“打包”给ViewData。这就要求这个控件能够同时在Controller和View中被很好地控制,并且View主要只起到显示的作用。还有一点就是SiteMapPath默认的sitemap格式已经无法满足MVC中http请求的规则,使得无法很好地进行控制。
问:新开发的导航栏有哪些功能?
答:
1、完全兼容原有WebForms项目下的Web.sitemap文件格式,即当网站中同时存在MVC和WebForms项目时, 可以共享sitemap文件。但须按照MVC的执行方式对原有文件稍加补充。
2、自动从Web.sitemap获得当前页面(Controller,Action)对应的网站地图位置,自动生成导航条, 使用时不需要编写任何代码。
3、根据MVC的Controller-Action规则自动创建对应链接,也可自由设置,包括只显示文字,不使用连接等。
4、可以完全或部分手动设置、增减节点。
5、可以限制节点显示层数。
以上的大部分功能都是在SiteMapPath可实现的,但是我们已经不再需要PostBack功能的设置。
遵照这些前提,我给大家展示一下我的实现方法:
一、建立全局共享的Model层的NavigationInfo,包含在Models/ExtentionEntity.cs中
public class NavigationInfo


{


public string Title
{ set; get; }


public string ActionName
{ set; get; }


public string ControllerName
{ set; get; }


public object Values
{ set; get; }

public void SetNavInfo(string title, string actionName, string controllerName, object values)


{

Title = title;

ActionName = actionName;

ControllerName = controllerName;

Values = values;

}

public NavigationInfo()


{ }

public NavigationInfo(string title, string actionName, string controllerName, object values)


{

this.SetNavInfo(title, actionName, controllerName, values);

}

public NavigationInfo(string title, string actionName, string controllerName)


{

this.SetNavInfo(title, actionName, controllerName, null);

}

public NavigationInfo(string title, string actionName, object values)


{

this.SetNavInfo(title, actionName, null, values);

}

public NavigationInfo(string title)


{

this.SetNavInfo(title, null, null, null);

}

}


其中,Title、ActionName、ControllerName、Values分别对应View页面ActionLink需要的链接文字、action、controller和values。里面也提供了对NavigationInfo的4种重写的方法,以便和ActionLink的参数重写尽量配套,在适应使用习惯的同时也提供了更大的灵活性。
二、创建BaseViewData,所有继承了这个类的ViewData都将获得NavigationInfo属性。Models/BaseViewData.cs
public class BaseViewData


{


public string Title
{ get; set; }//这个Title现在这儿埋个伏笔,我会在下文中说明,和导航拦没有太直接关系


public List<ExtentionEntity.NavigationInfo> NavInfo
{ get; set; }

}

三、创建Views/Shared/Navigation.ascx,供页面调用。
注:这里我使用了用户控件的形式是因为考虑到开发时实际调用导航栏的页面不会太多(一般都只在母板页),如果你觉引用的比较多,这样不太方便,也可以加到HtmlHelper中,其中要执行的代码是一样的:

<%
@ Control Language="C#" AutoEventWireup="true" CodeBehind="Navigation.ascx.cs"

Inherits="MVCTools.Views.Shared.Navigation" %>

<div id="Navigation" class="Navigation">


<%

if (ViewData.ContainsDataItem("navInfo")) {

System.Collections.Generic.List<MVCTools.Models.ExtentionEntity.NavigationInfo> navInfo = new System.Collections.Generic.List<MVCTools.Models.ExtentionEntity.NavigationInfo>();

if (ViewData["navInfo"] == null || ((System.Collections.Generic.List<MVCTools.Models.ExtentionEntity.NavigationInfo>)ViewData["navInfo"]).Count == 0)

{

navInfo = MVCTools.Common.Navigation.GetAutoNavigationInfo();

}

else

{

navInfo = (System.Collections.Generic.List<MVCTools.Models.ExtentionEntity.NavigationInfo>)ViewData["navInfo"];

}


%>您的位置:<%

int i = 0;

foreach (MVCTools.Models.ExtentionEntity.NavigationInfo nav in navInfo)

{

if (++i > 1)


{%> ><%
--//TODO:间隔标记可以扩展为用户自定义--%> <%}%>


<%
if (nav.Values != null)

{

if (!string.IsNullOrEmpty(nav.ActionName))//if (nav.Values.ToAttributeList().Contains("controller"))


{%><%= Html.ActionLink(nav.Title,nav.ActionName, nav.Values)%><%
}

else


{%><%= nav.Title%><%
}

}

else if (!string.IsNullOrEmpty(nav.ActionName))


{%><%= Html.ActionLink(nav.Title, nav.ActionName, nav.ControllerName)%><%
}

else


{ %><%= nav.Title%><%
}

} %>


<%
} %>

</div>


在aspx中,我们只需要这样引用就行了:
<!-- 导航条 -->

<%= Html.RenderUserControl("/Views/Shared/Navigation.ascx")%>

四、准备工作基本做好了,下面来看一下在Controller中如何对导航栏灵活控制。
在此之前,我需要在Common中建了一个专门负责处理NavigationInfo的类Common/Navigation.cs:

Code
1
public static class Navigation
2
3
{
4
5
/**//// <summary>
6
7
/// 自动获取所有节点
8
9
/// </summary>
10
11
/// <returns></returns>
12
13
public static List<ExtentionEntity.NavigationInfo> GetAutoNavigationInfo()
14
15
{
16
17
return GetAutoNavigationInfo(999);//有多少层都取下来
18
19
}
20
21
/**//// <summary>
22
23
/// 指定获取layer层节点
24
25
/// </summary>
26
27
/// <param name="layer">从1层开始计,非0。这么做是为了区别List数据操作和对Nav数据操作</param>
28
29
/// <returns></returns>
30
31
public static List<ExtentionEntity.NavigationInfo> GetAutoNavigationInfo(int layer)
32
33
{
34
35
List<ExtentionEntity.NavigationInfo> navInfo = new List<ExtentionEntity.NavigationInfo>();
36
37
//获取sitemap文件,TODO:如果有多个文件,这里可以改成对Web.config中SiteMapProvider设置的引用
38
39
XElement sitemapX = XElement.Load(HttpContext.Current.Server.MapPath("~/Web.sitemap"));
40
41
IEnumerable<XElement> sitemapNodes = sitemapX.Elements();
42
43
string action = "Index";//默认action为"Index"
44
45
string controller = "";
46
47
string pageUrl = System.Web.HttpContext.Current.Request.Url.PathAndQuery;
48
49
bool goNext = false;
50
51
int theLayer = 0;//层计数
52
53
do
54
55
{
56
57
goNext = false;
58
59
foreach (XElement node in sitemapNodes)
60
61
{
62
63
//string nodeUrl = node.Attribute("url").Value;
64
65
string sitemapUrl = ResolveUrl(node.Attribute("url").Value);
66
67
if (CheckUrlMatches(sitemapUrl, pageUrl))
68
69
{
70
71
if (node.Attributes().Count(a => a.Name == "controller") > 0)
72
73
{
74
75
controller = node.Attribute("controller").Value;//添加controller信息
76
77
}
78
79
if (node.Attributes().Count(a => a.Name == "action") > 0)
80
81
{
82
83
action = node.Attribute("action").Value;//添加action信息
84
85
}
86
87
string title = node.Attribute("title").Value;//获取title信息
88
89
navInfo.Add(new ExtentionEntity.NavigationInfo(title, action, new
{ controller = controller }));
90
91
sitemapNodes = sitemapNodes.Elements();
92
93
goNext = true;
94
95
break;
96
97
}
98
99
}
100
101
} while (goNext && ++theLayer < layer);
102
103
return navInfo;
104
105
}
106
107
/**//// <summary>
108
109
/// 插入导航条记录。
110
111
/// </summary>
112
113
/// <param name="navInfos">原始导航数据列表</param>
114
115
/// <param name="navInfo">需要插入的节点数据</param>
116
117
/// <param name="layer">
118
119
/// layer大于0:从根节点开始计,插入到该层之前(和原始Insert方法相同,但是从1开始记)。
120
121
/// layer小于0:从最后一层开始记,插入到该层之前。
122
123
/// layer等于0:插入到最后(和原始Add方法等效)。
124
125
/// 如果越界,则自动调整为可以取到的最近阀值
126
127