ASP.NET 管理网站(虚拟目录)

时间:2022-08-24 12:18:36

       部署了网站之后,就可以通过 IIS 的功能来管理网站宿主和执行的方式。

 

创建新站点

       IIS 7 能够在单台服务器上支持多个站点。要创建新站点,展开 IIS 管理器的树控件,右击“网站”,选择“添加网站”,会显示如下对话框:

       ASP.NET 管理网站(虚拟目录)

       网站名称中可以填写有意义的内容,不过,它只用于在 IIS 里标识网站,但并不影响网站的内容。物理路径指定了 IIS 7 到哪里寻找服务新网站请求的内容。我们创建了一个新目录 C:\FileCopySite。“连接为”和“测试设置”按钮可以指定用于访问网站内容的不同用户身份。

       “绑定”一节可以指定 IIS 7 如何监听来自客户端的请求。IIS 7 支持一系列的网络协议,“类型”下拉列表有众多选项,但我们只关注 HTTP,因为它是最广泛被使用的。IP 地址我们采用默认的“全部未分配”,也就是说 IIS 监听处理其他网站在同一 TCP 端口上已经被服务的网络接口之外的全部网络接口。“端口”值指定 IIS 7 要监听客户端请求的 TCP 端口号,一般而言每个网站必须仅由唯一的端口提供服务,因此我们选择了 81,这样不会和默认网站的 80 端口相冲突。

       勾选了“立即启动网站”,它表示在我们单击“确定”之后,IIS 就创建网站并开始监听请求。现在单击确定。

 

创建虚拟目录

       在之前的部署网站系列文章里,我们把内容放到了 IIS 7 寻找内容的默认目录里。但我们也可以不这样做,而是把内容放到其他地方,然后通过虚拟目录来引用它。为了演示,我们在服务器上创建一个新目录,并把网站的内容复制到了那里,新目录路径如下:

C:\WebSiteDeployment\VirtualDirectory

       为了把新目录关联到 IIS ,右击“默认网站”,选择“添加虚拟目录”,会打开如下对话框:

       ASP.NET 管理网站(虚拟目录)

       我们希望网站的 URL 是这样的:/virtual">/virtual">/virtual">http://<servername>/virtual,因此在别名里输入了 virtual。单击“确定”创建虚拟目录。为了测试它,浏览器现在访问 URL http://localhost/virtual,你会看到自己的简单网站,但这一回内容是以新目录作为源的,并且通过自定义的 URL 来进行访问。

       甚至可以有多个虚拟目录指向一个实际物理路径,访问同一资源时,显示的 URL 是不同的。

ASP.NET 管理网站(虚拟目录) ASP.NET 管理网站(虚拟目录)

 

      

使用 VirtualPathProvider

       VirtualPathProvider 类提供了虚拟目录之外的另一个选择,不再由文件系统提供网站内容,内容可以通过编程生成或者取自数据库。

       下面我将通过一个从 SQL Server 表读取 ASPX 文件的简单示例来理解 VirtualPathProvider 类。我们在本地 SQL Server 上有一个如下图所示的数据库表,里面保存了 3 个页面:

       ASP.NET 管理网站(虚拟目录)

       你可以看到表里有一个文件名(同时还是主键)和真是内容。内容可以是 ASP.NET 能够理解的任意类型的代码。

 

      

       VirtualPathProvider 类被定义在 System.Web.Hosting 命名空间里。在 App_Code 目录里新增一个类并继承 VirtualPathProvider,至少要实现以下方法:

using System;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Web.Hosting;
 
// VirtualPathProvider: 使 Web 应用程序可以从虚拟文件系统中检索资源
public class DBPathProvider : VirtualPathProvider
{
    public static void AppInitialize()
    {
        // HostingEnvironment: 在托管应用程序的应用程序域内向托管应用程
        // 序提供应用程序管理功能和应用程序服务
        // RegisterVirtualPathProvider(): 在 ASP.NET 编译系统中注册新
        // 的 VirtualPathProvider 实例
        HostingEnvironment.RegisterVirtualPathProvider(new DBPathProvider());
    }
 
