Skip to content

Instantly share code, notes, and snippets.

@Scooletz
Created May 21, 2011 06:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Scooletz/984310 to your computer and use it in GitHub Desktop.
Save Scooletz/984310 to your computer and use it in GitHub Desktop.
EntityAwareModelBinder to be used with NHibernate
/// <summary>
/// The model binder aware of entities types.
/// </summary>
/// <remarks>
/// If an action parameter is an entity, it uses custom binding,
/// searching for already existing (if id provided) and applying changes onto it.
///
/// The binder does not cascade onto entities' properties which are references to the other entities.
/// All it does is one level (for entity->entity relationships) mapping.
/// </remarks>
public class EntityAwareModelBinder : DefaultModelBinder
{
private readonly IWindsorContainer _container;
private readonly Func<ISession> _session;
private readonly ISessionFactory _sessionFactory;
/// <summary>
/// Initializes a new instance of <see cref="EntityAwareModelBinder"/>.
/// </summary>
/// <param name="container">The container used for model resolving.</param>
/// <param name="session">The getter of the current session.</param>
/// <param name="sessionFactory">The session factory.</param>
/// <remarks>
/// The second parameter <paramref name="session"/> must be passed, as by default model binders in mvc are singletons.
/// </remarks>
public EntityAwareModelBinder(IWindsorContainer container, Func<ISession> session, ISessionFactory sessionFactory)
{
_container = container;
_session = session;
_sessionFactory = sessionFactory;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// to bind controller context as fast as possible
if (bindingContext.ModelType == typeof(ControllerContext))
return controllerContext;
return base.BindModel(controllerContext, bindingContext);
}
private static bool IsValueSubmitted(ValueProviderResult v)
{
return v != null && !String.IsNullOrEmpty(v.AttemptedValue);
}
/// <summary>
/// The override for getting property value, checks whether a property can be bound to a mapped entity,
/// if so, the entity is session.Loaded and returned as a result.
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="bindingContext"></param>
/// <param name="desc"></param>
/// <param name="propertyBinder"></param>
/// <returns>A bound property value.</returns>
protected override object GetPropertyValue(ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor desc,
IModelBinder propertyBinder)
{
var v = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
NullableType idType;
if (IsEntityType(desc.PropertyType, out idType))
{
if (IsValueSubmitted(v))
{
var session = _session();
var id = idType.FromStringValue(v.AttemptedValue);
return session.Load(desc.PropertyType, id); // load to not hit db
}
return null;
}
// if not a property of a entity type, return base
return base.GetPropertyValue(controllerContext, bindingContext, desc, propertyBinder);
}
/// <summary>
/// This overload uses container for non default (dictionary, enumerable) complex types to create.
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="bindingContext"></param>
/// <param name="modelType"></param>
/// <returns></returns>
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
// handle generism
var type = modelType;
if (modelType.IsGenericType)
{
var genericTypeDefinition = modelType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(IDictionary<,>))
{
type = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());
}
else if (((genericTypeDefinition == typeof(IEnumerable<>)) || (genericTypeDefinition == typeof(ICollection<>))) || (genericTypeDefinition == typeof(IList<>)))
{
type = typeof(List<>).MakeGenericType(modelType.GetGenericArguments());
}
}
ApplyPropertyFiltering(bindingContext);
// root model && mapped
if (bindingContext.ModelMetadata.ContainerType == null)
{
NullableType idType;
if (IsEntityType(modelType, out idType) )
{
var idName = GetIdPropertyName(_sessionFactory, modelType);
var submittedValue = bindingContext.ValueProvider.GetValue(idName);
if (IsValueSubmitted(submittedValue))
{
var session = _session();
var id = idType.FromStringValue(submittedValue.AttemptedValue);
var got = session.Get(modelType, id);
if (got != null)
return got;
}
}
}
return _container.Resolve(type);
}
private static void ApplyPropertyFiltering(ModelBindingContext ctx)
{
var editablePropertyNames = ctx.ModelMetadata.Properties
.Where(p => !p.IsReadOnly && p.ShowForEdit)
.Select(p => p.PropertyName)
.ToDictionary(s => s);
var predicate = ctx.PropertyFilter;
if (predicate == null)
{
ctx.PropertyFilter = (p => editablePropertyNames.ContainsKey(p));
}
else
{
ctx.PropertyFilter = p => predicate(p) && editablePropertyNames.ContainsKey(p);
}
}
private bool IsEntityType(Type type, out NullableType idType)
{
var metadata = _sessionFactory.GetClassMetadata(type);
if (metadata != null)
{
idType = metadata.IdentifierType as NullableType;
return true;
}
idType = null;
return false;
}
public static string GetIdPropertyName (ISessionFactory sessionFactory, Type type)
{
var metadata = sessionFactory.GetClassMetadata(type);
if (metadata == null)
{
return null;
}
return metadata.IdentifierPropertyName;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment