2

Ok so I have this interesting ASP.NET MVC 4 solution/project structure, which creates pluggable application modules. I created it following this technique:

http://geekswithblogs.net/cokobware/archive/2013/01/15/asp.net-mvc-4-pluggable-application-modules.aspx

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 \bin folder.

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.

So conceptually, we have:

Main             <- Main application folder
   Areas         <- Main application folder
      Plugin     <- Plugin module application folder
         Areas   <- Plugin module application folder
            Test <- Plugin module application folder

To fix this, I created a way to interpret the AreaName property in a customized RazorViewEngine class to rewrite the URL the view engine is looking for to find the view files in these pluggable module areas.

First, I use the following convention to define my Area registration class for the Test Area belonging to my pluggable modules:

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

I then inherited the the RazorViewEngine and overrode some methods to parse and generate the views path in the pluggable module's Areas folder:

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 > 1 Then
            pp = pp.Replace(area, areaname(0) & "/Areas/" & 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 > 1 Then
            vp = vp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
            mp = mp.Replace(area, areaname(0) & "/Areas/" & 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 > 1 Then
            vp = vp.Replace(area, areaname(0) & "/Areas/" & areaname(1))
        End If
    End If
    Return MyBase.FileExists(controllerContext, vp)
End Function
End Class

I've modified the main application Global.asax file to pick up the new view engine:

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

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 _Layout.vbhtml being picked up from the main application.

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 _Layout.vbhtml isn't being included from the main application.

What am I missing to get the Areas views below the Plugin pluggable module to render the main application's master layout template?

4

0 に答える 0