diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs index 642581dded..840b076f7f 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs @@ -65,7 +65,7 @@ public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODa { if (!property.AddedExplicitly) { - edmTypeConfiguration.RemoveProperty(property.PropertyInfo); + edmTypeConfiguration.RemoveProperty(property.PropertyInfo.PropertyInfo); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs index 7ded2e39f4..440ef3d8b6 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs @@ -97,7 +97,7 @@ private static void ApplyNavigation(NavigationPropertyConfiguration navProperty, { Type dependentType = Nullable.GetUnderlyingType(dependent.PropertyInfo.PropertyType) ?? dependent.PropertyInfo.PropertyType; PrimitivePropertyConfiguration principal = principalEntity.Keys.FirstOrDefault( - k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo)); + k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo.MemberInfo)); if (principal != null) { @@ -136,7 +136,7 @@ private static void ApplyPrimitive(PrimitivePropertyConfiguration dependent, Ent Type dependentType = Nullable.GetUnderlyingType(dependent.PropertyInfo.PropertyType) ?? dependent.PropertyInfo.PropertyType; PrimitivePropertyConfiguration principal = principalEntity.Keys.FirstOrDefault( - k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo)); + k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo.MemberInfo)); if (principal != null) { navProperty.HasConstraint(dependent.PropertyInfo, principal.PropertyInfo); diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs index 21e9a6db85..9d1212fe22 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs @@ -52,7 +52,7 @@ public override void Apply(PropertyConfiguration edmProperty, } else { - structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo); + structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo.PropertyInfo); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs index 745c1e6f7d..ae06e595a2 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs @@ -40,7 +40,7 @@ public override void Apply(StructuralPropertyConfiguration edmProperty, EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration; if (entity != null) { - entity.HasKey(edmProperty.PropertyInfo); + entity.HasKey(edmProperty.PropertyInfo.PropertyInfo); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs index 2b2b189107..8522dff0af 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs @@ -42,7 +42,7 @@ public override void Apply(PropertyConfiguration edmProperty, if (!edmProperty.AddedExplicitly) { - structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo); + structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo.PropertyInfo); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs index 14703f3010..958531813d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs @@ -42,7 +42,7 @@ public override void Apply(EntityTypeConfiguration entity, ODataConventionModelB PropertyConfiguration key = GetKeyProperty(entity); if (key != null) { - entity.HasKey(key.PropertyInfo); + entity.HasKey(key.PropertyInfo.PropertyInfo); } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs index 0339a1c4d1..390df3978c 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -176,6 +176,7 @@ private static string ConvertBindingPath(EdmTypeMap edmMap, NavigationPropertyBi { Type typeCast = TypeHelper.AsType(bindingInfo); PropertyInfo propertyInfo = bindingInfo as PropertyInfo; + MethodInfo methodInfo = bindingInfo as MethodInfo; if (typeCast != null) { @@ -184,7 +185,13 @@ private static string ConvertBindingPath(EdmTypeMap edmMap, NavigationPropertyBi } else if (propertyInfo != null) { - bindings.Add(edmMap.EdmProperties[propertyInfo].Name); + MemberDescriptor propDescr = new MemberDescriptor(propertyInfo); + bindings.Add(edmMap.EdmProperties[propDescr].Name); + } + else if (methodInfo != null) + { + MemberDescriptor propDescr = new MemberDescriptor(methodInfo); + bindings.Add(edmMap.EdmProperties[propDescr].Name); } } @@ -438,7 +445,7 @@ private static Dictionary AddTypes(this EdmModel model, EdmTypeM model.AddClrTypeAnnotations(edmTypes); // add annotation for properties - Dictionary edmProperties = edmTypeMap.EdmProperties; + Dictionary edmProperties = edmTypeMap.EdmProperties; model.AddClrPropertyInfoAnnotations(edmProperties); model.AddClrEnumMemberInfoAnnotations(edmTypeMap); model.AddPropertyRestrictionsAnnotations(edmTypeMap.EdmPropertiesRestrictions); @@ -503,13 +510,13 @@ private static void AddClrTypeAnnotations(this EdmModel model, Dictionary edmProperties) + private static void AddClrPropertyInfoAnnotations(this EdmModel model, Dictionary edmProperties) { - foreach (KeyValuePair edmPropertyMap in edmProperties) + foreach (KeyValuePair edmPropertyMap in edmProperties) { IEdmProperty edmProperty = edmPropertyMap.Value; - PropertyInfo clrProperty = edmPropertyMap.Key; - if (edmProperty.Name != clrProperty.Name) + MemberDescriptor clrProperty = edmPropertyMap.Key; + if (clrProperty.MethodInfo != null || edmProperty.Name != clrProperty.Name) { model.SetAnnotationValue(edmProperty, new ClrPropertyInfoAnnotation(clrProperty)); } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs index a8af88ecf4..b983b3e9fe 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs @@ -23,7 +23,7 @@ internal class EdmTypeBuilder { private readonly List _configurations; private readonly Dictionary _types = new Dictionary(); - private readonly Dictionary _properties = new Dictionary(); + private readonly Dictionary _properties = new Dictionary(); private readonly Dictionary _propertiesRestrictions = new Dictionary(); private readonly Dictionary _propertiesQuerySettings = new Dictionary(); private readonly Dictionary _structuredTypeQuerySettings = new Dictionary(); @@ -462,7 +462,8 @@ private IList GetDeclaringPropertyInfo(IEnumerable GetDeclaringPropertyInfo(IEnumerable edmTypes, - Dictionary edmProperties, + Dictionary edmProperties, Dictionary edmPropertiesRestrictions, Dictionary edmPropertiesQuerySettings, Dictionary edmStructuredTypeQuerySettings, @@ -31,7 +31,7 @@ public EdmTypeMap( public Dictionary EdmTypes { get; private set; } - public Dictionary EdmProperties { get; private set; } + public Dictionary EdmProperties { get; private set; } public Dictionary EdmPropertiesRestrictions { get; private set; } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs index e6cd007f9c..599ff99448 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs @@ -123,7 +123,7 @@ public EntityTypeConfiguration DerivesFrom() where TBase [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] public EntityTypeConfiguration HasKey(Expression> keyDefinitionExpression) { - ICollection properties = PropertySelectorVisitor.GetSelectedProperties(keyDefinitionExpression); + ICollection properties = PropertySelectorVisitor.GetSelectedProperties(keyDefinitionExpression, false); foreach (PropertyInfo property in properties) { _configuration.HasKey(property); diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/MemberDescriptor.cs b/src/Microsoft.AspNet.OData.Shared/Builder/MemberDescriptor.cs new file mode 100644 index 0000000000..f86e3d8c5d --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/MemberDescriptor.cs @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Member descriptor + /// + public class MemberDescriptor + { + private readonly MemberInfo _memberInfo; + + /// + /// Initializes a new instance of the class. + /// + /// Property information + public MemberDescriptor(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + this._memberInfo = propertyInfo; + } + + /// + /// Initializes a new instance of the class. + /// + /// Extension method information + public MemberDescriptor(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw Error.ArgumentNull("methodInfo"); + } + + this._memberInfo = methodInfo; + } + + /// + /// Obtains information about member + /// + public MemberInfo MemberInfo + { + get + { + return _memberInfo; + } + } + + /// + /// Provide access to property metadata + /// + public PropertyInfo PropertyInfo + { + get + { + return _memberInfo as PropertyInfo; + } + } + + /// + /// Provide access to extension property metadata + /// + public MethodInfo MethodInfo + { + get + { + return _memberInfo as MethodInfo; + } + } + + /// + /// Get the name of the this property + /// + public string Name + { + get + { + return MemberInfo.Name; + } + } + + /// + /// Get the type of the this property + /// + public Type PropertyType + { + get + { + if (PropertyInfo != null) + { + return PropertyInfo.PropertyType; + } + + return MethodInfo.ReturnType; + } + } + + /// + /// Gets the class that declares this member + /// + public Type DeclaringType + { + get + { + if (PropertyInfo != null) + { + return PropertyInfo.DeclaringType; + } + + return MethodInfo.GetParameters()[0].ParameterType; + } + } + + /// + /// Returns the reflected type from a member info. + /// + public Type ReflectedType + { + get + { + if (PropertyInfo != null) + { + return TypeHelper.GetReflectedType(PropertyInfo); + } + + return MethodInfo.GetParameters()[0].ParameterType; + } + } + + /// + /// Cast MemberDescriptor to MemberInfo. + /// + /// The object to cast. + public static implicit operator MemberInfo(MemberDescriptor memberDescriptor) + { + return memberDescriptor.MemberInfo; + } + + /// + /// Returns a member information that represents the current object. + /// + /// A member information that represents the current object. + public MemberInfo ToMemberInfo() + { + return MemberInfo; + } + + /// + /// Retrieves an array of the custom attributes applied to an assembly. A parameter specifies the assembly. + /// + /// The type of attribute to search for. Only attributes that are assignable to this type are returned. + /// true to inspect the ancestors of element; otherwise, false. + /// An array of custom attributes applied to this member, or an array with zero elements if no attributes assignable to attributeType have been applied. + public object[] GetCustomAttributes(Type type, bool inherit) + { + return MemberInfo.GetCustomAttributes(type, inherit); + } + + /// + /// Retrieves a collection of custom attributes of a specified type that are applied to a specified member. + /// + /// The type of attribute to search for. + /// A collection of the custom attributes that are applied to element and that match T, or an empty collection if no such attributes exist. + public IEnumerable GetCustomAttributes() + where T : Attribute + { + return GetCustomAttributes(false); + } + + /// + /// Retrieves a collection of custom attributes of a specified type that are applied to a specified member. + /// + /// The type of attribute to search for. + /// true to inspect the ancestors of element; otherwise, false. + /// A collection of the custom attributes that are applied to element and that match T, or an empty collection if no such attributes exist. + public IEnumerable GetCustomAttributes(bool inherit) + where T : Attribute + { + return MemberInfo.GetCustomAttributes(inherit); + } + + /// + /// Retrieves a custom attribute of a specified type that is applied to a specified member, and optionally inspects the ancestors of that member. + /// + /// The type of attribute to search for. + /// A custom attribute that matches T, or null if no such attribute is found. + public T GetCustomAttribute() + where T : Attribute + { + return GetCustomAttribute(false); + } + + /// + /// Retrieves a custom attribute of a specified type that is applied to a specified member, and optionally inspects the ancestors of that member. + /// + /// The type of attribute to search for. + /// true to inspect the ancestors of element; otherwise, false. + /// A custom attribute that matches T, or null if no such attribute is found. + public T GetCustomAttribute(bool inherit) + where T : Attribute + { + return MemberInfo.GetCustomAttribute(inherit); + } + + /// + /// Serves hash function. + /// + /// Hash code. + public override int GetHashCode() + { + return MemberInfo.GetHashCode(); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// true if the specified object is equal to the current object; otherwise, false. + public override bool Equals(object obj) + { + MemberDescriptor propDescr = obj as MemberDescriptor; + if (propDescr == null) + { + return false; + } + + if (PropertyInfo != null) + { + return PropertyInfo.Equals(propDescr.PropertyInfo); + } + else + { + return MethodInfo.Equals(propDescr.MethodInfo); + } + } + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs index be44a28d03..d3941dc513 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs @@ -29,22 +29,34 @@ public class NavigationPropertyConfiguration : PropertyConfiguration /// The . /// The declaring structural type. public NavigationPropertyConfiguration(PropertyInfo property, EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) - : base(property, declaringType) + : this(CreatePropertyDescriptor(property), multiplicity, declaringType) { - if (property == null) + + } + + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR property. + /// The . + /// The declaring structural type. + public NavigationPropertyConfiguration(MemberDescriptor memberDescriptor, EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) + : base(memberDescriptor, declaringType) + { + if (memberDescriptor == null) { - throw Error.ArgumentNull("property"); + throw Error.ArgumentNull("memberDescriptor"); } Multiplicity = multiplicity; - _relatedType = property.PropertyType; + _relatedType = memberDescriptor.PropertyType; if (multiplicity == EdmMultiplicity.Many) { Type elementType; if (!TypeHelper.IsCollection(_relatedType, out elementType)) { - throw Error.Argument("property", SRResources.ManyToManyNavigationPropertyMustReturnCollection, property.Name, TypeHelper.GetReflectedType(property).Name); + throw Error.Argument("memberDescriptor", SRResources.ManyToManyNavigationPropertyMustReturnCollection, memberDescriptor.Name, TypeHelper.GetReflectedType(memberDescriptor).Name); } _relatedType = elementType; @@ -53,6 +65,16 @@ public NavigationPropertyConfiguration(PropertyInfo property, EdmMultiplicity mu OnDeleteAction = EdmOnDeleteAction.None; } + private static MemberDescriptor CreatePropertyDescriptor(PropertyInfo property) + { + if (property == null) + { + return null; + } + + return new MemberDescriptor(property); + } + /// /// The partner relationship of this navigation property. /// @@ -197,6 +219,18 @@ public NavigationPropertyConfiguration HasConstraint(PropertyInfo dependentPrope principalPropertyInfo)); } + /// + /// Configures the referential constraint with the specified + /// and . + /// + /// The dependent property info for the referential constraint. + /// The principal property info for the referential constraint. + public NavigationPropertyConfiguration HasConstraint(MemberDescriptor dependentPropertyInfo, + MemberDescriptor principalPropertyInfo) + { + return HasConstraint(dependentPropertyInfo.PropertyInfo, principalPropertyInfo.PropertyInfo); + } + /// /// Configures the referential constraint with the dependent and principal property pair. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs index 0d03f916cd..ae3e4fe088 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.AspNet.OData.Common; namespace Microsoft.AspNet.OData.Builder @@ -525,8 +526,8 @@ private void VerifyBindingPath(NavigationPropertyConfiguration navigationConfigu Contract.Assert(navigationConfiguration != null); Contract.Assert(bindingPath != null); - PropertyInfo navigation = bindingPath.Last() as PropertyInfo; - if (navigation == null || navigation != navigationConfiguration.PropertyInfo) + MemberInfo navigation = bindingPath.Last() as MemberInfo; + if (navigation == null || navigation != navigationConfiguration.PropertyInfo.MemberInfo) { throw Error.Argument("navigationConfiguration", SRResources.NavigationPropertyBindingPathIsNotValid, bindingPath.ConvertBindingPath(), navigationConfiguration.Name); @@ -552,13 +553,25 @@ private static Type VerifyBindingSegment(Type current, MemberInfo info) return derivedType.BaseType; } + Type declaringType = null; + Type propertyType = null; PropertyInfo propertyInfo = info as PropertyInfo; - if (propertyInfo == null) + MethodInfo methodInfo = info as MethodInfo; + if (propertyInfo != null) + { + declaringType = info.DeclaringType; + propertyType = propertyInfo.PropertyType; + } + else if (methodInfo != null && methodInfo.GetCustomAttribute() != null) + { + declaringType = methodInfo.GetParameters().First().ParameterType; + propertyType = methodInfo.ReturnType; + } + else { throw Error.NotSupported(SRResources.NavigationPropertyBindingPathNotSupported, info.Name, info.MemberType); } - Type declaringType = propertyInfo.DeclaringType; if (declaringType == null || !(declaringType.IsAssignableFrom(current) || current.IsAssignableFrom(declaringType))) { @@ -567,12 +580,12 @@ private static Type VerifyBindingSegment(Type current, MemberInfo info) } Type elementType; - if (TypeHelper.IsCollection(propertyInfo.PropertyType, out elementType)) + if (TypeHelper.IsCollection(propertyType, out elementType)) { return elementType; } - return propertyInfo.PropertyType; + return propertyType; } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs index 8891e030b1..11cbc67ec6 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs @@ -20,7 +20,7 @@ public abstract class PropertyConfiguration /// /// The name of the property. /// The declaring EDM type of the property. - protected PropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + protected PropertyConfiguration(MemberDescriptor property, StructuralTypeConfiguration declaringType) { if (property == null) { @@ -67,7 +67,7 @@ public string Name /// /// Gets the mapping CLR . /// - public PropertyInfo PropertyInfo { get; private set; } + public MemberDescriptor PropertyInfo { get; private set; } /// /// Gets the CLR of the property. diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs index 25896a8ab7..04cbf1b15d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs @@ -6,21 +6,24 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.AspNet.OData.Common; namespace Microsoft.AspNet.OData.Builder { internal class PropertySelectorVisitor : ExpressionVisitor { - private List _properties = new List(); + private List _properties = new List(); + private readonly bool includeExtensionProperty; [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Class is internal, virtual call okay")] - internal PropertySelectorVisitor(Expression exp) + internal PropertySelectorVisitor(Expression exp, bool includeExtensionProperty) { + this.includeExtensionProperty = includeExtensionProperty; Visit(exp); } - public PropertyInfo Property + public MemberInfo Property { get { @@ -28,7 +31,7 @@ public PropertyInfo Property } } - public ICollection Properties + public ICollection Properties { get { @@ -59,14 +62,50 @@ protected override Expression VisitMember(MemberExpression node) return node; } + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + MethodInfo minfo = node.Method; + + if (!IsExtensionProperty(minfo)) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeProperties, TypeHelper.GetReflectedType(node.Method).FullName, node.Method.Name); + } + + if (node.Arguments.First().NodeType != ExpressionType.Parameter) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeBoundToLambdaParameter); + } + + _properties.Add(minfo); + return node; + } + + private static bool IsExtensionProperty(MethodInfo methodInfo) + { + return methodInfo!=null + && methodInfo.IsStatic + && methodInfo.IsDefined(typeof(ExtensionAttribute), false) + && methodInfo.GetParameters().Length == 1; + } + public static PropertyInfo GetSelectedProperty(Expression exp) { - return new PropertySelectorVisitor(exp).Property; + return GetSelectedProperty(exp, false) as PropertyInfo; + } + + public static MemberInfo GetSelectedProperty(Expression exp, bool includeExtensionProperty) + { + return new PropertySelectorVisitor(exp, includeExtensionProperty).Property; } - public static ICollection GetSelectedProperties(Expression exp) + public static ICollection GetSelectedProperties(Expression exp, bool includeExtensionProperty=false) { - return new PropertySelectorVisitor(exp).Properties; + return new PropertySelectorVisitor(exp, includeExtensionProperty).Properties; } public override Expression Visit(Expression exp) @@ -82,6 +121,11 @@ public override Expression Visit(Expression exp) case ExpressionType.MemberAccess: case ExpressionType.Lambda: return base.Visit(exp); + case ExpressionType.Call: + if (includeExtensionProperty) + return base.Visit(exp); + else + goto default; default: throw Error.NotSupported(SRResources.UnsupportedExpressionNodeType); } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs index f31d7a13b9..142a638023 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs @@ -17,7 +17,7 @@ public abstract class StructuralPropertyConfiguration : PropertyConfiguration /// The property of the configuration. /// The declaring type of the property. protected StructuralPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) - : base(property, declaringType) + : base(new MemberDescriptor(property), declaringType) { OptionalProperty = EdmLibHelpers.IsNullable(property.PropertyType); } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs index a9b6123315..a6e0c0b74b 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -30,7 +30,7 @@ public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration /// The default constructor is intended for use by unit testing only. protected StructuralTypeConfiguration() { - ExplicitProperties = new Dictionary(); + ExplicitProperties = new Dictionary(); RemovedProperties = new List(); QueryConfiguration = new QueryConfiguration(); } @@ -216,7 +216,7 @@ public virtual IEnumerable NavigationProperties /// /// Gets the collection of explicitly added properties. /// - protected internal IDictionary ExplicitProperties { get; private set; } + protected internal IDictionary ExplicitProperties { get; private set; } /// /// Gets the base type of this structural type. @@ -315,7 +315,7 @@ public virtual PrimitivePropertyConfiguration AddProperty(PropertyInfo propertyI propertyConfiguration = new PrecisionPropertyConfiguration(propertyInfo, this); } } - ExplicitProperties[propertyInfo] = propertyConfiguration; + ExplicitProperties[propertyConfiguration.PropertyInfo] = propertyConfiguration; } return propertyConfiguration; @@ -358,12 +358,17 @@ public virtual EnumPropertyConfiguration AddEnumProperty(PropertyInfo propertyIn if (propertyConfiguration == null) { propertyConfiguration = new EnumPropertyConfiguration(propertyInfo, this); - ExplicitProperties[propertyInfo] = propertyConfiguration; + ExplicitProperties[propertyConfiguration.PropertyInfo] = propertyConfiguration; } return propertyConfiguration; } + internal ComplexPropertyConfiguration AddComplexProperty(MemberDescriptor propertyDescriptor) + { + return AddComplexProperty(propertyDescriptor.PropertyInfo); + } + /// /// Adds a complex property to this edm type. /// @@ -397,7 +402,7 @@ public virtual ComplexPropertyConfiguration AddComplexProperty(PropertyInfo prop if (propertyConfiguration == null) { propertyConfiguration = new ComplexPropertyConfiguration(propertyInfo, this); - ExplicitProperties[propertyInfo] = propertyConfiguration; + ExplicitProperties[propertyConfiguration.PropertyInfo] = propertyConfiguration; // Make sure the complex type is in the model. ModelBuilder.AddComplexType(propertyInfo.PropertyType); @@ -406,6 +411,11 @@ public virtual ComplexPropertyConfiguration AddComplexProperty(PropertyInfo prop return propertyConfiguration; } + internal CollectionPropertyConfiguration AddCollectionProperty(MemberDescriptor propertyDescriptor) + { + return AddCollectionProperty(propertyDescriptor.PropertyInfo); + } + /// /// Adds a collection property to this edm type. /// @@ -438,7 +448,7 @@ public virtual CollectionPropertyConfiguration AddCollectionProperty(PropertyInf if (propertyConfiguration == null) { propertyConfiguration = new CollectionPropertyConfiguration(propertyInfo, this); - ExplicitProperties[propertyInfo] = propertyConfiguration; + ExplicitProperties[propertyConfiguration.PropertyInfo] = propertyConfiguration; // If the ElementType is not primitive or enum treat as a ComplexType and Add to the model. IEdmPrimitiveTypeReference edmType = @@ -491,6 +501,20 @@ public virtual void AddDynamicPropertyDictionary(PropertyInfo propertyInfo) _dynamicPropertyDictionary = propertyInfo; } + /// + /// Removes the given property. + /// + /// The property being removed. + public virtual void RemoveProperty(MemberDescriptor propertyDescriptor) + { + if (propertyDescriptor == null) + { + throw new ArgumentNullException("propertyDescriptor"); + } + + RemoveProperty(propertyDescriptor.PropertyInfo); + } + /// /// Removes the given property. /// @@ -530,6 +554,18 @@ public virtual void RemoveProperty(PropertyInfo propertyInfo) /// The of the navigation property. /// Returns the of the added property. public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity) + { + MemberDescriptor propertyDescriptor = new MemberDescriptor(navigationProperty); + return AddNavigationProperty(propertyDescriptor, multiplicity, containsTarget: false); + } + + /// + /// Adds a non-contained EDM navigation property to this entity type. + /// + /// The backing CLR property. + /// The of the navigation property. + /// Returns the of the added property. + public virtual NavigationPropertyConfiguration AddNavigationProperty(MemberDescriptor navigationProperty, EdmMultiplicity multiplicity) { return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: false); } @@ -541,18 +577,30 @@ public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInf /// The of the navigation property. /// Returns the of the added property. public virtual NavigationPropertyConfiguration AddContainedNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity) + { + MemberDescriptor memberDescriptor = new MemberDescriptor(navigationProperty); + return AddNavigationProperty(memberDescriptor, multiplicity, containsTarget: true); + } + + /// + /// Adds a contained EDM navigation property to this entity type. + /// + /// The backing CLR property. + /// The of the navigation property. + /// Returns the of the added property. + public virtual NavigationPropertyConfiguration AddContainedNavigationProperty(MemberDescriptor navigationProperty, EdmMultiplicity multiplicity) { return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: true); } - private NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity, bool containsTarget) + private NavigationPropertyConfiguration AddNavigationProperty(MemberDescriptor navigationProperty, EdmMultiplicity multiplicity, bool containsTarget) { if (navigationProperty == null) { throw Error.ArgumentNull("navigationProperty"); } - if (!TypeHelper.GetReflectedType(navigationProperty).IsAssignableFrom(ClrType)) + if (!navigationProperty.ReflectedType.IsAssignableFrom(ClrType)) { throw Error.Argument("navigationProperty", SRResources.PropertyDoesNotBelongToType, navigationProperty.Name, ClrType.FullName); } @@ -611,7 +659,7 @@ internal T ValidatePropertyNotAlreadyDefinedOtherTypes(PropertyInfo propertyI return propertyConfiguration; } - internal void ValidatePropertyNotAlreadyDefinedInBaseTypes(PropertyInfo propertyInfo) + internal void ValidatePropertyNotAlreadyDefinedInBaseTypes(MemberInfo propertyInfo) { PropertyConfiguration baseProperty = this.DerivedProperties().FirstOrDefault(p => p.Name == propertyInfo.Name); @@ -622,7 +670,7 @@ internal void ValidatePropertyNotAlreadyDefinedInBaseTypes(PropertyInfo property } } - internal void ValidatePropertyNotAlreadyDefinedInDerivedTypes(PropertyInfo propertyInfo) + internal void ValidatePropertyNotAlreadyDefinedInDerivedTypes(MemberInfo propertyInfo) { foreach (StructuralTypeConfiguration derivedType in ModelBuilder.DerivedTypes(this)) { diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs index 4ad57a656c..aefb1e8e92 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs @@ -881,14 +881,24 @@ public StructuralTypeConfiguration Expand() internal NavigationPropertyConfiguration GetOrCreateNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity) { - PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression); - return _configuration.AddNavigationProperty(navigationProperty, multiplicity); + MemberInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression, true); + PropertyInfo pinfo = navigationProperty as PropertyInfo; + MethodInfo minfo = navigationProperty as MethodInfo; + + MemberDescriptor propDescr = pinfo!=null ? new MemberDescriptor(pinfo) + : new MemberDescriptor(minfo); + return _configuration.AddNavigationProperty(propDescr, multiplicity); } internal NavigationPropertyConfiguration GetOrCreateContainedNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity) { - PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression); - return _configuration.AddContainedNavigationProperty(navigationProperty, multiplicity); + MemberInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression, true); + PropertyInfo pinfo = navigationProperty as PropertyInfo; + MethodInfo minfo = navigationProperty as MethodInfo; + + MemberDescriptor propDescr = pinfo != null ? new MemberDescriptor(pinfo) + : new MemberDescriptor(minfo); + return _configuration.AddContainedNavigationProperty(propDescr, multiplicity); } private PrimitivePropertyConfiguration GetPrimitivePropertyConfiguration(Expression propertyExpression, bool optional) diff --git a/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs b/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs index 51dfc22ae7..e0bd8cc9ae 100644 --- a/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs +++ b/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Reflection; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.OData.Edm; @@ -23,12 +24,26 @@ public ClrPropertyInfoAnnotation(PropertyInfo clrPropertyInfo) throw Error.ArgumentNull("clrPropertyInfo"); } - ClrPropertyInfo = clrPropertyInfo; + ClrPropertyInfo = new MemberDescriptor(clrPropertyInfo); + } + + /// + /// Initializes a new instance of class. + /// + /// The backing CLR property info for the EDM property. + public ClrPropertyInfoAnnotation(MemberDescriptor propertyDescriptor) + { + if (propertyDescriptor == null) + { + throw Error.ArgumentNull("propetyDescriptor"); + } + + ClrPropertyInfo = propertyDescriptor; } /// /// Gets the backing CLR property info for the EDM property. /// - public PropertyInfo ClrPropertyInfo { get; private set; } + public MemberDescriptor ClrPropertyInfo { get; private set; } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs index f00f98de28..1a216962b0 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs @@ -694,16 +694,37 @@ public static string GetClrPropertyName(IEdmProperty edmProperty, IEdmModel edmM ClrPropertyInfoAnnotation annotation = edmModel.GetAnnotationValue(edmProperty); if (annotation != null) { - PropertyInfo propertyInfo = annotation.ClrPropertyInfo; - if (propertyInfo != null) + MemberDescriptor propDescr = annotation.ClrPropertyInfo; + if (propDescr != null) { - propertyName = propertyInfo.Name; + propertyName = propDescr.Name; } } return propertyName; } + public static MemberDescriptor GetClrMemberDescriptor(IEdmProperty edmProperty, IEdmModel edmModel) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + ClrPropertyInfoAnnotation annotation = edmModel.GetAnnotationValue(edmProperty); + if (annotation != null) + { + return annotation.ClrPropertyInfo; + } + + return null; + } + public static ClrEnumMemberAnnotation GetClrEnumMemberAnnotation(this IEdmModel edmModel, IEdmEnumType enumType) { if (edmModel == null) diff --git a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems index 5dd8aa7f5b..7eedb2edde 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -15,6 +15,7 @@ + diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs index d6c1fbf89c..4491326664 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Interfaces; @@ -544,14 +545,18 @@ private Expression BindAccessor(QueryNode node, Expression baseElement = null) private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null) { - string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model); + MemberDescriptor memberDescriptor = EdmLibHelpers.GetClrMemberDescriptor(property, Model); + string propertyName = memberDescriptor != null + ? memberDescriptor.MemberInfo.Name + : EdmLibHelpers.GetClrPropertyName(property, Model); propertyPath = propertyPath ?? propertyName; if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && source != this._lambdaParameter) { Expression cleanSource = RemoveInnerNullPropagation(source); Expression propertyAccessExpression = null; - propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) ?? Expression.Property(cleanSource, propertyName); + propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) + ?? CreatePropertyAccessExpression(cleanSource, propertyName, memberDescriptor); // source.property => source == null ? null : [CastToNullable]RemoveInnerNullPropagation(source).property // Notice that we are checking if source is null already. so we can safely remove any null checks when doing source.Property @@ -565,7 +570,27 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert } else { - return GetFlattenedPropertyExpression(propertyPath) ?? ConvertNonStandardPrimitives(Expression.Property(source, propertyName)); + return GetFlattenedPropertyExpression(propertyPath) + ?? ConvertNonStandardPrimitives(CreatePropertyAccessExpression(source, propertyName, memberDescriptor)); + } + } + + private static Expression CreatePropertyAccessExpression(Expression source, string propertyName, MemberDescriptor memberDescriptor) + { + if (memberDescriptor != null) + { + if (memberDescriptor.MethodInfo != null) + { + return Expression.Call(null, memberDescriptor.MethodInfo, source); + } + else + { + return Expression.Property(source, memberDescriptor.PropertyInfo); + } + } + else + { + return Expression.Property(source, propertyName); } } diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs index 7d61f5ee80..345b8a5516 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Interfaces; @@ -670,7 +671,10 @@ public virtual Expression BindSingleComplexNode(SingleComplexNode singleComplexN private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null) { - string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model); + MemberDescriptor memberDescriptor = EdmLibHelpers.GetClrMemberDescriptor(property, Model); + string propertyName = memberDescriptor!=null + ? memberDescriptor.MemberInfo.Name + : EdmLibHelpers.GetClrPropertyName(property, Model); propertyPath = propertyPath ?? propertyName; if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && source != _lambdaParameters[ODataItParameterName]) @@ -678,7 +682,8 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert var cleanSource = RemoveInnerNullPropagation(source); Expression propertyAccessExpression = null; - propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) ?? Expression.Property(cleanSource, propertyName); + propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) + ?? CreatePropertyAccessExpression(cleanSource, propertyName, memberDescriptor); // source.property => source == null ? null : [CastToNullable]RemoveInnerNullPropagation(source).property // Notice that we are checking if source is null already. so we can safely remove any null checks when doing source.Property @@ -692,10 +697,24 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert } else { - return GetFlattenedPropertyExpression(propertyPath) ?? ConvertNonStandardPrimitives(Expression.Property(source, propertyName)); + return GetFlattenedPropertyExpression(propertyPath) + ?? ConvertNonStandardPrimitives(CreatePropertyAccessExpression(source, propertyName, memberDescriptor)); } } + private static Expression CreatePropertyAccessExpression(Expression source, string propertyName, MemberDescriptor propertyDescriptor) + { + if (propertyDescriptor != null) + { + if (propertyDescriptor.MethodInfo != null) + return Expression.Call(null, propertyDescriptor.MethodInfo, source); + else + return Expression.Property(source, propertyDescriptor.PropertyInfo); + } + else + return Expression.Property(source, propertyName); + } + /// /// Binds a to create a LINQ that /// represents the semantics of the . diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs index 1065ca40e1..62403051d1 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs @@ -184,8 +184,26 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType eleme source = Expression.TypeAs(source, castType); } - string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); - Expression propertyValue = Expression.Property(source, propertyName); + var memberDescriptor = EdmLibHelpers.GetClrMemberDescriptor(property, _model); + string propertyName = null; + Expression propertyValue; + if (memberDescriptor != null) + { + propertyName = memberDescriptor.Name; + if (memberDescriptor.PropertyInfo != null) + { + propertyValue = Expression.Property(source, memberDescriptor.PropertyInfo); + } + else + { + propertyValue = Expression.Call(memberDescriptor.MethodInfo, source); + } + } + else + { + propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); + propertyValue = Expression.Property(source, propertyName); + } Type nullablePropertyType = TypeHelper.ToNullable(propertyValue.Type); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); @@ -211,7 +229,7 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType eleme if (isCollection) { Expression filterSource = - typeof(IEnumerable).IsAssignableFrom(source.Type.GetProperty(propertyName).PropertyType) + typeof(IEnumerable).IsAssignableFrom(propertyValue.Type) ? Expression.Call( ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(clrElementType), nullablePropertyValue) diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs index 355754a222..46efbf681b 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs @@ -5,6 +5,7 @@ using System.Diagnostics.Contracts; using System.Linq; using System.Reflection; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.OData.Edm; using Newtonsoft.Json; @@ -58,8 +59,8 @@ public JsonPropertyNameMapper(IEdmModel model, IEdmStructuredType type) public string MapProperty(string propertyName) { IEdmProperty property = _type.Properties().Single(s => s.Name == propertyName); - PropertyInfo info = GetPropertyInfo(property); - JsonPropertyAttribute jsonProperty = GetJsonProperty(info); + MemberDescriptor descriptor = GetMemberDescriptor(property); + JsonPropertyAttribute jsonProperty = GetJsonProperty(descriptor); if (jsonProperty != null && !String.IsNullOrWhiteSpace(jsonProperty.PropertyName)) { return jsonProperty.PropertyName; @@ -70,7 +71,7 @@ public string MapProperty(string propertyName) } } - private PropertyInfo GetPropertyInfo(IEdmProperty property) + private MemberDescriptor GetMemberDescriptor(IEdmProperty property) { ClrPropertyInfoAnnotation clrPropertyAnnotation = _model.GetAnnotationValue(property); if (clrPropertyAnnotation != null) @@ -84,12 +85,12 @@ private PropertyInfo GetPropertyInfo(IEdmProperty property) PropertyInfo info = clrTypeAnnotation.ClrType.GetProperty(property.Name); Contract.Assert(info != null); - return info; + return new MemberDescriptor(info); } - private static JsonPropertyAttribute GetJsonProperty(PropertyInfo property) + private static JsonPropertyAttribute GetJsonProperty(MemberDescriptor memberDescriptor) { - return property.GetCustomAttributes(typeof(JsonPropertyAttribute), inherit: false) + return memberDescriptor.GetCustomAttributes(typeof(JsonPropertyAttribute), inherit: false) .OfType().SingleOrDefault(); } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/CollectionPropertyConfigurationTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/CollectionPropertyConfigurationTest.cs index 17e6185e64..5742a131f6 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/CollectionPropertyConfigurationTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/CollectionPropertyConfigurationTest.cs @@ -61,7 +61,7 @@ public void HasCorrectKindPropertyInfoAndName(PropertyInfo property, Type elemen Assert.Equal(PropertyKind.Collection, configuration.Kind); Assert.Equal(elementType, configuration.ElementType); Assert.Equal(elementType, configuration.RelatedClrType); - Assert.Equal(property, configuration.PropertyInfo); + Assert.Equal(property, configuration.PropertyInfo.PropertyInfo); Assert.Equal(property.Name, configuration.Name); Assert.Equal(structuralType.Object, configuration.DeclaringType); } @@ -83,7 +83,7 @@ public void CanCorrectlyDetectCollectionProperties(PropertyInfo property, Type e { Mock structuralType = new Mock(); CollectionPropertyConfiguration configuration = new CollectionPropertyConfiguration(property, structuralType.Object); - Assert.Same(property, configuration.PropertyInfo); + Assert.Same(property, configuration.PropertyInfo.PropertyInfo); Assert.Same(elementType, configuration.ElementType); Assert.Same(property.Name, configuration.Name); } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConventionTests.cs index b67b8864ba..c62fb56321 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConventionTests.cs @@ -58,7 +58,12 @@ public static bool Apply() property.Setup(p => p.GetCustomAttributes(It.IsAny())).Returns(new[] { attribute }); Mock propertyConfiguration; - if (typeof(TProperty) == typeof(NavigationPropertyConfiguration)) + if(typeof(TProperty) == typeof(PropertyConfiguration)) + { + Mock propertyDescriptor = new Mock(property.Object); + propertyConfiguration = new Mock(propertyDescriptor.Object, structuralType.Object); + } + else if (typeof(TProperty) == typeof(NavigationPropertyConfiguration)) { propertyConfiguration = new Mock(property.Object, EdmMultiplicity.ZeroOrOne, structuralType.Object); } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConventionTests.cs index 2690b2a5f1..b99d134283 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConventionTests.cs @@ -35,11 +35,11 @@ public void Apply_RemovesAllPropertiesThatAreNotDataMembers() type.Setup(t => t.ClrType).Returns(clrType.Object); var mockPropertyWithoutAttributes = CreateMockProperty(); - type.Object.ExplicitProperties.Add(new MockPropertyInfo(), CreateMockProperty(new DataMemberAttribute())); - type.Object.ExplicitProperties.Add(new MockPropertyInfo(), CreateMockProperty(new DataMemberAttribute())); - type.Object.ExplicitProperties.Add(new MockPropertyInfo(), mockPropertyWithoutAttributes); + type.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), CreateMockProperty(new DataMemberAttribute())); + type.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), CreateMockProperty(new DataMemberAttribute())); + type.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), mockPropertyWithoutAttributes); - type.Setup(t => t.RemoveProperty(mockPropertyWithoutAttributes.PropertyInfo)).Verifiable(); + type.Setup(t => t.RemoveProperty(mockPropertyWithoutAttributes.PropertyInfo.PropertyInfo)).Verifiable(); // Act _convention.Apply(type.Object, builder); @@ -61,7 +61,7 @@ public void Apply_DoesnotRemove_ExplicitlyAddedProperties() _convention.Apply(entity, builder); // Assert - Assert.Contains(propertyInfo, entity.ExplicitProperties.Keys); + Assert.Contains(new MemberDescriptor(propertyInfo), entity.ExplicitProperties.Keys); Assert.DoesNotContain(propertyInfo, entity.RemovedProperties); } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConventionTests.cs index 76a1a63e90..eeb41a6cff 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConventionTests.cs @@ -31,12 +31,14 @@ public void Apply_Calls_RemovesProperty() property.Setup(p => p.PropertyType).Returns(typeof(int)); property.Setup(p => p.GetCustomAttributes(It.IsAny())).Returns(new[] { new IgnoreDataMemberAttribute() }); + Mock propertyDescriptor = new Mock(property.Object); + Mock type = new Mock(); Mock structuralType = new Mock(MockBehavior.Strict); structuralType.Setup(e => e.RemoveProperty(property.Object)).Verifiable(); structuralType.Setup(s => s.ClrType).Returns(type.Object); - Mock primitiveProperty = new Mock(property.Object, structuralType.Object); + Mock primitiveProperty = new Mock(propertyDescriptor.Object, structuralType.Object); primitiveProperty.Object.AddedExplicitly = false; // Act @@ -65,13 +67,15 @@ public void Apply_DoesnotRemoveProperty_TypeIsDataContractAndPropertyHasDataMemb new DataContractAttribute() }); + Mock propertyDescriptor = new Mock(property.Object); + Mock type = new Mock(); type.Setup(t => t.GetCustomAttributes(It.IsAny(), It.IsAny())).Returns(new[] { new DataContractAttribute() }); Mock structuralType = new Mock(MockBehavior.Strict); structuralType.Setup(s => s.ClrType).Returns(type.Object); - Mock primitiveProperty = new Mock(property.Object, structuralType.Object); + Mock primitiveProperty = new Mock(propertyDescriptor.Object, structuralType.Object); // Act new IgnoreDataMemberAttributeEdmPropertyConvention().Apply(primitiveProperty.Object, structuralType.Object, builder); @@ -86,6 +90,7 @@ public void Apply_DoesnotRemove_ExplicitlyAddedProperties() // Arrange ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); PropertyInfo propertyInfo = typeof(TestEntity).GetProperty("ExplicitlyAddedProperty"); + MemberDescriptor propertyDescriptor = new MemberDescriptor(propertyInfo); EntityTypeConfiguration entity = builder.AddEntityType(typeof(TestEntity)); PropertyConfiguration property = entity.AddProperty(propertyInfo); @@ -93,7 +98,7 @@ public void Apply_DoesnotRemove_ExplicitlyAddedProperties() new IgnoreDataMemberAttributeEdmPropertyConvention().Apply(property, entity, builder); // Assert - Assert.Contains(propertyInfo, entity.ExplicitProperties.Keys); + Assert.Contains(propertyDescriptor, entity.ExplicitProperties.Keys); Assert.DoesNotContain(propertyInfo, entity.RemovedProperties); } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/NotMappedAttributeConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/NotMappedAttributeConventionTests.cs index 183aba38f5..628ac7a121 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/NotMappedAttributeConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/NotMappedAttributeConventionTests.cs @@ -31,10 +31,12 @@ public void Apply_Calls_RemovesProperty_ForInferredProperties() property.Setup(p => p.PropertyType).Returns(typeof(int)); property.Setup(p => p.GetCustomAttributes(It.IsAny())).Returns(new[] { new NotMappedAttribute() }); + Mock propertyDescriptor = new Mock(property.Object); + Mock structuralType = new Mock(MockBehavior.Strict); structuralType.Setup(e => e.RemoveProperty(property.Object)).Verifiable(); - Mock primitiveProperty = new Mock(property.Object, structuralType.Object); + Mock primitiveProperty = new Mock(propertyDescriptor.Object, structuralType.Object); primitiveProperty.Object.AddedExplicitly = false; // Act @@ -51,6 +53,7 @@ public void Apply_DoesnotRemove_ExplicitlyAddedProperties() ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); PropertyInfo propertyInfo = typeof(TestEntity).GetProperty("Property"); + MemberDescriptor propertyDescriptor = new MemberDescriptor(propertyInfo); EntityTypeConfiguration entity = builder.AddEntityType(typeof(TestEntity)); PropertyConfiguration property = entity.AddProperty(propertyInfo); @@ -58,7 +61,7 @@ public void Apply_DoesnotRemove_ExplicitlyAddedProperties() new NotMappedAttributeConvention().Apply(property, entity, builder); // Assert - Assert.Contains(propertyInfo, entity.ExplicitProperties.Keys); + Assert.Contains(propertyDescriptor, entity.ExplicitProperties.Keys); Assert.DoesNotContain(propertyInfo, entity.RemovedProperties); } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConventionTests.cs index a3c1940f70..925b812735 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConventionTests.cs @@ -21,7 +21,7 @@ public void TimestampConvention_AppliesWhenTheAttributeIsAppliedToASinglePropert PropertyInfo property = CreateMockPropertyInfo("TestProperty"); EntityTypeConfiguration entityType = new EntityTypeConfiguration(); PrimitivePropertyConfiguration primitiveProperty = new PrimitivePropertyConfiguration(property, entityType); - entityType.ExplicitProperties.Add(property, primitiveProperty); + entityType.ExplicitProperties.Add(primitiveProperty.PropertyInfo, primitiveProperty); TimestampAttributeEdmPropertyConvention convention = new TimestampAttributeEdmPropertyConvention(); // Act @@ -38,7 +38,7 @@ public void TimestampConvention_DoesntApplyWhenTheAttributeIsAppliedOnANonEntity PropertyInfo property = CreateMockPropertyInfo("TestProperty"); ComplexTypeConfiguration complexType = new ComplexTypeConfiguration(); PrimitivePropertyConfiguration primitiveProperty = new PrimitivePropertyConfiguration(property, complexType); - complexType.ExplicitProperties.Add(property, primitiveProperty); + complexType.ExplicitProperties.Add(primitiveProperty.PropertyInfo, primitiveProperty); TimestampAttributeEdmPropertyConvention convention = new TimestampAttributeEdmPropertyConvention(); // Act @@ -56,8 +56,9 @@ public void TimestampConvention_DoesntApplyWhenTheAttributeIsAppliedToMultiplePr PropertyInfo otherProperty = CreateMockPropertyInfo("OtherTestProperty"); EntityTypeConfiguration entityType = new EntityTypeConfiguration(); PrimitivePropertyConfiguration primitiveProperty = new PrimitivePropertyConfiguration(property, entityType); - entityType.ExplicitProperties.Add(property, primitiveProperty); - entityType.ExplicitProperties.Add(otherProperty, new PrimitivePropertyConfiguration(otherProperty, entityType)); + PrimitivePropertyConfiguration otherPrimitiveProperty = new PrimitivePropertyConfiguration(otherProperty, entityType); + entityType.ExplicitProperties.Add(primitiveProperty.PropertyInfo, primitiveProperty); + entityType.ExplicitProperties.Add(otherPrimitiveProperty.PropertyInfo, otherPrimitiveProperty); TimestampAttributeEdmPropertyConvention convention = new TimestampAttributeEdmPropertyConvention(); // Act @@ -80,8 +81,9 @@ public void TimestampConvention_DoesntApplyWhenTheAttributeIsAppliedToMultiplePr entityType.BaseType = baseEntityType; PrimitivePropertyConfiguration primitiveProperty = new PrimitivePropertyConfiguration(property, entityType); - entityType.ExplicitProperties.Add(property, primitiveProperty); - baseEntityType.ExplicitProperties.Add(otherProperty, new PrimitivePropertyConfiguration(otherProperty, baseEntityType)); + PrimitivePropertyConfiguration otherPrimitiveProperty = new PrimitivePropertyConfiguration(otherProperty, baseEntityType); + entityType.ExplicitProperties.Add(primitiveProperty.PropertyInfo, primitiveProperty); + baseEntityType.ExplicitProperties.Add(otherPrimitiveProperty.PropertyInfo, otherPrimitiveProperty); TimestampAttributeEdmPropertyConvention convention = new TimestampAttributeEdmPropertyConvention(); // Act diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/EntityKeyConventionTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/EntityKeyConventionTests.cs index def91bbe10..0012097afb 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/EntityKeyConventionTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/EntityKeyConventionTests.cs @@ -24,11 +24,12 @@ public void Apply_Calls_HasKey_OnEdmType(string propertyName) { // Arrange Mock mockEntityType = new Mock(); - Mock property = new Mock(typeof(EntityKeyConventionTests_EntityType).GetProperty(propertyName), mockEntityType.Object); + MemberDescriptor propertyDescriptor = new MemberDescriptor(typeof(EntityKeyConventionTests_EntityType).GetProperty(propertyName)); + Mock property = new Mock(propertyDescriptor, mockEntityType.Object); mockEntityType.Setup(e => e.Name).Returns("SampleEntity"); mockEntityType.Setup(entityType => entityType.HasKey(typeof(EntityKeyConventionTests_EntityType).GetProperty(propertyName))).Returns(mockEntityType.Object).Verifiable(); - mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo(), property.Object); + mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), property.Object); // Act new EntityKeyConvention().Apply(mockEntityType.Object, null); @@ -42,8 +43,9 @@ public void Apply_Calls_HasKey_ForEnumProperty_OnEdmType() { // Arrange Mock mockEntityType = new Mock(); + MemberDescriptor propertyDescriptor = new MemberDescriptor(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId")); Mock property = - new Mock(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId"), + new Mock(propertyDescriptor, mockEntityType.Object); property.Setup(c => c.Kind).Returns(PropertyKind.Enum); @@ -53,7 +55,7 @@ public void Apply_Calls_HasKey_ForEnumProperty_OnEdmType() .Returns(mockEntityType.Object) .Verifiable(); - mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo(), property.Object); + mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), property.Object); // Act new EntityKeyConvention().Apply(mockEntityType.Object, null); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EdmTypeConfigurationExtensionsTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EdmTypeConfigurationExtensionsTest.cs index 449f51a262..2b298fcc65 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EdmTypeConfigurationExtensionsTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EdmTypeConfigurationExtensionsTest.cs @@ -19,17 +19,17 @@ public void EntityType_DerivedProperties_ReturnsAllDerivedProperties() { // Arrange Mock entityA = new Mock(); - entityA.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("A1", entityA.Object)); - entityA.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("A2", entityA.Object)); + entityA.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("A1", entityA.Object)); + entityA.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("A2", entityA.Object)); Mock entityB = new Mock(); - entityB.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("B1", entityB.Object)); - entityB.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("B2", entityB.Object)); + entityB.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("B1", entityB.Object)); + entityB.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("B2", entityB.Object)); entityB.Setup(e => e.BaseType).Returns(entityA.Object); Mock entityC = new Mock(); - entityC.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("C1", entityC.Object)); - entityC.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("C2", entityC.Object)); + entityC.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("C1", entityC.Object)); + entityC.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("C2", entityC.Object)); entityC.Setup(e => e.BaseType).Returns(entityB.Object); // Act & Assert @@ -43,17 +43,17 @@ public void ComplexType_DerivedProperties_ReturnsAllDerivedProperties() { // Arrange Mock complexA = new Mock(); - complexA.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("A1", complexA.Object)); - complexA.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("A2", complexA.Object)); + complexA.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("A1", complexA.Object)); + complexA.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("A2", complexA.Object)); Mock complexB = new Mock(); - complexB.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("B1", complexB.Object)); - complexB.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("B2", complexB.Object)); + complexB.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("B1", complexB.Object)); + complexB.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("B2", complexB.Object)); complexB.Setup(e => e.BaseType).Returns(complexA.Object); Mock complexC = new Mock(); - complexC.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("C1", complexC.Object)); - complexC.Object.ExplicitProperties.Add(new MockPropertyInfo(), MockProperty("C2", complexC.Object)); + complexC.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("C1", complexC.Object)); + complexC.Object.ExplicitProperties.Add(new MockPropertyInfo().AsPropertyDescriptor(), MockProperty("C2", complexC.Object)); complexC.Setup(e => e.BaseType).Returns(complexB.Object); // Act & Assert @@ -437,7 +437,9 @@ private static PropertyConfiguration MockProperty(string name, StructuralTypeCon Mock propertyInfo = new Mock(); propertyInfo.Setup(p => p.Name).Returns(name); - Mock property = new Mock(propertyInfo.Object, declaringType); + Mock propertyDescriptor = new Mock(propertyInfo.Object); + + Mock property = new Mock(propertyDescriptor.Object, declaringType); return property.Object; } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs index f827b96e33..20e39a6e52 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs @@ -55,6 +55,34 @@ public void CreateEntityTypeWithRelationship() Assert.Equal(5, customerType.DeclaredProperties.Count()); } + [Fact] + public void CreateEntityTypeWithExtensionRelationship() + { + var builder = new ODataModelBuilder().Add_Customer_EntityType().Add_Order_EntityType().Add_OrderCustomer_Relationship_Extension(); + + var model = builder.GetServiceModel(); + var orderType = model.SchemaElements.OfType().SingleOrDefault(t => t.Name == "Order"); + Assert.NotNull(orderType); + Assert.Equal("Order", orderType.Name); + Assert.Equal(typeof(Order).Namespace, orderType.Namespace); + Assert.Equal("OrderId", orderType.DeclaredKey.Single().Name); + Assert.Equal(5, orderType.DeclaredProperties.Count()); + Assert.Single(orderType.NavigationProperties()); + var deliveryDateProperty = orderType.DeclaredProperties.Single(dp => dp.Name == "DeliveryDate"); + Assert.NotNull(deliveryDateProperty); + Assert.True(deliveryDateProperty.Type.IsNullable); + + Assert.Equal("CustomerExt", orderType.NavigationProperties().First().Name); + Assert.Equal("Customer", orderType.NavigationProperties().First().ToEntityType().Name); + + var customerType = model.SchemaElements.OfType().SingleOrDefault(t => t.Name == "Customer"); + Assert.NotNull(customerType); + Assert.Equal("Customer", customerType.Name); + Assert.Equal(typeof(Customer).Namespace, customerType.Namespace); + Assert.Equal("CustomerId", customerType.DeclaredKey.Single().Name); + Assert.Equal(5, customerType.DeclaredProperties.Count()); + } + [Fact] public void CanCreateEntityWithCompoundKey() { diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/MetadataTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/MetadataTest.cs index ac182e793e..715d8c5158 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/MetadataTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/MetadataTest.cs @@ -174,6 +174,23 @@ public void CanEmitModelWithSingletonHasEntitysetBinding() var csdl = GetCSDL(model); } + [Fact] + public void CanEmitModelWithExtensionMethodBinding() + { + //Arrange + var builder = new ODataModelBuilder() + .Add_Company_EntityType() + .Add_Employee_EntityType() + .Add_CompaniesEmployees_Binding_Extension() + .Add_EmployeeComplany_Relationship_Extension(); + + // Act + var model = builder.GetServiceModel(); + + //Assert + var csdl = GetCSDL(model); + } + public static string GetCSDL(IEdmModel model) { StringWriter writer = new StringWriter(); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/NavigationPropertyConfigurationTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/NavigationPropertyConfigurationTest.cs index 0a27091930..995c987170 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/NavigationPropertyConfigurationTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/NavigationPropertyConfigurationTest.cs @@ -30,7 +30,7 @@ public void Ctor_ThrowsArgument_IfMultiplicityIsManyAndPropertyIsNotCollection() ExceptionAssert.Throws( () => new NavigationPropertyConfiguration(mockEntity.GetProperty("ID"), EdmMultiplicity.Many, new EntityTypeConfiguration()), - "The property 'ID' on the type 'T' is being configured as a Many-to-Many navigation property. Many to Many navigation properties must be collections.\r\nParameter name: property"); + "The property 'ID' on the type 'T' is being configured as a Many-to-Many navigation property. Many to Many navigation properties must be collections.\r\nParameter name: memberDescriptor"); } [Fact] diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/PropertyConfigurationTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/PropertyConfigurationTest.cs index 4587e66e80..045010aa44 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/PropertyConfigurationTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/PropertyConfigurationTest.cs @@ -16,15 +16,18 @@ public class PropertyConfigurationTest private string _name = "name"; private StructuralTypeConfiguration _declaringType; private PropertyInfo _propertyInfo; + private MemberDescriptor _propertyDescriptor; public PropertyConfigurationTest() { Mock mockPropertyInfo = new Mock(); _propertyInfo = mockPropertyInfo.Object; + Mock mockPropertyDescriptor = new Mock(_propertyInfo); + _propertyDescriptor = mockPropertyDescriptor.Object; Mock mockTypeConfig = new Mock(); _declaringType = mockTypeConfig.Object; Mock mockConfiguration = - new Mock(_propertyInfo, _declaringType) { CallBase = true }; + new Mock(_propertyDescriptor, _declaringType) { CallBase = true }; mockConfiguration.Object.Name = "Name"; _configuration = mockConfiguration.Object; } @@ -44,7 +47,7 @@ public void Property_DeclaringType_Get() [Fact] public void Property_PropertyInfo_Get() { - Assert.Equal(_propertyInfo, _configuration.PropertyInfo); + Assert.Equal(_propertyInfo, _configuration.PropertyInfo.PropertyInfo); } [Fact] diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Company.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Company.cs index b6f8d35298..37452e4299 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Company.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Company.cs @@ -19,4 +19,12 @@ public class Company public List Customers { get; set; } public List
Subsidiaries { get; set; } } + + public static class CompanyExtensions + { + public static IList CompanyEmployeesExt(this Company company) + { + return company.ComplanyEmployees; + } + } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Employee.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Employee.cs index fb8e253dfe..3e562d2386 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Employee.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Employee.cs @@ -39,4 +39,12 @@ public class SalesPerson : Employee public Decimal Bonus { get; set; } public IList Customers { get; set; } } + + public static class EmployeeExtensions + { + public static Company WorkCompanyExt(this Employee employee) + { + return employee.WorkCompany; + } + } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Order.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Order.cs index ff86a263fa..cdce8b4ee3 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Order.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Order.cs @@ -14,4 +14,12 @@ public class Order public DateTimeOffset OrderDate { get; set; } public DateTimeOffset? DeliveryDate { get; set; } } + + public static class OrderExtensions + { + public static Customer CustomerExt(this Order order) + { + return order.Customer; + } + } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Common/MockPropertyInfo.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Common/MockPropertyInfo.cs index 88a88899c4..6ae5fd681f 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Common/MockPropertyInfo.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Common/MockPropertyInfo.cs @@ -3,6 +3,7 @@ using System; using System.Reflection; +using Microsoft.AspNet.OData.Builder; using Moq; namespace Microsoft.AspNet.OData.Test.Common @@ -44,5 +45,10 @@ public MockPropertyInfo Abstract() return this; } + + public MemberDescriptor AsPropertyDescriptor() + { + return new MemberDescriptor(this); + } } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/Models/Customer.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/Models/Customer.cs index 970ee256e2..e32d3b1ee9 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/Models/Customer.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/Models/Customer.cs @@ -24,6 +24,14 @@ public Customer() public Address HomeAddress { get; set; } } + public static class CustomerExtensions + { + public static IList OrdersExt(this Customer customer) + { + return customer.Orders; + } + } + public class SpecialCustomer : Customer { public int Level { get; set; } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/AggregationBinderTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/AggregationBinderTests.cs index cd49ee7f70..fa690fb53d 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/AggregationBinderTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/AggregationBinderTests.cs @@ -52,12 +52,31 @@ public void NavigationGroupBy() [Fact] public void NavigationMultipleGroupBy() - { + { var filters = VerifyQueryDeserialization( "groupby((Category/CategoryName, SupplierAddress/State))", ".GroupBy($it => new GroupByWrapper() {GroupByContainer = new NestedProperty() {Name = SupplierAddress, NestedValue = new GroupByWrapper() {GroupByContainer = new LastInChain() {Name = State, Value = $it.SupplierAddress.State, }, }, Next = new NestedPropertyLastInChain() {Name = Category, NestedValue = new GroupByWrapper() {GroupByContainer = new LastInChain() {Name = CategoryName, Value = $it.Category.CategoryName, }, }, }, }, })" + ".Select($it => new AggregationWrapper() {GroupByContainer = $it.Key.GroupByContainer, })"); } + + [Fact] + public void NavigationExtensionGroupBy() + { + var filters = VerifyQueryDeserialization( + "groupby((CategoryExt/CategoryName))", + ".GroupBy($it => new GroupByWrapper() {GroupByContainer = new NestedPropertyLastInChain() {Name = CategoryExt, NestedValue = new GroupByWrapper() {GroupByContainer = new LastInChain() {Name = CategoryName, Value = $it.CategoryExt().CategoryName, }, }, }, })" + + ".Select($it => new AggregationWrapper() {GroupByContainer = $it.Key.GroupByContainer, })"); + } + + [Fact] + public void NavigationExtensionMultipleGroupBy() + { + + var filters = VerifyQueryDeserialization( + "groupby((CategoryExt/CategoryName, AlternateCategory/CategoryName))", + ".GroupBy($it => new GroupByWrapper() {GroupByContainer = new NestedProperty() {Name = AlternateCategory, NestedValue = new GroupByWrapper() {GroupByContainer = new LastInChain() {Name = CategoryName, Value = $it.AlternateCategory().CategoryName, }, }, Next = new NestedPropertyLastInChain() {Name = CategoryExt, NestedValue = new GroupByWrapper() {GroupByContainer = new LastInChain() {Name = CategoryName, Value = $it.CategoryExt().CategoryName, }, }, }, }, })" + + ".Select($it => new AggregationWrapper() {GroupByContainer = $it.Key.GroupByContainer, })"); + } [Fact] public void SingleDynamicGroupBy() @@ -244,6 +263,8 @@ private IEdmModel GetModel() where T : class { model.EntityType().DerivesFrom(); model.EntityType().DerivesFrom(); + model.EntityType().HasRequired(p => p.CategoryExt()); + model.EntityType().HasRequired(p => p.AlternateCategory()); } value = _modelCache[key] = model.GetEdmModel(); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/DataModel.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/DataModel.cs index 5db4e5099a..c9bac31ffd 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/DataModel.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/DataModel.cs @@ -60,6 +60,19 @@ public class Product public Address[] NotFilterableAlternateAddresses { get; set; } } + public static class ProductExtensions + { + public static Category CategoryExt(this Product product) + { + return product.Category; + } + + public static Category AlternateCategory(this Product product) + { + return null; + } + } + public class Category { public int CategoryID { get; set; } @@ -73,6 +86,14 @@ public class Category public IQueryable QueryableProducts { get; set; } } + public static class CategoryExtensions + { + public static IEnumerable EnumerableProductsExt(this Category category) + { + return category.EnumerableProducts; + } + } + public class Address { public string City { get; set; } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/FilterBinderTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/FilterBinderTests.cs index a1f872b547..11e302ee83 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/FilterBinderTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/FilterBinderTests.cs @@ -523,6 +523,20 @@ public void ComplexPropertyNavigation() new { WithNullPropagation = true, WithoutNullPropagation = true }); } + [Fact] + public void NavigationPropertyExtension() + { + var filters = VerifyQueryDeserialization("Category/CategoryName eq 'Electronic'"); + + RunFilters(filters, + new Product { }, + new { WithNullPropagation = false, WithoutNullPropagation = typeof(NullReferenceException) }); + + RunFilters(filters, + new Product { Category = new Category { CategoryName = "Electronic" } }, + new { WithNullPropagation = true, WithoutNullPropagation = true }); + } + #region Any/All [Fact] @@ -3007,6 +3021,7 @@ private IEdmModel GetModel() where T : class { model.EntityType().DerivesFrom(); model.EntityType().DerivesFrom(); + model.EntityType().HasRequired(p=>p.CategoryExt()); } value = _modelCache[key] = model.GetEdmModel(); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs index d8e2c79906..9a434218db 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs @@ -828,6 +828,51 @@ public void CreatePropertyValueExpressionWithFilter_Collection_Works_HandleNullP Assert.Equal(1, orders.ToList()[0].ID); } + [Fact] + public void CreatePropertyValueExpressionWithFilter_Collection_Works_NavigationExtension() + { + // Arrange + var ordersProperty = _model.Customer.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + Name = "OrdersExt", + Target = _model.Order, + TargetMultiplicity = EdmMultiplicity.Many + }); + _model.Model.SetAnnotationValue(_model.Order, new ClrTypeAnnotation(typeof(Order))); + var extensionMethod = typeof(CustomerExtensions).GetMethod("OrdersExt", BindingFlags.Static|BindingFlags.Public); + var memberDescriptor = new MemberDescriptor(extensionMethod); + _model.Model.SetAnnotationValue(ordersProperty, new ClrPropertyInfoAnnotation(memberDescriptor)); + _settings.HandleNullPropagation = HandleNullPropagationOption.True; + var customer = + Expression.Constant(new Customer { Orders = new[] { new Order { ID = 1 }, new Order { ID = 2 } } }); + var parser = new ODataQueryOptionParser( + _model.Model, + _model.Order, + _model.Orders, + new Dictionary { { "$filter", "ID eq 1" } }); + var filterCaluse = parser.ParseFilter(); + + // Act + var filterInExpand = _binder.CreatePropertyValueExpressionWithFilter( + _model.Customer, + ordersProperty, + customer, + filterCaluse); + + // Assert + Assert.Equal( + string.Format( + "IIF((value({0}) == null), null, IIF((value({0}).OrdersExt() == null), null, " + + "value({0}).OrdersExt().AsQueryable().Where($it => ($it.ID == value({1}).TypedProperty))))", + customer.Type, + "Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Int32]"), + filterInExpand.ToString()); + var orders = Expression.Lambda(filterInExpand).Compile().DynamicInvoke() as IEnumerable; + Assert.Single(orders); + Assert.Equal(1, orders.ToList()[0].ID); + } + [Fact] public void CreatePropertyValueExpressionWithFilter_Collection_Works_HandleNullPropagationOptionIsFalse() { diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/UsefulBuilders.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/UsefulBuilders.cs index 6752266aa0..582cbd98cb 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/UsefulBuilders.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/UsefulBuilders.cs @@ -229,6 +229,12 @@ public static ODataModelBuilder Add_EmployeeComplany_Relationship(this ODataMode return builder; } + public static ODataModelBuilder Add_EmployeeComplany_Relationship_Extension(this ODataModelBuilder builder) + { + builder.EntityType().HasRequired(o => o.WorkCompanyExt()); + return builder; + } + // EntitySet -> EntitySet public static ODataModelBuilder Add_CompaniesEmployees_Binding(this ODataModelBuilder builder) { @@ -236,6 +242,12 @@ public static ODataModelBuilder Add_CompaniesEmployees_Binding(this ODataModelBu return builder; } + public static ODataModelBuilder Add_CompaniesEmployees_Binding_Extension(this ODataModelBuilder builder) + { + builder.EntitySet("Companies").HasManyBinding(c => c.CompanyEmployeesExt(), "Employees"); + return builder; + } + // EntitySet -> Singleton public static ODataModelBuilder Add_CompaniesCEO_Binding(this ODataModelBuilder builder) { @@ -269,6 +281,12 @@ public static ODataModelBuilder Add_OrderCustomer_Relationship(this ODataModelBu return builder; } + public static ODataModelBuilder Add_OrderCustomer_Relationship_Extension(this ODataModelBuilder builder) + { + builder.EntityType().HasRequired(o => o.CustomerExt()); + return builder; + } + public static ODataModelBuilder Add_Customers_EntitySet(this ODataModelBuilder builder) { builder.Add_Customer_EntityType().EntitySet("Customers"); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl index 5f94b73ced..8efcd599d4 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl @@ -204,9 +204,10 @@ public class Microsoft.AspNet.OData.ClrEnumMemberAnnotation { } public class Microsoft.AspNet.OData.ClrPropertyInfoAnnotation { + public ClrPropertyInfoAnnotation (MemberDescriptor propertyDescriptor) public ClrPropertyInfoAnnotation (System.Reflection.PropertyInfo clrPropertyInfo) - System.Reflection.PropertyInfo ClrPropertyInfo { public get; } + MemberDescriptor ClrPropertyInfo { public get; } } public class Microsoft.AspNet.OData.ClrTypeAnnotation { @@ -1022,7 +1023,7 @@ public abstract class Microsoft.AspNet.OData.Builder.ParameterConfiguration { } public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { - protected PropertyConfiguration (System.Reflection.PropertyInfo property, StructuralTypeConfiguration declaringType) + protected PropertyConfiguration (MemberDescriptor property, StructuralTypeConfiguration declaringType) bool AddedExplicitly { public get; public set; } bool AutoExpand { public get; public set; } @@ -1038,7 +1039,7 @@ public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { bool NotNavigable { public get; public set; } bool NotSortable { public get; public set; } int Order { public get; public set; } - System.Reflection.PropertyInfo PropertyInfo { public get; } + MemberDescriptor PropertyInfo { public get; } QueryConfiguration QueryConfiguration { public get; public set; } System.Type RelatedClrType { public abstract get; } bool Unsortable { public get; public set; } @@ -1097,7 +1098,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration StructuralTypeConfiguration BaseTypeInternal { protected virtual get; } System.Type ClrType { public virtual get; } System.Reflection.PropertyInfo DynamicPropertyDictionary { public get; } - System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } + System.Collections.Generic.IDictionary`2[[Microsoft.AspNet.OData.Builder.MemberDescriptor],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } string FullName { public virtual get; } System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Reflection.PropertyInfo]] IgnoredProperties { public get; } System.Nullable`1[[System.Boolean]] IsAbstract { public virtual get; public virtual set; } @@ -1114,13 +1115,16 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration internal virtual void AbstractImpl () public virtual CollectionPropertyConfiguration AddCollectionProperty (System.Reflection.PropertyInfo propertyInfo) public virtual ComplexPropertyConfiguration AddComplexProperty (System.Reflection.PropertyInfo propertyInfo) + public virtual NavigationPropertyConfiguration AddContainedNavigationProperty (MemberDescriptor navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual NavigationPropertyConfiguration AddContainedNavigationProperty (System.Reflection.PropertyInfo navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual void AddDynamicPropertyDictionary (System.Reflection.PropertyInfo propertyInfo) public virtual EnumPropertyConfiguration AddEnumProperty (System.Reflection.PropertyInfo propertyInfo) + public virtual NavigationPropertyConfiguration AddNavigationProperty (MemberDescriptor navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual NavigationPropertyConfiguration AddNavigationProperty (System.Reflection.PropertyInfo navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual PrimitivePropertyConfiguration AddProperty (System.Reflection.PropertyInfo propertyInfo) internal virtual void DerivesFromImpl (StructuralTypeConfiguration baseType) internal virtual void DerivesFromNothingImpl () + public virtual void RemoveProperty (MemberDescriptor propertyDescriptor) public virtual void RemoveProperty (System.Reflection.PropertyInfo propertyInfo) } @@ -1515,6 +1519,28 @@ public class Microsoft.AspNet.OData.Builder.LowerCamelCaser { public virtual string ToLowerCamelCase (string name) } +public class Microsoft.AspNet.OData.Builder.MemberDescriptor { + public MemberDescriptor (System.Reflection.MethodInfo methodInfo) + public MemberDescriptor (System.Reflection.PropertyInfo propertyInfo) + + System.Type DeclaringType { public get; } + System.Reflection.MemberInfo MemberInfo { public get; } + System.Reflection.MethodInfo MethodInfo { public get; } + string Name { public get; } + System.Reflection.PropertyInfo PropertyInfo { public get; } + System.Type PropertyType { public get; } + System.Type ReflectedType { public get; } + + public virtual bool Equals (object obj) + public T GetCustomAttribute () + public T GetCustomAttribute (bool inherit) + public IEnumerable`1 GetCustomAttributes () + public IEnumerable`1 GetCustomAttributes (bool inherit) + public object[] GetCustomAttributes (System.Type type, bool inherit) + public virtual int GetHashCode () + public System.Reflection.MemberInfo ToMemberInfo () +} + public class Microsoft.AspNet.OData.Builder.NavigationLinkBuilder { public NavigationLinkBuilder (System.Func`3[[Microsoft.AspNet.OData.ResourceContext],[Microsoft.OData.Edm.IEdmNavigationProperty],[System.Uri]] navigationLinkFactory, bool followsConventions) @@ -1533,6 +1559,7 @@ public class Microsoft.AspNet.OData.Builder.NavigationPropertyBindingConfigurati } public class Microsoft.AspNet.OData.Builder.NavigationPropertyConfiguration : PropertyConfiguration { + public NavigationPropertyConfiguration (MemberDescriptor memberDescriptor, Microsoft.OData.Edm.EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) public NavigationPropertyConfiguration (System.Reflection.PropertyInfo property, Microsoft.OData.Edm.EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) bool ContainsTarget { public get; } @@ -1549,6 +1576,7 @@ public class Microsoft.AspNet.OData.Builder.NavigationPropertyConfiguration : Pr public NavigationPropertyConfiguration CascadeOnDelete (bool cascade) public NavigationPropertyConfiguration Contained () public NavigationPropertyConfiguration HasConstraint (System.Collections.Generic.KeyValuePair`2[[System.Reflection.PropertyInfo],[System.Reflection.PropertyInfo]] constraint) + public NavigationPropertyConfiguration HasConstraint (MemberDescriptor dependentPropertyInfo, MemberDescriptor principalPropertyInfo) public NavigationPropertyConfiguration HasConstraint (System.Reflection.PropertyInfo dependentPropertyInfo, System.Reflection.PropertyInfo principalPropertyInfo) public NavigationPropertyConfiguration NonContained () public NavigationPropertyConfiguration Optional () diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl index 0f902c3911..72164d034a 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl @@ -211,9 +211,10 @@ public class Microsoft.AspNet.OData.ClrEnumMemberAnnotation { } public class Microsoft.AspNet.OData.ClrPropertyInfoAnnotation { + public ClrPropertyInfoAnnotation (MemberDescriptor propertyDescriptor) public ClrPropertyInfoAnnotation (System.Reflection.PropertyInfo clrPropertyInfo) - System.Reflection.PropertyInfo ClrPropertyInfo { public get; } + MemberDescriptor ClrPropertyInfo { public get; } } public class Microsoft.AspNet.OData.ClrTypeAnnotation { @@ -1070,7 +1071,7 @@ public abstract class Microsoft.AspNet.OData.Builder.ParameterConfiguration { } public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { - protected PropertyConfiguration (System.Reflection.PropertyInfo property, StructuralTypeConfiguration declaringType) + protected PropertyConfiguration (MemberDescriptor property, StructuralTypeConfiguration declaringType) bool AddedExplicitly { public get; public set; } bool AutoExpand { public get; public set; } @@ -1086,7 +1087,7 @@ public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { bool NotNavigable { public get; public set; } bool NotSortable { public get; public set; } int Order { public get; public set; } - System.Reflection.PropertyInfo PropertyInfo { public get; } + MemberDescriptor PropertyInfo { public get; } QueryConfiguration QueryConfiguration { public get; public set; } System.Type RelatedClrType { public abstract get; } bool Unsortable { public get; public set; } @@ -1145,7 +1146,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration StructuralTypeConfiguration BaseTypeInternal { protected virtual get; } System.Type ClrType { public virtual get; } System.Reflection.PropertyInfo DynamicPropertyDictionary { public get; } - System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } + System.Collections.Generic.IDictionary`2[[Microsoft.AspNet.OData.Builder.MemberDescriptor],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } string FullName { public virtual get; } System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Reflection.PropertyInfo]] IgnoredProperties { public get; } System.Nullable`1[[System.Boolean]] IsAbstract { public virtual get; public virtual set; } @@ -1162,13 +1163,16 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration internal virtual void AbstractImpl () public virtual CollectionPropertyConfiguration AddCollectionProperty (System.Reflection.PropertyInfo propertyInfo) public virtual ComplexPropertyConfiguration AddComplexProperty (System.Reflection.PropertyInfo propertyInfo) + public virtual NavigationPropertyConfiguration AddContainedNavigationProperty (MemberDescriptor navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual NavigationPropertyConfiguration AddContainedNavigationProperty (System.Reflection.PropertyInfo navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual void AddDynamicPropertyDictionary (System.Reflection.PropertyInfo propertyInfo) public virtual EnumPropertyConfiguration AddEnumProperty (System.Reflection.PropertyInfo propertyInfo) + public virtual NavigationPropertyConfiguration AddNavigationProperty (MemberDescriptor navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual NavigationPropertyConfiguration AddNavigationProperty (System.Reflection.PropertyInfo navigationProperty, Microsoft.OData.Edm.EdmMultiplicity multiplicity) public virtual PrimitivePropertyConfiguration AddProperty (System.Reflection.PropertyInfo propertyInfo) internal virtual void DerivesFromImpl (StructuralTypeConfiguration baseType) internal virtual void DerivesFromNothingImpl () + public virtual void RemoveProperty (MemberDescriptor propertyDescriptor) public virtual void RemoveProperty (System.Reflection.PropertyInfo propertyInfo) } @@ -1563,6 +1567,28 @@ public class Microsoft.AspNet.OData.Builder.LowerCamelCaser { public virtual string ToLowerCamelCase (string name) } +public class Microsoft.AspNet.OData.Builder.MemberDescriptor { + public MemberDescriptor (System.Reflection.MethodInfo methodInfo) + public MemberDescriptor (System.Reflection.PropertyInfo propertyInfo) + + System.Type DeclaringType { public get; } + System.Reflection.MemberInfo MemberInfo { public get; } + System.Reflection.MethodInfo MethodInfo { public get; } + string Name { public get; } + System.Reflection.PropertyInfo PropertyInfo { public get; } + System.Type PropertyType { public get; } + System.Type ReflectedType { public get; } + + public virtual bool Equals (object obj) + public T GetCustomAttribute () + public T GetCustomAttribute (bool inherit) + public IEnumerable`1 GetCustomAttributes () + public IEnumerable`1 GetCustomAttributes (bool inherit) + public object[] GetCustomAttributes (System.Type type, bool inherit) + public virtual int GetHashCode () + public System.Reflection.MemberInfo ToMemberInfo () +} + public class Microsoft.AspNet.OData.Builder.NavigationLinkBuilder { public NavigationLinkBuilder (System.Func`3[[Microsoft.AspNet.OData.ResourceContext],[Microsoft.OData.Edm.IEdmNavigationProperty],[System.Uri]] navigationLinkFactory, bool followsConventions) @@ -1581,6 +1607,7 @@ public class Microsoft.AspNet.OData.Builder.NavigationPropertyBindingConfigurati } public class Microsoft.AspNet.OData.Builder.NavigationPropertyConfiguration : PropertyConfiguration { + public NavigationPropertyConfiguration (MemberDescriptor memberDescriptor, Microsoft.OData.Edm.EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) public NavigationPropertyConfiguration (System.Reflection.PropertyInfo property, Microsoft.OData.Edm.EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) bool ContainsTarget { public get; } @@ -1597,6 +1624,7 @@ public class Microsoft.AspNet.OData.Builder.NavigationPropertyConfiguration : Pr public NavigationPropertyConfiguration CascadeOnDelete (bool cascade) public NavigationPropertyConfiguration Contained () public NavigationPropertyConfiguration HasConstraint (System.Collections.Generic.KeyValuePair`2[[System.Reflection.PropertyInfo],[System.Reflection.PropertyInfo]] constraint) + public NavigationPropertyConfiguration HasConstraint (MemberDescriptor dependentPropertyInfo, MemberDescriptor principalPropertyInfo) public NavigationPropertyConfiguration HasConstraint (System.Reflection.PropertyInfo dependentPropertyInfo, System.Reflection.PropertyInfo principalPropertyInfo) public NavigationPropertyConfiguration NonContained () public NavigationPropertyConfiguration Optional ()