四、Web服務處理程序
對於Web服務來說,標准的方式是使用SOAP協議,在SOAP中,請求和回應的數據通過XML格式進行描述。在Asp.net 4.0下,對於Web服務來說,還可以選擇支持Ajax訪問,因此,Web服務的處理程序變得有一些復雜。為了同時支持者兩種類型的請求處理,在Asp.net 4.0下,處理程序工廠采用了兩級的結構,首先,通過標准的處理程序工廠來取得服務的處理程序,其次,在內部根據請求的內容來取得實際的處理程序工廠,最終,取得處理請求的處理程序。
1、Web服務處理程序工廠
在Asp.net 2.0中,對於Web服務的配置如下:
<add path="*.asmx" verb="*" type="System.Web.Services.Protocols.WebServiceHandlerFactory,System.Web.Services,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" validate="False" />
在Asp.net 4.0中,為了同時兼顧在Ajax中對於Web服務的訪問,系統中的Web服務修改為如下的配置形式:
<add path="*.asmx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory,System.Web.Extensions,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e53" validate="False" />
可以看到,Web服務處理程序工廠從WebServiceHandlerFactory變為了ScriptHandlerFactory。在ScriptHandlerFactory內部,定義了兩個處理程序工廠。在ScriptHandlerFactory內部,當通過GetHandler方法獲取一個處理程序對象實例的時候,將首先判斷請求是否是一個REST請求,根據判斷的結果來決定當前實際使用的處理程序工廠。
REST表示表述性狀態轉移,定義了應該如下正確地使用Web標准,例如HTTP和URI。如果在設計應用程序時能堅持REST原則,那就預示着將會得到一個使用了優質Web架構的系統。
2、使用Web服務處理程序
對於Web服務的處理程序來說,每個Web服務將在服務器上創建一個對應的asmx擴展名的標記文件,例如,一個新創建的Web服務WebService1.asmx中可能包含如下的內容:
<% WebService Language="C#" CodeBehind="WebServicel.asmx.cs" Class="WebService1" %>
在這個文件中,通過CodeBehind和Class說明了處理這個Web服務的代碼文件和其中實現的Web服務的類名。
當Web服務的處理工廠收到針對這個WebService1.asmx的請求的時候,將通過反射創建Class中說明的類的對象實例,並調用對應的方法完成服務的處理。
默認情況下,新建的WebService代碼如下:
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] // 若要允許使用 ASP.NET AJAX 從腳本中調用此 Web 服務,請取消對下行的注釋。 // [System.Web.Script.Services.ScriptService] public class Service : System.Web.Services.WebService { public Service () { //如果使用設計的組件,請取消注釋以下行 //InitializeComponent(); } [WebMethod] public string HelloWorld() { return "Hello World"; } }
貼有[WebMethod]標簽的方法將被作為Web服務的方法。Web服務的處理程序負責完成請求參數和返回參數的XML序列化任務。所以,我們僅僅需要些一個公共的、貼有[WebMethod]標簽的方法就可以了。
3、Web服務的常用標簽
在Web服務中,除了最常用的[WebMethod]標簽之外,Asp.net中還支持另外幾個重要的標簽:
- [ScriptService]標簽;
- [WebService]標簽;
- [WebServiceBinding]標簽;
- [SoapRpcMethod]標簽;
1、[ScriptService]
表示這個服務方法可以通過Asp.net AJAX訪問。當通過REST方式訪問這個服務的時候,服務器通過RestHandlerFactory返回一個處理程序,這個處理程序將完成請求和返回參數的JSON化任務。
2、[WebService]
由於Web服務從根本上講是通過XML來表示數據,所以,不管在服務器上使用什么類型的對象來表示數據,最終,這些數據必須轉換為XML才能使用,這就帶來了數據在兩個空間中有不同描述方法的問題。通過WebService這個標簽,可以設置這個Web服務的其他參數來區別:
[WebService(Namespace = "http://tempuri.org")]
- Name:設置Web服務的名稱,當希望用戶看到的Web服務名稱不同於類名的時候使用。
- Namespace:設置Web服務所使用的默認XML命名空間。
3、[WebServiceBinding]
WebServiceBinding標簽用來描述Web服務的綁定信息。
[WebServiceBinding(ConformsTo=WsProfiles.BasicProfile1_1)]
- ConformsTo:綁定需要遵守的WS-I標准。
- Name:獲取和設置綁定的名稱。
- Namespace:綁定關聯的命名空間。
- Location:綁定的位置,默認值為當前Web服務的URL。
EmitConformanceClaims,如果為true,當WSDL發布時,綁定會發出遵守的聲明。
4、[SoapRpcMethod]
SOAP擴展允許將SOAP消息的樣式聲明為文檔或RPC,SoapRpcMethod標簽用來描述RPC樣式的信息。
[SoapRpcMethod(OneWay=true)]
SoapDocumentMethod用來描述文檔樣式的消息。例如:
[SoapDocumentMethod(
Action="http://www.contoso.com/Sample",
RequestNamespace="http://www.contoso.com/Request",
RequestElementName="GetUserNameRequest",
ResponseNamespace="http://www.contoso.com/Response",
ResponseElementName="GetUserNameResponse"
)]
4、派生自System.Web.Services.WebService類的意義
派生自WebService的Web服務,可以直接訪問ASP.NET網站的狀態服務,例如,Application,Session等。
即使沒有派生自WebService類,也可以通過HttpContext訪問服務器。
五、MVC處理程序
在Asp.net MVC2中,請求將被首先被路由解析到Controller進行處理,然后由Controller分配到相應的Action完成實際的處理工作。處理的數據結果就是Model,然后這個Model被傳遞到View轉換成顯示的界面元素,最終發送到客戶端完成處理任務。
在Asp.net MVC2中,整個MVC的路由中心也是一個處理程序,由這個處理程序再通過一個控制器的工廠來取得實際的Controller,開始處理工作。默認情況下,這個處理程序的類型是MvcRouteHandler,這個處理鄭旭可以在routes.MapRoute方法中進行指定,例如,默認的MVC設置如下:
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults );
示意圖如下:
1、MVC的路由接口IRouteHandler
起着路由作用的處理程序不是一個普通的處理程序,因為MVC根本就不可能通過請求的擴展名來判斷。所以,這個處理程序通過一個類型為UrlRoutingModule的Module配置在網站中,通過HttpApplication的事件管道得到這個處理程序。
在Asp.net MVC中,路由處理程序必須實現接口IRouteHandler。IRouteHandler定義在命名空間System.Web.Routing下,定義了一個獲取MVC處理程序的方法GetHttpHandler。
public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }
默認情況下,Asp.net MVC2使用MvcRouteHandler這個處理程序,RouteCollection還提供了Add方法,可以通過提供一個Route類型的參數指定特定的路由處理程序。設置方法如下:
public static void RegisterRoutes(RouteCollection routes) { routes.Add(new Route("Category/{action}/{categoryName}",new CategoryRouteHandler())); }
2、自定義的IRouteHandler
下面實現一個能夠顯示當前路由表的路由處理程序。如果用戶請求地址的最后為?routeinfo,那么將返回一個匹配當前路由的描述。
首先是類的定義,這個類必須實現接口IRouteHandler,在GetHttpHandler中,我們檢查請求是否以?routeinfo結束,如果是的話,調用自定義的方法返回當前的路由描述串,否則,通過默認的MvcRouteHandler返回控制器。
public class ShowRouteHandler : System.Web.Routing.IRouteHandler { static readonly Regex regex = new Regex(@"^\?routeinfo$", RegexOptions.IgnoreCase); public IHttpHandler GetHttpHandler(RequestContext requestContext) { //如果URL以?routeinfo結束,則顯示路由 if (regex.IsMatch(requestContext.HttpContext.Request.Url.Query)) { OutputReouteTable(requestContext); } //否則使用默認的MvcRouteHandler返回一般的結果 IRouteHandler handler = new MvcRouteHandler(); return handler.GetHttpHandler(requestContext); } private void OutputReouteTable(RequestContext requestContext) { HttpResponseBase response = requestContext.HttpContext.Response; System.Web.Routing.RouteData DATA = requestContext.RouteData; Table table = new Table(); foreach (Route route in RouteTable.Routes) { TableRow row = new TableRow(); table.Rows.Add(row); TableCell cell = new TableCell(); row.Cells.Add(cell); Label lbl = new Label(); lbl.Text = route.Url; cell.Controls.Add(lbl); if (route.GetRouteData(requestContext.HttpContext) != null) { lbl.ForeColor = System.Drawing.Color.Black; } else { lbl.ForeColor = System.Drawing.Color.Gray; } } HtmlTextWriter writer = new HtmlTextWriter(response.Output); table.RenderControl(writer); response.End(); } }
3、注冊路由處理程序
在Global.asax的RegisterRoutes方法中,使用自定義的路由處理程序替換默認的路由處理程序,代碼如下:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //定義自己的路由處理程序 RouteTable.Routes.Add( new Route( "{controller}/{action}/{id}", new RouteValueDictionary(new { Action = "Index", id = (string)null }), new ShowRouteHandler() //使用自己的RouteHandler ) ); }
顯示效果如下:
4、獲取控制器的工廠接口IControllerFactory
在Asp.net MVC2中,獲取控制器的工廠接口為IControllerFactory,這個接口定義在命名空間System.Web.Mvc下,具體的定義如下:
public interface IControllerFactory { IController CreateController(RequestContext requestContext,string controllerName); void ReleaseController(IController controller); }
默認情況下,我們使用定義在命名空間System.Web.Mvc下的DefaultControllerFactory實現。
public class DefauleControllerFactory : IControllerFactory
5、MVC請求的處理過程
如表:
處理步驟 | 說明 |
當網站應用程序收到第一次請求的時候 | 在Global.asax中,將Route對象添加到RouteTable對象中 |
實施路由 | UrlRoutingModule使用第一個匹配的Route對象創建RouteData參數對象,然后創建RequestContext對象 |
創建MVC路由處理對象 | MvcRouteHandler對象創建一個MvcHandler類的對象實例 |
創建控制器 | MvcHandler對象通過IControllerFactory對象,一般使用DefaultControllerFactory這個實現類創建控制器的對象實例 |
執行控制器 | MvcHandler調用控制器的Execute方法 |
調用Action | Controller檢查被調用的Action,然后執行Action方法 |
處理返回結果 | Action方法接收用戶的輸入參數,處理后得到准備顯示的數據,通過返回一個ViewResult類型的結果用於輸出。 |
六、資源處理程序
資源處理程序AssemblyResourceLoader允許程序員通過HTTP訪問嵌入在網站程序集中的資源,例如:script腳本、圖片或者數據文件等。
AssemblyResourceLoader通過GetWebResourceUrl方法得到一個簽入資源的地址,通過這個地址,瀏覽器可以通過請求得到嵌入的資源。具體資源地址通過請求參數傳遞給AssemblyResourceLoader,地址形式如下:
WebResource.axd?d=<encrypted identifier>&t=<time stamp value>
- <encryped identifier>用來唯一標識這個嵌入的Web資源;
- <time stamp value>是一個時間戳,當程序集發生變化的時候可以被檢測出來,以便使緩沖的資源失效;
1、資源處理程序的配置
資源處理程序已經在系統的web.config中進行了配置,可以直接在程序中使用,代碼如下:
<add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" />
2、定義嵌入的資源
WebResourceAttribute標簽必須附加在嵌入資源的程序集中。
WebResourceAttribute標簽用來說明嵌入在程序集中的資源。它接受兩個參數:一個是嵌入資源的名稱,另一個是資源的MIME類型。例如:
[assembly:WebResource("image1.jpg","image/jpeg")]
資源名稱並不一定是資源文件的文件名,這個名稱是由資源所在的命名空間加上資源文件名組成,如果文件在一個文件夾中,相當於增加了一層與文件夾同名的命名空間。注冊和使用的時候,都是以資源名稱為准的。
例如,項目的默認命名空間是MySpace,如果client.js在項目的根目錄下,那么實際的資源名稱就是MySpace.client.js,如果文件還被放在目錄script下,那么實際的資源名稱就是MySpace.script.client.js。
從.Net 2.0開始,還提供了一個PerformSubstitution的屬性,用來指示是否分析資源中的WebResource。如果資源中還包含其他資源的引用,注意設置為true。代碼如下:
[assembly: WebResource("help.htm","text/html",PerformSubstitution=true)]
例如,我們可以將上邊用到的腳本以文件的形式加入到類庫項目中,然后,在項目中通過標簽設置這個資源將在Web中使用,如下所示:
[assembly:WebResource("MySpace.client.js","text/javascript")]
3、獲取資源的地址
如果需要在HTTP中使用嵌入的資源,必須獲取嵌入資源的地址,這可以通過ClientScriptManager對象的方法GetWebResourceUrl得到。通常我們通過頁面對象的ClientScript屬性來得到這個對象,方法的定義如下:
public string GetWebResourceUrl(Type type,string resourceName)
其中type表示資源所在的程序集中定義的某個對象類型,resourceName表示資源的名稱。GetWebResourceUrl方法返回一個沒有經過編碼的URL地址,使用這個地址可以得到嵌入在網站程序集中的資源,資源可以是腳本、圖片或者任何靜態的文件:
//獲取一個定義在包含資源的程序集中的類的類型 System.Type type = typeof(MySpace.FileItem); string url = this.ClientScript.GetWebResourceUrl(type,"MySpace.Client.js");
4、使用嵌入的資源
在沒有RegisterClientScriptResource之前,我們通常都要自己從程序集中獲取資源,在嵌入到網頁中:
Type type = typeof(MySpace.FileItem); Stream stream = type.Assembly.GetManifestResourceStream("MySpace.client.js"); using(TextReader reader = new StreamReader(stream)) { string script = reader.ReadToEnd(); this.ClientScript.RegisterClientScriptBlock(this.GetType(),"script",script); }
RegisterClientScriptResource也是ClientScriptManager中的一個方法,用於在網頁中嵌入資源,通過RegisterClientScriptResource,我們可以簡單地如下完成:
this.ClientScript.RegisterClientScriptResource(type,"MySpace.client.js");
type為嵌入資源的程序集中定義的某個類型,第二個參數就是資源的名稱。
下面再給出一個在MVC中顯示嵌入的圖片資源的例子:
public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult GetImg() { Assembly myAssembly = Assembly.GetExecutingAssembly(); Stream myStream = myAssembly.GetManifestResourceStream("MvcApplication1.meinv.jpg"); Bitmap bmp = new Bitmap(myStream); using (MemoryStream ms = new MemoryStream()) { bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); return File(ms.ToArray(), "image/jpeg"); } } }
其中,meinv.jpg是一張普通的圖片,但設置成了前途的資源。
視圖代碼如下:
<img src="/Home/GetImg" />
再給一個顯示所有前途資源的例子:
Assembly myAssembly = Assembly.GetExecutingAssembly(); string[] names = myAssembly.GetManifestResourceNames(); foreach (string name in names) { Response.Write(name); }
七、禁止的處理程序
對於一個Web程序來說,有一些服務器資源是不允許客戶端直接請求獲取的,例如網站的配置文件、App_Code文件夾中的代碼文件等。
對於不允許客戶端獲取的文件,可以將這些請求的處理程序映射到一個特殊的處理程序來解決,這個處理程序簡單地返回一個禁止訪問的回應即可。在Asp.net中這個處理程序的類型是HttpForbiddenHandler。HttpForbiddenHandler直接返回一個狀態碼為403的HTTP回應,來表示訪問被拒絕。可以使用它來禁止所希望禁止的任何請求,在系統的web.config中,已經將項目文件、代碼文件以及其他類型被禁止客戶端訪問的文件映射到了這個處理程序。
1、配置禁止訪問的資源
在系統的web.config配置文件中,已經使用這個處理程序對禁止訪問的資源進行了配置。
例如,對於C#代碼文件的配置如下:
<add path="*.cs" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
其他還包括:*.asax,*.ascx,*.master,*.skin,*.browser,*.sitemap,*.config,*.csproj.....
2、禁止訪問Excel
如果在Web服務器上使用Excel保存了一些數據,並且這個Excel文件被保存在網站的可訪問目錄下,那么客戶端如果猜測出這個Excel文件的路徑,就可能通過它的地址直接將其下載。
在Asp.net中,通過在web.config中簡單地將*.xls和*.xlsx的請求映射到禁止訪問的處理程序上就可以解決這個問題:
<add path="*.xls" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" /> <add path="*.xlsx" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
八、虛擬路徑提供器
1、定義虛擬路徑提供器
在Asp.net網站應用程序中,網站應用程序中的資源可以分為兩類,一類是固定定位的,另一類是可通過虛擬目錄動態訪問的。
第一類的資源都是必須保存在固定的位置,使用固定命名規則的資源:
- Gloval.asax:全局應用程序類。
- web.config:網站配置文件。
- 使用XmlSiteMapProvider的站點地圖文件。
- bin文件夾下面的程序集,App_Code文件夾下面的代碼文件,全局資源文件夾App_GlobalResources下面的資源,任何的本地資源App_LocalResources。
- App_Data:網站應用程序的數據文件夾。
第二類資源是通過虛擬目錄訪問的資源。包括以下的類型:
- Asp.net頁面,模板頁,用戶控件,以及其他生成的對象。
- 標准的Web資源,例如,擴展名為.htm和.jpg的資源等。
- 任何映射到BuildProvider實例的自定義擴展。
- 在App_Theme文件夾中的命名主題。
對於第二類資源,默認情況下是直接在網站的文件中進行定位,比如,我們訪問網站根目錄下的default.aspx,那么網站應用程序程序將在網站的根目錄下尋找這個defaule.aspx文件。在Asp.net 2.0之后,通過虛擬目錄訪問的資源的過程是可定義的。
System.Web.Hosting命名空間下的抽象基類VirtualPathProvider定義了定位資源的約定如下:
public abstract class VirtualPathProvider:MarshalByRefObject
在網站中,虛擬目錄通過類型VirtualDirectory表示,這個類型也定義在同一個命名空間下:
public avstract class VirtualDirectory : VirtualFileBase
虛擬目錄中的文件通過VirtualFile表示:
public abstract class VirtualFile : VirtualFileBase
對於VirtualFile來說,可以通過對象的Open方法獲得一個字節流,以便讀取文件的實際內容:
public abstract Stream Open()
當實現自定義的VirtualPathProvider的時候,必須至少實現下面列出的兩個方法:FileExists和GetFile。
如果實現中還涉及虛擬文件系統中的目錄,那么,還必須實現關於目錄的兩個方法:DirectoryExists和GetDirectory。
2、注冊虛擬路徑提供器
必須注冊自定義的VirtualPathProvider,才能在網站應用程序中使用。注冊必須在第一次通過虛擬文件系統定位資源之前進行。在.Net 4.0之前,有兩種注冊的方法:
- 通過Global.asax中的Application_Start事件注冊。
- 通過定義在App_Code文件夾中任意類中的AppInitialize靜態方法。
AppInitialize方法的原型定義如下:
public static void AppInitizalize()
AppInitialize方法是Asp.net中一個特殊的方法,在網站應用程序啟動之后進行初始化的時候將被首先調用。這個方法只能出現一次,只能出現在App_Code中定義的一個類中,如果出現在兩個類中,Asp.net將會報編譯錯誤。這個方法甚至不能定義在一個程序集中定義的類中。
通過Global.asax中Application_Start方式進行注冊:
void Application_Start(object sender,EventArgs e) { MyVirtualPathProvider virtualPathProvider = new MyVirtualPathProvider(); System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider); }
通過AppInitialize靜態方法完成注冊:
public class MyAppStart { public static void AppInitialize() { MyVirtualPathProvider virtualPathProvider = new MyVirtualPathProvider(); System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider); } }
Asp.net 4開始提供了一個新的特性PreApplicationStartMethod,允許我們在網站初始化之前完成網站的初始化,這樣,我們可以在一個類庫項目中使用這個特征來標記需要在網站中提前初始化的方法。但是在多個程序集的情況下,不能保證調用程序集定義的應用程序啟動方法的順序。
因此,每個注冊的開始方法應該將代碼編寫為分開運行,不應該依賴於其他注冊開始方法的副作用。需要注意的是方法必須是一個沒有參數的公共、靜態方法:
[assembly:System.Web.PreApplicationStartMethod(typeof(AppStart),"AppInitialize")] public class AppStart { public static void AppInitialize() { ZipVirtualPathProvider virtualPathProcider = new ZipVirtualPathProvider(); System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider); } }
在調用RegisterVirtualPathProvider進行注冊的時候,根據注冊的提供程序的先后次序,將會形成一個虛擬路徑提供程序鏈,在Asp.net網站中,已經注冊了一個基於網站目錄的提供程序。后注冊的提供程序可以通過Previous屬性找到前面注冊的提供程序,這樣,當前的虛擬路徑提供程序不能獲取資源的時候,可以通過上一個提供程序來提供。
例如,我們可以通過一個壓縮文件提供網站中擴展的資源,而不需要在網站中創建額外的目錄和文件,當壓縮文件中不能提供請求的資源時,再繼續通過提供程序鏈到網站的目錄中尋找匹配的資源。
3、壓縮網站中的文件
為了說明自定義虛擬路徑,這里弄個示例,僅僅用一個壓縮包存放一個網站的多個文件。
這個東西是要需要通過實現3個抽象類來實現:
- System.Web.Hosting.VirtualPathProvider;
- System.Web.Hosting.VirtualDirectory;
- System.Web.Hosting.VirtualFile;
因為,我真的不知道如何去縮減,代碼比較多。首先新建一個Web項目,然后添加如下3個類:
1、虛擬目錄提供者類:
public class ZipVirtualPathProvider : System.Web.Hosting.VirtualPathProvider { private string fileRootPath; private string virtualRootPath; private ZipVirtualDirectory root; public ZipVirtualPathProvider() { this.fileRootPath = HttpRuntime.AppDomainAppPath; this.virtualRootPath = HttpRuntime.AppDomainAppVirtualPath + "/"; string path = string.Format("{0}App_Data\\test.zip", fileRootPath); using (ZipFile zipFile = new ZipFile(path)) { // 創建根目錄 this.root = new ZipVirtualDirectory(virtualRootPath); foreach (ICSharpCode.SharpZipLib.Zip.ZipEntry entry in zipFile) { string name = string.Format("{0}{1}", this.virtualRootPath, entry.Name); VirtualDirectory parent = GetParentDirectory(name); ZipVirtualDirectory zipParent = parent as ZipVirtualDirectory; System.Web.Hosting.VirtualFileBase vfb; if (entry.IsDirectory) { vfb = new ZipVirtualDirectory(name); } else { System.IO.Stream stream = zipFile.GetInputStream(entry); int size = (int)entry.Size; byte[] buffer = ReadAllBytes(stream, size); vfb = new ZipVirtualFile(name, buffer); } zipParent.AddVirtualItem(vfb); } } } //尋找子目錄所屬的父目錄 private VirtualDirectory GetParentDirectory(string virtualPath) { // 從根目錄開始找,直到找不到為止,說明就是所屬的父目錄,加入父目錄中 VirtualDirectory root = this.root; while (true) { bool isContinue = false; foreach (VirtualDirectory dir in root.Directories) { if (virtualPath.StartsWith(dir.VirtualPath)) { root = dir; isContinue = true; break; } } if (isContinue) continue; // 如果都不是,那么,當前的 root 就是其父目錄 return root; } } //是否存在目錄 public override bool DirectoryExists(string virtualDir) { /// 從根目錄開始遞歸尋找 bool result = SearchDirectory(this.root, virtualDir); if (result) { return true; } return this.Previous.DirectoryExists(virtualDir); } //搜索目錄 private bool SearchDirectory(VirtualDirectory parent, string virtualDir) { if (parent.Name == virtualDir) { return true; } foreach (VirtualDirectory child in parent.Directories) { bool result = SearchDirectory(child, virtualDir); if (result) { return true; } } return false; } //判斷文件是否存在 public override bool FileExists(string virtualPath) { //只檢查壓縮包的一級目錄(有就有,沒有就沒有) foreach (VirtualFile file in this.root.Files) { if (file.Name.Replace("//", "/") == virtualPath) { return true; } } return this.Previous.FileExists(virtualPath); } public override System.Web.Hosting.VirtualDirectory GetDirectory(string virtualDir) { VirtualDirectory dir = GetParentDirectory(virtualDir); // 不存在的話,找到父目錄,存在的話,找到自己。 bool exist = dir.VirtualPath == virtualDir; if (exist) { return dir; } return this.Previous.GetDirectory(virtualDir); } public override System.Web.Hosting.VirtualFile GetFile(string virtualPath) { //找到可能存在的目錄 VirtualDirectory dir = GetParentDirectory(virtualPath); // 遍歷查找 foreach (VirtualFile file in dir.Files) { if (file.VirtualPath == virtualPath) { return file; } } return this.Previous.GetFile(virtualPath); } public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart) { // 由於采用了壓縮文件,所以不生成緩存依賴對象 return null; } private byte[] ReadAllBytes(System.IO.Stream stream, int size) { byte[] buffer = new byte[size]; int count = stream.Read(buffer, 0, size); return buffer; } }
虛擬目錄與虛擬文件類:
public class ZipVirtualDirectory : System.Web.Hosting.VirtualDirectory { //保存文件夾中包含的子項目的集合 //包括文件和子目錄 private List<VirtualFileBase> items; private List<VirtualFile> files; private List<VirtualDirectory> directories; private string name; public override System.Collections.IEnumerable Files { get { return this.files; } } public override System.Collections.IEnumerable Children { get { return this.items; } } public override System.Collections.IEnumerable Directories { get { return directories; } } public override string Name { get { return base.Name; } } public ZipVirtualDirectory(string name) : base(name) { this.items = new List<VirtualFileBase>(); this.directories = new List<VirtualDirectory>(); this.files = new List<VirtualFile>(); this.name = name; } // 在目錄中增加一個項目 public void AddVirtualItem(VirtualFileBase item) { this.items.Add(item); if (item.IsDirectory) { this.directories.Add(item as VirtualDirectory); } else { this.files.Add(item as VirtualFile); } } } public class ZipVirtualFile : System.Web.Hosting.VirtualFile { public override string Name { get { return this.name; } } public override System.IO.Stream Open() { return new System.IO.MemoryStream(this.buffer); } private string name; private byte[] buffer; public ZipVirtualFile(string name, byte[] buffer) : base(name) { this.name = name; this.buffer = buffer; } }
注冊虛擬目錄提供程序,在App_Code目錄里面添加一個AppStart類:
public class AppStart { public static void AppInitialize() { ZipVirtualPathProvider virtualPathProvider = new ZipVirtualPathProvider(); System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(virtualPathProvider); } }
然后新建一個zip壓縮包,里面有如下兩個頁面:
然后啟動項目,打開路徑:/test/1.html。
網站程序中是不存在"test"這個目錄的,但是自定義的虛擬目錄將對此路徑的訪問映射到"test.zip"里面的1.html去了。這就是自定義虛擬路徑提供器的強大之處。