首页 > 编程 > .NET > 正文

在ASP.NET 2.0中建立站点导航层次

2024-07-10 13:05:58
字体:
来源:转载
供稿:网友

站点导航提供程序--asp.net 2.0中的站点导航提供程序暴露了应用程序中的页面的导航信息,它允许你单独地定义站点的结构,而不用考虑页面的实际物理布局。默认的站点导航提供程序是基于xml的,但是你也可以通过编写自定义的提供程序,从任何后端位置暴露这些信息。

  站点导航api--站点导航api用于在应用程序的代码中访问站点导航信息,它摘录了导航信息存储的细节。你可以使用api来编程访问应用程序的导航节点。

  导航控件--导航控件为页面之间的导航提供了通用的ui,例如树视图、菜单和breadcrumb("面包屑",一种显示当前所在页面的控件)。这些控件利用asp.net 2.0中的站点导航服务来检索你给站点定义的结构。sitemapdatasource控件还允许你把其它ui控件绑定到站点导航数据。 

  网站经常需要显示导航数据,来指导用户如何使用站点。asp.net中的导航特性允许开发者简单地定义导航数据,并根据这些信息来显示ui。

  站点导航api是一种用于访问站点导航数据的基于提供程序(provider)的编程内容。该api把导航数据存储在xml文件中,并通过一组sitemapnode类来暴露这些数据。应用程序和控件开发者可以构建sitemapnode实例并使用这些信息来显示导航界面。

  面向导航的服务器控件包括menu、treeview、sitemappath和sitemapdatasource控件。这些控件都是建立在站点导航类的顶端的,它们使用和显示导航数据的时候都是不考虑数据存储的特定细节问题的。menu和treeview控件还可以使用xml文件的数据和xmldatasource控件的数据。

  url映射特性允许开发者为不同url请求的重映射(re-mapping)定义简单的规则。

  使用站点导航控件

  menu、treeview、sitemappath和sitemapdatasource控件根据导航数据生成导航界面。导航数据可以存储在xml文件中,或者利用站点导航特性的基于提供程序的能力来保存。下面的例子演示了如何组合使用站点导航特性的不同控件。

  建立应用程序站点地图

  示例的导航结构存放在web.sitemap文件中,在下面你可以看到站点地图文件。web.sitemap文件包含一个顶层的<sitemap>元素。在<sitemap>元素内至少嵌套一个<sitemapnode>元素。在一个站点地图内必须有一个顶层的<sitemapnode>。站点导航特性需要一个根<sitemapnode>来确保沿着节点层次的访问最终汇聚到一个已知的节点。你可以在根<sitemapnode>元素下嵌套多个<sitemapnode>元素。此外,嵌套<sitemapnode>元素的深度是没有限制的。

  一个<sitemapnode>元素通常包含url(链接)、title(标题)和description(描述)属性。url属性指明与应用程序中的页面对应的路径。它也可以包含其它应用程序中的页面的路径,或者指向完全不同的网站的多个url。在下面的例子中,所有的url属性都使用应用程序相对语法来引用路径。title属性用于显示导航数据ui的文本内容。例如,sitemappath控件把title属性作为控件的超链接文本显示。如果提供了description属性,服务器控件就把显示为工具条提示或alt文本。开发者也可以给<sitemapnode>添加自定义属性,利用sitemapnode类的默认索引器(indexer)就可以检索这些属性了。你可以查阅.net框架组件文档找到更多的关于<sitemapnode>元素其它一些属性的信息。

  web.sitemap的内容

 

