Skip to content

[Devtooling-1011] Pre/Post Hooks for dotnet #1066

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@

namespace {{=it.packageName }}.Client
{
/// <summary>
/// Delegate for pre-request hooks
/// </summary>
/// <param name="request">The HTTP request to be modified</param>
/// <returns>Modified HTTP request</returns>
public delegate IHttpRequest PreRequestHook(IHttpRequest request);

/// <summary>
/// Delegate for post-request hooks
/// </summary>
/// <param name="response">The HTTP response to be modified</param>
/// <returns>Modified HTTP response</returns>
public delegate IHttpResponse PostRequestHook(IHttpResponse response);

/// <summary>
/// Abstract base class for HTTP client implementations that provides common functionality for making HTTP requests
/// </summary>
Expand All @@ -27,6 +41,16 @@ public abstract class AbstractHttpClient
/// <value>The User-Agent string</value>
protected string UserAgent { get; set; } = "null";

/// <summary>
/// Pre-request hook that will be called before each request
/// </summary>
protected PreRequestHook PreHook { get; set; }

/// <summary>
/// Post-request hook that will be called after each request
/// </summary>
protected PostRequestHook PostHook { get; set; }

///<Summary>
/// Sets the request timeout
///</Summary>
Expand All @@ -51,6 +75,68 @@ public void SetUserAgent(string userAgent)
UserAgent = userAgent;
}

/// <summary>
/// Sets the pre-request hook
/// </summary>
/// <param name="hook">The hook function to call before each request</param>
public void SetPreRequestHook(PreRequestHook hook)
{
PreHook = hook;
}

/// <summary>
/// Sets the post-request hook
/// </summary>
/// <param name="hook">The hook function to call after each request</param>
public void SetPostRequestHook(PostRequestHook hook)
{
PostHook = hook;
}

/// <summary>
/// Applies the pre-request hook if set
/// </summary>
/// <param name="request">The original request</param>
/// <returns>The modified request</returns>
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;
}

/// <summary>
/// Applies the post-request hook if set
/// </summary>
/// <param name="response">The original response</param>
/// <returns>The modified response</returns>
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;
}

/// <summary>
/// Asynchronously executes an HTTP request
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat
/// </summary>
public override async Task<IHttpResponse> ExecuteAsync(IHttpRequest httpRequest, CancellationToken cancellationToken = default(CancellationToken))
{
httpRequest = ApplyPreRequestHook(httpRequest);
IHttpResponse resp;

if (usingMTLS) //using HttpWebRequest
{
var request = PrepareWebRequest((HttpRequestOptions)httpRequest);
Expand All @@ -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)
Expand All @@ -158,27 +161,35 @@ private bool ValidateServerCertificate(object sender, X509Certificate certificat

var restResp = await restClient.ExecuteAsync(request, cancellationToken);

return ConvertToHttpResponse(restResp);
resp = ConvertToHttpResponse(restResp);
}

return ApplyPostRequestHook(resp);
}

/// <summary>
/// Executes an HTTP request.
/// </summary>
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)
Expand Down
33 changes: 33 additions & 0 deletions resources/sdk/pureclouddotnet/templates/README.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
94 changes: 82 additions & 12 deletions resources/sdk/pureclouddotnet/templates/test-SdkTests.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

///<Summary>
/// SdkTests default constructor
///</Summary>
Expand Down Expand Up @@ -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<string>() { "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<string>() { "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);
}

///<Summary>
Expand Down Expand Up @@ -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;
}

///<Summary>
/// MTLSTests default constructor
///</Summary>
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}

///<Summary>
Expand Down Expand Up @@ -455,6 +522,9 @@ namespace {{packageName}}.Tests
Console.WriteLine($"Deleted user with ID {userId}");
}

///<Summary>
/// CleanUp
///</Summary>
[Test, Order(7)]
public void CleanUp()
{
Expand Down