Refactor CapyKit utilities: streamline namespaces, improve code consistency, and enhance XML documentation.
This commit is contained in:
parent
2b30a8b5e9
commit
f03f73afc7
28 changed files with 5180 additions and 4298 deletions
|
|
@ -1,43 +1,36 @@
|
||||||
using System;
|
namespace CapyKit.Attributes;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Attributes
|
/// <summary>
|
||||||
|
/// Custom attribute class for decorating enumeration fields with additional data.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">
|
||||||
|
/// Generic type parameter allowing for arbitrary declarations and assignments of meaning.
|
||||||
|
/// </typeparam>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public abstract class EnumerationAttribute<T> : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Constructors
|
||||||
/// Custom attribute class for decorating enumeration fields with additional data.
|
|
||||||
/// </summary>
|
/// <summary> Gets the value of the enumeration represented by this attribute. </summary>
|
||||||
/// <typeparam name="T">
|
/// <param name="value">
|
||||||
/// Generic type parameter allowing for arbitrary declarations and assignments of meaning.
|
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}" /> class with a
|
||||||
/// </typeparam>
|
/// specified value.
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
/// </param>
|
||||||
public abstract class EnumerationAttribute<T> : Attribute
|
protected EnumerationAttribute(T value)
|
||||||
{
|
{
|
||||||
#region Properties
|
this.Value = value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a
|
|
||||||
/// specified value.
|
|
||||||
/// </summary>
|
|
||||||
/// <value> The value. </value>
|
|
||||||
public T Value { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary> Gets the value of the enumeration represented by this attribute. </summary>
|
|
||||||
/// <param name="value">
|
|
||||||
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}"/> class with a
|
|
||||||
/// specified value.
|
|
||||||
/// </param>
|
|
||||||
protected EnumerationAttribute(T value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EnumerationAttribute{T}" /> class with a
|
||||||
|
/// specified value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value> The value. </value>
|
||||||
|
public T Value { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
using System;
|
namespace CapyKit.Attributes;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Attributes
|
/// <summary> An attribute class for decorating enumeration fields with a description. </summary>
|
||||||
|
/// <seealso cref="EnumerationAttribute{T}" />
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class EnumerationDescriptionAttribute : EnumerationAttribute<string>
|
||||||
{
|
{
|
||||||
/// <summary> An attribute class for decorating enumeration fields with a description. </summary>
|
/// <summary>
|
||||||
/// <seealso cref="EnumerationAttribute{T}"/>
|
/// Initializes a new instance of the <see cref="EnumerationDescriptionAttribute" /> class with
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
/// the specified description.
|
||||||
public class EnumerationDescriptionAttribute : EnumerationAttribute<string>
|
/// </summary>
|
||||||
|
/// <param name="description"> The description of the enumeration value. </param>
|
||||||
|
public EnumerationDescriptionAttribute(string description)
|
||||||
|
: base(description)
|
||||||
{
|
{
|
||||||
/// <summary>
|
//
|
||||||
/// Initializes a new instance of the <see cref="EnumerationDescriptionAttribute"/> class with
|
|
||||||
/// the specified description.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="description"> The description of the enumeration value. </param>
|
|
||||||
public EnumerationDescriptionAttribute(string description)
|
|
||||||
: base(description)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
namespace CapyKit.Attributes;
|
namespace CapyKit.Attributes;
|
||||||
|
|
||||||
/// <summary> Attribute describing the associated font-awesome icon of an <see cref="Enum"/> parameter. </summary>
|
/// <summary> Attribute describing the associated font-awesome icon of an <see cref="Enum" /> parameter. </summary>
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public class EnumerationIconAttribute : Attribute
|
public class EnumerationIconAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary> Gets or sets the description of the enumeration. </summary>
|
|
||||||
/// <value> The description. </value>
|
|
||||||
public string Icon { get; private set; }
|
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
/// <param name="icon"> The icon associated with the enumeration. </param>
|
/// <param name="icon"> The icon associated with the enumeration. </param>
|
||||||
public EnumerationIconAttribute(string icon)
|
public EnumerationIconAttribute(string icon)
|
||||||
{
|
{
|
||||||
this.Icon = icon;
|
this.Icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets or sets the description of the enumeration. </summary>
|
||||||
|
/// <value> The description. </value>
|
||||||
|
public string Icon { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
@ -4,14 +4,6 @@ namespace CapyKit.Attributes;
|
||||||
/// <remarks> Accepted values are stored as discreet values similar to an enumeration. Ranges are not supported. </remarks>
|
/// <remarks> Accepted values are stored as discreet values similar to an enumeration. Ranges are not supported. </remarks>
|
||||||
public class ParameterAcceptedValuesAttribute : Attribute
|
public class ParameterAcceptedValuesAttribute : Attribute
|
||||||
{
|
{
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary> Gets or sets the accepted values. </summary>
|
|
||||||
/// <value> The accepted values. </value>
|
|
||||||
public IEnumerable<object> AcceptedValues { get; private set; }
|
|
||||||
|
|
||||||
#endregion Properties
|
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
|
|
@ -22,4 +14,12 @@ public class ParameterAcceptedValuesAttribute : Attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Constructors
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary> Gets or sets the accepted values. </summary>
|
||||||
|
/// <value> The accepted values. </value>
|
||||||
|
public IEnumerable<object> AcceptedValues { get; private set; }
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
}
|
}
|
||||||
|
|
@ -13,13 +13,15 @@ public class ParameterAttribute : Attribute
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name parameter as passed in a POST method from the Browser. This can be hardcoded at the class level.
|
/// Gets or sets the name parameter as passed in a POST method from the Browser. This can be hardcoded at the class
|
||||||
|
/// level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value> The name of the parameter. </value>
|
/// <value> The name of the parameter. </value>
|
||||||
public string ParameterName { get; private set; }
|
public string ParameterName { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the description of the parameter. This serves as a human-readable explanation or label for the parameter's purpose or usage.
|
/// Gets the description of the parameter. This serves as a human-readable explanation or label for the parameter's
|
||||||
|
/// purpose or usage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The description text of the parameter.</value>
|
/// <value>The description text of the parameter.</value>
|
||||||
public string Description { get; private set; }
|
public string Description { get; private set; }
|
||||||
|
|
@ -48,8 +50,10 @@ public class ParameterAttribute : Attribute
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
/// <param name="systemName"> The name of the system. </param>
|
/// <param name="systemName"> The name of the system. </param>
|
||||||
/// <param name="description"> Name of the resource that contains the description of
|
/// <param name="description">
|
||||||
/// the parameter. </param>
|
/// Name of the resource that contains the description of
|
||||||
|
/// the parameter.
|
||||||
|
/// </param>
|
||||||
public ParameterAttribute(string systemName, string description)
|
public ParameterAttribute(string systemName, string description)
|
||||||
{
|
{
|
||||||
this.ParameterName = systemName;
|
this.ParameterName = systemName;
|
||||||
|
|
@ -60,8 +64,10 @@ public class ParameterAttribute : Attribute
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
/// <param name="systemName"> The name of the system. </param>
|
/// <param name="systemName"> The name of the system. </param>
|
||||||
/// <param name="description"> Name of the resource that contains the description of
|
/// <param name="description">
|
||||||
/// the parameter. </param>
|
/// Name of the resource that contains the description of
|
||||||
|
/// the parameter.
|
||||||
|
/// </param>
|
||||||
/// <param name="iconName"> The FontAwesome icon name. </param>
|
/// <param name="iconName"> The FontAwesome icon name. </param>
|
||||||
public ParameterAttribute(string systemName, string description, string iconName)
|
public ParameterAttribute(string systemName, string description, string iconName)
|
||||||
{
|
{
|
||||||
|
|
@ -73,8 +79,10 @@ public class ParameterAttribute : Attribute
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
/// <param name="systemName"> The name of the system. </param>
|
/// <param name="systemName"> The name of the system. </param>
|
||||||
/// <param name="description"> Name of the resource that contains the description of
|
/// <param name="description">
|
||||||
/// the parameter. </param>
|
/// Name of the resource that contains the description of
|
||||||
|
/// the parameter.
|
||||||
|
/// </param>
|
||||||
/// <param name="iconName"> The FontAwesome icon name. </param>
|
/// <param name="iconName"> The FontAwesome icon name. </param>
|
||||||
/// <param name="ordinal"> The ordinal position. </param>
|
/// <param name="ordinal"> The ordinal position. </param>
|
||||||
public ParameterAttribute(string systemName, string description, string iconName, int ordinal)
|
public ParameterAttribute(string systemName, string description, string iconName, int ordinal)
|
||||||
|
|
@ -87,8 +95,10 @@ public class ParameterAttribute : Attribute
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
/// <summary> Constructor. </summary>
|
||||||
/// <param name="systemName"> The name of the system. </param>
|
/// <param name="systemName"> The name of the system. </param>
|
||||||
/// <param name="description"> Name of the resource that contains the description of
|
/// <param name="description">
|
||||||
/// the parameter. </param>
|
/// Name of the resource that contains the description of
|
||||||
|
/// the parameter.
|
||||||
|
/// </param>
|
||||||
/// <param name="ordinal"> The ordinal position. </param>
|
/// <param name="ordinal"> The ordinal position. </param>
|
||||||
public ParameterAttribute(string systemName, string description, int ordinal)
|
public ParameterAttribute(string systemName, string description, int ordinal)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,51 @@
|
||||||
using System;
|
namespace CapyKit.Attributes;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Attributes
|
/// <summary>
|
||||||
|
/// Custom attribute for formatting values in a specific way.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ValueFormatAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Properties
|
||||||
/// Custom attribute for formatting values in a specific way.
|
|
||||||
/// </summary>
|
/// <summary> Gets or sets the format to use for formatting the value. </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
/// <value> The format string used to format the value. </value>
|
||||||
public class ValueFormatAttribute : Attribute
|
public string Format { get; }
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Gets a parameterized formatted string for the specified index. </summary>
|
||||||
|
/// <param name="index"> (Optional) Zero-based index of the item in the string to format. </param>
|
||||||
|
/// <returns> A formatted string with the specified index and format. </returns>
|
||||||
|
public string GetFormatParameterizedString(int index = 0)
|
||||||
{
|
{
|
||||||
#region Properties
|
return "{" + index + ":" + this.Format + "}";
|
||||||
|
|
||||||
/// <summary> Gets or sets the format to use for formatting the value. </summary>
|
|
||||||
/// <value> The format string used to format the value. </value>
|
|
||||||
public string Format { get; private set; }
|
|
||||||
|
|
||||||
#endregion Properties
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor. Initializes a new instance of the <see cref="ValueFormatAttribute"/>
|
|
||||||
/// class with an empty format string.
|
|
||||||
/// </summary>
|
|
||||||
public ValueFormatAttribute()
|
|
||||||
{
|
|
||||||
this.Format = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor. Initializes a new instance of the <see cref="ValueFormatAttribute"/> class with
|
|
||||||
/// the specified format string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"> The format string used to format the value. </param>
|
|
||||||
public ValueFormatAttribute(string format)
|
|
||||||
{
|
|
||||||
this.Format = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Constructors
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Gets a parameterized formatted string for the specified index. </summary>
|
|
||||||
/// <param name="index"> (Optional) Zero-based index of the item in the string to format. </param>
|
|
||||||
/// <returns> A formatted string with the specified index and format. </returns>
|
|
||||||
public string GetFormatParameterizedString(int index = 0)
|
|
||||||
{
|
|
||||||
return "{" + index + ":" + this.Format + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion Methods
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor. Initializes a new instance of the <see cref="ValueFormatAttribute" />
|
||||||
|
/// class with an empty format string.
|
||||||
|
/// </summary>
|
||||||
|
public ValueFormatAttribute()
|
||||||
|
{
|
||||||
|
this.Format = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor. Initializes a new instance of the <see cref="ValueFormatAttribute" /> class with
|
||||||
|
/// the specified format string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format"> The format string used to format the value. </param>
|
||||||
|
public ValueFormatAttribute(string format)
|
||||||
|
{
|
||||||
|
this.Format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
}
|
||||||
|
|
@ -1,223 +1,224 @@
|
||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CapyKit.Attributes;
|
using CapyKit.Attributes;
|
||||||
using CapyKit.Extensions;
|
|
||||||
|
|
||||||
namespace CapyKit
|
namespace CapyKit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CapyEventReporter class is responsible for managing event subscriptions and emissions within CapyKit.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter" />
|
||||||
|
/// provides
|
||||||
|
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
|
||||||
|
/// a logging solution for CapyKit. Consumers are free to treat these events however they see fit.
|
||||||
|
/// </remarks>
|
||||||
|
public static class CapyEventReporter
|
||||||
{
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The CapyEventReporter class is responsible for managing event subscriptions and emissions within CapyKit.
|
/// A dictionary storing event handlers and their corresponding origins for each subscription level.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new();
|
||||||
|
|
||||||
|
/// <summary> A hash set storing unique identifiers for events intended to only be emitted once. </summary>
|
||||||
|
/// <seealso cref="EmitEventOnce(EventLevel, string, string, string, object[])" />
|
||||||
|
private static readonly HashSet<string> uniqueIdentifiers = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the specified event handler to the event with the given subscription level and
|
||||||
|
/// origin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter"/> provides
|
/// If there is no existing list for the given subscription level, a new list is created and
|
||||||
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
|
/// added to the dictionary.
|
||||||
/// a logging solution for CapyKit. Consumers are free to treat these events however they see fit.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class CapyEventReporter
|
/// <param name="callback"> The event handler to subscribe. </param>
|
||||||
|
/// <param name="subscriptionLevel"> The severity level of the event to subscribe to. </param>
|
||||||
|
/// <param name="origin">
|
||||||
|
/// (Optional) The name of the method or class where the subscription is made.
|
||||||
|
/// </param>
|
||||||
|
public static void Subscribe(CapyEventHandler callback, EventLevel subscriptionLevel,
|
||||||
|
[CallerMemberName] string origin = null)
|
||||||
{
|
{
|
||||||
#region Members
|
if (!CapyEventReporter.subscribers.ContainsKey(subscriptionLevel))
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dictionary storing event handlers and their corresponding origins for each subscription level.
|
|
||||||
/// </summary>
|
|
||||||
private static Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>>();
|
|
||||||
|
|
||||||
/// <summary> A hash set storing unique identifiers for events intended to only be emitted once. </summary>
|
|
||||||
/// <seealso cref="EmitEventOnce(EventLevel, string, string, string, object[])"/>
|
|
||||||
private static HashSet<string> uniqueIdentifiers = new HashSet<string>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Subscribes the specified event handler to the event with the given subscription level and
|
|
||||||
/// origin.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If there is no existing list for the given subscription level, a new list is created and
|
|
||||||
/// added to the dictionary.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="callback"> The event handler to subscribe. </param>
|
|
||||||
/// <param name="subscriptionLevel"> The severity level of the event to subscribe to. </param>
|
|
||||||
/// <param name="origin">
|
|
||||||
/// (Optional) The name of the method or class where the subscription is made.
|
|
||||||
/// </param>
|
|
||||||
public static void Subscribe(CapyEventHandler callback, EventLevel subscriptionLevel, [CallerMemberName] string origin = null)
|
|
||||||
{
|
{
|
||||||
if (!subscribers.ContainsKey(subscriptionLevel))
|
CapyEventReporter.subscribers.Add(subscriptionLevel, new List<(CapyEventHandler Handler, string origin)>());
|
||||||
{
|
|
||||||
subscribers.Add(subscriptionLevel, new List<(CapyEventHandler Handler, string origin)>());
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribers[subscriptionLevel].Add((callback, origin ?? "[Unknown]"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
CapyEventReporter.subscribers[subscriptionLevel].Add((callback, origin ?? "[Unknown]"));
|
||||||
/// Unsubscribes the specified event handler from the event with the given origin.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback"> The event handler to unsubscribe. </param>
|
|
||||||
/// <param name="origin">
|
|
||||||
/// The name of the method or class where the subscription was made.
|
|
||||||
/// </param>
|
|
||||||
public static void Unsubscribe(CapyEventHandler callback, string origin)
|
|
||||||
{
|
|
||||||
foreach (var value in Enum.GetValues(typeof(EventLevel)))
|
|
||||||
{
|
|
||||||
if (value is EventLevel)
|
|
||||||
{
|
|
||||||
subscribers[(EventLevel)value].RemoveAll(c => c.Handler == callback && c.origin == origin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Emits an event with the given severity level, message, and method name. </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute"/>
|
|
||||||
/// , it is suggested that <paramref name="args"/> is defined explicitly for formatted messages.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="eventLevel"> The severity level of the event. </param>
|
|
||||||
/// <param name="message">
|
|
||||||
/// The message describing the reason for the event. String formatting for <paramref name="args"/>
|
|
||||||
/// is accepted.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="method">
|
|
||||||
/// (Optional) The name of the method where the event was raised.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// A variable-length parameters list containing arguments for formatting the message.
|
|
||||||
/// </param>
|
|
||||||
/// <example>
|
|
||||||
/// <code>
|
|
||||||
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration });
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
/// <seealso cref="CallerMemberNameAttribute"/>
|
|
||||||
public static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null, params object[] args)
|
|
||||||
{
|
|
||||||
if (!subscribers.ContainsKey(eventLevel))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formattedMessage = string.Format(message, args);
|
|
||||||
|
|
||||||
var capyEventArgs = new CapyEventArgs(eventLevel, formattedMessage, method);
|
|
||||||
|
|
||||||
foreach (var subscriber in subscribers[eventLevel])
|
|
||||||
{
|
|
||||||
subscriber.Handler(capyEventArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Emits an event with the given severity level, message, unique identifier, and method name one
|
|
||||||
/// time.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method is similar to <see cref="EmitEvent(EventLevel, string, string, string, object[])"/>
|
|
||||||
/// , but requires a unique identifier (such as a <see cref="Guid"/>) to prevent duplicate
|
|
||||||
/// emissions.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="eventLevel"> The severity level of the event. </param>
|
|
||||||
/// <param name="message">
|
|
||||||
/// The message describing the reason for the event. String formatting for <paramref name="args"/>
|
|
||||||
/// is accepted.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="uniqueIdentifier"> A unique identifier for the event emission. </param>
|
|
||||||
/// <param name="method">
|
|
||||||
/// (Optional) The name of the method where the event was raised.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// A variable-length parameters list containing arguments for formatting the message.
|
|
||||||
/// </param>
|
|
||||||
/// <seealso cref="CallerMemberNameAttribute"/>
|
|
||||||
/// <seealso cref="Guid"/>
|
|
||||||
public static void EmitEventOnce(EventLevel eventLevel, string message, string uniqueIdentifier, [CallerMemberName] string method = null, params object[] args)
|
|
||||||
{
|
|
||||||
if(uniqueIdentifiers.Contains(uniqueIdentifier))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uniqueIdentifiers.Add(uniqueIdentifier);
|
|
||||||
EmitEvent(eventLevel, message, method: method, args: args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A delegate representing an event handler that accepts a <see cref="CapyEventArgs"/> instance.
|
/// Unsubscribes the specified event handler from the event with the given origin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="e">The CapyEventArgs instance containing event data.</param>
|
/// <param name="callback"> The event handler to unsubscribe. </param>
|
||||||
public delegate void CapyEventHandler(CapyEventArgs e);
|
/// <param name="origin">
|
||||||
|
/// The name of the method or class where the subscription was made.
|
||||||
/// <summary>
|
/// </param>
|
||||||
/// The CapyEventArgs class represents an event argument instance with event level, message, and
|
public static void Unsubscribe(CapyEventHandler callback, string origin)
|
||||||
/// method name information.
|
|
||||||
/// </summary>
|
|
||||||
public class CapyEventArgs : EventArgs
|
|
||||||
{
|
{
|
||||||
#region Properties
|
foreach (var value in Enum.GetValues(typeof(EventLevel)))
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the severity level of the event.
|
|
||||||
/// </summary>
|
|
||||||
public EventLevel Level { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the message describing the reason for the event.
|
|
||||||
/// </summary>
|
|
||||||
public string Message { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the method where the event was raised.
|
|
||||||
/// </summary>
|
|
||||||
public string MethodName { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructor
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the CapyEventArgs class with the specified event level, message, and method name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level">The severity level of the event.</param>
|
|
||||||
/// <param name="message">A descriptive message explaining the reason for the event.</param>
|
|
||||||
/// <param name="method">The name of the method where the event was raised.</param>
|
|
||||||
public CapyEventArgs(EventLevel level, string message, string method = null)
|
|
||||||
{
|
{
|
||||||
this.Level = level;
|
if (value is EventLevel)
|
||||||
this.Message = message;
|
{
|
||||||
this.MethodName = method ?? "[Unknown]";
|
CapyEventReporter.subscribers[(EventLevel)value]
|
||||||
|
.RemoveAll(c => c.Handler == callback && c.origin == origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Emits an event with the given severity level, message, and method name. </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute" />
|
||||||
|
/// , it is suggested that <paramref name="args" /> is defined explicitly for formatted messages.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventLevel"> The severity level of the event. </param>
|
||||||
|
/// <param name="message">
|
||||||
|
/// The message describing the reason for the event. String formatting for <paramref name="args" />
|
||||||
|
/// is accepted.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="method">
|
||||||
|
/// (Optional) The name of the method where the event was raised.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// A variable-length parameters list containing arguments for formatting the message.
|
||||||
|
/// </param>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration });
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
/// <seealso cref="CallerMemberNameAttribute" />
|
||||||
|
public static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null,
|
||||||
|
params object[] args)
|
||||||
|
{
|
||||||
|
if (!CapyEventReporter.subscribers.ContainsKey(eventLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
var formattedMessage = string.Format(message, args);
|
||||||
|
|
||||||
|
var capyEventArgs = new CapyEventArgs(eventLevel, formattedMessage, method);
|
||||||
|
|
||||||
|
foreach (var subscriber in CapyEventReporter.subscribers[eventLevel])
|
||||||
|
{
|
||||||
|
subscriber.Handler(capyEventArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// <summary> Enumeration representing different event level severity values. </summary>
|
/// Emits an event with the given severity level, message, unique identifier, and method name one
|
||||||
public enum EventLevel
|
/// time.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is similar to <see cref="EmitEvent(EventLevel, string, string, string, object[])" />
|
||||||
|
/// , but requires a unique identifier (such as a <see cref="Guid" />) to prevent duplicate
|
||||||
|
/// emissions.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventLevel"> The severity level of the event. </param>
|
||||||
|
/// <param name="message">
|
||||||
|
/// The message describing the reason for the event. String formatting for <paramref name="args" />
|
||||||
|
/// is accepted.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="uniqueIdentifier"> A unique identifier for the event emission. </param>
|
||||||
|
/// <param name="method">
|
||||||
|
/// (Optional) The name of the method where the event was raised.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// A variable-length parameters list containing arguments for formatting the message.
|
||||||
|
/// </param>
|
||||||
|
/// <seealso cref="CallerMemberNameAttribute" />
|
||||||
|
/// <seealso cref="Guid" />
|
||||||
|
public static void EmitEventOnce(EventLevel eventLevel, string message, string uniqueIdentifier,
|
||||||
|
[CallerMemberName] string method = null, params object[] args)
|
||||||
{
|
{
|
||||||
/// <summary> Represents a critical error that requires immediate attention. </summary>
|
if (CapyEventReporter.uniqueIdentifiers.Contains(uniqueIdentifier))
|
||||||
[EnumerationDescription("Represents a critical error that requires immediate attention.")]
|
{
|
||||||
Critical = 0,
|
return;
|
||||||
/// <summary> Represents an error that prevents the normal execution of the application. </summary>
|
}
|
||||||
[EnumerationDescription("Represents an error that prevents the normal execution of the application.")]
|
|
||||||
Error = 1,
|
CapyEventReporter.uniqueIdentifiers.Add(uniqueIdentifier);
|
||||||
/// <summary> Represents a warning indicating a non-critical issue that should be addressed. </summary>
|
EmitEvent(eventLevel, message, method, args);
|
||||||
[EnumerationDescription("Represents a warning indicating a non-critical issue that should be addressed.")]
|
|
||||||
Warning = 2,
|
|
||||||
/// <summary> Represents informational messages that provide useful context to the consumer. </summary>
|
|
||||||
[EnumerationDescription("Represents informational messages that provide useful context to the consumer.")]
|
|
||||||
Information = 3,
|
|
||||||
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
|
|
||||||
[EnumerationDescription("Represents detailed messages that are typically used for debugging purposes.")]
|
|
||||||
Debug = 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A delegate representing an event handler that accepts a <see cref="CapyEventArgs" /> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e">The CapyEventArgs instance containing event data.</param>
|
||||||
|
public delegate void CapyEventHandler(CapyEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CapyEventArgs class represents an event argument instance with event level, message, and
|
||||||
|
/// method name information.
|
||||||
|
/// </summary>
|
||||||
|
public class CapyEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the CapyEventArgs class with the specified event level, message, and method name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The severity level of the event.</param>
|
||||||
|
/// <param name="message">A descriptive message explaining the reason for the event.</param>
|
||||||
|
/// <param name="method">The name of the method where the event was raised.</param>
|
||||||
|
public CapyEventArgs(EventLevel level, string message, string method = null)
|
||||||
|
{
|
||||||
|
this.Level = level;
|
||||||
|
this.Message = message;
|
||||||
|
this.MethodName = method ?? "[Unknown]";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the severity level of the event.
|
||||||
|
/// </summary>
|
||||||
|
public EventLevel Level { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message describing the reason for the event.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the method where the event was raised.
|
||||||
|
/// </summary>
|
||||||
|
public string MethodName { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Enumeration representing different event level severity values. </summary>
|
||||||
|
public enum EventLevel
|
||||||
|
{
|
||||||
|
/// <summary> Represents a critical error that requires immediate attention. </summary>
|
||||||
|
[EnumerationDescription("Represents a critical error that requires immediate attention.")]
|
||||||
|
Critical = 0,
|
||||||
|
|
||||||
|
/// <summary> Represents an error that prevents the normal execution of the application. </summary>
|
||||||
|
[EnumerationDescription("Represents an error that prevents the normal execution of the application.")]
|
||||||
|
Error = 1,
|
||||||
|
|
||||||
|
/// <summary> Represents a warning indicating a non-critical issue that should be addressed. </summary>
|
||||||
|
[EnumerationDescription("Represents a warning indicating a non-critical issue that should be addressed.")]
|
||||||
|
Warning = 2,
|
||||||
|
|
||||||
|
/// <summary> Represents informational messages that provide useful context to the consumer. </summary>
|
||||||
|
[EnumerationDescription("Represents informational messages that provide useful context to the consumer.")]
|
||||||
|
Information = 3,
|
||||||
|
|
||||||
|
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
|
||||||
|
[EnumerationDescription("Represents detailed messages that are typically used for debugging purposes.")]
|
||||||
|
Debug = 4
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<Version>1.0.4</Version>
|
<Version>1.0.4</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\README.md">
|
<None Include="..\README.md">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Import Project="NuGetPublish.targets" Condition="Exists('NuGetPublish.targets')" />
|
<Import Project="NuGetPublish.targets" Condition="Exists('NuGetPublish.targets')"/>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
||||||
using System;
|
namespace CapyKit;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit
|
/// <summary>
|
||||||
|
/// Holds a value that has been encrypted.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class EncryptedValue<T>
|
||||||
{
|
{
|
||||||
|
#region Properties
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds a value that has been encrypted.
|
/// The encrypted value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
public T Value { get; set; }
|
||||||
public class EncryptedValue<T>
|
|
||||||
{
|
|
||||||
#region Members
|
|
||||||
|
|
||||||
//
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#region Members
|
||||||
|
|
||||||
#region Properties
|
//
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// The encrypted value.
|
}
|
||||||
/// </summary>
|
|
||||||
public T Value { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
using System;
|
namespace CapyKit.Enumerations;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Enumerations
|
/// <summary>
|
||||||
|
/// An enumeration representing different measurement systems.
|
||||||
|
/// </summary>
|
||||||
|
public enum MeasurementSystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary> The imperial measurement system. </summary>
|
||||||
/// An enumeration representing different measurement systems.
|
Imperial = 0,
|
||||||
/// </summary>
|
|
||||||
public enum MeasurementSystem
|
|
||||||
{
|
|
||||||
/// <summary> The imperial measurement system. </summary>
|
|
||||||
Imperial = 0,
|
|
||||||
|
|
||||||
/// <summary> The metric measurement system. </summary>
|
/// <summary> The metric measurement system. </summary>
|
||||||
Metric = 1
|
Metric = 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,94 +1,91 @@
|
||||||
using CapyKit.Attributes;
|
using System.Reflection;
|
||||||
|
using CapyKit.Attributes;
|
||||||
using CapyKit.Helpers;
|
using CapyKit.Helpers;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Extensions
|
namespace CapyKit.Extensions;
|
||||||
|
|
||||||
|
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="Enum" /> types. </summary>
|
||||||
|
public static class EnumerationExtensions
|
||||||
{
|
{
|
||||||
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="Enum"/> types. </summary>
|
#region Methods
|
||||||
public static class EnumerationExtensions
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <typeparamref name="T" /> extension method that parses a string into an enumeration.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <param name="value"> The value. </param>
|
||||||
|
/// <returns> A T. </returns>
|
||||||
|
public static T Parse<T>(this T enumeration, string value) where T : Enum
|
||||||
{
|
{
|
||||||
#region Methods
|
return (T)Enum.Parse(typeof(T), value);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <param name="value"> The value. </param>
|
|
||||||
/// <returns> A T. </returns>
|
|
||||||
public static T Parse<T>(this T enumeration, string value) where T : Enum
|
|
||||||
{
|
|
||||||
return (T)Enum.Parse(typeof(T), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <typeparamref name="T"/> extension method that parses a string into an enumeration.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <param name="value"> The string value of the <see cref="Enum"/>. </param>
|
|
||||||
/// <param name="ignoreCase"> True to ignore case. </param>
|
|
||||||
/// <returns> A T. </returns>
|
|
||||||
public static T Parse<T>(this T enumeration, string value, bool ignoreCase) where T : Enum
|
|
||||||
{
|
|
||||||
return (T)Enum.Parse(typeof(T), value, ignoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="Enum"/> extension method that gets an integer value representing the enumation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <returns> The integer value of the enumeration. </returns>
|
|
||||||
public static int GetValue(this Enum enumeration)
|
|
||||||
{
|
|
||||||
return (int)Convert.ChangeType(enumeration, TypeCode.Int32);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> An <see cref="Enum"/> extension method that gets a name. </summary>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <returns> The name of the enumeration. </returns>
|
|
||||||
public static string GetName(this Enum enumeration)
|
|
||||||
{
|
|
||||||
return Enum.GetName(enumeration.GetType(), enumeration) ?? "[Unknown]";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> An <see cref="Enum"/> extension method that gets a human readable name. </summary>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <returns> The human readable name of the enumeration. </returns>
|
|
||||||
public static string GetPrettyName(this Enum enumeration)
|
|
||||||
{
|
|
||||||
return LanguageHelper.CamelCaseToHumanReadable(GetName(enumeration));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> An <see cref="Enum"/> extension method that gets a description. </summary>
|
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The description if available, otherwise the string representation of the enumeration.
|
|
||||||
/// </returns>
|
|
||||||
public static string GetDescription(this Enum enumeration)
|
|
||||||
{
|
|
||||||
var memInfo = enumeration.GetType().GetMember(enumeration.GetName());
|
|
||||||
if (memInfo.Any())
|
|
||||||
{
|
|
||||||
var attribute = memInfo.First().GetCustomAttribute(typeof(EnumerationDescriptionAttribute)) as EnumerationDescriptionAttribute;
|
|
||||||
if (attribute == null)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.", args: new[] { enumeration });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return attribute.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return enumeration.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <typeparamref name="T" /> extension method that parses a string into an enumeration.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <param name="value"> The string value of the <see cref="Enum" />. </param>
|
||||||
|
/// <param name="ignoreCase"> True to ignore case. </param>
|
||||||
|
/// <returns> A T. </returns>
|
||||||
|
public static T Parse<T>(this T enumeration, string value, bool ignoreCase) where T : Enum
|
||||||
|
{
|
||||||
|
return (T)Enum.Parse(typeof(T), value, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="Enum" /> extension method that gets an integer value representing the enumation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <returns> The integer value of the enumeration. </returns>
|
||||||
|
public static int GetValue(this Enum enumeration)
|
||||||
|
{
|
||||||
|
return (int)Convert.ChangeType(enumeration, TypeCode.Int32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An <see cref="Enum" /> extension method that gets a name. </summary>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <returns> The name of the enumeration. </returns>
|
||||||
|
public static string GetName(this Enum enumeration)
|
||||||
|
{
|
||||||
|
return Enum.GetName(enumeration.GetType(), enumeration) ?? "[Unknown]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An <see cref="Enum" /> extension method that gets a human readable name. </summary>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <returns> The human readable name of the enumeration. </returns>
|
||||||
|
public static string GetPrettyName(this Enum enumeration)
|
||||||
|
{
|
||||||
|
return LanguageHelper.CamelCaseToHumanReadable(enumeration.GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An <see cref="Enum" /> extension method that gets a description. </summary>
|
||||||
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The description if available, otherwise the string representation of the enumeration.
|
||||||
|
/// </returns>
|
||||||
|
public static string GetDescription(this Enum enumeration)
|
||||||
|
{
|
||||||
|
var memInfo = enumeration.GetType().GetMember(enumeration.GetName());
|
||||||
|
if (memInfo.Any())
|
||||||
|
{
|
||||||
|
var attribute =
|
||||||
|
memInfo.First().GetCustomAttribute(typeof(EnumerationDescriptionAttribute)) as
|
||||||
|
EnumerationDescriptionAttribute;
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.",
|
||||||
|
args: new[] { enumeration });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return attribute.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enumeration.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,260 +1,266 @@
|
||||||
using System;
|
using System.Linq.Expressions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Extensions
|
namespace CapyKit.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides static extension methods for performing common LINQ operations on <see cref="IEnumerable{T}" /> and
|
||||||
|
/// <see cref="IQueryable{T}" /> collections.
|
||||||
|
/// </summary>
|
||||||
|
public static class LINQExtensions
|
||||||
{
|
{
|
||||||
/// <summary> Provides static extension methods for performing common LINQ operations on <see cref="IEnumerable{T}"/> and <see cref="IQueryable{T}"/> collections. </summary>
|
/// <summary>
|
||||||
public static class LINQExtensions
|
/// Filters out items matching a <paramref name="predicate" /> from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="predicate"> The predicate. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process remove in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
||||||
{
|
{
|
||||||
/// <summary>
|
return source.Where(item => !predicate(item));
|
||||||
/// Filters out items matching a <paramref name="predicate"/> from the collection.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <summary>
|
||||||
/// <param name="source"> The source to act on. </param>
|
/// Filters out items matching a <paramref name="predicate" /> from the collection.
|
||||||
/// <param name="predicate"> The predicate. </param>
|
/// </summary>
|
||||||
/// <returns>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// An enumerator that allows foreach to be used to process remove in this collection.
|
/// <param name="source"> The source to act on. </param>
|
||||||
/// </returns>
|
/// <param name="predicate"> The predicate. </param>
|
||||||
public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process remove in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IQueryable<T> Filter<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
if (predicate.Parameters.Count > 1)
|
||||||
{
|
{
|
||||||
return source.Where(item => !predicate(item));
|
CapyEventReporter.EmitEvent(EventLevel.Warning,
|
||||||
|
"More than one parameter was found in the predicate, which could result in unexpected behavior.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var parameter = predicate.Parameters.FirstOrDefault();
|
||||||
/// Filters out items matching a <paramref name="predicate"/> from the collection.
|
var negatedPredicate = Expression.Not(predicate);
|
||||||
/// </summary>
|
var lamda = Expression.Lambda<Func<T, bool>>(negatedPredicate, parameter);
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="source"> The source to act on. </param>
|
return source.Where(lamda);
|
||||||
/// <param name="predicate"> The predicate. </param>
|
}
|
||||||
/// <returns>
|
|
||||||
/// An enumerator that allows foreach to be used to process remove in this collection.
|
/// <summary>
|
||||||
/// </returns>
|
/// Get a page of items from a collection, skipping <paramref name="pageNumber" /> pages of
|
||||||
public static IQueryable<T> Filter<T>(this IQueryable<T> source, System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
/// <paramref name="pageSize" /> items per page.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageNumber" /> is less than <c>1</c> or if
|
||||||
|
/// <paramref name="pageSize" /> is less than
|
||||||
|
/// <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageNumber"> The page number to retrieve. </param>
|
||||||
|
/// <param name="pageSize"> Number of items per page. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process page in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Page<T>(this IEnumerable<T> source, int pageNumber, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageNumber < 1)
|
||||||
{
|
{
|
||||||
if(predicate.Parameters.Count > 1)
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]",
|
||||||
{
|
args: new[] { pageNumber });
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Warning, "More than one parameter was found in the predicate, which could result in unexpected behavior.");
|
throw new ArgumentOutOfRangeException("pageNumber");
|
||||||
}
|
|
||||||
|
|
||||||
var parameter = predicate.Parameters.FirstOrDefault();
|
|
||||||
var negatedPredicate = Expression.Not(predicate);
|
|
||||||
var lamda = Expression.Lambda<Func<T, bool>>(negatedPredicate, parameter);
|
|
||||||
|
|
||||||
return source.Where(lamda);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (pageSize < 1)
|
||||||
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of
|
|
||||||
/// <paramref name="pageSize"/> items per page.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||||
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if
|
|
||||||
/// <paramref name="pageSize"/> is less than
|
|
||||||
/// <c>1</c>.
|
|
||||||
/// </exception>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="source"> The source to act on. </param>
|
|
||||||
/// <param name="pageNumber"> The page number to retrieve. </param>
|
|
||||||
/// <param name="pageSize"> Number of items per page. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// An enumerator that allows foreach to be used to process page in this collection.
|
|
||||||
/// </returns>
|
|
||||||
public static IEnumerable<T> Page<T>(this IEnumerable<T> source, int pageNumber, int pageSize)
|
|
||||||
{
|
{
|
||||||
if (pageNumber < 1)
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
{
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber });
|
|
||||||
throw new ArgumentOutOfRangeException("pageNumber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageSize < 1)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
|
||||||
throw new ArgumentOutOfRangeException("pageSize");
|
|
||||||
}
|
|
||||||
|
|
||||||
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
||||||
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of
|
}
|
||||||
/// <paramref name="pageSize"/> items per page.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
/// Get a page of items from a collection, skipping <paramref name="pageNumber" /> pages of
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
/// <paramref name="pageSize" /> items per page.
|
||||||
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if
|
/// </summary>
|
||||||
/// <paramref name="pageSize"/> is less than
|
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
||||||
/// <c>1</c>.
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
/// </exception>
|
/// Thrown when <paramref name="pageNumber" /> is less than <c>1</c> or if
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <paramref name="pageSize" /> is less than
|
||||||
/// <param name="source"> The source to act on. </param>
|
/// <c>1</c>.
|
||||||
/// <param name="pageNumber"> The page number to retrieve. </param>
|
/// </exception>
|
||||||
/// <param name="pageSize"> . </param>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <returns>
|
/// <param name="source"> The source to act on. </param>
|
||||||
/// An enumerator that allows foreach to be used to process page in this collection.
|
/// <param name="pageNumber"> The page number to retrieve. </param>
|
||||||
/// </returns>
|
/// <param name="pageSize"> . </param>
|
||||||
public static IQueryable<T> Page<T>(this IQueryable<T> source, int pageNumber, int pageSize)
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process page in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IQueryable<T> Page<T>(this IQueryable<T> source, int pageNumber, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageNumber < 1)
|
||||||
{
|
{
|
||||||
if (pageNumber < 1)
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]",
|
||||||
{
|
args: new[] { pageNumber });
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber });
|
throw new ArgumentOutOfRangeException("pageNumber");
|
||||||
throw new ArgumentOutOfRangeException("pageNumber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageSize < 1)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
|
||||||
throw new ArgumentOutOfRangeException("pageSize");
|
|
||||||
}
|
|
||||||
|
|
||||||
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (pageSize < 1)
|
||||||
/// The number of pages of <paramref name="pageSize"/> size in the given collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||||
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>.
|
|
||||||
/// </exception>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="source"> The source to act on. </param>
|
|
||||||
/// <param name="pageSize"> Size of the page. </param>
|
|
||||||
/// <returns> An int. </returns>
|
|
||||||
public static int PageCount<T>(this IEnumerable<T> source, int pageSize)
|
|
||||||
{
|
{
|
||||||
if (pageSize < 1)
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
{
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
|
||||||
throw new ArgumentOutOfRangeException("pageSize");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
|
||||||
return Convert.ToInt32(ceiling);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
||||||
/// The number of pages of <paramref name="pageSize"/> size in the given collection.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||||
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>.
|
|
||||||
/// </exception>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="source"> The source to act on. </param>
|
|
||||||
/// <param name="pageSize"> Size of the page. </param>
|
|
||||||
/// <returns> An int. </returns>
|
|
||||||
public static int PageCount<T>(this IQueryable<T> source, int pageSize)
|
|
||||||
{
|
|
||||||
if (pageSize < 1)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
|
||||||
throw new ArgumentOutOfRangeException("pageSize");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
/// <summary>
|
||||||
return Convert.ToInt32(ceiling);
|
/// The number of pages of <paramref name="pageSize" /> size in the given collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageSize" /> is less than <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageSize"> Size of the page. </param>
|
||||||
|
/// <returns> An int. </returns>
|
||||||
|
public static int PageCount<T>(this IEnumerable<T> source, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageSize < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
return Convert.ToInt32(ceiling);
|
||||||
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
}
|
||||||
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
|
||||||
/// <typeparam name="R"> Type of the r. </typeparam>
|
/// <summary>
|
||||||
/// <param name="source"> The source to act on. </param>
|
/// The number of pages of <paramref name="pageSize" /> size in the given collection.
|
||||||
/// <param name="inner"> The inner. </param>
|
/// </summary>
|
||||||
/// <param name="outerSelector"> The outer selector. </param>
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
/// <param name="innerSelector"> The inner selector. </param>
|
/// Thrown when <paramref name="pageSize" /> is less than <c>1</c>.
|
||||||
/// <param name="resultSelector"> The result selector. </param>
|
/// </exception>
|
||||||
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <returns> An IQueryable<R> </returns>
|
/// <param name="source"> The source to act on. </param>
|
||||||
public static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
|
/// <param name="pageSize"> Size of the page. </param>
|
||||||
|
/// <returns> An int. </returns>
|
||||||
|
public static int PageCount<T>(this IQueryable<T> source, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageSize < 1)
|
||||||
{
|
{
|
||||||
Func<T, IEnumerable<U>, R> resultOrDefaultSelector = (i, o) =>
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
{
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
if (defaultGenerator == null)
|
|
||||||
{
|
|
||||||
defaultGenerator = (t) => default(U);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!o.Any())
|
|
||||||
{
|
|
||||||
return resultSelector(i, new[] { defaultGenerator(i) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultSelector(i, o);
|
|
||||||
};
|
|
||||||
|
|
||||||
return source.LeftOuterJoin(inner, outerSelector, innerSelector, (a, b) => resultSelector(a, b));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
return Convert.ToInt32(ceiling);
|
||||||
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
}
|
||||||
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
|
||||||
/// <typeparam name="R"> Type of the r. </typeparam>
|
|
||||||
/// <param name="source"> The source to act on. </param>
|
|
||||||
/// <param name="inner"> The inner. </param>
|
|
||||||
/// <param name="outerSelector"> The outer selector. </param>
|
|
||||||
/// <param name="innerSelector"> The inner selector. </param>
|
|
||||||
/// <param name="resultSelector"> The result selector. </param>
|
|
||||||
/// <returns> An IQueryable<R> </returns>
|
|
||||||
private static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Expression<Func<T, IEnumerable<U>, R>> resultSelector)
|
|
||||||
{
|
|
||||||
return source.GroupJoin(inner, outerSelector, innerSelector, resultSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> An IEnumable<T> extension method that left outer join. </summary>
|
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
/// <typeparam name="R"> Type of the r. </typeparam>
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
/// <param name="source"> The source to act on. </param>
|
/// <param name="source"> The source to act on. </param>
|
||||||
/// <param name="inner"> The inner. </param>
|
/// <param name="inner"> The inner. </param>
|
||||||
/// <param name="outerSelector"> The outer selector. </param>
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
/// <param name="innerSelector"> The inner selector. </param>
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
/// <param name="resultSelector"> The result selector. </param>
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
||||||
/// <returns>
|
/// <returns> An IQueryable<R> </returns>
|
||||||
/// An enumerator that allows foreach to be used to process left outter join in this collection.
|
public static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner,
|
||||||
/// </returns>
|
Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector,
|
||||||
public static IEnumerable<R> LeftOuterJoin<T, U, TKey, R>(this IEnumerable<T> source, IEnumerable<U> inner, Func<T, TKey> outerSelector, Func<U, TKey> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
|
Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
|
||||||
|
{
|
||||||
|
Func<T, IEnumerable<U>, R> resultOrDefaultSelector = (i, o) =>
|
||||||
{
|
{
|
||||||
var combined = source.GroupJoin(inner, outerSelector, innerSelector, (i, o) => new { inner = i, outer = o });
|
|
||||||
|
|
||||||
if (defaultGenerator == null)
|
if (defaultGenerator == null)
|
||||||
{
|
{
|
||||||
defaultGenerator = (t) => default(U);
|
defaultGenerator = t => default;
|
||||||
}
|
}
|
||||||
|
|
||||||
return combined.Select(anon =>
|
if (!o.Any())
|
||||||
{
|
{
|
||||||
if (!anon.outer.Any())
|
return resultSelector(i, new[] { defaultGenerator(i) });
|
||||||
{
|
}
|
||||||
return resultSelector(anon.inner, new[] { defaultGenerator(anon.inner) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultSelector(anon.inner, anon.outer);
|
return resultSelector(i, o);
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return source.LeftOuterJoin(inner, outerSelector, innerSelector, (a, b) => resultSelector(a, b));
|
||||||
/// Enumerates distinct items in this collection as defined by the key <paramref name="property"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
|
||||||
/// <typeparam name="U"> Generic type parameter property value. </typeparam>
|
|
||||||
/// <param name="items"> The items to act on. </param>
|
|
||||||
/// <param name="property"> The property. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// An enumerator that allows foreach to be used to process distinct items in this collection.
|
|
||||||
/// </returns>
|
|
||||||
public static IEnumerable<T> Distinct<T, U>(this IEnumerable<T> items, Func<T, U> property)
|
|
||||||
{
|
|
||||||
var propertyComparer = new PropertyComparer<T, U>(property);
|
|
||||||
return items.Distinct(propertyComparer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="inner"> The inner. </param>
|
||||||
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
|
/// <returns> An IQueryable<R> </returns>
|
||||||
|
private static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner,
|
||||||
|
Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector,
|
||||||
|
Expression<Func<T, IEnumerable<U>, R>> resultSelector)
|
||||||
|
{
|
||||||
|
return source.GroupJoin(inner, outerSelector, innerSelector, resultSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An IEnumable<T> extension method that left outer join. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="inner"> The inner. </param>
|
||||||
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
|
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process left outter join in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<R> LeftOuterJoin<T, U, TKey, R>(this IEnumerable<T> source, IEnumerable<U> inner,
|
||||||
|
Func<T, TKey> outerSelector, Func<U, TKey> innerSelector, Func<T, IEnumerable<U>, R> resultSelector,
|
||||||
|
Func<T, U> defaultGenerator = null)
|
||||||
|
{
|
||||||
|
var combined = source.GroupJoin(inner, outerSelector, innerSelector, (i, o) => new { inner = i, outer = o });
|
||||||
|
|
||||||
|
if (defaultGenerator == null)
|
||||||
|
{
|
||||||
|
defaultGenerator = t => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined.Select(anon =>
|
||||||
|
{
|
||||||
|
if (!anon.outer.Any())
|
||||||
|
{
|
||||||
|
return resultSelector(anon.inner, new[] { defaultGenerator(anon.inner) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSelector(anon.inner, anon.outer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates distinct items in this collection as defined by the key <paramref name="property" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter property value. </typeparam>
|
||||||
|
/// <param name="items"> The items to act on. </param>
|
||||||
|
/// <param name="property"> The property. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process distinct items in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Distinct<T, U>(this IEnumerable<T> items, Func<T, U> property)
|
||||||
|
{
|
||||||
|
var propertyComparer = new PropertyComparer<T, U>(property);
|
||||||
|
return items.Distinct(propertyComparer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,52 +1,47 @@
|
||||||
using System;
|
namespace CapyKit.Extensions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Extensions
|
/// <summary> An class containing extenstions that apply to any object type. </summary>
|
||||||
|
public static class ObjectExtensions
|
||||||
{
|
{
|
||||||
/// <summary> An class containing extenstions that apply to any object type. </summary>
|
/// <summary>
|
||||||
public static class ObjectExtensions
|
/// An object extension method that updates the properties of a given <paramref name="target" />
|
||||||
|
/// object with the values from a given <paramref name="source" /> object.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="target"> The target object to act on. </param>
|
||||||
|
/// <param name="source"> Source for the new property values. </param>
|
||||||
|
public static void UpdateProperties<T>(this T target, T source)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var properties = typeof(T).GetProperties();
|
||||||
/// An object extension method that updates the properties of a given <paramref name="target"/>
|
foreach (var prop in properties)
|
||||||
/// object with the values from a given <paramref name="source"/> object.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="target"> The target object to act on. </param>
|
|
||||||
/// <param name="source"> Source for the new property values. </param>
|
|
||||||
public static void UpdateProperties<T>(this T target, T source)
|
|
||||||
{
|
{
|
||||||
var properties = typeof(T).GetProperties();
|
if (prop.CanWrite)
|
||||||
foreach (var prop in properties)
|
|
||||||
{
|
{
|
||||||
if (prop.CanWrite)
|
prop.SetValue(target, prop.GetValue(source));
|
||||||
{
|
|
||||||
prop.SetValue(target, prop.GetValue(source));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An object extension method that updates the properties of a given <paramref name="target"/>
|
|
||||||
/// object with the values from a given <paramref name="source"/> object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="target"> The target object to act on. </param>
|
|
||||||
/// <param name="source"> Source for the new property values. </param>
|
|
||||||
public static void UpdateProperties(this object target, object source)
|
|
||||||
{
|
|
||||||
var targetProperties = target.GetType().GetProperties();
|
|
||||||
var sourceProperties = source.GetType().GetProperties();
|
|
||||||
var matchingProperties = targetProperties.Join(sourceProperties, outer => new { outer.Name, outer.PropertyType }, inner => new { inner.Name, inner.PropertyType }, (outer, inner) => new { Target = outer, Source = inner });
|
|
||||||
|
|
||||||
foreach (var propertyMatch in matchingProperties)
|
|
||||||
{
|
|
||||||
if(propertyMatch.Target.CanWrite)
|
|
||||||
{
|
|
||||||
propertyMatch.Target.SetValue(target, propertyMatch.Source.GetValue(source));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// An object extension method that updates the properties of a given <paramref name="target" />
|
||||||
|
/// object with the values from a given <paramref name="source" /> object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target"> The target object to act on. </param>
|
||||||
|
/// <param name="source"> Source for the new property values. </param>
|
||||||
|
public static void UpdateProperties(this object target, object source)
|
||||||
|
{
|
||||||
|
var targetProperties = target.GetType().GetProperties();
|
||||||
|
var sourceProperties = source.GetType().GetProperties();
|
||||||
|
var matchingProperties = targetProperties.Join(sourceProperties,
|
||||||
|
outer => new { outer.Name, outer.PropertyType }, inner => new { inner.Name, inner.PropertyType },
|
||||||
|
(outer, inner) => new { Target = outer, Source = inner });
|
||||||
|
|
||||||
|
foreach (var propertyMatch in matchingProperties)
|
||||||
|
{
|
||||||
|
if (propertyMatch.Target.CanWrite)
|
||||||
|
{
|
||||||
|
propertyMatch.Target.SetValue(target, propertyMatch.Source.GetValue(source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,56 +1,49 @@
|
||||||
using System;
|
namespace CapyKit.Extensions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Extensions
|
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="string" /> types. </summary>
|
||||||
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="string"/> types. </summary>
|
#region Members
|
||||||
public static class StringExtensions
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Replaces a null or empty string with a specified replacement string. </summary>
|
||||||
|
/// <param name="value"> The original string. </param>
|
||||||
|
/// <param name="replacement"> The replacement string. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The original string if not null or empty, otherwise the replacement string.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="string.IsNullOrEmpty(string?)" />
|
||||||
|
public static string IfNullOrEmpty(this string value, string replacement)
|
||||||
{
|
{
|
||||||
#region Members
|
if (string.IsNullOrEmpty(value))
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Replaces a null or empty string with a specified replacement string. </summary>
|
|
||||||
/// <param name="value"> The original string. </param>
|
|
||||||
/// <param name="replacement"> The replacement string. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The original string if not null or empty, otherwise the replacement string.
|
|
||||||
/// </returns>
|
|
||||||
/// <seealso cref="string.IsNullOrEmpty(string?)"/>
|
|
||||||
public static string IfNullOrEmpty(this string value, string replacement)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
return replacement;
|
||||||
{
|
|
||||||
return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Replaces a null or whitespace string with a specified replacement string. </summary>
|
return value;
|
||||||
/// <param name="value"> The original string. </param>
|
|
||||||
/// <param name="replacement"> The replacement string. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The original string if not null or whitespace, otherwise the replacement string.
|
|
||||||
/// </returns>
|
|
||||||
/// <seealso cref="string.IsNullOrWhiteSpace(string?)"/>
|
|
||||||
public static string IfNullOrWhiteSpace(this string value, string replacement)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Replaces a null or whitespace string with a specified replacement string. </summary>
|
||||||
|
/// <param name="value"> The original string. </param>
|
||||||
|
/// <param name="replacement"> The replacement string. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The original string if not null or whitespace, otherwise the replacement string.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="string.IsNullOrWhiteSpace(string?)" />
|
||||||
|
public static string IfNullOrWhiteSpace(this string value, string replacement)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,160 +1,159 @@
|
||||||
using CapyKit.Enumerations;
|
using System.Security.Cryptography;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using CapyKit.Enumerations;
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
/// <summary> Static class providing helper methods for various calculations. </summary>
|
||||||
|
public static class CalculationHelper
|
||||||
{
|
{
|
||||||
/// <summary> Static class providing helper methods for various calculations. </summary>
|
#region Members
|
||||||
public static class CalculationHelper
|
|
||||||
|
/// <summary> The earth's radius in kilometers. </summary>
|
||||||
|
public const int EARTH_RADIUS_KILOMETERS = 6371;
|
||||||
|
|
||||||
|
/// <summary> Ratio of miles per kilometer . </summary>
|
||||||
|
public const double MILES_PER_KILOMETER = 0.621371;
|
||||||
|
|
||||||
|
/// <summary> The valid hexidecimal characters. </summary>
|
||||||
|
private const string chars = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the hash of a given string using an <see cref="MD5" /> value as the first 32 bits.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str"> The string to be hashed. </param>
|
||||||
|
/// <returns> The calculated hash. </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is used for a quick and consistent hash function. It should not be considered
|
||||||
|
/// cryptographically sound or used in security contexts.
|
||||||
|
/// </remarks>
|
||||||
|
public static int CalculateHash(string str)
|
||||||
{
|
{
|
||||||
#region Members
|
var md5Hasher = MD5.Create();
|
||||||
|
var md5Hash = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(str));
|
||||||
|
var hash = BitConverter.ToInt32(md5Hash, 0);
|
||||||
|
|
||||||
/// <summary> The earth's radius in kilometers. </summary>
|
return hash;
|
||||||
public const int EARTH_RADIUS_KILOMETERS = 6371;
|
|
||||||
|
|
||||||
/// <summary> Ratio of miles per kilometer . </summary>
|
|
||||||
public const double MILES_PER_KILOMETER = 0.621371;
|
|
||||||
|
|
||||||
/// <summary> The valid hexidecimal characters. </summary>
|
|
||||||
private const string chars = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the hash of a given string using an <see cref="MD5"/> value as the first 32 bits.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="str"> The string to be hashed. </param>
|
|
||||||
/// <returns> The calculated hash. </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method is used for a quick and consistent hash function. It should not be considered
|
|
||||||
/// cryptographically sound or used in security contexts.
|
|
||||||
/// </remarks>
|
|
||||||
public static int CalculateHash(string str)
|
|
||||||
{
|
|
||||||
MD5 md5Hasher = MD5.Create();
|
|
||||||
var md5Hash = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(str));
|
|
||||||
var hash = BitConverter.ToInt32(md5Hash, 0);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Calculates the hexadecimal hash. </summary>
|
|
||||||
/// <param name="str"> The string to be hashed. </param>
|
|
||||||
/// <returns> The calculated 16 character hexadecimal hash. </returns>
|
|
||||||
public static string CalculateHexHash(string str)
|
|
||||||
{
|
|
||||||
byte[] bytes = Encoding.UTF8.GetBytes(str);
|
|
||||||
|
|
||||||
MD5 md5Hasher = MD5.Create();
|
|
||||||
byte[] hash = md5Hasher.ComputeHash(bytes);
|
|
||||||
|
|
||||||
char[] hash2 = new char[16];
|
|
||||||
|
|
||||||
// Note that here we are wasting bits of hash!
|
|
||||||
// But it isn't really important, because hash.Length == 32
|
|
||||||
for (int i = 0; i < hash2.Length; i++)
|
|
||||||
{
|
|
||||||
hash2[i] = CalculationHelper.chars[hash[i] % CalculationHelper.chars.Length];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(hash2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the distance between two points on earth using the <c>haversine</c> formula.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="latitudeOrigin"> The latitude origin. </param>
|
|
||||||
/// <param name="longitudeOrigin"> The longitude origin. </param>
|
|
||||||
/// <param name="latitudeDestination"> The latitude destination. </param>
|
|
||||||
/// <param name="longitudeDestination"> The longitude destination. </param>
|
|
||||||
/// <param name="measurementSystem"> (Optional) The measurement system. </param>
|
|
||||||
/// <returns> The distance. </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
|
|
||||||
/// to calculate the "as-the-crow-flies" distance between two points on earth.
|
|
||||||
/// </remarks>
|
|
||||||
/// <seealso cref="GetDistance(double, double, double, double, MeasurementSystem)"/>
|
|
||||||
public static decimal GetDistance(decimal latitudeOrigin, decimal longitudeOrigin, decimal latitudeDestination, decimal longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
|
|
||||||
{
|
|
||||||
double latitudeOriginalDouble = Convert.ToDouble(latitudeOrigin);
|
|
||||||
double longitudeOriginDouble = Convert.ToDouble(longitudeOrigin);
|
|
||||||
double latitudeDestinationDouble = Convert.ToDouble(latitudeDestination);
|
|
||||||
double longitudeDestinationDouble = Convert.ToDouble(longitudeDestination);
|
|
||||||
|
|
||||||
var result = GetDistance(latitudeOriginalDouble, longitudeOriginDouble, latitudeDestinationDouble, longitudeDestinationDouble, measurementSystem);
|
|
||||||
|
|
||||||
return Convert.ToDecimal(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets the distance between two points on earth using the <c>haversine</c> formula. </summary>
|
|
||||||
/// <param name="latitudeOrigin"> The latitude of the origin. </param>
|
|
||||||
/// <param name="longitudeOrigin"> The longitude of the origin. </param>
|
|
||||||
/// <param name="latitudeDestination"> The latitude destination. </param>
|
|
||||||
/// <param name="longitudeDestination"> The longitude destination. </param>
|
|
||||||
/// <param name="measurementSystem"> (Optional) The measurement system. </param>
|
|
||||||
/// <returns> The distance. </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
|
|
||||||
/// to calculate the "as-the-crow-flies" distance between two points on earth.
|
|
||||||
/// </remarks>
|
|
||||||
public static double GetDistance(double latitudeOrigin, double longitudeOrigin, double latitudeDestination, double longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
|
|
||||||
{
|
|
||||||
var thetaLatitude = DegreesToRadians(latitudeOrigin);
|
|
||||||
var thetaLongitude = DegreesToRadians(longitudeOrigin);
|
|
||||||
var deltaLatitude = DegreesToRadians(latitudeDestination - latitudeOrigin);
|
|
||||||
var deltaLongitude = DegreesToRadians(longitudeDestination - longitudeOrigin);
|
|
||||||
|
|
||||||
var haversineTheta = Math.Sin(deltaLatitude / 2) * Math.Sin(deltaLatitude / 2) + Math.Cos(thetaLatitude) * Math.Cos(thetaLongitude) * Math.Sin(deltaLongitude / 2) * Math.Sin(deltaLongitude / 2);
|
|
||||||
var angularDistance = 2 * Math.Atan2(Math.Sqrt(haversineTheta), Math.Sqrt(1 - haversineTheta));
|
|
||||||
|
|
||||||
var distance = EARTH_RADIUS_KILOMETERS * angularDistance;
|
|
||||||
|
|
||||||
if (measurementSystem == MeasurementSystem.Imperial)
|
|
||||||
{
|
|
||||||
return KilometersToMiles(distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Converts kilometers to miles. </summary>
|
|
||||||
/// <param name="kilometers"> The value in kilometers. </param>
|
|
||||||
/// <returns> The value in miles. </returns>
|
|
||||||
public static double KilometersToMiles(double kilometers)
|
|
||||||
{
|
|
||||||
return kilometers * MILES_PER_KILOMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Converts miles to kilometers. </summary>
|
|
||||||
/// <param name="miles"> The value in miles. </param>
|
|
||||||
/// <returns> The value in kilometers. </returns>
|
|
||||||
public static double MilesToKilometers(double miles)
|
|
||||||
{
|
|
||||||
return miles / MILES_PER_KILOMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Convers degrees to radians. </summary>
|
|
||||||
/// <param name="degrees"> The degree value. </param>
|
|
||||||
/// <returns> The value as radians. </returns>
|
|
||||||
public static double DegreesToRadians(double degrees)
|
|
||||||
{
|
|
||||||
return degrees * Math.PI / 180.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Converts radians to degrees. </summary>
|
|
||||||
/// <param name="radians"> The radian value. </param>
|
|
||||||
/// <returns> The value as degrees. </returns>
|
|
||||||
public static double RadiansToDegrees(double radians)
|
|
||||||
{
|
|
||||||
return radians * 180.0 / Math.PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Calculates the hexadecimal hash. </summary>
|
||||||
|
/// <param name="str"> The string to be hashed. </param>
|
||||||
|
/// <returns> The calculated 16 character hexadecimal hash. </returns>
|
||||||
|
public static string CalculateHexHash(string str)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(str);
|
||||||
|
|
||||||
|
var md5Hasher = MD5.Create();
|
||||||
|
var hash = md5Hasher.ComputeHash(bytes);
|
||||||
|
|
||||||
|
var hash2 = new char[16];
|
||||||
|
|
||||||
|
// Note that here we are wasting bits of hash!
|
||||||
|
// But it isn't really important, because hash.Length == 32
|
||||||
|
for (var i = 0; i < hash2.Length; i++)
|
||||||
|
{
|
||||||
|
hash2[i] = CalculationHelper.chars[hash[i] % CalculationHelper.chars.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(hash2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the distance between two points on earth using the <c>haversine</c> formula.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="latitudeOrigin"> The latitude origin. </param>
|
||||||
|
/// <param name="longitudeOrigin"> The longitude origin. </param>
|
||||||
|
/// <param name="latitudeDestination"> The latitude destination. </param>
|
||||||
|
/// <param name="longitudeDestination"> The longitude destination. </param>
|
||||||
|
/// <param name="measurementSystem"> (Optional) The measurement system. </param>
|
||||||
|
/// <returns> The distance. </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
|
||||||
|
/// to calculate the "as-the-crow-flies" distance between two points on earth.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="GetDistance(double, double, double, double, MeasurementSystem)" />
|
||||||
|
public static decimal GetDistance(decimal latitudeOrigin, decimal longitudeOrigin, decimal latitudeDestination,
|
||||||
|
decimal longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
|
||||||
|
{
|
||||||
|
var latitudeOriginalDouble = Convert.ToDouble(latitudeOrigin);
|
||||||
|
var longitudeOriginDouble = Convert.ToDouble(longitudeOrigin);
|
||||||
|
var latitudeDestinationDouble = Convert.ToDouble(latitudeDestination);
|
||||||
|
var longitudeDestinationDouble = Convert.ToDouble(longitudeDestination);
|
||||||
|
|
||||||
|
var result = GetDistance(latitudeOriginalDouble, longitudeOriginDouble, latitudeDestinationDouble,
|
||||||
|
longitudeDestinationDouble, measurementSystem);
|
||||||
|
|
||||||
|
return Convert.ToDecimal(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the distance between two points on earth using the <c>haversine</c> formula. </summary>
|
||||||
|
/// <param name="latitudeOrigin"> The latitude of the origin. </param>
|
||||||
|
/// <param name="longitudeOrigin"> The longitude of the origin. </param>
|
||||||
|
/// <param name="latitudeDestination"> The latitude destination. </param>
|
||||||
|
/// <param name="longitudeDestination"> The longitude destination. </param>
|
||||||
|
/// <param name="measurementSystem"> (Optional) The measurement system. </param>
|
||||||
|
/// <returns> The distance. </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses the <a href="https://www.movable-type.co.uk/scripts/latlong.html">haversine</a> formula
|
||||||
|
/// to calculate the "as-the-crow-flies" distance between two points on earth.
|
||||||
|
/// </remarks>
|
||||||
|
public static double GetDistance(double latitudeOrigin, double longitudeOrigin, double latitudeDestination,
|
||||||
|
double longitudeDestination, MeasurementSystem measurementSystem = MeasurementSystem.Imperial)
|
||||||
|
{
|
||||||
|
var thetaLatitude = DegreesToRadians(latitudeOrigin);
|
||||||
|
var thetaLongitude = DegreesToRadians(longitudeOrigin);
|
||||||
|
var deltaLatitude = DegreesToRadians(latitudeDestination - latitudeOrigin);
|
||||||
|
var deltaLongitude = DegreesToRadians(longitudeDestination - longitudeOrigin);
|
||||||
|
|
||||||
|
var haversineTheta = Math.Sin(deltaLatitude / 2) * Math.Sin(deltaLatitude / 2) + Math.Cos(thetaLatitude) *
|
||||||
|
Math.Cos(thetaLongitude) * Math.Sin(deltaLongitude / 2) * Math.Sin(deltaLongitude / 2);
|
||||||
|
var angularDistance = 2 * Math.Atan2(Math.Sqrt(haversineTheta), Math.Sqrt(1 - haversineTheta));
|
||||||
|
|
||||||
|
var distance = CalculationHelper.EARTH_RADIUS_KILOMETERS * angularDistance;
|
||||||
|
|
||||||
|
if (measurementSystem == MeasurementSystem.Imperial)
|
||||||
|
{
|
||||||
|
return KilometersToMiles(distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Converts kilometers to miles. </summary>
|
||||||
|
/// <param name="kilometers"> The value in kilometers. </param>
|
||||||
|
/// <returns> The value in miles. </returns>
|
||||||
|
public static double KilometersToMiles(double kilometers)
|
||||||
|
{
|
||||||
|
return kilometers * CalculationHelper.MILES_PER_KILOMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Converts miles to kilometers. </summary>
|
||||||
|
/// <param name="miles"> The value in miles. </param>
|
||||||
|
/// <returns> The value in kilometers. </returns>
|
||||||
|
public static double MilesToKilometers(double miles)
|
||||||
|
{
|
||||||
|
return miles / CalculationHelper.MILES_PER_KILOMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Convers degrees to radians. </summary>
|
||||||
|
/// <param name="degrees"> The degree value. </param>
|
||||||
|
/// <returns> The value as radians. </returns>
|
||||||
|
public static double DegreesToRadians(double degrees)
|
||||||
|
{
|
||||||
|
return degrees * Math.PI / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Converts radians to degrees. </summary>
|
||||||
|
/// <param name="radians"> The radian value. </param>
|
||||||
|
/// <returns> The value as degrees. </returns>
|
||||||
|
public static double RadiansToDegrees(double radians)
|
||||||
|
{
|
||||||
|
return radians * 180.0 / Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,106 +1,102 @@
|
||||||
using System;
|
using System.IO.Compression;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
/// <summary> A class that contains methods for managing data compression. </summary>
|
||||||
|
public static class CompressionHelper
|
||||||
{
|
{
|
||||||
/// <summary> A class that contains methods for managing data compression. </summary>
|
#region Members
|
||||||
public static class CompressionHelper
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Compresses a given object using the <c>gzip</c> algorithm. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A byte array representing the compressed object in <c>gzip</c> format. </returns>
|
||||||
|
public static byte[] Compress(object obj)
|
||||||
{
|
{
|
||||||
#region Members
|
var bytes = SerializationHelper.SerializeToBytes(obj);
|
||||||
|
|
||||||
//
|
try
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Compresses a given object using the <c>gzip</c> algorithm. </summary>
|
|
||||||
/// <param name="obj"> The object. </param>
|
|
||||||
/// <returns> A byte array representing the compressed object in <c>gzip</c> format. </returns>
|
|
||||||
public static byte[] Compress(object obj)
|
|
||||||
{
|
{
|
||||||
var bytes = SerializationHelper.SerializeToBytes(obj);
|
using (var inputStream = new MemoryStream(bytes))
|
||||||
|
using (var outputStream = new MemoryStream())
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using (var inputStream = new MemoryStream(bytes))
|
using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress))
|
||||||
using (var outputStream = new MemoryStream())
|
|
||||||
{
|
{
|
||||||
using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress))
|
inputStream.Position = 0;
|
||||||
{
|
inputStream.CopyTo(gzipStream);
|
||||||
inputStream.Position = 0;
|
gzipStream.Flush();
|
||||||
inputStream.CopyTo(gzipStream);
|
|
||||||
gzipStream.Flush();
|
|
||||||
}
|
|
||||||
return outputStream.ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
return outputStream.ToArray();
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not compress the object.");
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary> Compresses a given object to a string using <c>base64</c> encoding of <c>gzip</c> format. </summary>
|
|
||||||
/// <param name="obj"> The object. </param>
|
|
||||||
/// <returns> A string in <c>base64</c> encoding. </returns>
|
|
||||||
public static string CompressToString(object obj)
|
|
||||||
{
|
{
|
||||||
var bytes = Compress(obj);
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not compress the object.");
|
||||||
return Convert.ToBase64String(bytes);
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Decompresses a given <c>base64</c> encoded string of <c>gzip</c> format. </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="encodedString"> The <c>base64</c> encoded <c>gzip</c> string. </param>
|
|
||||||
/// <returns> A <typeparamref name="T"/> typed object. </returns>
|
|
||||||
public static T Decompress<T>(string encodedString)
|
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(encodedString);
|
|
||||||
return Decompress<T>(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Decompresses a given compressed <c>gzip</c> byte stream. </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="byteStream"> The compressed byte stream. </param>
|
|
||||||
/// <returns> A <typeparamref name="T"/> typed object. </returns>
|
|
||||||
public static T Decompress<T>(byte[] byteStream)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var inputStream = new MemoryStream(byteStream))
|
|
||||||
using (var outputStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress))
|
|
||||||
{
|
|
||||||
gzipStream.CopyTo(outputStream);
|
|
||||||
}
|
|
||||||
var bytes = outputStream.ToArray();
|
|
||||||
return SerializationHelper.Deserialize<T>(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not decompress the deflated object.");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Decompresses the given <c>base64</c> string in <c>gzip</c> format. </summary>
|
|
||||||
/// <param name="compressed"> The compressed string. </param>
|
|
||||||
/// <returns> A decomressed string. </returns>
|
|
||||||
public static string DecompressToString(string compressed)
|
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(compressed);
|
|
||||||
|
|
||||||
return Decompress<string>(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Compresses a given object to a string using <c>base64</c> encoding of <c>gzip</c> format. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A string in <c>base64</c> encoding. </returns>
|
||||||
|
public static string CompressToString(object obj)
|
||||||
|
{
|
||||||
|
var bytes = Compress(obj);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses a given <c>base64</c> encoded string of <c>gzip</c> format. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="encodedString"> The <c>base64</c> encoded <c>gzip</c> string. </param>
|
||||||
|
/// <returns> A <typeparamref name="T" /> typed object. </returns>
|
||||||
|
public static T Decompress<T>(string encodedString)
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(encodedString);
|
||||||
|
return Decompress<T>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses a given compressed <c>gzip</c> byte stream. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="byteStream"> The compressed byte stream. </param>
|
||||||
|
/// <returns> A <typeparamref name="T" /> typed object. </returns>
|
||||||
|
public static T Decompress<T>(byte[] byteStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var inputStream = new MemoryStream(byteStream))
|
||||||
|
using (var outputStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress))
|
||||||
|
{
|
||||||
|
gzipStream.CopyTo(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = outputStream.ToArray();
|
||||||
|
return SerializationHelper.Deserialize<T>(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not decompress the deflated object.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses the given <c>base64</c> string in <c>gzip</c> format. </summary>
|
||||||
|
/// <param name="compressed"> The compressed string. </param>
|
||||||
|
/// <returns> A decomressed string. </returns>
|
||||||
|
public static string DecompressToString(string compressed)
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(compressed);
|
||||||
|
|
||||||
|
return Decompress<string>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,44 +1,36 @@
|
||||||
using System;
|
namespace CapyKit.Helpers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices.ObjectiveC;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
public class EncryptionHelper
|
||||||
{
|
{
|
||||||
public class EncryptionHelper
|
#region Members
|
||||||
|
|
||||||
|
private string encryptionKey;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public EncryptionHelper(string encryptionKey)
|
||||||
{
|
{
|
||||||
#region Members
|
this.encryptionKey = encryptionKey;
|
||||||
|
|
||||||
private string encryptionKey;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public EncryptionHelper(string encryptionKey)
|
|
||||||
{
|
|
||||||
this.encryptionKey = encryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IEncryptionAlgorithm
|
#endregion
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
string AlgorithmName { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
EncryptedValue<T> Encrypt<T>(object obj, params object[] args);
|
|
||||||
|
|
||||||
T Decrypt<T>(EncryptedValue<T> encryptedValue, params object[] args);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IEncryptionAlgorithm
|
||||||
|
{
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
string AlgorithmName { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
EncryptedValue<T> Encrypt<T>(object obj, params object[] args);
|
||||||
|
|
||||||
|
T Decrypt<T>(EncryptedValue<T> encryptedValue, params object[] args);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ using CapyKit.Extensions;
|
||||||
|
|
||||||
namespace CapyKit.Helpers;
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
/// <summary> A class that contains methods for managing <see cref="Enum"/> objects. </summary>
|
/// <summary> A class that contains methods for managing <see cref="Enum" /> objects. </summary>
|
||||||
public static class EnumerationHelper
|
public static class EnumerationHelper
|
||||||
{
|
{
|
||||||
#region Methods
|
#region Methods
|
||||||
|
|
@ -18,7 +18,7 @@ public static class EnumerationHelper
|
||||||
return (T)(object)value;
|
return (T)(object)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return default(T);
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets enumeration value from a string value. </summary>
|
/// <summary> Gets enumeration value from a string value. </summary>
|
||||||
|
|
@ -39,16 +39,16 @@ public static class EnumerationHelper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return default(T);
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An Enum extension method that deconstructs the given enumeration into a dictionary of its
|
/// An Enum extension method that deconstructs the given enumeration into a dictionary of its
|
||||||
/// integer value and its name.
|
/// integer value and its name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// An enumerator that allows foreach to be used to process deconstruct in this collection.
|
/// An enumerator that allows foreach to be used to process deconstruct in this collection.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static IEnumerable<T> Deconstruct<T>() where T : struct, Enum
|
public static IEnumerable<T> Deconstruct<T>() where T : struct, Enum
|
||||||
{
|
{
|
||||||
|
|
@ -62,13 +62,13 @@ public static class EnumerationHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An Enum extension method that deconstructs the given enumeration into a dictionary of its
|
/// An Enum extension method that deconstructs the given enumeration into a dictionary of its
|
||||||
/// integer value and its name.
|
/// integer value and its name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// An enumerator that allows foreach to be used to process deconstruct in this collection.
|
/// An enumerator that allows foreach to be used to process deconstruct in this collection.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static IEnumerable<T> Deconstruct<T>(this T enumeration) where T : struct, Enum
|
public static IEnumerable<T> Deconstruct<T>(this T enumeration) where T : struct, Enum
|
||||||
{
|
{
|
||||||
|
|
@ -78,8 +78,8 @@ public static class EnumerationHelper
|
||||||
/// <summary> An Enum extension method that deconstruct non default. </summary>
|
/// <summary> An Enum extension method that deconstruct non default. </summary>
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// An enumerator that allows foreach to be used to process deconstruct non default in this
|
/// An enumerator that allows foreach to be used to process deconstruct non default in this
|
||||||
/// collection.
|
/// collection.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static IEnumerable<T> DeconstructNonDefault<T>() where T : struct, Enum
|
public static IEnumerable<T> DeconstructNonDefault<T>() where T : struct, Enum
|
||||||
{
|
{
|
||||||
|
|
@ -90,8 +90,8 @@ public static class EnumerationHelper
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
/// <param name="enumeration"> The enumeration to act on. </param>
|
/// <param name="enumeration"> The enumeration to act on. </param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// An enumerator that allows foreach to be used to process deconstruct non default in this
|
/// An enumerator that allows foreach to be used to process deconstruct non default in this
|
||||||
/// collection.
|
/// collection.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static IEnumerable<T> DeconstructNonDefault<T>(this T enumeration) where T : struct, Enum
|
public static IEnumerable<T> DeconstructNonDefault<T>(this T enumeration) where T : struct, Enum
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,225 +1,238 @@
|
||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
/// <summary> A class that contains methods for managing key creation and validation against a master key. </summary>
|
||||||
|
public class KeyHelper
|
||||||
{
|
{
|
||||||
/// <summary> A class that contains methods for managing key creation and validation against a master key. </summary>
|
/// <summary> The master key accessor function. </summary>
|
||||||
public class KeyHelper
|
private Func<byte[]> masterKeyAccessor;
|
||||||
|
|
||||||
|
/// <summary> Number of parts accessor function. </summary>
|
||||||
|
private Func<int> numPartsAccessor;
|
||||||
|
|
||||||
|
/// <summary> The salt size accessor function. </summary>
|
||||||
|
private Func<int> saltSizeAccessor;
|
||||||
|
|
||||||
|
/// <summary> Sets the master key. </summary>
|
||||||
|
/// <param name="accessor"> The accessor function. </param>
|
||||||
|
public void SetMasterKeyAccessor(Func<byte[]> accessor)
|
||||||
{
|
{
|
||||||
/// <summary> The master key accessor function. </summary>
|
this.masterKeyAccessor = accessor;
|
||||||
private Func<byte[]> masterKeyAccessor;
|
}
|
||||||
/// <summary> The salt size accessor function. </summary>
|
|
||||||
private Func<int> saltSizeAccessor;
|
|
||||||
/// <summary> Number of parts accessor function. </summary>
|
|
||||||
private Func<int> numPartsAccessor;
|
|
||||||
|
|
||||||
/// <summary> Sets the master key. </summary>
|
/// <summary> Gets the master key. </summary>
|
||||||
/// <param name="accessor"> The accessor function. </param>
|
/// <exception cref="InvalidOperationException">
|
||||||
public void SetMasterKeyAccessor(Func<byte[]> accessor)
|
/// Thrown when the requested operation is invalid.
|
||||||
|
/// </exception>
|
||||||
|
/// <returns> An array of byte. </returns>
|
||||||
|
public byte[] GetMasterKey()
|
||||||
|
{
|
||||||
|
if (this.masterKeyAccessor == null)
|
||||||
{
|
{
|
||||||
this.masterKeyAccessor = accessor;
|
var errorMessage = "Master key accessor not set.";
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
||||||
|
throw new InvalidOperationException(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets the master key. </summary>
|
return this.masterKeyAccessor();
|
||||||
/// <exception cref="InvalidOperationException">
|
}
|
||||||
/// Thrown when the requested operation is invalid.
|
|
||||||
/// </exception>
|
/// <summary> Sets the salt size (in bytes). Default is 4. </summary>
|
||||||
/// <returns> An array of byte. </returns>
|
/// <param name="accessor"> The accessor function. </param>
|
||||||
public byte[] GetMasterKey()
|
public void SetSaltSizeAccessor(Func<int> accessor)
|
||||||
|
{
|
||||||
|
this.saltSizeAccessor = accessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the salt size. </summary>
|
||||||
|
/// <returns> The salt size. </returns>
|
||||||
|
public int GetSaltSize()
|
||||||
|
{
|
||||||
|
return this.saltSizeAccessor != null ? this.saltSizeAccessor() : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Set and get the number of parts for the formatted key. Default is 2. </summary>
|
||||||
|
/// <param name="accessor"> The accessor function. </param>
|
||||||
|
public void SetNumPartsAccessor(Func<int> accessor)
|
||||||
|
{
|
||||||
|
this.numPartsAccessor = accessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the number parts. </summary>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when one or more arguments have unsupported or illegal values.
|
||||||
|
/// </exception>
|
||||||
|
/// <returns> The number parts. </returns>
|
||||||
|
public int GetNumParts()
|
||||||
|
{
|
||||||
|
var parts = this.numPartsAccessor != null ? this.numPartsAccessor() : 2;
|
||||||
|
if (parts < 2)
|
||||||
{
|
{
|
||||||
if (this.masterKeyAccessor == null)
|
var errorMessage = "Number of parts must be 2 or more.";
|
||||||
{
|
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
||||||
var errorMessage = "Master key accessor not set.";
|
throw new ArgumentException(errorMessage);
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
|
||||||
throw new InvalidOperationException(errorMessage);
|
|
||||||
}
|
|
||||||
return this.masterKeyAccessor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Sets the salt size (in bytes). Default is 4. </summary>
|
return parts;
|
||||||
/// <param name="accessor"> The accessor function. </param>
|
}
|
||||||
public void SetSaltSizeAccessor(Func<int> accessor)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes an HMAC-SHA256 over the salt using the master key and truncates it to the same
|
||||||
|
/// number of bytes as the salt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="salt"> The salt. </param>
|
||||||
|
/// <returns> The calculated signature. </returns>
|
||||||
|
private byte[] ComputeSignature(byte[] salt)
|
||||||
|
{
|
||||||
|
var masterKey = GetMasterKey();
|
||||||
|
using (var hmac = new HMACSHA256(masterKey))
|
||||||
{
|
{
|
||||||
this.saltSizeAccessor = accessor;
|
var hash = hmac.ComputeHash(salt);
|
||||||
}
|
var sigLength = salt.Length;
|
||||||
|
var signature = new byte[sigLength];
|
||||||
/// <summary> Gets the salt size. </summary>
|
Array.Copy(hash, signature, sigLength);
|
||||||
/// <returns> The salt size. </returns>
|
return signature;
|
||||||
public int GetSaltSize()
|
|
||||||
{
|
|
||||||
return this.saltSizeAccessor != null ? this.saltSizeAccessor() : 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Set and get the number of parts for the formatted key. Default is 2. </summary>
|
|
||||||
/// <param name="accessor"> The accessor function. </param>
|
|
||||||
public void SetNumPartsAccessor(Func<int> accessor)
|
|
||||||
{
|
|
||||||
this.numPartsAccessor = accessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets the number parts. </summary>
|
|
||||||
/// <exception cref="ArgumentException">
|
|
||||||
/// Thrown when one or more arguments have unsupported or illegal values.
|
|
||||||
/// </exception>
|
|
||||||
/// <returns> The number parts. </returns>
|
|
||||||
public int GetNumParts()
|
|
||||||
{
|
|
||||||
int parts = this.numPartsAccessor != null ? this.numPartsAccessor() : 2;
|
|
||||||
if (parts < 2)
|
|
||||||
{
|
|
||||||
var errorMessage = "Number of parts must be 2 or more.";
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
|
||||||
throw new ArgumentException(errorMessage);
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes an HMAC-SHA256 over the salt using the master key and truncates it to the same
|
|
||||||
/// number of bytes as the salt.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="salt"> The salt. </param>
|
|
||||||
/// <returns> The calculated signature. </returns>
|
|
||||||
private byte[] ComputeSignature(byte[] salt)
|
|
||||||
{
|
|
||||||
byte[] masterKey = GetMasterKey();
|
|
||||||
using (var hmac = new HMACSHA256(masterKey))
|
|
||||||
{
|
|
||||||
byte[] hash = hmac.ComputeHash(salt);
|
|
||||||
int sigLength = salt.Length;
|
|
||||||
byte[] signature = new byte[sigLength];
|
|
||||||
Array.Copy(hash, signature, sigLength);
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Converts a byte array to a hex string. </summary>
|
|
||||||
/// <param name="bytes"> The bytes. </param>
|
|
||||||
/// <returns> A string. </returns>
|
|
||||||
private string BytesToHex(byte[] bytes)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder(bytes.Length * 2);
|
|
||||||
foreach (var b in bytes)
|
|
||||||
{
|
|
||||||
sb.Append(b.ToString("X2"));
|
|
||||||
}
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Converts a hex string back to a byte array. </summary>
|
|
||||||
/// <exception cref="ArgumentException">
|
|
||||||
/// Thrown when one or more arguments have unsupported or illegal values.
|
|
||||||
/// </exception>
|
|
||||||
/// <param name="hex"> The hexadecimal. </param>
|
|
||||||
/// <returns> A byte[]. </returns>
|
|
||||||
private byte[] HexToBytes(string hex)
|
|
||||||
{
|
|
||||||
if (hex.Length % 2 != 0)
|
|
||||||
{
|
|
||||||
var errorMessage = "Invalid hex string.";
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
|
||||||
throw new ArgumentException(errorMessage);
|
|
||||||
}
|
|
||||||
byte[] bytes = new byte[hex.Length / 2];
|
|
||||||
for (int i = 0; i < hex.Length; i += 2)
|
|
||||||
{
|
|
||||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Formats the given hex string into the desired number of parts (separated by dashes).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hex"> The hexadecimal. </param>
|
|
||||||
/// <returns> The formatted key. </returns>
|
|
||||||
private string FormatKey(string hex)
|
|
||||||
{
|
|
||||||
int parts = GetNumParts();
|
|
||||||
int totalLength = hex.Length;
|
|
||||||
int baseLength = totalLength / parts;
|
|
||||||
int remainder = totalLength % parts;
|
|
||||||
|
|
||||||
StringBuilder formatted = new StringBuilder();
|
|
||||||
int currentIndex = 0;
|
|
||||||
for (int i = 0; i < parts; i++)
|
|
||||||
{
|
|
||||||
// Distribute any extra characters across the first few groups.
|
|
||||||
int groupLength = baseLength + (i < remainder ? 1 : 0);
|
|
||||||
formatted.Append(hex.Substring(currentIndex, groupLength));
|
|
||||||
currentIndex += groupLength;
|
|
||||||
if (i < parts - 1)
|
|
||||||
formatted.Append("-");
|
|
||||||
}
|
|
||||||
return formatted.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Generates a random key. </summary>
|
|
||||||
/// <returns> The key. </returns>
|
|
||||||
public string GenerateKey()
|
|
||||||
{
|
|
||||||
int saltSize = GetSaltSize();
|
|
||||||
byte[] salt = new byte[saltSize];
|
|
||||||
using (var rng = RandomNumberGenerator.Create())
|
|
||||||
{
|
|
||||||
rng.GetBytes(salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] signature = ComputeSignature(salt);
|
|
||||||
string saltHex = BytesToHex(salt);
|
|
||||||
string signatureHex = BytesToHex(signature);
|
|
||||||
string combinedHex = saltHex + signatureHex;
|
|
||||||
return FormatKey(combinedHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Validates the provided key. </summary>
|
|
||||||
/// <param name="providedKey"> The provided key. </param>
|
|
||||||
/// <returns> True if it succeeds, false if it fails. </returns>
|
|
||||||
/// <seealso cref="GetMasterKey"/>
|
|
||||||
/// <seealso cref="SetMasterKeyAccessor(Func{byte[]})"/>
|
|
||||||
/// <seealso cref="masterKeyAccessor"/>
|
|
||||||
public bool ValidateKey(string providedKey)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(providedKey))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Remove dashes.
|
|
||||||
string cleanedKey = providedKey.Replace("-", "");
|
|
||||||
int saltSize = GetSaltSize();
|
|
||||||
int expectedTotalHexLength = 4 * saltSize; // salt (2*saltSize) + signature (2*saltSize)
|
|
||||||
if (cleanedKey.Length != expectedTotalHexLength)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
string saltHex = cleanedKey.Substring(0, 2 * saltSize);
|
|
||||||
string signatureHex = cleanedKey.Substring(2 * saltSize);
|
|
||||||
|
|
||||||
byte[] salt;
|
|
||||||
byte[] providedSignature;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
salt = HexToBytes(saltHex);
|
|
||||||
providedSignature = HexToBytes(signatureHex);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] expectedSignature = ComputeSignature(salt);
|
|
||||||
if (expectedSignature.Length != providedSignature.Length)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < expectedSignature.Length; i++)
|
|
||||||
{
|
|
||||||
if (expectedSignature[i] != providedSignature[i])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Converts a byte array to a hex string. </summary>
|
||||||
|
/// <param name="bytes"> The bytes. </param>
|
||||||
|
/// <returns> A string. </returns>
|
||||||
|
private string BytesToHex(byte[] bytes)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(bytes.Length * 2);
|
||||||
|
foreach (var b in bytes)
|
||||||
|
{
|
||||||
|
sb.Append(b.ToString("X2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Converts a hex string back to a byte array. </summary>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Thrown when one or more arguments have unsupported or illegal values.
|
||||||
|
/// </exception>
|
||||||
|
/// <param name="hex"> The hexadecimal. </param>
|
||||||
|
/// <returns> A byte[]. </returns>
|
||||||
|
private byte[] HexToBytes(string hex)
|
||||||
|
{
|
||||||
|
if (hex.Length % 2 != 0)
|
||||||
|
{
|
||||||
|
var errorMessage = "Invalid hex string.";
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, errorMessage);
|
||||||
|
throw new ArgumentException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = new byte[hex.Length / 2];
|
||||||
|
for (var i = 0; i < hex.Length; i += 2)
|
||||||
|
{
|
||||||
|
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats the given hex string into the desired number of parts (separated by dashes).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hex"> The hexadecimal. </param>
|
||||||
|
/// <returns> The formatted key. </returns>
|
||||||
|
private string FormatKey(string hex)
|
||||||
|
{
|
||||||
|
var parts = GetNumParts();
|
||||||
|
var totalLength = hex.Length;
|
||||||
|
var baseLength = totalLength / parts;
|
||||||
|
var remainder = totalLength % parts;
|
||||||
|
|
||||||
|
var formatted = new StringBuilder();
|
||||||
|
var currentIndex = 0;
|
||||||
|
for (var i = 0; i < parts; i++)
|
||||||
|
{
|
||||||
|
// Distribute any extra characters across the first few groups.
|
||||||
|
var groupLength = baseLength + (i < remainder ? 1 : 0);
|
||||||
|
formatted.Append(hex.Substring(currentIndex, groupLength));
|
||||||
|
currentIndex += groupLength;
|
||||||
|
if (i < parts - 1)
|
||||||
|
{
|
||||||
|
formatted.Append("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Generates a random key. </summary>
|
||||||
|
/// <returns> The key. </returns>
|
||||||
|
public string GenerateKey()
|
||||||
|
{
|
||||||
|
var saltSize = GetSaltSize();
|
||||||
|
var salt = new byte[saltSize];
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
rng.GetBytes(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var signature = ComputeSignature(salt);
|
||||||
|
var saltHex = BytesToHex(salt);
|
||||||
|
var signatureHex = BytesToHex(signature);
|
||||||
|
var combinedHex = saltHex + signatureHex;
|
||||||
|
return FormatKey(combinedHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Validates the provided key. </summary>
|
||||||
|
/// <param name="providedKey"> The provided key. </param>
|
||||||
|
/// <returns> True if it succeeds, false if it fails. </returns>
|
||||||
|
/// <seealso cref="GetMasterKey" />
|
||||||
|
/// <seealso cref="SetMasterKeyAccessor(Func{byte[]})" />
|
||||||
|
/// <seealso cref="masterKeyAccessor" />
|
||||||
|
public bool ValidateKey(string providedKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(providedKey))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dashes.
|
||||||
|
var cleanedKey = providedKey.Replace("-", "");
|
||||||
|
var saltSize = GetSaltSize();
|
||||||
|
var expectedTotalHexLength = 4 * saltSize; // salt (2*saltSize) + signature (2*saltSize)
|
||||||
|
if (cleanedKey.Length != expectedTotalHexLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var saltHex = cleanedKey.Substring(0, 2 * saltSize);
|
||||||
|
var signatureHex = cleanedKey.Substring(2 * saltSize);
|
||||||
|
|
||||||
|
byte[] salt;
|
||||||
|
byte[] providedSignature;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
salt = HexToBytes(saltHex);
|
||||||
|
providedSignature = HexToBytes(signatureHex);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedSignature = ComputeSignature(salt);
|
||||||
|
if (expectedSignature.Length != providedSignature.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < expectedSignature.Length; i++)
|
||||||
|
{
|
||||||
|
if (expectedSignature[i] != providedSignature[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,28 @@
|
||||||
using System;
|
using System.Text.RegularExpressions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for handling text transformations.
|
||||||
|
/// </summary>
|
||||||
|
public class LanguageHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Methods
|
||||||
/// Helper class for handling text transformations.
|
|
||||||
/// </summary>
|
/// <summary> Converts camel case text to human readable text. </summary>
|
||||||
public class LanguageHelper
|
/// <remarks>
|
||||||
|
/// Camel case is a naming convention for identifiers in which the first letter of each word is
|
||||||
|
/// capitalized.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="value"> The value. </param>
|
||||||
|
/// <returns> A string in human readable format. </returns>
|
||||||
|
public static string CamelCaseToHumanReadable(string value)
|
||||||
{
|
{
|
||||||
#region Methods
|
var regex = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])",
|
||||||
|
RegexOptions.IgnorePatternWhitespace);
|
||||||
|
|
||||||
/// <summary> Converts camel case text to human readable text. </summary>
|
return regex.Replace(value, " ");
|
||||||
/// <remarks>
|
|
||||||
/// Camel case is a naming convention for identifiers in which the first letter of each word is
|
|
||||||
/// capitalized.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="value"> The value. </param>
|
|
||||||
/// <returns> A string in human readable format. </returns>
|
|
||||||
public static string CamelCaseToHumanReadable(string value)
|
|
||||||
{
|
|
||||||
var regex = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
|
|
||||||
|
|
||||||
return regex.Replace(value, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,8 @@ namespace CapyKit.Helpers;
|
||||||
public static class PropertyHelper<T>
|
public static class PropertyHelper<T>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the reflected <typeparamref name="TProperty"/> value of an instanced
|
/// Gets the reflected <typeparamref name="TProperty" /> value of an instanced
|
||||||
/// <typeparamref name="T"/> object.
|
/// <typeparamref name="T" /> object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="obj"> The instanced object. </param>
|
/// <param name="obj"> The instanced object. </param>
|
||||||
|
|
@ -22,12 +22,12 @@ public static class PropertyHelper<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the reflected <see cref="PropertyInfo"/> of an instanced <typeparamref name="T"/>
|
/// Gets the reflected <see cref="PropertyInfo" /> of an instanced <typeparamref name="T" />
|
||||||
/// object.
|
/// object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj"> The instanced object. </param>
|
/// <param name="obj"> The instanced object. </param>
|
||||||
/// <param name="propertyName"> Name of the property. </param>
|
/// <param name="propertyName"> Name of the property. </param>
|
||||||
/// <returns> The reflected <see cref="PropertyInfo"/>. </returns>
|
/// <returns> The reflected <see cref="PropertyInfo" />. </returns>
|
||||||
public static PropertyInfo GetProperty(T obj, string propertyName)
|
public static PropertyInfo GetProperty(T obj, string propertyName)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
|
|
@ -40,7 +40,10 @@ public static class PropertyHelper<T>
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets the reflected <typeparamref name="TProperty"/> value of an instanced <typeparamref name="T"/> object property value as described by a <paramref name="selector"/>. </summary>
|
/// <summary>
|
||||||
|
/// Gets the reflected <typeparamref name="TProperty" /> value of an instanced <typeparamref name="T" /> object
|
||||||
|
/// property value as described by a <paramref name="selector" />.
|
||||||
|
/// </summary>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="obj"> The instanced object. </param>
|
/// <param name="obj"> The instanced object. </param>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
|
|
@ -51,16 +54,18 @@ public static class PropertyHelper<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the reflected <see cref="PropertyInfo"/> of an <typeparamref name="T"/> object as
|
/// Gets the reflected <see cref="PropertyInfo" /> of an <typeparamref name="T" /> object as
|
||||||
/// described by a <paramref name="selector"/>.
|
/// described by a <paramref name="selector" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException"> Thrown when the <paramref name="selector"/> does
|
/// <exception cref="InvalidOperationException">
|
||||||
/// not return a <see cref="LambdaExpression.Body"/>
|
/// Thrown when the <paramref name="selector" /> does
|
||||||
/// of type
|
/// not return a <see cref="LambdaExpression.Body" />
|
||||||
/// <see cref="ExpressionType.MemberAccess"/>. </exception>
|
/// of type
|
||||||
|
/// <see cref="ExpressionType.MemberAccess" />.
|
||||||
|
/// </exception>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
/// <returns> The reflected <see cref="PropertyInfo"/>. </returns>
|
/// <returns> The reflected <see cref="PropertyInfo" />. </returns>
|
||||||
public static PropertyInfo GetProperty<TProperty>(Expression<Func<T, TProperty>> selector)
|
public static PropertyInfo GetProperty<TProperty>(Expression<Func<T, TProperty>> selector)
|
||||||
{
|
{
|
||||||
Expression body = selector;
|
Expression body = selector;
|
||||||
|
|
@ -82,7 +87,7 @@ public static class PropertyHelper<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an <see cref="EnumerationDescriptionAttribute"/> attribute from a given property.
|
/// Gets an <see cref="EnumerationDescriptionAttribute" /> attribute from a given property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
|
|
@ -94,7 +99,7 @@ public static class PropertyHelper<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <see cref="ValueFormatAttribute"/> attribute from the given property.
|
/// Gets a <see cref="ValueFormatAttribute" /> attribute from the given property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
|
|
@ -105,7 +110,7 @@ public static class PropertyHelper<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <see cref="ParameterAttribute"/> attribute from the given property.
|
/// Gets a <see cref="ParameterAttribute" /> attribute from the given property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
|
|
@ -115,11 +120,11 @@ public static class PropertyHelper<T>
|
||||||
return GetPropertyCustomAttribute<ParameterAttribute, TProperty>(selector);
|
return GetPropertyCustomAttribute<ParameterAttribute, TProperty>(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets custom <typeparamref name="TAttribute"/> <see cref="Attribute"/> from a given property. </summary>
|
/// <summary> Gets custom <typeparamref name="TAttribute" /> <see cref="Attribute" /> from a given property. </summary>
|
||||||
/// <typeparam name="TAttribute"> Type of the attribute. </typeparam>
|
/// <typeparam name="TAttribute"> Type of the attribute. </typeparam>
|
||||||
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
/// <typeparam name="TProperty"> Type of the property. </typeparam>
|
||||||
/// <param name="selector"> The property selector function that identifies the property. </param>
|
/// <param name="selector"> The property selector function that identifies the property. </param>
|
||||||
/// <returns> The custom <see cref="Attribute"/> of the property. </returns>
|
/// <returns> The custom <see cref="Attribute" /> of the property. </returns>
|
||||||
public static TAttribute GetPropertyCustomAttribute<TAttribute, TProperty>(Expression<Func<T, TProperty>> selector)
|
public static TAttribute GetPropertyCustomAttribute<TAttribute, TProperty>(Expression<Func<T, TProperty>> selector)
|
||||||
where TAttribute : Attribute
|
where TAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,485 +1,488 @@
|
||||||
using CapyKit.Attributes;
|
using System.Security.Cryptography;
|
||||||
using CapyKit.Extensions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection.Metadata.Ecma335;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using CapyKit.Attributes;
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
/// <summary> A class that contains methods for managing secure data processing and cryptography. </summary>
|
||||||
|
public class SecurityHelper
|
||||||
{
|
{
|
||||||
/// <summary> A class that contains methods for managing secure data processing and cryptography. </summary>
|
#region Members
|
||||||
public class SecurityHelper
|
|
||||||
|
/// <summary> A string of all the lower case characters. </summary>
|
||||||
|
internal const string LOWER_CASE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
/// <summary> A string of all the upper case characters. </summary>
|
||||||
|
internal const string UPPER_CASE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
/// <summary> A string of all the numeric characters. </summary>
|
||||||
|
internal const string NUMBER_CHARACTERS = "0123456789";
|
||||||
|
|
||||||
|
/// <summary> A string of the most common non-alphanumeric characters. </summary>
|
||||||
|
internal const string SPECIAL_CHARACTERS = "!@#$%&?+-_";
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public static bool CompareHashedPassword<T>(Password existingPassword, string password, string salt,
|
||||||
|
params object[] args)
|
||||||
|
where T : IPasswordAlgorithm
|
||||||
{
|
{
|
||||||
#region Members
|
return CompareHashedPassword<T>(Convert.ToBase64String(existingPassword.Hash), password, salt, args);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> A string of all the lower case characters. </summary>
|
/// <summary>
|
||||||
internal const string LOWER_CASE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
|
/// Compares an unencrypted <paramref name="password" /> with a stored, encrypted <paramref name="existingPassword" />.
|
||||||
|
/// This method uses the specified password algorithm type <typeparamref name="T" /> to retrieve the hashed version
|
||||||
|
/// of the <paramref name="password" /> and then compares it with the <paramref name="existingPassword" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the password hashing algorithm.</typeparam>
|
||||||
|
/// <param name="existingPassword">The existing, encrypted password.</param>
|
||||||
|
/// <param name="password">The unencrypted password to be compared.</param>
|
||||||
|
/// <param name="salt">The salt value used in password hashing.</param>
|
||||||
|
/// <param name="args">Additional arguments required for constructing the password algorithm instance.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if hash comparison succeeds, <see langword="false" /> if it fails.
|
||||||
|
/// </returns>
|
||||||
|
public static bool CompareHashedPassword<T>(Password existingPassword, string password, byte[] salt,
|
||||||
|
params object[] args)
|
||||||
|
where T : IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
return CompareHashedPassword<T>(existingPassword.Hash, password, salt, args);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> A string of all the upper case characters. </summary>
|
/// <summary>
|
||||||
internal const string UPPER_CASE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
/// Compares a plaintext password with a persisted Base64-encoded hash and salt.
|
||||||
|
/// </summary>
|
||||||
/// <summary> A string of all the numeric characters. </summary>
|
/// <typeparam name="T">The password hashing algorithm.</typeparam>
|
||||||
internal const string NUMBER_CHARACTERS = "0123456789";
|
/// <param name="storedHash">The Base64-encoded persisted hash.</param>
|
||||||
|
/// <param name="password">The plaintext password to verify.</param>
|
||||||
/// <summary> A string of the most common non-alphanumeric characters. </summary>
|
/// <param name="salt">The Base64-encoded persisted salt.</param>
|
||||||
internal const string SPECIAL_CHARACTERS = "!@#$%&?+-_";
|
/// <param name="args">Arguments used to construct the password algorithm.</param>
|
||||||
|
/// <returns><see langword="true" /> when the password matches the persisted hash.</returns>
|
||||||
#endregion Members
|
public static bool CompareHashedPassword<T>(string storedHash, string password, string salt,
|
||||||
|
params object[] args)
|
||||||
#region Methods
|
where T : IPasswordAlgorithm
|
||||||
|
{
|
||||||
public static bool CompareHashedPassword<T>(Password existingPassword, string password, string salt,
|
if (string.IsNullOrWhiteSpace(storedHash) || string.IsNullOrEmpty(password) ||
|
||||||
params object[] args)
|
string.IsNullOrWhiteSpace(salt))
|
||||||
where T : IPasswordAlgorithm
|
|
||||||
{
|
{
|
||||||
return CompareHashedPassword<T>(Convert.ToBase64String(existingPassword.Hash), password, salt, args);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
try
|
||||||
/// Compares an unencrypted <paramref name="password"/> with a stored, encrypted <paramref name="existingPassword"/>.
|
|
||||||
/// This method uses the specified password algorithm type <typeparamref name="T"/> to retrieve the hashed version
|
|
||||||
/// of the <paramref name="password"/> and then compares it with the <paramref name="existingPassword"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the password hashing algorithm.</typeparam>
|
|
||||||
/// <param name="existingPassword">The existing, encrypted password.</param>
|
|
||||||
/// <param name="password">The unencrypted password to be compared.</param>
|
|
||||||
/// <param name="salt">The salt value used in password hashing.</param>
|
|
||||||
/// <param name="args">Additional arguments required for constructing the password algorithm instance.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if hash comparison succeeds, <see langword="false"/> if it fails.
|
|
||||||
/// </returns>
|
|
||||||
public static bool CompareHashedPassword<T>(Password existingPassword, string password, byte[] salt,
|
|
||||||
params object[] args)
|
|
||||||
where T : IPasswordAlgorithm
|
|
||||||
{
|
{
|
||||||
return CompareHashedPassword<T>(existingPassword.Hash, password, salt, args);
|
return CompareHashedPassword<T>(Convert.FromBase64String(storedHash), password,
|
||||||
|
Convert.FromBase64String(salt), args);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares a hashed password with a plaintext password by using a specified hashing algorithm
|
||||||
|
/// <typeparamref name="T" />.
|
||||||
|
/// This method requires the existing hash, the plaintext password to compare, and the associated salt value.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the password hashing algorithm implementing <see cref="IPasswordAlgorithm" />.</typeparam>
|
||||||
|
/// <param name="storedHash">The stored hash of the password as a byte array.</param>
|
||||||
|
/// <param name="password">The plaintext password to verify.</param>
|
||||||
|
/// <param name="salt">The salt used during the password hashing process as a byte array.</param>
|
||||||
|
/// <param name="args">Optional arguments required for constructing the password hashing algorithm instance.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the hashed password matches the stored hash, <see langword="false" /> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public static bool CompareHashedPassword<T>(byte[] storedHash, string password, byte[] salt,
|
||||||
|
params object[] args)
|
||||||
|
where T : IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
if (storedHash == null || storedHash.Length == 0 || string.IsNullOrEmpty(password) || salt == null ||
|
||||||
|
salt.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var candidatePassword = GetPassword<T>(password, salt, args);
|
||||||
/// Compares a plaintext password with a persisted Base64-encoded hash and salt.
|
return candidatePassword is not null && candidatePassword.Hash.Length == storedHash.Length &&
|
||||||
/// </summary>
|
CryptographicOperations.FixedTimeEquals(storedHash, candidatePassword.Hash);
|
||||||
/// <typeparam name="T">The password hashing algorithm.</typeparam>
|
}
|
||||||
/// <param name="storedHash">The Base64-encoded persisted hash.</param>
|
|
||||||
/// <param name="password">The plaintext password to verify.</param>
|
public static bool CompareHashedPassword(Password existingPassword, string password, string salt,
|
||||||
/// <param name="salt">The Base64-encoded persisted salt.</param>
|
IPasswordAlgorithm algorithm, params object[] args)
|
||||||
/// <param name="args">Arguments used to construct the password algorithm.</param>
|
{
|
||||||
/// <returns><see langword="true"/> when the password matches the persisted hash.</returns>
|
var saltBytes = Convert.FromBase64String(salt);
|
||||||
public static bool CompareHashedPassword<T>(string storedHash, string password, string salt,
|
|
||||||
params object[] args)
|
return CompareHashedPassword(existingPassword, password, saltBytes, algorithm, args);
|
||||||
where T : IPasswordAlgorithm
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares an unencrypted <paramref name="password" /> with a stored, encrypted <paramref name="existingPassword" />.
|
||||||
|
/// This method uses the specified password hashing algorithm of type <typeparamref name="T" /> to hash the
|
||||||
|
/// <paramref name="password" />
|
||||||
|
/// and then compares it with the <paramref name="existingPassword" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the password hashing algorithm, implementing <see cref="IPasswordAlgorithm" />.</typeparam>
|
||||||
|
/// <param name="existingPassword">The stored, encrypted password to compare against.</param>
|
||||||
|
/// <param name="password">The plain text password to be compared.</param>
|
||||||
|
/// <param name="salt">The salt value used for hashing the plain text password.</param>
|
||||||
|
/// <param name="args">Optional additional arguments required for initializing the password hashing algorithm.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the password hashes match, otherwise <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt,
|
||||||
|
IPasswordAlgorithm algorithm, params object[] args)
|
||||||
|
{
|
||||||
|
var algorithmType = algorithm.GetType();
|
||||||
|
|
||||||
|
var providedPassword = typeof(SecurityHelper)
|
||||||
|
.GetMethod("GetPassword", new[] { typeof(string), typeof(byte[]), typeof(object[]) })
|
||||||
|
?.MakeGenericMethod(algorithmType)
|
||||||
|
?.Invoke(null, new object[] { password, salt, args });
|
||||||
|
|
||||||
|
return existingPassword.Equals(providedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Produces a deterministic SHA-256 digest for an opaque secret that must be looked up
|
||||||
|
/// without persisting the raw value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="secret">The raw opaque secret.</param>
|
||||||
|
/// <returns>The Base64-encoded digest.</returns>
|
||||||
|
public static string HashOpaqueSecret(string secret)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(secret);
|
||||||
|
return Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(secret)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="Password" /> object using the specified password and generates a random salt value.
|
||||||
|
/// Then it uses that salt to call the overloaded <see cref="GetPassword{T}(string, byte[], object[])" /> method with
|
||||||
|
/// the given password and
|
||||||
|
/// the generated salt as arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> The type of <see cref="IPasswordAlgorithm" /> implementation to use. </typeparam>
|
||||||
|
/// <param name="password"> The plaintext password to be hashed. </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm" /> implementation
|
||||||
|
/// instance.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new <see cref="Password" /> object with the given password and a randomly generated salt, as well as an
|
||||||
|
/// instance of <typeparamref name="T" /> created using any optional constructor arguments provided.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="SecurityHelper.SALT_SIZE" />
|
||||||
|
public static Password GetPassword<T>(string password, int saltSize, params object[] args)
|
||||||
|
where T : IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
var salt = GetRandomBytes(saltSize);
|
||||||
|
return GetPassword<T>(password, salt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="Password" /> object using the specified password, salt, and optional
|
||||||
|
/// constructor arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method uses reflection to find a constructor for the specified password algorithm type (
|
||||||
|
/// <typeparamref name="T" />).
|
||||||
|
/// It emits an error event if a suitable constructor is not found or if there is an error invoking the constructor.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="T">
|
||||||
|
/// The type of <see cref="IPasswordAlgorithm" /> implementation to use.
|
||||||
|
/// </typeparam>
|
||||||
|
/// <param name="password"> The plaintext password to be hashed. </param>
|
||||||
|
/// <param name="salt">
|
||||||
|
/// A random value used as an additional input to the one-way function that hashes data, a
|
||||||
|
/// password or passphrase. This is used to make each output different for the same input
|
||||||
|
/// thus adding security.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm" /> implementation
|
||||||
|
/// instance.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new <see cref="Password" /> object with the given password and salt, as well as an instance
|
||||||
|
/// of <typeparamref name="T" /> created using the provided constructor arguments.
|
||||||
|
/// </returns>
|
||||||
|
public static Password GetPassword<T>(string password, byte[] salt, params object[] args)
|
||||||
|
where T : IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
//var allArgs = args.Prepend(salt).Prepend(password).ToArray(); // Prepend in reverse order so that password precedes salt.
|
||||||
|
var argTypes = args.Select(arg => arg.GetType()).ToArray();
|
||||||
|
var algorithmConstructor = typeof(T).GetConstructor(argTypes);
|
||||||
|
if (algorithmConstructor == null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(storedHash) || string.IsNullOrEmpty(password) ||
|
CapyEventReporter.EmitEvent(EventLevel.Error,
|
||||||
string.IsNullOrWhiteSpace(salt))
|
"Cannot find a constructor for {0} that matches the given arguments: {1}",
|
||||||
{
|
args: new[]
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return CompareHashedPassword<T>(Convert.FromBase64String(storedHash), password,
|
|
||||||
Convert.FromBase64String(salt), args);
|
|
||||||
}
|
|
||||||
catch (FormatException)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares a hashed password with a plaintext password by using a specified hashing algorithm <typeparamref name="T"/>.
|
|
||||||
/// This method requires the existing hash, the plaintext password to compare, and the associated salt value.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the password hashing algorithm implementing <see cref="IPasswordAlgorithm"/>.</typeparam>
|
|
||||||
/// <param name="storedHash">The stored hash of the password as a byte array.</param>
|
|
||||||
/// <param name="password">The plaintext password to verify.</param>
|
|
||||||
/// <param name="salt">The salt used during the password hashing process as a byte array.</param>
|
|
||||||
/// <param name="args">Optional arguments required for constructing the password hashing algorithm instance.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if the hashed password matches the stored hash, <see langword="false"/> otherwise.
|
|
||||||
/// </returns>
|
|
||||||
public static bool CompareHashedPassword<T>(byte[] storedHash, string password, byte[] salt,
|
|
||||||
params object[] args)
|
|
||||||
where T : IPasswordAlgorithm
|
|
||||||
{
|
|
||||||
if (storedHash == null || storedHash.Length == 0 || string.IsNullOrEmpty(password) || salt == null ||
|
|
||||||
salt.Length == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var candidatePassword = GetPassword<T>(password, salt, args);
|
|
||||||
return candidatePassword is not null && candidatePassword.Hash.Length == storedHash.Length &&
|
|
||||||
CryptographicOperations.FixedTimeEquals(storedHash, candidatePassword.Hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool CompareHashedPassword(Password existingPassword, string password, string salt,
|
|
||||||
IPasswordAlgorithm algorithm, params object[] args)
|
|
||||||
{
|
|
||||||
var saltBytes = Convert.FromBase64String(salt);
|
|
||||||
|
|
||||||
return CompareHashedPassword(existingPassword, password, saltBytes, algorithm, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares an unencrypted <paramref name="password"/> with a stored, encrypted <paramref name="existingPassword"/>.
|
|
||||||
/// This method uses the specified password hashing algorithm of type <typeparamref name="T"/> to hash the <paramref name="password"/>
|
|
||||||
/// and then compares it with the <paramref name="existingPassword"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the password hashing algorithm, implementing <see cref="IPasswordAlgorithm"/>.</typeparam>
|
|
||||||
/// <param name="existingPassword">The stored, encrypted password to compare against.</param>
|
|
||||||
/// <param name="password">The plain text password to be compared.</param>
|
|
||||||
/// <param name="salt">The salt value used for hashing the plain text password.</param>
|
|
||||||
/// <param name="args">Optional additional arguments required for initializing the password hashing algorithm.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if the password hashes match, otherwise <see langword="false"/>.
|
|
||||||
/// </returns>
|
|
||||||
public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt,
|
|
||||||
IPasswordAlgorithm algorithm, params object[] args)
|
|
||||||
{
|
|
||||||
var algorithmType = algorithm.GetType();
|
|
||||||
|
|
||||||
var providedPassword = typeof(SecurityHelper)
|
|
||||||
.GetMethod("GetPassword", new Type[] { typeof(string), typeof(byte[]), typeof(object[]) })
|
|
||||||
?.MakeGenericMethod(algorithmType)
|
|
||||||
?.Invoke(null, new object[] { password, salt, args });
|
|
||||||
|
|
||||||
return existingPassword.Equals(providedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Produces a deterministic SHA-256 digest for an opaque secret that must be looked up
|
|
||||||
/// without persisting the raw value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="secret">The raw opaque secret.</param>
|
|
||||||
/// <returns>The Base64-encoded digest.</returns>
|
|
||||||
public static string HashOpaqueSecret(string secret)
|
|
||||||
{
|
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(secret);
|
|
||||||
return Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(secret)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a <see cref="Password"/> object using the specified password and generates a random salt value.
|
|
||||||
/// Then it uses that salt to call the overloaded <see cref="GetPassword{T}(string, byte[], object[])"/> method with the given password and
|
|
||||||
/// the generated salt as arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> The type of <see cref="IPasswordAlgorithm"/> implementation to use. </typeparam>
|
|
||||||
/// <param name="password"> The plaintext password to be hashed. </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm"/> implementation
|
|
||||||
/// instance.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A new <see cref="Password"/> object with the given password and a randomly generated salt, as well as an
|
|
||||||
/// instance of <typeparamref name="T"/> created using any optional constructor arguments provided.
|
|
||||||
/// </returns>
|
|
||||||
/// <seealso cref="SecurityHelper.SALT_SIZE"/>
|
|
||||||
public static Password GetPassword<T>(string password, int saltSize, params object[] args)
|
|
||||||
where T : IPasswordAlgorithm
|
|
||||||
{
|
|
||||||
var salt = GetRandomBytes(saltSize);
|
|
||||||
return GetPassword<T>(password, salt, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a <see cref="Password"/> object using the specified password, salt, and optional
|
|
||||||
/// constructor arguments.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method uses reflection to find a constructor for the specified password algorithm type (<typeparamref name="T"/>).
|
|
||||||
/// It emits an error event if a suitable constructor is not found or if there is an error invoking the constructor.
|
|
||||||
/// </remarks>
|
|
||||||
/// <typeparam name="T">
|
|
||||||
/// The type of <see cref="IPasswordAlgorithm"/> implementation to use.
|
|
||||||
/// </typeparam>
|
|
||||||
/// <param name="password"> The plaintext password to be hashed. </param>
|
|
||||||
/// <param name="salt">
|
|
||||||
/// A random value used as an additional input to the one-way function that hashes data, a
|
|
||||||
/// password or passphrase. This is used to make each output different for the same input
|
|
||||||
/// thus adding security.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// Optional constructor arguments for the <see cref="IPasswordAlgorithm"/> implementation
|
|
||||||
/// instance.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A new <see cref="Password"/> object with the given password and salt, as well as an instance
|
|
||||||
/// of <typeparamref name="T"/> created using the provided constructor arguments.
|
|
||||||
/// </returns>
|
|
||||||
public static Password GetPassword<T>(string password, byte[] salt, params object[] args)
|
|
||||||
where T : IPasswordAlgorithm
|
|
||||||
{
|
|
||||||
//var allArgs = args.Prepend(salt).Prepend(password).ToArray(); // Prepend in reverse order so that password precedes salt.
|
|
||||||
var argTypes = args.Select(arg => arg.GetType()).ToArray();
|
|
||||||
var algorithmConstructor = typeof(T).GetConstructor(argTypes);
|
|
||||||
if (algorithmConstructor == null)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error,
|
|
||||||
"Cannot find a constructor for {0} that matches the given arguments: {1}",
|
|
||||||
args: new[]
|
|
||||||
{
|
|
||||||
typeof(T).Name,
|
|
||||||
string.Join(",", argTypes.Select(arg => arg.Name))
|
|
||||||
});
|
|
||||||
return default(Password);
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordInstance = (T)algorithmConstructor.Invoke(args);
|
|
||||||
|
|
||||||
if (passwordInstance == null)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error,
|
|
||||||
"There was an error invoking the constructor for {0} with the given arguments: {1}",
|
|
||||||
args: new[]
|
|
||||||
{
|
|
||||||
typeof(T).Name,
|
|
||||||
string.Join(",", args)
|
|
||||||
});
|
|
||||||
return default(Password);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Password(password, salt, passwordInstance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a new <see cref="Password"/> object using the PBKDF2 algorithm with the provided <paramref name="password"/>
|
|
||||||
/// and <paramref name="salt"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
|
|
||||||
/// a new password hash. The algorithm iteratively applies a pseudorandom function to the
|
|
||||||
/// password and salt, which increases the security of the resulting hash.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="password"> The clear text password to be hashed. </param>
|
|
||||||
/// <param name="salt">
|
|
||||||
/// A random value used to add an additional layer of security to the generated hash.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A new <see cref="Password"/> object containing the hashed password and salt.
|
|
||||||
/// </returns>
|
|
||||||
public static Password Pbkdf2(string password, byte[] salt)
|
|
||||||
{
|
|
||||||
var pwd = new Password(password, salt, Password.Pbkdf2Algorithm);
|
|
||||||
|
|
||||||
return pwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a new <see cref="Password"/> object using the PBKDF2 algorithm with the provided <paramref name="password"/>.
|
|
||||||
/// This overload of the method generates a random salt value for added security.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
|
|
||||||
/// a new password hash. The algorithm iteratively applies a pseudorandom function to the
|
|
||||||
/// password and salt, which increases the security of the resulting hash. In this overload,
|
|
||||||
/// a random salt value is generated using <see cref="SecurityHelper.GetRandomBytes(int)"/> method.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="password"> The clear text password to be hashed. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A new <see cref="Password"/> object containing the hashed password and a randomly generated salt.
|
|
||||||
/// </returns>
|
|
||||||
public static Password Pbkdf2(string password, int saltSize)
|
|
||||||
{
|
|
||||||
var salt = SecurityHelper.GetRandomBytes(saltSize);
|
|
||||||
var pwd = new Password(password, salt, Password.Pbkdf2Algorithm);
|
|
||||||
|
|
||||||
return pwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets a cryptographically strong random password. </summary>
|
|
||||||
/// <param name="length"> The length of the password to generate. </param>
|
|
||||||
/// <param name="validCharacters">
|
|
||||||
/// An array of <see cref="ValidCharacterCollection"/> enumeration values representing the desired
|
|
||||||
/// character sets.
|
|
||||||
/// </param>
|
|
||||||
/// <returns> The password. </returns>
|
|
||||||
public static string GetRandomPassword(int length, params ValidCharacterCollection[] validCharacters)
|
|
||||||
{
|
|
||||||
if (validCharacters.Length == 0)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Warning,
|
|
||||||
"No valid characters were provided, so all valid caharacters will be assumed.");
|
|
||||||
validCharacters = new[]
|
|
||||||
{
|
{
|
||||||
ValidCharacterCollection.Lowercase,
|
typeof(T).Name,
|
||||||
ValidCharacterCollection.Uppercase,
|
string.Join(",", argTypes.Select(arg => arg.Name))
|
||||||
ValidCharacterCollection.Numbers,
|
});
|
||||||
ValidCharacterCollection.Special
|
return default;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetRandomString(length, validCharacters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Compares two session identifiers. </summary>
|
var passwordInstance = (T)algorithmConstructor.Invoke(args);
|
||||||
/// <param name="first"> The first session identifier. </param>
|
|
||||||
/// <param name="second"> The second session identifier. </param>
|
if (passwordInstance == null)
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if comparison succeeds, <see langword="false"/> if not.
|
|
||||||
/// </returns>
|
|
||||||
public static bool CompareSessionID(string first, string second)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second))
|
CapyEventReporter.EmitEvent(EventLevel.Error,
|
||||||
|
"There was an error invoking the constructor for {0} with the given arguments: {1}",
|
||||||
|
args: new[]
|
||||||
|
{
|
||||||
|
typeof(T).Name,
|
||||||
|
string.Join(",", args)
|
||||||
|
});
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Password(password, salt, passwordInstance, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a new <see cref="Password" /> object using the PBKDF2 algorithm with the provided
|
||||||
|
/// <paramref name="password" />
|
||||||
|
/// and <paramref name="salt" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
|
||||||
|
/// a new password hash. The algorithm iteratively applies a pseudorandom function to the
|
||||||
|
/// password and salt, which increases the security of the resulting hash.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="password"> The clear text password to be hashed. </param>
|
||||||
|
/// <param name="salt">
|
||||||
|
/// A random value used to add an additional layer of security to the generated hash.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new <see cref="Password" /> object containing the hashed password and salt.
|
||||||
|
/// </returns>
|
||||||
|
public static Password Pbkdf2(string password, byte[] salt)
|
||||||
|
{
|
||||||
|
var pwd = new Password(password, salt, Password.Pbkdf2Algorithm);
|
||||||
|
|
||||||
|
return pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a new <see cref="Password" /> object using the PBKDF2 algorithm with the provided
|
||||||
|
/// <paramref name="password" />.
|
||||||
|
/// This overload of the method generates a random salt value for added security.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to generate
|
||||||
|
/// a new password hash. The algorithm iteratively applies a pseudorandom function to the
|
||||||
|
/// password and salt, which increases the security of the resulting hash. In this overload,
|
||||||
|
/// a random salt value is generated using <see cref="SecurityHelper.GetRandomBytes(int)" /> method.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="password"> The clear text password to be hashed. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A new <see cref="Password" /> object containing the hashed password and a randomly generated salt.
|
||||||
|
/// </returns>
|
||||||
|
public static Password Pbkdf2(string password, int saltSize)
|
||||||
|
{
|
||||||
|
var salt = GetRandomBytes(saltSize);
|
||||||
|
var pwd = new Password(password, salt, Password.Pbkdf2Algorithm);
|
||||||
|
|
||||||
|
return pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets a cryptographically strong random password. </summary>
|
||||||
|
/// <param name="length"> The length of the password to generate. </param>
|
||||||
|
/// <param name="validCharacters">
|
||||||
|
/// An array of <see cref="ValidCharacterCollection" /> enumeration values representing the desired
|
||||||
|
/// character sets.
|
||||||
|
/// </param>
|
||||||
|
/// <returns> The password. </returns>
|
||||||
|
public static string GetRandomPassword(int length, params ValidCharacterCollection[] validCharacters)
|
||||||
|
{
|
||||||
|
if (validCharacters.Length == 0)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Warning,
|
||||||
|
"No valid characters were provided, so all valid caharacters will be assumed.");
|
||||||
|
validCharacters = new[]
|
||||||
{
|
{
|
||||||
return CompareStrings(first, second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompareStrings(first.Trim(), second.Trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A convenience method to generate a random string of the specified length using all character sets.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="length"> The desired length of the generated random string.</param>
|
|
||||||
/// <seealso cref="ValidCharacterCollection"/>
|
|
||||||
/// <seealso cref="GetRandomString(int, ValidCharacterCollection[])"/>
|
|
||||||
public static string GetRandomString(int length)
|
|
||||||
{
|
|
||||||
return GetRandomString(length,
|
|
||||||
ValidCharacterCollection.Lowercase,
|
ValidCharacterCollection.Lowercase,
|
||||||
ValidCharacterCollection.Uppercase,
|
ValidCharacterCollection.Uppercase,
|
||||||
ValidCharacterCollection.Numbers,
|
ValidCharacterCollection.Numbers,
|
||||||
ValidCharacterCollection.Special);
|
ValidCharacterCollection.Special
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets a cryptographically strong random string using the character values found in <see cref="VALID_CHARACTERS"/>. </summary>
|
return GetRandomString(length, validCharacters);
|
||||||
/// <param name="length"> The length of the string to create. </param>
|
}
|
||||||
/// <returns> The random string. </returns>
|
|
||||||
public static string GetRandomString(int length, params ValidCharacterCollection[] validChars)
|
|
||||||
{
|
|
||||||
var buffer = new StringBuilder(length);
|
|
||||||
var randomNumberBuffer =
|
|
||||||
new byte[length * 3]; // Overprovision the buffer so we can discard a small percentage.
|
|
||||||
var validCharacters = GetValidCharacterComposition(validChars);
|
|
||||||
var validByteUpperLimit =
|
|
||||||
(256 / validCharacters.Length) * validCharacters.Length -
|
|
||||||
1; // Maintains equal distribution of valid characters.
|
|
||||||
|
|
||||||
using (var rng = RandomNumberGenerator.Create())
|
/// <summary> Compares two session identifiers. </summary>
|
||||||
|
/// <param name="first"> The first session identifier. </param>
|
||||||
|
/// <param name="second"> The second session identifier. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if comparison succeeds, <see langword="false" /> if not.
|
||||||
|
/// </returns>
|
||||||
|
public static bool CompareSessionID(string first, string second)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second))
|
||||||
|
{
|
||||||
|
return CompareStrings(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompareStrings(first.Trim(), second.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A convenience method to generate a random string of the specified length using all character sets.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"> The desired length of the generated random string.</param>
|
||||||
|
/// <seealso cref="ValidCharacterCollection" />
|
||||||
|
/// <seealso cref="GetRandomString(int, ValidCharacterCollection[])" />
|
||||||
|
public static string GetRandomString(int length)
|
||||||
|
{
|
||||||
|
return GetRandomString(length,
|
||||||
|
ValidCharacterCollection.Lowercase,
|
||||||
|
ValidCharacterCollection.Uppercase,
|
||||||
|
ValidCharacterCollection.Numbers,
|
||||||
|
ValidCharacterCollection.Special);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a cryptographically strong random string using the character values found in
|
||||||
|
/// <see cref="VALID_CHARACTERS" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"> The length of the string to create. </param>
|
||||||
|
/// <returns> The random string. </returns>
|
||||||
|
public static string GetRandomString(int length, params ValidCharacterCollection[] validChars)
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder(length);
|
||||||
|
var randomNumberBuffer =
|
||||||
|
new byte[length * 3]; // Overprovision the buffer so we can discard a small percentage.
|
||||||
|
var validCharacters = GetValidCharacterComposition(validChars);
|
||||||
|
var validByteUpperLimit =
|
||||||
|
256 / validCharacters.Length * validCharacters.Length -
|
||||||
|
1; // Maintains equal distribution of valid characters.
|
||||||
|
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
while (buffer.Length < length)
|
||||||
{
|
{
|
||||||
while (buffer.Length < length)
|
rng.GetBytes(randomNumberBuffer);
|
||||||
|
foreach (var b in randomNumberBuffer)
|
||||||
{
|
{
|
||||||
rng.GetBytes(randomNumberBuffer);
|
if (b <= validByteUpperLimit)
|
||||||
foreach (byte b in randomNumberBuffer)
|
|
||||||
{
|
{
|
||||||
if (b <= validByteUpperLimit)
|
var index = b % validCharacters.Length;
|
||||||
|
buffer.Append(validCharacters[index]);
|
||||||
|
if (buffer.Length == length)
|
||||||
{
|
{
|
||||||
int index = b % validCharacters.Length;
|
break;
|
||||||
buffer.Append(validCharacters[index]);
|
|
||||||
if (buffer.Length == length)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Generates a new byte array of the specified length with random values. </summary>
|
|
||||||
/// <param name="length"> The desired length of the generated byte array. </param>
|
/// <summary> Generates a new byte array of the specified length with random values. </summary>
|
||||||
/// <returns> A new byte array of the specified length filled with random values. </returns>
|
/// <param name="length"> The desired length of the generated byte array. </param>
|
||||||
public static byte[] GetRandomBytes(int length)
|
/// <returns> A new byte array of the specified length filled with random values. </returns>
|
||||||
|
public static byte[] GetRandomBytes(int length)
|
||||||
|
{
|
||||||
|
if (length <= 0)
|
||||||
{
|
{
|
||||||
if (length <= 0)
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Length must be greater than 0.");
|
||||||
{
|
return GetRandomBytes(16);
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Length must be greater than 0.");
|
|
||||||
return GetRandomBytes(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = new byte[length];
|
|
||||||
using (var rng = RandomNumberGenerator.Create())
|
|
||||||
{
|
|
||||||
rng.GetBytes(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var buffer = new byte[length];
|
||||||
/// Static method that returns a valid character composition based on the given ValidCharacterCollection parameters.
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
/// </summary>
|
|
||||||
/// <param name="validCharacters">An array of ValidCharacterCollection enumeration values representing the desired character sets.</param>
|
|
||||||
/// <returns>A string containing all the characters from the specified character sets.</returns>
|
|
||||||
public static string GetValidCharacterComposition(params ValidCharacterCollection[] validCharacters)
|
|
||||||
{
|
{
|
||||||
var composition = new StringBuilder();
|
rng.GetBytes(buffer);
|
||||||
foreach (var c in validCharacters)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case ValidCharacterCollection.Lowercase:
|
|
||||||
composition.Append(SecurityHelper.LOWER_CASE_CHARACTERS);
|
|
||||||
break;
|
|
||||||
case ValidCharacterCollection.Uppercase:
|
|
||||||
composition.Append(SecurityHelper.UPPER_CASE_CHARACTERS);
|
|
||||||
break;
|
|
||||||
case ValidCharacterCollection.Numbers:
|
|
||||||
composition.Append(SecurityHelper.NUMBER_CHARACTERS);
|
|
||||||
break;
|
|
||||||
case ValidCharacterCollection.Special:
|
|
||||||
composition.Append(SecurityHelper.SPECIAL_CHARACTERS);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return composition.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Compare two strings as case sensative. </summary>
|
return buffer;
|
||||||
/// <param name="first"> The first string. </param>
|
|
||||||
/// <param name="second"> The second string. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if the comparison succeeds, <see langword="false"/> if not.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method is a proxy for using
|
|
||||||
/// <see cref="string.Compare(string, int, string, int, int, StringComparison)"/> with the
|
|
||||||
/// <c>StringComparison</c> set to <see cref="StringComparison.Ordinal"/>.
|
|
||||||
/// </remarks>
|
|
||||||
private static bool CompareStrings(string first, string second)
|
|
||||||
{
|
|
||||||
return string.Compare(first, second, StringComparison.Ordinal) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumeration that defines the types of characters that can be included in a random string.
|
/// Static method that returns a valid character composition based on the given ValidCharacterCollection parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ValidCharacterCollection
|
/// <param name="validCharacters">
|
||||||
|
/// An array of ValidCharacterCollection enumeration values representing the desired
|
||||||
|
/// character sets.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A string containing all the characters from the specified character sets.</returns>
|
||||||
|
public static string GetValidCharacterComposition(params ValidCharacterCollection[] validCharacters)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var composition = new StringBuilder();
|
||||||
/// Indicates that lower case characters should be included in the random string.
|
foreach (var c in validCharacters)
|
||||||
/// </summary>
|
{
|
||||||
[EnumerationDescriptionAttribute(SecurityHelper.LOWER_CASE_CHARACTERS)]
|
switch (c)
|
||||||
Lowercase,
|
{
|
||||||
|
case ValidCharacterCollection.Lowercase:
|
||||||
|
composition.Append(SecurityHelper.LOWER_CASE_CHARACTERS);
|
||||||
|
break;
|
||||||
|
case ValidCharacterCollection.Uppercase:
|
||||||
|
composition.Append(SecurityHelper.UPPER_CASE_CHARACTERS);
|
||||||
|
break;
|
||||||
|
case ValidCharacterCollection.Numbers:
|
||||||
|
composition.Append(SecurityHelper.NUMBER_CHARACTERS);
|
||||||
|
break;
|
||||||
|
case ValidCharacterCollection.Special:
|
||||||
|
composition.Append(SecurityHelper.SPECIAL_CHARACTERS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
return composition.ToString();
|
||||||
/// Indicates that upper case characters should be included in the random string.
|
|
||||||
/// </summary>
|
|
||||||
[EnumerationDescriptionAttribute(SecurityHelper.UPPER_CASE_CHARACTERS)]
|
|
||||||
Uppercase,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that numeric characters should be included in the random string.
|
|
||||||
/// </summary>
|
|
||||||
[EnumerationDescriptionAttribute(SecurityHelper.NUMBER_CHARACTERS)]
|
|
||||||
Numbers,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that special characters should be included in the random string.
|
|
||||||
/// </summary>
|
|
||||||
[EnumerationDescriptionAttribute(SecurityHelper.SPECIAL_CHARACTERS)]
|
|
||||||
Special,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Compare two strings as case sensative. </summary>
|
||||||
|
/// <param name="first"> The first string. </param>
|
||||||
|
/// <param name="second"> The second string. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the comparison succeeds, <see langword="false" /> if not.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is a proxy for using
|
||||||
|
/// <see cref="string.Compare(string, int, string, int, int, StringComparison)" /> with the
|
||||||
|
/// <c>StringComparison</c> set to <see cref="StringComparison.Ordinal" />.
|
||||||
|
/// </remarks>
|
||||||
|
private static bool CompareStrings(string first, string second)
|
||||||
|
{
|
||||||
|
return string.Compare(first, second, StringComparison.Ordinal) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An enumeration that defines the types of characters that can be included in a random string.
|
||||||
|
/// </summary>
|
||||||
|
public enum ValidCharacterCollection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that lower case characters should be included in the random string.
|
||||||
|
/// </summary>
|
||||||
|
[EnumerationDescriptionAttribute(SecurityHelper.LOWER_CASE_CHARACTERS)]
|
||||||
|
Lowercase,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that upper case characters should be included in the random string.
|
||||||
|
/// </summary>
|
||||||
|
[EnumerationDescriptionAttribute(SecurityHelper.UPPER_CASE_CHARACTERS)]
|
||||||
|
Uppercase,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that numeric characters should be included in the random string.
|
||||||
|
/// </summary>
|
||||||
|
[EnumerationDescriptionAttribute(SecurityHelper.NUMBER_CHARACTERS)]
|
||||||
|
Numbers,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that special characters should be included in the random string.
|
||||||
|
/// </summary>
|
||||||
|
[EnumerationDescriptionAttribute(SecurityHelper.SPECIAL_CHARACTERS)]
|
||||||
|
Special
|
||||||
}
|
}
|
||||||
|
|
@ -1,105 +1,102 @@
|
||||||
using System;
|
using System.Text.Json;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
namespace CapyKit.Helpers;
|
||||||
|
|
||||||
|
public static class SerializationHelper
|
||||||
{
|
{
|
||||||
public static class SerializationHelper
|
#region Members
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Serializes an object to a byte array. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A <c>JSON</c> encoded string. </returns>
|
||||||
|
public static byte[] SerializeToBytes(object obj)
|
||||||
{
|
{
|
||||||
#region Members
|
return JsonSerializer.SerializeToUtf8Bytes(obj);
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Serializes an object to a byte array. </summary>
|
|
||||||
/// <param name="obj"> The object. </param>
|
|
||||||
/// <returns> A <c>JSON</c> encoded string. </returns>
|
|
||||||
public static byte[] SerializeToBytes(object obj)
|
|
||||||
{
|
|
||||||
return JsonSerializer.SerializeToUtf8Bytes(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Serializes an object to a <c>JSON</c> encoded string. </summary>
|
|
||||||
/// <param name="obj"> The object. </param>
|
|
||||||
/// <returns> A <c>JSON</c> encoded string. </returns>
|
|
||||||
public static string SerializeToString(object obj)
|
|
||||||
{
|
|
||||||
return JsonSerializer.Serialize(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary>
|
|
||||||
/// <exception cref="FormatException"> Thrown when the format of the byte array is incorrect. </exception>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="bytes"> The byte array representing a <typeparamref name="T"/> object. </param>
|
|
||||||
/// <returns> A <typeparamref name="T"/> object. </returns>
|
|
||||||
public static T Deserialize<T>(byte[] bytes)
|
|
||||||
{
|
|
||||||
var stream = new MemoryStream(bytes);
|
|
||||||
|
|
||||||
return Deserialize<T>(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary>
|
|
||||||
/// <exception cref="FormatException">
|
|
||||||
/// Thrown when the format of an input is incorrect.
|
|
||||||
/// </exception>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="stream"> The steam. </param>
|
|
||||||
/// <returns> A <typeparamref name="T"/> object. </returns>
|
|
||||||
public static T Deserialize<T>(Stream stream)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
|
||||||
|
|
||||||
if(obj == null)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "The deserialized object was null.");
|
|
||||||
throw new ArgumentNullException(nameof(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (T)obj;
|
|
||||||
}
|
|
||||||
catch (JsonException ex)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "JSON formatting error detected during deserialization of byte array for {0}.", args: new[] { typeof(T).Name });
|
|
||||||
throw new FormatException(string.Format("JSON formatting error detected during deserialization of byte array for {0}.", typeof(T).Name), ex);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize object of type {0}.", args: new[] { typeof(T).Name });
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Deserializes a <c>JSON</c> encoded string to the given <typeparamref name="T"/>. </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
|
||||||
/// <param name="str"> The <c>JSON</c> encoded string representing a <typeparamref name="T"/> object. </param>
|
|
||||||
/// <returns> A <typeparamref name="T"/> object. </returns>
|
|
||||||
public static T Deserialize<T>(string str)
|
|
||||||
{
|
|
||||||
if (typeof(T) == typeof(string))
|
|
||||||
{
|
|
||||||
return (T)Convert.ChangeType(str, typeof(T));
|
|
||||||
}
|
|
||||||
else if(string.IsNullOrWhiteSpace(str))
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize an empty string.");
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Serializes an object to a <c>JSON</c> encoded string. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A <c>JSON</c> encoded string. </returns>
|
||||||
|
public static string SerializeToString(object obj)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes an object to a given <typeparamref name="T" /> type. </summary>
|
||||||
|
/// <exception cref="FormatException"> Thrown when the format of the byte array is incorrect. </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="bytes"> The byte array representing a <typeparamref name="T" /> object. </param>
|
||||||
|
/// <returns> A <typeparamref name="T" /> object. </returns>
|
||||||
|
public static T Deserialize<T>(byte[] bytes)
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
return Deserialize<T>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes an object to a given <typeparamref name="T" /> type. </summary>
|
||||||
|
/// <exception cref="FormatException">
|
||||||
|
/// Thrown when the format of an input is incorrect.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="stream"> The steam. </param>
|
||||||
|
/// <returns> A <typeparamref name="T" /> object. </returns>
|
||||||
|
public static T Deserialize<T>(Stream stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||||
|
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "The deserialized object was null.");
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)obj;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error,
|
||||||
|
"JSON formatting error detected during deserialization of byte array for {0}.",
|
||||||
|
args: new[] { typeof(T).Name });
|
||||||
|
throw new FormatException(
|
||||||
|
string.Format("JSON formatting error detected during deserialization of byte array for {0}.",
|
||||||
|
typeof(T).Name), ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize object of type {0}.",
|
||||||
|
args: new[] { typeof(T).Name });
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes a <c>JSON</c> encoded string to the given <typeparamref name="T" />. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="str"> The <c>JSON</c> encoded string representing a <typeparamref name="T" /> object. </param>
|
||||||
|
/// <returns> A <typeparamref name="T" /> object. </returns>
|
||||||
|
public static T Deserialize<T>(string str)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(string))
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(str, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(str))
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize an empty string.");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,135 +1,128 @@
|
||||||
using System;
|
namespace CapyKit.Helpers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit.Helpers
|
/// <summary>
|
||||||
|
/// Static class containing helper methods for retrieving and setting application settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The specific means of accessing and storing the settings are determined by the consumer,
|
||||||
|
/// allowing for flexibility in various environments such as <c>App.config</c> or <c>Web.config</c>
|
||||||
|
/// .
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// This example demonstrates how to set up the SettingsHelper class with custom accessor and
|
||||||
|
/// detector methods that read from an App.config file. The setup is done at the beginning of the
|
||||||
|
/// application execution, before any other usage of the helper methods.
|
||||||
|
/// <code>
|
||||||
|
/// public int main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// // Set up SettingsHelper with custom accessor and detector methods
|
||||||
|
/// Func<string, object> accessor = (key) =>
|
||||||
|
/// {
|
||||||
|
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
||||||
|
/// return config.AppSettings.Settings[key].Value;
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// Func<string, bool> detector = (key) =>
|
||||||
|
/// {
|
||||||
|
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
||||||
|
/// return config.AppSettings.Settings.AllKeys.Contains(key);
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// SettingsHelper.SetAccessorMethod(accessor);
|
||||||
|
/// SettingsHelper.SetDetectorMethod(detector);
|
||||||
|
///
|
||||||
|
/// // Use the helper to retrieve and set settings
|
||||||
|
/// SettingsHelper.SetApplicationSetting<int>("MySettingKey", 42);
|
||||||
|
/// int newSetting = SettingsHelper.GetApplicationSetting<int>("MySettingKey");
|
||||||
|
/// Console.WriteLine("New setting: {0}", newSetting);
|
||||||
|
///
|
||||||
|
/// int mySetting = SettingsHelper.GetApplicationSetting<int>("MySettingKey");
|
||||||
|
/// Console.WriteLine("Retrieved setting: {0}", mySetting);
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public static class SettingsHelper
|
||||||
{
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Static class containing helper methods for retrieving and setting application settings.
|
/// Private delegate function that retrieves a setting with the given <c>key</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
private static Func<string, object> accessor = key => default;
|
||||||
/// The specific means of accessing and storing the settings are determined by the consumer,
|
|
||||||
/// allowing for flexibility in various environments such as <c>App.config</c> or <c>Web.config</c>
|
/// <summary>
|
||||||
/// .
|
/// Private delegate function that detects if a setting with a given <c>key</c> exists. Returns <see langword="true" />
|
||||||
/// </remarks>
|
/// if the setting exists, <see langword="false" /> if not.
|
||||||
/// <example>
|
/// </summary>
|
||||||
/// This example demonstrates how to set up the SettingsHelper class with custom accessor and
|
private static Func<string, bool> detector = key => false;
|
||||||
/// detector methods that read from an App.config file. The setup is done at the beginning of the
|
|
||||||
/// application execution, before any other usage of the helper methods.
|
#endregion Members
|
||||||
/// <code>
|
|
||||||
/// public int main(string[] args)
|
#region Methods
|
||||||
/// {
|
|
||||||
/// // Set up SettingsHelper with custom accessor and detector methods
|
/// <summary>
|
||||||
/// Func<string, object> accessor = (key) =>
|
/// Retrieves a setting with the given <c>key</c>.
|
||||||
/// {
|
/// </summary>
|
||||||
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
/// <typeparam name="T"> The type of the setting to be retrieved. </typeparam>
|
||||||
/// return config.AppSettings.Settings[key].Value;
|
/// <param name="settingName"> The name of the setting to retrieve. </param>
|
||||||
/// };
|
/// <returns> The value of the setting as an uncast <typeparamref name="T" />. </returns>
|
||||||
///
|
public static T GetApplicationSetting<T>(string settingName)
|
||||||
/// Func<string, bool> detector = (key) =>
|
|
||||||
/// {
|
|
||||||
/// Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
|
||||||
/// return config.AppSettings.Settings.AllKeys.Contains(key);
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// SettingsHelper.SetAccessorMethod(accessor);
|
|
||||||
/// SettingsHelper.SetDetectorMethod(detector);
|
|
||||||
///
|
|
||||||
/// // Use the helper to retrieve and set settings
|
|
||||||
/// SettingsHelper.SetApplicationSetting<int>("MySettingKey", 42);
|
|
||||||
/// int newSetting = SettingsHelper.GetApplicationSetting<int>("MySettingKey");
|
|
||||||
/// Console.WriteLine("New setting: {0}", newSetting);
|
|
||||||
///
|
|
||||||
/// int mySetting = SettingsHelper.GetApplicationSetting<int>("MySettingKey");
|
|
||||||
/// Console.WriteLine("Retrieved setting: {0}", mySetting);
|
|
||||||
/// }
|
|
||||||
/// </code>
|
|
||||||
/// </example>
|
|
||||||
public static class SettingsHelper
|
|
||||||
{
|
{
|
||||||
#region Members
|
if (SettingsHelper.detector(settingName))
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private delegate function that retrieves a setting with the given <c>key</c>.
|
|
||||||
/// </summary>
|
|
||||||
private static Func<string, object> accessor = (key) => default(object);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private delegate function that detects if a setting with a given <c>key</c> exists. Returns <see langword="true"/>
|
|
||||||
/// if the setting exists, <see langword="false"/> if not.
|
|
||||||
/// </summary>
|
|
||||||
private static Func<string, bool> detector = (key) => false;
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a setting with the given <c>key</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> The type of the setting to be retrieved. </typeparam>
|
|
||||||
/// <param name="settingName"> The name of the setting to retrieve. </param>
|
|
||||||
/// <returns> The value of the setting as an uncast <typeparamref name="T"/>. </returns>
|
|
||||||
public static T GetApplicationSetting<T>(string settingName)
|
|
||||||
{
|
{
|
||||||
if (SettingsHelper.detector(settingName))
|
var result = Convert.ChangeType(SettingsHelper.accessor(settingName), typeof(T));
|
||||||
|
if (result is T)
|
||||||
{
|
{
|
||||||
var result = Convert.ChangeType(SettingsHelper.accessor(settingName), typeof(T));
|
return (T)result;
|
||||||
if (result is T)
|
|
||||||
{
|
|
||||||
return (T)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return default(T);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return default(T);
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Sets the function used to retrieve application settings. </summary>
|
return default;
|
||||||
/// <exception cref="ArgumentNullException">
|
|
||||||
/// Thrown when one or more required arguments are null.
|
|
||||||
/// </exception>
|
|
||||||
/// <param name="accessor"> The new function used to retrieve application settings. </param>
|
|
||||||
public static void SetAccessorMethod(Func<string, object> accessor)
|
|
||||||
{
|
|
||||||
if (accessor != null)
|
|
||||||
{
|
|
||||||
SettingsHelper.accessor = accessor;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var error = "Cannot set the ApplicationSettingsHelper accessor method to a null function.";
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, error);
|
|
||||||
throw new ArgumentNullException(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the function used to detect if an application setting with a given <c>key</c> exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentNullException">
|
|
||||||
/// Thrown when one or more required arguments are null.
|
|
||||||
/// </exception>
|
|
||||||
/// <param name="detector">
|
|
||||||
/// The new function used to detect if an application setting exists.
|
|
||||||
/// </param>
|
|
||||||
public static void SetDetectorMethod(Func<string, bool> detector)
|
|
||||||
{
|
|
||||||
if (detector != null)
|
|
||||||
{
|
|
||||||
SettingsHelper.detector = detector;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var error = "Cannot set the ApplicationSettingsHelper detector method to a null function.";
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, error);
|
|
||||||
throw new ArgumentNullException(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary> Sets the function used to retrieve application settings. </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Thrown when one or more required arguments are null.
|
||||||
|
/// </exception>
|
||||||
|
/// <param name="accessor"> The new function used to retrieve application settings. </param>
|
||||||
|
public static void SetAccessorMethod(Func<string, object> accessor)
|
||||||
|
{
|
||||||
|
if (accessor != null)
|
||||||
|
{
|
||||||
|
SettingsHelper.accessor = accessor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var error = "Cannot set the ApplicationSettingsHelper accessor method to a null function.";
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, error);
|
||||||
|
throw new ArgumentNullException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the function used to detect if an application setting with a given <c>key</c> exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Thrown when one or more required arguments are null.
|
||||||
|
/// </exception>
|
||||||
|
/// <param name="detector">
|
||||||
|
/// The new function used to detect if an application setting exists.
|
||||||
|
/// </param>
|
||||||
|
public static void SetDetectorMethod(Func<string, bool> detector)
|
||||||
|
{
|
||||||
|
if (detector != null)
|
||||||
|
{
|
||||||
|
SettingsHelper.detector = detector;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var error = "Cannot set the ApplicationSettingsHelper detector method to a null function.";
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, error);
|
||||||
|
throw new ArgumentNullException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,289 +1,295 @@
|
||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.SqlTypes;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection.Metadata.Ecma335;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit
|
namespace CapyKit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a password with its hash, salt and algorithm used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
public class Password
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Members
|
||||||
/// Represents a password with its hash, salt and algorithm used for encryption.
|
|
||||||
/// </summary>
|
private static readonly Lazy<Pbkdf2Algorithm> pbkdf2Algorithm = new(() => new Pbkdf2Algorithm());
|
||||||
public class Password
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary> Constructor. </summary>
|
||||||
|
/// <param name="password"> The password to be hashed. </param>
|
||||||
|
/// <param name="salt"> The salt used for encryption. </param>
|
||||||
|
/// <param name="algorithm"> The algorithm used for password encryption. </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// A variable-length parameters list containing arguments to include for the
|
||||||
|
/// <paramref name="algorithm" />.
|
||||||
|
/// </param>
|
||||||
|
public Password(string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args)
|
||||||
{
|
{
|
||||||
#region Members
|
// We know there will always be a salt, so we can prepend it to h
|
||||||
|
var augmented = args.Prepend(salt).ToArray();
|
||||||
|
|
||||||
private static Lazy<Pbkdf2Algorithm> pbkdf2Algorithm = new Lazy<Pbkdf2Algorithm>(() => new Pbkdf2Algorithm());
|
this.Hash = algorithm.Encrypt(password, augmented);
|
||||||
|
this.Salt = salt;
|
||||||
#endregion
|
this.Algorithm = algorithm;
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the hash of the password.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Hash { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the salt used for encryption.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Salt { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the algorithm used for password encryption.
|
|
||||||
/// </summary>
|
|
||||||
public IPasswordAlgorithm Algorithm { get; private set; }
|
|
||||||
|
|
||||||
#region Preconfigued Password Algorithms
|
|
||||||
|
|
||||||
/// <summary> Gets the preconfigured PBKDF2 algorithm. </summary>
|
|
||||||
/// <value> The preconfigured PBKDF2 algorithm. </value>
|
|
||||||
public static Pbkdf2Algorithm Pbkdf2Algorithm
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return pbkdf2Algorithm.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
|
||||||
/// <param name="password"> The password to be hashed. </param>
|
|
||||||
/// <param name="salt"> The salt used for encryption. </param>
|
|
||||||
/// <param name="algorithm"> The algorithm used for password encryption. </param>
|
|
||||||
/// <param name="args"> A variable-length parameters list containing arguments to include for the <paramref name="algorithm"/>. </param>
|
|
||||||
public Password(string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args)
|
|
||||||
{
|
|
||||||
// We know there will always be a salt, so we can prepend it to h
|
|
||||||
var augmented = args.Prepend(salt).ToArray();
|
|
||||||
|
|
||||||
this.Hash = algorithm.Encrypt(password, augmented);
|
|
||||||
this.Salt = salt;
|
|
||||||
this.Algorithm = algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Password(string password, string salt, IPasswordAlgorithm algorithm, params object[] args)
|
|
||||||
{
|
|
||||||
// We know there will always be a salt, so we can prepend it to h
|
|
||||||
var saltBytes = Convert.FromBase64String(salt);
|
|
||||||
var augmented = args.Prepend(saltBytes).ToArray();
|
|
||||||
|
|
||||||
this.Hash = algorithm.Encrypt(password, augmented);
|
|
||||||
this.Salt = saltBytes;
|
|
||||||
this.Algorithm = algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if(obj == null) return false; // The object is null, and this object is not.
|
|
||||||
|
|
||||||
if(ReferenceEquals(this, obj)) return true; // The object is literally this object.
|
|
||||||
|
|
||||||
var objPassword = obj as Password;
|
|
||||||
|
|
||||||
if (objPassword == null)
|
|
||||||
{
|
|
||||||
return base.Equals(obj); // Objects aren't the same type. We can fall back to the default comparison.
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Algorithm.AlgorithmName == objPassword.Algorithm.AlgorithmName
|
|
||||||
&& this.Hash.SequenceEqual(objPassword.Hash)
|
|
||||||
&& this.Salt.SequenceEqual(objPassword.Salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format("Hash: {0}, Salt: {1}, Algorithm: {2}", BitConverter.ToString(this.Hash), BitConverter.ToString(this.Salt), this.Algorithm?.AlgorithmName);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Operators
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public static bool operator ==(Password a, Password b)
|
|
||||||
{
|
|
||||||
return ReferenceEquals(a, b) // Literally the same object.
|
|
||||||
|| (ReferenceEquals(a, null) && ReferenceEquals(b,null)) // Both are null
|
|
||||||
|| a.Equals(b); // Both are not null but not the same object.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public static bool operator !=(Password a, Password b)
|
|
||||||
{
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public Password(string password, string salt, IPasswordAlgorithm algorithm, params object[] args)
|
||||||
/// Defines the contract for password encryption algorithms.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPasswordAlgorithm
|
|
||||||
{
|
{
|
||||||
#region Properties
|
// We know there will always be a salt, so we can prepend it to h
|
||||||
|
var saltBytes = Convert.FromBase64String(salt);
|
||||||
|
var augmented = args.Prepend(saltBytes).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
this.Hash = algorithm.Encrypt(password, augmented);
|
||||||
/// Gets the name of the algorithm.
|
this.Salt = saltBytes;
|
||||||
/// </summary>
|
this.Algorithm = algorithm;
|
||||||
string AlgorithmName { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Encrypts the given password using a defined algorithm. </summary>
|
|
||||||
/// <param name="password"> The plaintext password. </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// Additional arguments for the encryption process, such as salt and length.
|
|
||||||
/// </param>
|
|
||||||
/// <returns> A byte array with the hashed <paramref name="password"/>. </returns>
|
|
||||||
byte[] Encrypt(string password, params object[] args);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares the given plaintext password with an encrypted value using PBKDF2 algorithm.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="password"> The plaintext password to compare. </param>
|
|
||||||
/// <param name="encryptedValue"> The encrypted value to compare against. </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// Additional arguments for the encryption process, such as salt and length.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the given <paramref name="password"/> matches the <paramref name="encryptedValue"/>,
|
|
||||||
/// false if they are different.
|
|
||||||
/// </returns>
|
|
||||||
bool Compare(string password, byte[] encryptedValue, params object[] args)
|
|
||||||
{
|
|
||||||
var challengedPassword = Encrypt(password, args);
|
|
||||||
|
|
||||||
return encryptedValue.SequenceEqual(challengedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Implementations
|
#region Properties
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements the PBKDF2 algorithm for password encryption.
|
/// Gets or sets the hash of the password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Pbkdf2Algorithm : IPasswordAlgorithm
|
public byte[] Hash { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the salt used for encryption.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Salt { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the algorithm used for password encryption.
|
||||||
|
/// </summary>
|
||||||
|
public IPasswordAlgorithm Algorithm { get; }
|
||||||
|
|
||||||
|
#region Preconfigued Password Algorithms
|
||||||
|
|
||||||
|
/// <summary> Gets the preconfigured PBKDF2 algorithm. </summary>
|
||||||
|
/// <value> The preconfigured PBKDF2 algorithm. </value>
|
||||||
|
public static Pbkdf2Algorithm Pbkdf2Algorithm
|
||||||
{
|
{
|
||||||
#region Members
|
get { return Password.pbkdf2Algorithm.Value; }
|
||||||
|
}
|
||||||
/// <summary> (Immutable) The default length. </summary>
|
|
||||||
/// <remarks> This member is immutable. </remarks>
|
|
||||||
public const int LENGTH = 32;
|
|
||||||
|
|
||||||
/// <summary> The default number of iterations. </summary>
|
#endregion
|
||||||
/// <remarks> This member is immutable. </remarks>
|
|
||||||
public const int ITERATIONS = 100000;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
#region Methods
|
||||||
public string AlgorithmName
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
{
|
{
|
||||||
get
|
return false; // The object is null, and this object is not.
|
||||||
{
|
|
||||||
return "Pbkdf2";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
if (ReferenceEquals(this, obj))
|
||||||
|
|
||||||
#region Constructor
|
|
||||||
|
|
||||||
/// <summary> Default constructor. </summary>
|
|
||||||
public Pbkdf2Algorithm()
|
|
||||||
{
|
{
|
||||||
//
|
return true; // The object is literally this object.
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
var objPassword = obj as Password;
|
||||||
|
|
||||||
#region Methods
|
if (objPassword == null)
|
||||||
|
|
||||||
/// <summary> Encrypts the given password using a PBKDF2 algorithm. </summary>
|
|
||||||
/// <param name="password"> The plaintext password. </param>
|
|
||||||
/// <param name="args">
|
|
||||||
/// Additional arguments for the encryption process, specifically
|
|
||||||
/// <list type="number">
|
|
||||||
/// <item><c>salt</c></item>
|
|
||||||
/// <item><c>length</c></item>
|
|
||||||
/// <item><c>iterations</c></item>
|
|
||||||
/// </list>
|
|
||||||
/// </param>
|
|
||||||
/// <returns> A byte array with the hashed <paramref name="password"/>. </returns>
|
|
||||||
public byte[] Encrypt(string password, params object[] args)
|
|
||||||
{
|
{
|
||||||
if (args.Length == 0)
|
return base.Equals(obj); // Objects aren't the same type. We can fall back to the default comparison.
|
||||||
{
|
}
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "No arguments passed.");
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
var salt = args[0] as byte[];
|
return this.Algorithm.AlgorithmName == objPassword.Algorithm.AlgorithmName
|
||||||
|
&& this.Hash.SequenceEqual(objPassword.Hash)
|
||||||
|
&& this.Salt.SequenceEqual(objPassword.Salt);
|
||||||
|
}
|
||||||
|
|
||||||
if (salt == null)
|
/// <inheritdoc />
|
||||||
{
|
public override string ToString()
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "The first parameters in the arguments wasn't a valid salt.");
|
{
|
||||||
return new byte[0];
|
return string.Format("Hash: {0}, Salt: {1}, Algorithm: {2}", BitConverter.ToString(this.Hash),
|
||||||
}
|
BitConverter.ToString(this.Salt), this.Algorithm?.AlgorithmName);
|
||||||
|
}
|
||||||
|
|
||||||
var length = 0;
|
#endregion
|
||||||
try
|
|
||||||
{
|
|
||||||
length = Convert.ToInt32(args[1]);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
|
|
||||||
length = LENGTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iterations = 0;
|
#region Operators
|
||||||
try
|
|
||||||
{
|
|
||||||
iterations = Convert.ToInt32(args[2]);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
|
|
||||||
iterations = ITERATIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = new byte[0];
|
/// <inheritdoc />
|
||||||
|
public static bool operator ==(Password a, Password b)
|
||||||
|
{
|
||||||
|
return ReferenceEquals(a, b) // Literally the same object.
|
||||||
|
|| (ReferenceEquals(a, null) && ReferenceEquals(b, null)) // Both are null
|
||||||
|
|| a.Equals(b); // Both are not null but not the same object.
|
||||||
|
}
|
||||||
|
|
||||||
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
|
/// <inheritdoc />
|
||||||
{
|
public static bool operator !=(Password a, Password b)
|
||||||
hash = deriveBytes.GetBytes(length);
|
{
|
||||||
}
|
return !(a == b);
|
||||||
|
|
||||||
if (hash.Length == 0)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Hash could not be generated.");
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for password encryption algorithms.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the algorithm.
|
||||||
|
/// </summary>
|
||||||
|
string AlgorithmName { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Encrypts the given password using a defined algorithm. </summary>
|
||||||
|
/// <param name="password"> The plaintext password. </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// Additional arguments for the encryption process, such as salt and length.
|
||||||
|
/// </param>
|
||||||
|
/// <returns> A byte array with the hashed <paramref name="password" />. </returns>
|
||||||
|
byte[] Encrypt(string password, params object[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the given plaintext password with an encrypted value using PBKDF2 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password"> The plaintext password to compare. </param>
|
||||||
|
/// <param name="encryptedValue"> The encrypted value to compare against. </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// Additional arguments for the encryption process, such as salt and length.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the given <paramref name="password" /> matches the <paramref name="encryptedValue" />,
|
||||||
|
/// false if they are different.
|
||||||
|
/// </returns>
|
||||||
|
bool Compare(string password, byte[] encryptedValue, params object[] args)
|
||||||
|
{
|
||||||
|
var challengedPassword = Encrypt(password, args);
|
||||||
|
|
||||||
|
return encryptedValue.SequenceEqual(challengedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the PBKDF2 algorithm for password encryption.
|
||||||
|
/// </summary>
|
||||||
|
public class Pbkdf2Algorithm : IPasswordAlgorithm
|
||||||
|
{
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
|
/// <summary> Default constructor. </summary>
|
||||||
|
public Pbkdf2Algorithm()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IPasswordAlgorithm Members
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string AlgorithmName
|
||||||
|
{
|
||||||
|
get { return "Pbkdf2"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Encrypts the given password using a PBKDF2 algorithm. </summary>
|
||||||
|
/// <param name="password"> The plaintext password. </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// Additional arguments for the encryption process, specifically
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>
|
||||||
|
/// <c>salt</c>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <c>length</c>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <c>iterations</c>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </param>
|
||||||
|
/// <returns> A byte array with the hashed <paramref name="password" />. </returns>
|
||||||
|
public byte[] Encrypt(string password, params object[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "No arguments passed.");
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var salt = args[0] as byte[];
|
||||||
|
|
||||||
|
if (salt == null)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "The first parameters in the arguments wasn't a valid salt.");
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
length = Convert.ToInt32(args[1]);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
|
||||||
|
length = Pbkdf2Algorithm.LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iterations = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
iterations = Convert.ToInt32(args[2]);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not convert the second parameter into an integer.");
|
||||||
|
iterations = Pbkdf2Algorithm.ITERATIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = new byte[0];
|
||||||
|
|
||||||
|
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
|
||||||
|
{
|
||||||
|
hash = deriveBytes.GetBytes(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash.Length == 0)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Hash could not be generated.");
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
/// <summary> (Immutable) The default length. </summary>
|
||||||
|
/// <remarks> This member is immutable. </remarks>
|
||||||
|
public const int LENGTH = 32;
|
||||||
|
|
||||||
|
/// <summary> The default number of iterations. </summary>
|
||||||
|
/// <remarks> This member is immutable. </remarks>
|
||||||
|
public const int ITERATIONS = 100000;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
506
CapyKit/Pool.cs
506
CapyKit/Pool.cs
|
|
@ -1,271 +1,255 @@
|
||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
namespace CapyKit
|
namespace CapyKit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A managed pool of resources. This class provides a thread-safe way to manage a collection of
|
||||||
|
/// objects of type <typeparamref name="T" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> The type of objects to be managed by the pool. </typeparam>
|
||||||
|
public class Pool<T>
|
||||||
{
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
/// <summary> The collection of pooled items. </summary>
|
||||||
|
private readonly ConcurrentBag<PoolItem<T>> poolItemCollection;
|
||||||
|
|
||||||
|
/// <summary> (Immutable) The number of items in the pool. </summary>
|
||||||
|
private readonly int poolSize;
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A managed pool of resources. This class provides a thread-safe way to manage a collection of
|
/// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified pool size.
|
||||||
/// objects of type <typeparamref name="T"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"> The type of objects to be managed by the pool. </typeparam>
|
/// <param name="poolSize"> The size of the pool. </param>
|
||||||
public class Pool<T>
|
public Pool(int poolSize)
|
||||||
{
|
{
|
||||||
#region Members
|
this.poolSize = poolSize;
|
||||||
|
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
||||||
/// <summary> The collection of pooled items. </summary>
|
FillPoolItemCollection(poolSize);
|
||||||
private readonly ConcurrentBag<PoolItem<T>> poolItemCollection;
|
|
||||||
|
|
||||||
/// <summary> (Immutable) The number of items in the pool. </summary>
|
|
||||||
private readonly int poolSize;
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="poolSize"> The size of the pool. </param>
|
|
||||||
public Pool(int poolSize)
|
|
||||||
{
|
|
||||||
this.poolSize = poolSize;
|
|
||||||
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
|
||||||
FillPoolItemCollection(poolSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified pool size
|
|
||||||
/// and constructor selector.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="poolSize"> The size of the pool. </param>
|
|
||||||
/// <param name="constructorSelector">
|
|
||||||
/// The constructor selector used to create new instances of <typeparamref name="T"/>.
|
|
||||||
/// </param>
|
|
||||||
public Pool(int poolSize, Func<T> constructorSelector)
|
|
||||||
{
|
|
||||||
this.poolSize = poolSize;
|
|
||||||
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
|
||||||
FillPoolItemCollection(poolSize, constructorSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Pool{T}"/> class with the specified collection
|
|
||||||
/// of items.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="collection">
|
|
||||||
/// The collection of <typeparamref name="T"/> items with which to seed the pool.
|
|
||||||
/// </param>
|
|
||||||
public Pool(IEnumerable<T> collection)
|
|
||||||
{
|
|
||||||
this.poolSize = collection.Count();
|
|
||||||
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
|
||||||
FillPoolItemCollection(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Constructors
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the pool with the specified number of items using the default constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="poolSize"> The size of the pool. </param>
|
|
||||||
private void FillPoolItemCollection(int poolSize)
|
|
||||||
{
|
|
||||||
FillPoolItemCollection(poolSize, () => default(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the pool with the specified number of items using the specified constructor
|
|
||||||
/// selector.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="poolSize"> The size of the pool. </param>
|
|
||||||
/// <param name="constructorSelector"> The constructor selector. </param>
|
|
||||||
private void FillPoolItemCollection(int poolSize, Func<T> constructorSelector)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < poolSize; i++)
|
|
||||||
{
|
|
||||||
this.poolItemCollection.Add(new PoolItem<T>(constructorSelector(), i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Fill the pool item collection from an existing <typeparamref name="T"/> collection. </summary>
|
|
||||||
/// <param name="collection"> The <typeparamref name="T"/> collection. </param>
|
|
||||||
private void FillPoolItemCollection(IEnumerable<T> collection)
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
foreach (var item in collection)
|
|
||||||
{
|
|
||||||
this.poolItemCollection.Add(new PoolItem<T>(item, index++));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets the first available item from the pool and sets its lock. </summary>
|
|
||||||
/// <returns> The first available item from the pool. </returns>
|
|
||||||
public PoolItem<T> GetAvailableItem()
|
|
||||||
{
|
|
||||||
lock (this.poolItemCollection)
|
|
||||||
{
|
|
||||||
if (this.poolItemCollection.Any(item => !item.Locked))
|
|
||||||
{
|
|
||||||
var firstAvailableItem = this.poolItemCollection.First(item => !item.Locked);
|
|
||||||
firstAvailableItem.SetLock();
|
|
||||||
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Debug, "Accessed ppol and retrieved {0}", args: new[] { firstAvailableItem });
|
|
||||||
|
|
||||||
return firstAvailableItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not return an available item.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Releases the lock on the specified item and returns it to the pool. </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method sets the <see cref="PoolItem{T}.Locked"/> flag to <see langword="false"/> so that
|
|
||||||
/// it can be retrieved by <see cref="Pool{T}.GetAvailableItem"/>.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="item"> The item to release. </param>
|
|
||||||
public void ReleaseItem(PoolItem<T> item)
|
|
||||||
{
|
|
||||||
item.ReleaseLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> A pool item. This class cannot be inherited. </summary>
|
/// <summary>
|
||||||
/// <typeparam name="T"> The type of the pooled item. </typeparam>
|
/// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified pool size
|
||||||
public sealed class PoolItem<T>
|
/// and constructor selector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolSize"> The size of the pool. </param>
|
||||||
|
/// <param name="constructorSelector">
|
||||||
|
/// The constructor selector used to create new instances of <typeparamref name="T" />.
|
||||||
|
/// </param>
|
||||||
|
public Pool(int poolSize, Func<T> constructorSelector)
|
||||||
{
|
{
|
||||||
#region Members
|
this.poolSize = poolSize;
|
||||||
|
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
||||||
/// <summary> The pooled item. </summary>
|
FillPoolItemCollection(poolSize, constructorSelector);
|
||||||
private readonly T item;
|
|
||||||
|
|
||||||
/// <summary> A flag indicating whether the item is locked or not. </summary>
|
|
||||||
private bool locked;
|
|
||||||
|
|
||||||
/// <summary> The zero-based index of the pooled item. </summary>
|
|
||||||
private readonly int index;
|
|
||||||
|
|
||||||
/// <summary> The name of the pooled item <see cref="Type"/>. </summary>
|
|
||||||
private readonly string typeName;
|
|
||||||
|
|
||||||
#endregion Members
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary> Gets the pooled resource. </summary>
|
|
||||||
/// <value> The pooled resource. </value>
|
|
||||||
public T Item
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets a value indicating whether this object is locked or not. </summary>
|
|
||||||
/// <value> A value indicating whether this object is locked or not. </value>
|
|
||||||
public bool Locked
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.locked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets the zero-based index of the pooled item. </summary>
|
|
||||||
/// <value> The index. </value>
|
|
||||||
public int Index
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Gets the name of the <see cref="Type"/> of the pooled item. </summary>
|
|
||||||
/// <value> The name of the <see cref="Type"/> of the pooled item. </value>
|
|
||||||
public string TypeName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.typeName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Properties
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="PoolItem{T}"/> class with the specified item and
|
|
||||||
/// index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item"> The pooled item. </param>
|
|
||||||
/// <param name="index"> The zero-based index of the pooled item. </param>
|
|
||||||
internal PoolItem(T item, int index)
|
|
||||||
{
|
|
||||||
this.item = item;
|
|
||||||
this.index = index;
|
|
||||||
this.locked = false;
|
|
||||||
this.typeName = typeof(T).Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Constructors
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary> Sets the lock on the item indicating that it is in use. </summary>
|
|
||||||
/// <remarks> If the item is already locked, an error event is emitted. </remarks>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true"/> if the item is locked successfully, <see langword="false"/> if it
|
|
||||||
/// fails.
|
|
||||||
/// </returns>
|
|
||||||
public bool SetLock()
|
|
||||||
{
|
|
||||||
if (this.locked)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock requested for {0}, but the lock request failed.", args: new[] { this });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.locked = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Releases the lock on the item. </summary>
|
|
||||||
/// <remarks> If the item is not locked, an error event is emitted. </remarks>
|
|
||||||
public void ReleaseLock()
|
|
||||||
{
|
|
||||||
if (!this.locked)
|
|
||||||
{
|
|
||||||
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock release requested for {0}, but the lock was already released.", args: new[] { this } );
|
|
||||||
}
|
|
||||||
|
|
||||||
this.locked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Methods
|
|
||||||
|
|
||||||
#region Overrides
|
|
||||||
|
|
||||||
/// <summary> Returns a string that represents the current object and its lock state. </summary>
|
|
||||||
/// <returns> A string that represents the current object and its lock state. </returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return string.Format("{0} {1} ({2})", this.typeName, this.index, this.locked ? "Locked" : "Unlocked");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Overrides
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Pool{T}" /> class with the specified collection
|
||||||
|
/// of items.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">
|
||||||
|
/// The collection of <typeparamref name="T" /> items with which to seed the pool.
|
||||||
|
/// </param>
|
||||||
|
public Pool(IEnumerable<T> collection)
|
||||||
|
{
|
||||||
|
this.poolSize = collection.Count();
|
||||||
|
this.poolItemCollection = new ConcurrentBag<PoolItem<T>>();
|
||||||
|
FillPoolItemCollection(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the pool with the specified number of items using the default constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolSize"> The size of the pool. </param>
|
||||||
|
private void FillPoolItemCollection(int poolSize)
|
||||||
|
{
|
||||||
|
FillPoolItemCollection(poolSize, () => default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the pool with the specified number of items using the specified constructor
|
||||||
|
/// selector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolSize"> The size of the pool. </param>
|
||||||
|
/// <param name="constructorSelector"> The constructor selector. </param>
|
||||||
|
private void FillPoolItemCollection(int poolSize, Func<T> constructorSelector)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < poolSize; i++)
|
||||||
|
{
|
||||||
|
this.poolItemCollection.Add(new PoolItem<T>(constructorSelector(), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Fill the pool item collection from an existing <typeparamref name="T" /> collection. </summary>
|
||||||
|
/// <param name="collection"> The <typeparamref name="T" /> collection. </param>
|
||||||
|
private void FillPoolItemCollection(IEnumerable<T> collection)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
foreach (var item in collection)
|
||||||
|
{
|
||||||
|
this.poolItemCollection.Add(new PoolItem<T>(item, index++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the first available item from the pool and sets its lock. </summary>
|
||||||
|
/// <returns> The first available item from the pool. </returns>
|
||||||
|
public PoolItem<T> GetAvailableItem()
|
||||||
|
{
|
||||||
|
lock (this.poolItemCollection)
|
||||||
|
{
|
||||||
|
if (this.poolItemCollection.Any(item => !item.Locked))
|
||||||
|
{
|
||||||
|
var firstAvailableItem = this.poolItemCollection.First(item => !item.Locked);
|
||||||
|
firstAvailableItem.SetLock();
|
||||||
|
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Debug, "Accessed ppol and retrieved {0}",
|
||||||
|
args: new[] { firstAvailableItem });
|
||||||
|
|
||||||
|
return firstAvailableItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not return an available item.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Releases the lock on the specified item and returns it to the pool. </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method sets the <see cref="PoolItem{T}.Locked" /> flag to <see langword="false" /> so that
|
||||||
|
/// it can be retrieved by <see cref="Pool{T}.GetAvailableItem" />.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="item"> The item to release. </param>
|
||||||
|
public void ReleaseItem(PoolItem<T> item)
|
||||||
|
{
|
||||||
|
item.ReleaseLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> A pool item. This class cannot be inherited. </summary>
|
||||||
|
/// <typeparam name="T"> The type of the pooled item. </typeparam>
|
||||||
|
public sealed class PoolItem<T>
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PoolItem{T}" /> class with the specified item and
|
||||||
|
/// index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"> The pooled item. </param>
|
||||||
|
/// <param name="index"> The zero-based index of the pooled item. </param>
|
||||||
|
internal PoolItem(T item, int index)
|
||||||
|
{
|
||||||
|
this.item = item;
|
||||||
|
this.index = index;
|
||||||
|
this.locked = false;
|
||||||
|
this.typeName = typeof(T).Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Constructors
|
||||||
|
|
||||||
|
#region Overrides
|
||||||
|
|
||||||
|
/// <summary> Returns a string that represents the current object and its lock state. </summary>
|
||||||
|
/// <returns> A string that represents the current object and its lock state. </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("{0} {1} ({2})", this.typeName, this.index, this.locked ? "Locked" : "Unlocked");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Overrides
|
||||||
|
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
/// <summary> The pooled item. </summary>
|
||||||
|
private readonly T item;
|
||||||
|
|
||||||
|
/// <summary> A flag indicating whether the item is locked or not. </summary>
|
||||||
|
private bool locked;
|
||||||
|
|
||||||
|
/// <summary> The zero-based index of the pooled item. </summary>
|
||||||
|
private readonly int index;
|
||||||
|
|
||||||
|
/// <summary> The name of the pooled item <see cref="Type" />. </summary>
|
||||||
|
private readonly string typeName;
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary> Gets the pooled resource. </summary>
|
||||||
|
/// <value> The pooled resource. </value>
|
||||||
|
public T Item
|
||||||
|
{
|
||||||
|
get { return this.item; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets a value indicating whether this object is locked or not. </summary>
|
||||||
|
/// <value> A value indicating whether this object is locked or not. </value>
|
||||||
|
public bool Locked
|
||||||
|
{
|
||||||
|
get { return this.locked; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the zero-based index of the pooled item. </summary>
|
||||||
|
/// <value> The index. </value>
|
||||||
|
public int Index
|
||||||
|
{
|
||||||
|
get { return this.index; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Gets the name of the <see cref="Type" /> of the pooled item. </summary>
|
||||||
|
/// <value> The name of the <see cref="Type" /> of the pooled item. </value>
|
||||||
|
public string TypeName
|
||||||
|
{
|
||||||
|
get { return this.typeName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Sets the lock on the item indicating that it is in use. </summary>
|
||||||
|
/// <remarks> If the item is already locked, an error event is emitted. </remarks>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the item is locked successfully, <see langword="false" /> if it
|
||||||
|
/// fails.
|
||||||
|
/// </returns>
|
||||||
|
public bool SetLock()
|
||||||
|
{
|
||||||
|
if (this.locked)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Lock requested for {0}, but the lock request failed.",
|
||||||
|
args: new[] { this });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.locked = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Releases the lock on the item. </summary>
|
||||||
|
/// <remarks> If the item is not locked, an error event is emitted. </remarks>
|
||||||
|
public void ReleaseLock()
|
||||||
|
{
|
||||||
|
if (!this.locked)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error,
|
||||||
|
"Lock release requested for {0}, but the lock was already released.", args: new[] { this });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
|
@ -1,101 +1,93 @@
|
||||||
using System;
|
namespace CapyKit;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CapyKit
|
/// <summary>
|
||||||
|
/// A object comparer that can accept a lambda expression to compare properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter of the property value. </typeparam>
|
||||||
|
/// <example>
|
||||||
|
/// using System;
|
||||||
|
/// using System.Collections.Generic;
|
||||||
|
/// using System.Linq;
|
||||||
|
/// class Program
|
||||||
|
/// {
|
||||||
|
/// static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// var people = new List<Person>
|
||||||
|
/// {
|
||||||
|
/// new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 30 }, new
|
||||||
|
/// Person { Name = "Charlie", Age = 35 }
|
||||||
|
/// };
|
||||||
|
/// var comparer = new PropertyComparer<Person, int>(p => p.Age);
|
||||||
|
/// var distinctPeople = people.Distinct(comparer).ToList();
|
||||||
|
/// foreach (var person in distinctPeople)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{person.Name} - {person.Age}");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// class Person
|
||||||
|
/// {
|
||||||
|
/// public string Name { get; set; }
|
||||||
|
/// public int Age { get; set; }
|
||||||
|
/// }
|
||||||
|
/// </example>
|
||||||
|
public class PropertyComparer<T, U> : IEqualityComparer<T>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary> The expression to retrieve the property. </summary>
|
||||||
/// A object comparer that can accept a lambda expression to compare properties.
|
private readonly Func<T, U> expression;
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
/// <summary> Constructor. </summary>
|
||||||
/// <typeparam name="U"> Generic type parameter of the property value. </typeparam>
|
/// <param name="expression"> The expression. </param>
|
||||||
/// <example>
|
public PropertyComparer(Func<T, U> expression)
|
||||||
/// using System;
|
|
||||||
/// using System.Collections.Generic;
|
|
||||||
/// using System.Linq;
|
|
||||||
///
|
|
||||||
/// class Program
|
|
||||||
/// {
|
|
||||||
/// static void Main(string[] args)
|
|
||||||
/// {
|
|
||||||
/// var people = new List<Person>
|
|
||||||
/// {
|
|
||||||
/// new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 30 }, new
|
|
||||||
/// Person { Name = "Charlie", Age = 35 }
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// var comparer = new PropertyComparer<Person, int>(p => p.Age);
|
|
||||||
/// var distinctPeople = people.Distinct(comparer).ToList();
|
|
||||||
///
|
|
||||||
/// foreach (var person in distinctPeople)
|
|
||||||
/// {
|
|
||||||
/// Console.WriteLine($"{person.Name} - {person.Age}");
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// class Person
|
|
||||||
/// {
|
|
||||||
/// public string Name { get; set; }
|
|
||||||
/// public int Age { get; set; }
|
|
||||||
/// }
|
|
||||||
/// </example>
|
|
||||||
public class PropertyComparer<T, U> : IEqualityComparer<T>
|
|
||||||
{
|
{
|
||||||
/// <summary> The expression to retrieve the property. </summary>
|
this.expression = expression;
|
||||||
private Func<T, U> expression;
|
|
||||||
|
|
||||||
/// <summary> Constructor. </summary>
|
|
||||||
/// <param name="expression"> The expression. </param>
|
|
||||||
public PropertyComparer(Func<T, U> expression)
|
|
||||||
{
|
|
||||||
this.expression = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Determines whether the specified properties are equal. </summary>
|
|
||||||
/// <param name="x"> The first object of type <typeparamref name="T"/> to compare. </param>
|
|
||||||
/// <param name="y"> The second object of type <typeparamref name="T" /> to compare. </param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see langword="true" /> if the specified objects are equal; otherwise,
|
|
||||||
/// <see langword="false" />.
|
|
||||||
/// </returns>
|
|
||||||
public bool Equals(T x, T y)
|
|
||||||
{
|
|
||||||
var left = expression.Invoke(x);
|
|
||||||
var right = expression.Invoke(y);
|
|
||||||
|
|
||||||
if (left == null && right == null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (left == null ^ right == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Returns a hash code for the specified object. </summary>
|
|
||||||
/// <param name="obj">
|
|
||||||
/// The <see cref="T:System.Object" /> for which a hash code is to be returned.
|
|
||||||
/// </param>
|
|
||||||
/// <returns> A hash code for the specified object. </returns>
|
|
||||||
///
|
|
||||||
/// ### <exception cref="T:System.ArgumentNullException">
|
|
||||||
/// The type of <paramref name="obj" /> is a reference type and
|
|
||||||
/// <paramref name="obj" /> is
|
|
||||||
/// <see langword="null" />.
|
|
||||||
/// </exception>
|
|
||||||
public int GetHashCode(T obj)
|
|
||||||
{
|
|
||||||
var property = expression(obj);
|
|
||||||
|
|
||||||
return (property == null) ? 0 : property.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#region IEqualityComparer<T> Members
|
||||||
|
|
||||||
|
/// <summary> Determines whether the specified properties are equal. </summary>
|
||||||
|
/// <param name="x"> The first object of type <typeparamref name="T" /> to compare. </param>
|
||||||
|
/// <param name="y"> The second object of type <typeparamref name="T" /> to compare. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the specified objects are equal; otherwise,
|
||||||
|
/// <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
public bool Equals(T x, T y)
|
||||||
|
{
|
||||||
|
var left = this.expression.Invoke(x);
|
||||||
|
var right = this.expression.Invoke(y);
|
||||||
|
|
||||||
|
if (left == null && right == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((left == null) ^ (right == null))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns a hash code for the specified object. </summary>
|
||||||
|
/// <param name="obj">
|
||||||
|
/// The <see cref="T:System.Object" /> for which a hash code is to be returned.
|
||||||
|
/// </param>
|
||||||
|
/// <returns> A hash code for the specified object. </returns>
|
||||||
|
/// ###
|
||||||
|
/// <exception cref="T:System.ArgumentNullException">
|
||||||
|
/// The type of <paramref name="obj" /> is a reference type and
|
||||||
|
/// <paramref name="obj" /> is
|
||||||
|
/// <see langword="null" />.
|
||||||
|
/// </exception>
|
||||||
|
public int GetHashCode(T obj)
|
||||||
|
{
|
||||||
|
var property = this.expression(obj);
|
||||||
|
|
||||||
|
return property == null ? 0 : property.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue