From c7360b2de494a26908c7fad2b106c878a3ceb048 Mon Sep 17 00:00:00 2001 From: Djcarrillo6 Date: Sun, 22 Oct 2023 14:56:19 -0700 Subject: [PATCH 1/3] Added guide for using raw JSON REST requests with low-level client. Signed-off-by: Djcarrillo6 Added sample for using raw JSON REST requests with low-level client. Signed-off-by: Djcarrillo6 Added sample for using raw JSON REST requests with low-level client #2 Signed-off-by: Djcarrillo6 Created new samples project & PR review changes. Signed-off-by: Djcarrillo6 Aligned action examples with Python guide. Signed-off-by: Djcarrillo6 PR change requests. Signed-off-by: Djcarrillo6 Changed to raw JSON in sample code. Signed-off-by: Djcarrillo6 Added new section to the guide for using PostData.Serializable() Signed-off-by: Djcarrillo6 --- OpenSearch.sln | 9 ++ USER_GUIDE.md | 7 ++ guides/json.md | 107 +++++++++++++++++++++++ samples/Samples/RawJsonSample/Program.cs | 63 +++++++++++++ samples/Samples/Samples.csproj | 15 ++++ 5 files changed, 201 insertions(+) create mode 100644 guides/json.md create mode 100644 samples/Samples/RawJsonSample/Program.cs create mode 100644 samples/Samples/Samples.csproj diff --git a/OpenSearch.sln b/OpenSearch.sln index 8fa85ddfc4..de0aba5da9 100644 --- a/OpenSearch.sln +++ b/OpenSearch.sln @@ -98,6 +98,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSearch.Stack.ArtifactsA EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{22DF419F-9A90-4317-957D-E239EB3F95DF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E89FE975-FA94-405F-B748-BF2EC8AFFEEE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "samples\Samples\Samples.csproj", "{0D084660-06BF-4F3A-A041-DAAB4837378F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -137,6 +141,7 @@ Global {22DF419F-9A90-4317-957D-E239EB3F95DF} = {87ABA679-F3F4-48CE-82B3-1AAE5D0A5935} {C80D225C-F072-4B24-9ACE-82EFD9362237} = {22DF419F-9A90-4317-957D-E239EB3F95DF} {1F5A7B1A-2566-481F-91B5-A63D7F939973} = {22DF419F-9A90-4317-957D-E239EB3F95DF} + {0D084660-06BF-4F3A-A041-DAAB4837378F} = {E89FE975-FA94-405F-B748-BF2EC8AFFEEE} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {5B393962-7586-49BA-BD99-3B1E35F48E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -231,5 +236,9 @@ Global {E7C0BDC2-28AD-4582-8FEA-0F6327A42C0E}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7C0BDC2-28AD-4582-8FEA-0F6327A42C0E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7C0BDC2-28AD-4582-8FEA-0F6327A42C0E}.Release|Any CPU.Build.0 = Release|Any CPU + {0D084660-06BF-4F3A-A041-DAAB4837378F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D084660-06BF-4F3A-A041-DAAB4837378F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D084660-06BF-4F3A-A041-DAAB4837378F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D084660-06BF-4F3A-A041-DAAB4837378F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 24ebaddce9..a202f52ffd 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -14,6 +14,8 @@ - [OpenSearch.Net](#opensearchnet) - [Getting Started](#getting-started-2) - [Connecting](#connecting-2) + - [Advanced Features](#advanced-features) + # User Guide This user guide specifies how to include and use the .NET client in your application. @@ -303,3 +305,8 @@ var client = new OpenSearchLowLevelClient(config); ``` Note the main difference here is that we are instantiating an `OpenSearchLowLevelClient` rather than `OpenSearchClient`, and `ConnectionConfiguration` instead of `ConnectionSettings`. + + +## Advanced Features + +- [Making Raw JSON Requests](guides/json.md) \ No newline at end of file diff --git a/guides/json.md b/guides/json.md new file mode 100644 index 0000000000..d89fcb6856 --- /dev/null +++ b/guides/json.md @@ -0,0 +1,107 @@ +- [Making Raw JSON REST Requests](#making-raw-json-rest-requests) + - [GET](#get) + - [PUT](#put) + - [POST](#post) + - [DELETE](#delete) + +# Making Raw JSON REST Requests +OpenSearch exposes a REST API that you can use to interact with OpenSearch. The OpenSearch .NET client provides a low-level API that allows you to send raw JSON requests to OpenSearch. This API is useful if you want to use a feature that is not yet supported by the OpenSearch .NET client, but it supported by the OpenSearch REST API. + +## GET +The following example returns the server version information via `GET /`. + +```csharp +var versionResponse = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); +Console.WriteLine(versionResponse.Body["version"]["distribution"].ToString(), versionResponse.Body["version"]["number"].ToString()); // Distribution & Version number +``` + +# PUT +The following example creates an index. + +```csharp + string indexBody = @" + {{ + ""settings"": { + ""index"": { + ""number_of_shards"": 4 + } + } + }}"; + +var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.String(indexBody)); +Console.WriteLine(putResponse.Body["acknowledged"].ToString()); // true +``` + +## POST +The following example searches for a document. + +```csharp +string q = "miller"; + +string query = $@" + {{ + ""size"": 5, + ""query"": {{ + ""multi_match"": {{ + ""query"": ""{q}"", + ""fields"": [""title^2"", ""director""] + }} + }} + }}"; + +var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.String(query)); +``` + +# DELETE +The following example deletes an index. + +```csharp +var deleteResponse = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, "/movies", CancellationToken.None); +Console.WriteLine(deleteResponse.Body["acknowledged"].ToString()); // true +``` + +## Using Different Types Of PostData +The OpenSearch .NET client also provides a `PostData` class that you can use to construct JSON requests. The following example shows how to use `PostData` to create the same request as the previous example, the difference being that the request body is constructed using `PostData.Serializable()` instead of `PostData.String()`. + +# PUT +```csharp +string q = "miller"; + +var indexBody = new +{ + settings = new + { + index = new + { + number_of_shards = 4 + } + } +}; + +var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, +"/movies", CancellationToken.None, PostData.Serializable(indexBody)); +Console.WriteLine(putResponse.Body["acknowledged"].ToString()); +``` + +## POST +```csharp +string q = "miller"; + +var query = new +{ + size = 5, + query = new + { + multi_match = new + { + query = q, + fields = new[] { "title^2", "director" } + } + } +}; + +var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.Serializable(query)); +``` + +# Sample Code +[Making Raw JSON Requests](/samples/Samples/Program.cs) \ No newline at end of file diff --git a/samples/Samples/RawJsonSample/Program.cs b/samples/Samples/RawJsonSample/Program.cs new file mode 100644 index 0000000000..864fbc981b --- /dev/null +++ b/samples/Samples/RawJsonSample/Program.cs @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: Apache-2.0 +* +* The OpenSearch Contributors require contributions made to +* this file be licensed under the Apache-2.0 license or a +* compatible open source license. +*/ + +using OpenSearch.Client; +using OpenSearch.Net; +using HttpMethod = OpenSearch.Net.HttpMethod; + + +public class Program +{ + public static async Task Main(string[] args) + { + var node = new Uri("https://localhost:9200"); + var config = new ConnectionSettings(node) + .ServerCertificateValidationCallback(CertificateValidations.AllowAll) + .BasicAuthentication("admin", "admin") + .DisableDirectStreaming(); + + var client = new OpenSearchClient(config); + + + // Sample Code: Making Raw JSON Requests + + // GET + var versionResponse = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); + Console.WriteLine(versionResponse.Body["version"]["distribution"].ToString() + " " + versionResponse.Body["version"]["number"].ToString()); + + // PUT + string indexBody = @" + {{ + ""settings"": { + ""index"": { + ""number_of_shards"": 4 + } + } + }}"; + + var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.String(indexBody)); + + // POST + string q = "miller"; + + string query = $@" + {{ + ""size"": 5, + ""query"": {{ + ""multi_match"": {{ + ""query"": ""{q}"", + ""fields"": [""title^2"", ""director""] + }} + }} + }}"; + + var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.String(query)); + + // DELETE + var deleteResponse = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, "/movies", CancellationToken.None); + } +} diff --git a/samples/Samples/Samples.csproj b/samples/Samples/Samples.csproj new file mode 100644 index 0000000000..684c46b599 --- /dev/null +++ b/samples/Samples/Samples.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + False + + + + + + + \ No newline at end of file From 38f07ef539422f4d6aca4658137f80147055320f Mon Sep 17 00:00:00 2001 From: Thomas Farr Date: Mon, 13 Nov 2023 17:27:22 +1300 Subject: [PATCH 2/3] Expand description of PostData types Signed-off-by: Thomas Farr --- guides/json.md | 144 ++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/guides/json.md b/guides/json.md index d89fcb6856..c2cb16bc25 100644 --- a/guides/json.md +++ b/guides/json.md @@ -1,72 +1,105 @@ - [Making Raw JSON REST Requests](#making-raw-json-rest-requests) - - [GET](#get) - - [PUT](#put) - - [POST](#post) - - [DELETE](#delete) + - [HTTP Methods](#http-methods) + - [GET](#get) + - [PUT](#put) + - [POST](#post) + - [DELETE](#delete) + - [Using Different Types Of PostData](#using-different-types-of-postdata) + - [PostData.String](#postdatastring) + - [PostData.Bytes](#postdatabytes) + - [PostData.Serializable](#postdataserializable) + - [PostData.MultiJson](#postdatamultijson) # Making Raw JSON REST Requests OpenSearch exposes a REST API that you can use to interact with OpenSearch. The OpenSearch .NET client provides a low-level API that allows you to send raw JSON requests to OpenSearch. This API is useful if you want to use a feature that is not yet supported by the OpenSearch .NET client, but it supported by the OpenSearch REST API. -## GET +The OpenSearch client implements many high-level REST DSLs that invoke OpenSearch APIs. However you may find yourself in a situation that requires you to invoke an API that is not supported by the client. You can use `client.LowLevel.DoRequest` to do so. See [samples/Samples/RawJsonSample/Program.cs](../samples/Samples/RawJsonSample/Program.cs) for a complete working sample. + +## HTTP Methods + +### GET The following example returns the server version information via `GET /`. ```csharp -var versionResponse = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); -Console.WriteLine(versionResponse.Body["version"]["distribution"].ToString(), versionResponse.Body["version"]["number"].ToString()); // Distribution & Version number +var info = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); +Console.WriteLine($"Welcome to {info.Body.version.distribution} {info.Body.version.number}!"); ``` -# PUT +### PUT The following example creates an index. ```csharp - string indexBody = @" - {{ - ""settings"": { - ""index"": { - ""number_of_shards"": 4 - } - } - }}"; +var indexBody = new { settings = new { index = new { number_of_shards = 4 } } }; -var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.String(indexBody)); -Console.WriteLine(putResponse.Body["acknowledged"].ToString()); // true +var createIndex = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.Serializable(indexBody)); +Debug.Assert(createIndex.Success && (bool)createIndex.Body.acknowledged, createIndex.DebugInformation); ``` -## POST +### POST The following example searches for a document. ```csharp -string q = "miller"; - -string query = $@" - {{ - ""size"": 5, - ""query"": {{ - ""multi_match"": {{ - ""query"": ""{q}"", - ""fields"": [""title^2"", ""director""] - }} - }} - }}"; - -var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.String(query)); +const string q = "miller"; + +var query = new +{ + size = 5, + query = new { multi_match = new { query = q, fields = new[] { "title^2", "director" } } } +}; + +var search = await client.LowLevel.DoRequestAsync(HttpMethod.POST, $"/{indexName}/_search", CancellationToken.None, PostData.Serializable(query)); +Debug.Assert(search.Success, search.DebugInformation); + +foreach (var hit in search.Body.hits.hits) Console.WriteLine(hit["_source"]["title"]); ``` -# DELETE +### DELETE The following example deletes an index. ```csharp -var deleteResponse = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, "/movies", CancellationToken.None); -Console.WriteLine(deleteResponse.Body["acknowledged"].ToString()); // true +var deleteDocument = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, $"/{indexName}/_doc/{id}", CancellationToken.None); +Debug.Assert(deleteDocument.Success, deleteDocument.DebugInformation); ``` ## Using Different Types Of PostData -The OpenSearch .NET client also provides a `PostData` class that you can use to construct JSON requests. The following example shows how to use `PostData` to create the same request as the previous example, the difference being that the request body is constructed using `PostData.Serializable()` instead of `PostData.String()`. +The OpenSearch .NET client provides a `PostData` class that is used to provide the request body for a request. The `PostData` class has several static methods that can be used to create a `PostData` object from different types of data. + +### PostData.String +The following example shows how to use the `PostData.String` method to create a `PostData` object from a string. + +```csharp +string indexBody = @" +{{ + ""settings"": { + ""index"": { + ""number_of_shards"": 4 + } + } +}}"; + +await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.String(indexBody)); +``` + +### PostData.Bytes +The following example shows how to use the `PostData.Bytes` method to create a `PostData` object from a byte array. -# PUT ```csharp -string q = "miller"; +byte[] indexBody = Encoding.UTF8.GetBytes(@" +{{ + ""settings"": { + ""index"": { + ""number_of_shards"": 4 + } + } +}}"); + +await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.Bytes(indexBody)); +``` + +### PostData.Serializable +The following example shows how to use the `PostData.Serializable` method to create a `PostData` object from a serializable object. +```csharp var indexBody = new { settings = new @@ -78,30 +111,21 @@ var indexBody = new } }; -var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, -"/movies", CancellationToken.None, PostData.Serializable(indexBody)); -Console.WriteLine(putResponse.Body["acknowledged"].ToString()); +await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.Serializable(indexBody)); ``` -## POST -```csharp -string q = "miller"; +### PostData.MultiJson +The following example shows how to use the `PostData.MultiJson` method to create a `PostData` object from a collection of serializable objects. +The `PostData.MultiJson` method is useful when you want to send multiple documents in a bulk request. -var query = new -{ - size = 5, - query = new - { - multi_match = new - { - query = q, - fields = new[] { "title^2", "director" } - } - } +```csharp +var bulkBody = new object[] +{ + new { index = new { _index = "movies", _id = "1" } }, + new { title = "The Godfather", director = "Francis Ford Coppola", year = 1972 }, + new { index = new { _index = "movies", _id = "2" } }, + new { title = "The Godfather: Part II", director = "Francis Ford Coppola", year = 1974 } }; -var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.Serializable(query)); +await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/_bulk", CancellationToken.None, PostData.MultiJson(bulkBody)); ``` - -# Sample Code -[Making Raw JSON Requests](/samples/Samples/Program.cs) \ No newline at end of file From 1b3954a7598f560fe48ece01e588d0c02e1dbcdd Mon Sep 17 00:00:00 2001 From: Thomas Farr Date: Mon, 13 Nov 2023 17:28:33 +1300 Subject: [PATCH 3/3] Complete working sample Signed-off-by: Thomas Farr --- samples/Samples/RawJsonSample/Program.cs | 90 +++++++++++++----------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/samples/Samples/RawJsonSample/Program.cs b/samples/Samples/RawJsonSample/Program.cs index 864fbc981b..2ad5796a72 100644 --- a/samples/Samples/RawJsonSample/Program.cs +++ b/samples/Samples/RawJsonSample/Program.cs @@ -5,59 +5,67 @@ * compatible open source license. */ +using System.Diagnostics; using OpenSearch.Client; using OpenSearch.Net; using HttpMethod = OpenSearch.Net.HttpMethod; - public class Program { public static async Task Main(string[] args) { - var node = new Uri("https://localhost:9200"); + var node = new Uri("http://localhost:9200"); var config = new ConnectionSettings(node) .ServerCertificateValidationCallback(CertificateValidations.AllowAll) .BasicAuthentication("admin", "admin") .DisableDirectStreaming(); - + var client = new OpenSearchClient(config); - - - // Sample Code: Making Raw JSON Requests - - // GET - var versionResponse = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); - Console.WriteLine(versionResponse.Body["version"]["distribution"].ToString() + " " + versionResponse.Body["version"]["number"].ToString()); - - // PUT - string indexBody = @" - {{ - ""settings"": { - ""index"": { - ""number_of_shards"": 4 - } - } - }}"; - - var putResponse = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, "/movies", CancellationToken.None, PostData.String(indexBody)); - - // POST - string q = "miller"; - - string query = $@" - {{ - ""size"": 5, - ""query"": {{ - ""multi_match"": {{ - ""query"": ""{q}"", - ""fields"": [""title^2"", ""director""] - }} - }} - }}"; - - var postResponse = await client.LowLevel.DoRequestAsync(HttpMethod.POST, "/movies/_search", CancellationToken.None, PostData.String(query)); - - // DELETE - var deleteResponse = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, "/movies", CancellationToken.None); + + var info = await client.LowLevel.DoRequestAsync(HttpMethod.GET, "/", CancellationToken.None); + Console.WriteLine($"Welcome to {info.Body.version.distribution} {info.Body.version.number}!"); + + // Create an index + + const string indexName = "movies"; + + var indexBody = new { settings = new { index = new { number_of_shards = 4 } } }; + + var createIndex = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, $"/{indexName}", CancellationToken.None, PostData.Serializable(indexBody)); + Debug.Assert(createIndex.Success && (bool)createIndex.Body.acknowledged, createIndex.DebugInformation); + + // Add a document to the index + var document = new { title = "Moneyball", director = "Bennett Miller", year = 2011}; + + const string id = "1"; + + var addDocument = await client.LowLevel.DoRequestAsync(HttpMethod.PUT, $"/{indexName}/_doc/{id}", CancellationToken.None, PostData.Serializable(document)); + Debug.Assert(addDocument.Success, addDocument.DebugInformation); + + // Refresh the index + var refresh = await client.LowLevel.DoRequestAsync(HttpMethod.POST, $"/{indexName}/_refresh", CancellationToken.None); + Debug.Assert(refresh.Success, refresh.DebugInformation); + + // Search for a document + const string q = "miller"; + + var query = new + { + size = 5, + query = new { multi_match = new { query = q, fields = new[] { "title^2", "director" } } } + }; + + var search = await client.LowLevel.DoRequestAsync(HttpMethod.POST, $"/{indexName}/_search", CancellationToken.None, PostData.Serializable(query)); + Debug.Assert(search.Success, search.DebugInformation); + + foreach (var hit in search.Body.hits.hits) Console.WriteLine(hit["_source"]["title"]); + + // Delete the document + var deleteDocument = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, $"/{indexName}/_doc/{id}", CancellationToken.None); + Debug.Assert(deleteDocument.Success, deleteDocument.DebugInformation); + + // Delete the index + var deleteIndex = await client.LowLevel.DoRequestAsync(HttpMethod.DELETE, $"/{indexName}", CancellationToken.None); + Debug.Assert(deleteIndex.Success && (bool)deleteIndex.Body.acknowledged, deleteIndex.DebugInformation); } }