Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET using embedded resources in Bundling

I'm trying to implement a generic approach for providing the possibility for different assemblies in my web solution to use embedded JavaScript and CSS files from embedded resources. This blog post shows a technique using a VirtualPathProvider. This works fine, but the VirtualPathProvider needs to be included in each assembly containing embedded resources.

I tried to enhance the VirtualPathProvider from the blog post, so that an assembly can be passed into it and it loads the resource from its assembly:

public EmbeddedVirtualPathProvider(VirtualPathProvider previous, Assembly assembly)
{
    this.previous = previous;
    this.assembly = assembly;
}

On initialization it reads all embedded resources from the passed assembly:

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

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

And the GetFilereads the content from the passed assembly:

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);
}

Checking if resource is an embedded resource of this assembly is by checking the resource names read in the Initialize method:

private bool IsEmbeddedPath(string path)
{
    var resourceName = string.Concat(this.assemblyName, path.TrimStart('~').Replace("/", "."));
    return this.assemblyResourceNames.Contains(resourceName, StringComparer.OrdinalIgnoreCase);
}

I moved the EmbeddedVirtualPathProvider class to the main web project (ProjectA), so that it doesn't need to be included in each assembly containing embedded resources and registered it using the following code in Global.asax:

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

In the project containing the embedded resources (ProjectB) I still create the following bundle in a PostApplicationStartMethod:

 BundleTable.Bundles.Add(new ScriptBundle("~/Embedded/Js")
     .Include("~/Scripts/SomeFolder/MyScript.js")
 );

Scripts/MyScript.js is the embedded resource in ProjectB.

With this I receive the following exception:

Directory 'C:\webs\ProjectA\Scripts\SomeFolder\' does not exist. Failed to start monitoring file changes.

Update Full stack trace available in this Gist.

Update Also the VirtualPathProvider itself seems to work fine. If I load the file directly and not through the bundle and set the following entry in the web.config it loads the embedded javascript from ProjectB:

<system.webServer>
  <handlers>
    <add name="MyStaticFileHandler" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler"/>
  </handlers>
</system.webServer>
like image 820
Pascal Berger Avatar asked Jan 14 '16 10:01

Pascal Berger


1 Answers

When ASP.net optimization create the bundle it call GetCacheDependency for the virtual directory of the script. Your GetCacheDependency implementation only check virtual file, for virtual directory it relies on the base VirtualPathProvider which check if directory exists and failed.

To solve this issue, you have to check if the path is a directory of one of your script and return null for the GetCacheDependency.

To safely determine if virtualPath is a bundle directory, you can use the BundleTable.Bundles collection or using a convention (ie: every bundle should starts with ~/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);
    }
}
like image 88
Cyril Durand Avatar answered Sep 19 '22 19:09

Cyril Durand