Asp.net MVC Модель для просмотра и макета - PullRequest
5 голосов
/ 03 апреля 2012

Я пытался найти хороший способ обработки моделей наших веб-сайтов Asp.net MVC, когда у них есть общие свойства для всех страниц.Эти свойства должны отображаться в макете (мастер-страница).Я использую класс «BaseModel», который содержит эти свойства, и мой макет использует эту BaseModel в качестве модели.

Каждая другая модель наследуется от этой BaseModel, и у каждой есть специфические свойства относительно представления, которое она представляет.Как вы уже могли догадаться, мои модели на самом деле являются моделями просмотра, даже если это не совсем уместно здесь.

Я пробовал разные способы инициализации значений BaseModel

  1. "вручную" вEvery View
  2. Наличие базового контроллера, который имеет виртуальный метод Initialize для этого (поэтому конкретный контроллер может реализовывать определенное общее поведение для примера)
  3. Наличие базового контроллера, который переопределяет OnActionExecuting для вызова InitializeМетод
  4. Использование вспомогательного класса, чтобы сделать это вне контроллера
  5. Использование фабрики моделей

Но ни один из них на самом деле мне не нравится:

  1. Кажется очевидным для меня, но СУХОЙ является одной из причин, достаточных для оправдания этого (на самом деле я никогда не пробовал это решение вообще, я просто помещаю его, чтобы иметь возможность зацикливаться на этой точке в последней точке).
  2. Мне не нравится этот, потому что это означает, что всякий раз, когда добавляется новый контроллер, вы должны знать, что он должен наследоваться от BaseControllerи что вам нужно вызвать метод Initialize, не говоря уже о том, что, если ваш контроллер переопределил базовый, в любом случае вызовите базу, чтобы сохранить значения.
  3. см. следующий пункт
  4. и 3... это вариация на ту же тему, но это не очень помогает в вопросах, связанных со вторым решением.
  5. Мой любимый вариант, но сейчас я должен передать еще несколько переменных для установки этих значений.Мне нравится это для инверсии зависимости.Но затем, если я хочу предоставить значения из сеанса, мне нужно передать их явно для примера, тогда я возвращаюсь к исходной точке, поскольку я должен предоставить их вручную (будучи ссылками или через интерфейс любого вида)

Конечно, (почти) все эти решения работают, но я ищу лучший способ сделать это.

При наборе этого вопроса я нашел, возможно, новый путь, шаблон компоновщика , который также может сработать, но реализации также могут быстро стать бременем, поскольку у нас могут быть десятки представлений и контроллеров.

Я с удовольствием приму любую серьезную рекомендацию / подсказку / совет / шаблоны / предложение!

Обновление

Благодаря @EBarr я придумалдругое решение, использующее атрибут ActionFilterAttribute (не производственный код, выполнил это за 5 минут):

public class ModelAttribute : ActionFilterAttribute
{
    public Type ModelType { get; private set; }

    public ModelAttribute(string typeName) : this(Type.GetType(typeName)) { }

    public ModelAttribute(Type modelType)
    {
        if(modelType == null) { throw new ArgumentNullException("modelType"); }

        ModelType = modelType;
        if (!typeof(BaseModel).IsAssignableFrom(ModelType))
        {
            throw new ArgumentException("model type should inherit BaseModel");
        }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var model = ModelFactory.GetModel(ModelType);

        var foo = filterContext.RequestContext.HttpContext.Session["foo"] as Foo;

        model.Foo = foo;
        model.Bar = somevalue;

        filterContext.Controller.TempData["model"] = model;
    } 
}

В этом случае вызов действительно очень прост:

[Model(typeof(HomeModel))]
public ActionResult Index()
{
    var homeModel = TempData["model"] as HomeModel;

    // Add View Specific stuff

    return View(homeModel);
}

И он дает мне лучшее изкаждый мир.Единственный недостаток - найти правильный способ вернуть модель к действию.

Здесь это делается с использованием объекта TempData, но я также рассматриваю возможность обновления модели, которую можно найти в ActionParameters.

Я все еще принимаю любую серьезную рекомендацию / подсказка / совет / шаблоны /предложение для этого или предыдущие пункты.

Ответы [ 2 ]

2 голосов
/ 03 апреля 2012

Я прошел почти такой же процесс, как и в MVC.И вы правы, ни одно из решений не кажется таким замечательным.

В итоге я использовал серию базовых моделей.По разным причинам у меня было несколько разных типов базовых моделей, но логика должна применяться к одному базовому типу.Большинство моих моделей зрения тогда унаследовано от одной из основ.Затем, в зависимости от необходимости / времени, я заполняю базовую часть модели в ActionExecuting или OnActionExecuted.

Фрагмент моего кода, который должен прояснить процесс:

