Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Views in separate assemblies in ASP.NET MVC

I'm trying to create a webapplication where I want to be able to plug-in separate assemblies. I'm using MVC preview 4 combined with Unity for dependency injection, which I use to create the controllers from my plugin assemblies. I'm using WebForms (default aspx) as my view engine.

If I want to use a view, I'm stuck on the ones that are defined in the core project, because of the dynamic compiling of the ASPX part. I'm looking for a proper way to enclose ASPX files in a different assembly, without having to go through the whole deployment step. Am I missing something obvious? Or should I resort to creating my views programmatically?


Update: I changed the accepted answer. Even though Dale's answer is very thorough, I went for the solution with a different virtual path provider. It works like a charm, and takes only about 20 lines in code altogether I think.

like image 215
Erik van Brakel Avatar asked Aug 21 '08 12:08

Erik van Brakel


People also ask

Can we have multiple views in MVC?

Yes You can use multiple View in one Controller.

Can one action method have multiple views?

Yes, completely possible. And, it can be multiple views, or even a FileResult or other result type.

Should you split your ASP NET MVC project into multiple projects?

It shouldn't! That's why it's designed in a modular way. In most web applications out there, we version and deploy all these assemblies (Web, BLL and DAL) together. So, separating a project into 3 projects does not add any values.

What are razor views?

Razor View Engine is a markup syntax which helps us to write HTML and server-side code in web pages using C# or VB.Net. It is server-side markup language however it is not at all a programming language.


2 Answers

It took me way too long to get this working properly from the various partial samples, so here's the full code needed to get views from a Views folder in a shared library structured the same as a regular Views folder but with everything set to build as embedded resources. It will only use the embedded file if the usual file does not exist.

The first line of Application_Start:

HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedViewPathProvider()); 

The VirtualPathProvider

   public class EmbeddedVirtualFile : VirtualFile {     public EmbeddedVirtualFile(string virtualPath)         : base(virtualPath)     {     }      internal static string GetResourceName(string virtualPath)     {         if (!virtualPath.Contains("/Views/"))         {             return null;         }            var resourcename = virtualPath             .Substring(virtualPath.IndexOf("Views/"))             .Replace("Views/", "OrangeGuava.Common.Views.")             .Replace("/", ".");          return resourcename;      }       public override Stream Open()     {         Assembly assembly = Assembly.GetExecutingAssembly();           var resourcename = GetResourceName(this.VirtualPath);         return assembly.GetManifestResourceStream(resourcename);     }     }  public class EmbeddedViewPathProvider : VirtualPathProvider {       private bool ResourceFileExists(string virtualPath)     {          Assembly assembly = Assembly.GetExecutingAssembly();           var resourcename = EmbeddedVirtualFile.GetResourceName(virtualPath);         var result = resourcename != null && assembly.GetManifestResourceNames().Contains(resourcename);         return result;     }      public override bool FileExists(string virtualPath)     {         return base.FileExists(virtualPath) || ResourceFileExists(virtualPath);     }       public override VirtualFile GetFile(string virtualPath)     {          if (!base.FileExists(virtualPath))         {             return new EmbeddedVirtualFile(virtualPath);         }         else         {             return base.GetFile(virtualPath);         }      }  } 

The final step to get it working is that the root Web.Config must contain the right settings to parse strongly typed MVC views, as the one in the views folder won't be used:

<pages     validateRequest="false"     pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"     pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"     userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">   <controls>     <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />   </controls> </pages> 

A couple of additional steps are required to get it working with Mono. First, you need to implement GetDirectory, since all files in the views folder get loaded when the app starts rather than as needed:

public override VirtualDirectory GetDirectory(string virtualDir)     {         Log.LogInfo("GetDirectory - " + virtualDir);         var b = base.GetDirectory(virtualDir);         return new EmbeddedVirtualDirectory(virtualDir, b);     }  public class EmbeddedVirtualDirectory : VirtualDirectory {     private VirtualDirectory FileDir { get; set; }       public EmbeddedVirtualDirectory(string virtualPath, VirtualDirectory filedir)         : base(virtualPath)     {         FileDir = filedir;     }      public override System.Collections.IEnumerable Children     {         get { return FileDir.Children; }     }      public override System.Collections.IEnumerable Directories     {         get { return FileDir.Directories; }     }      public override System.Collections.IEnumerable Files     {         get {              if (!VirtualPath.Contains("/Views/") || VirtualPath.EndsWith("/Views/"))             {                 return FileDir.Files;             }              var fl = new List<VirtualFile>();              foreach (VirtualFile f in FileDir.Files)             {                 fl.Add(f);             }               var resourcename = VirtualPath.Substring(VirtualPath.IndexOf("Views/")) .Replace("Views/", "OrangeGuava.Common.Views.") .Replace("/", ".");              Assembly assembly = Assembly.GetExecutingAssembly();              var rfl = assembly.GetManifestResourceNames()                 .Where(s => s.StartsWith(resourcename))                 .Select(s => VirtualPath + s.Replace(resourcename, ""))                 .Select(s => new EmbeddedVirtualFile(s));             fl.AddRange(rfl);              return fl;         }     } } 

Finally, strongly typed views will almost but not quite work perfectly. Model will be treated as an untyped object, so to get strong typing back you need to start your shared views with something like

<% var Model2 = Model as IEnumerable<AppModel>;  %> 
like image 55
Tom Clarkson Avatar answered Sep 25 '22 14:09

Tom Clarkson


Essentially this is the same issue as people had with WebForms and trying to compile their UserControl ASCX files into a DLL. I found this http://www.codeproject.com/KB/aspnet/ASP2UserControlLibrary.aspx that might work for you too.

like image 37
Joseph Kingry Avatar answered Sep 24 '22 14:09

Joseph Kingry