打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
为ASP.NET MVC开发一些常用插件(一)——导航栏

WebForms中,大家应该都体会过SiteMapPath给开发带来的便利,而今格式各样的导航栏、导航菜单已经成了网站不可缺少的一部分,接下去大家会看到一个在MVC下使用的,并且符合MVC设计规范的导航栏“插件”,以在MVC中取代之前SiteMapPath的应用。

首先我们还是明确一下这个插件的意义和需要完成的基本功能:

问:既然有SiteMapPath,为什么还要重复开发一个同样功能的导航栏?

答:没错,SiteMapPath服务器控件在MVC(以下无特别说明都专指ASP.NET MVC)中仍然可以很好地“显示”,但是显然无法很好满足C-V结构的分离,SiteMapPath控件依赖于aspx页面,而在MVC中,早在aspx页面执行之前,几乎所有数据都应该在Controller处理完成,“打包”给ViewData这就要求这个控件能够同时在ControllerView中被很好地控制,并且View主要只起到显示的作用。还有一点就是SiteMapPath默认的sitemap格式已经无法满足MVChttp请求的规则,使得无法很好地进行控制。

问:新开发的导航栏有哪些功能?

答:

1、完全兼容原有WebForms项目下的Web.sitemap文件格式,即当网站中同时存在MVCWebForms项目时, 可以共享sitemap文件。但须按照MVC的执行方式对原有文件稍加补充。

2、自动从Web.sitemap获得当前页面(Controller,Action)对应的网站地图位置,自动生成导航条, 使用时不需要编写任何代码。

3、根据MVCController-Action规则自动创建对应链接,也可自由设置,包括只显示文字,不使用连接等。

4、可以完全或部分手动设置、增减节点。

5、可以限制节点显示层数。

以上的大部分功能都是在SiteMapPath可实现的,但是我们已经不再需要PostBack功能的设置。

遵照这些前提,我给大家展示一下我的实现方法:

一、建立全局共享的Model层的NavigationInfo,包含在Models/ExtentionEntity.cs

public class NavigationInfo

        {

            public string Title { setget; }

            public string ActionName { setget; }

            public string ControllerName { setget; }

            public object Values { setget; }

            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, nullnullnull);

            }

        }

   
    其中,TitleActionNameControllerNameValues分别对应View页面ActionLink需要的链接文字、actioncontrollervalues。里面也提供了对NavigationInfo4种重写的方法,以便和ActionLink的参数重写尽量配套,在适应使用习惯的同时也提供了更大的灵活性。

