/// <summary> /// Sets the single instance of <see cref="ClientTypeAnnotation"/> on the given instance of <paramref name="edmType"/>. /// </summary> /// <param name="model">The model the <paramref name="edmType"/> belongs to.</param> /// <param name="edmType">IEdmType instance to set the annotation on.</param> /// <param name="annotation">The annotation to set</param> internal static void SetClientTypeAnnotation(this IEdmModel model, IEdmType edmType, ClientTypeAnnotation annotation) { Debug.Assert(model != null, "model != null"); Debug.Assert(edmType != null, "edmType != null"); Debug.Assert(annotation != null, "annotation != null"); model.SetAnnotationValue<ClientTypeAnnotation>(edmType, annotation); }
/// <summary> /// Creates instance of EntityPropertyMappingInfo class. /// </summary> /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param> /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param> /// <param name="actualPropertyType">ClientType whose property is to be read.</param> public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, Type definingType, ClientTypeAnnotation actualPropertyType) { #endif Debug.Assert(attribute != null, "attribute != null"); Debug.Assert(definingType != null, "definingType != null"); Debug.Assert(actualPropertyType != null, "actualPropertyType != null"); this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualPropertyType; Debug.Assert(!string.IsNullOrEmpty(attribute.SourcePath), "Invalid source path"); this.propertyValuePath = attribute.SourcePath.Split('/'); // Infer the mapping type from the attribute this.isSyndicationMapping = this.attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty; }
/// <summary> /// Initializes a new instance of the <see cref="ClientEdmStructuredValue"/> class. /// </summary> /// <param name="structuredValue">The structured value.</param> /// <param name="model">The model.</param> /// <param name="clientTypeAnnotation">The client type annotation.</param> public ClientEdmStructuredValue(object structuredValue, ClientEdmModel model, ClientTypeAnnotation clientTypeAnnotation) { Debug.Assert(structuredValue != null, "entity != null"); Debug.Assert(model != null, "model != null"); Debug.Assert(clientTypeAnnotation != null, "clientTypeAnnotation != null"); if (clientTypeAnnotation.EdmType.TypeKind == EdmTypeKind.Complex) { // TODO: nullable? this.Type = new EdmComplexTypeReference((IEdmComplexType)clientTypeAnnotation.EdmType, true); } else { Debug.Assert(clientTypeAnnotation.EdmType.TypeKind == EdmTypeKind.Entity, "Only complex and entity values supported"); // TODO: nullable? this.Type = new EdmEntityTypeReference((IEdmEntityType)clientTypeAnnotation.EdmType, true); } this.structuredValue = structuredValue; this.typeAnnotation = clientTypeAnnotation; this.model = model; }
/// <summary> /// Tries to resolve the server type corresponding to the client type. /// </summary> /// <param name="clientTypeAnnotation">The client type annotation.</param> /// <param name="serverType">The server type, if the server type could be resolved.</param> /// <returns>Whether or not the server type could be resolved.</returns> private bool TryToResolveServerType(ClientTypeAnnotation clientTypeAnnotation, out IEdmStructuredType serverType) { Debug.Assert(this.serviceModel != null, "this.serviceModel != null"); Debug.Assert(clientTypeAnnotation != null, "clientTypeAnnotation != null"); var serverTypeName = this.resolveNameFromType(clientTypeAnnotation.ElementType); if (serverTypeName == null) { serverType = null; return false; } serverType = this.serviceModel.FindType(serverTypeName) as IEdmStructuredType; return serverType != null; }
public void Init() { var clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V4); this.typeWithUnserializableProperties = new ClientTypeAnnotation(new EdmEntityType("NS", "Type1"), typeof(EntityWithUnserializableProperties), "NS.Type1", clientEdmModel); this.typeWithPropertiesInStrangeOrder = new ClientTypeAnnotation(new EdmEntityType("NS", "Type2"), typeof(EntityWithPropertiesInStrangeOrder), "NS.Type2", clientEdmModel); }
/// <summary> /// Get the server type name - either from the entity descriptor or using the type resolver. /// </summary> /// <param name="clientTypeAnnotation">Client type annotation.</param> /// <returns>The server type name for the entity.</returns> internal string GetServerTypeName(ClientTypeAnnotation clientTypeAnnotation) { string serverTypeName = this.ResolveNameFromType(clientTypeAnnotation.ElementType); return serverTypeName; }
/// <summary> /// Converts the object to ODataCollectionValue, ODataEntityReferenceLinks, or /// a list of ODataEntry. /// </summary> /// <param name="paramName">The name of the <see cref="UriOperationParameter"/>. Used for error reporting.</param> /// <param name="value">The value of the <see cref="UriOperationParameter"/>.</param> /// <param name="itemTypeAnnotation">The client type annotation of the value.</param> /// <param name="useEntityReference">If true, use entity reference, instead of entity to serialize the parameter.</param> /// <returns>The converted result.</returns> private object ConvertToCollectionValue(string paramName, object value, ClientTypeAnnotation itemTypeAnnotation, bool useEntityReference) { object valueInODataFormat; switch (itemTypeAnnotation.EdmType.TypeKind) { case EdmTypeKind.Primitive: case EdmTypeKind.Enum: case EdmTypeKind.Complex: valueInODataFormat = this.propertyConverter.CreateODataCollection(itemTypeAnnotation.ElementType, null, value, null, false, false); break; case EdmTypeKind.Entity: if (useEntityReference) { var list = value as IEnumerable; var links = (from object o in list select new ODataEntityReferenceLink() { Url = this.requestInfo.EntityTracker.GetEntityDescriptor(o).GetLatestIdentity(), }).ToList(); valueInODataFormat = new ODataEntityReferenceLinks() { Links = links, }; } else { valueInODataFormat = this.propertyConverter.CreateODataEntries( itemTypeAnnotation.ElementType, value); } break; default: throw new NotSupportedException(Strings.Serializer_InvalidCollectionParamterItemType(paramName, itemTypeAnnotation.EdmType.TypeKind)); } return valueInODataFormat; }
/// <summary> /// Applies the values of the specified <paramref name="properties"/> to a given <paramref name="instance"/>. /// </summary> /// <param name="type">Type to which properties will be applied.</param> /// <param name="properties">Properties to assign to the specified <paramref name="instance"/>.</param> /// <param name="instance">Instance on which values will be applied.</param> internal void ApplyDataValues(ClientTypeAnnotation type, IEnumerable<ODataProperty> properties, object instance) { Debug.Assert(type != null, "type != null"); Debug.Assert(properties != null, "properties != null"); Debug.Assert(instance != null, "instance != context"); foreach (var p in properties) { this.ApplyDataValue(type, p, instance); } }
public void Init() { this.serverModel = new EdmModel(); this.serverEntityType = new EdmEntityType("Server.NS", "ServerEntityType"); this.serverEntityTypeName = "Server.NS.ServerEntityType"; this.serverModel.AddElement(this.serverEntityType); this.serverEntityType.AddKeys(this.serverEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); var addressType = new EdmComplexType("Server.NS", "Address"); addressType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); this.serverEntityType.AddStructuralProperty("Address", new EdmComplexTypeReference(addressType, true)); this.serverComplexTypeName = "Server.NS.Address"; var homeAddressType = new EdmComplexType("Server.NS", "HomeAddress", addressType); homeAddressType.AddStructuralProperty("FamilyName", EdmPrimitiveTypeKind.String); this.serverComplexTypeName = "Server.NS.HomeAddress"; var colorType = new EdmEnumType("Server.NS", "Color"); colorType.AddMember(new EdmEnumMember(colorType, "Red", new EdmIntegerConstant(1))); colorType.AddMember(new EdmEnumMember(colorType, "Blue", new EdmIntegerConstant(2))); colorType.AddMember(new EdmEnumMember(colorType, "Green", new EdmIntegerConstant(3))); this.serverEntityType.AddStructuralProperty("Color", new EdmEnumTypeReference(colorType, false)); this.serverEntityType.AddStructuralProperty("Nicknames", new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false)))); this.serverEntityType.AddStructuralProperty("OtherAddresses", new EdmCollectionTypeReference(new EdmCollectionType(new EdmComplexTypeReference(addressType, false)))); this.clientModel = new ClientEdmModel(ODataProtocolVersion.V4); this.clientTypeAnnotation = this.clientModel.GetClientTypeAnnotation(typeof(TestClientEntityType)); this.context = new DataServiceContext(new Uri("http://temp/"), ODataProtocolVersion.V4, this.clientModel); this.context.Format.UseJson(this.serverModel); this.testSubject = new ODataPropertyConverter(new RequestInfo(this.context)); this.context.Format.UseJson(this.serverModel); this.context.ResolveName = t => { if (t == typeof(TestClientEntityType)) { return this.serverEntityTypeName; } if (t == typeof(Address)) { return this.serverComplexTypeName; } return null; }; this.entity = new TestClientEntityType { Id = 1, Name = "foo", Number = 1.23f, Address = new Address(), OpenAddress = new Address(), Nicknames = new string[0], OtherAddresses = new[] { new Address() }, Color = 0 }; this.entityWithDerivedComplexProperty = new TestClientEntityType { Id = 1, Name = "foo", Number = 1.23f, Address = new HomeAddress(), OpenAddress = new Address(), Nicknames = new string[0], OtherAddresses = new[] { new Address() } }; this.entityWithDerivedComplexInCollection = new TestClientEntityType { Id = 1, Name = "foo", Number = 1.23f, Address = new HomeAddress(), OpenAddress = new Address(), Nicknames = new string[0], OtherAddresses = new[] { new Address(), new HomeAddress() } }; }
/// <summary> /// Figures out value to be written in OData-Version HTTP header for the given entity based on features used in this entity. /// </summary> /// <param name="clientType">Entity type for which data service version needs to be determined.</param> /// <returns>Data service version for the given entity and state.</returns> private static Version DetermineRequestVersion(ClientTypeAnnotation clientType) { Debug.Assert(clientType != null, "clientType != null"); Debug.Assert(clientType.IsEntityType, "This method should be called only for entities"); // Determine what the version is based on the client type. Version requestVersion = Util.ODataVersion4; WebUtil.RaiseVersion(ref requestVersion, clientType.GetMetadataVersion()); return requestVersion; }
/// <summary> /// Gets or creates client type annotation. /// </summary> /// <param name="edmType">The EdmType to use for creating client type annotation</param> /// <param name="type">The Clr type to create client type annotation for.</param> /// <returns>Client type annotation</returns> private ClientTypeAnnotation GetOrCreateClientTypeAnnotation(IEdmType edmType, Type type) { ClientTypeAnnotation clientTypeAnnotation; string qualifiedName = type.ToString(); // all that are not built-in types need to be cached: enum, complex, entity if (edmType.TypeKind == EdmTypeKind.Enum || edmType.TypeKind == EdmTypeKind.Complex || edmType.TypeKind == EdmTypeKind.Entity) { lock (this.typeNameToClientTypeAnnotationCache) { if (this.typeNameToClientTypeAnnotationCache.TryGetValue(qualifiedName, out clientTypeAnnotation) && clientTypeAnnotation.ElementType != type) { qualifiedName = type.AssemblyQualifiedName; if (this.typeNameToClientTypeAnnotationCache.TryGetValue(qualifiedName, out clientTypeAnnotation) && clientTypeAnnotation.ElementType != type) { throw c.Error.InvalidOperation(Strings.ClientType_MultipleTypesWithSameName(qualifiedName)); } } if (clientTypeAnnotation == null) { clientTypeAnnotation = new ClientTypeAnnotation(edmType, type, qualifiedName, this); this.typeNameToClientTypeAnnotationCache.Add(qualifiedName, clientTypeAnnotation); } else { Debug.Assert(clientTypeAnnotation.ElementType == type, "existing clientTypeAnnotation.ElementType == type"); } } } else { clientTypeAnnotation = new ClientTypeAnnotation(edmType, type, qualifiedName, this); } return clientTypeAnnotation; }
/// <summary> /// Materializes the link properties if any with the url in the response payload /// </summary> /// <param name="actualType">Actual client type that is getting materialized.</param> /// <param name="entry">MaterializerEntry instance containing all the links that came in the response.</param> private static void ApplyLinkProperties( ClientTypeAnnotation actualType, MaterializerEntry entry) { Debug.Assert(actualType != null, "actualType != null"); Debug.Assert(entry.Entry != null, "entry != null"); if (entry.ShouldUpdateFromPayload) { foreach (var linkProperty in actualType.Properties().Where(p => p.PropertyType == typeof(DataServiceStreamLink))) { string propertyName = linkProperty.PropertyName; StreamDescriptor streamDescriptor; if (entry.EntityDescriptor.TryGetNamedStreamInfo(propertyName, out streamDescriptor)) { // At this time we have materialized the stream link onto the stream descriptor object // we'll always make sure the stream link is the same instance on the entity and the descriptor linkProperty.SetValue(entry.ResolvedObject, streamDescriptor.StreamLink, propertyName, true /*allowAdd*/); } } } }
/// <summary> /// Determines whether a given property should be serialized into an insert or update payload. /// </summary> /// <param name="type">The declaring type of the property.</param> /// <param name="property">The property.</param> /// <returns>Whether or not the property should be serialized.</returns> private static bool ShouldSerializeProperty(ClientTypeAnnotation type, ClientPropertyAnnotation property) { // don't write property if it is a dictionary // don't write mime data member or the mime type member for it // link properties need to be ignored return !property.IsDictionary && property != type.MediaDataMember && !property.IsStreamLinkProperty && (type.MediaDataMember == null || type.MediaDataMember.MimeTypeProperty != property); }
/// <summary> /// Sets the single instance of <see cref="ClientTypeAnnotation"/> on the given instance of <paramref name="edmType"/>. /// </summary> /// <param name="model">The model the <paramref name="edmType"/> belongs to.</param> /// <param name="edmType">IEdmType instance to set the annotation on.</param> /// <param name="annotation">The annotation to set</param> internal static void SetClientTypeAnnotation(this IEdmModel model, IEdmType edmType, ClientTypeAnnotation annotation) { Debug.Assert(model != null, "model != null"); Debug.Assert(edmType != null, "edmType != null"); Debug.Assert(annotation != null, "annotation != null"); model.SetAnnotationValue <ClientTypeAnnotation>(edmType, annotation); }
/// <summary> /// Creates an ODataEntry for the given EntityDescriptor and fills in its ODataLib metadata. /// </summary> /// <param name="entityDescriptor">The entity descriptor.</param> /// <param name="serverTypeName">Name of the server type.</param> /// <param name="entityType">The client-side entity type.</param> /// <param name="clientFormat">The current client format.</param> /// <returns>An odata entry with its metadata filled in.</returns> internal static ODataEntry CreateODataEntry(EntityDescriptor entityDescriptor, string serverTypeName, ClientTypeAnnotation entityType, DataServiceClientFormat clientFormat) { ODataEntry entry = new ODataEntry(); // If the client type name is different from the server type name, then add SerializationTypeNameAnnotation // which tells ODataLib to write the type name in the annotation in the payload. if (entityType.ElementTypeName != serverTypeName) { entry.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = serverTypeName }); } // We always need to write the client type name, since this is the type name used by ODataLib // to resolve the entity type using EdmModel.FindSchemaElement. entry.TypeName = entityType.ElementTypeName; // Continue to send the entry's ID in update payloads in Atom for compatibility with V1-V3, // but for JSON-Light we do not want the extra information on the wire. if (clientFormat.UsingAtom && EntityStates.Modified == entityDescriptor.State) { // <id>http://host/service/entityset(key)</id> entry.Id = entityDescriptor.GetLatestIdentity(); } if (entityDescriptor.IsMediaLinkEntry || entityType.IsMediaLinkEntry) { // Since we are already enabled EnableWcfDataServicesClientBehavior in the writer settings, // setting the MediaResource value will tell ODataLib to write MLE payload, irrespective of // what the metadata says. entry.MediaResource = new ODataStreamReferenceValue(); } return entry; }
/// <summary>Applies a data value to the specified <paramref name="instance"/>.</summary> /// <param name="type">Type to which a property value will be applied.</param> /// <param name="property">Property with value to apply.</param> /// <param name="instance">Instance on which value will be applied.</param> internal void ApplyDataValue(ClientTypeAnnotation type, ODataProperty property, object instance) { Debug.Assert(type != null, "type != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(instance != null, "instance != null"); var prop = type.GetProperty(property.Name, this.MaterializerContext.IgnoreMissingProperties); if (prop == null) { return; } // Is it a collection? (note: property.Properties will be null if the Collection is empty (contains no elements)) Type enumTypeTmp = null; if (prop.IsPrimitiveOrEnumOrComplexCollection) { // Collections must not be null if (property.Value == null) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_NullCollectionNotSupported(property.Name)); } // This happens if the payload contain just primitive value for a Collection property if (property.Value is string) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MixedTextWithComment); } if (property.Value is ODataComplexValue) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidCollectionItem(property.Name)); } // ODataLib already parsed the data and materialized all the primitive types. There is nothing more to materialize // anymore. Only complex type instance and collection instances need to be materialized, but those will be // materialized later on. // We need to materialize items before we change collectionInstance since this may throw. If we tried materializing // after the Collection is wiped or created we would leave the object in half constructed state. object collectionInstance = prop.GetValue(instance); if (collectionInstance == null) { collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionPropertyInstance(property, prop.PropertyType); // allowAdd is false - we need to assign instance as the new property value prop.SetValue(instance, collectionInstance, property.Name, false /* allowAdd? */); } else { // Clear existing Collection prop.ClearBackingICollectionInstance(collectionInstance); } bool isElementNullable = prop.EdmProperty.Type.AsCollection().ElementType().IsNullable; this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( property, collectionInstance, prop.PrimitiveOrComplexCollectionItemType, prop.AddValueToBackingICollectionInstance, isElementNullable); } else if ((enumTypeTmp = Nullable.GetUnderlyingType(prop.NullablePropertyType) ?? prop.NullablePropertyType) != null && enumTypeTmp.IsEnum()) { ODataEnumValue enumValue = property.Value as ODataEnumValue; object tmpValue = EnumValueMaterializationPolicy.MaterializeODataEnumValue(enumTypeTmp, enumValue); // TODO: 1. use EnumValueMaterializationPolicy 2. handle nullable enum property prop.SetValue(instance, tmpValue, property.Name, false /* allowAdd? */); } else { object propertyValue = property.Value; ODataComplexValue complexValue = propertyValue as ODataComplexValue; if (propertyValue != null && complexValue != null) { if (!prop.EdmProperty.Type.IsComplex()) { // The error message is a bit odd, but it's compatible with V1. throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_ExpectingSimpleValue); } // Complex type. bool needToSet = false; ClientEdmModel edmModel = this.MaterializerContext.Model; ClientTypeAnnotation complexType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(prop.PropertyType)); object complexInstance = prop.GetValue(instance); // Validating property inheritance in complexvalue and instance if (prop.PropertyType.Name != property.Name) { complexType = this.MaterializerContext.ResolveTypeForMaterialization(prop.PropertyType, complexValue.TypeName); // recreate complexInstance with derived type complexInstance = null; } if (complexValue.Properties.Any() || complexInstance == null) { complexInstance = this.CreateNewInstance(complexType.EdmTypeReference, complexType.ElementType); needToSet = true; } this.MaterializeDataValues(complexType, complexValue.Properties, this.MaterializerContext.IgnoreMissingProperties); this.ApplyDataValues(complexType, complexValue.Properties, complexInstance); if (needToSet) { prop.SetValue(instance, complexInstance, property.Name, true /* allowAdd? */); } if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) { // Set instance annotation for this complex instance this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(property, complexInstance); } } else { this.MaterializePrimitiveDataValue(prop.NullablePropertyType, property); prop.SetValue(instance, property.GetMaterializedValue(), property.Name, true /* allowAdd? */); } } if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) { // Apply instance annotation for Property this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(property, type.ElementType, instance); } }
/// <summary> /// Creates an ODataEntry using some properties extracted from an entity operation parameter. /// </summary> /// <param name="clientTypeAnnotation">The client type annotation of the entity.</param> /// <param name="parameterValue">The Clr value of the entity.</param> /// <returns>The ODataEntry created.</returns> private ODataEntry CreateODataEntryFromEntityOperationParameter(ClientTypeAnnotation clientTypeAnnotation, object parameterValue) { ClientPropertyAnnotation[] properties = new ClientPropertyAnnotation[0]; if (sendOption == EntityParameterSendOption.SendOnlySetProperties) { try { var descripter = this.requestInfo.Context.EntityTracker.GetEntityDescriptor(parameterValue); properties = clientTypeAnnotation.PropertiesToSerialize().Where(p => descripter.PropertiesToSerialize.Contains(p.PropertyName)).ToArray(); } catch (InvalidOperationException) { throw Error.InvalidOperation(Strings.Context_MustBeUsedWith("EntityParameterSendOption.SendOnlySetProperties", "DataServiceCollection")); } } return this.propertyConverter.CreateODataEntry(clientTypeAnnotation.ElementType, parameterValue, properties); }
/// <summary> /// Materializes the primitive data values in the given list of <paramref name="values"/>. /// </summary> /// <param name="actualType">Actual type for properties being materialized.</param> /// <param name="values">List of values to materialize.</param> /// <param name="ignoreMissingProperties"> /// Whether properties missing from the client types should be ignored. /// </param> /// <remarks> /// Values are materialized in-place withi each <see cref="ODataProperty"/> /// instance. /// </remarks> internal void MaterializeDataValues(ClientTypeAnnotation actualType, IEnumerable<ODataProperty> values, bool ignoreMissingProperties) { Debug.Assert(actualType != null, "actualType != null"); Debug.Assert(values != null, "values != null"); foreach (var odataProperty in values) { if (odataProperty.Value is ODataStreamReferenceValue) { continue; } string propertyName = odataProperty.Name; var property = actualType.GetProperty(propertyName, ignoreMissingProperties); // may throw if (property == null) { continue; } // we should throw if the property type on the client does not match with the property type in the server // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa if (ClientTypeUtil.TypeOrElementTypeIsEntity(property.PropertyType)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(property.EntityCollectionItemType ?? property.PropertyType)); } if (property.IsKnownType) { // Note: MaterializeDataValue materializes only properties of primitive types. Materialization specific // to complex types and collections is done later. this.MaterializePrimitiveDataValue(property.NullablePropertyType, odataProperty); } } }