<sitemap>
<sitemapnode title="home" url="~/default.aspx" >
<sitemapnode title="introduction to asp.net" url="~/introduction/default.aspx">
<sitemapnode title="what's new in whidbey?" url="~/introduction/whatsnew.aspx"/>
<sitemapnode title="sample applications (starter kits)" url="~/introduction/starterkits.aspx"/>
<sitemapnode title="introduction to visual web developer" url="~/introduction/vwd.aspx"/>
</sitemapnode>
<sitemapnode title="building a web application" url="~/development/default.aspx">
<sitemapnode title="building a simple application" url="~/development/simple/default.aspx">
<sitemapnode title="introduction to asp.net pages" url="~/development/simple/pages.aspx"/>
……
</sitemapnode>
</sitemapnode>
</sitemap>
  使用站点导航控件

  在web页面上提供站点导航的最简单办法就是使用图形化的站点导航控件(sitemappath、treeview和 menu)。

  · sitemappath--一个breadcrumb控件,它检索用户的当前页面并显示页面的层次结构。这让用户可以导航回层次中其它的页面。sitemappath只能与sitemapprovider一起使用,需要设置控件的sitemapprovider属性。

  · treeview--在web页面上提供垂直的用户界面,它可以展开和收缩选中的节点,也可以提供检查框功能以供用户选择数据项。treeview控件支持宣告式的或编程设置数据元素和数据源控件。如果你使用sitemapdatasource控件,那么数据绑定就是自动进行的。

  · menu--提供水平的或垂直的用户界面,当用户把鼠标放在一项上的时候会弹出子菜单。menu控件支持宣告式的或编程设置数据元素或数据源控件。如果你使用sitemapdatasource控件,数据绑定就是自动进行的。

  请注意,treeview和menu控件都可以用于非导航的情形。

  下表描述了treeview和menu之间的差别,以便于你能根据需要做出适当的选择。

特性 menu treeview 
扩展 弹出 位置扩展 
根据需要下载 no yes 
检查框 no yes 
模板 yes no 
布局 水平 & 垂直 垂直 
style选项 yes yes 
选择模式 静态的、 动态的层次 层次或父/根/叶,或按数据项 

  在下面的例子中,你看到的treeview和menu控件为不同的区域和操作(例如nodestyle和hovernodestyle)配置了一些样式属性。例子中的treeview和menu控件都连接到页面上的同一个sitemapdatasource控件。sitemappath控件通过web.config文件中设置的默认的sitemapprovider来访问相同的数据。

<asp:treeview id="treeview1" forecolor="white" datasourceid="sitemapdatasource1" nodeindent="0" nodestyle-childnodespadding="10" runat="server">
<levelstyles>
<asp:treenodestyle font-bold="true"/>
<asp:treenodestyle />
<asp:treenodestyle font-size="x-small"/>
</levelstyles>
<nodestyle forecolor="white" horizontalpadding="5"/>
<selectednodestyle backcolor="lightblue" forecolor="blue" />
<hovernodestyle font-underline="true" />
</asp:treeview>
<asp:sitemappath id="sitemappath1" runat="server" />
<asp:menu id="menu1" datasourceid="sitemapdatasource1" runat="server">
<dynamicselectedstyle backcolor="lightblue" forecolor="blue" />
<dynamichoverstyle font-underline="true" />
<statichoverstyle font-underline="true" />
</asp:menu>
<asp:sitemapdatasource id="sitemapdatasource1" runat="server" />

  使用站点导航api

  站点导航api是使用可配置的提供程序访问导航数据的编程抽象内容。站点导航提供程序把导航数据的存储细节信息与api的其它部分隔离开来。站点导航api通过sitemap和sitemapnode类来暴露导航数据。sitemap类返回与当前页面对应的sitemapnode实例。它还可以访问那些为站点导航特性配置的提供程序。sitemapprovider为执行下面一些事务提供了丰富的api:

  · 依据当前的httpcontext或任意url检索sitemapnode实例。

  · 检索sitemapnode的父或子节点。

  · 访问当前页面的sitemapnode,以及整个导航层次中的根sitemapnode。

  · 执行授权规则,这样就保证了提供程序只返回用户可以看见的节点。

  sitemapnode实例暴露的基本导航信息和功能包括:

  · url、title和description属性,以及开发者给sitemapnode添加的自定义属性。

  · 获取某个节点的父和子节点。

  · 在某个节点的前后节点之间进行导航。

  · 获取sitemapprovider实例的指针,它返回一个节点。

  asp.net发布的时候带有xmlsitemapprovider提供程序。该提供程序使用xml文件(web.sitemap)中的数据,并根据数据返回sitemapnode实例。xmlsitemapprovider有下面一些功能:

  · 多个站点地图(sitemap)文件可以链接在一起来构建一个"虚拟的"导航数据集合。

  · 多个xmlsitemapprovider实例可以链接到一起来构建一个"虚拟的"导航数据集合。

  · 提供程序可以根据站点当前的文件授权和url授权规则来返回过虑后的节点。

  有了sitemapprovider指针之后,你就可以根据url来检索特定节点的站点导航数据。它会让你获取站点导航数据中的sitemapnode实例指针。可以检索任意sitemapnode实例的能力和从任何sitemapnode开始进行站点导航的能力组合在一起,使你能够轻易地遍历站点的导航数据。

  作为一名开发者,你也可以把导航数据用其它的格式进行存储(例如作为关系数据存储在数据库中)。接着你应该构建一个衍生自sitemapprovider的自定义提供程序。

  使用站点导航类编程

  你可以在代码中编程获取导航数据。编程获取站点导航数据的出发点是sitemap类。在这个类中有大量的静态方法,其中最重要的一个是currentnode属性。在网站的任何页面中,你都可以调用sitemap.currentnode来引用与当前的执行页面相匹配的导航数据片断。导航数据是用sitemapnode实例的形式返回的--当你调用sitemap.currentnode的时候,该属性返回与当前页面对应的sitemapnode实例。站点导航特性根据存储在xml文件中的导航数据返回正确的节点。

  下面的例子演示了一个带有简单的分页功能的用户控件。在显示的页面中,用户控件位于页面的底部中间。最初该链接的内容是"下一个主题"。当你点击这个链接的时候,用户控件调用sitemapnode对象来检测当前页面的附近是否存在页面。代码检测sitemap.currentnode属性,看它的前面是否有页面(sitemap.currentnode.previoussibling)、它的后面是否有页面(sitemap.currentnode.nextsibling)。如果存在页面,用户控件就显示超链接,并把超链接的navigateurl属性设置为附近节点的url属性。

  如果你点击页面左部的treeview链接,可以看到用户控件是如何自动地显示适当的"前一个主题"和"后一个主题"链接的。用户控件还显示了另外一个超链接,你可以点击它返回主页。如果你查看这个超链接如何工作就会发现,该控件利用了主页<sitemapnode>元素中的自定义属性"customattribute"。该控件演示了如何使用sitemapnode的默认索引器来检索自定义属性的值。

 

