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 resolves to.
internal static Uri GetAbsoluteUriFromReference(string reference, CachedRequestParams request)
{
return GetAbsoluteUriFromReference(reference, request.AbsoluteServiceUri);
}
/// 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 resolves to.
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;
}
/// 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 for the hosted service.
/// Host for the service.
/// A non-null for the hosted service.
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;
}
}
/// Gets a non-null for the hosted service.
/// Host for the service.
/// A non-null for the hosted service.
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;
}
}
///
/// 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 for a request.
/// 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.
/// 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 array for the given .
/// Segments to process.
/// Service for which segments are being processed.
/// 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 from the
/// .
///
/// Host with request information.
/// Operation with parameters to be read.
/// 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 for the specified .
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.
//----------------------------------------------------------------------
//
// 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 resolves to.
internal static Uri GetAbsoluteUriFromReference(string reference, CachedRequestParams request)
{
return GetAbsoluteUriFromReference(reference, request.AbsoluteServiceUri);
}
/// 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 resolves to.
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;
}
/// 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 for the hosted service.
/// Host for the service.
/// A non-null for the hosted service.
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;
}
}
/// Gets a non-null for the hosted service.
/// Host for the service.
/// A non-null for the hosted service.
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;
}
}
///
/// 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 for a request.
/// 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.
/// 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 array for the given .
/// Segments to process.
/// Service for which segments are being processed.
/// 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 from the
/// .
///
/// Host with request information.
/// Operation with parameters to be read.
/// 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 for the specified .
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.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ChildTable.cs
- PriorityBinding.cs
- InfoCardArgumentException.cs
- PackagingUtilities.cs
- ToolStripDropDownClosedEventArgs.cs
- SqlUtils.cs
- ISO2022Encoding.cs
- WithStatement.cs
- InternalControlCollection.cs
- BuildProviderAppliesToAttribute.cs
- VScrollBar.cs
- RemoteArgument.cs
- ProcessActivityTreeOptions.cs
- TokenCreationParameter.cs
- _OSSOCK.cs
- ControlDesignerState.cs
- TemplatedWizardStep.cs
- XmlElementList.cs
- SystemResourceKey.cs
- ProtocolViolationException.cs
- XPathDocument.cs
- columnmapkeybuilder.cs
- HtmlToClrEventProxy.cs
- XpsInterleavingPolicy.cs
- OrderByBuilder.cs
- FlowLayoutSettings.cs
- ResourceManagerWrapper.cs
- SystemParameters.cs
- GeometryCombineModeValidation.cs
- Localizer.cs
- ListViewGroupItemCollection.cs
- PerformanceCounterLib.cs
- SrgsSubset.cs
- EntityProxyTypeInfo.cs
- MetadataUtil.cs
- EnumConverter.cs
- WorkflowEnvironment.cs
- EpmSourceTree.cs
- DependencyObject.cs
- MetadataStore.cs
- OutputWindow.cs
- QueryableDataSourceHelper.cs
- ByteBufferPool.cs
- BitmapEffectGroup.cs
- ReadOnlyMetadataCollection.cs
- ScriptComponentDescriptor.cs
- OdbcPermission.cs
- DatagridviewDisplayedBandsData.cs
- FileStream.cs
- XmlByteStreamReader.cs
- ObservableCollection.cs
- CachedPathData.cs
- RequestCache.cs
- ModelVisual3D.cs
- IDispatchConstantAttribute.cs
- SafeSecurityHandles.cs
- HttpInputStream.cs
- ViewValidator.cs
- AdRotator.cs
- XamlClipboardData.cs
- EntityReference.cs
- CodeArrayIndexerExpression.cs
- SetterBase.cs
- ProxyElement.cs
- XmlDeclaration.cs
- DataControlReferenceCollection.cs
- XmlAnyElementAttributes.cs
- IPEndPoint.cs
- panel.cs
- DataGridColumnReorderingEventArgs.cs
- XmlChildEnumerator.cs
- Native.cs
- ResourceDictionary.cs
- DependencyPropertyChangedEventArgs.cs
- RectangleHotSpot.cs
- ServiceProviders.cs
- ThreadBehavior.cs
- sqlpipe.cs
- OutOfProcStateClientManager.cs
- FillRuleValidation.cs
- SoapInteropTypes.cs
- ZipIOCentralDirectoryFileHeader.cs
- PageAsyncTask.cs
- GlobalizationSection.cs
- PolicyManager.cs
- ZipIOExtraFieldElement.cs
- WebResourceAttribute.cs
- DataTableReaderListener.cs
- SlipBehavior.cs
- SystemIcons.cs
- MaskDescriptors.cs
- ItemAutomationPeer.cs
- FormViewUpdateEventArgs.cs
- JoinTreeSlot.cs
- Codec.cs
- ImageBrush.cs
- AttachedPropertyBrowsableWhenAttributePresentAttribute.cs
- WebPartEditVerb.cs
- TypeExtensionConverter.cs
- CaseInsensitiveHashCodeProvider.cs