diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs index f8b1df7520..8d9d33c29c 100644 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ b/src/Umbraco.Web/Mvc/RenderModelBinder.cs @@ -1,11 +1,14 @@ +using System; +using System.Globalization; using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Web.Models; namespace Umbraco.Web.Mvc { public class RenderModelBinder : IModelBinder { - /// /// Binds the model to a value by using the specified controller context and binding context. /// @@ -15,17 +18,90 @@ namespace Umbraco.Web.Mvc /// The controller context.The binding context. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { - var requestMatchesType = typeof(RenderModel) == bindingContext.ModelType; + if (bindingContext.ModelType != typeof (RenderModel)) return null; - if (requestMatchesType) - { - //get the model from the route data - if (!controllerContext.RouteData.DataTokens.ContainsKey("umbraco")) - return null; - var model = controllerContext.RouteData.DataTokens["umbraco"] as RenderModel; - return model; - } - return null; + object model; + if (controllerContext.RouteData.DataTokens.TryGetValue("umbraco", out model) == false) + return null; + + return model as RenderModel; } - } + + // 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) + throw new Exception(string.Format("Cannot bind source content type {0} to model type {1}.", + 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) + throw new Exception(string.Format("Cannot bind source content type {0} to model content type {1}.", + 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 + throw new Exception(string.Format("Cannot bind source type {0} to model type {1}.", + sourceType, modelType)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 480f182855..a7fc18d2e3 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -141,92 +141,56 @@ namespace Umbraco.Web.Mvc // maps model protected override void SetViewData(ViewDataDictionary viewData) { - // if view data contains no model, nothing to do - var source = viewData.Model; - if (source == null) - { - base.SetViewData(viewData); - return; - } + // capture the model before we tinker with the viewData + var viewDataModel = viewData.Model; - // get the type of the view data model (what we have) - // get the type of this view model (what we want) - var sourceType = source.GetType(); - var targetType = typeof (TModel); + // map the view data (may change its type, may set model to null) + viewData = MapViewDataDictionary(viewData, typeof (TModel)); - // it types already match, nothing to do - if (sourceType.Inherits()) // includes == - { - base.SetViewData(viewData); - return; - } - - // try to grab the content - // if no content is found, return, nothing we can do - 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 => get the content - sourceContent = ((IRenderModel)source).Content; - } - if (sourceContent == null) - { - // else check if we can convert it to a content - var attempt = source.TryConvertTo(); - if (attempt.Success) sourceContent = attempt.Result; - } - - var ok = sourceContent != null; - if (sourceContent != null) - { - // try to grab the culture - // using context's culture by default - var culture = UmbracoContext.PublishedContentRequest.Culture; - var sourceRenderModel = source as RenderModel; - if (sourceRenderModel != null) - culture = sourceRenderModel.CurrentCulture; - - // reassign the model depending on its type - if (targetType.Implements()) - { - // it TModel implements IPublishedContent then use the content - // provided that the content is of the proper type - if ((sourceContent is TModel) == false) - throw new InvalidCastException(string.Format("Cannot cast source content type {0} to view model type {1}.", - sourceContent.GetType(), targetType)); - viewData.Model = sourceContent; - } - else if (targetType == typeof(RenderModel)) - { - // if TModel is a basic RenderModel just create it - viewData.Model = new RenderModel(sourceContent, culture); - } - else if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(RenderModel<>)) - { - // if TModel is a strongly-typed RenderModel<> then create it - // provided that the content is of the proper type - var targetContentType = targetType.GetGenericArguments()[0]; - if ((sourceContent.GetType().Inherits(targetContentType)) == false) - throw new InvalidCastException(string.Format("Cannot cast source content type {0} to view model content type {1}.", - sourceContent.GetType(), targetContentType)); - viewData.Model = Activator.CreateInstance(targetType, sourceContent, culture); - } - else - { - ok = false; - } - } - - if (ok == false) - { - // last chance : try to convert - var attempt = source.TryConvertTo(); - if (attempt.Success) viewData.Model = attempt.Result; - } + // bind the model (use context culture as default) + var culture = UmbracoContext.PublishedContentRequest.Culture; + viewData.Model = RenderModelBinder.BindModel(viewDataModel, typeof (TModel), culture); + // set the view data base.SetViewData(viewData); } + // viewData is the ViewDataDictionary (maybe ) that we have + // modelType is the type of the model that we need to bind to + // + // figure out whether viewData can accept modelType else replace it + // + private static ViewDataDictionary MapViewDataDictionary(ViewDataDictionary viewData, Type modelType) + { + var viewDataType = viewData.GetType(); + + // if viewData is not generic then it is a simple ViewDataDictionary instance and its + // Model property is of type 'object' and will accept anything, so it is safe to use + // viewData + if (viewDataType.IsGenericType == false) + return viewData; + + // ensure it is the proper generic type + var def = viewDataType.GetGenericTypeDefinition(); + if (def != typeof(ViewDataDictionary<>)) + throw new Exception("Could not map viewData of type \"" + viewDataType.FullName + "\"."); + + // get the viewData model type and compare with the actual view model type: + // viewData is ViewDataDictionary and we will want to assign an + // object of type modelType to the Model property of type viewDataModelType, we + // need to check whether that is possible + var viewDataModelType = viewDataType.GenericTypeArguments[0]; + + if (viewDataModelType.IsAssignableFrom(modelType)) + return viewData; + + // if not possible then we need to create a new ViewDataDictionary + var nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); + var tViewData = new ViewDataDictionary(viewData) { Model = null }; // temp view data to copy values + var nViewData = (ViewDataDictionary)Activator.CreateInstance(nViewDataType, tViewData); + return nViewData; + } + /// /// This will detect the end /body tag and insert the preview badge if in preview mode ///