从MVC中的控制器确定局部视图的模型

elb*_*web 18 model-view-controller asp.net-mvc asp.net-mvc-3

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

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

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

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

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

我目前正在使用MVC3/Razor.随意提出任何其他问题.

Pau*_*ell 22

我为此制作了一个可能的解决方案原型,因为它看起来像一个有趣的问题.我希望它对你有用.

楷模

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

调节器

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

ModelSelector

控制器使用的ModelSelector非常简单 - 它只是让一袋代表创建每个模型类型:

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]();
    }
}
Run Code Online (Sandbox Code Playgroud)

视图 - 简单的解决方案

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

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

<h2>The time is: @clock.Now</h2>
Run Code Online (Sandbox Code Playgroud)

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

观点 - 更好的解决方案

那太难看了.我希望我的观点看起来像这样:

@model MvcApplication2.Models.ClockModel
<h2>Clock</h2>
@Model.Now
Run Code Online (Sandbox Code Playgroud)

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

为了使这项工作,我不得不创建一个自定义视图引擎.

自定义视图引擎

编译Razor视图时,它会继承a ViewPage<T>,其中T@model.因此,我们可以使用反射来确定视图所需的类型,然后选择它.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new ModelSelectorEnabledRazorViewEngine());
Run Code Online (Sandbox Code Playgroud)

渲染

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

@Html.Action("Show", "Widget", new { widgetName = "Clock" })
@Html.Action("Show", "Widget", new { widgetName = "News" })
Run Code Online (Sandbox Code Playgroud)