From 1f48bed2de0da996054f804cfd243057bbb3b2de Mon Sep 17 00:00:00 2001 From: Sreejith Pazhampilly <37782322+Sreejithpin@users.noreply.github.com> Date: Thu, 17 Sep 2020 20:42:50 -0700 Subject: [PATCH] Add Instance annotations to WebAPI (#2219) * InstanceAnnotationChanges InstanceAnnotationChanges * Update ODataResourceDeserializerTests.cs * Add review comments * Review comment fix * Code review changes Code review changes * updates * Update ODataResourceSerializer.cs * changes after rebase * InstanceAnnotationChanges InstanceAnnotationChanges * Add review comments * Review comment fix * Code review changes Code review changes * Update Microsoft.AspNet.OData.PublicApi.bsl * public api * public api change * Address comments * Changes * Update ODataResourceSerializer.cs * changes * Comment changes * Update ODataResourceSerializer.cs * publi api * comment chantes * extra test * Update GlobalSuppressions.cs * name change * changes * changes and test * Update ODataResourceValueSerializer.cs * changes * Update ODataResourceValueSerializer.cs * changes * changes * updates * Update ODataInstanceAnnotationContainer.cs --- .../Builder/EdmModelHelperMethods.cs | 13 + .../Builder/EdmTypeBuilder.cs | 19 +- .../Builder/EdmTypeMap.cs | 6 +- .../IODataInstanceAnnotationContainer.cs | 59 + .../InstanceAnnotationContainerAnnotation.cs | 41 + .../Builder/ODataConventionModelBuilder.cs | 14 + .../ODataInstanceAnnotationContainer.cs | 166 +++ .../Builder/PropertyKind.cs | 8 +- .../Builder/StructuralTypeConfiguration.cs | 50 + ...turalTypeConfigurationOfTStructuralType.cs | 16 + .../Common/SRResources.Designer.cs | 141 +++ .../Common/SRResources.resx | 39 + .../Extensions/ContainerBuilderExtensions.cs | 1 + .../Deserialization/DeserializationHelpers.cs | 170 ++- .../ODataResourceDeserializer.cs | 21 +- .../Formatter/EdmLibHelpers.cs | 24 +- .../Formatter/ODataInputFormatterHelper.cs | 14 + .../ODataCollectionSerializer.cs | 30 +- .../Serialization/ODataResourceSerializer.cs | 111 +- .../ODataResourceValueSerializer.cs | 211 ++++ .../Microsoft.AspNet.OData.Shared.projitems | 4 + .../TypeHelper.cs | 9 + .../GlobalSuppressions.cs | 4 +- .../AnnotationController.cs | 283 +++++ .../AnnotationDataModel.cs | 53 + .../InstanceAnnotations/AnnotationEdmModel.cs | 111 ++ .../InstanceAnnotations/AnnotationTest.cs | 242 ++++ .../Microsoft.Test.E2E.AspNet.OData.csproj | 5 + .../Builder/ComplexTypeTest.cs | 38 +- .../ODataConventionModelBuilderTests.cs | 63 ++ .../Builder/EntityTypeTest.cs | 7 + .../StructuralTypeConfigurationTest.cs | 14 + .../Builder/TestModels/Customer.cs | 4 +- .../DeserializationHelpersTest.cs | 58 + .../ODataResourceDeserializerTests.cs | 446 ++++++++ .../ODataResourceSerializerTests.cs | 1005 ++++++++++++++--- .../OpenEntityTypeTest.cs | 6 +- .../Resources/SupplierRequestEntry.json | 18 +- .../TestCommon/SimpleOpenAddress.cs | 2 + .../TestCommon/SimpleOpenCustomer.cs | 2 + .../Microsoft.AspNet.OData.PublicApi.bsl | 40 +- .../Microsoft.AspNetCore.OData.PublicApi.bsl | 40 +- ...Microsoft.AspNetCore3x.OData.PublicApi.bsl | 40 +- 43 files changed, 3482 insertions(+), 166 deletions(-) create mode 100644 src/Microsoft.AspNet.OData.Shared/Builder/IODataInstanceAnnotationContainer.cs create mode 100644 src/Microsoft.AspNet.OData.Shared/Builder/InstanceAnnotationContainerAnnotation.cs create mode 100644 src/Microsoft.AspNet.OData.Shared/Builder/ODataInstanceAnnotationContainer.cs create mode 100644 src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceValueSerializer.cs create mode 100644 test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationController.cs create mode 100644 test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationDataModel.cs create mode 100644 test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationEdmModel.cs create mode 100644 test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationTest.cs diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs index 9d850b53a0..a161d93110 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -460,6 +460,9 @@ private static Dictionary AddTypes(this EdmModel model, EdmTypeM // add dynamic dictionary property annotation for open types model.AddDynamicPropertyDictionaryAnnotations(edmTypeMap.OpenTypes); + // add instance annotation dictionary property annotations + model.AddInstanceAnnotationsContainer(edmTypeMap.InstanceAnnotatableTypes); + return edmTypes; } @@ -554,6 +557,16 @@ private static void AddDynamicPropertyDictionaryAnnotations(this EdmModel model, } } + private static void AddInstanceAnnotationsContainer(this EdmModel model, + Dictionary instanceAnnotations) + { + foreach (KeyValuePair instanceAnnotation in instanceAnnotations) + { + IEdmStructuredType edmStructuredType = instanceAnnotation.Key; + PropertyInfo propertyInfo = instanceAnnotation.Value; + model.SetAnnotationValue(edmStructuredType, new ODataInstanceAnnotationContainerAnnotation(propertyInfo)); + } + } private static void AddPropertiesQuerySettings(this EdmModel model, Dictionary edmPropertiesQuerySettings) { diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs index c890f30a8e..af528225fd 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs @@ -30,6 +30,7 @@ internal class EdmTypeBuilder private readonly Dictionary _structuredTypeQuerySettings = new Dictionary(); private readonly Dictionary _members = new Dictionary(); private readonly Dictionary _openTypes = new Dictionary(); + private readonly Dictionary _instanceAnnotableTypes = new Dictionary(); internal EdmTypeBuilder(IEnumerable configurations) { @@ -44,6 +45,7 @@ private Dictionary GetEdmTypes() _members.Clear(); _openTypes.Clear(); _propertyConfigurations.Clear(); + _instanceAnnotableTypes.Clear(); // Create headers to allow CreateEdmTypeBody to blindly references other things. foreach (IEdmTypeConfiguration config in _configurations) @@ -91,6 +93,13 @@ private void CreateEdmTypeHeader(IEdmTypeConfiguration config) // add a mapping between the open complex type and its dynamic property dictionary. _openTypes.Add(complexType, complex.DynamicPropertyDictionary); } + + if (complex.SupportsInstanceAnnotations) + { + // add a mapping between the complex type and its instance annotation dictionary. + _instanceAnnotableTypes.Add(complexType, complex.InstanceAnnotationsContainer); + } + edmType = complexType; } else if (config.Kind == EdmTypeKind.Entity) @@ -116,6 +125,13 @@ private void CreateEdmTypeHeader(IEdmTypeConfiguration config) // add a mapping between the open entity type and its dynamic property dictionary. _openTypes.Add(entityType, entity.DynamicPropertyDictionary); } + + if (entity.SupportsInstanceAnnotations) + { + // add a mapping between the entity type and its instance annotation dictionary. + _instanceAnnotableTypes.Add(entityType, entity.InstanceAnnotationsContainer); + } + edmType = entityType; } else @@ -551,7 +567,8 @@ public static EdmTypeMap GetTypesAndProperties(IEnumerable diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs index 89c029fc0c..92b3cf705a 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs @@ -19,7 +19,8 @@ public EdmTypeMap( Dictionary edmStructuredTypeQuerySettings, Dictionary enumMembers, Dictionary openTypes, - Dictionary propertyConfigurations) + Dictionary propertyConfigurations, + Dictionary instanceAnnotatableTypes ) { EdmTypes = edmTypes; EdmProperties = edmProperties; @@ -29,6 +30,7 @@ public EdmTypeMap( EnumMembers = enumMembers; OpenTypes = openTypes; EdmPropertyConfigurations = propertyConfigurations; + InstanceAnnotatableTypes = instanceAnnotatableTypes; } public Dictionary EdmTypes { get; private set; } @@ -46,5 +48,7 @@ public EdmTypeMap( public Dictionary EnumMembers { get; private set; } public Dictionary OpenTypes { get; private set; } + + public Dictionary InstanceAnnotatableTypes { get; private set; } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/IODataInstanceAnnotationContainer.cs b/src/Microsoft.AspNet.OData.Shared/Builder/IODataInstanceAnnotationContainer.cs new file mode 100644 index 0000000000..b6118ac630 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/IODataInstanceAnnotationContainer.cs @@ -0,0 +1,59 @@ +// 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.Text; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Interface to used as a Container for holding Instance Annotations, An default implementation is provided + /// Custoer can implement the interface and can have their own implementation. + /// + public interface IODataInstanceAnnotationContainer + { + /// + /// Method to Add an Instance Annotation to the CLR type + /// + /// Name of Annotation + /// Value of Annotation + void AddResourceAnnotation(string annotationName, object value); + + /// + /// Method to Add an Instance Annotation to a property + /// + /// Name of the property + /// Name of Annotation + /// Value of Annotation + void AddPropertyAnnotation(string propertyName, string annotationName, object value); + + /// + /// Get an Instance Annotation from CLR Type + /// + /// Name of Annotation + /// Get Annotation value for the given annotation + object GetResourceAnnotation(string annotationName); + + /// + /// Get an Instance Annotation from the Property + /// + /// Name of the Property + /// Name of the Annotation + /// Get Annotation value for the given annotation and property + object GetPropertyAnnotation(string propertyName, string annotationName); + + /// + /// Get All Annotations from CLR Type + /// + /// Dictionary of string(annotation name) and object value(annotation value) + IDictionary GetResourceAnnotations(); + + /// + /// Get all Annotations for a Property + /// + /// Name of Property + /// Dictionary of string(annotation name) and object value(annotation value) + IDictionary GetPropertyAnnotations(string propertyName); + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/InstanceAnnotationContainerAnnotation.cs b/src/Microsoft.AspNet.OData.Shared/Builder/InstanceAnnotationContainerAnnotation.cs new file mode 100644 index 0000000000..87638c87e5 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/InstanceAnnotationContainerAnnotation.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// This annotation indicates the mapping from a to a . + /// The is a type of IODataInstanceAnnotationContainer and the is the specific + /// property which is used to save/retrieve the instance annotations. + /// + internal class ODataInstanceAnnotationContainerAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The backing . + public ODataInstanceAnnotationContainerAnnotation(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + TypeHelper.ValidateAssignableFromForArgument(typeof(IODataInstanceAnnotationContainer), propertyInfo.PropertyType, "IODataInstanceAnnotationContainer"); + + PropertyInfo = propertyInfo; + } + + /// + /// Gets the which backs the instance annotations of the clr type/resource etc. + /// + public PropertyInfo PropertyInfo + { + get; + } + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs index 177685b581..78cbfa1890 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs @@ -557,6 +557,10 @@ private void MapStructuralType(StructuralTypeConfiguration structuralType) { structuralType.AddDynamicPropertyDictionary(property); } + else if (propertyKind == PropertyKind.InstanceAnnotations) + { + structuralType.AddInstanceAnnotationContainer(property); + } else { // don't add this property if the user has already added it. @@ -700,6 +704,16 @@ private PropertyKind GetPropertyType(PropertyInfo property, out bool isCollectio return PropertyKind.Dynamic; } + // IODataInstanceAnnotationContainer is used as a container to save/retrieve instance annotation properties for a CLR type. + // It is different from other collections (for example, IDictionary>) + if (typeof(IODataInstanceAnnotationContainer).IsAssignableFrom(property.PropertyType)) + { + mappedType = null; + isCollection = false; + + return PropertyKind.InstanceAnnotations; + } + PropertyKind propertyKind; if (TryGetPropertyTypeKind(property.PropertyType, out mappedType, out propertyKind)) { diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/ODataInstanceAnnotationContainer.cs b/src/Microsoft.AspNet.OData.Shared/Builder/ODataInstanceAnnotationContainer.cs new file mode 100644 index 0000000000..7c74c8057e --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/ODataInstanceAnnotationContainer.cs @@ -0,0 +1,166 @@ +// 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.Text; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + ///Intended as Default implementation for IODataInstanceAnnotationContainer + /// + public sealed class ODataInstanceAnnotationContainer : IODataInstanceAnnotationContainer + { + private IDictionary> instanceAnnotations; + + + /// + /// Initializes a new instance of the class. + /// + public ODataInstanceAnnotationContainer() + { + instanceAnnotations = new Dictionary>(); + } + + /// + /// Method to Add an Instance Annotation to the CLR type + /// + /// Name of Annotation + /// Value of Annotation + public void AddResourceAnnotation(string annotationName, object value) + { + SetInstanceAnnotation(string.Empty, annotationName, value); + } + + /// + /// Method to Add an Instance Annotation to a property + /// + /// Name of the property + /// Name of Annotation + /// Value of Annotation + public void AddPropertyAnnotation(string propertyName, string annotationName, object value) + { + if (string.IsNullOrEmpty(propertyName)) + { + throw Error.ArgumentNull("propertyName"); + } + + SetInstanceAnnotation(propertyName, annotationName, value); + } + + /// + /// Get an Instance Annotation from CLR Type + /// + /// Name of Annotation + /// Get Annotation value for the given annotation + public object GetResourceAnnotation(string annotationName) + { + return GetInstanceAnnotation(string.Empty, annotationName); + } + + /// + /// Get an Instance Annotation from the Property + /// + /// Name of the Property + /// Name of the Annotation + /// Get Annotation value for the given annotation and property + public object GetPropertyAnnotation(string propertyName, string annotationName) + { + if (string.IsNullOrEmpty(propertyName)) + { + throw Error.ArgumentNull("propertyName"); + } + + return GetInstanceAnnotation(propertyName, annotationName); + } + + /// + /// Get All Annotations from CLR Type + /// + /// Dictionary of string(annotation name) and object value(annotation value) + public IDictionary GetResourceAnnotations() + { + return GetAllInstanceAnnotations(string.Empty); + } + + /// + /// Get all Annotation for a Property + /// + /// Name of Property + /// Dictionary of string(annotation name) and object value(annotation value) + public IDictionary GetPropertyAnnotations(string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + { + throw Error.ArgumentNull("propertyName"); + } + + return GetAllInstanceAnnotations(propertyName); + } + + private void SetInstanceAnnotation(string propertyName, string annotationName, object value) + { + ValidateInstanceAnnotation(annotationName); + + IDictionary annotationDictionary; + if (!instanceAnnotations.TryGetValue(propertyName, out annotationDictionary)) + { + annotationDictionary = new Dictionary(); + instanceAnnotations.Add(propertyName, annotationDictionary); + } + + annotationDictionary[annotationName] = value; + } + + private object GetInstanceAnnotation(string propertyName, string annotationName) + { + if (string.IsNullOrEmpty(annotationName)) + { + throw Error.ArgumentNull("annotationName"); + } + + IDictionary annotationDictionary; + if (instanceAnnotations.TryGetValue(propertyName, out annotationDictionary)) + { + object annotationValue; + if (annotationDictionary.TryGetValue(annotationName, out annotationValue)) + { + return annotationValue; + } + } + + return null; + } + + private IDictionary GetAllInstanceAnnotations(string propertyName) + { + IDictionary annotationDictionary; + if (instanceAnnotations.TryGetValue(propertyName, out annotationDictionary)) + { + return annotationDictionary; + } + + return null; + } + + private static void ValidateInstanceAnnotation(string annotationName) + { + if (string.IsNullOrEmpty(annotationName)) + { + throw Error.ArgumentNull("annotationName"); + } + + if (annotationName[0] == '@' || annotationName[0] == '.' || annotationName[annotationName.Length-1] == '.') + { + throw new ArgumentException(SRResources.InstanceAnnotationNotContain, annotationName); + } + + if (!annotationName.Contains(".")) + { + throw new ArgumentException(SRResources.InstanceAnnotationShouldContain, annotationName); + } + } + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs index 5e666462c4..5fe6aeaad7 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs @@ -36,6 +36,12 @@ public enum PropertyKind /// /// Represents a dynamic property dictionary for an open type. /// - Dynamic = 5 + Dynamic = 5, + + /// + /// Represents an instance annotation for a CLR type. + /// + InstanceAnnotations = 6 } } + diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs index a9b6123315..81a8b32c37 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -21,6 +21,7 @@ public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration private string _namespace; private string _name; private PropertyInfo _dynamicPropertyDictionary; + private PropertyInfo _instanceAnnotationContainer; private StructuralTypeConfiguration _baseType; private bool _baseTypeConfigured; @@ -143,6 +144,22 @@ public PropertyInfo DynamicPropertyDictionary get { return _dynamicPropertyDictionary; } } + /// + /// Gets a value indicating whether this type has instance annotations or not. + /// + public bool SupportsInstanceAnnotations + { + get { return _instanceAnnotationContainer != null; } + } + + /// + /// Gets the CLR property info of the instance annotations dictionary on this structural type. + /// + public PropertyInfo InstanceAnnotationsContainer + { + get { return _instanceAnnotationContainer; } + } + /// /// Gets or sets a value indicating whether this type is abstract. /// @@ -491,6 +508,39 @@ public virtual void AddDynamicPropertyDictionary(PropertyInfo propertyInfo) _dynamicPropertyDictionary = propertyInfo; } + + /// + /// Adds the property info of the instanceannotation to this structural type. + /// + /// The property being added. + public virtual void AddInstanceAnnotationContainer(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + TypeHelper.ValidateAssignableFromForArgument(typeof(IODataInstanceAnnotationContainer), propertyInfo.PropertyType, "IODataInstanceAnnotationContainer"); + + if (!propertyInfo.DeclaringType.IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType); + } + + // Remove from the ignored properties + if (IgnoredProperties.Contains(propertyInfo)) + { + RemovedProperties.Remove(propertyInfo); + } + + if (_instanceAnnotationContainer != null) + { + throw Error.Argument("propertyInfo", SRResources.MoreThanOneAnnotationPropertyContainerFound, ClrType.Name); + } + + _instanceAnnotationContainer = propertyInfo; + } + /// /// Removes the given property. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs index 4ad57a656c..e56ecf369d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs @@ -359,6 +359,22 @@ public void HasDynamicProperties(Expression + /// Adds an InstanceAnnotation container property. + /// + /// A lambda expression representing the instance annotation container property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "More specific expression type is clearer")] + public void HasInstanceAnnotations(Expression> propertyExpression) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + + _configuration.AddInstanceAnnotationContainer(propertyInfo); + } + /// /// Configures a many relationship from this structural type. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs b/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs index 08220aade9..abdfb2c0f6 100644 --- a/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs +++ b/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs @@ -322,6 +322,14 @@ internal static string CannotCastFilter } } + internal static string CannotCreateInstanceForProperty + { + get + { + return ResourceManager.GetString("CannotCreateInstanceForProperty", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot define keys on type '{0}' deriving from '{1}'. The base type in the entity inheritance hierarchy already contains keys.. /// @@ -1521,6 +1529,127 @@ internal static string MoreThanOneDynamicPropertyContainerFound } } + /// + /// Looks up a localized string similar to Found more than one Annotation property container in type '{0}'. Each open type must have at most one Annotation property container.. + /// + internal static string MoreThanOneAnnotationPropertyContainerFound + { + get + { + return ResourceManager.GetString("MoreThanOneAnnotationPropertyContainerFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name of Annotation property '{0}' was already used as the declared property name of open type '{1}'.. + /// + internal static string AnnotationPropertyNameAlreadyUsedAsDeclaredPropertyName + { + get + { + return ResourceManager.GetString("AnnotationPropertyNameAlreadyUsedAsDeclaredPropertyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate Annotation property name '{0}' found in open type '{1}'. Each Annotation property name must be unique.. + /// + internal static string DuplicateAnnotationPropertyNameFound + { + get + { + return ResourceManager.GetString("DuplicateAnnotationPropertyNameFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Annotation dictionary property '{0}' of type '{1}' cannot be set. The Annotation property dictionary must have a setter.. + /// + internal static string CannotSetAnnotationPropertyDictionary + { + get + { + return ResourceManager.GetString("CannotSetAnnotationPropertyDictionary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance annotation for '{0}' with Edm type '{1}' cannot be serialized.. + /// + internal static string InstanceAnnotationCannotBeSerialized + { + get + { + return ResourceManager.GetString("InstanceAnnotationCannotBeSerialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance annotation for should not start with @ or #. + /// + internal static string InstanceAnnotationNotContain + { + get + { + return ResourceManager.GetString("InstanceAnnotationNotContain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance annotation for should contain '.'. + /// + internal static string InstanceAnnotationShouldContain + { + get + { + return ResourceManager.GetString("InstanceAnnotationShouldContain", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance annotation for should not be empty. + /// + internal static string InstanceAnnotationNotEmpty + { + get + { + return ResourceManager.GetString("InstanceAnnotationNotEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The instance annotation for property should not be empty. + /// + internal static string InstanceAnnotationPropertyNotEmpty + { + get + { + return ResourceManager.GetString("InstanceAnnotationPropertyNotEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of instance annotation '{1}' is not supported.. + /// + internal static string TypeOfInstanceAnnotationNotSupported + { + get + { + return ResourceManager.GetString("TypeOfInstanceAnnotationNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' is not supported as Annotation property annotation. Referenced property must be of type '{1}'.. + /// + internal static string InvalidPropertyInfoForAnnotationPropertyAnnotation + { + get + { + return ResourceManager.GetString("InvalidPropertyInfoForAnnotationPropertyAnnotation", resourceCulture); + } + } + /// /// Looks up a localized string similar to More than one Operation called '{0}' was found. Try using the other RemoveOperation override.. /// @@ -2346,6 +2475,18 @@ internal static string PropertyMustHavePublicGetterAndSetter } } + /// + /// Looks up a localized string similar to PropertyType ShouldBeOf Type + /// + internal static string PropertyTypeShouldBeOfType + { + get + { + return ResourceManager.GetString("PropertyTypeShouldBeOfType", resourceCulture); + } + } + + /// /// Looks up a localized string similar to The EDM instance of type '{0}' is missing the property '{1}'.. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx b/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx index 840996c0d7..9f76141ae7 100644 --- a/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx +++ b/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx @@ -781,6 +781,21 @@ Type '{0}' is not supported as dynamic property annotation. Referenced property must be of type '{1}'. + + Found more than one Annotation property container in type '{0}'. Each open type must have at most one Annotation property container. + + + The name of Annotation property '{0}' was already used as the declared property name of open type '{1}'. + + + Duplicate Annotation property name '{0}' found in open type '{1}'. Each Annotation property name must be unique. + + + The Annotation dictionary property '{0}' of type '{1}' cannot be set. The Annotation property dictionary must have a setter. + + + Type '{0}' is not supported as Annotation property annotation. Referenced property must be of type '{1}'. + Request URI '{0}' too short to contain OData path '{1}'. @@ -799,6 +814,12 @@ The type '{0}' of dynamic property '{1}' is not supported. + + The instance annotation for '{0}' with Edm type '{1}' cannot be serialized. + + + The type '{0}' of instance annotation '{1}' is not supported. + The entity type '{0}' cannot be configured as a complex type because the derived type '{1}' is already configured as an entity type. @@ -943,4 +964,22 @@ Cannot Get the Enum Clr member using '{0}'. + + Cannot Create an instance for the property '{0}'. + + + The Property should be of Type {0}. + + + Instance Annotation name cannot have a '@' as the first character and cannot have a '.' as first or last character. + + + Instance Annotation should have a '.' in it and should not be the first or last character, usually in a format Namespace.TermName + + + An Instance Annotation name cannot be null or empty. + + + InstanceAnnotation Property name cannot be null or empty. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs b/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs index 72147911b5..6a6294fd2a 100644 --- a/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs +++ b/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs @@ -69,6 +69,7 @@ public static IContainerBuilder AddDefaultWebApiServices(this IContainerBuilder // Serializers. builder.AddService(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton); diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs index 80b08f6393..0619a91ab8 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.OData; using Microsoft.OData.Edm; @@ -18,9 +19,9 @@ internal static class DeserializationHelpers { internal static void ApplyProperty(ODataProperty property, IEdmStructuredTypeReference resourceType, object resource, ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext) - { + { IEdmProperty edmProperty = resourceType.FindProperty(property.Name); - + bool isDynamicProperty = false; string propertyName = property.Name; if (edmProperty != null) @@ -57,6 +58,20 @@ internal static void ApplyProperty(ODataProperty property, IEdmStructuredTypeRef } } + internal static void ApplyInstanceAnnotations(object resource, IEdmStructuredTypeReference structuredType, ODataResource oDataResource, + ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext) + { + PropertyInfo propertyInfo = EdmLibHelpers.GetInstanceAnnotationsContainer(structuredType.StructuredDefinition(), readContext.Model); + if (propertyInfo == null) + { + return; + } + + IODataInstanceAnnotationContainer instanceAnnotationContainer = GetAnnotationContainer(propertyInfo, resource); + + SetInstanceAnnotations(oDataResource, instanceAnnotationContainer, deserializerProvider, readContext); + } + internal static void SetDynamicProperty(object resource, IEdmStructuredTypeReference resourceType, EdmTypeKind propertyKind, string propertyName, object propertyValue, IEdmTypeReference propertyType, IEdmModel model) @@ -206,7 +221,7 @@ internal static void SetDynamicProperty(object resource, string propertyName, ob { return; } - + IDictionary dynamicPropertyDictionary; object dynamicDictionaryObject = propertyInfo.GetValue(resource); if (dynamicDictionaryObject == null) @@ -235,6 +250,92 @@ internal static void SetDynamicProperty(object resource, string propertyName, ob } } + internal static void SetInstanceAnnotations(ODataResource oDataResource, IODataInstanceAnnotationContainer instanceAnnotationContainer, + ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext) + { + if(oDataResource.InstanceAnnotations != null) + { + foreach (ODataInstanceAnnotation annotation in oDataResource.InstanceAnnotations) + { + AddInstanceAnnotationToContainer(instanceAnnotationContainer, deserializerProvider, readContext, annotation,string.Empty); + } + } + + foreach(ODataProperty property in oDataResource.Properties) + { + if(property.InstanceAnnotations != null) + { + foreach (ODataInstanceAnnotation annotation in property.InstanceAnnotations) + { + AddInstanceAnnotationToContainer(instanceAnnotationContainer, deserializerProvider, readContext, annotation, property.Name); + } + } + } + } + + private static void AddInstanceAnnotationToContainer(IODataInstanceAnnotationContainer instanceAnnotationContainer, ODataDeserializerProvider deserializerProvider, + ODataDeserializerContext readContext, ODataInstanceAnnotation annotation, string propertyName) + { + IEdmTypeReference propertyType = null; + + object annotationValue = ConvertAnnotationValue(annotation.Value, ref propertyType, deserializerProvider, readContext); + + if (string.IsNullOrEmpty(propertyName)) + { + instanceAnnotationContainer.AddResourceAnnotation(annotation.Name, annotationValue); + } + else + { + instanceAnnotationContainer.AddPropertyAnnotation(propertyName,annotation.Name, annotationValue); + } + } + + public static IODataInstanceAnnotationContainer GetAnnotationContainer(PropertyInfo propertyInfo, object resource) + { + IDelta delta = resource as IDelta; + object value; + if (delta != null) + { + delta.TryGetPropertyValue(propertyInfo.Name, out value); + } + else + { + value = propertyInfo.GetValue(resource); + } + + IODataInstanceAnnotationContainer instanceAnnotationContainer = value as IODataInstanceAnnotationContainer; + + if (instanceAnnotationContainer == null) + { + try + { + if (propertyInfo.PropertyType == typeof(ODataInstanceAnnotationContainer) || propertyInfo.PropertyType == typeof(IODataInstanceAnnotationContainer)) + { + instanceAnnotationContainer = new ODataInstanceAnnotationContainer(); + } + else + { + instanceAnnotationContainer = Activator.CreateInstance(propertyInfo.PropertyType) as IODataInstanceAnnotationContainer; + } + + if(delta != null) + { + delta.TrySetPropertyValue(propertyInfo.Name, instanceAnnotationContainer); + } + else + { + propertyInfo.SetValue(resource, instanceAnnotationContainer); + } + } + catch(Exception ex) + { + throw new ODataException(Error.Format(SRResources.CannotCreateInstanceForProperty, propertyInfo.Name), ex); + } + } + + return instanceAnnotationContainer; + } + internal static object ConvertValue(object oDataValue, ref IEdmTypeReference propertyType, ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext, out EdmTypeKind typeKind) { @@ -276,6 +377,69 @@ internal static object ConvertValue(object oDataValue, ref IEdmTypeReference pro return oDataValue; } + internal static object ConvertAnnotationValue(object oDataValue, ref IEdmTypeReference propertyType, ODataDeserializerProvider deserializerProvider, + ODataDeserializerContext readContext) + { + if (oDataValue == null) + { + return null; + } + + ODataEnumValue enumValue = oDataValue as ODataEnumValue; + if (enumValue != null) + { + return ConvertEnumValue(enumValue, ref propertyType, deserializerProvider, readContext); + } + + ODataCollectionValue collection = oDataValue as ODataCollectionValue; + if (collection != null) + { + return ConvertCollectionValue(collection, ref propertyType, deserializerProvider, readContext); + } + + ODataUntypedValue untypedValue = oDataValue as ODataUntypedValue; + if (untypedValue != null) + { + Contract.Assert(!String.IsNullOrEmpty(untypedValue.RawValue)); + + if (untypedValue.RawValue.StartsWith("[", StringComparison.Ordinal) || + untypedValue.RawValue.StartsWith("{", StringComparison.Ordinal)) + { + throw new ODataException(Error.Format(SRResources.InvalidODataUntypedValue, untypedValue.RawValue)); + } + + oDataValue = ConvertPrimitiveValue(untypedValue.RawValue); + } + + ODataResourceValue annotationVal = oDataValue as ODataResourceValue; + if (annotationVal != null) + { + var annotationType = readContext.Model.FindDeclaredType(annotationVal.TypeName).ToEdmTypeReference(true) as IEdmStructuredTypeReference; + + ODataResourceDeserializer deserializer = new ODataResourceDeserializer(deserializerProvider); + + object resource = deserializer.CreateResourceInstance(annotationType, readContext); + + if (resource != null) + { + foreach (var prop in annotationVal.Properties) + { + deserializer.ApplyStructuralProperty(resource, prop, annotationType, readContext); + } + } + + return resource; + } + + ODataPrimitiveValue primitiveValue = oDataValue as ODataPrimitiveValue; + if (primitiveValue != null) + { + return EdmPrimitiveHelpers.ConvertPrimitiveValue(primitiveValue.Value, primitiveValue.Value.GetType()); + } + + return oDataValue; + } + internal static Type GetPropertyType(object resource, string propertyName) { Contract.Assert(resource != null); diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs index 35842ca938..102785c4bd 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -384,6 +384,24 @@ public virtual void ApplyStructuralProperties(object resource, ODataResourceWrap } } + /// + /// Deserializes the instance annotations from into . + /// + /// The object into which the annotations should be read. + /// The resource object containing the annotations. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyInstanceAnnotations(object resource, ODataResourceWrapper resourceWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (resourceWrapper == null) + { + throw Error.ArgumentNull("resourceWrapper"); + } + + DeserializationHelpers.ApplyInstanceAnnotations(resource, structuredType, resourceWrapper.Resource,DeserializerProvider, readContext); + } + /// /// Deserializes the given into . /// @@ -393,7 +411,7 @@ public virtual void ApplyStructuralProperties(object resource, ODataResourceWrap /// The deserializer context. public virtual void ApplyStructuralProperty(object resource, ODataProperty structuralProperty, IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) - { + { if (resource == null) { throw Error.ArgumentNull("resource"); @@ -412,6 +430,7 @@ private void ApplyResourceProperties(object resource, ODataResourceWrapper resou { ApplyStructuralProperties(resource, resourceWrapper, structuredType, readContext); ApplyNestedProperties(resource, resourceWrapper, structuredType, readContext); + ApplyInstanceAnnotations(resource, resourceWrapper, structuredType, readContext); } private void ApplyResourceInNestedProperty(IEdmProperty nestedProperty, object resource, diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs index 855c47f5ae..7a907d1887 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs @@ -735,7 +735,7 @@ public static PropertyInfo GetDynamicPropertyDictionary(IEdmStructuredType edmTy if (edmModel == null) { throw Error.ArgumentNull("edmModel"); - } + } DynamicPropertyDictionaryAnnotation annotation = edmModel.GetAnnotationValue(edmType); @@ -747,6 +747,28 @@ public static PropertyInfo GetDynamicPropertyDictionary(IEdmStructuredType edmTy return null; } + public static PropertyInfo GetInstanceAnnotationsContainer(IEdmStructuredType edmType, IEdmModel edmModel) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + ODataInstanceAnnotationContainerAnnotation annotation = + edmModel.GetAnnotationValue(edmType); + if (annotation != null) + { + return annotation.PropertyInfo; + } + + return null; + } + public static bool HasLength(EdmPrimitiveTypeKind primitiveTypeKind) { return (primitiveTypeKind == EdmPrimitiveTypeKind.Binary || diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs index 62a55f25d6..ef9a5c3909 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs @@ -71,6 +71,20 @@ internal static object ReadFromStream( oDataReaderSettings.Validations = oDataReaderSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; IODataRequestMessage oDataRequestMessage = getODataRequestMessage(); + + string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(internalRequest.Headers); + string annotationFilter = null; + if (!String.IsNullOrEmpty(preferHeader)) + { + oDataRequestMessage.SetHeader(RequestPreferenceHelpers.PreferHeaderName, preferHeader); + annotationFilter = oDataRequestMessage.PreferHeader().AnnotationFilter; + } + + if (annotationFilter != null) + { + oDataReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter(annotationFilter); + } + ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, oDataReaderSettings, model); registerForDisposeAction(oDataMessageReader); diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs index 84e10e280c..49a58f9a2c 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNet.OData.Formatter.Serialization /// public class ODataCollectionSerializer : ODataEdmTypeSerializer { + bool isForAnnotations; + /// /// Initializes a new instance of the class. /// @@ -27,6 +29,17 @@ public ODataCollectionSerializer(ODataSerializerProvider serializerProvider) { } + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider to use to serialize nested objects. + /// bool value to check if its for instance annotations + public ODataCollectionSerializer(ODataSerializerProvider serializerProvider, bool isForAnnotations) + : base(ODataPayloadKind.Collection, serializerProvider) + { + this.isForAnnotations = isForAnnotations; + } + /// public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) @@ -161,11 +174,22 @@ public virtual ODataCollectionValue CreateODataCollectionValue(IEnumerable enume IEdmTypeReference actualType = writeContext.GetEdmType(item, item.GetType()); Contract.Assert(actualType != null); - itemSerializer = itemSerializer ?? SerializerProvider.GetEdmTypeSerializer(actualType); if (itemSerializer == null) { - throw new SerializationException( - Error.Format(SRResources.TypeCannotBeSerialized, actualType.FullName())); + //For instance annotations we need to use complex serializer for complex type and not ODataResourceSerializer + if (isForAnnotations && actualType.IsStructured()) + { + itemSerializer = new ODataResourceValueSerializer(SerializerProvider); + } + else + { + itemSerializer = SerializerProvider.GetEdmTypeSerializer(actualType); + if (itemSerializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, actualType.FullName())); + } + } } // ODataCollectionWriter expects the individual elements in the collection to be the underlying diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs index 3c4fe97629..bbceaf6ac3 100644 --- a/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs @@ -14,6 +14,7 @@ using Microsoft.AspNet.OData.Query.Expressions; using Microsoft.OData; using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; using Microsoft.OData.UriParser; namespace Microsoft.AspNet.OData.Formatter.Serialization @@ -442,6 +443,9 @@ public virtual ODataResource CreateResource(SelectExpandNode selectExpandNode, R // Try to add the dynamic properties if the structural type is open. AppendDynamicProperties(resource, selectExpandNode, resourceContext); + // Try to add instance annotations + AppendInstanceAnnotations(resource, resourceContext); + if (selectExpandNode.SelectedActions != null) { IEnumerable actions = CreateODataActions(selectExpandNode.SelectedActions, resourceContext); @@ -519,7 +523,7 @@ public virtual void AppendDynamicProperties(ODataResource resource, SelectExpand { Contract.Assert(resource != null); Contract.Assert(selectExpandNode != null); - Contract.Assert(resourceContext != null); + Contract.Assert(resourceContext != null); if (!resourceContext.StructuredType.IsOpen || // non-open type (!selectExpandNode.SelectAllDynamicProperties && selectExpandNode.SelectedDynamicProperties == null)) @@ -634,6 +638,111 @@ public virtual void AppendDynamicProperties(ODataResource resource, SelectExpand } } + /// + /// Method to append InstanceAnnotations to the ODataResource and Property. + /// Instance annotations are annotations for a resource or a property and couldb be of contain a primitive, comple , enum or collection type + /// These will be saved in to an Instance annotation dictionary + /// + /// The describing the resource, which is being annotated. + /// The context for the resource instance, which is being annotated. + public virtual void AppendInstanceAnnotations(ODataResource resource, ResourceContext resourceContext) + { + IEdmStructuredType structuredType = resourceContext.StructuredType; + IEdmStructuredObject structuredObject = resourceContext.EdmObject; + PropertyInfo instanceAnnotationInfo = EdmLibHelpers.GetInstanceAnnotationsContainer(structuredType, + resourceContext.EdmModel); + + object value; + + if (instanceAnnotationInfo == null || structuredObject == null || + !structuredObject.TryGetPropertyValue(instanceAnnotationInfo.Name, out value) || value == null) + { + return; + } + + IODataInstanceAnnotationContainer instanceAnnotationContainer = value as IODataInstanceAnnotationContainer; + + if (instanceAnnotationContainer != null) + { + IDictionary clrAnnotations = instanceAnnotationContainer.GetResourceAnnotations(); + + if (clrAnnotations != null) + { + foreach (KeyValuePair annotation in clrAnnotations) + { + AddODataAnnotations(resource.InstanceAnnotations, resourceContext, annotation); + } + } + + foreach(ODataProperty property in resource.Properties) + { + string propertyName = property.Name; + + if (property.InstanceAnnotations == null) + { + property.InstanceAnnotations = new List(); + } + + IDictionary propertyAnnotations = instanceAnnotationContainer.GetPropertyAnnotations(propertyName); + + if (propertyAnnotations != null) + { + foreach (KeyValuePair annotation in propertyAnnotations) + { + AddODataAnnotations(property.InstanceAnnotations, resourceContext, annotation); + } + } + } + } + } + + private void AddODataAnnotations(ICollection InstanceAnnotations, ResourceContext resourceContext, KeyValuePair annotation) + { + ODataValue annotationValue = null; + + if (annotation.Value != null) + { + IEdmTypeReference edmTypeReference = resourceContext.SerializerContext.GetEdmType(annotation.Value, + annotation.Value.GetType()); + + ODataEdmTypeSerializer edmTypeSerializer = GetEdmTypeSerializer(edmTypeReference); + + if (edmTypeSerializer != null) + { + annotationValue = edmTypeSerializer.CreateODataValue(annotation.Value, edmTypeReference, resourceContext.SerializerContext); + } + } + else + { + annotationValue = new ODataNullValue(); + } + + if (annotationValue != null) + { + InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.Key, annotationValue)); + } + } + + private ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmTypeReference) + { + ODataEdmTypeSerializer edmTypeSerializer; + + if (edmTypeReference.IsCollection()) + { + edmTypeSerializer = new ODataCollectionSerializer(SerializerProvider, true); + } + else if (edmTypeReference.IsStructured()) + { + edmTypeSerializer = new ODataResourceValueSerializer(SerializerProvider); + } + else + { + edmTypeSerializer = SerializerProvider.GetEdmTypeSerializer(edmTypeReference); + } + + return edmTypeSerializer; + } + /// /// Creates the ETag for the given entity. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceValueSerializer.cs b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceValueSerializer.cs new file mode 100644 index 0000000000..e5948f2f35 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceValueSerializer.cs @@ -0,0 +1,211 @@ +// 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.Contracts; +using System.Reflection; +using System.Xml.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing 's. + /// + public class ODataResourceValueSerializer : ODataEdmTypeSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataResourceValueSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.Resource, serializerProvider) + { + if (serializerProvider == null) + { + throw Error.ArgumentNull("serializerProvider"); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The kind of OData payload that this serializer generates. + /// The to use to write inner objects. + protected ODataResourceValueSerializer(ODataPayloadKind payloadKind, ODataSerializerProvider serializerProvider) + : base(payloadKind, serializerProvider) + { + if (serializerProvider == null) + { + throw Error.ArgumentNull("serializerProvider"); + } + + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (writeContext.RootElementName == null) + { + throw Error.Argument("writeContext", SRResources.RootElementNameMissing, typeof(ODataSerializerContext).Name); + } + + IEdmTypeReference edmType = writeContext.GetEdmType(graph, type); + Contract.Assert(edmType != null); + + messageWriter.WriteProperty(CreateProperty(graph, edmType, writeContext.RootElementName, writeContext)); + } + + /// + public sealed override ODataValue CreateODataValue(object graph, IEdmTypeReference expectedType, ODataSerializerContext writeContext) + { + if (!expectedType.IsStructured()) + { + throw Error.InvalidOperation(SRResources.CannotWriteType, typeof(ODataResourceValueSerializer), expectedType.FullName()); + } + + if (graph == null) + { + return new ODataNullValue(); + } + + ODataResourceValue value = CreateODataResourceValue(graph, expectedType as IEdmStructuredTypeReference, writeContext); + if (value == null) + { + return new ODataNullValue(); + } + + return value; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "edmTypeSerializer")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] + private ODataResourceValue CreateODataResourceValue(object graph, IEdmStructuredTypeReference expectedType, ODataSerializerContext writeContext) + { + List properties = new List(); + ODataResourceValue resourceValue = new ODataResourceValue { TypeName = writeContext.GetEdmType(graph, graph.GetType()).FullName() }; + + IDelta delta = graph as IDelta; + if (delta != null) + { + foreach (string propertyName in delta.GetChangedPropertyNames()) + { + SetDeltaPropertyValue(writeContext, properties, delta, propertyName); + } + + foreach (string propertyName in delta.GetUnchangedPropertyNames()) + { + SetDeltaPropertyValue(writeContext, properties, delta, propertyName); + } + } + else + { + HashSet structuralPropertyNames = new HashSet(); + + foreach(IEdmStructuralProperty structuralProperty in expectedType.DeclaredStructuralProperties()) + { + structuralPropertyNames.Add(structuralProperty.Name); + } + + foreach (PropertyInfo property in graph.GetType().GetProperties()) + { + if (structuralPropertyNames.Contains(property.Name)) + { + object propertyValue = property.GetValue(graph); + IEdmStructuredTypeReference expectedPropType = null; + + if (propertyValue == null) + { + expectedPropType = writeContext.GetEdmType(propertyValue, property.PropertyType) as IEdmStructuredTypeReference; + } + + SetPropertyValue(writeContext, properties, expectedPropType, property.Name, propertyValue); + } + } + } + + resourceValue.Properties = properties; + + return resourceValue; + } + + private void SetDeltaPropertyValue(ODataSerializerContext writeContext, List properties, IDelta delta, string propertyName) + { + object propertyValue; + + if (delta.TryGetPropertyValue(propertyName, out propertyValue)) + { + Type propertyType; + IEdmStructuredTypeReference expectedPropType = null; + + if (propertyValue == null) + { + // We need expected property type only if property value is null, else it will get from the value + if (delta.TryGetPropertyType(propertyName, out propertyType)) + { + expectedPropType = writeContext.GetEdmType(propertyValue, propertyType) as IEdmStructuredTypeReference; + } + } + + SetPropertyValue(writeContext, properties, expectedPropType, propertyName, propertyValue); + } + } + + private void SetPropertyValue(ODataSerializerContext writeContext, List properties, IEdmStructuredTypeReference expectedType, string propertyName, object propertyValue) + { + if (propertyValue == null && expectedType == null) + { + properties.Add(new ODataProperty { Name = propertyName, Value = new ODataNullValue() }); + } + else + { + IEdmTypeReference edmTypeReference; + ODataEdmTypeSerializer edmTypeSerializer = null; + + edmTypeReference = propertyValue == null ? expectedType : writeContext.GetEdmType(propertyValue, + propertyValue.GetType()); + + if (edmTypeReference != null) + { + edmTypeSerializer = GetResourceValueEdmTypeSerializer(edmTypeReference); + } + + if (edmTypeSerializer != null) + { + ODataValue odataValue = edmTypeSerializer.CreateODataValue(propertyValue, edmTypeReference, writeContext); + properties.Add(new ODataProperty { Name = propertyName, Value = odataValue }); + } + } + } + + private ODataEdmTypeSerializer GetResourceValueEdmTypeSerializer(IEdmTypeReference edmTypeReference) + { + ODataEdmTypeSerializer edmTypeSerializer; + + if (edmTypeReference.IsCollection()) + { + edmTypeSerializer = new ODataCollectionSerializer(SerializerProvider, true); + } + else if (edmTypeReference.IsStructured()) + { + edmTypeSerializer = new ODataResourceValueSerializer(SerializerProvider); + } + else + { + edmTypeSerializer = SerializerProvider.GetEdmTypeSerializer(edmTypeReference); + } + + return edmTypeSerializer; + } + } +} 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 d45c4131c0..6b48eccf27 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -17,7 +17,10 @@ + + + @@ -66,6 +69,7 @@ + diff --git a/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs b/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs index 822451cc71..20e25e9981 100644 --- a/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs +++ b/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs @@ -466,6 +466,15 @@ internal static Type GetTaskInnerTypeOrSelf(Type type) return type; } + internal static void ValidateAssignableFromForArgument(Type expectedType, Type type, string customTypeDescription = null) + { + if (!expectedType.IsAssignableFrom(type)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyTypeShouldBeOfType, + customTypeDescription ?? expectedType.FullName); + } + } + private static Type GetInnerGenericType(Type interfaceType) { // Getting the type T definition if the returning type implements IEnumerable diff --git a/src/Microsoft.AspNet.OData/GlobalSuppressions.cs b/src/Microsoft.AspNet.OData/GlobalSuppressions.cs index 62c9da41b0..30c5653b20 100644 --- a/src/Microsoft.AspNet.OData/GlobalSuppressions.cs +++ b/src/Microsoft.AspNet.OData/GlobalSuppressions.cs @@ -57,4 +57,6 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.DerivedTypeConstraintConfiguration.#Add`1()")] [assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.DerivedTypeConstraintConfiguration.#AddConstraint`1()")] [assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.ActionConfiguration.#HasDerivedTypeConstraintForReturnType`1()")] -[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.FunctionConfiguration.#HasDerivedTypeConstraintForReturnType`1()")] \ No newline at end of file +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.FunctionConfiguration.#HasDerivedTypeConstraintForReturnType`1()")] +[assembly: SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.IODataInstanceAnnotationContainer.#GetAllResourceAnnotations()")] +[assembly: SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.IODataInstanceAnnotationContainer.#GetResourceAnnotations()")] diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationController.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationController.cs new file mode 100644 index 0000000000..92f1a994c5 --- /dev/null +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationController.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Test.E2E.AspNet.OData.Common.Controllers; +using Xunit; + +namespace Microsoft.Test.E2E.AspNet.OData.InstanceAnnotations +{ + public class EmployeesController : TestODataController + { + public EmployeesController() + { + if (null == Employees) + { + InitEmployees(); + } + } + + /// + /// static so that the data is shared among requests. + /// + private static IList Employees = null; + + private void InitEmployees() + { + Employees = new List + { + new Employee() + { + ID=1, + Name="Name1", + SkillSet=new List{Skill.CSharp,Skill.Sql}, + Gender=Gender.Female, + AccessLevel=AccessLevel.Execute, + FavoriteSports=new FavoriteSports() + { + LikeMost=Sport.Pingpong, + Like=new List{Sport.Pingpong,Sport.Basketball} + } + }, + new Employee() + { + ID=2,Name="Name2", + SkillSet=new List(), + Gender=Gender.Female, + AccessLevel=AccessLevel.Read, + FavoriteSports=new FavoriteSports() + { + LikeMost=Sport.Pingpong, + Like=new List{Sport.Pingpong,Sport.Basketball} + } + }, + new Employee(){ + ID=3,Name="Name3", + SkillSet=new List{Skill.Web,Skill.Sql}, + Gender=Gender.Female, + AccessLevel=AccessLevel.Read|AccessLevel.Write, + FavoriteSports=new FavoriteSports() + { + LikeMost=Sport.Pingpong|Sport.Basketball, + Like=new List{Sport.Pingpong,Sport.Basketball} + } + }, + }; + + var instanceAnnot = new ODataInstanceAnnotationContainer(); + instanceAnnot.AddResourceAnnotation("NS.Test2", 2); + + Employees[1].InstanceAnnotations = instanceAnnot; + } + + [EnableQuery(PageSize = 10, MaxExpansionDepth = 5)] + public ITestActionResult Get() + { + return Ok(Employees.AsQueryable()); + } + + public ITestActionResult Get(int key) + { + return Ok(Employees.SingleOrDefault(e => e.ID == key)); + } + + public ITestActionResult GetAccessLevelFromEmployee(int key) + { + return Ok(Employees.SingleOrDefault(e => e.ID == key).AccessLevel); + } + + public ITestActionResult GetNameFromEmployee(int key) + { + return Ok(Employees.SingleOrDefault(e => e.ID == key).Name); + } + + [EnableQuery] + public ITestActionResult GetSkillSetFromEmployee(int key) + { + return Ok(Employees.SingleOrDefault(e => e.ID == key).SkillSet); + } + + [EnableQuery] + public ITestActionResult GetFavoriteSportsFromEmployee(int key) + { + var employee = Employees.SingleOrDefault(e => e.ID == key); + return Ok(employee.FavoriteSports); + } + + [HttpGet] + [ODataRoute("Employees({key})/FavoriteSports/LikeMost")] + public ITestActionResult GetFavoriteSportLikeMost(int key) + { + var firstOrDefault = Employees.FirstOrDefault(e => e.ID == key); + return Ok(firstOrDefault.FavoriteSports.LikeMost); + } + + public ITestActionResult Post([FromBody]Employee employee) + { + var instanceAnnotations = employee.InstanceAnnotations; + VerifyInstanceAnnotations(employee.Name, instanceAnnotations); + + employee.ID = Employees.Count + 1; + Employees.Add(employee); + + return Created(employee); + } + + [ODataRoute("Employees({key})/FavoriteSports/LikeMost")] + public ITestActionResult PostToSkillSet(int key, [FromBody]Skill newSkill) + { + Employee employee = Employees.FirstOrDefault(e => e.ID == key); + if (employee == null) + { + return NotFound(); + } + employee.SkillSet.Add(newSkill); + return Updated(employee.SkillSet); + } + + public ITestActionResult Put(int key, [FromBody]Employee employee) + { + employee.ID = key; + Employee originalEmployee = Employees.SingleOrDefault(c => c.ID == key); + + if (originalEmployee == null) + { + Employees.Add(employee); + + return Created(employee); + } + + Employees.Remove(originalEmployee); + Employees.Add(employee); + return Ok(employee); + } + + public ITestActionResult Patch(int key, [FromBody]Delta delta) + { + Employee originalEmployee = Employees.SingleOrDefault(c => c.ID == key); + + if (originalEmployee == null) + { + Employee temp = new Employee(); + delta.Patch(temp); + Employees.Add(temp); + return Created(temp); + } + + delta.Patch(originalEmployee); + return Ok(delta); + } + + public ITestActionResult Delete(int key) + { + IEnumerable appliedEmployees = Employees.Where(c => c.ID == key); + + if (appliedEmployees.Count() == 0) + { + return BadRequest(string.Format("The entry with ID {0} doesn't exist", key)); + } + + Employee employee = appliedEmployees.Single(); + Employees.Remove(employee); + return this.StatusCode(HttpStatusCode.NoContent); + } + + [HttpPost] + public ITestActionResult AddSkill([FromODataUri] int key, [FromBody]ODataActionParameters parameters) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + Skill skill = (Skill)parameters["skill"]; + + Employee employee = Employees.FirstOrDefault(e => e.ID == key); + if (!employee.SkillSet.Contains(skill)) + { + employee.SkillSet.Add(skill); + } + + return Ok(employee.SkillSet); + } + + [HttpPost] + [ODataRoute("ResetDataSource")] + public ITestActionResult ResetDataSource() + { + this.InitEmployees(); + return this.StatusCode(HttpStatusCode.NoContent); + } + + [HttpPost] + [ODataRoute("SetAccessLevel")] + public ITestActionResult SetAccessLevel([FromBody]ODataActionParameters parameters) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + int ID = (int)parameters["ID"]; + AccessLevel accessLevel = (AccessLevel)parameters["accessLevel"]; + Employee employee = Employees.FirstOrDefault(e => e.ID == ID); + employee.AccessLevel = accessLevel; + return Ok(employee.AccessLevel); + } + + [HttpGet] + public ITestActionResult GetAccessLevel([FromODataUri] int key) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + Employee employee = Employees.FirstOrDefault(e => e.ID == key); + + return Ok(employee.AccessLevel); + } + + [HttpGet] + [ODataRoute("HasAccessLevel(ID={id},AccessLevel={accessLevel})")] + public ITestActionResult HasAccessLevel([FromODataUri] int id, [FromODataUri] AccessLevel accessLevel) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + Employee employee = Employees.FirstOrDefault(e => e.ID == id); + var result = employee.AccessLevel.HasFlag(accessLevel); + return Ok(result); + } + + private void VerifyInstanceAnnotations(string name, IODataInstanceAnnotationContainer instanceAnnotationContainer) + { + switch (name) + { + case "Name1": + Assert.Equal(1, instanceAnnotationContainer.GetResourceAnnotation("NS.Test")); + break; + case "Name2": + Assert.Equal(100, instanceAnnotationContainer.GetPropertyAnnotation("Gender","NS.TestGender")); + break; + case "Name3": + Assert.Equal(1, instanceAnnotationContainer.GetResourceAnnotation("NS.Test")); + Assert.Equal(100, instanceAnnotationContainer.GetPropertyAnnotation("Gender", "NS.TestGender")); + break; + case "Name4": + Assert.Equal(100, instanceAnnotationContainer.GetResourceAnnotation("NS.Test1")); + Assert.Equal("Testing", instanceAnnotationContainer.GetResourceAnnotation("NS.Test2")); + Assert.Equal(500, instanceAnnotationContainer.GetPropertyAnnotation("Gender", "NS.TestGender")); + Assert.Equal("TestName1", instanceAnnotationContainer.GetPropertyAnnotation("Name", "NS.TestName")); + break; + default: + break; + } + } + } +} diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationDataModel.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationDataModel.cs new file mode 100644 index 0000000000..9eaaa27c67 --- /dev/null +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationDataModel.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Builder; +using System; +using System.Collections.Generic; + +namespace Microsoft.Test.E2E.AspNet.OData.InstanceAnnotations +{ + public class Employee + { + public int ID { get; set; } + public String Name { get; set; } + public List SkillSet { get; set; } + public Gender Gender { get; set; } + public AccessLevel AccessLevel { get; set; } + public FavoriteSports FavoriteSports { get; set; } + public ODataInstanceAnnotationContainer InstanceAnnotations { get; set; } + } + + [Flags] + public enum AccessLevel + { + Read = 1, + Write = 2, + Execute = 4 + } + + public enum Gender + { + Male = 1, + Female = 2 + } + + public enum Skill + { + CSharp, + Sql, + Web, + } + + public enum Sport + { + Pingpong, + Basketball + } + + public class FavoriteSports + { + public Sport LikeMost { get; set; } + public List Like { get; set; } + } +} diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationEdmModel.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationEdmModel.cs new file mode 100644 index 0000000000..eebb7327df --- /dev/null +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationEdmModel.cs @@ -0,0 +1,111 @@ +// 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 Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Builder; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.Test.E2E.AspNet.OData.Common; +using Microsoft.Test.E2E.AspNet.OData.Common.Execution; + +namespace Microsoft.Test.E2E.AspNet.OData.InstanceAnnotations +{ + internal class AnnotationEdmModel + { + public static IEdmModel GetExplicitModel(WebRouteConfiguration configuration) + { + ODataModelBuilder builder = new ODataModelBuilder(); + var employee = builder.EntityType(); + employee.HasKey(c => c.ID); + employee.Property(c => c.Name); + employee.CollectionProperty(c => c.SkillSet); + employee.EnumProperty(c => c.Gender); + employee.EnumProperty(c => c.AccessLevel); + employee.ComplexProperty(c => c.FavoriteSports); + employee.HasInstanceAnnotations(c => c.InstanceAnnotations); + + var skill = builder.EnumType(); + skill.Member(Skill.CSharp); + skill.Member(Skill.Sql); + skill.Member(Skill.Web); + + var gender = builder.EnumType(); + gender.Member(Gender.Female); + gender.Member(Gender.Male); + + var accessLevel = builder.EnumType(); + accessLevel.Member(AccessLevel.Execute); + accessLevel.Member(AccessLevel.Read); + accessLevel.Member(AccessLevel.Write); + + var favoriteSports = builder.ComplexType(); + favoriteSports.EnumProperty(f => f.LikeMost); + favoriteSports.CollectionProperty(f => f.Like); + + var sport = builder.EnumType(); + sport.Member(Sport.Basketball); + sport.Member(Sport.Pingpong); + + AddBoundActionsAndFunctions(employee); + AddUnboundActionsAndFunctions(builder); + + EntitySetConfiguration employees = builder.EntitySet("Employees"); + employees.HasEditLink(link, true); + employees.HasIdLink(link, true); + builder.Namespace = "NS"; + return builder.GetEdmModel(); + } + + public static IEdmModel GetConventionModel(WebRouteConfiguration configuration) + { + ODataConventionModelBuilder builder = configuration.CreateConventionModelBuilder(); + EntitySetConfiguration employees = builder.EntitySet("Employees"); + EntityTypeConfiguration employee = employees.EntityType; + + AddBoundActionsAndFunctions(employee); + AddUnboundActionsAndFunctions(builder); + + builder.Namespace = "NS"; + + var edmModel = builder.GetEdmModel(); + return edmModel; + } + + private static void AddBoundActionsAndFunctions(EntityTypeConfiguration employee) + { + var actionConfiguration = employee.Action("AddSkill"); + actionConfiguration.Parameter("skill"); + actionConfiguration.ReturnsCollection(); + + var functionConfiguration = employee.Function("GetAccessLevel"); + functionConfiguration.Returns(); + } + + private static void AddUnboundActionsAndFunctions(ODataModelBuilder odataModelBuilder) + { + var actionConfiguration = odataModelBuilder.Action("SetAccessLevel"); + actionConfiguration.Parameter("ID"); + actionConfiguration.Parameter("accessLevel"); + actionConfiguration.Returns(); + + var functionConfiguration = odataModelBuilder.Function("HasAccessLevel"); + functionConfiguration.Parameter("ID"); + functionConfiguration.Parameter("AccessLevel"); + functionConfiguration.Returns(); + + var actionConfiguration2 = odataModelBuilder.Action("ResetDataSource"); + } + + private static Func link = entityContext => + { + object id; + entityContext.EdmObject.TryGetPropertyValue("ID", out id); + string uri = ResourceContextHelper.CreateODataLink(entityContext, + new EntitySetSegment(entityContext.NavigationSource as IEdmEntitySet), + new KeySegment(new[] { new KeyValuePair("ID", id) }, entityContext.StructuredType as IEdmEntityType, null)); + return new Uri(uri); + }; + } +} diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationTest.cs b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationTest.cs new file mode 100644 index 0000000000..2ee6692369 --- /dev/null +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/InstanceAnnotations/AnnotationTest.cs @@ -0,0 +1,242 @@ +// 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.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.Test.E2E.AspNet.OData.Common.Execution; +using Microsoft.Test.E2E.AspNet.OData.Common.Extensions; +using Microsoft.Test.E2E.AspNet.OData.ModelBuilder; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Test.E2E.AspNet.OData.InstanceAnnotations +{ + public class AnnotationTest : WebHostTestBase + { + public AnnotationTest(WebHostTestFixture fixture) + :base(fixture) + { + } + + protected override void UpdateConfiguration(WebRouteConfiguration configuration) + { + var controllers = new[] { typeof(EmployeesController), typeof(MetadataController) }; + configuration.AddControllers(controllers); + + configuration.Routes.Clear(); + configuration.Count().Filter().OrderBy().Expand().MaxTop(null).Select(); + configuration.MapODataServiceRoute("convention", "convention", AnnotationEdmModel.GetConventionModel(configuration)); + configuration.MapODataServiceRoute("explicit", "explicit", AnnotationEdmModel.GetExplicitModel(configuration), new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault()); + configuration.EnsureInitialized(); + } + + #region InstanceAnnotation + + + + [Theory] + [InlineData("application/json;odata.metadata=full","convention")] + [InlineData("application/json;odata.metadata=minimal", "convention")] + [InlineData("application/json;odata.metadata=none", "convention")] + [InlineData("application/json;odata.metadata=full", "explicit")] + [InlineData("application/json;odata.metadata=minimal", "explicit")] + [InlineData("application/json;odata.metadata=none", "explicit")] + public async Task InstanceAnnotationOnTypeTest(string format, string mode) + { + await ResetDatasource(); + string postUri = this.BaseAddress + string.Format("/{0}/Employees?$format={1}", mode, format); + + var postContent = JObject.Parse(@"{""ID"":1, + ""Name"":""Name1"", + ""SkillSet"":[""Sql""], + ""Gender"":""Female"", + ""AccessLevel"":""Read,Write"", + ""FavoriteSports"":{ + ""LikeMost"":""Pingpong"", + ""Like"":[""Pingpong"",""Basketball""] + }, + ""@NS.Test"":1}"); + + Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); + + using (HttpResponseMessage response = await this.Client.PostAsJsonAsync(postUri, postContent)) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + var json = await response.Content.ReadAsObject(); + VerifyInstanceAnnotations("Name1", json.ToString()); + } + + } + + [Theory] + [InlineData("application/json;odata.metadata=full", "convention")] + [InlineData("application/json;odata.metadata=minimal", "convention")] + [InlineData("application/json;odata.metadata=none", "convention")] + [InlineData("application/json;odata.metadata=full", "explicit")] + [InlineData("application/json;odata.metadata=minimal", "explicit")] + [InlineData("application/json;odata.metadata=none", "explicit")] + public async Task InstanceAnnotationOnPropertyTest(string format, string mode) + { + await ResetDatasource(); + string postUri = this.BaseAddress + string.Format("/{0}/Employees?$format={1}", mode, format); + + var postContent = JObject.Parse(@"{""ID"":1, + ""Name"":""Name2"", + ""SkillSet"":[""Sql""], + ""Gender@NS.TestGender"":100, + ""Gender"":""Female"", + ""AccessLevel"":""Read,Write"", + ""FavoriteSports"":{ + ""LikeMost"":""Pingpong"", + ""Like"":[""Pingpong"",""Basketball""] + }, + }"); + + Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); + + using (HttpResponseMessage response = await this.Client.PostAsJsonAsync(postUri, postContent)) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var json = await response.Content.ReadAsObject(); + VerifyInstanceAnnotations("Name2", json.ToString()); + } + } + + [Theory] + [InlineData("application/json;odata.metadata=full", "convention")] + [InlineData("application/json;odata.metadata=minimal", "convention")] + [InlineData("application/json;odata.metadata=none", "convention")] + [InlineData("application/json;odata.metadata=full", "explicit")] + [InlineData("application/json;odata.metadata=minimal", "explicit")] + [InlineData("application/json;odata.metadata=none", "explicit")] + public async Task InstanceAnnotationOnTypeAndPropertyTest(string format,string mode) + { + await ResetDatasource(); + string postUri = this.BaseAddress + string.Format("/{0}/Employees?$format={1}", mode, format); + + var postContent = JObject.Parse(@"{""ID"":1, + ""Name"":""Name3"", + ""SkillSet"":[""Sql""], + ""Gender@NS.TestGender"":100, + ""Gender"":""Female"", + ""AccessLevel"":""Read,Write"", + ""FavoriteSports"":{ + ""LikeMost"":""Pingpong"", + ""Like"":[""Pingpong"",""Basketball""] + }, + ""@NS.Test"":1}"); + + Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); + using (HttpResponseMessage response = await this.Client.PostAsJsonAsync(postUri, postContent)) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var json = await response.Content.ReadAsObject(); + VerifyInstanceAnnotations("Name3", json.ToString()); + } + } + + [Theory] + [InlineData("application/json;odata.metadata=full", "convention")] + [InlineData("application/json;odata.metadata=minimal", "convention")] + [InlineData("application/json;odata.metadata=none", "convention")] + [InlineData("application/json;odata.metadata=full", "explicit")] + [InlineData("application/json;odata.metadata=minimal", "explicit")] + [InlineData("application/json;odata.metadata=none", "explicit")] + public async Task InstanceAnnotationGetTest(string format, string mode) + { + await ResetDatasource(); + string getUri = this.BaseAddress + string.Format("/{0}/Employees?$format={1}", mode, format); + + Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); + using (HttpResponseMessage response = await this.Client.GetAsync(getUri)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var json = await response.Content.ReadAsObject(); + Assert.Contains(@"""@NS.Test2"": 2", json.ToString()); + } + } + + [Theory] + [InlineData("application/json;odata.metadata=full", "convention")] + [InlineData("application/json;odata.metadata=minimal", "convention")] + [InlineData("application/json;odata.metadata=none", "convention")] + [InlineData("application/json;odata.metadata=full", "explicit")] + [InlineData("application/json;odata.metadata=minimal", "explicit")] + [InlineData("application/json;odata.metadata=none", "explicit")] + public async Task InstanceAnnotationMultipleTest(string format, string mode) + { + await ResetDatasource(); + string postUri = this.BaseAddress + string.Format("/{0}/Employees?$format={1}", mode, format); + + var postContent = JObject.Parse(@"{""ID"":1, + ""Name@NS.TestName"":""TestName1"", + ""Name"":""Name4"", + ""SkillSet"":[""Sql""], + ""Gender@NS.TestGender"":500, + ""Gender"":""Female"", + ""AccessLevel"":""Read,Write"", + ""FavoriteSports"":{ + ""LikeMost"":""Pingpong"", + ""Like"":[""Pingpong"",""Basketball""] + }, + ""@NS.Test1"":100, + ""@NS.Test2"":""Testing""}"); + + Client.DefaultRequestHeaders.Add("Prefer", @"odata.include-annotations=""*"""); + using (HttpResponseMessage response = await this.Client.PostAsJsonAsync(postUri, postContent)) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var json = await response.Content.ReadAsObject(); + VerifyInstanceAnnotations("Name4", json.ToString()); + } + } + #endregion + + + private async Task ResetDatasource() + { + var uriReset = this.BaseAddress + "/convention/ResetDataSource"; + var response = await this.Client.PostAsync(uriReset, null); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + return response; + } + + private void VerifyInstanceAnnotations(string name, string json) + { + switch (name) + { + case "Name1": + Assert.Contains(@"""@NS.Test"": 1", json); + break; + case "Name2": + Assert.Contains(@"""Gender@NS.TestGender"": 100", json); + break; + case "Name3": + Assert.Contains(@"""@NS.Test"": 1", json); + Assert.Contains(@"""Gender@NS.TestGender"": 100", json); + break; + case "Name4": + Assert.Contains(@"""@NS.Test1"": 100", json); + Assert.Contains(@"""Gender@NS.TestGender"": 500", json); + Assert.Contains(@"""@NS.Test2"": ""Testing""", json); + Assert.Contains(@"""Name@NS.TestName"": ""TestName1""", json); + break; + default: + break; + } + } + } +} \ No newline at end of file diff --git a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/Microsoft.Test.E2E.AspNet.OData.csproj b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/Microsoft.Test.E2E.AspNet.OData.csproj index b250fc48a8..5257eb0138 100644 --- a/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/Microsoft.Test.E2E.AspNet.OData.csproj +++ b/test/E2ETest/Microsoft.Test.E2E.AspNet.OData/Build.AspNet/Microsoft.Test.E2E.AspNet.OData.csproj @@ -1862,6 +1862,10 @@ Validation\DeltaOfTValidationTests.cs + + + + @@ -1919,6 +1923,7 @@ + diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/ComplexTypeTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/ComplexTypeTest.cs index e35d0d47ee..3dd00ce8d6 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/ComplexTypeTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/ComplexTypeTest.cs @@ -174,7 +174,30 @@ public void AddDynamicDictionary_ThrowsException_IfMoreThanOneDynamicPropertyInO } [Fact] - public void GetEdmModel_WorksOnModelBuilder_ForOpenComplexType() + public void AddAnnotationDictionary_ThrowsException_IfMoreThanOneDynamicPropertyInComplexType() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.ComplexType(); + + // Act & Assert +#if NETCOREAPP3_1 + ExceptionAssert.ThrowsArgument(() => builder.GetEdmModel(), + "propertyInfo", + "Found more than one Annotation property container in type 'BadAnnotationComplexType'. " + + "Each open type must have at most one Annotation property container. (Parameter 'propertyInfo')"); +#else + ExceptionAssert.ThrowsArgument(() => builder.GetEdmModel(), + "propertyInfo", + "Found more than one Annotation property container in type 'BadAnnotationComplexType'. " + + "Each open type must have at most one Annotation property container.\r\n" + + "Parameter name: propertyInfo"); +#endif + } + + + [Fact] + public void GetEdmModel_WorksOnModelBuilder_ForAnnotationComplexType() { // Arrange ODataModelBuilder builder = new ODataModelBuilder(); @@ -598,6 +621,19 @@ public class BadOpenComplexType public IDictionary DynamicProperties2 { get; set; } } + public class SimpleAnnotationComplexType + { + public int IntProperty { get; set; } + public IODataInstanceAnnotationContainer InstanceAnnotations { get; set; } + } + + public class BadAnnotationComplexType + { + public int IntProperty { get; set; } + public IODataInstanceAnnotationContainer InstanceAnnotations1 { get; set; } + public IODataInstanceAnnotationContainer InstanceAnnotations2 { get; set; } + } + public class MyDynamicProperty : Dictionary { } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs index eba31dcc54..2e06cef0e8 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs @@ -3013,6 +3013,58 @@ public void GetEdmModel_WorksOnConventionModelBuilder_ForDerivedOpenEntityType() Assert.Equal(baseDynamicPropertyAnnotation.PropertyInfo.Name, derivedDynamicPropertyAnnotation.PropertyInfo.Name); } + [Fact] + public void GetEdmModel_WorksOnConventionModelBuilder_ForEntityType_InstanceAnnotation() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + + // Act + IEdmModel model = builder.GetEdmModel(); + + // Assert + Assert.NotNull(model); + IEdmEntityType entityType = + Assert.Single(model.SchemaElements.OfType().Where(c => c.Name == "EntityTypeWithAnnotation")); + Assert.Single(entityType.Properties()); + + ODataInstanceAnnotationContainerAnnotation instanceAnnoteDict = + model.GetAnnotationValue(entityType); + + Assert.Equal("InstanceAnnotations", instanceAnnoteDict.PropertyInfo.Name); + } + + + [Fact] + public void GetEdmModel_WorksOnConventionModelBuilder_ForDerivedEntityType() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + + // Act + IEdmModel model = builder.GetEdmModel(); + + // Assert + Assert.NotNull(model); + IEdmEntityType baseEntityType = + Assert.Single(model.SchemaElements.OfType().Where(c => c.Name == "EntityTypeWithAnnotation")); + Assert.Single(baseEntityType.Properties()); + + IEdmEntityType derivedEntityType = + Assert.Single(model.SchemaElements.OfType().Where(c => c.Name == "DerivedEntityTypeWithAnnotation")); + Assert.Equal(2, derivedEntityType.Properties().Count()); + + ODataInstanceAnnotationContainerAnnotation basePropertyAnnotation = + model.GetAnnotationValue(baseEntityType); + + ODataInstanceAnnotationContainerAnnotation derivedPropertyAnnotation = + model.GetAnnotationValue(derivedEntityType); + + Assert.Equal(basePropertyAnnotation.PropertyInfo.Name, derivedPropertyAnnotation.PropertyInfo.Name); + } + [Fact] public void GetEdmModel_WorksOnConventionModelBuilder_ForBaseEntityType_DerivedOpenEntityType() { @@ -3622,6 +3674,17 @@ public class DerivedOpenEntityType : BaseOpenEntityType public string DerivedProperty { get; set; } } + public class EntityTypeWithAnnotation + { + public int Id { get; set; } + public ODataInstanceAnnotationContainer InstanceAnnotations { get; set; } + } + + public class DerivedEntityTypeWithAnnotation : EntityTypeWithAnnotation + { + public string DerivedProperty { get; set; } + } + public class RecursiveEmployee { public RecursiveEmployee Manager { get; set; } 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 d5a8de51ed..39e0f8eb0d 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/EntityTypeTest.cs @@ -692,6 +692,7 @@ public void GetEdmModel_WorksOnModelBuilder_ForOpenEntityType() Assert.True(entityType.Properties().Where(c => c.Name == "Name").Any()); } + [Fact] public void GetEdmModel_WorksOnModelBuilder_WithDateTime() { @@ -726,6 +727,12 @@ public class SimpleOpenEntityType public IDictionary DynamicProperties { get; set; } } + public class SimpleAnnotationEntityType + { + public int Id { get; set; } + public string Name { get; set; } + public IODataInstanceAnnotationContainer InstanceAnnotations { get; set; } + } public class EntityTypeWithDateAndTimeOfDay { public Date Date { get; set; } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/StructuralTypeConfigurationTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/StructuralTypeConfigurationTest.cs index d410234197..df2d0fba3c 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/StructuralTypeConfigurationTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/StructuralTypeConfigurationTest.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Test.Builder.TestModels; using Microsoft.AspNet.OData.Test.Common; using Moq; @@ -56,6 +57,19 @@ public void AddDynamicPropertyDictionary_ThrowsIfTypeIsNotDictionary() string.Format("The argument must be of type '{0}'.", "IDictionary")); } + [Fact] + public void AddInstanceAnnotationDictionary_ThrowsIfTypeIsNotDictionary() + { + // Arrange + MockPropertyInfo property = new MockPropertyInfo(typeof(Int32), "Test"); + Mock mock = new Mock { CallBase = true }; + StructuralTypeConfiguration configuration = mock.Object; + + // Act & Assert + ExceptionAssert.ThrowsArgument(() => configuration.AddInstanceAnnotationContainer(property), + "propertyInfo", string.Format(SRResources.PropertyTypeShouldBeOfType, "IODataInstanceAnnotationContainer")); + } + /// /// Tests the namespace assignment logic to ensure that user assigned namespaces are honored during registration. /// diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Customer.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Customer.cs index 2f531147aa..33551ec5cd 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Customer.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/TestModels/Customer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNet.OData.Builder; namespace Microsoft.AspNet.OData.Test.Builder.TestModels { @@ -22,5 +23,6 @@ public class Customer public List
Addresses { get; set; } public Dictionary DynamicProperties { get; set; } public DateTimeOffset? StartDate { get; set; } - } + public IODataInstanceAnnotationContainer InstanceAnnotations { get; set; } + } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/DeserializationHelpersTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/DeserializationHelpersTest.cs index 27534abfb5..f011608df6 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/DeserializationHelpersTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/DeserializationHelpersTest.cs @@ -6,10 +6,12 @@ using System.Collections.ObjectModel; using System.Linq; using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Formatter.Deserialization; using Microsoft.AspNet.OData.Test.Abstraction; +using Microsoft.AspNet.OData.Test.Builder.Conventions; using Microsoft.AspNet.OData.Test.Common; using Microsoft.AspNet.OData.Test.Common.Types; using Microsoft.OData; @@ -287,6 +289,34 @@ public void ApplyProperty_DoesNotIgnoreKeyProperty() resource.Verify(); } + [Fact] + public void ApplyProperty_DoesNotIgnoreKeyProperty_WithInstanceAnnotation() + { + // Arrange + ODataProperty property = new ODataProperty { Name = "Key1", Value = "Value1" }; + EdmEntityType entityType = new EdmEntityType("namespace", "name"); + entityType.AddKeys(entityType.AddStructuralProperty("Key1", + EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(typeof(string)))); + + EdmEntityTypeReference entityTypeReference = new EdmEntityTypeReference(entityType, isNullable: false); + ODataDeserializerProvider provider = ODataDeserializerProviderFactory.Create(); + + var resource = new Mock(MockBehavior.Strict); + Type propertyType = typeof(string); + resource.Setup(r => r.TryGetPropertyType("Key1", out propertyType)).Returns(true).Verifiable(); + resource.Setup(r => r.TrySetPropertyValue("Key1", "Value1")).Returns(true).Verifiable(); + + // Act + DeserializationHelpers.ApplyInstanceAnnotations(resource.Object, entityTypeReference, null,provider, + new ODataDeserializerContext { Model = new EdmModel() }); + + DeserializationHelpers.ApplyProperty(property, entityTypeReference, resource.Object, provider, + new ODataDeserializerContext { Model = new EdmModel() }); + + // Assert + resource.Verify(); + } + [Fact] public void ApplyProperty_FailsWithUsefulErrorMessageOnUnknownProperty() { @@ -314,6 +344,34 @@ public void ApplyProperty_FailsWithUsefulErrorMessageOnUnknownProperty() Assert.Equal(HelpfulErrorMessage, exception.Message); } + + [Fact] + public void ApplyAnnotations_FailsWithUsefulErrorMessageOnUnknownProperty() + { + // Arrange + const string HelpfulErrorMessage = + "The property 'Unknown' does not exist on type 'namespace.name'. Make sure to only use property names " + + "that are defined by the type."; + + var property = new ODataProperty { Name = "Unknown", Value = "Value" }; + var entityType = new EdmComplexType("namespace", "name"); + entityType.AddStructuralProperty("Known", EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(typeof(string))); + + var entityTypeReference = new EdmComplexTypeReference(entityType, isNullable: false); + + // Act + var exception = Assert.Throws(() => + DeserializationHelpers.ApplyProperty( + property, + entityTypeReference, + resource: null, + deserializerProvider: null, + readContext: null)); + + // Assert + Assert.Equal(HelpfulErrorMessage, exception.Message); + } + private static IEdmProperty GetMockEdmProperty(string name, EdmPrimitiveTypeKind elementType) { Mock property = new Mock(); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/ODataResourceDeserializerTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/ODataResourceDeserializerTests.cs index c391bc76e5..a46d2196ff 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/ODataResourceDeserializerTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Deserialization/ODataResourceDeserializerTests.cs @@ -467,6 +467,363 @@ public void ReadResource_CanReadDynamicPropertiesForOpenEntityType() Assert.Equal(new List { 1, 2, 3, 4 }, collectionValues[1].Properties["ArrayProperty"]); } + [Fact] + public void ReadResource_CanReadDynamicPropertiesForOpenEntityTypeAndAnnotations() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + builder.EnumType(); + IEdmModel model = builder.GetEdmModel(); + + IEdmEntityTypeReference customerTypeReference = model.GetEdmTypeReference(typeof(SimpleOpenCustomer)).AsEntity(); + + var deserializer = new ODataResourceDeserializer(_deserializerProvider); + + ODataEnumValue enumValue = new ODataEnumValue("Third", typeof(SimpleEnum).FullName); + + var instAnn1 = new List(); + instAnn1.Add(new ODataInstanceAnnotation("NS.Test1",new ODataPrimitiveValue( 123) )); + var instAnn = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test2", new ODataPrimitiveValue(345))); + var instAnn2 = new List(); + instAnn2.Add(new ODataInstanceAnnotation("NS.ChildTest2", new ODataPrimitiveValue(999))); + + ODataResource[] complexResources = + { + new ODataResource + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty {Name = "Street", Value = "Street 1"}, + new ODataProperty {Name = "City", Value = "City 1"}, + + // dynamic properties + new ODataProperty + { + Name = "DateTimeProperty", + Value = new DateTimeOffset(new DateTime(2014, 5, 6)) + } + } + }, + new ODataResource + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty { Name = "Street", Value = "Street 2" ,InstanceAnnotations =instAnn2}, + new ODataProperty { Name = "City", Value = "City 2" }, + + // dynamic properties + new ODataProperty + { + Name = "ArrayProperty", + Value = new ODataCollectionValue { TypeName = "Collection(Edm.Int32)", Items = new[] {1, 2, 3, 4}.Cast() } + } + } + } + }; + + ODataResource odataResource = new ODataResource + { + Properties = new[] + { + // declared properties + new ODataProperty { Name = "CustomerId", Value = 991 ,InstanceAnnotations =instAnn1}, + new ODataProperty { Name = "Name", Value = "Name #991" }, + + // dynamic properties + new ODataProperty { Name = "GuidProperty", Value = new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA") }, + new ODataProperty { Name = "EnumValue", Value = enumValue }, + }, + TypeName = typeof(SimpleOpenCustomer).FullName, + InstanceAnnotations = instAnn + }; + + ODataDeserializerContext readContext = new ODataDeserializerContext() + { + Model = model + }; + + ODataResourceWrapper topLevelResourceWrapper = new ODataResourceWrapper(odataResource); + + ODataNestedResourceInfo resourceInfo = new ODataNestedResourceInfo + { + IsCollection = true, + Name = "CollectionProperty" + }; + ODataNestedResourceInfoWrapper resourceInfoWrapper = new ODataNestedResourceInfoWrapper(resourceInfo); + ODataResourceSetWrapper resourceSetWrapper = new ODataResourceSetWrapper(new ODataResourceSet + { + TypeName = String.Format("Collection({0})", typeof(SimpleOpenAddress).FullName) + }); + foreach (var complexResource in complexResources) + { + resourceSetWrapper.Resources.Add(new ODataResourceWrapper(complexResource)); + } + resourceInfoWrapper.NestedItems.Add(resourceSetWrapper); + topLevelResourceWrapper.NestedResourceInfos.Add(resourceInfoWrapper); + + // Act + SimpleOpenCustomer customer = deserializer.ReadResource(topLevelResourceWrapper, customerTypeReference, readContext) + as SimpleOpenCustomer; + + // Assert + Assert.NotNull(customer); + + // Verify the declared properties + Assert.Equal(991, customer.CustomerId); + Assert.Equal("Name #991", customer.Name); + + // Verify the dynamic properties + Assert.NotNull(customer.CustomerProperties); + Assert.Equal(3, customer.CustomerProperties.Count()); + Assert.Equal(new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA"), customer.CustomerProperties["GuidProperty"]); + Assert.Equal(SimpleEnum.Third, customer.CustomerProperties["EnumValue"]); + + // Verify the dynamic collection property + var collectionValues = Assert.IsType>(customer.CustomerProperties["CollectionProperty"]); + Assert.NotNull(collectionValues); + Assert.Equal(2, collectionValues.Count()); + + Assert.Equal(new DateTimeOffset(new DateTime(2014, 5, 6)), collectionValues[0].Properties["DateTimeProperty"]); + Assert.Equal(new List { 1, 2, 3, 4 }, collectionValues[1].Properties["ArrayProperty"]); + + //Verify Instance Annotations + Assert.Equal(1, customer.InstanceAnnotations.GetResourceAnnotations().Count); + Assert.Equal(1, collectionValues[1].InstanceAnnotations.GetPropertyAnnotations("Street").Count); + Assert.Equal("NS.Test2", customer.InstanceAnnotations.GetResourceAnnotations().First().Key); + } + + [Fact] + public void ReadResource_CanReadDynamicPropertiesForOpenEntityTypeAndAnnotations_CollectionAndEnum() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + builder.EnumType(); + IEdmModel model = builder.GetEdmModel(); + + IEdmEntityTypeReference customerTypeReference = model.GetEdmTypeReference(typeof(SimpleOpenCustomer)).AsEntity(); + + var deserializer = new ODataResourceDeserializer(_deserializerProvider); + + ODataEnumValue enumValue = new ODataEnumValue("Third", typeof(SimpleEnum).FullName); + + ODataEnumValue enumValue1 = new ODataEnumValue("Second", typeof(SimpleEnum).FullName); + + var coll = new ODataCollectionValue { TypeName = "Collection(Edm.Int32)", Items = new[] { 100, 200, 300, 400 }.Cast() }; + + var resourceVal = new ODataResourceValue + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty {Name = "Street", Value = "Street 1"}, + new ODataProperty {Name = "City", Value = "City 1"}, + } + }; + + var instAnn1 = new List(); + instAnn1.Add(new ODataInstanceAnnotation("NS.Test1", new ODataPrimitiveValue(123))); + var instAnn = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test2", new ODataPrimitiveValue(345))); + var instAnn2 = new List(); + instAnn2.Add(new ODataInstanceAnnotation("NS.ChildTest2", coll)); + var instAnn3 = new List(); + instAnn3.Add(new ODataInstanceAnnotation("NS.ChildTest3", enumValue1)); + + + ODataResource[] complexResources = + { + new ODataResource + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty {Name = "Street", Value = "Street 1"}, + new ODataProperty {Name = "City", Value = "City 1"}, + + // dynamic properties + new ODataProperty + { + Name = "DateTimeProperty", + Value = new DateTimeOffset(new DateTime(2014, 5, 6)) + } + } + }, + new ODataResource + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty { Name = "Street", Value = "Street 2" ,InstanceAnnotations =instAnn2}, + new ODataProperty { Name = "City", Value = "City 2" }, + + // dynamic properties + new ODataProperty + { + Name = "ArrayProperty", + Value = new ODataCollectionValue { TypeName = "Collection(Edm.Int32)", Items = new[] {1, 2, 3, 4}.Cast() } + } + } + } + }; + + ODataResource odataResource = new ODataResource + { + Properties = new[] + { + // declared properties + new ODataProperty { Name = "CustomerId", Value = 991 ,InstanceAnnotations =instAnn1}, + new ODataProperty { Name = "Name", Value = "Name #991" }, + + // dynamic properties + new ODataProperty { Name = "GuidProperty", Value = new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA"),InstanceAnnotations =instAnn3 }, + new ODataProperty { Name = "EnumValue", Value = enumValue }, + }, + TypeName = typeof(SimpleOpenCustomer).FullName, + InstanceAnnotations = instAnn + }; + + ODataDeserializerContext readContext = new ODataDeserializerContext() + { + Model = model + }; + + ODataResourceWrapper topLevelResourceWrapper = new ODataResourceWrapper(odataResource); + + ODataNestedResourceInfo resourceInfo = new ODataNestedResourceInfo + { + IsCollection = true, + Name = "CollectionProperty" + }; + ODataNestedResourceInfoWrapper resourceInfoWrapper = new ODataNestedResourceInfoWrapper(resourceInfo); + ODataResourceSetWrapper resourceSetWrapper = new ODataResourceSetWrapper(new ODataResourceSet + { + TypeName = String.Format("Collection({0})", typeof(SimpleOpenAddress).FullName) + }); + foreach (var complexResource in complexResources) + { + resourceSetWrapper.Resources.Add(new ODataResourceWrapper(complexResource)); + } + resourceInfoWrapper.NestedItems.Add(resourceSetWrapper); + topLevelResourceWrapper.NestedResourceInfos.Add(resourceInfoWrapper); + + // Act + SimpleOpenCustomer customer = deserializer.ReadResource(topLevelResourceWrapper, customerTypeReference, readContext) + as SimpleOpenCustomer; + + // Assert + Assert.NotNull(customer); + + // Verify the declared properties + Assert.Equal(991, customer.CustomerId); + Assert.Equal("Name #991", customer.Name); + + // Verify the dynamic properties + Assert.NotNull(customer.CustomerProperties); + Assert.Equal(3, customer.CustomerProperties.Count()); + Assert.Equal(new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA"), customer.CustomerProperties["GuidProperty"]); + Assert.Equal(SimpleEnum.Third, customer.CustomerProperties["EnumValue"]); + + // Verify the dynamic collection property + var collectionValues = Assert.IsType>(customer.CustomerProperties["CollectionProperty"]); + Assert.NotNull(collectionValues); + Assert.Equal(2, collectionValues.Count()); + + Assert.Equal(new DateTimeOffset(new DateTime(2014, 5, 6)), collectionValues[0].Properties["DateTimeProperty"]); + Assert.Equal(new List { 1, 2, 3, 4 }, collectionValues[1].Properties["ArrayProperty"]); + + //Verify Instance Annotations + var dict1 = customer.InstanceAnnotations.GetResourceAnnotations(); + var dict2 = collectionValues[1].InstanceAnnotations.GetPropertyAnnotations("Street"); + var dict3 = customer.InstanceAnnotations.GetPropertyAnnotations("GuidProperty"); + + Assert.Equal(3, dict1.Count+dict2.Count+dict3.Count); + Assert.Equal("NS.Test2", dict1.First().Key); + Assert.Equal(typeof(SimpleEnum),dict3["NS.ChildTest3"].GetType()); + Assert.Equal(SimpleEnum.Second, (SimpleEnum)dict3["NS.ChildTest3"]); + //Verify Collection Instance Annotations + Assert.Equal(1, dict2.Count); + Assert.Equal(new List { 100, 200, 300, 400 }, dict2["NS.ChildTest2"]); + } + + [Fact] + public void ReadResource_CanReadAnnotations_ODataResourceValue() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + builder.EnumType(); + IEdmModel model = builder.GetEdmModel(); + + IEdmEntityTypeReference customerTypeReference = model.GetEdmTypeReference(typeof(SimpleOpenCustomer)).AsEntity(); + + var deserializer = new ODataResourceDeserializer(_deserializerProvider); + + var resourceVal = new ODataResourceValue + { + TypeName = typeof(SimpleOpenAddress).FullName, + Properties = new[] + { + // declared properties + new ODataProperty {Name = "Street", Value = "Street 1"}, + new ODataProperty {Name = "City", Value = "City 1"}, + } + }; + + var instAnn = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test2", resourceVal)); + + ODataResource odataResource = new ODataResource + { + Properties = new[] + { + // declared properties + new ODataProperty { Name = "CustomerId", Value = 991 }, + new ODataProperty { Name = "Name", Value = "Name #991" }, + + }, + TypeName = typeof(SimpleOpenCustomer).FullName, + InstanceAnnotations = instAnn + }; + + ODataDeserializerContext readContext = new ODataDeserializerContext() + { + Model = model + }; + + ODataResourceWrapper topLevelResourceWrapper = new ODataResourceWrapper(odataResource); + + // Act + SimpleOpenCustomer customer = deserializer.ReadResource(topLevelResourceWrapper, customerTypeReference, readContext) + as SimpleOpenCustomer; + + // Assert + Assert.NotNull(customer); + + // Verify the declared properties + Assert.Equal(991, customer.CustomerId); + Assert.Equal("Name #991", customer.Name); + + //Verify Instance Annotations + var dict1 = customer.InstanceAnnotations.GetResourceAnnotations(); + + Assert.Equal(1, dict1.Count); + Assert.Equal(typeof(SimpleOpenAddress), dict1["NS.Test2"].GetType()); + var resValue = dict1["NS.Test2"] as SimpleOpenAddress; + Assert.NotNull(resValue); + Assert.Equal("Street 1", resValue.Street); + Assert.Equal("City 1", resValue.City); + } + [Fact] public void ReadSource_CanReadDynamicPropertiesForInheritanceOpenEntityType() { @@ -567,6 +924,53 @@ public void ReadResource_CanReadDatTimeRelatedProperties() Assert.Equal(new TimeSpan(0, 1, 2, 3, 4), customer.ReleaseTime); } + + [Fact] + public void ReadResource_CanReadInstanceAnnotationforOpenType() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.EntityType(); + IEdmModel model = builder.GetEdmModel(); + + IEdmEntityTypeReference vipCustomerTypeReference = model.GetEdmTypeReference(typeof(SimpleOpenCustomer)).AsEntity(); + + var deserializer = new ODataResourceDeserializer(_deserializerProvider); + + var instAnn1 = new List(); + instAnn1.Add(new ODataInstanceAnnotation("NS.Test1", new ODataPrimitiveValue(123))); + var instAnn = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test2", new ODataPrimitiveValue(345))); + + + ODataResource odataResource = new ODataResource + { + Properties = new[] + { + // declared properties + new ODataProperty { Name = "CustomerId", Value = 991 ,InstanceAnnotations =instAnn1}, + new ODataProperty { Name = "Name", Value = "Name #991" }, + + // dynamic properties + new ODataProperty { Name = "GuidProperty", Value = new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA"), InstanceAnnotations = instAnn }, + + }, + TypeName = typeof(SimpleOpenCustomer).FullName, + + }; + + ODataDeserializerContext readContext = new ODataDeserializerContext { Model = model }; + ODataResourceWrapper resourceWrapper = new ODataResourceWrapper(odataResource); + + // Act + var customer = deserializer.ReadResource(resourceWrapper, vipCustomerTypeReference, readContext) as SimpleOpenCustomer; + + // Assert + Assert.NotNull(customer); + Assert.Equal(991, customer.CustomerId); + Assert.Equal(1, customer.InstanceAnnotations.GetPropertyAnnotations("GuidProperty").Count); + Assert.Equal(1, customer.InstanceAnnotations.GetPropertyAnnotations("CustomerId").Count); + } [Fact] public void CreateResourceInstance_ThrowsArgumentNull_ReadContext() { @@ -877,6 +1281,7 @@ public void ApplyStructuralProperties_Calls_ApplyStructuralPropertyOnEachPropert // Act deserializer.Object.ApplyStructuralProperties(42, resourceWrapper, _productEdmType, _readContext); + deserializer.Object.ApplyInstanceAnnotations(42, resourceWrapper, _productEdmType, _readContext); // Assert deserializer.Verify(); @@ -901,6 +1306,45 @@ public void ApplyStructuralProperty_ThrowsArgumentNull_StructuralProperty() "structuralProperty"); } + + [Fact] + public void ApplyStructuralPropertiesAndInstanceAnnotations_Calls_ApplyStructuralPropertyOnEachPropertyInResource() + { + // Arrange + var deserializer = new Mock(_deserializerProvider); + ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; + + var instAnn = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test2", new ODataPrimitiveValue(345))); + + var instAnn1 = new List(); + instAnn.Add(new ODataInstanceAnnotation("NS.Test1", new ODataPrimitiveValue(123))); + + properties[0].InstanceAnnotations = instAnn1; + + ODataResourceWrapper resourceWrapper = new ODataResourceWrapper(new ODataResource { Properties = properties, InstanceAnnotations = instAnn }); + + deserializer.CallBase = true; + deserializer.Setup(d => d.ApplyStructuralProperty(42, properties[0], _productEdmType, _readContext)).Verifiable(); + deserializer.Setup(d => d.ApplyStructuralProperty(42, properties[1], _productEdmType, _readContext)).Verifiable(); + + // Act + deserializer.Object.ApplyStructuralProperties(42, resourceWrapper, _productEdmType, _readContext); + deserializer.Object.ApplyInstanceAnnotations(42, resourceWrapper, _productEdmType, _readContext); + + // Assert + deserializer.Verify(); + } + + [Fact] + public void ApplyInstanceAnnotations_ThrowsArgumentNull_ResourceWrapper() + { + var deserializer = new ODataResourceDeserializer(_deserializerProvider); + ExceptionAssert.ThrowsArgumentNull( + () => deserializer.ApplyInstanceAnnotations(42, resourceWrapper: null, structuredType: _productEdmType, readContext: _readContext), + "resourceWrapper"); + } + [Fact] public void ApplyStructuralProperty_SetsProperty() { @@ -1052,6 +1496,8 @@ public class Product public virtual Category Category { get; set; } public virtual Supplier Supplier { get; set; } + + public Dictionary> InstanceAnnotations { get; set; } } public class Category diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/ODataResourceSerializerTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/ODataResourceSerializerTests.cs index 9de7f44f9a..584e527ade 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/ODataResourceSerializerTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Formatter/Serialization/ODataResourceSerializerTests.cs @@ -11,11 +11,13 @@ using System.Web.Http.Routing; #endif using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Formatter.Serialization; using Microsoft.AspNet.OData.Test.Abstraction; using Microsoft.AspNet.OData.Test.Builder; +using Microsoft.AspNet.OData.Test.Builder.TestModels; using Microsoft.AspNet.OData.Test.Common; using Microsoft.AspNet.OData.Test.Common.Types; using Microsoft.OData; @@ -59,7 +61,7 @@ public ODataResourceSerializerTests() { FirstName = "Foo", LastName = "Bar", - ID = 10, + ID = 10, }; _orderSet = _model.EntityContainer.FindEntitySet("Orders"); @@ -619,190 +621,889 @@ public void CreateResource_Calls_CreateStructuralProperty_ForEachSelectedStructu Mock serializer = new Mock(_serializerProvider); serializer.CallBase = true; - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) - .Returns(properties[0]) - .Verifiable(); - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) - .Returns(properties[1]) - .Verifiable(); + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) + .Returns(properties[0]) + .Verifiable(); + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) + .Returns(properties[1]) + .Verifiable(); + + // Act + ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + serializer.Verify(); + Assert.Equal(properties, entry.Properties); + } + + [Fact] + public void CreateResource_SetsETagToNull_IfRequestIsNull() + { + // Arrange + SelectExpandNode selectExpandNode = new SelectExpandNode + { + SelectedStructuralProperties = new HashSet + { + new Mock().Object, new Mock().Object + } + }; + ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; + Mock serializer = new Mock(_serializerProvider); + serializer.CallBase = true; + + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) + .Returns(properties[0]); + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) + .Returns(properties[1]); + + // Act + ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + Assert.Null(entry.ETag); + } + + [Fact] + public void CreateResource_SetsETagToNull_IfModelDontHaveConcurrencyProperty() + { + // Arrange + IEdmEntitySet orderSet = _model.EntityContainer.FindEntitySet("Orders"); + Order order = new Order() + { + Name = "Foo", + Shipment = "Bar", + ID = 10, + }; + + _writeContext.NavigationSource = orderSet; + _entityContext = new ResourceContext(_writeContext, orderSet.EntityType().AsReference(), order); + + SelectExpandNode selectExpandNode = new SelectExpandNode + { + SelectedStructuralProperties = new HashSet + { + new Mock().Object, new Mock().Object + } + }; + ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; + Mock serializer = new Mock(_serializerProvider); + serializer.CallBase = true; + + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) + .Returns(properties[0]); + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) + .Returns(properties[1]); + + var config = RoutingConfigurationFactory.CreateWithRootContainer("Route"); + var request = RequestFactory.Create(config, "Route"); + _entityContext.Request = request; + + // Act + ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + Assert.Null(entry.ETag); + } + + [Fact] + public void CreateResource_SetsEtagToNotNull_IfWithConcurrencyProperty() + { + // Arrange + Mock mockConcurrencyProperty = new Mock(); + mockConcurrencyProperty.SetupGet(s => s.Name).Returns("City"); + SelectExpandNode selectExpandNode = new SelectExpandNode + { + SelectedStructuralProperties = new HashSet { new Mock().Object, mockConcurrencyProperty.Object } + }; + ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; + Mock serializer = new Mock(_serializerProvider); + serializer.CallBase = true; + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) + .Returns(properties[0]); + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) + .Returns(properties[1]); + + Mock mockETagHandler = new Mock(); + string tag = "\"'anycity'\""; + EntityTagHeaderValue etagHeaderValue = new EntityTagHeaderValue(tag, isWeak: true); + mockETagHandler.Setup(e => e.CreateETag(It.IsAny>())).Returns(etagHeaderValue); + + var configuration = RoutingConfigurationFactory.CreateWithRootContainer("Route", (config) => + { +#if NETCORE + config.AddService(ServiceLifetime.Singleton, (services) => mockETagHandler.Object); + }); +#else + }); + + configuration.SetETagHandler(mockETagHandler.Object); +#endif + + var request = RequestFactory.Create(configuration, "Route"); + _entityContext.Request = request; + + // Act + ODataResource resource = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + Assert.Equal(etagHeaderValue.ToString(), resource.ETag); + } + + [Fact] + public void CreateResource_IgnoresProperty_IfCreateStructuralPropertyReturnsNull() + { + // Arrange + SelectExpandNode selectExpandNode = new SelectExpandNode + { + SelectedStructuralProperties = new HashSet { new Mock().Object } + }; + Mock serializer = new Mock(_serializerProvider); + serializer.CallBase = true; + + serializer + .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) + .Returns(null); + + // Act + ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + serializer.Verify(); + Assert.Empty(entry.Properties); + } + + [Fact] + public void CreateResource_Calls_CreateODataAction_ForEachSelectAction() + { + // Arrange + ODataAction[] actions = new ODataAction[] { new ODataAction(), new ODataAction() }; + SelectExpandNode selectExpandNode = new SelectExpandNode + { + SelectedActions = new HashSet { new Mock().Object, new Mock().Object } + }; + Mock serializer = new Mock(_serializerProvider); + serializer.CallBase = true; + + serializer.Setup(s => s.CreateODataAction(selectExpandNode.SelectedActions.ElementAt(0), _entityContext)).Returns(actions[0]).Verifiable(); + serializer.Setup(s => s.CreateODataAction(selectExpandNode.SelectedActions.ElementAt(1), _entityContext)).Returns(actions[1]).Verifiable(); + + // Act + ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + + // Assert + Assert.Equal(actions, entry.Actions); + serializer.Verify(); + } + + [Fact] + public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new DynamicPropertyDictionaryAnnotation( + simpleOpenCustomer.GetProperty("CustomerProperties"))); + + model.SetAnnotationValue(addressType, new DynamicPropertyDictionaryAnnotation( + simpleOpenAddress.GetProperty("Properties"))); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + CustomerProperties = new Dictionary() + }; + DateTime dateTime = new DateTime(2014, 10, 24, 0, 0, 0, DateTimeKind.Utc); + customer.CustomerProperties.Add("EnumProperty", SimpleEnum.Fourth); + customer.CustomerProperties.Add("GuidProperty", new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA")); + customer.CustomerProperties.Add("ListProperty", new List { 5, 4, 3, 2, 1 }); + customer.CustomerProperties.Add("DateTimeProperty", dateTime); + + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); + + // Act + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); + + // Assert + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(6, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "CustomerId")); + Assert.Equal(991, street.Value); + + ODataProperty city = Assert.Single(resource.Properties.Where(p => p.Name == "Name")); + Assert.Equal("Name #991", city.Value); + + // Verify the nested open complex property + Assert.Empty(resource.Properties.Where(p => p.Name == "Address")); + + // Verify the dynamic properties + ODataProperty enumProperty = Assert.Single(resource.Properties.Where(p => p.Name == "EnumProperty")); + ODataEnumValue enumValue = Assert.IsType(enumProperty.Value); + Assert.Equal("Fourth", enumValue.Value); + Assert.Equal("Default.SimpleEnum", enumValue.TypeName); + + ODataProperty guidProperty = Assert.Single(resource.Properties.Where(p => p.Name == "GuidProperty")); + Assert.Equal(new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA"), guidProperty.Value); + + ODataProperty listProperty = Assert.Single(resource.Properties.Where(p => p.Name == "ListProperty")); + ODataCollectionValue collectionValue = Assert.IsType(listProperty.Value); + Assert.Equal(new List { 5, 4, 3, 2, 1 }, collectionValue.Items.OfType().ToList()); + Assert.Equal("Collection(Edm.Int32)", collectionValue.TypeName); + + ODataProperty dateTimeProperty = Assert.Single(resource.Properties.Where(p => p.Name == "DateTimeProperty")); + Assert.Equal(new DateTimeOffset(dateTime), dateTimeProperty.Value); + } + + [Theory] + [InlineData("@NS.test1", "notcontain")] + [InlineData("NStest1.", "notcontain")] + [InlineData("NStest", "contain")] + public void CreateResource_ThrowsErr_WithBadInstanceAnnotations(string annotationName, string error) + { + var in1 = new Dictionary(); + in1.Add(annotationName, 123); + + var instAnn = new ODataInstanceAnnotationContainer(); + + ExceptionAssert.ThrowsArgument( + () => AddInstanceAnnotations(instAnn, in1, string.Empty), annotationName, + error == "notcontain" ? SRResources.InstanceAnnotationNotContain : SRResources.InstanceAnnotationShouldContain); + } + + [Fact] + public void CreateResource_Works_WithInstanceAnnotations() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + var in1 = new Dictionary(); + in1.Add("NS.test1", 123); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, string.Empty); + AddInstanceAnnotations(instAnn, in2, "Name"); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn + }; + + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); + + // Act + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); + + + // Assert + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "CustomerId")); + Assert.Equal(991, street.Value); + + ODataProperty city = Assert.Single(resource.Properties.Where(p => p.Name == "Name")); + Assert.Equal("Name #991", city.Value); + + // Verify the nested open complex property + Assert.Empty(resource.Properties.Where(p => p.Name == "Address")); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "Name").First().InstanceAnnotations.Count); + } + + [Fact] + public void CreateResource_Works_WithInstanceAnnotations_WithNullAnnotation() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + var in1 = new Dictionary(); + in1.Add("NS.test1", 123); + + var in2 = new Dictionary(); + in2.Add("NS.test2", null); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, string.Empty); + AddInstanceAnnotations(instAnn, in2, "Name"); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn + }; + + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); + + // Act + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); + + // Assert + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "Name").First().InstanceAnnotations.Count); + } + + + [Fact] + public void CreateResource_Works_WithInstanceAnnotations_WithUntypedAnnotation() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + IEdmEntityType entityType = model.SchemaElements.OfType().First(c => c.Name == "Customer"); + EdmEntityObject customer1 = new EdmEntityObject(entityType); + customer1.TrySetPropertyValue("ID", 3); + customer1.TrySetPropertyValue("Name", "TestName"); + + var in1 = new Dictionary(); + in1.Add("NS.test1", customer1); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, string.Empty); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn + }; + + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); + + // Act + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); + + // Assert + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal("NS.test1", resource.InstanceAnnotations.First().Name); + var resVal = resource.InstanceAnnotations.First().Value as ODataResourceValue; + Assert.NotNull(resVal); + Assert.Equal(4, resVal.Properties.Count()); + Assert.Equal("ID", resVal.Properties.First().Name); + Assert.Equal(3, resVal.Properties.First().Value); + } + private void AddInstanceAnnotations(IODataInstanceAnnotationContainer container, IDictionary annotation, string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + { + foreach (var kv in annotation) + { + container.AddResourceAnnotation(kv.Key, kv.Value); + } + } + else + { + foreach (var kv in annotation) + { + container.AddPropertyAnnotation(propertyName, kv.Key, kv.Value); + } + } + } + + [Fact] + public void CreateResource_Works_WithInstanceAnnotations_OfTypeCollection() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + List lst = new List { 1, 2, 3 }; + var in1 = new Dictionary(); + in1.Add("NS.test1", lst); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, null); + AddInstanceAnnotations(instAnn, in2, "Name"); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn + }; + + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); + + // Act + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); + + // Assert + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "CustomerId")); + Assert.Equal(991, street.Value); + + ODataProperty city = Assert.Single(resource.Properties.Where(p => p.Name == "Name")); + Assert.Equal("Name #991", city.Value); + + // Verify the nested open complex property + Assert.Empty(resource.Properties.Where(p => p.Name == "Address")); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "Name").First().InstanceAnnotations.Count); + + var collVal = resource.InstanceAnnotations.First().Value as ODataCollectionValue; + Assert.NotNull(collVal); + Assert.Equal>((IEnumerable )lst, (IEnumerable)collVal.Items.Select(x => (int)x)); + } + + [Fact] + public void CreateResource_Works_WithInstanceAnnotations_OfTypeComplex() + { + // Arrange + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + var addr = new SimpleOpenAddress { City = "Redmond", Street = "152nd st" }; + + var in1 = new Dictionary(); + in1.Add("NS.test1", addr); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, null); + AddInstanceAnnotations(instAnn, in2, "Name"); - // Act - ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); - // Assert - serializer.Verify(); - Assert.Equal(properties, entry.Properties); - } + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext + { + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; - [Fact] - public void CreateResource_SetsETagToNull_IfRequestIsNull() - { - // Arrange - SelectExpandNode selectExpandNode = new SelectExpandNode + SimpleOpenCustomer customer = new SimpleOpenCustomer() { - SelectedStructuralProperties = new HashSet + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress { - new Mock().Object, new Mock().Object - } + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn }; - ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; - Mock serializer = new Mock(_serializerProvider); - serializer.CallBase = true; - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) - .Returns(properties[0]); - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) - .Returns(properties[1]); + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); // Act - ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); // Assert - Assert.Null(entry.ETag); + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "CustomerId")); + Assert.Equal(991, street.Value); + + ODataProperty city = Assert.Single(resource.Properties.Where(p => p.Name == "Name")); + Assert.Equal("Name #991", city.Value); + + // Verify the nested open complex property + Assert.Empty(resource.Properties.Where(p => p.Name == "Address")); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(typeof(ODataResourceValue), resource.InstanceAnnotations.First().Value.GetType()); + Assert.Equal("Default.Address", ((ODataResourceValue)resource.InstanceAnnotations.First().Value).TypeName); + Assert.Equal("152nd st", ((ODataResourceValue)resource.InstanceAnnotations.First().Value).Properties.ToList()[0].Value); + Assert.Equal("Redmond", ((ODataResourceValue)resource.InstanceAnnotations.First().Value).Properties.ToList()[1].Value); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "Name").First().InstanceAnnotations.Count); } + [Fact] - public void CreateResource_SetsETagToNull_IfModelDontHaveConcurrencyProperty() + public void CreateResource_Works_WithInstanceAnnotations_OfTypeComplex_Collection() { // Arrange - IEdmEntitySet orderSet = _model.EntityContainer.FindEntitySet("Orders"); - Order order = new Order() + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); + + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); + + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); + + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + var addr = new SimpleOpenAddress { City = "Redmond", Street = "152nd st" }; + + var lstAddr = new List (){ addr }; + var in1 = new Dictionary(); + in1.Add("NS.test1", lstAddr); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, null); + AddInstanceAnnotations(instAnn, in2, "Name"); + + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); + ODataSerializerContext writeContext = new ODataSerializerContext { - Name = "Foo", - Shipment = "Bar", - ID = 10, + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) }; - _writeContext.NavigationSource = orderSet; - _entityContext = new ResourceContext(_writeContext, orderSet.EntityType().AsReference(), order); - - SelectExpandNode selectExpandNode = new SelectExpandNode + SimpleOpenCustomer customer = new SimpleOpenCustomer() { - SelectedStructuralProperties = new HashSet + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress { - new Mock().Object, new Mock().Object - } + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } + }, + InstanceAnnotations = instAnn }; - ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; - Mock serializer = new Mock(_serializerProvider); - serializer.CallBase = true; - - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) - .Returns(properties[0]); - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) - .Returns(properties[1]); - var config = RoutingConfigurationFactory.CreateWithRootContainer("Route"); - var request = RequestFactory.Create(config, "Route"); - _entityContext.Request = request; + ResourceContext resourceContext = new ResourceContext(writeContext, + customerType.ToEdmTypeReference(false) as IEdmEntityTypeReference, customer); // Act - ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); // Assert - Assert.Null(entry.ETag); + Assert.Equal("Default.Customer", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "CustomerId")); + Assert.Equal(991, street.Value); + + ODataProperty city = Assert.Single(resource.Properties.Where(p => p.Name == "Name")); + Assert.Equal("Name #991", city.Value); + + // Verify the nested open complex property + Assert.Empty(resource.Properties.Where(p => p.Name == "Address")); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(typeof(ODataCollectionValue), resource.InstanceAnnotations.First().Value.GetType()); + Assert.True( ((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.Count() ==1); + Assert.Equal("Collection(Default.Address)", ((ODataCollectionValue)resource.InstanceAnnotations.First().Value).TypeName); + Assert.Equal("152nd st", ((ODataResourceValue)((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.ToList().First()).Properties.ToList()[0].Value); + Assert.Equal("Redmond", ((ODataResourceValue)((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.ToList().First()).Properties.ToList()[1].Value); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "Name").First().InstanceAnnotations.Count); } [Fact] - public void CreateResource_SetsEtagToNotNull_IfWithConcurrencyProperty() + public void CreateResource_Works_WithInstanceAnnotations_OnComplexType_AndPropertyInComplexType() { // Arrange - Mock mockConcurrencyProperty = new Mock(); - mockConcurrencyProperty.SetupGet(s => s.Name).Returns("City"); - SelectExpandNode selectExpandNode = new SelectExpandNode - { - SelectedStructuralProperties = new HashSet { new Mock().Object, mockConcurrencyProperty.Object } - }; - ODataProperty[] properties = new[] { new ODataProperty(), new ODataProperty() }; - Mock serializer = new Mock(_serializerProvider); - serializer.CallBase = true; - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) - .Returns(properties[0]); - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(1), _entityContext)) - .Returns(properties[1]); + IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); - Mock mockETagHandler = new Mock(); - string tag = "\"'anycity'\""; - EntityTagHeaderValue etagHeaderValue = new EntityTagHeaderValue(tag, isWeak: true); - mockETagHandler.Setup(e => e.CreateETag(It.IsAny>())).Returns(etagHeaderValue); + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); - var configuration = RoutingConfigurationFactory.CreateWithRootContainer("Route", (config) => - { -#if NETCORE - config.AddService(ServiceLifetime.Singleton, (services) => mockETagHandler.Object); - }); -#else - }); + IEdmEntityType customerType = model.FindDeclaredType("Default.Customer") as IEdmEntityType; + Type simpleOpenCustomer = typeof(SimpleOpenCustomer); + model.SetAnnotationValue(customerType, new ClrTypeAnnotation(simpleOpenCustomer)); - configuration.SetETagHandler(mockETagHandler.Object); -#endif + IEdmComplexType addressType = model.FindDeclaredType("Default.Address") as IEdmComplexType; + Type simpleOpenAddress = typeof(SimpleOpenAddress); + model.SetAnnotationValue(addressType, new ClrTypeAnnotation(simpleOpenAddress)); - var request = RequestFactory.Create(configuration, "Route"); - _entityContext.Request = request; + IEdmEnumType enumType = model.FindDeclaredType("Default.SimpleEnum") as IEdmEnumType; + Type simpleEnumType = typeof(SimpleEnum); + model.SetAnnotationValue(enumType, new ClrTypeAnnotation(simpleEnumType)); - // Act - ODataResource resource = serializer.Object.CreateResource(selectExpandNode, _entityContext); + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); - // Assert - Assert.Equal(etagHeaderValue.ToString(), resource.ETag); - } + model.SetAnnotationValue(addressType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenAddress.GetProperty("InstanceAnnotations"))); - [Fact] - public void CreateResource_IgnoresProperty_IfCreateStructuralPropertyReturnsNull() - { - // Arrange - SelectExpandNode selectExpandNode = new SelectExpandNode - { - SelectedStructuralProperties = new HashSet { new Mock().Object } - }; - Mock serializer = new Mock(_serializerProvider); - serializer.CallBase = true; + var addr = new SimpleOpenAddress { City = "Redmond", Street = "152nd st" }; - serializer - .Setup(s => s.CreateStructuralProperty(selectExpandNode.SelectedStructuralProperties.ElementAt(0), _entityContext)) - .Returns(null); + var lstAddr = new List() { addr }; + var in1 = new Dictionary(); + in1.Add("NS.test1", lstAddr); - // Act - ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); - // Assert - serializer.Verify(); - Assert.Empty(entry.Properties); - } + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, null); + AddInstanceAnnotations(instAnn, in2, "City"); - [Fact] - public void CreateResource_Calls_CreateODataAction_ForEachSelectAction() - { - // Arrange - ODataAction[] actions = new ODataAction[] { new ODataAction(), new ODataAction() }; - SelectExpandNode selectExpandNode = new SelectExpandNode + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); + + SelectExpandNode selectExpandNode = new SelectExpandNode(null, addressType, model); + ODataSerializerContext writeContext = new ODataSerializerContext { - SelectedActions = new HashSet { new Mock().Object, new Mock().Object } + Model = model, + Path = new ODataPath(new EntitySetSegment(customers)) + }; + + SimpleOpenCustomer customer = new SimpleOpenCustomer() + { + CustomerId = 991, + Name = "Name #991", + Address = new SimpleOpenAddress + { + City = "a city", + Street = "a street", + Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } }, + InstanceAnnotations = instAnn + } + }; - Mock serializer = new Mock(_serializerProvider); - serializer.CallBase = true; - serializer.Setup(s => s.CreateODataAction(selectExpandNode.SelectedActions.ElementAt(0), _entityContext)).Returns(actions[0]).Verifiable(); - serializer.Setup(s => s.CreateODataAction(selectExpandNode.SelectedActions.ElementAt(1), _entityContext)).Returns(actions[1]).Verifiable(); + ResourceContext resourceContext = new ResourceContext(writeContext, + addressType.ToEdmTypeReference(false) as IEdmComplexTypeReference, customer.Address); // Act - ODataResource entry = serializer.Object.CreateResource(selectExpandNode, _entityContext); + ODataResource resource = serializer.CreateResource(selectExpandNode, resourceContext); // Assert - Assert.Equal(actions, entry.Actions); - serializer.Verify(); + Assert.Equal("Default.Address", resource.TypeName); + Assert.Equal(2, resource.Properties.Count()); + + // Verify the declared properties + ODataProperty street = Assert.Single(resource.Properties.Where(p => p.Name == "Street")); + Assert.Equal("a street", street.Value); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(typeof(ODataCollectionValue), resource.InstanceAnnotations.First().Value.GetType()); + Assert.True(((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.Count() == 1); + Assert.Equal("Collection(Default.Address)", ((ODataCollectionValue)resource.InstanceAnnotations.First().Value).TypeName); + Assert.Equal("152nd st", ((ODataResourceValue)((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.ToList().First()).Properties.ToList()[0].Value); + Assert.Equal("Redmond", ((ODataResourceValue)((ODataCollectionValue)(resource.InstanceAnnotations.First().Value)).Items.ToList().First()).Properties.ToList()[1].Value); + Assert.Equal(1, resource.Properties.ToList().Where(x => x.Name == "City").First().InstanceAnnotations.Count); } + [Fact] - public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType() + public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType_WithAnnotations() { // Arrange IEdmModel model = SerializationTestsHelpers.SimpleOpenTypeModel(); @@ -827,6 +1528,19 @@ public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType() model.SetAnnotationValue(addressType, new DynamicPropertyDictionaryAnnotation( simpleOpenAddress.GetProperty("Properties"))); + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + + var in1 = new Dictionary(); + in1.Add("NS.test1", 123); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, null); + AddInstanceAnnotations(instAnn, in2, "Name"); + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); SelectExpandNode selectExpandNode = new SelectExpandNode(null, customerType, model); @@ -846,7 +1560,8 @@ public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType() Street = "a street", Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } }, - CustomerProperties = new Dictionary() + CustomerProperties = new Dictionary(), + InstanceAnnotations = instAnn }; DateTime dateTime = new DateTime(2014, 10, 24, 0, 0, 0, DateTimeKind.Utc); customer.CustomerProperties.Add("EnumProperty", SimpleEnum.Fourth); @@ -890,6 +1605,10 @@ public void CreateResource_Works_ToAppendDynamicProperties_ForOpenEntityType() ODataProperty dateTimeProperty = Assert.Single(resource.Properties.Where(p => p.Name == "DateTimeProperty")); Assert.Equal(new DateTimeOffset(dateTime), dateTimeProperty.Value); + + //Verify Instance Annotations + Assert.Equal(1, resource.InstanceAnnotations.Count); + Assert.Equal(1, resource.Properties.ToList().Where(x=>x.Name=="Name").First().InstanceAnnotations.Count); } [Theory] @@ -920,6 +1639,9 @@ public void CreateResource_Works_ToAppendNullDynamicProperties_ForOpenEntityType model.SetAnnotationValue(addressType, new DynamicPropertyDictionaryAnnotation( simpleOpenAddress.GetProperty("Properties"))); + model.SetAnnotationValue(customerType, new ODataInstanceAnnotationContainerAnnotation( + simpleOpenCustomer.GetProperty("InstanceAnnotations"))); + ODataResourceSerializer serializer = new ODataResourceSerializer(_serializerProvider); var config = RoutingConfigurationFactory.CreateWithRootContainer("Route"); @@ -933,6 +1655,16 @@ public void CreateResource_Works_ToAppendNullDynamicProperties_ForOpenEntityType Request = request }; + var in1 = new Dictionary(); + in1.Add("NS.test1", 123); + + var in2 = new Dictionary(); + in2.Add("NS.test2", 345); + var instAnn = new ODataInstanceAnnotationContainer(); + AddInstanceAnnotations(instAnn, in1, string.Empty); + AddInstanceAnnotations(instAnn, in2, "Name"); + + SimpleOpenCustomer customer = new SimpleOpenCustomer() { CustomerId = 991, @@ -943,7 +1675,8 @@ public void CreateResource_Works_ToAppendNullDynamicProperties_ForOpenEntityType Street = "a street", Properties = new Dictionary { { "ArrayProperty", new[] { "15", "14", "13" } } } }, - CustomerProperties = new Dictionary() + CustomerProperties = new Dictionary(), + InstanceAnnotations = instAnn }; customer.CustomerProperties.Add("GuidProperty", new Guid("181D3A20-B41A-489F-9F15-F91F0F6C9ECA")); @@ -2003,12 +2736,15 @@ private class Customer public Customer() { this.Orders = new List(); + InstanceAnnotations = new ODataInstanceAnnotationContainer(); } public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string City { get; set; } public IList Orders { get; private set; } + public IODataInstanceAnnotationContainer InstanceAnnotations { get; } + } private class SpecialCustomer @@ -2087,6 +2823,7 @@ private static Tuple CreateKey(IEdmElement element, { return new Tuple(element, namespaceName, localName); } + } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/OpenEntityTypeTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/OpenEntityTypeTest.cs index a57cc9b01c..bc63b90c73 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/OpenEntityTypeTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/OpenEntityTypeTest.cs @@ -333,11 +333,13 @@ public async Task Post_OpenEntityType() const string Payload = "{" + "\"@odata.context\":\"http://localhost/odata/$metadata#OpenCustomers/$entity\"," + "\"CustomerId\":6,\"Name\":\"FirstName 6\"," + - "\"Address\":{" + + "\"Address\":{" + "\"Street\":\"Street 6\",\"City\":\"City 6\",\"Place\":\"Earth\",\"Token@odata.type\":\"#Guid\"," + "\"Token\":\"4DB52263-4382-4BCB-A63E-3129C1B5FA0D\"," + - "\"Number\":990" + + "\"@NS1.abc\": \"Test\"," + + "\"Number\":990" + "}," + + "\"Website@NS.Street\": \"Test\"," + "\"Website\": \"WebSite #6\"," + "\"Place@odata.type\":\"#String\",\"Place\":\"My Dynamic Place\"," + // odata.type is necessary, otherwise it will get an ODataUntypedValue "\"Token@odata.type\":\"#Guid\",\"Token\":\"2c1f450a-a2a7-4fe1-a25d-4d9332fc0694\"," + diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Resources/SupplierRequestEntry.json b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Resources/SupplierRequestEntry.json index 02b9f79299..1525be6a72 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Resources/SupplierRequestEntry.json +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Resources/SupplierRequestEntry.json @@ -12,15 +12,15 @@ }, Products: [ - { - "ID":1, - "Name":"Milk", - "Description":"Low fat milk", - "ReleaseDate":"1995-10-01T00:00:00z", - "DiscontinuedDate":null, - "Rating":3, - "Price":3.5 - }, + { + "ID": 1, + "Name": "Milk", + "Description": "Low fat milk", + "ReleaseDate": "1995-10-01T00:00:00z", + "DiscontinuedDate": "2005-10-03T00:00:00z", + "Rating": 3, + "Price": 3.5 + }, { "ID":2, "Name":"soda", diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenAddress.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenAddress.cs index d7c5b7bb6a..690332a773 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenAddress.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenAddress.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.AspNet.OData.Builder; namespace Microsoft.AspNet.OData.Test.Common { @@ -10,5 +11,6 @@ public class SimpleOpenAddress public string Street { get; set; } public string City { get; set; } public IDictionary Properties { get; set; } + public ODataInstanceAnnotationContainer InstanceAnnotations { get; set; } } } \ No newline at end of file diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenCustomer.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenCustomer.cs index 8cfdea6505..d79a3fb9b8 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenCustomer.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/TestCommon/SimpleOpenCustomer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Builder; namespace Microsoft.AspNet.OData.Test.Common { @@ -15,5 +16,6 @@ public class SimpleOpenCustomer public string Website { get; set; } public List Orders { get; set; } public IDictionary CustomerProperties { get; set; } + public ODataInstanceAnnotationContainer InstanceAnnotations { get; set; } } } \ No newline at end of file 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 3e1c2c1354..15e6a2231d 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 @@ -926,6 +926,7 @@ public enum Microsoft.AspNet.OData.Builder.PropertyKind : int { Complex = 1 Dynamic = 5 Enum = 4 + InstanceAnnotations = 6 Navigation = 3 Primitive = 0 } @@ -939,6 +940,15 @@ public interface Microsoft.AspNet.OData.Builder.IEdmTypeConfiguration { string Namespace { public abstract get; } } +public interface Microsoft.AspNet.OData.Builder.IODataInstanceAnnotationContainer { + void AddPropertyAnnotation (string propertyName, string annotationName, object value) + void AddResourceAnnotation (string annotationName, object value) + object GetPropertyAnnotation (string propertyName, string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + object GetResourceAnnotation (string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + public abstract class Microsoft.AspNet.OData.Builder.NavigationSourceConfiguration { protected NavigationSourceConfiguration () protected NavigationSourceConfiguration (ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) @@ -1125,6 +1135,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[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.Reflection.PropertyInfo InstanceAnnotationsContainer { public get; } System.Nullable`1[[System.Boolean]] IsAbstract { public virtual get; public virtual set; } bool IsOpen { public get; } Microsoft.OData.Edm.EdmTypeKind Kind { public abstract get; } @@ -1135,6 +1146,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IEnumerable`1[[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] Properties { public get; } QueryConfiguration QueryConfiguration { public get; public set; } System.Collections.Generic.IList`1[[System.Reflection.PropertyInfo]] RemovedProperties { protected get; } + bool SupportsInstanceAnnotations { public get; } internal virtual void AbstractImpl () public virtual CollectionPropertyConfiguration AddCollectionProperty (System.Reflection.PropertyInfo propertyInfo) @@ -1142,6 +1154,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration 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 void AddInstanceAnnotationContainer (System.Reflection.PropertyInfo propertyInfo) 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) @@ -1180,6 +1193,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 Filter (string[] properties) public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting, string[] properties) public void HasDynamicProperties (Expression`1 propertyExpression) + public void HasInstanceAnnotations (Expression`1 propertyExpression) public NavigationPropertyConfiguration HasMany (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression, Expression`1 referentialConstraintExpression) @@ -1200,13 +1214,13 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) - public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) - public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) + public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) + public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public StructuralTypeConfiguration`1 Select () @@ -1799,6 +1813,17 @@ public sealed class Microsoft.AspNet.OData.Builder.MediaTypeAttribute : System.A public MediaTypeAttribute () } +public sealed class Microsoft.AspNet.OData.Builder.ODataInstanceAnnotationContainer : IODataInstanceAnnotationContainer { + public ODataInstanceAnnotationContainer () + + public virtual void AddPropertyAnnotation (string propertyName, string annotationName, object value) + public virtual void AddResourceAnnotation (string annotationName, object value) + public virtual object GetPropertyAnnotation (string propertyName, string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + public virtual object GetResourceAnnotation (string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + [ AttributeUsageAttribute(), ] @@ -3203,6 +3228,7 @@ public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataPrimitiveDese public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataResourceDeserializer : ODataEdmTypeDeserializer { public ODataResourceDeserializer (ODataDeserializerProvider deserializerProvider) + public virtual void ApplyInstanceAnnotations (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperty (object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyStructuralProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) @@ -3285,6 +3311,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.EntitySelfLinks { public class Microsoft.AspNet.OData.Formatter.Serialization.ODataCollectionSerializer : ODataEdmTypeSerializer { public ODataCollectionSerializer (ODataSerializerProvider serializerProvider) + public ODataCollectionSerializer (ODataSerializerProvider serializerProvider, bool isForAnnotations) protected static void AddTypeNameAnnotationAsNeeded (Microsoft.OData.ODataCollectionValue value, ODataMetadataLevel metadataLevel) public virtual Microsoft.OData.ODataCollectionValue CreateODataCollectionValue (System.Collections.IEnumerable enumerable, Microsoft.OData.Edm.IEdmTypeReference elementType, ODataSerializerContext writeContext) @@ -3355,6 +3382,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSeriali public ODataResourceSerializer (ODataSerializerProvider serializerProvider) public virtual void AppendDynamicProperties (Microsoft.OData.ODataResource resource, SelectExpandNode selectExpandNode, ResourceContext resourceContext) + public virtual void AppendInstanceAnnotations (Microsoft.OData.ODataResource resource, ResourceContext resourceContext) public virtual string CreateETag (ResourceContext resourceContext) public virtual Microsoft.OData.ODataNestedResourceInfo CreateNavigationLink (Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, ResourceContext resourceContext) public virtual Microsoft.OData.ODataAction CreateODataAction (Microsoft.OData.Edm.IEdmAction action, ResourceContext resourceContext) @@ -3376,6 +3404,14 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSeri public virtual void WriteObjectInline (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, ODataSerializerContext writeContext) } +public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceValueSerializer : ODataEdmTypeSerializer { + public ODataResourceValueSerializer (ODataSerializerProvider serializerProvider) + protected ODataResourceValueSerializer (Microsoft.OData.ODataPayloadKind payloadKind, ODataSerializerProvider serializerProvider) + + public virtual Microsoft.OData.ODataValue CreateODataValue (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, ODataSerializerContext writeContext) +} + public class Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerContext { public ODataSerializerContext () public ODataSerializerContext (ResourceContext resource, Microsoft.OData.UriParser.SelectExpandClause selectExpandClause, Microsoft.OData.Edm.IEdmProperty edmProperty) 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 3532547b7a..573a04e300 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 @@ -978,6 +978,7 @@ public enum Microsoft.AspNet.OData.Builder.PropertyKind : int { Complex = 1 Dynamic = 5 Enum = 4 + InstanceAnnotations = 6 Navigation = 3 Primitive = 0 } @@ -991,6 +992,15 @@ public interface Microsoft.AspNet.OData.Builder.IEdmTypeConfiguration { string Namespace { public abstract get; } } +public interface Microsoft.AspNet.OData.Builder.IODataInstanceAnnotationContainer { + void AddPropertyAnnotation (string propertyName, string annotationName, object value) + void AddResourceAnnotation (string annotationName, object value) + object GetPropertyAnnotation (string propertyName, string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + object GetResourceAnnotation (string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + public abstract class Microsoft.AspNet.OData.Builder.NavigationSourceConfiguration { protected NavigationSourceConfiguration () protected NavigationSourceConfiguration (ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) @@ -1177,6 +1187,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[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.Reflection.PropertyInfo InstanceAnnotationsContainer { public get; } System.Nullable`1[[System.Boolean]] IsAbstract { public virtual get; public virtual set; } bool IsOpen { public get; } Microsoft.OData.Edm.EdmTypeKind Kind { public abstract get; } @@ -1187,6 +1198,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IEnumerable`1[[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] Properties { public get; } QueryConfiguration QueryConfiguration { public get; public set; } System.Collections.Generic.IList`1[[System.Reflection.PropertyInfo]] RemovedProperties { protected get; } + bool SupportsInstanceAnnotations { public get; } internal virtual void AbstractImpl () public virtual CollectionPropertyConfiguration AddCollectionProperty (System.Reflection.PropertyInfo propertyInfo) @@ -1194,6 +1206,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration 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 void AddInstanceAnnotationContainer (System.Reflection.PropertyInfo propertyInfo) 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) @@ -1232,6 +1245,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 Filter (string[] properties) public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting, string[] properties) public void HasDynamicProperties (Expression`1 propertyExpression) + public void HasInstanceAnnotations (Expression`1 propertyExpression) public NavigationPropertyConfiguration HasMany (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression, Expression`1 referentialConstraintExpression) @@ -1252,13 +1266,13 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) - public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) - public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) + public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) + public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public StructuralTypeConfiguration`1 Select () @@ -1852,6 +1866,17 @@ public sealed class Microsoft.AspNet.OData.Builder.MediaTypeAttribute : System.A public MediaTypeAttribute () } +public sealed class Microsoft.AspNet.OData.Builder.ODataInstanceAnnotationContainer : IODataInstanceAnnotationContainer { + public ODataInstanceAnnotationContainer () + + public virtual void AddPropertyAnnotation (string propertyName, string annotationName, object value) + public virtual void AddResourceAnnotation (string annotationName, object value) + public virtual object GetPropertyAnnotation (string propertyName, string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + public virtual object GetResourceAnnotation (string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + [ AttributeUsageAttribute(), ] @@ -3341,6 +3366,7 @@ public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataPrimitiveDese public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataResourceDeserializer : ODataEdmTypeDeserializer { public ODataResourceDeserializer (ODataDeserializerProvider deserializerProvider) + public virtual void ApplyInstanceAnnotations (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperty (object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyStructuralProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) @@ -3423,6 +3449,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.EntitySelfLinks { public class Microsoft.AspNet.OData.Formatter.Serialization.ODataCollectionSerializer : ODataEdmTypeSerializer { public ODataCollectionSerializer (ODataSerializerProvider serializerProvider) + public ODataCollectionSerializer (ODataSerializerProvider serializerProvider, bool isForAnnotations) protected static void AddTypeNameAnnotationAsNeeded (Microsoft.OData.ODataCollectionValue value, ODataMetadataLevel metadataLevel) public virtual Microsoft.OData.ODataCollectionValue CreateODataCollectionValue (System.Collections.IEnumerable enumerable, Microsoft.OData.Edm.IEdmTypeReference elementType, ODataSerializerContext writeContext) @@ -3493,6 +3520,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSeriali public ODataResourceSerializer (ODataSerializerProvider serializerProvider) public virtual void AppendDynamicProperties (Microsoft.OData.ODataResource resource, SelectExpandNode selectExpandNode, ResourceContext resourceContext) + public virtual void AppendInstanceAnnotations (Microsoft.OData.ODataResource resource, ResourceContext resourceContext) public virtual string CreateETag (ResourceContext resourceContext) public virtual Microsoft.OData.ODataNestedResourceInfo CreateNavigationLink (Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, ResourceContext resourceContext) public virtual Microsoft.OData.ODataAction CreateODataAction (Microsoft.OData.Edm.IEdmAction action, ResourceContext resourceContext) @@ -3514,6 +3542,14 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSeri public virtual void WriteObjectInline (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, ODataSerializerContext writeContext) } +public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceValueSerializer : ODataEdmTypeSerializer { + public ODataResourceValueSerializer (ODataSerializerProvider serializerProvider) + protected ODataResourceValueSerializer (Microsoft.OData.ODataPayloadKind payloadKind, ODataSerializerProvider serializerProvider) + + public virtual Microsoft.OData.ODataValue CreateODataValue (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, ODataSerializerContext writeContext) +} + public class Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerContext { public ODataSerializerContext () public ODataSerializerContext (ResourceContext resource, Microsoft.OData.UriParser.SelectExpandClause selectExpandClause, Microsoft.OData.Edm.IEdmProperty edmProperty) diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl index 2387496313..51ab0dc509 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl @@ -978,6 +978,7 @@ public enum Microsoft.AspNet.OData.Builder.PropertyKind : int { Complex = 1 Dynamic = 5 Enum = 4 + InstanceAnnotations = 6 Navigation = 3 Primitive = 0 } @@ -991,6 +992,15 @@ public interface Microsoft.AspNet.OData.Builder.IEdmTypeConfiguration { string Namespace { public abstract get; } } +public interface Microsoft.AspNet.OData.Builder.IODataInstanceAnnotationContainer { + void AddPropertyAnnotation (string propertyName, string annotationName, object value) + void AddResourceAnnotation (string annotationName, object value) + object GetPropertyAnnotation (string propertyName, string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + object GetResourceAnnotation (string annotationName) + System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + public abstract class Microsoft.AspNet.OData.Builder.NavigationSourceConfiguration { protected NavigationSourceConfiguration () protected NavigationSourceConfiguration (ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) @@ -1177,6 +1187,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[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.Reflection.PropertyInfo InstanceAnnotationsContainer { public get; } System.Nullable`1[[System.Boolean]] IsAbstract { public virtual get; public virtual set; } bool IsOpen { public get; } Microsoft.OData.Edm.EdmTypeKind Kind { public abstract get; } @@ -1187,6 +1198,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration System.Collections.Generic.IEnumerable`1[[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] Properties { public get; } QueryConfiguration QueryConfiguration { public get; public set; } System.Collections.Generic.IList`1[[System.Reflection.PropertyInfo]] RemovedProperties { protected get; } + bool SupportsInstanceAnnotations { public get; } internal virtual void AbstractImpl () public virtual CollectionPropertyConfiguration AddCollectionProperty (System.Reflection.PropertyInfo propertyInfo) @@ -1194,6 +1206,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration 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 void AddInstanceAnnotationContainer (System.Reflection.PropertyInfo propertyInfo) 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) @@ -1232,6 +1245,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 Filter (string[] properties) public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting, string[] properties) public void HasDynamicProperties (Expression`1 propertyExpression) + public void HasInstanceAnnotations (Expression`1 propertyExpression) public NavigationPropertyConfiguration HasMany (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression, Expression`1 referentialConstraintExpression) @@ -1252,13 +1266,13 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) - public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) - public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) + public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) + public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public StructuralTypeConfiguration`1 Select () @@ -1852,6 +1866,17 @@ public sealed class Microsoft.AspNet.OData.Builder.MediaTypeAttribute : System.A public MediaTypeAttribute () } +public sealed class Microsoft.AspNet.OData.Builder.ODataInstanceAnnotationContainer : IODataInstanceAnnotationContainer { + public ODataInstanceAnnotationContainer () + + public virtual void AddPropertyAnnotation (string propertyName, string annotationName, object value) + public virtual void AddResourceAnnotation (string annotationName, object value) + public virtual object GetPropertyAnnotation (string propertyName, string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetPropertyAnnotations (string propertyName) + public virtual object GetResourceAnnotation (string annotationName) + public virtual System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] GetResourceAnnotations () +} + [ AttributeUsageAttribute(), ] @@ -3512,6 +3537,7 @@ public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataPrimitiveDese public class Microsoft.AspNet.OData.Formatter.Deserialization.ODataResourceDeserializer : ODataEdmTypeDeserializer { public ODataResourceDeserializer (ODataDeserializerProvider deserializerProvider) + public virtual void ApplyInstanceAnnotations (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyNestedProperty (object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) public virtual void ApplyStructuralProperties (object resource, ODataResourceWrapper resourceWrapper, Microsoft.OData.Edm.IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) @@ -3594,6 +3620,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.EntitySelfLinks { public class Microsoft.AspNet.OData.Formatter.Serialization.ODataCollectionSerializer : ODataEdmTypeSerializer { public ODataCollectionSerializer (ODataSerializerProvider serializerProvider) + public ODataCollectionSerializer (ODataSerializerProvider serializerProvider, bool isForAnnotations) protected static void AddTypeNameAnnotationAsNeeded (Microsoft.OData.ODataCollectionValue value, ODataMetadataLevel metadataLevel) public virtual Microsoft.OData.ODataCollectionValue CreateODataCollectionValue (System.Collections.IEnumerable enumerable, Microsoft.OData.Edm.IEdmTypeReference elementType, ODataSerializerContext writeContext) @@ -3664,6 +3691,7 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSeriali public ODataResourceSerializer (ODataSerializerProvider serializerProvider) public virtual void AppendDynamicProperties (Microsoft.OData.ODataResource resource, SelectExpandNode selectExpandNode, ResourceContext resourceContext) + public virtual void AppendInstanceAnnotations (Microsoft.OData.ODataResource resource, ResourceContext resourceContext) public virtual string CreateETag (ResourceContext resourceContext) public virtual Microsoft.OData.ODataNestedResourceInfo CreateNavigationLink (Microsoft.OData.Edm.IEdmNavigationProperty navigationProperty, ResourceContext resourceContext) public virtual Microsoft.OData.ODataAction CreateODataAction (Microsoft.OData.Edm.IEdmAction action, ResourceContext resourceContext) @@ -3685,6 +3713,14 @@ public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSeri public virtual void WriteObjectInline (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, ODataSerializerContext writeContext) } +public class Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceValueSerializer : ODataEdmTypeSerializer { + public ODataResourceValueSerializer (ODataSerializerProvider serializerProvider) + protected ODataResourceValueSerializer (Microsoft.OData.ODataPayloadKind payloadKind, ODataSerializerProvider serializerProvider) + + public virtual Microsoft.OData.ODataValue CreateODataValue (object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, ODataSerializerContext writeContext) + public virtual void WriteObject (object graph, System.Type type, Microsoft.OData.ODataMessageWriter messageWriter, ODataSerializerContext writeContext) +} + public class Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerContext { public ODataSerializerContext () public ODataSerializerContext (ResourceContext resource, Microsoft.OData.UriParser.SelectExpandClause selectExpandClause, Microsoft.OData.Edm.IEdmProperty edmProperty)