private static HttpConfiguration InitializeConfiguration(string controllerName, bool useCustomEdmModel, ODataUriResolver resolver = null) { var controllers = new[] { typeof(QueryCompositionPrimitiveController), typeof(QueryCompositionCustomerController), typeof(QueryCompositionCustomerQueryableController), typeof(QueryCompositionCustomerWithTaskOfIEnumerableController), typeof(QueryCompositionCustomerGlobalController), typeof(QueryCompositionCustomerValidationController), typeof(QueryCompositionCustomerLowLevelController), typeof(QueryCompositionCustomerLowLevel_ODataQueryOptionsOfTController), typeof(QueryCompositionCategoryController), typeof(QueryCompositionAnonymousTypesController) }; var config = RoutingConfigurationFactory.CreateWithTypes(controllers); config.Routes.MapHttpRoute("default", "{controller}/{key}", new { key = RouteParameter.Optional }); config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; if (controllerName == "QueryCompositionCustomerGlobal") { config.Filters.Add(new EnableQueryAttribute()); } if (useCustomEdmModel) { if (_queryCompositionCustomerModel == null) { ODataModelBuilder modelBuilder = ODataConventionModelBuilderFactory.Create(); modelBuilder.EntitySet <QueryCompositionCustomer>(typeof(QueryCompositionCustomer).Name); _queryCompositionCustomerModel = modelBuilder.GetEdmModel(); } if (resolver == null) { config.EnableODataDependencyInjectionSupport("default", _queryCompositionCustomerModel); } else { config.EnableODataDependencyInjectionSupport("default", b => b.AddService(ServiceLifetime.Singleton, sp => _queryCompositionCustomerModel) .AddService(ServiceLifetime.Singleton, sp => resolver)); } config.Filters.Add(new SetModelFilter(_queryCompositionCustomerModel)); } else { config.EnableODataDependencyInjectionSupport("default"); } return(config); }
/// <summary> /// Initializes a new instance of the <see cref="ODataQueryOptions"/> class based on the incoming request and some metadata information from /// the <see cref="ODataQueryContext"/>. /// </summary> /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information.</param> private void Initialize(ODataQueryContext context) { Contract.Assert(context != null); ODataUriResolver uriResolver = null; if (context.RequestContainer != null) { uriResolver = context.RequestContainer.GetService <ODataUriResolver>(); } if (uriResolver != null) { _enableNoDollarSignQueryOptions = uriResolver.EnableNoDollarQueryOptions; } else { // Use the global setting _enableNoDollarSignQueryOptions = context.Request.IsNoDollarQueryEnable(); } // Parse the query from request Uri, including only keys which are OData query parameters or parameter alias // OData query parameters are normalized with the $-sign prefixes when the // <code>EnableNoDollarSignPrefixSystemQueryOption</code> option is used. RawValues = new ODataRawQueryOptions(); IDictionary <string, string> normalizedQueryParameters = GetODataQueryParameters(); _queryOptionParser = new ODataQueryOptionParser( context.Model, context.ElementType, context.NavigationSource, normalizedQueryParameters); if (uriResolver != null) { _queryOptionParser.Resolver = uriResolver; } else { // By default, let's enable the property name case-insensitive _queryOptionParser.Resolver = new ODataUriResolver { EnableCaseInsensitive = true }; } BuildQueryOptions(normalizedQueryParameters); Validator = ODataQueryValidator.GetODataQueryValidator(context); }
public bool IsSupportedQueryOption(string queryOptionName) { ODataUriResolver resolver = _queryOptionParser != null ? _queryOptionParser.Resolver : Request.GetRequestContainer().GetRequiredService <ODataUriResolver>(); if (!resolver.EnableCaseInsensitive) { return(IsSystemQueryOption(queryOptionName, this._enableNoDollarSignQueryOptions)); } string lowcaseQueryOptionName = queryOptionName.ToLowerInvariant(); return(IsSystemQueryOption(lowcaseQueryOptionName, this._enableNoDollarSignQueryOptions)); }
/// <summary> /// Enables common OData functionality like pagination, filtering, selection /// </summary> /// <param name="builder"></param> /// <returns></returns> public static IRouteBuilder EnableCommonOData(this IRouteBuilder builder) { builder.EnableDependencyInjection(o => { o.AddService( ServiceLifetime.Singleton, _ => { var resolver = new ODataUriResolver { EnableCaseInsensitive = true }; return(resolver); }); }); builder.Filter().Count().Select().MaxTop(200).OrderBy(); return(builder); }
private static HttpConfiguration GetQueryOptionConfiguration(bool caseInsensitive) { var config = RoutingConfigurationFactory.CreateWithTypes(new[] { typeof(ParserExtenstionCustomersController) }); ODataUriResolver resolver = new ODataUriResolver { EnableCaseInsensitive = caseInsensitive, }; config.Count().OrderBy().Filter().Expand().MaxTop(null).Select(); config.MapODataServiceRoute("query", "query", builder => builder.AddService(ServiceLifetime.Singleton, sp => GetEdmModel()) .AddService <IEnumerable <IODataRoutingConvention> >(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting("query", config)) .AddService(ServiceLifetime.Singleton, sp => resolver)); return(config); }
/// <summary> /// Initializes a new instance of the <see cref="ODataQueryOptions"/> class based on the incoming request and some metadata information from /// the <see cref="ODataQueryContext"/>. /// </summary> /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information.</param> /// <param name="request">The incoming request message.</param> public ODataQueryOptions(ODataQueryContext context, HttpRequestMessage request) { if (context == null) { throw Error.ArgumentNull("context"); } if (request == null) { throw Error.ArgumentNull("request"); } // Set the request container into context Contract.Assert(context.RequestContainer == null); context.RequestContainer = request.GetRequestContainer(); // Remember the context and request Context = context; Request = request; ODataUriResolver uriResolver = request.GetRequestContainer().GetRequiredService <ODataUriResolver>(); if (uriResolver != null) { _enableNoDollarSignQueryOptions = uriResolver.EnableNoDollarQueryOptions; } // Parse the query from request Uri, including only keys which are OData query parameters or parameter alias // OData query parameters are normalized with the $-sign prefixes when the // <code>EnableNoDollarSignPrefixSystemQueryOption</code> option is used. RawValues = new ODataRawQueryOptions(); IDictionary <string, string> normalizedqueryParameters = GetODataQueryParameters(); _queryOptionParser = new ODataQueryOptionParser( context.Model, context.ElementType, context.NavigationSource, normalizedqueryParameters); _queryOptionParser.Resolver = uriResolver; BuildQueryOptions(normalizedqueryParameters); Validator = ODataQueryValidator.GetODataQueryValidator(context); }
public static ODataPath Parse(this IODataPathHandler handler, IEdmModel model, string serviceRoot, string odataPath, ODataUriResolver resolver = null) { Contract.Assert(handler != null); Action <IContainerBuilder> action; if (resolver != null) { action = b => b.AddService(ServiceLifetime.Singleton, sp => model) .AddService(ServiceLifetime.Singleton, sp => resolver); } else { action = b => b.AddService(ServiceLifetime.Singleton, sp => model); } return(handler.Parse(serviceRoot, odataPath, new MockContainer(action))); }
public async Task QueryComposition_WorkAsExpect_ForCaseInsensitive() { // Arrange const string caseInSensitive = "?$fIlTer=iD Eq 33"; ODataUriResolver resolver = new ODataUriResolver { EnableCaseInsensitive = true, }; HttpServer server = new HttpServer(InitializeConfiguration("QueryCompositionCustomer", true, resolver)); HttpClient client = new HttpClient(server); // Act HttpResponseMessage response = await GetResponse(client, server.Configuration, "http://localhost:8080/QueryCompositionCustomer" + caseInSensitive); // Assert Assert.True(response.IsSuccessStatusCode); Assert.Contains("[{\"Name\":\"Highest\",\"Add", await response.Content.ReadAsStringAsync()); }
public void QueryComposition_WorkAsExpect_ForOptionalDollarSignPrefixForSystemQuery( string noDollarSignSystemQuery, bool enableCaseInsensitive) { // Arrange ODataUriResolver resolver = new ODataUriResolver { EnableNoDollarQueryOptions = true, EnableCaseInsensitive = enableCaseInsensitive }; HttpServer server = new HttpServer(InitializeConfiguration("QueryCompositionCustomer", true, resolver)); HttpClient client = new HttpClient(server); // Act HttpResponseMessage response = GetResponse(client, server.Configuration, "http://localhost:8080/QueryCompositionCustomer" + noDollarSignSystemQuery); // Assert Assert.True(response.IsSuccessStatusCode); Assert.Contains("[{\"Name\":\"Highest\",\"Add", response.Content.ReadAsStringAsync().Result); }
public bool IsSupportedQueryOption(string queryOptionName) { if (string.IsNullOrEmpty(queryOptionName)) { throw Error.ArgumentNullOrEmpty(nameof(queryOptionName)); } ODataUriResolver resolver = _queryOptionParser != null ? _queryOptionParser.Resolver : Request.GetSubServiceProvider().GetRequiredService <ODataUriResolver>(); if (!resolver.EnableCaseInsensitive) { return(IsSystemQueryOption(queryOptionName, this._enableNoDollarSignQueryOptions)); } string lowcaseQueryOptionName = queryOptionName.ToLowerInvariant(); return(IsSystemQueryOption(lowcaseQueryOptionName, this._enableNoDollarSignQueryOptions)); }
public void QueryComposition_ThrowsException_ForCaseSensitive() { // Arrange const string caseInSensitive = "?$fIlTer=iD Eq 33"; ODataUriResolver resolver = new ODataUriResolver { EnableCaseInsensitive = false }; HttpServer server = new HttpServer(InitializeConfiguration("QueryCompositionCustomer", true, resolver)); HttpClient client = new HttpClient(server); // Act HttpResponseMessage response = GetResponse(client, server.Configuration, "http://localhost:8080/QueryCompositionCustomer" + caseInSensitive); // Assert Assert.False(response.IsSuccessStatusCode); Assert.Contains("The query parameter '$fIlTer' is not supported.", response.Content.ReadAsStringAsync().Result); }
/// <summary> /// Adds the default OData services to the <see cref="IContainerBuilder"/>. /// </summary> /// <param name="builder">The <see cref="IContainerBuilder"/> to add the services to.</param> /// <returns>The <see cref="IContainerBuilder"/> instance itself.</returns> public static IContainerBuilder AddDefaultODataServices(this IContainerBuilder builder) { Debug.Assert(builder != null, "builder != null"); builder.AddService <IJsonReaderFactory, DefaultJsonReaderFactory>(ServiceLifetime.Singleton); builder.AddService <IJsonWriterFactory, DefaultJsonWriterFactory>(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton, sp => ODataMediaTypeResolver.GetMediaTypeResolver(null)); builder.AddService <ODataMessageInfo>(ServiceLifetime.Scoped); builder.AddServicePrototype(new ODataMessageReaderSettings()); builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype <ODataMessageReaderSettings>().Clone()); builder.AddServicePrototype(new ODataMessageWriterSettings()); builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype <ODataMessageWriterSettings>().Clone()); builder.AddService(ServiceLifetime.Singleton, sp => ODataPayloadValueConverter.GetPayloadValueConverter(null)); builder.AddService <IEdmModel>(ServiceLifetime.Singleton, sp => EdmCoreModel.Instance); builder.AddService(ServiceLifetime.Singleton, sp => ODataUriResolver.GetUriResolver(null)); builder.AddService <ODataUriParserSettings>(ServiceLifetime.Scoped); builder.AddService <UriPathParser>(ServiceLifetime.Scoped); builder.AddServicePrototype(new ODataSimplifiedOptions()); builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype <ODataSimplifiedOptions>().Clone()); return(builder); }
private static ODataQueryOptions GetQueryOptions(string queryOption) { string uri = "Http://localhost/RoutingCustomers?" + queryOption; ODataUriResolver resolver = new ODataUriResolver { EnableCaseInsensitive = true }; var configuration = RoutingConfigurationFactory.CreateWithRootContainer("OData", b => b.AddService(ServiceLifetime.Singleton, sp => resolver)); var request = RequestFactory.Create(HttpMethod.Get, uri, configuration, "OData"); IEdmModel model = ODataRoutingModel.GetModel(); IEdmEntitySet entityset = model.EntityContainer.FindEntitySet("RoutingCustomers"); IEdmEntityType entityType = model.SchemaElements.OfType <IEdmEntityType>().Single(e => e.Name == "RoutingCustomer"); ODataPath path = new ODataPath(new[] { new EntitySetSegment(entityset) }); ODataQueryContext context = new ODataQueryContext(model, entityType, path); return(new ODataQueryOptions(context, request)); }
protected void TestConflictWithExactMatch <TResult>( string originalStr, string caseInsensitiveString, Func <ODataUriParser, TResult> parse, Action <TResult> verify, string conflictMessage, IEdmModel model, ODataUriResolver resolver) { Uri originalCase = new Uri(originalStr, UriKind.Relative); Uri insensitiveCase = new Uri(caseInsensitiveString, UriKind.Relative); // Original string should pass case sensitive ODataUriParser parser = new ODataUriParser(model, originalCase) { Resolver = new ODataUriResolver() { EnableCaseInsensitive = false } }; verify(parse(parser)); // Original string should pass case insensitive verify(parse(new ODataUriParser(model, originalCase) { Resolver = resolver })); // Insensitive case should fail with case insensitive parser with errorMessage Action action = () => parse(new ODataUriParser(model, insensitiveCase) { Resolver = resolver }); action.ShouldThrow <ODataException>().WithMessage(conflictMessage); }
private static HttpConfiguration GetConfiguration(bool caseInsensitive, bool unqualifiedNameCall) { IEdmModel model = ODataRoutingModel.GetModel(); HttpConfiguration config = RoutingConfigurationFactory.CreateWithTypes(new[] { typeof(MetadataController), typeof(ProductsController), typeof(RoutingCustomersController), }); ODataUriResolver resolver = new ODataUriResolver(); if (unqualifiedNameCall) { resolver = new UnqualifiedODataUriResolver(); if (caseInsensitive) { resolver = new UnqualifiedCaseInsensitiveResolver(); } } else { if (caseInsensitive) { resolver = new CaseInsensitiveResolver(); } } config.Count().Filter().OrderBy().Expand().MaxTop(null).Select(); config.MapODataServiceRoute("odata", "odata", builder => builder.AddService(ServiceLifetime.Singleton, sp => model) .AddService <IEnumerable <IODataRoutingConvention> >(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", config)) .AddService(ServiceLifetime.Singleton, sp => resolver)); return(config); }
public ODataUriResolver CreateResolver() { ODataUriResolver resolver; if (UnqualifiedNameCall && EnumPrefixFree) { resolver = new UnqualifiedCallAndEnumPrefixFreeResolver(); } else if (UnqualifiedNameCall) { resolver = new UnqualifiedODataUriResolver(); } else if (EnumPrefixFree) { resolver = new StringAsEnumResolver(); } else { resolver = new ODataUriResolver(); } resolver.EnableCaseInsensitive = CaseInsensitive; return(resolver); }
/// <summary> /// Initializes a new instance of the <see cref="ODataQueryOptions"/> class based on the incoming request and some metadata information from /// the <see cref="ODataQueryContext"/>. /// </summary> /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information.</param> private void Initialize(ODataQueryContext context) { Contract.Assert(context != null); // ODataUriResolver uriResolver = context.RequestContainer.GetRequiredService<ODataUriResolver>(); ODataUriResolver uriResolver = context.RequestContainer.GetService <ODataUriResolver>(); if (uriResolver != null) { _enableNoDollarSignQueryOptions = uriResolver.EnableNoDollarQueryOptions; } // Parse the query from request Uri, including only keys which are OData query parameters or parameter alias // OData query parameters are normalized with the $-sign prefixes when the // <code>EnableNoDollarSignPrefixSystemQueryOption</code> option is used. RawValues = new ODataRawQueryOptions(); IDictionary <string, string> normalizedQueryParameters = GetODataQueryParameters(); _queryOptionParser = new ODataQueryOptionParser( context.Model, context.ElementType, context.NavigationSource, normalizedQueryParameters); // Note: the context.RequestContainer must be set by the ODataQueryOptions constructor. Contract.Assert(context.RequestContainer != null); if (uriResolver != null) { _queryOptionParser.Resolver = uriResolver; } BuildQueryOptions(normalizedQueryParameters); Validator = ODataQueryValidator.GetODataQueryValidator(context); }
/// <summary>Tries to create a key segment for the given filter if it is non empty.</summary> /// <param name="previous">Segment on which to compose.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="parenthesisExpression">Parenthesis expression of segment.</param> /// <param name="keySegment">The key segment that was created if the key was non-empty.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>Whether the key was non-empty.</returns> internal static bool TryCreateKeySegmentFromParentheses(ODataPathSegment previous, KeySegment previousKeySegment, string parenthesisExpression, out ODataPathSegment keySegment, bool enableUriTemplateParsing = false, ODataUriResolver resolver = null) { Debug.Assert(parenthesisExpression != null, "parenthesisExpression != null"); Debug.Assert(previous != null, "segment!= null"); if (resolver == null) { resolver = ODataUriResolver.Default; } if (previous.SingleResult) { throw ExceptionUtil.CreateSyntaxError(); } SegmentArgumentParser key; if (!SegmentArgumentParser.TryParseKeysFromUri(parenthesisExpression, out key, enableUriTemplateParsing)) { throw ExceptionUtil.CreateSyntaxError(); } // People/NS.Employees() is OK, just like People() is OK if (key.IsEmpty) { keySegment = null; return(false); } keySegment = CreateKeySegment(previous, previousKeySegment, key, resolver); return(true); }
/// <summary> /// Build a property visitor to visit the select tree and decorate a SelectExpandClause /// </summary> /// <param name="model">The model used for binding.</param> /// <param name="edmType">The entity type that the $select is being applied to.</param> /// <param name="maxDepth">the maximum recursive depth.</param> /// <param name="expandClauseToDecorate">The already built expand clause to decorate</param> /// <param name="resolver">Resolver for uri parser.</param> public SelectPropertyVisitor(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver) { this.model = model; this.edmType = edmType; this.maxDepth = maxDepth; this.expandClauseToDecorate = expandClauseToDecorate; this.resolver = resolver ?? ODataUriResolver.Default; }
internal static bool ResolveOperationImportFromList(string identifier, IList <string> parameterNames, IEdmModel model, out IEdmOperationImport matchingOperationImport, ODataUriResolver resolver) { IList <IEdmOperationImport> candidateMatchingOperationImports = null; IList <IEdmActionImport> foundActionImportsWhenLookingForFunctions = new List <IEdmActionImport>(); try { if (parameterNames.Count > 0) { // In this case we have to return a function so filter out actions because the number of parameters > 0. candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier).RemoveActionImports(out foundActionImportsWhenLookingForFunctions).FilterFunctionsByParameterNames(parameterNames, resolver.EnableCaseInsensitive).Cast <IEdmOperationImport>().ToList(); } else { candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier).ToList(); } } catch (Exception exc) { if (!ExceptionUtils.IsCatchableExceptionType(exc)) { throw; } throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperationImport(identifier), exc); } if (foundActionImportsWhenLookingForFunctions.Count > 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL if (candidateMatchingOperationImports.Any(f => f.IsActionImport())) { if (candidateMatchingOperationImports.Count > 1) { if (candidateMatchingOperationImports.Any(o => o.IsFunctionImport())) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); } else { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionImportOverloads(identifier)); } } if (parameterNames.Count() != 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } matchingOperationImport = candidateMatchingOperationImports.Single(); return(true); } // If parameter count is zero and there is one function import whoese parameter count is zero, return this function import. if (candidateMatchingOperationImports.Count > 1 && parameterNames.Count == 0) { candidateMatchingOperationImports = candidateMatchingOperationImports.Where(operationImport => operationImport.Operation.Parameters.Count() == 0).ToList(); } if (candidateMatchingOperationImports.Count == 0) { matchingOperationImport = null; return(false); } if (candidateMatchingOperationImports.Count > 1) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); } matchingOperationImport = candidateMatchingOperationImports.Single(); return(matchingOperationImport != null); }
internal static bool ResolveOperationFromList(string identifier, IEnumerable <string> parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver) { // TODO: update code that is duplicate between operation and operation import, add more tests. // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name. if (bindingType != null) { // TODO: look up actual container names here? // When using extension, there may be function call with unqualified name. So loose the restriction here. if (bindingType.IsOpenType() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver)) { matchingOperation = null; return(false); } } IList <IEdmOperation> candidateMatchingOperations = null; // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception // so catching all of them and simply putting it in the inner exception. try { if (bindingType != null) { candidateMatchingOperations = resolver.ResolveBoundOperations(model, identifier, bindingType).ToList(); } else { candidateMatchingOperations = resolver.ResolveUnboundOperations(model, identifier).ToList(); } } catch (Exception exc) { if (ExceptionUtils.IsCatchableExceptionType(exc)) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc); } throw; } IList <IEdmAction> foundActionsWhenLookingForFunctions = new List <IEdmAction>(); int parameterNamesCount = parameterNames.Count(); if (bindingType != null) { candidateMatchingOperations = candidateMatchingOperations.EnsureOperationsBoundWithBindingParameter().ToList(); } // If the number of parameters > 0 then this has to be a function as actions can't have parameters on the uri only in the payload. Filter further by parameters in this case, otherwise don't. if (parameterNamesCount > 0) { // can only be a function as only functions have parameters on the uri. candidateMatchingOperations = candidateMatchingOperations.RemoveActions(out foundActionsWhenLookingForFunctions).FilterFunctionsByParameterNames(parameterNames, resolver.EnableCaseInsensitive).Cast <IEdmOperation>().ToList(); } else if (bindingType != null) { // Filter out functions with more than one parameter. Actions should not be filtered as the parameters are in the payload not the uri candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && o.Parameters.Count() == 1) || o.IsAction()).ToList(); } else { // Filter out functions with any parameters candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && !o.Parameters.Any()) || o.IsAction()).ToList(); } // Only filter if there is more than one and its needed. if (candidateMatchingOperations.Count > 1) { candidateMatchingOperations = candidateMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType).ToList(); } // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL if (candidateMatchingOperations.Any(f => f.IsAction())) { if (candidateMatchingOperations.Count > 1) { if (candidateMatchingOperations.Any(o => o.IsFunction())) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationOverloads(identifier)); } else { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionOverloads(identifier)); } } if (parameterNames.Count() != 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } matchingOperation = candidateMatchingOperations.Single(); return(true); } if (foundActionsWhenLookingForFunctions.Count > 0) { throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); } if (candidateMatchingOperations.Count > 1) { throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray()))); } matchingOperation = candidateMatchingOperations.SingleOrDefault(); return(matchingOperation != null); }
/// <summary>Tries to convert values to the keys of the specified type.</summary> /// <param name="targetEntityType">The specified type.</param> /// <param name="keyPairs">The converted key-value pairs.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>true if all values were converted; false otherwise.</returns> public bool TryConvertValues(IEdmEntityType targetEntityType, out IEnumerable <KeyValuePair <string, object> > keyPairs, ODataUriResolver resolver) { Debug.Assert(!this.IsEmpty, "!this.IsEmpty -- caller should check"); IList <IEdmStructuralProperty> keyProperties = targetEntityType.Key().ToList(); Debug.Assert(keyProperties.Count == this.ValueCount || resolver.GetType() != typeof(ODataUriResolver), "type.KeyProperties.Count == this.ValueCount -- will change with containment"); if (this.NamedValues != null) { keyPairs = resolver.ResolveKeys(targetEntityType, this.NamedValues, this.ConvertValueWrapper); } else { Debug.Assert(this.positionalValues != null, "positionalValues != null -- otherwise this is Empty"); Debug.Assert(this.PositionalValues.Count == keyProperties.Count || resolver.GetType() != typeof(ODataUriResolver), "Count of positional values does not match."); keyPairs = resolver.ResolveKeys(targetEntityType, this.PositionalValues, this.ConvertValueWrapper); } return(true); }
public BinaryOperatorBinderTests() { this.shouldReturnLeft = true; this.binaryOperatorBinder = new BinaryOperatorBinder(this.BindMethodThatReturnsSingleValueQueryNode, ODataUriResolver.GetUriResolver(null)); }
/// <summary> /// Explores the OData query options for the specified API descriptions. /// </summary> /// <param name="apiDescriptions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="ApiDescription">API descriptions</see> to explore.</param> /// <param name="uriResolver">The associated <see cref="ODataUriResolver">OData URI resolver</see>.</param> protected virtual void ExploreQueryOptions(IEnumerable <ApiDescription> apiDescriptions, ODataUriResolver uriResolver) { Arg.NotNull(apiDescriptions, nameof(apiDescriptions)); Arg.NotNull(uriResolver, nameof(uriResolver)); var queryOptions = Options.QueryOptions; var settings = new ODataQueryOptionSettings() { NoDollarPrefix = uriResolver.EnableNoDollarQueryOptions, DescriptionProvider = queryOptions.DescriptionProvider, DefaultQuerySettings = DefaultQuerySettings, ModelMetadataProvider = MetadataProvider, }; queryOptions.ApplyTo(apiDescriptions, settings); }
/// <summary> /// Follow any type segments on the path, stopping at the first segment that isn't a type token. /// </summary> /// <param name="firstTypeToken">the first type segment</param> /// <param name="model">the model these types are contained in.</param> /// <param name="maxDepth">the maximum recursive depth</param> /// <param name="resolver">Resolver for uri parser.</param> /// <param name="currentLevelType">the top level type, will be overwritten with the last entity type in the chain</param> /// <param name="firstNonTypeToken">the first non type token in the path</param> /// <returns>A path with type segments added to it.</returns> public static IEnumerable <ODataPathSegment> FollowTypeSegments(PathSegmentToken firstTypeToken, IEdmModel model, int maxDepth, ODataUriResolver resolver, ref IEdmStructuredType currentLevelType, out PathSegmentToken firstNonTypeToken) { ExceptionUtils.CheckArgumentNotNull(firstTypeToken, "firstTypeToken"); ExceptionUtils.CheckArgumentNotNull(model, "model"); if (!firstTypeToken.IsNamespaceOrContainerQualified()) { throw new ODataException(ODataErrorStrings.SelectExpandPathBinder_FollowNonTypeSegment(firstTypeToken.Identifier)); } int index = 0; List <ODataPathSegment> pathToReturn = new List <ODataPathSegment>(); PathSegmentToken currentToken = firstTypeToken; while (currentToken.IsNamespaceOrContainerQualified() && currentToken.NextToken != null) { IEdmType previousLevelEntityType = currentLevelType; currentLevelType = UriEdmHelpers.FindTypeFromModel(model, currentToken.Identifier, resolver) as IEdmStructuredType; if (currentLevelType == null) { // TODO: fix this error message? throw new ODataException(ODataErrorStrings.ExpandItemBinder_CannotFindType(currentToken.Identifier)); } UriEdmHelpers.CheckRelatedTo(previousLevelEntityType, currentLevelType); pathToReturn.Add(new TypeSegment(currentLevelType, /*entitySet*/ null)); index++; currentToken = currentToken.NextToken; if (index >= maxDepth) { throw new ODataException(ODataErrorStrings.ExpandItemBinder_PathTooDeep); } } firstNonTypeToken = currentToken; return(pathToReturn); }
/// <summary> /// Try to get an IEdmTypeReference for a given type as a string, returns null if none exists /// </summary> /// <param name="model">the model for validation</param> /// <param name="fullTypeName">the type name to find</param> /// <param name="resolver">Resolver for this func.</param> /// <returns>an IEdmTypeReference for this type string.</returns> private static IEdmTypeReference TryGetTypeReference(IEdmModel model, string fullTypeName, ODataUriResolver resolver) { IEdmTypeReference typeReference = UriEdmHelpers.FindTypeFromModel(model, fullTypeName, resolver).ToTypeReference(); if (typeReference == null) { if (fullTypeName.StartsWith("Collection", StringComparison.Ordinal)) { string[] tokenizedString = fullTypeName.Split('('); string baseElementType = tokenizedString[1].Split(')')[0]; return(EdmCoreModel.GetCollection(UriEdmHelpers.FindTypeFromModel(model, baseElementType, resolver).ToTypeReference())); } else { return(null); } } return(typeReference); }
/// <summary> /// Parses the key properties based on the segment's target type, then creates a new segment for the key. /// </summary> /// <param name="segment">The segment to apply the key to.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="key">The key to apply.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>The newly created key segment.</returns> private static KeySegment CreateKeySegment(ODataPathSegment segment, KeySegment previousKeySegment, SegmentArgumentParser key, ODataUriResolver resolver) { Debug.Assert(segment != null, "segment != null"); Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty"); Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); IEdmEntityType targetEntityType = null; if (!(segment.TargetEdmType != null && segment.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType))) { throw ExceptionUtil.CreateSyntaxError(); } Debug.Assert(targetEntityType != null, "targetEntityType != null"); // Make sure the keys specified in the uri matches with the number of keys in the metadata var keyProperties = targetEntityType.Key().ToList(); if (keyProperties.Count != key.ValueCount) { NavigationPropertySegment currentNavPropSegment = segment as NavigationPropertySegment; if (currentNavPropSegment != null) { key = KeyFinder.FindAndUseKeysFromRelatedSegment(key, keyProperties, currentNavPropSegment.NavigationProperty, previousKeySegment); } // if we still didn't find any keys, then throw an error. if (keyProperties.Count != key.ValueCount && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.BadRequest_KeyCountMismatch(targetEntityType.FullName())); } } if (!key.AreValuesNamed && key.ValueCount > 1 && resolver.GetType() == typeof(ODataUriResolver)) { throw ExceptionUtil.CreateBadRequestError(ErrorStrings.RequestUriProcessor_KeysMustBeNamed); } IEnumerable <KeyValuePair <string, object> > keyPairs; if (!key.TryConvertValues(targetEntityType, out keyPairs, resolver)) { throw ExceptionUtil.CreateSyntaxError(); } IEdmEntityType entityType; bool isEntity = segment.TargetEdmType.IsEntityOrEntityCollectionType(out entityType); Debug.Assert(isEntity, "Key target type should be an entity type."); var keySegment = new KeySegment(keyPairs, entityType, segment.TargetEdmNavigationSource); keySegment.CopyValuesFrom(segment); keySegment.SingleResult = true; return(keySegment); }
/// <summary> /// Given a property name, if the associated type reference is strucutred, then this returns /// the property of the structured type. Otherwise, it returns null. /// </summary> /// <param name="parentReference">The parent type to be used to find binding options.</param> /// <param name="propertyName">The string designated the property name to be bound.</param> /// <param name="resolver">Resolver for uri parser.</param> /// <returns>The property associated with string and parent type.</returns> internal static IEdmProperty BindProperty(IEdmTypeReference parentReference, string propertyName, ODataUriResolver resolver = null) { if (resolver == null) { resolver = ODataUriResolver.Default; } IEdmStructuredTypeReference structuredParentType = parentReference == null ? null : parentReference.AsStructuredOrNull(); return(structuredParentType == null ? null : resolver.ResolveProperty(structuredParentType.StructuredDefinition(), propertyName)); }
/// <summary> /// Constructs a BinaryOperatorBinder with the given method to be used binding the parent token if needed. /// </summary> /// <param name="bindMethod">Method to use for binding the parent token, if needed.</param> /// <param name="resolver">Resolver for parsing.</param> internal BinaryOperatorBinder(Func <QueryToken, QueryNode> bindMethod, ODataUriResolver resolver) { this.bindMethod = bindMethod; this.resolver = resolver ?? ODataUriResolver.Default; }
/// <summary> /// Tries to handle the current segment as a key property value. /// </summary> /// <param name="segmentText">The segment text.</param> /// <param name="previous">The previous segment.</param> /// <param name="previousKeySegment">The parent node's key segment.</param> /// <param name="urlConvention">The current url convention for the server.</param> /// <param name="keySegment">The key segment that was created if the segment could be interpreted as a key.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <param name="resolver">The resolver to use.</param> /// <returns>Whether or not the segment was interpreted as a key.</returns> internal static bool TryHandleSegmentAsKey(string segmentText, ODataPathSegment previous, KeySegment previousKeySegment, UrlConvention urlConvention, out KeySegment keySegment, bool enableUriTemplateParsing = false, ODataUriResolver resolver = null) { Debug.Assert(previous != null, "previous != null"); Debug.Assert(urlConvention != null, "urlConvention != null"); if (resolver == null) { resolver = ODataUriResolver.Default; } keySegment = null; // If the current convention does not support keys-as-segments, then this does not apply. if (!(urlConvention.GenerateKeyAsSegment || urlConvention.ODataSimplified)) { return(false); } // Keys only apply to collections, so if the prior segment is already a singleton, do not treat this segment as a key. if (previous.SingleResult) { return(false); } // System segments (ie '$count') are never keys. if (IsSystemSegment(segmentText)) { return(false); } // If the previous type is not an entity collection type // TODO: collapse this and SingleResult. IEdmEntityType targetEntityType; if (previous.TargetEdmType == null || !previous.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType)) { return(false); } // Previously KeyAsSegment only allows single key, but we can also leverage related key finder to auto fill // missed key value from referential constraint information, which would be done in CreateKeySegment. // CreateKeySegment method will check whether key properties are missing after taking in related key values. keySegment = CreateKeySegment(previous, previousKeySegment, SegmentArgumentParser.FromSegment(segmentText, enableUriTemplateParsing), resolver); return(true); }