ASP.NET MVC 3视图层次结构之间传递的数据

时间:2012-01-22 15:49:10

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

动机

我希望能够在Javascript中构建一个类似于树的对象层次结构,该层次结构对应于页面上的ASP.NET MVC 3 Razor视图。计划是在Razor视图和定义其逻辑的Javascript文件之间进行一对一的对应(以接受一些初始化参数的构造函数的形式)。简单的例子可能如下所示:

  • _Layout.cshtml <-> Global.js
    • SplitterPane.cshtml <-> SplitterPane.js
      • Grid.cshtml <-> Grid.js
      • Tree.cshtml <-> Tree.js

我会使用构造函数来构建层次结构,例如

var page = new Global(global_options);
var splitter = new SplitterPane(splitter_options);
var grid = new Grid(grid_options);
var tree = new Tree(tree_options);

page.addChild(splitter);
splitter.addChild(grid);
splitter.addChild(tree);

当然,所有这些代码都应该根据从部分视图收集的元数据在根(布局)视图的上下文中自动构建。视图提供的元数据包含初始化其Javascript对象和要加载的Javascript文件所需的选项。

问题

与WebForms不同,MVC视图没有任何我所知道的自然层次结构,在视图及其部分(子)视图之间传递信息似乎相当棘手。如果在视图中使用Html.Action之类的帮助程序,则“子视图”的整个处理将独立进行,因此它们甚至不共享Page对象。我需要的是某种中心位置,其中视图可以在呈现时存储元数据,以便可以在布局视图中使用它来组合和输出完整的脚本。

解决方案?

我能想到的一种方法是使用HttpContext.Current.Items临时存储一组视图元数据对象。所有视图都将元数据存放在那里,布局视图将使用它。执行的顺序似乎符合我的期望,但是我仍然无法重建视图的树层次结构。为了能够做到这一点,我需要使用一个堆栈,其中视图将在其渲染开始时注册并在结束时取消注册,以便可以在顶部找到父级。

  1. 有没有办法在我可以放置这个逻辑的地方有一些渲染前/渲染后的钩子?
  2. 这首先是一个好主意吗?
  3. 我看不到完全不同的解决方案吗?

2 个答案:

答案 0 :(得分:2)

您可以编写自定义视图引擎:

public class MyViewEngine : RazorViewEngine
{
    private class MyRazorView : RazorView
    {
        public MyRazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator)
            : base(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions)
        {
        }

        protected override void RenderView(ViewContext viewContext, System.IO.TextWriter writer, object instance)
        {
            var stack = viewContext.HttpContext.Items["stack"] as Stack<string>;
            if (stack == null)
            {
                stack = new Stack<string>();
                viewContext.HttpContext.Items["stack"] = stack;
            }
            // depending on the required logic you could
            // use a stack of some model and push some additional
            // information about the view (see below)
            stack.Push(this.ViewPath);
            base.RenderView(viewContext, writer, instance);
        }
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        return new MyRazorView(controllerContext, viewPath, masterPath, true, base.FileExtensions, base.ViewPageActivator);
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        return new MyRazorView(controllerContext, partialPath, null, false, base.FileExtensions, base.ViewPageActivator);
    }
}

您将在Application_Start注册:

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

现在您可以编写一个自定义HTML帮助程序,它将选择存储在HttpContext中的堆栈并执行一些有用的操作:

public static class HtmlExtensions
{
    public static IHtmlString BuildTree(this HtmlHelper htmlHelper)
    {
        var stack = htmlHelper.ViewContext.HttpContext.Items["stack"] as Stack<string>;
        if (stack == null)
        {
            return MvcHtmlString.Empty;
        }


        // TODO: your custom logic to build the tree
        ...
    }
}

并在你的_Layout结束时:

    ...
    <script type="text/javascript">
        @Html.BuildTree()
    </script>
</body>

答案 1 :(得分:-2)

如果您只想将视图与Javascript文件关联,请在layout.cshtml中定义一个部分(在正文标记结束之前):

@RenderSection("scripts", false")

然后在您的页面和视图中:

@section scripts {
<script type="text/javascript" src="path to script"></script> }

但是,一旦您进入部分视图,这将无效。为了处理部分视图,我使用了自定义Html扩展。 @ Html.RenderResource(“{sectionname},”〜/ {资源路径}“)