From 0a09d064722b1389ca619a2d6e39c22e59a62c13 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 11 Jun 2026 17:16:16 -0700 Subject: [PATCH 1/6] Fix bug --- src/Core/Parsers/RequestParser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Core/Parsers/RequestParser.cs b/src/Core/Parsers/RequestParser.cs index 081018e820..d802abaf78 100644 --- a/src/Core/Parsers/RequestParser.cs +++ b/src/Core/Parsers/RequestParser.cs @@ -321,6 +321,8 @@ private static bool IsNull(string value) return null; } + parameterName = parameterName.Replace("$", "%24"); + // Split on '&' which are parameter separators in properly URL-encoded query strings. // Any '&' characters within parameter values will be encoded as %26. foreach (string param in queryString.TrimStart('?').Split('&')) From 024e0b08656ca1b53a2ceb2bf1ea01dd9bd1ce31 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 12 Jun 2026 10:20:36 -0700 Subject: [PATCH 2/6] Fix bug --- src/Core/Parsers/RequestParser.cs | 2 -- src/Core/Services/RestService.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Core/Parsers/RequestParser.cs b/src/Core/Parsers/RequestParser.cs index d802abaf78..081018e820 100644 --- a/src/Core/Parsers/RequestParser.cs +++ b/src/Core/Parsers/RequestParser.cs @@ -321,8 +321,6 @@ private static bool IsNull(string value) return null; } - parameterName = parameterName.Replace("$", "%24"); - // Split on '&' which are parameter separators in properly URL-encoded query strings. // Any '&' characters within parameter values will be encoded as %26. foreach (string param in queryString.TrimStart('?').Split('&')) diff --git a/src/Core/Services/RestService.cs b/src/Core/Services/RestService.cs index 5014d942bb..0db66ccbcc 100644 --- a/src/Core/Services/RestService.cs +++ b/src/Core/Services/RestService.cs @@ -71,7 +71,7 @@ RequestValidator requestValidator DatabaseObject dbObject = sqlMetadataProvider.EntityToDatabaseObject[entityName]; QueryString? query = GetHttpContext().Request.QueryString; - string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString(); + string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString().Replace("%24", "$"); //Add replacement in order to ensure '$' sign is present as expected // Read the request body early so it can be used for downstream processing. string requestBody = string.Empty; From a5e72f45b8a4b748c992ad08057e4a47b4557a0c Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 12 Jun 2026 11:39:17 -0700 Subject: [PATCH 3/6] Add testing --- .../Configuration/ConfigurationTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 203de98aef..7499a24939 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4224,6 +4224,42 @@ public async Task OpenApi_InteractiveSwaggerUI( } } + /// + /// End to end test that validates that REST requests with OData query + /// options $filter and $orderby succeed to ensure no regression can occur. + /// + [TestMethod] + [TestCategory(TestCategory.MSSQL)] + public async Task TestForRestRequestsWithFilterAndOrderbyParameters() + { + // The configuration file is constructed by merging hard-coded JSON strings to simulate the scenario where users manually edit the + // configuration file (instead of using CLI). + string configJson = TestHelper.AddPropertiesToJson(TestHelper.BASE_CONFIG, BOOK_ENTITY_JSON); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig( + configJson, + out RuntimeConfig deserializedConfig, + replacementSettings: new(), + connectionString: GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL))); + string configFileName = "custom-config.json"; + File.WriteAllText(configFileName, deserializedConfig.ToJson()); + string[] args = new[] + { + $"--ConfigFileName={configFileName}" + }; + + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + // Act + RuntimeConfigProvider configProvider = server.Services.GetService(); + using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book?$orderby=id desc&$filter=publisher_id eq 1234"); + using HttpResponseMessage restResponse = await client.SendAsync(restRequest); + + // Assert - Verify REST response + Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed"); + } + } + /// /// Test different loglevel values that are avaliable by deserializing RuntimeConfig with specified LogLevel /// and checks if value exists properly inside the deserialized RuntimeConfig. From f83f1220b5d80daace1403496926e8a6f2879bd2 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 12 Jun 2026 12:02:28 -0700 Subject: [PATCH 4/6] Address comments by copilot --- src/Core/Services/RestService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Services/RestService.cs b/src/Core/Services/RestService.cs index 0db66ccbcc..bb3888e7d9 100644 --- a/src/Core/Services/RestService.cs +++ b/src/Core/Services/RestService.cs @@ -71,7 +71,8 @@ RequestValidator requestValidator DatabaseObject dbObject = sqlMetadataProvider.EntityToDatabaseObject[entityName]; QueryString? query = GetHttpContext().Request.QueryString; - string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString().Replace("%24", "$"); //Add replacement in order to ensure '$' sign is present as expected + string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString() + .Replace("?%24", "?$").Replace("&%24", "&$"); //Add replacement in order to ensure '$' sign is present as expected only for the case where it is after a '?' or '&' signs. // Read the request body early so it can be used for downstream processing. string requestBody = string.Empty; From d2eee0d91e7f3ecc4442e89b4062b4b232511c95 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 12 Jun 2026 14:20:40 -0700 Subject: [PATCH 5/6] Address comments based on copilot --- src/Service.Tests/Configuration/ConfigurationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 7499a24939..8c7f64f4e0 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4251,7 +4251,6 @@ public async Task TestForRestRequestsWithFilterAndOrderbyParameters() using (HttpClient client = server.CreateClient()) { // Act - RuntimeConfigProvider configProvider = server.Services.GetService(); using HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book?$orderby=id desc&$filter=publisher_id eq 1234"); using HttpResponseMessage restResponse = await client.SendAsync(restRequest); From b3e59d5b69aea5eb9d74a87d3dad58559c43d24e Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 16 Jun 2026 11:26:25 -0700 Subject: [PATCH 6/6] Changes by copilot --- src/Core/Parsers/RequestParser.cs | 4 ++++ src/Core/Services/RestService.cs | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Parsers/RequestParser.cs b/src/Core/Parsers/RequestParser.cs index 081018e820..27684bd773 100644 --- a/src/Core/Parsers/RequestParser.cs +++ b/src/Core/Parsers/RequestParser.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Net; +using System.Web; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Services; using Azure.DataApiBuilder.Service.Exceptions; @@ -321,6 +322,9 @@ private static bool IsNull(string value) return null; } + // Encode the parameterName to ensure it matches the encoding in the query string. + parameterName = HttpUtility.UrlEncode(parameterName); + // Split on '&' which are parameter separators in properly URL-encoded query strings. // Any '&' characters within parameter values will be encoded as %26. foreach (string param in queryString.TrimStart('?').Split('&')) diff --git a/src/Core/Services/RestService.cs b/src/Core/Services/RestService.cs index bb3888e7d9..5014d942bb 100644 --- a/src/Core/Services/RestService.cs +++ b/src/Core/Services/RestService.cs @@ -71,8 +71,7 @@ RequestValidator requestValidator DatabaseObject dbObject = sqlMetadataProvider.EntityToDatabaseObject[entityName]; QueryString? query = GetHttpContext().Request.QueryString; - string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString() - .Replace("?%24", "?$").Replace("&%24", "&$"); //Add replacement in order to ensure '$' sign is present as expected only for the case where it is after a '?' or '&' signs. + string queryString = query is null ? string.Empty : GetHttpContext().Request.QueryString.ToString(); // Read the request body early so it can be used for downstream processing. string requestBody = string.Empty;