/// <summary> /// Binds the paths from the request's $select query option to the sets/types/properties from the metadata provider of the service. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="dataService">The data service.</param> /// <param name="selectQueryOption">The raw value of the $select query option.</param> /// <returns>The bound select segments.</returns> internal static IList <IList <SelectItem> > BindSelectSegments(RequestDescription requestDescription, IDataService dataService, string selectQueryOption) { Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(dataService != null, "dataService != null"); if (string.IsNullOrEmpty(selectQueryOption)) { return(new List <IList <SelectItem> >()); } // Throw if $select requests have been disabled by the user Debug.Assert(dataService.Configuration != null, "dataService.Configuration != null"); if (!dataService.Configuration.DataServiceBehavior.AcceptProjectionRequests) { throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotAccepted); } IList <IList <string> > selectPathsAsText = SplitSelect(selectQueryOption, dataService.Provider); Debug.Assert(selectPathsAsText != null, "selectPathsAsText != null"); List <IList <SelectItem> > boundSegments = new List <IList <SelectItem> >(selectPathsAsText.Count); if (selectPathsAsText.Count == 0) { return(boundSegments); } ValidateSelectIsAllowedForRequest(requestDescription); MetadataProviderEdmModel metadataProviderEdmModel = dataService.Provider.GetMetadataProviderEdmModel(); for (int i = selectPathsAsText.Count - 1; i >= 0; i--) { IList <string> path = selectPathsAsText[i]; List <SelectItem> boundSegmentPath = new List <SelectItem>(path.Count); boundSegments.Add(boundSegmentPath); ResourceType targetResourceType = requestDescription.TargetResourceType; ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet; // if we get to here, we're building a partial selection List <TypeSegment> typeSegments = new List <TypeSegment>(); bool previousSegmentIsTypeSegment = false; for (int j = 0; j < path.Count; j++) { string pathSegment = path[j]; bool lastPathSegment = (j == path.Count - 1); // '*' is special, it means "Project all immediate properties on this level." if (pathSegment == "*") { Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one. This should have been checked already when splitting appart the paths."); boundSegmentPath.Add(CreateWildcardSelection()); continue; } bool nameIsContainerQualified; string nameFromContainerQualifiedName = dataService.Provider.GetNameFromContainerQualifiedName(pathSegment, out nameIsContainerQualified); if (nameFromContainerQualifiedName == "*") { Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one. This should have been checked already when splitting appart the paths."); Debug.Assert(nameIsContainerQualified, "nameIsContainerQualified == true"); boundSegmentPath.Add(CreateContainerQualifiedWildcardSelection(metadataProviderEdmModel)); continue; } ResourceProperty property = targetResourceType.TryResolvePropertyName(pathSegment); if (property == null) { ResourceType resolvedResourceType = WebUtil.ResolveTypeIdentifier(dataService.Provider, pathSegment, targetResourceType, previousSegmentIsTypeSegment); if (resolvedResourceType != null) { previousSegmentIsTypeSegment = true; if (lastPathSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, resolvedResourceType.FullName)); } targetResourceType = resolvedResourceType; // Whenever we encounter the type segment, we need to only verify that the MPV is set to 3.0 or higher. // There is no need to check for request DSV, request MinDSV or request MaxDSV since there are no protocol changes in // the payload for uri's with type identifier. requestDescription.VerifyProtocolVersion(VersionUtil.Version3Dot0, dataService); IEdmSchemaType edmType = metadataProviderEdmModel.EnsureSchemaType(targetResourceType); TypeSegment typeSegment = new TypeSegment(edmType); typeSegments.Add(typeSegment); continue; } previousSegmentIsTypeSegment = false; // If the currentResourceType is an open type, we require the service action name to be fully qualified or else we treat it as an open property name. if (!targetResourceType.IsOpenType || nameIsContainerQualified) { // Note that if the service does not implement IDataServiceActionProvider and the MaxProtocolVersion < V3, // GetActionsBoundToAnyTypeInResourceSet() would simply return an empty ServiceOperationWrapper collection. Debug.Assert(dataService.ActionProvider != null, "dataService.ActionProvider != null"); IEnumerable <OperationWrapper> allOperationsInSet = dataService.ActionProvider.GetActionsBoundToAnyTypeInResourceSet(targetResourceSet); List <OperationWrapper> selectedServiceActions = allOperationsInSet.Where(o => o.Name == nameFromContainerQualifiedName).ToList(); if (selectedServiceActions.Count > 0) { if (!lastPathSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ServiceActionMustBeLastSegmentInSelect(pathSegment)); } boundSegmentPath.Add(CreateOperationSelection(metadataProviderEdmModel, selectedServiceActions, typeSegments)); continue; } } if (!targetResourceType.IsOpenType) { throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(targetResourceType.FullName, pathSegment)); } if (!lastPathSegment) { // Open navigation properties are not supported on OpenTypes. throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment)); } boundSegmentPath.Add(CreateOpenPropertySelection(pathSegment, typeSegments)); } else { previousSegmentIsTypeSegment = false; ValidateSelectedProperty(targetResourceType, property, lastPathSegment); boundSegmentPath.Add(CreatePropertySelection(metadataProviderEdmModel, targetResourceType, property, typeSegments)); if (property.TypeKind == ResourceTypeKind.EntityType) { targetResourceSet = dataService.Provider.GetResourceSet(targetResourceSet, targetResourceType, property); targetResourceType = property.ResourceType; } } } // Note that this check is also covering cases where a type segment is followed by a wildcard. if (previousSegmentIsTypeSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, targetResourceType.FullName)); } } return(boundSegments); }