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

4

动机

我希望能够在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个回答

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>

多好的、周全的解决方案啊!! - Ofer Zelig
谢谢,听起来很酷。重建树所缺少的唯一一件事是父视图和子视图之间的链接。我想我可以通过保持一个并行的“父”堆栈来实现,在调用base.RenderView之前推入一个项目,并在调用后弹出它。那应该可以工作,对吧? - Tomas Vana

-2
如果您只想将视图与 JavaScript 文件关联起来,请在 layout.cshtml 中定义一个部分(在 body 标签关闭之前):
@RenderSection("scripts", false")

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

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

然而,一旦你涉及到局部视图,这种方法就不再适用了。为了处理局部视图,我使用了自定义的Html扩展。@Html.RenderResource("{sectionname}, "~/{path to resource}")


正如你所说,部分视图上无法使用节。你提到的扩展程序使用了类似于我提出的解决方案,因此我提出的三个问题仍然适用 :) - Tomas Vana

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