Note that there are some explanatory texts on larger screens.

plurals
  1. POASP.NET MVC 4 Areas within Areas won't render the Shared _Layout.vbhtml from Master project
    primarykey
    data
    text
    <p>Ok so I have this interesting ASP.NET MVC 4 solution/project structure, which creates pluggable application modules. I created it following this technique:</p> <p><a href="http://geekswithblogs.net/cokobware/archive/2013/01/15/asp.net-mvc-4-pluggable-application-modules.aspx" rel="nofollow">http://geekswithblogs.net/cokobware/archive/2013/01/15/asp.net-mvc-4-pluggable-application-modules.aspx</a></p> <p>As a result, I have a main application with an empty Areas folder in the project. I also have a Plugin project that resides in the Areas folder of the main application on disk, and it also sets its build output folder to the main application <code>\bin</code> folder.</p> <p>In my pluggable module application, I decided to create an Areas section within it, and created an Area called Test. By default, the ASP.NET MVC 4 view engine doesn't support it as a pluggable module because it tries to look for the View in the incorrect location. </p> <p>So conceptually, we have:</p> <pre><code>Main &lt;- Main application folder Areas &lt;- Main application folder Plugin &lt;- Plugin module application folder Areas &lt;- Plugin module application folder Test &lt;- Plugin module application folder </code></pre> <p>To fix this, I created a way to interpret the <code>AreaName</code> property in a customized <code>RazorViewEngine</code> class to rewrite the URL the view engine is looking for to find the view files in these pluggable module areas.</p> <p>First, I use the following convention to define my Area registration class for the Test Area belonging to my pluggable modules:</p> <pre><code>Namespace Areas.Plugin Public Class PluginAreaRegistration Inherits AreaRegistration Public Overrides ReadOnly Property AreaName() As String Get Return "Plugin.Test" End Get End Property Public Overrides Sub RegisterArea(ByVal context As System.Web.Mvc.AreaRegistrationContext) context.MapRoute( _ "Plugin_default", _ "Plugin/Test/{controller}/{action}/{id}", _ New With {.controller = "Home", .action = "Index", .id = UrlParameter.Optional}, {"Plugin.Test.Controllers"} ) End Sub End Class End Namespace </code></pre> <p>I then inherited the the <code>RazorViewEngine</code> and overrode some methods to parse and generate the views path in the pluggable module's Areas folder:</p> <pre><code>Public Class MyExtendedRazorViewEngine Inherits RazorViewEngine ' set the location format strings Public Sub New() MyBase.PartialViewLocationFormats = _ { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml", "~/Areas/{3}/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{3}/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{1}/Views/Shared/{0}.cshtml", "~/Areas/{1}/Views/Shared/{0}.vbhtml", "~/Areas/{2}/Areas/{1}/Views/{0}.cshtml", "~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml" } MyBase.AreaViewLocationFormats = { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml", "~/Areas/{2}/Areas/{1}/Views/{0}.cshtml", "~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml" } MyBase.AreaMasterLocationFormats = { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml", "~/Areas/{2}/Areas/{1}/Views/{0}.cshtml", "~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml" } MyBase.AreaPartialViewLocationFormats = { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml", "~/Areas/{2}/Areas/{1}/Views/{0}.cshtml", "~/Areas/{2}/Areas/{1}/Views/{0}.vbhtml" } MyBase.ViewLocationFormats = { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" } MyBase.MasterLocationFormats = { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" } MyBase.PartialViewLocationFormats = { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" } End Sub Protected Overrides Function CreatePartialView(controllerContext As ControllerContext, partialPath As String) As IView Dim area As String = controllerContext.RouteData.DataTokens.Item("Area") Dim areaname As String() Dim pp As String = partialPath If Not area Is Nothing Then areaname = area.Split(".") If areaname.Length &gt; 1 Then pp = pp.Replace(area, areaname(0) &amp; "/Areas/" &amp; areaname(1)) End If End If Return MyBase.CreatePartialView(controllerContext, pp) End Function Protected Overrides Function CreateView(controllerContext As ControllerContext, viewPath As String, masterPath As String) As IView Dim area As String = controllerContext.RouteData.DataTokens.Item("Area") Dim areaname As String() Dim vp As String = viewPath Dim mp As String = masterPath If Not area Is Nothing Then areaname = area.Split(".") If areaname.Length &gt; 1 Then vp = vp.Replace(area, areaname(0) &amp; "/Areas/" &amp; areaname(1)) mp = mp.Replace(area, areaname(0) &amp; "/Areas/" &amp; areaname(1)) End If End If Return MyBase.CreateView(controllerContext, vp, mp) End Function Protected Overrides Function FileExists(controllerContext As ControllerContext, virtualPath As String) As Boolean Dim area As String = controllerContext.RouteData.DataTokens.Item("Area") Dim areaname As String() Dim vp As String = virtualPath If Not area Is Nothing Then areaname = area.Split(".") If areaname.Length &gt; 1 Then vp = vp.Replace(area, areaname(0) &amp; "/Areas/" &amp; areaname(1)) End If End If Return MyBase.FileExists(controllerContext, vp) End Function End Class </code></pre> <p>I've modified the main application <code>Global.asax</code> file to pick up the new view engine:</p> <pre><code>Imports System.Web.Http Imports System.Web.Optimization Public Class MvcApplication Inherits System.Web.HttpApplication Sub Application_Start() AreaRegistration.RegisterAllAreas() WebApiConfig.Register(GlobalConfiguration.Configuration) FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters) RouteConfig.RegisterRoutes(RouteTable.Routes) BundleConfig.RegisterBundles(BundleTable.Bundles) ViewEngines.Engines.Clear() ViewEngines.Engines.Add(New MyExtendedRazorViewEngine()) End Sub End Class </code></pre> <p>After launching the browser and invoking the Home controller for my main application, I see the correct pages and layout render. When I go to the Home controller action for the Index for my Plugin module, again the Index view renders properly with the <code>_Layout.vbhtml</code> being picked up from the main application. </p> <p>However, when I invoke the Home controller action for the Index view of Plugin's Test Area, I can only see the Index page view render, but the master <code>_Layout.vbhtml</code> isn't being included from the main application.</p> <p>What am I missing to get the Areas views below the Plugin pluggable module to render the main application's master layout template?</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload