initial files
This commit is contained in:
parent
7943589101
commit
2aa9ce3eb7
6
.nuget/NuGet.Config
Normal file
6
.nuget/NuGet.Config
Normal 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
144
.nuget/NuGet.targets
Normal 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
34
BinaryDad.Extensions.sln
Normal 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
|
21
BinaryDad.Extensions/Annotations/CommandFlagAttribute.cs
Normal file
21
BinaryDad.Extensions/Annotations/CommandFlagAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
BinaryDad.Extensions/Annotations/DataRowPopulateAttribute.cs
Normal file
10
BinaryDad.Extensions/Annotations/DataRowPopulateAttribute.cs
Normal 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 { }
|
||||||
|
}
|
19
BinaryDad.Extensions/Annotations/EnumAliasAttribute.cs
Normal file
19
BinaryDad.Extensions/Annotations/EnumAliasAttribute.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
BinaryDad.Extensions/BinaryDad.Extensions.csproj
Normal file
22
BinaryDad.Extensions/BinaryDad.Extensions.csproj
Normal 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>
|
17
BinaryDad.Extensions/BinaryDad.Extensions.nuspec
Normal file
17
BinaryDad.Extensions/BinaryDad.Extensions.nuspec
Normal 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>
|
197
BinaryDad.Extensions/CacheHelper.cs
Normal file
197
BinaryDad.Extensions/CacheHelper.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
57
BinaryDad.Extensions/ConsoleHelper.cs
Normal file
57
BinaryDad.Extensions/ConsoleHelper.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
BinaryDad.Extensions/CryptoHelper.cs
Normal file
183
BinaryDad.Extensions/CryptoHelper.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
BinaryDad.Extensions/Exceptions/MaxRecursionException.cs
Normal file
14
BinaryDad.Extensions/Exceptions/MaxRecursionException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
|
}
|
715
BinaryDad.Extensions/Extensions/CollectionExtensions.cs
Normal file
715
BinaryDad.Extensions/Extensions/CollectionExtensions.cs
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
347
BinaryDad.Extensions/Extensions/DataTableExtensions.cs
Normal file
347
BinaryDad.Extensions/Extensions/DataTableExtensions.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
48
BinaryDad.Extensions/Extensions/DateTimeExtensions.cs
Normal file
48
BinaryDad.Extensions/Extensions/DateTimeExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
BinaryDad.Extensions/Extensions/DirectoryExtensions.cs
Normal file
22
BinaryDad.Extensions/Extensions/DirectoryExtensions.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
BinaryDad.Extensions/Extensions/EnumExtensions.cs
Normal file
49
BinaryDad.Extensions/Extensions/EnumExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
BinaryDad.Extensions/Extensions/GenericExtensions.cs
Normal file
192
BinaryDad.Extensions/Extensions/GenericExtensions.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
44
BinaryDad.Extensions/Extensions/NumericExtensions.cs
Normal file
44
BinaryDad.Extensions/Extensions/NumericExtensions.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
BinaryDad.Extensions/Extensions/ObjectExtensions.cs
Normal file
158
BinaryDad.Extensions/Extensions/ObjectExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
BinaryDad.Extensions/Extensions/ReflectionExtensions.cs
Normal file
173
BinaryDad.Extensions/Extensions/ReflectionExtensions.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
28
BinaryDad.Extensions/Extensions/StreamExtensions.cs
Normal file
28
BinaryDad.Extensions/Extensions/StreamExtensions.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
357
BinaryDad.Extensions/Extensions/StringExtensions.cs
Normal file
357
BinaryDad.Extensions/Extensions/StringExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
BinaryDad.Extensions/Extensions/TypeExtensions.cs
Normal file
17
BinaryDad.Extensions/Extensions/TypeExtensions.cs
Normal 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<>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
BinaryDad.Extensions/Extensions/XmlExtensions.cs
Normal file
57
BinaryDad.Extensions/Extensions/XmlExtensions.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
253
BinaryDad.Extensions/RestUtility.cs
Normal file
253
BinaryDad.Extensions/RestUtility.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
34
BinaryDad.Extensions/UrlUtility.cs
Normal file
34
BinaryDad.Extensions/UrlUtility.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user