C# 使用自定义Razor视图引擎处理布局属性

C# 使用自定义Razor视图引擎处理布局属性,c#,asp.net-mvc,razor,multi-tenant,viewengine,C#,Asp.net Mvc,Razor,Multi Tenant,Viewengine,我已经实现了一个多租户视图引擎,与这里描述的类似: 这使我可以覆盖视图的搜索位置,如下所示: MasterLocationFormats = new[] { "~/Views/%1/{1}/{0}.cshtml", "~/Views/%1/Shared/{0}.cshtml", "~/Views/Default/{1}/{0}.cshtml", "~/Views/Default/Shared/{0}.cshtm

我已经实现了一个多租户视图引擎,与这里描述的类似:

这使我可以覆盖视图的搜索位置,如下所示:

    MasterLocationFormats = new[]
    {
        "~/Views/%1/{1}/{0}.cshtml",
        "~/Views/%1/Shared/{0}.cshtml",
        "~/Views/Default/{1}/{0}.cshtml",
        "~/Views/Default/Shared/{0}.cshtml",
    };
Layout = "~/Views/Default/Shared/_MyLyout.cshtml";
Layout = "~/Views/%1/Shared/_MyLyout.cshtml";
其中,
%1
被替换为活动租户的正确文件夹。这是一个很好的例外问题。当我在视图上定义布局路径时,如下所示:

    MasterLocationFormats = new[]
    {
        "~/Views/%1/{1}/{0}.cshtml",
        "~/Views/%1/Shared/{0}.cshtml",
        "~/Views/Default/{1}/{0}.cshtml",
        "~/Views/Default/Shared/{0}.cshtml",
    };
Layout = "~/Views/Default/Shared/_MyLyout.cshtml";
Layout = "~/Views/%1/Shared/_MyLyout.cshtml";
这有点违背了拥有多租户的目的,因为我必须硬编码布局页面的确切位置。我希望能够做到以下几点:

    MasterLocationFormats = new[]
    {
        "~/Views/%1/{1}/{0}.cshtml",
        "~/Views/%1/Shared/{0}.cshtml",
        "~/Views/Default/{1}/{0}.cshtml",
        "~/Views/Default/Shared/{0}.cshtml",
    };
Layout = "~/Views/Default/Shared/_MyLyout.cshtml";
Layout = "~/Views/%1/Shared/_MyLyout.cshtml";
如果我想让租户拥有一个布局页面,我将如何支持这一点

我尝试过摆弄我忽略的视图引擎方法:

  • CreatePartialView
  • CreateView
  • 文件存在
但似乎没有任何东西能够动态地指定布局页面

更新:

以下是我迄今为止所做的工作。我使用这个问题的答案稍微修改一下,创建了一个HTML助手:

public static string GetLayoutPageForTenant( this HtmlHelper html, string LayoutPageName )
{
    var layoutLocationFormats = new[]
    {
        "~/Views/{2}/{1}/{0}.cshtml",
        "~/Views/{2}/Shared/{0}.cshtml",
        "~/Views/Default/{1}/{0}.cshtml",
        "~/Views/Default/Shared/{0}.cshtml",
    };

    var controller = html.ViewContext.Controller as MultiTenantController;
    if( controller != null )
    {
        var tenantName = controller.GetTenantSchema();
        var controllerName = html.ViewContext.RouteData.Values["Controller"].ToString();

        foreach( var item in layoutLocationFormats )
        {
            var resolveLayoutUrl = string.Format( item, LayoutPageName, controllerName, tenantName );
            var fullLayoutPath = HostingEnvironment.IsHosted ? HostingEnvironment.MapPath( resolveLayoutUrl ) : System.IO.Path.GetFullPath( resolveLayoutUrl );
            if( File.Exists( fullLayoutPath ) ) return resolveLayoutUrl;
        }
    }

    throw new Exception( "Page not found." );
}
这与萨拉瓦南的建议相似。然后,我可以使用以下代码在视图中设置布局:

Layout = Html.GetLayoutPageForTenant( "_Home" );

不幸的是,这重复了自定义视图引擎所做的工作,这似乎是错误的做法。

我想提出以下想法

