Asp.net 初学者“路径”的一些内容






4.89/5 (48投票s)
为 Asp.net 初学者澄清“路径”相关问题
引言
“路径”为什么如此重要?
2004年10月
一位年轻的开发者正在与地球上最困难的问题搏斗。他试图在两个不同的网页中显示一张图片。图片在一个网页上显示,而在另一个网页上却不显示!
他正在努力解决的 HTML
元素如下:
<img id="imgAction" src="images/action.jpg" />
你猜怎么着,那个可怜的开发者就是我!
我至今仍记得,我在为网页中的不同元素(图片/文档/视频
等)设置适当的路径时遇到了麻烦。我常常纠结于如何在页面中放置下载链接以及如何显示图片和/视频文件(我至今仍然需要这样做),通过使用不同的 HTML
元素或服务器控件并设置它们的不同属性(href、src 等)值。听起来很可笑,但我经常发现自己处于这样的境地:图片在一个页面中显示,而在另一个页面中却不显示,或者下载链接经常失效。我当时不明白为什么!
在那些早期日子里,这对我来说是一项巨大的挑战,伴随着我每天经历的许多其他神秘事物。
很明显,我对如何为 HTML
和 Asp.net
服务器控件适当地设置 href 或 src 属性值没有清晰的理解,这就是我不得不遭受痛苦的原因。
那些日子早已过去。我成长了一些,现在对那些神秘的路径不再有任何问题。然而,直到今天,我仍然看到许多新手在与这些路径相关的问题作斗争(参见此处和此处),这让我很痛心。因此,我决定为绝对的初学者(像我 2004 年 10 月那样)揭示那些“路径之谜”,这样他们就不必浪费一分钟来为 Asp.net
页面设置适当的路径值。
请注意,本文不讨论任何“火箭科学”。它只谈论一些基本的基础知识,很有可能您已经精通此主题。但是,我经常喜欢回顾基础知识,并尝试磨练我的知识,让自己比昨天更好一点。谁知道呢?也许有人也会有同样的感觉。如果您是其中之一,请随意继续。
我说的“路径”是什么意思?
在 Asp.net
应用程序中,我们通常使用 HTML
元素或服务器控件来在页面中添加多个元素(例如,Image
、HyperLink
等)。对于 <img>
元素,我们需要设置一个“src
”属性值(通常是相对 URL 路径),它通常指向应用程序中的一个图像。此外,对于锚点 <a>
元素,我们需要设置一个“href
”值,它指向应用程序目录中的一个资源(无论是页面、图像还是可下载的 zip 文件)。这些就是我通常所指的“路径
”属性。
嗯,这些看起来很直接。但有些情况下,您可能会发现它们不起作用,并且您想知道为什么。本文将尝试解决这些问题并揭示所有奥秘。
对于大多数 HTML
元素,在 Asp.net
中都有相应的服务器控件。自然,服务器控件也具有相应的属性,这些属性接受相对 URL 作为路径值,并将其设置为它们在浏览器中呈现的 HTML
元素的路径属性。因此,像 HTML
元素一样,服务器控件也存在与路径相关的问题。因此,首先理解 HTML
元素的基本路径相关问题将使我们更容易理解后续的服务器控件相关路径问题。
浏览器如何加载包含“路径
”属性的元素?
在 HTTP
中,用户在浏览器中点击 URL
(或者做一些会点击 URL
的操作,例如点击 链接
或 按钮
),然后 HTTP
请求通过网络发送到指定的网络 URL
。服务器反过来生成一些响应(通常是 HTML
标记形式),浏览器接收并将其解释为用户界面,以某种可理解的输出形式。
一旦服务器为页面生成了 HTML
标记,浏览器会首先加载 HTML
,加载完成后,它会开始加载 HTML
标记中具有“路径
”属性的元素。对于每个此类元素(例如,<img>、<a>、<link>、<script>
等),浏览器会再次向服务器发送一个异步请求,其中包含其“路径
”属性(src
或 href
)中提供的 URL
。异步请求意味着浏览器在“幕后”发送请求,并且在执行其他活动时不会等待服务器的响应。因此,用户界面保持响应,当从服务器收到响应时,用户界面会使用响应数据进行更新。
图: 浏览器中图像的异步加载
现在,大多数情况下,提供给这些 HTML
元素的 URL
值是相对的,因为 URL
请求的资源位于 Web 应用程序的文件夹结构中。当浏览器加载这些相对路径时,它通过将宿主应用程序的基本 URL
与相对 URL
结合起来计算绝对 URL
路径,并将请求发送到同一个宿主应用程序。但是,如果由于某种原因资源(图像
文件、javascript
或 css
文件或供用户下载的文件)保存在不同的 Web 应用程序中并从那里提供服务(即,这些文件未保存在 Web 应用程序的根文件夹中),则 URL
值将包含一些绝对路径。在这种情况下,浏览器会向路径属性中提供的绝对 URL
发送异步请求,并接收响应并更新 UI 输出。这主要是为了优化用户浏览器中网页加载的性能(因为从浏览器 HTTP
允许每个域地址只使用两个并发连接,因此有时从单个宿主 Web 应用程序加载所有标记和资源需要太多时间)。
基于这个相对路径,浏览器计算出一个绝对路径并向服务器发送请求以获取实际资源并在浏览器中渲染或加载。现在,如果由于任何原因 src 值设置不正确,浏览器将无法确定资源的正确 URL
,因此 URL
请求将失败,并且相应的资源将无法加载或显示在页面中。
因此,让我们尝试理解浏览器如何根据 src 属性设置的相对 url 实际确定绝对图片 url。
路径计算基本规则
如前所述,图像和其他资源大多保存在 Web 应用程序的根文件夹中的指定目录中,我们通常提供图像(和其他资源)的相对路径,以便浏览器可以使用提供的相对路径作为 src 属性值来计算绝对图像 URL
。我们当然可以为应用程序中的所有图像和资源设置绝对 URL(假设资源位于 Web 应用程序的根文件夹结构中),但这是非常不鼓励的,因为如果由于某种原因将来站点 URL 发生更改,所有 src 属性值都需要更改。那将是可怕的。(绝对 URL 仅在资源保存在不同服务器上以优化性能时使用,这些服务器称为内容分发网络-CDN)。
现在,让我们假设我们有一个页面 http://www.mysite.com/default.aspx
,其中包含一个 <img>
元素,其 src
属性值为:
src = "/images/action.jpg"
正如我们所猜测的,在 Web 应用程序的根文件夹中,有一个“
images
”文件夹,其中有一个“action.jpg
”文件。当遇到这个元素时,浏览器会尝试确定一个绝对 URL(http://www.mysite.com/images/action.jpg
),并向这个 URL
发送一个异步请求。如果 Web 服务器在这个位置找到图像,它将读取并发送图像作为响应,浏览器会在输出中显示它。
一点点复杂性
并非所有页面都直接在 Web 应用程序的根文件夹中实现,在某些文件夹结构下实现页面是很常见的。因此,让我们假设我们的应用程序中存在以下情况:
当前浏览的页面是:http://www.mysite.com/pages/default.aspx
,
页面中的 img src 设置为:src = "/images/action.jpg"
这将无法加载图像并在浏览器中显示。为什么?嗯,这是因为浏览器现在计算绝对图像路径如下:
http://www.mysite.com/pages/images/action.jpg
而且,由于图像在上述
URL
路径中不可用,浏览器将无法显示图像,因为正确的图像 URL
应该是 http://www.mysite.com/images/action.jpg
。
那么,如何修复这个 URL 路径呢?
您可以通过两种方法修复它,一种方法是使用基于当前页面当前目录的相对路径,另一种方法是使用基于网站基本 URL
的相对路径。
基于当前目录的相对路径
让我们假设以下场景
您页面的当前目录是:http://www.mysite.com/pages/
并且请求图片的目录是:http://www.mysite.com/images/
因此,为了从页面内部计算图像的正确相对路径,您应该从“
pages
”目录向上退一级(通过使用“..”
),然后通过添加“/images/action.jpg”
来添加图像的相对路径。因此,这次正确的 src 应该设置为:
src = "../images/action.jpg"
而且,基于 /pages 文件夹内的上述相对路径,浏览器将能够确定正确的绝对图像
URL
如下:
http://www.mysite.com/images/action.jpg
而且,图片将显示在浏览器中。
基于站点基本 URL 的相对路径
或者,您可以使用图像路径,让浏览器根据网站的基 URL 地址计算绝对 URL。为此,您必须将 src
值设置为:
src = "/images/action.jpg"
(注意,这次路径以 "/"
开头)
这将导致浏览器计算绝对路径(相对于根目录),无论您的页面在 Web 根文件夹下的何种文件夹结构中。
http://www.mysite.com/images/action.jpg
所以,浏览器将能够成功地在浏览器中显示图像。
注意!
第二种策略仅在 Web 应用程序部署在网站下,而不是 IIS
中的虚拟目录(虚拟目录是附加在网站基本 URL
之后的目录/路径名,指向 Web 应用程序的根文件夹。一个网站下可以部署多个虚拟目录,每个目录指向不同的 Asp.net
应用程序代码库)时使用。原因是,浏览器根据网站的根 URL
(而不是虚拟目录的根 URL)加上相对 URL 来计算完整的 URL
路径。
因此,如果我们将 Web 应用程序部署在虚拟目录下,如下所示:http://www.mysite.com/app/pages/default.aspx
(这里的 app 是虚拟目录)
并且,如果我们将此页面中图像元素的 src
属性设置如下:src = "images/action.jpg"
浏览器将按如下方式计算绝对
URL
(仅使用站点 URL
):http://www.mysite.com/images/action.jpg
而且,这次上述 URL
请求将失败,因为正确的图像 URL
实际上应该如下:http://www.mysite.com/app/images/action.jpg
Asp.net 服务器控件的路径相关问题
关于 HTML
元素路径和 URL 的学习已经足够,现在我们已经对浏览器如何确定 HTML
元素的 URL
路径有了扎实的理解。我们现在已经足够了解 Asp.net
服务器控件可能发生的路径相关问题。
几乎每个 HTML
元素都有一个对应的 Asp.net 服务器控件。例如,对于 HTML
的 <img>
元素,Asp.net
有以下服务器控件:
<asp:image id="Image1" ImageUrl="~/image/action.png" runat="server"/>
上述服务器控件实际上在浏览器中渲染一个 <img> HTML
元素,并且 ImageUrl
属性值被设置为 <img>
元素的 src 属性值。请注意,当您在 Visual Studio 中设置 ImageUrl
的属性值时,您通常通过使用“选择 URL
” Visual Studio 智能感知来设置。
图: Visual Studio 选择 ImageUrl
属性的智能感知
而且,如果通过点击任何服务器控件的“选择 URL
”来设置路径,Visual Studio 会将 ImageUrl
属性值设置为以字符 "~"
(波浪号)开头的路径。
"~" 是什么?
"~"
(波浪号)是一个特殊字符,通常用于设置 Asp.net
服务器控件的 URL
路径,此字符指示 Asp.net 运行时解析服务器控件的相对路径。
不清楚?让我们看一些例子来简化它。
示例1
假设,我们有一个位于以下 URL
的网页:http://www.mysite.com/pages/default.aspx
此页面包含一个
<asp:Image />
元素,其 ImageUrl
值设置为:
</asp:Image ImageUrl="~/image/action.png" id="Image1" runat="server"/>
因此,当浏览器请求此页面时,Asp.net
运行时会将 "~"
替换为相对路径,该路径将浏览器从当前文件夹(http://www.mysite.com/pages/
)导航到 Web 应用程序的根文件夹(http://www.mysite.com/
),即向上一个目录("..
"),以构建 img 元素的正确相对 URL
。
因此,完整的相对 URL
将被解析为:"~/image/action.png" = “../image/action.jpg"
示例2:
假设,我们有另一个位于以下 URL
的网页:
http://www.mysite.com/default.aspx
此网页还包含一个 <asp:Image>
元素,其 ImageUrl
值设置为:ImageUrl="~/image/action.png"
因此,当浏览器请求此页面时,Asp.net
运行时会将 "~"
替换为相对路径,该路径将浏览器从当前目录(http://www.mysite.com/
)导航到 Web 应用程序的根目录(http://www.mysite.com/
)。也就是说,两者都使用空白( ""
),因为现在两个当前目录路径相同。
因此,完整的相对 URL
将被解析为:"~/image/action.png" = "/image/action.jpg"
基于两种情况下的相对 URL,浏览器将能够确定图像的正确绝对 URL
,并向服务器发出异步请求以加载图像并显示在输出中。
用户控件相关的路径问题:一个经典问题
假设我们的 Asp.net
Web 应用程序拥有组织良好的 aspx 页面和用户控件(ascx
)文件夹结构,以便在根文件夹中以逻辑结构布局页面和控件。
让我们假设我们有以下场景:
用户控件位于:/UserControls/Products/UCProductDetails.ascx
用户控件有一个
HTML
图像元素:<img src=""..>
(src
尚未设置)
页面位于 :/Pages/Products/ProductDetails.aspx
,它使用此用户控件。
另一个页面位于:/Pages/Home.aspx
,它也使用此用户控件。
图: 从不同位置的多个 aspx 访问用户控件
请注意,<img>
元素在用户控件中使用。无论用户控件在文件夹结构中的位置如何,当从浏览器请求页面时,Asp.net 运行时会加载页面以及用户控件,执行它并将 HTML
输出发送到浏览器。因此,在浏览器端,只有 HTML
标记,浏览器将简单地尝试根据页面的当前位置确定图像的相对路径。
请注意,用户控件在两个 aspx 页面中都使用。因此,如果当前浏览的页面是http://www.mysite.com/Pages/Products/ProductDetails.aspx
,<img>
元素的 src 值应设置为:src = "../../images/details.jpg"
(因为浏览器必须向上导航两个文件夹,然后在“images
”文件夹中搜索图像。)
但是,如果当前浏览的页面是 http://www.mysite.com/Pages/Home.aspx
,<img>
元素的 src 值应设置为:src = "../images/details.jpg"
(因为浏览器现在必须向上导航一个文件夹才能访问“images
”文件夹中的图像。)
休斯顿,我们遇到麻烦了。 :(
为了解决这个问题,用户控件中 <img>
元素的正确相对 URL
必须根据请求页面的文件夹位置来确定。幸运的是,我们不必这样做,因为我们有老朋友 “~”
(波浪号)。我们所要做的就是将 HTML <img>
元素替换为服务器控件,并将 ImageUrl
属性设置为以 "~"
(波浪号) 开头。也就是说:
ImageUrl = "~/images/details.jpg"
或者,我们应该将 <img>
元素转换为服务器控件,如下所示:<img runat="server" id="imgDetails" src="~/images/details.jpg" />
无论哪种情况,Asp.net 应用程序都能够根据当前请求页面的位置,在服务器端计算出适当的相对目录,无论页面或用户控件在 Web 应用程序的文件夹结构中的位置如何。
其他元素呢?
不仅是图片元素/控件,还有其他 HTML
元素和 Asp.net
服务器控件,当它们在用户控件中使用时也会遇到相同的路径相关问题,并且应该对它们应用相同的逻辑来解决问题。
不幸的是,并非所有 HTML
元素在 Asp.net 中都有对应的服务器控件。对于这类元素,添加 runat=”server”
属性并设置以 “~”
(波浪号) 开头的路径将不起作用。为什么呢?因为,添加 runat="server"
会将这些元素在服务器端转换为 HtmlGenericControl
,而这种类型的对象无法解析波浪号 (~
) 运算符来确定正确的相对 URL。
对于这类元素,Page.ResolveClientUrl()
是完美的解决方案。
例如,假设一个用户控件具有以下 HTML
元素:
<script src="../js/common.js" type="text/javascript" language="javascript"/>
<link rel="Stylesheet" href="../style/common.css" type="text/css" />
由于此用户控件可以在 Web 应用程序中的任何网页中使用,因此这些 <script>
和 <link>
元素也存在路径相关问题(如果为一个页面适当地设置了路径属性,则相同的路径将无法在不同目录中的其他页面上工作)。
为了解决这个问题,上述元素也应该用 ResolveClientUrl()
修改,如下所示:
<script src='<%=ResolveClientUrl("~/js/common.js")%>' type="text/javascript" language="javascript"/>
<link rel="Stylesheet" href='<%=ResolveClientUrl("~/style/common.css")%>' type="text/css" />
代码隐藏和后端中的路径相关问题
到目前为止,我们已经学习了 Asp.net
运行时如何解析 HTML
元素和服务器控件的相对路径,以及在不同情况下适当地设置路径的最佳实践。需要注意的一点是,在所有情况下,我们都讨论了在 XHTML
标记中设置适当的路径,而不是在代码隐藏文件中。Control.ResolveUrl(string path)
如果路径/URL 在设计时已知,我们当然可以在 XHTML
标记中设置路径/URL。但是,如果由于某种原因路径/URL 未提前已知(例如,路径/URL 会根据某些条件而变化),或者仅仅是您更感兴趣在后端设置路径/URL,则需要了解一种以编程方式解析正确相对路径的方法。
在最基本的层面上,每个服务器控件都有一个 ResolveUrl()
方法(继承自基类 Control),它接受一个字符串路径/URL 值。因此,如果您有一个服务器控件如下:<asp:Image ID="Image1" runat="server" />
您可以在代码隐藏中设置 ImageUrl
属性,如下所示:Image1.ImageUrl = Image1.ResolveUrl("~/images/action.jpg");
或者简单地,Image1.ImageUrl = “~/images/action.jpg”;
这假设 action.jpg
在此相对路径下可用:/images/action.jpg
。请注意,您必须提供以波浪号 ("~
") 开头的“根相对”路径,才能获得正确的相对路径。
我使用 FireBug 检查了为在标记中设置 ImageUrl
而渲染的 HTML
标记,发现其 HTML
与使用 Image1.ResolveUrl("~/images/action.jpg");
在代码隐藏中设置 ImageUrl
所渲染的 HTML
略有不同,这很有趣。以下是差异:
在标记中设置 ImageUrl
生成的 HTML
<img style="border-width: 0px;" src="../images/action.jpg" id="Image1">
在代码隐藏中设置 ImageUrl
生成的 HTML
标记<img style="border-width: 0px;" src="/WebSite3/images/action.jpg" id="Image1">
请注意,这两个 URL 略有不同。第一个是相对于当前页面在 /Pages/
中的位置,第二个是相对于 Web 应用程序的根文件夹。但是,无论哪种情况,请求的图像都将从浏览器中的正确位置呈现。
由于页面或用户控件也是控件,这意味着您也可以通过以下方式调用 ResolveUrl()
:Image1.ImageUrl = this.ResolveUrl("~/images/action.jpg");
或者,简单地Image1.ImageUrl = ResolveUrl("~/images/action.jpg");
VirtualPathUtility.ToAbsolute(string path)
由于每个控件都有 ResolveUrl()
方法,因此很容易调用它并获取任何服务器控件的正确相对路径(通过提供以波浪号“~
”运算符开头的根相对路径)。但是,如果您必须从 HttpHandler
或 HttpModule
,甚至从类库中解析相对路径呢?
您将无法在 Httphandler
或 HttpModule
中获取 Control 对象。因此,您无法调用 ResolveUrl()
方法。幸运的是,有一种简单的方法。您可以使用以下方法,该方法接受相同的参数(如 ResolveUrl()
)。
VirtualPathUtility.ToAbsolute("~/images/action.jpg")
除了这个方法,
VirtualPathUtility
还有许多其他实用方法,可以帮助计算各种路径相关逻辑,使我们的生活更轻松。
从相对 URL 获取物理文件路径
根据相对 URL 路径确定文件的物理位置是一种非常常见的需求。您可能需要读取和写入存储在 Web 应用程序文件夹中的文件,而您只有文件的相对路径/URL,而不是物理文件位置。您会怎么做?
幸运的是,我们有一个老朋友,Server.MapPath()。
当您使用相对路径/URL 调用 Server.MapPath()
时,它会返回存储在 Web 应用程序文件夹中的文件的完整物理位置。让我们看一些示例:
假设我们的 Web 应用程序的根文件夹位置如下:
C:\applications\aspnet\www.mysite.com\
string RootPath = Server.MapPath(“~”)
这将返回路径
“C:\applications\aspnet\www.mysite.com\”
string FilePath = Server.MapPath(“~/images/search.jpg”
这将返回路径
“C:\applications\aspnet\www.mysite.com\images\search.jpg”
请注意,
Server.MapPath()
返回的文件/文件夹的物理路径并不保证文件或文件夹确实存在。它只是根据 Web 应用程序的根目录将提供的相对路径映射到物理路径位置。在进行下一步之前,您有责任确保文件或文件夹的物理位置确实存在(使用 System.IO.Path.Exists(FilePath)
)。
Request 对象中的路径属性
Request 对象中有一些重要的路径属性,对于 Asp.net 极客来说是“必知”的。它们如下:
Request.ApplicationPath
这会返回相对于站点路径的应用程序路径。
请注意,“Application
”可以是虚拟目录或站点本身。因此,如果当前应用程序部署在站点下,对于 URL http://www.mysite.com/home.aspx
,它将简单地返回 “/”
而且,如果当前应用程序部署在虚拟目录下,对于
URL
http://www.mysite.com/account/home.aspx
(其中 account 是虚拟目录名称),这将返回路径 “/account”
如果您知道应用程序中的资源位置,此属性在计算应用程序中任何资源的相对
URL
时非常方便。
Request.FilePath
返回当前请求的相对或虚拟路径,以站点 URL
为准。也就是说,对于 URL http://www.mysite.com/account/home.aspx
,它将返回 /account/home.aspx
。无论应用程序是虚拟目录还是站点,都无关紧要。
Request.CurrentExecutionFilePath
与 Request.FilePath
(见上文)相同,只是它甚至在当前页面因调用 Server.Execute()
或 Server.Transfer()
而执行时也返回路径。
让我们看一个例子
您访问了一个 URL http://www.mysite.com/pages/home.aspx
并在 Home.aspx
中调用了 Server.Execute(“~/pages/common/CheckStatus.aspx”)
或 Server.Transfer(“~/pages/common/CheckStatus.aspx”)
现在,
CheckStatus.aspx
中的 Request.CurrentExecutionFilePath
将返回 /pages/common/checkstatus.aspx
(正在从 Home.aspx 执行的页面)
而且,CheckStatus.aspx
中的 Request.FilePath
将返回 /Pages/Home.aspx
(执行 Server.Execute()
或 Server.Transfer()
的原始页面)
Request.Path
这会返回 Request.FilePath
,包括任何查询字符串参数(如果存在)。
Request.PhysicalApplicationPath
这会返回 Web 应用程序的物理文件夹位置,无论它是站点还是虚拟目录。例如,C:\applications\aspnet\www.mysite.com\
Request.PhysicalPath
这会返回 URL
中当前请求文件的物理路径。例如,C:\applications\aspnet\www.mysite.com\home.aspx
总结:
-
如果任何用户控件具有任何路径属性(
src
或href
或ImageUrl
)的HTML
元素或Asp.net
服务器控件,则路径值应以"~/"
(波浪号) 开头,并且该元素应转换为服务器控件(通过添加runat=”server”
)。 -
如果任何页面具有任何路径属性的
HTML
元素,则路径值应以相对路径"/"
或"../"
开头(假设已设置正确的相对路径)。对于页面中的任何服务器控件,属性值应以"~/"
(波浪号)开头。 -
如果在 CodeBehind 文件中设置路径值,则必须使用
control.ResolveUrl(“~/path”)
。如果在HttpHandler
、HttpModule
或类库中设置路径值,则必须使用VirtualPathUtility.ToAbsolute(“~/path”)
。
对基础知识有清晰的理解是不是很棒?我真希望我早年就能有这样的理解!