从 MVC 中的控制器确定局部视图的模型 [英] Determine the model of a partial view from the controller within MVC

查看:20
本文介绍了从 MVC 中的控制器确定局部视图的模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前的问题是我有一个局部视图,我想确定它正在使用什么模型.

My current problem is that I have a partial view that I want to determine what model is being used by it.

我不得不为我的项目处理一些奇怪的场景,所以我会尝试在这里概述它,也许有人可以提供更好的方法来做到这一点.

I have had to deal with a few strange scenarios for my project so I will try to outline it here, maybe someone can offer a better way to do this.

我正在设计类似 Google iGoogle 页面的内容.带有多个小部件的主页,可以根据需要移动或配置.当前系统加载实际小部件的数据异步查看我的应用程序中控制器的 POST.该控制器要么将部分视图呈现为可以返回的 HTML(然后加载到页面视图 JQUERY),要么只是直接存储在数据库中的 HTML/JavaScript.

I am designing something like the Google iGoogle page. A main page with multiple widgets that are able to move around or be configured as needed. The current system loads the actual widget's data asynchronously view a POST to a controller within my application. That controller will either render a partial view to HTML that can be returned (and then loaded into the page view JQUERY) or just straight HTML/JavaScript that is stored in a database.

这对我来说很好用,我有一个小部件模型,其中包含通过数据库描述的选项字典,然后由局部视图使用.当我想将数据传递给局部视图时,问题就出现了.我能想出的最佳解决方案是让控制器确定所讨论的局部视图使用哪个模型,具有一些将填充模型的函数,然后将它与局部视图一起传递给将其呈现给的函数控制器中的 HTML.

This was working fine for me, I had a model for the widgets that holds a dictionary of options that are described via the database, and then used by the partial view. The problem came when I wanted to pass data to a partial view. The best solution I could come up with was having the controller determine which model the partial view in question uses, have some function that will fill the model, and then pass it, along with the partial view, to the function that will render it to HTML within the controller.

我意识到这对于 MVC 来说是一个奇怪的场景(层正在混合......),任何关于基本设计或实现的建议将不胜感激.

I realize this is an odd scenario for MVC (the layers are blending...) and any advice on fundamental design, or implementation of this would be greatly appreciated.

我目前正在使用 MVC3/Razor.如有任何其他问题,请随时提问.

I am currently using MVC3/Razor. Feel free to ask any other questions.

推荐答案

我设计了一个可能的解决方案原型,因为这看起来是一个有趣的问题.我希望它对你有用.

I prototyped a possible solution to this, because it seemed like a fun problem. I hope it's useful to you.

首先是模型.我决定创建两个小部件",一个用于新闻,一个用于时钟.

First, the models. I decided to create two 'widgets', one for news, and one for a clock.

public class NewsModel
{
    public string[] Headlines { get; set; }

    public NewsModel(params string[] headlines)
    {
        Headlines = headlines;
    }
}

public class ClockModel
{
    public DateTime Now { get; set; }

    public ClockModel(DateTime now)
    {
        Now = now;
    }
}

控制器

我的控制器对视图一无所知.它所做的是返回单个模型,但该模型能够根据视图的要求动态获取正确的模型.

Controller

My controller doesn't know anything about the views. What it does is returns a single model, but that model has the ability to dynamically fetch the right model as required by the view.

public ActionResult Show(string widgetName)
{
    var selector = new ModelSelector();
    selector.WhenRendering<ClockModel>(() => new ClockModel(DateTime.Now));
    selector.WhenRendering<NewsModel>(() => new NewsModel("Headline 1", "Headline 2", "Headline 3"));
    return PartialView(widgetName, selector);
}

使用委托,以便仅在实际使用时才创建/获取正确的模型.

Delegates are used so that the correct model is only created/fetched if it is actually used.

控制器使用的 ModelSelector 非常简单 - 它只保留一袋委托来创建每个模型类型:

The ModelSelector that the controller uses is pretty simple - it just keeps a bag of delegates to create each model type:

public class ModelSelector
{
    private readonly Dictionary<Type, Func<object>> modelLookup = new Dictionary<Type, Func<object>>();

