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

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

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)

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

<%  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
  1public 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

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

 

QQ:498977166

http://szw.cnblogs.com/
研究、探讨.NET开发
转载请注明出处和作者,谢谢!

 

微信开发深度解析:微信公众号、小程序高效开发秘籍
Senparc官方教程《微信开发深度解析:微信公众号、小程序高效开发秘籍》,耗时2年精心打造的微信开发权威教程,点击这里,购买正版

 

目录
相关文章
|
1月前
|
SQL 开发框架 数据可视化
企业应用开发中.NET EF常用哪种模式?
企业应用开发中.NET EF常用哪种模式?
|
2月前
|
开发框架 JavaScript 前端开发
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
|
3月前
|
XML 开发框架 .NET
ASP.NET COR3.1 集成日志插件NLog
ASP.NET COR3.1 集成日志插件NLog
33 0
|
4天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
|
30天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
28 0
|
30天前
|
数据安全/隐私保护 Windows
.net三层架构开发步骤
.net三层架构开发步骤
9 0
|
30天前
深入.net平台的分层开发
深入.net平台的分层开发
47 0
|
30天前
mvc.net分页查询案例——mvc-paper.css
mvc.net分页查询案例——mvc-paper.css
5 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
95 5
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
32 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!