diff --git a/resources/sdk/pureclouddotnet/extensions/Client/AbstractHttpClient.cs b/resources/sdk/pureclouddotnet/extensions/Client/AbstractHttpClient.cs index 4ad6286c..059520e0 100644 --- a/resources/sdk/pureclouddotnet/extensions/Client/AbstractHttpClient.cs +++ b/resources/sdk/pureclouddotnet/extensions/Client/AbstractHttpClient.cs @@ -10,6 +10,20 @@ namespace {{=it.packageName }}.Client { + /// + /// Delegate for pre-request hooks + /// + /// The HTTP request to be modified + /// Modified HTTP request + public delegate IHttpRequest PreRequestHook(IHttpRequest request); + + /// + /// Delegate for post-request hooks + /// + /// The HTTP response to be modified + /// Modified HTTP response + public delegate IHttpResponse PostRequestHook(IHttpResponse response); + /// /// Abstract base class for HTTP client implementations that provides common functionality for making HTTP requests /// @@ -27,6 +41,16 @@ public abstract class AbstractHttpClient /// The User-Agent string protected string UserAgent { get; set; } = "null"; + /// + /// Pre-request hook that will be called before each request + /// + protected PreRequestHook PreHook { get; set; } + + /// + /// Post-request hook that will be called after each request + /// + protected PostRequestHook PostHook { get; set; } + /// /// Sets the request timeout /// @@ -51,6 +75,68 @@ public void SetUserAgent(string userAgent) UserAgent = userAgent; } + /// + /// Sets the pre-request hook + /// + /// The hook function to call before each request + public void SetPreRequestHook(PreRequestHook hook) + { + PreHook = hook; + } + + /// + /// Sets the post-request hook + /// + /// The hook function to call after each request + public void SetPostRequestHook(PostRequestHook hook) + { + PostHook = hook; + } + + /// + /// Applies the pre-request hook if set + /// + /// The original request + /// The modified request + protected IHttpRequest ApplyPreRequestHook(IHttpRequest request) + { + if (PreHook != null) + { + try + { + return PreHook(request); + } + catch (Exception ex) + { + Console.WriteLine($"Error in pre-request hook: {ex.Message}"); + throw ex; + } + } + return request; + } + + /// + /// Applies the post-request hook if set + /// + /// The original response + /// The modified response + protected IHttpResponse ApplyPostRequestHook(IHttpResponse response) + { + if (PostHook != null) + { + try + { + return PostHook(response); + } + catch (Exception ex) + { + Console.WriteLine($"Error in post-request hook: {ex.Message}"); + throw ex; + } + } + return response; + } + /// /// Asynchronously executes an HTTP request /// diff --git a/resources/sdk/pureclouddotnet/extensions/Client/DefaultHttpClient.cs b/resources/sdk/pureclouddotnet/extensions/Client/DefaultHttpClient.cs index a14a8cec..d74d1a66 100644 --- a/resources/sdk/pureclouddotnet/extensions/Client/DefaultHttpClient.cs +++ b/resources/sdk/pureclouddotnet/extensions/Client/DefaultHttpClient.cs @@ -114,6 +114,9 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat /// public override async Task ExecuteAsync(IHttpRequest httpRequest, CancellationToken cancellationToken = default(CancellationToken)) { + httpRequest = ApplyPreRequestHook(httpRequest); + IHttpResponse resp; + if (usingMTLS) //using HttpWebRequest { var request = PrepareWebRequest((HttpRequestOptions)httpRequest); @@ -140,7 +143,7 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat { using (var response = (HttpWebResponse)await request.GetResponseAsync()) { - return await ConvertToHttpResponse(response); + resp = await ConvertToHttpResponse(response); } } catch (WebException ex) @@ -158,8 +161,10 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat var restResp = await restClient.ExecuteAsync(request, cancellationToken); - return ConvertToHttpResponse(restResp); + resp = ConvertToHttpResponse(restResp); } + + return ApplyPostRequestHook(resp); } /// @@ -167,18 +172,24 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat /// public override IHttpResponse Execute(IHttpRequest httpRequest) { + IHttpResponse resp; + if (usingMTLS) //using HttpWebRequest { - return ExecuteAsync(httpRequest).GetAwaiter().GetResult(); + resp = ExecuteAsync(httpRequest).GetAwaiter().GetResult(); } else //using RestSharp (HttpClient) { + httpRequest = ApplyPreRequestHook(httpRequest); var request = PrepareRestRequest((HttpRequestOptions)httpRequest); var restResp = restClient.Execute(request); - return ConvertToHttpResponse(restResp); + resp = ConvertToHttpResponse(restResp); + resp = ApplyPostRequestHook(resp); } + + return resp; } private IHttpResponse ConvertToHttpResponse(RestResponse response) diff --git a/resources/sdk/pureclouddotnet/templates/README.mustache b/resources/sdk/pureclouddotnet/templates/README.mustache index 209f9310..e82297a9 100644 --- a/resources/sdk/pureclouddotnet/templates/README.mustache +++ b/resources/sdk/pureclouddotnet/templates/README.mustache @@ -633,6 +633,39 @@ var certPass = "x509Password"; Configuration.Default.ApiClient.SetMTLSCertificates(certPath, certPass); ``` +### Using Pre Commit and Post Commit Hooks + +For any custom requirements like pre validations or post cleanups (for ex: OCSP and CRL validation), we can inject the prehook and posthook functions. +The SDK's default client will make sure the injected hook functions are executed. + +The Pre/Post Hook functions must have the following method signature shown below + +```csharp +IHttpRequest preHook(IHttpRequest request) +IHttpResponse postHook(IHttpRepsonse response) +``` + +Here is an example of using a Pre Hook function: + +```csharp +private IHttpRequest preHook(IHttpRequest request) +{ + try { + Console.WriteLine("Running PreHook: Certificate Validation Checks"); + + // custom validation here + + Console.WriteLine("Certificate Validation Complete"); + } catch (Exception ex) { + Console.WriteLine($"Error in prehook validation: {ex.Message}"); + throw ex; // Reject request if validation fails + } +} + +Configuration.Default.ApiClient.HttpClient.SetPreRequestHook(preHook); + +``` + ### Building from Source If you're working inside Visual Studio, adding the files to your project allows you to edit and build inside an IDE. diff --git a/resources/sdk/pureclouddotnet/templates/test-SdkTests.mustache b/resources/sdk/pureclouddotnet/templates/test-SdkTests.mustache index d078885b..b326073d 100644 --- a/resources/sdk/pureclouddotnet/templates/test-SdkTests.mustache +++ b/resources/sdk/pureclouddotnet/templates/test-SdkTests.mustache @@ -39,6 +39,31 @@ namespace {{packageName}}.Tests string busyPresenceId = "31fe3bac-dea6-44b7-bed7-47f91660a1a0"; string availablePresenceId = "6a3af858-942f-489d-9700-5f9bcdcdae9b"; + bool preHookCalled = false; + bool postHookCalled = false; + private IHttpRequest preHook(IHttpRequest request) + { + preHookCalled = true; + Console.WriteLine("Running Pre-Request Hook"); + var options = (HttpRequestOptions)request; + + options.AddHeaderParam("Test-Header", "Pre-Hook-Test"); + + Console.WriteLine($"Pre-Hook: Making {options.Method} request to {options.Url}"); + + return request; + } + + private IHttpResponse postHook(IHttpResponse response) + { + postHookCalled = true; + Console.WriteLine("Running Post-Request Hook"); + + Console.WriteLine($"Post-Hook: Received status code {response.StatusCode}"); + + return response; + } + /// /// SdkTests default constructor /// @@ -207,18 +232,27 @@ namespace {{packageName}}.Tests [Test, Retry(2), Order(6)] public void GetUser() { - Thread.Sleep(6000); - var user = usersApi.GetUserWithHttpInfo(userId, new List() { "profileSkills" }, null, null); - Assert.AreEqual(user.Data.Id, userId); - Assert.AreEqual(user.Data.Name, userName); - Assert.AreEqual(user.Data.Email, userEmail); - Assert.AreEqual(user.Data.Department, userDepartment); - Console.WriteLine($"CorrelationId for GetUserWithHttpInfo {user.CorrelationId}"); - Console.WriteLine($"Version for GetUserWithHttpInfo {user.Data.Version}"); - // Commented out until the issue with APIs to send the latest Version of the User is fixed. - // Assert.IsNotNull(user.Data.ProfileSkills); - // Assert.AreEqual(user.Data.ProfileSkills.Count, 1); - // Assert.AreEqual(user.Data.ProfileSkills[0], userProfileSkill); + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPreRequestHook(preHook); + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPostRequestHook(postHook); + + Thread.Sleep(6000); + var user = usersApi.GetUserWithHttpInfo(userId, new List() { "profileSkills" }, null, null); + + Assert.IsTrue(preHookCalled, "Pre-Hook was not called"); + Assert.IsTrue(postHookCalled, "Post-Hook was not called"); + Assert.AreEqual(user.Data.Id, userId); + Assert.AreEqual(user.Data.Name, userName); + Assert.AreEqual(user.Data.Email, userEmail); + Assert.AreEqual(user.Data.Department, userDepartment); + Console.WriteLine($"CorrelationId for GetUserWithHttpInfo {user.CorrelationId}"); + Console.WriteLine($"Version for GetUserWithHttpInfo {user.Data.Version}"); + // Commented out until the issue with APIs to send the latest Version of the User is fixed. + // Assert.IsNotNull(user.Data.ProfileSkills); + // Assert.AreEqual(user.Data.ProfileSkills.Count, 1); + // Assert.AreEqual(user.Data.ProfileSkills[0], userProfileSkill); + + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPreRequestHook(null); + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPostRequestHook(null); } /// @@ -309,6 +343,31 @@ namespace {{packageName}}.Tests string userName = ".NET SDK MTLS Tester"; string userDepartment = "Ministry of MTLS Testing"; + bool preHookCalled = false; + bool postHookCalled = false; + private IHttpRequest preHook(IHttpRequest request) + { + preHookCalled = true; + Console.WriteLine("Running Pre-Request Hook"); + var options = (HttpRequestOptions)request; + + options.AddHeaderParam("Test-Header", "Pre-Hook-Test"); + + Console.WriteLine($"Pre-Hook: Making {options.Method} request to {options.Url}"); + + return request; + } + + private IHttpResponse postHook(IHttpResponse response) + { + postHookCalled = true; + Console.WriteLine("Running Post-Request Hook"); + + Console.WriteLine($"Post-Hook: Received status code {response.StatusCode}"); + + return response; + } + /// /// MTLSTests default constructor /// @@ -414,6 +473,9 @@ namespace {{packageName}}.Tests [Test, Order(4)] public void UpdateUser() { + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPreRequestHook(preHook); + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPostRequestHook(postHook); + var updateUser = new UpdateUser() { Department = userDepartment, @@ -422,10 +484,15 @@ namespace {{packageName}}.Tests var user = usersApi.PatchUser(userId, updateUser); + Assert.IsTrue(preHookCalled, "Pre-Hook was not called"); + Assert.IsTrue(postHookCalled, "Post-Hook was not called"); Assert.AreEqual(user.Id, userId); Assert.AreEqual(user.Name, userName); Assert.AreEqual(user.Email, userEmail); Assert.AreEqual(user.Department, userDepartment); + + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPreRequestHook(null); + {{packageName}}.Client.Configuration.Default.ApiClient.HttpClient.SetPostRequestHook(null); } /// @@ -455,6 +522,9 @@ namespace {{packageName}}.Tests Console.WriteLine($"Deleted user with ID {userId}"); } + /// + /// CleanUp + /// [Test, Order(7)] public void CleanUp() {