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.
Yes You can use multiple View in one Controller.
Yes, completely possible. And, it can be multiple views, or even a FileResult or other result type.
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.
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.
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>; %>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With