/// <summary>Initializes a new <see cref="RequestQueryProcessor"/> instance.</summary>
        /// <param name="service">Service with data and configuration.</param>
        /// <param name="description">Description for request processed so far.</param>
        private RequestQueryProcessor(IDataService service, RequestDescription description)
        {
            this.service = service;
            this.description = description;
#if DEBUG
            this.orderApplied = false;
#endif
            this.skipCount = null;
            this.topCount = null;
            this.queryExpression = description.RequestExpression;

            this.setQueryApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) ||
                                       description.CountOption == RequestQueryCountOption.CountSegment;

            // Server Driven Paging is not considered for the following cases: 
            // 1. Top level result is not or resource type or it is a single valued result.
            // 2. $count segment provided.
            // 3. Non-GET requests do not honor SDP.
            // 4. Only exception for Non-GET requests is if the request is coming from a Service
            //    operation that returns a set of result values of entity type.
            this.pagingApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) &&
                                    (description.CountOption != RequestQueryCountOption.CountSegment) &&
                                    !description.IsRequestForEnumServiceOperation &&
                                    (service.OperationContext.RequestMessage.HttpVerb.IsQuery() || description.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation);

            this.appliedCustomPaging = false;

            this.rootProjectionNode = null;
            this.topLevelOrderingInfo = null;

            this.skipTokenExpressionBuilder = new SkipTokenExpressionBuilder(NodeToExpressionTranslator.Create(this.service, description, Expression.Parameter(typeof(object))));
        }
Beispiel #2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ClientPreference"/> class.
 /// </summary>
 /// <param name="requestDescription">The request description.</param>
 /// <param name="verb">The request verb.</param>
 /// <param name="requestMessage">The request message.</param>
 /// <param name="effectiveMaxResponseVersion">The effective max response version for the request, which is the min of MDSV and MPV.</param>
 public ClientPreference(RequestDescription requestDescription, HttpVerbs verb, IODataRequestMessage requestMessage, Version effectiveMaxResponseVersion)
     : this(InterpretClientPreference(requestDescription, verb, requestMessage))
 {
     if (effectiveMaxResponseVersion >= VersionUtil.Version4Dot0)
     {
         this.annotationFilter = requestMessage.PreferHeader().AnnotationFilter;
     }
 }
 public void AnnotationFilterShouldNotBeSetAndRequiredResponseVersionShouldBe10WhenTheODataAnnotationsPreferenceIsMissing()
 {
     RequestDescription descrption = new RequestDescription(RequestTargetKind.Link, RequestTargetSource.ServiceOperation, new Uri("http://service/set"));
     IODataRequestMessage requestMessage = new ODataRequestMessageSimulator();
     ClientPreference preference = new ClientPreference(descrption, HttpVerbs.None, requestMessage, effectiveMaxResponseVersion: VersionUtil.Version4Dot0);
     preference.AnnotationFilter.Should().BeNull();
     preference.RequiredResponseVersion.Should().Be(VersionUtil.Version4Dot0);
 }
 public void AnnotationFilterShouldBeSetWithODataAnnotationsPreferenceAndRequiredResponseVersionShouldBe30WhenEffectiveMaxResponseVersionIs30()
 {
     RequestDescription descrption = new RequestDescription(RequestTargetKind.Link, RequestTargetSource.ServiceOperation, new Uri("http://service/set"));
     IODataRequestMessage requestMessage = new ODataRequestMessageSimulator();
     requestMessage.PreferHeader().AnnotationFilter = "*";
     ClientPreference preference = new ClientPreference(descrption, HttpVerbs.None, requestMessage, effectiveMaxResponseVersion: VersionUtil.Version4Dot0);
     preference.AnnotationFilter.Should().Be("*");
     preference.RequiredResponseVersion.Should().Be(VersionUtil.Version4Dot0);
 }
        /// <summary>
        /// Initializes a new instance of the <see cref="Microsoft.OData.Service.ExpandAndSelectParseResult"/> class.
        /// </summary>
        /// <param name="requestDescription">The request description.</param>
        /// <param name="dataService">The data service.</param>
        internal ExpandAndSelectParseResult(RequestDescription requestDescription, IDataService dataService)
        {
            Debug.Assert(dataService != null, "dataService != null");
            Debug.Assert(dataService.OperationContext != null, "dataService.OperationContext != null");
            Debug.Assert(dataService.OperationContext.RequestMessage != null, "dataService.OperationContext.RequestMessage != null");

            string expand = dataService.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
            this.RawSelectQueryOptionValue = dataService.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringSelect);

            ResourceType targetResourceType = requestDescription.TargetResourceType;
            ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet;
            if (!string.IsNullOrEmpty(expand))
            {
                if (targetResourceType == null || targetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType || targetResourceSet == null)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
                }
            }

            if (!string.IsNullOrEmpty(this.RawSelectQueryOptionValue))
            {
                ValidateSelectIsAllowedForRequest(requestDescription);

                // Throw if $select requests have been disabled by the user
                Debug.Assert(dataService.Configuration != null, "dataService.Configuration != null");
                if (!dataService.Configuration.DataServiceBehavior.AcceptProjectionRequests)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotAccepted);
                }
            }

            MetadataProviderEdmModel model = dataService.Provider.GetMetadataProviderEdmModel();

            var uriParser = RequestUriProcessor.CreateUriParserWithBatchReferenceCallback(dataService, dataService.OperationContext.AbsoluteRequestUri);

            try
            {
                Debug.Assert(model.Mode == MetadataProviderEdmModelMode.Serialization, "Model expected to be in serialization mode by default");
                model.Mode = MetadataProviderEdmModelMode.SelectAndExpandParsing;
                this.Clause = uriParser.ParseSelectAndExpand();
            }
            catch (ODataException ex)
            {
                throw new DataServiceException(400, null, ex.Message, null, ex);
            }
            finally
            {
                model.Mode = MetadataProviderEdmModelMode.Serialization;
            }

            if (this.Clause != null)
            {
                this.HasExpand = this.Clause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Any();
                this.HasSelect = HasSelectedItemAtAnyLevel(this.Clause);
            }
        }
        /// <summary>
        /// Create a new instance of ODataMessageWriterSettings for normal requests.
        /// </summary>
        /// <param name="dataService">Data service instance.</param>
        /// <param name="requestDescription">The current request description.</param>
        /// <param name="responseMessage">IODataResponseMessage implementation.</param>
        /// <param name="model">The model to provide to the message writer.</param>
        /// <returns>An instance of a message writer with the appropriate settings.</returns>
        internal static MessageWriterBuilder ForNormalRequest(IDataService dataService, RequestDescription requestDescription, IODataResponseMessage responseMessage, IEdmModel model)
        {
            Debug.Assert(dataService != null, "dataService != null");
            Debug.Assert(dataService.OperationContext != null, "dataService.OperationContext != null");
            Debug.Assert(requestDescription != null, "requestDescription != null");
            Debug.Assert(dataService.OperationContext.RequestMessage != null, "dataService.OperationContext.RequestMessage != null");
            Debug.Assert(responseMessage != null, "responseMessage != null");

            Uri serviceUri = dataService.OperationContext.AbsoluteServiceUri;
            Version responseVersion = requestDescription.ActualResponseVersion;

            MessageWriterBuilder messageWriterBuilder = new MessageWriterBuilder(serviceUri, responseVersion, dataService, responseMessage, model);

            // ODataLib doesn't allow custom MIME types on raw values (must be text/plain for non-binary, and application/octet for binary values).
            // To maintain existing V1/V2 behavior, work around this by setting the format as RawValue (we handle conneg ourself for this, so don't make ODL do its own),
            // and then later manually override the content type header. Conneg is done by Astoria in DataService.CreateResponseBodyWriter.
            if (requestDescription.ResponsePayloadKind == ODataPayloadKind.Value && !string.IsNullOrEmpty(requestDescription.MimeType))
            {
                messageWriterBuilder.WriterSettings.SetContentType(ODataFormat.RawValue);
            }
            else
            {
                string acceptHeaderValue = dataService.OperationContext.RequestMessage.GetAcceptableContentTypes();

                // In V1/V2 we defaulted to charset=utf-8 for the response when there was no specific Accept-Charset.
                // ODataMessageWriter uses a different default in some cases depending on the media type, so we need to override that here.
                string requestAcceptCharSet = dataService.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader();
                if (string.IsNullOrEmpty(requestAcceptCharSet) || requestAcceptCharSet == "*")
                {
                    requestAcceptCharSet = XmlConstants.Utf8Encoding;
                }

                messageWriterBuilder.WriterSettings.SetContentType(acceptHeaderValue, requestAcceptCharSet);
            }

            // always set the metadata document URI. ODataLib will decide whether or not to write it.
            messageWriterBuilder.WriterSettings.ODataUri = new ODataUri()
            {
                ServiceRoot = serviceUri,
                SelectAndExpand = requestDescription.ExpandAndSelect.Clause,
                Path = requestDescription.Path
            };

            messageWriterBuilder.WriterSettings.JsonPCallback = requestDescription.JsonPaddingFunctionName;

            return messageWriterBuilder;
        }
