U4-7754 - fix UmbracoViewPage with models

This commit is contained in:
Stephan
2016-01-18 17:31:59 +01:00
parent a6b7e26606
commit a1b98b6e31
2 changed files with 132 additions and 92 deletions

View File

@@ -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
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
@@ -15,17 +18,90 @@ namespace Umbraco.Web.Mvc
/// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
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<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)
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<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)
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));
}
}
}

View File

@@ -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<TModel>()) // 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<IRenderModel>())
{
// 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<IPublishedContent>();
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<IPublishedContent>())
{
// 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<TModel>();
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 <TModel>) 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<viewDataModelType> 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;
}
/// <summary>
/// This will detect the end /body tag and insert the preview badge if in preview mode
/// </summary>