1

RazorGenerator を使用して、カスタム (派生) RazorViewEngine とプリコンパイル済みビューを使用しようとしています。

いくつかのコンテキスト:

複数のクライアントの実装に使用する基本製品があります。これで、基本ビューのコア セットができました。ほとんどの場合、ほとんどのビューが機能します。現時点では、新しいソリューションごとに既存のビューをコピーし、必要に応じて変更することになります。これにより、ビューの 95% がクライアント間で同じになり、5% が変更されます。

私がやりたいことは、ビューの基本セットを取得し、それらを DLL にコンパイルして、クライアント間で再利用することです。これまでのところ、RazorGenerator を使用してうまく機能しています。

次のステップは、ビューのカスタマイズ (オーバーライド) を可能にすることです。ただし、注意点があります。このアプリケーションには、ユーザーがいる 2 つの「モード」があります。ユーザーがいるモードでは、別のビューが必要になる場合があります。

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);
    }
}

問題は:

  1. このコードは最後に (PosViewEngine を登録した後) 実行され、最初の位置にエンジンが挿入されます (これは、応答を提供するときに最初に解決されるエンジンであることを意味します)。これはビューを見つけることになります - それはコアビューです。
  2. StartUp のコードを変更して、最初にカスタム ビュー エンジンを登録し、次に 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' はここでは許可されていません。」

明らかに、物理パスと仮想パスの両方が混在していることに関係があります。

他の誰かがここで同じことをしようとしていたようですが、これに関する答えはありませんでした。

4

1 に答える 1

2

このアプローチを試してみたい他の人のために、答えを投稿します。基本的に、RazorGenerator アセンブリにある PrecompiledMvc​​Engine から派生するカスタム ビュー エンジンを実装する必要があります。

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} パスを参照するという規則を実装しました。ビューが見つからない場合は、従来の /Views/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 がインストールされたら、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 がビューの検索に最初に使用されます。

このアプローチにより、ビューをオーバーライドする手段を許可しながら、複数のアプリケーションでビューを再利用できます。

これはほとんどのアプリケーションにとってやり過ぎかもしれません - 質問で述べたように失敗 - これは、複数のクライアント アプリケーションを開発するために使用する基本製品です。クライアントごとにある程度の柔軟性を維持しながら再利用を達成しようとすることは、私たちが達成しようとしていたことです.

于 2016-06-30T16:21:11.597 に答える