【问题标题】:Layout's in a modular MVC application模块化 MVC 应用程序中的布局
【发布时间】:2012-11-29 04:10:22
【问题描述】:

我用 ninject for MVC 为自己制作了一个模块化框架。

每个模块都可以注册自己的路由并包含自己的视图。

模块目录(dll 位置):
~/Modules/<module name>/

模块视图位于内部:
<Module dir>/Views/
它们的排列方式与普通的 mvc 应用程序完全相同,IE 每个控制器都有一个文件夹和一个共享文件夹。

我想渲染一个带有布局的视图,但是我希望布局位置由核心框架设置(以便我可以更改主题)。

我有一个具有layout = _layout.cshtml 的视图,当我运行应用程序时它返回:

The layout page "_Layout.cshtml" could not be found at the following path: "~/Modules/Module2/Views/Home/_Layout.cshtml".

被调用的视图在这里~/Modules/Module2/Views/Home/Index.cshtml。但我希望它在另一个位置寻找布局而不是在每个视图中设置它。无论如何我可以在核心框架中做到这一点吗?请注意,我将它设置为 MasterLocationFormats 以查看共享,它显然没有(我通过在其中放置 _layout.cshtml 进行了测试)。


自定义视图引擎:

public NinjectRazorViewEngine(): base()
    {
        ViewLocationFormats = new[] {
            "~/Modules/%1/Views/{1}/{0}.cshtml",
            "~/Modules/%1/Views/{1}/{0}.vbhtml",
            "~/Modules/%1/Views/Shared/{0}.cshtml",
            "~/Modules/%1/Views/Shared/{0}.vbhtml"
        };

        MasterLocationFormats = new[] {
            "~/Modules/%1/Views/{1}/{0}.cshtml",
            "~/Modules/%1/Views/{1}/{0}.vbhtml",
            "~/Modules/%1/Views/Shared/{0}.cshtml",
            "~/Modules/%1/Views/Shared/{0}.vbhtml",
        };

        PartialViewLocationFormats = new[] {
            "~/Modules/%1/Views/{1}/{0}.cshtml",
            "~/Modules/%1/Views/{1}/{0}.vbhtml",
            "~/Modules/%1/Views/Shared/{0}.cshtml",
            "~/Modules/%1/Views/Shared/{0}.vbhtml"
        };

        PartialViewLocationFormats = ViewLocationFormats;
        AreaPartialViewLocationFormats = AreaViewLocationFormats;
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        object moduleName;
        if(controllerContext.RequestContext.RouteData.Values.TryGetValue("module",out moduleName))
            return base.CreatePartialView(controllerContext, partialPath.Replace("%1", (string)moduleName));
        return base.CreatePartialView(controllerContext, partialPath);
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        object moduleName;
        if (controllerContext.RequestContext.RouteData.Values.TryGetValue("module", out moduleName))
            return base.CreateView(controllerContext, viewPath.Replace("%1", (string)moduleName), masterPath.Replace("%1", (string)moduleName));
        return base.CreateView(controllerContext, viewPath, masterPath);
    }

    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        object moduleName;
        if (controllerContext.RequestContext.RouteData.Values.TryGetValue("module", out moduleName))
            return base.FileExists(controllerContext, virtualPath.Replace("%1", (string)moduleName));
        return base.FileExists(controllerContext, virtualPath);
    }

【问题讨论】:

  • 模块是如何实现的?它们是作为 MVC 区域实现的吗?
  • 它们本质上是向内核注册控制器和其他任何东西的 ninject 模块。自定义控制器工厂通过内核查找控制器。每个模块都存在于一个单独的程序集中。
  • 您解决了吗?我目前遇到了同样的问题。
  • 是的,我现在就发布代码
  • @ngm 我已经发布了答案。

标签: c# .net asp.net-mvc razor asp.net-mvc-4


【解决方案1】:

这需要大量的工作。

必须对视图引擎进行更改以正确公开FindViewFindPartialView 方法。问题中列出的方法是错误的。

viewEngineClass 应该是这样的