    public void WhenRendering<T>(Func<object> getter)
    {
        modelLookup.Add(typeof(T), getter);
    }

    public object GetModel(Type modelType)
    {
        if (!modelLookup.ContainsKey(modelType))
        {
            throw new KeyNotFoundException(string.Format("A provider for the model type '{0}' was not provided", modelType.FullName));
        }

        return modelLookup[modelType]();
    }
}

视图 - 简单的解决方案

现在,实现视图的最简单方法是:

The Views - Simple solution

Now, the easiest way to implement a view would be:

@model MvcApplication2.ModelSelector
@using MvcApplication2.Models
@{
    var clock = (ClockModel) Model.GetModel(typeof (ClockModel));
}

<h2>The time is: @clock.Now</h2>

你可以在这里结束并使用这种方法.

You could end here and use this approach.

太丑了.我希望我的视图看起来像这样:

That's pretty ugly. I wanted my views to look like this:

@model MvcApplication2.Models.ClockModel
<h2>Clock</h2>
@Model.Now

@model MvcApplication2.Models.NewsModel
<h2>News Widget</h2>
@foreach (var headline in Model.Headlines)
{
    <h3>@headline</h3>
}

为了完成这项工作,我必须创建一个自定义视图引擎.

To make this work, I had to create a custom view engine.

当一个 Razor 视图被编译时,它继承了一个 ViewPage,其中 T@model.所以我们可以使用反射来找出视图想要的类型,然后选择它.

When a Razor view is compiled, it inherits a ViewPage<T>, where T is the @model. So we can use reflection to figure out what type the view wanted, and select it.

public class ModelSelectorEnabledRazorViewEngine : RazorViewEngine
{
    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        var result = base.CreateView(controllerContext, viewPath, masterPath);

        if (result == null)
            return null;

        return new CustomRazorView((RazorView) result);
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        var result = base.CreatePartialView(controllerContext, partialPath);

        if (result == null)
            return null;

        return new CustomRazorView((RazorView)result);
    }

    public class CustomRazorView : IView
    {
        private readonly RazorView view;

        public CustomRazorView(RazorView view)
        {
            this.view = view;
        }

        public void Render(ViewContext viewContext, TextWriter writer)
        {
            var modelSelector = viewContext.ViewData.Model as ModelSelector;
            if (modelSelector == null)
            {
                // This is not a widget, so fall back to stock-standard MVC/Razor rendering
                view.Render(viewContext, writer);
                return;
            }

            // We need to work out what @model is on the view, so that we can pass the correct model to it. 
            // We can do this by using reflection over the compiled views, since Razor views implement a 
            // ViewPage<T>, where T is the @model value. 
            var compiledViewType = BuildManager.GetCompiledType(view.ViewPath);
            var baseType = compiledViewType.BaseType;
            if (baseType == null || !baseType.IsGenericType)
            {
                throw new Exception(string.Format("When the view '{0}' was compiled, the resulting type was '{1}', with base type '{2}'. I expected a base type with a single generic argument; I don't know how to handle this type.", view.ViewPath, compiledViewType, baseType));
            }

            // This will be the value of @model
            var modelType = baseType.GetGenericArguments()[0];
            if (modelType == typeof(object))
            {
                // When no @model is set, the result is a ViewPage<object>
                throw new Exception(string.Format("The view '{0}' needs to include the @model directive to specify the model type. Did you forget to include an @model line?", view.ViewPath));                    
            }

            var model = modelSelector.GetModel(modelType);

            // Switch the current model from the ModelSelector to the value of @model
            viewContext.ViewData.Model = model;

            view.Render(viewContext, writer);
        }
    }
}

通过将其放入 Global.asax.cs 来注册视图引擎:

The view engine is registered by putting this in Global.asax.cs:

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

渲染

我的主页视图包括以下几行来测试它:

Rendering

My home view includes the following lines to test it all out:

@Html.Action("Show", "Widget", new { widgetName = "Clock" })
@Html.Action("Show", "Widget", new { widgetName = "News" })

这篇关于从 MVC 中的控制器确定局部视图的模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