Beispiel #7
0
        /// <summary>
        /// Binds the expand paths from the requests $expand query option to the sets/types/properties from the metadata provider of the service.
        /// </summary>
        /// <param name="requestDescription">The request description.</param>
        /// <param name="dataService">The data service.</param>
        /// <param name="expandQueryOption">The value of the $expand query option.</param>
        /// <returns>The bound expand segments.</returns>
        internal static IList<IList<ExpandItem>> BindExpandSegments(RequestDescription requestDescription, IDataService dataService, string expandQueryOption)
        {
            Debug.Assert(requestDescription != null, "requestDescription != null");
            Debug.Assert(dataService != null, "dataService != null");

            if (string.IsNullOrWhiteSpace(expandQueryOption))
            {
                return new List<IList<ExpandItem>>();
            }

            ResourceType targetResourceType = requestDescription.TargetResourceType;
            ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet;
            if (targetResourceType == null || targetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType || targetResourceSet == null)
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
            }

            MetadataProviderEdmModel model = dataService.Provider.GetMetadataProviderEdmModel();
            IEdmEntityType targetType = (IEdmEntityType)model.EnsureSchemaType(targetResourceType);
            IEdmEntitySet targetSet = model.EnsureEntitySet(targetResourceSet);

            SelectExpandClause clause;
            try
            {
                model.Mode = MetadataProviderEdmModelMode.SelectAndExpandParsing;
                clause = ODataUriParser.ParseSelectAndExpand(/*select*/ null, expandQueryOption, model, targetType, targetSet);
            }
            catch (ODataException ex)
            {
                throw new DataServiceException(400, null, ex.Message, null, ex);
            }
            finally
            {
                model.Mode = MetadataProviderEdmModelMode.Serialization;
            }

            return new ExpandAndSelectPathExtractor(clause).ExpandPaths;
        }
        private static void RunPayloadKindTest(RequestTargetKind requestTargetKind, RequestTargetSource requestTargetSource, ResourceType targetResourceType, bool singleResult, bool isLinkUri, ODataPayloadKind expectedKind)
        {
            var segment = new SegmentInfo
            {
                TargetKind = requestTargetKind,
                TargetSource = requestTargetSource,
                TargetResourceType = targetResourceType,
                SingleResult = singleResult,
                Identifier = "Fake",
            };

            var operation = new ServiceOperation("Fake", ServiceOperationResultKind.Void, null, null, "GET", null);
            operation.SetReadOnly();
            segment.Operation = new OperationWrapper(operation);
            segment.ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int)));

            SegmentInfo[] segmentInfos;
            if (isLinkUri)
            {
                segmentInfos = new[]
                {
                    new SegmentInfo(),
                    new SegmentInfo
                    {
                       TargetKind = RequestTargetKind.Link,  
                    },
                    segment
                };
            }
            else
            {
                segmentInfos = new[]
                {
                    new SegmentInfo
                    {
                        Identifier = "Fake",
                    },
                    new SegmentInfo(),
                    segment
                };
            }

            var requestDescription = new RequestDescription(segmentInfos, new Uri("http://temp.org/"));
            requestDescription.ResponsePayloadKind.Should().Be(expectedKind);
        }
        private static void RunNegotiatedFormatTest(string requestAccept, string requestMaxVersion, Microsoft.OData.Client.ODataProtocolVersion maxProtocolVersion, ODataFormat expectedFormat)
        {
            DataServiceHostSimulator host = new DataServiceHostSimulator
            {
                RequestHttpMethod = "GET",
                RequestAccept = requestAccept,
                RequestMaxVersion = requestMaxVersion,
                RequestVersion = "4.0",
            };
            DataServiceSimulator service = new DataServiceSimulator
            {
                OperationContext = new DataServiceOperationContext(host),
                Configuration = new DataServiceConfiguration(new DataServiceProviderSimulator()),
            };

            service.Configuration.DataServiceBehavior.MaxProtocolVersion = maxProtocolVersion;
            service.OperationContext.InitializeAndCacheHeaders(service);
            service.OperationContext.RequestMessage.InitializeRequestVersionHeaders(VersionUtil.ToVersion(maxProtocolVersion));

            var d = new RequestDescription(RequestTargetKind.Primitive, RequestTargetSource.Property, new Uri("http://temp.org/"));

            d.DetermineWhetherResponseBodyOrETagShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb);
            d.DetermineWhetherResponseBodyShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb);
            d.DetermineResponseFormat(service);
            d.ResponseFormat.Should().NotBeNull();
            d.ResponseFormat.Format.Should().BeSameAs(expectedFormat);
        }
 private static void RunSimpleFormatTest(RequestTargetKind requestTargetKind, ODataFormat expectedFormat)
 {
     var requestDescription = new RequestDescription(requestTargetKind, RequestTargetSource.None, new Uri("http://temp.org/"));
     requestDescription.DetermineResponseFormat(new DataServiceSimulator());
     if (expectedFormat == null)
     {
         requestDescription.ResponseFormat.Should().BeNull();
     }
     else
     {
         requestDescription.ResponseFormat.Should().NotBeNull();
         requestDescription.ResponseFormat.Format.Should().BeSameAs(expectedFormat);
     }
 }
        public void RawValueWithMimeTypeShouldUseFormat()
        {
            this.host.AbsoluteServiceUri = new Uri("http://myservice.org/");
            this.host.AbsoluteRequestUri = new Uri("http://myservice.org/FakeSet");

            SegmentInfo segment = new SegmentInfo
            {
                TargetKind = RequestTargetKind.PrimitiveValue,
                TargetSource = RequestTargetSource.Property,
                ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(string))) { MimeType = "fake/things" }
            };

            var requestDescription = new RequestDescription(new[] { segment }, new Uri("http://temp.org"));

            var testSubject = this.ForNormalRequest(requestDescription);
            testSubject.WriterSettings.UseFormat.Should().BeTrue();
            testSubject.WriterSettings.Format.Should().BeSameAs(ODataFormat.RawValue);
        }
        /// <summary>
        /// Calls the Execution provider to invoke the request expressions for the current request
        /// </summary>
        /// <param name="description">Request description.</param>
        /// <param name="service">Service instance.</param>
        private static void InvokeRequestExpression(RequestDescription description, IDataService service)
        {
            Debug.Assert(description != null, "description != null");
            Debug.Assert(service != null, "service != null");

            HttpVerbs httpVerb = service.OperationContext.RequestMessage.HttpVerb;
            bool isPostOperationRequest = httpVerb == HttpVerbs.POST && description.TargetSource == RequestTargetSource.ServiceOperation;

            if (httpVerb.IsQuery() || isPostOperationRequest)
            {
                SegmentInfo segmentToInvoke = description.LastSegmentInfo;
                if (httpVerb.IsQuery() && description.TargetSource == RequestTargetSource.Property)
                {
                    // GET ~/Customer(1)/Address/State for example we need to execute the expression at the resource level, i.e. Customer(1).
                    // Then we call IDSQP.GetValue() to get the property values for Address and State.
                    segmentToInvoke = description.SegmentInfos[description.GetIndexOfTargetEntityResource()];
                }

                if (segmentToInvoke.RequestExpression != null && segmentToInvoke.RequestEnumerable == null)
                {
                    segmentToInvoke.RequestEnumerable = service.ExecutionProvider.GetResultEnumerableFromRequest(segmentToInvoke);
                }
            }
        }
        /// <summary>Gets a string with methods allowed on the target for the <paramref name="description"/>.</summary>
        /// <param name="configuration">configuration object which has the data</param>
        /// <param name="description">Description with target.</param>
        /// <returns>A string with methods allowed on the description; possibly null.</returns>
        internal static string GetAllowedMethods(DataServiceConfiguration configuration, RequestDescription description)
        {
            Debug.Assert(description != null, "description != null");
            Debug.Assert(
                description.TargetKind != RequestTargetKind.Nothing,
                "description.TargetKind != RequestTargetKind.Void - otherwise it hasn't been determined yet");
            Debug.Assert(
                description.TargetKind != RequestTargetKind.VoidOperation,
                "description.TargetKind != RequestTargetKind.VoidOperation - this method is only for containers");
            if (description.TargetKind == RequestTargetKind.Metadata ||
                description.TargetKind == RequestTargetKind.ServiceDirectory)
            {
                return XmlConstants.HttpMethodGet;
            }

            if (description.TargetKind == RequestTargetKind.Batch)
            {
                return XmlConstants.HttpMethodPost;
            }

            int index = description.GetIndexOfTargetEntityResource();
            Debug.Assert(index >= 0 && index < description.SegmentInfos.Count, "index >=0 && index <description.SegmentInfos.Count");
            ResourceSetWrapper container = description.SegmentInfos[index].TargetResourceSet;
            return GetAllowedMethods(configuration, container, description);
        }
 /// <summary>
 /// Create a new request description from the given request description and new entity as the result.
 /// </summary>
 /// <param name="description">Existing request description.</param>
 /// <param name="entity">entity that needs to be the result of the new request.</param>
 /// <returns>a new instance of request description containing information about the given entity.</returns>
 internal static RequestDescription CreateSingleResultRequestDescription(RequestDescription description, object entity)
 {
     return RequestDescription.CreateSingleResultRequestDescription(description, entity, description.TargetResourceType);
 }
 public object GetResource(RequestDescription description, int segmentIndex, string typeFullName)
 {
     throw new NotImplementedException();
 }
        private static void RunSpatialDistanceFilterTest(RequestDescription d, Action<GeographyPoint> verifyPoint1, Action<GeographyPoint> verifyPoint2, double distanceToReturn, int expectedCount)
        {
            bool called = false;
            Func<Geography, Geography, double> callback =
                (Geography geo1, Geography geo2) =>
                {
                    called = true;

                    var point1 = geo1 as GeographyPointImplementation;
                    Assert.IsNotNull(point1);

                    var point2 = geo2 as GeographyPointImplementation;
                    Assert.IsNotNull(point2);

                    if (verifyPoint1 != null)
                    {
                        verifyPoint1(point1);
                    }

                    if (verifyPoint2 != null)
                    {
                        verifyPoint2(point2);
                    }

                    return distanceToReturn;
                };

            AssertConstantExpressionsAreOfPublicType(d.RequestExpression);

            var e = Invoke(d.RequestExpression).GetEnumerator();

            using (SpatialTestUtils.RegisterOperations(new DistanceOperationImplementation(callback)))
            {
                int count = 0;
                while (e.MoveNext())
                {
                    count++;
                }
                Assert.IsTrue(called);
                Assert.AreEqual(expectedCount, count);
            }
        }
        /// <summary>Sets the response status code and the default caching and versioning headers.</summary>
        /// <param name="description">The request description for the current request.</param>
        /// <param name="statusCode">The status code for the response.</param>
        internal void SetResponseHeaders(RequestDescription description, int statusCode)
        {
            // Set the caching policy appropriately - for the time being, we disable caching.
            this.SetHeader(XmlConstants.HttpResponseCacheControl, XmlConstants.HttpCacheControlNoCache);

            // If a preference was applied, add corresponding response header.
            ClientPreference preference = description.Preference;
            this.PreferenceAppliedHeader().ReturnContent = preference.ShouldIncludeResponseBody ? true : (preference.ShouldNotIncludeResponseBody ? (bool?)false : null);

            // Only set the annotation filter to the Preference-Applied if we are writing a response body
            // since instance annotations only appear in the response body.
            if (description.ShouldWriteResponseBody && !string.IsNullOrEmpty(preference.AnnotationFilter))
            {
                this.PreferenceAppliedHeader().AnnotationFilter = preference.AnnotationFilter;
            }

            this.SetHeader(XmlConstants.HttpODataVersion, description.ResponseVersion.ToString() + ";");
            this.StatusCode = statusCode;
        }
