65.9K
CodeProject 正在变化。 阅读更多。
Home

从嵌入式资源加载 WebForms 和 UserControls

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (21投票s)

2006年9月5日

CPOL

4分钟阅读

viewsIcon

274430

downloadIcon

2042

从嵌入式资源加载 WebForms 和 UserControls。

引言

你是否曾经想过将那个庞大的 Web 项目分解成多个项目,或者在多个 Web 项目中重用用户控件和 Web Forms?

目前,在多个 ASP.NET 项目中重用 Web Forms 和用户控件需要复制相关的 aspxascx 文件。你可以将 Web 控件放在单独的程序集中,但你会失去设计时拖放的功能,而这正是用户控件和 Web Forms 最初易于创建的原因。如果你曾经尝试将用户控件或 Web Forms 放入单独的程序集中,你可能会遇到空白页面或运行时错误。原因在于 LoadControl() 实际上会读取 ascxaspx 文件来填充 Controls 集合,然后将它们绑定到你的类变量。如果找不到 ascx 文件,除非你在代码中进行了(如 WebControls 所做的那样)添加,否则不会添加任何控件。

我想要的是能够从另一个程序集中动态调用 LoadControl(),以便在多个 Web 项目中重用用户控件,而无需复制大量的 ascx 文件。要求太多了吗?

是否可以将这些 ascxaspx 文件嵌入到程序集资源中,然后加载它们?LoadControl() 期望一个虚拟路径,并且似乎没有办法从资源流加载控件。然后我发现了这个

解决方案

ASP.NET 2.0 中的虚拟路径提供程序可以用来从你选择的位置加载 ascx 文件。对我而言,我决定将 ascx 文件存储在程序集本身中。不再有与更新的程序集不兼容的过时的 ascx 文件。只需部署一个文件,即程序集本身,如果你将该程序集添加为引用,VS 将会自动复制它!要将 ascx/aspx 文件嵌入到程序集中,你必须在属性页中将文件的“生成操作”更改为“嵌入式资源”,我们创建的虚拟路径提供程序将完成其余的工作。

当需要解析虚拟路径时,ASP.NET 会询问最后一个注册的虚拟路径提供程序文件是否存在,如果存在,它将调用 GetFile 来获取 VirtualFile 实例。

在我们加载资源之前,我们需要知道资源位于哪个程序集以及要加载的资源名称。我选择将此信息编码到虚拟路径中。我的最终 URL 看起来像这样

~/App_Resources/WebApplicationControls.dll/WebApplicationControls.WebUserControl1.ascx

它有点长,但包含了我需要的所有信息。我不想拦截所有 URL,所以我们需要能够识别哪些 URL 需要处理,哪些 URL 交给默认的虚拟路径提供程序处理。为此,我选择只处理位于 App_Resources 下的 URL。这个文件夹不存在,这就是重点,因为这个位置的所有路径都将被拦截。第二部分包含程序集名称,最后一部分是资源名称,其中包含命名空间。

我已按如下方式实现了虚拟提供程序

public class AssemblyResourceProvider : System.Web.Hosting.VirtualPathProvider 
{
    public AssemblyResourceProvider() {}
    private bool IsAppResourcePath(string virtualPath)
    {
        String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return checkPath.StartsWith("~/App_Resource/", 
               StringComparison.InvariantCultureIgnoreCase);
    }
    public override bool FileExists(string virtualPath)
    {
        return (IsAppResourcePath(virtualPath) || 
                base.FileExists(virtualPath));
    }
    public override VirtualFile GetFile(string virtualPath)
    {
        if (IsAppResourcePath(virtualPath))
            return new AssemblyResourceVirtualFile(virtualPath); 
        else
            return base.GetFile(virtualPath);
    }
    public override System.Web.Caching.CacheDependency 
           GetCacheDependency( string virtualPath, 
           System.Collections.IEnumerable virtualPathDependencies, 
           DateTime utcStart)
    {
    if (IsAppResourcePath(virtualPath))
        return null;
    else 
        return base.GetCacheDependency(virtualPath, 
               virtualPathDependencies, utcStart);
    }
}

IsAppResource 是一个私有的辅助方法,用于确定我们是否应该处理请求,还是让默认提供程序处理请求。虚拟路径提供程序是链式连接的,因此调用基类很重要。还有一个必要的操作是重写 GetCacheDependency 以返回 null,否则 ASP.NET 将尝试监视文件更改并引发 FileNotFound 异常。请注意,GetFile 返回一个 AssemblyResourceVirtualFile 的实例,此类提供一个 Open() 方法来获取资源流,实现如下

class AssemblyResourceVirtualFile : VirtualFile
{
    string path;
    public AssemblyResourceVirtualFile(string virtualPath) : base(virtualPath)
    {
        path = VirtualPathUtility.ToAppRelative(virtualPath); 
    }
    public override System.IO.Stream Open()
    {
        string[] parts = path.Split('/');
        string assemblyName = parts[2]; 
        string resourceName = parts[3];
        assemblyName = Path.Combine(HttpRuntime.BinDirectory, 
                                    assemblyName);
        System.Reflection.Assembly assembly = 
           System.Reflection.Assembly.LoadFile(assemblyName);
        if (assembly != null)
        {
            return assembly.GetManifestResourceStream(resourceName);
        }
        return null;
    }
}

我们在这里需要做的就是解析 virtualPath 来获取程序集名称和资源名称。我假设程序集位于 bin 目录中,因为这是它们的典型位置,并且 GetManifestResourceStream 会完成所有繁重的工作。

现在我们只需要告诉 ASP.NET 使用我们的虚拟路径提供程序。这可以在两个地方之一完成:Global.asax 中的 Application_Start(),或者 App_Code 目录中的任何类文件中的签名如下的静态方法

public static void AppInitialize() { ... }

重要的是,它必须在我们提供程序捕获的任何虚拟路径被解析之前注册。我选择将其放在 Application_Start 中,如下所示。

protected void Application_Start(object sender, EventArgs e)
{
    System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(
       new AssemblyResourceProvider());
}

现在,我们可以使用常规的 LoadControl 将我们的自定义控件加载到 PlaceHolder 中,如下所示

protected void Page_Load(object sender, EventArgs e)
{
    Control ctrl = LoadControl("/App_Resource/WebApplicationControls.dll/"+
      "WebApplicationControls.WebUserControl1.ascx");
    PlaceHolder1.Controls.Add(ctrl);
}

URL 有点长,因为它们包含程序集和类名称的信息,但你可以拦截任何你想要的 URL。我选择将其前缀为 App_Resources,以便更容易识别要拦截的 URL。另一种方法是定义一个虚拟路径提供程序,该提供程序使用反射搜索 bin 目录中的所有程序集,查找以 ascx/aspx 结尾的资源,并只拦截带有或不带有命名空间的 URL。

Using the Code

演示项目使用的是 VS 2005 的 Web 应用程序项目更新,可以在这里找到:MSDN

© . All rights reserved.