好的,我有这个有趣的 ASP.NET MVC 4 解决方案/项目结构,它创建可插入的应用程序模块。我按照这种技术创建了它:
结果,我在项目中有一个带有空区域文件夹的主应用程序。我还有一个插件项目,它位于磁盘上主应用程序的区域文件夹中,它还将其构建输出文件夹设置为主应用程序\bin
文件夹。
在我的可插拔模块应用程序中,我决定在其中创建一个区域部分,并创建了一个名为 Test 的区域。默认情况下,ASP.NET MVC 4 视图引擎不支持将其作为可插入模块,因为它会尝试在不正确的位置查找视图。
所以从概念上讲,我们有:
Main <- Main application folder
Areas <- Main application folder
Plugin <- Plugin module application folder
Areas <- Plugin module application folder
Test <- Plugin module application folder
为了解决这个问题,我创建了一种方法来解释AreaName
自定义RazorViewEngine
类中的属性,以重写视图引擎正在寻找的 URL,以在这些可插入模块区域中查找视图文件。
首先,我使用以下约定为属于我的可插拔模块的测试区域定义我的区域注册类:
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
然后我继承RazorViewEngine
并覆盖了一些方法来解析和生成可插入模块的区域文件夹中的视图路径:
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
我已经修改了主应用程序Global.asax
文件以获取新的视图引擎:
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
启动浏览器并为我的主应用程序调用 Home 控制器后,我看到了正确的页面和布局渲染。当我转到我的插件模块的索引的 Home 控制器操作时,索引视图再次正确呈现,_Layout.vbhtml
并从主应用程序中获取。
但是,当我为插件测试区的索引视图调用 Home 控制器操作时,我只能看到索引页面视图呈现,但_Layout.vbhtml
主应用程序中没有包含主控。
为了让插件可插入模块下方的区域视图呈现主应用程序的主布局模板,我缺少什么?