Archive for July, 2013

ASP.NET Dropin DLL Plugin – Part Two

Tuesday, July 9th, 2013

ASP.NET Dropin DLL Plugin – Part One

The first post was a quick intro to the project. In this post, we will cover this works in more detail. So let’s just jump right in.

Typically in ASP.NET MVC, the framework knows where to look for files based on the conventions of the framework. Views are in view folder. With a plugin, the framework needs to be told if a files exists in a plugin DLL and be given a stream to the file. This where the System.Web.Hosting.VirtualPathProvider and System.Web.Hosting.VirtualFile come into play.

In the example there are two class that inherit from the above two classes: AssembleVirtualPathProvider and AssembleVirtualFile. These are Terrible implementations because they are only looking for the one plugin dll. This is a major area for improvement as these classes should look within any DLL that is a plugin. There are many options for this but that is another topic.

The AssembleVirtualPathProvider main job is to see if a view exists within a dll. To do this a number of methods need to be overridden. These methods by default look for views following MVC default naming and file location conventions. In order to check inside of the assembles, these methods need to be overridden and code added to look for the views within the DLLs. Make sure to call the base methods! If you don’t MVC will not be able to find the files local to main MVC project.

The AssembleVirtualFile is a representation of the a file being loaded from a plugin. It has one job, to open a file stream to the file that is in a dll and return it. That is it. This class is used by the AssembleVirtualPathProvider to return a file when GetFile method is called.

Once these are created, the main MVC project must be told to use these providers. In the main MVC project’s global.asax, the new path provider needs to be registered.

System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new Lib.AssembleVirtualPathProvider());

Now there is a catch to this. Plugins must be in the main MVCs project’s bin directory. I have tried multiple ways to get around this but have not had any luck. The reason is when DLLs are in the bin directory, the website’s process does a deep inspection of all the files in the bin directory. It keeps tabs on what files exist, exception model and controllers. If the DLLs are not in the bin directory, MVC will not be able to resolve the models and controller classes.

Moving on, serving static files from the DLLs. One of the goals of this was that the code in the plugins was written as close as possible to a normal MVC application. I did not want to have some special syntax for plugins vs non plugins. In order for this works, images and files need to be handled by a HTTP handler. In this case a static handler. Bring in the System.Web.StaticFileHandler. This will serve files from DLLs or the file system. It is pretty handy. In the web.config of the main MVC project an entry needs to be added for each static file type you would like to serve from the plugins.

<add name="AspNetStaticFileHandler-GIF" path="*.gif" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="AspNetStaticFileHandler-JPG" path="*.jpg" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="AspNetStaticFileHandler-PNG" path="*.png" verb="GET,HEAD" type="System.Web.StaticFileHandler" />
<add name="AspNetStaticFileHandler-JS" path="*.js" verb="GET,HEAD" type="System.Web.StaticFileHandler" />

On top of this, routes need to be ignored for static file extensions that are going to be handled by the StaticFileHandler.

routes.IgnoreRoute("{*staticfile}", new { staticfile = @".*\.(css|js|gif|jpg)(/.*)?" });

But wait, another catch! System.Web.StaticFileHandler does not correctly set the HTTP response headers correctly for caching when serving files from plugins. It works perfectly when serving files from the file system. In order to fix this, a http module needs to be created that looks to see if the file was served from the StaticFileHandler and set the cache headers or use a different StaticFileHandler. Super secret 3rd option (which is sort not good), is to serve all static files from the main MVC project.

Generally speaking that is it. No hidden projects, mirrors or DLL references. A bonus is the plugins will run independently from the main MVC project when doing development if needed.

Some areas that can be improved.
-Better assembly handling in the file and path providers.
-Loaded routes, filters, ect from plugins using MEF (or similar)
-Use/write a better static file handler

Github repo with example: