ASP.NET使用嵌入式资源进行捆绑

15
我正在尝试实现一种通用方法,为我的Web解决方案中的不同程序集提供使用嵌入式资源中的JavaScript和CSS文件的可能性。这篇博客文章展示了使用VirtualPathProvider的技巧。这很有效,但是每个包含嵌入式资源的程序集都需要包含VirtualPathProvider。
我试图增强来自博客文章的VirtualPathProvider,以便可以将程序集传递给它并从其程序集加载资源。
public EmbeddedVirtualPathProvider(VirtualPathProvider previous, Assembly assembly)
{
    this.previous = previous;
    this.assembly = assembly;
}

初始化时,它会从传递的程序集中读取所有嵌入的资源:

protected override void Initialize()
{
    base.Initialize();

    this.assemblyResourceNames = this.assembly.GetManifestResourceNames();
    this.assemblyName = this.assembly.GetName().Name;
}

GetFile 从传递的程序集中读取内容:

public override VirtualFile GetFile(string virtualPath)
{
    if (IsEmbeddedPath(virtualPath))
    {
        if (virtualPath.StartsWith("~", System.StringComparison.OrdinalIgnoreCase))
        {
            virtualPath = virtualPath.Substring(1);
        }

        if (!virtualPath.StartsWith("/", System.StringComparison.OrdinalIgnoreCase))
        {
            virtualPath = string.Concat("/", virtualPath);
        }

        var resourceName = string.Concat(this.assembly.GetName().Name, virtualPath.Replace("/", "."));
        var stream = this.assembly.GetManifestResourceStream(resourceName);

        if (stream != null)
        {
            return new EmbeddedVirtualFile(virtualPath, stream);
        }
        else
        {
            return _previous.GetFile(virtualPath);
        }
    }
    else
        return _previous.GetFile(virtualPath);
}

检查资源是否为此程序集的嵌入式资源是通过检查在Initialize方法中读取的资源名称来实现的:
private bool IsEmbeddedPath(string path)
{
    var resourceName = string.Concat(this.assemblyName, path.TrimStart('~').Replace("/", "."));
    return this.assemblyResourceNames.Contains(resourceName, StringComparer.OrdinalIgnoreCase);
}

我将 EmbeddedVirtualPathProvider 类移动到主 web 项目 (ProjectA),这样它就不需要包含在每个包含嵌入资源的程序集中了,并使用以下代码在 Global.asax 中注册:

HostingEnvironment.RegisterVirtualPathProvider(
    new EmbeddedVirtualPathProvider(
        HostingEnvironment.VirtualPathProvider,
        typeof(ProjectB.SomeType).Assembly));

在包含嵌入资源的项目(ProjectB)中,我仍然在PostApplicationStartMethod中创建以下捆绑包:
 BundleTable.Bundles.Add(new ScriptBundle("~/Embedded/Js")
     .Include("~/Scripts/SomeFolder/MyScript.js")
 );

Scripts/MyScript.js是ProjectB中的嵌入资源。

由此我收到以下异常:

目录'C:\webs\ProjectA\Scripts\SomeFolder\'不存在。无法启动文件更改监视器。

更新 完整的堆栈跟踪可在this Gist中找到。

更新 VirtualPathProvider本身似乎也正常工作。如果我直接加载文件而不通过捆绑包并在web.config中设置以下条目,则可以从ProjectB加载嵌入的javascript:

<system.webServer>
  <handlers>
    <add name="MyStaticFileHandler" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
  </handlers>
</system.webServer>

你的启动类在哪里?在ProjectA还是ProjectB中? - George Vovos
ProjectA是一个NuGet包,其中包含VirtualPathProvider。ProjectB是另一个NuGet包,提供一些视图功能(有多个)。NuGet包ProjectB依赖于NuGet包ProjectA。应用程序安装ProjectB NuGet包。因此,Startup位于ProjectA和ProjectB之外,但ProjectA和ProjectB可以钩入PreApplicationStartMethod。 - Pascal Berger
@PascalBerger IsEmbeddedPath总是返回true。通过查看调用堆栈,我们可以看到先调用了GetCacheDependency,然后调用了基础的GetCacheDependency,这意味着IsEmbeddedPath有一次返回了false - Cyril Durand
如果我在FileExists中设置一个条件为virtualPath.Contains("MyScript.js")的断点,如果它被调用,则virtualPath被设置为~/Scripts/SomeFolder/MyScript.js。在IsEmbeddedPath中,resourceName是ProjectA.SomeFolder.MyScript.js,它存在于assemblyResourceNames中,因此返回true。我还在GetFile中设置了另一个具有相同条件的断点,然后调用该函数。资源的VirtualFile对象从那里返回。之后不会再调用FileExistsGetFile - Pascal Berger
@CyrilDurand VirtualPathProvider本身似乎工作正常(请参见更新的问题)。如果我直接加载JS文件,不使用bundle,并将所有*.js文件声明为静态文件,则可以找到嵌入的JavaScript文件。 - Pascal Berger
显示剩余2条评论
2个回答

1
当ASP.net进行优化创建捆绑包时,它会调用GetCacheDependency来获取脚本的虚拟目录。您的GetCacheDependency实现仅检查虚拟文件,对于虚拟目录,它依赖于基本的VirtualPathProvider,该提供程序检查目录是否存在并失败。
要解决此问题,您必须检查路径是否是您的脚本之一的目录,并为GetCacheDependency返回null。
为了安全地确定virtualPath是否为捆绑目录,您可以使用BundleTable.Bundles集合或使用约定(即:每个捆绑包应以~/Embedded开头)。
public override CacheDependency GetCacheDependency(
    string virtualPath, 
    IEnumerable virtualPathDependencies, 
    DateTime utcStart)
{
    // if(virtualPath.StartsWith("~/Embedded"))
    if(BundleTables.Bundles.Any(b => b.Path == virtualPath))
    {
        return null; 
    }
    if (this.IsEmbeddedPath(virtualPath))
    {
        return null;
    }
    else
    {
        return this._previous
                   .GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
}

谢谢。我尝试了这个,但是GetCacheDependency没有为C:\webs\ProjectA\Scripts\SomeFolder\调用,只有为~/Embedded/Js调用。 - Pascal Berger
@PascalBerger 异常发生时,virtualPath 的值是多少? - Cyril Durand
啊,抱歉。我的文件/路径检测有误。如果我修复对~/Embedded/Js的检查并返回null,就可以解决问题了。现在我需要找到一种安全的方式来确定virtualPath是目录还是文件 :) - Pascal Berger

1

关于以下错误

目录 'C:\webs\ProjectA\Scripts\SomeFolder\' 不存在。无法启动文件更改监视。

如果 SomeFolder 的所有资源文件都被嵌入并且在发布的站点中,就会发生这种情况 - 它不会创建此文件夹。

在捆绑包的情况下 - 它在创建捆绑包时保留时间戳,并监视任何文件更改以触发捆绑文件的更新。

在这里 - 没有要监视的 SomeFolder 中的文件 - 因为它们都被嵌入了。没有找到防止文件夹监视的方法 - 但通过处理此特定异常,可以忽略它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接