    // VirtualPathProvider.FileExists(): 获取一个值,该值指示文件是否
    // 存在于虚拟文件系统中
    public override bool FileExists(string virtualPath)
    {
        throw new Exception("The method or operation is not implemented.");
    }
 
    // VirtualPathProvider.GetFile(): 从虚拟文件系统中获取一个虚拟文件
    public override VirtualFile GetFile(string virtualPath)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}

       提供程序必须实现一个静态的 AppInitialize 方法,这个方法应创建自定义类的实例并把它注册为 Framework 的提供程序;FileExists 方法用于检查某个路径是否可以由提供程序提供;GetFile 方法用于获取某个路径的内容,返回抽象类 VirtualFile 的实例。

 

       Framework 没有提供 VirtualFile 类的具体实现,我们必须自己扩展 VirtualFile 类来支持自己的提供程序。下面是我们的提供程序的 VirtualFile 类的实现,它被放在同一个代码文件中:

public class DBVirtualFile : VirtualFile
{
    private string _FileContent;
 
    public DBVirtualFile(string virtualPath, string fileContent)
        : base(virtualPath)
    {
        _FileContent = fileContent;
    }
 
    public override Stream Open()
    {
        Stream stream = new MemoryStream();
        StreamWriter write = new StreamWriter(stream,Encoding.Unicode);
        write.Write(_FileContent);
        write.Flush();
        stream.Seek(0, SeekOrigin.Begin);
        return stream;
    }
}

       这个类的构造函数获取虚拟路径以及文件的内容。在 Open 方法里,真实内容的字符串被保存到了 MemoryStream,然后返回这个流。ASP.NET 使用这个流读取内容,就好像它在读取文件系统一样(感谢基于 Stream 类的字节抽象)。

 

       下一步是继续完成 VirtualPathProvider 类。它需要读取来自数据库文件的真实数据。如果文件在数据库里不存在,提供程序就把请求转送给它的前一个提供程序(由框架在静态方法 AppInitialize 里注册时选定)。给 DBPathProvider 类增加一个从数据库获取内容的方法

private string GetFileFromDB(string virtualPath)
{
    string contents;
    string fileName = virtualPath.Substring(virtualPath.IndexOf('/', 1), +1);
 
    // Read the file from the database.
    SqlConnection conn = new SqlConnection();
    conn.ConnectionString = @"Data Source=.\SqlExpress;Initial Catalog=AspNetContents;
        Integrated Security=true";
    conn.Open();
 
    try
    {
        SqlCommand cmd = new SqlCommand("select FileContents from AspContent "
            + "where FileName=@fn", conn);
        cmd.Parameters.AddWithValue("@fn", fileName);
        contents = cmd.ExecuteScalar() as string;
        if (contents == null)
        {
            contents = string.Empty;
        }
    }
    catch
    {
        contents = string.Empty;
    }
    finally
    {
        conn.Close();
    }
    return contents;
}

 

       GetFileFromDB 函数从虚拟路径获取文件名并从数据库读取该文件名对应的内容。此方法将同时被 FileExists() 和 GetFile() 使用

// VirtualPathProvider.FileExists(): 获取一个值,该值指示文件是否
// 存在于虚拟文件系统中
public override bool FileExists(string virtualPath)
{
    string contents = this.GetFileFromDB(virtualPath);
    if (contents.Equals(string.Empty))
    {
        return false;
    }
    else
    {
        return true;
    }
}
 
// VirtualPathProvider.GetFile(): 从虚拟文件系统中获取一个虚拟文件
public override VirtualFile GetFile(string virtualPath)
{
    string contents = this.GetFileFromDB(virtualPath);
    if (contents.Equals(string.Empty))
    {
        // VirtualPathProvider.Previous: 获取对编译系统中以前注册的 
        // System.Web.Hosting.VirtualPathProvider 对象的引用
        return Previous.GetFile(virtualPath);
    }
    else
    {
        return new DBVirtualFile(virtualPath, contents);
    }
}

       你还可以在自己的提供程序里实现一些额外的方法,它们对更复杂的模型会很有用。比如,验证目录是否存在(DirectoryExists)、计算文件散列值(GetFileHash),以及执行缓存验证(GetCacheDependency)等等

 

       现在输入以下3个请求URL可以看见数据库中这 3 个简单网页的效果:

       ASP.NET 管理网站(虚拟目录)

       现在你不使用网站的文件系统,也可也动态的生成网页了,将网站部署到服务器即可。

 

       示例完整代码如下:

