As we saw in the previous post, you can override the default MVC Views of the Falafel Dashboard Widgets for Sitefinity that are served from the Embedded Resources of the assembly. But how did we get the views there in the first place, and how do we serve them to the Sitefinity widgets while allowing them to be overridden with the physical files? That's what we will be exploring today.
Embedded Resources
The first step is of course to embed the views inside the assembly. This is as simple as adding the views to the assembly project, and setting the Build Action to "Embedded Resource", as well as adding the assembly-level attribute to expose the resource externally. Here, you can see for convenience we've setup the folder structure to match the expected MVC folder structure from the Sitefinity website. As we'll see later, this will make it much simpler to retrieve resources from the expected path at runtime. For more details on creating and using Embedded Resources, take a look at this Microsoft support kb: How to embed resources in ASP.NET 2.0 assemblies. Although it specifies ASP.NET 2.0, the procedure is still valid for later versions of .NET. Now that our Views are embedded, we need a way to retrieve them automatically when requested by the widgets.
The Virtual Path Provider
Most of that magic is actually done for us, because Sitefinity makes use of the Virtual Path Provider model. This class exposes several methods that allow you to serve resources from a virtual file system such as from a database or in our case, the Embedded Resources of the Dashboard class library. Fortunately, Sitefinity has already implemented their own SitefinityVirtualPathProvider, so we simply need to hook into it to serve our resources. For more information on the Sitefinity Virtual Path Provider implementation, take a look at this post: Taking advantage of the Virtual Path Provider in Sitefinity 4.1. To plug into this, the Falafel Dashboard installer registers a new entry in the list of virtual paths that corresponds to the location from which we want to serve the Views. As you can see here, the virtual path corresponds to the actual MVC folder, with a separate area for the Falafel Dashboard widgets. It is this reason that you are able to override the embedded View templates with your own, because if the Virtual Provider finds a physical file, it serves that first instead of the Embedded Resource. But when the file doesn't exist, how do we turn this virtual path into the actual desired resource path?
Embedded Resource Resolver
The answer is to build an Embedded Resource Resolver. This is the component that is responsible for mapping the Virtual Path to the actual embedded resource path in the class library and serving it to the browser. Sitefinity has its own set of resolvers, such as SitefinityPageResolver for rendering pages, ControlPresentationResolver for resolving the embedded Sitefinity widget templates, and the general EmbeddedResourceResolver which is mentioned by the blog post article above to help you resolve your own embedded resources. For the most part, the EmbeddedResourceResolver is sufficient to retrieve resources from an assembly. In fact this same resolver is used for resolving the image, script, and css references in the Dashboard assembly. However, because our views were setup with a specific custom path, we need to implement our own resolver so that we can customize the locating of our resources based on the requested virtual path. This is done by inheriting from IVirtualFileResolver and implementing the following methods:
Exists - searches our assembly to determine if the requested Virtual Path maps to a matching resource
GetAssembly - loads the assembly specified in the Virtual Path configuration setting (in our case, Falafel.Sitefinity.Dashboard)
GetResourceName - converts the Virtual Path into a name that can be used to search the list of assembly resources
Open - finally opens and returns the resource stream
By doing some clever string manipulation in the GetResourceName method, we can convert a path like "~/Mvc/Areas/FalafelDashboard/Views/AdminBoard/Index.cshtml" to the more resource-friendly name "Falafel.Sitefinity.Dashboard.Mvc.Areas.FalafelDashboard.Views.AdminBoard.Index.cshtml". Then we can easily search for that name using the "Exists" method, finally actually opening and returning the resource stream in the Open method. Here's the implementation of that Open method from the Dashboard, where you can see we are using the modified name to search for, open, and return the resource stream back to the response so it can be rendered on the page.
public Stream Open(PathDefinition definition, string virtualPath) { Assembly assembly = this.GetAssembly(definition); string resourceName = this.GetResourceName(definition, virtualPath); var resource = assembly.GetManifestResourceNames().First(s => s.ToLower() == resourceName.ToLower()); return assembly.GetManifestResourceStream(resource); }
Registering the Resource Resolver
The very last piece of the puzzle is to register our custom resolver so that Sitefinity knows to use it whenever it encounters a matching Virtual Path. This is done with the ObjectFactory, which has a Unity container for dependency injection that we can use to register our type as an available implementation of the IVirtualFileResolver.
if (!ObjectFactory.Container.IsRegistered<FalafelDashboardResourceResolver>(typeof(FalafelDashboardResourceResolver).Name)) { ObjectFactory.Container.RegisterType<IVirtualFileResolver, FalafelDashboardResourceResolver>(typeof(FalafelDashboardResourceResolver).Name, new ContainerControlledLifetimeManager(), new InjectionConstructor()); }
Putting it All Together
As you can see from the Virtual Path definition for the Dashboard views, we simply need to specify the name of the resolver, and Sitefinity will automatically wire it up at runtime to resolve requests to the matching Virtual Paths and serve them from the assembly! By taking advantage of this helpful yet extremely flexible part of Sitefinity, we are able to embed all the Falafel Dashboard Widget resources into a single assembly, making it a breeze to not only install and use the widgets, but to customize them with your own Views by simply creating a matching file in the MVC folder. I hope this is helpful!
Enjoyed this post and/or found it useful?