Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataWeb / Server / System / Data / Services / RequestUriProcessor.cs / 2 / RequestUriProcessor.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a class capable of processing Astoria request Uris. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services { #region Namespaces. using System; using System.Collections; using System.Collections.Generic; #if ASTORIA_OPEN_OBJECT using System.Data.Services.OpenTypes; #endif using System.Data.Services.Parsing; using System.Data.Services.Providers; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. // Syntax for Astoria segments: // segment ::= identifier [ query ] // query ::= "(" key ")" // key ::= keyvalue *["," keyvalue] // keyvalue ::= *quotedvalue | *unquotedvalue // quotedvalue ::= "'" *qvchar "'" // qvchar ::= char-except-quote | "''" // unquotedvalue ::= char ////// Use this class to process a web data service request Uri. /// internal static class RequestUriProcessor { ///MethodInfo for the RequestUriProcessor.InvokeWhere method. private static readonly MethodInfo InvokeWhereMethodInfo = typeof(RequestUriProcessor).GetMethod("InvokeWhere", BindingFlags.NonPublic | BindingFlags.Static); ///Recursion limit on segment length. private const int RecursionLimit = 100; ////// Parses the request Uri that the host is exposing and returns /// information about the intended results. /// /// Request uri that needs to get processed. /// Data service for which the request is being processed. ////// An initialized RequestDescription instance describing what the /// request is for. /// ////// A ///with status code 404 (Not Found) is returned if an identifier /// in a segment cannot be resolved; 400 (Bad Request) is returned if a syntax /// error is found when processing a restriction (parenthesized text) or /// in the query portion. /// /// Very important: no rights are checked on the last segment of the request. /// internal static RequestDescription ProcessRequestUri(Uri absoluteRequestUri, IDataService service) { Debug.Assert(service != null, "service != null"); Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")"); string[] segments = EnumerateSegments(absoluteRequestUri, service.Host.AbsoluteServiceUri); if (segments.Length > RecursionLimit) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_TooManySegments); } SegmentInfo[] segmentInfos = CreateSegments(segments, service); SegmentInfo lastSegment = (segmentInfos.Length == 0) ? null : segmentInfos[segmentInfos.Length - 1]; RequestTargetKind targetKind = (lastSegment == null) ? RequestTargetKind.ServiceDirectory : lastSegment.TargetKind; // Create a ResultDescription from the processed segments. RequestDescription resultDescription; Uri resultUri = GetResultUri(service.Host); if (targetKind == RequestTargetKind.Metadata || targetKind == RequestTargetKind.Batch || targetKind == RequestTargetKind.ServiceDirectory) { resultDescription = new RequestDescription(targetKind, RequestTargetSource.None, resultUri); } else if (targetKind == RequestTargetKind.VoidServiceOperation) { Debug.Assert(lastSegment != null, "lastSegment != null"); Debug.Assert(lastSegment.TargetSource == RequestTargetSource.ServiceOperation, "targetSource == RequestTargetSource.ServiceOperation"); lastSegment.Operation.Method.Invoke(service, lastSegment.OperationParameters); resultDescription = new RequestDescription( RequestTargetKind.VoidServiceOperation, // targetKind RequestTargetSource.ServiceOperation, // targetSource resultUri); // resultUri } else { Debug.Assert(lastSegment != null, "lastSegment != null"); Debug.Assert( targetKind == RequestTargetKind.ComplexObject || #if ASTORIA_OPEN_OBJECT targetKind == RequestTargetKind.OpenProperty || targetKind == RequestTargetKind.OpenPropertyValue || #endif targetKind == RequestTargetKind.Primitive || targetKind == RequestTargetKind.PrimitiveValue || targetKind == RequestTargetKind.Resource, "Known targetKind " + targetKind); RequestTargetSource targetSource = lastSegment.TargetSource; ResourceProperty projectedProperty = lastSegment.ProjectedProperty; string containerName = #if ASTORIA_OPEN_OBJECT (lastSegment.TargetKind != RequestTargetKind.PrimitiveValue && lastSegment.TargetKind != RequestTargetKind.OpenPropertyValue) ? #else lastSegment.TargetKind != RequestTargetKind.PrimitiveValue ? #endif lastSegment.Identifier : segmentInfos[segmentInfos.Length - 2].Identifier; bool usesContainerName = (targetSource == RequestTargetSource.Property && projectedProperty != null && projectedProperty.Kind != ResourcePropertyKind.ResourceSetReference) || (targetSource == RequestTargetSource.ServiceOperation && lastSegment.Operation.ResultKind == ServiceOperationResultKind.QueryWithSingleResult); string mimeType = (targetSource == RequestTargetSource.Property && projectedProperty != null) ? projectedProperty.MimeType : (targetSource == RequestTargetSource.ServiceOperation) ? lastSegment.Operation.MimeType : null; resultDescription = new RequestDescription( segmentInfos, containerName, usesContainerName, mimeType, resultUri); } // Process query options ($filter, $orderby, $expand, etc.) resultDescription = RequestQueryProcessor.ProcessQuery(service, resultDescription); return resultDescription; } ///Appends a segment with the specified escaped /// URI to append to. /// Segment text, already escaped. ///. A new URI with a new segment escaped. internal static Uri AppendEscapedSegment(Uri uri, string text) { Debug.Assert(uri != null, "uri != null"); Debug.Assert(text != null, "text != null"); UriBuilder builder = new UriBuilder(uri); if (!builder.Path.EndsWith("/", StringComparison.Ordinal)) { builder.Path += "/"; } builder.Path += text; return builder.Uri; } ///Appends a segment with the specified unescaped /// URI to append to. /// Segment text, already escaped. ///. A new URI with a new segment escaped. internal static Uri AppendUnescapedSegment(Uri uri, string text) { Debug.Assert(uri != null, "uri != null"); Debug.Assert(text != null, "text != null"); return AppendEscapedSegment(uri, Uri.EscapeDataString(text)); } ///Gets the absolute URI that a reference (typically from a POST or PUT body) points to. /// Textual, URI-encoded reference. /// Request parameters with request and service URIs. ///The absolute URI that internal static Uri GetAbsoluteUriFromReference(string reference, CachedRequestParams request) { return GetAbsoluteUriFromReference(reference, request.AbsoluteServiceUri); } ///resolves to. Gets the absolute URI that a reference (typically from a POST or PUT body) points to. /// Textual, URI-encoded reference. /// Absolure URI for service, used to validate that the URI points within. ///The absolute URI that internal static Uri GetAbsoluteUriFromReference(string reference, Uri absoluteServiceUri) { Debug.Assert(!String.IsNullOrEmpty(reference), "!String.IsNullOrEmpty(reference) -- caller should check and throw appropriate message"); Debug.Assert(absoluteServiceUri != null, "absoluteServiceUri != null"); Debug.Assert(absoluteServiceUri.IsAbsoluteUri, "absoluteServiceUri.IsAbsoluteUri(" + absoluteServiceUri + ")"); Uri referenceAsUri = new Uri(reference, UriKind.RelativeOrAbsolute); if (!referenceAsUri.IsAbsoluteUri) { string slash = String.Empty; if (absoluteServiceUri.OriginalString.EndsWith("/", StringComparison.Ordinal)) { if (reference.StartsWith("/", StringComparison.Ordinal)) { reference = reference.Substring(1, reference.Length - 1); } } else if (!reference.StartsWith("/", StringComparison.Ordinal)) { slash = "/"; } referenceAsUri = new Uri(absoluteServiceUri.OriginalString + slash + reference); } if (!UriUtil.UriInvariantInsensitiveIsBaseOf(absoluteServiceUri, referenceAsUri)) { string message = Strings.BadRequest_RequestUriDoesNotHaveTheRightBaseUri(referenceAsUri, absoluteServiceUri); throw DataServiceException.CreateBadRequestError(message); } Debug.Assert( referenceAsUri.IsAbsoluteUri, "referenceAsUri.IsAbsoluteUri(" + referenceAsUri + ") - otherwise composition from absolute yielded relative"); return referenceAsUri; } ///resolves to. Gets the specified ///as a string suitable for an HTTP request. to get string for. /// A string suitable for an HTTP request. internal static string GetHttpRequestUrl(Uri uri) { Debug.Assert(uri != null, "uri != null"); if (uri.IsAbsoluteUri) { return uri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped); } else { return uri.OriginalString; } } ///Gets the URI to the results, without the query component. /// Host with request information. ///The URI to the results, without the query component. internal static Uri GetResultUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri requestUri = GetAbsoluteRequestUri(host); UriBuilder resultBuilder = new UriBuilder(requestUri); resultBuilder.Query = null; // This is fix for bug 565322. // Since we don't allow uri to compose on collections, () must be present // as the last thing in the uri, if present. We need to remove the () from // the uri, since its a optional thing and we want to return a canonical // uri from the server. if (resultBuilder.Path.EndsWith("()", StringComparison.Ordinal)) { resultBuilder.Path = resultBuilder.Path.Substring(0, resultBuilder.Path.Length - 2); } return resultBuilder.Uri; } ///Gets a non-null /// Host for the service. ///for the hosted service. A non-null internal static Uri GetAbsoluteRequestUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri serviceUri = host.AbsoluteRequestUri; if (serviceUri == null) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriCannotBeNull); } else if (!serviceUri.IsAbsoluteUri) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriMustBeAbsolute); } else { return serviceUri; } } ///for the hosted service. Gets a non-null /// Host for the service. ///for the hosted service. A non-null internal static Uri GetServiceUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri serviceUri = host.AbsoluteServiceUri; if (serviceUri == null) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriCannotBeNull); } else if (!serviceUri.IsAbsoluteUri) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriMustBeAbsolute); } else { return serviceUri; } } ///for the hosted service. /// Given the uri, extract the key values from the uri /// /// uri from which the key values needs to be extracted /// Data context for which the request is being processed. /// returns the name of the source resource container name ///key values as specified in the uri internal static KeyInstance ExtractKeyValuesFromUri(Uri absoluteRequestUri, IDataService service, out string containerName) { Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")"); Debug.Assert(service != null, "service != null"); RequestDescription description = RequestUriProcessor.ProcessRequestUri(absoluteRequestUri, service); // if (description.TargetKind != RequestTargetKind.Resource || !description.IsSingleResult || description.TargetSource != RequestTargetSource.EntitySet) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_MustSpecifyCanonicalUriInPayload(absoluteRequestUri)); } // Dereferencing an object by specifying its key values implied the right to read it. Debug.Assert(description.LastSegmentInfo.SingleResult, "description.LastSegmentInfo.SingleResult"); service.Configuration.CheckResourceRightsForRead(description.LastSegmentInfo.TargetContainer, true /* singleResult */); containerName = description.ContainerName; Debug.Assert(description.LastSegmentInfo.Key != null && !description.LastSegmentInfo.Key.IsEmpty, "Key Must be specified"); return description.LastSegmentInfo.Key; } ///Invokes Queryable.Select for the specified query and selector. /// Query to invoke .Select method on. /// Type that will be projected out. /// Selector lambda expression. ///The resulting query. internal static IQueryable InvokeSelectForTypes(IQueryable query, Type projectedType, LambdaExpression selector) { Debug.Assert(query != null, "query != null"); Debug.Assert(projectedType != null, "projectedType != null"); Debug.Assert(selector != null, "selector != null"); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, projectedType); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Invokes Queryable.Where for the specified query and predicate. /// Query to invoke .Where method on. /// Predicate to pass as argument. ///The resulting query. internal static IQueryable InvokeWhereForType(IQueryable query, LambdaExpression predicate) { Debug.Assert(query != null, "query != null"); Debug.Assert(predicate != null, "predicate != null"); MethodInfo whereMethod = InvokeWhereMethodInfo.MakeGenericMethod(query.ElementType); return (IQueryable)whereMethod.Invoke(null, new object[] { query, predicate }); } #if ASTORIA_OPEN_OBJECT ////// Composes the existing query on an open property segment to include the key. /// /// Filter portion of segment. /// Target for the composition. private static void ComposeOpenPropertyQuery(string filter, SegmentInfo segment) { Debug.Assert(filter != null, "filter != null"); Debug.Assert(segment != null, "segment != null"); segment.TargetKind = RequestTargetKind.OpenProperty; segment.Key = ExtractOpenPropertyKeyValues(filter); segment.SingleResult = !segment.Key.IsEmpty; if (segment.SingleResult) { segment.RequestQueryable = SelectOpenResourceByKey(segment.RequestQueryable, segment.Key); } } #endif ///Composes the filter portion of a segment onto the specifies query. /// Data provider that supplies metadata. /// Filter portion of segment, possibly null. /// Segment on which to compose. private static void ComposeQuery(IDataServiceProvider provider, string filter, SegmentInfo segment) { Debug.Assert(provider != null, "provider != null"); Debug.Assert(filter != null, "filter != null"); Debug.Assert(segment != null, "segment!= null"); Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); ResourceType resourceType = provider.GetResourceType(segment.RequestQueryable.ElementType); segment.Key = ExtractKeyValues(resourceType, filter); segment.SingleResult = !segment.Key.IsEmpty; segment.TargetKind = RequestTargetKind.Resource; if (segment.SingleResult) { if (!segment.Key.AreValuesNamed && segment.Key.ValueCount > 1) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_KeysMustBeNamed); } segment.RequestQueryable = SelectResourceByKey(segment.RequestQueryable, resourceType, segment.Key); segment.SingleResult = true; } } ///Creates the first /// Service for which the request is being processed. /// Identifier portion of segment. /// Whether rights should be checked on this segment. /// Query portion with key; possibly null. /// whether this segment references some other segment. ///for a request. A description of the information on the segment. private static SegmentInfo CreateFirstSegment(IDataService service, string identifier, bool checkRights, string queryPortion, out bool crossReferencingUrl) { Debug.Assert(service != null, "service != null"); Debug.Assert(identifier != null, "identifier != null"); crossReferencingUrl = false; SegmentInfo segment = new SegmentInfo(); segment.Identifier = identifier; // Look for well-known system entry points. if (segment.Identifier == XmlConstants.UriMetadataSegment) { WebUtil.CheckSyntaxValid(queryPortion == null); segment.TargetKind = RequestTargetKind.Metadata; return segment; } if (segment.Identifier == XmlConstants.UriBatchSegment) { WebUtil.CheckSyntaxValid(queryPortion == null); segment.TargetKind = RequestTargetKind.Batch; return segment; } // Look for a service operation. segment.Operation = service.Provider.TryResolveServiceOperation(segment.Identifier); if (segment.Operation != null) { WebUtil.DebugEnumIsDefined(segment.Operation.ResultKind); segment.TargetSource = RequestTargetSource.ServiceOperation; string requiredMethod = segment.Operation.Invoke ? XmlConstants.HttpMethodPost : XmlConstants.HttpMethodGet; if (service.Host.RequestHttpMethod != requiredMethod) { throw DataServiceException.CreateMethodNotAllowed(Strings.RequestUriProcessor_MethodNotAllowed, requiredMethod); } segment.TargetContainer = segment.Operation.EntitySet; segment.TargetElementType = segment.Operation.ResultType; segment.OperationParameters = ReadOperationParameters(service.Host, segment.Operation); switch (segment.Operation.ResultKind) { case ServiceOperationResultKind.QueryWithMultipleResults: case ServiceOperationResultKind.QueryWithSingleResult: segment.RequestQueryable = (IQueryable)segment.Operation.Method.Invoke(service, segment.OperationParameters); WebUtil.CheckResourceExists(segment.RequestQueryable != null, segment.Identifier); segment.SingleResult = (segment.Operation.ResultKind == ServiceOperationResultKind.QueryWithSingleResult); break; case ServiceOperationResultKind.DirectValue: case ServiceOperationResultKind.Enumeration: object methodResult; try { methodResult = segment.Operation.Method.Invoke(service, segment.OperationParameters); } catch (TargetInvocationException exception) { ErrorHandler.HandleTargetInvocationException(exception); throw; } segment.SingleResult = (segment.Operation.ResultKind == ServiceOperationResultKind.DirectValue); WebUtil.CheckResourceExists(segment.SingleResult || methodResult != null, segment.Identifier); // Enumerations shouldn't be null. segment.RequestEnumerable = segment.SingleResult ? new object[1] { methodResult } : (IEnumerable)methodResult; segment.TargetElementType = segment.Operation.ResultType; segment.TargetKind = TargetKindFromType(segment.TargetElementType, service.Provider); WebUtil.CheckSyntaxValid(queryPortion == null); RequestQueryProcessor.CheckEmptyQueryArguments(service); break; default: Debug.Assert(segment.Operation.ResultKind == ServiceOperationResultKind.Nothing, "segment.Operation.ResultKind == ServiceOperationResultKind.Nothing"); segment.TargetKind = RequestTargetKind.VoidServiceOperation; break; } if (segment.RequestQueryable != null) { segment.TargetKind = TargetKindFromType(segment.RequestQueryable.ElementType, service.Provider); if (queryPortion != null) { WebUtil.CheckSyntaxValid(!segment.SingleResult); ComposeQuery(service.Provider, queryPortion, segment); } } if (checkRights) { service.Configuration.CheckServiceRights(segment.Operation, segment.SingleResult); } return segment; } SegmentInfo newSegmentInfo = service.GetSegmentForContentId(segment.Identifier); if (newSegmentInfo != null) { newSegmentInfo.Identifier = segment.Identifier; crossReferencingUrl = true; return newSegmentInfo; } // Look for an entity set. ResourceContainer container = service.Provider.TryResolveContainerName(segment.Identifier); WebUtil.CheckResourceExists(container != null, segment.Identifier); segment.RequestQueryable = service.Provider.ResolveContainerName(segment.Identifier); WebUtil.CheckResourceExists(segment.RequestQueryable != null, segment.Identifier); segment.TargetContainer = container; segment.TargetElementType = container.ElementType; segment.TargetSource = RequestTargetSource.EntitySet; segment.TargetKind = RequestTargetKind.Resource; segment.SingleResult = false; if (queryPortion != null) { ComposeQuery(service.Provider, queryPortion, segment); } if (checkRights) { service.Configuration.CheckResourceRightsForRead(container, segment.SingleResult); } segment.RequestQueryable = DataServiceConfiguration.ComposeResourceContainer(service, container, segment.RequestQueryable); return segment; } ///Creates a /// Segments to process. /// Service for which segments are being processed. ///array for the given . Segment information describing the given private static SegmentInfo[] CreateSegments(string[] segments, IDataService service) { Debug.Assert(segments != null, "segments != null"); Debug.Assert(service != null, "service != null"); SegmentInfo previous = null; SegmentInfo[] segmentInfos = new SegmentInfo[segments.Length]; bool crossReferencingUri = false; bool postLinkSegment = false; for (int i = 0; i < segments.Length; i++) { string segmentText = segments[i]; bool checkRights = (i != segments.Length - 1); // Whether rights should be checked on this segment. string identifier; bool hasQuery = ExtractSegmentIdentifier(segmentText, out identifier); string queryPortion = hasQuery ? segmentText.Substring(identifier.Length) : null; SegmentInfo segment; // We allow a single trailing '/', which results in an empty segment. // However System.Uri removes it, so any empty segment we see is a 404 error. if (identifier.Length == 0) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_EmptySegmentInRequestUrl); } if (previous == null) { segment = CreateFirstSegment(service, identifier, checkRights, queryPortion, out crossReferencingUri); } else if (previous.TargetKind == RequestTargetKind.Batch || previous.TargetKind == RequestTargetKind.Metadata || previous.TargetKind == RequestTargetKind.PrimitiveValue || previous.TargetKind == RequestTargetKind.VoidServiceOperation) { // Nothing can come after a $metadata, $value or $batch segment. // Nothing can come after a service operation with void return type. throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); } else if (postLinkSegment) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_CannotSpecifyAfterPostLinkSegment(identifier, XmlConstants.UriLinkSegment)); } else if (previous.TargetKind == RequestTargetKind.Primitive) { // $value is the well-known identifier to return a primitive property // in its natural MIME format. if (identifier != XmlConstants.UriValueSegment) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_ValueSegmentAfterScalarPropertySegment(previous.Identifier, identifier)); } WebUtil.CheckSyntaxValid(!hasQuery); segment = new SegmentInfo(previous); segment.Identifier = identifier; segment.SingleResult = true; segment.TargetKind = RequestTargetKind.PrimitiveValue; } else if (( #if ASTORIA_OPEN_OBJECT previous.TargetKind == RequestTargetKind.OpenProperty || #endif previous.TargetKind == RequestTargetKind.Resource) && previous.SingleResult && identifier == XmlConstants.UriLinkSegment) { segment = new SegmentInfo(previous); segment.Identifier = identifier; segment.TargetKind = RequestTargetKind.Link; } else { Debug.Assert( previous.TargetKind == RequestTargetKind.ComplexObject || previous.TargetKind == RequestTargetKind.Resource || #if ASTORIA_OPEN_OBJECT previous.TargetKind == RequestTargetKind.OpenProperty || #endif previous.TargetKind == RequestTargetKind.Link, "previous.TargetKind(" + previous.TargetKind + ") can have properties"); postLinkSegment = (previous.TargetKind == RequestTargetKind.Link); // Enumerable results cannot be composed at all. if (previous.Operation != null && previous.Operation.ResultKind == ServiceOperationResultKind.Enumeration) { throw DataServiceException.ResourceNotFoundError( Strings.RequestUriProcessor_IEnumerableServiceOperationsCannotBeFurtherComposed(previous.Identifier)); } if (!previous.SingleResult) { throw DataServiceException.CreateBadRequestError( Strings.RequestUriProcessor_CannotQueryCollections(previous.Identifier)); } segment = new SegmentInfo(); segment.Identifier = identifier; segment.TargetSource = RequestTargetSource.Property; // A segment will correspond to a property in the object model; // if we are processing an open type, anything further in the // URI also represents an open type property. #if ASTORIA_OPEN_OBJECT if (previous.TargetElementType == typeof(object)) { Debug.Assert(previous.TargetKind == RequestTargetKind.OpenProperty, "For open properties, the target element type must be object"); segment.ProjectedProperty = null; } else #endif { #if ASTORIA_OPEN_OBJECT Debug.Assert(previous.TargetKind != RequestTargetKind.OpenProperty, "Since the query element type is known, this can't be open property"); #endif segment.ProjectedProperty = service.Provider.TryResolvePropertyName(previous.TargetElementType, identifier); #if !ASTORIA_OPEN_OBJECT WebUtil.CheckResourceExists(segment.ProjectedProperty != null, identifier); #endif } #if ASTORIA_OPEN_OBJECT if (identifier == XmlConstants.UriValueSegment && previous.TargetKind == RequestTargetKind.OpenProperty) { segment.RequestEnumerable = previous.RequestEnumerable; segment.SingleResult = true; segment.TargetKind = RequestTargetKind.OpenPropertyValue; segment.TargetElementType = previous.TargetElementType; } else if (segment.ProjectedProperty == null) { // Handle an open type property. If the current leaf isn't an // object (which implies it's already an open type), then // it should be marked as an open type. if (previous.TargetElementType != typeof(object)) { ResourceType resourceType = service.Provider.GetResourceType(previous.TargetElementType); Debug.Assert(resourceType != null, "resourceType != null -- otherwise WebUtil.CheckResourceExists should have thrown."); WebUtil.CheckResourceExists(resourceType.IsOpenType, segment.Identifier); } segment.TargetElementType = typeof(object); segment.TargetKind = RequestTargetKind.OpenProperty; segment.SingleResult = !hasQuery; if (!crossReferencingUri) { if (hasQuery) { segment.RequestQueryable = SelectOpenPropertyMultiple(previous.RequestQueryable, identifier); ComposeOpenPropertyQuery(queryPortion, segment); } else { segment.RequestQueryable = SelectOpenProperty(previous.RequestQueryable, identifier); } } } else #endif { // Handle a strongly-typed property. segment.TargetContainer = segment.ProjectedProperty.ResourceContainer; segment.TargetElementType = segment.ProjectedProperty.ResourceClrType; ResourcePropertyKind propertyKind = segment.ProjectedProperty.Kind; segment.SingleResult = (propertyKind != ResourcePropertyKind.ResourceSetReference); if (!crossReferencingUri) { segment.RequestQueryable = segment.SingleResult ? SelectElement(previous.RequestQueryable, segment.ProjectedProperty) : SelectMultiple(previous.RequestQueryable, segment.ProjectedProperty); } if (previous.TargetKind == RequestTargetKind.Link && segment.ProjectedProperty.TypeKind != ResourceTypeKind.EntityType) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment(identifier, XmlConstants.UriLinkSegment)); } switch (propertyKind) { case ResourcePropertyKind.ComplexType: segment.TargetKind = RequestTargetKind.ComplexObject; break; case ResourcePropertyKind.ResourceReference: case ResourcePropertyKind.ResourceSetReference: segment.TargetKind = RequestTargetKind.Resource; segment.TargetContainer = service.Provider.GetContainer( previous.TargetContainer.Name, previous.TargetElementType, segment.ProjectedProperty); Debug.Assert( segment.TargetContainer != null, "segment.TargetContainer != null -- for strongly typed navigation properties, we should always know the target container"); break; default: Debug.Assert(segment.ProjectedProperty.IsOfKind(ResourcePropertyKind.Primitive), "must be primitive type property"); segment.TargetKind = RequestTargetKind.Primitive; break; } if (hasQuery) { WebUtil.CheckSyntaxValid(!segment.SingleResult); if (!crossReferencingUri) { ComposeQuery(service.Provider, queryPortion, segment); } else { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation); } } // Do security checks and authorization query composition. if (segment.TargetContainer != null) { if (checkRights) { service.Configuration.CheckResourceRightsForRead(segment.TargetContainer, segment.SingleResult); } if (!crossReferencingUri) { segment.RequestQueryable = DataServiceConfiguration.ComposeResourceContainer(service, segment.TargetContainer, segment.RequestQueryable); } } } } #if DEBUG segment.AssertValid(); #endif segmentInfos[i] = segment; previous = segment; } if (segments.Length != 0 && previous.TargetKind == RequestTargetKind.Link) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_MissingSegmentAfterLink(XmlConstants.UriLinkSegment)); } return segmentInfos; } ///. /// Returns an object that can enumerate the segments in the specified path (eg: /foo/bar -> foo, bar). /// /// A valid path portion of an uri. /// baseUri for the request that is getting processed. ///An enumerable object of unescaped segments. private static string[] EnumerateSegments(Uri absoluteRequestUri, Uri baseUri) { Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri.IsAbsoluteUri + ")"); Debug.Assert(baseUri != null, "baseUri != null"); Debug.Assert(baseUri.IsAbsoluteUri, "baseUri.IsAbsoluteUri(" + baseUri + ")"); if (!UriUtil.UriInvariantInsensitiveIsBaseOf(baseUri, absoluteRequestUri)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_RequestUriDoesNotHaveTheRightBaseUri(absoluteRequestUri, baseUri)); } try { // Uri uri = absoluteRequestUri; int numberOfSegmentsToSkip = 0; // Since there is a svc part in the segment, we will need to skip 2 segments numberOfSegmentsToSkip = baseUri.Segments.Length; string[] uriSegments = uri.Segments; int populatedSegmentCount = 0; for (int i = numberOfSegmentsToSkip; i < uriSegments.Length; i++) { string segment = uriSegments[i]; if (segment.Length != 0 && segment != "/") { populatedSegmentCount++; } } string[] segments = new string[populatedSegmentCount]; int segmentIndex = 0; for (int i = numberOfSegmentsToSkip; i < uriSegments.Length; i++) { string segment = uriSegments[i]; if (segment.Length != 0 && segment != "/") { if (segment[segment.Length - 1] == '/') { segment = segment.Substring(0, segment.Length - 1); } segments[segmentIndex++] = Uri.UnescapeDataString(segment); } } Debug.Assert(segmentIndex == segments.Length, "segmentIndex == segments.Length -- otherwise we mis-counted populated/skipped segments."); return segments; } catch (UriFormatException) { throw DataServiceException.CreateSyntaxError(); } } ///Returns an object that can enumerate key values. /// resource type whose keys need to be extracted /// Key (query) part of an Astoria segment. ///An object that can enumerate key values. private static KeyInstance ExtractKeyValues(ResourceType resourceType, string filter) { KeyInstance key; filter = RemoveFilterParens(filter); WebUtil.CheckSyntaxValid(KeyInstance.TryParseFromUri(filter, out key)); if (!key.IsEmpty) { // Make sure the keys specified in the uri matches with the number of keys in the metadata if (resourceType.KeyProperties.Count != key.ValueCount) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(resourceType.FullName)); } WebUtil.CheckSyntaxValid(key.TryConvertValues(resourceType)); } return key; } ///Returns an object that can enumerate key values for an open property. /// Key (query) part of an Astoria segment. ///An object that can enumerate key values. private static KeyInstance ExtractOpenPropertyKeyValues(string filter) { KeyInstance key; filter = RemoveFilterParens(filter); WebUtil.CheckSyntaxValid(KeyInstance.TryParseFromUri(filter, out key)); key.RemoveQuotes(); return key; } ///Extracts the identifier part of the unescaped Astoria segment. /// Unescaped Astoria segment. /// On returning, the identifier in the segment. ///true if keys follow the identifier. private static bool ExtractSegmentIdentifier(string segment, out string identifier) { Debug.Assert(segment != null, "segment != null"); int filterStart = 0; while (filterStart < segment.Length && segment[filterStart] != '(') { filterStart++; } identifier = segment.Substring(0, filterStart); return filterStart < segment.Length; } ///Generic method to invoke a Select method on an IQueryable source. ///Element type of the source. ///Result type of the projection. /// Source query. /// Lambda expression that turns TSource into TResult. ///A new query that projects TSource into TResult. private static IQueryable InvokeSelect(IQueryable source, LambdaExpression selector) { Debug.Assert(source != null, "source != null"); Debug.Assert(selector != null, "selector != null"); IQueryable typedSource = (IQueryable )source; Expression > typedSelector = (Expression >)selector; return Queryable.Select (typedSource, typedSelector); } /// Generic method to invoke a SelectMany method on an IQueryable source. ///Element type of the source. ///Result type of the projection. /// Source query. /// Lambda expression that turns TSource into IEnumerable<TResult>. ///A new query that projects TSource into IEnumerable<TResult>. private static IQueryable InvokeSelectMany(IQueryable source, LambdaExpression selector) { Debug.Assert(source != null, "source != null"); Debug.Assert(selector != null, "selector != null"); IQueryable typedSource = (IQueryable )source; Expression >> typedSelector = (Expression >>)selector; return Queryable.SelectMany (typedSource, typedSelector); } /// Generic method to invoke a Where method on an IQueryable source. ///Element type of the source. /// Source query. /// Lambda expression that filters the result of the query. ///A new query that filters the query. private static IQueryable InvokeWhere(IQueryable query, LambdaExpression predicate) { Debug.Assert(query != null, "query != null"); Debug.Assert(predicate != null, "predicate != null"); IQueryable typedQueryable = (IQueryable )query; Expression > typedPredicate = (Expression >)predicate; return Queryable.Where (typedQueryable, typedPredicate); } /// /// Reads the parameters for the specified /// Host with request information. /// Operation with parameters to be read. ///from the /// . /// A new object[] with parameter values. private static object[] ReadOperationParameters(IDataServiceHost host, ServiceOperation operation) { Debug.Assert(host != null, "host != null"); object[] operationParameters = new object[operation.Parameters.Length]; for (int i = 0; i < operation.Parameters.Length; i++) { Type parameterType = operation.Parameters[i].Type; string queryStringValue = host.GetQueryStringItem(operation.Parameters[i].Name); Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (String.IsNullOrEmpty(queryStringValue)) { WebUtil.CheckSyntaxValid(parameterType.IsClass || underlyingType != null); operationParameters[i] = null; } else { // We choose to be a little more flexible than with keys and // allow surrounding whitespace (which is never significant). // See SQLBUDT #555944. queryStringValue = queryStringValue.Trim(); Type targetType = underlyingType ?? parameterType; if (WebConvert.IsKeyTypeQuoted(targetType)) { WebUtil.CheckSyntaxValid(WebConvert.IsKeyValueQuoted(queryStringValue)); } WebUtil.CheckSyntaxValid(WebConvert.TryKeyStringToPrimitive(queryStringValue, targetType, out operationParameters[i])); } } return operationParameters; } ///Removes the parens around the filter part of a query. /// Filter with parens included. ///Filter without parens. private static string RemoveFilterParens(string filter) { Debug.Assert(filter != null, "filter != null"); WebUtil.CheckSyntaxValid(filter.Length > 0 && filter[0] == '(' && filter[filter.Length - 1] == ')'); return filter.Substring(1, filter.Length - 2); } ///Project a property with a single element out of the specified query. /// Base query to project from. /// Property to project. ///A query with a composed primitive property projection. private static IQueryable SelectElement(IQueryable query, ResourceProperty property) { Debug.Assert(query != null, "query != null"); Debug.Assert(property.Kind != ResourcePropertyKind.ResourceSetReference, "property != ResourcePropertyKind.ResourceSetReference"); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); MemberExpression body = Expression.Property(parameter, property.Name); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, property.Type); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Project a property with multiple elements out of the specified query. /// Base query to project from. /// Property to project. ///A query with a composed primitive property projection. private static IQueryable SelectMultiple(IQueryable query, ResourceProperty property) { Debug.Assert(query != null, "query != null"); Debug.Assert(property.Kind == ResourcePropertyKind.ResourceSetReference, "property == ResourcePropertyKind.ResourceSetReference"); Type enumerableElement = BaseServiceProvider.GetIEnumerableElement(property.Type); Debug.Assert(enumerableElement != null, "Providers should never expose a property as a resource-set if it doesn't implement IEnumerable`1."); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); UnaryExpression body = Expression.ConvertChecked( Expression.Property(parameter, property.Name), typeof(IEnumerable<>).MakeGenericType(enumerableElement)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelectMany", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, enumerableElement); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } #if ASTORIA_OPEN_OBJECT ///Project a property with a single element out of the specified query over an open property. /// Base query to project from. /// Name of property to project. ///A query with a composed property projection. private static IQueryable SelectOpenProperty(IQueryable query, string propertyName) { Debug.Assert(query != null, "query != null"); Debug.Assert(propertyName != null, "propertyName != null"); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); MethodCallExpression body = Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, parameter, Expression.Constant(propertyName)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, typeof(object)); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Project a property with multiple elements out of the specified query over an open property. /// Base query to project from. /// Name of property to project. ///A query with a composed primitive property projection. private static IQueryable SelectOpenPropertyMultiple(IQueryable query, string propertyName) { Debug.Assert(query != null, "query != null"); Debug.Assert(propertyName != null, "propertyName != null"); Type enumerableElement = typeof(object); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); Expression body = Expression.Call(null /* instance */, LateBoundMethods.GetSequenceValueMethodInfo, parameter, Expression.Constant(propertyName)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelectMany", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, enumerableElement); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Selects a single resource by key values. /// Base query for resources /// Key values for the given resource type. ///A new query that selects the single resource that matches the specified key values. private static IQueryable SelectOpenResourceByKey(IQueryable query, KeyInstance key) { Debug.Assert(query != null, "query != null"); Debug.Assert(query.ElementType == typeof(object), "query.Expression.Type == typeof(object) -- otherwise we aren't primary-key-filtering on an open type."); Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty -- otherwise we aren't filtering"); // if (key.AreValuesNamed) { throw Error.NotImplemented(); } ParameterExpression parameter = Expression.Parameter(typeof(object), "element"); Expression body = Expression.Call( null /* instance */, LateBoundMethods.KeyEqualsMethodInfo, Expression.Call(null /* instance */, LateBoundMethods.KeyMethodInfo, parameter), Expression.Constant(key.PositionalValues.ToArray(), typeof(object[]))); LambdaExpression predicate = Expression.Lambda(body, parameter); return InvokeWhereForType(query, predicate); } #endif ///Selects a single resource by key values. /// Base query for resources /// resource type whose keys are specified /// Key values for the given resource type. ///A new query that selects the single resource that matches the specified key values. private static IQueryable SelectResourceByKey(IQueryable query, ResourceType resourceType, KeyInstance key) { Debug.Assert(query != null, "query != null"); Debug.Assert(key != null && key.ValueCount != 0, "key != null && key.ValueCount != 0"); Debug.Assert(resourceType.KeyProperties.Count == key.ValueCount, "resourceType.KeyProperties.Count == key.ValueCount"); for (int i = 0; i < resourceType.KeyProperties.Count; i++) { ResourceProperty keyProperty = resourceType.KeyProperties[i]; Debug.Assert(keyProperty.IsOfKind(ResourcePropertyKind.Key), "keyProperty.IsOfKind(ResourcePropertyKind.Key)"); object keyValue; if (key.AreValuesNamed) { keyValue = key.NamedValues[keyProperty.Name]; } else { keyValue = key.PositionalValues[i]; } ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); BinaryExpression body = Expression.Equal( Expression.Property(parameter, keyProperty.Name), Expression.Constant(keyValue)); LambdaExpression predicate = Expression.Lambda(body, parameter); query = InvokeWhereForType(query, predicate); } return query; } ///Determines a matching target kind from the specified type. ///of element to get kind for. /// that supplies type metadata. /// An appropriate private static RequestTargetKind TargetKindFromType(Type type, IDataServiceProvider provider) { Debug.Assert(type != null, "type != null"); Debug.Assert(provider != null, "provider != null"); ResourceTypeKind typeKind = provider.GetResourceTypeKind(type); switch (typeKind) { case ResourceTypeKind.ComplexType: return RequestTargetKind.ComplexObject; case ResourceTypeKind.EntityType: return RequestTargetKind.Resource; default: Debug.Assert(typeKind == ResourceTypeKind.Primitive, "typeKind == ResourceTypeKind.Primitive"); return RequestTargetKind.Primitive; } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- //for the specified . // Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a class capable of processing Astoria request Uris. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services { #region Namespaces. using System; using System.Collections; using System.Collections.Generic; #if ASTORIA_OPEN_OBJECT using System.Data.Services.OpenTypes; #endif using System.Data.Services.Parsing; using System.Data.Services.Providers; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. // Syntax for Astoria segments: // segment ::= identifier [ query ] // query ::= "(" key ")" // key ::= keyvalue *["," keyvalue] // keyvalue ::= *quotedvalue | *unquotedvalue // quotedvalue ::= "'" *qvchar "'" // qvchar ::= char-except-quote | "''" // unquotedvalue ::= char ////// Use this class to process a web data service request Uri. /// internal static class RequestUriProcessor { ///MethodInfo for the RequestUriProcessor.InvokeWhere method. private static readonly MethodInfo InvokeWhereMethodInfo = typeof(RequestUriProcessor).GetMethod("InvokeWhere", BindingFlags.NonPublic | BindingFlags.Static); ///Recursion limit on segment length. private const int RecursionLimit = 100; ////// Parses the request Uri that the host is exposing and returns /// information about the intended results. /// /// Request uri that needs to get processed. /// Data service for which the request is being processed. ////// An initialized RequestDescription instance describing what the /// request is for. /// ////// A ///with status code 404 (Not Found) is returned if an identifier /// in a segment cannot be resolved; 400 (Bad Request) is returned if a syntax /// error is found when processing a restriction (parenthesized text) or /// in the query portion. /// /// Very important: no rights are checked on the last segment of the request. /// internal static RequestDescription ProcessRequestUri(Uri absoluteRequestUri, IDataService service) { Debug.Assert(service != null, "service != null"); Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")"); string[] segments = EnumerateSegments(absoluteRequestUri, service.Host.AbsoluteServiceUri); if (segments.Length > RecursionLimit) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_TooManySegments); } SegmentInfo[] segmentInfos = CreateSegments(segments, service); SegmentInfo lastSegment = (segmentInfos.Length == 0) ? null : segmentInfos[segmentInfos.Length - 1]; RequestTargetKind targetKind = (lastSegment == null) ? RequestTargetKind.ServiceDirectory : lastSegment.TargetKind; // Create a ResultDescription from the processed segments. RequestDescription resultDescription; Uri resultUri = GetResultUri(service.Host); if (targetKind == RequestTargetKind.Metadata || targetKind == RequestTargetKind.Batch || targetKind == RequestTargetKind.ServiceDirectory) { resultDescription = new RequestDescription(targetKind, RequestTargetSource.None, resultUri); } else if (targetKind == RequestTargetKind.VoidServiceOperation) { Debug.Assert(lastSegment != null, "lastSegment != null"); Debug.Assert(lastSegment.TargetSource == RequestTargetSource.ServiceOperation, "targetSource == RequestTargetSource.ServiceOperation"); lastSegment.Operation.Method.Invoke(service, lastSegment.OperationParameters); resultDescription = new RequestDescription( RequestTargetKind.VoidServiceOperation, // targetKind RequestTargetSource.ServiceOperation, // targetSource resultUri); // resultUri } else { Debug.Assert(lastSegment != null, "lastSegment != null"); Debug.Assert( targetKind == RequestTargetKind.ComplexObject || #if ASTORIA_OPEN_OBJECT targetKind == RequestTargetKind.OpenProperty || targetKind == RequestTargetKind.OpenPropertyValue || #endif targetKind == RequestTargetKind.Primitive || targetKind == RequestTargetKind.PrimitiveValue || targetKind == RequestTargetKind.Resource, "Known targetKind " + targetKind); RequestTargetSource targetSource = lastSegment.TargetSource; ResourceProperty projectedProperty = lastSegment.ProjectedProperty; string containerName = #if ASTORIA_OPEN_OBJECT (lastSegment.TargetKind != RequestTargetKind.PrimitiveValue && lastSegment.TargetKind != RequestTargetKind.OpenPropertyValue) ? #else lastSegment.TargetKind != RequestTargetKind.PrimitiveValue ? #endif lastSegment.Identifier : segmentInfos[segmentInfos.Length - 2].Identifier; bool usesContainerName = (targetSource == RequestTargetSource.Property && projectedProperty != null && projectedProperty.Kind != ResourcePropertyKind.ResourceSetReference) || (targetSource == RequestTargetSource.ServiceOperation && lastSegment.Operation.ResultKind == ServiceOperationResultKind.QueryWithSingleResult); string mimeType = (targetSource == RequestTargetSource.Property && projectedProperty != null) ? projectedProperty.MimeType : (targetSource == RequestTargetSource.ServiceOperation) ? lastSegment.Operation.MimeType : null; resultDescription = new RequestDescription( segmentInfos, containerName, usesContainerName, mimeType, resultUri); } // Process query options ($filter, $orderby, $expand, etc.) resultDescription = RequestQueryProcessor.ProcessQuery(service, resultDescription); return resultDescription; } ///Appends a segment with the specified escaped /// URI to append to. /// Segment text, already escaped. ///. A new URI with a new segment escaped. internal static Uri AppendEscapedSegment(Uri uri, string text) { Debug.Assert(uri != null, "uri != null"); Debug.Assert(text != null, "text != null"); UriBuilder builder = new UriBuilder(uri); if (!builder.Path.EndsWith("/", StringComparison.Ordinal)) { builder.Path += "/"; } builder.Path += text; return builder.Uri; } ///Appends a segment with the specified unescaped /// URI to append to. /// Segment text, already escaped. ///. A new URI with a new segment escaped. internal static Uri AppendUnescapedSegment(Uri uri, string text) { Debug.Assert(uri != null, "uri != null"); Debug.Assert(text != null, "text != null"); return AppendEscapedSegment(uri, Uri.EscapeDataString(text)); } ///Gets the absolute URI that a reference (typically from a POST or PUT body) points to. /// Textual, URI-encoded reference. /// Request parameters with request and service URIs. ///The absolute URI that internal static Uri GetAbsoluteUriFromReference(string reference, CachedRequestParams request) { return GetAbsoluteUriFromReference(reference, request.AbsoluteServiceUri); } ///resolves to. Gets the absolute URI that a reference (typically from a POST or PUT body) points to. /// Textual, URI-encoded reference. /// Absolure URI for service, used to validate that the URI points within. ///The absolute URI that internal static Uri GetAbsoluteUriFromReference(string reference, Uri absoluteServiceUri) { Debug.Assert(!String.IsNullOrEmpty(reference), "!String.IsNullOrEmpty(reference) -- caller should check and throw appropriate message"); Debug.Assert(absoluteServiceUri != null, "absoluteServiceUri != null"); Debug.Assert(absoluteServiceUri.IsAbsoluteUri, "absoluteServiceUri.IsAbsoluteUri(" + absoluteServiceUri + ")"); Uri referenceAsUri = new Uri(reference, UriKind.RelativeOrAbsolute); if (!referenceAsUri.IsAbsoluteUri) { string slash = String.Empty; if (absoluteServiceUri.OriginalString.EndsWith("/", StringComparison.Ordinal)) { if (reference.StartsWith("/", StringComparison.Ordinal)) { reference = reference.Substring(1, reference.Length - 1); } } else if (!reference.StartsWith("/", StringComparison.Ordinal)) { slash = "/"; } referenceAsUri = new Uri(absoluteServiceUri.OriginalString + slash + reference); } if (!UriUtil.UriInvariantInsensitiveIsBaseOf(absoluteServiceUri, referenceAsUri)) { string message = Strings.BadRequest_RequestUriDoesNotHaveTheRightBaseUri(referenceAsUri, absoluteServiceUri); throw DataServiceException.CreateBadRequestError(message); } Debug.Assert( referenceAsUri.IsAbsoluteUri, "referenceAsUri.IsAbsoluteUri(" + referenceAsUri + ") - otherwise composition from absolute yielded relative"); return referenceAsUri; } ///resolves to. Gets the specified ///as a string suitable for an HTTP request. to get string for. /// A string suitable for an HTTP request. internal static string GetHttpRequestUrl(Uri uri) { Debug.Assert(uri != null, "uri != null"); if (uri.IsAbsoluteUri) { return uri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped); } else { return uri.OriginalString; } } ///Gets the URI to the results, without the query component. /// Host with request information. ///The URI to the results, without the query component. internal static Uri GetResultUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri requestUri = GetAbsoluteRequestUri(host); UriBuilder resultBuilder = new UriBuilder(requestUri); resultBuilder.Query = null; // This is fix for bug 565322. // Since we don't allow uri to compose on collections, () must be present // as the last thing in the uri, if present. We need to remove the () from // the uri, since its a optional thing and we want to return a canonical // uri from the server. if (resultBuilder.Path.EndsWith("()", StringComparison.Ordinal)) { resultBuilder.Path = resultBuilder.Path.Substring(0, resultBuilder.Path.Length - 2); } return resultBuilder.Uri; } ///Gets a non-null /// Host for the service. ///for the hosted service. A non-null internal static Uri GetAbsoluteRequestUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri serviceUri = host.AbsoluteRequestUri; if (serviceUri == null) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriCannotBeNull); } else if (!serviceUri.IsAbsoluteUri) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriMustBeAbsolute); } else { return serviceUri; } } ///for the hosted service. Gets a non-null /// Host for the service. ///for the hosted service. A non-null internal static Uri GetServiceUri(IDataServiceHost host) { Debug.Assert(host != null, "host != null"); Uri serviceUri = host.AbsoluteServiceUri; if (serviceUri == null) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriCannotBeNull); } else if (!serviceUri.IsAbsoluteUri) { throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriMustBeAbsolute); } else { return serviceUri; } } ///for the hosted service. /// Given the uri, extract the key values from the uri /// /// uri from which the key values needs to be extracted /// Data context for which the request is being processed. /// returns the name of the source resource container name ///key values as specified in the uri internal static KeyInstance ExtractKeyValuesFromUri(Uri absoluteRequestUri, IDataService service, out string containerName) { Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")"); Debug.Assert(service != null, "service != null"); RequestDescription description = RequestUriProcessor.ProcessRequestUri(absoluteRequestUri, service); // if (description.TargetKind != RequestTargetKind.Resource || !description.IsSingleResult || description.TargetSource != RequestTargetSource.EntitySet) { throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_MustSpecifyCanonicalUriInPayload(absoluteRequestUri)); } // Dereferencing an object by specifying its key values implied the right to read it. Debug.Assert(description.LastSegmentInfo.SingleResult, "description.LastSegmentInfo.SingleResult"); service.Configuration.CheckResourceRightsForRead(description.LastSegmentInfo.TargetContainer, true /* singleResult */); containerName = description.ContainerName; Debug.Assert(description.LastSegmentInfo.Key != null && !description.LastSegmentInfo.Key.IsEmpty, "Key Must be specified"); return description.LastSegmentInfo.Key; } ///Invokes Queryable.Select for the specified query and selector. /// Query to invoke .Select method on. /// Type that will be projected out. /// Selector lambda expression. ///The resulting query. internal static IQueryable InvokeSelectForTypes(IQueryable query, Type projectedType, LambdaExpression selector) { Debug.Assert(query != null, "query != null"); Debug.Assert(projectedType != null, "projectedType != null"); Debug.Assert(selector != null, "selector != null"); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, projectedType); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Invokes Queryable.Where for the specified query and predicate. /// Query to invoke .Where method on. /// Predicate to pass as argument. ///The resulting query. internal static IQueryable InvokeWhereForType(IQueryable query, LambdaExpression predicate) { Debug.Assert(query != null, "query != null"); Debug.Assert(predicate != null, "predicate != null"); MethodInfo whereMethod = InvokeWhereMethodInfo.MakeGenericMethod(query.ElementType); return (IQueryable)whereMethod.Invoke(null, new object[] { query, predicate }); } #if ASTORIA_OPEN_OBJECT ////// Composes the existing query on an open property segment to include the key. /// /// Filter portion of segment. /// Target for the composition. private static void ComposeOpenPropertyQuery(string filter, SegmentInfo segment) { Debug.Assert(filter != null, "filter != null"); Debug.Assert(segment != null, "segment != null"); segment.TargetKind = RequestTargetKind.OpenProperty; segment.Key = ExtractOpenPropertyKeyValues(filter); segment.SingleResult = !segment.Key.IsEmpty; if (segment.SingleResult) { segment.RequestQueryable = SelectOpenResourceByKey(segment.RequestQueryable, segment.Key); } } #endif ///Composes the filter portion of a segment onto the specifies query. /// Data provider that supplies metadata. /// Filter portion of segment, possibly null. /// Segment on which to compose. private static void ComposeQuery(IDataServiceProvider provider, string filter, SegmentInfo segment) { Debug.Assert(provider != null, "provider != null"); Debug.Assert(filter != null, "filter != null"); Debug.Assert(segment != null, "segment!= null"); Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); ResourceType resourceType = provider.GetResourceType(segment.RequestQueryable.ElementType); segment.Key = ExtractKeyValues(resourceType, filter); segment.SingleResult = !segment.Key.IsEmpty; segment.TargetKind = RequestTargetKind.Resource; if (segment.SingleResult) { if (!segment.Key.AreValuesNamed && segment.Key.ValueCount > 1) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_KeysMustBeNamed); } segment.RequestQueryable = SelectResourceByKey(segment.RequestQueryable, resourceType, segment.Key); segment.SingleResult = true; } } ///Creates the first /// Service for which the request is being processed. /// Identifier portion of segment. /// Whether rights should be checked on this segment. /// Query portion with key; possibly null. /// whether this segment references some other segment. ///for a request. A description of the information on the segment. private static SegmentInfo CreateFirstSegment(IDataService service, string identifier, bool checkRights, string queryPortion, out bool crossReferencingUrl) { Debug.Assert(service != null, "service != null"); Debug.Assert(identifier != null, "identifier != null"); crossReferencingUrl = false; SegmentInfo segment = new SegmentInfo(); segment.Identifier = identifier; // Look for well-known system entry points. if (segment.Identifier == XmlConstants.UriMetadataSegment) { WebUtil.CheckSyntaxValid(queryPortion == null); segment.TargetKind = RequestTargetKind.Metadata; return segment; } if (segment.Identifier == XmlConstants.UriBatchSegment) { WebUtil.CheckSyntaxValid(queryPortion == null); segment.TargetKind = RequestTargetKind.Batch; return segment; } // Look for a service operation. segment.Operation = service.Provider.TryResolveServiceOperation(segment.Identifier); if (segment.Operation != null) { WebUtil.DebugEnumIsDefined(segment.Operation.ResultKind); segment.TargetSource = RequestTargetSource.ServiceOperation; string requiredMethod = segment.Operation.Invoke ? XmlConstants.HttpMethodPost : XmlConstants.HttpMethodGet; if (service.Host.RequestHttpMethod != requiredMethod) { throw DataServiceException.CreateMethodNotAllowed(Strings.RequestUriProcessor_MethodNotAllowed, requiredMethod); } segment.TargetContainer = segment.Operation.EntitySet; segment.TargetElementType = segment.Operation.ResultType; segment.OperationParameters = ReadOperationParameters(service.Host, segment.Operation); switch (segment.Operation.ResultKind) { case ServiceOperationResultKind.QueryWithMultipleResults: case ServiceOperationResultKind.QueryWithSingleResult: segment.RequestQueryable = (IQueryable)segment.Operation.Method.Invoke(service, segment.OperationParameters); WebUtil.CheckResourceExists(segment.RequestQueryable != null, segment.Identifier); segment.SingleResult = (segment.Operation.ResultKind == ServiceOperationResultKind.QueryWithSingleResult); break; case ServiceOperationResultKind.DirectValue: case ServiceOperationResultKind.Enumeration: object methodResult; try { methodResult = segment.Operation.Method.Invoke(service, segment.OperationParameters); } catch (TargetInvocationException exception) { ErrorHandler.HandleTargetInvocationException(exception); throw; } segment.SingleResult = (segment.Operation.ResultKind == ServiceOperationResultKind.DirectValue); WebUtil.CheckResourceExists(segment.SingleResult || methodResult != null, segment.Identifier); // Enumerations shouldn't be null. segment.RequestEnumerable = segment.SingleResult ? new object[1] { methodResult } : (IEnumerable)methodResult; segment.TargetElementType = segment.Operation.ResultType; segment.TargetKind = TargetKindFromType(segment.TargetElementType, service.Provider); WebUtil.CheckSyntaxValid(queryPortion == null); RequestQueryProcessor.CheckEmptyQueryArguments(service); break; default: Debug.Assert(segment.Operation.ResultKind == ServiceOperationResultKind.Nothing, "segment.Operation.ResultKind == ServiceOperationResultKind.Nothing"); segment.TargetKind = RequestTargetKind.VoidServiceOperation; break; } if (segment.RequestQueryable != null) { segment.TargetKind = TargetKindFromType(segment.RequestQueryable.ElementType, service.Provider); if (queryPortion != null) { WebUtil.CheckSyntaxValid(!segment.SingleResult); ComposeQuery(service.Provider, queryPortion, segment); } } if (checkRights) { service.Configuration.CheckServiceRights(segment.Operation, segment.SingleResult); } return segment; } SegmentInfo newSegmentInfo = service.GetSegmentForContentId(segment.Identifier); if (newSegmentInfo != null) { newSegmentInfo.Identifier = segment.Identifier; crossReferencingUrl = true; return newSegmentInfo; } // Look for an entity set. ResourceContainer container = service.Provider.TryResolveContainerName(segment.Identifier); WebUtil.CheckResourceExists(container != null, segment.Identifier); segment.RequestQueryable = service.Provider.ResolveContainerName(segment.Identifier); WebUtil.CheckResourceExists(segment.RequestQueryable != null, segment.Identifier); segment.TargetContainer = container; segment.TargetElementType = container.ElementType; segment.TargetSource = RequestTargetSource.EntitySet; segment.TargetKind = RequestTargetKind.Resource; segment.SingleResult = false; if (queryPortion != null) { ComposeQuery(service.Provider, queryPortion, segment); } if (checkRights) { service.Configuration.CheckResourceRightsForRead(container, segment.SingleResult); } segment.RequestQueryable = DataServiceConfiguration.ComposeResourceContainer(service, container, segment.RequestQueryable); return segment; } ///Creates a /// Segments to process. /// Service for which segments are being processed. ///array for the given . Segment information describing the given private static SegmentInfo[] CreateSegments(string[] segments, IDataService service) { Debug.Assert(segments != null, "segments != null"); Debug.Assert(service != null, "service != null"); SegmentInfo previous = null; SegmentInfo[] segmentInfos = new SegmentInfo[segments.Length]; bool crossReferencingUri = false; bool postLinkSegment = false; for (int i = 0; i < segments.Length; i++) { string segmentText = segments[i]; bool checkRights = (i != segments.Length - 1); // Whether rights should be checked on this segment. string identifier; bool hasQuery = ExtractSegmentIdentifier(segmentText, out identifier); string queryPortion = hasQuery ? segmentText.Substring(identifier.Length) : null; SegmentInfo segment; // We allow a single trailing '/', which results in an empty segment. // However System.Uri removes it, so any empty segment we see is a 404 error. if (identifier.Length == 0) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_EmptySegmentInRequestUrl); } if (previous == null) { segment = CreateFirstSegment(service, identifier, checkRights, queryPortion, out crossReferencingUri); } else if (previous.TargetKind == RequestTargetKind.Batch || previous.TargetKind == RequestTargetKind.Metadata || previous.TargetKind == RequestTargetKind.PrimitiveValue || previous.TargetKind == RequestTargetKind.VoidServiceOperation) { // Nothing can come after a $metadata, $value or $batch segment. // Nothing can come after a service operation with void return type. throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); } else if (postLinkSegment) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_CannotSpecifyAfterPostLinkSegment(identifier, XmlConstants.UriLinkSegment)); } else if (previous.TargetKind == RequestTargetKind.Primitive) { // $value is the well-known identifier to return a primitive property // in its natural MIME format. if (identifier != XmlConstants.UriValueSegment) { throw DataServiceException.ResourceNotFoundError(Strings.RequestUriProcessor_ValueSegmentAfterScalarPropertySegment(previous.Identifier, identifier)); } WebUtil.CheckSyntaxValid(!hasQuery); segment = new SegmentInfo(previous); segment.Identifier = identifier; segment.SingleResult = true; segment.TargetKind = RequestTargetKind.PrimitiveValue; } else if (( #if ASTORIA_OPEN_OBJECT previous.TargetKind == RequestTargetKind.OpenProperty || #endif previous.TargetKind == RequestTargetKind.Resource) && previous.SingleResult && identifier == XmlConstants.UriLinkSegment) { segment = new SegmentInfo(previous); segment.Identifier = identifier; segment.TargetKind = RequestTargetKind.Link; } else { Debug.Assert( previous.TargetKind == RequestTargetKind.ComplexObject || previous.TargetKind == RequestTargetKind.Resource || #if ASTORIA_OPEN_OBJECT previous.TargetKind == RequestTargetKind.OpenProperty || #endif previous.TargetKind == RequestTargetKind.Link, "previous.TargetKind(" + previous.TargetKind + ") can have properties"); postLinkSegment = (previous.TargetKind == RequestTargetKind.Link); // Enumerable results cannot be composed at all. if (previous.Operation != null && previous.Operation.ResultKind == ServiceOperationResultKind.Enumeration) { throw DataServiceException.ResourceNotFoundError( Strings.RequestUriProcessor_IEnumerableServiceOperationsCannotBeFurtherComposed(previous.Identifier)); } if (!previous.SingleResult) { throw DataServiceException.CreateBadRequestError( Strings.RequestUriProcessor_CannotQueryCollections(previous.Identifier)); } segment = new SegmentInfo(); segment.Identifier = identifier; segment.TargetSource = RequestTargetSource.Property; // A segment will correspond to a property in the object model; // if we are processing an open type, anything further in the // URI also represents an open type property. #if ASTORIA_OPEN_OBJECT if (previous.TargetElementType == typeof(object)) { Debug.Assert(previous.TargetKind == RequestTargetKind.OpenProperty, "For open properties, the target element type must be object"); segment.ProjectedProperty = null; } else #endif { #if ASTORIA_OPEN_OBJECT Debug.Assert(previous.TargetKind != RequestTargetKind.OpenProperty, "Since the query element type is known, this can't be open property"); #endif segment.ProjectedProperty = service.Provider.TryResolvePropertyName(previous.TargetElementType, identifier); #if !ASTORIA_OPEN_OBJECT WebUtil.CheckResourceExists(segment.ProjectedProperty != null, identifier); #endif } #if ASTORIA_OPEN_OBJECT if (identifier == XmlConstants.UriValueSegment && previous.TargetKind == RequestTargetKind.OpenProperty) { segment.RequestEnumerable = previous.RequestEnumerable; segment.SingleResult = true; segment.TargetKind = RequestTargetKind.OpenPropertyValue; segment.TargetElementType = previous.TargetElementType; } else if (segment.ProjectedProperty == null) { // Handle an open type property. If the current leaf isn't an // object (which implies it's already an open type), then // it should be marked as an open type. if (previous.TargetElementType != typeof(object)) { ResourceType resourceType = service.Provider.GetResourceType(previous.TargetElementType); Debug.Assert(resourceType != null, "resourceType != null -- otherwise WebUtil.CheckResourceExists should have thrown."); WebUtil.CheckResourceExists(resourceType.IsOpenType, segment.Identifier); } segment.TargetElementType = typeof(object); segment.TargetKind = RequestTargetKind.OpenProperty; segment.SingleResult = !hasQuery; if (!crossReferencingUri) { if (hasQuery) { segment.RequestQueryable = SelectOpenPropertyMultiple(previous.RequestQueryable, identifier); ComposeOpenPropertyQuery(queryPortion, segment); } else { segment.RequestQueryable = SelectOpenProperty(previous.RequestQueryable, identifier); } } } else #endif { // Handle a strongly-typed property. segment.TargetContainer = segment.ProjectedProperty.ResourceContainer; segment.TargetElementType = segment.ProjectedProperty.ResourceClrType; ResourcePropertyKind propertyKind = segment.ProjectedProperty.Kind; segment.SingleResult = (propertyKind != ResourcePropertyKind.ResourceSetReference); if (!crossReferencingUri) { segment.RequestQueryable = segment.SingleResult ? SelectElement(previous.RequestQueryable, segment.ProjectedProperty) : SelectMultiple(previous.RequestQueryable, segment.ProjectedProperty); } if (previous.TargetKind == RequestTargetKind.Link && segment.ProjectedProperty.TypeKind != ResourceTypeKind.EntityType) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment(identifier, XmlConstants.UriLinkSegment)); } switch (propertyKind) { case ResourcePropertyKind.ComplexType: segment.TargetKind = RequestTargetKind.ComplexObject; break; case ResourcePropertyKind.ResourceReference: case ResourcePropertyKind.ResourceSetReference: segment.TargetKind = RequestTargetKind.Resource; segment.TargetContainer = service.Provider.GetContainer( previous.TargetContainer.Name, previous.TargetElementType, segment.ProjectedProperty); Debug.Assert( segment.TargetContainer != null, "segment.TargetContainer != null -- for strongly typed navigation properties, we should always know the target container"); break; default: Debug.Assert(segment.ProjectedProperty.IsOfKind(ResourcePropertyKind.Primitive), "must be primitive type property"); segment.TargetKind = RequestTargetKind.Primitive; break; } if (hasQuery) { WebUtil.CheckSyntaxValid(!segment.SingleResult); if (!crossReferencingUri) { ComposeQuery(service.Provider, queryPortion, segment); } else { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation); } } // Do security checks and authorization query composition. if (segment.TargetContainer != null) { if (checkRights) { service.Configuration.CheckResourceRightsForRead(segment.TargetContainer, segment.SingleResult); } if (!crossReferencingUri) { segment.RequestQueryable = DataServiceConfiguration.ComposeResourceContainer(service, segment.TargetContainer, segment.RequestQueryable); } } } } #if DEBUG segment.AssertValid(); #endif segmentInfos[i] = segment; previous = segment; } if (segments.Length != 0 && previous.TargetKind == RequestTargetKind.Link) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_MissingSegmentAfterLink(XmlConstants.UriLinkSegment)); } return segmentInfos; } ///. /// Returns an object that can enumerate the segments in the specified path (eg: /foo/bar -> foo, bar). /// /// A valid path portion of an uri. /// baseUri for the request that is getting processed. ///An enumerable object of unescaped segments. private static string[] EnumerateSegments(Uri absoluteRequestUri, Uri baseUri) { Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri.IsAbsoluteUri + ")"); Debug.Assert(baseUri != null, "baseUri != null"); Debug.Assert(baseUri.IsAbsoluteUri, "baseUri.IsAbsoluteUri(" + baseUri + ")"); if (!UriUtil.UriInvariantInsensitiveIsBaseOf(baseUri, absoluteRequestUri)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_RequestUriDoesNotHaveTheRightBaseUri(absoluteRequestUri, baseUri)); } try { // Uri uri = absoluteRequestUri; int numberOfSegmentsToSkip = 0; // Since there is a svc part in the segment, we will need to skip 2 segments numberOfSegmentsToSkip = baseUri.Segments.Length; string[] uriSegments = uri.Segments; int populatedSegmentCount = 0; for (int i = numberOfSegmentsToSkip; i < uriSegments.Length; i++) { string segment = uriSegments[i]; if (segment.Length != 0 && segment != "/") { populatedSegmentCount++; } } string[] segments = new string[populatedSegmentCount]; int segmentIndex = 0; for (int i = numberOfSegmentsToSkip; i < uriSegments.Length; i++) { string segment = uriSegments[i]; if (segment.Length != 0 && segment != "/") { if (segment[segment.Length - 1] == '/') { segment = segment.Substring(0, segment.Length - 1); } segments[segmentIndex++] = Uri.UnescapeDataString(segment); } } Debug.Assert(segmentIndex == segments.Length, "segmentIndex == segments.Length -- otherwise we mis-counted populated/skipped segments."); return segments; } catch (UriFormatException) { throw DataServiceException.CreateSyntaxError(); } } ///Returns an object that can enumerate key values. /// resource type whose keys need to be extracted /// Key (query) part of an Astoria segment. ///An object that can enumerate key values. private static KeyInstance ExtractKeyValues(ResourceType resourceType, string filter) { KeyInstance key; filter = RemoveFilterParens(filter); WebUtil.CheckSyntaxValid(KeyInstance.TryParseFromUri(filter, out key)); if (!key.IsEmpty) { // Make sure the keys specified in the uri matches with the number of keys in the metadata if (resourceType.KeyProperties.Count != key.ValueCount) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(resourceType.FullName)); } WebUtil.CheckSyntaxValid(key.TryConvertValues(resourceType)); } return key; } ///Returns an object that can enumerate key values for an open property. /// Key (query) part of an Astoria segment. ///An object that can enumerate key values. private static KeyInstance ExtractOpenPropertyKeyValues(string filter) { KeyInstance key; filter = RemoveFilterParens(filter); WebUtil.CheckSyntaxValid(KeyInstance.TryParseFromUri(filter, out key)); key.RemoveQuotes(); return key; } ///Extracts the identifier part of the unescaped Astoria segment. /// Unescaped Astoria segment. /// On returning, the identifier in the segment. ///true if keys follow the identifier. private static bool ExtractSegmentIdentifier(string segment, out string identifier) { Debug.Assert(segment != null, "segment != null"); int filterStart = 0; while (filterStart < segment.Length && segment[filterStart] != '(') { filterStart++; } identifier = segment.Substring(0, filterStart); return filterStart < segment.Length; } ///Generic method to invoke a Select method on an IQueryable source. ///Element type of the source. ///Result type of the projection. /// Source query. /// Lambda expression that turns TSource into TResult. ///A new query that projects TSource into TResult. private static IQueryable InvokeSelect(IQueryable source, LambdaExpression selector) { Debug.Assert(source != null, "source != null"); Debug.Assert(selector != null, "selector != null"); IQueryable typedSource = (IQueryable )source; Expression > typedSelector = (Expression >)selector; return Queryable.Select (typedSource, typedSelector); } /// Generic method to invoke a SelectMany method on an IQueryable source. ///Element type of the source. ///Result type of the projection. /// Source query. /// Lambda expression that turns TSource into IEnumerable<TResult>. ///A new query that projects TSource into IEnumerable<TResult>. private static IQueryable InvokeSelectMany(IQueryable source, LambdaExpression selector) { Debug.Assert(source != null, "source != null"); Debug.Assert(selector != null, "selector != null"); IQueryable typedSource = (IQueryable )source; Expression >> typedSelector = (Expression >>)selector; return Queryable.SelectMany (typedSource, typedSelector); } /// Generic method to invoke a Where method on an IQueryable source. ///Element type of the source. /// Source query. /// Lambda expression that filters the result of the query. ///A new query that filters the query. private static IQueryable InvokeWhere(IQueryable query, LambdaExpression predicate) { Debug.Assert(query != null, "query != null"); Debug.Assert(predicate != null, "predicate != null"); IQueryable typedQueryable = (IQueryable )query; Expression > typedPredicate = (Expression >)predicate; return Queryable.Where (typedQueryable, typedPredicate); } /// /// Reads the parameters for the specified /// Host with request information. /// Operation with parameters to be read. ///from the /// . /// A new object[] with parameter values. private static object[] ReadOperationParameters(IDataServiceHost host, ServiceOperation operation) { Debug.Assert(host != null, "host != null"); object[] operationParameters = new object[operation.Parameters.Length]; for (int i = 0; i < operation.Parameters.Length; i++) { Type parameterType = operation.Parameters[i].Type; string queryStringValue = host.GetQueryStringItem(operation.Parameters[i].Name); Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (String.IsNullOrEmpty(queryStringValue)) { WebUtil.CheckSyntaxValid(parameterType.IsClass || underlyingType != null); operationParameters[i] = null; } else { // We choose to be a little more flexible than with keys and // allow surrounding whitespace (which is never significant). // See SQLBUDT #555944. queryStringValue = queryStringValue.Trim(); Type targetType = underlyingType ?? parameterType; if (WebConvert.IsKeyTypeQuoted(targetType)) { WebUtil.CheckSyntaxValid(WebConvert.IsKeyValueQuoted(queryStringValue)); } WebUtil.CheckSyntaxValid(WebConvert.TryKeyStringToPrimitive(queryStringValue, targetType, out operationParameters[i])); } } return operationParameters; } ///Removes the parens around the filter part of a query. /// Filter with parens included. ///Filter without parens. private static string RemoveFilterParens(string filter) { Debug.Assert(filter != null, "filter != null"); WebUtil.CheckSyntaxValid(filter.Length > 0 && filter[0] == '(' && filter[filter.Length - 1] == ')'); return filter.Substring(1, filter.Length - 2); } ///Project a property with a single element out of the specified query. /// Base query to project from. /// Property to project. ///A query with a composed primitive property projection. private static IQueryable SelectElement(IQueryable query, ResourceProperty property) { Debug.Assert(query != null, "query != null"); Debug.Assert(property.Kind != ResourcePropertyKind.ResourceSetReference, "property != ResourcePropertyKind.ResourceSetReference"); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); MemberExpression body = Expression.Property(parameter, property.Name); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, property.Type); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Project a property with multiple elements out of the specified query. /// Base query to project from. /// Property to project. ///A query with a composed primitive property projection. private static IQueryable SelectMultiple(IQueryable query, ResourceProperty property) { Debug.Assert(query != null, "query != null"); Debug.Assert(property.Kind == ResourcePropertyKind.ResourceSetReference, "property == ResourcePropertyKind.ResourceSetReference"); Type enumerableElement = BaseServiceProvider.GetIEnumerableElement(property.Type); Debug.Assert(enumerableElement != null, "Providers should never expose a property as a resource-set if it doesn't implement IEnumerable`1."); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); UnaryExpression body = Expression.ConvertChecked( Expression.Property(parameter, property.Name), typeof(IEnumerable<>).MakeGenericType(enumerableElement)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelectMany", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, enumerableElement); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } #if ASTORIA_OPEN_OBJECT ///Project a property with a single element out of the specified query over an open property. /// Base query to project from. /// Name of property to project. ///A query with a composed property projection. private static IQueryable SelectOpenProperty(IQueryable query, string propertyName) { Debug.Assert(query != null, "query != null"); Debug.Assert(propertyName != null, "propertyName != null"); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); MethodCallExpression body = Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, parameter, Expression.Constant(propertyName)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelect", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, typeof(object)); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Project a property with multiple elements out of the specified query over an open property. /// Base query to project from. /// Name of property to project. ///A query with a composed primitive property projection. private static IQueryable SelectOpenPropertyMultiple(IQueryable query, string propertyName) { Debug.Assert(query != null, "query != null"); Debug.Assert(propertyName != null, "propertyName != null"); Type enumerableElement = typeof(object); ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); Expression body = Expression.Call(null /* instance */, LateBoundMethods.GetSequenceValueMethodInfo, parameter, Expression.Constant(propertyName)); LambdaExpression selector = Expression.Lambda(body, parameter); MethodInfo method = typeof(RequestUriProcessor).GetMethod("InvokeSelectMany", BindingFlags.Static | BindingFlags.NonPublic); method = method.MakeGenericMethod(query.ElementType, enumerableElement); return (IQueryable)method.Invoke(null, new object[] { query, selector }); } ///Selects a single resource by key values. /// Base query for resources /// Key values for the given resource type. ///A new query that selects the single resource that matches the specified key values. private static IQueryable SelectOpenResourceByKey(IQueryable query, KeyInstance key) { Debug.Assert(query != null, "query != null"); Debug.Assert(query.ElementType == typeof(object), "query.Expression.Type == typeof(object) -- otherwise we aren't primary-key-filtering on an open type."); Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty -- otherwise we aren't filtering"); // if (key.AreValuesNamed) { throw Error.NotImplemented(); } ParameterExpression parameter = Expression.Parameter(typeof(object), "element"); Expression body = Expression.Call( null /* instance */, LateBoundMethods.KeyEqualsMethodInfo, Expression.Call(null /* instance */, LateBoundMethods.KeyMethodInfo, parameter), Expression.Constant(key.PositionalValues.ToArray(), typeof(object[]))); LambdaExpression predicate = Expression.Lambda(body, parameter); return InvokeWhereForType(query, predicate); } #endif ///Selects a single resource by key values. /// Base query for resources /// resource type whose keys are specified /// Key values for the given resource type. ///A new query that selects the single resource that matches the specified key values. private static IQueryable SelectResourceByKey(IQueryable query, ResourceType resourceType, KeyInstance key) { Debug.Assert(query != null, "query != null"); Debug.Assert(key != null && key.ValueCount != 0, "key != null && key.ValueCount != 0"); Debug.Assert(resourceType.KeyProperties.Count == key.ValueCount, "resourceType.KeyProperties.Count == key.ValueCount"); for (int i = 0; i < resourceType.KeyProperties.Count; i++) { ResourceProperty keyProperty = resourceType.KeyProperties[i]; Debug.Assert(keyProperty.IsOfKind(ResourcePropertyKind.Key), "keyProperty.IsOfKind(ResourcePropertyKind.Key)"); object keyValue; if (key.AreValuesNamed) { keyValue = key.NamedValues[keyProperty.Name]; } else { keyValue = key.PositionalValues[i]; } ParameterExpression parameter = Expression.Parameter(query.ElementType, "element"); BinaryExpression body = Expression.Equal( Expression.Property(parameter, keyProperty.Name), Expression.Constant(keyValue)); LambdaExpression predicate = Expression.Lambda(body, parameter); query = InvokeWhereForType(query, predicate); } return query; } ///Determines a matching target kind from the specified type. ///of element to get kind for. /// that supplies type metadata. /// An appropriate private static RequestTargetKind TargetKindFromType(Type type, IDataServiceProvider provider) { Debug.Assert(type != null, "type != null"); Debug.Assert(provider != null, "provider != null"); ResourceTypeKind typeKind = provider.GetResourceTypeKind(type); switch (typeKind) { case ResourceTypeKind.ComplexType: return RequestTargetKind.ComplexObject; case ResourceTypeKind.EntityType: return RequestTargetKind.Resource; default: Debug.Assert(typeKind == ResourceTypeKind.Primitive, "typeKind == ResourceTypeKind.Primitive"); return RequestTargetKind.Primitive; } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.for the specified .
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- SqlInfoMessageEvent.cs
- MeasurementDCInfo.cs
- DbConnectionPoolCounters.cs
- clipboard.cs
- LineInfo.cs
- SpellerHighlightLayer.cs
- InternalRelationshipCollection.cs
- ConfigurationManager.cs
- WebSysDisplayNameAttribute.cs
- StateBag.cs
- DataPointer.cs
- UIElement3D.cs
- DesignTimeParseData.cs
- SiteMapSection.cs
- WebPageTraceListener.cs
- SkipQueryOptionExpression.cs
- XMLUtil.cs
- IncrementalCompileAnalyzer.cs
- ReliabilityContractAttribute.cs
- PerformanceCounterPermissionEntry.cs
- PingReply.cs
- DataSetViewSchema.cs
- MonitoringDescriptionAttribute.cs
- GridViewRow.cs
- DispatcherObject.cs
- PropertyGridEditorPart.cs
- CacheOutputQuery.cs
- FontNameEditor.cs
- AttributeEmitter.cs
- _CacheStreams.cs
- SpAudioStreamWrapper.cs
- InkSerializer.cs
- SubclassTypeValidatorAttribute.cs
- CookielessHelper.cs
- DataMemberConverter.cs
- ObfuscationAttribute.cs
- BitmapEffect.cs
- CodeCommentStatementCollection.cs
- ColorAnimation.cs
- HtmlContainerControl.cs
- SynchronizedRandom.cs
- ObjectFactoryCodeDomTreeGenerator.cs
- TrackingMemoryStream.cs
- CompatibleIComparer.cs
- CommandBinding.cs
- Underline.cs
- MetadataException.cs
- ServiceModelExtensionElement.cs
- ReadOnlyTernaryTree.cs
- ArrayList.cs
- InternalTypeHelper.cs
- OrElse.cs
- ComponentManagerBroker.cs
- GridView.cs
- PrimitiveDataContract.cs
- ObjectDataSourceEventArgs.cs
- EditCommandColumn.cs
- Italic.cs
- ObjectDataSourceMethodEventArgs.cs
- ViewPort3D.cs
- CryptoConfig.cs
- DataGridHeaderBorder.cs
- PolicyException.cs
- SqlReorderer.cs
- InvokeGenerator.cs
- GroupBox.cs
- DesignerSerializerAttribute.cs
- TableLayout.cs
- EntityContainer.cs
- SecureStringHasher.cs
- IndentedWriter.cs
- HtmlInputRadioButton.cs
- RequiredAttributeAttribute.cs
- NameTable.cs
- CatchDesigner.xaml.cs
- SignedInfo.cs
- NamedObject.cs
- AssemblyAttributesGoHere.cs
- SoapTypeAttribute.cs
- UnitySerializationHolder.cs
- Page.cs
- RepeaterItemCollection.cs
- RenderContext.cs
- Oid.cs
- Decoder.cs
- SmtpClient.cs
- SeparatorAutomationPeer.cs
- sqlmetadatafactory.cs
- BitSet.cs
- SmtpNtlmAuthenticationModule.cs
- SecurityException.cs
- RTLAwareMessageBox.cs
- DiscriminatorMap.cs
- UIElementHelper.cs
- MimeXmlReflector.cs
- MimeMapping.cs
- SingleBodyParameterMessageFormatter.cs
- InfoCardCryptoHelper.cs
- BehaviorDragDropEventArgs.cs
- XmlIlVisitor.cs