using System;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Web.Hosting;
 
// VirtualPathProvider: 使 Web 应用程序可以从虚拟文件系统中检索资源
public class DBPathProvider : VirtualPathProvider
{
    public static void AppInitialize()
    {
        // HostingEnvironment: 在托管应用程序的应用程序域内向托管应用程
        // 序提供应用程序管理功能和应用程序服务
        // RegisterVirtualPathProvider(): 在 ASP.NET 编译系统中注册新
        // 的 VirtualPathProvider 实例
        HostingEnvironment.RegisterVirtualPathProvider(new DBPathProvider());
    }
 
    // VirtualPathProvider.FileExists(): 获取一个值,该值指示文件是否
    // 存在于虚拟文件系统中
    public override bool FileExists(string virtualPath)
    {
        string contents = this.GetFileFromDB(virtualPath);
        if (contents.Equals(string.Empty))
        {
            return false;
        }
        else
        {
            return true;
        }
    }
 
    // VirtualPathProvider.GetFile(): 从虚拟文件系统中获取一个虚拟文件
    public override VirtualFile GetFile(string virtualPath)
    {
        string contents = this.GetFileFromDB(virtualPath);
        if (contents.Equals(string.Empty))
        {
            // VirtualPathProvider.Previous: 获取对编译系统中以前注册的 
            // System.Web.Hosting.VirtualPathProvider 对象的引用
            return Previous.GetFile(virtualPath);
        }
        else
        {
            return new DBVirtualFile(virtualPath, contents);
        }
    }
 
    private string GetFileFromDB(string virtualPath)
    {
        string contents;
        string fileName = virtualPath.Substring(virtualPath.IndexOf('/', 1) + 1);
 
        // Read the file from the database.
        SqlConnection conn = new SqlConnection();
        conn.ConnectionString = @"Data Source=.\SqlExpress;Initial Catalog=AspNetContents;
            Integrated Security=true";
        conn.Open();
        try
        {
            SqlCommand cmd = new SqlCommand("select FileContents from AspContent "
                + "where FileName=@fn", conn);
            cmd.Parameters.AddWithValue("@fn", fileName);
            contents = cmd.ExecuteScalar() as string;
            if (contents == null)
            {
                contents = string.Empty;
            }
        }
        catch
        {
            contents = string.Empty;
        }
        finally
        {
            conn.Close();
        }
        return contents;
    }
}
 
public class DBVirtualFile : VirtualFile
{
    private string _FileContent;
 
    public DBVirtualFile(string virtualPath, string fileContent)
        : base(virtualPath)
    {
        _FileContent = fileContent;
    }
 
    public override Stream Open()
    {
        Stream stream = new MemoryStream();
        StreamWriter write = new StreamWriter(stream, Encoding.Unicode);
        write.Write(_FileContent);
        write.Flush();
        stream.Seek(0, SeekOrigin.Begin);
        return stream;
    }
}

       总结一下整篇代码的工作顺序与原理。

  1. AppInitialize() 被 Framework 调用。我们向 ASP.NET 编译系统注册了自定义的 DBPathProvider 类,使得这个 Web 应用程序可以从虚拟文件系统中检索资源。
  2. FileExists(string virtualPath) 被调用。它告诉系统是否存在请求路径,如果存在,则通知程序继续查找内容(事件激发系统继续调用 GetFile 方法)
  3. GetFile(string virtualPath) 被调用。如果获取不到文件内容,则将请求交由编译系统以前注册的提供程序处理(回复到 ASP.NET 网页请求的默认处理流程)。如果成功获取数据库中对应文件名的网页内容,则创建 VirtualFile 类的实例(我们的扩展类 DBVirtualFile 实现了它),同时传入路径和网页内容至 DBVirtualFile 的构造函数
  4. Open() 被调用。它将字符串表现形式的网页内容读取到内存流中并返回这个流。
  5. ASP.NET 使用这个流读取内容,就好像它在读取文件一样!

       上述方法的调用均由 .NET 内部事件机制做驱动,并无显式的调用逻辑和顺序。