Skip to content
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
17 changes: 15 additions & 2 deletions MADE.NET.sln
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
54 changes: 34 additions & 20 deletions src/MADE.Diagnostics/AppDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
}
}
2 changes: 1 addition & 1 deletion src/MADE.Networking/Extensions/UriExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class UriExtensions
/// <param name="uri">The <see cref="Uri"/> to extract a query value from.</param>
/// <param name="queryParam">The key of the parameter in the query to extract the value for.</param>
/// <returns>The value for the query parameter.</returns>
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);
Expand Down
4 changes: 3 additions & 1 deletion src/MADE.Networking/Http/INetworkRequestManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -100,5 +101,6 @@ void AddOrUpdate<TRequest, TResponse, TErrorResponse>(
/// <summary>
/// Processes the current queue of network requests.
/// </summary>
void ProcessCurrentQueue();
/// <returns>An asynchronous operation.</returns>
Task ProcessCurrentQueueAsync();
}
14 changes: 7 additions & 7 deletions src/MADE.Networking/Http/NetworkRequestFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,43 @@ private NetworkRequestFactory(IHttpClientFactory httpClientFactory, string? clie
/// <inheritdoc/>
public JsonGetNetworkRequest Get(string url, Dictionary<string, string>? headers = null)
{
return new JsonGetNetworkRequest(this.CreateClient(), url, headers!);
return new JsonGetNetworkRequest(this.CreateClient(), url, headers);
}

/// <inheritdoc/>
public JsonPostNetworkRequest Post(string url, string? jsonData = null, Dictionary<string, string>? headers = null)
{
return new JsonPostNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
return new JsonPostNetworkRequest(this.CreateClient(), url, jsonData, headers);
}

/// <inheritdoc/>
public JsonPutNetworkRequest Put(string url, string? jsonData = null, Dictionary<string, string>? headers = null)
{
return new JsonPutNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
return new JsonPutNetworkRequest(this.CreateClient(), url, jsonData, headers);
}

/// <inheritdoc/>
public JsonPatchNetworkRequest Patch(string url, string? jsonData = null, Dictionary<string, string>? headers = null)
{
return new JsonPatchNetworkRequest(this.CreateClient(), url, jsonData!, headers!);
return new JsonPatchNetworkRequest(this.CreateClient(), url, jsonData, headers);
}

/// <inheritdoc/>
public JsonDeleteNetworkRequest Delete(string url, Dictionary<string, string>? headers = null)
{
return new JsonDeleteNetworkRequest(this.CreateClient(), url, headers!);
return new JsonDeleteNetworkRequest(this.CreateClient(), url, headers);
}

/// <inheritdoc/>
public StreamGetNetworkRequest GetStream(string url, Dictionary<string, string>? headers = null)
{
return new StreamGetNetworkRequest(this.CreateClient(), url, headers!);
return new StreamGetNetworkRequest(this.CreateClient(), url, headers);
}

/// <inheritdoc/>
public MultipartFormDataPostNetworkRequest PostMultipart(string url, Dictionary<string, string>? headers = null)
{
return new MultipartFormDataPostNetworkRequest(this.CreateClient(), url, headers!);
return new MultipartFormDataPostNetworkRequest(this.CreateClient(), url, headers);
}

/// <inheritdoc/>
Expand Down
31 changes: 23 additions & 8 deletions src/MADE.Networking/Http/NetworkRequestManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{
this.CurrentQueue = new ConcurrentDictionary<string, NetworkRequestCallback>();
this.processTimer = new Timer();
this.processTimer.Tick += this.OnProcessTimerTick;

Check warning on line 33 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Nullability of reference types in type of parameter 'sender' of 'void NetworkRequestManager.OnProcessTimerTick(object sender, object e)' doesn't match the target delegate 'EventHandler<object>' (possibly because of nullability attributes).

Check warning on line 33 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Nullability of reference types in type of parameter 'sender' of 'void NetworkRequestManager.OnProcessTimerTick(object sender, object e)' doesn't match the target delegate 'EventHandler<object>' (possibly because of nullability attributes).
}

/// <summary>
Expand Down Expand Up @@ -74,7 +74,7 @@
return;
}

this.processTimer.Tick -= this.OnProcessTimerTick;

