我正在尝试使用RazorGenerator自定义(派生)RazorViewEngine和预编译视图。
某些背景信息:
我们有一个基础产品,我们用于多个客户端实现。有了它,我们有一组核心基础视图。大多数观点大部分时间都有效。现在,我们最终为每个新解决方案复制现有视图,并根据需要进行修改。最终,95%的观点在客户端之间是相同的,5%的观点发生了变化。
我想要做的是获取一组基本视图,将它们编译成DLL并在客户端重用它。到目前为止,我使用RazorGenerator工作得很好。
现在,下一步是允许自定义(覆盖)视图。但有一点需要注意。我们的应用程序有两个用户所在的“模式”。它们所处的模式可能需要不同的视图。
我已经从RazorGeneratorView创建了一个派生类。该视图基本上检查Autofac解析的UserProfile对象中的“OrderingMode”。根据模式 - 路径定位器将替换为视图分辨率。
单个客户端应用程序的想法将首先尝试在传统的Views文件夹中解析视图。只有我在Views / {OrderingMode} / {Controller} / {View} .cshtml的子目录中添加。
如果找不到视图 - 那么它将在编译库(核心视图)中查找。
这允许我根据需要覆盖客户端的个别视图/部分。
public PosViewEngine() : base()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"Areas/{2}/Views/%1/{1}/Paritals/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"Areas/{2}/Views/%1/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"Views/%1/{1}/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"Views/%1/{1}/Partials/{0}.cshtml",
"Views/%1/Shared/{0}.cshtml",
"Views/{1}/Partials/{0}.cshtml",
"Views/Shared/{0}.cshtml"
};
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.Replace("%1/",string.Empty));
}
private OrderType CurrentOrderingMode()
{
OrderType result;
_profileService = DependencyResolver.Current.GetService<IUserProfileService>();
if (_profileService == null || _profileService.OrderingType == 0)
{
IApplicationSettingService settingService =
DependencyResolver.Current.GetService<IApplicationSettingService>();
result =
settingService.GetApplicationSetting(ApplicationSettings.DefaultOrderingMode)
.ToEnumTypeOf<OrderType>();
}
else
{
result = _profileService.OrderingType;
}
return result;
}
}
这是RazorGenerator用于注册ViewEngine的StartUp类。
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
问题是:
如果我首先将StartUp中的代码更改为首先注册我的自定义视图引擎,然后再更改RazorGenerator引擎</ p>
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Clear();
ViewEngines.Engines.Insert(0, new PosViewEngine());
ViewEngines.Engines.Insert(1, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
我最终在FileExists(ControllerContext controllerContext,string virtualPath)方法上遇到异常 - “此处不允许相对虚拟路径'Views / Account / LogOn.cshtml'。”
这显然与物理和虚拟路径混合在一起有关。
看起来其他人试图做同样的事情here,但我没有看到答案。
答案 0 :(得分:1)
对于其他想要尝试这种方法的人,我会发布答案。基本上,您需要实现一个自定义视图引擎,该引擎派生自RazorGenerator程序集中的PrecompiledMvcEngine。
public class PosPrecompileEngine : PrecompiledMvcEngine
{
private IUserProfileService _profileService;
public PosPrecompileEngine(Assembly assembly) : base(assembly)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath) : base(assembly, baseVirtualPath)
{
LocatorConfig();
}
public PosPrecompileEngine(Assembly assembly, string baseVirtualPath, IViewPageActivator viewPageActivator) : base(assembly, baseVirtualPath, viewPageActivator)
{
LocatorConfig();
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return base.CreatePartialView(controllerContext, partialPath.ReplaceOrderType(CurrentOrderingMode()));
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
OrderType orderType = CurrentOrderingMode();
return base.CreateView(controllerContext, viewPath.ReplaceOrderType(orderType), masterPath.ReplaceOrderType(orderType));
}
protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
{
return base.FileExists(controllerContext, virtualPath.ReplaceOrderType(CurrentOrderingMode()));
}
}
在这个类中 - 我重写定位器路径。因为我有&#34;基地&#34;来自Web应用程序的另一个程序集中的已编译视图 - 我们实现了一种约定,其中视图引擎将首先在Web应用程序中查看PosViews / {ordering mode} / {controller} / {view}路径。如果找不到视图 - 那么它将在传统/视图/控制器/视图中查找。这里的技巧是后者是位于另一个类库中的虚拟路径。
这使我们能够覆盖&#34;应用程序的现有视图。
private void LocatorConfig()
{
//{0} = View Name
//{1} = ControllerName
//{2} = Area Name
AreaViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the POS Areas Shared
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core assembly
"Areas/{2}/Views/{1}/{0}.cshtml"
};
//Same format logic
AreaMasterLocationFormats = AreaViewLocationFormats;
AreaPartialViewLocationFormats = new[]
{
//First look in the hosting application area folder / Views / ordering type
//Areas/{AreaName}/{OrderType}/{ControllerName}/Partials/{PartialViewName}.cshtml
"PosAreas/{2}/Views/%1/{1}/Partials/{0}.cshtml",
//Next look in the hosting application area folder / Views / ordering type / Shared
//Areas/{AreaName}/{OrderType}/{ControllerName}/{ViewName}.cshtml
"PosAreas/{2}/Views/%1/Shared/(0}.cshtml",
//Next look in the hosting application shared folder
"PosAreas/{2}/Views/Shared/(0}.cshtml",
//Finally look in the IMS.POS.Web.Views.Core
"Areas/{2}/Views/{1}/{0}.cshtml"
};
ViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
MasterLocationFormats = ViewLocationFormats;
PartialViewLocationFormats = new[]
{
"~/PosViews/%1/{1}/{0}.cshtml",
"~/PosViews/%1/Shared/{0}.cshtml",
"~/PosViews/Shared/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml"
};
}
在应用程序启动事件中注册此引擎。
public static void Configure()
{
var engine = new PosPrecompileEngine(typeof(ViewEngineConfig).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
这是最后一把钥匙。当RazorGenerator安装完成后,查看NuGet - 你最终会得到这个将在启动时运行的启动类
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(Views.Core.RazorGeneratorMvcStart), "Start")]
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = true,
PreemptPhysicalFiles = true
};
ViewEngines.Engines.Add(engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
}
默认情况下 - RazorGenerator将ViewEngine添加到集合中的第一个
ViewEngines.Engines.Insert(0,engine);
您需要将其更改为添加
ViewEngines.Engines.Add(engine);
因此它最后被添加到引擎中 - 这样您的自定义ViewEngine首先用于定位视图。
此方法允许您在多个应用程序中重用视图,同时允许使用覆盖该视图的方法。
对于大多数应用程序来说这可能有点过头了 - 正如我在问题中提到的那样 - 这是我们用来开发多个客户端应用程序的基础产品。尝试实现重用,同时在每个客户端基础上保持一定的灵活性是我们努力实现的目标。