diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json
index de73e4e206..3f5065d27d 100644
--- a/.github/AL-Go-Settings.json
+++ b/.github/AL-Go-Settings.json
@@ -42,5 +42,6 @@
"main"
],
"rulesetFile": "..\\Build\\Rulesets\\module.ruleset.json",
- "PullRequestTrigger": "pull_request"
+ "PullRequestTrigger": "pull_request",
+ "alwaysBuildAllProjects": true
}
diff --git a/Modules/System Tests/Rest Client/app.json b/Modules/System Tests/Rest Client/app.json
new file mode 100644
index 0000000000..8df2fbd440
--- /dev/null
+++ b/Modules/System Tests/Rest Client/app.json
@@ -0,0 +1,49 @@
+{
+ "id": "ae153cbb-ad55-447c-9226-5af3ef57280f",
+ "name": "Rest Client Tests",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0",
+ "brief": "Tests for the Rest Client module",
+ "description": "Tests for the Rest Client module",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2182906",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2206603",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=72401",
+ "dependencies": [
+ {
+ "id": "812b339d-a9db-4a6e-84e4-fe35cbef0c44",
+ "name": "Rest Client",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0"
+ },
+ {
+ "id": "dd0be2ea-f733-4d65-bb34-a28f4624fb14",
+ "name": "Library Assert",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0"
+ },
+ {
+ "id": "e31ad830-3d46-472e-afeb-1d3d35247943",
+ "name": "BLOB Storage",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "platform": "23.0.0.0",
+ "target": "OnPrem",
+ "idRanges": [
+ {
+ "from": 134970,
+ "to": 134974
+ }
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "features": [
+ "NoImplicitWith"
+ ]
+}
diff --git a/Modules/System Tests/Rest Client/src/HttpAuthenticationTests.Codeunit.al b/Modules/System Tests/Rest Client/src/HttpAuthenticationTests.Codeunit.al
new file mode 100644
index 0000000000..7b5e65284f
--- /dev/null
+++ b/Modules/System Tests/Rest Client/src/HttpAuthenticationTests.Codeunit.al
@@ -0,0 +1,58 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Test.RestClient;
+
+using System.RestClient;
+using System.TestLibraries.Utilities;
+
+codeunit 134973 "Http Authentication Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+
+ [Test]
+ procedure TestAnonymousAuthentication()
+ var
+ HttpAuthenticationAnonymous: Codeunit "Http Authentication Anonymous";
+ begin
+ // [GIVEN] An anonymous authentication object
+
+ // [WHEN] The authentication object is asked if authentication is required
+ // [THEN] The authentication object should return false
+ Assert.IsFalse(HttpAuthenticationAnonymous.IsAuthenticationRequired(), 'Anonymous authentication should report that authentication is not required');
+
+ // [WHEN] The authentication object is asked to return the authorization header
+ // [THEN] The authentication object should return an empty list
+ Assert.AreEqual(HttpAuthenticationAnonymous.GetAuthorizationHeaders().Count, 0, 'Anonymous authentication should not return an authorization header');
+ end;
+
+ [NonDebuggable]
+ [Test]
+ procedure TestBasicAuthentication()
+ var
+ HttpAuthenticationBasic: Codeunit "Http Authentication Basic";
+ AuthHeader: Dictionary of [Text, SecretText];
+ BasicAuthHeaderValue: SecretText;
+ begin
+ // [GIVEN] A basic authentication object
+ HttpAuthenticationBasic.Initialize('USER01', SecretText.SecretStrSubstNo('Password123!'));
+
+ // [WHEN] The authentication object is asked if authentication is required
+ // [THEN] The authentication object should return true
+ Assert.IsTrue(HttpAuthenticationBasic.IsAuthenticationRequired(), 'Basic authentication should report that authentication is required');
+
+ // [WHEN] The authentication object is asked to return the authorization header
+ // [THEN] THe authentication object should return a dictionary with one element that is a base64 encoded string
+ AuthHeader := HttpAuthenticationBasic.GetAuthorizationHeaders();
+ Assert.AreEqual(AuthHeader.Count, 1, 'Basic authentication should return one authorization header');
+ Assert.AreEqual(AuthHeader.ContainsKey('Authorization'), true, 'Basic authentication should return an authorization header');
+
+ BasicAuthHeaderValue := AuthHeader.Get('Authorization');
+ Assert.AreEqual(BasicAuthHeaderValue.Unwrap(), 'Basic VVNFUjAxOlBhc3N3b3JkMTIzIQ==', 'Basic authentication should return a base64 encoded string');
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System Tests/Rest Client/src/HttpContentTests.Codeunit.al b/Modules/System Tests/Rest Client/src/HttpContentTests.Codeunit.al
new file mode 100644
index 0000000000..b7d63f86f3
--- /dev/null
+++ b/Modules/System Tests/Rest Client/src/HttpContentTests.Codeunit.al
@@ -0,0 +1,458 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Test.RestClient;
+
+using System.RestClient;
+using System.TestLibraries.Utilities;
+using System.Utilities;
+
+codeunit 134970 "Http Content Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+
+ [Test]
+ procedure TestCreateWithText()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with text
+
+ // [GIVEN] AL Http Content created with text
+ ALHttpContent := ALHttpContent.Create('Hello World');
+
+ // [WHEN] Content is retrieved as Text
+ TextContent := ALHttpContent.AsText();
+
+ // [THEN] Content is equal to Hello World
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is text/plain
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('text/plain', HeaderValues.Get(1), 'The Content-Type header must be text/plain');
+ end;
+
+ [Test]
+ procedure TestCreateWithTextAndContentType()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with text and content type
+
+ // [GIVEN] AL Http Content created with text and content type
+ ALHttpContent := ALHttpContent.Create('Hello World', 'text/html');
+
+ // [WHEN] Content is retrieved as Text
+ TextContent := ALHttpContent.AsText();
+
+ // [THEN] Content is equal to Hello World
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is text/html
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('text/html', HeaderValues.Get(1), 'The Content-Type header must be text/html');
+ end;
+
+ [Test]
+ procedure TestCreateWithJsonObject()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ JsonObject1: JsonObject;
+ JsonObject2: JsonObject;
+ JsonText1: Text;
+ JsonText2: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with json object
+
+ // [GIVEN] AL Http Content created with json object
+ JsonObject1.Add('name', 'John');
+ JsonObject1.Add('age', 30);
+ ALHttpContent := ALHttpContent.Create(JsonObject1);
+
+ // [WHEN] Http Content is retrieved as JsonObject
+ JsonObject2 := ALHttpContent.AsJson().AsObject();
+
+ // [THEN] JsonObject is equal to JsonObject2
+ JsonObject1.WriteTo(JsonText1);
+ JsonObject2.WriteTo(JsonText2);
+ Assert.AreEqual(JsonText1, JsonText2, 'The json objects must be equal');
+
+ // [THEN] Header Content-Type is application/json
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/json', HeaderValues.Get(1), 'The Content-Type header must be application/json');
+ end;
+
+ [Test]
+ procedure TestCreateWithJsonArray()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ JsonArray1: JsonArray;
+ JsonArray2: JsonArray;
+ JsonText1: Text;
+ JsonText2: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with json array
+
+ // [GIVEN] AL Http Content created with json array
+ JsonArray1.Add('John');
+ JsonArray1.Add('Doe');
+ ALHttpContent := ALHttpContent.Create(JsonArray1);
+
+ // [WHEN] Http Content is retrieved as JsonArray
+ JsonArray2 := ALHttpContent.AsJson().AsArray();
+
+ // [THEN] JsonArray is equal to JsonArray2
+ JsonArray1.WriteTo(JsonText1);
+ JsonArray2.WriteTo(JsonText2);
+ Assert.AreEqual(JsonText1, JsonText2, 'The json arrays must be equal');
+
+ // [THEN] Header Content-Type is application/json
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/json', HeaderValues.Get(1), 'The Content-Type header must be application/json');
+ end;
+
+ [Test]
+ procedure TestCreateWithJsonToken()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ JsonToken1: JsonToken;
+ JsonToken2: JsonToken;
+ JsonText1: Text;
+ JsonText2: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with json token
+
+ // [GIVEN] AL Http Content created with json token
+ JsonToken1.ReadFrom('{"name":"John","age":30}');
+ ALHttpContent := ALHttpContent.Create(JsonToken1);
+
+ // [WHEN] Http Content is retrieved as JsonToken
+ JsonToken2 := ALHttpContent.AsJson();
+
+ // [THEN] JsonToken is equal to JsonToken2
+ JsonToken1.WriteTo(JsonText1);
+ JsonToken2.WriteTo(JsonText2);
+ Assert.AreEqual(JsonText1, JsonText2, 'The json tokens must be equal');
+
+ // [THEN] Header Content-Type is application/json
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/json', HeaderValues.Get(1), 'The Content-Type header must be application/json');
+ end;
+
+ [Test]
+ procedure TestCreateWithXmlDocument()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ XmlDoc1: XmlDocument;
+ XmlDoc2: XmlDocument;
+ XmlReadOptions: XmlReadOptions;
+ XmlWriteOptions: XmlWriteOptions;
+ Xml1: Text;
+ Xml2: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with xml document
+
+ // [GIVEN] AL Http Content created with xml document
+ XmlReadOptions.PreserveWhitespace(false);
+ XmlDocument.ReadFrom('John30', XmlReadOptions, XmlDoc1);
+ ALHttpContent := ALHttpContent.Create(XmlDoc1);
+
+ // [WHEN] Http Content is retrieved as XmlDocument
+ XmlDoc2 := ALHttpContent.AsXmlDocument();
+
+ // [THEN] Xml is equal to XmlDoc
+ XmlWriteOptions.PreserveWhitespace(false);
+ XmlDoc1.WriteTo(XmlWriteOptions, Xml1);
+
+ Clear(XmlWriteOptions); // PreserveWhitespace setting is not preserved between two calls to WriteTo, needed to reset it
+ XmlWriteOptions.PreserveWhitespace(false);
+ XmlDoc2.WriteTo(XmlWriteOptions, Xml2);
+ Assert.AreEqual(Xml1, Xml2, 'The xml documents must be equal');
+
+ // [THEN] Header Content-Type is text/xml
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('text/xml', HeaderValues.Get(1), 'The Content-Type header must be application/xml');
+ end;
+
+ [Test]
+ procedure TestCreateWithInStream()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ TempBlob: Codeunit "Temp Blob";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ OutStream: OutStream;
+ InStream1: InStream;
+ InStream2: InStream;
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with InStream
+
+ // [GIVEN] AL Http Content created with InStream
+ TempBlob.CreateOutStream(OutStream);
+ OutStream.WriteText('Hello World');
+ TempBlob.CreateInStream(InStream1);
+ ALHttpContent := ALHttpContent.Create(InStream1);
+
+ // [WHEN] Http Content is retrieved as InStream
+ InStream2 := ALHttpContent.AsInStream();
+
+ // [THEN] Content is equal to Hello World
+ InStream2.ReadText(TextContent);
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is application/octet-stream
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/octet-stream', HeaderValues.Get(1), 'The Content-Type header must be application/octet-stream');
+ end;
+
+ [Test]
+ procedure TestCreateWithInStreamWithContentType()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ TempBlob: Codeunit "Temp Blob";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ OutStream: OutStream;
+ InStream1: InStream;
+ InStream2: InStream;
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with InStream
+
+ // [GIVEN] AL Http Content created with InStream
+ TempBlob.CreateOutStream(OutStream);
+ OutStream.WriteText('Hello World');
+ TempBlob.CreateInStream(InStream1);
+ ALHttpContent := ALHttpContent.Create(InStream1, 'text/plain');
+
+ // [WHEN] Http Content is retrieved as InStream
+ InStream2 := ALHttpContent.AsInStream();
+
+ // [THEN] Content is equal to Hello World
+ InStream2.ReadText(TextContent);
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is application/octet-stream
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('text/plain', HeaderValues.Get(1), 'The Content-Type header must be text/plain');
+ end;
+
+ [Test]
+ procedure TestCreateWithTempBlob()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ TempBlob1: Codeunit "Temp Blob";
+ TempBlob2: Codeunit "Temp Blob";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ OutStream: OutStream;
+ InStream: InStream;
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with Temp Blob
+
+ // [GIVEN] AL Http Content created with Temp Blob
+ TempBlob1.CreateOutStream(OutStream);
+ OutStream.WriteText('Hello World');
+ ALHttpContent := ALHttpContent.Create(TempBlob1);
+
+ // [WHEN] Http Content is retrieved as Temp Blob
+ TempBlob2 := ALHttpContent.AsBlob();
+
+ // [THEN] Content is equal to Hello World
+ TempBlob2.CreateInStream(InStream);
+ InStream.ReadText(TextContent);
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is application/octet-stream
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/octet-stream', HeaderValues.Get(1), 'The Content-Type header must be application/octet-stream');
+ end;
+
+ [Test]
+ procedure TestCreateWithTempBlobWithContentType()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ TempBlob1: Codeunit "Temp Blob";
+ TempBlob2: Codeunit "Temp Blob";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ OutStream: OutStream;
+ InStream: InStream;
+ TextContent: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with Temp Blob
+
+ // [GIVEN] AL Http Content created with Temp Blob
+ TempBlob1.CreateOutStream(OutStream);
+ OutStream.WriteText('Hello World');
+ ALHttpContent := ALHttpContent.Create(TempBlob1, 'text/plain');
+
+ // [WHEN] Http Content is retrieved as Temp Blob
+ TempBlob2 := ALHttpContent.AsBlob();
+
+ // [THEN] Content is equal to Hello World
+ TempBlob2.CreateInStream(InStream);
+ InStream.ReadText(TextContent);
+ Assert.AreEqual('Hello World', TextContent, 'The content must be equal to Hello World');
+
+ // [THEN] Header Content-Type is application/octet-stream
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('text/plain', HeaderValues.Get(1), 'The Content-Type header must be text/plain');
+ end;
+
+ [Test]
+ procedure TestCreateWithHttpContent()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpContentHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ Json1: Text;
+ Json2: Text;
+ begin
+ // [SCENARIO] Create AL Http Content object with HttpContent
+
+ // [GIVEN] AL Http Content is created with HttpContent
+ Json1 := '{"name":"John","age":30}';
+ HttpContent.WriteFrom(Json1);
+ HttpContent.GetHeaders(HttpContentHeaders);
+ if HttpContentHeaders.Contains('Content-Type') then
+ HttpContentHeaders.Remove('Content-Type');
+ HttpContentHeaders.Add('Content-Type', 'application/json');
+ ALHttpContent := ALHttpContent.Create(HttpContent);
+
+ // [WHEN] HttpContent object is retrieved
+ HttpContent := ALHttpContent.GetHttpContent();
+
+ // [THEN] HttpContent is equal to json
+ HttpContent.ReadAs(Json2);
+ Assert.AreEqual(Json1, Json2, 'The http content must be equal to json');
+
+ // [THEN] Header Content-Type is application/json
+ HttpContent.GetHeaders(HttpContentHeaders);
+ HttpContentHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/json', HeaderValues.Get(1), 'The Content-Type header must be application/json');
+ end;
+
+ [Test]
+ procedure TestSetContentTypeHeader()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ begin
+ // [SCENARIO] Set Content-Type header
+
+ // [GIVEN] AL Http Content created with text
+ ALHttpContent := ALHttpContent.Create('{ "name": "John", "age": 30 }');
+
+ // [WHEN] Content-Type header is set
+ ALHttpContent.SetContentTypeHeader('application/json');
+
+ // [THEN] Header Content-Type is application/json
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Type', HeaderValues);
+ Assert.AreEqual('application/json', HeaderValues.Get(1), 'The Content-Type header must be application/json');
+ end;
+
+ [Test]
+ procedure TestSetContentEncoding()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ begin
+ // [SCENARIO] Set Content-Encoding header
+
+ // [GIVEN] AL Http Content created with text
+ ALHttpContent := ALHttpContent.Create('{ "name": "John", "age": 30 }');
+
+ // [WHEN] Content-Encoding header is set
+ ALHttpContent.AddContentEncoding('deflate');
+ ALHttpContent.AddContentEncoding('br');
+
+ // [THEN] Header Content-Encoding is deflate, br
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('Content-Encoding', HeaderValues);
+ Assert.AreEqual('deflate', HeaderValues.Get(1), 'The Content-Encoding header must be deflate');
+ Assert.AreEqual('br', HeaderValues.Get(2), 'The Content-Encoding header must be br');
+ end;
+
+ [Test]
+ procedure TestSetHeader()
+ var
+ ALHttpContent: Codeunit "Http Content";
+ HttpContent: HttpContent;
+ HttpHeaders: HttpHeaders;
+ HeaderValues: List of [Text];
+ begin
+ // [SCENARIO] Set header
+
+ // [GIVEN] AL Http Content created with text
+ ALHttpContent := ALHttpContent.Create('{ "name": "John", "age": 30 }');
+
+ // [WHEN] Header is set
+ ALHttpContent.SetHeader('X-My-Custom-Header', 'BC Rest Client');
+
+ // [THEN] Header X-My-Custom-Header is BC Rest Client
+ HttpContent := ALHttpContent.GetHttpContent();
+ HttpContent.GetHeaders(HttpHeaders);
+ HttpHeaders.GetValues('X-My-Custom-Header', HeaderValues);
+ Assert.AreEqual('BC Rest Client', HeaderValues.Get(1), 'The X-Test header must be BC Rest Client');
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System Tests/Rest Client/src/RequestMessageTests.Codeunit.al b/Modules/System Tests/Rest Client/src/RequestMessageTests.Codeunit.al
new file mode 100644
index 0000000000..10d54896a4
--- /dev/null
+++ b/Modules/System Tests/Rest Client/src/RequestMessageTests.Codeunit.al
@@ -0,0 +1,158 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Test.RestClient;
+
+using System.RestClient;
+using System.TestLibraries.Utilities;
+
+codeunit 134972 "Request Message Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+
+ [Test]
+ procedure TestSetHttpMethod()
+ var
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpRequestMessage: HttpRequestMessage;
+ begin
+ // [SCENARIO] The request message is initialized with an empty content
+
+ // [GIVEN] An initialized Http Request Message
+ ALHttpRequestMessage.SetHttpMethod(Enum::"Http Method"::PATCH);
+
+ // [WHEN] The request message is read
+ HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage();
+
+ // [THEN] The request message is initialized correctly
+ Assert.AreEqual(HttpRequestMessage.Method(), 'PATCH', 'The request message method is not correct.');
+ end;
+
+ [Test]
+ procedure TestRequestMessageWithoutContent()
+ var
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpRequestMessage: HttpRequestMessage;
+ begin
+ // [SCENARIO] The request message is initialized without content
+
+ // [GIVEN] An initialized Http Request Message
+ ALHttpRequestMessage.SetHttpMethod('GET');
+ ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/');
+
+ // [WHEN] The request message is read
+ HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage();
+
+ // [THEN] The request message is initialized correctly
+ Assert.AreEqual(HttpRequestMessage.Method(), 'GET', 'The request message method is not correct.');
+ Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.');
+ end;
+
+ [Test]
+ procedure TestRequestMessageWithTextContent()
+ var
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpContent: Codeunit "Http Content";
+ HttpRequestMessage: HttpRequestMessage;
+ ContentHeaders: HttpHeaders;
+ ContentHeaderValues: List of [Text];
+ ContentText: Text;
+ begin
+ // [GIVEN] An initialized Http Request Message
+ ALHttpRequestMessage.SetHttpMethod('POST');
+ ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/');
+
+ // [GIVEN] The request message content is a text
+ ALHttpRequestMessage.SetContent(HttpContent.Create('Hello World!'));
+
+ // [WHEN] The request message is read
+ HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage();
+
+ // [THEN] The request message is initialized correctly
+ Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.');
+ Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.');
+
+ HttpRequestMessage.Content().ReadAs(ContentText);
+ Assert.AreEqual(ContentText, 'Hello World!', 'The request message content is not correct.');
+
+ HttpRequestMessage.Content.GetHeaders(ContentHeaders);
+ Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.');
+
+ ContentHeaders.GetValues('Content-Type', ContentHeaderValues);
+ Assert.AreEqual(ContentHeaderValues.Get(1), 'text/plain', 'The request message content type is not correct.');
+ end;
+
+ [Test]
+ procedure TestRequestMessageWithJsonContent()
+ var
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpContent: Codeunit "Http Content";
+ HttpRequestMessage: HttpRequestMessage;
+ ContentHeaders: HttpHeaders;
+ ContentHeaderValues: List of [Text];
+ ContentJson: JsonObject;
+ ContentText: Text;
+ begin
+ // [GIVEN] An initialized Http Request Message
+ ALHttpRequestMessage.SetHttpMethod('POST');
+ ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/');
+
+ // [GIVEN] The request message content is a JSON object
+ ContentJson.Add('value', 'Hello World!');
+ ALHttpRequestMessage.SetContent(HttpContent.Create(ContentJson));
+
+ // [WHEN] The request message is read
+ HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage();
+
+ // [THEN] The request message is initialized correctly
+ Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.');
+ Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.');
+
+ HttpRequestMessage.Content().ReadAs(ContentText);
+ Assert.AreEqual(ContentJson.ReadFrom(ContentText), true, 'The request message content is not a valid JSON object.');
+ Assert.AreEqual(ContentJson.Contains('value'), true, 'The request message content does not contain the expected property "value".');
+ Assert.AreEqual(GetJsonToken(ContentJson, 'value').AsValue().AsText(), 'Hello World!', 'The request message content property "value" is not correct.');
+
+ HttpRequestMessage.Content.GetHeaders(ContentHeaders);
+ Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.');
+
+ ContentHeaders.GetValues('Content-Type', ContentHeaderValues);
+ Assert.AreEqual(ContentHeaderValues.Get(1), 'application/json', 'The request message content type is not correct.');
+ end;
+
+ [Test]
+ procedure TestAddRequestHeader()
+ var
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpRequestMessage: HttpRequestMessage;
+ ContentHeaders: HttpHeaders;
+ ContentHeaderValues: List of [Text];
+ begin
+ // [GIVEN] An initialized Http Request Message
+ ALHttpRequestMessage.SetHttpMethod('GET');
+ ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/');
+
+ // [GIVEN] The request message has a custom header
+ ALHttpRequestMessage.SetHeader('X-Custom-Header', 'My Request Header');
+
+ // [WHEN] The request message is read
+ HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage();
+
+ // [THEN] The request message is initialized correctly
+ HttpRequestMessage.GetHeaders(ContentHeaders);
+ Assert.AreEqual(ContentHeaders.Contains('X-Custom-Header'), true, 'The custom header is missing.');
+
+ ContentHeaders.GetValues('X-Custom-Header', ContentHeaderValues);
+ Assert.AreEqual(ContentHeaderValues.Get(1), 'My Request Header', 'The custom header value is not correct.');
+ end;
+
+ local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken
+ begin
+ JsonObject.Get(Name, JsonToken);
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System Tests/Rest Client/src/RestClientTests.Codeunit.al b/Modules/System Tests/Rest Client/src/RestClientTests.Codeunit.al
new file mode 100644
index 0000000000..a29dde8c92
--- /dev/null
+++ b/Modules/System Tests/Rest Client/src/RestClientTests.Codeunit.al
@@ -0,0 +1,434 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Test.RestClient;
+
+using System.RestClient;
+using System.TestLibraries.Utilities;
+
+codeunit 134971 "Rest Client Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+ HttpClientHandler: Codeunit "Test Http Client Handler";
+
+ [Test]
+ procedure TestGet()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Get method is called
+ HttpResponseMessage := RestClient.Get('https://httpbin.org/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestPost()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test POST request
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Post method is called
+ HttpResponseMessage := RestClient.Post('https://httpbin.org/post', HttpGetContent.Create('Hello World'));
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url');
+ Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestPatch()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test PATCH request
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Patch method is called
+ HttpResponseMessage := RestClient.Patch('https://httpbin.org/patch', HttpGetContent.Create('Hello World'));
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url');
+ Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestPut()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test PUT request
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Put method is called
+ HttpResponseMessage := RestClient.Put('https://httpbin.org/put', HttpGetContent.Create('Hello World'));
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url');
+ Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestDelete()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test DELETE request
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Delete method is called
+ HttpResponseMessage := RestClient.Delete('https://httpbin.org/delete');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/delete', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestGetWithDefaultHeaders()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request with headers
+
+ // [GIVEN] An initialized Rest Client with default request headers
+ RestClient.Initialize(HttpClientHandler);
+ RestClient.SetDefaultRequestHeader('X-Test-Header', 'Test');
+
+ // [WHEN] The Get method is called with headers
+ HttpResponseMessage := RestClient.Get('https://httpbin.org/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ Assert.AreEqual(SelectJsonToken('$.headers.X-Test-Header', JsonObject).AsValue().AsText(), 'Test', 'The response should contain the expected header');
+ end;
+
+ [Test]
+ procedure TestBaseAddress()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request with base address
+
+ // [GIVEN] An initialized Rest Client with base address
+ RestClient.Initialize(HttpClientHandler);
+ RestClient.SetBaseAddress('https://httpbin.org');
+
+ // [WHEN] The Get method is called with relative url
+ HttpResponseMessage := RestClient.Get('/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestDefaultUserAgentHeader()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request with default User-Agent header
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Get method is called using the default User-Agent header
+ HttpResponseMessage := RestClient.Get('https://httpbin.org/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.IsTrue(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText().StartsWith('Dynamics 365 Business Central '), 'The response should contain a User-Agent header');
+ end;
+
+ [Test]
+ procedure TestCustomUserAgentHeader()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request with custom User-Agent header
+
+ // [GIVEN] An initialized Rest Client with a customer User-Agent header
+ RestClient.Initialize(HttpClientHandler);
+ RestClient.SetUserAgentHeader('BC Rest Client Test');
+
+ // [WHEN] The Get method is called using a custom User-Agent header
+ HttpResponseMessage := RestClient.Get('https://httpbin.org/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText(), 'BC Rest Client Test', 'The response should contain the expected User-Agent header');
+ end;
+
+ [Test]
+ procedure TestGetAsJson()
+ var
+ RestClient: Codeunit "Rest Client";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test GET request with JSON response
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The GetAsJson method is called
+ JsonObject := RestClient.GetAsJson('https://httpbin.org/get').AsObject();
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestPostAsJson()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ JsonObject1: JsonObject;
+ JsonObject2: JsonObject;
+ begin
+ // [SCENARIO] Test POST request with JSON request and response
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [GIVEN] A Json object
+ JsonObject1.Add('name', 'John');
+ JsonObject1.Add('age', 30);
+
+ // [WHEN] The PostAsJson method is called with a Json object
+ HttpGetContent := HttpGetContent.Create(JsonObject1);
+ JsonObject2 := RestClient.PostAsJson('https://httpbin.org/post', JsonObject1).AsObject();
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url');
+ JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText());
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data');
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestPatchAsJson()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ JsonObject1: JsonObject;
+ JsonObject2: JsonObject;
+ begin
+ // [SCENARIO] Test PATCH request with JSON request and response
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [GIVEN] A Json object
+ JsonObject1.Add('name', 'John');
+ JsonObject1.Add('age', 30);
+
+ // [WHEN] The PatchAsJson method is called with a Json object
+ HttpGetContent := HttpGetContent.Create(JsonObject1);
+ JsonObject2 := RestClient.PatchAsJson('https://httpbin.org/patch', JsonObject1).AsObject();
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url');
+ JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText());
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data');
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestPutAsJson()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpGetContent: Codeunit "Http Content";
+ JsonObject1: JsonObject;
+ JsonObject2: JsonObject;
+ begin
+ // [SCENARIO] Test PUT request with JSON request and response
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [GIVEN] A Json object
+ JsonObject1.Add('name', 'John');
+ JsonObject1.Add('age', 30);
+
+ // [WHEN] The PutAsJson method is called with a Json object
+ HttpGetContent := HttpGetContent.Create(JsonObject1);
+ JsonObject2 := RestClient.PutAsJson('https://httpbin.org/put', JsonObject1).AsObject();
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url');
+ JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText());
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data');
+ Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestSendWithoutGetContent()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test Send method without Getcontent
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Send method is called without Getcontent
+ HttpResponseMessage := RestClient.Send(Enum::"Http Method"::GET, 'https://httpbin.org/get');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestSendWithGetContent()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpContent: Codeunit "Http Content";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test Send method with Getcontent
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Send method is called with Getcontent
+ HttpResponseMessage := RestClient.Send(Enum::"Http Method"::POST, 'https://httpbin.org/post', HttpContent.Create('Hello World'));
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url');
+ Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data');
+ end;
+
+ [Test]
+ procedure TestSendRequestMessage()
+ var
+ RestClient: Codeunit "Rest Client";
+ ALHttpRequestMessage: Codeunit "Http Request Message";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ begin
+ // [SCENARIO] Test Send method with request message
+
+ // [GIVEN] An uninitialized Rest Client
+ RestClient.Initialize(HttpClientHandler);
+
+ // [WHEN] The Send method is called with a request message
+ ALHttpRequestMessage.SetHttpMethod(Enum::"Http Method"::GET);
+ ALHttpRequestMessage.SetRequestUri('https://httpbin.org/get');
+ HttpResponseMessage := RestClient.Send(ALHttpRequestMessage);
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url');
+ end;
+
+ [Test]
+ procedure TestBasicAuthentication()
+ var
+ RestClient: Codeunit "Rest Client";
+ HttpAuthenticationBasic: Codeunit "Http Authentication Basic";
+ HttpResponseMessage: Codeunit "Http Response Message";
+ JsonObject: JsonObject;
+ PasswordText: Text;
+ begin
+ // [SCENARIO] Test Http Get with Basic Authentication
+
+ // [GIVEN] An initialized Rest Client with Basic Authentication
+ PasswordText := 'Password123';
+ HttpAuthenticationBasic.Initialize('user01', PasswordText);
+ RestClient.Initialize(HttpClientHandler, HttpAuthenticationBasic);
+
+ // [WHEN] The Get method is called
+ HttpResponseMessage := RestClient.Get('https://httpbin.org/basic-auth/user01/Password123');
+
+ // [THEN] The response contains the expected data
+ Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200');
+ JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject();
+ Assert.AreEqual(GetJsonToken(JsonObject, 'authenticated').AsValue().AsBoolean(), true, 'The response should contain the expected data');
+ end;
+
+ local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken
+ begin
+ JsonObject.Get(Name, JsonToken);
+ end;
+
+ local procedure SelectJsonToken(Path: Text; JsonObject: JsonObject) JsonToken: JsonToken
+ begin
+ JsonObject.SelectToken(Path, JsonToken);
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System Tests/Rest Client/src/TestHttpClientHandler.Codeunit.al b/Modules/System Tests/Rest Client/src/TestHttpClientHandler.Codeunit.al
new file mode 100644
index 0000000000..a7df69fcf7
--- /dev/null
+++ b/Modules/System Tests/Rest Client/src/TestHttpClientHandler.Codeunit.al
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace System.Test.RestClient;
+
+using System.RestClient;
+
+codeunit 134974 "Test Http Client Handler" implements "Http Client Handler"
+{
+ SingleInstance = true;
+
+ procedure Send(HttpClient: HttpClient; HttpRequestMessage: codeunit "Http Request Message"; var HttpResponseMessage: codeunit "Http Response Message") Success: Boolean;
+ var
+ ResponseMessage: HttpResponseMessage;
+ begin
+ Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage);
+ HttpResponseMessage.SetResponseMessage(ResponseMessage);
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/app.json b/Modules/System/Rest Client/app.json
new file mode 100644
index 0000000000..1e0a202bd9
--- /dev/null
+++ b/Modules/System/Rest Client/app.json
@@ -0,0 +1,42 @@
+{
+ "id": "812b339d-a9db-4a6e-84e4-fe35cbef0c44",
+ "name": "Rest Client",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0",
+ "brief": "Provides functionality to call REST services from AL",
+ "description": "Provides functionality to call REST services from AL",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2103698",
+ "url": "https://go.microsoft.com/fwlink/?linkid=724011",
+ "dependencies": [
+ {
+ "id": "e31ad830-3d46-472e-afeb-1d3d35247943",
+ "name": "BLOB Storage",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0"
+ },
+ {
+ "id": "7e3b999e-1182-45d2-8b82-d5127ddba9b2",
+ "name": "DotNet Aliases",
+ "publisher": "Microsoft",
+ "version": "23.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "platform": "23.0.0.0",
+ "idRanges": [
+ {
+ "from": 2350,
+ "to": 2360
+ }
+ ],
+ "target": "OnPrem",
+ "runtime": "12.0",
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "features": ["NoImplicitWith","TranslationFile"]
+}
diff --git a/Modules/System/Rest Client/src/Authentication/HttpAuthentication.Interface.al b/Modules/System/Rest Client/src/Authentication/HttpAuthentication.Interface.al
new file mode 100644
index 0000000000..35e0f3ecdc
--- /dev/null
+++ b/Modules/System/Rest Client/src/Authentication/HttpAuthentication.Interface.al
@@ -0,0 +1,17 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+/// An interface to support different authorization mechanism in a generic way.
+interface "Http Authentication"
+{
+ /// Indicates whether authentication is required for the request.
+ /// True if authentication is required, false otherwise.
+ procedure IsAuthenticationRequired(): Boolean
+
+ /// Gets the authorization headers for the request.
+ /// A dictionary of authorization headers.
+ procedure GetAuthorizationHeaders(): Dictionary of [Text, SecretText]
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al b/Modules/System/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al
new file mode 100644
index 0000000000..abf1c542c3
--- /dev/null
+++ b/Modules/System/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+/// Implementation of the "Http Authentication" interface for a anonymous request.
+codeunit 2358 "Http Authentication Anonymous" implements "Http Authentication"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ /// Indicates if authentication is required.
+ /// False, because no authentication is required.
+ procedure IsAuthenticationRequired(): Boolean
+ begin
+ exit(false);
+ end;
+
+ /// Gets the authorization headers.
+ /// Empty dictionary, because no authentication is required.
+ procedure GetAuthorizationHeaders() Header: Dictionary of [Text, SecretText]
+ begin
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al b/Modules/System/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al
new file mode 100644
index 0000000000..552d139504
--- /dev/null
+++ b/Modules/System/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al
@@ -0,0 +1,65 @@
+/// Implementation of the "Http Authentication" interface for a request that requires basic authentication
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+using System;
+
+codeunit 2359 "Http Authentication Basic" implements "Http Authentication"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ GlobalUsername: Text;
+ GlobalPassword: SecretText;
+ UsernameDomainTxt: Label '%1\%2', Locked = true;
+
+ /// Initializes the authentication object with the given username and password
+ /// The username to use for authentication
+ /// The password to use for authentication
+ procedure Initialize(Username: Text; Password: SecretText)
+ begin
+ Initialize(Username, '', Password);
+ end;
+
+ /// Initializes the authentication object with the given username, domain and password
+ /// The username to use for authentication
+ /// The domain to use for authentication
+ /// The password to use for authentication
+ procedure Initialize(Username: Text; Domain: Text; Password: SecretText)
+ begin
+ if Domain = '' then
+ GlobalUsername := Username
+ else
+ GlobalUsername := StrSubstNo(UsernameDomainTxt, Username, Domain);
+
+ GlobalPassword := Password;
+ end;
+
+ /// Checks if authentication is required for the request
+ /// Returns true because authentication is required
+ procedure IsAuthenticationRequired(): Boolean;
+ begin
+ exit(true);
+ end;
+
+ /// Gets the authorization headers for the request
+ /// Returns a dictionary of headers that need to be added to the request
+ procedure GetAuthorizationHeaders() Header: Dictionary of [Text, SecretText];
+ begin
+ Header.Add('Authorization', SecretStrSubstNo('Basic %1', ToBase64(SecretStrSubstNo('%1:%2', GlobalUsername, GlobalPassword))));
+ end;
+
+ local procedure ToBase64(String: SecretText) Base64String: SecretText
+ var
+ Convert: DotNet Convert;
+ Encoding: DotNet Encoding;
+ begin
+#pragma warning disable AL0796
+ Base64String := Convert.ToBase64String(Encoding.UTF8().GetBytes(String.Unwrap()));
+#pragma warning restore AL0796
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al b/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al
new file mode 100644
index 0000000000..d17d921197
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al
@@ -0,0 +1,19 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+codeunit 2360 "Http Client Handler" implements "Http Client Handler"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ procedure Send(HttpClient: HttpClient; HttpRequestMessage: codeunit "Http Request Message"; var HttpResponseMessage: codeunit "Http Response Message") Success: Boolean;
+ var
+ ResponseMessage: HttpResponseMessage;
+ begin
+ Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage);
+ HttpResponseMessage.SetResponseMessage(ResponseMessage);
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al b/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al
new file mode 100644
index 0000000000..849a4605ea
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+interface "Http Client Handler"
+{
+ procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpContent.Codeunit.al b/Modules/System/Rest Client/src/HttpContent.Codeunit.al
new file mode 100644
index 0000000000..507c3ed01c
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpContent.Codeunit.al
@@ -0,0 +1,287 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+using System.Utilities;
+
+/// Holder object for the Http Content data.
+codeunit 2354 "Http Content"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ HttpContentImpl: Codeunit "Http Content Impl.";
+
+ #region Constructors
+ /// Initializes a new instance of the Http Content class with the specified Text content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'text/plain'.
+ procedure Create(Content: Text) HttpContent: Codeunit "Http Content"
+ begin
+ HttpContent := Create(Content, '');
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified SecretText content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'text/plain'.
+ procedure Create(Content: SecretText) HttpContent: Codeunit "Http Content"
+ begin
+ HttpContent := Create(Content, '');
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified Text content and content type.
+ /// The content to send to the server.
+ /// The content type of the content to send to the server.
+ /// The Http Content object.
+ /// If the ContentType parameter is not specified, it will be set to 'text/plain'.
+ procedure Create(Content: Text; ContentType: Text) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content, ContentType);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified SecretText content and content type.
+ /// The content to send to the server.
+ /// The content type of the content to send to the server.
+ /// The Http Content object.
+ /// If the ContentType parameter is not specified, it will be set to 'text/plain'.
+ procedure Create(Content: SecretText; ContentType: Text) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content, ContentType);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified JsonObject content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'application/json'.
+ procedure Create(Content: JsonObject) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified JsonArray content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'application/json'.
+ procedure Create(Content: JsonArray) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified JsonToken content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'application/json'.
+ procedure Create(Content: JsonToken) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified XmlDocument content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'text/xml'.
+ procedure Create(Content: XmlDocument) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'application/octet-stream'.
+ procedure Create(Content: Codeunit "Temp Blob") HttpContent: Codeunit "Http Content"
+ begin
+ HttpContent := Create(Content, '');
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content and content type.
+ /// The content to send to the server.
+ /// The content type of the content to send to the server.
+ /// The Http Content object.
+ /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'.
+ procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content, ContentType);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified InStream content.
+ /// The content to send to the server.
+ /// The Http Content object.
+ /// The Content-Type header will be set to 'application/octet-stream'.
+ procedure Create(Content: InStream) HttpContent: Codeunit "Http Content"
+ begin
+ HttpContent := Create(Content, '');
+ end;
+
+ /// Initializes a new instance of the Http Content class with the specified InStream content and content type.
+ /// The content to send to the server.
+ /// The content type of the content to send to the server.
+ /// The Http Content object.
+ /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'.
+ procedure Create(Content: InStream; ContentType: Text) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content, ContentType);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+
+ /// Initializes a new instance of the Http Content object with the specified HttpContent object.
+ /// The HttpContent object.
+ /// The HttpContent object.
+ /// The HttpContent must be properly prepared including the Content-Type header.
+ procedure Create(Content: HttpContent) HttpContent: Codeunit "Http Content"
+ begin
+ SetContent(Content);
+ HttpContent.SetHttpContentImpl(HttpContentImpl);
+ end;
+ #endregion
+
+ #region Public Methods
+
+ /// Sets the Content-Type header of the HttpContent object.
+ /// The value of the header to add.
+ /// If the header already exists, it will be overwritten.
+ procedure SetContentTypeHeader(ContentType: Text)
+ begin
+ HttpContentImpl.SetContentTypeHeader(ContentType);
+ end;
+
+ /// Sets the Content-Encoding header of the HttpContent object.
+ /// The value of the header to add.
+ /// If the header already exists, the value will be added to the end of the list.
+ procedure AddContentEncoding(ContentEncoding: Text)
+ begin
+ HttpContentImpl.AddContentEncoding(ContentEncoding);
+ end;
+
+ /// Sets a new value for an existing header of the HttpContent object, or adds the header if it does not already exist.
+ /// The name of the header to add.
+ /// The value of the header to add.
+ procedure SetHeader(Name: Text; Value: Text)
+ begin
+ HttpContentImpl.SetHeader(Name, Value);
+ end;
+
+ /// Sets a new value for an existing header of the HttpContent object, or adds the header if it does not already exist.
+ /// The name of the header to add.
+ /// The value of the header to add.
+ procedure SetHeader(Name: Text; Value: SecretText)
+ begin
+ HttpContentImpl.SetHeader(Name, Value);
+ end;
+
+ /// Gets the HttpContent object.
+ /// The HttpContent object.
+ procedure GetHttpContent() ReturnValue: HttpContent
+ begin
+ ReturnValue := HttpContentImpl.GetHttpContent();
+ end;
+
+ /// Gets the content of the HTTP response message as a string.
+ /// The content of the HTTP response message as a string.
+ procedure AsText() ReturnValue: Text
+ begin
+ ReturnValue := HttpContentImpl.AsText();
+ end;
+
+ /// Gets the content of the HTTP response message as a SecretText.
+ /// The content of the HTTP response message as a SecretText.
+ procedure AsSecretText() ReturnValue: SecretText
+ begin
+ ReturnValue := HttpContentImpl.AsSecretText();
+ end;
+
+ /// Gets the content of the HTTP response message as a "Temp Blob" Codeunit.
+ /// The content of the HTTP response message as a "Temp Blob" Codeunit.
+ procedure AsBlob() TempBlob: Codeunit "Temp Blob"
+ begin
+ TempBlob := HttpContentImpl.AsBlob();
+ end;
+
+ /// Gets the content of the HTTP response message as an InStream.
+ /// The content of the HTTP response message as an InStream.
+ procedure AsInStream() InStr: InStream
+ begin
+ HttpContentImpl.AsInStream(InStr);
+ end;
+
+ /// Gets the content of the HTTP response message as an XmlDocument.
+ /// The content of the HTTP response message as an XmlDocument.
+ /// Fails in case the content is not a valid XML document.
+ procedure AsXmlDocument() XmlDoc: XmlDocument
+ begin
+ XmlDoc := HttpContentImpl.AsXmlDocument();
+ end;
+
+ /// Gets the content of the HTTP response message as a JsonToken.
+ /// The content of the HTTP response message as a JsonToken.
+ /// Fails in case the content is not a valid JSON document.
+ procedure AsJson() JsonToken: JsonToken
+ begin
+ JsonToken := HttpContentImpl.AsJson();
+ end;
+ #endregion
+
+ #region Internal Methods
+ internal procedure SetContent(Content: Text; ContentType: Text)
+ begin
+ HttpContentImpl.SetContent(Content, ContentType);
+ end;
+
+ internal procedure SetContent(Content: SecretText; ContentType: Text)
+ begin
+ HttpContentImpl.SetContent(Content, ContentType);
+ end;
+
+ internal procedure SetContent(Content: InStream; ContentType: Text)
+ begin
+ HttpContentImpl.SetContent(Content, ContentType);
+ end;
+
+ internal procedure SetContent(TempBlob: Codeunit "Temp Blob"; ContentType: Text)
+ begin
+ HttpContentImpl.SetContent(TempBlob, ContentType);
+ end;
+
+ internal procedure SetContent(Content: XmlDocument)
+ begin
+ HttpContentImpl.SetContent(Content);
+ end;
+
+ internal procedure SetContent(Content: JsonObject)
+ begin
+ SetContent(Content.AsToken());
+ end;
+
+ internal procedure SetContent(Content: JsonArray)
+ begin
+ SetContent(Content.AsToken());
+ end;
+
+ internal procedure SetContent(Content: JsonToken)
+ begin
+ HttpContentImpl.SetContent(Content);
+ end;
+
+ internal procedure SetContent(var Value: HttpContent)
+ begin
+ HttpContentImpl.SetContent(Value);
+ end;
+
+ internal procedure SetHttpContentImpl(Value: Codeunit "Http Content Impl.")
+ begin
+ HttpContentImpl := Value;
+ end;
+ #endregion
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpContentImpl.Codeunit.al b/Modules/System/Rest Client/src/HttpContentImpl.Codeunit.al
new file mode 100644
index 0000000000..a49bceff42
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpContentImpl.Codeunit.al
@@ -0,0 +1,174 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+using System.Utilities;
+
+codeunit 2355 "Http Content Impl."
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ Access = Internal;
+
+ var
+ HttpContent: HttpContent;
+ ContentTypeEmptyErr: Label 'The value of the Content-Type header must be specified.';
+ MimeTypeTextPlainTxt: Label 'text/plain', Locked = true;
+ MimeTypeTextXmlTxt: Label 'text/xml', Locked = true;
+ MimeTypeApplicationOctetStreamTxt: Label 'application/octet-stream', Locked = true;
+ MimeTypeApplicationJsonTxt: Label 'application/json', Locked = true;
+
+ procedure SetContentTypeHeader(ContentType: Text)
+ begin
+ if ContentType = '' then
+ Error(ContentTypeEmptyErr);
+ SetHeader('Content-Type', ContentType);
+ end;
+
+ procedure AddContentEncoding(ContentEncoding: Text)
+ var
+ Headers: HttpHeaders;
+ begin
+ if not HttpContent.GetHeaders(Headers) then begin
+ HttpContent.Clear();
+ HttpContent.GetHeaders(Headers);
+ end;
+ Headers.Add('Content-Encoding', ContentEncoding);
+ end;
+
+ procedure SetHeader(Name: Text; Value: Text)
+ var
+ Headers: HttpHeaders;
+ begin
+ if not HttpContent.GetHeaders(Headers) then begin
+ HttpContent.Clear();
+ HttpContent.GetHeaders(Headers);
+ end;
+
+ if Headers.Contains(Name) then
+ Headers.Remove(Name);
+
+ Headers.Add(Name, Value);
+ end;
+
+ procedure SetHeader(Name: Text; Value: SecretText)
+ var
+ Headers: HttpHeaders;
+ begin
+ if not HttpContent.GetHeaders(Headers) then begin
+ HttpContent.Clear();
+ HttpContent.GetHeaders(Headers);
+ end;
+
+ if Headers.Contains(Name) then
+ Headers.Remove(Name);
+
+ Headers.Add(Name, Value);
+ end;
+
+ procedure GetHttpContent() ReturnValue: HttpContent
+ begin
+ ReturnValue := HttpContent;
+ end;
+
+ procedure AsText() ReturnValue: Text
+ begin
+ HttpContent.ReadAs(ReturnValue);
+ end;
+
+ procedure AsSecretText() ReturnValue: SecretText
+ begin
+ HttpContent.ReadAs(ReturnValue);
+ end;
+
+ procedure AsBlob() ReturnValue: Codeunit "Temp Blob"
+ var
+ InStr: InStream;
+ OutStr: OutStream;
+ begin
+ HttpContent.ReadAs(InStr);
+ ReturnValue.CreateOutStream(OutStr);
+ CopyStream(OutStr, InStr);
+ end;
+
+ procedure AsInStream(var InStr: InStream)
+ begin
+ HttpContent.ReadAs(InStr);
+ end;
+
+ procedure AsXmlDocument() ReturnValue: XmlDocument
+ var
+ XmlReadOptions: XmlReadOptions;
+ begin
+ XmlReadOptions.PreserveWhitespace(false);
+ XmlDocument.ReadFrom(AsText(), XmlReadOptions, ReturnValue);
+ end;
+
+ procedure AsJson() ReturnValue: JsonToken
+ begin
+ ReturnValue.ReadFrom(AsText());
+ end;
+
+ procedure SetContent(Content: Text; ContentType: Text)
+ begin
+ HttpContent.Clear();
+ HttpContent.WriteFrom(Content);
+ if ContentType = '' then
+ ContentType := MimeTypeTextPlainTxt;
+ SetContentTypeHeader(ContentType);
+ end;
+
+ procedure SetContent(Content: SecretText; ContentType: Text)
+ begin
+ HttpContent.Clear();
+ HttpContent.WriteFrom(Content);
+ if ContentType = '' then
+ ContentType := MimeTypeTextPlainTxt;
+ SetContentTypeHeader(ContentType);
+ end;
+
+ procedure SetContent(Content: InStream; ContentType: Text)
+ begin
+ HttpContent.Clear();
+ HttpContent.WriteFrom(Content);
+ if ContentType = '' then
+ ContentType := MimeTypeApplicationOctetStreamTxt;
+ SetContentTypeHeader(ContentType);
+ end;
+
+ procedure SetContent(TempBlob: Codeunit "Temp Blob"; ContentType: Text)
+ var
+ InStream: InStream;
+ begin
+ InStream := TempBlob.CreateInStream(TextEncoding::UTF8);
+ if ContentType = '' then
+ ContentType := MimeTypeApplicationOctetStreamTxt;
+ SetContent(InStream, ContentType);
+ end;
+
+ procedure SetContent(Content: XmlDocument)
+ var
+ Xml: Text;
+ XmlWriteOptions: XmlWriteOptions;
+ begin
+ XmlWriteOptions.PreserveWhitespace(false);
+ Content.WriteTo(XmlWriteOptions, Xml);
+ SetContent(Xml, MimeTypeTextXmlTxt);
+ end;
+
+ procedure SetContent(Content: JsonToken)
+ var
+ Json: Text;
+ begin
+ Content.WriteTo(Json);
+ SetContent(Json, MimeTypeApplicationJsonTxt);
+ end;
+
+ procedure SetContent(var Value: HttpContent)
+ begin
+ HttpContent := Value;
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpMethod.Enum.al b/Modules/System/Rest Client/src/HttpMethod.Enum.al
new file mode 100644
index 0000000000..9bb1d51482
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpMethod.Enum.al
@@ -0,0 +1,69 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+///
+/// This enum contains the REST Http Methods.
+///
+enum 2350 "Http Method"
+{
+ Extensible = true;
+
+ ///
+ /// Specifies that the Http method is GET.
+ ///
+ value(0; GET)
+ {
+ Caption = 'GET', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is POST.
+ ///
+ value(1; POST)
+ {
+ Caption = 'POST', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is PATCH.
+ ///
+ value(2; PATCH)
+ {
+ Caption = 'PATCH', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is PUT.
+ ///
+ value(3; PUT)
+ {
+ Caption = 'PUT', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is DELETE.
+ ///
+ value(4; DELETE)
+ {
+ Caption = 'DELETE', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is HEAD.
+ ///
+ value(5; HEAD)
+ {
+ Caption = 'HEAD', Locked = true;
+ }
+
+ ///
+ /// Specifies that the Http method is OPTIONS.
+ ///
+ value(6; OPTIONS)
+ {
+ Caption = 'OPTIONS', Locked = true;
+ }
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpRequestMessage.Codeunit.al b/Modules/System/Rest Client/src/HttpRequestMessage.Codeunit.al
new file mode 100644
index 0000000000..fe0fe9319b
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpRequestMessage.Codeunit.al
@@ -0,0 +1,91 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+/// Holder object for the HTTP request data.
+codeunit 2352 "Http Request Message"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ HttpRequestMessageImpl: Codeunit "Http Request Message Impl.";
+
+
+ /// Sets the HTTP method or the HttpRequestMessage object.
+ /// The HTTP method to use. Valid options are GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS
+ /// Default method is GET
+ procedure SetHttpMethod(Method: Text)
+ begin
+ HttpRequestMessageImpl.SetHttpMethod(Method);
+ end;
+
+ /// Sets the HTTP method for the HttpRequestMessage object.
+ /// The HTTP method to use.
+ /// Default method is GET
+ procedure SetHttpMethod(Method: Enum "Http Method")
+ begin
+ HttpRequestMessageImpl.SetHttpMethod(Method);
+ end;
+
+ /// Gets the HTTP method for the HttpRequestMessage object.
+ /// The HTTP method for the HttpRequestMessage object.
+ procedure GetHttpMethod() Method: Text
+ begin
+ Method := HttpRequestMessageImpl.GetHttpMethod();
+ end;
+
+ /// Sets the Uri used for the HttpRequestMessage object.
+ /// The Uri to use for the HTTP request.
+ /// The valued must not be a relative URI.
+ procedure SetRequestUri(Uri: Text)
+ begin
+ HttpRequestMessageImpl.SetRequestUri(Uri);
+ end;
+
+ /// Gets the Uri used for the HttpRequestMessage object.
+ /// The Uri used for the HttpRequestMessage object.
+ procedure GetRequestUri() Uri: Text
+ begin
+ Uri := HttpRequestMessageImpl.GetRequestUri();
+ end;
+
+ /// Sets a new value for an existing header of the Http Request object, or addds the header if it does not already exist.
+ /// The name of the header to add.
+ /// The value of the header to add.
+ procedure SetHeader(HeaderName: Text; HeaderValue: Text)
+ begin
+ HttpRequestMessageImpl.SetHeader(HeaderName, HeaderValue);
+ end;
+
+ /// Sets a new value for an existing header of the Http Request object, or addds the header if it does not already exist.
+ /// The name of the header to add.
+ /// The value of the header to add.
+ procedure SetHeader(HeaderName: Text; HeaderValue: SecretText)
+ begin
+ HttpRequestMessageImpl.SetHeader(HeaderName, HeaderValue);
+ end;
+
+ /// Sets the HttpRequestMessage that is represented by the HttpRequestMessage object.
+ /// The HttpRequestMessage to set.
+ procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage)
+ begin
+ HttpRequestMessageImpl.SetHttpRequestMessage(RequestMessage);
+ end;
+
+ /// Gets the HttpRequestMessage that is represented by the HttpRequestMessage object.
+ /// The HttpRequestMessage that is represented by the HttpRequestMessage object.
+ procedure GetHttpRequestMessage() ReturnValue: HttpRequestMessage
+ begin
+ ReturnValue := HttpRequestMessageImpl.GetRequestMessage();
+ end;
+
+ /// Sets the content of the HttpRequestMessage that is represented by the HttpRequestMessage object.
+ /// The Http Content object to set.
+ procedure SetContent(HttpContent: Codeunit "Http Content")
+ begin
+ HttpRequestMessageImpl.SetContent(HttpContent);
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpRequestMessageImpl.Codeunit.al b/Modules/System/Rest Client/src/HttpRequestMessageImpl.Codeunit.al
new file mode 100644
index 0000000000..e95a1f0907
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpRequestMessageImpl.Codeunit.al
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+codeunit 2353 "Http Request Message Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ HttpRequestMessage: HttpRequestMessage;
+
+ procedure SetHttpMethod(Method: Text)
+ begin
+ HttpRequestMessage.Method := Method;
+ end;
+
+ procedure SetHttpMethod(Method: Enum "Http Method")
+ begin
+ SetHttpMethod(Method.Names.Get(Method.Ordinals.IndexOf(Method.AsInteger())));
+ end;
+
+ procedure GetHttpMethod() ReturnValue: Text
+ begin
+ ReturnValue := HttpRequestMessage.Method;
+ end;
+
+ procedure SetRequestUri(Uri: Text)
+ begin
+ HttpRequestMessage.SetRequestUri(Uri);
+ end;
+
+ procedure GetRequestUri() Uri: Text
+ begin
+ Uri := HttpRequestMessage.GetRequestUri();
+ end;
+
+ procedure SetHeader(HeaderName: Text; HeaderValue: Text)
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ HttpRequestMessage.GetHeaders(HttpHeaders);
+ if HttpHeaders.Contains(HeaderName) then
+ HttpHeaders.Remove(HeaderName);
+ HttpHeaders.Add(HeaderName, HeaderValue);
+ end;
+
+ procedure SetHeader(HeaderName: Text; HeaderValue: SecretText)
+ var
+ HttpHeaders: HttpHeaders;
+ begin
+ HttpRequestMessage.GetHeaders(HttpHeaders);
+ if HttpHeaders.Contains(HeaderName) then
+ HttpHeaders.Remove(HeaderName);
+ HttpHeaders.Add(HeaderName, HeaderValue);
+ end;
+
+ procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage)
+ begin
+ HttpRequestMessage := RequestMessage;
+ end;
+
+ procedure SetContent(HttpContent: Codeunit "Http Content")
+ begin
+ HttpRequestMessage.Content := HttpContent.GetHttpContent();
+ end;
+
+ procedure GetRequestMessage() ReturnValue: HttpRequestMessage
+ begin
+ ReturnValue := HttpRequestMessage;
+ end;
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpResponseMessage.Codeunit.al b/Modules/System/Rest Client/src/HttpResponseMessage.Codeunit.al
new file mode 100644
index 0000000000..d53e3ca94e
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpResponseMessage.Codeunit.al
@@ -0,0 +1,145 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+/// Holder object for the HTTP response data.
+codeunit 2356 "Http Response Message"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ HttpResponseMessageImpl: Codeunit "Http Response Message Impl.";
+
+ #region IsBlockedByEnvironment
+ /// Sets whether the request is blocked by the environment.
+ /// True if the request is blocked by the environment; otherwise, false.
+ procedure SetIsBlockedByEnvironment(Value: Boolean)
+ begin
+ HttpResponseMessageImpl.SetIsBlockedByEnvironment(Value);
+ end;
+
+ /// Gets whether the request is blocked by the environment.
+ /// True if the request is blocked by the environment; otherwise, false.
+ procedure GetIsBlockedByEnvironment() ReturnValue: Boolean
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetIsBlockedByEnvironment();
+ end;
+ #endregion
+
+ #region HttpStatusCode
+ procedure SetHttpStatusCode(Value: Integer)
+ begin
+ HttpResponseMessageImpl.SetHttpStatusCode(Value);
+ end;
+
+ /// Gets the HTTP status code of the response message.
+ /// The HTTP status code.
+ procedure GetHttpStatusCode() ReturnValue: Integer
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetHttpStatusCode();
+ end;
+ #endregion
+
+ #region IsSuccessStatusCode
+ /// Sets whether the HTTP response message has a success status code.
+ /// True if the HTTP response message has a success status code; otherwise, false.
+ /// Any value in the HTTP status code range 2xx is considered to be successful.
+ procedure SetIsSuccessStatusCode(Value: Boolean)
+ begin
+ HttpResponseMessageImpl.SetIsSuccessStatusCode(Value);
+ end;
+
+ /// Indicates whether the HTTP response message has a success status code.
+ /// True if the HTTP response message has a success status code; otherwise, false.
+ /// Any value in the HTTP status code range 2xx is considered to be successful.
+ procedure GetIsSuccessStatusCode() Result: Boolean
+ begin
+ Result := HttpResponseMessageImpl.GetIsSuccessStatusCode();
+ end;
+
+ #endregion
+
+ #region ReasonPhrase
+ /// Sets the reason phrase which typically is sent by servers together with the status code.
+ /// The reason phrase sent by the server.
+ procedure SetReasonPhrase(Value: Text)
+ begin
+ HttpResponseMessageImpl.SetReasonPhrase(Value);
+ end;
+
+ /// Gets the reason phrase which typically is sent by servers together with the status code.
+ /// The reason phrase sent by the server.
+ procedure GetReasonPhrase() ReturnValue: Text
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetReasonPhrase();
+ end;
+ #endregion
+
+ #region HttpContent
+ /// Sets the HTTP content sent back by the server.
+ /// The content of the HTTP response message.
+ procedure SetContent(Content: Codeunit "Http Content")
+ begin
+ HttpResponseMessageImpl.SetContent(Content);
+ end;
+
+ /// Gets the HTTP content sent back by the server.
+ /// The content of the HTTP response message.
+ procedure GetContent() ReturnValue: Codeunit "Http Content"
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetContent();
+ end;
+ #endregion
+
+ #region HttpResponseMessage
+ /// Sets the HTTP response message.
+ /// The HTTP response message.
+ procedure SetResponseMessage(ResponseMessage: HttpResponseMessage)
+ begin
+ HttpResponseMessageImpl.SetResponseMessage(ResponseMessage);
+ end;
+
+ /// Gets the HTTP response message.
+ /// The HTTPResponseMessage object.
+ procedure GetResponseMessage() ReturnValue: HttpResponseMessage
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetResponseMessage();
+ end;
+ #endregion
+
+ #region HttpHeaders
+ /// Sets the HTTP headers.
+ /// The HTTP headers.
+ procedure SetHeaders(Headers: HttpHeaders)
+ begin
+ HttpResponseMessageImpl.SetHeaders(Headers);
+ end;
+
+ /// Gets the HTTP headers.
+ /// The HTTP headers.
+ procedure GetHeaders() ReturnValue: HttpHeaders
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetHeaders();
+
+ end;
+ #endregion
+
+ #region ErrorMessage
+ /// Sets an error message when the request failed.
+ /// The error message.
+ procedure SetErrorMessage(Value: Text)
+ begin
+ HttpResponseMessageImpl.SetErrorMessage(Value);
+ end;
+
+ /// Gets the error message when the request failed.
+ /// The error message.
+ procedure GetErrorMessage() ReturnValue: Text
+ begin
+ ReturnValue := HttpResponseMessageImpl.GetErrorMessage();
+ end;
+ #endregion
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/Modules/System/Rest Client/src/HttpResponseMessageImpl.Codeunit.al
new file mode 100644
index 0000000000..7355f0244d
--- /dev/null
+++ b/Modules/System/Rest Client/src/HttpResponseMessageImpl.Codeunit.al
@@ -0,0 +1,142 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+codeunit 2357 "Http Response Message Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ #region IsBlockedByEnvironment
+ var
+ IsBlockedByEnvironment: Boolean;
+
+ procedure SetIsBlockedByEnvironment(Value: Boolean)
+ begin
+ IsBlockedByEnvironment := Value;
+ end;
+
+ procedure GetIsBlockedByEnvironment() ReturnValue: Boolean
+ begin
+ ReturnValue := IsBlockedByEnvironment;
+ end;
+ #endregion
+
+ #region HttpStatusCode
+ var
+ HttpStatusCode: Integer;
+
+ procedure SetHttpStatusCode(Value: Integer)
+ begin
+ HttpStatusCode := Value;
+ IsSuccessStatusCode := Value in [200 .. 299];
+ end;
+
+ procedure GetHttpStatusCode() ReturnValue: Integer
+ begin
+ ReturnValue := HttpStatusCode
+ end;
+ #endregion
+
+ #region IsSuccessStatusCode
+ var
+ IsSuccessStatusCode: Boolean;
+
+ procedure GetIsSuccessStatusCode() Result: Boolean
+ begin
+ Result := HttpResponseMessage.IsSuccessStatusCode;
+ end;
+
+ procedure SetIsSuccessStatusCode(Value: Boolean)
+ begin
+ IsSuccessStatusCode := Value;
+ end;
+ #endregion
+
+ #region ReasonPhrase
+ var
+ ReasonPhrase: Text;
+
+ procedure SetReasonPhrase(Value: Text)
+ begin
+ ReasonPhrase := Value;
+ end;
+
+ procedure GetReasonPhrase() ReturnValue: Text
+ begin
+ ReturnValue := HttpResponseMessage.ReasonPhrase;
+ end;
+ #endregion
+
+ #region HttpContent
+ var
+ HttpContent: Codeunit "Http Content";
+
+ procedure SetContent(Content: Codeunit "Http Content")
+ begin
+ HttpContent := Content;
+ end;
+
+ procedure GetContent() ReturnValue: Codeunit "Http Content"
+ begin
+ ReturnValue := HttpContent;
+ end;
+ #endregion
+
+ #region HttpResponseMessage
+ var
+ HttpResponseMessage: HttpResponseMessage;
+
+ procedure SetResponseMessage(var ResponseMessage: HttpResponseMessage)
+ begin
+ HttpResponseMessage := ResponseMessage;
+ SetIsBlockedByEnvironment(ResponseMessage.IsBlockedByEnvironment);
+ SetHttpStatusCode(ResponseMessage.HttpStatusCode);
+ SetReasonPhrase(ResponseMessage.ReasonPhrase);
+ SetIsSuccessStatusCode(ResponseMessage.IsSuccessStatusCode);
+ SetHeaders(ResponseMessage.Headers);
+ SetContent(HttpContent.Create(ResponseMessage.Content));
+ end;
+
+ procedure GetResponseMessage() ReturnValue: HttpResponseMessage
+ begin
+ ReturnValue := HttpResponseMessage;
+ end;
+ #endregion
+
+ #region HttpHeaders
+ var
+ HttpHeaders: HttpHeaders;
+
+ procedure SetHeaders(Headers: HttpHeaders)
+ begin
+ HttpHeaders := Headers;
+ end;
+
+ procedure GetHeaders() ReturnValue: HttpHeaders
+ begin
+ ReturnValue := HttpHeaders;
+ end;
+ #endregion
+
+ #region ErrorMessage
+ var
+ ErrorMessage: Text;
+
+ procedure SetErrorMessage(Value: Text)
+ begin
+ ErrorMessage := Value;
+ end;
+
+ procedure GetErrorMessage() ReturnValue: Text
+ begin
+ if ErrorMessage <> '' then
+ ReturnValue := ErrorMessage
+ else
+ ReturnValue := GetLastErrorText();
+ end;
+ #endregion
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/RestClient.Codeunit.al b/Modules/System/Rest Client/src/RestClient.Codeunit.al
new file mode 100644
index 0000000000..e9afb1b08c
--- /dev/null
+++ b/Modules/System/Rest Client/src/RestClient.Codeunit.al
@@ -0,0 +1,339 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+/// Provides functionality to easily work with the HttpClient object.
+codeunit 2350 "Rest Client"
+{
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ RestClientImpl: Codeunit "Rest Client Impl.";
+
+ #region Initialization
+ /// Initializes the Rest Client with the default Http Client Handler and anonymous Http authentication.
+ procedure Initialize()
+ begin
+ RestClientImpl.Initialize();
+ end;
+
+ /// Initializes the Reest Client with the given Http Client Handler
+ /// The Http Client Handler to use.
+ /// The anynomous Http Authentication will be used.
+ procedure Initialize(HttpClientHandler: Interface "Http Client Handler")
+ begin
+ RestClientImpl.Initialize(HttpClientHandler);
+ end;
+
+ /// Initializes the Rest Client with the given Http Authentication.
+ /// The authentication to use.
+ /// The default Http Client Handler will be used.
+ procedure Initialize(HttpAuthentication: Interface "Http Authentication")
+ begin
+ RestClientImpl.Initialize(HttpAuthentication);
+ end;
+
+ /// Initializes the Rest Client with the given Http Client Handler and Http Authentication.
+ /// The Http Client Handler to use.
+ /// The authentication to use.
+ procedure Initialize(HttpClientHandler: Interface "Http Client Handler"; HttpAuthentication: Interface "Http Authentication")
+ begin
+ RestClientImpl.Initialize(HttpClientHandler, HttpAuthentication);
+ end;
+
+ /// Sets a new value for an existing default header of the Http Client object, or addds the header if it does not already exist.
+ /// The name of the request header.
+ /// The header of request header.
+ /// Default request headers will be added to every request that is sent with this Rest Client instance
+ /// The Rest Client will be initialized if it was not initialized before.
+ procedure SetDefaultRequestHeader(Name: Text; Value: Text)
+ begin
+ RestClientImpl.SetDefaultRequestHeader(Name, Value);
+ end;
+
+ /// Sets a new value for an existing default header of the Http Client object, or addds the header if it does not already exist.
+ /// The name of the request header.
+ /// The header of request header.
+ /// Default request headers will be added to every request that is sent with this Rest Client instance
+ /// The Rest Client will be initialized if it was not initialized before.
+ procedure SetDefaultRequestHeader(Name: Text; Value: SecretText)
+ begin
+ RestClientImpl.SetDefaultRequestHeader(Name, Value);
+ end;
+
+ /// Sets the base address of the Rest Client.
+ /// The base address will be used for every request that is sent with this Rest Client instance.
+ /// Calls to the Get, Post, Patch, Put and Delete methods must use a relative path which will be appended to the base address.
+ /// The Rest Client will be initialized if it was not initialized before.
+ /// The base address to use.
+ procedure SetBaseAddress(Url: Text)
+ begin
+ RestClientImpl.SetBaseAddress(Url);
+ end;
+
+ /// Gets the base address of the Rest Client.
+ /// The base address of the Rest Client.
+ procedure GetBaseAddress() Url: Text
+ begin
+ Url := RestClientImpl.GetBaseAddress();
+ end;
+
+ /// Sets the timeout of the Rest Client.
+ /// The timeout to use.
+ /// The timeout will be used for every request that is sent with this Rest Client instance.
+ /// The Rest Client will be initialized if it was not initialized before.
+ procedure SetTimeOut(Timeout: Duration)
+ begin
+ RestClientImpl.SetTimeOut(Timeout);
+ end;
+
+ /// Gets the timeout of the Rest Client.
+ /// The timeout of the Rest Client.
+ procedure GetTimeOut() Timeout: Duration
+ begin
+ Timeout := RestClientImpl.GetTimeOut();
+ end;
+
+ /// Adds a certificate to the Rest Client.
+ /// The Base64 encoded certificate
+ /// The certificate will be used for every request that is sent with this Rest Client instance.
+ /// The Rest Client will be initialized if it was not initialized before.
+ procedure AddCertificate(Certificate: Text)
+ begin
+ RestClientImpl.AddCertificate(Certificate);
+ end;
+
+ /// Adds a certificate to the Rest Client.
+ /// The Base64 encoded certificate
+ /// The password of the certificate
+ /// The certificate will be used for every request that is sent with this Rest Client instance.
+ /// The Rest Client will be initialized if it was not initialized before.
+
+ procedure AddCertificate(Certificate: Text; Password: SecretText)
+ begin
+ RestClientImpl.AddCertificate(Certificate, Password);
+ end;
+
+ /// Sets the user agent header of the Rest Client.
+ /// Use this function to overwrite the default User-Agent header.
+ /// The default user agent header is "Dynamics 365 Business Central - |[Publisher]| [App Name]/[App Version]".
+ /// The Rest Client will be initialized if it was not initialized before.
+ /// The user agent header to use.
+ procedure SetUserAgentHeader(Value: Text)
+ begin
+ RestClientImpl.SetUserAgentHeader(Value);
+ end;
+
+ /// Sets the authorization header of the Rest Client.
+ /// Use this function to set the authorization header.
+ /// The Rest Client will be initialized if it was not initialized before.
+ /// The authorization header to use.
+ procedure SetAuthorizationHeader(Value: SecretText)
+ begin
+ RestClientImpl.SetAuthorizationHeader(Value);
+ end;
+ #endregion
+
+ #region BasicMethods
+ /// Sends a GET request to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The Uri the request is sent to.
+ /// The response message object
+ procedure Get(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri);
+ end;
+
+ /// Sends a POST request to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The Uri the request is sent to.
+ /// The content to send.
+ /// The response message object
+ procedure Post(RequestUri: Text; Content: Codeunit "Http Content") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, Content);
+ end;
+
+ /// Sends a PATCH request to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The Uri the request is sent to.
+ /// The content to send.
+ /// The response message object
+ procedure Patch(RequestUri: Text; Content: Codeunit "Http Content") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, Content);
+ end;
+
+ /// Sends a PUT request to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The Uri the request is sent to.
+ /// The content to send.
+ /// The response message object
+ procedure Put(RequestUri: Text; Content: Codeunit "Http Content") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, Content);
+ end;
+
+ /// Sends a DELETE request to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The Uri the request is sent to.
+ /// The response message object
+ procedure Delete(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message";
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::DELETE, RequestUri);
+ end;
+
+ #endregion
+
+ #region BasicMethodsAsJson
+ /// Sends a GET request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The response content as JsonToken
+ procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken
+ begin
+ JsonToken := RestClientImpl.GetAsJson(RequestUri);
+ end;
+
+ /// Sends a POST request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonObject.
+ /// The response content as JsonToken
+ procedure PostAsJson(RequestUri: Text; Content: JsonObject) Response: JsonToken
+ begin
+ Response := PostAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a POST request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonArray.
+ /// The response content as JsonToken
+ procedure PostAsJson(RequestUri: Text; Content: JsonArray) Response: JsonToken
+ begin
+ Response := PostAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a POST request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonToken.
+ /// The response content as JsonToken
+ procedure PostAsJson(RequestUri: Text; Content: JsonToken) Response: JsonToken
+ begin
+ Response := RestClientImpl.PostAsJson(RequestUri, Content);
+ end;
+
+ /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonObject.
+ /// The response content as JsonToken
+ procedure PatchAsJson(RequestUri: Text; Content: JsonObject) Response: JsonToken
+ begin
+ Response := PatchAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonArray.
+ /// The response content as JsonToken
+ procedure PatchAsJson(RequestUri: Text; Content: JsonArray) Response: JsonToken
+ begin
+ Response := PatchAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonToken.
+ /// The response content as JsonToken
+ procedure PatchAsJson(RequestUri: Text; Content: JSonToken) Response: JsonToken
+ begin
+ Response := RestClientImpl.PatchAsJson(RequestUri, Content);
+ end;
+
+ /// Sends a PUT request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonObject.
+ /// The response content as JsonToken
+ procedure PutAsJson(RequestUri: Text; Content: JsonObject) Response: JsonToken
+ begin
+ Response := PutAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a PUT request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonArray.
+ /// The response content as JsonToken
+ procedure PutAsJson(RequestUri: Text; Content: JsonArray) Response: JsonToken
+ begin
+ Response := PutAsJson(RequestUri, Content.AsToken());
+ end;
+
+ /// Sends a PUT request to the specified Uri and returns the response content as JsonToken.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The function also fails in case the response does not contain a success status code or a valid JSON content.
+ /// The Uri the request is sent to.
+ /// The content to send as a JsonToken.
+ /// The response content as JsonToken
+ procedure PutAsJson(RequestUri: Text; Content: JSonToken) Response: JsonToken
+ begin
+ Response := RestClientImpl.PutAsJson(RequestUri, Content);
+ end;
+ #endregion
+
+ #region GenericSendMethods
+ /// Sends a request with the specific Http method and an empty content to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The HTTP method to use.
+ /// The Uri the request is sent to.
+ /// The response message object
+ procedure Send(Method: Enum "Http Method"; RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := RestClientImpl.Send(Method, RequestUri);
+ end;
+
+ /// Sends a request with the specific Http method and the given content to the specified Uri and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// If a response was received, then the response message object contains information about the status.
+ /// The HTTP method to use.
+ /// The Uri the request is sent to.
+ /// The content to send.
+ /// The response message object
+ procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := RestClientImpl.Send(Method, RequestUri, Content);
+ end;
+
+ /// Sends the given request message and returns the response message.
+ /// The function fails with an error message if the request could not be sent or a response was not received.
+ /// The request message to send.
+ /// The response message object
+ procedure Send(var HttpRequestMessage: Codeunit "Http Request Message") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ HttpResponseMessage := RestClientImpl.Send(HttpRequestMessage);
+ end;
+ #endregion
+}
\ No newline at end of file
diff --git a/Modules/System/Rest Client/src/RestClientImpl.Codeunit.al b/Modules/System/Rest Client/src/RestClientImpl.Codeunit.al
new file mode 100644
index 0000000000..fd5afd4051
--- /dev/null
+++ b/Modules/System/Rest Client/src/RestClientImpl.Codeunit.al
@@ -0,0 +1,259 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace System.RestClient;
+
+codeunit 2351 "Rest Client Impl."
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ DefaultHttpClientHandler: Codeunit "Http Client Handler";
+ HttpAuthenticationAnonymous: Codeunit "Http Authentication Anonymous";
+ HttpAuthentication: Interface "Http Authentication";
+ HttpClientHandler: Interface "Http Client Handler";
+ HttpClient: HttpClient;
+ IsInitialized: Boolean;
+ EnvironmentBlocksErr: label 'Environment blocks an outgoing HTTP request to ''%1''.', Comment = '%1 = url, e.g. https://microsoft.com';
+ ConnectionErr: label 'Connection to the remote service ''%1'' could not be established.', Comment = '%1 = url, e.g. https://microsoft.com';
+ RequestFailedErr: label 'The request failed: %1 %2', Comment = '%1 = HTTP status code, %2 = Reason phrase';
+ UserAgentLbl: Label 'Dynamics 365 Business Central - |%1| %2/%3', Locked = true, Comment = '%1 = App Publisher; %2 = App Name; %3 = App Version';
+
+ #region Initialization
+ procedure Initialize()
+ begin
+ Initialize(DefaultHttpClientHandler, HttpAuthenticationAnonymous);
+ end;
+
+ #pragma warning disable AA0244
+ procedure Initialize(HttpClientHandler: Interface "Http Client Handler")
+ begin
+ Initialize(HttpClientHandler, HttpAuthenticationAnonymous);
+ end;
+
+ procedure Initialize(HttpAuthentication: Interface "Http Authentication")
+ begin
+ Initialize(DefaultHttpClientHandler, HttpAuthentication);
+ end;
+ #pragma warning restore AA0244
+
+ procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication")
+ begin
+ ClearAll();
+
+ HttpClient.Clear();
+ HttpClientHandler := HttpClientHandlerInstance;
+ HttpAuthentication := HttpAuthenticationInstance;
+ IsInitialized := true;
+ SetDefaultUserAgentHeader();
+ end;
+
+ procedure SetDefaultRequestHeader(Name: Text; Value: Text)
+ begin
+ CheckInitialized();
+ if HttpClient.DefaultRequestHeaders.Contains(Name) then
+ HttpClient.DefaultRequestHeaders.Remove(Name);
+ HttpClient.DefaultRequestHeaders.Add(Name, Value);
+ end;
+
+ procedure SetDefaultRequestHeader(Name: Text; Value: SecretText)
+ begin
+ CheckInitialized();
+ if HttpClient.DefaultRequestHeaders.Contains(Name) then
+ HttpClient.DefaultRequestHeaders.Remove(Name);
+ HttpClient.DefaultRequestHeaders.Add(Name, Value);
+ end;
+
+ procedure SetBaseAddress(Url: Text)
+ begin
+ CheckInitialized();
+ HttpClient.SetBaseAddress(Url);
+ end;
+
+ procedure GetBaseAddress() Url: Text
+ begin
+ CheckInitialized();
+ Url := HttpClient.GetBaseAddress;
+ end;
+
+ procedure SetTimeOut(TimeOut: Duration)
+ begin
+ CheckInitialized();
+ HttpClient.Timeout := TimeOut;
+ end;
+
+ procedure GetTimeOut() TimeOut: Duration
+ begin
+ CheckInitialized();
+ TimeOut := HttpClient.Timeout;
+ end;
+
+ procedure AddCertificate(Certificate: Text)
+ begin
+ CheckInitialized();
+ HttpClient.AddCertificate(Certificate);
+ end;
+
+ procedure AddCertificate(Certificate: Text; Password: SecretText)
+ begin
+ CheckInitialized();
+ HttpClient.AddCertificate(Certificate, Password);
+ end;
+
+ procedure SetAuthorizationHeader(Value: SecretText)
+ begin
+ SetDefaultRequestHeader('Authorization', Value);
+ end;
+
+ procedure SetUserAgentHeader(Value: Text)
+ begin
+ SetDefaultRequestHeader('User-Agent', Value);
+ end;
+ #endregion
+
+
+ #region BasicMethodsAsJson
+ procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken
+ var
+ HttpResponseMessage: Codeunit "Http Response Message";
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri);
+ if not HttpResponseMessage.GetIsSuccessStatusCode() then
+ Error(HttpResponseMessage.GetErrorMessage());
+
+ JsonToken := HttpResponseMessage.GetContent().AsJson();
+ end;
+
+ procedure PostAsJson(RequestUri: Text; Content: JsonToken) Response: JsonToken
+ var
+ HttpResponseMessage: Codeunit "Http Response Message";
+ HttpContent: Codeunit "Http Content";
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, HttpContent.Create(Content));
+
+ if not HttpResponseMessage.GetIsSuccessStatusCode() then
+ Error(HttpResponseMessage.GetErrorMessage());
+
+ Response := HttpResponseMessage.GetContent().AsJson();
+ end;
+
+ procedure PatchAsJson(RequestUri: Text; Content: JSonToken) Response: JsonToken
+ var
+ HttpResponseMessage: Codeunit "Http Response Message";
+ HttpContent: Codeunit "Http Content";
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, HttpContent.Create(Content));
+
+ if not HttpResponseMessage.GetIsSuccessStatusCode() then
+ Error(HttpResponseMessage.GetErrorMessage());
+
+ Response := HttpResponseMessage.GetContent().AsJson();
+ end;
+
+ procedure PutAsJson(RequestUri: Text; Content: JSonToken) Response: JsonToken
+ var
+ HttpResponseMessage: Codeunit "Http Response Message";
+ HttpContent: Codeunit "Http Content";
+ begin
+ HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, HttpContent.Create(Content));
+
+ if not HttpResponseMessage.GetIsSuccessStatusCode() then
+ Error(HttpResponseMessage.GetErrorMessage());
+
+ Response := HttpResponseMessage.GetContent().AsJson();
+ end;
+ #endregion
+
+ #region GenericSendMethods
+ procedure Send(Method: Enum "Http Method"; RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message"
+ var
+ EmptyHttpContent: Codeunit "Http Content";
+ begin
+ HttpResponseMessage := Send(Method, RequestUri, EmptyHttpContent);
+ end;
+
+ procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpResponseMessage: Codeunit "Http Response Message"
+ var
+ HttpRequestMessage: Codeunit "Http Request Message";
+ begin
+ CheckInitialized();
+
+ HttpRequestMessage.SetHttpMethod(Method);
+ if RequestUri.StartsWith('http://') or RequestUri.StartsWith('https://') then
+ HttpRequestMessage.SetRequestUri(RequestUri)
+ else
+ HttpRequestMessage.SetRequestUri(GetBaseAddress() + RequestUri);
+ HttpRequestMessage.SetContent(Content);
+
+ HttpResponseMessage := Send(HttpRequestMessage);
+ end;
+
+ procedure Send(var HttpRequestMessage: Codeunit "Http Request Message") HttpResponseMessage: Codeunit "Http Response Message"
+ begin
+ CheckInitialized();
+
+ if not SendRequest(HttpRequestMessage, HttpResponseMessage) then
+ Error(HttpResponseMessage.GetErrorMessage());
+ end;
+ #endregion
+
+ #region Local Methods
+ local procedure CheckInitialized()
+ begin
+ if not IsInitialized then
+ Initialize();
+ end;
+
+ local procedure SetDefaultUserAgentHeader()
+ var
+ ModuleInfo: ModuleInfo;
+ UserAgentString: Text;
+ begin
+ if NavApp.GetCurrentModuleInfo(ModuleInfo) then
+ UserAgentString := StrSubstNo(UserAgentLbl, ModuleInfo.Publisher(), ModuleInfo.Name(), ModuleInfo.AppVersion());
+
+ SetUserAgentHeader(UserAgentString);
+ end;
+
+ local procedure SendRequest(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean
+ var
+ ErrorMessage: Text;
+ begin
+ Clear(HttpResponseMessage);
+
+ if HttpAuthentication.IsAuthenticationRequired() then
+ Authorize(HttpRequestMessage);
+
+ if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, HttpResponseMessage) then begin
+ if HttpResponseMessage.GetIsBlockedByEnvironment() then
+ ErrorMessage := StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri())
+ else
+ ErrorMessage := StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri());
+ exit(false);
+ end;
+
+ if not HttpResponseMessage.GetIsSuccessStatusCode() then begin
+ ErrorMessage := StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase());
+ HttpResponseMessage.SetErrorMessage(ErrorMessage);
+ end;
+
+ exit(true);
+ end;
+
+ local procedure Authorize(HttpRequestMessage: Codeunit "Http Request Message")
+ var
+ AuthorizationHeaders: Dictionary of [Text, SecretText];
+ HeaderName: Text;
+ HeaderValue: SecretText;
+ begin
+ AuthorizationHeaders := HttpAuthentication.GetAuthorizationHeaders();
+ foreach HeaderName in AuthorizationHeaders.Keys do begin
+ HeaderValue := AuthorizationHeaders.Get(HeaderName);
+ HttpRequestMessage.SetHeader(HeaderName, HeaderValue);
+ end;
+ end;
+ #endregion
+}
\ No newline at end of file