MVC 3多租户和视图编译缓存问题

4
我在ASP.NET MVC 3中实现了一种多租户方式,但遇到了一个意外问题。假设我有两个网站:example.comexample.fr,它们都由同一个IIS中的MVC网站提供服务。然后我有一个自定义的VirtualPathProvider,根据域名从不同位置提供视图,控制器始终相同,只有视图从不同位置获取。这一切都很顺利,但问题出在ASP.NET视图编译上。假设两个域都具有相同名称和路径的视图(为了清晰起见,是MVC视图路径):
example.com/Views/MyController/Index.cshtml
example.fr/Views/MyController/Index.cshtml

这应该很好用。但是ASP.NET BuildManager(编译Razor代码为程序集)仅基于虚拟路径缓存构建。
这意味着当我在访问example.com时首次呈现视图时,它可以正常工作。但是,如果我尝试在example.fr的上下文中呈现视图,则ASP.NET认为该视图尚未被修改(因为虚拟路径相同),并且会从缓存中执行该视图,因此呈现不正确的视图。
解决办法可能是根据域将视图编译到不同的命名空间中。
到目前为止,我已经使用MvcWebRazorHostFactory重写CreateHost方法,以返回具有正确命名空间的RazorEngineHost。不确定它是否起作用,因为我认为此时缺少所有必要的信息(其中之一是HttpContext)。
任何人有任何想法吗?我错过了明显的东西吗?
谢谢
2个回答

2

好的,解决起来很简单。

我只需要在我的VirtualPathProvider中覆盖GetCacheKey方法,并返回一个考虑到主机名的键字符串。

在我的情况下,我只是将主机名和虚拟路径连接起来,然后返回结果字符串的哈希码。


嗨Pedro,您能否将此代码放在Gist或其他在线位置上吗?我目前正在处理一个问题,即查看第二租户上的虚拟路径时出现编译错误,而您似乎已经解决了这个问题 - 也许只需关注CreateHost就可以了... - mcintyre321
我认为这肯定是 CreateHost 的问题,因为我按照描述重写了 GetCacheKey,并且可以从我的租户中加载 js 文件,只是 xshtml 有些困难。 - mcintyre321
这个概念的缺点是,每次请求每个域的页面时,之前进行的缓存都会丢失。因此,您基本上失去了缓存已编译视图的好处。是否有更好的解决方案,可以让框架在第一次加载每个单独的视图后仍然缓存它们? - Kevin Warnke

1

我不知道你是否已经走得太远,无法考虑其他方法,但我也正在开发一个多租户系统,并且我是通过重写视图引擎(基于 Razor)来完成的。

public class MultiTenancyRazorViewEngine : RazorViewEngine
{
    /// <summary>
    /// Finds the specified partial view by using the specified controller context.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="partialViewName">The name of the partial view.</param>
    /// <param name="useCache">true to use the cached partial view.</param>
    /// <returns>The partial view.</returns>
    /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext"/> parameter is null (Nothing in Visual Basic).</exception>
    /// <exception cref="T:System.ArgumentException">The <paramref name="partialViewName"/> parameter is null or empty.</exception>
    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        var searchedLocations = new List<string>();
        var foundFile = Support.ResolvePath(string.Format("{0}.cshtml", partialViewName), controllerContext.HttpContext, controllerContext.RouteData, searchedLocations);

        return foundFile == null 
            ? new ViewEngineResult(searchedLocations) 
            : base.FindPartialView(controllerContext, foundFile, useCache);
    }

    /// <summary>
    /// Finds the view.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="viewName">Name of the view.</param>
    /// <param name="layoutPath">The layout path.</param>
    /// <param name="useCache">if set to <c>true</c> [use cache].</param>
    /// <returns></returns>
    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string layoutPath, bool useCache)
    {
        var searchedLocations = new List<string>();
        var foundFile = Support.ResolvePath(string.Format("{0}.cshtml", viewName), controllerContext.HttpContext, controllerContext.RouteData, searchedLocations);

        return foundFile == null 
            ? new ViewEngineResult(searchedLocations) 
            : base.FindView(controllerContext, foundFile, layoutPath, useCache);
    }

我有自己的支持方法来查找视图:“ResolvePath”。我使用HttpContext,因为我已经存储了正在访问的站点(通过主机名),并基于该主机名(或客户端的唯一ID)缓存结果。它还允许我进行自己的路径搜索视图的形式,所以我可以有:
Views/Controller/Action.cshtml 或者 Views/Custom/[client]/Controller/Action.cshtml(或者真正非常小的部分) 如果我想覆盖视图的某个部分。
抱歉,这并没有真正回答你的具体问题,但是有帮助吗?如果您对此方法感兴趣,我可以提供更多代码。

谢谢那个主意,它也会非常有效。我已经找到了我这种特殊情况的标准解决方案,并在上面回答以供将来参考。 - Pedro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接