Определить модель частичного представления из контроллера в MVC - PullRequest
18 голосов
/ 07 февраля 2011

Моя текущая проблема заключается в том, что у меня есть частичное представление, которое я хочу определить, какая модель используется им.

Мне пришлось столкнуться с несколькими странными сценариями для моего проекта, поэтому я постараюсь изложить его здесь, может быть, кто-то может предложить лучший способ сделать это.

Я создаю что-то вродестраница Google iGoogle.Главная страница с несколькими виджетами, которые могут перемещаться или настраиваться по мере необходимости.Текущая система загружает фактические данные виджета асинхронно просматривать POST для контроллера в моем приложении.Этот контроллер будет либо визуализировать частичное представление HTML, которое можно вернуть (а затем загрузить в представление страницы JQUERY), либо просто прямой HTML / JavaScript, который хранится в базе данных.

Для меня это работало нормально,У меня была модель для виджетов, которая содержит словарь опций, которые описываются через базу данных, а затем используются частичным представлением.Проблема возникла, когда я хотел передать данные в частичное представление.Лучшее решение, которое я мог придумать, - это чтобы контроллер определял, какую модель использует рассматриваемое частичное представление, имеет некоторую функцию, которая заполнит модель, а затем передаст ее вместе с частичным представлением функции, которая отобразит ееHTML в контроллере.

Я понимаю, что это странный сценарий для MVC (слои смешиваются ...), и любые советы по фундаментальному проектированию или реализации этого будут с благодарностью.

В настоящее время я использую MVC3 / Razor.Не стесняйтесь задавать любые другие вопросы.

Ответы [ 5 ]

22 голосов
/ 14 августа 2011

Я прототипировал возможное решение этой проблемы, потому что это казалось забавной проблемой.Я надеюсь, что это полезно для вас.

Модели

Сначала модели.Я решил создать два «виджета», один для новостей и один для часов.

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>, где 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" })
0 голосов
/ 14 августа 2011

Я писал о том, что делал именно это. Пожалуйста, смотрите 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);
}
0 голосов
/ 28 марта 2011

Я не уверен на 100%, что это то, что вам нужно, но атрибут [ChildActionOnly] можно добавить к методу в вашем контроллере.Для этого необходимо, чтобы метод вызывался только из partial view.Затем вы можете настроить частичное представление для этого метода, который в основном напоминает один из ваших виджетов.Посмотрите пример музыкального магазина MVC здесь:

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

0 голосов
/ 10 июня 2011

А как насчет модели с динамическим представлением? Макеты в MVC3 используют их, и, возможно, вы можете использовать нечто похожее для ваших целей:

  1. Динамика в C # 4.0: введение в ExpandoObject
  2. Fun с отсутствующим методом и C # 4
  3. Страница динамического просмотра, MVC без модели просмотра
0 голосов
/ 07 февраля 2011

Один из вариантов - расширить представление о частичных запросах в вашем приложении. У Стива Сандерсона есть фантастический пример этого , хотя пост относится к MVC 1 и 2. Я думаю, что это все равно поможет вам в v3, но я не исследовал v3, чтобы увидеть, реализовала ли команда MVC их собственная версия. В вашем асинхронном сценарии вам нужно немного поиграть с реализацией, возможно, изменить определение PartialRequest, чтобы принимать другую информацию по мере необходимости, но я думаю, что это может быть хорошим началом. Конечным результатом будет лучшая изоляция проблем, позволяющая отдельным контроллерам управлять частичным типом, и, в свою очередь, лучше знать тип модели, с которой вы хотите работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...