Beispiel #18
0
        /// <summary>
        /// Interprets the client preference for having a response body.
        /// </summary>
        /// <param name="requestDescription">The request description.</param>
        /// <param name="verb">The request verb.</param>
        /// <param name="requestMessage">The request message.</param>
        /// <returns>An enum representation of the client's preference.</returns>
        private static ResponseBodyPreference InterpretClientPreference(RequestDescription requestDescription, HttpVerbs verb, IODataRequestMessage requestMessage)
        {
            Debug.Assert(requestDescription != null, "requestDescription != null");

            // If no responseBodyPreference given, we have default behavior of producing content for POST and not producing it for PUT/PATCH.
            // If responseBodyPreference is given we honor the responseBodyPreference only if the request was for an entity and following conditions are true:
            // This is not a service operation invoke
            // DSV was set to 3.0 and above
            // Server is configured to be >= 3.0
            if (requestDescription.LinkUri || requestDescription.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation || requestDescription.RequestVersion < VersionUtil.Version4Dot0)
            {
                return ResponseBodyPreference.None;
            }

            if ((verb.IsInsert()) || ((verb.IsUpdate()) && (requestDescription.TargetKind == RequestTargetKind.Resource || requestDescription.IsRequestForNonEntityProperty)))
            {
                if (requestMessage.PreferHeader().ReturnContent.HasValue)
                {
                    return requestMessage.PreferHeader().ReturnContent.Value ? ResponseBodyPreference.Content : ResponseBodyPreference.NoContent;
                }
            }

            // TODO: move logic for when/when-not-to write a response body here and remove all checks for 'none' elsewhere
            return ResponseBodyPreference.None;
        }
        private MessageWriterBuilder ForNormalRequest(RequestDescription requestDescription)
        {
            this.dataServiceSimulator.OperationContext.InitializeAndCacheHeaders(this.dataServiceSimulator);
            this.dataServiceSimulator.OperationContext.RequestMessage.CacheHeaders();
            this.dataServiceSimulator.OperationContext.RequestMessage.InitializeRequestVersionHeaders(VersionUtil.Version4Dot0);

            requestDescription.ParseExpandAndSelect(this.dataServiceSimulator);
            requestDescription.DetermineWhetherResponseBodyOrETagShouldBeWritten(HttpVerbs.GET);
            requestDescription.DetermineWhetherResponseBodyShouldBeWritten(HttpVerbs.GET);
            requestDescription.DetermineResponseFormat(this.dataServiceSimulator);
            var testSubject = MessageWriterBuilder.ForNormalRequest(this.dataServiceSimulator, requestDescription, this.responseMessageSimulator, new EdmModel());
            return testSubject;
        }
        private MessageWriterBuilder ForNormalRequest()
        {
            var requestDescription = new RequestDescription(RequestTargetKind.Resource, RequestTargetSource.EntitySet, new Uri("http://temp.org/"));

            var resourceType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "Fake", "Type", false) { CanReflectOnInstanceType = false, IsOpenType = true };
            resourceType.AddProperty(new ResourceProperty("Id", ResourcePropertyKind.Key | ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int))) { CanReflectOnInstanceTypeProperty = false });
            var resourceSet = new ResourceSet("FakeSet", resourceType);
            resourceSet.SetReadOnly();

            requestDescription.LastSegmentInfo.TargetResourceType = resourceType;
            requestDescription.LastSegmentInfo.TargetResourceSet = ResourceSetWrapper.CreateForTests(resourceSet);

            return this.ForNormalRequest(requestDescription);
        }
        /// <summary>
        /// Copies settings from another request-description instance.
        /// </summary>
        /// <param name="other">The description to copy from.</param>
        private void CopyFrom(RequestDescription other)
        {
            this.SkipTokenExpressionCount = other.SkipTokenExpressionCount;
            this.SkipTokenProperties = other.SkipTokenProperties;

            this.CountOption = other.CountOption;
            this.CountValue = other.CountValue;

            this.Preference = other.Preference;

            this.ResponseVersion = other.ResponseVersion;
            this.ActualResponseVersion = other.ActualResponseVersion;
            this.serviceMaxProtocolVersion = other.serviceMaxProtocolVersion;
            this.requestMaxVersion = other.requestMaxVersion;
#if DEBUG
            this.maxFeatureVersion = other.maxFeatureVersion;
#endif

            this.ResponseFormat = other.ResponseFormat;
            this.PayloadMetadataParameterInterpreter = other.PayloadMetadataParameterInterpreter;
            this.ExpandAndSelect = other.ExpandAndSelect;
            this.responseBodyOrETagShouldBeWritten = other.responseBodyOrETagShouldBeWritten;
            this.responseBodyShouldBeWritten = other.responseBodyShouldBeWritten;
            this.JsonPaddingFunctionName = other.JsonPaddingFunctionName;
            this.Path = other.Path;
        }
        /// <summary>Initializes a new RequestDescription based on an existing one.</summary>
        /// <param name="other">Other description to base new description on.</param>
        /// <param name="resultExpression">Query results for new request description.</param>
        /// <param name="rootProjectionNode">Projection segment describing the projections on the top level of the query.</param>
        internal RequestDescription(
            RequestDescription other,
            Expression resultExpression,
            RootProjectionNode rootProjectionNode)
        {
            Debug.Assert(
                resultExpression == null || other.SegmentInfos != null,
                "queryResults == null || other.SegmentInfos != null -- otherwise there isn't a segment in which to replace the query.");
            Debug.Assert(
                rootProjectionNode == null || resultExpression != null,
                "rootProjectionNode == null || queryResults != null -- otherwise there isn't a query to execute and expand");

            this.rootProjectionNode = rootProjectionNode;

            this.containerName = other.ContainerName;
            this.mimeType = other.MimeType;
            this.resultUri = other.ResultUri;
            this.segmentInfos = other.SegmentInfos;

            this.CopyFrom(other);

            if (resultExpression != null)
            {
                this.LastSegmentInfo.RequestExpression = resultExpression;
            }
        }
