Идея, которая дала мне @EBarr, использовать фильтр действий, на самом деле работала, но в конце показалась неправильной, потому что не было чистого способа извлечь модель без прохождения через пакет просмотра, элементы httpcontext или что-то подобное.Также сделано обязательным оформление каждого действия своей моделью.Это также сделало обратную передачу более трудной для обработки.Я все еще считаю, что это решение имеет свои достоинства и может быть полезно в некоторых конкретных сценариях.
Итак, я вернулся к исходной точке и стал больше разбираться в этой теме.Я пришел к следующему.Во-первых, проблема имеет два аспекта:
- Инициализация данных для представлений
- Отображение данных
В поисках дополнительной идеи я понял, что я былне глядя на проблему с правильной точки зрения.Я смотрел на него из 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 в каждом представлении, что кажется немного болезненным (особенно потому, что мы не можем поместить разделы вчастичный вид).
Я просматриваю шаблонные делегаты , чтобы посмотреть, не сможет ли это заменить секции.