From b4763aabaf665419427d0b4b7c3236d264c40fc6 Mon Sep 17 00:00:00 2001 From: Louis DeJardin Date: Mon, 1 Mar 2010 16:43:51 -0800 Subject: [PATCH] Making the use of BaseViewModel optional Providing BaseViewModel.From(x) static methods to adapt properties from various context objects Adds DevTools\HomeController actions to verify results AdaptedViewModel exposes public properties or view data dictionary entries as appropriate Layout view engine restriction loosened to allow any ViewResult to be themed (not Partial of course) --HG-- branch : dev --- .../Core/Feeds/Services/FeedFilter.cs | 2 +- .../Core/Themes/Preview/PreviewThemeFilter.cs | 6 +- .../Controllers/HomeController.cs | 18 ++++-- .../Modules/Orchard.DevTools/Models/Simple.cs | 6 ++ .../Orchard.DevTools/Orchard.DevTools.csproj | 3 + .../Orchard.DevTools/Views/Home/Simple.aspx | 10 +++ .../Views/Home/_RenderableAction.ascx | 2 + .../Themes/TheAdmin/Views/Menu.ascx | 4 +- src/Orchard/Mvc/ViewEngines/LayoutView.cs | 40 ++++++++---- .../Mvc/ViewEngines/LayoutViewEngine.cs | 2 - .../Mvc/ViewModels/AdaptedViewModel.cs | 62 +++++++++++++++++++ src/Orchard/Mvc/ViewModels/BaseViewModel.cs | 23 ++++--- src/Orchard/Orchard.csproj | 2 + src/Orchard/Security/SecurityFilter.cs | 6 +- src/Orchard/Themes/ThemedAttribute.cs | 14 +++++ src/Orchard/UI/Navigation/MenuFilter.cs | 6 +- src/Orchard/UI/Notify/NotifyFilter.cs | 2 +- src/Orchard/UI/Resources/ResourceFilter.cs | 2 +- 18 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Models/Simple.cs create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Simple.aspx create mode 100644 src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/_RenderableAction.ascx create mode 100644 src/Orchard/Mvc/ViewModels/AdaptedViewModel.cs create mode 100644 src/Orchard/Themes/ThemedAttribute.cs diff --git a/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs b/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs index b75a42c43..1241be30f 100644 --- a/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs +++ b/src/Orchard.Web/Core/Feeds/Services/FeedFilter.cs @@ -14,7 +14,7 @@ namespace Orchard.Core.Feeds.Services { } public void OnResultExecuting(ResultExecutingContext filterContext) { - var model = filterContext.Controller.ViewData.Model as BaseViewModel; + var model = BaseViewModel.From(filterContext.Result); if (model == null) { return; } diff --git a/src/Orchard.Web/Core/Themes/Preview/PreviewThemeFilter.cs b/src/Orchard.Web/Core/Themes/Preview/PreviewThemeFilter.cs index 1d43240f7..73cb0e4f6 100644 --- a/src/Orchard.Web/Core/Themes/Preview/PreviewThemeFilter.cs +++ b/src/Orchard.Web/Core/Themes/Preview/PreviewThemeFilter.cs @@ -16,11 +16,7 @@ namespace Orchard.Core.Themes.Preview { } public void OnResultExecuting(ResultExecutingContext filterContext) { - var viewResult = filterContext.Result as ViewResult; - if (viewResult == null) - return; - - var baseViewModel = viewResult.ViewData.Model as BaseViewModel; + var baseViewModel = BaseViewModel.From(filterContext.Result); if (baseViewModel == null) return; diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/HomeController.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/HomeController.cs index 301155379..f40699486 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/HomeController.cs +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/HomeController.cs @@ -1,19 +1,18 @@ using System.Web.Mvc; +using Orchard.DevTools.Models; using Orchard.Mvc.ViewModels; using Orchard.UI.Notify; -namespace Orchard.DevTools.Controllers -{ - public class HomeController : Controller - { +namespace Orchard.DevTools.Controllers { + //[Themed] + public class HomeController : Controller { private readonly INotifier _notifier; public HomeController(INotifier notifier) { _notifier = notifier; } - public ActionResult Index() - { + public ActionResult Index() { return View(new BaseViewModel()); } @@ -22,5 +21,12 @@ namespace Orchard.DevTools.Controllers return new HttpUnauthorizedResult(); } + public ActionResult Simple() { + return View(new Simple { Title = "This is a simple text", Quantity = 5 }); + } + + public ActionResult _RenderableAction() { + return PartialView("_RenderableAction", "This is render action"); + } } } diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Models/Simple.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Models/Simple.cs new file mode 100644 index 000000000..219d5516c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Models/Simple.cs @@ -0,0 +1,6 @@ +namespace Orchard.DevTools.Models { + public class Simple { + public string Title { get; set; } + public int Quantity { get; set; } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj index 819c30b2e..3a03b8417 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj @@ -70,6 +70,7 @@ + @@ -77,6 +78,8 @@ + + diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Simple.aspx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Simple.aspx new file mode 100644 index 000000000..e6d8d8872 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Simple.aspx @@ -0,0 +1,10 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.DevTools.Models" %> +

+ <%= H(Model.Title) %>

+

+ Quantity: + <%= Model.Quantity %>

+
+ <% Html.RenderAction("_RenderableAction"); %>
diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/_RenderableAction.ascx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/_RenderableAction.ascx new file mode 100644 index 000000000..51df0fece --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/_RenderableAction.ascx @@ -0,0 +1,2 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +

<%=H(Model)%>

diff --git a/src/Orchard.Web/Themes/TheAdmin/Views/Menu.ascx b/src/Orchard.Web/Themes/TheAdmin/Views/Menu.ascx index 64315d7e5..ec6b7f7b8 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Views/Menu.ascx +++ b/src/Orchard.Web/Themes/TheAdmin/Views/Menu.ascx @@ -9,9 +9,9 @@ ? Html.ActionLink(menuSection.Text, (string)firstSectionItem.RouteValues["action"], firstSectionItem.RouteValues).ToHtmlString() : string.Format("{0}", Html.Encode(menuSection.Text)); var classification = ""; - if (menuSection == Model.AdminMenu.First()) + if (menuSection == Model.Menu.First()) classification = "first "; - if (menuSection == Model.AdminMenu.Last()) + if (menuSection == Model.Menu.Last()) classification += "last "; %> diff --git a/src/Orchard/Mvc/ViewEngines/LayoutView.cs b/src/Orchard/Mvc/ViewEngines/LayoutView.cs index b32c0d2cb..9997229fa 100644 --- a/src/Orchard/Mvc/ViewEngines/LayoutView.cs +++ b/src/Orchard/Mvc/ViewEngines/LayoutView.cs @@ -2,8 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Web.Mvc; +using Orchard.Mvc.ViewModels; +using Orchard.Security; +using Orchard.UI.Navigation; +using Orchard.UI.Notify; +using Orchard.UI.Zones; namespace Orchard.Mvc.ViewEngines { public class LayoutView : IView { @@ -20,25 +24,37 @@ namespace Orchard.Mvc.ViewEngines { var layoutViewContext = LayoutViewContext.From(viewContext); for (var index = 0; index != _viewEngineResults.Length; ++index) { + bool isFirst = index == 0; + bool isLast = index == _viewEngineResults.Length - 1; + + var effectiveWriter = isLast ? viewContext.Writer : new StringWriter(); + var effectiveViewData = isFirst ? viewContext.ViewData : CoerceViewData(viewContext.ViewData); var viewEngineResult = _viewEngineResults[index]; - if (index == _viewEngineResults.Length - 1) { - viewEngineResult.View.Render(viewContext, writer); - } - else { - //TEMP: to be replaced with an efficient spooling writer - var childContext = new ViewContext( + + var effectiveContext = new ViewContext( viewContext, viewEngineResult.View, - viewContext.ViewData, + effectiveViewData, viewContext.TempData, - new StringWriter()); - viewEngineResult.View.Render(childContext, childContext.Writer); - layoutViewContext.BodyContent = childContext.Writer.ToString(); - } + effectiveWriter); + + viewEngineResult.View.Render(effectiveContext, effectiveWriter); + + if (!isLast) + layoutViewContext.BodyContent = effectiveWriter.ToString(); } } } + private static ViewDataDictionary CoerceViewData(ViewDataDictionary dictionary) { + if (dictionary.Model is BaseViewModel) + return dictionary; + + return new ViewDataDictionary(BaseViewModel.From(dictionary)); + } + + + public void ReleaseViews(ControllerContext context) { foreach (var viewEngineResult in _viewEngineResults) { viewEngineResult.ViewEngine.ReleaseView(context, viewEngineResult.View); diff --git a/src/Orchard/Mvc/ViewEngines/LayoutViewEngine.cs b/src/Orchard/Mvc/ViewEngines/LayoutViewEngine.cs index 7113fd46b..9e6617db3 100644 --- a/src/Orchard/Mvc/ViewEngines/LayoutViewEngine.cs +++ b/src/Orchard/Mvc/ViewEngines/LayoutViewEngine.cs @@ -24,8 +24,6 @@ namespace Orchard.Mvc.ViewEngines { var skipLayoutViewEngine = false; if (string.IsNullOrEmpty(masterName) == false) skipLayoutViewEngine = true; - if (!(controllerContext.Controller.ViewData.Model is BaseViewModel)) - skipLayoutViewEngine = true; if (_viewEngines == null || _viewEngines.Count == 0) skipLayoutViewEngine = true; if (skipLayoutViewEngine) diff --git a/src/Orchard/Mvc/ViewModels/AdaptedViewModel.cs b/src/Orchard/Mvc/ViewModels/AdaptedViewModel.cs new file mode 100644 index 000000000..ad797b95c --- /dev/null +++ b/src/Orchard/Mvc/ViewModels/AdaptedViewModel.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Orchard.Security; +using Orchard.UI.Navigation; +using Orchard.UI.Notify; +using Orchard.UI.Zones; + +namespace Orchard.Mvc.ViewModels { + class AdaptedViewModel : BaseViewModel { + private readonly Property _zones; + private readonly Property> _menu; + private readonly Property> _messages; + private readonly Property _currentUser; + + public AdaptedViewModel(ViewDataDictionary original) { + _zones = new Property(original, "Zones", () => new ZoneCollection()); + _menu = new Property>(original, "Menu", Enumerable.Empty); + _messages = new Property>(original, "Messages", () => new NotifyEntry[0]); + _currentUser = new Property(original, "CurrentUser", () => null); + } + + public override ZoneCollection Zones { get { return _zones.Value; } set { _zones.Value = value; } } + public override IEnumerable Menu { get { return _menu.Value; } set { _menu.Value = value; } } + public override IList Messages { get { return _messages.Value; } set { _messages.Value = value; } } + public override IUser CurrentUser { get { return _currentUser.Value; } set { _currentUser.Value = value; } } + + + class Property where TProperty : class { + private readonly ViewDataDictionary _original; + private readonly string _expression; + private Func _builder; + private TProperty _value; + + public Property(ViewDataDictionary original, string expression, Func builder) { + _original = original; + _expression = expression; + _builder = builder; + } + + public TProperty Value { + get { + if (_value == null && _builder != null) { + _value = _original.Eval(_expression) as TProperty; + if (_value == null) + SetValue(_builder()); + } + + return _value; + } + set { SetValue(value); } + } + + private void SetValue(TProperty value) { + _builder = null; + _value = value; + _original[_expression] = _value; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Mvc/ViewModels/BaseViewModel.cs b/src/Orchard/Mvc/ViewModels/BaseViewModel.cs index 863bda65c..d94cec592 100644 --- a/src/Orchard/Mvc/ViewModels/BaseViewModel.cs +++ b/src/Orchard/Mvc/ViewModels/BaseViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web.Mvc; using Orchard.Security; using Orchard.UI.Navigation; using Orchard.UI.Notify; @@ -10,18 +11,20 @@ namespace Orchard.Mvc.ViewModels { private ZoneCollection _zones = new ZoneCollection(); private IList _messages = new List(); - public virtual ZoneCollection Zones { - get { return _zones; } - set { _zones = value; } - } - - public virtual IList Messages { - get { return _messages; } - set { _messages = value; } - } - + public virtual ZoneCollection Zones { get { return _zones; } set { _zones = value; } } + public virtual IList Messages { get { return _messages; } set { _messages = value; } } public virtual IUser CurrentUser { get; set; } public virtual IEnumerable Menu { get; set; } + + public static BaseViewModel From(ViewDataDictionary viewData) { + var model = viewData.Model as BaseViewModel; + return model ?? new AdaptedViewModel(viewData); + } + + public static BaseViewModel From(ActionResult actionResult) { + var viewResult = actionResult as ViewResult; + return viewResult == null ? null : From(viewResult.ViewData); + } } [Obsolete("Please change your code to use BaseViewModel, as AdminViewModel will likely be removed in the near future.")] diff --git a/src/Orchard/Orchard.csproj b/src/Orchard/Orchard.csproj index c66fa8ea0..259a8e8a0 100644 --- a/src/Orchard/Orchard.csproj +++ b/src/Orchard/Orchard.csproj @@ -153,6 +153,7 @@ + @@ -224,6 +225,7 @@ + diff --git a/src/Orchard/Security/SecurityFilter.cs b/src/Orchard/Security/SecurityFilter.cs index 925cab1cf..79a3030fc 100644 --- a/src/Orchard/Security/SecurityFilter.cs +++ b/src/Orchard/Security/SecurityFilter.cs @@ -19,11 +19,7 @@ namespace Orchard.Security { public ILogger Logger { get; set; } public void OnResultExecuting(ResultExecutingContext filterContext) { - var viewResult = filterContext.Result as ViewResultBase; - if (viewResult == null) - return; - - var baseViewModel = viewResult.ViewData.Model as BaseViewModel; + var baseViewModel = BaseViewModel.From(filterContext.Result); if (baseViewModel == null) return; diff --git a/src/Orchard/Themes/ThemedAttribute.cs b/src/Orchard/Themes/ThemedAttribute.cs new file mode 100644 index 000000000..07ce1c63b --- /dev/null +++ b/src/Orchard/Themes/ThemedAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Orchard.Themes { + public class ThemedAttribute : Attribute { + public ThemedAttribute() { + Enabled = true; + } + public ThemedAttribute(bool enabled) { + Enabled = enabled; + } + + public bool Enabled { get; set; } + } +} diff --git a/src/Orchard/UI/Navigation/MenuFilter.cs b/src/Orchard/UI/Navigation/MenuFilter.cs index ee0ade9c5..e844ac5f4 100644 --- a/src/Orchard/UI/Navigation/MenuFilter.cs +++ b/src/Orchard/UI/Navigation/MenuFilter.cs @@ -12,11 +12,7 @@ namespace Orchard.UI.Navigation { } public void OnResultExecuting(ResultExecutingContext filterContext) { - var viewResult = filterContext.Result as ViewResult; - if (viewResult == null) - return; - - var baseViewModel = viewResult.ViewData.Model as BaseViewModel; + var baseViewModel = BaseViewModel.From(filterContext.Result); if (baseViewModel == null) return; diff --git a/src/Orchard/UI/Notify/NotifyFilter.cs b/src/Orchard/UI/Notify/NotifyFilter.cs index 5acb6d3f5..d25c841b5 100644 --- a/src/Orchard/UI/Notify/NotifyFilter.cs +++ b/src/Orchard/UI/Notify/NotifyFilter.cs @@ -53,7 +53,7 @@ namespace Orchard.UI.Notify { if (viewResult == null) return; - var baseViewModel = viewResult.ViewData.Model as BaseViewModel; + var baseViewModel = BaseViewModel.From(viewResult); // if it's not a view model that holds messages, don't touch temp data either if (baseViewModel == null) return; diff --git a/src/Orchard/UI/Resources/ResourceFilter.cs b/src/Orchard/UI/Resources/ResourceFilter.cs index c6f67c1a0..1e1429568 100644 --- a/src/Orchard/UI/Resources/ResourceFilter.cs +++ b/src/Orchard/UI/Resources/ResourceFilter.cs @@ -13,7 +13,7 @@ namespace Orchard.UI.Resources { } public void OnResultExecuting(ResultExecutingContext filterContext) { - var model = filterContext.Controller.ViewData.Model as BaseViewModel; + var model = BaseViewModel.From(filterContext.Result); if (model == null) { return; }