U4-8709 - refactor UmbracoViewPage, RenderModel

This commit is contained in:
Stephan
2016-06-30 19:06:44 +02:00
parent d2de66ab5c
commit 9332335dff
20 changed files with 349 additions and 438 deletions

View File

@@ -165,7 +165,7 @@ namespace Umbraco.Tests.Routing
{
}
public ActionResult HomePage(RenderModel model)
public ActionResult HomePage(ContentModel model)
{
return View();
}

View File

@@ -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<ActionResult> Index(RenderModel model)
public new async Task<ActionResult> Index(ContentModel model)
{
return await Task.FromResult(base.Index(model));
}

View File

@@ -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<MyContent>));
found = binder.GetBinder(typeof(ContentModel<MyContent>));
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<IPublishedContent>());
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<IPublishedContent>());
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<IPublishedContent>());
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<IPublishedContent>());
var bound = (IRenderModel)RenderModelBinder.BindModel(content, typeof(RenderModel<MyContent>), CultureInfo.CurrentCulture);
var bound = (IContentModel)ContentModelBinder.BindModel(content, typeof(ContentModel<MyContent>));
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<HttpContextBase>(), routeData, Mock.Of<ControllerBase>()),
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<IPublishedContent>());
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

View File

@@ -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<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType1>(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<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType2>(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<ContentType1>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType1>(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<ContentType1>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType1>(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<ContentType2>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType2>(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<ContentType1>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType1>(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<ContentType1>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType1>(content);
var view = new RenderModelOfContentType1TestPage();
var viewData = new ViewDataDictionary(model);
view.ViewContext = GetViewContext();
view.SetViewDataX(viewData);
Assert.IsInstanceOf<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType1>(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<ContentType2>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType2>(content);
var view = new RenderModelOfContentType1TestPage();
var viewData = new ViewDataDictionary(model);
view.ViewContext = GetViewContext();
view.SetViewDataX(viewData);
Assert.IsInstanceOf<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType2>(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<ContentType1>(content, CultureInfo.InvariantCulture);
var model = new ContentModel<ContentType1>(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<RenderModel>(view.Model);
Assert.IsInstanceOf<ContentModel>(view.Model);
}
[Test]
@@ -272,7 +272,7 @@ namespace Umbraco.Tests.Web.Mvc
view.ViewContext = GetViewContext();
view.SetViewDataX(viewData);
Assert.IsInstanceOf<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType1>(view.Model.Content);
}
@@ -286,7 +286,7 @@ namespace Umbraco.Tests.Web.Mvc
view.ViewContext = GetViewContext();
view.SetViewDataX(viewData);
Assert.IsInstanceOf<RenderModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentModel<ContentType1>>(view.Model);
Assert.IsInstanceOf<ContentType1>(view.Model.Content);
}
@@ -355,13 +355,13 @@ namespace Umbraco.Tests.Web.Mvc
}
}
public class RenderModelTestPage : TestPage<RenderModel>
public class RenderModelTestPage : TestPage<ContentModel>
{ }
public class RenderModelOfContentType1TestPage : TestPage<RenderModel<ContentType1>>
public class RenderModelOfContentType1TestPage : TestPage<ContentModel<ContentType1>>
{ }
public class RenderModelOfContentType2TestPage : TestPage<RenderModel<ContentType2>>
public class RenderModelOfContentType2TestPage : TestPage<ContentModel<ContentType2>>
{ }
public class ContentType1TestPage : TestPage<ContentType1>

View File

@@ -0,0 +1,26 @@
using System;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models
{
/// <summary>
/// Represents the model for the current Umbraco view.
/// </summary>
public class ContentModel : IContentModel
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentModel"/> class with a content.
/// </summary>
/// <param name="content"></param>
public ContentModel(IPublishedContent content)
{
if (content == null) throw new ArgumentNullException(nameof(content));
Content = content;
}
/// <summary>
/// Gets the content.
/// </summary>
public IPublishedContent Content { get; }
}
}

View File

@@ -0,0 +1,23 @@
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models
{
public class ContentModel<TContent> : ContentModel
where TContent : IPublishedContent
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentModel{TContent}"/> class with a content.
/// </summary>
/// <param name="content"></param>
public ContentModel(TContent content)
: base(content)
{
Content = content;
}
/// <summary>
/// Gets the content.
/// </summary>
public new TContent Content { get; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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
{
/// <summary>
/// Represents the model for the current rendering page in Umbraco
/// </summary>
public class RenderModel : IRenderModel
{
/// <summary>
/// Constructor specifying both the IPublishedContent and the CultureInfo
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
public RenderModel(IPublishedContent content, CultureInfo culture)
{
if (content == null) throw new ArgumentNullException("content");
if (culture == null) throw new ArgumentNullException("culture");
Content = content;
CurrentCulture = culture;
}
/// <summary>
/// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext
/// </summary>
/// <param name="content"></param>
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;
}
/// <summary>
/// Returns the current IPublishedContent object
/// </summary>
public IPublishedContent Content { get; private set; }
/// <summary>
/// Returns the current Culture assigned to the page being rendered
/// </summary>
public CultureInfo CurrentCulture { get; private set; }
}
}

View File

@@ -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<TContent> : RenderModel
where TContent : IPublishedContent
{
/// <summary>
/// Constructor specifying both the IPublishedContent and the CultureInfo
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
public RenderModel(TContent content, CultureInfo culture)
: base(content, culture)
{
Content = content;
}
/// <summary>
/// Constructor to set the IPublishedContent and the CurrentCulture is set by the UmbracoContext
/// </summary>
/// <param name="content"></param>
public RenderModel(TContent content)
: base(content)
{
Content = content;
}
/// <summary>
/// Returns the current IPublishedContent object
/// </summary>
public new TContent Content { get; private set; }
}
}

View File

@@ -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
{
/// <summary>
/// Allows for Model Binding any IPublishedContent or IRenderModel
/// </summary>
public class RenderModelBinder : DefaultModelBinder, IModelBinderProvider
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
/// <returns>
/// The bound value.
/// </returns>
/// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
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<TContent>, IPublishedContent }
// to
// { RenderModel, RenderModel<TContent>, 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<IRenderModel>())
{
// 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<IPublishedContent>();
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<IPublishedContent>())
{
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<TContent>, 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<TContent> (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
{
/// <summary>
/// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel.
/// </summary>
public class ContentModelBinder : DefaultModelBinder, IModelBinderProvider
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
/// <returns>
/// The bound value.
/// </returns>
/// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
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<TContent>, IPublishedContent }
// to
// { ContentModel, ContentModel<TContent>, 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<IContentModel>())
{
// 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<IPublishedContent>();
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<IPublishedContent>())
{
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<TContent>, 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<TContent> (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;
}
}
}

View File

@@ -17,6 +17,6 @@ namespace Umbraco.Web.Mvc
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
ActionResult Index(RenderModel model);
ActionResult Index(ContentModel model);
}
}

View File

@@ -109,7 +109,7 @@ namespace Umbraco.Web.Mvc
/// <param name="model"></param>
/// <returns></returns>
[RenderIndexActionSelector]
public virtual ActionResult Index(RenderModel model)
public virtual ActionResult Index(ContentModel model)
{
return CurrentTemplate(model);
}

View File

@@ -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
/// <summary>
/// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views
/// </summary>
/// <param name="renderModel"></param>
/// <param name="contentModel"></param>
/// <param name="requestContext"></param>
/// <param name="docRequest"></param>
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);

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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
{
/// <summary>
/// The View that umbraco front-end views inherit from
/// Represents the properties and methods that are needed in order to render an Umbraco view.
/// </summary>
public abstract class UmbracoViewPage<TModel> : WebViewPage<TModel>
{
/// <summary>
/// Returns the current UmbracoContext
/// </summary>
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;
/// <summary>
/// Returns the current ApplicationContext
/// Gets the current <see cref="UmbracoContext"/>.
/// </summary>
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);
/// <summary>
/// Returns the current PublishedContentRequest
/// Gets the current <see cref="ApplicationContext"/>.
/// </summary>
public ApplicationContext ApplicationContext => UmbracoContext.Application;
/// <summary>
/// Gets the current <see cref="PublishedContentRequest"/>.
/// </summary>
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;
/// <summary>
/// Gets an UmbracoHelper
/// Gets an <see cref="UmbracoHelper"/> instance.
/// </summary>
/// <remarks>
/// This constructs the UmbracoHelper with the content model of the page routed to
/// </remarks>
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;
}
}
/// <summary>
/// Returns the MemberHelper instance
/// Gets a <see cref="MembershipHelper"/> instance.
/// </summary>
public MembershipHelper Members
{
get { return _membershipHelper ?? (_membershipHelper = new MembershipHelper(UmbracoContext)); }
}
public MembershipHelper Members => _membershipHelper ??
(_membershipHelper = new MembershipHelper(UmbracoContext));
/// <summary>
/// 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));