public NinjectRazorViewEngine(): base()
    {
        ViewLocationFormats = new[] {
            "~/Modules/{2}/Views/{1}/{0}.cshtml",
            "~/Modules/{2}/Views/{1}/{0}.vbhtml",
            "~/Modules/{2}/Views/Shared/{0}.cshtml",
            "~/Modules/{2}/Views/Shared/{0}.vbhtml",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        MasterLocationFormats = new[] {
            "~/Modules/{2}/Views/{1}/{0}.cshtml",
            "~/Modules/{2}/Views/{1}/{0}.vbhtml",
            "~/Modules/{2}/Views/Shared/{0}.cshtml",
            "~/Modules/{2}/Views/Shared/{0}.vbhtml",
        };

        PartialViewLocationFormats = new[] {
            "~/Modules/{2}/Views/{1}/{0}.cshtml",
            "~/Modules/{2}/Views/{1}/{0}.vbhtml",
            "~/Modules/{2}/Views/Shared/{0}.cshtml",
            "~/Modules/{2}/Views/Shared/{0}.vbhtml",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/{1}/{0}.vbhtml",
            "~/Views/Shared/{0}.cshtml",
            "~/Views/Shared/{0}.vbhtml"
        };

        PartialViewLocationFormats = ViewLocationFormats;
        AreaPartialViewLocationFormats = AreaViewLocationFormats;

        //Used to test cache
        //ViewLocationCache = new DefaultViewLocationCache();
    }
    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        return FindView(controllerContext, partialViewName, "", useCache);
    }
    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        //Implement defualt exceptions
        if(controllerContext == null)
            throw new ArgumentNullException("The controllerContext parameter is null");
        if(string.IsNullOrEmpty(viewName))
            throw new ArgumentException("The viewName parameter is null or empty.");

        //Check cache if specified
        if(useCache && this.ViewLocationCache != null){
            string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, generateCacheKey(controllerContext, viewName));
            if (!string.IsNullOrEmpty(cachedLocation))
                return new ViewEngineResult(CreateView(controllerContext, cachedLocation, masterName), this);
        }

        //Create arguments for location formatting
        string trimmedViewName = string.Empty;
        if (viewName.EndsWith(".cshtml"))
            trimmedViewName = viewName.Remove(viewName.Length - 7);
        else
            trimmedViewName = viewName;
        object[] args = new object[] { trimmedViewName, controllerContext.RouteData.GetRequiredString("controller"), controllerContext.RouteData.GetRequiredString("module") };

        //Attempt to locate file
        List<string> searchedLocations = new List<string>();
        foreach(string location in ViewLocationFormats){
            string formatedLocation = string.Format(location,args);
            searchedLocations.Add(formatedLocation);
            if (FileExists(controllerContext, formatedLocation))
            {
                //File has been found. Add to cache and return view
                if(this.ViewLocationCache != null)
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, generateCacheKey(controllerContext, viewName), formatedLocation);

                return new ViewEngineResult(CreateView(controllerContext, formatedLocation, masterName), this);
            }
        }

        //Couldnt find view, return searched locations
        return new ViewEngineResult(searchedLocations);
    }
    public string generateCacheKey(ControllerContext controllerContext, string viewName)
    {
        return string.Format("{0}|{1}", controllerContext.RouteData.GetRequiredString("module"), viewName);
    }

然后您需要像这样实现自定义System.Web.Mvc.WebViewPage&lt;T&gt;

public abstract class WebViewPage<T> : System.Web.Mvc.WebViewPage<T>
{
    public override string Layout
    {
        get
        {
            return base.Layout;
        }
        set
        {
            NinjectRazorViewEngine viewEngine = new NinjectRazorViewEngine();
            System.Web.Mvc.ViewEngineResult engineResult = viewEngine.FindView(this.ViewContext.Controller.ControllerContext, value, string.Empty, true);
            System.Web.Mvc.RazorView razorView = engineResult.View as System.Web.Mvc.RazorView;
            if (razorView == null)
            {
                string searchedIn = "";
                foreach (string item in engineResult.SearchedLocations)
                {
                    searchedIn += item + "\n";
                }
                throw new HttpException(500, "Could not find views in locations:\n" + searchedIn);
            }
            base.Layout = razorView.ViewPath;
        }
    }
}

希望有帮助:)

【讨论】:

    【解决方案2】:

    您可以实现自己的 ViewEngine,它将在自定义位置查找视图。

    public class MyViewEngine : RazorViewEngine {
       public MyViewEngine() {
           this.MasterLocationFormats = new string[] {
               "PATH TO YOUR LAYOUT FILES", "ALTERNATIVE PATH"
           }
       }
    }
    

    然后在您的应用程序启动期间(例如在 Global.asax.cs 中)设置您的应用程序以使用您的自定义引擎

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new ThemableViewEngine());
    

    【讨论】:

    • 我有一个自定义视图引擎,我将编辑问题以包含代码
    【解决方案3】:

    您真的应该尝试使用带有RazorGenerator 的预编译视图。

    编译视图允许您将模块作为单个 DLL 删除,这比将所有内容(如 cshtml 视图)带入 DLL 更容易,此外,您可以在启动时或运行时使用 MEF 或任何其他反射机制,使您的 MVC 应用真正模块化并减少耦合。

    我发现自己在模块化网站上这种实现并不划算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-15
      • 1970-01-01
      相关资源
      最近更新 更多