Asp.net处理程序(第六篇)

四、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网站应用程序中,网站应用程序中的资源可以分为两类,一类是固定定位的,另一类是可通过虚拟目录动态访问的。

  第一类的资源都是必须保存在固定的位置,使用固定命名规则的资源:

  1. Gloval.asax:全局应用程序类。
  2. web.config:网站配置文件。
  3. 使用XmlSiteMapProvider的站点地图文件。
  4. bin文件夹下面的程序集,App_Code文件夹下面的代码文件,全局资源文件夹App_GlobalResources下面的资源,任何的本地资源App_LocalResources。
  5. App_Data:网站应用程序的数据文件夹。

  第二类资源是通过虚拟目录访问的资源。包括以下的类型:

  1. Asp.net页面,模板页,用户控件,以及其他生成的对象。
  2. 标准的Web资源,例如,扩展名为.htm和.jpg的资源等。
  3. 任何映射到BuildProvider实例的自定义扩展。
  4. 在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之前,有两种注册的方法:

  1. 通过Global.asax中的Application_Start事件注册。
  2. 通过定义在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去了。这就是自定义虚拟路径提供器的强大之处。

原文地址:https://www.cnblogs.com/kissdodog/p/3540397.html