View File

@@ -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);

View File

@@ -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);

View File

@@ -505,7 +505,7 @@
<Compile Include="Models\ContentEditing\PropertyTypeValidation.cs" />
<Compile Include="Models\ImageCropData.cs" />
<Compile Include="Models\ImageCropRatioMode.cs" />
<Compile Include="Models\IRenderModel.cs" />
<Compile Include="Models\IContentModel.cs" />
<Compile Include="Models\PartialViewMacroModelExtensions.cs" />
<Compile Include="Models\PostRedirectModel.cs" />
<Compile Include="Models\PublishedProperty.cs" />
@@ -520,7 +520,7 @@
<Compile Include="PropertyEditors\TagsDataController.cs" />
<Compile Include="PublishedCache\PublishedMember.cs" />
<Compile Include="PublishedCache\RawValueProperty.cs" />
<Compile Include="Models\RenderModelOfTContent.cs" />
<Compile Include="Models\ContentModelOfTContent.cs" />
<Compile Include="Mvc\JsonNetResult.cs" />
<Compile Include="Mvc\MinifyJavaScriptResultAttribute.cs" />
<Compile Include="Mvc\EnsurePublishedContentRequestAttribute.cs" />
@@ -1068,8 +1068,8 @@
<Compile Include="Mvc\MasterControllerFactory.cs" />
<Compile Include="Mvc\RenderActionInvoker.cs" />
<Compile Include="Mvc\RenderControllerFactory.cs" />
<Compile Include="Models\RenderModel.cs" />
<Compile Include="Mvc\RenderModelBinder.cs" />
<Compile Include="Models\ContentModel.cs" />
<Compile Include="Mvc\ContentModelBinder.cs" />
<Compile Include="Mvc\RenderMvcController.cs" />
<Compile Include="Mvc\RenderRouteHandler.cs" />
<Compile Include="Mvc\RenderViewEngine.cs" />

View File

@@ -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());