if (filterContext.ActionParameters.ContainsKey("model")) {
   var tempModel = (System.Object)filterContext.ActionParameters["model"];

   if (typeof(BaseModel_SuperLight).IsAssignableFrom(tempModel.GetType())) {
       //do stuff required by light weight model
   }

   if (typeof(BaseModel_RegularWeight).IsAssignableFrom(tempModel.GetType())) {
      //do more costly stuff for regular weight model here
   }
}

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

1 голос
/ 30 апреля 2012

Идея, которая дала мне @EBarr, использовать фильтр действий, на самом деле работала, но в конце показалась неправильной, потому что не было чистого способа извлечь модель без прохождения через пакет просмотра, элементы httpcontext или что-то подобное.Также сделано обязательным оформление каждого действия своей моделью.Это также сделало обратную передачу более трудной для обработки.Я все еще считаю, что это решение имеет свои достоинства и может быть полезно в некоторых конкретных сценариях.

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

  1. Инициализация данных для представлений
  2. Отображение данных

В поисках дополнительной идеи я понял, что я былне глядя на проблему с правильной точки зрения.Я смотрел на него из POV «Контроллер», тогда как конечным клиентом для модели является представление.Мне также напомнили, что страница «Макет / Мастер» не является представлением и не должна иметь связанную с ним модель.Это понимание поставило меня на то, что кажется правильным для меня.Потому что это означало, что каждая «динамическая» часть макета должна обрабатываться вне его.Конечно, разделы, кажется, идеально подходят для этого из-за их гибкости.

В тестовом решении, которое я сделал, у меня было (только) 4 различных раздела, некоторые обязательные, некоторые нет.Проблема с разделами заключается в том, что вам нужно добавлять их на каждую страницу, что может быстро затруднить обновление / изменение.Чтобы решить эту проблему, я попробовал это:

public interface IViewModel
{
    KeyValuePair<string, PartialViewData>[] Sections { get; }
}

public class PartialViewData
{
    public string PartialViewName { get; set; }
    public object PartialViewModel { get; set; }
    public ViewDataDictionary ViewData { get; set; }
}   

Например, моя модель для представления такова:

public class HomeViewModel : IViewModel
{
    public Article[] Articles { get; set; }             // Article is just a dummy class 
    public string QuickContactMessage { get; set; }     // just here to try things

    public HomeViewModel() { Articles = new Article[0]; }

    private Dictionary<string, PartialViewData> _Sections = new Dictionary<string, PartialViewData>();
    public KeyValuePair<string, PartialViewData>[] Sections
    {
        get { return _Sections.ToArray(); }
        set { _Sections = value.ToDictionary(item => item.Key, item => item.Value); }
    }
}

Это инициализируется в действии:

public ActionResult Index()
{
    var hvm = ModelFactory.Get<HomeViewModel>(); // Does not much, basicaly a new HomeViewModel();

    hvm.Sections = LayoutHelper.GetCommonSections().ToArray(); // more on this just after
    hvm.Articles = ArticlesProvider.GetArticles(); // ArticlesProvider could support DI

    return View(hvm);
}

LayoutHelper - это свойство контроллера (которое может быть DI'ed при необходимости):

public class DefaultLayoutHelper
{
    private Controller Controller;
    public DefaultLayoutHelper(Controller controller) { Controller = controller; }

    public Dictionary<string, PartialViewData> GetCommonSections(QuickContactModel quickContactModel = null)
    {
        var sections = new Dictionary<string, PartialViewData>();
        // those calls were made in methods in the solution, I removed it to reduce the length of the answer
        sections.Add("header",
                     Controller.UserLoggedIn() // simple extension that check if there is a user logged in
                     ? new PartialViewData { PartialViewName = "HeaderLoggedIn", PartialViewModel = new HeaderLoggedInViewModel { Username = "Bishop" } } 
                     : new PartialViewData { PartialViewName = "HeaderNotLoggedIn", PartialViewModel = new HeaderLoggedOutViewModel() });
        sections.Add("quotes", new PartialViewData { PartialViewName = "Quotes" });
        sections.Add("quickcontact", new PartialViewData { PartialViewName = "QuickContactForm", PartialViewModel = model ?? new QuickContactModel() });
        return sections;
    }
}

И в представлениях (.cshtml):

@section       quotes { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "quotes").Value); } }
@section        login { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "header").Value); } }
@section       footer { @{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "footer").Value); } }

ФактическоеРешение имеет больше кода, я попытался упростить, чтобы просто получить идею здесь.Это все еще немного необработанно и требует полировки / обработки ошибок, но с этим я могу определить в своем действии, какие будут разделы, какую модель они будут использовать и так далее.Его легко проверить, и настройка DI не должна быть проблемой.

Мне все еще приходится дублировать строки @section в каждом представлении, что кажется немного болезненным (особенно потому, что мы не можем поместить разделы вчастичный вид).

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

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