Skip to content

Commit

Permalink
Merge pull request #17 from dragonfruitnetwork/ns-changes
Browse files Browse the repository at this point in the history
Namespace and ApiClient changes
  • Loading branch information
aspriddell authored Jul 30, 2020
2 parents ef99315 + c5d2c3f commit bc114f9
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 132 deletions.
7 changes: 2 additions & 5 deletions DragonFruit.Common.Data.Tests/Advanced/PostRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ public void JsonPostRequestTest()
{
var request = new DatabaseUpdateRequest();

var firstResponse = Client.Perform<JObject>(request);
Assert.AreEqual(request.Employee.Department, firstResponse["json"].ToObject<Employee>().Department);

var secondResponse = Client.PerformLast<JObject>();
Assert.AreEqual(secondResponse["json"].ToObject<Employee>().Department, firstResponse["json"].ToObject<Employee>().Department);
var response = Client.Perform<JObject>(request);
Assert.AreEqual(request.Employee.Department, response["json"].ToObject<Employee>().Department);
}
}
}
184 changes: 62 additions & 122 deletions DragonFruit.Common.Data/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
using System.Threading;
using System.Threading.Tasks;
using DragonFruit.Common.Data.Exceptions;
using DragonFruit.Common.Data.Helpers;
using DragonFruit.Common.Data.Extensions;
using DragonFruit.Common.Data.Serializers;
using DragonFruit.Common.Data.Utils;

namespace DragonFruit.Common.Data
{
Expand Down Expand Up @@ -66,15 +67,7 @@ public ApiClient(ISerializer serializer)
/// <remarks>
/// The old <see cref="HttpMessageHandler"/> will be disposed on setting a new one.
/// </remarks>
protected HttpMessageHandler Handler
{
get => _handler;
set
{
_handler?.Dispose();
_handler = value;
}
}
protected HttpMessageHandler Handler { get; set; }

/// <summary>
/// The <see cref="ISerializer"/> to use when encoding/decoding request and response streams.
Expand Down Expand Up @@ -143,7 +136,8 @@ protected virtual HttpClient GetClient()
//lock for modification
if (!Monitor.TryEnter(_clientAdjustmentLock, AdjustmentTimeout))
{
throw new TimeoutException($"The {nameof(ApiClient)} is being overloaded with reconstruction requests. Consider creating a separate {nameof(ApiClient)} and delegating clients to specific types of requests");
throw new TimeoutException(
$"The {nameof(ApiClient)} is being overloaded with reconstruction requests. Consider creating a separate {nameof(ApiClient)} and delegating clients to specific types of requests");
}

//wait for all ongoing requests to end
Expand All @@ -159,8 +153,12 @@ protected virtual HttpClient GetClient()
if (resetClient)
{
Client?.Dispose();
Client = Handler != null ? new HttpClient(Handler, false) : new HttpClient();
_handler?.Dispose();

_handler = Handler;
_lastHandlerHash = handlerHash;

Client = Handler != null ? new HttpClient(_handler, false) : new HttpClient();
}

// reset the headers if any have changed (or the client has been reinitialised)
Expand Down Expand Up @@ -232,121 +230,45 @@ protected virtual void SetupRequest(HttpRequestMessage request)
#endregion

/// <summary>
/// Perform an <see cref="ApiRequest"/> with a specified return type.
/// Perform a <see cref="ApiRequest"/> that returns the response message. The <see cref="HttpResponseMessage"/> returned cannot be used for reading data, as the underlying <see cref="Task"/> will be disposed.
/// </summary>
public virtual T Perform<T>(ApiRequest requestData) where T : class
public virtual HttpResponseMessage Perform(ApiRequest requestData)
{
ValidateRequest(requestData);

//cache in case we need to PerformLast<T>();
CachedRequest = requestData.Clone();

//get client and request (disposables)
var client = GetClient();
Interlocked.Increment(ref _currentRequests);

var request = requestData.GetRequest(Serializer);

//post-modification
SetupRequest(request);

//send request
var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

try
{
//validate and process
var output = ValidateAndProcess<T>(response);

//return
return output;
}
finally
{
//un-bump reqs
Interlocked.Decrement(ref _currentRequests);

//dispose
response.Result.Dispose();
response.Dispose();

request.Dispose();
}
// perform and return postProcess result
return InternalPerform(requestData.GetRequest(Serializer), response => response);
}

/// <summary>
/// Perform a <see cref="ApiRequest"/> that returns the response message. The <see cref="HttpResponseMessage"/> returned cannot be used for reading data, as the underlying <see cref="Task"/> will be disposed.
/// Perform a pre-fabricated <see cref="HttpRequestMessage"/>
/// </summary>
/// <param name="requestData"></param>
public virtual HttpResponseMessage Perform(ApiRequest requestData)
public virtual HttpResponseMessage Perform(HttpRequestMessage request)
{
ValidateRequest(requestData);

//cache in case we need to PerformLast<T>();
CachedRequest = requestData.Clone();

//get client and request (disposables)
var client = GetClient();
Interlocked.Increment(ref _currentRequests);

var request = requestData.GetRequest(Serializer);

//post-modification
SetupRequest(request);

//send request
var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

try
{
//all possible exceptions from client.SendAsync() will be released here
return response.Result;
}
finally
{
//un-bump reqs
Interlocked.Decrement(ref _currentRequests);

//dispose
response.Result.Dispose();
response.Dispose();

request.Dispose();
}
return InternalPerform(request, response => response);
}

#region Perform Last Request

/// <summary>
/// Perform the last <see cref="ApiRequest"/> made (regardless of failure) on this <see cref="ApiClient"/> again
/// Perform an <see cref="ApiRequest"/> with a specified return type.
/// </summary>
public T PerformLast<T>() where T : class
public virtual T Perform<T>(ApiRequest requestData) where T : class
{
if (CachedRequest == null)
{
throw new NullRequestException();
}
ValidateRequest(requestData);
var request = requestData.GetRequest(Serializer);

return Perform<T>(CachedRequest);
return InternalPerform(request, response => ValidateAndProcess<T>(response, request));
}

/// <summary>
/// Perform the last <see cref="ApiRequest"/> made (regardless of failure) on this <see cref="ApiClient"/> again. Returns a <see cref="HttpResponseMessage"/>, where deserializing the data may not be desired
/// Perform a pre-fabricated <see cref="HttpRequestMessage"/> and deserialize the result to the specified type
/// </summary>
public HttpResponseMessage PerformLast()
public virtual T Perform<T>(HttpRequestMessage request) where T : class
{
if (CachedRequest == null)
{
throw new NullRequestException();
}

return Perform(CachedRequest);
return InternalPerform(request, response => ValidateAndProcess<T>(response, request));
}

#endregion

/// <summary>
/// Download a file with an <see cref="ApiRequest"/>. Incompatible with <see cref="PerformLast{T}"/> and bypasses <see cref="ValidateAndProcess{T}"/>
/// Download a file with an <see cref="ApiRequest"/>. Bypasses <see cref="ValidateAndProcess{T}"/>
/// </summary>
public virtual void Perform(ApiFileRequest requestData)
{
Expand All @@ -358,52 +280,70 @@ public virtual void Perform(ApiFileRequest requestData)
throw new NullRequestException();
}

HttpResponseMessage CopyProcess(HttpResponseMessage response)
{
//validate
response.EnsureSuccessStatusCode();

//copy result to file
using (var stream = File.Open(requestData.Destination, requestData.FileCreationMode))
using (var networkStream = response.Content.ReadAsStreamAsync().Result)
{
networkStream.CopyTo(stream);
}

return response; //we're not using this so return anything...
}

_ = InternalPerform(requestData.GetRequest(Serializer), CopyProcess);
}

