diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 872e61da06..6553ac5116 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -165,7 +165,7 @@ namespace Umbraco.Tests.Routing { } - public ActionResult HomePage(RenderModel model) + public ActionResult HomePage(ContentModel model) { return View(); } diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index 112981bfae..c264076d9a 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Web.Mvc if (x.ReturnParameter == null || x.ReturnParameter.ParameterType != typeof (ActionResult)) return false; var p = x.GetParameters(); if (p.Length != 1) return false; - if (p[0].ParameterType != typeof (RenderModel)) return false; + if (p[0].ParameterType != typeof (ContentModel)) return false; return true; }); } @@ -154,7 +154,7 @@ namespace Umbraco.Tests.Web.Mvc { } - public override ActionResult Index(RenderModel model) + public override ActionResult Index(ContentModel model) { return base.Index(model); } @@ -166,7 +166,7 @@ namespace Umbraco.Tests.Web.Mvc { } - public ActionResult Index(RenderModel model, int page) + public ActionResult Index(ContentModel model, int page) { return base.Index(model); } @@ -178,7 +178,7 @@ namespace Umbraco.Tests.Web.Mvc { } - public new async Task Index(RenderModel model) + public new async Task Index(ContentModel model) { return await Task.FromResult(base.Index(model)); } diff --git a/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs index a0e1bc5f9d..41c133b6b7 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderModelBinderTests.cs @@ -30,35 +30,35 @@ namespace Umbraco.Tests.Web.Mvc [Test] public void Returns_Binder_For_IPublishedContent_And_IRenderModel() { - var binder = new RenderModelBinder(); + var binder = new ContentModelBinder(); var found = binder.GetBinder(typeof (IPublishedContent)); Assert.IsNotNull(found); - found = binder.GetBinder(typeof(RenderModel)); + found = binder.GetBinder(typeof(ContentModel)); Assert.IsNotNull(found); found = binder.GetBinder(typeof(MyContent)); Assert.IsNotNull(found); - found = binder.GetBinder(typeof(RenderModel)); + found = binder.GetBinder(typeof(ContentModel)); Assert.IsNotNull(found); found = binder.GetBinder(typeof(MyOtherContent)); Assert.IsNull(found); - found = binder.GetBinder(typeof(MyCustomRenderModel)); + found = binder.GetBinder(typeof(MyCustomContentModel)); Assert.IsNull(found); - found = binder.GetBinder(typeof(IRenderModel)); + found = binder.GetBinder(typeof(IContentModel)); Assert.IsNull(found); } [Test] public void BindModel_Null_Source_Returns_Null() { - Assert.IsNull(RenderModelBinder.BindModel(null, typeof(MyContent), CultureInfo.CurrentCulture)); + Assert.IsNull(ContentModelBinder.BindModel(null, typeof(MyContent))); } [Test] public void BindModel_Returns_If_Same_Type() { var content = new MyContent(Mock.Of()); - var bound = RenderModelBinder.BindModel(content, typeof (IPublishedContent), CultureInfo.CurrentCulture); + var bound = ContentModelBinder.BindModel(content, typeof (IPublishedContent)); Assert.AreSame(content, bound); } @@ -66,8 +66,8 @@ namespace Umbraco.Tests.Web.Mvc public void BindModel_RenderModel_To_IPublishedContent() { var content = new MyContent(Mock.Of()); - var renderModel = new RenderModel(content, CultureInfo.CurrentCulture); - var bound = RenderModelBinder.BindModel(renderModel, typeof(IPublishedContent), CultureInfo.CurrentCulture); + var renderModel = new ContentModel(content); + var bound = ContentModelBinder.BindModel(renderModel, typeof(IPublishedContent)); Assert.AreSame(content, bound); } @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Web.Mvc public void BindModel_IPublishedContent_To_RenderModel() { var content = new MyContent(Mock.Of()); - var bound = (IRenderModel)RenderModelBinder.BindModel(content, typeof(RenderModel), CultureInfo.CurrentCulture); + var bound = (IContentModel)ContentModelBinder.BindModel(content, typeof(ContentModel)); Assert.AreSame(content, bound.Content); } @@ -83,14 +83,14 @@ namespace Umbraco.Tests.Web.Mvc public void BindModel_IPublishedContent_To_Generic_RenderModel() { var content = new MyContent(Mock.Of()); - var bound = (IRenderModel)RenderModelBinder.BindModel(content, typeof(RenderModel), CultureInfo.CurrentCulture); + var bound = (IContentModel)ContentModelBinder.BindModel(content, typeof(ContentModel)); Assert.AreSame(content, bound.Content); } [Test] public void No_DataToken_Returns_Null() { - var binder = new RenderModelBinder(); + var binder = new ContentModelBinder(); var routeData = new RouteData(); var result = binder.BindModel(new ControllerContext(Mock.Of(), routeData, Mock.Of()), new ModelBindingContext()); @@ -101,7 +101,7 @@ namespace Umbraco.Tests.Web.Mvc [Test] public void Invalid_DataToken_Model_Type_Returns_Null() { - var binder = new RenderModelBinder(); + var binder = new ContentModelBinder(); var routeData = new RouteData(); routeData.DataTokens[Core.Constants.Web.UmbracoDataToken] = "hello"; @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Web.Mvc public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation() { var content = new MyContent(Mock.Of()); - var binder = new RenderModelBinder(); + var binder = new ContentModelBinder(); var routeData = new RouteData(); routeData.DataTokens[Core.Constants.Web.UmbracoDataToken] = content; @@ -156,14 +156,11 @@ namespace Umbraco.Tests.Web.Mvc Assert.AreEqual(content, result); } - public class MyCustomRenderModel : RenderModel + public class MyCustomContentModel : ContentModel { - public MyCustomRenderModel(IPublishedContent content, CultureInfo culture) : base(content, culture) - { - } - public MyCustomRenderModel(IPublishedContent content) : base(content) - { - } + public MyCustomContentModel(IPublishedContent content) + : base(content) + { } } public class MyOtherContent diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index 7b30182b59..ecd88f2e87 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -51,7 +51,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_To_RenderModel() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelTestPage(); var viewData = new ViewDataDictionary(model); @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType1_To_ContentType1() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType1TestPage(); var viewData = new ViewDataDictionary(model); @@ -79,7 +79,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType2_To_ContentType1() { var content = new ContentType2(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType1TestPage(); var viewData = new ViewDataDictionary(model); @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType1_To_ContentType2() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType2TestPage(); var viewData = new ViewDataDictionary(model); @@ -106,14 +106,14 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType1_To_RenderModelOf_ContentType1() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); var viewData = new ViewDataDictionary(model); view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -121,14 +121,14 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType2_To_RenderModelOf_ContentType1() { var content = new ContentType2(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); var viewData = new ViewDataDictionary(model); view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModel_ContentType1_To_RenderModelOf_ContentType2() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType2TestPage(); var viewData = new ViewDataDictionary(model); @@ -153,7 +153,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType1_To_RenderModel() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelTestPage(); var viewData = new ViewDataDictionary(model); @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType1_To_ContentType1() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType1TestPage(); var viewData = new ViewDataDictionary(model); @@ -181,7 +181,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType2_To_ContentType1() { var content = new ContentType2(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType1TestPage(); var viewData = new ViewDataDictionary(model); @@ -195,7 +195,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType1_To_ContentType2() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new ContentType2TestPage(); var viewData = new ViewDataDictionary(model); @@ -207,14 +207,14 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType1() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); var viewData = new ViewDataDictionary(model); view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -222,14 +222,14 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() { var content = new ContentType2(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); var viewData = new ViewDataDictionary(model); view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -237,7 +237,7 @@ namespace Umbraco.Tests.Web.Mvc public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType2() { var content = new ContentType1(null); - var model = new RenderModel(content, CultureInfo.InvariantCulture); + var model = new ContentModel(content); var view = new RenderModelOfContentType2TestPage(); var viewData = new ViewDataDictionary(model); @@ -259,7 +259,7 @@ namespace Umbraco.Tests.Web.Mvc view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf(view.Model); + Assert.IsInstanceOf(view.Model); } [Test] @@ -272,7 +272,7 @@ namespace Umbraco.Tests.Web.Mvc view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -286,7 +286,7 @@ namespace Umbraco.Tests.Web.Mvc view.ViewContext = GetViewContext(); view.SetViewDataX(viewData); - Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } @@ -355,13 +355,13 @@ namespace Umbraco.Tests.Web.Mvc } } - public class RenderModelTestPage : TestPage + public class RenderModelTestPage : TestPage { } - public class RenderModelOfContentType1TestPage : TestPage> + public class RenderModelOfContentType1TestPage : TestPage> { } - public class RenderModelOfContentType2TestPage : TestPage> + public class RenderModelOfContentType2TestPage : TestPage> { } public class ContentType1TestPage : TestPage diff --git a/src/Umbraco.Web/Models/ContentModel.cs b/src/Umbraco.Web/Models/ContentModel.cs new file mode 100644 index 0000000000..b644efd806 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentModel.cs @@ -0,0 +1,26 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + /// + /// Represents the model for the current Umbraco view. + /// + public class ContentModel : IContentModel + { + /// + /// Initializes a new instance of the class with a content. + /// + /// + public ContentModel(IPublishedContent content) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + Content = content; + } + + /// + /// Gets the content. + /// + public IPublishedContent Content { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentModelOfTContent.cs b/src/Umbraco.Web/Models/ContentModelOfTContent.cs new file mode 100644 index 0000000000..bf4d81dbf3 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentModelOfTContent.cs @@ -0,0 +1,23 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public class ContentModel : ContentModel + where TContent : IPublishedContent + { + /// + /// Initializes a new instance of the class with a content. + /// + /// + public ContentModel(TContent content) + : base(content) + { + Content = content; + } + + /// + /// Gets the content. + /// + public new TContent Content { get; } + } +} diff --git a/src/Umbraco.Web/Models/IRenderModel.cs b/src/Umbraco.Web/Models/IContentModel.cs similarity index 78% rename from src/Umbraco.Web/Models/IRenderModel.cs rename to src/Umbraco.Web/Models/IContentModel.cs index 1b951a7755..d0d4f175d7 100644 --- a/src/Umbraco.Web/Models/IRenderModel.cs +++ b/src/Umbraco.Web/Models/IContentModel.cs @@ -1,10 +1,10 @@ -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web.Models -{ - public interface IRenderModel - { - IPublishedContent Content { get; } - } -} +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Models +{ + public interface IContentModel + { + IPublishedContent Content { get; } + } +} diff --git a/src/Umbraco.Web/Models/RenderModel.cs b/src/Umbraco.Web/Models/RenderModel.cs deleted file mode 100644 index a51bd8130f..0000000000 --- a/src/Umbraco.Web/Models/RenderModel.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Models -{ - /// - /// Represents the model for the current rendering page in Umbraco - /// - public class RenderModel : IRenderModel - { - /// - /// Constructor specifying both the IPublishedContent and the CultureInfo - /// - /// - /// - public RenderModel(IPublishedContent content, CultureInfo culture) - { - if (content == null) throw new ArgumentNullException("content"); - if (culture == null) throw new ArgumentNullException("culture"); - Content = content; - CurrentCulture = culture; - } - - /// - /// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext - /// - /// - public RenderModel(IPublishedContent content) - { - if (content == null) throw new ArgumentNullException("content"); - if (UmbracoContext.Current == null) - { - throw new InvalidOperationException("Cannot construct a RenderModel without specifying a CultureInfo when no UmbracoContext has been initialized"); - } - Content = content; - CurrentCulture = UmbracoContext.Current.PublishedContentRequest.Culture; - } - - /// - /// Returns the current IPublishedContent object - /// - public IPublishedContent Content { get; private set; } - - /// - /// Returns the current Culture assigned to the page being rendered - /// - public CultureInfo CurrentCulture { get; private set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/RenderModelOfTContent.cs b/src/Umbraco.Web/Models/RenderModelOfTContent.cs deleted file mode 100644 index 0a7b3292ea..0000000000 --- a/src/Umbraco.Web/Models/RenderModelOfTContent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Models -{ - public class RenderModel : RenderModel - where TContent : IPublishedContent - { - /// - /// Constructor specifying both the IPublishedContent and the CultureInfo - /// - /// - /// - public RenderModel(TContent content, CultureInfo culture) - : base(content, culture) - { - Content = content; - } - - /// - /// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext - /// - /// - public RenderModel(TContent content) - : base(content) - { - Content = content; - } - - /// - /// Returns the current IPublishedContent object - /// - public new TContent Content { get; private set; } - } -} diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/ContentModelBinder.cs similarity index 71% rename from src/Umbraco.Web/Mvc/RenderModelBinder.cs rename to src/Umbraco.Web/Mvc/ContentModelBinder.cs index 2646e4de8b..5e012a422f 100644 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ b/src/Umbraco.Web/Mvc/ContentModelBinder.cs @@ -1,184 +1,170 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Mvc -{ - /// - /// Allows for Model Binding any IPublishedContent or IRenderModel - /// - public class RenderModelBinder : DefaultModelBinder, IModelBinderProvider - { - /// - /// Binds the model to a value by using the specified controller context and binding context. - /// - /// - /// The bound value. - /// - /// The controller context.The binding context. - public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) - { - object model; - if (controllerContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out model) == false) - return null; - - //This model binder deals with IRenderModel and IPublishedContent by extracting the model from the route's - // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler - // and both always set the model to an instance of `RenderModel`. So if this isn't an instance of IRenderModel then - // we need to let the DefaultModelBinder deal with the logic. - var renderModel = model as IRenderModel; - if (renderModel == null) - { - model = base.BindModel(controllerContext, bindingContext); - if (model == null) return null; - } - - //if for any reason the model is not either IRenderModel or IPublishedContent, then we return since those are the only - // types this binder is dealing with. - if ((model is IRenderModel) == false && (model is IPublishedContent) == false) return null; - - // default culture, unless we have an UmbracoContext and a PublishedContentRequest with a culture - var culture = CultureInfo.CurrentCulture; - var umbracoContext = controllerContext.GetUmbracoContext(); - if (umbracoContext?.PublishedContentRequest != null) - culture = umbracoContext.PublishedContentRequest.Culture; - - return BindModel(model, bindingContext.ModelType, culture); - } - - // source is the model that we have - // modelType is the type of the model that we need to bind to - // culture is the CultureInfo that we have, used by RenderModel - // - // create a model object of the modelType by mapping: - // { RenderModel, RenderModel, IPublishedContent } - // to - // { RenderModel, RenderModel, IPublishedContent } - // - public static object BindModel(object source, Type modelType, CultureInfo culture) - { - // null model, return - if (source == null) return null; - - // if types already match, return - var sourceType = source.GetType(); - if (sourceType.Inherits(modelType)) // includes == - return source; - - // try to grab the content - var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent - if (sourceContent == null && sourceType.Implements()) - { - // else check if it's an IRenderModel, and get the content - sourceContent = ((IRenderModel)source).Content; - } - if (sourceContent == null) - { - // else check if we can convert it to a content - var attempt1 = source.TryConvertTo(); - if (attempt1.Success) sourceContent = attempt1.Result; - } - - // if we have a content - if (sourceContent != null) - { - // try to grab the culture - // using supplied culture by default - var sourceRenderModel = source as RenderModel; - if (sourceRenderModel != null) - culture = sourceRenderModel.CurrentCulture; - - // if model is IPublishedContent, check content type and return - if (modelType.Implements()) - { - if ((sourceContent.GetType().Inherits(modelType)) == false) - ThrowModelBindingException(true, false, sourceContent.GetType(), modelType); - return sourceContent; - } - - // if model is RenderModel, create and return - if (modelType == typeof(RenderModel)) - { - return new RenderModel(sourceContent, culture); - } - - // if model is RenderModel, check content type, then create and return - if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) - { - var targetContentType = modelType.GetGenericArguments()[0]; - if ((sourceContent.GetType().Inherits(targetContentType)) == false) - ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); - return Activator.CreateInstance(modelType, sourceContent, culture); - } - } - - // last chance : try to convert - var attempt2 = source.TryConvertTo(modelType); - if (attempt2.Success) return attempt2.Result; - - // fail - ThrowModelBindingException(false, false, sourceType, modelType); - return null; - } - - private static void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) - { - var msg = new StringBuilder(); - - msg.Append("Cannot bind source"); - if (sourceContent) msg.Append(" content"); - msg.Append(" type "); - msg.Append(sourceType.FullName); - msg.Append(" to model"); - if (modelContent) msg.Append(" content"); - msg.Append(" type "); - msg.Append(modelType.FullName); - msg.Append("."); - - // compare FullName for the time being because when upgrading ModelsBuilder, - // Umbraco does not know about the new attribute type - later on, can compare - // on type directly (ie after v7.4.2). - var sourceAttr = sourceType.Assembly.CustomAttributes.FirstOrDefault(x => - x.AttributeType.FullName == "Umbraco.ModelsBuilder.PureLiveAssemblyAttribute"); - var modelAttr = modelType.Assembly.CustomAttributes.FirstOrDefault(x => - x.AttributeType.FullName == "Umbraco.ModelsBuilder.PureLiveAssemblyAttribute"); - - // bah.. names are App_Web_all.generated.cs.8f9494c4.jjuvxz55 so they ARE different, fuck! - // we cannot compare purely on type.FullName 'cos we might be trying to map Sub to Main = fails! - if (sourceAttr != null && modelAttr != null - && sourceType.Assembly.GetName().Version.Revision != modelType.Assembly.GetName().Version.Revision) - { - msg.Append(" Types come from two PureLive assemblies with different versions,"); - msg.Append(" this usually indicates that the application is in an unstable state."); - msg.Append(" The application is restarting now, reload the page and it should work."); - var context = HttpContext.Current; - if (context == null) - AppDomain.Unload(AppDomain.CurrentDomain); - else - ApplicationContext.Current.RestartApplicationPool(new HttpContextWrapper(context)); - } - - throw new ModelBindingException(msg.ToString()); - } - - public IModelBinder GetBinder(Type modelType) - { - // can bind to RenderModel (exact type match) - if (modelType == typeof(RenderModel)) return this; - - // can bind to RenderModel (exact generic type match) - if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this; - - // can bind to TContent where TContent : IPublishedContent (any IPublishedContent implementation) - if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; - return null; - } - } +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Mvc +{ + /// + /// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel. + /// + public class ContentModelBinder : DefaultModelBinder, IModelBinderProvider + { + /// + /// Binds the model to a value by using the specified controller context and binding context. + /// + /// + /// The bound value. + /// + /// The controller context.The binding context. + public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + object model; + if (controllerContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out model) == false) + return null; + + // this model binder deals with IContentModel and IPublishedContent by extracting the model from the route's + // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler + // and both always set the model to an instance of `ContentMOdel`. So if this isn't an instance of IContentModel then + // we need to let the DefaultModelBinder deal with the logic. + var contentModel = model as IContentModel; + if (contentModel == null) + { + model = base.BindModel(controllerContext, bindingContext); + if (model == null) return null; + } + + //if for any reason the model is not either IContentModel or IPublishedContent, then we return since those are the only + // types this binder is dealing with. + if ((model is IContentModel) == false && (model is IPublishedContent) == false) return null; + + return BindModel(model, bindingContext.ModelType); + } + + // source is the model that we have + // modelType is the type of the model that we need to bind to + // + // create a model object of the modelType by mapping: + // { ContentModel, ContentModel, IPublishedContent } + // to + // { ContentModel, ContentModel, IPublishedContent } + // + public static object BindModel(object source, Type modelType) + { + // null model, return + if (source == null) return null; + + // if types already match, return + var sourceType = source.GetType(); + if (sourceType.Inherits(modelType)) // includes == + return source; + + // try to grab the content + var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent + if (sourceContent == null && sourceType.Implements()) + { + // else check if it's an IContentModel, and get the content + sourceContent = ((IContentModel)source).Content; + } + if (sourceContent == null) + { + // else check if we can convert it to a content + var attempt1 = source.TryConvertTo(); + if (attempt1.Success) sourceContent = attempt1.Result; + } + + // if we have a content + if (sourceContent != null) + { + // if model is IPublishedContent, check content type and return + if (modelType.Implements()) + { + if ((sourceContent.GetType().Inherits(modelType)) == false) + ThrowModelBindingException(true, false, sourceContent.GetType(), modelType); + return sourceContent; + } + + // if model is ContentModel, create and return + if (modelType == typeof(ContentModel)) + { + return new ContentModel(sourceContent); + } + + // if model is ContentModel, check content type, then create and return + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) + { + var targetContentType = modelType.GetGenericArguments()[0]; + if ((sourceContent.GetType().Inherits(targetContentType)) == false) + ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); + return Activator.CreateInstance(modelType, sourceContent); + } + } + + // last chance : try to convert + var attempt2 = source.TryConvertTo(modelType); + if (attempt2.Success) return attempt2.Result; + + // fail + ThrowModelBindingException(false, false, sourceType, modelType); + return null; + } + + private static void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) + { + var msg = new StringBuilder(); + + msg.Append("Cannot bind source"); + if (sourceContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(sourceType.FullName); + msg.Append(" to model"); + if (modelContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(modelType.FullName); + msg.Append("."); + + // compare FullName for the time being because when upgrading ModelsBuilder, + // Umbraco does not know about the new attribute type - later on, can compare + // on type directly (ie after v7.4.2). + var sourceAttr = sourceType.Assembly.CustomAttributes.FirstOrDefault(x => + x.AttributeType.FullName == "Umbraco.ModelsBuilder.PureLiveAssemblyAttribute"); + var modelAttr = modelType.Assembly.CustomAttributes.FirstOrDefault(x => + x.AttributeType.FullName == "Umbraco.ModelsBuilder.PureLiveAssemblyAttribute"); + + // bah.. names are App_Web_all.generated.cs.8f9494c4.jjuvxz55 so they ARE different, fuck! + // we cannot compare purely on type.FullName 'cos we might be trying to map Sub to Main = fails! + if (sourceAttr != null && modelAttr != null + && sourceType.Assembly.GetName().Version.Revision != modelType.Assembly.GetName().Version.Revision) + { + msg.Append(" Types come from two PureLive assemblies with different versions,"); + msg.Append(" this usually indicates that the application is in an unstable state."); + msg.Append(" The application is restarting now, reload the page and it should work."); + var context = HttpContext.Current; + if (context == null) + AppDomain.Unload(AppDomain.CurrentDomain); + else + ApplicationContext.Current.RestartApplicationPool(new HttpContextWrapper(context)); + } + + throw new ModelBindingException(msg.ToString()); + } + + public IModelBinder GetBinder(Type modelType) + { + // can bind to ContentModel (exact type match) + if (modelType == typeof(ContentModel)) return this; + + // can bind to ContentModel (exact generic type match) + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) return this; + + // can bind to TContent where TContent : IPublishedContent (any IPublishedContent implementation) + if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; + return null; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/IRenderMvcController.cs b/src/Umbraco.Web/Mvc/IRenderMvcController.cs index 53865cd4ee..9393e38504 100644 --- a/src/Umbraco.Web/Mvc/IRenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/IRenderMvcController.cs @@ -17,6 +17,6 @@ namespace Umbraco.Web.Mvc /// /// /// - ActionResult Index(RenderModel model); + ActionResult Index(ContentModel model); } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index c3adfc770d..b9ec03d394 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -109,7 +109,7 @@ namespace Umbraco.Web.Mvc /// /// [RenderIndexActionSelector] - public virtual ActionResult Index(RenderModel model) + public virtual ActionResult Index(ContentModel model) { return CurrentTemplate(model); } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index a3b7d9def8..711f90cd6a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -79,7 +79,7 @@ namespace Umbraco.Web.Mvc } SetupRouteDataForRequest( - new RenderModel(docRequest.PublishedContent, docRequest.Culture), + new ContentModel(docRequest.PublishedContent), requestContext, docRequest); @@ -92,23 +92,23 @@ namespace Umbraco.Web.Mvc /// /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views /// - /// + /// /// /// - internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) + internal void SetupRouteDataForRequest(ContentModel contentModel, RequestContext requestContext, PublishedContentRequest docRequest) { //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); //required for the RenderModelBinder and view engine + requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, contentModel); //required for the RenderModelBinder and view engine requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, docRequest); //required for RenderMvcController requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, UmbracoContext); //required for UmbracoTemplatePage } - private void UpdateRouteDataForRequest(RenderModel renderModel, RequestContext requestContext) + private void UpdateRouteDataForRequest(ContentModel contentModel, RequestContext requestContext) { - if (renderModel == null) throw new ArgumentNullException("renderModel"); + if (contentModel == null) throw new ArgumentNullException("contentModel"); if (requestContext == null) throw new ArgumentNullException("requestContext"); - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = renderModel; + requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; // the rest should not change -- it's only the published content that has changed } @@ -423,7 +423,7 @@ namespace Umbraco.Web.Mvc // else we are running Mvc // update the route data - because the PublishedContent has changed UpdateRouteDataForRequest( - new RenderModel(publishedContentRequest.PublishedContent, publishedContentRequest.Culture), + new ContentModel(publishedContentRequest.PublishedContent), requestContext); // update the route definition routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs index fdf61059e0..9b1766e84c 100644 --- a/src/Umbraco.Web/Mvc/RenderViewEngine.cs +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -97,13 +97,13 @@ namespace Umbraco.Web.Mvc //first check if we're rendering a partial view for the back office, or surface controller, etc... //anything that is not IUmbracoRenderModel as this should only pertain to Umbraco views. - if (isPartial && ((umbracoToken is RenderModel) == false)) + if (isPartial && ((umbracoToken is ContentModel) == false)) { return true; } //only find views if we're rendering the umbraco front end - if (umbracoToken is RenderModel) + if (umbracoToken is ContentModel) { return true; } diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs index 3a8a2efffc..74d2da2420 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPage.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPage.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 6c19836be5..eb564c3cd3 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -7,7 +7,6 @@ using System.Web.WebPages; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; using Umbraco.Web.Routing; @@ -16,99 +15,79 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Mvc { /// - /// The View that umbraco front-end views inherit from + /// Represents the properties and methods that are needed in order to render an Umbraco view. /// public abstract class UmbracoViewPage : WebViewPage { - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext - { - get - { - //we should always try to return the context from the data tokens just in case its a custom context and not - //using the UmbracoContext.Current, we will fallback to the singleton if necessary. - var umbCtx = ViewContext.GetUmbracoContext() - //lastly, we will use the singleton, the only reason this should ever happen is is someone is rendering a page that inherits from this - //class and are rendering it outside of the normal Umbraco routing process. Very unlikely. - ?? UmbracoContext.Current; - - return umbCtx; - } - } + private UmbracoContext _umbracoContext; + private UmbracoHelper _helper; + private MembershipHelper _membershipHelper; /// - /// Returns the current ApplicationContext + /// Gets the current . /// - public ApplicationContext ApplicationContext - { - get { return UmbracoContext.Application; } - } + // always try to return the context from the data tokens just in case its a custom context and not + // using the Current.UmbracoContext. Fallback to that singleton if necessary, the only reason this + // should ever happen is is someone is rendering a page that inherits from this class and are rendering + // it outside of the normal Umbraco routing process. Very unlikely. + public UmbracoContext UmbracoContext => _umbracoContext ?? + (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); /// - /// Returns the current PublishedContentRequest + /// Gets the current . + /// + public ApplicationContext ApplicationContext => UmbracoContext.Application; + + /// + /// Gets the current . /// internal PublishedContentRequest PublishedContentRequest { get { - //we should always try to return the object from the data tokens just in case its a custom object and not - //using the UmbracoContext.Current. - //we will fallback to the singleton if necessary. - if (ViewContext.RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken)) - { - return (PublishedContentRequest)ViewContext.RouteData.DataTokens.GetRequiredObject(Core.Constants.Web.PublishedDocumentRequestDataToken); - } - //next check if it is a child action and see if the parent has it set in data tokens - if (ViewContext.IsChildAction) - { - if (ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken)) - { - return (PublishedContentRequest)ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(Core.Constants.Web.PublishedDocumentRequestDataToken); - } - } + const string token = Core.Constants.Web.PublishedDocumentRequestDataToken; - //lastly, we will use the singleton, the only reason this should ever happen is is someone is rendering a page that inherits from this - //class and are rendering it outside of the normal Umbraco routing process. Very unlikely. - return UmbracoContext.Current.PublishedContentRequest; + // we should always try to return the object from the data tokens just in case its a custom object and not + // the one from UmbracoContext. Fallback to UmbracoContext if necessary. + + // try view context + if (ViewContext.RouteData.DataTokens.ContainsKey(token)) + return (PublishedContentRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(token); + + // child action, try parent view context + if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(token)) + return (PublishedContentRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(token); + + // fallback to UmbracoContext + return UmbracoContext.PublishedContentRequest; } } - private UmbracoHelper _helper; - private MembershipHelper _membershipHelper; - /// - /// Gets an UmbracoHelper + /// Gets an instance. /// - /// - /// This constructs the UmbracoHelper with the content model of the page routed to - /// public virtual UmbracoHelper Umbraco { get { - if (_helper == null) - { - var model = ViewData.Model; - var content = model as IPublishedContent; - if (content == null && model is IRenderModel) - content = ((IRenderModel) model).Content; - _helper = content == null - ? new UmbracoHelper(UmbracoContext) - : new UmbracoHelper(UmbracoContext, content); - } + if (_helper != null) return _helper; + + var model = ViewData.Model; + var content = model as IPublishedContent; + if (content == null && model is IContentModel) + content = ((IContentModel) model).Content; + _helper = content == null + ? new UmbracoHelper(UmbracoContext) + : new UmbracoHelper(UmbracoContext, content); return _helper; } } /// - /// Returns the MemberHelper instance + /// Gets a instance. /// - public MembershipHelper Members - { - get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); } - } + public MembershipHelper Members => _membershipHelper ?? + (_membershipHelper = new MembershipHelper(UmbracoContext)); /// /// Ensure that the current view context is added to the route data tokens so we can extract it if we like @@ -119,17 +98,14 @@ namespace Umbraco.Web.Mvc protected override void InitializePage() { base.InitializePage(); - if (ViewContext.IsChildAction == false) - { - //this is used purely for partial view macros that contain forms - // and mostly just when rendered within the RTE - This should already be set with the - // EnsurePartialViewMacroViewContextFilterAttribute - if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) - { - ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); - } - } + if (ViewContext.IsChildAction) return; + + // this is used purely for partial view macros that contain forms and mostly + // just when rendered within the RTE - this should already be set with the + // EnsurePartialViewMacroViewContextFilterAttribute + if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) + ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); } // maps model @@ -141,11 +117,8 @@ namespace Umbraco.Web.Mvc // map the view data (may change its type, may set model to null) viewData = MapViewDataDictionary(viewData, typeof (TModel)); - var culture = CultureInfo.CurrentCulture; - // bind the model (use context culture as default, if available) - if (UmbracoContext.PublishedContentRequest != null && UmbracoContext.PublishedContentRequest.Culture != null) - culture = UmbracoContext.PublishedContentRequest.Culture; - viewData.Model = RenderModelBinder.BindModel(viewDataModel, typeof (TModel), culture); + // bind the model + viewData.Model = ContentModelBinder.BindModel(viewDataModel, typeof (TModel)); // set the view data base.SetViewData(viewData); @@ -209,7 +182,7 @@ namespace Umbraco.Web.Mvc { // creating previewBadge markup markupToInject = - String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, + string.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, IOHelper.ResolveUrl(SystemDirectories.Umbraco), IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index 5d323d449b..afd63f5094 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Mvc PreparePublishedContentRequest(umbracoContext.PublishedContentRequest); //create the render model - var renderModel = new RenderModel(umbracoContext.PublishedContentRequest.PublishedContent, umbracoContext.PublishedContentRequest.Culture); + var renderModel = new ContentModel(umbracoContext.PublishedContentRequest.PublishedContent); //assigns the required tokens to the request requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index d7d331d887..92f547343f 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -138,7 +138,7 @@ namespace Umbraco.Web.Templates }); var routeHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory(), _umbracoContext); var routeDef = routeHandler.GetUmbracoRouteDefinition(requestContext, contentRequest); - var renderModel = new RenderModel(contentRequest.PublishedContent, contentRequest.Culture); + var renderModel = new ContentModel(contentRequest.PublishedContent); //manually add the action/controller, this is required by mvc requestContext.RouteData.Values.Add("action", routeDef.ActionName); requestContext.RouteData.Values.Add("controller", routeDef.ControllerName); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6010511526..7fe0c6330f 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -505,7 +505,7 @@ - + @@ -520,7 +520,7 @@ - + @@ -1068,8 +1068,8 @@ - - + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b766291f95..a7e3345067 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -570,7 +570,7 @@ namespace Umbraco.Web ViewEngines.Engines.Add(new PluginViewEngine()); //set model binder - ModelBinderProviders.BinderProviders.Add(new RenderModelBinder()); // is a provider + ModelBinderProviders.BinderProviders.Add(new ContentModelBinder()); // is a provider ////add the profiling action filter //GlobalFilters.Filters.Add(new ProfilingActionFilter());