<script language="vb" runat="server">
sub page_load()
if (not sitemap.currentnode.nextsibling is nothing) then
nexttopic.navigateurl = sitemap.currentnode.nextsibling.url
else
nexttopic.visible = false
separator.visible = false
end if

if (not sitemap.currentnode.previoussibling is nothing)
prevtopic.navigateurl = sitemap.currentnode.previoussibling.url
else
prevtopic.visible = false
separator.visible = false
end if

'使用findsitemapnode查找url中的节点并提取一些信息
dim rootnode as sitemapnode = sitemap.provider.findsitemapnode("~/home.aspx")
gohome.navigateurl = rootnode.url
gohome.tooltip = rootnode.description
gohome.text = rootnode("customattribute")
end sub
</script>

  站点导航的安全性

  站点导航特性可以根据授权规则过虑提供程序所返回的sitemapnode实例。xmlsitemapprovider可以根据当前网站使用的文件和url授权规则过虑节点。

  下面的例子使用了窗体授权规则,预定义的用户凭证存储在web.config中。在global.asax中,根据用户名,用户的角色都被附加到当前的请求上。在web.config中,嵌套在<sitemap>元素之下的站点地图提供程序使用的<add>元素的securitytrimmingenabled属性被设置为真。同时,web.config文件的末尾定义了一组url授权规则。当你运行示例并登陆之后,xmlsitemapprovider会自动地依据用户所属的角色和web.config中定义的授权规则来对sitemapnode执行授权检测。

  请使用下面三个帐户之一运行示例:

  · userid: sectionone password: sectionone 
  · userid: sectiontwo password: sectiontwo 
  · userid: allsections password: allsections 

  在页面的右上角有一个"退出"链接,因此你可以用不同的帐号登陆和退出。请注意,根据你登陆所使用的帐号不同,导航ui显示的treeview和menu控件会自动地反映该用户所获得的访问权限。提供程序自动地过虑了返回的节点--实现这种功能不需要额外的代码。用"sectionone"帐号登陆的时候,只在左边的treeview控件中显示"sectionone"链接和外部链接。用"sectiontwo"帐号登陆的时候,只在左边的treeview控件中显示"sectiontwo"链接和外部链接。用"allsections"帐号登陆的时候,treeview控件中显示了所有的链接。web.config中的授权规则配置为给"sectionone"和"sectiontwo"层次授予了部分访问权力。

  这个示例还演示了如何处理应用程序目录范围之外的url安全性。在web.sitemap文件中,外部链接的节点使用了roles属性。语法roles="*"授予所有用户访问和查看导航控件中的节点的权力。语法roles="adminstrators,regular users"只允许这些角色的用户检索和查看导航控件中的节点。由于在示例中global.asax文件把用户分成了这两种角色,所以你一直可以看到外部链接。

  开发者可以选择同时使用文件/url授权规则和roles属性来控制用户对sitemapnode实例的访问权。如果两者的设置信息都是正确的,站点导航提供程序就会根据文件/url授权规则和roles属性中的角色来对当前用户进行认证。如果当前用户通过了任何一种授权检查,就可以访问节点。

  如果默认的安全性操作不适用于你的应用程序,开发者还可以从xmlsitemapprovider衍生类,并用自定义的节点授权实现来重载isaccessibletouser方法。

  web.config的内容

 