Beispiel #23
0
        /// <summary>
        /// Binds the paths from the request's $select query option to the sets/types/properties from the metadata provider of the service.
        /// </summary>
        /// <param name="requestDescription">The request description.</param>
        /// <param name="dataService">The data service.</param>
        /// <param name="selectQueryOption">The raw value of the $select query option.</param>
        /// <returns>The bound select segments.</returns>
        internal static IList<IList<SelectItem>> BindSelectSegments(RequestDescription requestDescription, IDataService dataService, string selectQueryOption)
        {
            Debug.Assert(requestDescription != null, "requestDescription != null");
            Debug.Assert(dataService != null, "dataService != null");

            if (string.IsNullOrEmpty(selectQueryOption))
            {
                return new List<IList<SelectItem>>();
            }

            // Throw if $select requests have been disabled by the user
            Debug.Assert(dataService.Configuration != null, "dataService.Configuration != null");
            if (!dataService.Configuration.DataServiceBehavior.AcceptProjectionRequests)
            {
                throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotAccepted);
            }

            IList<IList<string>> selectPathsAsText = SplitSelect(selectQueryOption, dataService.Provider);
            Debug.Assert(selectPathsAsText != null, "selectPathsAsText != null");

            List<IList<SelectItem>> boundSegments = new List<IList<SelectItem>>(selectPathsAsText.Count);
            if (selectPathsAsText.Count == 0)
            {
                return boundSegments;
            }

            ValidateSelectIsAllowedForRequest(requestDescription);

            MetadataProviderEdmModel metadataProviderEdmModel = dataService.Provider.GetMetadataProviderEdmModel();

            for (int i = selectPathsAsText.Count - 1; i >= 0; i--)
            {
                IList<string> path = selectPathsAsText[i];
                List<SelectItem> boundSegmentPath = new List<SelectItem>(path.Count);
                boundSegments.Add(boundSegmentPath);

                ResourceType targetResourceType = requestDescription.TargetResourceType;
                ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet;

                // if we get to here, we're building a partial selection
                List<TypeSegment> typeSegments = new List<TypeSegment>();
                bool previousSegmentIsTypeSegment = false;
                for (int j = 0; j < path.Count; j++)
                {
                    string pathSegment = path[j];
                    bool lastPathSegment = (j == path.Count - 1);

                    // '*' is special, it means "Project all immediate properties on this level."
                    if (pathSegment == "*")
                    {
                        Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one. This should have been checked already when splitting appart the paths.");
                        boundSegmentPath.Add(CreateWildcardSelection());
                        continue;
                    }

                    bool nameIsContainerQualified;
                    string nameFromContainerQualifiedName = dataService.Provider.GetNameFromContainerQualifiedName(pathSegment, out nameIsContainerQualified);
                    if (nameFromContainerQualifiedName == "*")
                    {
                        Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one.  This should have been checked already when splitting appart the paths.");
                        Debug.Assert(nameIsContainerQualified, "nameIsContainerQualified == true");
                        boundSegmentPath.Add(CreateContainerQualifiedWildcardSelection(metadataProviderEdmModel));
                        continue;
                    }

                    ResourceProperty property = targetResourceType.TryResolvePropertyName(pathSegment);
                    if (property == null)
                    {
                        ResourceType resolvedResourceType = WebUtil.ResolveTypeIdentifier(dataService.Provider, pathSegment, targetResourceType, previousSegmentIsTypeSegment);
                        if (resolvedResourceType != null)
                        {
                            previousSegmentIsTypeSegment = true;
                            if (lastPathSegment)
                            {
                                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, resolvedResourceType.FullName));
                            }

                            targetResourceType = resolvedResourceType;

                            // Whenever we encounter the type segment, we need to only verify that the MPV is set to 3.0 or higher.
                            // There is no need to check for request DSV, request MinDSV or request MaxDSV since there are no protocol changes in
                            // the payload for uri's with type identifier.
                            requestDescription.VerifyProtocolVersion(VersionUtil.Version3Dot0, dataService);

                            IEdmSchemaType edmType = metadataProviderEdmModel.EnsureSchemaType(targetResourceType);
                            TypeSegment typeSegment = new TypeSegment(edmType);
                            typeSegments.Add(typeSegment);
                            continue;
                        }

                        previousSegmentIsTypeSegment = false;

                        // If the currentResourceType is an open type, we require the service action name to be fully qualified or else we treat it as an open property name.
                        if (!targetResourceType.IsOpenType || nameIsContainerQualified)
                        {
                            // Note that if the service does not implement IDataServiceActionProvider and the MaxProtocolVersion < V3, 
                            // GetActionsBoundToAnyTypeInResourceSet() would simply return an empty ServiceOperationWrapper collection.
                            Debug.Assert(dataService.ActionProvider != null, "dataService.ActionProvider != null");
                            IEnumerable<OperationWrapper> allOperationsInSet = dataService.ActionProvider.GetActionsBoundToAnyTypeInResourceSet(targetResourceSet);
                            List<OperationWrapper> selectedServiceActions = allOperationsInSet.Where(o => o.Name == nameFromContainerQualifiedName).ToList();

                            if (selectedServiceActions.Count > 0)
                            {
                                if (!lastPathSegment)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ServiceActionMustBeLastSegmentInSelect(pathSegment));
                                }

                                boundSegmentPath.Add(CreateOperationSelection(metadataProviderEdmModel, selectedServiceActions, typeSegments));
                                continue;
                            }
                        }

                        if (!targetResourceType.IsOpenType)
                        {
                            throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(targetResourceType.FullName, pathSegment));
                        }

                        if (!lastPathSegment)
                        {
                            // Open navigation properties are not supported on OpenTypes.
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment));
                        }

                        boundSegmentPath.Add(CreateOpenPropertySelection(pathSegment, typeSegments));
                    }
                    else
                    {
                        previousSegmentIsTypeSegment = false;

                        ValidateSelectedProperty(targetResourceType, property, lastPathSegment);

                        boundSegmentPath.Add(CreatePropertySelection(metadataProviderEdmModel, targetResourceType, property, typeSegments));

                        if (property.TypeKind == ResourceTypeKind.EntityType)
                        {
                            targetResourceSet = dataService.Provider.GetResourceSet(targetResourceSet, targetResourceType, property);
                            targetResourceType = property.ResourceType;
                        }
                    }
                }

                // Note that this check is also covering cases where a type segment is followed by a wildcard.
                if (previousSegmentIsTypeSegment)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, targetResourceType.FullName));
                }
            }

            return boundSegments;
        }
        /// <summary>
        /// Create a new request description from the given request description and new entity as the result.
        /// </summary>
        /// <param name="description">Existing request description.</param>
        /// <param name="entity">entity that needs to be the result of the new request.</param>
        /// <param name="targetResourceType">The new target resource type for the new request description.</param>
        /// <returns>a new instance of request description containing information about the given entity.</returns>
        internal static RequestDescription CreateSingleResultRequestDescription(RequestDescription description, object entity, ResourceType targetResourceType)
        {
            // Create a new request description for the results that will be returned.
            SegmentInfo segmentInfo = new SegmentInfo
            {
                RequestExpression = Expression.Constant(entity),
                RequestEnumerable = new[] { entity },
                TargetKind = description.TargetKind,
                TargetSource = description.TargetSource,
                SingleResult = true,
                ProjectedProperty = description.Property,
                TargetResourceType = targetResourceType,
                TargetResourceSet = description.LastSegmentInfo.TargetResourceSet,
                Identifier = description.LastSegmentInfo.Identifier
            };
#if DEBUG
            segmentInfo.AssertValid();
#endif
            IList<SegmentInfo> segmentInfos = description.SegmentInfos;
            segmentInfos[segmentInfos.Count - 1] = segmentInfo;

            RequestDescription resultDescription = new RequestDescription(segmentInfos, description.ResultUri);
            resultDescription.CopyFrom(description);

            return resultDescription;
        }
        internal static RequestDescription ProcessRequestUri(Uri absoluteRequestUri, IDataService service, bool internalQuery)
        {
            Debug.Assert(service != null, "service != null");
            Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null");
            Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")");
            
            MetadataProviderEdmModel metadataProviderEdmModel = service.Provider.GetMetadataProviderEdmModel();

            ODataPath path = ParsePath(metadataProviderEdmModel, absoluteRequestUri, service);

            IList<SegmentInfo> segmentInfos = ODataPathToSegmentInfoConverter.Create(service).ConvertPath(path);

            SegmentInfo lastSegment = null;
            if (segmentInfos.Count > 0)
            {
                lastSegment = segmentInfos[segmentInfos.Count - 1];

                // we need to check syntax validity for Service Ops here, if it's the last segment in the URI
                // Reason for checking here is we need to fail before it's invoked
                // Reason for checking the last segment instead of inside the CreateSegmentForSOP is further composing on a SOP segment
                // could change its result kind (i.e., using Navigation Properties on SingleResult SOP)
                if (lastSegment.Operation != null)
                {
                    ServiceOperationResultKind operationResultKind = lastSegment.Operation.ResultKind;
                    if (operationResultKind == ServiceOperationResultKind.QueryWithSingleResult)
                    {
                        WebUtil.CheckEmptySetQueryArguments(service);
                    }

                    if (operationResultKind == ServiceOperationResultKind.DirectValue || operationResultKind == ServiceOperationResultKind.Enumeration)
                    {
                        // no further composition should be allowed
                        WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/);
                    }

                    if (lastSegment.IsServiceActionSegment)
                    {
                        if (service.OperationContext.RequestMethod != lastSegment.Operation.Method)
                        {
                            throw DataServiceException.CreateMethodNotAllowed(Strings.RequestUriProcessor_MethodNotAllowed, lastSegment.Operation.Method);
                        }

                        WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/);
                    }
                }

                if (lastSegment.Identifier == XmlConstants.UriCountSegment)
                {
                    if (service.OperationContext.RequestMessage.HttpVerb.IsChange())
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_RequestVerbCannotCountError);
                    }
                }

                var entityReferenceSegmentInfo = segmentInfos.FirstOrDefault(segmentInfo => segmentInfo.Identifier == XmlConstants.UriLinkSegment);

                // Check for entity reference segment $ref
                // DEVNOTE: Ideally $ref should be the last segment, but in order to keep minimum changes to server code, this order is not changed
                // If the order is changed, lot of server code needs to be changed since LastSegment of RequestDescription is used in many places
                if (entityReferenceSegmentInfo != null
                    && service.OperationContext.RequestMessage.HttpVerb == HttpVerbs.DELETE)
                {
                    WebUtil.CheckEmptyQueryArguments(service, /*checkForOnlyV2QueryParameters*/false);

                    NameValueCollection queryCollection = HttpUtility.ParseQueryString(absoluteRequestUri.Query);

                    // For single-valued navigation properites, there should not be any query parameters
                    if (lastSegment.SingleResult
                        && queryCollection.Count > 0)
                    {
                        // For single-valued navigation properties, the $id or any other query option MUST NOT be specified.
                        throw DataServiceException.CreateBadRequestError(Strings.BadRequest_QueryOptionsShouldNotBeSpecifiedForDeletingSingleEntityReference);
                    }

                    // For collection-valued navigation properties, the entity reference of the entity to be removed MUST be specified using the $id query string option. 
                    if (!lastSegment.SingleResult)
                    {
                        // only one query ($id) parameter needs to be specified for DELETE operation on a collection of entity reference
                        if (queryCollection.Count > 1)
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidUriForDeleteOperation(absoluteRequestUri.Query));
                        }

                        // For DELETE operations on a collection, $id query parameter is required
                        var entityId = service.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringId);

                        // $id must be specified for DELETE operation on a collection of entity references
                        if (string.IsNullOrEmpty(entityId))
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_IdMustBeSpecifiedForDeletingCollectionOfEntityReference);
                        }

                        // Get the EntityId from $id query parameter
                        Uri uri = RequestUriProcessor.GetAbsoluteUriFromReference(entityId, service.OperationContext);
                        var parser = CreateUriParserWithBatchReferenceCallback(service, uri);

                        lastSegment.SingleResult = true;
                        lastSegment.Key = (KeySegment)parser.ParsePath().LastSegment;
                    }
                }

                if (lastSegment.TargetKind == RequestTargetKind.Batch)
                {
                    CheckNoDollarFormat(service);
                }

                if (lastSegment.TargetKind == RequestTargetKind.MediaResource)
                {
                    // There is no valid query option for Media Resource.
                    WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/);
                }

                SegmentInfo openPropertySegment = segmentInfos.FirstOrDefault(p => p.TargetKind == RequestTargetKind.OpenProperty);
                if (openPropertySegment != null)
                {
                    if (service.OperationContext.RequestMessage.HttpVerb == HttpVerbs.POST)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(openPropertySegment.Identifier));
                    }
                }
            }

            bool isCrossReferencingUri = segmentInfos.Count > 0 && path.FirstSegment is BatchReferenceSegment;
            ComposeExpressionForSegments(segmentInfos, service, isCrossReferencingUri);

            RequestTargetKind targetKind = (lastSegment == null) ? RequestTargetKind.ServiceDirectory : lastSegment.TargetKind;

            // Create a ResultDescription from the processed segments.
            RequestDescription resultDescription;
            Uri resultUri = GetResultUri(service.OperationContext);
            if (targetKind == RequestTargetKind.Metadata || targetKind == RequestTargetKind.Batch || targetKind == RequestTargetKind.ServiceDirectory)
            {
                resultDescription = new RequestDescription(targetKind, RequestTargetSource.None, resultUri);
            }
            else
            {
                Debug.Assert(lastSegment != null, "lastSegment != null");
                Debug.Assert(
                    targetKind == RequestTargetKind.ComplexObject ||
                    targetKind == RequestTargetKind.Collection ||
                    targetKind == RequestTargetKind.OpenProperty ||
                    targetKind == RequestTargetKind.OpenPropertyValue ||
                    targetKind == RequestTargetKind.Primitive ||
                    targetKind == RequestTargetKind.PrimitiveValue ||
                    targetKind == RequestTargetKind.Resource ||
                    targetKind == RequestTargetKind.MediaResource ||
                    targetKind == RequestTargetKind.VoidOperation ||
                    targetKind == RequestTargetKind.Link,
                    "Unknown targetKind " + targetKind);

                resultDescription = new RequestDescription(segmentInfos, resultUri);

                // Only GET and PUT operations are allowed for a request uri refering to a named stream.
                // Note that we defer the same test for the default atom stream till later when we know if the instance type is an MLE.
                if (resultDescription.TargetKind == RequestTargetKind.MediaResource &&
                    resultDescription.IsNamedStream &&
                    service.OperationContext.RequestMessage.HttpVerb.IsChange() &&                    
                    service.OperationContext.RequestMessage.HttpVerb != HttpVerbs.PUT)
                {
                    throw DataServiceException.CreateMethodNotAllowed(
                        Strings.RequestUriProcessor_InvalidHttpMethodForNamedStream(UriUtil.UriToString(service.OperationContext.AbsoluteRequestUri), service.OperationContext.RequestMethod),
                        DataServiceConfiguration.GetAllowedMethods(service.Configuration, resultDescription));
                }
            }

            // apply the $count segment if it is present
            resultDescription.ApplyCountOption(service);

            resultDescription.DetermineWhetherResponseBodyOrETagShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb);

            if (!internalQuery)
            {
                // Analyze client preferences
                resultDescription.AnalyzeClientPreference(service);

                // Determine whether the response will have a body based on verb and client preference
                resultDescription.DetermineWhetherResponseBodyShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb);

                // Determine the response format
                resultDescription.DetermineResponseFormat(service);

                // Update the response version according to the features used by the request.
                resultDescription.UpdateVersion(service);

                // Handle $callback
                resultDescription.HandleCallbackQueryOption(service);
            }

            // In some cases, like CUD operations, we do not want to allow any query parameters to be specified.
            // But in V1, we didn't have this check hence we cannot fix this now. But we need to check only for 
            // V2 query parameters and stop them
            if ((service.OperationContext.RequestMessage.HttpVerb.IsChange()) && resultDescription.SegmentInfos[0].TargetSource != RequestTargetSource.ServiceOperation)
            {
                WebUtil.CheckV2EmptyQueryArguments(service);
            }

            resultDescription.ParseExpandAndSelect(service);

            // Process query options ($filter, $orderby, $expand, etc.)
            resultDescription = RequestQueryProcessor.ProcessQuery(service, resultDescription);

            RequestUriProcessor.InvokeRequestExpression(resultDescription, service);
            resultDescription.Path = path;

            return resultDescription;
        }
        /// <summary>
        /// Gets a string representation of allowed methods on the container (with the specified target cardinality),
        /// suitable for an 'Allow' header.
        /// </summary>
        /// <param name="configuration">configuration object which has the data</param>
        /// <param name="container">Targetted container, possibly null.</param>
        /// <param name="description">Description with target.</param>
        /// <returns>A value for an 'Allow' header; null if <paramref name="container"/> is null.</returns>
        internal static string GetAllowedMethods(DataServiceConfiguration configuration, ResourceSetWrapper container, RequestDescription description)
        {
            if (container == null)
            {
                return null;
            }

            System.Text.StringBuilder result = new System.Text.StringBuilder();
            EntitySetRights rights = configuration.GetResourceSetRights(container.ResourceSet);
            if (description.IsSingleResult)
            {
                AppendRight(rights, EntitySetRights.ReadSingle, XmlConstants.HttpMethodGet, result);
                AppendRight(rights, EntitySetRights.WriteReplace, XmlConstants.HttpMethodPut, result);
                if (description.TargetKind != RequestTargetKind.MediaResource)
                {
                    AppendRight(rights, EntitySetRights.WriteMerge, XmlConstants.HttpMethodPatch, result);
                    AppendRight(rights, EntitySetRights.WriteDelete, XmlConstants.HttpMethodDelete, result);
                }
            }
            else
            {
                AppendRight(rights, EntitySetRights.ReadMultiple, XmlConstants.HttpMethodGet, result);
                AppendRight(rights, EntitySetRights.WriteAppend, XmlConstants.HttpMethodPost, result);
            }

            return result.ToString();
        }
        /// <summary>Initializes a new <see cref="ResponseBodyWriter"/> that can write the body of a response.</summary>
        /// <param name="service">Service for the request being processed.</param>
        /// <param name="queryResults">Enumerator for results.</param>
        /// <param name="requestDescription">Description of request made to the system.</param>        
        /// <param name="actualResponseMessageWhoseHeadersMayBeOverridden">IODataResponseMessage instance for the response.</param>
        internal ResponseBodyWriter(
            IDataService service,
            QueryResultInfo queryResults,
            RequestDescription requestDescription, 
            IODataResponseMessage actualResponseMessageWhoseHeadersMayBeOverridden)
        {
            Debug.Assert(service != null, "service != null");
            Debug.Assert(requestDescription != null, "requestDescription != null");
            Debug.Assert(actualResponseMessageWhoseHeadersMayBeOverridden != null, "actualResponseMessageWhoseHeadersMayBeOverridden != null");

            this.service = service;
            this.queryResults = queryResults;
            this.requestDescription = requestDescription;
            this.actualResponseMessageWhoseHeadersMayBeOverridden = actualResponseMessageWhoseHeadersMayBeOverridden;

            Debug.Assert(this.PayloadKind != ODataPayloadKind.Unsupported, "payloadKind != ODataPayloadKind.Unsupported");

            this.encoding = ContentTypeUtil.EncodingFromAcceptCharset(this.service.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader());

            if (this.PayloadKind == ODataPayloadKind.Entry ||
                this.PayloadKind == ODataPayloadKind.Feed ||
                this.PayloadKind == ODataPayloadKind.Property ||
                this.PayloadKind == ODataPayloadKind.Collection ||
                this.PayloadKind == ODataPayloadKind.EntityReferenceLink ||
                this.PayloadKind == ODataPayloadKind.EntityReferenceLinks ||
                this.PayloadKind == ODataPayloadKind.Error ||
                this.PayloadKind == ODataPayloadKind.ServiceDocument ||
                this.PayloadKind == ODataPayloadKind.Parameter)
            {
                AstoriaRequestMessage requestMessage = service.OperationContext.RequestMessage;
                IODataResponseMessage responseMessageOnOperationContext = service.OperationContext.ResponseMessage;

                Version effectiveMaxResponseVersion = VersionUtil.GetEffectiveMaxResponseVersion(service.Configuration.DataServiceBehavior.MaxProtocolVersion.ToVersion(), requestMessage.RequestMaxVersion);
                bool isEntityOrFeed = this.PayloadKind == ODataPayloadKind.Entry || this.PayloadKind == ODataPayloadKind.Feed;
                if (ContentTypeUtil.IsResponseMediaTypeJsonLight(requestMessage.GetAcceptableContentTypes(), isEntityOrFeed, effectiveMaxResponseVersion))
                {
                    // If JSON light 'wins', then bump the version to V3.
                    requestDescription.VerifyAndRaiseResponseVersion(VersionUtil.Version4Dot0, service);
                    responseMessageOnOperationContext.SetHeader(XmlConstants.HttpODataVersion, XmlConstants.ODataVersion4Dot0 + ";");
                }
            }

            if (this.requestDescription.TargetKind == RequestTargetKind.MediaResource)
            {
                Debug.Assert(this.PayloadKind == ODataPayloadKind.BinaryValue, "payloadKind == ODataPayloadKind.BinaryValue");

                // Note that GetReadStream will set the ResponseETag before it returns
                this.mediaResourceStream = service.StreamProvider.GetReadStream(
                    this.queryResults.Current,
                    this.requestDescription.StreamProperty,
                    this.service.OperationContext);
            }
            else if (this.PayloadKind != ODataPayloadKind.BinaryValue)
            {
                IEdmModel model;
                if (this.PayloadKind == ODataPayloadKind.MetadataDocument)
                {
                    model = MetadataSerializer.PrepareModelForSerialization(this.service.Provider, this.service.Configuration);
                }
                else
                {
                    model = this.GetModelFromService();
                }

                // Create the message writer using which the response needs to be written.
                this.messageWriterBuilder = MessageWriterBuilder.ForNormalRequest(
                    this.service,
                    this.requestDescription,
                    this.actualResponseMessageWhoseHeadersMayBeOverridden,
                    model);
                
                this.messageWriter = this.messageWriterBuilder.CreateWriter();

                try
                {
                    // Make sure all the headers are written before the method returns.
                    this.contentFormat = ODataUtils.SetHeadersForPayload(this.messageWriter, this.PayloadKind);
                }
                catch (ODataContentTypeException contentTypeException)
                {
                    throw new DataServiceException(415, null, Strings.DataServiceException_UnsupportedMediaType, null, contentTypeException);
                }

                Debug.Assert(requestDescription.ResponseFormat != null, "Response format should already have been determined.");
                Debug.Assert(ReferenceEquals(this.contentFormat, requestDescription.ResponseFormat.Format), "Response format in request description did not match format when writing.");

                if (this.PayloadKind == ODataPayloadKind.Value && !String.IsNullOrEmpty(this.requestDescription.MimeType))
                {
                    this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpContentType, this.requestDescription.MimeType);
                }

                // EPM is currently removed, but this doesn't seem to be used for EPM only. The old comment was saying:
                // In astoria, there is a bug in V1/V2 that while computing response version, we did not take
                // epm into account. Hence while creating the writer, we need to pass the RequestDescription.ActualResponseVersion
                // so that ODataLib can do the correct payload validation. But we need to write the response version without
                // the epm into the response headers because of backward-compat issue. Hence over-writing the response version
                // header with the wrong version value.
                string responseVersion = this.requestDescription.ResponseVersion.ToString() + ";";
                this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpODataVersion, responseVersion);
            }
        }
        /// <summary>
        /// Throws exceptions if the $select query option cannot be specified on this request.
        /// </summary>
        /// <param name="requestDescription">The request description.</param>
        private static void ValidateSelectIsAllowedForRequest(RequestDescription requestDescription)
        {
            // We only allow $select on entity/entityset queries. Queries which return a primitive/complex value don't support $select.
            if (requestDescription.TargetResourceType == null || (requestDescription.TargetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable);
            }

            // $select can't be used on $ref URIs as it doesn't make sense
            if (requestDescription.SegmentInfos.Any(si => si.TargetKind == RequestTargetKind.Link))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable);
            }
        }
        /// <summary>
        /// Processes query arguments and returns a request description for 
        /// the resulting query.
        /// </summary>
        /// <param name="service">Service with data and configuration information.</param>
        /// <param name="description">Description for request processed so far.</param>
        /// <returns>A new <see cref="RequestDescription"/>.</returns>
        internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description)
        {
            Debug.Assert(service != null, "service != null");

            // When the request doesn't produce an IQueryable result or it is a service action
            // we can short-circuit all further processing.
            if (description.RequestExpression == null || description.IsServiceActionRequest || !(typeof(IQueryable).IsAssignableFrom(description.RequestExpression.Type)))
            {
                WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/);
                return description;
            }

            return new RequestQueryProcessor(service, description).ProcessQuery();
        }