二、创建BaseViewData,所有继承了这个类的ViewData都将获得NavigationInfo属性。Models/BaseViewData.cs

   public class BaseViewData

    {

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

        public List<ExtentionEntity.NavigationInfo> NavInfo { getset; }

    }

    三、创建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 (++> 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


  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
        
/// </param>
128

129
        public static void Insert(this List<ExtentionEntity.NavigationInfo> navInfos, ExtentionEntity.NavigationInfo navInfo, int layer)
130

131
        {
132

133
            if (layer > 0)
134

135
            {
136

137
                if (layer <= navInfos.Count)
138

139
                    navInfos.Insert(layer - 1, navInfo);
140

141
                else//上标越界
142

143
                    navInfos.Insert(navInfos.Count, navInfo);//纠正为追加到末尾
144

145
            }
146

147
            else if (layer < 0)
148

149
            {
150

151
                if (Math.Abs(layer) >= navInfos.Count)
152

153
                    navInfos.Insert(navInfos.Count - Math.Abs(layer), navInfo);
154

155
                else//下标越界
156

157
                    navInfos.Insert(0, navInfo);//纠正为插入到第一条
158

159
            }
160

161
            else//layer = 0
162

163
            {
164

165
                navInfos.Add(navInfo);
166

167
            }
168

169
        }
170

171
        public static bool CheckUrlMatches(string sitemapUrl, string pageUrl)
172

173
        {
174

175
            if (!sitemapUrl.EndsWith("/"))
176

177
            {
178

179
                sitemapUrl += "/";
180

181
            }
182

183
            if (!pageUrl.EndsWith("/"))
184

185
            {
186

187
                pageUrl += "/";
188

189
            }
190

191
            if (Regex.Matches(pageUrl, sitemapUrl + @""w*", RegexOptions.IgnoreCase).Count > 0)
192

193
            {
194

195
                return true;
196

197
            }
198

199
            else
200

201
            {
202

203
                return false;
204

205
            }
206

207
        }
208

209
        /// <summary>
210

211
        
/// Creates a client-resolvable Url based on the passed-in value
212

213
        
/// </summary>
214

215
        
/// <param name="virtualUrl">Relative Url to evaluate. Use ~/ to resolve from the root</param>
216

217
        
/// <returns></returns>
218

219
        public static string ResolveUrl(string virtualUrl)
220

221
        {
222

223
            string result = virtualUrl;
224

225
            if (virtualUrl.StartsWith("~/"))
226

227
            {
228

229
                virtualUrl = virtualUrl.Remove(02);
230

231
                //get the site root
232

233
                string siteRoot = HttpContext.Current.Request.ApplicationPath;// ctx.Request.ApplicationPath;
234

235
                if (siteRoot == string.Empty)
236

237
                    siteRoot = "/";
238

239
                result = siteRoot + virtualUrl;
240

241
            }
242

243
            return result;
244

245
        }
246

247
    }
248

249

上面可以看到,相比使用XMLDocument的传统方式,Linq to XML大大简化了对XML的操作。其中关键的地方都作了比较详细的注释,欢迎大家提出建议或批评!

下面是上述方法在Controller中的使用。

注:这里还有一个注意点:虽然NavigationInfo中的Title提供直接在ActionLink中显示的text内容,但是建议不要在Controller中定义的过死,否则可能导致C-V数据的脱节,我在Controller演示设置NavigationInfo的功能,并不在于要在Controller中就把导航栏HTML代码输出,只是输出一个包含节点信息的ListViewDate,让其在View中具体生成。

我举NavController为例,里面有一个Actionvoid More(string type)

public void More(string type)

        {

            Models.MoreViewData vd = new MVCTools.Models.MoreViewData();

            switch (type)

            {

                case "manual"://完全手动创建

                    vd.SomePageInfo = "完全手动创建";

                    vd.NavInfo = new List<MVCTools.Models.ExtentionEntity.NavigationInfo>()

                    {

                        new ExtentionEntity.NavigationInfo("手动创建第一节","Index","Home"),

                        new ExtentionEntity.NavigationInfo("手动创建第二节","Index","Home"),

                        new ExtentionEntity.NavigationInfo("手动创建第三节","Index","Home"),

                        new ExtentionEntity.NavigationInfo("手动创建第四节","Index","Home"),

                        //只设Title时,此节点不会显示为链接

                        new ExtentionEntity.NavigationInfo("手动创建第五节"),

                    };

                    //最后插入“根节点”,使用扩展方法

                    vd.NavInfo.Insert(new ExtentionEntity.NavigationInfo("手动创建的根节点""Index""Home"), 1);

                    //以下方法和上一句等效

                    
//vd.NavInfo.Insert(0,new ExtentionEntity.NavigationInfo("手动创建的根节点", "Index", "Home"));

                    break;

                case "add"://增加节点

                    vd.SomePageInfo = "自动获取基础上增加节点";

                    vd.NavInfo = Navigation.GetAutoNavigationInfo();//自动获取全部节点

                    
//在前面插入

                    vd.NavInfo.Insert( new ExtentionEntity.NavigationInfo("手动创建第一个"),-1);

                    //在后面插入

                    vd.NavInfo.Add(new ExtentionEntity.NavigationInfo("手动创建第二个"));

                    vd.NavInfo.Add(new ExtentionEntity.NavigationInfo("手动创建第三个"));

                    //以下方法和上一句等效

                    
//vd.NavInfo.Insert(new ExtentionEntity.NavigationInfo("手动创建第三个"), 0);

                    break;

                case "subtract"://限制、扣除节点

                    vd.SomePageInfo = "自动获取基础上限制节点,只显示到第2层";

                    vd.NavInfo = Navigation.GetAutoNavigationInfo(2);

                    break;

                default:

                    //自动获取,什么都不用做

                    vd.SomePageInfo = "完全自动读取Web.sitemap";

                    break;

            }

            RenderView("More",vd);

        }

通过上面case4种情况我向大家演示了文章开头我们需要完成的5项基本功能,其中如果你不需要对导航栏信息修改,让其自动获取的话,只要传递一个BaseViewData给页面,什么都不用做:

 

public void More()
        {
            MoreViewData vd = new  MoreViewData ();
//Your Code
            RenderView("More ", vd);
        }

 

而接下去在aspx页面(Views)中,你可以什么都不要做,如果需要获取信息或者修改的话只需要查看ViewData.NavInfo或者ViewData[“NavInfo”]就行了。

这里要小说一下上面提到的BaseViewDataTitle参数,是用于网页标题Title的显示,由于Title基本属于View层面的东西,在这里设Title主要是方便ControllerView的沟通,具体设置也可以在View中完成(我还是觉得在程序员和美工有很好沟通的情况下,在Controller先设置好基础部分更方便一些,而且不用劳烦每个aspx文件都对title设置,那样有时实在很辛苦)。这个Title你只需要设置最“个性化”的信息,全局的包括每个母板的特定信息会自动加上。有了这个功能,你还可以自己进行一些扩展,比如:把导航栏的最后一个节点的信息作为标题等等。由于和导航栏没有非常直接的关系,这里我暂不多加论述,具体应用你可以在我的Demo中的母板页文件看到(注意.Master.cs文件)

这里是上面代码的Demo下载:/Files/szw/MVCTools_mvc.rar 

补充一下,对于sitemap的变化,只是为每个节点多加了两个controller和action属性,以控制节点显示的链接,具体大家可以看Demo中的Web.sitemap

    这个导航栏的实现很简单,希望能够发上来抛砖引玉,也省去一些朋友重复劳动之苦。如果有任何不周之处,还望大家见谅和赐教!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
mvc基础系列说谈(3)——controller与action
浅析ASP.NET MVC中Controller与View数据传递
ASP.NET MVC 流程概述(转)
ASP.NET MVC Controllers and Actions
「深入討論」認識 MVC﹣View 與 Controller(上)
ASP.NET MVC案例教程—第三篇:ASP....
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服