<system.web>
<authentication mode="forms">
<forms name=".aspxauth" loginurl="login.aspx" protection="all" timeout="30" path="/" requiressl="false" slidingexpiration="true" defaulturl="home.aspx" cookieless="usecookies" enablecrossappredirects="false">
<credentials passwordformat="clear">
<user name="sectionone" password="sectionone"/>
<user name="sectiontwo" password="sectiontwo"/>
<user name="allsections" password="allsections"/>
</credentials>
</forms>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
<location path="sectionone.aspx">
<system.web>
<authorization>
<allow users="sectionone" roles="administrators" />
<deny users="*"/>
</authorization>
</system.web>
</location>
<location path="sectionone">
<system.web>
<authorization>
<allow users="sectionone" roles="administrators"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
<location path="sectiontwo.aspx">
<system.web>
<authorization>
<allow users="sectiontwo" roles="administrators"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
<location path="sectiontwo">
<system.web>
<authorization>
<allow users="sectiontwo" roles="administrators"/>
<deny users="*"/>
</authorization>
</system.web>

  本地化站点地图数据

  存储在sitemap文件中的导航数据可能需要进行本地化(localize)。<sitemapnode>元素中的url、title和description属性也可以本地化。此外,开发者放置在<sitemapnode>元素中的任何自定义属性也可以本地化。

  下面的示例包含了英语和法语的本地化文本。它的web.sitemap文件使用两种类型(隐式的和显式的)的本地化表达式来实现这种功能。sitemap文件在根sitemap元素中使用了enablelocalization=true就表明它使用了本地化数据。

  站点地图文件的隐式表达式让开发者能够轻易地用查找键(lookup key)标记每个<sitemapnode>元素,而查找键是用于从资源文件检索资源的。在示例的web.sitemap中,除了第一个节点之外,所有的节点都有隐式的资源表达式。它的语法类似resourcekey="autos"。当xmlsitemapprovider根据web.sitemap文件中的信息检索sitemapnode的时候,它根据sitemapnode属性的名称、resourcekey和为提供程序配置的sitemapfile属性的值来检索字符串资源。使用示例中的"autos"节点的时候,提供程序(provider)会根据当前的文化来查找以"web.sitemap"开头的资源文件。这意味着,对于一个发送法语头信息的浏览器来说,提供程序会查找名称为web.sitemap.fr.resx的资源文件。在这个资源文件中,提供程序会依据resourcekey + "." + [sitemapnode属性名]来查找资源键。例如,把"autos"节点的title属性当作例子,提供程序会在web.sitemap.fr.resx资源文件中查找键为autos.title的资源。

  显式表达式使开发者对包含本地资源的文件和资源键(resource key)的名称有更强的控制能力。在示例web.sitemap中,第一个<sitemapnode>元素使用了显式资源表达式。显式表达式在每个属性上指定。第一个<sitemapnode>元素的title属性使用了显式表达式。显式表达式必须以$resource:开头。在这个标识符之后,开发者必须提供资源文件的根名称和资源键。开发者可以选择提供一个默认值。在例子中,表达式$resources: title, mytitle , home表明提供程序应该查看以"title"开头的资源文件。对于发送法语头信息的浏览器开说,提供程序会查找title.fr.resx资源文件。接下来提供程序查看键为mytitle的资源。如果提供程序无法找到这种资源,它会把字符串"home"作为默认值。

  你可以运行示例来查看站点地图本地化的效果。把英语作为默认语言的浏览器会显式英语文本。如果使用ie,你可以通过点击"工具->internet选项",并在"通用"选项卡点击"语言"按钮,点击"添加"按钮并选择添加"法语"。如果需要,还需要选中法语并点击"向上移动"按钮,使它成为ie的默认请求语言。把默认的语言改成法语之后,刷新示例页面。请注意,menu、treeview和sitemappath控件中的文本自动地显式为app_globalresources目录中存放的法语资源文件中的法语文本。

  web.sitemap的内容

 

