Last active
August 24, 2016 07:55
-
-
Save restlessmedia/da58972516182e6dd92e61480e593958 to your computer and use it in GitHub Desktop.
.Net Mvc bind abstract properties
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
using System; | |
namespace MvcApplication1 | |
{ | |
public class BindAsAttribute : Attribute | |
{ | |
public BindAsAttribute(Type modelType) | |
{ | |
ModelType = modelType; | |
} | |
public Type ModelType { get; private set; } | |
} | |
} |
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
using MvcApplication1.Models; | |
using System; | |
using System.ComponentModel; | |
using System.Linq; | |
using System.Web.Mvc; | |
using System.Web; | |
using System.Collections.Generic; | |
namespace MvcApplication1 | |
{ | |
public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder | |
{ | |
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) | |
{ | |
BindAsAttribute bindAs = GetBindAsAttribute(controllerContext, propertyDescriptor); | |
if (bindAs != null) | |
propertyDescriptor = BindAsProperty(bindingContext, propertyDescriptor, bindAs.ModelType); | |
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); | |
} | |
protected virtual PropertyDescriptor BindAsProperty(ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, Type modelType) | |
{ | |
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, modelType); | |
return GetPropertyDescriptor(propertyDescriptor, modelType); | |
} | |
private static PropertyDescriptor GetPropertyDescriptor(PropertyDescriptor propertyDescriptor, Type modelType) | |
{ | |
if (!propertyDescriptor.PropertyType.IsAssignableFrom(modelType)) | |
throw IncompatibleException(propertyDescriptor.PropertyType, modelType); | |
return new PropertyDescriptorWrapper(propertyDescriptor, modelType); | |
} | |
private static BindAsAttribute GetBindAsAttribute(ControllerContext controllerContext, PropertyDescriptor propertyDescriptor) | |
{ | |
return propertyDescriptor.Attributes.OfType<BindAsAttribute>().FirstOrDefault(); | |
} | |
private static Exception IncompatibleException(Type propertyType, Type modelType) | |
{ | |
const string format = "The BindAs type {0} is incompatible with the property type {1}."; | |
return new InvalidOperationException(string.Format(format, modelType, propertyType)); | |
} | |
private class PropertyDescriptorWrapper : PropertyDescriptor | |
{ | |
public PropertyDescriptorWrapper(PropertyDescriptor descriptor, Type type) | |
: base(descriptor) | |
{ | |
_descriptor = descriptor; | |
_type = type; | |
} | |
public override bool CanResetValue(object component) | |
{ | |
return _descriptor.CanResetValue(component); | |
} | |
public override Type ComponentType | |
{ | |
get { return _descriptor.ComponentType; } | |
} | |
public override object GetValue(object component) | |
{ | |
return _descriptor.GetValue(component); | |
} | |
public override bool IsReadOnly | |
{ | |
get { return _descriptor.IsReadOnly; } | |
} | |
public override Type PropertyType | |
{ | |
get { return _type; } | |
} | |
public override void ResetValue(object component) | |
{ | |
_descriptor.ResetValue(component); | |
} | |
public override void SetValue(object component, object value) | |
{ | |
_descriptor.SetValue(component, value); | |
} | |
public override bool ShouldSerializeValue(object component) | |
{ | |
return _descriptor.ShouldSerializeValue(component); | |
} | |
private readonly PropertyDescriptor _descriptor; | |
private readonly Type _type; | |
} | |
} | |
} |
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
namespace MvcApplication1.Models | |
{ | |
public class TestModel | |
{ | |
[BindAs(typeof(Address))] | |
public IAddress Address { get; set; } | |
// Supports generics. | |
[BindAs(typeof(List<Address>))] | |
public IEnumerable<IAddress> { get; set; } | |
} | |
} |
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
using System; | |
using System.ComponentModel; | |
using System.Linq; | |
using System.Web.Mvc; | |
namespace MvcApplication1 | |
{ | |
/// <summary> | |
/// Provides support for interface inheritance when accessing model properties | |
/// </summary> | |
/// <remarks> | |
/// Fixes bug where model property uses interface which inherits another interface. The previous provider would fail when accessing the property through razor. | |
/// Model | |
/// { | |
/// IFoo Foo; | |
/// } | |
/// IFoo | |
/// { | |
/// IBar Bar | |
/// } | |
/// i.e. TextBoxFor(m => m.Foo.Bar) | |
/// </remarks> | |
public class GenericModelMetadataProvider : DataAnnotationsModelMetadataProvider | |
{ | |
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName) | |
{ | |
PropertyDescriptor property = GetPropertyDescriptor(containerType, propertyName); | |
Type containerTypeToUse = containerType; | |
if (property == null && containerType.IsInterface) | |
{ | |
Tuple<PropertyDescriptor, Type> foundProperty = ( | |
from t in containerType.GetInterfaces() | |
let p = GetTypeDescriptor(t).GetProperties().Find(propertyName, true) | |
where p != null | |
select (new Tuple<PropertyDescriptor, Type>(p, t)) | |
).FirstOrDefault(); | |
if (foundProperty != null) | |
{ | |
property = foundProperty.Item1; | |
containerTypeToUse = foundProperty.Item2; | |
} | |
} | |
if (property == null) | |
throw CreateNotFoundException(containerType, propertyName); | |
return GetMetadataForProperty(modelAccessor, containerTypeToUse, property); | |
} | |
protected PropertyDescriptor GetPropertyDescriptor(Type containerType, string propertyName) | |
{ | |
if (containerType == null) | |
throw new ArgumentNullException("containerType"); | |
if (string.IsNullOrEmpty(propertyName)) | |
throw new ArgumentException("The property {0} cannot be null or empty", "propertyName"); | |
return GetTypeDescriptor(containerType).GetProperties().Find(propertyName, true); | |
} | |
protected Exception CreateNotFoundException(Type containerType, string propertyName) | |
{ | |
throw new ArgumentException(string.Format("The property {0}.{1} could not be found", containerType.FullName, propertyName)); | |
} | |
} | |
} |
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
using System.Web.Http; | |
using System.Web.Mvc; | |
using System.Web.Routing; | |
namespace MvcApplication1 | |
{ | |
// Note: For instructions on enabling IIS6 or IIS7 classic mode, | |
// visit http://go.microsoft.com/?LinkId=9394801 | |
public class MvcApplication : System.Web.HttpApplication | |
{ | |
protected void Application_Start() | |
{ | |
ModelMetadataProviders.Current = new GenericModelMetadataProvider(); | |
ModelBinders.Binders.DefaultBinder = new DefaultModelBinder(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Allow the binding of abstract properties in Mvc.