Check warning on line 77 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Nullability of reference types in type of parameter 'sender' of 'void NetworkRequestManager.OnProcessTimerTick(object sender, object e)' doesn't match the target delegate 'EventHandler<object>' (possibly because of nullability attributes).
this.processTimer.Stop();
this.processTimer.Dispose();
this.disposed = true;
Expand All @@ -84,7 +84,8 @@
/// <summary>
/// Processes the current queue of network requests.
/// </summary>
public void ProcessCurrentQueue()
/// <returns>An asynchronous operation.</returns>
public async Task ProcessCurrentQueueAsync()
{
if (this.CurrentQueue.Count == 0 || this.isProcessingRequests)
{
Expand All @@ -102,13 +103,13 @@
{
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
{
Expand Down Expand Up @@ -136,7 +137,7 @@
public void AddOrUpdate<TRequest, TResponse>(TRequest request, Action<TResponse> successCallback)
where TRequest : NetworkRequest
{
this.AddOrUpdate<TRequest, TResponse, Exception>(request, successCallback, null);

Check warning on line 140 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Cannot convert null literal to non-nullable reference type.
}

/// <summary>
Expand Down Expand Up @@ -201,7 +202,7 @@
/// <param name="key">The key corresponding to the network request to remove from the queue.</param>
public void RemoveByKey(string key)
{
this.CurrentQueue.TryRemove(key, out NetworkRequestCallback _);

Check warning on line 205 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference assignment.
}

private static async Task ExecuteRequestsAsync(
Expand All @@ -220,23 +221,37 @@
}

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;
Comment thread
jamesmcroft marked this conversation as resolved.
}

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)!);

Check warning on line 239 in src/MADE.Networking/Http/NetworkRequestManager.cs

View workflow job for this annotation

GitHub Actions / build-test

Dereference of a possibly null reference.

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.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace MADE.Networking.Http.Requests.Json;
/// </summary>
public sealed class JsonDeleteNetworkRequest : NetworkRequest
{
private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };

private readonly HttpClient client;

/// <summary>
Expand Down Expand Up @@ -43,7 +45,7 @@ public JsonDeleteNetworkRequest(HttpClient client, string url)
/// <param name="headers">
/// The additional headers.
/// </param>
public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary<string, string> headers)
public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary<string, string>? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
Expand All @@ -64,7 +66,8 @@ public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary<string
public override async Task<TResponse> ExecuteAsync<TResponse>(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize<TResponse>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return JsonSerializer.Deserialize<TResponse>(json, DefaultJsonOptions)
?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
Comment thread
jamesmcroft marked this conversation as resolved.

/// <summary>
Expand All @@ -84,7 +87,8 @@ public override async Task<object> ExecuteAsync(
CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize(json, expectedResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return JsonSerializer.Deserialize(json, expectedResponse, DefaultJsonOptions)
?? throw new InvalidOperationException($"Failed to deserialize response to {expectedResponse.Name}.");
}

private async Task<string> GetJsonResponseAsync(CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -118,6 +122,6 @@ private async Task<string> GetJsonResponseAsync(CancellationToken cancellationTo

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace MADE.Networking.Http.Requests.Json;
/// </summary>
public sealed class JsonGetNetworkRequest : NetworkRequest
{
private static readonly JsonSerializerOptions DefaultJsonOptions = new() { PropertyNameCaseInsensitive = true };

private readonly HttpClient client;

/// <summary>
Expand Down Expand Up @@ -43,7 +45,7 @@ public JsonGetNetworkRequest(HttpClient client, string url)
/// <param name="headers">
/// The additional headers.
/// </param>
public JsonGetNetworkRequest(HttpClient client, string url, Dictionary<string, string> headers)
public JsonGetNetworkRequest(HttpClient client, string url, Dictionary<string, string>? headers)
: base(url, headers)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
Expand All @@ -64,7 +66,8 @@ public JsonGetNetworkRequest(HttpClient client, string url, Dictionary<string, s
public override async Task<TResponse> ExecuteAsync<TResponse>(CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize<TResponse>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return JsonSerializer.Deserialize<TResponse>(json, DefaultJsonOptions)
?? throw new InvalidOperationException($"Failed to deserialize response to {typeof(TResponse).Name}.");
}
Comment thread
jamesmcroft marked this conversation as resolved.

/// <summary>
Expand All @@ -84,7 +87,8 @@ public override async Task<object> ExecuteAsync(
CancellationToken cancellationToken = default)
{
string json = await this.GetJsonResponseAsync(cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize(json, expectedResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return JsonSerializer.Deserialize(json, expectedResponse, DefaultJsonOptions)
?? throw new InvalidOperationException($"Failed to deserialize response to {expectedResponse.Name}.");
}

private async Task<string> GetJsonResponseAsync(CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -118,6 +122,6 @@ private async Task<string> GetJsonResponseAsync(CancellationToken cancellationTo

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
}
}
Loading
Loading