在我们设置布局页面的
\u ViewStart.cshtml
文件中,您可以使用类似的方法,使用基于租户的布局url,或者通过从数据库中获取来在控制器中填充文件夹名称

@{
    Layout = ViewBag.TenantLayoutPageUrl;
 }

如果您有一些静态租户数据表示,比如静态
Identity
类,它将跟踪租户的定制,那么我们可以使用它并最小化到数据库的往返

请分享您对此实施的想法,以便对社区有用

public class CustomWebViewPage : WebViewPage
{
    public override void ExecutePageHierarchy()
    {
        if (Context.Items["__MainView"] == null)
        {
            this.Layout = String.Format("~/Views/Shared/{0}/_Layout.cshtml", ViewContext.Controller.GetType().Namespace);
            Context.Items["__MainView"] = "Not Null";
        }
        base.ExecutePageHierarchy();
    }

    public override void Execute()
    {
    }
}

public class CustomWebViewPage<T> : WebViewPage<T>
{
    public override void ExecutePageHierarchy()
    {
        if (Context.Items["__MainView"] == null)
        {
            this.Layout = String.Format("~/Views/Shared/{0}/_Layout.cshtml", ViewContext.Controller.GetType().Namespace);
            Context.Items["__MainView"] = "Not Null";
        }
        base.ExecutePageHierarchy();
    }

    public override void Execute()
    {
    }
}

<system.web.webPages.razor>
  <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <pages pageBaseType="Mv4App.CustomWebViewPage">
公共类CustomWebViewPage:WebViewPage
{
公共覆盖无效ExecutePageHierarchy()
{
if(Context.Items[“\uu MainView”]==null)
{
this.Layout=String.Format(“~/Views/Shared/{0}/_Layout.cshtml”,ViewContext.Controller.GetType().Namespace);
Context.Items[“\uu MainView”]=“非空”;
}
base.ExecutePageHierarchy();
}
公共覆盖无效执行()
{
}
}
公共类CustomWebViewPage:WebViewPage
{
公共覆盖无效ExecutePageHierarchy()
{
if(Context.Items[“\uu MainView”]==null)
{
this.Layout=String.Format(“~/Views/Shared/{0}/_Layout.cshtml”,ViewContext.Controller.GetType().Namespace);
Context.Items[“\uu MainView”]=“非空”;
}
base.ExecutePageHierarchy();
}
公共覆盖无效执行()
{
}
}

您可以在租户视图文件夹(
~/views/%1/\u ViewStart.cshtml
)中添加以下内容。每个租户都可以管理自己的布局文件

@{
    Layout =  VirtualPathUtility.GetDirectory(PageContext.Page.VirtualPath) + "Shared/_Layout.cshtml";
}

这种方法的唯一问题是,并非每个租户都有自己的自定义布局页面。如果找不到自定义布局页,我仍然希望搜索默认为默认文件夹。@Sparafusile:在这种情况下,我们可以在ViewBag中使用回退id。因此,在控制器中,我们将检查租户是否有自定义布局页面,如果有,我们将在视图包中设置该文件夹,否则我们将设置租户的文件夹名称<代码>ViewBag.TenantFolderName=“defaultPath”;如果(tenant Has customFolder){ViewBag.TenantFolderName=TenantFolderName;}IMHO,这将是故障保护。虽然我同意这可以工作,但它有点违背了自定义视图引擎在控制器中完成所有工作的目的。我将继续寻找更优雅的解决方案。当然,我们将等待更好的解决方案。我也将从中学习。@Sparafusile:谢谢你分享你的想法。如果您愿意分享您实现的逻辑,请与我们分享。这是一个有趣的想法,但我希望能够使用默认视图的自定义布局和/或使用自定义视图的默认布局。除非我误解了你的答案,否则我不能用你的代码来做。@Sparafusile你可以这样做,你可以把
\u ViewStart.cshtml
放在任何你需要自定义的文件夹中,你甚至可以把它放在controller文件夹或默认文件夹中。默认情况下,Razor Engine搜索控制器文件夹的起始页(
\u ViewStart.cshtml
),如果找不到,请尝试父文件夹,依此类推,直到应用程序根目录。