<?xml version="1.0" encoding="utf-8" ?>
<sitemap xmlns="http://schemas.microsoft.com/aspnet/sitemap-file-1.0" enablelocalization="true">
<sitemapnode url="~/default.aspx" title=" $resources: title, mytitle , home" description="default page description when no localized value exists." >
<sitemapnode url="~/category.aspx" resourcekey="category">
<sitemapnode title="autos" description="autos" url="~/autos.aspx" resourcekey="autos" />
<sitemapnode title="games" description="games" url="~/games.aspx" resourcekey="games" />
<sitemapnode title="health" description="health" url="~/health.aspx" resourcekey="health" />
<sitemapnode title="news" description="news" url="~/news.aspx" resourcekey="news" />
</sitemapnode>
</sitemapnode>
</sitemap>

  修改提供程序(provider)返回的站点导航数据

  存储在web.sitemap中、供xmlsitemapprovider使用的导航数据是静态的--这些数据被载入内存中并作为只读数据存储。但是,很多站点的导航结构是根据查询字符串的值来参数化的。例如,新闻组(newsgroup)站点可能拥有良好定义的页面结构(例如,主页、新闻类别页面和新闻内容页面),但是实际的内容可能会有很大的不同,这依赖于查询字符串中的标识符。尽管把每种可能的查询字符串值都存储在<sitemapnode>元素中也是可能的,但是即使是中等数量的查询字符串值,也要求sitemap文件包含数百个<sitemapnode>元素。

  站点导航特性在sitemapprovider基类中暴露了sitemapresolve事件。可以使用sitemap.sitemapresolve或直接使用提供程序sitemap.provider.sitemapresolve来执行这个事件。这个事件的返回值是一个sitemapnode实例。你可以在自己的事件处理程序中编写自定义逻辑来建立sitemapnode实例的层次结构。这个逻辑可以修改每个sitemapnode的属性,因此url和title等属性会反映查询字符串带有的数据信息。

  下面的例子在global.asax中注册了一个事件处理程序。这个事件处理程序的代码是app_code目录中的一个类。这个自定义的类复制与当前页面对应的sitemapnode实例。xmlsitemapprovider返回的节点都是只读的,而调用sitemapnode上的clone方法返回的是可写入的节点。在实例中,如果给clone传递了true值,将导致当前的sitemapnode和它的所有父节点都是可写入的。这个类的代码的其它部分检查当前的页面和当前页面的查询字符串,确定当前页面位于站点层次结构的什么位置。代码修改了url和title属性,包含一些额外的信息,这样sitemappath控件显示的导航ui就反映了网站用户为到达当前页面的实际点击路径。

  运行示例的时候,你开始位于站点的主页。sitemappath控件也反映了这一点。点击任何链接都会带你进入分类页面,它显示相关新闻类别中的新闻链接。请注意,如果你把鼠标停留在sitemappath控件的最后一个链接上,浏览器状态栏中显示的url包含了查询字符串信息(它指定了新闻类别)。点击任何一个发布链接都会把你带回到新闻发布页面。如果你把鼠标停留sitemappath控件的链接上,可以注意到控件中的最后两个链接带有的url和title包含了点击路径的正确查询字符串和描述信息。如果你导航到站点的主页,并点击其它的新闻组和内容链接,sitemappath控件会被更新并反映第二次点击的链接。

 

public class pathexpansionhandler
public shared function expandpath(byval sender as object, byval e as sitemapresolveeventargs) as sitemapnode
'获取当前和之前节点的引用
dim nodecopy as sitemapnode = sitemap.currentnode.clone(true) 
dim tempnode as sitemapnode = nodecopy 

