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

发布于 2024-10-16 12:48:57 字数 543 浏览 1 评论 0原文

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

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

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

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

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

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

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.

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.

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.

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

○闲身 2024-10-23 12:48:57

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

模型

首先,模型。我决定创建两个“小部件”,一个用于新闻,一个用于时钟。

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;
    }
}

控制器

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

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);
}

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

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]();
    }
}

视图 - 简单的解决方案

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

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

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

您可以在此处结束并使用此方法。

观点 - 更好的解决方案

这非常丑陋。我希望我的视图看起来像这样:

@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>
}

实现这一点,我必须创建一个自定义视图引擎。

自定义视图引擎

当编译 Razor 视图时,它会继承 ViewPage,其中 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);
        }
    }
}

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

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

渲染

我的主视图包含以下几行来对其进行全部测试:

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

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

Models

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.

The Views - Better solution

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

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

And

@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.

Custom view engine

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);
        }
    }
}

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" })
季末如歌 2024-10-23 12:48:57

一种选择是在应用程序中扩展部分请求的想法。 Steve Sanderson 有一个很棒的例子 ,尽管该帖子与 MVC 1 和 MVC 1 相关。 2.我认为它对你v3仍然有帮助,但我还没有调查v3来看看MVC团队是否实现了他们自己的版本。在异步场景中,您需要稍微尝试一下实现,也许更改 PartialRequest 定义以根据需要接受不同的信息,但我认为这可能是一个好的开始。最终结果将是更好地隔离关注点,允许各个控制器管理特定类型的部分,进而更好地了解您想要使用的模型类型。

One option would be to extend the idea of partial requests in your application. Steve Sanderson has a fantastic example of this, although the post relates to MVC 1 & 2. I think it would still help in you v3, but I haven't investigated v3 to see if the MVC team implemented their own version. In your asynch scenario, you'll need to toy with the implementation a bit, perhaps change the PartialRequest definition to accept different information as needed, but I think this might be a good start. The net result would be better isolation of concerns, allowing individual controllers to manage a particular type of partial, and in turn be better aware of the model Type you want to work with.

×纯※雪 2024-10-23 12:48:57

我不能 100% 确定这就是您想要的,但可以将 [ChildActionOnly] 属性添加到控制器内的方法中。这要求该方法只能从部分视图调用。然后,您可以为该方法设置部分视图,该方法基本上类似于您的小部件之一。在此处查看 MVC 音乐商店示例:

http://www.asp.net/mvc/tutorials/mvc-music-store-part-10" asp.net/mvc/tutorials/mvc-music-store-part-10

I'm not 100% sure that this is what you'd be looking for, but the [ChildActionOnly] attribute can be added to a method within your controller. That requires that the method can only be called from a partial view. Then you can set up your partial view for that method that basically resembles one of your widgets. Check out the MVC Music Store example here:

http://www.asp.net/mvc/tutorials/mvc-music-store-part-10

任谁 2024-10-23 12:48:57

动态视图模型怎么样? MVC3 中的布局使用它们,也许您可​​以使用类似的东西来达到您的目的:

  1. C# 4.0 中的动态:ExpandoObject 简介
  2. 方法缺失和 C# 4 的乐趣
  3. 动态视图页面,没有视图模型的 MVC

What about a dynamic view model? Layouts in MVC3 use them, and maybe you can use something similar for your purposes:

  1. Dynamic in C# 4.0: Introducing the ExpandoObject
  2. Fun With Method Missing and C# 4
  3. Dynamic View Page, MVC without a View Model
骑趴 2024-10-23 12:48:57

我在博客中谈到了这样做。请参阅 http://blogs.planetcloud.co.uk/mygreatdiscovery/?tag= /widget

本质上我构建了一个类似的小部件系统。这些帖子还介绍了如何处理这些小部件的配置。这利用了 Mvc3 中的动态支持,以便任何模型对象都可以从单个控制器操作传递到视图。

默认情况下,所有小部件都有一组 KVP 属性(我相信这就是 OP 所拥有的)。因此,对于一个简单的小部件,我们可以从视图内访问这些属性。我使用了一个显示一些 html 的小部件(其中 html 存储在这些属性之一中)。

然而,对于更复杂的小部件,我们实现了IWidgetWithDisplayModel。这告诉我们,在将加载的小部件传递回视图之前,我们需要“构建”我们的显示模型。

这是执行此操作的控制器操作。检查帖子以获取完整的详细信息。

[HttpGet]
public ActionResult Get(string name)
{
    var widget = widgetService.GetWidgetBySystemName(name, true);

    if (widget == null)
        return Content(string.Format("Widget [{0}] not found!", name));

    if (!this.ViewExists(widget.WidgetName))
        return Content(string.Format("A template for widget [{0}] was not found.", widget.WidgetName));

    if (widget is IWidgetWithDisplayModel) {
        (widget as IWidgetWithDisplayModel).CreateDisplayModel();
    }

    return PartialView(widget.WidgetName, widget);
}

I blogged about doing exactly this. Please see http://blogs.planetcloud.co.uk/mygreatdiscovery/?tag=/widget

Essentially I built out a similar widget system. The posts also cover how to handle configuration of those widgets. This makes use of the dynamic support in Mvc3 so that any model object can be passed to the view, from a single controller action.

By default all widgets have a collection of KVP properties (I believe this is what the OP has). So for a simple widget we get access to those properties from within the view. I used for a widget that displayed some html (where the html was stored in one of those properties).

However, for more complex widgets we implement IWidgetWithDisplayModel. This tells us that before we pass the loaded widget back to the view, we need to "build" our display model.

Here's the controller action that does that. Check the posts for full details.

[HttpGet]
public ActionResult Get(string name)
{
    var widget = widgetService.GetWidgetBySystemName(name, true);

    if (widget == null)
        return Content(string.Format("Widget [{0}] not found!", name));

    if (!this.ViewExists(widget.WidgetName))
        return Content(string.Format("A template for widget [{0}] was not found.", widget.WidgetName));

    if (widget is IWidgetWithDisplayModel) {
        (widget as IWidgetWithDisplayModel).CreateDisplayModel();
    }

    return PartialView(widget.WidgetName, widget);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文