diff --git a/MADE.NET.sln b/MADE.NET.sln
index cedbba29..b6a44409 100644
--- a/MADE.NET.sln
+++ b/MADE.NET.sln
@@ -1,5 +1,4 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
@@ -59,6 +58,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MADE.Testing.Tests", "tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MADE.Web.Mvc.Tests", "tests\MADE.Web.Mvc.Tests\MADE.Web.Mvc.Tests.csproj", "{F994F941-474A-4FDD-A9CB-280EB0D78407}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MADE.Foundation.Tests", "tests\MADE.Foundation.Tests\MADE.Foundation.Tests.csproj", "{EE7B8716-5B54-45EC-91AD-4764F4AC863B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MADE.Runtime.Tests", "tests\MADE.Runtime.Tests\MADE.Runtime.Tests.csproj", "{D360BA13-4DAD-4C62-AE4A-737553F34E01}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -169,6 +172,14 @@ Global
{F994F941-474A-4FDD-A9CB-280EB0D78407}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F994F941-474A-4FDD-A9CB-280EB0D78407}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F994F941-474A-4FDD-A9CB-280EB0D78407}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE7B8716-5B54-45EC-91AD-4764F4AC863B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE7B8716-5B54-45EC-91AD-4764F4AC863B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE7B8716-5B54-45EC-91AD-4764F4AC863B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE7B8716-5B54-45EC-91AD-4764F4AC863B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D360BA13-4DAD-4C62-AE4A-737553F34E01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D360BA13-4DAD-4C62-AE4A-737553F34E01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D360BA13-4DAD-4C62-AE4A-737553F34E01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D360BA13-4DAD-4C62-AE4A-737553F34E01}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -200,6 +211,8 @@ Global
{865FBD49-C64B-4B36-AEFC-FD960DDC4CF8} = {69149D0F-BB09-411B-88F0-A1E845058D70}
{40B5F4EB-45DD-410A-B0FB-2384C863FC33} = {69149D0F-BB09-411B-88F0-A1E845058D70}
{F994F941-474A-4FDD-A9CB-280EB0D78407} = {69149D0F-BB09-411B-88F0-A1E845058D70}
+ {EE7B8716-5B54-45EC-91AD-4764F4AC863B} = {69149D0F-BB09-411B-88F0-A1E845058D70}
+ {D360BA13-4DAD-4C62-AE4A-737553F34E01} = {69149D0F-BB09-411B-88F0-A1E845058D70}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3921AD86-E6C0-4436-8880-2D9EDFAD6151}
diff --git a/src/MADE.Diagnostics/AppDiagnostics.cs b/src/MADE.Diagnostics/AppDiagnostics.cs
index d2d81493..23109968 100644
--- a/src/MADE.Diagnostics/AppDiagnostics.cs
+++ b/src/MADE.Diagnostics/AppDiagnostics.cs
@@ -78,38 +78,52 @@ public void StopRecordingDiagnostics()
private async void OnTaskUnobservedException(object? sender, UnobservedTaskExceptionEventArgs args)
{
- args.SetObserved();
-
- var correlationId = Guid.NewGuid();
+ try
+ {
+ args.SetObserved();
- await this.EventLogger.WriteCritical(
- args.Exception != null
- ? $"An unobserved task exception was thrown. Correlation ID: {correlationId}. Error: {args.Exception}."
- : $"An unobserved task exception was thrown. Correlation ID: {correlationId}. Error: No exception information was available.").ConfigureAwait(false);
+ var correlationId = Guid.NewGuid();
- if (args.Exception != null)
+ await this.EventLogger.WriteCritical(
+ args.Exception != null
+ ? $"An unobserved task exception was thrown. Correlation ID: {correlationId}. Error: {args.Exception}."
+ : $"An unobserved task exception was thrown. Correlation ID: {correlationId}. Error: No exception information was available.").ConfigureAwait(false);
+
+ if (args.Exception != null)
+ {
+ this.ExceptionObserved?.Invoke(this, new ExceptionObservedEventArgs(correlationId, args.Exception));
+ }
+ }
+ catch (Exception)
{
- this.ExceptionObserved?.Invoke(this, new ExceptionObservedEventArgs(correlationId, args.Exception));
+ // Swallow exceptions in last-resort exception handlers to prevent crashing the process.
}
}
private async void OnAppUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
- if (args.IsTerminating)
+ try
{
- await this.EventLogger.WriteCritical(
- "The application is terminating due to an unhandled exception being thrown.").ConfigureAwait(false);
- }
+ if (args.IsTerminating)
+ {
+ await this.EventLogger.WriteCritical(
+ "The application is terminating due to an unhandled exception being thrown.").ConfigureAwait(false);
+ }
- if (args.ExceptionObject is not Exception ex)
- {
- return;
- }
+ if (args.ExceptionObject is not Exception ex)
+ {
+ return;
+ }
- var correlationId = Guid.NewGuid();
+ var correlationId = Guid.NewGuid();
- await this.EventLogger.WriteCritical($"An unhandled exception was thrown. Correlation ID: {correlationId}. Error: {ex}").ConfigureAwait(false);
+ await this.EventLogger.WriteCritical($"An unhandled exception was thrown. Correlation ID: {correlationId}. Error: {ex}").ConfigureAwait(false);
- this.ExceptionObserved?.Invoke(this, new ExceptionObservedEventArgs(correlationId, ex));
+ this.ExceptionObserved?.Invoke(this, new ExceptionObservedEventArgs(correlationId, ex));
+ }
+ catch (Exception)
+ {
+ // Swallow exceptions in last-resort exception handlers to prevent crashing the process.
+ }
}
}
diff --git a/src/MADE.Networking/Extensions/UriExtensions.cs b/src/MADE.Networking/Extensions/UriExtensions.cs
index 89be6fd7..fd172e2e 100644
--- a/src/MADE.Networking/Extensions/UriExtensions.cs
+++ b/src/MADE.Networking/Extensions/UriExtensions.cs
@@ -17,7 +17,7 @@ public static class UriExtensions
/// The to extract a query value from.
/// The key of the parameter in the query to extract the value for.
/// The value for the query parameter.
- public static string GetQueryValue(this Uri uri, string queryParam)
+ public static string? GetQueryValue(this Uri uri, string queryParam)
{
NameValueCollection queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
return queryDictionary.Get(queryParam);
diff --git a/src/MADE.Networking/Http/INetworkRequestManager.cs b/src/MADE.Networking/Http/INetworkRequestManager.cs
index cb57f5c4..ef43b2b8 100644
--- a/src/MADE.Networking/Http/INetworkRequestManager.cs
+++ b/src/MADE.Networking/Http/INetworkRequestManager.cs
@@ -2,6 +2,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
+using System.Threading.Tasks;
using MADE.Networking.Http.Requests;
namespace MADE.Networking.Http;
@@ -100,5 +101,6 @@ void AddOrUpdate(
///
/// Processes the current queue of network requests.
///
- void ProcessCurrentQueue();
+ /// An asynchronous operation.
+ Task ProcessCurrentQueueAsync();
}
diff --git a/src/MADE.Networking/Http/NetworkRequestFactory.cs b/src/MADE.Networking/Http/NetworkRequestFactory.cs
index 0aca7ef2..2031e9bb 100644
--- a/src/MADE.Networking/Http/NetworkRequestFactory.cs
+++ b/src/MADE.Networking/Http/NetworkRequestFactory.cs
@@ -33,43 +33,43 @@ private NetworkRequestFactory(IHttpClientFactory httpClientFactory, string? clie
///
public JsonGetNetworkRequest Get(string url, Dictionary? headers = null)
{
- return new JsonGetNetworkRequest(this.CreateClient(), url, headers!);
+ return new JsonGetNetworkRequest(this.CreateClient(), url, headers);
}
///
public JsonPostNetworkRequest Post(string url, string? jsonData = null, Dictionary? headers = null)
{
- return new JsonPostNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
+ return new JsonPostNetworkRequest(this.CreateClient(), url, jsonData, headers);
}
///
public JsonPutNetworkRequest Put(string url, string? jsonData = null, Dictionary? headers = null)
{
- return new JsonPutNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
+ return new JsonPutNetworkRequest(this.CreateClient(), url, jsonData, headers);
}
///
public JsonPatchNetworkRequest Patch(string url, string? jsonData = null, Dictionary? headers = null)
{
- return new JsonPatchNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
+ return new JsonPatchNetworkRequest(this.CreateClient(), url, jsonData, headers);
}
///
public JsonDeleteNetworkRequest Delete(string url, Dictionary? headers = null)
{
- return new JsonDeleteNetworkRequest(this.CreateClient(), url, headers!);
+ return new JsonDeleteNetworkRequest(this.CreateClient(), url, headers);
}
///
public StreamGetNetworkRequest GetStream(string url, Dictionary? headers = null)
{
- return new StreamGetNetworkRequest(this.CreateClient(), url, headers!);
+ return new StreamGetNetworkRequest(this.CreateClient(), url, headers);
}
///
public MultipartFormDataPostNetworkRequest PostMultipart(string url, Dictionary? headers = null)
{
- return new MultipartFormDataPostNetworkRequest(this.CreateClient(), url, headers!);
+ return new MultipartFormDataPostNetworkRequest(this.CreateClient(), url, headers);
}
///
diff --git a/src/MADE.Networking/Http/NetworkRequestManager.cs b/src/MADE.Networking/Http/NetworkRequestManager.cs
index c869d245..10f5ebd0 100644
--- a/src/MADE.Networking/Http/NetworkRequestManager.cs
+++ b/src/MADE.Networking/Http/NetworkRequestManager.cs
@@ -84,7 +84,8 @@ public void Dispose()
///
/// Processes the current queue of network requests.
///
- public void ProcessCurrentQueue()
+ /// An asynchronous operation.
+ public async Task ProcessCurrentQueueAsync()
{
if (this.CurrentQueue.Count == 0 || this.isProcessingRequests)
{
@@ -102,13 +103,13 @@ public void ProcessCurrentQueue()
{
if (this.CurrentQueue.TryRemove(
this.CurrentQueue.FirstOrDefault().Key,
- out NetworkRequestCallback request))
+ out NetworkRequestCallback? request))
{
requestTasks.Add(ExecuteRequestsAsync(this.CurrentQueue, request, cts.Token));
}
}
- Task.WhenAll(requestTasks).GetAwaiter().GetResult();
+ await Task.WhenAll(requestTasks).ConfigureAwait(false);
}
finally
{
@@ -220,23 +221,37 @@ private static async Task ExecuteRequestsAsync(
}
NetworkRequest request = requestCallback.Request;
- WeakReferenceCallback successCallback = requestCallback.SuccessCallback;
- WeakReferenceCallback errorCallback = requestCallback.ErrorCallback;
+ WeakReferenceCallback? successCallback = requestCallback.SuccessCallback;
+ WeakReferenceCallback? errorCallback = requestCallback.ErrorCallback;
try
{
+ if (successCallback is null)
+ {
+ return;
+ }
+
object response = await request.ExecuteAsync(successCallback.Type, cancellationToken).ConfigureAwait(false);
successCallback.Invoke(response);
}
catch (Exception ex)
{
- successCallback.Invoke(Activator.CreateInstance(successCallback.Type));
+ successCallback.Invoke(Activator.CreateInstance(successCallback.Type)!);
+
errorCallback?.Invoke(ex);
}
}
- private void OnProcessTimerTick(object sender, object e)
+ private async void OnProcessTimerTick(object sender, object e)
{
- this.ProcessCurrentQueue();
+ try
+ {
+ await this.ProcessCurrentQueueAsync().ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ // Swallow exceptions in the async void timer callback to prevent them from
+ // escaping as unhandled exceptions and crashing the process.
+ }
}
}
diff --git a/src/MADE.Networking/Http/Requests/Json/JsonDeleteNetworkRequest.cs b/src/MADE.Networking/Http/Requests/Json/JsonDeleteNetworkRequest.cs
index caff9ceb..846012b9 100644
--- a/src/MADE.Networking/Http/Requests/Json/JsonDeleteNetworkRequest.cs
+++ b/src/MADE.Networking/Http/Requests/Json/JsonDeleteNetworkRequest.cs
@@ -15,6 +15,8 @@ namespace MADE.Networking.Http.Requests.Json;
///
public sealed class JsonDeleteNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -43,7 +45,7 @@ public JsonDeleteNetworkRequest(HttpClient client, string url)
///
/// The additional headers.
///
- public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary headers)
+ public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -64,7 +66,8 @@ public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions)
+ ?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
///
@@ -84,7 +87,8 @@ public override async Task
public sealed class JsonGetNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -43,7 +45,7 @@ public JsonGetNetworkRequest(HttpClient client, string url)
///
/// The additional headers.
///
- public JsonGetNetworkRequest(HttpClient client, string url, Dictionary headers)
+ public JsonGetNetworkRequest(HttpClient client, string url, Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -64,7 +66,8 @@ public JsonGetNetworkRequest(HttpClient client, string url, Dictionary ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions)
+ ?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
///
@@ -84,7 +87,8 @@ public override async Task
public sealed class JsonPatchNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -44,7 +46,7 @@ public JsonPatchNetworkRequest(HttpClient client, string url)
///
/// The JSON data to post.
///
- public JsonPatchNetworkRequest(HttpClient client, string url, string jsonData)
+ public JsonPatchNetworkRequest(HttpClient client, string url, string? jsonData)
: this(client, url, jsonData, null)
{
}
@@ -67,8 +69,8 @@ public JsonPatchNetworkRequest(HttpClient client, string url, string jsonData)
public JsonPatchNetworkRequest(
HttpClient client,
string url,
- string jsonData,
- Dictionary headers)
+ string? jsonData,
+ Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -78,7 +80,7 @@ public JsonPatchNetworkRequest(
///
/// Gets or sets the data.
///
- public string Data { get; set; }
+ public string? Data { get; set; }
///
/// Executes the network request.
@@ -95,7 +97,8 @@ public JsonPatchNetworkRequest(
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions)
+ ?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
///
@@ -115,7 +118,8 @@ public override async Task
public sealed class JsonPostNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -44,7 +46,7 @@ public JsonPostNetworkRequest(HttpClient client, string url)
///
/// The JSON data to post.
///
- public JsonPostNetworkRequest(HttpClient client, string url, string jsonData)
+ public JsonPostNetworkRequest(HttpClient client, string url, string? jsonData)
: this(client, url, jsonData, null)
{
}
@@ -67,8 +69,8 @@ public JsonPostNetworkRequest(HttpClient client, string url, string jsonData)
public JsonPostNetworkRequest(
HttpClient client,
string url,
- string jsonData,
- Dictionary headers)
+ string? jsonData,
+ Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -78,7 +80,7 @@ public JsonPostNetworkRequest(
///
/// Gets or sets the data.
///
- public string Data { get; set; }
+ public string? Data { get; set; }
///
/// Executes the network request.
@@ -95,7 +97,8 @@ public JsonPostNetworkRequest(
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions)
+ ?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
///
@@ -115,7 +118,8 @@ public override async Task
public sealed class JsonPutNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -44,7 +46,7 @@ public JsonPutNetworkRequest(HttpClient client, string url)
///
/// The JSON data to put.
///
- public JsonPutNetworkRequest(HttpClient client, string url, string jsonData)
+ public JsonPutNetworkRequest(HttpClient client, string url, string? jsonData)
: this(client, url, jsonData, null)
{
}
@@ -64,7 +66,7 @@ public JsonPutNetworkRequest(HttpClient client, string url, string jsonData)
///
/// The additional headers.
///
- public JsonPutNetworkRequest(HttpClient client, string url, string jsonData, Dictionary headers)
+ public JsonPutNetworkRequest(HttpClient client, string url, string? jsonData, Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -74,7 +76,7 @@ public JsonPutNetworkRequest(HttpClient client, string url, string jsonData, Dic
///
/// Gets or sets the data.
///
- public string Data { get; set; }
+ public string? Data { get; set; }
///
/// Executes the network request.
@@ -91,7 +93,8 @@ public JsonPutNetworkRequest(HttpClient client, string url, string jsonData, Dic
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions)
+ ?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
///
@@ -111,7 +114,8 @@ public override async Task
public sealed class MultipartFormDataPostNetworkRequest : NetworkRequest
{
+ private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };
+
private readonly HttpClient client;
///
@@ -32,7 +34,7 @@ public MultipartFormDataPostNetworkRequest(HttpClient client, string url)
public MultipartFormDataPostNetworkRequest(
HttpClient client,
string url,
- Dictionary headers)
+ Dictionary? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
@@ -100,7 +102,7 @@ public MultipartFormDataPostNetworkRequest AddByteArrayContent(
public override async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
string json = await this.PostAndGetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return JsonSerializer.Deserialize(json, DefaultJsonOptions);
}
///
@@ -109,7 +111,7 @@ public override async Task