diff --git a/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs b/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs index cd444b6afc..2f76c40b5a 100644 --- a/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs +++ b/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs @@ -30,6 +30,12 @@ public override void Apply(EntityTypeConfiguration edmTypeConfiguration, ODataCo { edmTypeConfiguration.RemoveKey(key); } + + EnumPropertyConfiguration[] enumKeys = edmTypeConfiguration.EnumKeys.ToArray(); + foreach (EnumPropertyConfiguration key in enumKeys) + { + edmTypeConfiguration.RemoveKey(key); + } } } } diff --git a/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs b/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs index 5d495a357d..45113b9ad7 100644 --- a/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs +++ b/OData/src/System.Web.OData/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs @@ -10,7 +10,7 @@ namespace System.Web.OData.Builder.Conventions.Attributes /// /// Configures properties that have the as keys in the . /// - internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention { public KeyAttributeEdmPropertyConvention() : base(attribute => attribute.GetType() == typeof(KeyAttribute), allowMultiple: false) @@ -24,7 +24,7 @@ public KeyAttributeEdmPropertyConvention() /// The edm type being configured. /// The found on the property. /// The ODataConventionModelBuilder used to build the model. - public override void Apply(PrimitivePropertyConfiguration edmProperty, + public override void Apply(StructuralPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute, ODataConventionModelBuilder model) @@ -34,10 +34,13 @@ public override void Apply(PrimitivePropertyConfiguration edmProperty, throw Error.ArgumentNull("edmProperty"); } - EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration; - if (entity != null) + if (edmProperty.Kind == PropertyKind.Primitive || edmProperty.Kind == PropertyKind.Enum) { - entity.HasKey(edmProperty.PropertyInfo); + EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration; + if (entity != null) + { + entity.HasKey(edmProperty.PropertyInfo); + } } } } diff --git a/OData/src/System.Web.OData/OData/Builder/Conventions/EntityKeyConvention.cs b/OData/src/System.Web.OData/OData/Builder/Conventions/EntityKeyConvention.cs index fa9c513e4c..5a272857f0 100644 --- a/OData/src/System.Web.OData/OData/Builder/Conventions/EntityKeyConvention.cs +++ b/OData/src/System.Web.OData/OData/Builder/Conventions/EntityKeyConvention.cs @@ -27,19 +27,21 @@ public override void Apply(EntityTypeConfiguration entity, ODataConventionModelB } // Suppress the EntityKeyConvention if there is any key in EntityTypeConfiguration. - if (entity.Keys.Any()) + if (entity.Keys.Any() || entity.EnumKeys.Any()) { return; } - // Try to figure out keys only if there is no base type. - if (entity.BaseType == null) + // Suppress the EntityKeyConvention if base type has any key. + if (entity.BaseType != null && entity.BaseType.Keys().Any()) { - PropertyConfiguration key = GetKeyProperty(entity); - if (key != null) - { - entity.HasKey(key.PropertyInfo); - } + return; + } + + PropertyConfiguration key = GetKeyProperty(entity); + if (key != null) + { + entity.HasKey(key.PropertyInfo); } } @@ -48,7 +50,7 @@ private static PropertyConfiguration GetKeyProperty(EntityTypeConfiguration enti IEnumerable keys = entityType.Properties .Where(p => (p.Name.Equals(entityType.Name + "Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)) - && EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null); + && (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null || TypeHelper.IsEnum(p.PropertyInfo.PropertyType))); if (keys.Count() == 1) { diff --git a/OData/src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs b/OData/src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs index 01764e9024..fac47b91e2 100644 --- a/OData/src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs +++ b/OData/src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs @@ -290,6 +290,10 @@ private void CreateEntityTypeBody(EdmEntityType type, EntityTypeConfiguration co CreateStructuralTypeBody(type, config); IEnumerable keys = config.Keys.Select(p => type.DeclaredProperties.OfType().First(dp => dp.Name == p.Name)); type.AddKeys(keys); + + // Add the Enum keys + keys = config.EnumKeys.Select(p => type.DeclaredProperties.OfType().First(dp => dp.Name == p.Name)); + type.AddKeys(keys); } private void CreateNavigationProperty(EntityTypeConfiguration config) diff --git a/OData/src/System.Web.OData/OData/Builder/EdmTypeConfigurationExtensions.cs b/OData/src/System.Web.OData/OData/Builder/EdmTypeConfigurationExtensions.cs index 224caee05c..6196ce4927 100644 --- a/OData/src/System.Web.OData/OData/Builder/EdmTypeConfigurationExtensions.cs +++ b/OData/src/System.Web.OData/OData/Builder/EdmTypeConfigurationExtensions.cs @@ -77,9 +77,9 @@ public static IEnumerable DerivedProperties( public static IEnumerable Keys(this EntityTypeConfiguration entity) { Contract.Assert(entity != null); - if (entity.Keys.Any()) + if (entity.Keys.Any() || entity.EnumKeys.Any()) { - return entity.Keys; + return entity.Keys.OfType().Concat(entity.EnumKeys); } if (entity.BaseType == null) diff --git a/OData/src/System.Web.OData/OData/Builder/EntityTypeConfiguration.cs b/OData/src/System.Web.OData/OData/Builder/EntityTypeConfiguration.cs index 08df190eab..b7e355846f 100644 --- a/OData/src/System.Web.OData/OData/Builder/EntityTypeConfiguration.cs +++ b/OData/src/System.Web.OData/OData/Builder/EntityTypeConfiguration.cs @@ -19,6 +19,7 @@ namespace System.Web.OData.Builder public class EntityTypeConfiguration : StructuralTypeConfiguration { private List _keys = new List(); + private List _enumKeys = new List(); /// /// Initializes a new instance of the class. @@ -68,6 +69,14 @@ public virtual IEnumerable Keys } } + /// + /// Gets the collection of enum keys for this entity type. + /// + public virtual IEnumerable EnumKeys + { + get { return _enumKeys; } + } + /// /// Gets or sets the base type of this entity type. /// @@ -105,14 +114,31 @@ public virtual EntityTypeConfiguration HasKey(PropertyInfo keyProperty) throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, BaseType.FullName); } - PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty); + // Add the enum key if the property type is enum + if (keyProperty.PropertyType.IsEnum) + { + ModelBuilder.AddEnumType(keyProperty.PropertyType); + EnumPropertyConfiguration enumConfig = AddEnumProperty(keyProperty); - // keys are always required - propertyConfig.IsRequired(); + // keys are always required + enumConfig.IsRequired(); - if (!_keys.Contains(propertyConfig)) + if (!_enumKeys.Contains(enumConfig)) + { + _enumKeys.Add(enumConfig); + } + } + else { - _keys.Add(propertyConfig); + PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty); + + // keys are always required + propertyConfig.IsRequired(); + + if (!_keys.Contains(propertyConfig)) + { + _keys.Add(propertyConfig); + } } return this; @@ -134,6 +160,22 @@ public virtual void RemoveKey(PrimitivePropertyConfiguration keyProperty) _keys.Remove(keyProperty); } + /// + /// Removes the enum property from the entity enum keys collection. + /// + /// The key to be removed. + /// This method just disable the property to be not a key anymore. It does not remove the property all together. + /// To remove the property completely, use the method + public virtual void RemoveKey(EnumPropertyConfiguration enumKeyProperty) + { + if (enumKeyProperty == null) + { + throw Error.ArgumentNull("enumKeyProperty"); + } + + _enumKeys.Remove(enumKeyProperty); + } + /// /// Sets the base type of this entity type to null meaning that this entity type /// does not derive from anything. @@ -152,7 +194,7 @@ public virtual EntityTypeConfiguration DerivesFromNothing() /// Returns itself so that multiple calls can be chained. public virtual EntityTypeConfiguration DerivesFrom(EntityTypeConfiguration baseType) { - if (Keys.Any() && baseType.Keys().Any()) + if ((Keys.Any() || EnumKeys.Any()) && baseType.Keys().Any()) { throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, baseType.FullName); } diff --git a/OData/src/System.Web.OData/OData/Builder/ODataConventionModelBuilder.cs b/OData/src/System.Web.OData/OData/Builder/ODataConventionModelBuilder.cs index 49a47a21d8..601ee69150 100644 --- a/OData/src/System.Web.OData/OData/Builder/ODataConventionModelBuilder.cs +++ b/OData/src/System.Web.OData/OData/Builder/ODataConventionModelBuilder.cs @@ -308,6 +308,11 @@ internal void DiscoverInheritanceRelationships() { entity.RemoveKey(keyProperty); } + + foreach (EnumPropertyConfiguration enumKeyProperty in entity.EnumKeys.ToArray()) + { + entity.RemoveKey(enumKeyProperty); + } } entity.DerivesFrom(baseEntityType); diff --git a/OData/src/System.Web.OData/OData/Routing/ODataPathSegmentTranslator.cs b/OData/src/System.Web.OData/OData/Routing/ODataPathSegmentTranslator.cs index 5297e00c3f..6af9f8cc55 100644 --- a/OData/src/System.Web.OData/OData/Routing/ODataPathSegmentTranslator.cs +++ b/OData/src/System.Web.OData/OData/Routing/ODataPathSegmentTranslator.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics.Contracts; using System.Linq; using System.Web.Http; @@ -422,6 +421,16 @@ private static string TranslateKeySegmentValue(object value, bool enableUriTempl } } + ConstantNode constantNode = value as ConstantNode; + if (constantNode != null) + { + ODataEnumValue enumValue = constantNode.Value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4); + } + } + return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V4); } diff --git a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConventionTests.cs b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConventionTests.cs index 99b1d38663..ae51743c7f 100644 --- a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConventionTests.cs +++ b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConventionTests.cs @@ -79,6 +79,7 @@ public void Apply_IgnoresKey_ComplexProperty() Mock complexProperty = new Mock(property.Object, entityType.Object); + complexProperty.Setup(c => c.Kind).Returns(PropertyKind.Complex); // Act new KeyAttributeEdmPropertyConvention().Apply(complexProperty.Object, entityType.Object, builder); @@ -102,6 +103,7 @@ public void Apply_IgnoresKey_NavigationProperty() Mock navigationProperty = new Mock(property.Object, EdmMultiplicity.ZeroOrOne, entityType.Object); + navigationProperty.Setup(c => c.Kind).Returns(PropertyKind.Navigation); // Act new KeyAttributeEdmPropertyConvention().Apply(navigationProperty.Object, entityType.Object, builder); @@ -109,5 +111,33 @@ public void Apply_IgnoresKey_NavigationProperty() // Assert entityType.Verify(); } + + [Fact] + public void Apply_AddsEnumKey_EntityTypeConfiguration() + { + // Arrange + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + + Mock property = new Mock(); + property.Setup(p => p.Name).Returns("Property"); + property.Setup(p => p.PropertyType).Returns(typeof(MyEnumType)); + property.Setup(p => p.GetCustomAttributes(It.IsAny())).Returns(new[] { new KeyAttribute() }); + + Mock entityType = new Mock(MockBehavior.Strict); + entityType.Setup(e => e.HasKey(property.Object)).Returns(entityType.Object).Verifiable(); + + Mock enumProperty = + new Mock(property.Object, entityType.Object); + + // Act + new KeyAttributeEdmPropertyConvention().Apply(enumProperty.Object, entityType.Object, builder); + + // Assert + entityType.Verify(); + } + + enum MyEnumType + { + } } } diff --git a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/EntityKeyConventionTests.cs b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/EntityKeyConventionTests.cs index c58d233b99..a0f2e4add6 100644 --- a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/EntityKeyConventionTests.cs +++ b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/EntityKeyConventionTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.ComponentModel.DataAnnotations.Schema; +using System.Web.OData.Builder.TestModels; using System.Web.OData.TestCommon; using Microsoft.OData.Edm; using Microsoft.TestCommon; @@ -35,6 +36,33 @@ public void Apply_Calls_HasKey_OnEdmType(string propertyName) mockEntityType.Verify(); } + [Fact] + public void Apply_Calls_HasKey_ForEnumProperty_OnEdmType() + { + // Arrange + Mock mockEntityType = new Mock(); + Mock property = + new Mock(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId"), + mockEntityType.Object); + property.Setup(c => c.Kind).Returns(PropertyKind.Enum); + + mockEntityType.Setup(e => e.Name).Returns("Color"); + mockEntityType.Setup( + entityType => entityType.HasKey(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId"))) + .Returns(mockEntityType.Object) + .Verifiable(); + + mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo(), property.Object); + + var mockModelBuilder = new Mock(MockBehavior.Strict); + + // Act + new EntityKeyConvention().Apply(mockEntityType.Object, mockModelBuilder.Object); + + // Assert + mockEntityType.Verify(); + } + [Fact] public void EntityKeyConvention_FiguresOutTheKeyProperty() { @@ -51,6 +79,31 @@ public void EntityKeyConvention_FiguresOutTheKeyProperty() entity.AssertHasKey(model, "ID", EdmPrimitiveTypeKind.Int64); } + [Fact] + public void EntityKeyConvention_FiguresOutTheEnumKeyProperty() + { + // Arrange + MockType baseType = + new MockType("BaseType") + .Property("ID"); + + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + builder.AddEntityType(baseType); + + // Act + IEdmModel model = builder.GetEdmModel(); + + // Assert + IEdmEntityType entity = model.AssertHasEntityType(baseType); + + IEdmStructuralProperty enumProperty = entity.AssertHasProperty(model, "ID", typeof(Color), false); + IEdmProperty enumKey = Assert.Single(entity.DeclaredKey); + Assert.Same(enumProperty, enumKey); + + Assert.Equal(EdmTypeKind.Enum, enumKey.Type.TypeKind()); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", enumKey.Type.Definition.FullTypeName()); + } + [Fact] public void EntityKeyConvention_DoesnotFigureOutKeyPropertyOnDerivedTypes() { @@ -101,6 +154,8 @@ class EntityKeyConventionTests_EntityType public string iD { get; set; } public string SampleEntityID { get; set; } + + public Color ColorId { get; set; } } } } diff --git a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/ODataConventionModelBuilderTests.cs b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/ODataConventionModelBuilderTests.cs index 08b76c9d4d..68f488d814 100644 --- a/OData/test/System.Web.OData.Test/OData/Builder/Conventions/ODataConventionModelBuilderTests.cs +++ b/OData/test/System.Web.OData.Test/OData/Builder/Conventions/ODataConventionModelBuilderTests.cs @@ -243,6 +243,39 @@ public void ModelBuilder_ProductsWithKeyAttribute() version.AssertHasPrimitiveProperty(model, "Minor", EdmPrimitiveTypeKind.Int32, isNullable: false); } + [Fact] + public void ModelBuilder_ProductsWithEnumKeyAttribute() + { + // Arrange + ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder(); + modelBuilder.EntityType(); + + // Act + IEdmModel model = modelBuilder.GetEdmModel(); + + // Assert + Assert.NotNull(model); + Assert.Equal(2, model.SchemaElements.OfType().Count()); + + IEdmEntityType product = model.AssertHasEntityType(mappedEntityClrType: typeof(ProductWithEnumKeyAttribute)); + product.AssertHasPrimitiveProperty(model, "Name", EdmPrimitiveTypeKind.String, isNullable: true); + Assert.Equal(2, product.StructuralProperties().Count()); + Assert.Empty(product.NavigationProperties()); + + IEdmStructuralProperty enumKey = Assert.Single(product.DeclaredKey); + Assert.Equal("ProductColor", enumKey.Name); + Assert.Equal(EdmTypeKind.Enum, enumKey.Type.Definition.TypeKind); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", enumKey.Type.Definition.FullTypeName()); + Assert.False(enumKey.Type.IsNullable); + + IEdmEnumType colorType = Assert.Single(model.SchemaElements.OfType()); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", enumKey.Type.Definition.FullTypeName()); + Assert.Equal(3, colorType.Members.Count()); + Assert.Single(colorType.Members.Where(m => m.Name == "Red")); + Assert.Single(colorType.Members.Where(m => m.Name == "Green")); + Assert.Single(colorType.Members.Where(m => m.Name == "Blue")); + } + [Fact] public void ModelBuilder_ProductsWithFilterSortable() { @@ -1671,6 +1704,42 @@ public void DerivedTypes_Can_DefineKeys_InQueryCompositionMode() derivedEntityType.AssertHasPrimitiveProperty(model, "DerivedTypeId", EdmPrimitiveTypeKind.Int32, isNullable: false); } + [Fact] + public void DerivedTypes_Can_DefineEnumKeys_InQueryCompositionMode() + { + // Arrange + MockType baseType = new MockType("BaseType") + .Property(typeof(Color), "ID"); + + MockType derivedType = new MockType("DerivedType") + .Property(typeof(Color), "DerivedTypeId") + .BaseType(baseType); + + MockAssembly assembly = new MockAssembly(baseType, derivedType); + + HttpConfiguration configuration = new HttpConfiguration(); + configuration.Services.Replace(typeof(IAssembliesResolver), new TestAssemblyResolver(assembly)); + var builder = new ODataConventionModelBuilder(configuration, isQueryCompositionMode: true); + + builder.AddEntitySet("bases", builder.AddEntityType(baseType)); + + // Act + IEdmModel model = builder.GetEdmModel(); + + // Assert + model.AssertHasEntitySet("bases", baseType); + IEdmEntityType baseEntityType = model.AssertHasEntityType(baseType); + IEdmStructuralProperty key = Assert.Single(baseEntityType.DeclaredKey); + Assert.Equal(EdmTypeKind.Enum, key.Type.TypeKind()); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", key.Type.Definition.FullTypeName()); + + IEdmEntityType derivedEntityType = model.AssertHasEntityType(derivedType, baseType); + Assert.Null(derivedEntityType.DeclaredKey); + IEdmProperty derivedId = Assert.Single(derivedEntityType.DeclaredProperties); + Assert.Equal(EdmTypeKind.Enum, derivedId.Type.TypeKind()); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", derivedId.Type.Definition.FullTypeName()); + } + [Fact] public void ModelBuilder_DerivedComplexTypeHavingKeys_Throws() { @@ -2654,6 +2723,14 @@ public class ProductWithKeyAttribute public CategoryWithKeyAttribute Category { get; set; } } + public class ProductWithEnumKeyAttribute + { + [Key] + public Color ProductColor { get; set; } + + public string Name { get; set; } + } + public class ProductWithFilterSortable { public int ID { get; set; } diff --git a/OData/test/System.Web.OData.Test/OData/Builder/EntityTypeTest.cs b/OData/test/System.Web.OData.Test/OData/Builder/EntityTypeTest.cs index 6fa39a1341..4d7663fa65 100644 --- a/OData/test/System.Web.OData.Test/OData/Builder/EntityTypeTest.cs +++ b/OData/test/System.Web.OData.Test/OData/Builder/EntityTypeTest.cs @@ -92,6 +92,84 @@ public void CanCreateEntityWithCompoundKey_ForDateAndTimeOfDay() Assert.NotNull(entityType.DeclaredKey.SingleOrDefault(k => k.Name == "TimeOfDay")); } + [Fact] + public void CanCreateEntityWithEnumKey() + { + // Arrange + ODataModelBuilder builder = new ODataModelBuilder(); + var enumEntityType = builder.EntityType(); + enumEntityType.HasKey(c => c.Simple); + enumEntityType.Property(c => c.Id); + + // Act + IEdmModel model = builder.GetServiceModel(); + + // Assert + IEdmEntityType entityType = model.SchemaElements.OfType() + .FirstOrDefault(c => c.Name == "EnumModel"); + Assert.NotNull(entityType); + Assert.Equal(2, entityType.Properties().Count()); + + Assert.Equal(1, entityType.DeclaredKey.Count()); + IEdmStructuralProperty key = entityType.DeclaredKey.First(); + Assert.Equal(EdmTypeKind.Enum, key.Type.TypeKind()); + Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", key.Type.Definition.FullTypeName()); + } + + [Fact] + public void CanCreateEntityWithCompoundEnumKeys() + { + // Arrange + ODataModelBuilder builder = new ODataModelBuilder(); + var enumEntityType = builder.EntityType(); + enumEntityType.HasKey(c => new { c.Simple, c.Long }); + + // Act + IEdmModel model = builder.GetServiceModel(); + + // Assert + IEdmEntityType entityType = model.SchemaElements.OfType() + .FirstOrDefault(c => c.Name == "EnumModel"); + Assert.NotNull(entityType); + Assert.Equal(2, entityType.Properties().Count()); + + Assert.Equal(2, entityType.DeclaredKey.Count()); + IEdmStructuralProperty simpleKey = entityType.DeclaredKey.First(k => k.Name == "Simple"); + Assert.Equal(EdmTypeKind.Enum, simpleKey.Type.TypeKind()); + Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", simpleKey.Type.Definition.FullTypeName()); + + IEdmStructuralProperty longKey = entityType.DeclaredKey.First(k => k.Name == "Long"); + Assert.Equal(EdmTypeKind.Enum, longKey.Type.TypeKind()); + Assert.Equal("Microsoft.TestCommon.Types.LongEnum", longKey.Type.Definition.FullTypeName()); + } + + [Fact] + public void CanCreateEntityWithPrimitiveAndEnumKey() + { + // Arrange + ODataModelBuilder builder = new ODataModelBuilder(); + var enumEntityType = builder.EntityType(); + enumEntityType.HasKey(c => new { c.Simple, c.Id }); + + // Act + IEdmModel model = builder.GetServiceModel(); + + // Assert + IEdmEntityType entityType = model.SchemaElements.OfType() + .FirstOrDefault(c => c.Name == "EnumModel"); + Assert.NotNull(entityType); + Assert.Equal(2, entityType.Properties().Count()); + + Assert.Equal(2, entityType.DeclaredKey.Count()); + IEdmStructuralProperty enumKey = entityType.DeclaredKey.First(k => k.Name == "Simple"); + Assert.Equal(EdmTypeKind.Enum, enumKey.Type.TypeKind()); + Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", enumKey.Type.Definition.FullTypeName()); + + IEdmStructuralProperty primitiveKey = entityType.DeclaredKey.First(k => k.Name == "Id"); + Assert.Equal(EdmTypeKind.Primitive, primitiveKey.Type.TypeKind()); + Assert.Equal("Edm.Int32", primitiveKey.Type.Definition.FullTypeName()); + } + [Fact] public void CanCreateEntityWithCollectionProperties() { @@ -522,6 +600,40 @@ public void RemoveKey_Removes_KeyProperty() Assert.Empty(motorcycle.Keys); } + [Fact] + public void RemoveEnumKey_ThrowsArgumentNull() + { + // Arrange + var builder = new ODataModelBuilder(); + var motorcycle = builder.AddEntityType(typeof(Motorcycle)); + + // Act & Assert + Assert.ThrowsArgumentNull( + () => motorcycle.RemoveKey(enumKeyProperty: null), + "enumKeyProperty"); + } + + [Fact] + public void RemoveEnumKey_Removes_EnumKeyProperty() + { + // Arrange + ODataModelBuilder builder = new ODataModelBuilder(); + var enumEntityType = builder.AddEntityType(typeof(EnumModel)); + enumEntityType.HasKey(typeof(EnumModel).GetProperty("Simple")); + + EnumPropertyConfiguration enumProperty = + enumEntityType.AddEnumProperty(typeof(EnumModel).GetProperty("Simple")); + + Assert.Equal(new[] { enumProperty }, enumEntityType.EnumKeys); // Guard + + // Act + enumEntityType.RemoveKey(enumProperty); + + // Assert + Assert.Empty(enumEntityType.EnumKeys); + } + + [Fact] public void DynamicDictionaryProperty_Works_ToSetEntityTypeAsOpen() { diff --git a/OData/test/System.Web.OData.Test/OData/Builder/EnumTypeTest.cs b/OData/test/System.Web.OData.Test/OData/Builder/EnumTypeTest.cs index 4213fa3c43..61c65b3e3e 100644 --- a/OData/test/System.Web.OData.Test/OData/Builder/EnumTypeTest.cs +++ b/OData/test/System.Web.OData.Test/OData/Builder/EnumTypeTest.cs @@ -150,6 +150,31 @@ public void CreateEnumTypePropertyInEntityType() Assert.True(EdmCoreModel.Instance.GetInt64(false).Definition == longEnum.Type.AsEnum().EnumDefinition().UnderlyingType); } + [Fact] + public void CreateEnumKeyInEntityType() + { + // Arrange + var builder = new ODataModelBuilder().Add_Color_EnumType().Add_LongEnum_EnumType(); + var entityTypeConfiguration = builder.EntityType(); + entityTypeConfiguration.HasKey(e => e.RequiredColor); + + // Act + var model = builder.GetEdmModel(); + var entityType = model.SchemaElements.OfType().Single(); + + // Assert + IEdmProperty requiredColorProperty = Assert.Single(entityType.Properties()); + Assert.NotNull(requiredColorProperty); + + IEdmStructuralProperty requiredColorKey = Assert.Single(entityType.DeclaredKey); + Assert.NotNull(requiredColorKey); + + Assert.Same(requiredColorProperty, requiredColorKey); + + Assert.Equal(EdmTypeKind.Enum, requiredColorKey.Type.TypeKind()); + Assert.Equal("System.Web.OData.Builder.TestModels.Color", requiredColorKey.Type.Definition.FullTypeName()); + } + [Fact] public void AddAndRemoveEnumMemberFromEnumType() { diff --git a/OData/test/System.Web.OData.Test/OData/Formatter/ODataFormatterTests.cs b/OData/test/System.Web.OData.Test/OData/Formatter/ODataFormatterTests.cs index 5483ffe3cb..2b5ec5225b 100644 --- a/OData/test/System.Web.OData.Test/OData/Formatter/ODataFormatterTests.cs +++ b/OData/test/System.Web.OData.Test/OData/Formatter/ODataFormatterTests.cs @@ -336,6 +336,37 @@ public void CustomSerializerWorks() } } + [Fact] + public void EnumKeySimpleSerializerTest() + { + // Arrange + ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); + builder.EntitySet("EnumKeyCustomers"); + builder.EntityType().HasKey(c => c.Color); + IEdmModel model = builder.GetEdmModel(); + var controllers = new[] { typeof(EnumKeyCustomersController) }; + + HttpConfiguration configuration = controllers.GetHttpConfiguration(); + configuration.MapODataServiceRoute("odata", routePrefix: null, model: model); + HttpServer host = new HttpServer(configuration); + HttpClient client = new HttpClient(host); + + // Act + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, + "http://localhost/EnumKeyCustomers(System.Web.OData.Builder.TestModels.Color'Red')"); + HttpResponseMessage response = client.SendAsync(request).Result; + + // Assert + Assert.True(response.IsSuccessStatusCode); + var customer = response.Content.ReadAsAsync().Result; + Assert.Equal(9, customer["ID"]); + Assert.Equal(Color.Red, Enum.Parse(typeof(Color), customer["Color"].ToString())); + var colors = customer["Colors"].Select(c => Enum.Parse(typeof(Color), c.ToString())); + Assert.Equal(2, colors.Count()); + Assert.Contains(Color.Blue, colors); + Assert.Contains(Color.Red, colors); + } + [Fact] public void EnumTypeRoundTripTest() { @@ -536,6 +567,21 @@ public IHttpActionResult GetColor(int key) } } + public class EnumKeyCustomersController : ODataController + { + public IHttpActionResult Get([FromODataUri]Color key) + { + EnumCustomer customer = new EnumCustomer + { + ID = 9, + Color = key, + Colors = new List { Color.Blue, Color.Red } + }; + + return Ok(customer); + } + } + public class CollectionSerializerCustomer { public int ID { get; set; } diff --git a/OData/test/System.Web.OData.Test/OData/MetadataControllerTest.cs b/OData/test/System.Web.OData.Test/OData/MetadataControllerTest.cs index 6c21cb43a0..90a6dee31d 100644 --- a/OData/test/System.Web.OData.Test/OData/MetadataControllerTest.cs +++ b/OData/test/System.Web.OData.Test/OData/MetadataControllerTest.cs @@ -9,11 +9,13 @@ using System.Net.Http.Headers; using System.Web.Http; using System.Web.Http.Tracing; +using System.Web.OData.Builder.TestModels; using System.Web.OData.Extensions; using System.Web.OData.Formatter; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Library; using Microsoft.TestCommon; +using Microsoft.TestCommon.Types; using Moq; namespace System.Web.OData.Builder @@ -579,6 +581,40 @@ public void DollarMetadata_Works_WithDerivedEntityTypeWithOwnKeys() Assert.Contains(expectMetadata, payload); } + [Fact] + public void DollarMetadata_Works_WithEntityTypeWithEnumKeys() + { + // Arrange + const string expectMetadata = + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " "; + + ODataModelBuilder builder = new ODataModelBuilder(); + builder.EntityType().HasKey(e => e.Simple).Namespace = "NS"; + builder.EnumType().Namespace = "NS"; + IEdmModel model = builder.GetEdmModel(); + + var config = new[] { typeof(MetadataController) }.GetHttpConfiguration(); + config.MapODataServiceRoute(model); + HttpServer server = new HttpServer(config); + HttpClient client = new HttpClient(server); + + // Act + var response = client.GetAsync("http://localhost/$metadata").Result; + + // Assert + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("application/xml", response.Content.Headers.ContentType.MediaType); + + string payload = response.Content.ReadAsStringAsync().Result; + Assert.Contains(expectMetadata, payload); + } + private static void AssertHasEntitySet(HttpClient client, string uri, string entitySetName) { var response = client.GetAsync(uri).Result;