/// <summary>
/// Internal procedure for performing a web-request
/// </summary>
/// <param name="request">The request to perform</param>
/// <param name="processResult"><see cref="Func{T,TResult}"/> to process the <see cref="HttpResponseMessage"/></param>
protected T InternalPerform<T>(HttpRequestMessage request, Func<HttpResponseMessage, T> processResult)
{
//get client and request (disposables)
var client = GetClient();
Interlocked.Increment(ref _currentRequests);

var request = requestData.GetRequest(Serializer);

//post-modification
SetupRequest(request);

//send request
var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

try
{
//validate
response.Result.EnsureSuccessStatusCode();

//copy result to file
using (var stream = File.Open(requestData.Destination, requestData.FileCreationMode))
using (var networkStream = response.Result.Content.ReadAsStreamAsync().Result)
{
networkStream.CopyTo(stream);
}
//all possible exceptions from client.SendAsync() will be released here
return processResult.Invoke(response.Result);
}
finally
{
//un-bump reqs
Interlocked.Decrement(ref _currentRequests);

//dispose
response.Result.Dispose();
response.Dispose();
response?.Result?.Dispose();
response?.Dispose();

request.Dispose();
request?.Dispose();
}
}

/// <summary>
/// Validates the <see cref="HttpResponseMessage"/> and uses the <see cref="Serializer"/> to deserialize data (if successful)
/// </summary>
protected virtual T ValidateAndProcess<T>(Task<HttpResponseMessage> response) where T : class
protected virtual T ValidateAndProcess<T>(HttpResponseMessage response, HttpRequestMessage request) where T : class
{
if (!response.Result.IsSuccessStatusCode)
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Response was unsuccessful ({response.Result.StatusCode})");
throw new HttpRequestException($"Response was unsuccessful ({response.StatusCode})");
}

return Serializer.Deserialize<T>(response.Result.Content.ReadAsStreamAsync());
return Serializer.Deserialize<T>(response.Content.ReadAsStreamAsync());
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion DragonFruit.Common.Data/Extensions/HashExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// DragonFruit.Common Copyright 2020 DragonFruit Network
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details

namespace DragonFruit.Common.Data.Helpers
namespace DragonFruit.Common.Data.Extensions
{
public static class HashExtensions
{
Expand Down
2 changes: 1 addition & 1 deletion DragonFruit.Common.Data/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Collections.Generic;
using Newtonsoft.Json.Linq;

namespace DragonFruit.Common.Data.Helpers
namespace DragonFruit.Common.Data.Extensions
{
public static class JsonExtensions
{
Expand Down
2 changes: 1 addition & 1 deletion DragonFruit.Common.Data/Serializers/ApiXmlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using DragonFruit.Common.Data.Helpers;
using DragonFruit.Common.Data.Utils;

namespace DragonFruit.Common.Data.Serializers
{
Expand Down
2 changes: 1 addition & 1 deletion DragonFruit.Common.Data/Utils/DictionaryHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;

namespace DragonFruit.Common.Data.Helpers
namespace DragonFruit.Common.Data.Utils
{
internal static class DictionaryHelpers
{
Expand Down
2 changes: 1 addition & 1 deletion DragonFruit.Common.Data/Utils/HashableDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Linq;

namespace DragonFruit.Common.Data.Helpers
namespace DragonFruit.Common.Data.Utils
{
/// <summary>
/// A superset of <see cref="T:System.Collections.Generic.Dictionary`2" /> with a hash code function that calculates the hash based on the keys and values inside.
Expand Down

0 comments on commit bc114f9

Please sign in to comment.