为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)

                 {
%>&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

上面可以看到,相比使用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

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

posted on 2008-04-12 15:57  SZW  阅读(8990)  评论(6编辑  收藏  举报