CODE-使用ASP.NET 4.0 Routing處理圖檔下載
12 |
網友Slash提問:
如何在ASP.NET實現將/boo/a.jpg格式的網址導向ashx進行檔案下載?
一時程式毒癮發作,索性把這個題目當成自己的ASP.NET 4 Routing私房練習題止癢,也看看是否能順便解答Slash的疑問。
ASP.NET從4.0起加入內建Routing功能,相較於3.5 SP1做了不少強化,例如: 增加PageRouteHandler以簡便地完成WebPage導向、提供HttpRequest.RequestContext.RouteData方便路徑參數存取... 等等[參考]。
在這個練習中,我打算分別用WebForm(aspx)及Generic Handler(ashx)各實做一次。首要之務是在Global.asax中註冊路由,其中有個小把戲: 在ASP.NET 4中,可以直接用PageRouteHandler處理WebForm導向,卻沒有為ashx提供相似的機制(依Phil Haack的說法,想到時已來不及加進去了),因此要用Phil提供的因應解法,自行宣告一個HttpHandlerRouteHandler類別解決。我們將images/{imgname}註冊給Download.ashx、imgs/{imgname}註冊給DownloadImage.aspx。
<%@ Application Language="C#" %>
<%@ Import Namespace = "System.Web.Routing" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
//分別為ashx及aspx註冊路由, 一個用images/*.*, 一個用imgs/*.*
//ashx註冊時有個小Trick,見HttpHandlerRouteHandler說明
routes.Add("Images", new Route("Images/{imgname}",
new HttpHandlerRouteHandler<DownloadImage>()));
routes.MapPageRoute("Imgs", "Imgs/{imgname}",
"~/DownloadImage.aspx");
}
//ASP.NET 4不支援直接將IHttpHandler視為RouterHandler(太晚想到)
//在此引用Phil Haack的解決方案
//http://haacked.com/archive/2009/11/04/routehandler-for-http-handlers.aspx
public class HttpHandlerRouteHandler<THandler>
: IRouteHandler where THandler : IHttpHandler, new()
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new THandler();
}
}
接著看DownloadImage.ashx。由context.Request.RequestContext.RouteData.Values[“imgname”]取出路徑中的圖檔名,檢查該檔是否存在App_Data下,有則以Response.WriteFile()傳回內容,否則丟出404的HttpException,讓IIS視同網頁不存在處理。
using System;
using System.Web;
using System.IO;
public class DownloadImage : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var routeData = context.Request.RequestContext.RouteData.Values;
string file = context.Server.MapPath(
"~/App_Data/" + routeData["imgname"].ToString());
if (File.Exists(file))
{
var resp = context.Response;
//註: 理論上要由副檔名決定ContentType,此處省略
resp.ContentType = "image/gif";
resp.WriteFile(file);
resp.End();
}
else
{
throw new HttpException(404, "HTTP/1.1 404 Not Found");
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
再來是DownloadImage.aspx,寫法跟ashx接近,唯一的差別是在WebForm中throw HttpException會被IIS當成程式出錯,觸發HTTP 500程式錯誤例外,無法模擬HTTP 404的狀況,即便狀態碼為404,但在未開啟CustomErrors時,會視同程式錯誤顯示例外所在的原始碼內容,開啟後則出現Runtime Error及web.config customErrors mode設定提示,與熟知的找不到網頁訊息形式有些出入,故改為設定StatusCode跟輸出Not Found訊息代替。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
public partial class DownloadImage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string file = Server.MapPath("~/App_Data/" +
(RouteData.Values["imgname"] ?? string.Empty).ToString());
if (File.Exists(file))
{
//註: 理論上要由副檔名決定ContentType,此處省略
Response.ContentType = "image/gif";
Response.WriteFile(file);
Response.End();
}
else
{
//在WebForm中無法透過throw HttpException方式導向IIS 404錯誤頁
//故在此自行模擬傳回HTTP 404結果
Response.Clear();
Response.StatusCode = 404;
Response.Status = "404 Not Found";
Response.Write("Not Found");
Response.End();
}
}
}
執行結果如下:
- ashx正確讀取圖檔的情境
- ashx找不到圖檔的情境
- aspx正確讀取圖檔的情境
- aspx找不到圖檔的情境
Comments
# by 小黑
請問黑大,這手法是否同樣是適用 asp.net mvc 中?
# by Ammon
黑大,目前我還沒有遇過 aspx 拋出 HttpException 404 但 IIS 當成 500 的情況。即便是黃色死亡畫面,Status code 依然是 404。這中間是不是有甚麼誤會?
# by Jeffrey
to Ammon, 謝謝指正,的確狀態碼是404沒錯,原本的寫法有誤,我做了調整希望能更精準些。 ...即便狀態碼為404,但在未開啟CustomErrors時,會視同程式錯誤顯示例外所在的原始碼內容,開啟後則出現Runtime Error及web.config customErrors mode設定提示,與熟知的找不到網頁訊息形式有些出入,故改為設定StatusCode跟輸出Not Found訊息代替。
# by Jeffrey
to 小黑, 在ASP.NET MVC中,可以用HttpNotFoundResult。 http://weblogs.asp.net/gunnarpeipman/archive/2010/07/27/asp-net-mvc-3-using-httpnotfoundresult-action-result.aspx
# by Ammon
黑大,custom error 可以依據Status code 設定不同頁面
# by Edward
黑大您好: 我有一個圖片下載的問題, 我到美國gap集團的網頁看衣服, 發現一個奇怪的問題, 某些衣服點進去看後, 檢視原始碼時, 可以看到衣服圖片的網址, 如下:http://oldnavy.gap.com/browse/product.do?cid=26068&vid=1&pid=251613032 而有些衣服點進去看後, 檢視原始碼時, 會出現img src='/', 找不到圖片網址, 但網頁還是看得到圖片, 這是什麼原因呢?? 我利用HttpWebRequest、HttpWebResponse去下載該網站時,原始碼又和在瀏覽器時也不同, 這又是什麼原因呢?? 不好意思,問題有點長,麻煩您撥空看一下,謝謝。
# by Edward
補上看得到圖片,但是src="/"的網址。 如下: http://oldnavy.gap.com/browse/product.do?cid=26068&vid=1&pid=329791022 請用{<div id="productContentLeft"}尋找,謝謝。
# by Jeffrey
to Edward, 你所說的src="/"應該是指這個: <div id="productContentLeft" class="brand3"> <noscript> <div><img alt='Main product image: Men's Striped Slub-Knit Tees' id="product_image" src='/' /></div> </noscript> <img>被包在<noscript>中,意思是當瀏覽器不支援JavaScript時才適用,絕大多數瀏覽器因為支援JavaScript,看到的圖片並不是這個<img>,而是 id="product_image_bg">裡的<img id="product_image">,而該<img>的src已被JavaScript動態切換成衣服的圖檔(選顏色後會換圖)。 View Source看到的是一開始下載回來的HTML內容,事後JavaScript會去更動其中的src,要使用IE Dev Tools或Firefox之類的工具去看即時的HTML,才是當下的狀態。
# by Edward
了解了,謝謝大大的指導。
# by Edward
黑大您好,可以另外再向您請教一件事嗎?? 我在網址上打“http://oldnavy.gap.com/browse/productData.do?pid=330654022” 看到的內容,跟用HttpWebRequest、HttpWebResponse去下載的內容會完全不一樣。 如果我曾經在瀏覽器上看過該網頁,就會一樣了,請教您知道原因是什麼嗎??謝謝
# by Jeffrey
to Edward, 沒帶Cookie,會被導向 /browse/productData.do?targetURL=http%3A%2F%2Foldnavy.gap.com%2Fbrowse%2FproductData.do%3Fpid%3D330654022&CookieSet=Set,設完Cookie後才連至真正的網址。我猜HttpWebRequest下載到的內容是一個HTTP/1.1 302 Moved Temporarily轉址回應,你不妨比對看看。
# by Edward
嗯,謝謝黑大, 後來觀察封包後, 發現是cookie在作怪, 已經解決該問題了, 感激不盡