Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataWeb / Server / System / Data / Services / Serializers / JsonDeserializer.cs / 3 / JsonDeserializer.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a deserializer for json content. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Serializers { using System; using System.Collections; using System.Diagnostics; using System.Globalization; using System.IO; using System.Data.Services.Providers; ////// Provides a deserializer for json content. /// internal class JsonDeserializer : Deserializer { ///json reader which reads json content private readonly JsonReader jsonReader; ///Initializes a new /// input stream reader from which json content must be read. /// indicates whether this is a update operation or not /// Data service for which the deserializer will act. /// Tracker to use for modifications. internal JsonDeserializer(StreamReader request, bool update, IDataService dataService, UpdateTracker tracker) : base(update, dataService, tracker) { Debug.Assert(request != null, "request != null"); this.jsonReader = new JsonReader(request); } ///for the specified stream. returns the content format for the deserializer protected override ContentFormat ContentFormat { get { return ContentFormat.Json; } } ////// Converts the given value to the expected type as per json reader rules /// Make sure these rules are in [....] with jsonwriter. /// /// value to the converted /// name of the property whose value is getting converted /// clr type to which the value needs to be converted to ///object which is in [....] with the properties type internal static object ConvertValues(object value, string propertyName, Type typeToBeConverted) { if (value == null) { return null; } Type propertyType = Nullable.GetUnderlyingType(typeToBeConverted) ?? typeToBeConverted; try { string stringValue = value as string; if (stringValue != null) { if (propertyType == typeof(byte[])) { return Convert.FromBase64String(stringValue); } else if (propertyType == typeof(System.Data.Linq.Binary)) { return new System.Data.Linq.Binary(Convert.FromBase64String(stringValue)); } else if (propertyType == typeof(System.Xml.Linq.XElement)) { return System.Xml.Linq.XElement.Parse(stringValue, System.Xml.Linq.LoadOptions.PreserveWhitespace); } else if (propertyType == typeof(Guid)) { return new Guid(stringValue); } else { // For string types, we support conversion to all possible primitive types return Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture); } } else if (value is Int32) { int intValue = (int)value; if (propertyType == typeof(Int16)) { return Convert.ToInt16(intValue); } else if (propertyType == typeof(Byte)) { return Convert.ToByte(intValue); } else if (propertyType == typeof(SByte)) { return Convert.ToSByte(intValue); } else if (propertyType == typeof(Single)) { return Convert.ToSingle(intValue); } else if (propertyType == typeof(Double)) { return Convert.ToDouble(intValue); } else if (propertyType == typeof(Decimal) || propertyType == typeof(Int64)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInConvertingNumericValues(propertyName)); } } else if (value is Double) { Double doubleValue = (Double)value; if (propertyType == typeof(Single)) { return Convert.ToSingle(doubleValue); } } } catch (Exception e) { if (WebUtil.IsCatchableExceptionType(e)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInConvertingPropertyValue(propertyName, propertyType.Name), e); } } // otherwise just return the value without doing any conversion return value; } ////// Assumes the payload to represent a single object and processes accordingly /// /// info about the object being created ///the newly formed object that the payload represents protected override object CreateSingleObject(SegmentInfo segmentInfo) { object jsonObject = this.jsonReader.ReadValue(); bool existingRelationship; return this.CreateObject(jsonObject, segmentInfo, true /*topLevel*/, out existingRelationship); } ///Provides an opportunity to clean-up resources. /// /// Whether the call is being made from an explicit call to /// IDisposable.Dispose() rather than through the finalizer. /// protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { this.jsonReader.Dispose(); } } ////// Get the resource referred by the uri in the payload /// ///resource referred by the uri in the payload. protected override string GetLinkUriFromPayload() { object jsonObject = this.jsonReader.ReadValue(); // Otherwise top level json content must be Hashtable, since we don't allow multiple inserts // at the top level Hashtable jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } string uri = ReadUri(jsonObjectHashtable); if (String.IsNullOrEmpty(uri)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_MissingUriForLinkOperation); } return uri; } ////// Gets the array list object /// /// object representing json array ///strongly type array list object that json object represents private static ArrayList GetArrayList(object jsonObject) { ArrayList arrayList = jsonObject as ArrayList; if (arrayList == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceSetPropertyMustBeArray); } return arrayList; } ////// Verifies if the given element value is a deferred element or not /// /// element value ///true if this value is a deferred content else returns false private static bool IsDeferredElement(object element) { Hashtable hashtable = element as Hashtable; if (hashtable != null && hashtable.Count == 1 && hashtable[XmlConstants.JsonDeferredString] != null) { return true; } return false; } ////// Returns true if the payload is correct for the top level non-entity target. /// /// json object representing the data in the payload. /// information about the last segment in the request uri. /// resource object as specified in the payload. ///returns true if the payload is correct for non-entity resource. private static bool HandleTopLevelNonEntityProperty(Hashtable jsonObject, SegmentInfo segment, out object resource) { Debug.Assert(jsonObject != null, "jsonObject != null"); Debug.Assert(segment != null, "segment != null"); resource = null; if (segment.TargetKind == RequestTargetKind.Primitive || #if ASTORIA_OPEN_OBJECT segment.TargetKind == RequestTargetKind.OpenProperty || #endif segment.TargetKind == RequestTargetKind.ComplexObject) { if (jsonObject.Count == 1 && jsonObject.ContainsKey(segment.Identifier)) { // For open property, assume it to be a primitive or complex payload. // If its targeting an entity, then the type must be specified resource = jsonObject[segment.Identifier]; return true; } else #if ASTORIA_OPEN_OBJECT if (segment.TargetKind != RequestTargetKind.OpenProperty) #endif { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } } // its an entity resource payload return false; } ////// Read the uri from the json object /// /// metadata object which contains the uri. ///returns the uri as specified in the object. private static string ReadUri(Hashtable metadata) { string uri = null; // Get the uri for the metadata element object uriObject = metadata[XmlConstants.JsonUriString]; if (uriObject != null) { uri = uriObject as string; if (uri == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidUriMetadata); } } return uri; } ////// Create the object given the list of the properties. One of the properties will be __metadata property /// which will contain type information /// /// list of the properties and values specified in the payload /// info about the object being created /// true if the current object is a top level one, otherwise false /// does this resource already binded to its parent ///instance of the object created private object CreateObject(object jsonObject, SegmentInfo segmentInfo, bool topLevel, out bool existingRelationship) { this.RecurseEnter(); existingRelationship = true; bool existingResource = true; object resource = null; ResourceType resourceType; Hashtable jsonObjectHashtable; if (topLevel) { // Every top level json content must be Hashtable - primitive, complex or entity jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } object nonEntityResource; if (HandleTopLevelNonEntityProperty(jsonObjectHashtable, segmentInfo, out nonEntityResource)) { // if the segment refers to primitive type, then return the value if (segmentInfo.TargetKind == RequestTargetKind.Primitive || #if ASTORIA_OPEN_OBJECT (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(nonEntityResource.GetType())) || #endif nonEntityResource == null) { return nonEntityResource; } jsonObject = nonEntityResource; } } else if ( jsonObject == null || #if ASTORIA_OPEN_OBJECT (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(jsonObject.GetType())) || #endif segmentInfo.TargetKind == RequestTargetKind.Primitive) { // For reference properties, we do not know if there was already some relationship setup // By setting it to null, we are unbinding the old relationship and hence existing relationship // is false // For open properties, if its null, there is no way we will be able to deduce the type existingRelationship = false; return jsonObject; } // Otherwise top level json content must be Hashtable, since we don't allow multiple inserts // at the top level jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } ResourceType targetResourceType = null; #if ASTORIA_OPEN_OBJECT if (segmentInfo.TargetKind != RequestTargetKind.OpenProperty) #endif { targetResourceType = this.Service.Provider.GetResourceType(segmentInfo.TargetElementType); Debug.Assert(targetResourceType != null, "Should be able to resolve type for well known segments"); Debug.Assert( targetResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType || targetResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "targetType must be entity type or complex type"); } // Get the type and uri from the metadata element, if specified string uri; bool metadataElementSpecified; resourceType = this.GetTypeAndUriFromMetadata( jsonObjectHashtable, targetResourceType, topLevel, out uri, out metadataElementSpecified); Debug.Assert((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.Primitive) || uri != null, "Either uri or resource type must be specified"); this.CheckAndIncrementObjectCount(); if ((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.ComplexType) || uri != null) { // For inserts/updates, its okay not to specify anything in the payload. // Someone might just want to create a entity with default values or // merge nothing or replace everything with default values. if (this.Update) { if (!topLevel) { if (metadataElementSpecified && jsonObjectHashtable.Count > 1 || !metadataElementSpecified) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepUpdateNotSupported); } else if (uri == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_UriMissingForUpdateForDeepUpdates); } } if (topLevel) { // Checking for merge vs replace semantics // Only checking for top level resource entity // since we don't support update of deep resources resource = CreateObjectFromUri( resourceType, segmentInfo, (RequestDescription)null, true /*checkETag*/, true /*checkForNull*/, this.Service.RequestParams.AstoriaHttpVerb == AstoriaVerbs.PUT /*replaceResource*/); } else { // case of binding at the first level. existingRelationship = false; return CreateObjectFromUri(resourceType, segmentInfo, uri, false /*checkETag*/, false /*checkForNull*/); } } else { // For insert, its a new resource that is getting created or an existing resource // getting binded. Either case, its a new relationship. existingRelationship = false; // For POST operations, the following rules holds true: // 1> If the uri is specified for navigation properties and no other property is specified, then its a bind operation. // Otherwise, ignore the uri and insert the new resource. if (uri != null) { if (segmentInfo.TargetSource == RequestTargetSource.Property && jsonObjectHashtable.Count == 1) { this.RecurseLeave(); return CreateObjectFromUri(resourceType, null, uri, false /*checkETag*/, false /*checkForNull*/); } } } } Debug.Assert(resourceType != null, "resourceType != null"); if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert(resource == null, "resource == null"); resource = this.Service.Provider.CreateResource(null, resourceType.FullName); existingResource = false; } else if (!this.Update) { Debug.Assert(resource == null, "resource == null"); if (segmentInfo.TargetKind == RequestTargetKind.Resource) { // check for append rights whenever we need to create a resource this.Service.Configuration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend); resource = this.Service.Provider.CreateResource(segmentInfo.TargetContainer.Name, resourceType.FullName); this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Add); } #if ASTORIA_OPEN_OBJECT else { Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.OpenProperty, "segmentInfo.TargetKind == RequestTargetKind.OpenProperty"); ResourceContainer container = this.Service.Provider.GetContainerForResourceType(resourceType.Type); resource = this.Service.Provider.CreateResource(container.Name, resourceType.FullName); } #endif existingResource = false; } bool changed = this.PopulateProperties(jsonObjectHashtable, resource, resourceType); // For put operations, you need not specify any property and that means reset all the properties. // hence for put operations, change is always true. changed = changed || this.Service.RequestParams.AstoriaHttpVerb == AstoriaVerbs.PUT; if (changed && existingResource && segmentInfo.TargetContainer != null) { this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Change); } this.RecurseLeave(); return resource; } ////// Populate the properties of the given resource /// /// hashtable containing property name and values /// instance of the resource whose properties needs to be populated /// resource type whose properties needs to be populated ///true if any properties were set; false otherwise. private bool PopulateProperties(Hashtable jsonObject, object resource, ResourceType parentResourceType) { // Update all the properties specified in the payload. // Don't touch the properties which are not specified. Its upto the provider to interpret // the meaning of things which are not specified bool changed = false; foreach (string propertyName in jsonObject.Keys) { // Ignore the metadata property if (propertyName == XmlConstants.JsonMetadataString) { continue; } // Check if the property exists and try and set the value ResourceProperty resourceProperty = parentResourceType.TryResolvePropertyName(propertyName); if ( #if ASTORIA_OPEN_OBJECT parentResourceType.OpenTypeKind != OpenTypeKind.CompletelyOpen && #endif resourceProperty == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(propertyName, parentResourceType.FullName)); } // Get the property value, set it appropriately, and mark the object as changed. object propertyValue = jsonObject[propertyName]; bool existingRelationship; // If its a open property if (resourceProperty == null) { #if ASTORIA_OPEN_OBJECT this.HandleOpenTypeProperties(resource, propertyName, propertyValue); #endif changed = true; } else if (resourceProperty.TypeKind == ResourceTypeKind.ComplexType) { SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); segmentInfo.TargetKind = RequestTargetKind.ComplexObject; propertyValue = this.CreateObject(propertyValue, segmentInfo, false /*topLevel*/, out existingRelationship); SetPropertyValue(resourceProperty, resource, propertyValue, ContentFormat.Json, this.Service.Provider); changed = true; } else if (resourceProperty.TypeKind == ResourceTypeKind.Primitive) { // Ignoring the value of key properties in PUT payload if (!this.Update || !resourceProperty.IsOfKind(ResourcePropertyKind.Key)) { SetPropertyValue(resourceProperty, resource, propertyValue, ContentFormat.Json, this.Service.Provider); } changed = true; } else { Debug.Assert(ResourceTypeKind.EntityType == resourceProperty.TypeKind, "only expecting nav properties"); if (IsDeferredElement(propertyValue)) { // Skip the deferred element continue; } else { Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); if (resourceProperty.Kind == ResourcePropertyKind.ResourceReference) { SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); segmentInfo.TargetKind = RequestTargetKind.Resource; // For navigation property, allow both inserts and binding in this case propertyValue = this.CreateObject(propertyValue, segmentInfo, false /*topLevel*/, out existingRelationship); if (!existingRelationship) { this.Service.Provider.SetReference(resource, resourceProperty.Name, propertyValue); } changed = true; } else if (resourceProperty.Kind == ResourcePropertyKind.ResourceSetReference) { if (propertyValue == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CannotSetCollectionsToNull(propertyName)); } ArrayList resourceCollection = GetArrayList(propertyValue); SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); foreach (object resourceObject in resourceCollection) { object resourceInstance = this.CreateObject(resourceObject, segmentInfo, false /*topLevel*/, out existingRelationship); Debug.Assert(resourceInstance != null, "resourceInstance != null"); if (!existingRelationship) { this.Service.Provider.AddReferenceToCollection(resource, resourceProperty.Name, resourceInstance); } changed = true; } } } } } return changed; } #if ASTORIA_OPEN_OBJECT ////// Handle the open type property /// /// parent resource to which the open property belongs to /// name of the property /// value of the property private void HandleOpenTypeProperties(object parentResource, string propertyName, object propertyValue) { bool existingRelationship; if (IsDeferredElement(propertyValue)) { // Skip the deferred element return; } // Check if its a collection or not ArrayList arrayList = propertyValue as ArrayList; if (arrayList == null) { SegmentInfo openPropertySegmentInfo = CreateSegment(null, propertyName, true /* singleResult */); propertyValue = this.CreateObject( propertyValue, openPropertySegmentInfo, false /*topLevel*/, out existingRelationship); // Resolve the type of the value if (propertyValue == null || WebUtil.IsPrimitiveType(propertyValue.GetType())) { // For open properties, just set the value since we only support primitive type properties as // open properties SetOpenPropertyValue(parentResource, propertyName, propertyValue, this.Service.Provider); } else { ResourceType openPropertyResourceType = this.Service.Provider.GetResourceType(propertyValue.GetType()); if (openPropertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { this.Service.Provider.SetValue(parentResource, propertyName, propertyValue); } else { Debug.Assert(openPropertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "resource must be of entity type"); Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); if (!existingRelationship) { this.Service.Provider.SetReference(parentResource, propertyName, propertyValue); } } } } else { Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); SegmentInfo openPropertySegmentInfo = CreateSegment(null, propertyName, false /* singleResult */); foreach (object openPropertyValue in arrayList) { propertyValue = this.CreateObject( openPropertyValue, openPropertySegmentInfo, false /*topLevel*/, out existingRelationship); if (propertyValue == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CannotSetCollectionsToNull(propertyName)); } if (!existingRelationship) { this.Service.Provider.AddReferenceToCollection(parentResource, propertyName, propertyValue); } } } } #endif ////// Gets the type and uri specified in the metadata object in the given json object. /// /// jsonObject which contains the metadata information /// expected type that this segment of the uri is targeted to /// whether the segment represents the top level object. /// uri as specified in the metadata object. If its not specified, this is set to null /// returns true if the metadata element was specified ///typename and uri as specified in the metadata object private ResourceType GetTypeAndUriFromMetadata( Hashtable jsonObject, ResourceType expectedType, bool topLevel, out string uri, out bool metadataElementSpecified) { metadataElementSpecified = false; // Get the metadata object object metadataObject = jsonObject[XmlConstants.JsonMetadataString]; ResourceType targetType = expectedType; bool typeNameSpecified = false; uri = null; if (metadataObject != null) { metadataElementSpecified = true; Hashtable metadata = metadataObject as Hashtable; if (metadata == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidMetadataContent); } // Get the type information from the metadata object. if the type name is not specified, // then return the expectedType as the target type object objectTypeName = metadata[XmlConstants.JsonTypeString]; if (objectTypeName != null) { string typeName = objectTypeName as string; if (string.IsNullOrEmpty(typeName)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidTypeMetadata); } // Resolve resource type name targetType = this.Service.Provider.TryResolveTypeName(typeName); if (targetType == null || targetType.ResourceTypeKind == ResourceTypeKind.Primitive) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeName(typeName)); } if (expectedType != null && !expectedType.Type.IsAssignableFrom(targetType.Type)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeSpecified(typeName, expectedType.FullName)); } typeNameSpecified = true; } uri = JsonDeserializer.ReadUri(metadata); } // Type information is optional for bind operations. // Top level operations cannot be bind operations, since uri need to have $links // for top level bind operations and that's a different code path. // For bind operations, uri must be specified and nothing else should be specified. bool bindOperation = !topLevel && uri != null && jsonObject.Count == 1; // type name must be specified for POST or PUT/MERGE operations. if (!typeNameSpecified) { if (!bindOperation) { if (expectedType == null) { // For open properties, you must specify the type information throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_MissingTypeInformationForOpenTypeProperties); } else if (expectedType.HasDerivedTypes) { // For types that take part in inheritance, type information must be specified. throw DataServiceException.CreateBadRequestError(Strings.BadRequest_TypeInformationMustBeSpecifiedForInhertiance); } } else { // If the type name is not specified, we should set the type name to null, since in case of inheritance, // we don't want to guess the type information. targetType = null; } } return targetType; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a deserializer for json content. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Serializers { using System; using System.Collections; using System.Diagnostics; using System.Globalization; using System.IO; using System.Data.Services.Providers; ////// Provides a deserializer for json content. /// internal class JsonDeserializer : Deserializer { ///json reader which reads json content private readonly JsonReader jsonReader; ///Initializes a new /// input stream reader from which json content must be read. /// indicates whether this is a update operation or not /// Data service for which the deserializer will act. /// Tracker to use for modifications. internal JsonDeserializer(StreamReader request, bool update, IDataService dataService, UpdateTracker tracker) : base(update, dataService, tracker) { Debug.Assert(request != null, "request != null"); this.jsonReader = new JsonReader(request); } ///for the specified stream. returns the content format for the deserializer protected override ContentFormat ContentFormat { get { return ContentFormat.Json; } } ////// Converts the given value to the expected type as per json reader rules /// Make sure these rules are in [....] with jsonwriter. /// /// value to the converted /// name of the property whose value is getting converted /// clr type to which the value needs to be converted to ///object which is in [....] with the properties type internal static object ConvertValues(object value, string propertyName, Type typeToBeConverted) { if (value == null) { return null; } Type propertyType = Nullable.GetUnderlyingType(typeToBeConverted) ?? typeToBeConverted; try { string stringValue = value as string; if (stringValue != null) { if (propertyType == typeof(byte[])) { return Convert.FromBase64String(stringValue); } else if (propertyType == typeof(System.Data.Linq.Binary)) { return new System.Data.Linq.Binary(Convert.FromBase64String(stringValue)); } else if (propertyType == typeof(System.Xml.Linq.XElement)) { return System.Xml.Linq.XElement.Parse(stringValue, System.Xml.Linq.LoadOptions.PreserveWhitespace); } else if (propertyType == typeof(Guid)) { return new Guid(stringValue); } else { // For string types, we support conversion to all possible primitive types return Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture); } } else if (value is Int32) { int intValue = (int)value; if (propertyType == typeof(Int16)) { return Convert.ToInt16(intValue); } else if (propertyType == typeof(Byte)) { return Convert.ToByte(intValue); } else if (propertyType == typeof(SByte)) { return Convert.ToSByte(intValue); } else if (propertyType == typeof(Single)) { return Convert.ToSingle(intValue); } else if (propertyType == typeof(Double)) { return Convert.ToDouble(intValue); } else if (propertyType == typeof(Decimal) || propertyType == typeof(Int64)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInConvertingNumericValues(propertyName)); } } else if (value is Double) { Double doubleValue = (Double)value; if (propertyType == typeof(Single)) { return Convert.ToSingle(doubleValue); } } } catch (Exception e) { if (WebUtil.IsCatchableExceptionType(e)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ErrorInConvertingPropertyValue(propertyName, propertyType.Name), e); } } // otherwise just return the value without doing any conversion return value; } ////// Assumes the payload to represent a single object and processes accordingly /// /// info about the object being created ///the newly formed object that the payload represents protected override object CreateSingleObject(SegmentInfo segmentInfo) { object jsonObject = this.jsonReader.ReadValue(); bool existingRelationship; return this.CreateObject(jsonObject, segmentInfo, true /*topLevel*/, out existingRelationship); } ///Provides an opportunity to clean-up resources. /// /// Whether the call is being made from an explicit call to /// IDisposable.Dispose() rather than through the finalizer. /// protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { this.jsonReader.Dispose(); } } ////// Get the resource referred by the uri in the payload /// ///resource referred by the uri in the payload. protected override string GetLinkUriFromPayload() { object jsonObject = this.jsonReader.ReadValue(); // Otherwise top level json content must be Hashtable, since we don't allow multiple inserts // at the top level Hashtable jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } string uri = ReadUri(jsonObjectHashtable); if (String.IsNullOrEmpty(uri)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_MissingUriForLinkOperation); } return uri; } ////// Gets the array list object /// /// object representing json array ///strongly type array list object that json object represents private static ArrayList GetArrayList(object jsonObject) { ArrayList arrayList = jsonObject as ArrayList; if (arrayList == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceSetPropertyMustBeArray); } return arrayList; } ////// Verifies if the given element value is a deferred element or not /// /// element value ///true if this value is a deferred content else returns false private static bool IsDeferredElement(object element) { Hashtable hashtable = element as Hashtable; if (hashtable != null && hashtable.Count == 1 && hashtable[XmlConstants.JsonDeferredString] != null) { return true; } return false; } ////// Returns true if the payload is correct for the top level non-entity target. /// /// json object representing the data in the payload. /// information about the last segment in the request uri. /// resource object as specified in the payload. ///returns true if the payload is correct for non-entity resource. private static bool HandleTopLevelNonEntityProperty(Hashtable jsonObject, SegmentInfo segment, out object resource) { Debug.Assert(jsonObject != null, "jsonObject != null"); Debug.Assert(segment != null, "segment != null"); resource = null; if (segment.TargetKind == RequestTargetKind.Primitive || #if ASTORIA_OPEN_OBJECT segment.TargetKind == RequestTargetKind.OpenProperty || #endif segment.TargetKind == RequestTargetKind.ComplexObject) { if (jsonObject.Count == 1 && jsonObject.ContainsKey(segment.Identifier)) { // For open property, assume it to be a primitive or complex payload. // If its targeting an entity, then the type must be specified resource = jsonObject[segment.Identifier]; return true; } else #if ASTORIA_OPEN_OBJECT if (segment.TargetKind != RequestTargetKind.OpenProperty) #endif { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } } // its an entity resource payload return false; } ////// Read the uri from the json object /// /// metadata object which contains the uri. ///returns the uri as specified in the object. private static string ReadUri(Hashtable metadata) { string uri = null; // Get the uri for the metadata element object uriObject = metadata[XmlConstants.JsonUriString]; if (uriObject != null) { uri = uriObject as string; if (uri == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidUriMetadata); } } return uri; } ////// Create the object given the list of the properties. One of the properties will be __metadata property /// which will contain type information /// /// list of the properties and values specified in the payload /// info about the object being created /// true if the current object is a top level one, otherwise false /// does this resource already binded to its parent ///instance of the object created private object CreateObject(object jsonObject, SegmentInfo segmentInfo, bool topLevel, out bool existingRelationship) { this.RecurseEnter(); existingRelationship = true; bool existingResource = true; object resource = null; ResourceType resourceType; Hashtable jsonObjectHashtable; if (topLevel) { // Every top level json content must be Hashtable - primitive, complex or entity jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } object nonEntityResource; if (HandleTopLevelNonEntityProperty(jsonObjectHashtable, segmentInfo, out nonEntityResource)) { // if the segment refers to primitive type, then return the value if (segmentInfo.TargetKind == RequestTargetKind.Primitive || #if ASTORIA_OPEN_OBJECT (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(nonEntityResource.GetType())) || #endif nonEntityResource == null) { return nonEntityResource; } jsonObject = nonEntityResource; } } else if ( jsonObject == null || #if ASTORIA_OPEN_OBJECT (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(jsonObject.GetType())) || #endif segmentInfo.TargetKind == RequestTargetKind.Primitive) { // For reference properties, we do not know if there was already some relationship setup // By setting it to null, we are unbinding the old relationship and hence existing relationship // is false // For open properties, if its null, there is no way we will be able to deduce the type existingRelationship = false; return jsonObject; } // Otherwise top level json content must be Hashtable, since we don't allow multiple inserts // at the top level jsonObjectHashtable = jsonObject as Hashtable; if (jsonObjectHashtable == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity); } ResourceType targetResourceType = null; #if ASTORIA_OPEN_OBJECT if (segmentInfo.TargetKind != RequestTargetKind.OpenProperty) #endif { targetResourceType = this.Service.Provider.GetResourceType(segmentInfo.TargetElementType); Debug.Assert(targetResourceType != null, "Should be able to resolve type for well known segments"); Debug.Assert( targetResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType || targetResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "targetType must be entity type or complex type"); } // Get the type and uri from the metadata element, if specified string uri; bool metadataElementSpecified; resourceType = this.GetTypeAndUriFromMetadata( jsonObjectHashtable, targetResourceType, topLevel, out uri, out metadataElementSpecified); Debug.Assert((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.Primitive) || uri != null, "Either uri or resource type must be specified"); this.CheckAndIncrementObjectCount(); if ((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.ComplexType) || uri != null) { // For inserts/updates, its okay not to specify anything in the payload. // Someone might just want to create a entity with default values or // merge nothing or replace everything with default values. if (this.Update) { if (!topLevel) { if (metadataElementSpecified && jsonObjectHashtable.Count > 1 || !metadataElementSpecified) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepUpdateNotSupported); } else if (uri == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_UriMissingForUpdateForDeepUpdates); } } if (topLevel) { // Checking for merge vs replace semantics // Only checking for top level resource entity // since we don't support update of deep resources resource = CreateObjectFromUri( resourceType, segmentInfo, (RequestDescription)null, true /*checkETag*/, true /*checkForNull*/, this.Service.RequestParams.AstoriaHttpVerb == AstoriaVerbs.PUT /*replaceResource*/); } else { // case of binding at the first level. existingRelationship = false; return CreateObjectFromUri(resourceType, segmentInfo, uri, false /*checkETag*/, false /*checkForNull*/); } } else { // For insert, its a new resource that is getting created or an existing resource // getting binded. Either case, its a new relationship. existingRelationship = false; // For POST operations, the following rules holds true: // 1> If the uri is specified for navigation properties and no other property is specified, then its a bind operation. // Otherwise, ignore the uri and insert the new resource. if (uri != null) { if (segmentInfo.TargetSource == RequestTargetSource.Property && jsonObjectHashtable.Count == 1) { this.RecurseLeave(); return CreateObjectFromUri(resourceType, null, uri, false /*checkETag*/, false /*checkForNull*/); } } } } Debug.Assert(resourceType != null, "resourceType != null"); if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert(resource == null, "resource == null"); resource = this.Service.Provider.CreateResource(null, resourceType.FullName); existingResource = false; } else if (!this.Update) { Debug.Assert(resource == null, "resource == null"); if (segmentInfo.TargetKind == RequestTargetKind.Resource) { // check for append rights whenever we need to create a resource this.Service.Configuration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend); resource = this.Service.Provider.CreateResource(segmentInfo.TargetContainer.Name, resourceType.FullName); this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Add); } #if ASTORIA_OPEN_OBJECT else { Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.OpenProperty, "segmentInfo.TargetKind == RequestTargetKind.OpenProperty"); ResourceContainer container = this.Service.Provider.GetContainerForResourceType(resourceType.Type); resource = this.Service.Provider.CreateResource(container.Name, resourceType.FullName); } #endif existingResource = false; } bool changed = this.PopulateProperties(jsonObjectHashtable, resource, resourceType); // For put operations, you need not specify any property and that means reset all the properties. // hence for put operations, change is always true. changed = changed || this.Service.RequestParams.AstoriaHttpVerb == AstoriaVerbs.PUT; if (changed && existingResource && segmentInfo.TargetContainer != null) { this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Change); } this.RecurseLeave(); return resource; } ////// Populate the properties of the given resource /// /// hashtable containing property name and values /// instance of the resource whose properties needs to be populated /// resource type whose properties needs to be populated ///true if any properties were set; false otherwise. private bool PopulateProperties(Hashtable jsonObject, object resource, ResourceType parentResourceType) { // Update all the properties specified in the payload. // Don't touch the properties which are not specified. Its upto the provider to interpret // the meaning of things which are not specified bool changed = false; foreach (string propertyName in jsonObject.Keys) { // Ignore the metadata property if (propertyName == XmlConstants.JsonMetadataString) { continue; } // Check if the property exists and try and set the value ResourceProperty resourceProperty = parentResourceType.TryResolvePropertyName(propertyName); if ( #if ASTORIA_OPEN_OBJECT parentResourceType.OpenTypeKind != OpenTypeKind.CompletelyOpen && #endif resourceProperty == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(propertyName, parentResourceType.FullName)); } // Get the property value, set it appropriately, and mark the object as changed. object propertyValue = jsonObject[propertyName]; bool existingRelationship; // If its a open property if (resourceProperty == null) { #if ASTORIA_OPEN_OBJECT this.HandleOpenTypeProperties(resource, propertyName, propertyValue); #endif changed = true; } else if (resourceProperty.TypeKind == ResourceTypeKind.ComplexType) { SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); segmentInfo.TargetKind = RequestTargetKind.ComplexObject; propertyValue = this.CreateObject(propertyValue, segmentInfo, false /*topLevel*/, out existingRelationship); SetPropertyValue(resourceProperty, resource, propertyValue, ContentFormat.Json, this.Service.Provider); changed = true; } else if (resourceProperty.TypeKind == ResourceTypeKind.Primitive) { // Ignoring the value of key properties in PUT payload if (!this.Update || !resourceProperty.IsOfKind(ResourcePropertyKind.Key)) { SetPropertyValue(resourceProperty, resource, propertyValue, ContentFormat.Json, this.Service.Provider); } changed = true; } else { Debug.Assert(ResourceTypeKind.EntityType == resourceProperty.TypeKind, "only expecting nav properties"); if (IsDeferredElement(propertyValue)) { // Skip the deferred element continue; } else { Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); if (resourceProperty.Kind == ResourcePropertyKind.ResourceReference) { SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); segmentInfo.TargetKind = RequestTargetKind.Resource; // For navigation property, allow both inserts and binding in this case propertyValue = this.CreateObject(propertyValue, segmentInfo, false /*topLevel*/, out existingRelationship); if (!existingRelationship) { this.Service.Provider.SetReference(resource, resourceProperty.Name, propertyValue); } changed = true; } else if (resourceProperty.Kind == ResourcePropertyKind.ResourceSetReference) { if (propertyValue == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CannotSetCollectionsToNull(propertyName)); } ArrayList resourceCollection = GetArrayList(propertyValue); SegmentInfo segmentInfo = CreateSegment(resourceProperty, resourceProperty.Name, true /* singleResult */); foreach (object resourceObject in resourceCollection) { object resourceInstance = this.CreateObject(resourceObject, segmentInfo, false /*topLevel*/, out existingRelationship); Debug.Assert(resourceInstance != null, "resourceInstance != null"); if (!existingRelationship) { this.Service.Provider.AddReferenceToCollection(resource, resourceProperty.Name, resourceInstance); } changed = true; } } } } } return changed; } #if ASTORIA_OPEN_OBJECT ////// Handle the open type property /// /// parent resource to which the open property belongs to /// name of the property /// value of the property private void HandleOpenTypeProperties(object parentResource, string propertyName, object propertyValue) { bool existingRelationship; if (IsDeferredElement(propertyValue)) { // Skip the deferred element return; } // Check if its a collection or not ArrayList arrayList = propertyValue as ArrayList; if (arrayList == null) { SegmentInfo openPropertySegmentInfo = CreateSegment(null, propertyName, true /* singleResult */); propertyValue = this.CreateObject( propertyValue, openPropertySegmentInfo, false /*topLevel*/, out existingRelationship); // Resolve the type of the value if (propertyValue == null || WebUtil.IsPrimitiveType(propertyValue.GetType())) { // For open properties, just set the value since we only support primitive type properties as // open properties SetOpenPropertyValue(parentResource, propertyName, propertyValue, this.Service.Provider); } else { ResourceType openPropertyResourceType = this.Service.Provider.GetResourceType(propertyValue.GetType()); if (openPropertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { this.Service.Provider.SetValue(parentResource, propertyName, propertyValue); } else { Debug.Assert(openPropertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "resource must be of entity type"); Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); if (!existingRelationship) { this.Service.Provider.SetReference(parentResource, propertyName, propertyValue); } } } } else { Deserializer.CheckForBindingInPutOperations(this.Service.RequestParams.AstoriaHttpVerb); SegmentInfo openPropertySegmentInfo = CreateSegment(null, propertyName, false /* singleResult */); foreach (object openPropertyValue in arrayList) { propertyValue = this.CreateObject( openPropertyValue, openPropertySegmentInfo, false /*topLevel*/, out existingRelationship); if (propertyValue == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CannotSetCollectionsToNull(propertyName)); } if (!existingRelationship) { this.Service.Provider.AddReferenceToCollection(parentResource, propertyName, propertyValue); } } } } #endif ////// Gets the type and uri specified in the metadata object in the given json object. /// /// jsonObject which contains the metadata information /// expected type that this segment of the uri is targeted to /// whether the segment represents the top level object. /// uri as specified in the metadata object. If its not specified, this is set to null /// returns true if the metadata element was specified ///typename and uri as specified in the metadata object private ResourceType GetTypeAndUriFromMetadata( Hashtable jsonObject, ResourceType expectedType, bool topLevel, out string uri, out bool metadataElementSpecified) { metadataElementSpecified = false; // Get the metadata object object metadataObject = jsonObject[XmlConstants.JsonMetadataString]; ResourceType targetType = expectedType; bool typeNameSpecified = false; uri = null; if (metadataObject != null) { metadataElementSpecified = true; Hashtable metadata = metadataObject as Hashtable; if (metadata == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidMetadataContent); } // Get the type information from the metadata object. if the type name is not specified, // then return the expectedType as the target type object objectTypeName = metadata[XmlConstants.JsonTypeString]; if (objectTypeName != null) { string typeName = objectTypeName as string; if (string.IsNullOrEmpty(typeName)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidTypeMetadata); } // Resolve resource type name targetType = this.Service.Provider.TryResolveTypeName(typeName); if (targetType == null || targetType.ResourceTypeKind == ResourceTypeKind.Primitive) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeName(typeName)); } if (expectedType != null && !expectedType.Type.IsAssignableFrom(targetType.Type)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidTypeSpecified(typeName, expectedType.FullName)); } typeNameSpecified = true; } uri = JsonDeserializer.ReadUri(metadata); } // Type information is optional for bind operations. // Top level operations cannot be bind operations, since uri need to have $links // for top level bind operations and that's a different code path. // For bind operations, uri must be specified and nothing else should be specified. bool bindOperation = !topLevel && uri != null && jsonObject.Count == 1; // type name must be specified for POST or PUT/MERGE operations. if (!typeNameSpecified) { if (!bindOperation) { if (expectedType == null) { // For open properties, you must specify the type information throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_MissingTypeInformationForOpenTypeProperties); } else if (expectedType.HasDerivedTypes) { // For types that take part in inheritance, type information must be specified. throw DataServiceException.CreateBadRequestError(Strings.BadRequest_TypeInformationMustBeSpecifiedForInhertiance); } } else { // If the type name is not specified, we should set the type name to null, since in case of inheritance, // we don't want to guess the type information. targetType = null; } } return targetType; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- AvTrace.cs
- codemethodreferenceexpression.cs
- PasswordBox.cs
- TimeoutTimer.cs
- CaretElement.cs
- Size3DValueSerializer.cs
- TextProviderWrapper.cs
- GenericWebPart.cs
- DataTableCollection.cs
- CommandPlan.cs
- RegionData.cs
- Activator.cs
- MruCache.cs
- DataSourceProvider.cs
- FormatControl.cs
- peersecurityelement.cs
- MetadataHelper.cs
- ProbeRequestResponseAsyncResult.cs
- ProbeMatches11.cs
- GridViewAutomationPeer.cs
- DocumentXmlWriter.cs
- ComboBoxAutomationPeer.cs
- CreateSequenceResponse.cs
- ToolStripHighContrastRenderer.cs
- WebEncodingValidatorAttribute.cs
- EntityDataSourceUtil.cs
- TemplatedMailWebEventProvider.cs
- IndicFontClient.cs
- RuntimeHandles.cs
- WebPartEventArgs.cs
- RegisteredHiddenField.cs
- PartitionedStream.cs
- PEFileReader.cs
- XslTransform.cs
- WinEventQueueItem.cs
- XmlAggregates.cs
- Material.cs
- CodeConditionStatement.cs
- CompilationUtil.cs
- DesignerSerializationOptionsAttribute.cs
- DispatcherTimer.cs
- NativeMethods.cs
- MaskedTextProvider.cs
- XmlDataProvider.cs
- EventLogPermissionEntry.cs
- DetailsView.cs
- SerializationObjectManager.cs
- WrappingXamlSchemaContext.cs
- StateMachineHelpers.cs
- PerformanceCounterPermissionEntry.cs
- CustomTypeDescriptor.cs
- Adorner.cs
- Setter.cs
- RegistryKey.cs
- Style.cs
- StateMachineTimers.cs
- AccessedThroughPropertyAttribute.cs
- ImmComposition.cs
- TakeQueryOptionExpression.cs
- mediaclock.cs
- ShapeTypeface.cs
- WebPartConnectionCollection.cs
- ProtocolsConfiguration.cs
- DesignerAdapterUtil.cs
- Border.cs
- FunctionCommandText.cs
- ToolStripSplitButton.cs
- DataStreamFromComStream.cs
- smtpconnection.cs
- RawUIStateInputReport.cs
- DashStyles.cs
- GridToolTip.cs
- BitmapPalettes.cs
- WebPartConnection.cs
- SqlCacheDependency.cs
- StylusButtonCollection.cs
- ColorEditor.cs
- ComponentDispatcher.cs
- XmlSerializerNamespaces.cs
- UnauthorizedWebPart.cs
- Encoder.cs
- EventMappingSettingsCollection.cs
- CollectionEditVerbManager.cs
- DoubleStorage.cs
- DbConnectionClosed.cs
- HtmlInputButton.cs
- ISO2022Encoding.cs
- ReliableRequestSessionChannel.cs
- codemethodreferenceexpression.cs
- LoadedOrUnloadedOperation.cs
- NewExpression.cs
- TextDecoration.cs
- DataGridRowAutomationPeer.cs
- HealthMonitoringSectionHelper.cs
- DefaultPropertiesToSend.cs
- MultiByteCodec.cs
- CodeDomDesignerLoader.cs
- LocalFileSettingsProvider.cs
- Size3D.cs
- sqlmetadatafactory.cs