U4-7754 - fix UmbracoViewPage with models
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user