'check if there is a newsgroup type in the query string
dim typeid as string = nothing 
dim typeidurlencoded as string = nothing 
if not string.isnullorempty(e.context.request.querystring("type")) then
typeid = e.context.server.htmlencode(e.context.request.querystring("type"))
typeidurlencoded = e.context.server.urlencode(e.context.request.querystring("type"))
end if

'首先执行发布页面url的固定
'如果查询字符串中包含发布id,我们就知道当前节点式发布页面
if not string.isnullorempty(e.context.request.querystring("postingid")) then
dim postingid as string = _
e.context.server.htmlencode(e.context.request.querystring("postingid"))
dim postingidurlencoded as string = _
e.context.server.urlencode(e.context.request.querystring("postingid"))
dim newurl as string = tempnode.url + "?type=" + typeidurlencoded + "&postingid=" + postingidurlencoded 
dim newtitle as string = tempnode.title + ": " + postingid 
tempnode.url = newurl
tempnode.title = newtitle

tempnode = tempnode.parentnode
end if

'然后,对新闻组页面进行固定
'这时候nodecopy 变量知贤了新闻组节点
if not string.isnullorempty(e.context.request.querystring("type")) then
dim newurl as string = tempnode.url + "?type=" + typeidurlencoded 
dim newtitle as string = tempnode.title + ": " + typeid 
tempnode.url = newurl
tempnode.title = newtitle
end if

'最后返回当前节点
return nodecopy
end function
end class
  url映射

  url映射特性利用web.config中存储的配置信息把收到的请求重映射(remap)到不同的url。重映射发生在对请求的所有其它处理操作之前。下面的例子演示的是重映射一个页面请求,实际上任意文件类型都可以把请求重映射到不同的url。

  定义重映射url

  url映射的配置信息存储在web.config中。<urlmappings >元素中嵌套的每个<add>元素为重映射进入站点的(inbound)url定义了一条规则。url属性定义了进入站点的url的exact(原样)属性,url映射特性会试图用它进行匹配操作。如果exact匹配操作发生了,就会给进入站点的url重新写入mappedurl属性值。请注意,这个特性不支持更高级的规则(例如基于通配符和正则表达式的匹配)。 

  示例web.config为大量的url定义了映射规则。示例使用的web.sitemap文件定义的大量带有url值的节点都会被重映射。其结果是,url映射和站点导航的组合使用,使得开发者可以用友好的url来定义导航结构,并使用url映射把请求重新写到不同的页面来执行实际的处理过程。

  当你运行示例的时候,请注意menu和treeview控件是如何根据web.sitemap文件中定义的站点结构来显示导航数据的。如果你把鼠标停留在treeview控件或右上角的sitemappath控件的链接上方,状态栏中显示的url是一个友好的url链接。当你点击任何导航链接的时候,实际运行的页面是default.aspx。但是,menu、treeview和sitemappath控件中的导航信息仍然反映为友好的url结构。

  在页面的底部你还可以看到request.path、request.querystring["category"]和request.rawurl返回的值。request.path和request.querystring["category"]返回的值一直反映重映射进入站点的url的结果。但是,request.rawurl的值反映了重映射之前的友好的url。当站点导航特性试图把url信息与sitemap文件包含的数据进行匹配的时候,它会使用request.rawurl。如果匹配的值没有找到,xmlsitemapprovider就把request.path作为替代者。在例子中,所有的友好url在web.sitemap文件中都有条目,因此使用站点导航的控件一直根据友好的url来显示和引用节点。

<?xml version="1.0" ?>
<configuration xmlns="http://schemas.microsoft.com/.netconfiguration/v2.0">
<system.web>
<urlmappings enabled="true">
<add url="~/category.aspx" mappedurl="~/default.aspx?category=default" />
<add url="~/autos.aspx" mappedurl="~/default.aspx?category=autos" />
<add url="~/games.aspx" mappedurl="~/default.aspx?category=games" />
<add url="~/health.aspx" mappedurl="~/default.aspx?category=health" />
<add url="~/news.aspx" mappedurl="~/default.aspx?category=news" />
</urlmappings>
</system.web>
</configuration> 

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表