Created
May 21, 2011 06:33
-
-
Save Scooletz/984310 to your computer and use it in GitHub Desktop.
EntityAwareModelBinder to be used with NHibernate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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