diff --git a/MADE.App.Networking/INetworkRequestManager.cs b/MADE.App.Networking/INetworkRequestManager.cs new file mode 100644 index 00000000..4dcd93bb --- /dev/null +++ b/MADE.App.Networking/INetworkRequestManager.cs @@ -0,0 +1,95 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines an interface for a network request manager. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking +{ + using System; + using System.Collections.Concurrent; + + using MADE.App.Networking.Requests; + + /// + /// Defines an interface for a network request manager. + /// + public interface INetworkRequestManager + { + /// + /// Gets the current queue of network requests. + /// + ConcurrentDictionary CurrentQueue { get; } + + /// + /// Starts the manager processing the queue of network requests at a default time period of 1 minute. + /// + void Start(); + + /// + /// Starts the manager processing the queue of network requests. + /// + /// + /// The time period between each process of the queue. + /// + void Start(TimeSpan processPeriod); + + /// + /// Stops the processing of the network manager queues. + /// + void Stop(); + + /// + /// Adds or updates a network request in the queue. + /// + /// + /// The type of network request. + /// + /// + /// The expected response type. + /// + /// + /// The network request to execute. + /// + /// + /// The action to execute when receiving a successful response. + /// + void AddOrUpdate(TRequest request, Action successCallback) + where TRequest : NetworkRequest; + + /// + /// Adds or updates a network request in the queue. + /// + /// + /// The type of network request. + /// + /// + /// The expected response type. + /// + /// + /// The expected error response type. + /// + /// + /// The network request to execute. + /// + /// + /// The action to execute when receiving a successful response. + /// + /// + /// The action to execute when receiving an error response. + /// + void AddOrUpdate( + TRequest request, + Action successCallback, + Action errorCallback) + where TRequest : NetworkRequest; + + /// + /// Processes the current queue of network requests. + /// + void ProcessCurrentQueue(); + } +} \ No newline at end of file diff --git a/MADE.App.Networking/MADE.App.Networking.csproj b/MADE.App.Networking/MADE.App.Networking.csproj new file mode 100644 index 00000000..19d0aad9 --- /dev/null +++ b/MADE.App.Networking/MADE.App.Networking.csproj @@ -0,0 +1,70 @@ + + + + netstandard2.0;uap10.0.16299 + + + + bin\Debug\netstandard2.0\MADE.App.Networking.xml + + + + bin\Release\netstandard2.0\MADE.App.Networking.xml + + + + + + + + + + + + + + + + Docfx-$(TargetFramework).log + true + true + 1.0.0.0 + MADE Apps + MADE Apps + MADE App Networking Library + Making App Development Easier with a collection of easy to use APIs for working with networking requests for .NET projects across Windows, Android, and iOS. + Copyright (C) MADE Apps. All rights reserved. + https://github.com/MADE-Apps/MADE-App-Components/blob/master/LICENSE + https://github.com/MADE-Apps/MADE-App-Components + https://github.com/MADE-Apps/MADE-App-Components + git + https://pbs.twimg.com/profile_images/927154020422160385/6HSRU36P_400x400.jpg + MADE App Development View Networking HttpClient Windows Android iOS Xamarin + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MADE.App.Networking/NetworkRequestManager.cs b/MADE.App.Networking/NetworkRequestManager.cs new file mode 100644 index 00000000..75d3b1d2 --- /dev/null +++ b/MADE.App.Networking/NetworkRequestManager.cs @@ -0,0 +1,242 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a manager for executing queued network requests. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using MADE.App.Networking.Requests; + + /// + /// Defines a manager for executing queued network requests. + /// + public sealed class NetworkRequestManager : INetworkRequestManager + { +#if WINDOWS_UWP + private readonly Windows.UI.Xaml.DispatcherTimer processTimer; +#else + private Timer processTimer; +#endif + + private bool isProcessingRequests; + + /// + /// Initializes a new instance of the class. + /// + public NetworkRequestManager() + { + this.CurrentQueue = new ConcurrentDictionary(); + +#if WINDOWS_UWP + this.processTimer = new Windows.UI.Xaml.DispatcherTimer { Interval = TimeSpan.FromMinutes(1) }; + this.processTimer.Tick += (sender, o) => this.ProcessCurrentQueue(); +#endif + } + + /// + /// Gets the current queue of network requests. + /// + public ConcurrentDictionary CurrentQueue { get; } + + /// + /// Starts the manager processing the queue of network requests at a default time period of 1 minute. + /// + public void Start() + { + this.Start(TimeSpan.FromMinutes(1)); + } + + /// + /// Starts the manager processing the queue of network requests. + /// + /// + /// The time period between each process of the queue. + /// + public void Start(TimeSpan processPeriod) + { +#if WINDOWS_UWP + this.processTimer.Interval = processPeriod; + + if (!this.processTimer.IsEnabled) + { + this.processTimer.Start(); + } +#else + if (this.processTimer == null) + { + this.processTimer = new Timer( + state => this.ProcessCurrentQueue(), + null, + TimeSpan.FromMinutes(0), + processPeriod); + } + else + { + this.processTimer.Change(TimeSpan.FromMinutes(0), processPeriod); + } +#endif + } + + /// + /// Stops the processing of the network manager queues. + /// + public void Stop() + { +#if WINDOWS_UWP + if (this.processTimer.IsEnabled) + { + this.processTimer.Stop(); + } +#else + this.processTimer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); +#endif + } + + /// + /// Processes the current queue of network requests. + /// + public void ProcessCurrentQueue() + { + if (this.isProcessingRequests) + { + return; + } + + if (this.CurrentQueue.Count > 0) + { + return; + } + + this.isProcessingRequests = true; + + try + { + CancellationTokenSource cts = new CancellationTokenSource(); + List requestTasks = new List(); + List requestCallbacks = new List(); + + while (this.CurrentQueue.Count > 0) + { + if (this.CurrentQueue.TryRemove( + this.CurrentQueue.FirstOrDefault().Key, + out NetworkRequestCallback request)) + { + requestCallbacks.Add(request); + } + } + + foreach (NetworkRequestCallback container in requestCallbacks) + { + requestTasks.Add(ExecuteRequestsAsync(this.CurrentQueue, container, cts)); + } + } + finally + { + this.isProcessingRequests = false; + } + } + + /// + /// Adds or updates a network request in the queue. + /// + /// + /// The type of network request. + /// + /// + /// The expected response type. + /// + /// + /// The network request to execute. + /// + /// + /// The action to execute when receiving a successful response. + /// + public void AddOrUpdate(TRequest request, Action successCallback) + where TRequest : NetworkRequest + { + this.AddOrUpdate(request, successCallback, null); + } + + /// + /// Adds or updates a network request in the queue. + /// + /// + /// The type of network request. + /// + /// + /// The expected response type. + /// + /// + /// The expected error response type. + /// + /// + /// The network request to execute. + /// + /// + /// The action to execute when receiving a successful response. + /// + /// + /// The action to execute when receiving an error response. + /// + public void AddOrUpdate( + TRequest request, + Action successCallback, + Action errorCallback) + where TRequest : NetworkRequest + { + WeakReferenceCallback weakSuccessCallback = new WeakReferenceCallback(successCallback, typeof(TResponse)); + WeakReferenceCallback weakErrorCallback = new WeakReferenceCallback(errorCallback, typeof(TErrorResponse)); + NetworkRequestCallback requestCallback = new NetworkRequestCallback( + request, + weakSuccessCallback, + weakErrorCallback); + + this.CurrentQueue.AddOrUpdate( + request.Identifier.ToString(), + requestCallback, + (s, callback) => requestCallback); + } + + private static async Task ExecuteRequestsAsync( + ConcurrentDictionary queue, + NetworkRequestCallback requestCallback, + CancellationTokenSource cts) + { + if (cts.IsCancellationRequested) + { + queue.AddOrUpdate( + requestCallback.Request.Identifier.ToString(), + requestCallback, + (s, callback) => requestCallback); + + return; + } + + NetworkRequest request = requestCallback.Request; + WeakReferenceCallback successCallback = requestCallback.SuccessCallback; + WeakReferenceCallback errorCallback = requestCallback.ErrorCallback; + + try + { + object response = await request.ExecuteAsync(successCallback.Type); + successCallback.Invoke(response); + } + catch (Exception ex) + { + successCallback.Invoke(Activator.CreateInstance(successCallback.Type)); + errorCallback.Invoke(ex); + } + } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Json/JsonDeleteNetworkRequest.cs b/MADE.App.Networking/Requests/Json/JsonDeleteNetworkRequest.cs new file mode 100644 index 00000000..d48bfa87 --- /dev/null +++ b/MADE.App.Networking/Requests/Json/JsonDeleteNetworkRequest.cs @@ -0,0 +1,155 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a DELETE call with a JSON response. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests.Json +{ +#if WINDOWS_UWP + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Web.Http; + + using Newtonsoft.Json; + +#else + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using Newtonsoft.Json; +#endif + + /// + /// Defines a network request for a DELETE call with a JSON response. + /// + public sealed class JsonDeleteNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public JsonDeleteNetworkRequest(HttpClient client, string url) + : this(client, url, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The additional headers. + /// + public JsonDeleteNetworkRequest(HttpClient client, string url, Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json); + } + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json, expectedResponse); + } + + private async Task GetJsonResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, uri); + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + +#if WINDOWS_UWP + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token); +#else + HttpResponseMessage response = cts == null + ? await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cts.Token); +#endif + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Json/JsonGetNetworkRequest.cs b/MADE.App.Networking/Requests/Json/JsonGetNetworkRequest.cs new file mode 100644 index 00000000..c4d8bcc8 --- /dev/null +++ b/MADE.App.Networking/Requests/Json/JsonGetNetworkRequest.cs @@ -0,0 +1,155 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a GET call with a JSON response. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests.Json +{ +#if WINDOWS_UWP + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Web.Http; + + using Newtonsoft.Json; + +#else + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using Newtonsoft.Json; +#endif + + /// + /// Defines a network request for a GET call with a JSON response. + /// + public sealed class JsonGetNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public JsonGetNetworkRequest(HttpClient client, string url) + : this(client, url, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The additional headers. + /// + public JsonGetNetworkRequest(HttpClient client, string url, Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json); + } + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json, expectedResponse); + } + + private async Task GetJsonResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + +#if WINDOWS_UWP + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token); +#else + HttpResponseMessage response = cts == null + ? await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cts.Token); +#endif + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Json/JsonPatchNetworkRequest.cs b/MADE.App.Networking/Requests/Json/JsonPatchNetworkRequest.cs new file mode 100644 index 00000000..a2e11b51 --- /dev/null +++ b/MADE.App.Networking/Requests/Json/JsonPatchNetworkRequest.cs @@ -0,0 +1,172 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a PATCH call with a JSON response. +// +// -------------------------------------------------------------------------------------------------------------------- + +#if WINDOWS_UWP +namespace MADE.App.Networking.Requests.Json +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Storage.Streams; + using Windows.Web.Http; + + using MADE.App.Networking.Requests; + + using Newtonsoft.Json; + + /// + /// Defines a network request for a PATCH call with a JSON response. + /// + public sealed class JsonPatchNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public JsonPatchNetworkRequest(HttpClient client, string url) + : this(client, url, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to post. + /// + public JsonPatchNetworkRequest(HttpClient client, string url, string jsonData) + : this(client, url, jsonData, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to post. + /// + /// + /// The additional headers. + /// + public JsonPatchNetworkRequest( + HttpClient client, + string url, + string jsonData, + Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + this.Data = jsonData; + } + + /// + /// Gets or sets the data. + /// + public string Data { get; set; } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json); + } + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json, expectedResponse); + } + + private async Task GetJsonResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Patch, uri) + { + Content = new HttpStringContent( + this.Data, + UnicodeEncoding.Utf8, + "application/json") + }; + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead) + .AsTask(cts.Token); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + } +} +#endif \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Json/JsonPostNetworkRequest.cs b/MADE.App.Networking/Requests/Json/JsonPostNetworkRequest.cs new file mode 100644 index 00000000..b5af054d --- /dev/null +++ b/MADE.App.Networking/Requests/Json/JsonPostNetworkRequest.cs @@ -0,0 +1,208 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a POST call with a JSON response. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests.Json +{ +#if WINDOWS_UWP + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Storage.Streams; + using Windows.Web.Http; + + using Newtonsoft.Json; + +#else + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + using Newtonsoft.Json; + +#endif + + /// + /// Defines a network request for a POST call with a JSON response. + /// + public sealed class JsonPostNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public JsonPostNetworkRequest(HttpClient client, string url) + : this(client, url, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to post. + /// + public JsonPostNetworkRequest(HttpClient client, string url, string jsonData) + : this(client, url, jsonData, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to post. + /// + /// + /// The additional headers. + /// + public JsonPostNetworkRequest( + HttpClient client, + string url, + string jsonData, + Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + this.Data = jsonData; + } + + /// + /// Gets or sets the data. + /// + public string Data { get; set; } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json); + } + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json, expectedResponse); + } + + private async Task GetJsonResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + +#if WINDOWS_UWP + + var request = new HttpRequestMessage(HttpMethod.Post, uri) + { + Content = + new HttpStringContent( + this.Data, + UnicodeEncoding.Utf8, + "application/json") + }; +#else + HttpRequestMessage request = + new HttpRequestMessage(HttpMethod.Post, uri) + { + Content = new StringContent( + this.Data, + Encoding.UTF8, + "application/json") + }; +#endif + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + +#if WINDOWS_UWP + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token); +#else + HttpResponseMessage response = cts == null + ? await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cts.Token); +#endif + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Json/JsonPutNetworkRequest.cs b/MADE.App.Networking/Requests/Json/JsonPutNetworkRequest.cs new file mode 100644 index 00000000..36895e72 --- /dev/null +++ b/MADE.App.Networking/Requests/Json/JsonPutNetworkRequest.cs @@ -0,0 +1,192 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a PUT call with a JSON response. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests.Json +{ +#if WINDOWS_UWP + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Storage.Streams; + using Windows.Web.Http; + + using Newtonsoft.Json; + +#else + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + using Newtonsoft.Json; + +#endif + + /// + /// Defines a network request for a PUT call with a JSON response. + /// + public sealed class JsonPutNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public JsonPutNetworkRequest(HttpClient client, string url) + : this(client, url, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to put. + /// + public JsonPutNetworkRequest(HttpClient client, string url, string jsonData) + : this(client, url, jsonData, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The JSON data to put. + /// + /// + /// The additional headers. + /// + public JsonPutNetworkRequest(HttpClient client, string url, string jsonData, Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + this.Data = jsonData; + } + + /// + /// Gets or sets the data. + /// + public string Data { get; set; } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json); + } + + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + string json = await this.GetJsonResponse(cts); + return JsonConvert.DeserializeObject(json, expectedResponse); + } + + private async Task GetJsonResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + +#if WINDOWS_UWP + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri) + { + Content = + new HttpStringContent( + this.Data, + UnicodeEncoding.Utf8, + "application/json") + }; +#else + HttpRequestMessage request = + new HttpRequestMessage(HttpMethod.Put, uri) + { + Content = new StringContent( + this.Data, + Encoding.UTF8, + "application/json") + }; +#endif + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + +#if WINDOWS_UWP + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync( + request, + HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token); +#else + HttpResponseMessage response = cts == null + ? await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cts.Token); +#endif + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/NetworkRequest.cs b/MADE.App.Networking/Requests/NetworkRequest.cs new file mode 100644 index 00000000..a9a0498a --- /dev/null +++ b/MADE.App.Networking/Requests/NetworkRequest.cs @@ -0,0 +1,92 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines the model for a network request. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Defines the model for a network request. + /// + public abstract class NetworkRequest + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The URL for the request. + /// + protected NetworkRequest(string url) + : this(url, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The URL for the request. + /// + /// + /// Additional headers for the request. + /// + protected NetworkRequest(string url, Dictionary headers) + { + this.Identifier = Guid.NewGuid(); + this.Url = url; + this.Headers = headers ?? new Dictionary(); + } + + /// + /// Gets the identifier for the request. + /// + public Guid Identifier { get; } + + /// + /// Gets or sets the URL for the request. + /// + public string Url { get; set; } + + /// + /// Gets the headers for the request. + /// + public Dictionary Headers { get; } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public abstract Task ExecuteAsync(CancellationTokenSource cts = null); + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public abstract Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null); + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/NetworkRequestCallback.cs b/MADE.App.Networking/Requests/NetworkRequestCallback.cs new file mode 100644 index 00000000..22a68518 --- /dev/null +++ b/MADE.App.Networking/Requests/NetworkRequestCallback.cs @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a model for a network request callback. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App.Networking.Requests +{ + using System; + + /// + /// Defines a model for a network request callback. + /// + public sealed class NetworkRequestCallback + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The network request. + /// + public NetworkRequestCallback(NetworkRequest request) + : this(request, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The network request. + /// + /// + /// The success callback. + /// + public NetworkRequestCallback(NetworkRequest request, WeakReferenceCallback successCallback) + : this(request, successCallback, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The network request. + /// + /// + /// The success callback. + /// + /// + /// The error callback. + /// + public NetworkRequestCallback( + NetworkRequest request, + WeakReferenceCallback successCallback, + WeakReferenceCallback errorCallback) + { + this.Request = request ?? throw new ArgumentNullException(nameof(request)); + this.SuccessCallback = successCallback; + this.ErrorCallback = errorCallback; + } + + /// + /// Gets the network process. + /// + public NetworkRequest Request { get; } + + /// + /// Gets the success callback. + /// + public WeakReferenceCallback SuccessCallback { get; } + + /// + /// Gets the error callback. + /// + public WeakReferenceCallback ErrorCallback { get; } + } +} \ No newline at end of file diff --git a/MADE.App.Networking/Requests/Streams/StreamGetNetworkRequest.cs b/MADE.App.Networking/Requests/Streams/StreamGetNetworkRequest.cs new file mode 100644 index 00000000..0606d25d --- /dev/null +++ b/MADE.App.Networking/Requests/Streams/StreamGetNetworkRequest.cs @@ -0,0 +1,130 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a network request for a GET call with a data stream response. +// +// -------------------------------------------------------------------------------------------------------------------- + +#if WINDOWS_UWP +namespace MADE.App.Networking.Requests.Streams +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Windows.Web.Http; + + using MADE.App.Networking.Requests; + + /// + /// Defines a network request for a GET call with a data stream response. + /// + public sealed class StreamGetNetworkRequest : NetworkRequest + { + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + public StreamGetNetworkRequest(HttpClient client, string url) + : this(client, url, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The for executing the request. + /// + /// + /// The URL for the request. + /// + /// + /// The additional headers. + /// + public StreamGetNetworkRequest(HttpClient client, string url, Dictionary headers) + : base(url, headers) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + /// Executes the network request. + /// + /// + /// The cancellation token source. + /// + /// + /// The type of object returned from the request. + /// + /// + /// Returns the response of the request as the specified type. + /// + public override async Task ExecuteAsync(CancellationTokenSource cts = null) + { + return (TResponse)await this.GetStreamResponse(cts); + } + + /// + /// Executes the network request. + /// + /// + /// The type expected by the response of the request. + /// + /// + /// The cancellation token source. + /// + /// + /// Returns the response of the request as an object. + /// + public override async Task ExecuteAsync(Type expectedResponse, CancellationTokenSource cts = null) + { + return await this.GetStreamResponse(cts); + } + + private async Task GetStreamResponse(CancellationTokenSource cts = null) + { + if (this.client == null) + { + throw new InvalidOperationException( + "No HttpClient has been specified for executing the network request."); + } + + if (string.IsNullOrWhiteSpace(this.Url)) + { + throw new InvalidOperationException("No URL has been specified for executing the network request."); + } + + Uri uri = new Uri(this.Url); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); + + if (this.Headers != null) + { + foreach (KeyValuePair header in this.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + HttpResponseMessage response = cts == null + ? await this.client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead) + : await this.client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead) + .AsTask(cts.Token); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsInputStreamAsync(); + } + } +} +#endif \ No newline at end of file diff --git a/MADE.App.sln b/MADE.App.sln index d9e6e398..e82156b0 100644 --- a/MADE.App.sln +++ b/MADE.App.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MADE.App.Design", "MADE.App EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MADE.App.Views.Xamarin.Forms", "MADE.App.Views.Xamarin.Forms\MADE.App.Views.Xamarin.Forms.csproj", "{C60DF964-C19D-467E-9503-F69B0284D13C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MADE.App.Networking", "MADE.App.Networking\MADE.App.Networking.csproj", "{A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -229,6 +231,22 @@ Global {C60DF964-C19D-467E-9503-F69B0284D13C}.Release|x64.Build.0 = Release|Any CPU {C60DF964-C19D-467E-9503-F69B0284D13C}.Release|x86.ActiveCfg = Release|Any CPU {C60DF964-C19D-467E-9503-F69B0284D13C}.Release|x86.Build.0 = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|ARM.Build.0 = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|x64.ActiveCfg = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|x64.Build.0 = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|x86.ActiveCfg = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Debug|x86.Build.0 = Debug|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|Any CPU.Build.0 = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|ARM.ActiveCfg = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|ARM.Build.0 = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|x64.ActiveCfg = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|x64.Build.0 = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|x86.ActiveCfg = Release|Any CPU + {A509C0DD-BB0C-4624-AB74-82F4FBF5E38F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MADE.App/WeakReferenceCallback.cs b/MADE.App/WeakReferenceCallback.cs new file mode 100644 index 00000000..d8a341d8 --- /dev/null +++ b/MADE.App/WeakReferenceCallback.cs @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) MADE Apps. +// +// +// Defines a model for providing a weak referenced callback. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace MADE.App +{ + using System; + using System.Reflection; + + /// + /// Defines a model for providing a weak referenced callback. + /// + public sealed class WeakReferenceCallback + { + private readonly MethodInfo actionInfo; + + private readonly WeakReference weakReference; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The callback action. + /// + /// + /// The expected type for the callback. + /// + public WeakReferenceCallback(Delegate action, Type callbackType) + { + this.actionInfo = action.GetMethodInfo(); + this.weakReference = new WeakReference(action.Target); + this.Type = callbackType; + } + + /// + /// Gets a value indicating whether the callback is alive. + /// + public bool IsAlive => this.weakReference.IsAlive; + + /// + /// Gets the expected type for the callback. + /// + public Type Type { get; } + + /// + /// Invokes the callback with the specified parameter. + /// + /// + /// The parameter to pass to the callback. + /// + /// + /// Thrown if the weak reference is no longer alive. + /// + public void Invoke(object param) + { + if (this.IsAlive) + { + this.actionInfo.Invoke(this.weakReference.Target, new[] { param }); + } + else + { + throw new InvalidOperationException("The callback is no longer alive."); + } + } + } +} \ No newline at end of file