initial files

This commit is contained in:
Ryan Peters 2020-09-04 21:19:24 -04:00
parent 7943589101
commit 2aa9ce3eb7
28 changed files with 3246 additions and 0 deletions

6
.nuget/NuGet.Config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>

144
.nuget/NuGet.targets Normal file
View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
<!-- Enable the restore command to run before builds -->
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
<!-- Property that enables building a package from a project -->
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
<!-- Determines if package restore consent is required to restore packages -->
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
<!-- Download NuGet.exe if it does not already exist -->
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!--
<PackageSource Include="https://www.nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
</ItemGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup>
<PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
<PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
</PropertyGroup>
<PropertyGroup>
<PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
<PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
</PropertyGroup>
<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
$(BuildDependsOn);
</BuildDependsOn>
<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>
</PropertyGroup>
<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate nuget.exe -->
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
<!--
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
This effectively acts as a lock that makes sure that the download operation will only happen once and all
parallel builds will have to wait for it to complete.
-->
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
</Target>
<Target Name="_DownloadNuGet">
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
</Target>
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(RestoreCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
<Exec Command="$(RestoreCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />
<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputFilename ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Net" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
OutputFilename = Path.GetFullPath(OutputFilename);
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
</Project>

34
BinaryDad.Extensions.sln Normal file
View File

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BinaryDad.Extensions", "BinaryDad.Extensions\BinaryDad.Extensions.csproj", "{2676B147-0E5A-4161-88B6-6EAFE814B769}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2676B147-0E5A-4161-88B6-6EAFE814B769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2676B147-0E5A-4161-88B6-6EAFE814B769}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2676B147-0E5A-4161-88B6-6EAFE814B769}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2676B147-0E5A-4161-88B6-6EAFE814B769}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0AB60B-3317-435D-9945-0EE5492D5141}
EndGlobalSection
GlobalSection(TeamFoundationVersionControl) = preSolution
SccNumberOfProjects = 2
SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
SccTeamFoundationServer = http://tfs.binarydad.com:8080/tfs/defaultcollection
SccLocalPath0 = .
SccProjectUniqueName1 = BinaryDad.Extensions\\BinaryDad.Extensions.csproj
SccProjectName1 = BinaryDad.Extensions
SccLocalPath1 = BinaryDad.Extensions
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,21 @@
using System;
namespace BinaryDad.Extensions
{
/// <summary>
/// Binds data from command line flags to attached property. In the example, [<see cref="CommandFlagAttribute"/>("context")] public string Context { get; set; }, a command of "command.exe -context Production" will set Context property equal to "Production".
/// </summary>
public class CommandFlagAttribute : Attribute
{
public string Flag { get; }
/// <summary>
/// The name of the flag, without the preceding dash (-)
/// </summary>
/// <param name="flag"></param>
public CommandFlagAttribute(string flag)
{
Flag = flag;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace BinaryDad.Extensions
{
/// <summary>
/// Allows for a complex property to be populated via ToType().
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class DataRowPopulateAttribute : Attribute { }
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace BinaryDad.Extensions
{
/// <summary>
/// Allows for additional metadata to be applied to Enum values
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class EnumAliasAttribute : Attribute
{
public IEnumerable<string> EnumAliasNames { get; private set; }
public EnumAliasAttribute(params string[] ids)
{
EnumAliasNames = ids;
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Globals">
<SccProjectName>SAK</SccProjectName>
<SccProvider>SAK</SccProvider>
<SccAuxPath>SAK</SccAuxPath>
<SccLocalPath>SAK</SccLocalPath>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>A bunch of common extensions</Description>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.Runtime.Caching" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>BinaryDad.Extensions</id>
<version>1</version>
<authors>Ryan Peters</authors>
<owners>Ryan Peters</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>
A set of common utilities and extension methods for .NET..
</description>
<releaseNotes>Check TFS history for changes since previous release.</releaseNotes>
</metadata>
<files>
<file src="bin\$configuration$\$id$.pdb" target="lib\net452\" />
</files>
</package>

View File

@ -0,0 +1,197 @@
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;
namespace BinaryDad.Extensions
{
/// <summary>
/// In-memory cache helper wrapping the <see cref="MemoryCache.Default"/>
/// </summary>
public static class CacheHelper
{
private const int DefaultCacheDuration = 10; // minutes
#region Add
/// <summary>
/// Adds an item to the cache for a duration (in minutes)
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="cacheDuration">The length of the cache duration in minutes</param>
/// <param name="isSliding">Indicates whether the cache duration is sliding or absolute</param>
public static void Add(string key, object value, int cacheDuration = DefaultCacheDuration, bool isSliding = false) => Add(key, value, GetCacheItemPolicy(cacheDuration, isSliding));
/// <summary>
/// Adds an item to the cache with an absolute expiration
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="absoluteExpiration"></param>
public static void Add(string key, object value, DateTime absoluteExpiration) => Add(key, value, GetCacheItemPolicy(absoluteExpiration));
#endregion
#region Get
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="cacheDuration">Amount of time in minutes to persist the cache if loaded from source</param>
/// <param name="isSliding">Indicates whether the cache duration is sliding or absolute</param>
/// <returns></returns>
public static T Get<T>(string key, Func<T> source, int cacheDuration = DefaultCacheDuration, bool isSliding = false) => Get(key, source, GetCacheItemPolicy(cacheDuration, isSliding));
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="cacheDuration">Amount of time in minutes to persist the cache if loaded from source</param>
/// <param name="isSliding">Indicates whether the cache duration is sliding or absolute</param>
/// <returns></returns>
public static Task<T> GetAsync<T>(string key, Func<Task<T>> source, int cacheDuration = DefaultCacheDuration, bool isSliding = false) => GetAsync(key, source, GetCacheItemPolicy(cacheDuration, isSliding));
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="absoluteExpiration">The date and time when the cache will invalidate</param>
/// <returns></returns>
public static T Get<T>(string key, Func<T> source, DateTime absoluteExpiration) => Get(key, source, GetCacheItemPolicy(absoluteExpiration));
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="absoluteExpiration">The date and time when the cache will invalidate</param>
/// <returns></returns>
public static Task<T> GetAsync<T>(string key, Func<Task<T>> source, DateTime absoluteExpiration) => GetAsync(key, source, GetCacheItemPolicy(absoluteExpiration));
/// <summary>
/// Retrieves an object from the cache
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public static T Get<T>(string key) => MemoryCache.Default[key].To<T>();
#endregion
/// <summary>
/// Removes an item from the cache
/// </summary>
/// <param name="key"></param>
public static void Remove(string key) => MemoryCache.Default.Remove(key);
/// <summary>
/// Returns true if the value exists in the cache
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Exists(string key) => MemoryCache.Default[key] != null;
#region Private Methods
/// <summary>
/// Adds an item to the cache with a custom <see cref="CacheItemPolicy"/>
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="cacheItemPolicy"></param>
private static void Add(string key, object value, CacheItemPolicy cacheItemPolicy)
{
Remove(key);
MemoryCache.Default.Add(key, value, cacheItemPolicy);
}
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="cacheItemPolicy"></param>
/// <returns></returns>
private static T Get<T>(string key, Func<T> source, CacheItemPolicy cacheItemPolicy)
{
if (Exists(key))
{
return Get<T>(key);
}
// if the value does not exist in the cache, automatically add it and return it
var value = source.Invoke();
Add(key, value, cacheItemPolicy);
return value;
}
/// <summary>
/// Retrieves an object from the cache, and if not found, retrieves and sets from a source delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="source"></param>
/// <param name="cacheItemPolicy"></param>
/// <returns></returns>
private static async Task<T> GetAsync<T>(string key, Func<Task<T>> source, CacheItemPolicy cacheItemPolicy)
{
if (Exists(key))
{
return Get<T>(key);
}
// if the value does not exist in the cache, automatically add it and return it
var value = await source.Invoke();
Add(key, value, cacheItemPolicy);
return value;
}
/// <summary>
/// Creates a <see cref="CacheItemPolicy"/> given an absolute expiration date/time
/// </summary>
/// <param name="absoluteExpiration"></param>
/// <returns></returns>
private static CacheItemPolicy GetCacheItemPolicy(DateTime absoluteExpiration)
{
return new CacheItemPolicy
{
AbsoluteExpiration = absoluteExpiration
};
}
/// <summary>
/// Creates a <see cref="CacheItemPolicy"/> given a cache duration (in minutes) and if the cache expiration is sliding
/// </summary>
/// <param name="cacheDuration"></param>
/// <param name="isSliding"></param>
/// <returns></returns>
private static CacheItemPolicy GetCacheItemPolicy(int cacheDuration, bool isSliding)
{
if (isSliding)
{
return new CacheItemPolicy
{
SlidingExpiration = new TimeSpan(0, cacheDuration, 0)
};
}
return GetCacheItemPolicy(DateTime.Now.AddMinutes(cacheDuration));
}
#endregion
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Reflection;
namespace BinaryDad.Extensions
{
/// <summary>
/// A set of utilities to assist with Console applications
/// </summary>
public static class ConsoleHelper
{
/// <summary>
/// Parses command argument flags in the format "-flag1 value -flag2 value2"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="args">A raw string of arguments</param>
/// <returns></returns>
public static T ParseCommandFlags<T>(string args) where T : new()
{
return ParseCommandFlags<T>(args.Split(' '));
}
/// <summary>
/// Parses command argument flags in the format "command.exe -flag1 value -flag2 value2"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="args">Collection of arguments, typically from Program.Main(string[] args)</param>
/// <returns></returns>
public static T ParseCommandFlags<T>(string[] args) where T : new()
{
// the new parameter instance
var parameters = new T();
parameters
.GetType()
.GetProperties()
.EmptyIfNull()
.ForEach(p =>
{
var commandFlagAttribute = p.GetCustomAttribute<CommandFlagAttribute>(true);
if (commandFlagAttribute != null)
{
var valueFlagIndex = args.IndexOf($"-{commandFlagAttribute.Flag}", StringComparer.OrdinalIgnoreCase);
var valueIndex = valueFlagIndex + 1;
// find the argument value in the list, convert to the desired type, and set the value
if (valueFlagIndex >= 0 && args.Length > valueIndex)
{
p.SetValue(parameters, args[valueIndex].To(p.PropertyType));
}
}
});
return parameters;
}
}
}

View File

@ -0,0 +1,183 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace BinaryDad.Extensions
{
/// <summary>
/// Set of cryptography helpers using AES and Rijndael cipher
/// </summary>
public static class CryptoHelper
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int DefaultKeySize = 256;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
#region Encrypt
/// <summary>
/// Encrypts a string with pass key using AES (Rijndael cipher)
/// </summary>
/// <param name="plainText"></param>
/// <param name="passPhrase"></param>
/// <returns></returns>
public static string Encrypt(string plainText, string passPhrase)
{
return Encrypt(plainText, passPhrase, DefaultKeySize);
}
/// <summary>
/// Encrypts a string with pass key using AES (Rijndael cipher)
/// </summary>
/// <param name="plainText"></param>
/// <param name="passPhrase"></param>
/// <param name="keySize"></param>
/// <returns></returns>
public static string Encrypt(string plainText, string passPhrase, int keySize)
{
ValidateKeySize(keySize);
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = GenerateBitsOfRandomEntropy(keySize);
var ivStringBytes = GenerateBitsOfRandomEntropy(keySize);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(keySize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = keySize;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
#endregion
#region Decrypt
/// <summary>
/// Decrypts a string with pass key using AES (Rijndael cipher)
/// </summary>
/// <param name="cipherText"></param>
/// <param name="passPhrase"></param>
/// <returns></returns>
public static string Decrypt(string cipherText, string passPhrase)
{
return Decrypt(cipherText, passPhrase, DefaultKeySize);
}
/// <summary>
/// Decrypts a string with pass key using AES (Rijndael cipher)
/// </summary>
/// <param name="cipherText"></param>
/// <param name="passPhrase"></param>
/// <param name="keySize"></param>
/// <returns></returns>
public static string Decrypt(string cipherText, string passPhrase, int keySize)
{
ValidateKeySize(keySize);
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(keySize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(keySize / 8).Take(keySize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((keySize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((keySize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(keySize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = keySize;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
#endregion
#region Private Methods
private static byte[] GenerateBitsOfRandomEntropy(int keySize)
{
var randomBytes = new byte[keySize / 8];
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
private static void ValidateKeySize(int keySize)
{
if (keySize % 8 != 0)
{
throw new ArgumentException("Key size must be a multiple of 8", nameof(keySize));
}
}
#endregion
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Reflection;
namespace BinaryDad.Extensions
{
[Serializable]
public class DataPropertyConversionException : Exception
{
public object Item { get; private set; }
public object Value { get; private set; }
public PropertyInfo Property { get; private set; }
/// <summary>
/// Represents an exception that occurs upon setting the value of a property on an object through type conversion
/// </summary>
/// <param name="item">The parent object containing the property</param>
/// <param name="property">The property info instance of the property</param>
/// <param name="value">The value being set</param>
/// <param name="ex">The original exception (assigned as inner)</param>
public DataPropertyConversionException(object item, PropertyInfo property, object value, Exception ex)
: base($"Unable to assign value {value ?? "null"} ({value?.GetType().Name}) to property {item?.GetType().Name}.{property?.Name} ({property?.PropertyType.Name})", ex)
{
Value = value;
Item = item;
Property = property;
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace BinaryDad.Extensions
{
[Serializable]
public class MaxRecursionException : Exception
{
public MaxRecursionException() { }
public MaxRecursionException(string message) : base(message) { }
public MaxRecursionException(string message, Exception innerException) : base(message, innerException) { }
}
}

View File

@ -0,0 +1,715 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
namespace BinaryDad.Extensions
{
public static class CollectionExtensions
{
public static void AddReplace<TItem, TProperty>(this ICollection<TItem> items, TItem item, Func<TItem, TProperty> matchingProperty) where TItem : class where TProperty : IComparable
{
var existing = items.FirstOrDefault(i => matchingProperty(i).Equals(matchingProperty(item)));
// if existing exists, replace existing
if (existing != null)
{
items.Remove(existing);
}
items.Add(item);
}
/// <summary>
/// Returns a distinct list of elements using the first-matched item on a specific property
/// </summary>
/// <typeparam name="TItem"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <param name="items"></param>
/// <param name="property">Property of object to compare</param>
/// <returns></returns>
public static IEnumerable<TItem> Distinct<TItem, TProperty>(this IEnumerable<TItem> items, Func<TItem, TProperty> property) where TProperty : IComparable
{
return items
.GroupBy(property)
.Select(i => i.FirstOrDefault());
}
public static IEnumerable<T> Concat<T>(this IEnumerable<T> items, T additionalValue)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
return items.Concat(new[]
{
additionalValue
});
}
#region ToDataTable
/// <summary>
/// Converts a typed collection into a <see cref="DataTable"/>. This method excludes the column if <see cref="NotMappedAttribute"/> is bound to a property.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="useColumnAttributeName">Specifies whether the data column should use the name from <see cref="ColumnAttribute"/></param> or <see cref="PropertyAliasAttribute"/>, if bound to a property.
/// <param name="tableName"></param>
/// <returns></returns>
public static DataTable ToDataTable<T>(this IEnumerable<T> collection, bool useColumnAttributeName = false, string tableName = null) where T : class
{
using (var table = new DataTable(tableName))
{
#region Build Table Schema
var propertyInfo = typeof(T)
.GetProperties()
.Select(p => new
{
Property = p,
// set column name to be either the property name
// or, if specified, based on the attribute
ColumnName = useColumnAttributeName
? p.GetDataColumnNames().FirstOrDefault()
: p.Name,
// include the column if [NotMapped] is NOT attached
IncludeColumn = !p.HasCustomAttribute<NotMappedAttribute>()
})
.Where(p => p.IncludeColumn)
.ToList();
foreach (var info in propertyInfo)
{
var columnType = info.Property.PropertyType;
if (columnType.IsGenericType)
{
columnType = columnType.GetGenericArguments()[0];
}
table.Columns.Add(info.ColumnName, columnType);
}
#endregion
#region Populate the rows
foreach (var item in collection)
{
var row = table.NewRow();
foreach (var info in propertyInfo)
{
var value = info.Property.GetValue(item, null);
if (value != null)
{
row[info.ColumnName] = value;
}
}
table.Rows.Add(row);
}
#endregion
return table;
}
}
#endregion
#region Join
public static string Join<T>(this IEnumerable<T> items, string separator = "") where T : IComparable
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (separator == null)
{
throw new ArgumentNullException(nameof(separator));
}
return string.Join(separator, items.ToArray());
}
public static string Join<T, TSelector>(this IEnumerable<T> items, Func<T, TSelector> selector, string separator = "") where TSelector : IComparable => Join(items.Select(selector), separator);
/// <summary>
/// Performs a simple join of list type T, returning items from source only
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="outer"></param>
/// <param name="inner"></param>
/// <param name="key"></param>
/// <returns></returns>
public static IEnumerable<T> Join<T>(this IEnumerable<T> outer, IEnumerable<T> inner, Func<T, object> key) => outer.Join(inner, key, key, (t, i) => t);
#endregion
#region Zip
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> combine, bool symmetric)
{
if (first == null)
{
throw new ArgumentNullException(nameof(first));
}
if (second == null)
{
throw new ArgumentNullException(nameof(second));
}
if (combine == null)
{
throw new ArgumentNullException(nameof(combine));
}
var iter1 = first.GetEnumerator();
var iter2 = second.GetEnumerator();
var mn1 = iter1.MoveNext();
var mn2 = iter2.MoveNext();
while ((symmetric && mn1 && mn2) || (!symmetric && (mn1 || mn2)))
{
var c1 = default(TFirst);
var c2 = default(TSecond);
if (mn1)
{
c1 = iter1.Current;
mn1 = iter1.MoveNext();
}
if (mn2)
{
c2 = iter2.Current;
mn2 = iter2.MoveNext();
}
yield return combine(c1, c2);
}
}
#endregion
#region RemoveEmpty
public static IEnumerable<T> RemoveEmpty<T>(this IEnumerable<T> list) where T : class
{
if (list == null)
{
throw new ArgumentNullException(nameof(list));
}
return list.Where(i => i != null);
}
#endregion
#region Apply
public static T Apply<T>(this IEnumerable<Func<T, T>> functions, T input)
{
if (functions == null)
{
throw new ArgumentNullException(nameof(functions));
}
functions.ForEach(f => input = f(input));
return input;
}
#endregion
#region Where
public static IEnumerable<T> Where<T>(this IEnumerable<T> list, IEnumerable<Func<T, bool>> filters)
{
if (list == null)
{
throw new ArgumentNullException(nameof(list));
}
if (filters == null)
{
throw new ArgumentNullException(nameof(filters));
}
filters.ForEach(f => list = list.Where(f));
return list;
}
#endregion
#region ForEach
public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
{
if (list == null)
{
return;
}
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
foreach (var i in list)
{
action(i);
}
}
#endregion
#region Traverse
public static IEnumerable<TYield> Traverse<TYield, T>(this IEnumerable<T> source, Func<T, IEnumerable<TYield>> getYield, Func<T, IEnumerable<T>> getChildren)
{
if (source == null)
{
yield break;
}
if (getYield == null)
{
throw new ArgumentNullException(nameof(getYield));
}
if (getChildren == null)
{
throw new ArgumentNullException(nameof(getChildren));
}
foreach (var item in source)
{
var yields = getYield(item);
if (yields != null)
{
foreach (var yield in yields)
{
yield return yield;
}
}
var children = getChildren(item);
if (children != null)
{
foreach (var child in Traverse(children, getYield, getChildren))
{
yield return child;
}
}
}
}
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> recurse)
{
if (source == null)
{
yield break;
}
if (recurse == null)
{
throw new ArgumentNullException(nameof(recurse));
}
foreach (var item in source)
{
yield return item;
var children = recurse(item);
if (children != null)
{
foreach (var child in Traverse(children, recurse))
{
yield return child;
}
}
}
}
#endregion
#region JoinAction
/// <summary>
/// Performs an action on a joined set of lists of the same type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="key"></param>
/// <param name="assignment"></param>
public static void JoinAction<T>(this IEnumerable<T> left, IEnumerable<T> right, Func<T, object> key, Action<T, T> assignment) => left.JoinAction(right, key, key, assignment);
/// <summary>
/// Performs an action on a joined set of lists of a different type
/// </summary>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="leftKey"></param>
/// <param name="rightKey"></param>
/// <param name="assignment"></param>
public static void JoinAction<TLeft, TRight>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, object> leftKey, Func<TRight, object> rightKey, Action<TLeft, TRight> assignment)
{
left
.Join(right, leftKey, rightKey, (l, r) => new { Left = l, Right = r })
.ForEach(j => assignment(j.Left, j.Right));
}
/// <summary>
/// Performs an action on a group joined set of lists of a different type
/// </summary>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="leftKey"></param>
/// <param name="rightKey"></param>
/// <param name="assignment"></param>
public static void GroupJoinAction<TLeft, TRight>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<TLeft, object> leftKey, Func<TRight, object> rightKey, Action<TLeft, IEnumerable<TRight>> assignment)
{
left
.GroupJoin(right, leftKey, rightKey, (l, r) => new { Left = l, Right = r })
.ForEach(j => assignment(j.Left, j.Right));
}
#endregion
/// <summary>
/// Returns a collection of items containing the items of a second collection
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="items"></param>
/// <param name="comparer"></param>
/// <returns></returns>
public static IEnumerable<T> Containing<T>(this IEnumerable<T> source, IEnumerable<T> items, IEqualityComparer<T> comparer = null) => source.Where(s => items.Contains(s, comparer));
public static bool Contains(this string[] source, string value, StringComparison comparison) => source.AnyAndNotNull(s => s.IndexOf(value, comparison) >= 0);
public static IEnumerable<T> Convert<T>(this IEnumerable source) where T : IConvertible
{
return source
.Cast<object>()
.Select(s => s.To<T>());
}
public static TSource FirstOfType<TSource>(this IEnumerable source)
{
return source
.OfType<TSource>()
.FirstOrDefault();
}
public static bool Matches<T>(this ICollection<T> source, ICollection<T> items, IEqualityComparer<T> comparer = null) where T : IComparable
{
return source.Count == items.Count
&& !source.Except(items, comparer).Any()
&& !items.Except(source, comparer).Any();
}
public static IEnumerable<T> Insert<T>(this IEnumerable<T> items, int index, T item)
{
var count = 0;
foreach (var i in items)
{
if (count == index)
{
yield return item;
}
yield return i;
count++;
}
}
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<KeyValuePair<TKey, TSource>> source, IEqualityComparer<TKey> comparer = null) => source.ToDictionary(k => k.Key, v => v.Value, comparer);
#region Replace
/// <summary>
/// Replaces an item at index with replacement
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="index">Index for item getting replaced</param>
/// <param name="replacement">Replacement item</param>
/// <returns></returns>
public static IEnumerable<T> Replace<T>(this IEnumerable<T> items, int index, T replacement) => items.Replace(index, t => replacement);
/// <summary>
/// Replaces an item at index with replacement (lambda)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="index">Index for item getting replaced</param>
/// <param name="replacement">Replacement lambda</param>
/// <returns></returns>
public static IEnumerable<T> Replace<T>(this IEnumerable<T> items, int index, Func<T, T> replacement)
{
var count = 0;
foreach (var i in items)
{
if (count == index)
{
yield return replacement(i);
}
else
{
yield return i;
}
count++;
}
}
#endregion
public static IEnumerable<T> RandomTake<T>(this ICollection<T> collection, int take) => RandomTake(collection, collection.Count, take);
public static IEnumerable<T> RandomTake<T>(this IEnumerable<T> items, int collectionCount, int take)
{
var rand = new Random();
var needed = take;
var available = collectionCount;
foreach (var i in items)
{
if (needed == 0)
{
yield break;
}
if (rand.NextDouble() < (double)needed / available)
{
yield return i;
needed--;
}
available--;
}
}
public static T RandomFirstOrDefault<T>(this IEnumerable<T> items) => RandomShuffle(items).FirstOrDefault();
public static T RandomFirstOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate) => RandomShuffle(items).Where(predicate).FirstOrDefault();
public static void Swap<T>(this IList<T> list, int indexX, int indexY)
{
if (list == null)
{
throw new ArgumentNullException(nameof(list));
}
if (indexX < 0 || indexX >= list.Count)
{
throw new ArgumentOutOfRangeException(nameof(indexX));
}
if (indexY < 0 || indexY >= list.Count)
{
throw new ArgumentOutOfRangeException(nameof(indexY));
}
var tmp = list[indexX];
list[indexX] = list[indexY];
list[indexY] = tmp;
}
/// <remarks>Knuth-Fisher-Yates shuffle algorithm, see http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html
/// </remarks>
public static ICollection<T> RandomShuffle<T>(this IEnumerable<T> collection)
{
var rnd = new Random();
var list = collection.ToList();
for (var i = list.Count - 1; i > 0; i--)
{
var n = rnd.Next(i + 1);
list.Swap(i, n);
}
return list;
}
public static int IndexOf<T>(this IEnumerable<T> items, T value, IEqualityComparer<T> comparer)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (comparer == null)
{
throw new ArgumentNullException(nameof(comparer));
}
var index = 0;
foreach (var item in items)
{
if (comparer.Equals(item, value))
{
return index;
}
index++;
}
return -1;
}
public static int IndexOf<T>(this IEnumerable<T> items, T value)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
return items.IndexOf(value, EqualityComparer<T>.Default);
}
/// <summary>
/// Determines whether a sequence has any elements. Null collections return false.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static bool AnyAndNotNull<T>(this IEnumerable<T> items, Func<T, bool> predicate = null)
{
if (items == null)
{
return false;
}
return predicate != null ? items.Any(predicate) : items.Any();
}
/// <summary>
/// Determines whether a sequence is null or is an empty set.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static bool NoneOrNull<T>(this IEnumerable<T> items, Func<T, bool> predicate = null) => !items.AnyAndNotNull(predicate);
/// <summary>
/// If list has one item, display singular string. Otherwise, display plural string.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="plural">Word to be used if not 1 item count</param>
/// <param name="singular">Word to be used if only 1 item count</param>
/// <returns></returns>
public static string IfPlural<T>(this IEnumerable<T> list, string plural, string singular)
{
var items = list.ToList();
return items.AnyAndNotNull() && items.Count() == 1 ? singular : plural;
}
/// <summary>
/// Executes an inline conditional statement if the sequence contains at least one element
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="list"></param>
/// <param name="anyResult"></param>
/// <param name="noneResult"></param>
/// <returns></returns>
public static TResult IfAny<TSource, TResult>(this IEnumerable<TSource> list, Func<IEnumerable<TSource>, TResult> anyResult, Func<IEnumerable<TSource>, TResult> noneResult)
{
if (list.AnyAndNotNull())
{
return anyResult(list);
}
return noneResult(list);
}
#region OrderBy
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool ascending)
{
return ascending
? source.OrderBy(keySelector)
: source.OrderByDescending(keySelector);
}
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string key) => source.OrderBy(key, true);
public static IEnumerable<T> OrderByDescending<T>(this IEnumerable<T> source, string key) => source.OrderBy(key, false);
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string key, bool ascending)
{
// i => i.SomeProperty
// "i" is a parameter
// "i.SomeProperty" is the body of the expression
var param = Expression.Parameter(typeof(T), "i");
var body = Expression.Property(param, key);
// create expression using body and parameters
var expression = Expression.Lambda<Func<T, object>>(body, param).Compile();
return source.OrderBy(expression, ascending);
}
#endregion
#region Sort
public static IEnumerable<T> Sort<T>(this IEnumerable<T> items) where T : IComparable<T> => items.OrderBy(i => i);
public static IEnumerable<T> SortDescending<T>(this IEnumerable<T> items) where T : IComparable<T> => items.OrderByDescending(i => i);
#endregion
/// <summary>
/// Returns an empty collection if collection is null
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <returns></returns>
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> items) => items ?? Enumerable.Empty<T>();
/// <summary>
/// Retrieves form data in a key/value pair collection
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
public static IEnumerable<KeyValuePair<string, string>> GetParameters(this NameValueCollection collection)
{
foreach (string k in collection.Keys)
{
yield return new KeyValuePair<string, string>(k, collection[k]);
}
}
}
}

View File

@ -0,0 +1,347 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace BinaryDad.Extensions
{
public static class DataTableExtensions
{
/// <summary>
/// Maps table data to a collection of objects with matching properties. Either <see cref="PropertyAliasAttribute"></see> or <see cref="ColumnAttribute"/> may be used to bind columns to properties.
/// </summary>
/// <typeparam name="T">The type to convert each <see cref="DataRow"/> row to</typeparam>
/// <param name="table"></param>
/// <param name="afterRowBinding">Action used to modify each object with additional data from row. This is applied after binding.</param>
/// <returns></returns>
public static IList<T> ToList<T>(this DataTable table, Action<T, DataRow> afterRowBinding = null)
{
#region Null checks
if (table == null)
{
return null;
}
#endregion
return table.Rows
.Cast<DataRow>()
.ToList(afterRowBinding);
}
/// <summary>
/// Maps a collection of rows to a collection of objects with matching properties. Either <see cref="PropertyAliasAttribute"></see> or <see cref="ColumnAttribute"/> may be used to bind columns to properties.
/// </summary>
/// <typeparam name="T">The type to convert each <see cref="DataRow"/> row to</typeparam>
/// <param name="rows"></param>
/// <param name="afterRowBinding">Action used to modify each object with additional data from row. This is applied after binding.</param>
/// <returns></returns>
public static IList<T> ToList<T>(this IEnumerable<DataRow> rows, Action<T, DataRow> afterRowBinding = null)
{
#region Null checks
if (rows == null)
{
return null;
}
#endregion
var type = typeof(T);
// Prevent deferred execution: http://stackoverflow.com/questions/3628425/ienumerable-vs-list-what-to-use-how-do-they-work
return rows
.Select(row =>
{
// map the row to the object
var instance = (T)row.To(type);
// invoke any modifiers
afterRowBinding?.Invoke(instance, row);
return instance;
})
.ToList();
}
/// <summary>
/// Maps the first <see cref="DataRow"/> of a <see cref="DataTable"/> to an object type. Either <see cref="PropertyAliasAttribute"/> or <see cref="ColumnAttribute"/> may be used to bind columns to properties.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="table"></param>
/// <returns></returns>
public static T To<T>(this DataTable table) => table.FirstRow().To<T>();
/// <summary>
/// Maps a <see cref="DataRow"/> to an object type. Either <see cref="PropertyAliasAttribute"/> or <see cref="ColumnAttribute"/> may be used to bind columns to properties.
/// </summary>
/// <typeparam name="T">The type to convert the <see cref="DataRow"/> to</typeparam>
/// <param name="row"></param>
/// <returns></returns>
public static T To<T>(this DataRow row) => (T)row.To(typeof(T));
/// <summary>
/// Maps a <see cref="DataRow"/> to an object type. Either <see cref="PropertyAliasAttribute"/> or <see cref="ColumnAttribute"/> may be used to bind columns to properties.
/// </summary>
/// <param name="row"></param>
/// <param name="type">The type to convert the <see cref="DataRow"/> to</param>
/// <returns></returns>
public static object To(this DataRow row, Type type)
{
#region Null checks
if (row == null)
{
throw new ArgumentNullException(nameof(row));
}
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
#endregion
var properties = type.GetProperties();
if (type.IsValueType || type == typeof(string))
{
// If the object is a value/string type, we can only pull one item, so assume it's the first in the row
return row[0].To(type);
}
var instance = Activator.CreateInstance(type);
/* 01/09/13 - SJK: Class properties can be decorated with our custom DataRowField attribute. This allows us to
* declare a mapping between an object and a database column that are named differently. If the attribute is
* present on a property, we use its name for the customProperties collection. Otherwise, we use the name
* of the property as we assume the name on the database column is the same.
*/
properties.ForEach(p => RecursiveSetValue(p, row, instance));
return instance;
}
/// <summary>
/// Returns a dictionary of key (column header) and value (cell value) pairs
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
public static IDictionary<string, object> ToDictionary(this DataRow row)
{
return row.Table.Columns
.Cast<DataColumn>()
.ToDictionary(c => c.ColumnName, c => row[c]);
}
/// <summary>
/// Converts a data table to a CSV string
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static string ToCsv(this DataTable table)
{
using (var stream = new MemoryStream())
{
table.ToCsv(stream);
return Encoding.UTF8.GetString(stream.ToArray());
}
}
/// <summary>
/// Converts data table to a CSV string and writes to a file
/// </summary>
/// <param name="table"></param>
/// <param name="filePath"></param>
public static void ToCsv(this DataTable table, string filePath)
{
using (var file = File.OpenWrite(filePath))
{
table.ToCsv(file);
}
}
/// <summary>
/// Converts data table to a CSV string and writes to a stream
/// </summary>
/// <param name="table"></param>
/// <param name="stream"></param>
public static void ToCsv(this DataTable table, Stream stream)
{
var encoding = new UTF8Encoding(false, true);
// leave the underlying stream open (uses default parameters)
using (var sw = new StreamWriter(stream, encoding, 1024, true))
{
#region Write Columns
var columnNames = table.Columns.Cast<DataColumn>()
.Select(column => column.ColumnName)
.ToArray();
sw.WriteLine(string.Join(",", columnNames));
#endregion
#region Write Rows
foreach (DataRow row in table.Rows)
{
var fields = row.ItemArray
.Select(field => QuoteValue(field.ToString()))
.ToArray();
sw.WriteLine(string.Join(",", fields));
}
#endregion
sw.Close();
}
}
/// <summary>
/// Performs an inline replacement of a <see cref="DataRow"/> value
/// </summary>
/// <param name="row"></param>
/// <param name="columnName"></param>
/// <param name="value">Allows for update/modification of the existing value</param>
public static void SetField(this DataRow row, string columnName, Func<object, object> value) => row[columnName] = value(row[columnName]);
/// <summary>
/// Iterates through a collection of <see cref="DataRow"/>
/// </summary>
/// <param name="rows"></param>
/// <param name="predicate"></param>
public static void ForEach(this DataRowCollection rows, Action<DataRow> predicate)
{
#region Null checks
if (rows == null)
{
return;
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
#endregion
rows
.Cast<DataRow>()
.ForEach(predicate);
}
/// <summary>
/// Returns whether the <see cref="DataTable"/> is not null and has rows
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static bool HasRows(this DataTable data)
{
if (data?.Rows == null)
{
return false;
}
return data.Rows.Count > 0;
}
/// <summary>
/// Returns the first row of a data table, or null if there are no rows
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static DataRow FirstRow(this DataTable data)
{
if (data.HasRows())
{
return data.Rows[0];
}
return null;
}
/// <summary>
/// Projects <see cref="DataRow"/> of a <see cref="DataTable"/> into a new form
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="selector"></param>
/// <returns></returns>
public static IEnumerable<T> Select<T>(this DataTable data, Func<DataRow, T> selector) => data.Rows.Cast<DataRow>().Select(selector);
#region Private Methods
private static void RecursiveSetValue(PropertyInfo property, DataRow row, object instance, int maxDepth = 5, int currentDepth = 0)
{
#region Max Depth
//Ensure we prevent too complex objects. This could be a sign of poor design or a condition prompting infinite recursion.
if (currentDepth > maxDepth)
{
throw new MaxRecursionException($"The currentDepth {currentDepth} may not exceed the maxDepth {maxDepth} for recursion. Check the complexity of the object and its implementation of the DataRowPopulate attribute.");
}
#endregion
try
{
// ignore the property if specified
if (property.HasCustomAttribute<NotMappedAttribute>())
{
return;
}
// if we found DataRowPopulate attributes, we go a level deeper
if (property.HasCustomAttribute<DataRowPopulateAttribute>())
{
currentDepth++;
// get all properties on this type and continue if we have any
var subProperties = property.PropertyType.GetProperties();
if (subProperties.AnyAndNotNull())
{
// since we have properties, we attempt to create an item (e.g. Address) based on the type of the current p
var subItem = Activator.CreateInstance(Type.GetType(property.PropertyType.AssemblyQualifiedName));
// attempt to set each value on subItem (e.g. populating Street1, Street2 on Address).
subProperties.ForEach(p2 => RecursiveSetValue(p2, row, subItem, maxDepth, currentDepth));
// now that subItem (instance of Address) has been populated, we in turn need to populate the current p (Address) on item.
property.SetValue(instance, subItem, null);
}
else
{
property.SetValue(row, instance);
}
}
else
{
property.SetValue(row, instance);
}
}
catch (MaxRecursionException)
{
throw;
}
catch (Exception ex)
{
throw new Exception($"Property {property.Name} cannot be set from row.", ex);
}
}
private static string QuoteValue(string value) => string.Concat("\"", value.Replace("\"", "\"\""), "\"");
#endregion
}
}

View File

@ -0,0 +1,48 @@
using System;
namespace BinaryDad.Extensions
{
public static class DateTimeExtensions
{
public static bool IsWeekDay(this DateTime date)
{
//default to false
var returnValue = false;
switch (date.DayOfWeek)
{
case DayOfWeek.Monday:
case DayOfWeek.Tuesday:
case DayOfWeek.Wednesday:
case DayOfWeek.Thursday:
case DayOfWeek.Friday:
returnValue = true;
break;
default:
break;
}
return returnValue;
}
public static bool IsWeekEnd(this DateTime date)
{
//default to false
var returnValue = false;
switch (date.DayOfWeek)
{
case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
returnValue = true;
break;
default:
break;
}
return returnValue;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace BinaryDad.Extensions
{
public static class DirectoryExtensions
{
public static IEnumerable<FileInfo> GetFilesByExtensions(this DirectoryInfo directory, params string[] extensions)
{
if (extensions == null)
{
throw new ArgumentNullException(nameof(extensions));
}
return directory
.EnumerateFiles()
.Where(f => extensions.Contains(f.Extension, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace BinaryDad.Extensions
{
// NOTE: RJP - This extension may be generic enough to be used outside of just enums. Something to think about later.
public static class EnumExtensions
{
/// <summary>
/// Retrieves a descriptive attribute associated with the enum field using DescriptionAttribute
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string GetDescription(this Enum value)
{
#region DescriptionAttribute
var description = value.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
{
return description.Description;
}
#endregion
// default
return value.ToString();
}
/// <summary>
/// Retrieves a custom attribute of a specified type that is applied to a specified member
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T GetCustomAttribute<T>(this Enum value) where T : Attribute
{
var member = value
.GetType()
.GetMember(value.ToString())
.FirstOrDefault();
return member?.GetCustomAttribute<T>() ?? default;
}
}
}

View File

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BinaryDad.Extensions
{
public static class GenericExtensions
{
/// <summary>
/// Returns true if the value is between the lower and upper range. This is inclusive in its comparison.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="lower"></param>
/// <param name="upper"></param>
/// <returns></returns>
public static bool Between<T>(this T value, T lower, T upper) where T : IComparable
{
return Comparer<T>.Default.Compare(value, lower) >= 0
&& Comparer<T>.Default.Compare(value, upper) <= 0;
}
#region IfNotNull
/// <summary>
/// Executes an inline statement if the source value is not null
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="value"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public static TResult IfNotNull<TSource, TResult>(this TSource source, Func<TSource, TResult> value, TResult defaultValue)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (source != null)
{
return value(source);
}
return defaultValue == null ? default : defaultValue;
}
/// <summary>
/// Executes an inline statement if the source value is not null
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="value"></param>
/// <returns></returns>
public static TResult IfNotNull<TSource, TResult>(this TSource source, Func<TSource, TResult> value) => source.IfNotNull(value, default(TResult));
/// <summary>
/// Executes an inline statement if the source value is not null
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="value"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public static TResult IfNotNull<TSource, TResult>(this TSource source, TResult value, TResult defaultValue) => source.IfNotNull(o => value, defaultValue);
/// <summary>
/// Executes an inline statement if the source value is not null
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="value"></param>
/// <returns></returns>
public static TResult IfNotNull<TSource, TResult>(this TSource source, TResult value) => source.IfNotNull(o => value, default);
#endregion
#region If
/// <summary>
/// Executes an inline conditional statement, allowing for an evaluation for true or false
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="condition"></param>
/// <param name="trueResult"></param>
/// <param name="falseResult"></param>
/// <returns></returns>
public static TResult If<TSource, TResult>(this TSource source, Func<TSource, bool> condition, Func<TSource, TResult> trueResult, Func<TSource, TResult> falseResult = null)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (condition == null)
{
throw new ArgumentNullException(nameof(condition));
}
return source.If(condition(source), trueResult, falseResult);
}
/// <summary>
/// Executes an inline conditional statement, allowing for an evaluation for true or false
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="condition"></param>
/// <param name="trueResult"></param>
/// <param name="falseResult"></param>
/// <returns></returns>
public static TResult If<TSource, TResult>(this TSource source, bool condition, Func<TSource, TResult> trueResult, Func<TSource, TResult> falseResult = null)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (trueResult == null)
{
throw new ArgumentNullException(nameof(trueResult));
}
if (condition)
{
return trueResult(source);
}
if (falseResult != null)
{
return falseResult(source);
}
return default;
}
#endregion
#region In
/// <summary>
/// Returns whether a value is in a particular sequence of items
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="items">The collection of items the value may belong to</param>
/// <returns></returns>
public static bool In<T>(this T value, params T[] items) where T : IComparable => items.AnyAndNotNull(t => t.Equals(value));
/// <summary>
/// Returns whether a value is in a particular sequence of items
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="items">The collection of items the value may belong to</param>
/// <returns></returns>
public static bool In<T>(this T value, IEnumerable<T> items) where T : IComparable => In(value, items.ToArray());
#endregion
#region With
/// <summary>
/// Performs an action on an object and returns that instance
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="action"></param>
/// <returns></returns>
public static T With<T>(this T value, Action<T> action)
{
if (action == null)
{
// could throw null arg exception but "action" is the whole point of the method
throw new InvalidOperationException("No action specified in With<T> or is null. Please omit use of this method.");
}
action(value);
return value;
}
#endregion
}
}

View File

@ -0,0 +1,44 @@
namespace BinaryDad.Extensions
{
public static class NumericExtensions
{
public static string ToCurrency(this decimal currency, bool alwaysShowCents = false)
{
// only hide the cents if value is an even amount (i.e., has no cents LOL)
if (!alwaysShowCents && (int)currency == currency)
{
return ((int)currency).ToString("C0");
}
return currency.ToString("C");
}
public static string WithOrdinal(this int value)
{
if (value <= 0)
{
return value.ToString();
}
switch (value % 100)
{
case 11:
case 12:
case 13:
return $"{value}th";
}
switch (value % 10)
{
case 1:
return $"{value}st";
case 2:
return $"{value}nd";
case 3:
return $"{value}rd";
default:
return $"{value}th";
}
}
}
}

View File

@ -0,0 +1,158 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace BinaryDad.Extensions
{
public static class ObjectExtensions
{
/// <summary>
/// Target is the object wanted to be merged to, if source has the value and target does not, copy source value to target
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target"></param>
/// <param name="source"></param>
public static void CoalesceValues<T>(this T target, T source)
{
var t = typeof(T);
var properties = t.GetProperties().Where(prop => prop.CanRead && prop.CanWrite);
foreach (var prop in properties)
{
var valueT = prop.GetValue(target, null);
var valueS = prop.GetValue(source, null);
if (valueT == null)
{
if (valueS != null)
{
prop.SetValue(target, valueS, null);
}
}
}
}
#region To
/// <summary>
/// Casts or converts a value to type T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T To<T>(this object value)
{
if (value is T castValue)
{
return castValue;
}
return (T)value.To(typeof(T));
}
/// <summary>
/// Casts or converts a value to a specified type
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
public static object To(this object value, Type type)
{
#region Check if null
// if value is null and is value type, return "default" value
// otherwise, return null
if (value == null || value == DBNull.Value)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
#endregion
#region Check if empty string
if (type.IsValueType && value is string stringValue && stringValue == string.Empty)
{
return Activator.CreateInstance(type);
}
#endregion
var convertType = Nullable.GetUnderlyingType(type) ?? type;
#region Check if Enum
if (convertType.IsEnum)
{
return value.ToString().ToEnum(convertType);
}
#endregion
#region Attempt convert using TypeConverter.ConvertFrom
var converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(value.GetType()))
{
return converter.ConvertFrom(value);
}
#endregion
#region Convert using ChangeType
return Convert.ChangeType(value, convertType);
#endregion
}
#endregion
/// <summary>
/// Serializes an object to a JSON string. Wraps <see cref="JSON.Serialize{T}(T)"/>
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string Serialize(this object value) => JsonConvert.SerializeObject(value);
/// <summary>
/// Returns a dictionary of properties and values for an object or an empty dictionary if value is null or no properties (e.g., value types and string)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static IDictionary<string, object> GetPropertyValues(this object value)
{
// check for null
if (value == null)
{
return null;
}
var valueType = value.GetType();
// check for value types, and string
if (valueType.IsValueType || valueType == typeof(string))
{
return null;
}
return valueType
.GetProperties()
.EmptyIfNull()
.Select(p => new KeyValuePair<string, object>(p.Name, p.GetValue(value, null)))
.ToDictionary(k => k.Key, k => k.Value);
}
}
}

View File

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Reflection;
namespace BinaryDad.Extensions
{
public static class ReflectionExtensions
{
#region Public
/// <summary>
/// Determines whether a property has a specific <see cref="Attribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="property"></param>
/// <returns></returns>
public static bool HasCustomAttribute<T>(this PropertyInfo property) where T : Attribute
{
return property.GetCustomAttributes(typeof(T), false).Any();
}
/// <summary>
/// Sets the value of a property using the matched column from the data row, using or <see cref="ColumnAttribute"/> for binding.
/// </summary>
/// <param name="property"></param>
/// <param name="row"></param>
/// <param name="instance"></param>
public static void SetValue(this PropertyInfo property, DataRow row, object instance)
{
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
if (row == null)
{
return;
}
// get the column name from the row
var matchedColumnName = property.GetDataColumnName(row);
//Set the value if we matched a column. If we don't have a match, there's simply no way to set a value.
if (matchedColumnName != null)
{
// raw value from the row, matching on column name
var tableValue = row[matchedColumnName];
// if DBNull, set as null
var value = tableValue == DBNull.Value ? null : tableValue;
// if the value is null, we know to just set as null
if (value == null)
{
property.SetValue(instance, null, null);
}
else
{
try
{
property.SetValue(instance, value.To(property.PropertyType), null);
}
catch (Exception ex)
{
// encapsulate more detail about why the conversion failed
throw new DataPropertyConversionException(instance, property, value, ex);
}
}
}
}
#endregion
#region Internal
/// <summary>
/// Retrieves a list of available property binding aliases using <see cref="ColumnAttribute"/>.
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
internal static IEnumerable<string> GetDataColumnNames(this PropertyInfo property)
{
#region Null checks
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
#endregion
var dataRowFieldNames = new List<string>();
var attributes = property
.GetCustomAttributes(true);
var columnAttribute = attributes.FirstOfType<ColumnAttribute>();
if (columnAttribute != null)
{
dataRowFieldNames.Add(columnAttribute.Name);
}
// add the property's name at the end, so it's the last in the lookup
dataRowFieldNames.Add(property.Name);
return dataRowFieldNames;
}
/// <summary>
/// Retrieves a list of available property binding aliases using <see cref="EnumAliasAttribute"/>
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
internal static IEnumerable<string> GetEnumAliases(this FieldInfo field)
{
#region Null checks
if (field == null)
{
throw new ArgumentNullException(nameof(field));
}
#endregion
var aliasNames = new List<string>();
var enumAlias = field
.GetCustomAttributes(true)
.FirstOfType<EnumAliasAttribute>();
if (enumAlias != null)
{
aliasNames.AddRange(enumAlias.EnumAliasNames);
}
aliasNames.Add(field.Name);
return aliasNames;
}
/// <summary>
/// Retrieves the first matched column name from the data row. Uses <see cref="PropertyAliasAttribute"></see> or <see cref="ColumnAttribute"/>.
/// </summary>
/// <param name="property"></param>
/// <param name="row"></param>
/// <returns></returns>
internal static string GetDataColumnName(this PropertyInfo property, DataRow row)
{
return property.GetDataColumnName(row.Table.Columns);
}
/// <summary>
/// Retrieves the first matched column name from the collection of data columns. Uses <see cref="PropertyAliasAttribute"></see> or <see cref="ColumnAttribute"/>.
/// </summary>
/// <param name="property"></param>
/// <param name="columns"></param>
/// <returns></returns>
internal static string GetDataColumnName(this PropertyInfo property, DataColumnCollection columns)
{
//Check whether we have any columns with the data row field names. This should not be null if the property
//was configured correctly with PropertyAliasAttribute.
return property
.GetDataColumnNames()
.FirstOrDefault(f => columns.Contains(f));
}
#endregion
}
}

View File

@ -0,0 +1,28 @@
using System.IO;
using System.Threading.Tasks;
namespace BinaryDad.Extensions
{
public static class StreamExtensions
{
public static byte[] GetBytes(this Stream stream)
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
public static async Task<byte[]> GetBytesAsync(this Stream stream)
{
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
}
}

View File

@ -0,0 +1,357 @@
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace BinaryDad.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Searches a list of strings with a delimiter for a match on specified value.
/// </summary>
/// <param name="s">The the delimited string to search</param>
/// <param name="value">The value to match against delimited values</param>
/// <param name="separator">The string separator</param>
/// <param name="isCaseSensitive">True if a match is found, otherwise false</param>
/// <returns></returns>
public static bool ContainsMatch(this string s, string value, char separator = ';', bool isCaseSensitive = false)
{
if (s == null)
{
return false;
}
if (isCaseSensitive)
{
return s.SafeSplit(separator).Any(item => value.Trim().Contains(item.Trim()));
}
return s.SafeSplit(separator).Any(item => value.ToLower().Trim().Contains(item.ToLower().Trim()));
}
public static bool Contains(this string source, string value, StringComparison comparisonType) => source.IndexOf(value, comparisonType) >= 0;
/// <summary>
/// Retrieves the last length of characters
/// </summary>
/// <param name="source"></param>
/// <param name="length"></param>
/// <returns></returns>
public static string GetLast(this string source, int length)
{
return length >= source.Length
? source
: source.Substring(source.Length - length);
}
/// <summary>
/// Parse a datetime string using DateTime.TryParse, returning null if it cannot be parsed.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static DateTime? ParseDateTime(this string value)
{
if (DateTime.TryParse(value, out var date))
{
return date;
}
return null;
}
/// <summary>
/// Validate date string
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
public static bool IsDate(this string date)
{
try
{
var dt = DateTime.Parse(date);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Checks whether the string is an email.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsEmail(this string value)
{
if (value.IsNullOrWhiteSpace())
{
return false;
}
try
{
return Regex.IsMatch(value,
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
}
catch (RegexMatchTimeoutException)
{
return false;
}
}
/// <summary>
/// Wrapper for String.IsNullOrEmpty(value)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value);
/// <summary>
/// Wrapper for String.IsNullOrWhiteSpace(value)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value);
/// <summary>
/// Allows for a replacement string if the value is null
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
/// <returns></returns>
public static string ValueIfNull(this string obj, string value)
{
if (obj == null)
{
return value;
}
return obj;
}
/// <summary>
/// Returns true if the string is not null and has a value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsNotEmpty(this string value) => !value.IsNullOrWhiteSpace();
/// <summary>
/// Returns whether a value is in a particular sequence of strings
/// </summary>
/// <param name="value"></param>
/// <param name="comparisonType"></param>
/// <param name="items">The collection of items the value may belong to</param>
/// <returns></returns>
public static bool In(this string value, StringComparison comparisonType, params string[] items) => items.AnyAndNotNull(t => t.Equals(value, comparisonType));
/// <summary>
/// Checks whether the entire string is numeric. This purely evaluates numbers, not currency (decimals/thousand-separators).
/// </summary>
/// <param name="value"></param>
/// <returns>True, if entire string is numeric; otherwise, false.</returns>
public static bool IsNumeric(this string value) => decimal.TryParse(value, out _);
/// <summary>
/// Returns whether a string matches a regular expression pattern
/// </summary>
/// <param name="value">The string to check</param>
/// <param name="regex">The regex pattern</param>
/// <param name="ignoreCase"></param>
/// <returns></returns>
public static bool Like(this string value, string regex, bool ignoreCase = true) => Regex.IsMatch(value, regex, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
public static string[] Split(this string text, params string[] separator) => text.Split(separator, StringSplitOptions.None);
public static string[] SafeSplit(this string s, char separator = ';')
{
if (s == null)
{
return new string[] { };
}
return (from sr in s.Split(separator)
let tr = sr.Trim()
where !string.IsNullOrEmpty(tr)
select tr).ToArray();
}
public static string NullIfWhiteSpace(this string text) => text.If(s => s.IsNullOrWhiteSpace(), s => null, s => s);
#region ToEnum
/// <summary>
/// Casts or converts a string value to type T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T ToEnum<T>(this string value) where T : Enum
{
var type = typeof(T);
var parsedEnum = value.ToEnum(type);
if (parsedEnum == null)
{
throw new ApplicationException($"Could not cast value {value} to type {type.FullName}");
}
return (T)parsedEnum;
}
/// <summary>
/// Casts or converts a string value to a type
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
public static object ToEnum(this string value, Type type)
{
#region Constraints
if (value == null)
{
return null;
}
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (!type.IsEnum)
{
throw new ArgumentException("Destination conversion type must be an Enum", nameof(type));
}
#endregion
#region Helpers
bool isDefined(string s)
{
return int.TryParse(s, out var num)
? Enum.IsDefined(type, num)
: Enum.IsDefined(type, s);
}
#endregion
// Try to parse normally (works for int values and literal strings matching enum field name)
// Calling the isDefined wrapper handles checks for ints or string values, as we can't use TryParse
if (isDefined(value))
{
return Enum.Parse(type, value);
}
else // If no match, dig up enum alias attributes for a match
{
return type
.GetFields()
.FirstOrDefault(field => field.GetEnumAliases().Any(a => a.Equals(value, StringComparison.OrdinalIgnoreCase)))
?.GetValue(null);
}
}
#endregion
public static string NullableTrim(this string value) => value?.Trim();
public static string ToPhoneNumber(this string value) => Regex.Replace(value, @"^\d?(\d{3})(\d{3})(\d{4})$", "($1) $2-$3");
public static string MaskSsn(this string value)
{
if (!value.IsNullOrWhiteSpace())
{
var match = Regex.Match(value, @"\d{3}.?\d{2}.?(\d{4})");
if (match.Success)
{
return $"XXX-XX-{match.Groups[1].Value}";
}
}
return null;
}
/// <summary>
/// Truncates a string to a maximum length value
/// </summary>
/// <param name="text"></param>
/// <param name="limit"></param>
/// <param name="useWholeWords">Indicates whether to include whole words when truncating (i.e., whether to truncate in the middle of a word)</param>
/// <param name="useEllipsis">Indicates whether to include an ellipsis (...) if truncating occurs.</param>
/// <returns></returns>
public static string Truncate(this string text, int limit, bool useWholeWords = false, bool useEllipsis = false)
{
var output = text;
if (!text.IsNullOrWhiteSpace() && output.Length > limit && limit > 0)
{
output = output.Substring(0, limit);
// include whole words when truncating (don't truncate in middle of a word)
if (useWholeWords && text.Substring(output.Length, 1) != " ")
{
var lastSpaceIndex = output.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase);
if (lastSpaceIndex != -1)
{
output = output.Substring(0, lastSpaceIndex);
}
}
if (useEllipsis)
{
output += "...";
}
}
return output;
}
/// <summary>
/// Deserializes text to a type. Wraps <see cref="JsonConvert.DeserializeObject{T}(string)"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="text"></param>
/// <returns></returns>
public static T Deserialize<T>(this string text) => JsonConvert.DeserializeObject<T>(text);
/// <summary>
/// Deserializes text to a type. Wraps <see cref="JsonConvert.DeserializeObject(string, Type)"/>.
/// </summary>
/// <param name="text"></param>
/// <param name="type"></param>
/// <returns></returns>
public static object Deserialize(this string text, Type type) => JsonConvert.DeserializeObject(text, type);
/// <summary>
/// Encodes a plaintext string into Base-64
/// </summary>
/// <param name="plainText"></param>
/// <returns></returns>
public static string Base64Encode(this string plainText)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(bytes);
}
/// <summary>
/// Decodes a Base-64 encoded string into plaintext
/// </summary>
/// <param name="encodedText"></param>
/// <returns></returns>
public static string Base64Decode(this string encodedText)
{
var bytes = Convert.FromBase64String(encodedText);
return System.Text.Encoding.UTF8.GetString(bytes);
}
}
}

View File

@ -0,0 +1,17 @@
using System;
namespace BinaryDad.Extensions
{
public static class TypeExtensions
{
public static bool IsNullable(this Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
}

View File

@ -0,0 +1,57 @@
using System.Xml;
namespace BinaryDad.Extensions
{
public static class XmlExtensions
{
/// <summary>
/// Null-safe retrieval of a <see cref="XmlNode.Attributes"/> collection. Returns null if node is null.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="node"></param>
/// <param name="attribute"></param>
/// <returns></returns>
public static T GetAttributeValue<T>(this XmlNode node, string attribute)
{
var selectedAttribute = node?.Attributes?[attribute];
if (selectedAttribute != null)
{
return selectedAttribute.Value.To<T>();
}
return default;
}
/// <summary>
/// Null-safe retrieval of a <see cref="XmlNode.Attributes"/> collection. Returns null if node is null.
/// </summary>
/// <param name="node"></param>
/// <param name="attribute"></param>
/// <returns></returns>
public static string GetAttributeValue(this XmlNode node, string attribute) => node.GetAttributeValue<string>(attribute);
/// <summary>
/// Null-safe retrieval of a <see cref="XmlNode.InnerText"/>. Returns null if node is null.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="node"></param>
/// <returns></returns>
public static T GetInnerText<T>(this XmlNode node)
{
if (node?.InnerText != null)
{
return node.InnerText.To<T>();
}
return default;
}
/// <summary>
/// Null-safe retrieval of a <see cref="XmlNode.InnerText"/>. Returns null if node is null.
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public static string GetInnerText(this XmlNode node) => node.GetInnerText<string>();
}
}

View File

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
namespace BinaryDad.Extensions
{
public class RestUtility
{
public const int DefaultTimeout = 5000;
public const int DefaultSleepDelay = 3000;
public const int DefaultRetries = 3;
#region Get
/// <summary>
/// Invokes a GET request with optional headers
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static T Get<T>(string url, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
return Send(url, HttpMethod.Get, typeof(T), null, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed).To<T>();
}
/// <summary>
/// Invokes a GET request with optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static string Get(string url, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
// response type is always a string if no returnObjectType is used
return Send(url, HttpMethod.Get, null, null, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed) as string;
}
/// <summary>
/// Invokes a GET request with optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="returnObjectType"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <param name="sleepDelayMs"></param>
/// <param name="retries"></param>
/// <returns></returns>
public static object Get(string url, Type returnObjectType, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
return Send(url, HttpMethod.Get, returnObjectType, null, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed);
}
#endregion
#region Post
/// <summary>
/// Invokes a POST request with optional headers
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static T Post<T>(string url, object body, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
return Send(url, HttpMethod.Post, typeof(T), body, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed).To<T>();
}
/// <summary>
/// Invokes a POST request with optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static string Post(string url, object body, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
// response type is always a string if no returnObjectType is used
return Send(url, HttpMethod.Post, null, body, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed) as string;
}
/// <summary>
/// Invokes a POST request with optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="returnObjectType"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <param name="sleepDelayMs"></param>
/// <param name="retries"></param>
/// <returns></returns>
public static object Post(string url, Type returnObjectType, object body, Dictionary<string, string> additionalHeaders, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retries = DefaultRetries)
{
return Send(url, HttpMethod.Post, returnObjectType, body, additionalHeaders, timeoutMs, sleepDelayMs, retries);
}
#endregion
#region Send
/// <summary>
/// Invokes a request with custom method/verb and optional headers
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="method"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static T Send<T>(string url, HttpMethod method, object body = null, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
return Send(url, method, typeof(T), body, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed).To<T>();
}
/// <summary>
/// Invokes a request with custom method/verb and optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="method"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <returns></returns>
public static string Send(string url, HttpMethod method, object body = null, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
return Send(url, method, null, body, additionalHeaders, timeoutMs, sleepDelayMs, retriesAllowed) as string;
}
/// <summary>
/// Invokes a request with custom method/verb and optional headers
/// </summary>
/// <param name="url"></param>
/// <param name="method"></param>
/// <param name="returnObjectType"></param>
/// <param name="body"></param>
/// <param name="additionalHeaders"></param>
/// <param name="timeoutMs">Timeout of the request in milliseconds</param>
/// <param name="sleepDelayMs"></param>
/// <param name="retriesAllowed"></param>
/// <returns></returns>
public static object Send(string url, HttpMethod method, Type returnObjectType, object body = null, Dictionary<string, string> additionalHeaders = null, int timeoutMs = DefaultTimeout, int sleepDelayMs = DefaultSleepDelay, int retriesAllowed = DefaultRetries)
{
var serializedResponse = string.Empty;
var success = false;
for (var attempts = 1; attempts <= retriesAllowed && !success; attempts++)
{
var request = WebRequest.CreateHttp(url);
request.Method = method.ToString();
request.Timeout = timeoutMs;
request.ContentType = "application/json";
request.Accept = "application/json, text/javascript, *; q=0.01"; // Accept is a reserved header, so you must modify it rather than add
// add additional headers
if (additionalHeaders != null)
{
foreach (var key in additionalHeaders.Keys)
{
if (additionalHeaders[key] != null)
{
request.Headers.Add(key, additionalHeaders[key]);
}
else
{
request.Headers.Add(key);
}
}
}
try
{
if (body != null)
{
var serializedBody = body.Serialize();
var bytes = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(serializedBody);
request.ContentLength = bytes.Length;
using (var writeStream = request.GetRequestStream())
{
writeStream.Write(bytes, 0, bytes.Length);
}
}
else if (method == HttpMethod.Post) // POST requires a content length, set to 0 for null body
{
request.ContentLength = 0;
}
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode < HttpStatusCode.BadRequest)
{
// Success
using (var responseStream = response.GetResponseStream())
{
if (responseStream != null)
{
using (var reader = new StreamReader(responseStream))
{
serializedResponse = reader.ReadToEnd();
}
}
}
}
}
success = true;
}
catch
{
// only throw after we have reached our retry limit
if (attempts >= retriesAllowed)
{
throw;
}
}
// if post failed, pause before another attempt
if (!success)
{
Thread.Sleep(sleepDelayMs);
}
else
{
break;
}
}
if (success && returnObjectType != null)
{
return serializedResponse.Deserialize(returnObjectType);
}
else
{
return serializedResponse;
}
}
#endregion
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace BinaryDad.Extensions
{
public static class UrlUtility
{
/// <summary>
/// Combines a base URL with a relative URL
/// </summary>
/// <param name="baseUrl"></param>
/// <param name="relativeUrl"></param>
/// <returns></returns>
public static string Combine(string baseUrl, string relativeUrl)
{
// ensure domain ends with trailing slash
if (!baseUrl.EndsWith("/"))
{
baseUrl = $"{baseUrl}/";
}
var baseUri = new Uri(baseUrl);
if (!baseUri.IsAbsoluteUri)
{
throw new ArgumentException("Base URL must be absolute", nameof(baseUrl));
}
// ensure relative path does not have a prefixed slash
relativeUrl = relativeUrl.TrimStart('/');
return new Uri(baseUri, relativeUrl).OriginalString;
}
}
}