Developing Web-based Software as a Product: Part 2

Continuing on with the building of a ASP.NET MVC product, we previously discussed being able to access data directly from an assembly to build the views. The VirtualPathProvider provides what we need. Though first after more testing a better way to handle file access came up:

       private Assembly GetAssemblyReference()
        {
            if (assemblyReference != null)
                return assemblyReference;

            String binDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            String assemblyName = Path.Combine(binDir, VirtualFileSystemAssembly);

            if (!File.Exists(assemblyName))
            {
                assemblyName = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory,
                    Path.Combine("bin", VirtualFileSystemAssembly));
            }

            Assembly assembly;
            try
            {
                assembly = Assembly.LoadFile(assemblyName);
            }
            catch
            {
                return null;
            }

            // if it is a real assembly - keep it
            if (assembly != null)
                assemblyReference = assembly;

            return assembly;
        }

If the first method of trying to find the referenced assembly fails – which it can, we will try the bin directory. This should likely work for both unit tests & the production environment. Secondly, we adjust the AssemblyPathVirtualFile class to accept a couple more parameters to reduce the logic inside that class.

    public class AssemblyPathVirtualFile : VirtualFile
    {
        private String path;
        private String resourceName;
        private Assembly assembly;

        public AssemblyPathVirtualFile(String virtualPath, String assemblyResourceName, Assembly assemblyReference)
            : base(virtualPath)
        {
            path = VirtualPathUtility.ToAppRelative(virtualPath);
            resourceName = assemblyResourceName;
            assembly = assemblyReference;
        }

        public override System.IO.Stream Open()
        {
            if (assembly != null)
            {
                return assembly.GetManifestResourceStream(resourceName);
            }
            return null;
        }
    }

And lastly, the call to that class needs to be adjusted.

        public override VirtualFile GetFile(string virtualPath)
        {
            if (DoesExistInVirtualPath(virtualPath))
                return new AssemblyPathVirtualFile(virtualPath,
                    ConstructAssemblyResourceName(virtualPath),
                    GetAssemblyReference());
            else
                return base.GetFile(virtualPath);
        }

We now have the ability to load the views from an assembly. I created a Legomaster.ContactManager.Views.dll, the contents of the Views folder I dumped into the project; then I changed the compile action to embed resource. The root of the project would correspond to the Views folder, so it contains the Account, Contact, Group and Shared folders.

Next, the Views need be handled slightly differently; this requires web.config entries. Normally there’s a web.config file in the Views folder but that won’t work with an assmebly. We add it to the root web.config.

  <location path="Views">
    <system.web>
      <httpHandlers>
        <add path="*" verb="*"
            type="System.Web.HttpNotFoundHandler"/>
      </httpHandlers>

      <!--
        Enabling request validation in view pages would cause validation to occur
        after the input has already been processed by the controller. By default
        MVC performs request validation before a controller processes the input.
        To change this behavior apply the ValidateInputAttribute to a
        controller or action.
    -->
      <pages
          validateRequest="false"
          pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
          pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
          userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
        <controls>
          <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
        </controls>
      </pages>
    </system.web>
    <system.webServer>
      <validation validateIntegratedModeConfiguration="false"/>
      <handlers>
        <remove name="BlockViewHandler"/>
        <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
      </handlers>
    </system.webServer>
  </location>

Alright, everything is set – now we register to the provider in the Global.asax.cs:

        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);

            System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new
                Legomaster.AssemblyPathProvider.AssemblyPathProvider("~/Views",
                    "Legomaster.ContactManager.Views.dll", "Legomaster.ContactManager.Views"));
        }

Remove the Views directory from the main web application and attempt to run it. Everything should work as it did before.

Get the source for this part here.

This entry was posted in ASP.NET, ASP.NET MVC. Bookmark the permalink. Both comments and trackbacks are currently closed.

One Trackback

  1. By ASP.NET MVC Archived Blog Posts, Page 1 on 2009/05/17 at 10:08 pm

    [...] to VoteDeveloping Web-based Software as a Product: Part 2 (5/14/2009)Thursday, May 14, 2009 from legomaster.netContinuing on with the building of a ASP. … NET MVC [...]