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 GetFile
reads 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>
IsEmbeddedPath
method returnfalse
whereas it should returntrue
. Could you tell us the value ofpath
andresourceName
before the error occured ? – StraightawayIsEmbeddedPath
returnstrue
andGetFile
is also called and returns the stream of the embedded resource. – TeriIsEmbeddedPath
always returntrue
. By looking at the call stack, we can see thatGetCacheDependency
is called and then the baseGetCacheDependency
is called which means thatIsEmbeddedPath
returnfalse
one time. – StraightawayFileExists
with a condition ofvirtualPath.Contains("MyScript.js")
, if it is calledvirtualPath
is set to~/Scripts/SomeFolder/MyScript.js
. InIsEmbeddedPath
the resourceName isProjectA.SomeFolder.MyScript.js
which exists inassemblyResourceNames
and thereforetrue
is returned. I've also another breakpoint with the same condition set inGetFile
which is called afterwards. And theVirtualFile
object for the resource is returned from there. Afterwards no further calls to eitherFileExists
orGetFile
happen – Teri