internal static DataServiceProtocolVersion CalculateEntitySetUriSegmentRequestVersion(ODataRequest request, EntitySet entitySet, DataServiceProtocolVersion maxProtocolVersion, DataServiceProtocolVersion maxDataServiceVersion, string contentType) { bool processTypeMetadata = false; DataServiceProtocolVersion entitySetSegmentVersion = DataServiceProtocolVersion.V4; IEnumerable <EntityType> entityTypes = VersionHelper.GetEntityTypes(entitySet); if (request.GetEffectiveVerb().IsUpdateVerb() || request.GetEffectiveVerb() == HttpVerb.Post) { // Typically for all posts there is some type of payload so we should analyze the metadata if (request.GetEffectiveVerb() == HttpVerb.Post) { processTypeMetadata = true; } else if (request.PreferHeaderApplies(maxProtocolVersion)) { processTypeMetadata = true; } // Whenever there is an update operation and EPM is involved we need to check the metadata version if (entityTypes.SelectMany(et => et.Annotations.OfType <PropertyMappingAnnotation>()).Where(fma => fma.KeepInContent == false).Any()) { processTypeMetadata = true; } } if (processTypeMetadata) { entitySetSegmentVersion = entitySetSegmentVersion.IncreaseVersionIfRequired(entitySet.CalculateMinEntityPropertyMappingVersion(VersionCalculationType.Request, contentType, maxProtocolVersion, maxDataServiceVersion)); } return(entitySetSegmentVersion); }
/// <summary> /// Calculates the version based on the ODataUri provided /// </summary> /// <param name="request">Request to calculate from</param> /// <param name="maxProtocolVersion">Max Protocol version of the server</param> /// <returns>Data Service Protocol Version</returns> public DataServiceProtocolVersion CalculateMinRequestVersion(ODataRequest request, DataServiceProtocolVersion maxProtocolVersion) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(maxProtocolVersion != DataServiceProtocolVersion.Unspecified, "Max protocol version cannot be unspecified when calculating the MinVersion"); string contentType = request.GetHeaderValueIfExists(HttpHeaders.ContentType); DataServiceProtocolVersion dataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.DataServiceVersion)); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); HttpVerb effectiveVerb = request.GetEffectiveVerb(); if (contentType == null) { contentType = MimeTypes.Any; } // No real request payload for Delete so its automatically version 1 if (request.GetEffectiveVerb() == HttpVerb.Delete) { return(DataServiceProtocolVersion.V4); } if (effectiveVerb.IsUpdateVerb() || effectiveVerb == HttpVerb.Post) { EntitySet entitySet = null; if (request.Uri.TryGetExpectedEntitySet(out entitySet)) { // Determine if the operation and Uri combined yields something that we need to look at the metadata to determine the version or not bool processTypeMetadata = false; // Typically for all posts there is some type of payload so we should analyze the metadata if (effectiveVerb == HttpVerb.Post) { processTypeMetadata = true; } else if (dataServiceVersion != DataServiceProtocolVersion.Unspecified && request.PreferHeaderApplies(maxProtocolVersion)) { processTypeMetadata = true; } IEnumerable <EntityType> entityTypes = VersionHelper.GetEntityTypes(entitySet); // Whenever there is an update operation and EPM is involved we need to check the metadata version if (entityTypes.SelectMany(et => et.Annotations.OfType <PropertyMappingAnnotation>()).Where(fma => fma.KeepInContent == false).Any()) { processTypeMetadata = true; } if (processTypeMetadata) { return(VersionHelper.GetMaximumVersion(entityTypes.Select(et => et.CalculateEntityPropertyMappingProtocolVersion(VersionCalculationType.Request, contentType, maxProtocolVersion, maxDataServiceVersion)).ToArray())); } } return(DataServiceProtocolVersion.V4); } return(VersionHelper.CalculateUriMinRequestProtocolVersion(request.Uri, contentType, maxProtocolVersion, maxDataServiceVersion)); }
/// <summary> /// Calculates the version based on the ODataUri provided /// </summary> /// <param name="request">Request to calculate from</param> /// <param name="maxProtocolVersion">Max Protocol version of the server</param> /// <returns>Data Service Protocol Version</returns> public DataServiceProtocolVersion CalculateMinRequestVersion(ODataRequest request, DataServiceProtocolVersion maxProtocolVersion) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(maxProtocolVersion != DataServiceProtocolVersion.Unspecified, "Max protocol version cannot be unspecified when calculating the MinVersion"); string contentType = request.GetHeaderValueIfExists(HttpHeaders.ContentType); DataServiceProtocolVersion dataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.DataServiceVersion)); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); HttpVerb effectiveVerb = request.GetEffectiveVerb(); if (contentType == null) { contentType = MimeTypes.Any; } // No real request payload for Delete so its automatically version 1 if (request.GetEffectiveVerb() == HttpVerb.Delete) { return DataServiceProtocolVersion.V4; } if (effectiveVerb.IsUpdateVerb() || effectiveVerb == HttpVerb.Post) { EntitySet entitySet = null; if (request.Uri.TryGetExpectedEntitySet(out entitySet)) { // Determine if the operation and Uri combined yields something that we need to look at the metadata to determine the version or not bool processTypeMetadata = false; // Typically for all posts there is some type of payload so we should analyze the metadata if (effectiveVerb == HttpVerb.Post) { processTypeMetadata = true; } else if (dataServiceVersion != DataServiceProtocolVersion.Unspecified && request.PreferHeaderApplies(maxProtocolVersion)) { processTypeMetadata = true; } IEnumerable<EntityType> entityTypes = VersionHelper.GetEntityTypes(entitySet); // Whenever there is an update operation and EPM is involved we need to check the metadata version if (entityTypes.SelectMany(et => et.Annotations.OfType<PropertyMappingAnnotation>()).Where(fma => fma.KeepInContent == false).Any()) { processTypeMetadata = true; } if (processTypeMetadata) { return VersionHelper.GetMaximumVersion(entityTypes.Select(et => et.CalculateEntityPropertyMappingProtocolVersion(VersionCalculationType.Request, contentType, maxProtocolVersion, maxDataServiceVersion)).ToArray()); } } return DataServiceProtocolVersion.V4; } return VersionHelper.CalculateUriMinRequestProtocolVersion(request.Uri, contentType, maxProtocolVersion, maxDataServiceVersion); }
/// <summary> /// Gets the pre and post update representations of the entity updated by the given request /// </summary> /// <param name="request">The request</param> /// <param name="beforeUpdate">The entity before the update</param> /// <param name="afterUpdate">The entity after the update</param> public void GetUpdatedEntity(ODataRequest request, out QueryStructuralValue beforeUpdate, out QueryStructuralValue afterUpdate) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(this.requestsBegun.Contains(request), "Cannot use GetUpdatedEntity before calling Begin"); ExceptionUtilities.Assert(request.GetEffectiveVerb().IsUpdateVerb(), "Cannot use GetUpdatedEntity on non update requests"); KeyValuePair <QueryStructuralValue, QueryStructuralValue> beforeAndAfter; if (!this.updatedEntityCache.TryGetValue(request, out beforeAndAfter)) { var entityUri = request.Uri.ScopeToEntity(); var entity = (QueryStructuralValue)this.Evaluator.Evaluate(entityUri, false, false); beforeUpdate = this.QueryValueCopier.PerformDeepCopy(entity); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntity(c, entity)); afterUpdate = entity; beforeAndAfter = new KeyValuePair <QueryStructuralValue, QueryStructuralValue>(beforeUpdate, afterUpdate); this.updatedEntityCache[request] = beforeAndAfter; } else { beforeUpdate = beforeAndAfter.Key; afterUpdate = beforeAndAfter.Value; } }
internal string CalculateExpectedETag(ODataRequest request, ODataResponse response) { // error responses should never have an ETag if (response.StatusCode.IsError()) { return(null); } // $ref never involves ETags if (request.Uri.IsEntityReferenceLink()) { return(null); } var verb = request.GetEffectiveVerb(); if (verb == HttpVerb.Get) { return(this.CalculateExpectedETagForRetrieve(request)); } if (verb == HttpVerb.Delete) { // TODO: Server should write ETag header in response to property-value deletes return(null); } if (verb == HttpVerb.Post) { if (request.Uri.IsServiceOperation()) { return(this.CalculateExpectedETagForRetrieve(request)); } if (!request.Uri.IsEntitySet()) { return(null); } using (this.Context.Begin(request)) { var insertedEntity = this.Context.GetInsertedEntity(request, response); return(this.LiteralConverter.ConstructWeakETag(insertedEntity)); } } return(this.CalculateExpectedETagForUpdate(request)); }
internal static bool IsStreamRequest(ODataRequest request) { if (request.Uri.IsNamedStream() || request.Uri.IsMediaResource()) { return(true); } if (request.GetEffectiveVerb() == HttpVerb.Post && request.Uri.IsEntitySet()) { EntitySet entitySet; ExceptionUtilities.Assert(request.Uri.TryGetExpectedEntitySet(out entitySet), "Could not infer entity set from uri"); return(entitySet.EntityType.HasStream()); } return(false); }
private void VerifyStoreData(ODataRequest request, ODataResponse response, QueryStructuralValue storeValue) { string contentType; ExceptionUtilities.Assert(request.Headers.TryGetValue(HttpHeaders.ContentType, out contentType), "Could not get Content-Type header from request"); if (request.Uri.IsNamedStream()) { string streamName = request.Uri.Segments.OfType <NamedStreamSegment>().Last().Name; var streamValue = storeValue.GetStreamValue(streamName); this.VerifyStream(streamValue, contentType, request, response); return; } bool isInsert = request.GetEffectiveVerb() == HttpVerb.Post; bool isMediaResource = request.Uri.IsMediaResource(); if (isInsert) { EntitySet expectedEntitySet; if (request.Uri.TryGetExpectedEntitySet(out expectedEntitySet)) { isMediaResource = expectedEntitySet.EntityType.HasStream(); } } if (isMediaResource) { var streamValue = storeValue.GetDefaultStreamValue(); this.VerifyStream(streamValue, contentType, request, response); } else { if (isInsert) { this.VerifyTypeNameForInsert(request, response, storeValue); } var formatStrategy = this.FormatSelector.GetStrategy(contentType, request.Uri); var primitiveComparer = formatStrategy.GetPrimitiveComparer(); // TODO: verify relationships // TODO: verify PUT vs PATCH semantics this.VerifyPropertyValues(request, primitiveComparer, storeValue); } }
private void GetUpdatedEntityBeforeAndAfter(ODataRequest request, ODataResponse response, out QueryStructuralValue beforeUpdate, out QueryStructuralValue afterUpdate) { using (this.Context.Begin(request)) { if (request.GetEffectiveVerb() == HttpVerb.Post) { afterUpdate = this.Context.GetInsertedEntity(request, response); ExceptionUtilities.CheckObjectNotNull(afterUpdate, "Structural value returned by GetInsertedEntity unexpectedly null"); beforeUpdate = afterUpdate.Type.NullValue; } else { this.Context.GetUpdatedEntity(request, out beforeUpdate, out afterUpdate); ExceptionUtilities.CheckObjectNotNull(beforeUpdate, "Before-update structural value returned by GetUpdatedEntity unexpectedly null"); ExceptionUtilities.CheckObjectNotNull(afterUpdate, "After-update structural value returned by GetUpdatedEntity unexpectedly null"); } } }
/// <summary> /// Gets the entity inserted by the given request. /// </summary> /// <param name="request">The request</param> /// <param name="response">The response</param> /// <returns>The inserted entity</returns> public QueryStructuralValue GetInsertedEntity(ODataRequest request, ODataResponse response) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.CheckArgumentNotNull(response, "response"); ExceptionUtilities.Assert(this.requestsBegun.Contains(request), "Cannot use GetInsertedEntity before calling Begin"); ExceptionUtilities.Assert(request.GetEffectiveVerb() == HttpVerb.Post, "Cannot use GetInsertedEntity on non POST requests"); QueryStructuralValue insertedEntity; if (!this.insertedEntityCache.TryGetValue(request, out insertedEntity)) { EntitySet entitySet; ExceptionUtilities.Assert(request.Uri.TryGetExpectedEntitySet(out entitySet), "Could not infer entity set from URI"); var entitySetUri = new ODataUri(new EntitySetSegment(entitySet)); var beforeSync = this.Evaluator.Evaluate(entitySetUri, false, false) as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(beforeSync, "Could not evaluate entity set '{0}' before syncing", entitySet.Name); // create a shallow copy beforeSync = beforeSync.Type.CreateCollectionWithValues(beforeSync.Elements); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntireEntitySet(c, entitySet.Name)); var afterSync = this.Evaluator.Evaluate(entitySetUri, false, false) as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(afterSync, "Could not evaluate entity set '{0}' after syncing", entitySet.Name); // TODO: handle deep insert (using location header or response payload) var newElements = afterSync.Elements.Except(beforeSync.Elements).OfType <QueryStructuralValue>().ToList(); this.insertedEntityCache[request] = insertedEntity = newElements.Single(); // update the parent entity, if there is one var segments = request.Uri.Segments; var lastNavigationSegment = segments.OfType <NavigationSegment>().LastOrDefault(); if (lastNavigationSegment != null) { var segmentsBeforeNavigation = segments.TakeWhile(t => t != lastNavigationSegment); var parentEntity = (QueryStructuralValue)this.Evaluator.Evaluate(new ODataUri(segmentsBeforeNavigation), false, false); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntity(c, parentEntity)); } } return(insertedEntity); }
internal bool TryCalculateODataUriProcessingError(ODataRequest request, DataServiceProtocolVersion maxDataServiceVersion, DataServiceProtocolVersion maxProtocolVersion, string acceptType, out ExpectedErrorMessage expectedErrorMessage) { expectedErrorMessage = null; if (request.Uri.IsEntity() || request.Uri.IsEntitySet() || request.Uri.IsServiceOperation()) { bool isEntityFromNonOperationQuery = request.GetEffectiveVerb() == HttpVerb.Get && !request.Uri.IsServiceOperation(); bool isEntityFromOperation = request.Uri.IsServiceOperation() && (request.Uri.IsEntity() || request.Uri.IsEntitySet()); // Do an EPM on all returning sets for the response if (isEntityFromNonOperationQuery || isEntityFromOperation) { EntitySet startingEntitySet = null; if (request.Uri.TryGetExpectedEntitySet(out startingEntitySet)) { DataServiceProtocolVersion minVersion = startingEntitySet.CalculateEntitySetProtocolVersion(acceptType, VersionCalculationType.Response, maxProtocolVersion, maxDataServiceVersion); // Check Epm First if (maxDataServiceVersion != DataServiceProtocolVersion.Unspecified && minVersion > maxDataServiceVersion) { expectedErrorMessage = new ExpectedErrorMessage(MaxDataServiceVersionTooLow, maxDataServiceVersion.ConvertToHeaderFormat(), minVersion.ConvertToIntegerFormat(), "0"); return(true); } } // we need to check the root Entity Set first, then any/all, and later the Expand foreach (EntitySet entitySet in request.Uri.GetIncludingExpandsSets()) { DataServiceProtocolVersion minVersion = entitySet.CalculateEntitySetProtocolVersion(acceptType, VersionCalculationType.Response, maxProtocolVersion, maxDataServiceVersion); // Check Epm First if (maxDataServiceVersion != DataServiceProtocolVersion.Unspecified && minVersion > maxDataServiceVersion) { expectedErrorMessage = new ExpectedErrorMessage(MaxDataServiceVersionTooLow, maxDataServiceVersion.ConvertToHeaderFormat(), minVersion.ConvertToIntegerFormat(), "0"); return(true); } } } } return(false); }
/// <summary> /// Returns true if this is an update request /// </summary> /// <param name="request">The request being verified</param> /// <returns>Whether or not this verifier applies to the request</returns> public bool Applies(ODataRequest request) { if (request.Uri.LastSegment is FunctionSegment) { return(false); } if (request.Uri.IsServiceOperation()) { return(false); } if (request.Uri.IsBatch()) { return(false); } var verb = request.GetEffectiveVerb(); return(verb != HttpVerb.Delete && verb != HttpVerb.Get); }
/// <summary> /// Calculates the version based on the ODataUri provided /// </summary> /// <param name="request">Request to calculate from</param> /// <param name="maxProtocolVersion">Max Protocol version of the server</param> /// <returns>Data Service Protocol Version</returns> public DataServiceProtocolVersion CalculateMinResponseVersion(ODataRequest request, DataServiceProtocolVersion maxProtocolVersion) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(maxProtocolVersion != DataServiceProtocolVersion.Unspecified, "Max protocol version cannot be unspecified when calculating the MinVersion"); string contentType = request.GetHeaderValueIfExists(HttpHeaders.Accept); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); HttpVerb effectiveVerb = request.GetEffectiveVerb(); if (contentType == null) { contentType = MimeTypes.Any; } if (effectiveVerb == HttpVerb.Post || effectiveVerb.IsUpdateVerb()) { DataServiceProtocolVersion version = DataServiceProtocolVersion.V4; string preferHeaderValue = request.GetHeaderValueIfExists(HttpHeaders.Prefer); // Bump to Version 3 if prefer header is specified and its > V3 server if (preferHeaderValue != null && request.PreferHeaderApplies(maxProtocolVersion)) { version = version.IncreaseVersionIfRequired(DataServiceProtocolVersion.V4); } // for insert or update, versioning is specific to the type EntityType expectedEntityType; if (TryGetExpectedType(request, out expectedEntityType)) { version = VersionHelper.IncreaseVersionIfRequired(version, VersionHelper.CalculateProtocolVersion(expectedEntityType, contentType, VersionCalculationType.Response, maxProtocolVersion, maxDataServiceVersion)); } return(version); } return(VersionHelper.CalculateUriResponseMinProtocolVersion(request.Uri, contentType, maxProtocolVersion, maxDataServiceVersion)); }
/// <summary> /// Calculates the version based on the ODataUri provided /// </summary> /// <param name="request">Request to calculate from</param> /// <param name="maxProtocolVersion">Max Protocol version of the server</param> /// <returns>Data Service Protocol Version</returns> public DataServiceProtocolVersion CalculateMinResponseVersion(ODataRequest request, DataServiceProtocolVersion maxProtocolVersion) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(maxProtocolVersion != DataServiceProtocolVersion.Unspecified, "Max protocol version cannot be unspecified when calculating the MinVersion"); string contentType = request.GetHeaderValueIfExists(HttpHeaders.Accept); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); HttpVerb effectiveVerb = request.GetEffectiveVerb(); if (contentType == null) { contentType = MimeTypes.Any; } if (effectiveVerb == HttpVerb.Post || effectiveVerb.IsUpdateVerb()) { DataServiceProtocolVersion version = DataServiceProtocolVersion.V4; string preferHeaderValue = request.GetHeaderValueIfExists(HttpHeaders.Prefer); // Bump to Version 3 if prefer header is specified and its > V3 server if (preferHeaderValue != null && request.PreferHeaderApplies(maxProtocolVersion)) { version = version.IncreaseVersionIfRequired(DataServiceProtocolVersion.V4); } // for insert or update, versioning is specific to the type EntityType expectedEntityType; if (TryGetExpectedType(request, out expectedEntityType)) { version = VersionHelper.IncreaseVersionIfRequired(version, VersionHelper.CalculateProtocolVersion(expectedEntityType, contentType, VersionCalculationType.Response, maxProtocolVersion, maxDataServiceVersion)); } return version; } return VersionHelper.CalculateUriResponseMinProtocolVersion(request.Uri, contentType, maxProtocolVersion, maxDataServiceVersion); }
private void VerifyStoreData(ODataRequest request, ODataResponse response, QueryStructuralValue storeValue) { string contentType; ExceptionUtilities.Assert(request.Headers.TryGetValue(HttpHeaders.ContentType, out contentType), "Could not get Content-Type header from request"); if (request.Uri.IsNamedStream()) { string streamName = request.Uri.Segments.OfType<NamedStreamSegment>().Last().Name; var streamValue = storeValue.GetStreamValue(streamName); this.VerifyStream(streamValue, contentType, request, response); return; } bool isInsert = request.GetEffectiveVerb() == HttpVerb.Post; bool isMediaResource = request.Uri.IsMediaResource(); if (isInsert) { EntitySet expectedEntitySet; if (request.Uri.TryGetExpectedEntitySet(out expectedEntitySet)) { isMediaResource = expectedEntitySet.EntityType.HasStream(); } } if (isMediaResource) { var streamValue = storeValue.GetDefaultStreamValue(); this.VerifyStream(streamValue, contentType, request, response); } else { if (isInsert) { this.VerifyTypeNameForInsert(request, response, storeValue); } var formatStrategy = this.FormatSelector.GetStrategy(contentType, request.Uri); var primitiveComparer = formatStrategy.GetPrimitiveComparer(); // TODO: verify relationships // TODO: verify PUT vs PATCH semantics this.VerifyPropertyValues(request, primitiveComparer, storeValue); } }
internal string CalculateExpectedETag(ODataRequest request, ODataResponse response) { // error responses should never have an ETag if (response.StatusCode.IsError()) { return null; } // $ref never involves ETags if (request.Uri.IsEntityReferenceLink()) { return null; } var verb = request.GetEffectiveVerb(); if (verb == HttpVerb.Get) { return this.CalculateExpectedETagForRetrieve(request); } if (verb == HttpVerb.Delete) { // TODO: Server should write ETag header in response to property-value deletes return null; } if (verb == HttpVerb.Post) { if (request.Uri.IsServiceOperation()) { return this.CalculateExpectedETagForRetrieve(request); } if (!request.Uri.IsEntitySet()) { return null; } using (this.Context.Begin(request)) { var insertedEntity = this.Context.GetInsertedEntity(request, response); return this.LiteralConverter.ConstructWeakETag(insertedEntity); } } return this.CalculateExpectedETagForUpdate(request); }
private DataServiceProtocolVersion CalculateDataServiceProtocolVersion(ODataRequest request, ODataResponse response) { DataServiceProtocolVersion dataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.DataServiceVersion)); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); var responseContentType = response.GetHeaderValueIfExists(HttpHeaders.ContentType); if (responseContentType != null) { if (responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightNonStreaming, StringComparison.OrdinalIgnoreCase) || responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightStreaming, StringComparison.OrdinalIgnoreCase)) { return DataServiceProtocolVersion.V4; } } if (response.StatusCode.IsError()) { return DataServiceProtocolVersion.V4; } DataServiceProtocolVersion expectedVersion = DataServiceProtocolVersion.V4; // Apply minDsv if MPV > V2 if (maxDataServiceVersion != DataServiceProtocolVersion.Unspecified && this.maxProtocolVersion >= maxDataServiceVersion) { expectedVersion = maxDataServiceVersion; } else { expectedVersion = this.maxProtocolVersion; } // If body of a response is empty, the version is V1 unless it has prefer header. if (!this.IsResponseBodyEmpty(response)) { if (request.Uri.IsMetadata()) { // metadata payloads are not handled by the normal payload element visitor, but the response header will match the model version exactly expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.ModelVersionCalculator.CalculateProtocolVersion(this.model)); } else { // GET requests are versioned based on the URI because type information is not known until serialization if (request.GetEffectiveVerb() == HttpVerb.Get || request.Uri.IsServiceOperation() || request.Uri.IsAction()) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.UriVersionCalculator.CalculateProtocolVersion(request.Uri, response.Headers[HttpHeaders.ContentType], this.maxProtocolVersion, dataServiceVersion, maxDataServiceVersion)); } // Post and update requests are versioned based on the specific instance if (response.RootElement != null) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.PayloadElementVersionCalculator.CalculateProtocolVersion(response.RootElement, response.Headers[HttpHeaders.ContentType], this.maxProtocolVersion, maxDataServiceVersion)); } } } else { if (request.Uri.IsAction()) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.UriVersionCalculator.CalculateProtocolVersion(request.Uri, response.GetHeaderValueIfExists(HttpHeaders.ContentType), this.maxProtocolVersion, dataServiceVersion, maxDataServiceVersion)); } } // NOTE: the prefer verifier will ensure that this header is present if it should be, so our only concern here // is that the version is >= V3 if it is present if (response.Headers.ContainsKey(HttpHeaders.PreferenceApplied)) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, DataServiceProtocolVersion.V4); } return expectedVersion; }
/// <summary> /// Returns true if this is a DELETE request /// </summary> /// <param name="request">The request being verified</param> /// <returns>Whether or not this verifier applies to the request</returns> public bool Applies(ODataRequest request) { return(request.GetEffectiveVerb() == HttpVerb.Delete); }
/// <summary> /// Verifies response to request with prefer header /// </summary> /// <param name="request">The request to verify</param> /// <param name="response">The response to verify</param> public override void Verify(ODataRequest request, ODataResponse response) { base.Verify(request, response); string preferAppliedHeaderValue; string preferHeaderValue = null; bool preferIsValid = request.Headers.TryGetValue(HttpHeaders.Prefer, out preferHeaderValue) && validPreferValues.Contains(preferHeaderValue); // if the prefer header is present, valid, and applies to the request... if (preferIsValid && request.PreferHeaderApplies(this.maxProtocolVersion)) { // ensure the preference-applied header is there this.Assert( response.Headers.TryGetValue(HttpHeaders.PreferenceApplied, out preferAppliedHeaderValue), "Missing Preference-Applied header in the response", request, response); // ensure the value of preference-applied matches the prefer header sent this.Assert( preferHeaderValue == preferAppliedHeaderValue, "The Preference-Applied Header value on response is different from request", request, response); // verify whether the content matches the preference if (preferAppliedHeaderValue.Equals(HttpHeaders.ReturnContent)) { // if the URI refers to a property value, it may be empty if the value is empty // we will verify elsewhere that the value matches the expected property value if (!request.Uri.IsPropertyValue()) { this.AssertResponseIsNonEmpty(request, response); } } else if (preferAppliedHeaderValue.Equals(HttpHeaders.ReturnNoContent)) { this.AssertResponseIsEmpty(request, response); } } else { // no preference-applied header should be present this.Assert( !response.Headers.TryGetValue(HttpHeaders.PreferenceApplied, out preferAppliedHeaderValue), "Unexpected Preference-Applied header in the response", request, response); // the content should match the default behavior if (request.PreferHeaderApplies(this.maxProtocolVersion)) { if (request.GetEffectiveVerb() == HttpVerb.Post) { this.AssertResponseIsNonEmpty(request, response); } else if (request.Uri.IsEntity()) { this.AssertResponseIsEmpty(request, response); } } } }
/// <summary> /// Returns true if this is an update request /// </summary> /// <param name="request">The request being verified</param> /// <returns>Whether or not this verifier applies to the request</returns> public bool Applies(ODataRequest request) { if (request.Uri.LastSegment is FunctionSegment) { return false; } if (request.Uri.IsServiceOperation()) { return false; } if (request.Uri.IsBatch()) { return false; } var verb = request.GetEffectiveVerb(); return verb != HttpVerb.Delete && verb != HttpVerb.Get; }
/// <summary> /// Gets the entity inserted by the given request. /// </summary> /// <param name="request">The request</param> /// <param name="response">The response</param> /// <returns>The inserted entity</returns> public QueryStructuralValue GetInsertedEntity(ODataRequest request, ODataResponse response) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.CheckArgumentNotNull(response, "response"); ExceptionUtilities.Assert(this.requestsBegun.Contains(request), "Cannot use GetInsertedEntity before calling Begin"); ExceptionUtilities.Assert(request.GetEffectiveVerb() == HttpVerb.Post, "Cannot use GetInsertedEntity on non POST requests"); QueryStructuralValue insertedEntity; if (!this.insertedEntityCache.TryGetValue(request, out insertedEntity)) { EntitySet entitySet; ExceptionUtilities.Assert(request.Uri.TryGetExpectedEntitySet(out entitySet), "Could not infer entity set from URI"); var entitySetUri = new ODataUri(new EntitySetSegment(entitySet)); var beforeSync = this.Evaluator.Evaluate(entitySetUri, false, false) as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(beforeSync, "Could not evaluate entity set '{0}' before syncing", entitySet.Name); // create a shallow copy beforeSync = beforeSync.Type.CreateCollectionWithValues(beforeSync.Elements); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntireEntitySet(c, entitySet.Name)); var afterSync = this.Evaluator.Evaluate(entitySetUri, false, false) as QueryCollectionValue; ExceptionUtilities.CheckObjectNotNull(afterSync, "Could not evaluate entity set '{0}' after syncing", entitySet.Name); // TODO: handle deep insert (using location header or response payload) var newElements = afterSync.Elements.Except(beforeSync.Elements).OfType<QueryStructuralValue>().ToList(); this.insertedEntityCache[request] = insertedEntity = newElements.Single(); // update the parent entity, if there is one var segments = request.Uri.Segments; var lastNavigationSegment = segments.OfType<NavigationSegment>().LastOrDefault(); if (lastNavigationSegment != null) { var segmentsBeforeNavigation = segments.TakeWhile(t => t != lastNavigationSegment); var parentEntity = (QueryStructuralValue)this.Evaluator.Evaluate(new ODataUri(segmentsBeforeNavigation), false, false); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntity(c, parentEntity)); } } return insertedEntity; }
/// <summary> /// Gets the pre and post update representations of the entity updated by the given request /// </summary> /// <param name="request">The request</param> /// <param name="beforeUpdate">The entity before the update</param> /// <param name="afterUpdate">The entity after the update</param> public void GetUpdatedEntity(ODataRequest request, out QueryStructuralValue beforeUpdate, out QueryStructuralValue afterUpdate) { ExceptionUtilities.CheckArgumentNotNull(request, "request"); ExceptionUtilities.Assert(this.requestsBegun.Contains(request), "Cannot use GetUpdatedEntity before calling Begin"); ExceptionUtilities.Assert(request.GetEffectiveVerb().IsUpdateVerb(), "Cannot use GetUpdatedEntity on non update requests"); KeyValuePair<QueryStructuralValue, QueryStructuralValue> beforeAndAfter; if (!this.updatedEntityCache.TryGetValue(request, out beforeAndAfter)) { var entityUri = request.Uri.ScopeToEntity(); var entity = (QueryStructuralValue)this.Evaluator.Evaluate(entityUri, false, false); beforeUpdate = this.QueryValueCopier.PerformDeepCopy(entity); SyncHelpers.ExecuteActionAndWait(c => this.Synchronizer.SynchronizeEntity(c, entity)); afterUpdate = entity; beforeAndAfter = new KeyValuePair<QueryStructuralValue, QueryStructuralValue>(beforeUpdate, afterUpdate); this.updatedEntityCache[request] = beforeAndAfter; } else { beforeUpdate = beforeAndAfter.Key; afterUpdate = beforeAndAfter.Value; } }
/// <summary> /// Returns true if this is a DELETE request /// </summary> /// <param name="request">The request being verified</param> /// <returns>Whether or not this verifier applies to the request</returns> public bool Applies(ODataRequest request) { return request.GetEffectiveVerb() == HttpVerb.Delete; }
private DataServiceProtocolVersion CalculateDataServiceProtocolVersion(ODataRequest request, ODataResponse response) { DataServiceProtocolVersion dataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.DataServiceVersion)); DataServiceProtocolVersion maxDataServiceVersion = VersionHelper.ConvertToDataServiceProtocolVersion(request.GetHeaderValueIfExists(HttpHeaders.MaxDataServiceVersion)); var responseContentType = response.GetHeaderValueIfExists(HttpHeaders.ContentType); if (responseContentType != null) { if (responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightNonStreaming, StringComparison.OrdinalIgnoreCase) || responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightStreaming, StringComparison.OrdinalIgnoreCase)) { return(DataServiceProtocolVersion.V4); } } if (response.StatusCode.IsError()) { return(DataServiceProtocolVersion.V4); } DataServiceProtocolVersion expectedVersion = DataServiceProtocolVersion.V4; // Apply minDsv if MPV > V2 if (maxDataServiceVersion != DataServiceProtocolVersion.Unspecified && this.maxProtocolVersion >= maxDataServiceVersion) { expectedVersion = maxDataServiceVersion; } else { expectedVersion = this.maxProtocolVersion; } // If body of a response is empty, the version is V1 unless it has prefer header. if (!this.IsResponseBodyEmpty(response)) { if (request.Uri.IsMetadata()) { // metadata payloads are not handled by the normal payload element visitor, but the response header will match the model version exactly expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.ModelVersionCalculator.CalculateProtocolVersion(this.model)); } else { // GET requests are versioned based on the URI because type information is not known until serialization if (request.GetEffectiveVerb() == HttpVerb.Get || request.Uri.IsServiceOperation() || request.Uri.IsAction()) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.UriVersionCalculator.CalculateProtocolVersion(request.Uri, response.Headers[HttpHeaders.ContentType], this.maxProtocolVersion, dataServiceVersion, maxDataServiceVersion)); } // Post and update requests are versioned based on the specific instance if (response.RootElement != null) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.PayloadElementVersionCalculator.CalculateProtocolVersion(response.RootElement, response.Headers[HttpHeaders.ContentType], this.maxProtocolVersion, maxDataServiceVersion)); } } } else { if (request.Uri.IsAction()) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, this.UriVersionCalculator.CalculateProtocolVersion(request.Uri, response.GetHeaderValueIfExists(HttpHeaders.ContentType), this.maxProtocolVersion, dataServiceVersion, maxDataServiceVersion)); } } // NOTE: the prefer verifier will ensure that this header is present if it should be, so our only concern here // is that the version is >= V3 if it is present if (response.Headers.ContainsKey(HttpHeaders.PreferenceApplied)) { expectedVersion = VersionHelper.IncreaseVersionIfRequired(expectedVersion, DataServiceProtocolVersion.V4); } return(expectedVersion); }