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.Web.Models;
namespace Umbraco.Web.Mvc
{
///
/// Allows for Model Binding any IPublishedContent or IRenderModel
///
public class RenderModelBinder : DefaultModelBinder, IModelBinder, 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
var culture = CultureInfo.CurrentCulture;
var umbracoContext = controllerContext.GetUmbracoContext()
?? UmbracoContext.Current;
if (umbracoContext != null && 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;
}
}
}