使用自定义RazorViewEngine和RazorGenerator预编译视图

使用自定义RazorViewEngine和RazorGenerator预编译视图,viewengine,overriding,razorgenerator,Viewengine,Overriding,Razorgenerator,我正在尝试使用自定义(派生)RazorViewEngine和使用RazorGenerator的预编译视图 一些背景: 我们有一个用于多个客户端实现的基础产品。这样我们就有了一组核心的基本视图。大多数视图在大多数时间都有效。现在,我们将复制每个新解决方案的现有视图,并根据需要进行修改。最终,客户端之间95%的视图相同,5%的视图更改 我要做的是获取一组基本视图,将它们编译成一个DLL,并跨客户端重用它。到目前为止,我已经使用RazorGenerator很好地工作了 现在,下一步是允许自定义(覆盖)

我正在尝试使用自定义(派生)RazorViewEngine和使用RazorGenerator的预编译视图

一些背景:

我们有一个用于多个客户端实现的基础产品。这样我们就有了一组核心的基本视图。大多数视图在大多数时间都有效。现在,我们将复制每个新解决方案的现有视图,并根据需要进行修改。最终,客户端之间95%的视图相同,5%的视图更改

我要做的是获取一组基本视图,将它们编译成一个DLL,并跨客户端重用它。到目前为止,我已经使用RazorGenerator很好地工作了

现在,下一步是允许自定义(覆盖)视图。不过有一个警告。我们的应用程序有两种用户所处的“模式”。他们所处的模式可能需要不同的视图

我已经从RazorGeneratorView创建了一个派生类。此视图主要从Autofac解析的UserProfile对象检查“OrderingMode”。基于模式-替换路径定位器以获得视图分辨率

其想法是,单个客户端应用程序将尝试首先在传统视图文件夹中解析视图。只有我正在添加视图/{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;
    } 



}
问题是:

  • 这段代码是最后执行的(在我注册PosViewEngine之后),它将引擎插入到第一个位置(这意味着这是在提供响应时首先得到解析的引擎)。这最终找到了一个视图——它是核心视图
  • 如果我在启动时更改代码,先注册自定义视图引擎,然后注册RazorGenerator引擎

     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'

    这显然与物理路径和虚拟路径混合在一起有关


    看起来好像有人试图做同样的事情,但我没有看到答案

    对于任何想尝试这种方法的人,我将发布答案。基本上,您需要实现一个自定义视图引擎,该引擎派生自RazorGenerator程序集中的预编译MVCEngine

    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()));
        }
    }
    
    在这个类中,我覆盖定位器路径。因为我在web应用程序的另一个程序集中有“基本”编译视图,所以我们实现了一个约定,视图引擎将首先查看web应用程序中的PosViews/{ordering mode}/{controller}/{view}路径。如果未找到视图-则它将在传统/视图/控制器/视图中查看。这里的技巧是后者是位于另一个类库中的虚拟路径

    这允许我们“覆盖”应用程序的现有视图

        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安装后,view 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将首先用于定位视图

    这种方法允许您在多个应用程序中重用视图,同时允许覆盖该视图的方法


    对于大多数应用程序来说,这可能是杀伤力过大了——正如我在问题中提到的那样——这是我们用来开发多个客户端应用程序的基础产品。尝试实现重用,同时在每个客户机的基础上保持一定程度的灵活性,这是我们试图实现的目标

    对于任何想尝试这种方法的人,我将发布答案。基本上,您需要实现一个自定义视图引擎,该引擎派生自RazorGenerator程序集中的预编译MVCEngine

    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()));
        }
    }
    
    在这个类中,我覆盖定位器路径。因为我在web应用程序的另一个程序集中有“基本”编译视图,所以我们实现了一个约定,视图引擎将首先查看web应用程序中的PosViews/{ordering mode}/{controller}/{view}路径。如果未找到视图-则它将在传统/视图/控制器/视图中查看。这里的技巧是后者是位于另一个类库中的虚拟路径

    这允许我们“覆盖”应用程序的现有视图

        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安装后,view 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将首先用于定位视图

    这种方法允许您在多个应用程序中重用视图,同时允许覆盖该视图的方法


    对于大多数应用程序来说,这可能是杀伤力过大了——正如我在问题中提到的那样——这是我们用来开发多个客户端应用程序的基础产品。尝试实现重用,同时在每个客户机的基础上保持一定程度的灵活性,这是我们试图实现的目标

    这是否可以用于向现有视图添加自定义内容?我发布的这不是这种方法。这更像是一种约定,我将为控制器操作提供一组基本视图,并根据需要覆盖它们。对于您的问题,您可能需要使用自定义基本视图来公开您自己的属性,并使用RazorEngine之类的工具通过属性注入内容。这可以用于将自定义内容添加到现有视图中吗?我贴了这个