[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: RickStrahl\ncustom: \"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K677THUA2MJSE&source=url\"\ncustom: \"https://store.west-wind.com/product/donation\""
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\nTest/\nUnitTestProject2/\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n#*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n*.saved.bak\n"
  },
  {
    "path": "Changelog.md",
    "content": "# Westwind.Utilities Changelog\n\n\n## 5.3\n\n* **HttpClientUtils.DownloadBytes/Async**  \nAdded routine to easily download bytes from an Http server.\n\n* **HttpClientUtils.DownloadFile/Async**   \nDownload content directly to file from an Http Server.\n\n* **Additional HttpClientUtils Settings Object Response Properties**  \nProvided several helper properties to facilitate common header access. Explicitly exposed new Response properties: `ResponseContentType`, `ResponseContentLength`,  `ResponseContentHeaders` and `ResponseHeaders`.\n\n\n### 5.2\n\n* **Add .NET 10 Target**  \nChange Targets to .NET 10 and .NET 8.0 (and .NET Standard and net472). Dropped .NET 9 target (handled by .NET 8.0).\n\n* **Add HttpClientRequestSettings.AddPostKey() and AddPostFile() Methods**  \nYou can now easily create Http Form POST variables for using `settings.RequestPostMode` and using `setting.AddPostKey()` and `settings.AddPostFile()`. You can also retrieve the PostBuffer explicitly using `settings.GetPostBuffer()` and `settings.GetPostBufferBytes()` which allows easy creation of a POST buffer you can send to a raw Http request even outside of `HttpClientUtils`. Useful utility for quick form variable encoding.\n\n* **ImageUtils.IsImage()**  \nTwo versions one for filename extension check, and one for binary data image signature check.\n\n### 5.1.10\n\n* **FileUtils.CopyFileEnsureDirectory()**  \nAdded method that copies a file and creates the corresponding directory structure if it doesn't exist.\n\n* **StringUtils.StripAfter()**  \nMethod that strips text after a matched delimiter at the end of the string or returns the original string.\n\n* **StringUtils.ReplaceLastNthInstance()**  \nMethod that replaces the last nth instance of a sub string in an existing string. \n\n\n\n### 5.1.6\n\n* **FileUtils.GetRelativeFilePath()**  \nCompares two file paths and returns a relative path to the second file from the first. This version works with files, while the existing `GetRelativePath()` works with a base folder (slightly different behavior).\n\n* **FileUtils.ResolvePath()**   \nReturns an absolute file path for a relative path and a base file or folder.\n\n* **FileUtils.IsRelativePath()**  \nChecks a path string if a path is a relative path (ie. not rooted). Doesn't compare against another file, but simply checks whether the file meets the criteria for a relative Url.\n\n* **FileUtils.TildefyUserPath()**  \nReplaces the user path with the `~` to make the path generic. Useful if you store file or folder info in configuration files in a shared location that can be accessed from different computers. You can use `FileUtils.ExpandPathEnvironmentVariables()` to expand the `~` in addition to environment variables.\n\n* **Add Sync Methods to HttpClientUtils (Core only)**  \nAdd several sync methods to the `HttpClientUtils()` helper - `DownloadString()`, `DownloadBytes()`, `DownloadStream()` to complement the async versions. These new methods are not supported for .NET Framework - where you need to use HttpUtils that use older `WebClient()` for sync operations.\n\n* **Basic PasswordScrubber Class for Json and Sql Connections**  \nAdded a very basic Password scrubber that attempts to scrub passwords for keys you provide. By default `password` and `pwd` are parsed but you can scrub any values that you need specifically. Two methods: `ScrubJsonValues()` and `ScrubSqlConnectionValues()`.\n\n* **Option on FileUtils.CopyDirectory() to ignore errors**  \nAdded option to ignore errors typically caused by locked files when copying directories. By default exceptions are thrown for file copy errors. With the switch set errors are ignored. This can be useful in scenarios where directories need to be copied where there might be some locked files in use (bound images for example) where it's OK to not copy those files.\n\n### 5.0.9\n\n* **Add HttpClientUtils support for .NET Framework**  \nThe HttpClientUtils class now works on .NET Framework. It was previously only supported on .NET Core.\n\n* **Add FileUtils.ReadAllBytesAsync() and WriteAllBytesAsync()**  \nSupport functions for .NET Framework to support missing `System.IO.File` async methods.\n\n* **Update Microsoft.Data.SqlClient**  \nMicrosoft finally fixed the vulnerability related Azure dependencies in Microsoft.Data.SqlClient.\n\n\n### 5.0.7\n\n* **StringUtils 'Many' Operations**  \nString extension methods that operate on 'many' values for various string operations: `EqualsMany()`, `ContainsMany()`, `StartsWithMany()`, `ReplaceMany()`. The finds \n\n* **AsyncUtils.DelayExecution()**  \nAdded a couple of handlers that allow delayed code execution off a timer. Pass in an Action to execute, a timeout and an optional error handler.\n\n* **AsyncUtils.Timeout() and TimeoutWithResult()**  \nTask extension method that provide for timing out Task execution. Two overloads one that returns true/false and one that returns a value (similar to `Task.WaitAsync()` new in .NET 8.0 but not available prior).\n\n* **VersionUtils.FormatVersion()**  \nA generic and configurable string formatter that makes it easier to display version strings more consistently by specifying the number of tokens to display and which `.0` tokens to trim (and leave).\n\n* **Fix: UrlEncodingParser Null Value Handling**  \nFix null values in the query collection from blowing up the parser and string output. Null values explicitly assigned or missing values now return `string.Empty` - no more null returns.\n\n\n### 5.0 \n\n**Breaking Changes** \n\n* **New Westwind.utilities.Windows Package**  \nAdded a new package that moves the `WindowsUtils` class to the new package. Also a copied version of `HtmlUtils` has moved to this pacakge as it includes `System.Web` references. Several legacy, System.Web specific methods have been removed from `HtmlUtils` in the core library and now only exist in the Windows library:  \n  * HtmlUtils.ResolveUrl()\n  * HtmlUtils.ImageRef()\n  * HtmlUtils.Href()\n\n* **Westwind.Utilities.Data now uses `Microsoft.Data.SqlClient` for NETFX**  \nSwitched over for both the netStandard and net472 targets to use `Microsoft.Data.SqlClient` for consistency. This will increase the size of distribution but it allows for support for latest SQL features.\n\n* **Removed Westwind Logging**  \nThe old legacy logging library has been removed as it was severely data and introduced nasty dependencies. Apps that depend on it can stick to 4.x versions or move to some more modern logging abstraction.\n\n### 4.0.21\n\n* **StringUtils.GetLastCharacters()**  \nRetrieves up to the last n characters from the end of a string.\n\n* **WindowsUtils.IsUserAnAdmin()**  \nDetermines if user is an Administrator on Windows.\n\n* **StringUtils.ToBase64String()/FromBase64String()**  \nHelper method that simplifies converting strings to and from Base64 without having to go through the intermediary byte conversion explicitly.\n\n* **Fix: HttpClientUtils HasErrors Result**  \nFix `HttpClientRequestSettings.HasErrors` when requests hard fail due to network issues. \n\n### 4.0\n\n* **Refactor Data Utilities into separate Westwind.Utilities.Data Library**   \nDue to the large required SQL Data dependencies on .NET Core, we've removed the data access components from this library and separated them out into a `Westwind.Utilities.Data`. Functionality remains the same, but now requires adding this second library in order to use the data features. This keeps the original library dependencies small even on .NET Standard/Core.\n\n* **StringUtils.Occurs()**  \nCounts the number of times a character or substring occurs in a given string.\n\n* **StringUtils.StringToStream() and StringUtils.StreamToString()**  \nConverts a string to a `MemoryStream` with Encoding, or reads a string from an input stream.\n\n* **StringUtils.GetMaxCharacters()**  \nString extension method that retrieves a string with n characters max optionally from a specified start position.\n\n* **Fix StringSerializer Null Values**  \nFix null values for string properties that were returning the string `\"null\"` instead of an actual `null`.\n\n* **Fix: AppConfiguration.Write() if Provider is not configured**  \nFix `Write()` method when the provider is not configured yet by loading Provider via `OnCreateDefaultProvider()` rather than doing a full initialize which reloaded the config data. This preserves the data and only loads the provider and was the cause of occasional failures if assigning a new configuration object completely.\n\n* **Remove .NET 4.0 Target**  \nWe've removed the .NET 4.0 target, leaving `net462` and `netstandard2.0` as the two targets for this library.\n\n* **Update Newtonsoft.Json**  \nUpdate to latest JSON.NET Nuget package.\n\n* **ReflectionUtils.ShallowClone()**  \nHelper method to clone an object using shallow value cloning. Means: Single level clone only. Value types are cloned, References are just attached as is. Basically calls private `MemberwiseClone()` method on the source object.\n\n* **FileUtils.GetShortPath()**  \nReturns a short path that uses 8.3 character file syntax. This allows shortening long paths that exceed `MAX_PATH` to work with APIs that don't support long file or extended path syntax.\n\n* **TimeUtils.IsBetween**  \nAdd helper extension method to DateTime and TimeSpan for checking for date between a high and low date.\n\n* **Fix TimeUtils ShortDate Formatting**  \nMake short date formatting locale aware. Add optional date separator parameter.\n\n* **AsyncUtils.FireAndForget() / Task.FireAndForget()**  \nAdded a couple of extension methods that allow for safe execution of async methods by explicitly continuing the task if an exception occurs. Works around hte issue that exceptions can otherwise wait around to be fired until the finalizer runs which can cause unexpected failures.\n\n* **JsonSerializationUtils CamelCase Support for Serialization**  \nYou can now specify an additional optional parameter `camelCase` on the `Serialize()` and `serializeToFile()` methods that applies camelCasing to JSON output, instead of the exact case output used by default.\n\n* **StreamExtensions.AsString()**  \nReturns the contents of a stream as a string with optional encoding. \n\n* **MemoryStreamExtensions.AsString()**  \nHelper extension method that lets you convert a memory stream to a string with optional Encoding.\n\n### 3.0.55\n<small>March 12, 2021</small>\n\n* **NetworkUtils.IsLocalIpAddress**  \nChecks to see if an IP Address is a local address by checking for localhost/loopback and checking host IP (if valid) against local IP Addresses.\n\n* **Fix: Replace Timeout with TimeoutMs**  \nReplace Timeout property with TimeoutMs to fix issues with sub-1000ms request timeouts. Conversions from calling applications often resulted in invalid values being assigned due to the conversion from milliseconds to seconds. The existing Timeout property (in seconds) is marked `[Obsolete]` but remains available for use.\n\n* **ValidationErrors.HasErrors Property**   \nAdded a `HasErrors` property to make the check for errors more explicit than `ValidationErrors.Count > 0`.\n\n### 3.0.51\n<small>February 17, 2021</small>\n\n* **Switch Data Access to Microsoft.Data.SqlClient**  \nSwitch the Sql Client library to the new `Microsoft.Data.SqlClient` for .NET Standard and .NET Standard. The full framework libs continue using `System.Data.SqlClient` native in the framework `System.Data` assembly.\n\n* **Add HttpUtils.DownloadImageToFile\\Async()**  \nAdded method to download images from a URL and save them to file. Fixes up the file extension depending on the mime type of the Web request.\n\n* **ImageUtils.GetExtensionFromMediaType()**  \nThis method retrieves a file extension based on a media/content type. Commonly needed if you're downloading images from the Web to determine what type of file needs to be created.\n\n* **HttpUtils.DownloadBytes\\Async() for Binary data**  \nHttp helper to download HTTP contents into a `byte[]`.\n\n* **ComObject Wrapper for .NET Core**  \nAdded `ComObject` class that allows wrapping COM objects in .NET Core so they work with late binding since .NET Core doesn't support `dynamic` to access COM objects. This class implements `DynamicObject` and retrieves missing member data via Reflection simplifying COM access in .NET Core.\n\n* **Fix issues with StringUtils.TextAbstract() & Line Breaks**  \nFix behavior of text abstract with line breaks not being turned into spaces consistently. Also check for null.\n\n* **ShellUtils.OpenUrl() - Platform agnostic Browser Opening**  \nThis method opens a URL in the system browser on Windows, Mac and Linux.  \n\n* **WindowsUtils.TryGetRegistryKey() Signature Change**  \nChange the signature to TryGetRegistryKey() to pass in the base key into the optional `baseKey` parameter. The previously used `bool` value was not effective if something other than HKLM or HKCU was used. \nNote: this is a potentially breaking change.\n\n* **FileUtils.HasInvalidPathCharacters()**  \nAdded a function that checks paths for invalid characters. Uses default invalid character list, plus you can pass in additional characters to be deemed invalid.\n\n> ### Breaking Changes for 3.0.50\n> * **WindowUtilities.TryGetRegistryKey() parameter Change**  \nThe baseKey parameter replaces a `bool` parameter. Signature for most scenarios will stay the same if the parameter was omitted but the new version breaks binary compatibility which a simple recompile should fix.\n>\n> * **Renamed `DataAccessBase.Find<T>()` KeyLookup overload**  \n> Renamed this method to `FindKey<T>()` to avoid ambiguous reference errors. This will break compilation if the method is used for key lookups. You might also want to do a check for all `.Find()` usage to ensure it's not unintentionally firing `Find<T>()` when `FindKey<T>()` is desired.\n\n\n### 3.0.40\n*May 22nd, 2020*\n\n* **ReflectionUtils.InvokeEvent()**  \nMethod that allows triggering of events even from outside of the host class using Reflection without requiring a wrapper `OnEvent()` handler method to force operation into the original definition's class.\n\n* **XmlIgnore for XML Configuration**  \nYou can now specify `[XmlIgnore]` for properties when using XML configure to have properties ignored when serializing, deserializing. This worked for external files before, but not for config file configuration in full framework.\n\n* **FileUtils.ShortFilename**  \nTurn a Windows long filename into a short filename. Use to get around 256 MAX_PATH limitations for some operations.\n\n* **SqlDataAccess.DoesTableExist()**  \nAdded a method that checks to see if a table exist in the current database.\n\n* **DataUtils.RemoveBytes()**  \nRemoves a sequence of bytes from a byte array.\n\n* **Fix: TextAbstract() remove Line Breaks**  \n`TextAbstract()` now removes line breaks when creating the the abstract and replaces them with spaces.\n\n* **Fix: FileUtils.SafeFilename trailing Spaces**  \nFix trailing spaces issue in SafeFilename, when last character is a replacement character.\n\n* **Fix: Missing resources**  \nProject conversion appears to have removed resx resources for localization resulting in empty messages for some operations.\n\n* **Fix: Xml Logging Adapter with Empty File**  \nFix error when creating a new XML error log and appending the XML closing tag.\n\n### 3.0.30\n*August 28th, 2019*\n\n* **Add some Async Methods to DataAccessBase**  \nAdd `ExecuteNonQueryAsync`, `ExecuteScalarAsync`, `InsertEntityAsync` methods. More to come in future updates. \n\n* **StringUtils.Right() Method**  \nAdd method to retrieve the rightmost characters from a string.\n\n* **Add TextLogAdapter**  \nAdd a log adapter for plain text output.\n\n* **Update: ShellUtils.ExecuteProcess with Output Capture**  \nAdd overload for `ShellUtils.ExecuteProcess()` that allows passing in a string that is filled with the output generated from execution of a command line tool or an optional `Action<string>` that can intercept output as it's generated and fire your own capture code.\n\n### 3.0.28\n*June 23rd, 2019*\n\n* **DataUtils.IndexOfByteArray()**  \nSmall helper that finds a array of bytes or a string in an buffer of bytes and returns an index. Similar to `Span.IndexOf` but works in environments where not available, and also supports searching for decoded strings.\n\n* **Fix: HtmlUtils.HtmlEncode()**  \nHandle encoding of single quotes `'` as well as double quotes. Also marked this method as obsolete (despite the PR fix :-)) since this is handled by `System.Net.WebUtility` class now (since .NET 4.0).\n\n* **Fix: ReflectionUtils.CallMethodExCom()**  \nFix bug when traversing object hierarchy.\n\n* **Add .NET 4.8 to WindowsUtilities.GetDotnetVersion()**  \nAdd support for .NET 4.8 and also fix an errant `Debug.Writeline()` in this method.\n\n### 3.0.24\n*February 28th, 2019*\n\n* **Fix: HtmlUtils.SanitizeHtml() for multi-line**  \nFix SanitizeHtml() function to work across line breaks for a tag.\n\n* **XmlUtils.XmlString()**  \nCreate an XML encode string for elements or attributes.\n\n* **FileUtils.DeleteFiles()**  \nAdded routine that deletes files in a folder based on a path spec, optionally recursively.\n\n* **DebugUtils.GetCodeWithLineNumbers()**  \nAdd method that creates lines with line numbers appended. Useful for displaying source code.\n\n* **StringUtils.Truncate()**  \nAdded method that truncates a string if it exceeds a certain number of characters.\n\n### 3.0.20\n*September 6th, 2018*\n\n* **HtmlUtils.SanitizeHtml()**  \nRegEx based HTML sanitation that handles the most common script injection scenarios for `<script>`,`<iframe>`,`<form>` etc. tags, `javascript:` script embeds and `onXXX` DOM element event handlers.\n\n* **StringUtils.IndexOfNth() and .LastIndexOfNth()**  \nHelper that returns an index for the nTh occurrance of a matched character in string.\n\n* **ShellUtils.OpenInExplorer()**  \nAllows opening an Explorer Window for for a folder or file in a folder. (full framework only)\n\n* **ShellUtils.ExecuteProcess()**  \nWrapper around Process.Start() that captures exceptions and handles a few common scenarios simply including execution with timeout and presetting the Window. (full framework only)\n\n* **ShellUtils.OpenTerminal()**  \nOpens a shell window using Powershell or Command Prompt in a given pre-selected folder. (full framework only)\n\n* **WindowsUtils.GetWindowsVersion() and GetDotnetVerision()**  \nHelpers that retrieve a display version string that can be used by an application to display the Windows and .NET version in a meaningful way.\n\n* **HttpClient.HttpVerb Property**  \nAdded HttpVerb property directly to the HttpClient object. This replaces the previous approach that required `CreateHttpWebRequest()` followed by setting  `client.WebRequest.Method` explicit.\n\n* **LanguageUtils.IgnoreErrors()**   \nHelper functions that allows you to execute a block of code explicitly with a wrapped around try/catch block. Two version - one that returns true or false, one that allows the operation to return a result.\n\n* **ImageUtils.AdjustAspectRatio**   \nImage helper routine that crops an image according to a new aspect ratio from the center outward. Useful for creating uniform images in uploaded files for previews. Also optionally resizes the adjusted image to a fixed width or height.\n\n### 3.0.10\n*January 9th, 2018*\n\n* **FileUtils.AddTrailingSlash()**  \nAdds a trailing OS specific slash to the end of a path if there isn't one.\n\n* **FileUtils.ExpandPathEnvironmentVariables()**  \nMethod that checks for %envVar% embedded in the path and tries to evaluate the value from environment vars.\n\n* **Fix: FileUtils.GetRelativePath()**   \nFix Uri usage with local file paths.\n\n### 3.0.4\n*November 12th, 2017*\n\n* **DataUtils.GetSqlProviderFactory()**  \nAdd helper function to allow retrieving a SQL Provider factory without having to take a dependency on the assembly that contains the provider. This is provided for .NET Standard 2.0 which doesn't have `DbProviderFactories.GetFactory()` support that provides this functionality.\n\n* **FileUtils.NormalizePath() and NormalizeDirectory()**  \nAdded function to normalize a path for the given platform it runs on - forward backward slashes. Mainly useful for legacy code that explicitly formatted paths to Windows formatting. NormalizeDirectory ensures a trailing path slash on a path.\n\n* **FileUtils.GetCompactPath()**  \nAdded to return a filename that is trimmed in the middle with elipsis for long file names. You specify a max string length the and the method truncates accordingly.\n\n* **FileUtils.SafeFileName() Updates**  \n`SafeFileName()` now has options for the replacement character for invalid characters replaced as well (blank by default) as well as for spaces (which by default are not stripped).\n\n\n### 3.0.1\n*August 5th, 2017*\n\n* **Support for .NET Core 2.0**  \nVersion 3.0 adds support for .NET Core 2.0. Most features of the toolkit have been carried forward, but some features like configuration using standard .NET Configuration files is not available in .NET Core. There are a few other features that are not available.\n\n* **StringUtils.TokenizeString() and DetokenizeString()**  \nAdded a function that looks for a string pattern based on start and end characters, and replaces the text with numbered tokens. DetokenizeString() then can reinsert the tokens back into the string. Useful for parsing out parts of string for manipulation and then re-adding the values edited out.\n\n* **StringUtils.GetLines() optional maxLines Parameter**  \nAdded an optional parameter to `GetLines()` to allow specifying the number of lines returned. Internally still all strings are parsed first, but the result retrieves only the max number of lines.\n\n* **StringUtils.GenerateUniqueId() additional characters**\nYou can now add additional character to be included in the unique ID in addition to numbers and digits. This makes the string more resilient to avoid dupe values.\n\n* **Add support for HMAC hashing in Encryption.ComputeHash()**  \nHMAC provides a standardized way to introduces salted values into hashes that results in fixed length hashes are not vulnerable to length attacks. ComputeHash now exposes HMAC versions of the standard hash algorithms.\n\n* **Add Encryption.EncryptBytes() and Encryption.DecryptBytes()**  \nAdded additional overloads that allow passing byte buffer for the encryption key to make it easier to work with OS data API.\n\n* **Add SecureString Overloads to Encrypt/Decrypt Functions**   \nThe various implementations of Encrypt/DecryptString/Bytes now work with SecureString values for the encryption key to minimize holding unencrypted keys in memory as string for all but the immediate encrypt/decrypt operations.\n\n* **DataUtils.DataTableToObjectList<T>**   \nYou can now convert a data table to a strongly typed list with this new function.\n\n* **XmlUtils.GetXmlEnum()/GetXmlBool() and XmlUtils.GetAttributeXmlBool()**   \nAdded additional conversion methods to the XML helpers to facilitate retrieving values from XML documents more easily.\n\n* **LinqUtils.FlattenTree**   \nFlattens a tree type list into a flat enumarable by letting your provide the property for the children to flatten. `var topics = topicTree.FlattenTree(t=> t.Topics);`.\n\n* **FileUtils.CopyDirectory()**  \nCopies an entire directory tree to another directory. If the target exists files and folders are merged.\n\n* **Fix: HMAC Processing in Encryption.ComputeHash()**  \nHMAC hash computation was broken as salt was added to the data rather than just passed to the hash generator. Fixed.\n\n> ### Breaking Changes for 3.0\n> ##### .NET Core 2.0 Version\n> * ConfigurationFileProvider for Configuration is not supported\n> * We recommend you switch to JsonConfiguration\n> * SqlDataAccess ctor requires that you pass in a **full** connection string or SqlProvider Factory. \n> * SqlDataAccess connection string names and provider names are no longer supported. (this may get fixed as additional .NET Core APIs are made available by Microsoft to support providers)\n> * Encrypt.Encrypt()/Decrypt() use 24 bit keys where the old version for full framework uses 16 bit keys  \n> The result is that encrypted values can't be duplicated currently with .NET Core implementation. This may get fixed in later updates of .NET Core as 16 bit TripleDES keys are possibly reintroduced.\n> * Encryption DataProtection API methods don't work with .NET 2.0 since those are based on Windows APIs\n\n\n### 2.70\n*December 15th, 2016*\n\n* **Fix binary encoding for extended characters in Encryption class**  \nBinary encoding now uses UTF encoding to encrypt/decrypt strings in order to support extended characters.\n\n* **Encryption adds support for returning binary string data as BinHex**  \nYou can now return binary values in BinHex format in addition to the default base64 encoded string values.\n\n* **FileUtils.GetPhsysicalPath()**  \nThis function returns a given pathname with the proper casing for the file that exists on disk. If the file doesn't exist it\n\n* **Fix Encoding in HttpUtils**  \nFix encoding bug that didn't properly manage UTF-8 encoding in uploaded JSON content."
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n===========\n\nCopyright (c) 2012-2024 West Wind Technologies\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Readme.md",
    "content": "# Westwind.Utilities \n\n![](Westwind.Utilities/icon.png)\n\n\n| Library                 | Versions and Stats                                                                                                                                                                                      |\n|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| Westwind.Utilities      | <a href=\"https://www.nuget.org/packages/Westwind.Utilities/\">![](https://img.shields.io/nuget/v/Westwind.Utilities.svg)</a> ![](https://img.shields.io/nuget/dt/Westwind.Utilities.svg)                 |\n| Westwind.Utilities.Data | <a href=\"https://www.nuget.org/packages/Westwind.Utilities.Data/\">![](https://img.shields.io/nuget/v/Westwind.Utilities.Data.svg)</a>  ![](https://img.shields.io/nuget/dt/Westwind.Utilities.Data.svg) |\n| Westwind.Utilities.Windows | <a href=\"https://www.nuget.org/packages/Westwind.Utilities.Windows/\">![](https://img.shields.io/nuget/v/Westwind.Utilities.Windows.svg)</a>  ![](https://img.shields.io/nuget/dt/Westwind.Utilities.Windows.svg) |\n| Documentation           | <a href=\"https://docs.west-wind.com/westwind.utilities/\">![](https://img.shields.io/badge/documentation-blue.svg)</a>                                                                                   |\n\n### A general purpose utility and helper library for .NET development\n\n\nSupports the following Targets:\n\n* .NET 9.0,8.0\n* .NET Standard 2.0\n* .NET 4.62+\n\n### Installation\nYou can install the package [from NuGet](http://nuget.org/packages/Westwind.Utilities/) using the Visual Studio Package Manager or NuGet UI:\n\n```\nPM> install-package westwind.utilities\n```\n\nAs of version 4.0 the `Data` related features are located in a separate NuGet Package:\n\n```\nPM> install-package westwind.utilities.data\n```\n> #### Upgrading from v3 and using Data Access Features?\n> If you're switching from v3 or earlier and are using the Data Access features of the library, this is a breaking change that requires you to add the new `westwind.utilities.data` package.\n\n### What is it?\nEvery .NET application requires small, common and often repeated tasks. This library is a collection of those things that I commonly need on a regular basis and have compiled over the years.\n\n* [Get it on NuGet](https://nuget.org/packages/Westwind.Utilities/)\n* [Usage Documentation](https://docs.west-wind.com/westwind.utilities/_5am0u0jou.htm)\n* [Class Documentation](https://docs.west-wind.com/westwind.utilities/_5am0u09dd.htm)\n\nIt includes tools for:\n\n* [**Application Configuration**](https://docs.west-wind.com/westwind.utilities?page=_2le027umn.htm)  \nclass to create code-first strongly typed configuration classes for your applications\n\n* [**Lightweight ADO.NET Data Access Layer**](https://docs.west-wind.com/westwind.utilities?=page=_3ou0v2jum.htm)  \nideal for components or apps that need data access but don't need the bulk of Entity Framework or similar ORM.  \n<small>*requires adding the `westwind.utilities.data` package.*</small>\n\n\n* **General Purpose Utility Classes**:\n\t* [StringUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20StringUtils)\n    * [HtmlUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20HtmlUtils)\n\t* [ReflectionUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20ReflectionUtils)\n\t* [SerializationUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20SerializationUtils)\n\t* [DataUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20DataUtils)\t\n\t* [FileUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20FileUtils)\n    * [TimeUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20TimeUtils)\t\n    * [XmlUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20TimeUtils)\t    \n    * [StringSerializer](https://docs.west-wind.com/westwind.utilities?topic=Class%20StringSerializer)\n    * [Expando](https://docs.west-wind.com/westwind.utilities?topic=Class%20Expando)\n\t* [PropertyBag](https://docs.west-wind.com/westwind.utilities?topic=Class%20PropertyBag)\n    * [Scheduler](https://docs.west-wind.com/westwind.utilities?topic=Class%20Scheduler) (for background processing) \n    * [Encryption](https://docs.west-wind.com/westwind.utilities?topic=Class%20Encryption)\n    * [HttpClient](https://docs.west-wind.com/westwind.utilities?topic=Class%20HttpClient) (HttpWebRequest wrapper)\n    * [HttpUtils](https://docs.west-wind.com/westwind.utilities?topic=Class%20HttpUtils) (Simple REST client helpers)\n    * [SmptClientNative](https://docs.west-wind.com/westwind.utilities?topic=Class%20SmtpClientNative) (SmtpClient Wrapper)\n    \n    * [DelegateFactory](https://docs.west-wind.com/westwind.utilities?topic=Class%20DelegateFactory)\n\nand much, much more.\n\nIt's worthwhile to browse through the source code or the documentation\nto find out the myriad of useful functionality that is available, all\nin a small single assembly.\n\nThis assembly is the base for most other West Wind libraries.\n\n## License\nThis library is published under **MIT license** terms.\n\n**Copyright &copy; 2012-2023 Rick Strahl, West Wind Technologies**\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "Westwind.Utilities/.gitignore",
    "content": "###############\n#    folder   #\n###############\n/**/DROP/\n/**/TEMP/\n/**/packages/\n/**/bin/\n/**/obj/\n_site\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/AppConfiguration.cd",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ClassDiagram MajorVersion=\"1\" MinorVersion=\"1\">\n  <Comment CommentText=\"&lt;-- User implemented class&#xD;&#xA;\">\n    <Position X=\"2.865\" Y=\"3.531\" Height=\"0.255\" Width=\"1.7\" />\n  </Comment>\n  <Class Name=\"Westwind.Utilities.Configuration.AppConfiguration\">\n    <Position X=\"0.5\" Y=\"0.5\" Width=\"2.25\" />\n    <TypeIdentifier>\n      <HashCode>AQAAAAAAAAAEAQAAAAAAAAAgAAAAAAAAAAAAAAEAAAA=</HashCode>\n      <FileName>Configuration\\AppConfiguration.cs</FileName>\n    </TypeIdentifier>\n    <ShowAsAssociation>\n      <Field Name=\"Provider\" />\n    </ShowAsAssociation>\n  </Class>\n  <Class Name=\"Westwind.Utilities.Configuration.ConfigurationProviderBase&lt;TAppConfiguration&gt;\">\n    <Position X=\"6.75\" Y=\"0.5\" Width=\"3.25\" />\n    <TypeIdentifier>\n      <HashCode>AQBBAIAAAYAQAQAAAAAAAAAgAAAAAAAAAAAAIQEAAAE=</HashCode>\n      <FileName>Configuration\\ConfigurationProviderBase.cs</FileName>\n    </TypeIdentifier>\n    <Lollipop Position=\"0.2\" />\n  </Class>\n  <Class Name=\"Westwind.Utilities.Configuration.XmlFileConfigurationProvider&lt;TAppConfiguration&gt;\">\n    <Position X=\"9\" Y=\"5.25\" Width=\"3.5\" />\n    <TypeIdentifier>\n      <HashCode>QAAAAAAAgAAAAAAAAAAAAAAgAAASAAAAAAAAAAEAAAA=</HashCode>\n      <FileName>Configuration\\XmlFileConfigurationProvider.cs</FileName>\n    </TypeIdentifier>\n  </Class>\n  <Class Name=\"Westwind.Utilities.Configuration.ConfigurationFileConfigurationProvider&lt;TAppConfiguration&gt;\">\n    <Position X=\"0.5\" Y=\"5.25\" Width=\"3.75\" />\n    <TypeIdentifier>\n      <HashCode>AAABAQEIQAAACAAAAAAAgAAoAAAAAQAAAAAAIAEAAAA=</HashCode>\n      <FileName>Configuration\\ConfigurationFileConfigurationProvider.cs</FileName>\n    </TypeIdentifier>\n  </Class>\n  <Class Name=\"Westwind.Utilities.Configuration.SqlServerConfigurationProvider&lt;TAppConfiguration&gt;\">\n    <Position X=\"4.75\" Y=\"5.25\" Width=\"3.75\" />\n    <TypeIdentifier>\n      <HashCode>CAAAAAACAAAAAAQAAAAAgAAgAAAgAAAAACAAAQEQAAA=</HashCode>\n      <FileName>Configuration\\SqlServerConfigurationProvider.cs</FileName>\n    </TypeIdentifier>\n  </Class>\n  <Class Name=\"Westwind.Utilities.Configuration.MyAppConfiguration\">\n    <Position X=\"0.5\" Y=\"2.75\" Width=\"2.25\" />\n    <TypeIdentifier>\n      <HashCode>AgAAAAAAAAAAASAAAAAAEAAAAAAAAAAAAAAAAAAAAAA=</HashCode>\n      <FileName>Configuration\\AppConfiguration.cs</FileName>\n    </TypeIdentifier>\n  </Class>\n  <Interface Name=\"Westwind.Utilities.Configuration.IConfigurationProvider\">\n    <Position X=\"3.75\" Y=\"0.5\" Width=\"2.25\" />\n    <TypeIdentifier>\n      <HashCode>AQAAAIAAAYAAAQAAAAAAAAAgAAAAAAAAAAAAAQEAAAA=</HashCode>\n      <FileName>Configuration\\IConfigurationProvider.cs</FileName>\n    </TypeIdentifier>\n  </Interface>\n  <Font Name=\"Microsoft Sans Serif\" Size=\"8.25\" />\n</ClassDiagram>"
  },
  {
    "path": "Westwind.Utilities/Configuration/AppConfiguration.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          (c) West Wind Technologies, 2009-2024\n *          http://www.west-wind.com/ \n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Xml.Serialization;\nusing System.Diagnostics;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// This class provides a base class for code-first, strongly typed configuration \n    /// settings in .NET. It supports storing of configuration data in\n    /// .NET .config files, plain XML files, strings and SQL Server databases and\n    /// custom providers.\n    /// \n    /// Using this class is easy: Create a subclass of AppConfiguration and \n    /// then simply add properties to the class. Then instantiate the class, \n    /// call Initialize(), then simply access the class \n    /// properties to read configuration values.\n    /// \n    /// The default implementation uses standard .NET configuration files and a \n    /// custom section within that file to hold configuration values. Other \n    /// providers can be used to store data to different stores and you can create \n    /// your own custom providers to store configuration data in yet other stores.\n    /// <seealso>Managing Configuration Settings with AppConfiguration</seealso>\n    /// </summary>\n    public abstract class AppConfiguration\n    {\n        /// <summary>\n        /// An instance of a IConfigurationProvider that\n        /// needs to be passed in via constructor or set\n        /// explicitly to read and write from the configuration\n        /// store.\n        /// </summary>\n        [XmlIgnore]\n        [NonSerialized]\n        public IConfigurationProvider Provider = null;\n\n\n        /// <summary>\n        /// Contains an error message if a method returns false or the object fails to \n        /// load the configuration data.\n        /// <seealso>Class AppConfiguration</seealso>\n        /// </summary>\n        [XmlIgnore]\n        [NonSerialized]\n        public string ErrorMessage = string.Empty;\n\n        /// <summary>\n        /// Internal flag that checks to see if Initialize was called\n        /// if not - automatically calls it without parameters\n        /// </summary>\n        protected bool InitializeCalled = false;\n\n        /// <summary>\n        /// Default constructor of this class SHOULD ALWAYS be implemented in\n        /// every subclass to allow serialization instantiation and setting\n        /// of initial property values.\n        /// </summary>                 \n        public AppConfiguration()\n        { }\n\n        /// <summary>\n        /// This method initializes the configuration object with a provider\n        /// and performs an initial read from the config store.    \n        /// </summary>\n        /// <param name=\"provider\">\n        /// Optional - preconfigured ConfigurationProvider instance.\n        /// If not passed ConfigurationFileConfigurationProvider is used.\n        /// </param>\n        /// <param name=\"sectionName\">\n        /// Optional - sub-section name used for config files. Not used by all config stores\n        /// </param>\n        /// <param name=\"configData\">\n        /// Optional - additional config data to pass to OnInitialize if implemented\n        /// </param>\n        public void Initialize(IConfigurationProvider provider = null, \n                               string sectionName = null, \n                               object configData = null)\n        {\n            provider = provider ?? Provider;\n\n            // Initialization occurs only once\n            if (InitializeCalled)\n               return;\n            InitializeCalled = true;  \n            \n            if (string.IsNullOrEmpty(sectionName))\n                sectionName = GetType().Name;\n\n            OnInitialize(provider,sectionName,configData);\n        }\n\n        /// <summary>\n        /// Override this method to handle custom initialization tasks.\n        /// \n        /// This method should: create a provider and call it's Read()\n        /// method to populate the current instance of the configuration\n        /// object.\n        /// \n        /// If all you need is to create a default provider configuration\n        /// use the OnCreateDefaultProvider() method to override instead.\n        /// Use this method if you need to perform custom actions beyond\n        /// provider instantiation.\n        /// </summary>\n        /// <param name=\"provider\">Provider value - can be null in which case ConfigurationFileProvider is used</param>\n        /// <param name=\"sectionName\">Sub Section name - can be null. Classname is used if null. Can be \"appSettings\" </param>\n        /// <param name=\"configData\">\n        /// Any additional configuration data that can be used to\n        /// configure the provider.\n        /// </param>\n        protected virtual void OnInitialize(IConfigurationProvider provider, \n                                           string sectionName,\n                                           object configData)\n        {\n            if (provider == null)\n                provider = OnCreateDefaultProvider(sectionName, configData);\n\n            Provider = provider;\n            if (!Provider.Read(this) && !string.IsNullOrWhiteSpace(Provider.ErrorMessage))\n                Trace.WriteLine(Resources.ConfigurationInitializationError + \": \" + Provider.ErrorMessage);\n        }\n\n        /// <summary>\n        /// Override this method to use a specialized configuration provider for your config class\n        /// when no explicit provider is passed to the Initialize() method.\n        /// </summary>\n        /// <param name=\"sectionName\">Optional section name that was passed to Initialize()</param>\n        /// <param name=\"configData\">Optional config data that was passed to Initialize()</param>\n        /// <returns>Instance of configuration provider</returns>\n        protected virtual IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n\t\t\t// dynamically construct the generic provider type\n#if NETFRAMEWORK\n\t\t\tvar providerType = typeof(ConfigurationFileConfigurationProvider<>);\n#else\n\t\t\tvar providerType = typeof(JsonFileConfigurationProvider<>);\n#endif\n\t\t\tvar type = GetType();\n            Type typeProvider = providerType.MakeGenericType(type);\n\n            var provider = Activator.CreateInstance(typeProvider) as IConfigurationProvider;\n\n            // if no section name is passed it goes into standard appSettings\n            if (!string.IsNullOrEmpty(sectionName))\n                provider.ConfigurationSection = sectionName;\n\n            return provider;\n        }\n\n        /// <summary>\n        /// Writes the current configuration information data to the\n        /// provider's configuration store.\n        /// </summary>\n        /// <returns></returns>\n        public virtual bool Write()\n        {\n            if (Provider == null)\n            {\n                Provider = OnCreateDefaultProvider(null,null);\n                InitializeCalled = true;\n            }\n\n            if (!Provider.Write(this))\n            {\n                ErrorMessage = Provider.ErrorMessage;\n                return false;\n            }\n            \n            return true;\n        }\n\n        /// <summary>\n        /// Writes the current configuration information to an\n        /// XML string. String is in .NET XML Serialization format.\n        /// </summary>\n        /// <returns></returns>\n        public virtual string WriteAsString()\n        {\n            Initialize();\n\n            string xml = string.Empty;\n            Provider.EncryptFields(this);\n\n            SerializationUtils.SerializeObject(this, out xml);\n\n            if (!string.IsNullOrEmpty(xml))\n                Provider.DecryptFields(this);\n\n            return xml;\n        }\n\n        /// <summary>\n        /// Reads the configuration information from the \n        /// provider's store and returns a new instance\n        /// of an configuration object.\n        /// </summary>\n        /// <typeparam name=\"T\">This configuration class type</typeparam>\n        /// <returns></returns>\n        public virtual T Read<T>()\n                where T : AppConfiguration, new()\n        {\n            Initialize();\n\n            var inst = Provider.Read<T>();\n            if (inst == null)\n            {\n                ErrorMessage = Provider.ErrorMessage;\n                return null;\n            }\n\n            return inst;\n        }\n\n        /// <summary>\n        /// Reads the configuration from the provider's store\n        /// into the current object instance.\n        /// </summary>\n        /// <returns></returns>\n        public virtual bool Read()\n        {\n            Initialize();\n\n            if (!Provider.Read(this))\n            {\n                ErrorMessage = Provider.ErrorMessage;\n                return false;\n            }\n            return true;\n        }\n\n        /// <summary>\n        /// Reads configuration data from a string and populates the current\n        /// instance with the values.\n        /// \n        /// Data should be serialized in XML Searlization format created\n        /// with <seealso cref=\"WriteAsString\" />\n        /// </summary>\n        /// <param name=\"xml\">Xml string in XML Serialization format</param>\n        /// <returns>true or false</returns>\n        public virtual bool Read(string xml)\n        {\n            Initialize();\n\n            var newInstance = SerializationUtils.DeSerializeObject(xml, GetType());\n\n            DataUtils.CopyObjectData(newInstance, this, \"Provider,Errormessage\");\n\n            if (newInstance != null)\n            {\n                Provider.DecryptFields(this);\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Reads configuration based on a provider configuration\n        /// and returns a new instance of the configuration object\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"provider\">A configured  <seealso cref=\"IConfigurationProvider\"/>\" provider</param>\n        /// <returns>instance of configuration or null on failure</returns>\n        public static T Read<T>(IConfigurationProvider provider)\n            where T : AppConfiguration, new()\n        {            \n            return provider.Read<T>() as T;            \n        }\n\n        /// <summary>\n        /// Creates a new instance of the config object and retrieves\n        /// configuration information from the provided string. String \n        /// should be in XML Serialization format or created by the \n        /// <seealso cref=\"WriteAsString\"/> method.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"xml\"></param>\n        /// <param name=\"provider\">Required if encryption decryption is desired</param>\n        /// <returns>config instance or null on failure</returns>\n        public static T Read<T>(string xml, IConfigurationProvider provider)\n            where T : AppConfiguration, new()\n        {                        \n            T result =  SerializationUtils.DeSerializeObject(xml, typeof(T)) as T;            \n\n            if (result != null && provider != null)\n                provider.DecryptFields(result);\n\n            return result;\n        }\n\n        /// <summary>\n        /// Creates a new instance of the config object and retrieves\n        /// configuration information from the provided string. String \n        /// should be in XML Serialization format or created by the \n        /// <seealso cref=\"WriteAsString\"/> method.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"xml\"></param>\n        /// <returns>config instance or null on failure</returns>\n        public static T Read<T>(string xml)\n            where T : AppConfiguration, new()\n        {\n            return Read<T>(xml, null);\n        }\n\n    }\n\n    /// <summary>\n    /// Sample class for diagram display\n    /// </summary>\n    class MyAppConfiguration : AppConfiguration\n    {\n        public MyAppConfiguration()\n        {\n            MyProperty = \"My default property value\";\n            MaxPageListItems = 15;\n            ApplicationTitle = \"My great application!\";\n        }\n\n        public string MyProperty { get; set; }\n        public int MaxPageListItems { get; set; }\n        public string ApplicationTitle { get; set; }\n\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            return base.OnCreateDefaultProvider(sectionName, configData);\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/ConfigurationFileConfigurationProvider.cs",
    "content": "#if NETFULL\n\n#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.ComponentModel;\nusing System.Configuration;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reflection;\nusing System.Xml;\nusing System.Globalization;\nusing System.Xml.Serialization;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// Reads and Writes configuration settings in .NET config files and \n    /// sections. Allows reading and writing to default or external files \n    /// and specification of the configuration section that settings are\n    /// applied to.\n    /// </summary>\n    public class ConfigurationFileConfigurationProvider<TAppConfiguration> :\n        ConfigurationProviderBase<TAppConfiguration>\n        where TAppConfiguration : AppConfiguration, new()\n    {\n\n        /// <summary>\n        /// Optional - the Configuration file where configuration settings are\n        /// stored in. If not specified uses the default Configuration Manager\n        /// and its default store.\n        /// </summary>\n        public string ConfigurationFile { get; set; }\n\n        /// <summary>\n        /// internal property used to ensure there are no multiple write\n        /// operations at the same time\n        /// </summary>\n        private object syncWriteLock = new object();\n\n        /// <summary>\n        /// Internally used reference to the Namespace Manager object\n        /// used to make sure we're searching the proper Namespace\n        /// for the appSettings section when reading and writing manually\n        /// </summary>\n        private XmlNamespaceManager XmlNamespaces = null;\n\n        //Internally used namespace prefix for the default namespace\n        private string XmlNamespacePrefix = \"ww:\";\n\n\n        /// <summary>\n        /// Reads configuration settings into a new instance of the configuration object.\n        /// </summary>\n        /// <typeparam name=\"TAppConfig\"></typeparam>\n        /// <returns></returns>\n        public override TAppConfig Read<TAppConfig>()\n        {\n            TAppConfig config = Activator.CreateInstance(typeof (TAppConfig), true) as TAppConfig;\n            if (!Read(config))\n                return null;\n\n            return config;\n        }\n\n        /// <summary>\n        /// Reads configuration settings from the current configuration manager. \n        /// Uses the internal APIs to write these values.\n        /// </summary>        \n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Read(AppConfiguration config)\n        {\n            // Config reading from external files works a bit differently \n            // so use a separate method to handle it\n            if (!string.IsNullOrEmpty(ConfigurationFile))\n                return Read(config, ConfigurationFile);\n\n            Type typeWebConfig = config.GetType();\n            PropertyInfo[] properties =\n                typeWebConfig.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty);\n\n            // Set a flag for missing fields\n            // If we have any we'll need to write them out into .config\n            bool missingFields = false;\n\n            // Refresh the sections - req'd after write operations\n            // sometimes sections don't want to re-read            \n            if (string.IsNullOrEmpty(ConfigurationSection))\n                ConfigurationManager.RefreshSection(\"appSettings\");\n            else\n                ConfigurationManager.RefreshSection(ConfigurationSection);\n\n            NameValueCollection configManager;\n\n            configManager = string.IsNullOrEmpty(ConfigurationSection)\n                ? ConfigurationManager.AppSettings as NameValueCollection\n                : ConfigurationManager.GetSection(ConfigurationSection) as NameValueCollection;            \n\n            if (configManager == null)\n            {\n                Write(config);\n                return true;\n            }\n\n\n            // Loop through all fields and properties                 \n            foreach (PropertyInfo property in properties)\n            {\n                if (!property.CanWrite || Attribute.IsDefined(property, typeof(XmlIgnoreAttribute)))\n                    continue;\n                \n                Type fieldType = property.PropertyType;                \n                string fieldName = property.Name.ToLowerInvariant();\n                \n                // Error Message is an internal public property\n                if (fieldName == \"errormessage\" || fieldName == \"provider\")\n                    continue;\n\n                if (!IsIList(fieldType))\n                {\n                    // Single value\n                    string value = configManager[fieldName];\n\n                    if (value == null)\n                    {\n                        missingFields = true;\n                        continue;\n                    }\n\n                    try\n                    {\n                        if (property.CanWrite)\n                        {\n                            // Assign the value to the property\n                            ReflectionUtils.SetPropertyEx(config, property.Name,\n                                StringToTypedValue(value, fieldType, CultureInfo.InvariantCulture));\n                        }\n                    }\n                    catch\n                    {\n                    }\n                }\n                else\n                {\n                    // List Value\n                    var list = Activator.CreateInstance(fieldType) as IList;\n                    var elType = fieldType.GetElementType();\n                    if (elType == null)\n                    {\n                        var generic = fieldType.GetGenericArguments();\n                        if (generic != null && generic.Length > 0)\n                            elType = generic[0];\n                    }\n\n                    int count = 1;\n                    string value = string.Empty;\n\n                    while (value != null)\n                    {\n                        value = configManager[fieldName + count];                        \n                        if (value == null)\n                            break;\n                        list.Add(StringToTypedValue(value, elType, CultureInfo.InvariantCulture));\n                        count++;\n                    }\n\n                    try\n                    {\n                        if(property.CanWrite)\n                            ReflectionUtils.SetPropertyEx(config, property.Name, list);\n                    }\n                    catch { }\n                }\n            }\n\n            DecryptFields(config);\n\n            // We have to write any missing keys\n            if (missingFields)\n                Write(config);\n\n            return true;\n        }\n\n\n\n\n        bool IsIList(Type type)\n        {            \n            // Enumerable types explicitly supported as 'simple values'\n            if (type == typeof(string) || type == typeof( byte[]) )\n                return false;\n\n            if (type.GetInterface(\"IList\") != null)\n                return true;\n\n            return false;\n        }\n\n\n\n\n        /// <summary>\n        /// Reads Configuration settings from an external file or explicitly from a file.\n        /// Uses XML DOM to read values instead of using the native APIs.\n        /// </summary>        \n        /// <param name=\"config\">Configuration instance</param>\n        /// <param name=\"filename\">Filename to read from</param>\n        /// <returns></returns>\n        public override bool Read(AppConfiguration config, string filename)\n        {\n            Type typeWebConfig = config.GetType();\n            PropertyInfo[] properties = typeWebConfig.GetProperties(BindingFlags.Public |\n                                                           BindingFlags.Instance);\n\n            // Set a flag for missing fields\n            // If we have any we'll need to write them out \n            bool missingFields = false;\n\n            XmlDocument Dom = new XmlDocument();\n\n            try\n            {\n                Dom.Load(filename);\n            }\n            catch\n            {\n                // Can't open or doesn't exist - so try to create it\n                if (!Write(config))\n                    return false;\n\n                // Now load again\n                Dom.Load(filename);\n            }\n\n            // Retrieve XML Namespace information to assign default \n            // Namespace explicitly.\n            GetXmlNamespaceInfo(Dom);\n\n\n            string ConfigSection = ConfigurationSection;\n            if (ConfigSection == string.Empty)\n                ConfigSection = \"appSettings\";\n            \n\n            foreach (var property in properties)\n            {                \n                if (!property.CanWrite || Attribute.IsDefined(property, typeof(XmlIgnoreAttribute)))\n                    continue;\n\n                Type fieldType = null;\n                string typeName = null;\n    \n                fieldType = property.PropertyType;\n                typeName = property.PropertyType.Name.ToLower();\n                \n                string propertyName = property.Name;\n                if (propertyName == \"Provider\" || propertyName == \"ErrorMessage\")\n                    continue;\n\n                XmlNode Section = Dom.DocumentElement.SelectSingleNode(XmlNamespacePrefix + ConfigSection, XmlNamespaces);\n                if (Section == null)\n                {\n                    Section = CreateConfigSection(Dom, ConfigurationSection);\n                    Dom.DocumentElement.AppendChild(Section);\n                }\n\n                string Value = GetNamedValueFromXml(Dom, propertyName, ConfigSection);\n                if (Value == null)\n                {\n                    missingFields = true;\n                    continue;\n                }\n\n                // Assign the Property\n                ReflectionUtils.SetPropertyEx(config, propertyName,\n                    StringToTypedValue(Value, fieldType, CultureInfo.InvariantCulture));\n            }\n\n            DecryptFields(config);\n\n            // We have to write any missing keys\n            if (missingFields)\n                Write(config);\n\n            return true;\n        }\n\n\n        public override bool Write(AppConfiguration config)\n        {\n            EncryptFields(config);\n\n            lock (syncWriteLock)\n            {\n                // Load the config file into DOM parser\n                XmlDocument Dom = new XmlDocument();\n\n                string configFile = ConfigurationFile;\n                if (string.IsNullOrEmpty(configFile))\n                    configFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;\n\n                try\n                {\n                    Dom.Load(configFile);\n                }\n                catch\n                {\n                    // Can't load the file - create an empty document\n                    string Xml =\n                   @\"<?xml version='1.0'?>\n\t\t<configuration>\n\t\t</configuration>\";\n\n                    Dom.LoadXml(Xml);\n                }\n\n                // Load up the Namespaces object so we can \n                // reference the appropriate default namespace\n                GetXmlNamespaceInfo(Dom);\n\n                // Parse through each of hte properties of the properties\n                Type typeWebConfig = config.GetType();\n                PropertyInfo[] properties = typeWebConfig.GetProperties(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.Public);\n                \n                string ConfigSection = \"appSettings\";\n                if (!string.IsNullOrEmpty(ConfigurationSection))\n                    ConfigSection = ConfigurationSection;\n\n                // make sure we're getting the latest values before we write\n                ConfigurationManager.RefreshSection(ConfigSection);\n\n                foreach (var property in properties)\n                {\n                    if (!property.CanRead || Attribute.IsDefined(property, typeof(XmlIgnoreAttribute)))\n                        continue;\n\n                    // Don't persist ErrorMessage property\n                    if (property.Name == \"ErrorMessage\" || property.Name == \"Provider\")\n                        continue;\n\n                    object rawValue = null;\n                    rawValue = property.GetValue(config, null);\n                    \n                    string value = TypedValueToString(rawValue, CultureInfo.InvariantCulture);\n\n                    if (value == \"ILIST_TYPE\")\n                    {\n                        var count = 0;\n                        foreach (var item in rawValue as IList)\n                        {\n                            value = TypedValueToString(item, CultureInfo.InvariantCulture);\n                            WriteConfigurationValue(property.Name + ++count, value, property, Dom, ConfigSection);\n                        }\n                    }\n                    else\n                    {\n                            WriteConfigurationValue(property.Name, value, property, Dom, ConfigSection);\n                    }\n                } // for each\n\n                try\n                {\n                    // this will fail if permissions are not there\n                    Dom.Save(configFile);\n\n                    ConfigurationManager.RefreshSection(ConfigSection);\n                }\n                catch(Exception ex)\n                {\n\t\t\t\t\tErrorMessage = \"Unable to save configuration file: \" + ex.Message;\n                    return false;\n                }\n                finally\n                {\n                    DecryptFields(config);\n                }\n            }\n            return true;\n        }\n\n        private void WriteConfigurationValue(string keyName, string Value, MemberInfo Field, XmlDocument Dom, string ConfigSection)\n        {\n            XmlNode Node = Dom.DocumentElement.SelectSingleNode(\n                XmlNamespacePrefix + ConfigSection + \"/\" +\n                XmlNamespacePrefix + \"add[@key='\" + keyName + \"']\", XmlNamespaces);\n\n            if (Node == null)\n            {\n                // Create the node and attributes and write it\n                Node = Dom.CreateNode(XmlNodeType.Element, \"add\", Dom.DocumentElement.NamespaceURI);\n\n                XmlAttribute Attr2 = Dom.CreateAttribute(\"key\");\n                Attr2.Value = keyName;\n                XmlAttribute Attr = Dom.CreateAttribute(\"value\");\n                Attr.Value = Value;\n\n                Node.Attributes.Append(Attr2);\n                Node.Attributes.Append(Attr);\n\n                XmlNode Parent = Dom.DocumentElement.SelectSingleNode(\n                    XmlNamespacePrefix + ConfigSection, XmlNamespaces);\n\n                if (Parent == null)\n                    Parent = CreateConfigSection(Dom, ConfigSection);\n\n                Parent.AppendChild(Node);\n            }\n            else\n            {\n                // just write the value into the attribute\n                Node.Attributes.GetNamedItem(\"value\").Value = Value;\n            }\n        }\n\n        /// <summary>\n        /// Returns a single value from the XML in a configuration file.\n        /// </summary>\n        /// <param name=\"Dom\"></param>\n        /// <param name=\"Key\"></param>\n        /// <param name=\"ConfigSection\"></param>\n        /// <returns></returns>\n        protected string GetNamedValueFromXml(XmlDocument Dom, string Key, string ConfigSection)\n        {\n            XmlNode Node = Dom.DocumentElement.SelectSingleNode(\n                   XmlNamespacePrefix + ConfigSection + @\"/\" +\n                   XmlNamespacePrefix + \"add[@key='\" + Key + \"']\", XmlNamespaces);\n\n            if (Node == null)\n                return null;\n\n            return Node.Attributes[\"value\"].Value;\n        }\n\n        /// <summary>\n        /// Used to load up the default namespace reference and prefix\n        /// information. This is required so that SelectSingleNode can\n        /// find info in 2.0 or later config files that include a namespace\n        /// on the root element definition.\n        /// </summary>\n        /// <param name=\"Dom\"></param>\n        protected void GetXmlNamespaceInfo(XmlDocument Dom)\n        {\n            // Load up the Namespaces object so we can \n            // reference the appropriate default namespace\n            if (Dom.DocumentElement.NamespaceURI == null || Dom.DocumentElement.NamespaceURI == string.Empty)\n            {\n                XmlNamespaces = null;\n                XmlNamespacePrefix = string.Empty;\n            }\n            else\n            {\n                if (Dom.DocumentElement.Prefix == null || Dom.DocumentElement.Prefix == string.Empty)\n                    XmlNamespacePrefix = \"ww\";\n                else\n                    XmlNamespacePrefix = Dom.DocumentElement.Prefix;\n\n                XmlNamespaces = new XmlNamespaceManager(Dom.NameTable);\n                XmlNamespaces.AddNamespace(XmlNamespacePrefix, Dom.DocumentElement.NamespaceURI);\n\n                XmlNamespacePrefix += \":\";\n            }\n        }\n\n        /// <summary>\n        /// Creates a Configuration section and also creates a ConfigSections section for new \n        /// non appSettings sections.\n        /// </summary>\n        /// <param name=\"dom\"></param>\n        /// <param name=\"configSection\"></param>\n        /// <returns></returns>\n        private XmlNode CreateConfigSection(XmlDocument dom, string configSection)\n        {\n\n            // Create the actual section first and attach to document\n            XmlNode AppSettingsNode = dom.CreateNode(XmlNodeType.Element,\n                configSection, dom.DocumentElement.NamespaceURI);\n\n            XmlNode Parent = dom.DocumentElement.AppendChild(AppSettingsNode);\n\n            // Now check and make sure that the section header exists\n            if (configSection != \"appSettings\")\n            {\n                XmlNode ConfigSectionHeader = dom.DocumentElement.SelectSingleNode(XmlNamespacePrefix + \"configSections\",\n                                XmlNamespaces);\n                if (ConfigSectionHeader == null)\n                {\n                    // Create the node and attributes and write it\n                    XmlNode ConfigSectionNode = dom.CreateNode(XmlNodeType.Element,\n                             \"configSections\", dom.DocumentElement.NamespaceURI);\n\n                    // Insert as first element in DOM\n                    ConfigSectionHeader = dom.DocumentElement.InsertBefore(ConfigSectionNode,\n                             dom.DocumentElement.ChildNodes[0]);\n                }\n\n                // Check for the Section\n                XmlNode Section = ConfigSectionHeader.SelectSingleNode(XmlNamespacePrefix + \"section[@name='\" + configSection + \"']\",\n                        XmlNamespaces);\n\n                if (Section == null)\n                {\n                    Section = dom.CreateNode(XmlNodeType.Element, \"section\",\n                             null);\n\n                    XmlAttribute Attr = dom.CreateAttribute(\"name\");\n                    Attr.Value = configSection;\n                    XmlAttribute Attr2 = dom.CreateAttribute(\"type\");\n                    Attr2.Value = \"System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\";\n                    XmlAttribute Attr3 = dom.CreateAttribute(\"requirePermission\");\n                    Attr3.Value = \"false\";\n                    Section.Attributes.Append(Attr);\n                    Section.Attributes.Append(Attr3);\n                    Section.Attributes.Append(Attr2);\n                    ConfigSectionHeader.AppendChild(Section);\n                }\n            }\n\n            return Parent;\n        }\n\n\n        /// <summary>\n        /// Converts a type to string if possible. This method supports an optional culture generically on any value.\n        /// It calls the ToString() method on common types and uses a type converter on all other objects\n        /// if available\n        /// </summary>\n        /// <param name=\"rawValue\">The Value or Object to convert to a string</param>\n        /// <param name=\"culture\">Culture for numeric and DateTime values</param>\n        /// <param name=\"unsupportedReturn\">Return string for unsupported types</param>\n        /// <returns>string</returns>\n        static string TypedValueToString(object rawValue, CultureInfo culture = null, string unsupportedReturn = null)\n        {\n            if (rawValue == null)\n                return string.Empty;\n\n            if (culture == null)\n                culture = CultureInfo.CurrentCulture;\n\n            Type valueType = rawValue.GetType();\n            string returnValue = null;\n\n            if (valueType == typeof(string))\n                returnValue = rawValue as string;\n            else if (valueType == typeof(int) || valueType == typeof(decimal) ||\n                valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(Single))\n                returnValue = string.Format(culture.NumberFormat, \"{0}\", rawValue);\n            else if (valueType == typeof(DateTime))\n                returnValue = string.Format(culture.DateTimeFormat, \"{0}\", rawValue);\n            else if (valueType == typeof(bool) || valueType == typeof(Byte) || valueType.IsEnum)\n                returnValue = rawValue.ToString();\n            else if (valueType == typeof (byte[]))\n                returnValue = Convert.ToBase64String(rawValue as byte[]);\n            else if (valueType == typeof(Guid?))\n            {\n                if (rawValue == null)\n                    returnValue = string.Empty;\n                else\n                    return rawValue.ToString();\n            }\n            else if (rawValue is IList)\n                return \"ILIST_TYPE\";\n            else\n            {\n                // Any type that supports a type converter\n                TypeConverter converter = TypeDescriptor.GetConverter(valueType);\n                if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))\n                    returnValue = converter.ConvertToString(null, culture, rawValue);\n                else\n                {\n                    // Last resort - just call ToString() on unknown type\n                    if (!string.IsNullOrEmpty(unsupportedReturn))\n                        returnValue = unsupportedReturn;\n                    else\n                        returnValue = rawValue.ToString();\n                }\n            }\n\n            return returnValue;\n        }\n\n        /// <summary>\n        /// Turns a string into a typed value generically.\n        /// Explicitly assigns common types and falls back\n        /// on using type converters for unhandled types.         \n        /// \n        /// Common uses: \n        /// * UI -&gt; to data conversions\n        /// * Parsers\n        /// <seealso>Class ReflectionUtils</seealso>\n        /// </summary>\n        /// <param name=\"sourceString\">\n        /// The string to convert from\n        /// </param>\n        /// <param name=\"targetType\">\n        /// The type to convert to\n        /// </param>\n        /// <param name=\"culture\">\n        /// Culture used for numeric and datetime values.\n        /// </param>\n        /// <returns>object. Throws exception if it cannot be converted.</returns>\n        static object StringToTypedValue(string sourceString, Type targetType, CultureInfo culture = null)\n        {\n            object result = null;\n\n            bool isEmpty = string.IsNullOrEmpty(sourceString);\n\n            if (culture == null)\n                culture = CultureInfo.CurrentCulture;\n\n            if (targetType == typeof(string))\n                result = sourceString;\n            else if (targetType == typeof(Int32) || targetType == typeof(int))\n            {\n                if (isEmpty)\n                    result = 0;\n                else\n                    result = Int32.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Int64))\n            {\n                if (isEmpty)\n                    result = (Int64)0;\n                else\n                    result = Int64.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Int16))\n            {\n                if (isEmpty)\n                    result = (Int16)0;\n                else\n                    result = Int16.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(decimal))\n            {\n                if (isEmpty)\n                    result = 0M;\n                else\n                    result = decimal.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(DateTime))\n            {\n                if (isEmpty)\n                    result = DateTime.MinValue;\n                else\n                    result = Convert.ToDateTime(sourceString, culture.DateTimeFormat);\n            }\n            else if (targetType == typeof(byte))\n            {\n                if (isEmpty)\n                    result = 0;\n                else\n                    result = Convert.ToByte(sourceString);\n            }\n            else if (targetType == typeof(double))\n            {\n                if (isEmpty)\n                    result = 0F;\n                else\n                    result = Double.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Single))\n            {\n                if (isEmpty)\n                    result = 0F;\n                else\n                    result = Single.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(bool))\n            {\n                if (!isEmpty &&\n                    sourceString.ToLower() == \"true\" || sourceString.ToLower() == \"on\" || sourceString == \"1\")\n                    result = true;\n                else\n                    result = false;\n            }\n            else if (targetType == typeof(Guid))\n            {\n                if (isEmpty)\n                    result = Guid.Empty;\n                else\n                    result = new Guid(sourceString);\n            }\n            else if (targetType.IsEnum)\n                result = Enum.Parse(targetType, sourceString);\n            else if (targetType == typeof (byte[]))\n                result = Convert.FromBase64String(sourceString);\n            else if (targetType.Name.StartsWith(\"Nullable`\"))\n            {\n                if (sourceString.ToLower() == \"null\" || sourceString == string.Empty)\n                    result = null;\n                else\n                {\n                    targetType = Nullable.GetUnderlyingType(targetType);\n                    result = StringToTypedValue(sourceString, targetType);\n                }\n            }\n            else\n            {\n                // Check for TypeConverters or FromString static method\n                TypeConverter converter = TypeDescriptor.GetConverter(targetType);\n                if (converter != null && converter.CanConvertFrom(typeof (string)))\n                    result = converter.ConvertFromString(null, culture, sourceString);\n                else\n                {\n                    // Try to invoke a static FromString method if it exists\n                    try\n                    {\n                        var mi = targetType.GetMethod(\"FromString\");\n                        if (mi != null)\n                        {\n                            return mi.Invoke(null, new object[1] {sourceString});\n                        }\n                    }\n                    catch\n                    {\n                        // ignore error and assume not supported \n                    }\n\n                    Debug.Assert(false, string.Format(\"Type Conversion not handled in StringToTypedValue for {0} {1}\",\n                        targetType.Name, sourceString));\n                    throw (new InvalidCastException(Resources.StringToTypedValueValueTypeConversionFailed +\n                                                    targetType.Name));\n                }\n            }\n\n            return result;\n        }\n\n    }\n\n\n}\n\n#endif"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/ConfigurationProviderBase.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Reflection;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Configuration\n{\n    /// <summary>\n    /// Base Configuration Provider Implementation. This implementation provides\n    /// for the basic layout of a provider and fields that implement the\n    /// IConfigurationProvider interface.\n    /// \n    /// The Read and Write methods must be overridden - all other methods and \n    /// fields are optional\n    /// \n    /// </summary>\n    public abstract class ConfigurationProviderBase<TAppConfiguration> : IConfigurationProvider\n        where TAppConfiguration : AppConfiguration, new()\n    {\n\n        /// <summary>\n        /// Displays error information when results fail.\n        /// </summary>\n        public virtual string ErrorMessage\n        {\n            get { return _ErrorMessage; }\n            set { _ErrorMessage = value; }\n        }\n        private string _ErrorMessage = string.Empty;\n\n\n        /// <summary>\n        /// A comma delimiter list of property names that are \n        /// to be encrypted when persisted to the store\n        /// </summary>\n        public virtual string PropertiesToEncrypt\n        {\n            get { return _PropertiesToEncrypt; }\n            set { _PropertiesToEncrypt = value; }\n        }\n        private string _PropertiesToEncrypt = string.Empty;\n\n        /// <summary>\n        /// The encryption key to encrypt the fields \n        /// set with FieldsToEncrypt\n        /// </summary>\n        public virtual string EncryptionKey\n        {\n            get { return _EncryptionKey; }\n            set { _EncryptionKey = value; }\n        }\n        private string _EncryptionKey = \"x@3|zg?4%ui*\";\n\n        /// <summary>\n        /// Optional Section name that can differentiate groups of config\n        /// values in multi-section files like Config files.\n        /// </summary>\n        public string ConfigurationSection {get; set; }\n\n        \n        /// <summary>\n        /// Reads a configurations settings from the configuration store\n        /// into a new existing instance.\n        /// </summary>\n        /// <typeparam name=\"T\">Specific Config Settings Class</typeparam>\n        /// <returns></returns>\n        public abstract T Read<T>()\n                where T : AppConfiguration, new();\n\n\n        /// <summary>\n        /// Reads configuration settings from the store into a passed\n        /// instance of the configuration instance.\n        /// </summary>\n        /// <param name=\"config\">Specific config settings class instance</param>\n        /// <returns>true or false</returns>\n        public abstract bool Read(AppConfiguration config);\n\n        /// <summary>\n        /// Writes the configuration settings from a specific instance\n        /// into the configuration store.\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public abstract bool Write(AppConfiguration config);\n\n        /// <summary>\n        /// Creates a new instance of the application object and retrieves\n        /// configuration information from the provided string. String \n        /// should be in XML Serialization format or created by the WriteAsString \n        /// method.\n        /// </summary>\n        /// <typeparam name=\"T\">Type of the specific configuration class</typeparam>\n        /// <param name=\"xml\">An xml string that contains XML Serialized serialization data</param>\n        /// <returns>The deserialized instance or null on failure</returns>\n        public virtual T Read<T>(string xml)\n            where T : AppConfiguration, new()\n        {\n            if (string.IsNullOrEmpty((xml)))\n            {                \n                return null;\n            }\n\n            T result;\n            try\n            {\n                result = SerializationUtils.DeSerializeObject(xml, typeof(T)) as T;\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n            if (result != null)\n                DecryptFields(result);\n\n            return result;\n        }\n        /// <summary>\n        /// Reads data into configuration from an XML string into a passed \n        /// instance of the a configuration object.\n        /// </summary>\n        /// <param name=\"config\">An instance of a custom configuration object</param>\n        /// <param name=\"xml\">Xml of serialized configuration instance.</param>\n        /// <returns>true or false</returns>\n        public virtual bool Read(AppConfiguration config, string xml)\n        {\n            TAppConfiguration newConfig = null;\n\n            // if no data was passed leave the object\n            // in its initial state.\n            if (string.IsNullOrEmpty(xml))\n                return true;\n\n            try\n            {\n                newConfig = SerializationUtils.DeSerializeObject(xml, config.GetType()) as TAppConfiguration;\n                if (newConfig == null)\n                {\n                    SetError(Resources.ObjectCouldNotBeDeserializedFromXml);\n                    return false;\n                }\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return false;\n            }\n            if (newConfig != null)\n            {\n                DecryptFields(newConfig);\n                DataUtils.CopyObjectData(newConfig, config, \"Provider,ErrorMessage\");\n                return true;\n            }\n            return false;\n        }\n\n\n        /// <summary>\n        /// Writes the current configuration information to an\n        /// XML string. String is XML Serialization format.\n        /// </summary>\n        /// <returns>xml string of serialized config object</returns>\n        public virtual string WriteAsString(AppConfiguration config)\n        {\n            string xml = string.Empty;\n            EncryptFields(config);\n\n            try\n            {\n                SerializationUtils.SerializeObject(config, out xml, true);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return string.Empty;\n            }\n            finally\n            {\n                DecryptFields(config);\n            }\n\n            return xml;\n        }\n\n\n        /// <summary>\n        /// Encrypts all the fields in the current object based on the EncryptFieldList\n        /// </summary>\n        /// <returns></returns>\n        public virtual void EncryptFields(AppConfiguration config)\n        {\n            if (string.IsNullOrEmpty(PropertiesToEncrypt))\n                return;\n\n            string encryptFieldList = \",\" + PropertiesToEncrypt.ToLower() + \",\";\n            string[] fieldTokens = encryptFieldList.Split(new char[1] {','}, StringSplitOptions.RemoveEmptyEntries);            \n\n            foreach(string fieldName in fieldTokens)\n            {\n                // Encrypt the field if in list\n                if (encryptFieldList.Contains(\",\" + fieldName.ToLower() + \",\"))\n                {\n                    object val = string.Empty;\n                    try\n                    {\n                       val = ReflectionUtils.GetPropertyEx(config, fieldName);\n                    }\n                    catch\n                    {\n                        throw new ArgumentException(string.Format(\"{0}: {1}\",Resources.InvalidEncryptionPropertyName,fieldName));\n                    }\n\n                    // only encrypt string values\n                    var strVal = val as string;\n                    if (string.IsNullOrEmpty(strVal))\n                        continue;\n\n                    val = Encryption.EncryptString(strVal, EncryptionKey);\n                    try\n                    {\n                        ReflectionUtils.SetPropertyEx(config, fieldName, val);\n                    }\n                    catch\n                    {\n                        throw new ArgumentException(string.Format(\"{0}: {1}\", Resources.InvalidEncryptionPropertyName, fieldName));\n                    }\n                }\n            }\n        }\n\n        /// <summary>\n        /// Internally decryptes all the fields in the current object based on the EncryptFieldList\n        /// </summary>\n        /// <returns></returns>\n        public virtual void DecryptFields(AppConfiguration config)\n        {\n            if (string.IsNullOrEmpty(PropertiesToEncrypt))\n                return;\n\n            string encryptFieldList = \",\" + PropertiesToEncrypt.ToLower() + \",\";\n            string[] fieldTokens = encryptFieldList.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);\n\n            foreach (string fieldName in fieldTokens)\n            {\n                // Encrypt the field if in list\n                if (encryptFieldList.Contains(\",\" + fieldName.ToLower() + \",\"))\n                {\n                    object val = string.Empty;\n                    try\n                    {\n                        val = ReflectionUtils.GetPropertyEx(config, fieldName);\n                    }\n                    catch\n                    {\n                        throw new ArgumentException(string.Format(\"{0}: {1}\", Resources.InvalidEncryptionPropertyName, fieldName));\n                    }\n\n                    // only encrypt string values\n                    var strVal = val as string;\n                    if (string.IsNullOrEmpty(strVal))\n                        continue;\n\n                    val = Encryption.DecryptString(strVal, EncryptionKey);\n                    try\n                    {\n                        ReflectionUtils.SetPropertyEx(config, fieldName, val);\n                    }\n                    catch\n                    {\n                        throw new ArgumentException(string.Format(\"{0}: {1}\", Resources.InvalidEncryptionPropertyName, fieldName));\n                    }\n                }\n            }\n        }\n\n\n\n        /// <summary>\n        /// Sets an error message when an error occurs\n        /// </summary>\n        /// <param name=\"message\"></param>\n        protected virtual void SetError(string message)\n        {\n            if (string.IsNullOrEmpty(message))\n            {\n                ErrorMessage = string.Empty;\n                return;\n            }\n\n            ErrorMessage = message;\n        }\n\n        /// <summary>\n        /// Writes an exception and innerexception message\n        /// into the error message text\n        /// </summary>\n        /// <param name=\"ex\"></param>\n        protected virtual void SetError(Exception ex)\n        {\n            string message = ex.Message;\n            if (ex.InnerException != null)\n                message += \" \" + ex.InnerException.Message;\n            SetError(message);\n        }\n\n        /// <summary>\n        /// Helper method to create a new instance of the Configuration object.        \n        /// </summary>\n        /// <returns></returns>\n        protected TAppConfiguration CreateConfigurationInstance()\n        {\n            return Activator.CreateInstance(typeof(TAppConfiguration)) as TAppConfiguration;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/IConfigurationProvider.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Reflection;\n\nnamespace Westwind.Utilities.Configuration\n{\n    /// <summary>\n    /// Configuration Provider interface that provides read and write services\n    /// to various configuration storage mechanisms.\n    /// \n    /// Used in conjunction with the <seealso cref=\"AppConfiguration\"/> class. \n    /// A base implementation from which to inherit is provided in the\n    /// <seealso cref=\"Westwind.Utilities.Configuration.ConfigurationProviderBase{TAppConfiguration}\"/>  class.\n    /// </summary>\n    public interface IConfigurationProvider        \n    {\n        /// <summary>\n        /// Holds an error message after a read or write operation\n        /// failed.\n        /// </summary>\n        string ErrorMessage { get; set; }\n\n        /// <summary>\n        /// A comma delimited list of fields that are to be encrypted\n        /// </summary>\n        string PropertiesToEncrypt { get; set; }\n\n        /// <summary>\n        /// The encryption key used to encrypt fields in config objects\n        /// </summary>\n        string EncryptionKey { get; set; }\n\n        /// <summary>\n        /// Optional Section name that can be used to sub-segment in multi-config files\n        /// </summary>\n        string ConfigurationSection { get; set; }\n\n        /// <summary>\n        /// Reads configuration information into new configuration object instance\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <returns></returns>\n        T Read<T>()\n            where T : AppConfiguration, new();\n\n        /// <summary>\n        /// Reads configuration information into a provided config object instance\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        bool Read(AppConfiguration config);        \n        T Read<T>(string xml)\n            where T: AppConfiguration, new();\n\n        /// <summary>\n        /// Reads configuration information from an XML string (Xml Serialization format)\n        /// into a provided config object instance\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <param name=\"xml\"></param>\n        /// <returns></returns>\n        bool Read(AppConfiguration config, string xml);\n\n        /// <summary>\n        /// Writes configuration information into a provided object instance\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        bool Write(AppConfiguration config);\n\n        /// <summary>\n        /// Writes configuration for a provided config object and returns\n        /// the serialized data as a string.\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        string WriteAsString(AppConfiguration config);\n\n        /// <summary>\n        /// Encrypts fields in a config object as specified in the cref=\"PropertiesToEncrypt\" property.\n        /// </summary>\n        /// <param name=\"config\"></param>\n        void EncryptFields(AppConfiguration config);\n\n        /// <summary>\n        /// Decryptes the encyrpted fields in a config object a\n        /// </summary>\n        /// <param name=\"config\"></param>\n        void DecryptFields(AppConfiguration config);\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/JsonFileConfigurationProvider.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// Reads and Writes configuration settings in .NET config files and \n    /// sections. Allows reading and writing to default or external files \n    /// and specification of the configuration section that settings are\n    /// applied to.\n    /// </summary>\n    public class JsonFileConfigurationProvider<TAppConfiguration> : ConfigurationProviderBase<TAppConfiguration>\n        where TAppConfiguration : AppConfiguration, new()\n    {\n\n        /// <summary>\n        /// Optional - the Configuration file where configuration settings are\n        /// stored in. If not specified uses the default Configuration Manager\n        /// and its default store.\n        /// </summary>\n        public string JsonConfigurationFile\n        {\n            get { return _JsonConfigurationFile; }\n            set { _JsonConfigurationFile = value; }\n        }\n        private string _JsonConfigurationFile = \"applicationConfiguration.json\";\n\t\t\n\n        /// <summary>\n        /// Reads configuration into the current instance of the config object passed in.\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Read(AppConfiguration config)\n        {\n            var newConfig = JsonSerializationUtils.DeserializeFromFile(JsonConfigurationFile, typeof(TAppConfiguration)) as TAppConfiguration;\n            if (newConfig == null)\n            {\n                if (Write(config))\n                    return true;\n                return false;\n            }\n            DecryptFields(newConfig);\n            DataUtils.CopyObjectData(newConfig, config, \"Provider,ErrorMessage\");\n\n            return true;\n        }\n\n        /// <summary>\n        /// Return \n        /// </summary>\n        /// <typeparam name=\"TAppConfig\"></typeparam>\n        /// <returns></returns>\n        public override TAppConfig Read<TAppConfig>()\n        {\n            var result = JsonSerializationUtils.DeserializeFromFile(JsonConfigurationFile, typeof(TAppConfig)) as TAppConfig;\n            if (result != null)\n                DecryptFields(result);\n\n            return result;\n        }\n\n        /// <summary>\n        /// Write configuration to XmlConfigurationFile location\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Write(AppConfiguration config)\n        {\n            EncryptFields(config);\n\n            bool result = JsonSerializationUtils.SerializeToFile(config, JsonConfigurationFile, false, true);\n\n            // Have to decrypt again to make sure the properties are readable afterwards\n            DecryptFields(config);\n\n            return result;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/StringConfigurationProvider.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// Reads and Writes configuration settings from strings - which you manage\n    /// yourself. Using strings for the configuration provider allows for easy\n    /// storage into any non-supported configuration stores that you control\n    /// through your code as long as it supports strings.   \n    /// \n    /// The string provider is a real minimal implementation that only implements\n    /// WriteAsString(config) and Read(string). It inherits all of its functionality\n    /// from the base provider.\n    /// </summary>\n    public class StringConfigurationProvider<TAppConfiguration> : ConfigurationProviderBase<TAppConfiguration>\n        where TAppConfiguration : AppConfiguration, new()\n    {\n        public string InitialStringData { get; set; }\n\n        /// <summary>\n        /// Reads from the InitialStringData string data\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <returns></returns>\n        public override T Read<T>()\n        {\n            return this.Read<T>(InitialStringData);\n        }\n\n        /// <summary>\n        /// Reads configuration information into config from InitialStringData\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Read(AppConfiguration config)\n        {\n            return Read(config, InitialStringData);\n        }\n\n        /// <summary>\n        /// Not supported for StringConfiguration\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Write(AppConfiguration config)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Configuration/Providers/XmlFileConfigurationProvider.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.IO;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// Reads and Writes configuration settings in .NET config files and \n    /// sections. Allows reading and writing to default or external files \n    /// and specification of the configuration section that settings are\n    /// applied to.\n    /// </summary>\n    public class XmlFileConfigurationProvider<TAppConfiguration> : ConfigurationProviderBase<TAppConfiguration>\n        where TAppConfiguration: AppConfiguration, new()\n    {\n\n        /// <summary>\n        /// Optional - the Configuration file where configuration settings are\n        /// stored in. If not specified uses the default Configuration Manager\n        /// and its default store.\n        /// </summary>\n        public string XmlConfigurationFile\n        {\n            get { return _XmlConfigurationFile; }\n            set { _XmlConfigurationFile = value; }\n        }\n        private string _XmlConfigurationFile = string.Empty;\n\n        \n        public bool UseBinarySerialization\n        {\n          get { return _UseBinarySerialization; }\n          set { _UseBinarySerialization = value; }\n        }\n        private bool _UseBinarySerialization = false;\n\n\n        public override bool Read(AppConfiguration config)\n        {\n            var newConfig = SerializationUtils.DeSerializeObject(XmlConfigurationFile,typeof(TAppConfiguration),UseBinarySerialization) as TAppConfiguration;\n\n            if (File.Exists(XmlConfigurationFile) && newConfig == null)\n                throw new ArgumentException(string.Format(Resources.InvalidXMLConfigurationFileFormat,XmlConfigurationFile));\n\n            if (newConfig == null)\n            {\n                if(Write(config))\n                    return true;\n                return false;\n            }\n\n            DecryptFields(newConfig);\n            DataUtils.CopyObjectData(newConfig, config, \"Provider,ErrorMessage\");\n            \n            return true;\n        }\n\n        /// <summary>\n        /// Return \n        /// </summary>\n        /// <typeparam name=\"TAppConfig\"></typeparam>\n        /// <returns></returns>\n        public override TAppConfig Read<TAppConfig>()\n        {\n            var result = SerializationUtils.DeSerializeObject(XmlConfigurationFile,typeof(TAppConfig),UseBinarySerialization) as TAppConfig;\n            if (result != null)\n                DecryptFields(result);\n\n            return result;\n        }\n    \n        /// <summary>\n        /// Write configuration to XmlConfigurationFile location\n        /// </summary>\n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool  Write(AppConfiguration config)\n        {\n            EncryptFields(config);\n            \n            bool result = SerializationUtils.SerializeObject(config, XmlConfigurationFile, UseBinarySerialization);\n            \n            // Have to decrypt again to make sure the properties are readable afterwards\n            DecryptFields(config);\n\n            return result;\n \t    }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Data/ValidationError.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System.Collections;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n\n\t/// <summary>\n\t/// Object that holds a single Validation Error for the business object\n\t/// </summary>\n\tpublic class ValidationError \n\t{\n\t\t\n\t\t/// <summary>\n\t\t/// The error message for this validation error.\n\t\t/// </summary>\n\t\tpublic string Message\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\treturn cMessage;\n\t\t\t}\n\t\t\tset\n\t\t\t{\n\t\t\t\tcMessage = value;\n\t\t\t}\n\t\t}\n\t\tstring cMessage = \"\";\n\n\t\t/// <summary>\n\t\t/// The name of the field that this error relates to.\n\t\t/// </summary>\n\t\tpublic string ControlID \n\t\t{\n\t\t\tget { return this .cFieldName; }\n\t\t\tset { cFieldName = value; }\n\t\t}\n\t\tstring cFieldName = \"\";\n\t\t\n\t\t/// <summary>\n\t\t/// An ID set for the Error. This ID can be used as a correlation between bus object and UI code.\n\t\t/// </summary>\n\t\tpublic string ID \n\t\t{\n\t\t\tget { return cID; }\n\t\t\tset { cID = value; }\n\t\t}\n\t\tstring cID = \"\";\n\t\t\n\t\tpublic ValidationError() : base() {}\n\n\t\tpublic ValidationError(string message) \n\t\t{\n\t\t\tMessage = message;\n\t\t}\n\t\tpublic ValidationError(string message, string fieldName) \n\t\t{\n\t\t\tMessage = message;\n\t\t\tControlID = fieldName;\n\t\t}\n\t\tpublic ValidationError(string message, string fieldName, string id) \n\t\t{\n\t\t\tMessage = message;\n\t\t\tControlID = fieldName;\n\t\t\tID = id;\n\t\t}\n\n\n        public override string ToString()\n        {\n            return Message;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Data/ValidationErrorCollection.cs",
    "content": "using System.Collections;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// A collection of ValidationError objects that is used to collect\n    /// errors that occur duing calls to the Validate method.\n    /// </summary>\n    public class ValidationErrorCollection : CollectionBase\n    {\n\n        /// <summary>\n        /// Indexer property for the collection that returns and sets an item\n        /// </summary>\n        public ValidationError this[int index]\n        {\n            get\n            {\n                return (ValidationError)List[index];\n            }\n            set\n            {\n                List[index] = value;\n            }\n        }\n\n        /// <summary>\n        /// Shortcut to determine whether this instance has errors\n        /// </summary>\n        public bool HasErrors => this.Count > 0;\n\n        /// <summary>\n        /// Adds a new <see cref=\"T:ValidationError\">ValidationError</see> to the collection\n        /// \n        /// </summary>\n        /// <param name=\"error\">\n        /// Validation Error object\n        /// </param>\n        /// <returns>Void</returns>\n        public void Add(ValidationError error)\n        {\n            List.Add(error);\n        }\n\n\n        /// <summary>\n        /// Adds a new error to the collection\n        /// <seealso>T:ValidationErrorCollection</seealso>\n        /// </summary>\n        /// <param name=\"message\">\n        /// Message of the error\n        /// </param>\n        /// <param name=\"fieldName\">\n        /// optional field name that it applies to (used for Databinding errors on \n        /// controls)\n        /// </param>\n        /// <param name=\"id\">\n        /// An optional ID you assign the error\n        /// </param>\n        /// <returns>Void</returns>\n        public void Add(string message, string fieldName = \"\", string id = \"\")\n        {\n            var error = new ValidationError() \n            { \n                Message = message, \n                ControlID = fieldName, \n                ID = id \n            };\n            Add(error);\n        }\n\n        /// <summary>\n        /// Like Add but allows specifying of a format. \n        /// Adds a <see cref=\"T:ValidationError\">ValidationError</see>.\n        /// \n        /// <seealso>T:ValidationErrorCollection</seealso>\n        /// <seealso>T:ValidationError</seealso>        \n        /// </summary>\n        /// <param name=\"message\">A format message into which arguments are embedded using `{0}` `{1}` syntax etc.</param>\n        /// <param name=\"fieldName\">Optional name of the field</param>\n        /// <param name=\"id\">Optional Id</param>\n        /// <param name=\"arguments\">Any arguments to send</param>\n        public void AddFormat(string message, string fieldName, string id, params object[] arguments)\n        {\n            Add(string.Format(message, arguments), fieldName, id);\n        }\n\n        /// <summary>\n        /// Removes the item specified in the index from the Error collection\n        /// </summary>\n        /// <param name=\"index\"></param>\n        public void Remove(int index)\n        {\n            if (index > List.Count - 1 || index < 0)\n                List.RemoveAt(index);\n        }\n\n        /// <summary>\n        /// Adds a validation error if the condition is true. Otherwise no item is \n        /// added.\n        /// <seealso>ValidationErrorCollection</seealso>\n        /// </summary>\n        /// <param name=\"condition\">\n        /// If true this error is added. Otherwise not.\n        /// </param>\n        /// <param name=\"message\">\n        /// The message for this error\n        /// </param>\n        /// <param name=\"fieldName\">\n        /// Name of the UI field (optional) that this error relates to. Used optionally\n        ///  by the databinding classes.\n        /// </param>\n        /// <param name=\"id\">\n        /// An optional Error ID.\n        /// </param>\n        /// <returns>value of condition</returns>\n        public bool Assert(bool condition, string message, string fieldName, string id)\n        {\n            if (condition)\n                Add(message, fieldName, id);\n\n            return condition;\n        }\n\n        /// <summary>\n        /// Adds a validation error if the condition is true. Otherwise no item is \n        /// added.\n        /// <seealso>Class ValidationErrorCollection</seealso>\n        /// </summary>\n        /// <param name=\"condition\">\n        /// If true the Validation Error is added.\n        /// </param>\n        /// <param name=\"message\">\n        /// The Error Message for this error.\n        /// </param>\n        /// <returns>value of condition</returns>\n        public bool Assert(bool condition, string message)\n        {\n            if (condition)\n                Add(message);\n\n            return condition;\n        }\n\n        /// <summary>\n        /// Adds a validation error if the condition is true. Otherwise no item is \n        /// added.\n        /// <seealso>T:ValidationErrorCollection</seealso>\n        /// </summary>\n        /// <param name=\"condition\">\n        /// If true the Validation Error is added.\n        /// </param>\n        /// <param name=\"message\">\n        /// The Error Message for this error.\n        /// </param>\n        /// <param name=\"fieldName\">\n        /// Optional fieldName that can be linked in UI\n        /// </param>\n        /// <returns>string</returns>\n        public bool Assert(bool condition, string message, string fieldName)\n        {\n            if (condition)\n                Add(message, fieldName);\n\n            return condition;\n        }\n\n\n        /// <summary>\n        /// Asserts a business rule - if condition is true it's added otherwise not.\n        /// </summary>\n        /// <param name=\"condition\">\n        /// If this condition evaluates to true the Validation Error is added\n        /// </param>\n        /// <param name=\"error\">\n        /// Validation Error Object\n        /// </param>\n        /// <returns>value of condition</returns>\n        public bool Assert(bool condition, ValidationError error)\n        {\n            if (condition)\n                List.Add(error);\n\n            return condition;\n        }\n\n\n        /// <summary>\n        /// Returns a string representation of the errors in this collection.\n        /// The string is separated by CR LF after each line.\n        /// </summary>\n        /// <returns></returns>\n        public override string ToString()\n        {\n            if (Count < 1)\n                return string.Empty;\n\n            StringBuilder sb = new StringBuilder(128);\n\n            foreach (ValidationError error in this)\n            {\n                sb.AppendLine(error.Message);\n            }\n\n            return sb.ToString();\n        }\n\n        /// <summary>\n        /// Returns a string representation of the errors in this collection.\n        /// The string is separated by CR LF after each line but it\n        /// uses an optional string prefix on each line.\n        /// </summary>\n        /// <param name=\"prefixLine\">A string prefix that pre-pended on each error line (plus a space)</param>\n        /// <returns></returns>\n        public string ToString(string prefixLine)\n        {\n            if (Count < 1)\n                return string.Empty;\n\n            StringBuilder sb = new StringBuilder(128);\n\n            foreach (ValidationError error in this)\n            {\n                sb.AppendLine($\"{prefixLine} {error.Message}\");\n            }\n\n            return sb.ToString();\n        }\n\n        /// <summary>\n        /// Returns an HTML representation of the errors in this collection.\n        /// The string is returned as an HTML unordered list.\n        /// </summary>\n        /// <returns></returns>\n        public string ToHtml()\n        {\n            if (Count < 1)\n                return \"\";\n\n            StringBuilder sb = new StringBuilder(256);\n            sb.Append(\"<ul>\\r\\n\");\n\n            foreach (ValidationError error in this)\n            {\n                sb.Append(\"<li>\");                \n                if (error.ControlID != null && error.ControlID != \"\")\n                    sb.AppendFormat(\"<a href='#' onclick=\\\"_errorLinkClick('{0}');return false;\\\" \" +\n                                  \"style='text-decoration:none'>{1}</a>\", \n                                  error.ControlID.Replace(\".\",\"_\"),error.Message);\n                else\n                    sb.Append(error.Message);\n\n                sb.AppendLine(\"</li>\");\n            }\n\n            sb.Append(\"</ul>\\r\\n\");\n            string script =\n            @\"    <script>\n        function _errorLinkClick(id) {\n            var $t = $('#' + id);\n            if ($t.length == 0) {\n                $t = $('#txt' + id);\n            }\n            if ($t.length == 0) {\n                $t = $('#cmb' + id);\n            }\n            $t.addClass('errorhighlight').focus();            \n            setTimeout(function() {\n                $t.removeClass('errorhighlight');\n            }, 5000);\n        }\n    </script>\";\n            sb.AppendLine(script);\n            \n            return sb.ToString();\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/InternetTools/HttpClient.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          � West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\n\nusing System;\nusing System.Net;\nusing System.IO;\nusing System.Text;\nusing System.Net.Security;\nusing System.Security.Cryptography.X509Certificates;\nusing System.IO.Compression;\nusing System.Threading.Tasks;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.InternetTools\n{\n    /// <summary>\n    /// An HTTP wrapper class that abstracts away the common needs for adding post keys\n    /// and firing update events as data is received. This class is real easy to use\n    /// with many common operations requiring single method calls.\n    ///\n    /// The class also provides automated cookie and state handling, GZip compression\n    /// decompression, simplified proxy and authentication mechanisms to provide a \n    /// simple single level class interface. The underlying WebRequest is also \n    /// exposed so you will not loose any functionality from the .NET BCL class.\n    /// </summary>\n#if NET6_0_OR_GREATER\n    [Obsolete(\"Westwind.HttpClient is obsolete in .NET Core as it uses the old HttpWebRequest class. Please use System.Net.HttpClient or \" +\n        \"`Westwind.Utilities.HttpClientUtils` or in Net4.x for sync Operations `Westwind.Utilities.HttpUtils`\")]\n#endif\n    public class HttpClient : IDisposable\n\t{\n\t\t/// <summary>\n\t\t/// Determines how data is POSTed when when using AddPostKey() and other methods\n\t\t/// of posting data to the server. Support UrlEncoded, Multi-Part, XML and Raw modes.\n\t\t/// </summary>\n\t\tpublic HttpPostMode PostMode { get; set; } = HttpPostMode.UrlEncoded;\n\n        /// <summary>\n\t\t///  User name used for Authentication. \n\t\t///  To use the currently logged in user when accessing an NTLM resource you can use \"AUTOLOGIN\".\n\t\t/// </summary>\n\t\tpublic string Username { get; set; }\n\n        /// <summary>\n\t\t/// Password for Authentication.\n\t\t/// </summary>\n\t\tpublic string Password { get; set; }\n\n        /// <summary>\n\t\t/// Address of the Proxy Server to be used.\n\t\t/// Use optional DEFAULTPROXY value to specify that you want to IE's Proxy Settings\n\t\t/// </summary>\n\t\tpublic string ProxyAddress { get; set; }\n\n        /// <summary>\n\t\t/// Semicolon separated Address list of the servers the proxy is not used for.\n\t\t/// </summary>\n\t\tpublic string ProxyBypass { get; set; }\n\n        /// <summary>\n\t\t/// Username for a password validating Proxy. Only used if the proxy info is set.\n\t\t/// </summary>\n\t\tpublic string ProxyUsername { get; set; } \n\n        /// <summary>\n\t\t/// Password for a password validating Proxy. Only used if the proxy info is set.\n\t\t/// </summary>\n\t\tpublic string ProxyPassword { get; set; }\n\n\n        /// <summary>\n\t\t/// Timeout for the Web request in seconds. Times out on connection, read and send operations.\n\t\t/// Default is 30 seconds.\n\t\t/// </summary>\n\t\t[Obsolete(\"Please use TimeoutMs instead\")]\n        public int Timeout {\n            get\n            {\n                if (_timeoutMs < 0)\n                    return -1;\n\n                return _timeoutMs * 1000;\n            }\n            set\n            {\n                if(value >= 1)\n                    _timeoutMs = value * 1000;\n                else if (value == -1)\n                    _timeoutMs = -1;\n\n                {\n                    if (value > 0)\n                        _timeoutMs = 1;\n                    else if (value < 0)\n                        _timeoutMs = -1;\n                    else\n                        _timeoutMs = 0;\n                }\n            }\n        }\n\n        /// <summary>\n        /// Timeout for the Web request in seconds. Times out on connection, read and send operations.\n        /// Default is 30 seconds (30,000ms).\n        /// </summary>\n        public int TimeoutMs\n        {\n            get => _timeoutMs;\n            set => _timeoutMs = value;\n        }\n        private int _timeoutMs = 30_000;\n\n        /// <summary>\n\t\t/// Returns whether the last request was cancelled through one of the\n\t\t/// events.\n\t\t/// </summary>\n\t\tpublic bool Cancelled { get; set; }\n\n\n        /// <summary>\n        /// Use this option to set a custom content type. \n        /// If possible use PostMode to specify a predefined\n        /// content type as it will ensure that Post data is\n        /// appropriately formatted.\n        /// \n        /// If setting the content type manually POST data\n        /// </summary>\n        public string ContentType\n        {\n            get { return _ContentType; }\n            set { \n                _ContentType = value;\n                if (_ContentType == null)\n                    return;\n\n                if (_ContentType.StartsWith(\"application/x-www-form-urlencoded\"))\n                    PostMode = HttpPostMode.UrlEncoded;\n                else if (_ContentType.StartsWith(\"multipart/form-data\"))\n                    PostMode = HttpPostMode.MultiPart;\n                else\n                    PostMode = HttpPostMode.Raw;\n            }\n        }\n        private string _ContentType;\n\n\n        // this doesn't seem necessary - . NET will automatically decode common encodings like UTF-8\n        ///// <summary>\n        ///// The Encoding used to decode the response data\n        ///// </summary>        \n        //public Encoding ResponseEncoding\n        //{\n        //    get { return _ResponseEncoding; }\n        //    set { _ResponseEncoding = value; }\n        //}\n        //private Encoding _ResponseEncoding = Encoding.Default;\n\n        \n        /// <summary>\n        /// When true will automatically add Accept-Encoding: gzip,deflate header\n        /// and automatically decompress gzip and deflate content\n        /// </summary>\n        public bool UseGZip { get; set; }\n\n        /// <summary>\n        /// Keeps track of request timings for the last executed request. Tracks started, \n        /// firstbyte and lastbyte times as well as ms to first byte \n        /// (actually first 'buffer' loaded) and last byte.\n        /// </summary>\n        /// <remarks>Does not work with DownloadStream() since you control the stream's operations.</remarks>\n\t    public HttpTimings HttpTimings { get; set; }\n\n\t\t/// <summary>\n\t\t/// Error Message if the Error Flag is set or an error value is returned from a method.\n\t\t/// </summary>\n\t\tpublic string ErrorMessage \n\t\t{\n\t\t\tget { return _ErrorMessage; } \n\t\t\tset { _ErrorMessage = value; }\n\t\t}\n\t\t\n\t\t/// <summary>\n\t\t/// Error flag if an error occurred.\n\t\t/// </summary>\n\t\tpublic bool Error { get; set; } \n\n        /// <summary>\n\t\t/// Determines whether errors cause exceptions to be thrown. By default errors \n\t\t/// are handled in the class and the Error property is set for error conditions.\n\t\t/// (not implemented at this time).\n\t\t/// </summary>\n\t\tpublic bool ThrowExceptions { get; set; } \n\n        /// <summary>\n\t\t/// If set to true will automatically track cookies\n        /// between multiple successive requests on this \n        /// instance. Uses the CookieCollection property\n        /// to persist cookie status.\n        /// \n        /// When set posts values in the CookieCollection,\n        /// and on return fills the CookieCollection with\n        /// cookies from the Response.\n\t\t/// </summary>\n\t\tpublic bool HandleCookies { get; set; }\n\n\n        /// <summary>\n\t\t/// Holds the internal Cookie collection before or after a request. This \n\t\t/// collection is used only if HandleCookies is set to .t. which also causes it\n\t\t///  to capture cookies and repost them on the next request.\n\t\t/// </summary>\n\t\tpublic CookieCollection Cookies \n\t\t{\n\t\t\tget \n\t\t\t{\n\t\t\t\tif (_Cookies == null)\n\t\t\t\t\tCookies = new CookieCollection();\n\t\t\t\t\t  \n\t\t\t\treturn _Cookies; \n\t\t\t}\n\t\t\tset { _Cookies = value; }\n\t\t}\n\n\t\t/// <summary>\n\t\t/// WebResponse object that is accessible after the request is complete and \n\t\t/// allows you to retrieve additional information about the completed request.\n\t\t/// \n\t\t/// The Response Stream is already closed after the GetUrl methods complete \n\t\t/// (except GetUrlResponse()) but you can access the Response object members \n\t\t/// and collections to retrieve more detailed information about the current \n\t\t/// request that completed.\n\t\t/// </summary>\n\t\tpublic HttpWebResponse WebResponse { get; set; }\n\n        /// <summary>\n\t\t/// WebRequest object that can be manipulated and set up for the request if you\n\t\t///  called .\n\t\t/// \n\t\t/// Note: This object must be recreated and reset for each request, since a \n\t\t/// request's life time is tied to a single request. This object is not used if\n\t\t///  you specify a URL on any of the GetUrl methods since this causes a default\n\t\t///  WebRequest to be created.\n\t\t/// </summary>\n\t\tpublic HttpWebRequest WebRequest { get; set; }\n\n        /// <summary>\n\t\t/// The buffersize used for the Send and Receive operations\n\t\t/// </summary>\n\t\tpublic int BufferSize { get; set; } = 100;\n\n        /// <summary>\n\t\t/// Lets you specify the User Agent  browser string that is sent to the server.\n\t\t///  This allows you to simulate a specific browser if necessary.\n\t\t/// </summary>\n\t\tpublic string UserAgent { get; set; } = \"West Wind HTTP .NET Client\";\n\n        public string HttpVerb { get; set; } = \"GET\";\n\t\t\n\t\t// member properties\n\t\t//string cPostBuffer = string.Empty;\n        private MemoryStream _PostStream;\n        private BinaryWriter _PostData;\n\n\n        private string _ErrorMessage = string.Empty;\n        private CookieCollection _Cookies;\n        private string _MultiPartBoundary = \"-----------------------------\" + DateTime.Now.Ticks.ToString(\"x\");\n\n\t\t/// <summary>\n\t\t/// The HttpClient Default Constructor\n\t\t/// </summary>\n\t\tpublic HttpClient()\n\t\t{\n\t\t    HttpTimings = new HttpTimings();\n\t\t}\n\n\t\t/// <summary>\n\t\t/// Creates a new WebRequest instance that can be set prior to calling the \n\t\t/// various Get methods. You can then manipulate the WebRequest property, to \n\t\t/// custom configure the request.\n\t\t/// \n\t\t/// Instead of passing a URL you  can then pass null.\n\t\t/// \n\t\t/// Note - You need a new Web Request for each and every request so you need to\n\t\t///  set this object for every call if you manually customize it.\n\t\t/// </summary>\n\t\t/// <param name=\"String Url\">\n\t\t/// The Url to access with this WebRequest\n\t\t/// </param>\n\t\t/// <returns>Boolean</returns>\n\t\tpublic bool CreateWebRequestObject(string Url) \n\t\t{\n\t\t\ttry \n\t\t\t{\n#pragma warning disable SYSLIB0014\n                WebRequest =  (HttpWebRequest) System.Net.WebRequest.Create(Url);                \n#pragma warning restore SYSLIB0014\n                \n            }\n\t\t\tcatch (Exception ex)\n\t\t\t{\n\t\t\t\tErrorMessage = ex.Message;\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n        #region POST data\n        /// <summary>\n        /// Resets the Post buffer by clearing out all existing content\n        /// </summary>\n        public void ResetPostData()\n        {\n            _PostStream = new MemoryStream();\n            _PostData = new BinaryWriter(_PostStream);\n        }\n\n\t    public void SetPostStream(Stream postStream)\n        {\n            MemoryStream ms = new MemoryStream(1024);\n            FileUtils.CopyStream(postStream, ms, 1024);\n            ms.Flush();\n            ms.Position = 0;\n            _PostStream = ms;\n            _PostData = new BinaryWriter(ms);            \n        }\n\n\t    /// <summary>\n\t    /// Adds POST form variables to the request buffer.\n\t    /// PostMode determines how parms are handled.\n\t    /// </summary>\n\t    /// <param name=\"key\">Key value or raw buffer depending on post type</param>\n\t    /// <param name=\"value\">Value to store. Used only in key/value pair modes</param>\n\t    public void AddPostKey(string key, byte[] value)\n\t    {\n\t        if (value == null)\n\t            return;\n\n\t        if (key == \"RESET\")\n\t        {\n\t            ResetPostData();\n\t            return;\n\t        }\n\t\t\t\n\t        if (_PostData == null) \n\t        {\n\t            _PostStream = new MemoryStream();\n\t            _PostData = new BinaryWriter(_PostStream);\n\t        }\n\n\t        if (string.IsNullOrEmpty(key))\n\t            _PostData.Write(value);\n            else if(PostMode == HttpPostMode.UrlEncoded)\n\t                _PostData.Write( \n                        Encoding.Default.GetBytes(key + \"=\" +\n\t                                              StringUtils.UrlEncode(Encoding.Default.GetString(value)) +\n\t                                              \"&\") );\n            else if (PostMode == HttpPostMode.MultiPart)\n\t        {\n                Encoding iso = Encoding.GetEncoding(\"ISO-8859-1\");\n                _PostData.Write(iso.GetBytes(\n\t                \"--\" + _MultiPartBoundary + \"\\r\\n\" +\n\t                \"Content-Disposition: form-data; name=\\\"\" + key + \"\\\"\\r\\n\\r\\n\"));\n\n\t            _PostData.Write(value);\n\t            _PostData.Write(iso.GetBytes(\"\\r\\n\"));\n\t        }\n            else  // Raw or Xml, JSON modes\n                _PostData.Write( value );\t            \n\t    }\n\n\t    /// <summary>\n\t\t/// Adds POST form variables to the request buffer.\n\t\t/// PostMode determines how parms are handled.\n\t\t/// </summary>\n\t\t/// <param name=\"key\">Key value or raw buffer depending on post type</param>\n\t\t/// <param name=\"value\">Value to store. Used only in key/value pair modes</param>\n\t\tpublic void AddPostKey(string key, string value)\n\t\t{\n            if (value == null)\n                return;\n\t\t\tAddPostKey(key,Encoding.Default.GetBytes(value));\n\t\t}\n\n\t\t/// <summary>\n\t\t/// Adds a fully self contained POST buffer to the request.\n\t\t/// Works for XML or previously encoded content.\n\t\t/// </summary>\n\t\t/// <param name=\"fullPostBuffer\">String based full POST buffer</param>\n\t\tpublic void AddPostKey(string fullPostBuffer) \n\t\t{\n\t\t\tAddPostKey(null,fullPostBuffer );\n\t\t}\n\n\t    /// <summary>\n\t    /// Adds a fully self contained POST buffer to the request.\n\t    /// Works for XML or previously encoded content.\n\t    /// </summary>\t    \n        /// <param name=\"fullPostBuffer\">Byte array of a full POST buffer</param>\n\t    public void AddPostKey(byte[] fullPostBuffer) \n\t\t{\n\t\t\tAddPostKey(null,fullPostBuffer);\n\t\t}\n\n\t\t/// <summary>\n\t\t/// Allows posting a file to the Web Server. Make sure that you \n\t\t/// set PostMode\n\t\t/// </summary>\n\t\t/// <param name=\"key\"></param>\n\t\t/// <param name=\"fileName\"></param>\n\t\t/// <returns></returns>\n\t\tpublic bool AddPostFile(string key,string fileName, string contentType = \"application/octet-stream\") \n\t\t{\n\t\t\tbyte[] lcFile;\t\n\n\t\t\tif (PostMode != HttpPostMode.MultiPart) \n\t\t\t{\n\t\t\t\t_ErrorMessage = \"File upload allowed only with Multi-part forms\";\n\t\t\t\tError = true;\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\ttry \n\t\t\t{\t\t\t\n\t\t\t\tFileStream loFile = new FileStream(fileName,System.IO.FileMode.Open,System.IO.FileAccess.Read);\n\n\t\t\t\tlcFile = new byte[loFile.Length];\n                _ = loFile.Read(lcFile,0,(int) loFile.Length);\n                loFile.Close();\n\t\t\t}\n\t\t\tcatch(Exception e) \n\t\t\t{\n\t\t\t\t_ErrorMessage = e.Message;\n\t\t\t\tError = true;\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (_PostData == null) \n\t\t\t{\n\t\t\t\t_PostStream = new MemoryStream();\n\t\t\t\t_PostData = new BinaryWriter(_PostStream);\n\t\t\t}\n\n\t\t\t_PostData.Write( Encoding.Default.GetBytes(\n\t\t\t\t\"--\" + _MultiPartBoundary + \"\\r\\n\"  + \n\t\t\t\t\"Content-Disposition: form-data; name=\\\"\" + key + \"\\\"; filename=\\\"\" + \n\t\t\t\tnew FileInfo(fileName).Name + \"\\\"\\r\\n\" +\n                \"Content-Type: \" + contentType +\n                \"\\r\\n\\r\\n\" ) );\n            \n            \n\n\t\t\t_PostData.Write( lcFile );\n\n\t\t\t_PostData.Write( Encoding.Default.GetBytes(\"\\r\\n\")) ;\n\n\t\t\treturn true;\n\t\t}\n\n\t    /// <summary>\n\t    /// Returns the contents of the post buffer. Useful for debugging\n\t    /// </summary>\n\t    /// <returns></returns>\n\t    public string GetPostBuffer()\n\t    {\n\t        var bytes = _PostStream?.ToArray();\n\t        if (bytes == null)\n\t            return null;\n\t        return Encoding.Default.GetString(bytes);\n\t    }\n        #endregion\n\n        #region Run Requests\n\n\t\t/// <summary>\n\t\t/// Return a the result from an HTTP Url into a StreamReader.\n\t\t/// Client code should call Close() on the returned object when done reading.\n\t\t/// </summary>\n\t\t/// <param name=\"url\">Url to retrieve.</param>\t\t\n\t\t/// <returns></returns>\n\t\tpublic StreamReader DownloadStream(string url) \n\t\t{\n            try\n            {\n                Encoding enc;\n\n                HttpWebResponse Response = DownloadResponse(url);\n                if (Response == null)\n                    return null;\n\n                try\n                {\n                    if (!string.IsNullOrEmpty(Response.CharacterSet))\n                        enc = Encoding.GetEncoding(Response.CharacterSet);\n                    else\n                        enc = Encoding.Default;\n                }\n                catch\n                {\n                    // Invalid encoding passed\n                    enc = Encoding.Default;\n                }\n\n\n                Stream responseStream = Response.GetResponseStream();\n                //if (Response.ContentEncoding.ToLower().Contains(\"gzip\"))\n                //    responseStream = new GZipStream(Response.GetResponseStream(), CompressionMode.Decompress);\n                //else if (Response.ContentEncoding.ToLower().Contains(\"deflate\"))\n                //    responseStream = new DeflateStream(Response.GetResponseStream(), CompressionMode.Decompress);\n\n\n                // drag to a stream\n                StreamReader strResponse = new StreamReader(responseStream, enc);\n                return strResponse;\n            }\n            catch (Exception ex)\n            {\n                Error = true;\n                ErrorMessage = \"Unable to read response: \" + ex.Message;\n                return null;\n            }\n        }\n\n\n        /// <summary>\n        /// Return an HttpWebResponse object for a request. You can use the Response to\n        /// read the result as needed. This is a low level method. Most of the other 'Get'\n        /// methods call this method and process the results further.\n        /// </summary>\n        /// <remarks>Important: The Response object's Close() method must be called when you are done with the object.</remarks>\n        /// <param name=\"url\">Url to retrieve.</param>\n        /// <returns>An HttpWebResponse Object</returns>\n        [Obsolete(\"Use DownloadResponse instead.\")]\n        public HttpWebResponse GetUrlResponse(string url)\n        {\n            return DownloadResponse(url);\n        }\n        \n\n\t    public async Task<HttpWebResponse> DownloadResponseAsync(string url)\n\t    {\n            Cancelled = false;\n\n            //try \n            //{\n            Error = false;\n            _ErrorMessage = string.Empty;\n            Cancelled = false;\n\n            if (WebRequest == null)\n            {\n#pragma warning disable SYSLIB0014\n                WebRequest = (HttpWebRequest)System.Net.WebRequest.Create(url);\n#pragma warning restore SYSLIB0014\n                //WebRequest.Headers.Add(\"Cache\",\"no-cache\");                \n            }\n\n            WebRequest.UserAgent = UserAgent;\n            WebRequest.Timeout = TimeoutMs;\n            WebRequest.Method = HttpVerb;\n            \n#if NETFULL\n\t\t\tWebRequest.ReadWriteTimeout = TimeoutMs;\n#endif\n\n\n            // Handle Security for the request\n            if (!string.IsNullOrEmpty(Username))\n            {\n                if (Username == \"AUTOLOGIN\" || Username == \"AutoLogin\")\n                    WebRequest.Credentials = CredentialCache.DefaultCredentials;\n                else\n                    WebRequest.Credentials = new NetworkCredential(Username, Password);\n            }\n\n            // Handle Proxy Server configuration\n            if (!string.IsNullOrEmpty(ProxyAddress))\n            {\n                if (ProxyAddress == \"DEFAULTPROXY\")\n                {\n                    WebRequest.Proxy = HttpWebRequest.DefaultWebProxy;\n                }\n                else\n                {\n                    WebProxy Proxy = new WebProxy(ProxyAddress, true);\n\n                    if (ProxyBypass.Length > 0)\n                    {\n                        Proxy.BypassList = ProxyBypass.Split(';');\n                    }\n\n                    if (ProxyUsername.Length > 0)\n                        Proxy.Credentials = new NetworkCredential(ProxyUsername, ProxyPassword);\n\n                    WebRequest.Proxy = Proxy;\n                }\n            }\n\n            if (UseGZip)\n            {\n                // TODO: Check if already set                \n                WebRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, \"gzip,deflate\");\n                WebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;\n            }\n\n            // Handle cookies - automatically re-assign \n            if (HandleCookies || (_Cookies != null && _Cookies.Count > 0))\n            {\n                WebRequest.CookieContainer = new CookieContainer();\n                if (_Cookies != null && _Cookies.Count > 0)\n                {\n                    WebRequest.CookieContainer.Add(_Cookies);\n                }\n            }\n\n            HttpTimings.StartRequest();\n\n            // Deal with the POST buffer if any\n            if (_PostData != null)\n            {\n                if (WebRequest.Method == \"GET\")\n                    WebRequest.Method = \"POST\";\n\n                switch (PostMode)\n                {\n                    case HttpPostMode.UrlEncoded:\n                        WebRequest.ContentType = \"application/x-www-form-urlencoded\";\n                        break;\n                    case HttpPostMode.MultiPart:\n                        WebRequest.ContentType = \"multipart/form-data; boundary=\" + _MultiPartBoundary;\n                        _PostData.Write(Encoding.GetEncoding(1252).GetBytes(\"--\" + _MultiPartBoundary + \"--\\r\\n\"));\n                        break;\n                    case HttpPostMode.Xml:\n                        WebRequest.ContentType = \"text/xml\";\n                        break;\n                    case HttpPostMode.Json:\n                        WebRequest.ContentType = \"application/json\";\n                        break;\n                    case HttpPostMode.Raw:\n                        //WebRequest.ContentType = \"application/octet-stream\";\n                        break;\n                    default:\n                        goto case HttpPostMode.UrlEncoded;\n                }\n\n                if (!string.IsNullOrEmpty(ContentType))\n                    WebRequest.ContentType = ContentType;\n\n                // TODO: Make POSTing async\n                using (Stream requestStream = WebRequest.GetRequestStream())\n                {\n                    if (SendData == null)\n                        _PostStream.WriteTo(requestStream);  // Simplest version - no events\n                    else\n                        StreamPostBuffer(requestStream);     // Send in chunks and fire events\n\n                    //*** Close the memory stream\n                    _PostStream.Close();\n                    _PostStream = null;\n\n                    //*** Close the Binary Writer\n                    if (_PostData != null)\n                    {\n                        _PostData.Dispose();\n                        _PostData = null;\n                    }\n                }\n\n                // clear out the Post buffer\n                ResetPostData();\n\n                // If user cancelled the 'upload' exit\n                if (Cancelled)\n                {\n                    ErrorMessage = \"HTTP Request was cancelled.\";\n                    Error = true;\n                    return null;\n                }\n            }\n\n            // Retrieve the response headers \n            HttpWebResponse Response;\n            try\n            {\n                Response = await WebRequest.GetResponseAsync() as HttpWebResponse;\n            }\n            catch (WebException ex)\n            {\n                // Check for 500 error return - if so we still want to return a response\n                // Client can check oHttp.WebResponse.StatusCode\n                if (ex.Status == WebExceptionStatus.ProtocolError)\n                {\n                    Response = (HttpWebResponse)ex.Response;\n                }\n                else\n                {\n                    if(ThrowExceptions)\n                        throw;\n\n\t\t\t\t\tError = true;\n                    ErrorMessage = ex.Message + \".\" + url;\n\t\t\t\t\treturn null;\n                }\n            }\n\n            WebResponse = Response;\n\n            // Close out the request - it cannot be reused\n            WebRequest = null;\n\n            // ** Save cookies the server sends\n            if (HandleCookies)\n            {\n                if (Response.Cookies.Count > 0)\n                {\n                    if (_Cookies == null)\n                        _Cookies = Response.Cookies;\n                    else\n                    {\n                        // ** If we already have cookies update the list\n                        foreach (Cookie oRespCookie in Response.Cookies)\n                        {\n                            bool bMatch = false;\n                            foreach (Cookie oReqCookie in _Cookies)\n                            {\n                                if (oReqCookie.Name == oRespCookie.Name)\n                                {\n                                    oReqCookie.Value = oRespCookie.Value;\n                                    bMatch = true;\n                                    break; // \n                                }\n                            } // for each ReqCookies\n                            if (!bMatch)\n                                _Cookies.Add(oRespCookie);\n                        }\n                    }\n                }\n            }\n\n            return Response;\t        \n\t    }\n\n\t\t\t/// <summary>\n\t\t\t/// Return an HttpWebResponse object for a request. You can use the Response to\n\t\t\t/// read the result as needed. This is a low level method. Most of the other 'Get'\n\t\t\t/// methods call this method and process the results further.\n\t\t\t/// </summary>\n\t\t\t/// <remarks>Important: The Response object's Close() method must be called when you are done with the object.</remarks>\n\t\t\t/// <param name=\"url\">Url to retrieve.</param>\n\t\t\t/// <returns>An HttpWebResponse Object</returns>\n\t\t\tpublic HttpWebResponse  DownloadResponse(string url)\n\t\t{\n\t\t\tCancelled = false;\n\t\t\n            //try \n            //{\n\t\t\t\tError = false;\n\t\t\t\t_ErrorMessage = string.Empty;\n\t\t\t\tCancelled = false;\n\n\t\t\t\tif (WebRequest == null) \n\t\t\t\t{\n\n#pragma warning disable SYSLIB0014\n                    WebRequest =  (HttpWebRequest) System.Net.WebRequest.Create(url);                    \n#pragma warning restore SYSLIB0014\n                    //WebRequest.Headers.Add(\"Cache\",\"no-cache\");\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tWebRequest.UserAgent = UserAgent;\n                WebRequest.Timeout = TimeoutMs;\n                WebRequest.Method = HttpVerb;\n#if NETFULL\n\t\t\t\tWebRequest.ReadWriteTimeout = TimeoutMs;\n#endif\t\n\n\t\t\t\t// Handle Security for the request\n\t\t\t\tif (!string.IsNullOrEmpty(Username)) \n\t\t\t\t{\n\t\t\t\t\tif (Username  == \"AUTOLOGIN\" || Username == \"AutoLogin\")\n\t\t\t\t\t\tWebRequest.Credentials = CredentialCache.DefaultCredentials;\n\t\t\t\t\telse\n\t\t\t\t\t\tWebRequest.Credentials = new NetworkCredential(Username,Password);\n\t\t\t\t}\n\n\t\t\t\t// Handle Proxy Server configuration\n\t\t\t\tif (!string.IsNullOrEmpty(ProxyAddress))\n\t\t\t\t{\n\t\t\t\t\tif (ProxyAddress == \"DEFAULTPROXY\") \n\t\t\t\t\t{\n                        WebRequest.Proxy = HttpWebRequest.DefaultWebProxy;\n\t\t\t\t\t}\n\t\t\t\t\telse \n\t\t\t\t\t{\n                        WebProxy Proxy = new WebProxy(ProxyAddress,true);\n            \n\t\t\t\t\t\tif (ProxyBypass.Length > 0) \n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tProxy.BypassList = ProxyBypass.Split(';');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (ProxyUsername.Length > 0)\n\t\t\t\t\t\t\tProxy.Credentials = new NetworkCredential(ProxyUsername,ProxyPassword);\n\n\t\t\t\t\t\tWebRequest.Proxy = Proxy;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t    if (UseGZip)\n\t\t    {                                \n                // TODO: Check if already set                \n                WebRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, \"gzip,deflate\");\n                WebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;\n\t\t    }\n\n\t\t    // Handle cookies - automatically re-assign \n\t\t\t\tif (HandleCookies || (_Cookies != null && _Cookies.Count > 0)  )\n\t\t\t\t{\n\t\t\t\t\tWebRequest.CookieContainer = new CookieContainer();\n\t\t\t\t\tif (_Cookies != null && _Cookies.Count > 0) \n\t\t\t\t\t{\n\t\t\t\t\t\tWebRequest.CookieContainer.Add(_Cookies);\n\t\t\t\t\t}\n\t\t\t\t}\n\n                HttpTimings.StartRequest();\n\n\t\t\t\t// Deal with the POST buffer if any\n\t\t\t\tif (_PostData != null)\n\t\t\t\t{\n\t\t\t\t    if (WebRequest.Method == \"GET\")\n\t\t\t\t        WebRequest.Method = \"POST\";\n\n\t\t\t\t    switch (PostMode)\n\t\t\t\t    {\n\t\t\t\t        case HttpPostMode.UrlEncoded:\n\t\t\t\t            WebRequest.ContentType = \"application/x-www-form-urlencoded\";\n\t\t\t\t            break;\n\t\t\t\t        case HttpPostMode.MultiPart:\n\t\t\t\t            WebRequest.ContentType = \"multipart/form-data; boundary=\" + _MultiPartBoundary;\n\t\t\t\t            _PostData.Write(Encoding.GetEncoding(1252).GetBytes(\"--\" + _MultiPartBoundary + \"--\\r\\n\"));\n\t\t\t\t            break;\n\t\t\t\t        case HttpPostMode.Xml:\n\t\t\t\t            WebRequest.ContentType = \"text/xml\";\n\t\t\t\t            break;\n\t\t\t\t        case HttpPostMode.Json:\n\t\t\t\t            WebRequest.ContentType = \"application/json\";\n\t\t\t\t            break;\n\t\t\t\t        case HttpPostMode.Raw:\n\t\t\t\t            //WebRequest.ContentType = \"application/octet-stream\";\n\t\t\t\t            break;\n\t\t\t\t        default:\n\t\t\t\t            goto case HttpPostMode.UrlEncoded;\n\t\t\t\t    }\n\n\t\t\t\t    if (!string.IsNullOrEmpty(ContentType))\n                        WebRequest.ContentType = ContentType;\n\n                    using (Stream requestStream = WebRequest.GetRequestStream())\n                    {\n                        if (SendData == null)\n                            _PostStream.WriteTo(requestStream); // Simplest version - no events\n                        else\n                            StreamPostBuffer(requestStream); // Send in chunks and fire events\n\n                        //*** Close the memory stream\n                        _PostStream.Close();\n                        _PostStream = null;\n\n                        //*** Close the Binary Writer\n                        if (_PostData != null)\n                        {\n                            _PostData.Dispose();\n                            _PostData = null;\n                        }\n                    }\n\n\n                    // clear out the Post buffer\n                    ResetPostData();\n\n\t\t\t\t\t// If user cancelled the 'upload' exit\n\t\t\t\t\tif (Cancelled) \n\t\t\t\t\t{\n\t\t\t\t\t\tErrorMessage = \"HTTP Request was cancelled.\";\n\t\t\t\t\t\tError = true;\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\n                if ((WebRequest.Method == \"POST\" || WebRequest.Method == \"PUT\" || WebRequest.Method == \"PATCH\") &&\n                    _PostData == null)\n                {\n                    WebRequest.ContentLength = 0;\n                }\n\n\n            // Retrieve the response headers \n                HttpWebResponse Response;\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tResponse = (HttpWebResponse) WebRequest.GetResponse();                    \n\t\t\t\t}\n\t\t\t\tcatch(WebException ex)\n\t\t\t\t{\n\t\t\t\t\t// Check for 500 error return - if so we still want to return a response\n\t\t\t\t\t// Client can check oHttp.WebResponse.StatusCode\n                    if (ex.Status == WebExceptionStatus.ProtocolError)\n                    {\n                        Response = (HttpWebResponse) ex.Response;\n                    }\n                    else\n                    {\n                        if (ThrowExceptions)\n                            throw;\n\n\t\t\t\t\t\tError = true;\n                        ErrorMessage = ex.GetBaseException().Message + \"  \" + url;\n                        return null;\n                    }\n                }\n\n\t\t\t\tWebResponse = Response;\n                \t\t\t\t\n\t\t\t\t// Close out the request - it cannot be reused            \n\t\t\t\tWebRequest = null;\n\n\t\t\t\t// ** Save cookies the server sends\n\t\t\t\tif (HandleCookies)  \n\t\t\t\t{\n\t\t\t\t\tif (Response.Cookies.Count > 0)  \n\t\t\t\t\t{\n\t\t\t\t\t    if (_Cookies == null)\n\t\t\t\t\t        _Cookies = Response.Cookies;\n\t\t\t\t\t    else\n\t\t\t\t\t    {\n\t\t\t\t\t        // ** If we already have cookies update the list\n\t\t\t\t\t        foreach (Cookie oRespCookie in Response.Cookies)\n\t\t\t\t\t        {\n\t\t\t\t\t            bool bMatch = false;\n\t\t\t\t\t            foreach (Cookie oReqCookie in _Cookies)\n\t\t\t\t\t            {\n\t\t\t\t\t                if (oReqCookie.Name == oRespCookie.Name)\n\t\t\t\t\t                {\n\t\t\t\t\t                    oReqCookie.Value = oRespCookie.Value;\n\t\t\t\t\t                    bMatch = true;\n\t\t\t\t\t                    break; // \n\t\t\t\t\t                }\n\t\t\t\t\t            } // for each ReqCookies\n\t\t\t\t\t            if (!bMatch)\n\t\t\t\t\t                _Cookies.Add(oRespCookie);\n\t\t\t\t\t        }\n\t\t\t\t\t    } \n\t\t\t\t\t} \n\t\t\t\t}  \n\t\t\t\t\n\t\t\t\treturn Response;\n\t\t}\n\n\t\t/// <summary>\n\t\t/// Sends the Postbuffer to the server\n\t\t/// </summary>\n\t\t/// <param name=\"PostData\"></param>\n\t\tprotected void StreamPostBuffer(Stream PostData)\n\t\t{\n\n\t\t\tif (_PostStream.Length < BufferSize)\n\t\t\t{\n\t\t\t\t_PostStream.WriteTo(PostData);\n\n\t\t\t\t// Handle Send Data Even\n\t\t\t\t// Here just let it know we're done\n\t\t\t\tif (SendData != null)\n\t\t\t\t{\n\t\t\t\t\tReceiveDataEventArgs Args = new ReceiveDataEventArgs();\n\t\t\t\t\tArgs.CurrentByteCount = _PostStream.Length;\n\t\t\t\t\tArgs.Done = true;\n\t\t\t\t\tSendData(this, Args);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Send data up in 8k blocks\n\t\t\t\tbyte[] Buffer = _PostStream.GetBuffer();\n\t\t\t\tint lnSent = 0;\n\t\t\t\tint lnToSend = (int)_PostStream.Length;\n\t\t\t\tint lnCurrent = 1;\n\t\t\t\twhile (true)\n\t\t\t\t{\n\t\t\t\t\tif (lnToSend < 1 || lnCurrent < 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (SendData != null)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tReceiveDataEventArgs Args = new ReceiveDataEventArgs();\n\t\t\t\t\t\t\tArgs.CurrentByteCount = lnSent;\n\t\t\t\t\t\t\tArgs.TotalBytes = Buffer.Length;\n\t\t\t\t\t\t\tArgs.Done = true;\n\t\t\t\t\t\t\tSendData(this, Args);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tlnCurrent = lnToSend;\n\n\t\t\t\t\tif (lnCurrent > BufferSize)\n\t\t\t\t\t{\n\t\t\t\t\t\tlnCurrent = BufferSize;\n\t\t\t\t\t\tlnToSend = lnToSend - lnCurrent;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tlnToSend = lnToSend - lnCurrent;\n\t\t\t\t\t}\n\n\t\t\t\t\tPostData.Write(Buffer, lnSent, lnCurrent);\n\n\t\t\t\t\tlnSent = lnSent + lnCurrent;\n\n\t\t\t\t\tif (SendData != null)\n\t\t\t\t\t{\n\t\t\t\t\t\tReceiveDataEventArgs Args = new ReceiveDataEventArgs();\n\t\t\t\t\t\tArgs.CurrentByteCount = lnSent;\n\t\t\t\t\t\tArgs.TotalBytes = Buffer.Length;\n\t\t\t\t\t\tif (Buffer.Length == lnSent)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tArgs.Done = true;\n\t\t\t\t\t\t\tSendData(this, Args);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSendData(this, Args);\n\n\t\t\t\t\t\tif (Args.Cancel)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCancelled = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n        /// <summary>\n        /// Returns the content of a URL as a string\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"bufferSize\">The intermediate download buffer used</param>\n        /// <param name=\"encoding\">A .NET Encoding scheme or null to attempt sniffing from Charset.</param>\n        /// <returns></returns>\n        [Obsolete(\"Use the DownloadString() method instead.\")]\n        public string GetUrl(string url, long bufferSize = 8192, Encoding encoding = null)        \n        {            \n            return DownloadString(url, bufferSize, encoding);\n        }\n\n        /// <summary>\n        /// Returns the content of a URL as a string using a specified Encoding\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"bufferSize\">Internal download buffer size used to hold data chunks.</param>\n        /// <param name=\"encoding\">A .NET Encoding scheme or null to attempt sniffing from Charset.</param>\n        /// <returns></returns>\n        public string DownloadString(string url, long bufferSize = 8192, Encoding encoding = null)\n        {\n            byte[] bytes = DownloadBytes(url, bufferSize);\n            if (bytes == null)\n                return null;\n\n            if (encoding == null)\n            {\n                encoding = Encoding.Default;\n\n                try\n                {\n                    if (!string.IsNullOrEmpty(WebResponse.CharacterSet))\n                    {\n                        string charset = WebResponse.CharacterSet.ToLower();\n\n                        // special case UTF-8 since it's most common\n                        if (charset.Contains(\"utf-8\"))\n                            encoding = Encoding.UTF8;\n                        else if (charset.Contains(\"utf-16\"))\n                            encoding = Encoding.Unicode;\n                        else if (charset.Contains(\"utf-32\"))\n                            encoding = Encoding.UTF32;                        \n                        else\n                            encoding = Encoding.GetEncoding(WebResponse.CharacterSet);\n                    }\n                }\n                catch { } // ignore encoding assignment failures\n            }\n            return encoding.GetString(bytes);\n        }\n        \n        /// <summary>\n        /// Returns the content of a URL as a string using a specified Encoding\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"bufferSize\">Internal download buffer size used to hold data chunks.</param>\n        /// <param name=\"encoding\">A .NET Encoding scheme or null to attempt sniffing from Charset.</param>\n        /// <returns></returns>\n        public async Task<string> DownloadStringAsync(string url, long bufferSize = 8192, Encoding encoding = null)\n        {\n            byte[] bytes = await DownloadBytesAsync(url, bufferSize);\n            if (bytes == null)\n                return null;\n\n            if (encoding == null)\n            {\n                encoding = Encoding.Default;\n\n                try\n                {\n                    if (!string.IsNullOrEmpty(WebResponse.CharacterSet))\n                    {\n                        string charset = WebResponse.CharacterSet.ToLower();\n\n                        // special case UTF-8 since it's most common\n                        if (charset.Contains(\"utf-8\"))\n                            encoding = Encoding.UTF8;\n                        else if (charset.Contains(\"utf-16\"))\n                            encoding = Encoding.Unicode;\n                        else if (charset.Contains(\"utf-32\"))\n                            encoding = Encoding.UTF32;\n                        else\n                            encoding = Encoding.GetEncoding(WebResponse.CharacterSet);\n                    }\n                }\n                catch { } // ignore encoding assignment failures\n            }\n            return encoding.GetString(bytes);\n        }\n        \n        /// <summary>\n        /// Returns a partial response from the URL by specifying only \n        /// given number of bytes to retrieve. This can reduce network\n        /// traffic and keep string formatting down if you are only \n        /// interested a small port at the top of the page. Also \n        /// returns full headers.\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"size\"></param>\n        /// <returns></returns>\n        [Obsolete(\"Use DownloadStringPartial() instead.\")]\n        public string GetUrlPartial(string url, int size)\n        {\n            return GetUrlPartial(url, size);\n        }\n\n\n        /// <summary>\n        /// Returns a partial response from the URL by specifying only \n        /// given number of bytes to retrieve. This can reduce network\n        /// traffic and keep string formatting down if you are only \n        /// interested a small port at the top of the page. Also \n        /// returns full headers.\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"size\"></param>\n        /// <returns></returns>\n        public string DownloadStringPartial(string url, int size)\n        {\n            char[] buffer;\n            using (StreamReader sr = DownloadStream(url))\n            {\n                if (sr == null)\n                    return null;\n\n                buffer = new char[size];\n\n                sr.Read(buffer, 0, size);\n            }\n\n            return new string(buffer);\n        }\n\n     \n\n        /// <summary>\n        /// Retrieves URL into an Byte Array.\n        /// </summary>\n        /// <remarks>Fires the ReceiveData Event</remarks>\n        /// <param name=\"url\">Url to read</param>\n        /// <param name=\"bufferSize\">Size of the buffer for each read. 0 = 8192</param>\n        /// <returns></returns>\n        public byte[] DownloadBytes(string url, long bufferSize = 8192)\n        {         \n\n\t\t\tHttpWebResponse response = DownloadResponse(url);\n            if (response == null)\n                return null;\n\n            long responseSize = bufferSize;\n            if (response.ContentLength > 0)\n                responseSize = WebResponse.ContentLength;\n            else\n                // No content size provided\n                responseSize = -1;\n\n\n            Stream responseStream = responseStream = response.GetResponseStream();\n            \n            //if (response.ContentEncoding.ToLower().Contains(\"gzip\"))\n            //{\n            //    responseStream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress);\n            //    responseSize = -1; // we don't have a size\n            //}\n            //else if (response.ContentEncoding.ToLower().Contains(\"deflate\"))\n            //{\n            //    responseStream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress);\n            //    responseSize = -1; // we don't have a size\n            //}\n            //else\n                \n            if (responseStream == null)\n            {\n                Error = true;\n                ErrorMessage = \"Failed to retrieve a response from \" + url;\n                return null;\n            }\n\n            using (responseStream)\n            {\n                if (bufferSize < 1)\n                    bufferSize = 4096;                \n                \n                var ms = new MemoryStream((int) bufferSize);\n\n                byte[] buffer = new byte[bufferSize];\n\n                var args = new ReceiveDataEventArgs();\n                args.TotalBytes = responseSize;\n\n                long bytesRead = 1;\n                int count = 0;\n                long totalBytes = 0;\n\n                while (bytesRead > 0)\n                {\n                    if (responseSize != -1 && totalBytes + bufferSize > responseSize)\n                        bufferSize = responseSize - totalBytes;\n\n                    \n                    bytesRead = responseStream.Read(buffer, 0, (int) bufferSize);\n\n                    if (bytesRead > 0)\n                    {\n                        if (totalBytes == 0)\n                            HttpTimings.FirstByteTime = DateTime.UtcNow;\n\n                        // write to stream\n                        ms.Write(buffer, 0, (int) bytesRead);\n\n                        count++;\n                        totalBytes += bytesRead;\n\n                        // Raise an event if hooked up\n                        if (ReceiveData != null)\n                        {\n                            // Update the event handler\n                            args.CurrentByteCount = totalBytes;\n                            args.NumberOfReads = count;\n                            args.CurrentChunk = null; // don't send anything here\n                            ReceiveData(this, args);\n\n                            // Check for cancelled flag\n                            if (args.Cancel)\n                            {\n                                Cancelled = true;\n                                break;\n                            }\n                        }\n                    }\n                } // while\n\n                HttpTimings.LastByteTime = DateTime.UtcNow;         \n\n\n                // Send Done notification\n                if (ReceiveData != null && !args.Cancel)\n                {\n                    // Update the event handler\n                    args.Done = true;\n                    ReceiveData(this, args);\n                }\n\n                //ms.Flush();\n                ms.Position = 0;\n                return ms.ToArray();\n            }\n        }\n\n\n        /// <summary>\n        /// Retrieves URL into an Byte Array.\n        /// </summary>\n        /// <remarks>Fires the ReceiveData Event</remarks>\n        /// <param name=\"url\">Url to read</param>\n        /// <param name=\"bufferSize\">Size of the buffer for each read. 0 = 8192</param>\n        /// <returns></returns>\n        public async Task<byte[]> DownloadBytesAsync(string url, long bufferSize = 8192)\n        {\n            HttpWebResponse response = await DownloadResponseAsync(url);\n            if (response == null)\n                return null;\n\n            long responseSize = bufferSize;\n            if (response.ContentLength > 0)\n                responseSize = WebResponse.ContentLength;\n            else\n                // No content size provided\n                responseSize = -1;\n\n            Stream responseStream = response.GetResponseStream();\n            if (responseStream == null)\n            {\n                Error = true;\n                ErrorMessage = \"Failed to retrieve a response from \" + url;\n                return null;\n            }\n\n\t        if (response.ContentEncoding != null)\n\t        {\n\t\t        if(response.ContentEncoding.ToLower().Contains(\"gzip\"))\n\t\t        {\n\t\t\t        responseStream = new GZipStream(responseStream, CompressionMode.Decompress);\n\t\t\t        responseSize = -1; // we don't have a size\n\t\t        }\n\t\t        else if (response.ContentEncoding.ToLower().Contains(\"deflate\"))\n\t\t        {\n\t\t\t        responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);\n\t\t\t        responseSize = -1; // we don't have a size\n\t\t        }\n\t        }\n\n\t        using (responseStream)\n            {\n                if (bufferSize < 1)\n                    bufferSize = 4096;\n\n                var ms = new MemoryStream((int)bufferSize);\n\n                byte[] buffer = new byte[bufferSize];\n\n                var args = new ReceiveDataEventArgs();\n                args.TotalBytes = responseSize;\n\n                long bytesRead = 1;\n                int count = 0;\n                long totalBytes = 0;\n\n                while (bytesRead > 0)\n                {\n                    if (responseSize != -1 && totalBytes + bufferSize > responseSize)\n                        bufferSize = responseSize - totalBytes;\n\n                    bytesRead = await responseStream.ReadAsync(buffer, 0, (int)bufferSize);\n\n                    if (bytesRead > 0)\n                    {\n                        if (totalBytes == 0)\n                            HttpTimings.FirstByteTime = DateTime.UtcNow;\n\n                        // write to stream\n                        ms.Write(buffer, 0, (int)bytesRead);\n\n                        count++;\n                        totalBytes += bytesRead;\n\n                        // Raise an event if hooked up\n                        if (ReceiveData != null)\n                        {\n                            // Update the event handler\n                            args.CurrentByteCount = totalBytes;\n                            args.NumberOfReads = count;\n                            args.CurrentChunk = null; // don't send anything here\n                            ReceiveData(this, args);\n\n                            // Check for cancelled flag\n                            if (args.Cancel)\n                            {\n                                Cancelled = true;\n                                break;\n                            }\n                        }\n                    }\n                } // while\n\n                HttpTimings.LastByteTime = DateTime.UtcNow;\n\n\n                // Send Done notification\n                if (ReceiveData != null && !args.Cancel)\n                {\n                    // Update the event handler\n                    args.Done = true;\n                    ReceiveData(this, args);\n                }\n                \n                ms.Position = 0;\n                var bytes = ms.ToArray();\n                ms.Dispose();\n\n                return bytes;\n            }\n        }\n\n\n\t\t/// <summary>\n\t\t/// Writes the output from the URL request to a file firing events.\n\t\t/// </summary>\n\t\t/// <param name=\"Url\">Url to fire</param>\n\t\t/// <param name=\"BufferSize\">Buffersize - how often to fire events</param>\n\t\t/// <param name=\"OutputFile\">File to write response to</param>\n\t\t/// <returns>true or false</returns>\n        [Obsolete(\"Use DownloadFile() instead.\")]\n        public bool GetUrlFile(string Url, long BufferSize, string OutputFile)\n        {\n            return DownloadFile(Url, BufferSize, OutputFile);\n        }\n\n        /// <summary>\n        /// Writes the output from the URL request to a file firing events.\n        /// </summary>\n        /// <param name=\"url\">Url to fire</param>\n        /// <param name=\"bufferSize\">Buffersize - how often to fire events</param>\n        /// <param name=\"outputFile\">File to write response to</param>\n        /// <returns>true or false</returns>\n        public bool DownloadFile(string url,long bufferSize,string outputFile) \n\t\t{\n\t\t\tbyte[] result = DownloadBytes(url,bufferSize);\n\t\t\tif (result == null)\n\t\t\t\treturn false;\n\n            File.Delete(outputFile);\n            File.WriteAllBytes(outputFile, result);            \n            return File.Exists(outputFile);\n\t\t}\n\n        #endregion\n\n        #region Certificates\n        /// <summary>\n        /// Sets the certificate policy.\n        /// \n        /// Note this is a global setting and affects the entire application.\n        /// It's recommended you set this for the application and not on \n        /// a per request basis.\n        /// </summary>\n        /// <param name=\"Ignore\"></param>\n        public static bool IgnoreCertificateErrors\n        {\n            set\n            {\n\n                if (value)\n#pragma warning disable SYSLIB0014\n                    ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(CheckCertificateCallback);\n#pragma warning restore SYSLIB0014\n                else\n#pragma warning disable SYSLIB0014\n                    ServicePointManager.ServerCertificateValidationCallback -= new RemoteCertificateValidationCallback(CheckCertificateCallback);\n#pragma warning restore SYSLIB0014\n\n            }            \n        }\n\n        /// <summary>\n        /// Handles the Certificate check\n        /// </summary>\n        /// <param name=\"sender\"></param>\n        /// <param name=\"cert\"></param>\n        /// <param name=\"chain\"></param>\n        /// <param name=\"errors\"></param>\n        /// <returns></returns>\n        private static bool CheckCertificateCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)\n        {\n            return true;\n        }\n        #endregion\n\n\n\n        #region Events and Event Delegates and Arguments\n\n        /// <summary>\n        /// Fires progress events when receiving data from the server\n        /// </summary>\n        public event ReceiveDataDelegate ReceiveData;\n\t\tpublic delegate void ReceiveDataDelegate(object sender, ReceiveDataEventArgs e);\n\n\t\t/// <summary>\n\t\t/// Fires progress events when using GetUrlEvents() to retrieve a URL.\n\t\t/// </summary>\n\t\tpublic event ReceiveDataDelegate SendData;\n\t\t\n\t\t/// <summary>\n\t\t/// Event arguments passed to the ReceiveData event handler on each block of data sent\n\t\t/// </summary>\n\t\tpublic class ReceiveDataEventArgs \n\t\t{\n\t\t\t/// <summary>\n\t\t\t/// Size of the cumulative bytes read in this request\n\t\t\t/// </summary>\n\t\t\tpublic long CurrentByteCount=0;\n\n\t\t\t/// <summary>\n\t\t\t/// The number of total bytes of this request\n\t\t\t/// </summary>\n\t\t\tpublic long TotalBytes = 0;\n\n\t\t\t/// <summary>\n\t\t\t/// The number of reads that have occurred - how often has this event been called.\n\t\t\t/// </summary>\n\t\t\tpublic int NumberOfReads = 0;\n\t\t\t\n\t\t\t/// <summary>\n\t\t\t/// The current chunk of data being read\n\t\t\t/// </summary>\n\t\t\tpublic char[] CurrentChunk;\n\t\t\t\n\t\t\t/// <summary>\n\t\t\t/// Flag set if the request is currently done.\n\t\t\t/// </summary>\n\t\t\tpublic bool Done = false;\n\n\t\t\t/// <summary>\n\t\t\t/// Flag to specify that you want the current request to cancel. This is a write-only flag\n\t\t\t/// </summary>\n\t\t\tpublic bool Cancel = false;\n\t\t}\n#endregion\n\n\t    /// <summary>\n\t    /// Releases response and request data\n\t    /// </summary>\n\t    /// <filterpriority>2</filterpriority>\n\t    public void Dispose()\n\t    {\n\t        if (WebResponse != null)\n\t        {\n\t            WebResponse.Dispose();  // introduced in 4.5\n\n\t            WebResponse = null;\n\t        }\n\t        if (WebRequest != null)\n\t            WebRequest = null;\n\t    }\n\t}\n\n\t/// <summary>\n\t/// Enumeration of the various HTTP POST modes supported by HttpClient\n\t/// </summary>\n\t\n\tpublic enum HttpPostMode \n\t{\n\t\tUrlEncoded,\n\t\tMultiPart,\n\t\tXml,\n        Json,\n\t\tRaw\n\t};\n\n#if NETFULL\n\t/// <summary>\n\t/// Internal object used to allow setting WebRequest.CertificatePolicy to \n\t/// not fail on Cert errors\n\t/// </summary>\n\tinternal class AcceptAllCertificatePolicy : ICertificatePolicy\n    {\n        public AcceptAllCertificatePolicy()\n        {\n        }\n\n        public bool CheckValidationResult(ServicePoint sPoint,\n           X509Certificate cert, WebRequest wRequest, int certProb)\n        {\n            // Always accept\n            return true;\n        }\n    }\n#endif\n\n    public class HttpTimings\n    {\n        public DateTime StartedTime { get; set; }\n        public DateTime FirstByteTime { get; set;  }\n        public DateTime LastByteTime { get; set;  }\n\n        public void StartRequest()\n        {\n            StartedTime = DateTime.UtcNow;\n            FirstByteTime = DateTime.UtcNow;\n            LastByteTime = DateTime.UtcNow;\n        }\n\n        public int TimeToFirstByteMs\n        {\n            get { return (int) FirstByteTime.Subtract(StartedTime).TotalMilliseconds; }\n        }\n\n        public int TimeToLastByteMs\n        {\n            get { return (int)LastByteTime.Subtract(StartedTime).TotalMilliseconds; }\n        }\n\n        public bool IsEmpty()\n        {\n            return StartedTime < new DateTime(2010, 1, 1);\n        }\n    }\n\n\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/InternetTools/SmtpClientNative.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Text;\nusing System.Threading;\nusing System.IO;\nusing System.Net.Mail;\nusing System.Net;\nusing System.Security;\nusing System.Collections.Generic;\n\nnamespace Westwind.Utilities.InternetTools\n{\n    /// <summary>\n    /// SMTP Wrapper around System.Net.Email.SmtpClient. Provided \n    /// here mainly to provide compatibility with existing wwSmtp code\n    /// and to provide a slightly more user friendly front end interface\n    /// on a single object.\n    /// </summary>\n    public class SmtpClientNative : IDisposable\n    {\n\n        /// <summary>\n        /// Mail Server to send message through. Should be a domain name \n        /// (mail.yourserver.net) or IP Address (211.123.123.123).\n        /// \n        /// You can also provide a port number as part of the string which will \n        /// override the ServerPort (yourserver.net:211)\n        /// <seealso>Class wwSmtp</seealso>\n        /// </summary>\n        public string MailServer = string.Empty;\n\n        /// <summary>\n        /// Port on the mail server to send through. Defaults to port 25.\n        /// </summary>\n        public int ServerPort = 25;\n\n        /// <summary>\n        /// Use Tls Security\n        /// </summary>\n        public bool UseSsl = false;\n\n        /// <summary>\n        /// Email address or addresses of the Recipient. Comma delimit multiple addresses. To have formatted names use\n        /// \"Rick Strahl\" &lt;rstrahl@west-wind.com&gt;\n        /// </summary>\n        public string Recipient = string.Empty;\n\n        /// <summary>\n        /// Carbon Copy Recipients\n        /// </summary>\n        public string CC = string.Empty;\n\n        /// <summary>\n        /// Blind Copy Recipients\n        /// </summary>\n        public string BCC = string.Empty;\n\n        /// <summary>\n        /// Email address of the sender\n        /// </summary>\n        public string SenderEmail = string.Empty;\n\n        /// <summary>\n        /// Display name of the sender (optional)\n        /// </summary>\n        public string SenderName = String.Empty;\n\n        /// <summary>\n        /// The ReplyTo address\n        /// </summary>\n        public string ReplyTo = String.Empty;\n\n        /// <summary>\n        /// Message Subject.\n        /// </summary>\n        public string Subject = String.Empty;\n\n        /// <summary>\n        /// The body of the message.\n        /// </summary>\n        public string Message = String.Empty;\n\n        /// <summary>\n        /// Username to connect to the mail server.\n        /// </summary>\n        public string Username = String.Empty;\n\n        /// <summary>\n        /// Password to connect to the mail server.\n        /// </summary>\n        public string Password = String.Empty;\n\n        /// <summary>\n        /// Any attachments you'd like to send\n        /// </summary>\n        public string Attachments = String.Empty;\n\n        /// <summary>\n        /// List of attachment objects\n        /// </summary>\n        public List<Attachment> AttachmentList = new List<Attachment>();\n\n        /// <summary>\n        /// The content type of the message. text/plain default or you can set to any other type like text/html\n        /// </summary>\n        public string ContentType = \"text/plain\";\n\n        /// <summary>\n        /// Character Encoding for the message.\n        /// </summary>\n        public string CharacterEncoding = \"8bit\";\n\n        /// <summary>\n        /// The character Encoding used to write the stream out to disk\n        /// Defaults to the default Locale used on the server.\n        /// </summary>\n        public System.Text.Encoding Encoding = Encoding.Default;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public string AlternateText = string.Empty;\n\n        /// <summary>\n        /// The content type for the alternate \n        /// </summary>\n        public string AlternateTextContentType = \"text/plain\";\n\n        /// <summary>\n        /// The user agent for the x-mailer\n        /// </summary>\n        public string UserAgent = \"\";\n\n        /// <summary>\n        /// Determines the priority of the message\n        /// </summary>\n        public string Priority = \"Normal\";\n\n        /// <summary>\n        /// Determines whether a return receipt is sent\n        /// </summary>\n        public bool ReturnReceipt = false;\n\n        /// <summary>\n        /// \n        /// </summary>\n        /// <returns></returns>\n        protected internal List<AlternateView> AlternateViews = new List<AlternateView>();\n\n\n        /// <summary>\n        /// An optional file name that appends logging information for the TCP/IP messaging\n        /// to the specified file.\n        /// </summary>\n        public string LogFile = string.Empty;\n\n        /// <summary>\n        /// Determines whether wwSMTP passes back errors as exceptions or\n        /// whether it sets error properties. Right now only error properties\n        /// work reliably.\n        /// </summary>\n        public bool HandleExceptions = true;\n\n        /// <summary>\n        /// An Error Message if the result is negative or Error is set to true;\n        /// </summary>\n        public string ErrorMessage = string.Empty;\n\n        /// <summary>\n        /// Error Flag set when an error occurs.\n        /// </summary>\n        public bool Error = false;\n\n        /// <summary>\n        /// Connection timeouts for the mail server in seconds. If this timeout is exceeded waiting for a connection\n        /// or for receiving or sending data the request is aborted and fails.\n        /// </summary>\n        public int Timeout = 30;\n\n        /// <summary>\n        /// SMTP headers for this email request\n        /// </summary>\n        public Dictionary<string, string> Headers = new Dictionary<string, string>();\n\n\n        /// <summary>\n        /// Event fired when sending of a message or multiple messages\n        /// is complete and the connection is to be closed. This event\n        /// occurs only once per connection as opposed to the MessageSendComplete\n        /// event which fires after each message is sent regardless of the\n        /// number of SendMessage operations.\n        /// </summary>\n        public event delSmtpNativeEvent SendComplete;\n\n        /// <summary>\n        /// Event fired when an error occurs during processing and before\n        /// the connection is closed down.\n        /// </summary>\n        public event delSmtpNativeEvent SendError;\n\n        /// <summary>\n        /// Internal instance of SmtpClient that holds the 'connection'\n        /// effectively.\n        /// </summary>\n        private SmtpClient smtp = null;\n\n        /// <summary>\n        /// Adds an Smtp header to this email request. Headers are \n        /// always cleared after a message has been sent or failed.\n        /// </summary>\n        /// <param name=\"headerName\"></param>\n        /// <param name=\"value\"></param>\n        public void AddHeader(string headerName, string value)\n        {\n            if (headerName.ToLower() == \"clear\" || headerName.ToLower() == \"reset\")\n                this.Headers.Clear();\n            else\n            {\n                if (!Headers.ContainsKey(headerName))\n                    this.Headers.Add(headerName, value);\n                else\n                    this.Headers[headerName] = value;\n            }\n        }\n\n        /// <summary>\n        /// Adds headers from a CR/LF separate string that has key:value header pairs \n        /// defined.\n        /// </summary>\n        /// <param name=\"headers\"></param>\n        public void AddHeadersFromString(string headers)\n        {\n            string[] lines = headers.Split(new char[2] { '\\n', '\\r' }, StringSplitOptions.RemoveEmptyEntries);\n            foreach (string line in lines)\n            {\n                string[] tokens = line.Split(':');\n                if (tokens.Length != 2)\n                    continue;\n\n                this.AddHeader(tokens[0].Trim(), tokens[1].Trim());\n            }\n        }\n\n        /// <summary>\n        /// Lets you load the actual SMTP client instance\n        /// prior to use so you can manipulate the actual\n        /// Smtp instance.\n        /// </summary>\n        /// <returns></returns>\n        public SmtpClient LoadSmtpClient()\n        {            \n            int serverPort = this.ServerPort;\n            string server = this.MailServer;\n\n            // if there's a port we need to split the address\n            string[] parts = server.Split(':');\n            if (parts.Length > 1)\n            {\n                server = parts[0];\n                serverPort = int.Parse(parts[1]);\n            }\n\n            if (server == null || server == string.Empty)\n            {\n                this.SetError(\"No Mail Server specified.\");\n                this.Headers.Clear();\n                return null;\n            }\n\n            smtp = null;\n            try\n            {\n                smtp = new SmtpClient(server, serverPort);\n\n                if (this.UseSsl)\n                    smtp.EnableSsl = true;\n            }\n            catch (SecurityException)\n            {\n                this.SetError(\"Unable to create SmptClient due to missing permissions. If you are using a port other than 25 for your email server, SmtpPermission has to be explicitly added in Medium Trust.\");\n                this.Headers.Clear();\n                return null;\n            }\n\n            // This is a Total Send Timeout not a Connection timeout!\n            smtp.Timeout = this.Timeout * 1000;\n\n            if (!string.IsNullOrEmpty(this.Username))\n                smtp.Credentials = new NetworkCredential(this.Username, this.Password);\n\n            return smtp;\n        }\n\n        /// <summary>\n        /// Starts a new SMTP session. Note this doesn't actually open a connection\n        /// but just configures and sets up the SMTP session. The actual connection\n        /// is opened only when a message is actually sent\n        /// </summary>\n        /// <returns></returns>\n        public bool Connect()\n        {\n            if (smtp == null)\n                smtp = LoadSmtpClient();\n\n            if (smtp == null)\n                return false;\n\n            return true;\n        }\n\n        /// <summary>\n        /// Cleans up and closes the connection\n        /// </summary>\n        /// <returns></returns>\n        public bool Close()\n        {\n            this.smtp = null;\n\n            // clear all existing headers\n            this.Headers.Clear();\n\n            return true;\n        }\n\n        /// <summary>\n        /// Fully self contained mail sending method. Sends an email message by connecting \n        /// and disconnecting from the email server.\n        /// </summary>\n        /// <returns>true or false</returns>\n        public bool SendMail()\n        {\n            if (!this.Connect())\n                return false;\n\n            try\n            {\n                // Create and configure the message \n                using (MailMessage msg = this.GetMessage())\n                {\n                    smtp.Send(msg);\n\n                    if (this.SendComplete != null)\n                        this.SendComplete(this);\n                }\n            }\n            catch (Exception ex)\n            {\n                string msg = ex.Message;\n                if (ex.InnerException != null)\n                    msg = ex.InnerException.Message;\n\n                this.SetError(msg);\n                if (this.SendError != null)\n                    this.SendError(this);\n\n                return false;\n            }\n            finally\n            {\n                // close connection and clear out headers\n                this.Close();\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Run mail sending operation on a separate thread and asynchronously\n        /// Operation does not return any information about completion.\n        /// </summary>\n        /// <returns></returns>\n        public void SendMailAsync()\n        {\n            //ThreadStart delSendMail = new ThreadStart(this.SendMailRun);\n            //delSendMail.BeginInvoke(null, null);\n\n            Thread mailThread = new Thread(this.SendMailRun);\n            mailThread.Start();\n        }\n\n        protected void SendMailRun()\n        {\n            // Create an extra reference to insure GC doesn't collect\n            // the reference from the caller\n            SmtpClientNative Email = this;\n            Email.SendMail();\n        }\n\n        /// <summary>\n        /// Sends an individual message. Allows sending several messages\n        /// on the same SMTP session without having to reconnect each time.\n        /// \n        /// This version assigns default properties assigned from the main\n        /// mail object and allows overriding only of recipients\n        /// \n        /// Call after Connect() has been called and call Close() to \n        /// close the connection afterwards\n        /// </summary>\n        /// <returns></returns>\n        public bool SendMessage(string recipient, string ccList, string bccList)\n        {\n            try\n            {\n                // Create and configure the message \n                using (MailMessage msg = this.GetMessage())\n                {\n                    this.AssignMailAddresses(msg.To, recipient);\n                    this.AssignMailAddresses(msg.CC, ccList);\n                    this.AssignMailAddresses(msg.Bcc, bccList);\n\n                    smtp.Send(msg);\n                }\n\n                if (this.SendComplete != null)\n                    this.SendComplete(this);\n            }\n            catch (Exception ex)\n            {\n                this.SetError(ex.Message);\n                if (this.SendError != null)\n                    this.SendError(this);\n\n                return false;\n            }\n\n            return true;\n        }\n\n\n\n\n\n        /// <summary>\n        /// Configures the message interface\n        /// </summary>\n        /// <param name=\"msg\"></param>\n        protected virtual MailMessage GetMessage()\n        {\n            MailMessage msg = new MailMessage();\n\n            msg.Body = this.Message;\n            msg.Subject = this.Subject;\n            msg.From = new MailAddress(this.SenderEmail, this.SenderName);\n\n            if (!string.IsNullOrEmpty(this.ReplyTo))\n                msg.ReplyToList.Add(new MailAddress(this.ReplyTo));\n\n            // Send all the different recipients\n            this.AssignMailAddresses(msg.To, this.Recipient);\n            this.AssignMailAddresses(msg.CC, this.CC);\n            this.AssignMailAddresses(msg.Bcc, this.BCC);\n\n            // add string attachments from comma delimited list\n            if (!string.IsNullOrEmpty(this.Attachments))\n            {\n                string[] files = this.Attachments.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);\n                foreach (string file in files)\n                {\n                    msg.Attachments.Add(new Attachment(file));\n                }\n            }\n            // add actual attachment objects\n            foreach(var att in this.AttachmentList)\n            {\n                msg.Attachments.Add(att);\n            }\n\n            if (this.ContentType.StartsWith(\"text/html\"))\n                msg.IsBodyHtml = true;\n            else\n                msg.IsBodyHtml = false;\n\n            msg.BodyEncoding = this.Encoding;\n\n\n            msg.Priority = (MailPriority)Enum.Parse(typeof(MailPriority), this.Priority);\n            if (!string.IsNullOrEmpty(this.ReplyTo))\n                msg.ReplyToList.Add(new MailAddress(this.ReplyTo));\n\n            if (this.ReturnReceipt)\n                msg.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess;\n\n            if (!string.IsNullOrEmpty(this.UserAgent))\n                this.AddHeader(\"x-mailer\", this.UserAgent);\n\n\n            if (!string.IsNullOrEmpty(this.AlternateText))\n            {\n                byte[] alternateBytes = Encoding.Default.GetBytes(this.AlternateText);\n                MemoryStream ms = new MemoryStream(alternateBytes);\n                ms.Position = 0;\n                msg.AlternateViews.Add(new AlternateView(ms));\n                //ms.Close();\n            }\n            if (this.AlternateViews.Count > 0)\n            {\n                foreach (var view in this.AlternateViews)\n                {\n                    msg.AlternateViews.Add(view);\n                }\n            }\n\n            foreach (var header in this.Headers)\n            {\n                msg.Headers[header.Key] = header.Value;\n            }\n\n            return msg;\n        }\n\n\n        /// <summary>\n        /// Assigns mail addresses from a string or comma delimited string list.\n        /// Facilitates \n        /// </summary> \n        /// <param name=\"recipients\"></param>\n        /// <returns></returns>\n        private void AssignMailAddresses(MailAddressCollection address, string recipients)\n        {\n            if (string.IsNullOrEmpty(recipients))\n                return;\n\n            string[] recips = recipients.Split(',', ';');\n\n            for (int x = 0; x < recips.Length; x++)\n            {\n                address.Add(new MailAddress(recips[x]));\n            }\n        }\n\n        /// <summary>\n        /// Strips out just the email address from a full email address that might contain a display name\n        /// in the format of: \"Web Monitor\" &lt;rstrahl@west-wind.com&gt;\n        /// </summary>\n        /// <param name=\"fullEmail\">Full email address to parse. Note currently only &lt; and &gt; tags are recognized as message delimiters</param>\n        /// <returns>only the email address</returns>\n        string GetEmailFromFullAddress(string fullEmail)\n        {\n            if (fullEmail.IndexOf(\"<\") > 0)\n            {\n                int lnIndex = fullEmail.IndexOf(\"<\");\n                int lnIndex2 = fullEmail.IndexOf(\">\");\n                string lcEmail = fullEmail.Substring(lnIndex + 1, lnIndex2 - lnIndex - 1);\n                return lcEmail;\n            }\n\n            return fullEmail;\n        }\n\n        /// <summary>\n        /// Adds a new Alternate view to the request. Passed from FoxPro\n        /// which sets up this object.\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"contentType\"></param>\n        /// <param name=\"contentId\"></param>\n        public void AddAlternateView(AlternateView view)\n        {\n            this.AlternateViews.Add(view);\n        }\n\n\n\n        /// <summary>\n        /// Logs a message to the specified LogFile\n        /// </summary>\n        /// <param name=\"FormatString\"></param>\n        /// <param name=\"?\"></param>\n        protected void LogString(string message)\n        {\n            if (string.IsNullOrEmpty(this.LogFile))\n                return;\n\n            if (!message.EndsWith(\"\\r\\n\"))\n                message += \"\\r\\n\";\n\n            using (StreamWriter sw = new StreamWriter(this.LogFile, true))\n            {\n                sw.Write(message);\n            }\n        }\n\n\n        /// <summary>\n        /// Internally used to set errors\n        /// </summary>\n        /// <param name=\"errorMessage\"></param>\n        private void SetError(string errorMessage)\n        {\n            if (errorMessage == null || errorMessage.Length == 0)\n            {\n                this.ErrorMessage = string.Empty;\n                this.Error = false;\n                return;\n            }\n\n            ErrorMessage = errorMessage;\n            Error = true;\n        }\n\n\n        #region IDisposable Members\n\n        public void Dispose()\n        {\n            if (this.smtp != null)\n                this.smtp = null;\n\n        }\n\n        #endregion\n    }\n\n    /// <summary>\n    /// Delegate used to handle Completion and failure events\n    /// </summary>\n    /// <param name=\"Smtp\"></param>\n    public delegate void delSmtpNativeEvent(SmtpClientNative Smtp);\n\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/LICENSE.MD",
    "content": "West Wind Utilities Library\n===========================\n\nMIT License\n-----------\nCopyright (c) 2019-2023 West Wind Technologies\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Westwind.Utilities/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace Westwind.Utilities.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"17.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    public class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"Westwind.Utilities.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to A connection string must be passed to the constructor.\n        /// </summary>\n        public static string AConnectionStringMustBePassedToTheConstructor {\n            get {\n                return ResourceManager.GetString(\"AConnectionStringMustBePassedToTheConstructor\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to An error occurred in your application.\n        /// </summary>\n        public static string An_error_occurred_in_your_Application {\n            get {\n                return ResourceManager.GetString(\"An_error_occurred_in_your_Application\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Binary XML Serialization is not supported in .NET Core.\n        /// </summary>\n        public static string BinaryXmlSerializationNotSupported {\n            get {\n                return ResourceManager.GetString(\"BinaryXmlSerializationNotSupported\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Byte value greater than 20mb are not supported.\n        /// </summary>\n        public static string ByteValueGreaterThan20megsAreNotSupported {\n            get {\n                return ResourceManager.GetString(\"ByteValueGreaterThan20megsAreNotSupported\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Configuration Initialization Error.\n        /// </summary>\n        public static string ConfigurationInitializationError {\n            get {\n                return ResourceManager.GetString(\"ConfigurationInitializationError\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Configuration method no longer supported.\n        /// </summary>\n        public static string ConfigurationMethodNoLongerSupported {\n            get {\n                return ResourceManager.GetString(\"ConfigurationMethodNoLongerSupported\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connection opening failure: {0}.\n        /// </summary>\n        public static string ConnectionOpeningFailure {\n            get {\n                return ResourceManager.GetString(\"ConnectionOpeningFailure\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Couldn&apos;t create user token.\n        /// </summary>\n        public static string CouldntCreateUserToken {\n            get {\n                return ResourceManager.GetString(\"CouldntCreateUserToken\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Couldn&apos;t load entity. Invalid key provided..\n        /// </summary>\n        public static string CouldntLoadEntityInvalidKeyProvided {\n            get {\n                return ResourceManager.GetString(\"CouldntLoadEntityInvalidKeyProvided\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Data reader passed to object failure..\n        /// </summary>\n        public static string DataReaderPassedToDataReaderToObjectCannot {\n            get {\n                return ResourceManager.GetString(\"DataReaderPassedToDataReaderToObjectCannot\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Error:.\n        /// </summary>\n        public static string ErrorColon {\n            get {\n                return ResourceManager.GetString(\"ErrorColon\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid Connection String.\n        /// </summary>\n        public static string InvalidConnectionString {\n            get {\n                return ResourceManager.GetString(\"InvalidConnectionString\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid connection string name..\n        /// </summary>\n        public static string InvalidConnectionStringName {\n            get {\n                return ResourceManager.GetString(\"InvalidConnectionStringName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid encryption property name.\n        /// </summary>\n        public static string InvalidEncryptionPropertyName {\n            get {\n                return ResourceManager.GetString(\"InvalidEncryptionPropertyName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid hex digit.\n        /// </summary>\n        public static string InvalidHexDigit {\n            get {\n                return ResourceManager.GetString(\"InvalidHexDigit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid hex string length..\n        /// </summary>\n        public static string InvalidHexStringLength {\n            get {\n                return ResourceManager.GetString(\"InvalidHexStringLength\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid type for Xml type to .NET object conversion..\n        /// </summary>\n        public static string InvalidTypeForXmlTypeToNETTypeConversion {\n            get {\n                return ResourceManager.GetString(\"InvalidTypeForXmlTypeToNETTypeConversion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid token identifier - token must be at least 8 characters.\n        /// </summary>\n        public static string InvalidUserTokenIdentifier {\n            get {\n                return ResourceManager.GetString(\"InvalidUserTokenIdentifier\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid Xml configuration file format..\n        /// </summary>\n        public static string InvalidXMLConfigurationFileFormat {\n            get {\n                return ResourceManager.GetString(\"InvalidXMLConfigurationFileFormat\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to JSON.NET library not available..\n        /// </summary>\n        public static string JSON_NET_library_not_avaiable {\n            get {\n                return ResourceManager.GetString(\"JSON_NET_library_not_avaiable\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Missing encryption key..\n        /// </summary>\n        public static string MissingEncryptionKey {\n            get {\n                return ResourceManager.GetString(\"MissingEncryptionKey\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Missing or invalid token identifier..\n        /// </summary>\n        public static string MissingOrInvalidTokenIdentifier {\n            get {\n                return ResourceManager.GetString(\"MissingOrInvalidTokenIdentifier\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No active transaction to commit..\n        /// </summary>\n        public static string NoActiveTransactionToCommit {\n            get {\n                return ResourceManager.GetString(\"NoActiveTransactionToCommit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Object could not be deserialized from Xml.\n        /// </summary>\n        public static string ObjectCouldNotBeDeserializedFromXml {\n            get {\n                return ResourceManager.GetString(\"ObjectCouldNotBeDeserializedFromXml\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to SqlServer Compact Data Provider not supported on .NET Core..\n        /// </summary>\n        public static string SqlServerCompactDataProviderNotSupportedOnNetCore {\n            get {\n                return ResourceManager.GetString(\"SqlServerCompactDataProviderNotSupportedOnNetCore\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to String to typed value type conversion failed..\n        /// </summary>\n        public static string StringToTypedValueValueTypeConversionFailed {\n            get {\n                return ResourceManager.GetString(\"StringToTypedValueValueTypeConversionFailed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Today.\n        /// </summary>\n        public static string Today {\n            get {\n                return ResourceManager.GetString(\"Today\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unable to extract config data from string.\n        /// </summary>\n        public static string UnableToExtractKeys {\n            get {\n                return ResourceManager.GetString(\"UnableToExtractKeys\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unable to read configuration data from string..\n        /// </summary>\n        public static string UnableToReadConfigDataFromString {\n            get {\n                return ResourceManager.GetString(\"UnableToReadConfigDataFromString\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unable to retrieve DbProvider Factory Form.\n        /// </summary>\n        public static string UnableToRetrieveDbProviderFactoryForm {\n            get {\n                return ResourceManager.GetString(\"UnableToRetrieveDbProviderFactoryForm\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unsupported Provider Factory.\n        /// </summary>\n        public static string UnsupportedProviderFactory {\n            get {\n                return ResourceManager.GetString(\"UnsupportedProviderFactory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Token has expired..\n        /// </summary>\n        public static string UserTokenHasExpired {\n            get {\n                return ResourceManager.GetString(\"UserTokenHasExpired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Token not found..\n        /// </summary>\n        public static string UserTokenNotFound {\n            get {\n                return ResourceManager.GetString(\"UserTokenNotFound\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Yesterday.\n        /// </summary>\n        public static string Yesterday {\n            get {\n                return ResourceManager.GetString(\"Yesterday\", resourceCulture);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"UnableToReadConfigDataFromString\" xml:space=\"preserve\">\n    <value>Unable to read configuration data from string.</value>\n  </data>\n  <data name=\"UnableToExtractKeys\" xml:space=\"preserve\">\n    <value>Unable to extract config data from string</value>\n  </data>\n  <data name=\"ConfigurationMethodNoLongerSupported\" xml:space=\"preserve\">\n    <value>Configuration method no longer supported</value>\n  </data>\n  <data name=\"StringToTypedValueValueTypeConversionFailed\" xml:space=\"preserve\">\n    <value>String to typed value type conversion failed.</value>\n  </data>\n  <data name=\"InvalidTypeForXmlTypeToNETTypeConversion\" xml:space=\"preserve\">\n    <value>Invalid type for Xml type to .NET object conversion.</value>\n  </data>\n  <data name=\"DataReaderPassedToDataReaderToObjectCannot\" xml:space=\"preserve\">\n    <value>Data reader passed to object failure.</value>\n  </data>\n  <data name=\"ByteValueGreaterThan20megsAreNotSupported\" xml:space=\"preserve\">\n    <value>Byte value greater than 20mb are not supported</value>\n  </data>\n  <data name=\"AConnectionStringMustBePassedToTheConstructor\" xml:space=\"preserve\">\n    <value>A connection string must be passed to the constructor</value>\n  </data>\n  <data name=\"InvalidConnectionStringName\" xml:space=\"preserve\">\n    <value>Invalid connection string name.</value>\n  </data>\n  <data name=\"InvalidHexStringLength\" xml:space=\"preserve\">\n    <value>Invalid hex string length.</value>\n  </data>\n  <data name=\"InvalidHexDigit\" xml:space=\"preserve\">\n    <value>Invalid hex digit</value>\n  </data>\n  <data name=\"InvalidConnectionString\" xml:space=\"preserve\">\n    <value>Invalid Connection String</value>\n  </data>\n  <data name=\"ObjectCouldNotBeDeserializedFromXml\" xml:space=\"preserve\">\n    <value>Object could not be deserialized from Xml</value>\n  </data>\n  <data name=\"An_error_occurred_in_your_Application\" xml:space=\"preserve\">\n    <value>An error occurred in your application</value>\n  </data>\n  <data name=\"ErrorColon\" xml:space=\"preserve\">\n    <value>Error:</value>\n  </data>\n  <data name=\"CouldntLoadEntityInvalidKeyProvided\" xml:space=\"preserve\">\n    <value>Couldn't load entity. Invalid key provided.</value>\n  </data>\n  <data name=\"InvalidEncryptionPropertyName\" xml:space=\"preserve\">\n    <value>Invalid encryption property name</value>\n    <comment>.</comment>\n  </data>\n  <data name=\"InvalidXMLConfigurationFileFormat\" xml:space=\"preserve\">\n    <value>Invalid Xml configuration file format.</value>\n  </data>\n  <data name=\"JSON_NET_library_not_avaiable\" xml:space=\"preserve\">\n    <value>JSON.NET library not available.</value>\n  </data>\n  <data name=\"NoActiveTransactionToCommit\" xml:space=\"preserve\">\n    <value>No active transaction to commit.</value>\n  </data>\n  <data name=\"MissingEncryptionKey\" xml:space=\"preserve\">\n    <value>Missing encryption key.</value>\n  </data>\n  <data name=\"ConnectionOpeningFailure\" xml:space=\"preserve\">\n    <value>Connection opening failure: {0}</value>\n  </data>\n  <data name=\"SqlServerCompactDataProviderNotSupportedOnNetCore\" xml:space=\"preserve\">\n    <value>SqlServer Compact Data Provider not supported on .NET Core.</value>\n  </data>\n  <data name=\"UnableToRetrieveDbProviderFactoryForm\" xml:space=\"preserve\">\n    <value>Unable to retrieve DbProvider Factory Form</value>\n  </data>\n  <data name=\"UnsupportedProviderFactory\" xml:space=\"preserve\">\n    <value>Unsupported Provider Factory</value>\n  </data>\n  <data name=\"Today\" xml:space=\"preserve\">\n    <value>Today</value>\n  </data>\n  <data name=\"Yesterday\" xml:space=\"preserve\">\n    <value>Yesterday</value>\n  </data>\n  <data name=\"CouldntCreateUserToken\" xml:space=\"preserve\">\n    <value>Couldn't create user token</value>\n  </data>\n  <data name=\"InvalidUserTokenIdentifier\" xml:space=\"preserve\">\n    <value>Invalid token identifier - token must be at least 8 characters</value>\n  </data>\n  <data name=\"MissingOrInvalidTokenIdentifier\" xml:space=\"preserve\">\n    <value>Missing or invalid token identifier.</value>\n  </data>\n  <data name=\"UserTokenHasExpired\" xml:space=\"preserve\">\n    <value>Token has expired.</value>\n  </data>\n  <data name=\"UserTokenNotFound\" xml:space=\"preserve\">\n    <value>Token not found.</value>\n  </data>\n  <data name=\"ConfigurationInitializationError\" xml:space=\"preserve\">\n    <value>Configuration Initialization Error</value>\n  </data>\n  <data name=\"BinaryXmlSerializationNotSupported\" xml:space=\"preserve\">\n    <value>Binary XML Serialization is not supported in .NET Core</value>\n  </data>\n</root>"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/DelegateFactory.cs",
    "content": "using System;\nusing System.Reflection;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\n\nnamespace Westwind.Utilities\n{\n\n\n    /// <summary>\n    /// This class creates a generic method delegate from a MethodInfo signature\n    /// converting the method call into a LateBoundMethod delegate call. Using\n    /// this class allows making repeated calls very quickly.\n    /// \n    /// Note: this class will be very inefficient for individual dynamic method\n    /// calls - compilation of the expression is very expensive up front, so using\n    /// this delegate factory makes sense only if you re-use the dynamicly loaded\n    /// method repeatedly.\n    /// \n    /// Entirely based on Nate Kohari's blog post:\n    /// http://kohari.org/2009/03/06/fast-late-bound-invocation-with-expression-trees/\n    /// </summary>\n    public static class DelegateFactory\n    {\n\n        /// <summary>\n        /// Creates a LateBoundMethod delegate from a MethodInfo structure\n        /// Basically creates a dynamic delegate instance (code) on the fly.\n        /// </summary>\n        /// <param name=\"method\"></param>\n        /// <returns></returns>\n        public static LateBoundMethod Create(MethodInfo method)\n        {\n            ParameterExpression instanceParameter = Expression.Parameter(typeof(object), \"target\");\n            ParameterExpression argumentsParameter = Expression.Parameter(typeof(object[]), \"arguments\");\n\n            MethodCallExpression call = Expression.Call(\n                Expression.Convert(instanceParameter, method.DeclaringType),\n                method,\n                CreateParameterExpressions(method, argumentsParameter));\n\n            Expression<LateBoundMethod> lambda = Expression.Lambda<LateBoundMethod>(\n                Expression.Convert(call, typeof(object)),\n                instanceParameter,\n                argumentsParameter);\n\n            return lambda.Compile();\n        }\n\n        private static Expression[] CreateParameterExpressions(MethodInfo method, Expression argumentsParameter)\n        {\n            return method.GetParameters().Select((parameter, index) =>\n                Expression.Convert(\n                    Expression.ArrayIndex(argumentsParameter, Expression.Constant(index)),\n                    parameter.ParameterType)).ToArray();\n        }\n\n\n        /// <summary>\n        /// Creates a LateBoundMethod from type methodname and parameter signature that\n        /// is turned into a MethodInfo structure and then parsed into a dynamic delegate\n        /// </summary>\n        /// <param name=\"type\"></param>\n        /// <param name=\"methodName\"></param>\n        /// <param name=\"parameterTypes\"></param>\n        /// <returns></returns>\n        public static LateBoundMethod Create(Type type, string methodName, params Type[] parameterTypes)\n        {\n            return Create(type.GetMethod(methodName, parameterTypes));\n        }\n\n    }\n\n\n    /// <summary>\n    /// LateBoundMethod is a generic method signature that is passed an instance\n    /// and an array of parameters and returns an object. It basically can be \n    /// used to call any method.\n    /// \n    /// </summary>\n    /// <param name=\"target\">The instance that the dynamic method is called on</param>\n    /// <param name=\"arguments\"></param>\n    /// <returns></returns>\n    public delegate object LateBoundMethod(object target, object[] arguments);\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/Encryption.cs",
    "content": "using System;\nusing System.IO;\nusing System.IO.Compression;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Security;\nusing System.Security.Cryptography;\nusing System.Text;\nusing Westwind.Utilities.Properties;\n\n\nnamespace Westwind.Utilities\n{\n\t/// <summary>\n\t/// Class that provides a number of encryption helper utilities to \n    /// make it easier to create hashes and two-way encryption, create\n    /// checksums and more.\n\t/// </summary>\n\t/// <remarks>\n\t/// For best compatibility across platforms of the Encrypt/Decrypt methods use \n\t/// overloads that use the key as a byte[24] value rather than string or other sized buffers.\n\t/// </remarks>\n\tpublic static class Encryption\n    {\n        /// <summary>\n        /// Replace this value with some unique key of your own\n        /// Best set this in your App start up in a Static constructor\n        /// </summary>\n        public static string EncryptionKey = \"41a3f131dd91\";\n\n\t\t/// <summary>\n\t\t/// Global configuration property that can be overridden to\n\t\t/// set the key size used for Encrypt/Decrypt operations.\n\t\t/// Choose between 16 bytes (not recommended except for\n\t\t/// backwards compatibility) or 24 bytes (works both in\n\t\t/// NET Full and NET Core)\n\t\t/// </summary>\n#if NETFRAMEWORK\n\t\tpublic static int EncryptionKeySize = 16;  // set for full framework compatibility with previous version\n#else\n\t    public static int EncryptionKeySize = 24;  // this is the default for .NET Core (doesn't support 16)\n#endif\t\n\n\t\t#region Two-way Encryption\n\n\t\t/// <summary>\n\t\t/// Encodes a stream of bytes using DES encryption with a pass key. Lowest level method that \n\t\t/// handles all work.\n\t\t/// </summary>\n\t\t/// <param name=\"inputBytes\"></param>\n\t\t/// <param name=\"encryptionKey\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static byte[] EncryptBytes(byte[] inputBytes, string encryptionKey)\n        {\n            if (encryptionKey == null)\n                encryptionKey = Encryption.EncryptionKey;\n\n            return EncryptBytes(inputBytes, Encoding.UTF8.GetBytes(encryptionKey));\n        }\n\n\n        /// <summary>\n        /// Encodes a stream of bytes using DES encryption with a pass key. Lowest level method that \n        /// handles all work.\n        /// </summary>\n        /// <param name=\"inputBytes\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <returns></returns>\n        public static byte[] EncryptBytes(byte[] inputBytes, SecureString encryptionKey)\n        {\n            if (encryptionKey == null)\n                throw new ArgumentException(Resources.MissingEncryptionKey);\n\n            return EncryptBytes(inputBytes, Encoding.UTF8.GetBytes(encryptionKey.GetString()));\n        }\n\n\n        /// <summary>\n        /// Encrypts a byte buffer with a byte encryption key.\n        /// </summary>\n        /// <param name=\"inputBytes\">Bytes to convert</param>\n        /// <param name=\"encryptionKey\">The key bytes used to encode the data. Use a 24 byte key for best compatibility</param>\n\t\t/// <param name=\"cipherMode\">Optional CipherMode used. Defaults to older ECB for backwards compatibility</param>\n        /// <returns></returns>\n\t\tpublic static byte[] EncryptBytes(byte[] inputBytes, byte[] encryptionKey, CipherMode cipherMode = CipherMode.ECB)\n\t\t{\n            using (var des = TripleDES.Create()) //new TripleDESCryptoServiceProvider();\n            {\n                des.Mode = cipherMode;\n\n                if (EncryptionKeySize == 16)\n                {\n                    using (var hash = MD5.Create())\n                    {\n                        des.Key = hash.ComputeHash(encryptionKey);\n                    }\n                }\n                else\n                {\n                    using (var hash =   SHA256.Create())\n                    {\n                        des.Key = hash.ComputeHash(encryptionKey)\n                            .Take(EncryptionKeySize)\n                            .ToArray();\n                    }\n                }\n\t\t\n                var transform = des.CreateEncryptor();\n                byte[] buffer = inputBytes;\n                return transform.TransformFinalBlock(buffer, 0, buffer.Length);\n            }\n\t\t}\n\n        \n        /// <summary>\n        /// Encrypts a string into bytes using DES encryption with a Passkey. \n        /// </summary>\n        /// <param name=\"inputString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <returns></returns>\n        public static byte[] EncryptBytes(string inputString, string encryptionKey)\n        {\n            return EncryptBytes(Encoding.UTF8.GetBytes(inputString), encryptionKey);\n        }\n\n        /// <summary>\n        /// Encrypts a string using Triple DES encryption with a two way encryption key.String is returned as Base64 or BinHex\n        /// encoded value rather than binary.\n        /// </summary>\n        /// <param name=\"inputString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">if true returns bin hex rather than base64</param>\n        /// <returns></returns>\n        public static string EncryptString(string inputString, byte[] encryptionKey, bool useBinHex = false)\n        {\n            byte[] bytes = Encoding.UTF8.GetBytes(inputString);\n\n            if (useBinHex)\n                return BinaryToBinHex(EncryptBytes(bytes, encryptionKey));\n\n            return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey));\n        }\n\n        /// <summary>\n        /// Encrypts a string using Triple DES encryption with a two way encryption key.\n\t\t/// String is returned as Base64 or BinHex encoded string.\n        /// </summary>\n        /// <param name=\"inputString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\"></param>\n        /// <returns></returns>\n        public static string EncryptString(string inputString, string encryptionKey, bool useBinHex = false)\n        {\n            byte[] bytes = Encoding.UTF8.GetBytes(inputString);\n\n            if (useBinHex)\n                return BinaryToBinHex(EncryptBytes(bytes, encryptionKey));\n\n            return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey));\n        }\n\n\n        /// <summary>\n        /// Encrypts a string using Triple DES encryption with a two way encryption key.\n        /// String is returned as Base64 or BinHex encoded string.\n        /// </summary>\n        /// <param name=\"inputString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\"></param>\n        /// <returns></returns>\n        public static string EncryptString(string inputString,SecureString encryptionKey, bool useBinHex = false)\n        {\n            if (encryptionKey == null)\n                throw new ArgumentException(Resources.MissingEncryptionKey);\n\n            byte[] bytes = Encoding.UTF8.GetBytes(inputString);\n\n            if (useBinHex)\n                return BinaryToBinHex(EncryptBytes(bytes, encryptionKey.GetString()));\n\n            return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey.GetString()));\n        }\n\n\n        /// <summary>\n        /// Decrypts a Byte array from DES with an Encryption Key.\n        /// </summary>\n        /// <param name=\"decryptBuffer\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <returns></returns>\n        public static byte[] DecryptBytes(byte[] decryptBuffer, string encryptionKey)\n        {\n            if (decryptBuffer == null || decryptBuffer.Length == 0)\n                return null;\n\n            if (encryptionKey == null)\n                encryptionKey = Encryption.EncryptionKey;\n\n            return DecryptBytes(decryptBuffer, Encoding.UTF8.GetBytes(encryptionKey));            \n        }\n\n\n\n        /// <summary>\n        /// Decrypts a Byte array from DES with an Encryption Key.\n        /// </summary>\n        /// <param name=\"decryptBuffer\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <returns></returns>\n        public static byte[] DecryptBytes(byte[] decryptBuffer, SecureString encryptionKey)\n        {\n            if (decryptBuffer == null || decryptBuffer.Length == 0)\n                return null;\n\n            return DecryptBytes(decryptBuffer, Encoding.UTF8.GetBytes(encryptionKey.GetString()));\n        }\n\n\n\n        /// <summary>\n        /// Decrypts a byte buffer with a byte based encryption key\n        /// </summary>\n        /// <param name=\"decryptBuffer\">Data to encrypt</param>\n        /// <param name=\"encryptionKey\">The key bytes used to encode the data. Use a 24 byte key for best compatibility</param>\n        /// <param name=\"cipherMode\">Optional CipherMode used. Defaults to older ECB for backwards compatibility</param>\n        /// <returns></returns>\n        public static byte[] DecryptBytes(byte[] decryptBuffer, byte[] encryptionKey, CipherMode cipherMode = CipherMode.ECB)\n        {\n            if (decryptBuffer == null || decryptBuffer.Length == 0)\n                return null;\n\n            using (var des = TripleDES.Create())\n            {\n                des.Mode = cipherMode;\n\t\t\t\n                if (EncryptionKeySize == 16)\n                {\n                    using (var hash = MD5.Create())\n                    {\n                        des.Key = hash.ComputeHash(encryptionKey);\n                    }\n                }\n                else\n                {\n                    using (var hash = SHA256.Create())\n                    {\n                        des.Key = hash.ComputeHash(encryptionKey)\n                            .Take(EncryptionKeySize)\n                            .ToArray();\n                    }\n                }\n\n                var transform = des.CreateDecryptor();\n                return transform.TransformFinalBlock(decryptBuffer, 0, decryptBuffer.Length);\n            }\n\n           \n        }\n\n        /// <summary>\n        /// Decrypts a string using DES encryption and a pass key that was used for \n        /// encryption and returns a byte buffer.    \n        /// </summary>\n        /// <param name=\"decryptString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned.</param>\n        /// <returns>String</returns>\n        public static byte[] DecryptBytes(string decryptString, string encryptionKey, bool useBinHex = false)\n        {\n            if (useBinHex)\n                return DecryptBytes(BinHexToBinary(decryptString), encryptionKey);\n\n            return DecryptBytes(Convert.FromBase64String(decryptString), encryptionKey);\n        }\n\n\n        /// <summary>\n        /// Decrypts a string using DES encryption and a pass key that was used for \n        /// encryption and returns a byte buffer.    \n        /// </summary>\n        /// <param name=\"decryptString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned.</param>\n        /// <returns>String</returns>\n        public static byte[] DecryptBytes(string decryptString, SecureString encryptionKey, bool useBinHex = false)\n        {\n            if (encryptionKey == null)\n                throw new ArgumentException(Resources.MissingEncryptionKey);\n\n            if (useBinHex)\n                return DecryptBytes(BinHexToBinary(decryptString), encryptionKey.GetString());\n\n            return DecryptBytes(Convert.FromBase64String(decryptString), encryptionKey.GetString());\n        }\n\n        /// <summary>\n        /// Decrypts a string using DES encryption and a pass key that was used for \n        /// encryption.\n        /// <seealso>Class wwEncrypt</seealso>\n        /// </summary>\n        /// <param name=\"decryptString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned.</param>\n        /// <returns>String</returns>\n        public static string DecryptString(string decryptString, string encryptionKey,  bool useBinHex = false)\n        {\n            try\n            {\n                var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString);\n\n                byte[] decrypted = DecryptBytes(data, encryptionKey);\n                return Encoding.UTF8.GetString(decrypted);                \n            }\n            catch\n            {\n                // undecryptable data\n                return string.Empty;\n            }\n        }\n\n        /// <summary>\n        /// Decrypts a string using DES encryption and a pass key that was used for \n        /// encryption.\n        /// <seealso>Class wwEncrypt</seealso>\n        /// </summary>\n        /// <param name=\"decryptString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned.</param>\n        /// <returns>String</returns>\n        public static string DecryptString(string decryptString, SecureString encryptionKey, bool useBinHex = false)\n        {\n            if (encryptionKey == null)\n                throw new ArgumentException(Resources.MissingEncryptionKey);\n\n            var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString);\n\n            try\n            {\n                byte[] decrypted = DecryptBytes(data, encryptionKey.GetString());\n                return Encoding.UTF8.GetString(decrypted);\n            }\n            catch\n            {\n                return string.Empty;\n            }\n        }\n\n        /// <summary>\n        /// Decrypts a string using DES encryption and a pass key that was used for \n        /// encryption.\n        /// <seealso>Class wwEncrypt</seealso>\n        /// </summary>\n        /// <param name=\"decryptString\"></param>\n        /// <param name=\"encryptionKey\"></param>\n        /// <param name=\"useBinHex\">Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned</param>\n        /// <returns>String</returns>\n        public static string DecryptString(string decryptString, byte[] encryptionKey, bool useBinHex = false)\n        {\n            var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString);\n\n            try\n            {\n                byte[] decrypted = DecryptBytes(data, encryptionKey);\n                return Encoding.UTF8.GetString(decrypted);\n            }\n            catch\n            {\n                return string.Empty;\n            }\n        }\n\n\n      \n#if NETFULL\n\t\t/// <summary>\n\t\t/// Encrypt bytes using the Data Protection API on Windows. This API\n\t\t/// uses internal keys to encrypt data which is valid for decryption only\n\t\t/// on the same machine.        \n\t\t/// \n\t\t/// This is an idea storage mechanism for application registraions, \n\t\t/// service passwords and other semi-transient data that is specific\n\t\t/// to the software used on the current machine\n\t\t/// </summary>\n\t\t/// <remarks>\n\t\t/// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n\t\t/// </remarks>\n\t\t/// <param name=\"encryptBytes\"></param>\n\t\t/// <param name=\"key\"></param>\n\t\t/// <param name=\"scope\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static byte[] ProtectBytes(byte[] encryptBytes, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine)\n        {\n            return ProtectedData.Protect(encryptBytes,key,scope);            \n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptBytes\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>   \n        /// <returns></returns>\n        public static byte[] ProtectBytes(byte[] encryptBytes, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine)\n        {\n            return ProtectedData.Protect(encryptBytes, Encoding.UTF8.GetBytes(key),scope);\n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptString\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>\n        /// <param name=\"useBinHex\">returns bin hex data when set (010A0D10AF)</param>\n        /// <returns></returns>\n        public static string ProtectString(string encryptString, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false)\n        {\n            var encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(encryptString), Encoding.UTF8.GetBytes(key), scope);\n\n            if (useBinHex)\n                return BinaryToBinHex(encryptedBytes);\n\n            return Convert.ToBase64String(encryptedBytes);\n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptString\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>\n        /// <param name=\"useBinHex\">returns bin hex data when set (010A0D10AF)</param>\n        /// <returns></returns>\n        public static string ProtectString(string encryptString, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false)\n        {\n            var encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(encryptString), key, scope);\n\n            if (useBinHex)\n                return BinaryToBinHex(encryptedBytes);\n\n            return Convert.ToBase64String(encryptedBytes);\n        }\n\n        /// <summary>\n        /// Decrypts bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptBytes\"></param>\n        /// <param name=\"key\"></param>\n        /// <param name=\"scope\"></param>\n        /// <returns></returns>\n        public static byte[] UnprotectBytes(byte[] encryptBytes, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine)\n        {\n            return ProtectedData.Unprotect(encryptBytes, key, scope);\n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptBytes\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>   \n        /// <returns></returns>\n        public static byte[] UnprotectBytes(byte[] encryptBytes, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine)\n        {\n            return ProtectedData.Unprotect(encryptBytes, Encoding.UTF8.GetBytes(key), scope);\n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptString\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>\n        /// <param name=\"useBinHex\">returns bin hex data when set (010A0D10AF)</param>\n        /// <returns></returns>\n        public static string UnprotectString(string encryptString, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false)\n        {\n            byte[] buffer;\n            if (useBinHex)\n                buffer = BinHexToBinary(encryptString);\n            else\n                buffer = Convert.FromBase64String(encryptString);\n\n            buffer = ProtectedData.Unprotect(buffer, Encoding.UTF8.GetBytes(key), scope);\n\n            return Encoding.UTF8.GetString(buffer);\n        }\n\n        /// <summary>\n        /// Encrypt bytes using the Data Protection API on Windows. This API\n        /// uses internal keys to encrypt data which is valid for decryption only\n        /// on the same machine.        \n        /// \n        /// This is an idea storage mechanism for application registraions, \n        /// service passwords and other semi-transient data that is specific\n        /// to the software used on the current machine\n        /// </summary>\n        /// <remarks>\n        /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES\n        /// </remarks>\n        /// <param name=\"encryptString\"></param>\n        /// <param name=\"key\"></param>     \n        /// <param name=\"scope\"></param>\n        /// <param name=\"useBinHex\">returns bin hex data when set (010A0D10AF)</param>\n        /// <returns></returns>\n        public static string UnprotectString(string encryptString, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false)\n        {\n            byte[] buffer;\n            if (useBinHex)\n                buffer = BinHexToBinary(encryptString);\n            else\n                buffer = Convert.FromBase64String(encryptString);\n\n            buffer = ProtectedData.Unprotect(buffer, key, scope);\n\n            return Encoding.UTF8.GetString(buffer);        \n        }\n#endif\n\n        #endregion\n\n\n        #region Hashes\n\n        /// <summary>\n        /// Generates a hash for the given plain text value and returns a\n        /// base64-encoded result. Before the hash is computed, a random salt\n        /// is generated and appended to the plain text. This salt is stored at\n        /// the end of the hash value, so it can be used later for hash\n        /// verification.\n        /// </summary>\n        /// <param name=\"plainText\">\n        /// Plaintext value to be hashed. \n        /// </param>\n        /// <param name=\"hashAlgorithm\">\n        /// Name of the hash algorithm. Allowed values are: \"MD5\", \"SHA1\",\n        /// \"SHA256\", \"SHA384\", \"SHA512\", \"HMACMD5\", \"HMACSHA1\", \"HMACSHA256\",\n        ///  \"HMACSHA512\" (if any other value is specified  MD5 will be used). \n        /// \n        /// HMAC algorithms uses Hash-based Message Authentication Code.\n        /// The HMAC process mixes a secret key with the message data, hashes \n        /// the result with the hash function, mixes that hash value with \n        /// the secret key again, and then applies the hash function\n        /// a second time. HMAC hashes are fixed lenght and generally\n        /// much longer than non-HMAC hashes of the same type.\n        /// \n        /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx      \n        /// \n        /// This value is case-insensitive.\n        /// </param>\n        /// <param name=\"salt\">\n        /// Optional but recommended salt string to apply to the hash. If not passed the\n        /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for \n        /// file hashes etc.) HMAC versions REQUIRE that salt is passed.\n        /// </param>\n        /// <param name=\"useBinHex\">if true returns the data as BinHex byte pair string. Otherwise Base64 is returned.</param>\n        /// <returns>\n        /// Hash value formatted as a base64-encoded or BinHex stringstring.\n        /// </returns>\n        public static string ComputeHash(string plainText,\n                                         string hashAlgorithm,\n                                         byte[] saltBytes, \n                                         bool useBinHex = false)\n        {\n            if (string.IsNullOrEmpty(plainText))\n                return plainText;\n\n            return ComputeHash(Encoding.UTF8.GetBytes(plainText), hashAlgorithm, saltBytes, useBinHex);\n        }\n\n        /// <summary>\n        /// Generates a hash for the given plain text value and returns a\n        /// base64-encoded result. Before the hash is computed, a random salt\n        /// is generated and appended to the plain text. This salt is stored at\n        /// the end of the hash value, so it can be used later for hash\n        /// verification.\n        /// </summary>\n        /// <param name=\"plainText\">\n        /// Plaintext value to be hashed. \n        /// </param>\n        /// <param name=\"hashAlgorithm\">\n        /// Name of the hash algorithm. Allowed values are: \"MD5\", \"SHA1\",\n        /// \"SHA256\", \"SHA384\", \"SHA512\", \"HMACMD5\", \"HMACSHA1\", \"HMACSHA256\",\n        ///  \"HMACSHA512\" (if any other value is specified  MD5 will be used). \n        /// \n        /// HMAC algorithms uses Hash-based Message Authentication Code.\n        /// The HMAC process mixes a secret key with the message data, hashes \n        /// the result with the hash function, mixes that hash value with \n        /// the secret key again, and then applies the hash function\n        /// a second time. HMAC hashes are fixed lenght and generally\n        /// much longer than non-HMAC hashes of the same type.\n        /// \n        /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx      \n        /// \n        /// This value is case-insensitive.\n        /// </param>\n        /// <param name=\"salt\">\n        /// Optional but recommended salt string to apply to the hash. If not passed the\n        /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for \n        /// file hashes etc.) HMAC versions REQUIRE that salt is passed.\n        /// </param>\n        /// <param name=\"useBinHex\">if true returns the data as BinHex byte pair string. Otherwise Base64 is returned.</param>\n        /// <returns>\n        /// Hash value formatted as a base64-encoded or BinHex stringstring.\n        /// </returns>\n        public static string ComputeHash(string plainText,\n                                         string hashAlgorithm,\n                                         string salt,\n                                         bool useBinHex = false)\n        {\n            if (string.IsNullOrEmpty(plainText))\n                return plainText;\n\n            return ComputeHash(Encoding.UTF8.GetBytes(plainText), hashAlgorithm, Encoding.UTF8.GetBytes(salt), useBinHex);\n        }\n\n        /// <summary>\n        /// Generates a hash for the given plain text value and returns a\n        /// base64-encoded result. Before the hash is computed, a random salt\n        /// is generated and appended to the plain text. This salt is stored at\n        /// the end of the hash value, so it can be used later for hash\n        /// verification.\n        /// </summary>\n        /// <param name=\"byteData\">\n        /// Plaintext value to be hashed. \n        /// </param>\n        /// <param name=\"hashAlgorithm\">\n        /// Name of the hash algorithm. Allowed values are: \"MD5\", \"SHA1\",\n        /// \"SHA256\", \"SHA384\", \"SHA512\", \"HMACMD5\", \"HMACSHA1\", \"HMACSHA256\",\n        ///  \"HMACSHA512\" (if any other value is specified  MD5 will be used). \n        /// \n        /// HMAC algorithms uses Hash-based Message Authentication Code.\n        /// The HMAC process mixes a secret key with the message data, hashes \n        /// the result with the hash function, mixes that hash value with \n        /// the secret key again, and then applies the hash function\n        /// a second time. HMAC hashes are fixed lenght and generally\n        /// much longer than non-HMAC hashes of the same type.\n        /// \n        /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx      \n        /// \n        /// This value is case-insensitive.\n        /// </param>\n        /// <param name=\"saltBytes\">\n        /// Optional but recommended salt bytes to apply to the hash. If not passed the\n        /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for \n        /// file hashes etc.) HMAC versions REQUIRE that salt is passed.\n        /// </param>\n        /// <param name=\"useBinHex\">if true returns the data as BinHex byte pair string. Otherwise Base64 is returned.</param>\n        /// <returns>\n        /// Hash value formatted as a base64-encoded or BinHex stringstring.\n        /// </returns>\n        public static string ComputeHash(byte[] byteData,\n                                         string hashAlgorithm,\n                                         byte[] saltBytes,\n                                         bool useBinHex = false)\n        {\n            if (byteData == null)\n                return null;\n\n            // Convert plain text into a byte array.            \n            byte[] plainTextWithSaltBytes;\n\n            if (saltBytes != null && !hashAlgorithm.StartsWith(\"HMAC\"))\n            {\n                // Allocate array, which will hold plain text and salt.\n                plainTextWithSaltBytes =\n                    new byte[byteData.Length + saltBytes.Length];\n\n                // Copy plain text bytes into resulting array.\n                for (int i = 0; i < byteData.Length; i++)\n                    plainTextWithSaltBytes[i] = byteData[i];\n\n                // Append salt bytes to the resulting array.\n                for (int i = 0; i < saltBytes.Length; i++)\n                    plainTextWithSaltBytes[byteData.Length + i] = saltBytes[i];\n            }\n            else\n                plainTextWithSaltBytes = byteData;\n\n            HashAlgorithm hash;\n\n            // Make sure hashing algorithm name is specified.\n            if (hashAlgorithm == null)\n                hashAlgorithm = \"\";\n\n            // Initialize appropriate hashing algorithm class.\n            switch (hashAlgorithm.ToUpper())\n            {\n                case \"SHA1\":\n                    hash = SHA1.Create();\n                    break;\n                case \"SHA256\":\n                    hash = SHA256.Create();\n                    break;\n                case \"SHA384\":\n                    hash = SHA384.Create();\n                    break;\n                case \"SHA512\":\n                    hash = SHA512.Create();\n                    break;\n                case \"HMACMD5\":\n                    hash = new HMACMD5(saltBytes);\n                    break;\n                case \"HMACSHA1\":\n                    hash = new HMACSHA1(saltBytes);\n                    break;\n                case \"HMACSHA256\":\n                    hash = new HMACSHA256(saltBytes);                    \n                    break;\n                case \"HMACSHA512\":\n                    hash = new HMACSHA512(saltBytes);\n                    break;\n                default:\n                    // default to MD5\n                    hash = MD5.Create();\n                    break;\n            }\n            using (hash)\n            {\n                byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);\n\n                if (useBinHex)\n                    return BinaryToBinHex(hashBytes);\n\n                return Convert.ToBase64String(hashBytes);\n            }\n        }\n#endregion\n\n#region Gzip\n\n        /// <summary>\n        /// GZip encodes a memory buffer to a compressed memory buffer\n        /// </summary>\n        /// <param name=\"buffer\"></param>\n        /// <returns></returns>\n        public static byte[] GZipMemory(byte[] buffer)\n        {\n            MemoryStream ms = new MemoryStream();\n\n            GZipStream gZip = new GZipStream(ms, CompressionMode.Compress);\n\n            gZip.Write(buffer, 0, buffer.Length);\n            gZip.Close();\n\n            byte[] result = ms.ToArray();\n            ms.Close();\n\n            return result;\n        }\n\n        /// <summary>\n        /// Encodes a string to a gzip compressed memory buffer\n        /// </summary>\n        /// <param name=\"input\"></param>\n        /// <returns></returns>\n        public static byte[] GZipMemory(string input)\n        {\n            return GZipMemory(Encoding.UTF8.GetBytes(input));\n        }\n\n        /// <summary>\n        /// Encodes a file to a gzip memory buffer\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"isFile\"></param>\n        /// <returns></returns>\n        public static byte[] GZipMemory(string filename, bool isFile)\n        {\n            byte[] buffer = File.ReadAllBytes(filename);\n            return GZipMemory(buffer);\n        }\n\n        /// <summary>\n        /// Encodes one file to another file that is gzip compressed.\n        /// File is overwritten if it exists and not locked.\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"outputFile\"></param>\n        /// <returns></returns>\n        public static bool GZipFile(string filename, string outputFile)\n        {            \n            byte[] Buffer = File.ReadAllBytes(filename);\n            FileStream fs = new FileStream(outputFile, FileMode.OpenOrCreate, FileAccess.Write);\n            GZipStream GZip = new GZipStream(fs, CompressionMode.Compress);\n            GZip.Write(Buffer, 0, Buffer.Length);\n            GZip.Close();\n            fs.Close();\n\n            return true;\n        }\n\n#endregion\n\n#region CheckSum\n\n        /// <summary>\n        /// Creates an SHA256 or MD5 checksum of a file\n        /// </summary>\n        /// <param name=\"file\"></param>\n        /// <param name=\"mode\">SHA256,SHA512,MD5</param>\n        /// <returns>A binhex string</returns>\n        public static string GetChecksumFromFile(string file, string mode = \"SHA256\")\n        {\n            if (!File.Exists(file))\n                return null;\n\n            try\n            {\n                using (FileStream stream = File.OpenRead(file))\n                {\n                    if (mode == \"SHA256\")\n                    {\n                        byte[] checksum;\n                        using (var sha = SHA256.Create())\n                        {\n                            checksum = sha.ComputeHash(stream);\n                        }\n\n                        return BinaryToBinHex(checksum);\n                    }\n                    if (mode == \"SHA512\")\n                    {\n                        byte[] checksum;\n                        using (var sha = SHA512.Create())\n                        {\n                            checksum = sha.ComputeHash(stream);\n                        }\n\n                        return BinaryToBinHex(checksum);\n                    }\n                    if (mode == \"MD5\")\n                    {\n                        byte[] checkSum;\n                        using (var md = MD5.Create())\n                        {\n                            checkSum = md.ComputeHash(stream);\n                        }\n\n                        return BinaryToBinHex(checkSum);\n                    }\n                }\n            }\n            catch\n            {\n                return null;\n            }\n\n            return null;\n        }\n\n        /// <summary>\n        /// Create a SHA256 or MD5 checksum from a bunch of bytes\n        /// </summary>\n        /// <param name=\"fileData\"></param>\n        /// <param name=\"mode\">SHA256,SHA512,MD5</param>\n        /// <returns></returns>\n        public static string GetChecksumFromBytes(byte[] fileData, string mode = \"SHA256\")\n        {\n            using (MemoryStream stream = new MemoryStream(fileData))\n            {\n                if (mode == \"SHA256\")\n                {\n                    byte[] checksum;\n                    using (var sha =  SHA256.Create())\n                    {\n                        checksum = sha.ComputeHash(stream);\n                    }\n\n                    return BinaryToBinHex(checksum);\n                }\n                if (mode == \"SHA512\")\n                {\n                    byte[] checksum;\n                    using (var sha = SHA512.Create())\n                    {\n                        checksum = sha.ComputeHash(stream);\n                    }\n\n                    return BinaryToBinHex(checksum);\n                }\n                if (mode == \"MD5\")\n                {\n                    byte[] checkSum;\n                    using (var md = MD5.Create())\n                    {\n                        checkSum = md.ComputeHash(stream);\n                    }\n\n                    return BinaryToBinHex(checkSum);\n                }\n            }\n\n            return null;\n        }\n\n#endregion\n\n#region BinHex Helpers\n\n        /// <summary>\n        /// Converts a byte array into a BinHex string.\n        /// Example: 01552233 \n        /// where the numbers are packed\n        /// byte values.\n        /// </summary>\n        /// <param name=\"data\">Raw data to send</param>\n        /// <returns>string or null if input is null</returns>\n        public static string BinaryToBinHex(byte[] data)\n        {\n            if (data == null)\n                return null;\n\n            StringBuilder sb = new StringBuilder(data.Length * 2);\n            foreach (byte val in data)\n            {\n                sb.AppendFormat(\"{0:x2}\", val);\n            }\n            return sb.ToString().ToUpper();\n        }\n\n\n        /// <summary>\n        /// Turns a BinHex string that contains raw byte values\n        /// into a byte array\n        /// </summary>\n        /// <param name=\"hex\">BinHex string (011a031f) just two byte hex digits strung together)</param>\n        /// <returns></returns>\n        public static byte[] BinHexToBinary(string hex)\n        {\n            int offset = hex.StartsWith(\"0x\") ? 2 : 0;\n            if ((hex.Length % 2) != 0)\n                throw new ArgumentException(\"Invalid String Length\");\n\n            byte[] ret = new byte[(hex.Length - offset) / 2];\n\n            for (int i = 0; i < ret.Length; i++)\n            {\n                ret[i] = (byte)((ParseHexChar(hex[offset]) << 4)\n                                 | ParseHexChar(hex[offset + 1]));\n                offset += 2;\n            }\n            return ret;\n        }\n\n        static int ParseHexChar(char c)\n        {\n            if (c >= '0' && c <= '9')\n                return c - '0';\n            if (c >= 'A' && c <= 'F')\n                return c - 'A' + 10;\n            if (c >= 'a' && c <= 'f')\n                return c - 'a' + 10;\n\n            throw new ArgumentException(\"Invalid character\");\n        }\n\n#endregion\n    }\n\n    public static class SecureStringExtensions\n    {\n        public static string GetString(this SecureString source)\n        {\n            string result = null;\n            int length = source.Length;\n            IntPtr pointer = IntPtr.Zero;\n            char[] chars = new char[length];\n\n            try\n            {\n                pointer = Marshal.SecureStringToBSTR(source);\n                Marshal.Copy(pointer, chars, 0, length);\n                result = string.Join(\"\", chars);\n            }\n            finally\n            {\n                if (pointer != IntPtr.Zero)\n                {\n                    Marshal.ZeroFreeBSTR(pointer);\n                }\n            }\n\n            return result;\n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/Expando.cs",
    "content": "﻿#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2012\n *          http://www.west-wind.com/\n * \n * Created: Feb 2, 2012\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Dynamic;\nusing System.Reflection;\nusing ICollection = System.Collections.ICollection;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Class that provides extensible properties and methods to an\n    /// existing object when cast to dynamic. This\n    /// dynamic object stores 'extra' properties in a dictionary or\n    /// checks the actual properties of the instance passed via \n    /// constructor.\n    /// \n    /// This class can be subclassed to extend an existing type or \n    /// you can pass in an instance to extend. Properties (both\n    /// dynamic and strongly typed) can be accessed through an \n    /// indexer.\n    /// \n    /// This type allows you three ways to access its properties:\n    /// \n    /// Directly: any explicitly declared properties are accessible\n    /// Dynamic: dynamic cast allows access to dictionary and native properties/methods\n    /// Dictionary: Any of the extended properties are accessible via IDictionary interface\n    /// </summary>\n    [Serializable]\n    public class Expando : DynamicObject, IDynamicMetaObjectProvider\n    {\n        /// <summary>\n        /// Instance of object passed in\n        /// </summary>\n        protected object Instance;\n\n        /// <summary>\n        /// Cached type of the instance\n        /// </summary>\n        protected Type InstanceType;\n\n        PropertyInfo[] InstancePropertyInfo\n        {\n            get\n            {\n                if (_InstancePropertyInfo == null && Instance != null)                \n                    _InstancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)\n                                                            .Where(a => a.GetIndexParameters().Length == 0).ToArray();\n                return _InstancePropertyInfo;                \n            }\n        }\n        PropertyInfo[] _InstancePropertyInfo;\n\n\n        /// <summary>\n        /// String Dictionary that contains the extra dynamic values\n        /// stored on this object/instance\n        /// </summary>        \n        /// <remarks>Using PropertyBag to support XML Serialization of the dictionary</remarks>\n        public PropertyBag Properties = new PropertyBag();\n\n        //public Dictionary<string,object> Properties = new Dictionary<string, object>();\n\n        /// <summary>\n        /// This constructor just works off the internal dictionary and any \n        /// public properties of this object.\n        /// \n        /// Note you can subclass Expando.\n        /// </summary>\n        public Expando() \n        {\n            Initialize(this);            \n        }\n\n        /// <summary>\n        /// Allows passing in an existing instance variable to 'extend'.        \n        /// </summary>\n        /// <remarks>\n        /// You can pass in null here if you don't want to \n        /// check native properties and only check the Dictionary!\n        /// </remarks>\n        /// <param name=\"instance\"></param>\n        public Expando(object instance)\n        {\n            var dictionary = instance as IDictionary<string, object>;\n            if (dictionary == null)\n            {\n                Initialize(instance);\n                return;\n            }\n\n            var expando = this;\n\n            this.\n            Initialize(expando);\n            InitializeAsDictionary(expando, dictionary);\n        }\n\n        /// <summary>\n        /// Create an Expando from a dictionary\n        /// </summary>\n        /// <param name=\"dict\">A dictionary of property/value pairs</param>\n        public Expando(IDictionary<string, object> dict)\n        {            \n            var expando = this;\n\n            Initialize(expando);\n            InitializeAsDictionary(expando, dict);\n        }\n\n        private void InitializeAsDictionary(Expando expando, IDictionary<string, object> dict)\n        {\n            Properties = new PropertyBag();\n\n            foreach (var kvp in dict)\n            {\n                var kvpValue = kvp.Value;\n                if (kvpValue is IDictionary<string, object>)\n                {\n                    var expandoVal = new Expando(kvpValue);\n                    expando[kvp.Key] = expandoVal;\n                }\n                else if (kvp.Value is ICollection)\n                {\n                    // iterate through the collection and convert any string-object dictionaries\n                    // along the way into expando objects\n                    var objList = new List<object>();\n                    foreach (var item in (ICollection) kvp.Value)\n                    {\n                        var itemVals = item as IDictionary<string, object>;\n                        if (itemVals != null)\n                        {\n                            var expandoItem = new Expando(itemVals);\n                            objList.Add(expandoItem);\n                        }\n                        else\n                        {\n                            objList.Add(item);\n                        }\n                    }\n                    expando[kvp.Key] = objList;\n                }\n                else\n                {\n                    expando[kvp.Key] = kvpValue;\n                }\n            }\n        }\n\n        protected void Initialize(object instance)\n        {\n            Instance = instance;\n            if (instance != null)\n                InstanceType = instance.GetType();           \n        }\n\n\n        /// <summary>\n        /// Return both instance and dynamic names.\n        /// \n        /// Important to return both so JSON serialization with \n        /// Json.NET works.\n        /// </summary>\n        /// <returns></returns>\n        public override IEnumerable<string> GetDynamicMemberNames()\n        {\n            foreach (var prop in GetProperties(true))            \n                yield return prop.Key;            \n        }\n\n\n       /// <summary>\n       /// Try to retrieve a member by name first from instance properties\n       /// followed by the collection entries.\n       /// </summary>\n       /// <param name=\"binder\"></param>\n       /// <param name=\"result\"></param>\n       /// <returns></returns>\n        public override bool TryGetMember(GetMemberBinder binder, out object result)\n        {\n            result = null;\n\n            // first check the Properties collection for member\n            if (Properties.Keys.Contains(binder.Name))\n            {\n                result = Properties[binder.Name];\n                return true;\n            }\n\n\n            // Next check for Public properties via Reflection\n            if (Instance != null)\n            {\n                try\n                {\n                    return GetProperty(Instance, binder.Name, out result);                    \n                }\n                catch { }\n            }\n\n            // failed to retrieve a property\n            result = null;\n            return false;\n        }\n\n\n        /// <summary>\n        /// Property setter implementation tries to retrieve value from instance \n        /// first then into this object\n        /// </summary>\n        /// <param name=\"binder\"></param>\n        /// <param name=\"value\"></param>\n        /// <returns></returns>\n        public override bool TrySetMember(SetMemberBinder binder, object value)\n        {\n\n            // first check to see if there's a native property to set\n            if (Instance != null)\n            {\n                try\n                {\n                    bool result = SetProperty(Instance, binder.Name, value);\n                    if (result)\n                        return true;\n                }\n                catch\n                {\n                    return false;\n                }\n            }\n            \n            // no match - set or add to dictionary\n            Properties[binder.Name] = value;\n            return true;\n        }\n\n        /// <summary>\n        /// Dynamic invocation method. Currently allows only for Reflection based\n        /// operation (no ability to add methods dynamically).\n        /// </summary>\n        /// <param name=\"binder\"></param>\n        /// <param name=\"args\"></param>\n        /// <param name=\"result\"></param>\n        /// <returns></returns>\n        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n        {\n            if (Instance != null)\n            {\n                try\n                {\n                    // check instance passed in for methods to invoke\n                    if (InvokeMethod(Instance, binder.Name, args, out result))\n                        return true;                    \n                }\n                catch { }\n            }\n\n            result = null;\n            return false;\n        }\n        \n\n        /// <summary>\n        /// Reflection Helper method to retrieve a property\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        /// <param name=\"name\"></param>\n        /// <param name=\"result\"></param>\n        /// <returns></returns>\n        protected bool GetProperty(object instance, string name, out object result)\n        {\n            if (instance == null)\n                instance = this;\n\n            var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);\n            if (miArray != null && miArray.Length > 0)\n            {\n                var mi = miArray[0];\n                if (mi.MemberType == MemberTypes.Property)\n                {\n                    result = ((PropertyInfo)mi).GetValue(instance,null);\n                    return true;\n                }\n            }\n\n            result = null;\n            return false;                \n        }\n\n        /// <summary>\n        /// Reflection helper method to set a property value\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        /// <param name=\"name\"></param>\n        /// <param name=\"value\"></param>\n        /// <returns></returns>\n        protected bool SetProperty(object instance, string name, object value)\n        {\n            if (instance == null)\n                instance = this;\n\n            var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance);\n            if (miArray != null && miArray.Length > 0)\n            {\n                var mi = miArray[0];\n                if (mi.MemberType == MemberTypes.Property)\n                {\n                    ((PropertyInfo)mi).SetValue(Instance, value, null);\n                    return true;\n                }\n            }\n            return false;                \n        }\n\n        /// <summary>\n        /// Reflection helper method to invoke a method\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        /// <param name=\"name\"></param>\n        /// <param name=\"args\"></param>\n        /// <param name=\"result\"></param>\n        /// <returns></returns>\n        protected bool InvokeMethod(object instance, string name, object[] args, out object result)\n        {\n            if (instance == null)\n                instance = this;\n\n            // Look at the instanceType\n            var miArray = InstanceType.GetMember(name,\n                                    BindingFlags.InvokeMethod |\n                                    BindingFlags.Public | BindingFlags.Instance);\n\n            if (miArray != null && miArray.Length > 0)\n            {\n                var mi = miArray[0] as MethodInfo;\n                result = mi.Invoke(Instance, args);\n                return true;\n            }\n\n            result = null;\n            return false;\n        }\n\n\n\n\n\n        /// <summary>\n        /// Convenience method that provides a string Indexer \n        /// to the Properties collection AND the strongly typed\n        /// properties of the object by name.\n        /// \n        /// // dynamic\n        /// exp[\"Address\"] = \"112 nowhere lane\"; \n        /// // strong\n        /// var name = exp[\"StronglyTypedProperty\"] as string; \n        /// </summary>\n        /// <remarks>\n        /// The getter checks the Properties dictionary first\n        /// then looks in PropertyInfo for properties.\n        /// The setter checks the instance properties before\n        /// checking the Properties dictionary.\n        /// </remarks>\n        /// <param name=\"key\"></param>\n        /// \n        /// <returns></returns>\n        public object this[string key]\n        {\n            get\n            {\n                try\n                {\n                    // try to get from properties collection first\n                    return Properties[key];\n                }\n                catch (KeyNotFoundException)\n                {\n                    // try reflection on instanceType\n                    object result = null;\n                    if (GetProperty(Instance, key, out result))\n                        return result;\n\n                    // nope doesn't exist\n                    throw;\n                }\n            }\n            set\n            {\n                if (Properties.ContainsKey(key))\n                {\n                    Properties[key] = value;\n                    return;\n                }\n\n                // check instance for existance of type first\n                var miArray = InstanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);\n                if (miArray != null && miArray.Length > 0)\n                    SetProperty(Instance, key, value);\n                else\n                    Properties[key] = value;\n            }\n        }\n\n\n        /// <summary>\n        /// Returns and the properties of \n        /// </summary>\n        /// <param name=\"includeProperties\"></param>\n        /// <returns></returns>\n        public IEnumerable<KeyValuePair<string,object>> GetProperties(bool includeInstanceProperties = false)\n        {\n            if (includeInstanceProperties && Instance != null)\n            {\n                foreach (var prop in this.InstancePropertyInfo)\n                    yield return new KeyValuePair<string, object>(prop.Name, prop.GetValue(Instance, null));\n            }\n\n            foreach (var key in this.Properties.Keys)\n               yield return new KeyValuePair<string, object>(key, this.Properties[key]);\n\n        }\n  \n\n        /// <summary>\n        /// Checks whether a property exists in the Property collection\n        /// or as a property on the instance\n        /// </summary>\n        /// <param name=\"item\"></param>\n        /// <returns></returns>\n        public bool Contains(KeyValuePair<string, object> item, bool includeInstanceProperties = false)\n        {\n            bool res = Properties.ContainsKey(item.Key);\n            if (res)\n                return true;\n\n            if (includeInstanceProperties && Instance != null)\n            {\n                foreach (var prop in this.InstancePropertyInfo)\n                {\n                    if (prop.Name == item.Key)\n                        return true;\n                }\n            }\n\n            return false;\n        }        \n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/ObjectFactory.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Web;\nusing System.Threading;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// An object factory that can create instances of types\n    /// for Http Web Request and Thread Scoped object objects\n    /// and value types.\n    /// </summary>\n    /// <typeparam name=\"T\"></typeparam>\n    public class ObjectFactory<T>\n        where T: class, new()\n    {\n        /// <summary>\n        /// Internal locking for collection storage\n        /// </summary>\n        static object _syncLock = new object();\n\n\n        /// <summary>\n        /// Returns a standard instance of an object.\n        /// </summary>\n        /// <param name=\"args\"></param>\n        /// <returns></returns>\n        public static T CreateObject(params object[] args)\n        {\n            return (T) Activator.CreateInstance(typeof(T), args);            \n        }\n\n\t\t\n        /// <summary>\n        /// Create an instance scoped to a current thread.\n        /// </summary>\n        /// <param name=\"key\">Optional reusable key of the TLS item created</param>\n        /// <param name=\"args\"></param>\n        /// <returns></returns>\n        public static T CreateThreadScopedObject(params object[] args)\n        {\n            string key = GetUniqueObjectKey(args) + \"_\" +\n                                            Thread.CurrentThread.ManagedThreadId.ToString();              \n\n            LocalDataStoreSlot threadData = Thread.GetNamedDataSlot(key);\n\n            T item;\n\n            if (threadData != null)\n                item = (T)Thread.GetData(threadData);\n            else\n                item = null;\n\n            // no item - create and store\n            if ( item == null )\n            {\n                lock (_syncLock)\n                {\n                    // check again inside of lock\n                    threadData = Thread.GetNamedDataSlot(key);\n                    if (threadData == null)\n                        threadData = Thread.GetNamedDataSlot(key);\n\n                    if (item == null)\n                        item = CreateObject(args);\n\n                    Thread.SetData(threadData, item);                        \n                }                \n            }\n\n            return item;\n        }\n\t\t\n\n        /// <summary>\n        /// Returns a unique ID for a given type and parameter signature\n        /// </summary>        \n        /// <param name=\"args\"></param>\n        /// <returns></returns>\n        public static string GetUniqueObjectKey(params object[] args)\n        {\n            Type type = typeof(T);\n\n            StringBuilder sb = new StringBuilder(\"_\" + type.GetHashCode().ToString(\"x\"));\n            sb.Append( \"_\" + type.Name);            \n\n            foreach (var arg in args)\n            {\n                if (arg == null)\n                    sb.Append(\"_null\");\n                else\n                    sb.Append(\"_\" + arg.GetHashCode().ToString(\"x\"));\n            }\n\n            return sb.ToString();\n        }\n  \n        \n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/PropertyBag.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Xml.Serialization;\nusing System.Xml;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Creates a serializable string/object dictionary that is XML serializable\n    /// Encodes keys as element names and values as simple values with a type\n    /// attribute that contains an XML type name. Complex names encode the type \n    /// name with type='___namespace.classname' format followed by a standard xml\n    /// serialized format. The latter serialization can be slow so it's not recommended\n    /// to pass complex types if performance is critical.\n    /// </summary>\n    [XmlRoot(\"properties\")]\n    public class PropertyBag : PropertyBag<object>\n    {\n        /// <summary>\n        /// Creates an instance of a propertybag from an Xml string\n        /// </summary>\n        /// <param name=\"xml\">Serialize</param>\n        /// <returns></returns>\n        public new static PropertyBag CreateFromXml(string xml)\n        {\n            var bag = new PropertyBag();\n            bag.FromXml(xml);\n            return bag;\n        }\n    }\n\n    /// <summary>\n    /// Creates a serializable string for generic types that is XML serializable.\n    /// \n    /// Encodes keys as element names and values as simple values with a type\n    /// attribute that contains an XML type name. Complex names encode the type \n    /// name with type='___namespace.classname' format followed by a standard xml\n    /// serialized format. The latter serialization can be slow so it's not recommended\n    /// to pass complex types if performance is critical.\n    /// </summary>\n    /// <typeparam name=\"TValue\">Must be a reference type. For value types use type object</typeparam>\n    [XmlRoot(\"properties\")]\n    public class PropertyBag<TValue> : Dictionary<string, TValue>, IXmlSerializable\n    {\n        /// <summary>\n        /// Not implemented - this means no schema information is passed\n        /// so this won't work with ASMX/WCF services.\n        /// </summary>\n        /// <returns></returns>       \n        public System.Xml.Schema.XmlSchema GetSchema()\n        {\n            return null;\n        }\n\n        \n\n        /// <summary>\n        /// Serializes the dictionary to XML. Keys are \n        /// serialized to element names and values as \n        /// element values. An xml type attribute is embedded\n        /// for each serialized element - a .NET type\n        /// element is embedded for each complex type and\n        /// prefixed with three underscores.\n        /// </summary>\n        /// <param name=\"writer\"></param>\n        public void WriteXml(System.Xml.XmlWriter writer)\n        {\n            foreach (string key in this.Keys)\n            {\n                TValue value = this[key];\n\n                Type type = null;\n                if (value != null)\n                    type = value.GetType();\n\n                writer.WriteStartElement(\"item\");\n\n                writer.WriteStartElement(\"key\");\n                writer.WriteString(key as string);\n                writer.WriteEndElement();\n\n                writer.WriteStartElement(\"value\");\n\n                string xmlType = XmlUtils.MapTypeToXmlType(type);\n\n                bool isCustom = false;\n\n                // Type information attribute if not string\n                if (value == null)\n                {\n                    writer.WriteAttributeString(\"type\", \"nil\");\n                }\n                else if (!string.IsNullOrEmpty(xmlType))\n                {\n                    if (xmlType != \"string\")\n                    {\n                        writer.WriteStartAttribute(\"type\");\n                        writer.WriteString(xmlType);\n                        writer.WriteEndAttribute();\n                    }\n                }\n                else\n                {\n                    isCustom = true;\n                    xmlType = \"___\" + value.GetType().FullName;\n                    writer.WriteStartAttribute(\"type\");\n                    writer.WriteString(xmlType);\n                    writer.WriteEndAttribute();\n                }\n\n                \n                // Serialize simple types with WriteValue\n                if (!isCustom)\n                {\n\n                    if (value != null)\n                        writer.WriteValue(value);                                    \n                }\n                else\n                {                    \n                    // Complex types require custom XmlSerializer\n                    XmlSerializer ser = new XmlSerializer(value.GetType());\n                    ser.Serialize(writer, value);\n                }\n                writer.WriteEndElement(); // value\n\n                writer.WriteEndElement(); // item\n            }\n        }\n\n\n        /// <summary>\n        /// Reads the custom serialized format\n        /// </summary>\n        /// <param name=\"reader\"></param>\n        public void ReadXml(System.Xml.XmlReader reader)\n        {\n            this.Clear();\n            while (reader.Read())\n            {\n                if (reader.NodeType == XmlNodeType.Element && reader.Name == \"key\")\n                {                    \n                    string xmlType = null;\n                    string name = reader.ReadElementContentAsString(); \n\n                    // item element\n                    reader.ReadToNextSibling(\"value\");\n                    \n                    if (reader.MoveToNextAttribute())\n                        xmlType = reader.Value;\n                    if (string.IsNullOrEmpty(xmlType))\n                        xmlType = \"string\";\n\n                    reader.MoveToContent();\n\n                    TValue value;\n                    string strval = String.Empty;\n                    if (xmlType == \"nil\")\n                        value = default(TValue);   // null\n\n                    // .NET types that don't map to XML we have to manually\n                    // deserialize\n                    else if (xmlType.StartsWith(\"___\"))\n                    {\n                        // skip ahead to serialized value element                                                \n                        while (reader.Read() && reader.NodeType != XmlNodeType.Element)\n                        { }                        \n                        \n                        Type type = ReflectionUtils.GetTypeFromName(xmlType.Substring(3));\n                        XmlSerializer ser = new XmlSerializer(type);\n                        value = (TValue)ser.Deserialize(reader);\n                    }\n                    else\n                        value = (TValue) reader.ReadElementContentAs(XmlUtils.MapXmlTypeToType(xmlType), null);\n\n                    this.Add(name, value);\n                }\n            }\n        }        \n\n\n        /// <summary>\n        /// Serializes this dictionary to an XML string\n        /// </summary>\n        /// <returns>XML String or Null if it fails</returns>\n        public string ToXml()\n        {\n            string xml = null;\n            SerializationUtils.SerializeObject(this, out xml);\n            return xml;\n        }\n\n        /// <summary>\n        /// Deserializes from an XML string\n        /// </summary>\n        /// <param name=\"xml\"></param>\n        /// <returns>true or false</returns>\n        public bool FromXml(string xml)\n        {\n            this.Clear();\n\n            // if xml string is empty we return an empty dictionary                        \n            if (string.IsNullOrEmpty(xml))\n                return true;\n\n            var result = SerializationUtils.DeSerializeObject(xml, \n                                                 this.GetType()) as PropertyBag<TValue>;\n            if (result != null)\n            {\n                foreach (var item in result)\n                {\n                    this.Add(item.Key, item.Value);\n                }\n            }\n            else\n                // null is a failure\n                return false;\n\n            return true;\n        }\n\n\n        /// <summary>\n        /// Creates an instance of a propertybag from an Xml string\n        /// </summary>\n        /// <param name=\"xml\"></param>\n        /// <returns></returns>\n        public static PropertyBag<TValue> CreateFromXml(string xml)\n        {\n            var bag = new PropertyBag<TValue>();\n            bag.FromXml(xml);\n            return bag;\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/Scheduler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Net;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// A generic scheduling service that runs on a background\n    /// thread and fires events in a given check frequency.\n    /// \n    /// \n    /// </summary>\n    public class Scheduler : IDisposable\n    {\n        /// <summary>\n        /// Determines the status  the Scheduler\n        /// </summary>        \n        public bool Cancelled\n        {\n            get { return _Cancelled; }\n            private set { _Cancelled = value; }\n        }\n        private bool _Cancelled = true;\n\n        /// <summary>\n        /// The frequency in how often the main method is executed.\n        /// Given in milli-seconds.\n        /// </summary>\n        public int CheckFrequency\n        {\n            get { return _CheckFrequency; }\n            set { _CheckFrequency = value; }\n        }\n        private int _CheckFrequency = 60000;\n\n        /// <summary>\n        /// Optional URL that is pinged occasionally to\n        /// ensure the server stays alive.\n        /// \n        /// If empty hits root web page (~/yourapp/)\n        /// </summary>\n        public string WebServerPingUrl\n        {\n            get { return _WebServerPingUrl; }\n            set { _WebServerPingUrl = value; }\n        }\n        private string _WebServerPingUrl = \"\";\n\n\n        /// <summary>\n        /// Event that is fired when\n        /// </summary>\n        public event EventHandler ExecuteScheduledEvent;\n\n        AutoResetEvent _WaitHandle = new AutoResetEvent(false);\n\n        /// <summary>\n        ///  Internal property used for cross thread locking\n        /// </summary>\n        object _SyncLock = new Object();\n\n        /// <summary>\n        /// Optionally usable local memory based queue that \n        /// contains can be used to add items to a queue\n        /// ordered retrieval.\n        /// \n        /// If message persistence is important your scheduling store\n        /// should be a database. You can use the QueueMessageManager\n        /// object for example.\n        /// </summary>    \n        /// <remarks>\n        /// Note memory based! This means if app crashses\n        /// or is shut down messages might get lost.\n        /// </remarks>\n        public virtual Queue<object> Items\n        {\n            get { return _Items; }\n            set { _Items = value; }\n        }\n        private Queue<object> _Items = new Queue<object>();\n\n\n        /// <summary>\n        /// Starts the background thread processing       \n        /// </summary>\n        /// <param name=\"CheckFrequency\">Frequency that checks are performed in seconds</param>\n        public void Start(int checkFrequency)\n        {\n            // Ensure that any waiting instances are shut down\n            //this.WaitHandle.Set();\n\n            CheckFrequency = checkFrequency;\n            Cancelled = false;\n\n            Thread t = new Thread(Run);\n            t.Start();\n        }\n        /// <summary>\n        /// Starts the background Thread processing\n        /// </summary>\n        public void Start()\n        {\n            Cancelled = false;\n            IsRunning = false;\n            Start(CheckFrequency);\n        }\n\n        /// <summary>\n        /// Causes the processing to stop. If the operation is still\n        /// active it will stop after the current message processing\n        /// completes\n        /// </summary>\n        public void Stop()\n        {\n            lock (_SyncLock)\n            {\n                if (!IsRunning || Cancelled)\n                    return;\n\n                IsRunning = false;                \n                _WaitHandle.Set();\n            }\n        }\n\n        /// <summary>\n        /// Causes the processing to stop. If the operation is still\n        /// active it will stop after the current message processing\n        /// completes\n        /// </summary>\n        public void Cancel()\n        {\n            lock (_SyncLock)\n            {\n                if (Cancelled)\n                    return;\n\n                IsRunning = false;\n                Cancelled = true;\n                _WaitHandle.Set();\n            }\n        }\n\n        public bool IsRunning { get; set; }\n        \n        /// <summary>\n        /// Runs the actual processing loop by checking the mail box\n        /// </summary>\n        private void Run()\n        {\n            // Start out waiting for the timeout period defined \n            // on the scheduler\n            _WaitHandle.WaitOne(CheckFrequency, true);\n\n\n            IsRunning = true;\n            while (!Cancelled && IsRunning)\n            {\n                try\n                {\n                    // Call whatever logic is attached to the scheduler\n                    OnExecuteScheduledEvent();\n                    ExecuteScheduledEventAction?.Invoke(this);\n                }\n                // always eat the exception and notify listener\n                catch (Exception ex)\n                {\n                    OnError(ex);\n                    ErrorAction?.Invoke(this, ex);\n                }\n\n                // If execution caused cancelling we want to exit now\n                if (Cancelled || !IsRunning)\n                    break;\n\n                // if a keep alive ping is required fire it\n                if (!string.IsNullOrEmpty(WebServerPingUrl))\n                    PingServer();\n\n                // Wait for the specified time out\n                _WaitHandle.WaitOne(CheckFrequency, true);\n            }\n\n            IsRunning = false;\n        }\n\n        /// <summary>\n        /// Handles a scheduled operation. Checks to see if an event handler\n        /// is set up and if so calls it. \n        /// \n        /// This method can also be overrriden in a subclass to implemnent\n        /// custom functionality.\n        /// </summary>\n        protected virtual void OnExecuteScheduledEvent()\n        {\n            if (ExecuteScheduledEvent != null)\n                ExecuteScheduledEvent(this, EventArgs.Empty);\n        }\n\n        /// <summary>\n        /// Event handler you can hook up to handle scheduled events\n        /// </summary>\n        public virtual Action<Scheduler>ExecuteScheduledEventAction { get; set; }\n\n        /// <summary>\n        /// This method is called if an error occurs during processing\n        /// of the OnExecuteScheduledEvent request\n        /// \n        /// Override this method in your own implementation to provide\n        /// for error logging or other handling of an error that occurred\n        /// in processing.\n        /// \n        /// Ideally this shouldn't be necessary - your OnexecuteScheduledEvent\n        /// code should handle any errors internally and provide for its own \n        /// logging mechanism but this is here as an additional point of\n        /// control.\n        /// </summary>\n        /// <param name=\"ex\">Exception occurred during item execution</param>\n        protected virtual void OnError(Exception ex)\n        {\n\n        }\n\n        /// <summary>\n        /// Event handler Action you can hook up to respond to errors.\n        /// Receives the Scheduler Exception that occurred during processing\n        /// as a parameter in addition to the scheduler instance.\n        /// </summary>\n        public virtual Action<Scheduler, Exception> ErrorAction { get; set; } \n\n        /// <summary>\n        /// Adds an item to the queue. \n        /// </summary>\n        /// <param name=\"item\">Any data you want to add to the local queue</param>\n        public void AddItem(object item)\n        {\n            lock (_SyncLock)\n            {\n                Items.Enqueue(item);\n            }\n        }\n\n        /// <summary>\n        /// Adds an item to the queue. \n        /// </summary>\n        /// <param name=\"item\">A specific Scheduler Item to add to the local queue</param>\n        public void AddItem(SchedulerItem item)\n        {\n            AddItem(item);\n        }\n\n        /// <summary>\n        /// Adds a text item to the queue with a specific identification type\n        /// </summary>\n        /// <param name=\"textData\"></param>\n        /// <param name=\"type\"></param>\n        public void AddItem(string textData, string type = null)\n        {\n            SchedulerItem item = new SchedulerItem();\n            item.TextData = textData;\n            item.Type = type;\n            AddItem(item as object);\n        }\n\n        /// <summary>\n        /// Adds a binary item to the queue with a specific identification type\n        /// </summary>\n        /// <param name=\"data\"></param>\n        /// <param name=\"type\"></param>\n        public void AddItem(byte[] data, string type = null)\n        {\n            SchedulerItem item = new SchedulerItem\n            {\n                Data = data,\n                Type = type\n            };\n            AddItem(item as object);\n        }\n\n        /// <summary>\n        /// Returns the next queued item or null on failure.\n        /// </summary>\n        /// <returns></returns>\n        public object GetNextItem()\n        {\n            lock (_SyncLock)\n            {\n                if (Items.Count > 0)\n                    return Items.Dequeue();\n            }\n\n            return null;\n        }\n\n        /// <summary>\n        /// Optional routine that pings a Url on the server\n        /// to keep the server alive. \n        /// \n        /// Use this to avoid IIS shutting down your AppPools\n        /// </summary>\n        public void PingServer(string url = null)\n        {\n            if (string.IsNullOrEmpty(url))\n                url = WebServerPingUrl;\n\n            //if (Url.StartsWith(\"~\") && HttpContext.Current != null)\n            //    Url = wwUtils.ResolveUrl(Url);\n\n            try\n            {\n#pragma warning disable SYSLIB0014\n                WebClient http = new WebClient();\n#pragma warning restore SYSLIB0014\n                string Result = http.DownloadString(url);\n            }\n            catch {}\n        }\n\n\n        #region IDisposable Members\n\n        public void Dispose()\n        {\n            Stop();\n        }\n\n        #endregion\n    }\n\n    /// <summary>\n    /// A simple item wrapper that allows separating items\n    /// by type.\n    /// </summary>\n    public class SchedulerItem\n    {\n        /// <summary>\n        /// Allows identifying items by type\n        /// </summary>\n        public string Type\n        {\n            get { return _Type; }\n            set { _Type = value; }\n        }\n        private string _Type = \"\";\n\n        /// <summary>\n        /// Any text data you want to submit\n        /// </summary>\n        public string TextData\n        {\n            get { return _TextData; }\n            set { _TextData = value; }\n        }\n        private string _TextData = \"\";\n\n        /// <summary>\n        /// Any binary data you want to submit\n        /// </summary>\n        public byte[] Data\n        {\n            get { return _Data; }\n            set { _Data = value; }\n        }\n        private byte[] _Data = null;\n\n\n        /// <summary>\n        /// The initial date when the item was\n        /// created or submitted.\n        /// </summary>\n        public DateTime Entered\n        {\n            get { return _Entered; }\n            set { _Entered = value; }\n        }\n        private DateTime _Entered = DateTime.UtcNow;\n    }\n\n}"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/StringSerializer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// A very simple flat object serializer that can be used \n    /// for intra application serialization. It creates a very compact\n    /// positional string of properties.\n    /// Only serializes top level properties, with no nesting support\n    /// and only simple properties or those with a type converter are\n    /// supported. Complex properties or non-two way type convertered\n    /// values are ignored.\n    /// \n    /// Creates strings in the format of:\n    /// Rick|rstrahl@west-wind.com|1|True|3/29/2013 1:32:31 PM|1\n    /// </summary>\n    /// <remarks>\n    /// This component is meant for intra application serialization of\n    /// very compact objects. A common use case is for state serialization\n    /// for cookies or a Forms Authentication ticket to minimize the amount\n    /// of space used - the output produced here contains only the actual\n    /// data, no property info or validation like other serialization formats.\n    /// Use only on small objects when size and speed matter otherwise use\n    /// a JSON/XML/Binary serializer or the ASP.NET LosFormatter object.\n    /// </remarks>\n    public static class StringSerializer\n    {\n        private const string Seperator_Replace_String = \"-@-\";\n\n        /// <summary>\n        /// Serializes a flat object's properties into a String\n        /// separated by a separator character/string. Only\n        /// top level properties are serialized.\n        /// </summary>\n        /// <remarks>\n        /// Only serializes top level properties, with no nesting support\n        /// and only simple properties or those with a type converter are\n        /// 'serialized'. All other property types use ToString().\n        /// </remarks>\n        /// <param name=\"objectToSerialize\">The object to serialize</param>\n        /// <param name=\"separator\">Optional separator character or string. Default is |</param>\n        /// <returns></returns>\n        public static string SerializeObject(object objectToSerialize, string separator = null)\n        {\n            if (separator == null)\n                separator = \"|\";\n\n            if (objectToSerialize == null)\n                return \"null\";\n\n            var properties =\n                objectToSerialize.GetType()\n                                 .GetProperties(BindingFlags.Instance | BindingFlags.Public);\n            //.OrderBy(prop => prop.Name.ToLower())\n            //.ToArray();\n\n            var values = new List<string>();\n\n            for (int i = 0; i < properties.Length; i++)\n            {\n                var pi = properties[i];\n\n                // don't store read/write-only data\n                if (!pi.CanRead && !pi.CanWrite)\n                    continue;\n\n                object value = pi.GetValue(objectToSerialize, null);\n\n                string stringValue = \"null\";\n                if (value != null)\n                {\n                    if (value is string)\n                    {\n                        stringValue = (string)value;\n                        if (stringValue.Contains(separator))\n                            stringValue = stringValue.Replace(separator, Seperator_Replace_String);\n                    }\n                    else\n                        stringValue = ReflectionUtils.TypedValueToString(value, unsupportedReturn: \"null\");\n                }\n\n                values.Add(stringValue);\n            }\n\n            if (values.Count < 0)\n                // empty object (no properties)\n                return string.Empty;\n\n            return string.Join(separator, values.ToArray());\n        }\n\n        /// <summary>\n        /// Deserializes an object previously serialized by SerializeObject.\n        /// </summary>\n        /// <param name=\"serialized\"></param>\n        /// <param name=\"type\"></param>\n        /// <param name=\"separator\"></param>\n        /// <returns></returns>\n        public static object DeserializeObject(string serialized, Type type, string separator = null)\n        {\n            if (serialized == \"null\")\n                return null;\n\n            if (separator == null)\n                separator = \"|\";\n\n            object inst = ReflectionUtils.CreateInstanceFromType(type);\n            var properties = inst.GetType()\n                                 .GetProperties(BindingFlags.Instance | BindingFlags.Public);\n            //.OrderBy(prop => prop.Name.ToLower())\n            //.ToArray();\n\n            string[] tokens = serialized.Split(new string[] { separator }, StringSplitOptions.None);\n            if (tokens == null || tokens.Length < 1)\n                return null;\n\n            for (int i = 0; i < properties.Length; i++)\n            {\n                string token = tokens[i];\n                var prop = properties[i];\n\n                // don't store read/write-only data\n                if (!prop.CanRead && !prop.CanWrite)\n                    continue;\n\n                if (token == null || token == \"null\")\n                    token = null;\n                else\n                    token = token.Replace(Seperator_Replace_String, separator);\n\n                object value = null;\n                if (token != null)\n                {\n                    try\n                    {\n                        value = ReflectionUtils.StringToTypedValue(token, prop.PropertyType);\n                    }\n                    catch (InvalidCastException)\n                    {\n                        // DANGER: skip over unsupported types \n                        continue;  // leave value at default\n                    }\n                }\n\n                prop.SetValue(inst, value, null);\n            }\n\n\n            return inst;\n        }\n\n        /// <summary>\n        /// Deserializes an object serialized with SerializeObject.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"serialized\"></param>\n        /// <param name=\"separator\"></param>\n        /// <returns></returns>\n        public static T Deserialize<T>(string serialized, string separator = null)\n            where T : class, new()\n        {\n            return DeserializeObject(serialized, typeof(T), separator) as T;\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/UrlEncodingParser.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\n\n\nnamespace Westwind.Utilities\n{\n\n    /// <summary>\n    /// A query string or UrlEncoded form parser and editor \n    /// class that allows reading and writing of urlencoded\n    /// key value pairs used for query string and HTTP \n    /// form data.\n    /// \n    /// Useful for parsing and editing querystrings inside\n    /// of non-Web code that doesn't have easy access to\n    /// the HttpUtility class.                \n    /// </summary>\n    /// <remarks>\n    /// Supports multiple values per key\n    /// </remarks>\n    public class UrlEncodingParser : NameValueCollection\n    {\n\n        /// <summary>\n        /// Holds the original Url that was assigned if any\n        /// Url must contain // to be considered a url\n        /// </summary>\n        private string Url { get; set; }\n\n        /// <summary>\n        /// Determines whether plus signs in the UrlEncoded content\n        /// are treated as spaces.\n        /// </summary>\n        public bool DecodePlusSignsAsSpaces { get; set; }\n\n        /// <summary>\n        /// override indexer to ensure we always non-null value\n        /// </summary>\n        public new string this[string key]\n        {\n            get\n            {\n                return base[key] ?? string.Empty;                \n            }\n            set\n            {\n                base[key] = value ?? string.Empty;\n            }\n        }\n\n\n        /// <summary>\n        /// Always pass in a UrlEncoded data or a URL to parse from\n        /// unless you are creating a new one from scratch.\n        /// </summary>\n        /// <param name=\"queryStringOrUrl\">\n        /// Pass a query string or raw Form data, or a full URL.\n        /// If a URL is parsed the part prior to the ? is stripped\n        /// but saved. Then when you write the original URL is \n        /// re-written with the new query string.\n        /// </param>\n        public UrlEncodingParser(string queryStringOrUrl = null, bool decodeSpacesAsPlusSigns = false)\n        {\n            Url = string.Empty;\n            DecodePlusSignsAsSpaces = decodeSpacesAsPlusSigns;\n            if (!string.IsNullOrEmpty(queryStringOrUrl))\n            {\n                Parse(queryStringOrUrl);\n            }\n        }\n\n\n        /// <summary>\n        /// Assigns multiple values to the same key\n        /// </summary>\n        /// <param name=\"key\"></param>\n        /// <param name=\"values\"></param>\n        public void SetValues(string key, IEnumerable<string> values)\n        {\n            foreach (var val in values)\n                Add(key, val);\n        }\n\n        /// <summary>\n        /// Parses the query string into the internal dictionary\n        /// and optionally also returns this dictionary\n        /// </summary>\n        /// <param name=\"query\">\n        /// Query string key value pairs or a full URL. If URL is\n        /// passed the URL is re-written in Write operation\n        /// </param>\n        /// <returns></returns>\n        public NameValueCollection Parse(string query)\n        {\n            if (string.IsNullOrEmpty(query))\n            {\n                Clear();\n                return this;\n            }\n\n            if (Uri.IsWellFormedUriString(query, UriKind.Absolute))\n                Url = query;\n\n\n\n            int index = query.IndexOf('?');\n            if (index > -1)\n            {\n                if (query.Length >= index + 1)\n                    query = query.Substring(index + 1);\n            }\n\n            var pairs = query.Split('&');\n            foreach (var pair in pairs)\n            {\n                int index2 = pair.IndexOf('=');\n                if (index2 > 0)\n                {\n                    var val = pair.Substring(index2 + 1);\n                    if (!string.IsNullOrEmpty(val))\n                    {\n                        if (DecodePlusSignsAsSpaces)\n                            val = val.Replace(\"+\", \" \");\n                        val = Uri.UnescapeDataString(val);\n                    }\n\n                    Add(pair.Substring(0, index2), val);\n                }\n            }\n\n            return this;\n        }\n\n        /// <summary>\n        /// Writes out the urlencoded data/query string or full URL based \n        /// on the internally set values.\n        /// </summary>\n        /// <returns>urlencoded data or url</returns>\n        public override string ToString()\n        {\n            string query = string.Empty;\n            foreach (string key in Keys)\n            {\n                string[] values = GetValues(key);\n                if (values == null)\n                    continue;\n\n                foreach (var val in values)\n                {\n                    if (string.IsNullOrEmpty(val)) continue;\n\n                    query += key + \"=\" + Uri.EscapeDataString(val) + \"&\";\n                }\n            }\n            query = query.Trim('&');\n\n            if (!string.IsNullOrEmpty(Url))\n            {\n                if (Url.Contains(\"?\"))\n                    query = Url.Substring(0, Url.IndexOf('?') + 1) + query;\n                else\n                    query = Url + \"?\" + query;\n            }\n\n            return query;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/SupportClasses/UrlParser.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.IO;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Web;\nusing System.Globalization;\n\nnamespace Westwind.Utilities\n{\n\n    /// <summary>\n    /// Internally used class that is used to expand links in text\n    /// strings.\n    /// </summary>\n    public class UrlParser\n    {\n        internal string Target = string.Empty;\n        internal bool ParseFormattedLinks = false;\n\n        /// <summary>\n        /// Expands links into HTML hyperlinks inside of text or HTML.\n        /// </summary>\n        /// <param name=\"text\">The text to expand</param>\n        /// <param name=\"target\">Target frame where links are displayed</param>\n        /// <param name=\"parseFormattedLinks\">Allows parsing of links in the following format [text|www.site.com]</param>\n        /// <returns></returns>\n        public static string ExpandUrls(string text, string target = null, bool parseFormattedLinks = false)\n        {\n            if (target == null)\n                target = string.Empty;\n\n            UrlParser Parser = new UrlParser();\n            Parser.Target = target;\n            Parser.ParseFormattedLinks = parseFormattedLinks;\n\n            return Parser.ExpandUrlsInternal(text);\n        }\n\n        /// <summary>\n        /// Expands links into HTML hyperlinks inside of text or HTML.\n        /// </summary>\n        /// <param name=\"text\">The text to expand</param>    \n        /// <returns></returns>\n        private string ExpandUrlsInternal(string text)\n        {\n            MatchEvaluator matchEval = null;\n            string pattern = null;\n            string updated = null;\n\n\n            // Expand embedded hyperlinks\n            System.Text.RegularExpressions.RegexOptions options =\n                                                                  RegexOptions.Multiline |\n                                                                  RegexOptions.IgnoreCase;\n            if (ParseFormattedLinks)\n            {\n                pattern = @\"\\[(.*?)\\|(.*?)]\";\n\n                matchEval = new MatchEvaluator(ExpandFormattedLinks);\n                updated = Regex.Replace(text, pattern, matchEval, options);\n            }\n            else\n                updated = text;\n\n            pattern = @\"([\"\"'=]|&quot;)?(http://|ftp://|https://|www\\.|ftp\\.[\\w]+)([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])\";\n\n            matchEval = new MatchEvaluator(ExpandUrlsRegExEvaluator);\n            updated = Regex.Replace(updated, pattern, matchEval, options);\n\n\n\n            return updated;\n        }\n\n        /// <summary>\n        /// Internal RegExEvaluator callback\n        /// </summary>\n        /// <param name=\"M\"></param>\n        /// <returns></returns>\n        private string ExpandUrlsRegExEvaluator(System.Text.RegularExpressions.Match M)\n        {\n            string Href = M.Value; // M.Groups[0].Value;\n\n            // if string starts within an HREF don't expand it\n            if (Href.StartsWith(\"=\") ||\n                Href.StartsWith(\"'\") ||\n                Href.StartsWith(\"\\\"\") ||\n                Href.StartsWith(\"&quot;\"))\n                return Href;\n\n            string Text = Href;\n\n            if (Href.IndexOf(\"://\") < 0)\n            {\n                if (Href.StartsWith(\"www.\"))\n                    Href = \"http://\" + Href;\n                else if (Href.StartsWith(\"ftp\"))\n                    Href = \"ftp://\" + Href;\n                else if (Href.IndexOf(\"@\") > -1)\n                    Href = \"mailto:\" + Href;\n            }\n\n            string Targ = !string.IsNullOrEmpty(Target) ? \" target='\" + Target + \"'\" : string.Empty;\n\n            return \"<a href='\" + Href + \"'\" + Targ +\n                    \">\" + Text + \"</a>\";\n        }\n\n        private string ExpandFormattedLinks(System.Text.RegularExpressions.Match M)\n        {\n            //string Href = M.Value; // M.Groups[0].Value;\n\n            string Text = M.Groups[1].Value;\n            string Href = M.Groups[2].Value;\n\n            if (Href.IndexOf(\"://\") < 0)\n            {\n                if (Href.StartsWith(\"www.\"))\n                    Href = \"http://\" + Href;\n                else if (Href.StartsWith(\"ftp\"))\n                    Href = \"ftp://\" + Href;\n                else if (Href.IndexOf(\"@\") > -1)\n                    Href = \"mailto:\" + Href;\n                else\n                    Href = \"http://\" + Href;\n            }\n\n            string Targ = !string.IsNullOrEmpty(Target) ? \" target='\" + Target + \"'\" : string.Empty;\n\n            return \"<a href='\" + Href + \"'\" + Targ +\n                    \">\" + Text + \"</a>\";\n        }\n\n\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/AsyncUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Helper class to run async methods within a sync process.\n    /// Source: https://www.ryadel.com/en/asyncutil-c-helper-class-async-method-sync-result-wait/\n    /// </summary>\n    public static class AsyncUtils\n    {\n        private static readonly TaskFactory _taskFactory = new\n            TaskFactory(CancellationToken.None,\n                TaskCreationOptions.None,\n                TaskContinuationOptions.None,\n                TaskScheduler.Default);\n\n        /// <summary>\n        /// Executes an async Task method which has a void return value synchronously\n        /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());\n        /// </summary>\n        /// <param name=\"task\">Task method to execute</param>\n        public static void RunSync(Func<Task> task)\n            => _taskFactory\n                .StartNew(task)\n                .Unwrap()\n                .GetAwaiter()\n                .GetResult();\n\n        /// <summary>\n        /// Executes an async Task method which has a void return value synchronously\n        /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());\n        /// </summary>\n        /// <param name=\"task\">Task method to execute</param>\n        public static void RunSync(Func<Task> task,\n                    CancellationToken cancellationToken,\n                    TaskCreationOptions taskCreation = TaskCreationOptions.None,\n                    TaskContinuationOptions taskContinuation = TaskContinuationOptions.None,\n                    TaskScheduler taskScheduler = null)\n        {\n            if (taskScheduler == null)\n                taskScheduler = TaskScheduler.Default;\n\n            new TaskFactory(cancellationToken,\n                    taskCreation,\n                    taskContinuation,\n                    taskScheduler)\n                .StartNew(task)\n                .Unwrap()\n                .GetAwaiter()\n                .GetResult();\n        }\n\n        /// <summary>\n        /// Executes an async Task&lt;T&gt; method which has a T return type synchronously\n        /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod&lt;T&gt;());\n        /// </summary>\n        /// <typeparam name=\"TResult\">Return Type</typeparam>\n        /// <param name=\"task\">Task&lt;T&gt; method to execute</param>\n        /// <returns></returns>\n        public static TResult RunSync<TResult>(Func<Task<TResult>> task)\n            => _taskFactory\n                .StartNew(task)\n                .Unwrap()\n                .GetAwaiter()\n                .GetResult();\n\n\n        /// <summary>\n        /// Executes an async Task&lt;T&gt; method which has a T return type synchronously\n        /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod&lt;T&gt;());\n        /// </summary>\n        /// <typeparam name=\"TResult\">Return Type</typeparam>\n        /// <param name=\"func\">Task&lt;T&gt; method to execute</param>\n        /// <returns></returns>\n        public static TResult RunSync<TResult>(Func<Task<TResult>> func,\n            CancellationToken cancellationToken,\n            TaskCreationOptions taskCreation = TaskCreationOptions.None,\n            TaskContinuationOptions taskContinuation = TaskContinuationOptions.None,\n            TaskScheduler taskScheduler = null)\n        {\n            if (taskScheduler == null)\n                taskScheduler = TaskScheduler.Default;\n\n            return new TaskFactory(cancellationToken,\n                    taskCreation,\n                    taskContinuation,\n                    taskScheduler)\n                .StartNew(func, cancellationToken)\n                .Unwrap()\n                .GetAwaiter()\n                .GetResult();\n        }\n\n\n        /// <summary>\n        /// Ensures safe operation of a task without await even if\n        /// an execution fails with an exception. This forces the\n        /// exception to be cleared unlike a non-continued task.\n        /// \n        /// Exceptions are silently ignored.\n        /// </summary>\n        /// <param name=\"t\">Task Instance</param>\n        public static void FireAndForget(this Task t)\n        {\n            t.ContinueWith(tsk => tsk.Exception,\n                TaskContinuationOptions.OnlyOnFaulted);\n        }\n\n\n        /// <summary>\n        /// Ensures safe operation of a task without await even if\n        /// an execution fails with an exception. This forces the\n        /// exception to be cleared unlike a non-continued task.\n        /// \n        /// This version allows you to capture and respond to any\n        /// exceptions caused by the Task code executing.\n        /// </summary>\n        /// <param name=\"t\"></param>\n        /// <param name=\"del\">Action delegate that receives an Exception parameter you can use to log or otherwise handle (or ignore) any exceptions</param>\n        public static void FireAndForget(this Task t, Action<Exception> del)\n        {\n            t.ContinueWith((tsk) => del?.Invoke(tsk.Exception), TaskContinuationOptions.OnlyOnFaulted);\n        }\n\n        /// <summary>\n        /// Wait for a task to complete with a timeout\n        /// \n        /// Exceptions are thrown as normal if the task fails as\n        /// </summary>\n        /// <param name=\"task\">Task to wait on</param>\n        /// <param name=\"timeoutMs\">timeout to allow</param>\n        /// <returns>True if completed in time, false if timed out. If true task is completed and you can read the result</returns>\n        /// <exception cref=\"Exception\">Any exceptions thrown by the task code</exception>\"\n        public static async Task<bool> Timeout(this Task task, int timeoutMs)\n        {\n            var completed = await Task.WhenAny(task, Task.Delay(timeoutMs));\n            \n            if (task.IsFaulted)\n                throw task.Exception.GetBaseException();\n\n            return completed == task && task.IsCompleted;\n        }\n\n\n        /// <summary>\n        /// Wait for a task with a result and a timeout. If the task times out an\n        /// the `default` value is returned.        \n        /// </summary>        \n        /// <param name=\"task\">Task to wait on</param>\n        /// <param name=\"timeoutMs\">timeout to allow</param>        \n        /// <returns>Task result on success. Or exceptions for timeout or task operation exception</returns>\n        /// <exception cref=\"TimeoutException\">Thrown if the task times out</exception>\n        /// <exception cref=\"Exception\">Any exceptions thrown by the task code</exception>\n        public static async Task<TResult> TimeoutWithResult<TResult>(this Task<TResult> task, int timeoutMs)\n        {                       \n            var completed = await Task.WhenAny(task, Task.Delay(timeoutMs));     \n            \n            if (task.IsFaulted)\n                throw task.Exception.GetBaseException();\n\n            if (completed == task && task.IsCompleted)\n            {\n                return task.Result;\n            }\n\n            throw new TimeoutException();\n        }\n\n        /// <summary>\n        /// Executes an Action after a delay\n        /// </summary>\n        /// <remarks>\n        /// Code is executed on a background thread, so if UI code is executed\n        /// make sure you marshal back to the UI thread using a Dispatcher or Control.Invoke().\n        /// </remarks>\n        /// <param name=\"delayMs\">delay in Milliseconds</param>\n        /// <param name=\"action\">Action to execute after delay</param>\n        public static void DelayExecution(int delayMs, Action action, Action<Exception> errorHandler = null)\n        {\n            var t = new System.Timers.Timer();\n            t.Interval = delayMs;\n            t.AutoReset = false;\n            t.Elapsed += (s, e) =>\n            {\n                t.Stop();\n                try\n                {\n                    action.Invoke();\n                }\n                catch(Exception ex)\n                {\n                    errorHandler?.Invoke(ex);\n                }\n                t.Dispose();\n            };\n            t.Start();\n        }\n\n        /// <summary>\n        /// Executes an Action after a delay with a parameter\n        /// </summary>\n        /// <remarks>\n        /// Code is executed on a background thread, so if UI code is executed\n        /// make sure you marshal back to the UI thread using a Dispatcher or Control.Invoke().\n        /// </remarks>\n        /// <param name=\"delayMs\">delay in Milliseconds</param>\n        /// <param name=\"action\">Action to execute after delay</param>\n        public static void DelayExecution<T>(int delayMs, Action<T> action, T parm = default, Action<Exception> errorHandler = null)\n        {\n            var t = new System.Timers.Timer();\n            t.Interval = delayMs;\n            t.AutoReset = false;\n            t.Elapsed += (s, e) =>\n            {\n                t.Stop();\n                try\n                {\n                    action.Invoke(parm);\n                }\n                catch(Exception ex)\n                {\n                    errorHandler?.Invoke(ex);\n                }\n                t.Dispose();\n            };\n            t.Start();\n            return;\n        }\n    }\n\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/ComObject.cs",
    "content": "﻿using System;\nusing System.Dynamic;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\n\nnamespace Westwind.Utilities\n{\n\n    /// <summary>\n    /// Wrapper around a COM object that allows 'dynamic' like behavior to\n    /// work in .NET Core where dynamic with COM objects is not working. This\n    ///\n    /// Credit to: https://github.com/bubibubi/EntityFrameworkCore.Jet/blob/3.1-preview/src/System.Data.Jet/ComObject.cs\n    /// Added here with slight interface modifications\n    /// </summary>\n    #if NET6_0_OR_GREATER\n    [SupportedOSPlatform(\"windows\")]\n    #endif\n    public class ComObject : DynamicObject, IDisposable\n    {\n        internal object _instance;\n\n#if DEBUG\n        private readonly Guid Id = Guid.NewGuid();\n#endif\n\n        /// <summary>\n        /// Pass a COM Object reference to create this COM Object wrapper\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        public ComObject(object instance)\n        {\n            if (instance is ComObject)\n                _instance = ((ComObject)instance)._instance;\n\n            _instance = instance;\n        }\n\n        /// <summary>\n        /// Create a new instance based on ProgId\n        /// </summary>\n        /// <param name=\"progid\"></param>\n        /// <returns></returns>\n        public static ComObject CreateFromProgId(string progid)\n        {\n            var type = Type.GetTypeFromProgID(progid, false);\n            if (type != null)\n            {\n                var instance = Activator.CreateInstance(type);\n                if (instance != null)\n                {\n                    return new ComObject(instance);\n                }\n            }\n\n            throw new TypeLoadException(\"Couldn't create COM Wrapper for: \" + progid);\n        }\n\n        /// <summary>\n        /// Create a new instance based on ClassId\n        /// </summary>\n        /// <param name=\"clsid\">Guid class Id</param>\n        /// <returns></returns>\n        public static ComObject CreateFirstFrom(Guid clsid)\n        {\n            var type = Type.GetTypeFromCLSID(clsid, false);\n            if (type != null)\n            {\n                var instance = Activator.CreateInstance(type);\n                if (instance != null)\n                {\n                    return new ComObject(instance);\n                }\n            }\n\n            throw new TypeLoadException(\"Couldn't create COM Wrapper for: \" + clsid);\n        }\n\n\n        /// <summary>\n        /// Creates a new instance based on a ProgId\n        /// </summary>\n        /// <param name=\"progid\"></param>\n        public ComObject(string progid)\n            : this(Activator.CreateInstance(Type.GetTypeFromProgID(progid, true)))\n        {\n        }\n\n        /// <summary>\n        /// Creates an instance based on a class Id\n        /// </summary>\n        /// <param name=\"clsid\"></param>\n        public ComObject(Guid clsid)\n            : this(Activator.CreateInstance(Type.GetTypeFromCLSID(clsid, true)))\n        {\n        }\n\n        /// <summary>\n        /// Removes the COM reference linkage from this object\n        /// </summary>\n        /// <returns></returns>\n        public object Detach()\n        {\n            var instance = _instance;\n            _instance = null;\n            return instance;\n        }\n\n\n        #region DynamicObject implementation\n\n        public override bool TryGetMember(GetMemberBinder binder, out object result)\n        {\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        binder.Name,\n                        BindingFlags.GetProperty,\n                        Type.DefaultBinder,\n                        _instance,\n                        new object[0]\n                    ));\n            return true;\n        }\n\n\n        public override bool TrySetMember(SetMemberBinder binder, object value)\n        {\n            _instance.GetType()\n                .InvokeMember(\n                    binder.Name,\n                    BindingFlags.SetProperty,\n                    Type.DefaultBinder,\n                    _instance,\n                    new[]\n                    {\n                            value is ComObject comObject\n                                ? comObject._instance\n                                : value\n                    }\n                );\n            return true;\n        }\n\n        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n        {\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        binder.Name,\n                        BindingFlags.InvokeMethod,\n                        Type.DefaultBinder,\n                        _instance,\n                        args\n                    ));\n            return true;\n        }\n\n        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\n        {\n            // This should work for all specific interfaces derived from `_Collection` (like `_Tables`) in ADOX.\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        \"Item\",\n                        BindingFlags.GetProperty,\n                        Type.DefaultBinder,\n                        _instance,\n                        indexes\n                    ));\n            return true;\n        }\n\n        #endregion\n\n\n\n        // See https://github.com/dotnet/runtime/issues/12587#issuecomment-578431424\n        \n        /// <summary>\n        /// Wrap any embedded raw COM objects in a new ComObject wrapper as well\n        /// when returned from members or method results.\n        /// </summary>\n        /// <param name=\"obj\"></param>\n        /// <returns></returns>\n        private static object WrapIfRequired(object obj)\n            => obj != null && obj.GetType().IsCOMObject ? new ComObject(obj) : obj;\n\n        public void Dispose()\n        {\n            // The RCW is a .NET object and cannot be released from the finalizer,\n            // because it might not exist anymore.\n            if (_instance != null)\n            {\n                Marshal.ReleaseComObject(_instance);\n                _instance = null;\n            }\n\n            GC.SuppressFinalize(this);\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/DataUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          � West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Data;\nusing System.Linq;\n\n\n\nusing System.Reflection;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Configuration;\nusing Westwind.Utilities.Properties;\nusing System.Data.Common;\nusing System.IO;\n\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Utility library for common data operations.\n    /// </summary>\n    public static class DataUtils\n    {\n        const BindingFlags MemberAccess =\n            BindingFlags.Public | BindingFlags.NonPublic |\n            BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase;\n\n        //const BindingFlags MemberPublicInstanceAccess =\n        //    BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;\n\n        #region Unique Ids and Random numbers\n        /// <summary>\n        /// Generates a unique Id as a string of up to 16 characters.\n        /// Based on a GUID and the size takes that subset of a the\n        /// Guid's 16 bytes to create a string id.\n        /// \n        /// String Id contains numbers and lower case alpha chars 36 total.\n        /// \n        /// Sizes: 6 gives roughly 99.97% uniqueness. \n        ///        8 gives less than 1 in a million doubles.\n        ///        16 will give near full GUID strength uniqueness\n        /// </summary>\n        /// <param name=\"stringSize\">Number of characters to generate between 5 and 16</param>\n        /// <param name=\"additionalCharacters\">Any additional characters you allow in the string. \n        /// You can add upper case letters and symbols which are not included in the default\n        /// which includes only digits and lower case letters.\n        /// </param>\n        /// <returns></returns>        \n        public static string GenerateUniqueId(int stringSize = 8, string additionalCharacters = null)\n        {\n            string chars = \"abcdefghijkmnopqrstuvwxyz1234567890\" + additionalCharacters;\n            StringBuilder result = new StringBuilder(stringSize);\n            int count = 0;\n\n            if (stringSize < 5) stringSize = 5;\n\n            byte[] array = Guid.NewGuid().ToByteArray();\n            for (int i = array.Length - 1; i >= 0; i--)\n            {\n                byte b = array[i];\n                result.Append(chars[b % (chars.Length)]);\n                count++;\n                if (count >= stringSize)\n                    break;\n            }\n            return result.ToString();\n        }\n\n\n        ///<summary>\n        /// Generates a unique numeric ID. Generated off a GUID and\n        /// returned as a 64 bit long value\n        /// </summary>\n        /// <returns></returns>\n        public static long GenerateUniqueNumericId()\n        {\n            byte[] bytes = Guid.NewGuid().ToByteArray();\n            return (long)BitConverter.ToUInt64(bytes, 0);\n        }\n\n        private static Random rnd = new Random();\n\n        /// <summary>\n        /// Returns a random integer in a range of numbers\n        /// a single seed value.\n        /// </summary>\n        /// <param name=\"min\">The minimum value</param>\n        /// <param name=\"max\">The max value *including* this value (unlike Random.Next() which doesn't include it)</param>\n        /// <returns></returns>\n        public static int GetRandomNumber(int min, int max)\n        {\n            return rnd.Next(min, max + 1);\n        }\n\n        #endregion\n\n        #region Byte Data\n\n        /// <summary>\n        /// Returns an index into a byte array to find sequence of\n\t\t/// of bytes.\n\t\t/// Note: You can use Span.IndexOf() where available instead.\t\t\n        /// </summary>\n        /// <param name=\"buffer\">byte array to be searched</param>\n        /// <param name=\"bufferToFind\">bytes to find</param>\n        /// <returns></returns>\n        public static int IndexOfByteArray(byte[] buffer, byte[] bufferToFind)\n        {\n            if (buffer.Length == 0 || bufferToFind.Length == 0)\n                return -1;\n\n            for (int i = 0; i < buffer.Length; i++)\n            {\n                if (buffer[i] == bufferToFind[0])\n                {\n                    bool innerMatch = true;\n                    for (int j = 1; j < bufferToFind.Length; j++)\n                    {\n                        if (buffer[i + j] != bufferToFind[j])\n                        {\n                            innerMatch = false;\n                            break;\n                        }\n                    }\n                    if (innerMatch)\n                        return i;\n                }\n            }\n\n            return -1;\n        }\n\n        /// <summary>\n        /// Returns an index into a byte array to find a string in the byte array.\n        /// Exact match using the encoding provided or UTF-8 by default.\n        /// </summary>\n        /// <param name=\"buffer\">Source buffer to look for string</param>\n        /// <param name=\"stringToFind\">string to search for (case sensitive)</param>\n        /// <param name=\"encoding\">Optional encoding to use - defaults to UTF-8 if null</param>\n        /// <returns></returns>\n        public static int IndexOfByteArray(byte[] buffer, string stringToFind, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.UTF8;\n\n            if (buffer.Length == 0 || string.IsNullOrEmpty(stringToFind))\n                return -1;\n\n            var bytes = encoding.GetBytes(stringToFind);\n\n            return IndexOfByteArray(buffer, bytes);\n        }\n\n        /// <summary>\n        /// Removes a sequence of bytes from a byte array\n        /// </summary>\n        /// <param name=\"buffer\"></param>\n        /// <param name=\"bytesToRemove\"></param>\n        /// <returns></returns>\n        public static byte[] RemoveBytes(byte[] buffer, byte[] bytesToRemove)\n        {\n            if (buffer == null || buffer.Length == 0 ||\n                bytesToRemove == null || bytesToRemove.Length == 0)\n                return buffer;\n\n            using (var ms = new MemoryStream())\n            using(var bw = new BinaryWriter(ms))\n            {\n                var firstByte = bytesToRemove[0];\n                \n                for (int i = 0; i < buffer.Length; i++)\n                {\n                    var current = buffer[i];\n                    if (current == firstByte && buffer.Length >= i + bytesToRemove.Length)\n                    {\n                        bool found = true;\n                        for (int y = 1; y < bytesToRemove.Length; y++)\n                        {\n                            if (buffer[i + y] != bytesToRemove[y])\n                            {\n                                found = false;\n                                break;\n                            }\n                        }\n\n                        if (found)\n                            i += bytesToRemove.Length -1; // skip over\n                        else\n                            bw.Write(current);\n                    }\n                    else\n                        bw.Write(current);\n\n                }\n                \n                return ms.ToArray();\n            }\n        }\n\n        #endregion\n\n        #region Copying Objects and Data\n\n        /// <summary>\n        /// Copies the content of a data row to another. Runs through the target's fields\n        /// and looks for fields of the same name in the source row. Structure must mathc\n        /// or fields are skipped.\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"target\"></param>\n        /// <returns></returns>\n        public static bool CopyDataRow(DataRow source, DataRow target)\n        {\n            DataColumnCollection columns = target.Table.Columns;\n\n            for (int x = 0; x < columns.Count; x++)\n            {\n                string fieldname = columns[x].ColumnName;\n\n                try\n                {\n                    target[x] = source[fieldname];\n                }\n                catch {; }  // skip any errors\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Populates an object passed in from values in\n        /// a data row that's passed in.\n        /// </summary>\n        /// <param name=\"row\">Data row with values to fill from</param>\n        /// <param name=\"targetObject\">Object to file values from data row</param>\n        public static void CopyObjectFromDataRow(DataRow row, object targetObject, MemberInfo[] cachedMemberInfo = null)\n        {\n            if (cachedMemberInfo == null)\n            {\n                cachedMemberInfo = targetObject.GetType()\n                    .FindMembers(MemberTypes.Field | MemberTypes.Property,\n                        ReflectionUtils.MemberAccess, null, null);\n            }\n            foreach (MemberInfo Field in cachedMemberInfo)\n            {\n                string Name = Field.Name;\n                if (!row.Table.Columns.Contains(Name))\n                    continue;\n\n                object value = row[Name];\n                if (value == DBNull.Value)\n                    value = null;\n\n                if (Field.MemberType == MemberTypes.Field)\n                {\n                    ((FieldInfo)Field).SetValue(targetObject, value);\n                }\n                else if (Field.MemberType == MemberTypes.Property)\n                {\n                    ((PropertyInfo)Field).SetValue(targetObject, value, null);\n                }\n            }\n        }\n\n        /// <summary>\n        /// Copies the content of an object to a DataRow with matching field names.\n        /// Both properties and fields are copied. If a field copy fails due to a\n        /// type mismatch copying continues but the method returns false\n        /// </summary>\n        /// <param name=\"row\"></param>\n        /// <param name=\"target\"></param>\n        /// <returns></returns>\n        public static bool CopyObjectToDataRow(DataRow row, object target)\n        {\n            bool result = true;\n\n            MemberInfo[] miT = target.GetType().FindMembers(MemberTypes.Field | MemberTypes.Property, MemberAccess, null, null);\n            foreach (MemberInfo Field in miT)\n            {\n                string name = Field.Name;\n                if (!row.Table.Columns.Contains(name))\n                    continue;\n\n                try\n                {\n                    if (Field.MemberType == MemberTypes.Field)\n                    {\n                        row[name] = ((FieldInfo)Field).GetValue(target) ?? DBNull.Value;\n                    }\n                    else if (Field.MemberType == MemberTypes.Property)\n                    {\n                        row[name] = ((PropertyInfo)Field).GetValue(target, null) ?? DBNull.Value;\n                    }\n                }\n                catch { result = false; }\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Coverts a DataTable to a typed list of items\n        /// </summary>\n        /// <typeparam name=\"T\">Type to </typeparam>\n        /// <param name=\"dsTable\"></param>\n        /// <returns></returns>\n        public static List<T> DataTableToTypedList<T>(DataTable dsTable) where T : class, new()\n        {\n            var objectList = new List<T>();\n\n            MemberInfo[] cachedMemberInfo = null;\n            foreach (DataRow dr in dsTable.Rows)\n            {\n                var obj = default(T); // Activator.CreateInstance<T>();\t\t\t\t\n                CopyObjectFromDataRow(dr, obj, cachedMemberInfo);\n                objectList.Add(obj);\n            }\n\n            return objectList;\n        }\n\n\n\n\n        /// <summary>\n        /// Copies the content of one object to another. The target object 'pulls' properties of the first. \n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"target\"></param>\n        public static void CopyObjectData(object source, Object target)\n        {\n            CopyObjectData(source, target, MemberAccess);\n        }\n\n        /// <summary>\n        /// Copies the content of one object to another. The target object 'pulls' properties of the first. \n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"target\"></param>\n        /// <param name=\"memberAccess\"></param>\n        public static void CopyObjectData(object source, Object target, BindingFlags memberAccess)\n        {\n            CopyObjectData(source, target, null, memberAccess);\n        }\n\n        /// <summary>\n        /// Copies the content of one object to another. The target object 'pulls' properties of the first. \n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"target\"></param>\n        /// <param name=\"excludedProperties\"></param>\n        public static void CopyObjectData(object source, Object target, string excludedProperties)\n        {\n            CopyObjectData(source, target, excludedProperties, MemberAccess);\n        }\n\n        /// <summary>\n        /// Copies the data of one object to another. The target object 'pulls' properties of the first. \n        /// This any matching properties are written to the target.\n        /// \n        /// The object copy is a shallow copy only. Any nested types will be copied as \n        /// whole values rather than individual property assignments (ie. via assignment)\n        /// </summary>\n        /// <param name=\"source\">The source object to copy from</param>\n        /// <param name=\"target\">The object to copy to</param>\n        /// <param name=\"excludedProperties\">A comma delimited list of properties that should not be copied</param>\n        /// <param name=\"memberAccess\">Reflection binding access</param>\n        public static void CopyObjectData(object source, object target, string excludedProperties = null, BindingFlags memberAccess = MemberAccess)\n        {\n            string[] excluded = null;\n            if (!string.IsNullOrEmpty(excludedProperties))\n                excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);\n\n            MemberInfo[] miT = target.GetType().GetMembers(memberAccess);\n            foreach (MemberInfo Field in miT)\n            {\n                string name = Field.Name;\n\n                // Skip over any property exceptions\n                if (!string.IsNullOrEmpty(excludedProperties) &&\n                    excluded.Contains(name))\n                    continue;\n\n                if (Field.MemberType == MemberTypes.Field)\n                {\n                    FieldInfo SourceField = source.GetType().GetField(name);\n                    if (SourceField == null)\n                        continue;\n\n                    object SourceValue = SourceField.GetValue(source);\n                    ((FieldInfo)Field).SetValue(target, SourceValue);\n                }\n                else if (Field.MemberType == MemberTypes.Property)\n                {\n                    PropertyInfo piTarget = Field as PropertyInfo;\n                    PropertyInfo SourceField = source.GetType().GetProperty(name, memberAccess);\n                    if (SourceField == null)\n                        continue;\n\n                    if (piTarget.CanWrite && SourceField.CanRead)\n                    {\n                        object SourceValue = SourceField.GetValue(source, null);\n                        piTarget.SetValue(target, SourceValue, null);\n                    }\n                }\n            }\n        }\n        #endregion\n\n        #region DataTable and DataReader\n\n        /// <summary>\n        /// Coverts a DataTable to a typed list of items\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"dsTable\"></param>\n        /// <returns></returns>\n        public static List<T> DataTableToObjectList<T>(DataTable dsTable) where T : class, new()\n        {\n            var objectList = new List<T>();\n\n            foreach (DataRow dr in dsTable.Rows)\n            {\n                var obj = Activator.CreateInstance<T>();\n                CopyObjectFromDataRow(dr, obj);\n                objectList.Add(obj);\n            }\n\n            return objectList;\n        }\n\n\n        /// <summary>\n        /// Creates a list of a given type from all the rows in a DataReader.\n        /// \n        /// Note this method uses Reflection so this isn't a high performance\n        /// operation, but it can be useful for generic data reader to entity\n        /// conversions on the fly and with anonymous types.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"reader\">An open DataReader that's in position to read</param>\n        /// <param name=\"propertiesToSkip\">Optional - comma delimited list of fields that you don't want to update</param>\n        /// <param name=\"piList\">\n        /// Optional - Cached PropertyInfo dictionary that holds property info data for this object.\n        /// Can be used for caching hte PropertyInfo structure for multiple operations to speed up\n        /// translation. If not passed automatically created.\n        /// </param>\n        /// <returns></returns>\n        /// <remarks>DataReader is not closed by this method. Make sure you call reader.close() afterwards</remarks>\n        public static List<T> DataReaderToObjectList<T>(IDataReader reader, string propertiesToSkip = null, Dictionary<string, PropertyInfo> piList = null)\n            where T : new()\n        {\n            List<T> list = new List<T>();\n\n            using (reader)\n            {\n                // Get a list of PropertyInfo objects we can cache for looping            \n                if (piList == null)\n                {\n                    piList = new Dictionary<string, PropertyInfo>();\n                    var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);\n                    foreach (var prop in props)\n                        piList.Add(prop.Name.ToLower(), prop);\n                }\n\n                while (reader.Read())\n                {\n                    T inst = new T();\n                    DataReaderToObject(reader, inst, propertiesToSkip, piList);\n                    list.Add(inst);\n                }\n            }\n\n            return list;\n        }\n\n        /// <summary>\n        /// Creates an IEnumerable of T from an open DataReader instance.\n        ///\n        /// Note this method uses Reflection so this isn't a high performance\n        /// operation, but it can be useful for generic data reader to entity\n        /// conversions on the fly and with anonymous types.\n        /// </summary>\n        /// <param name=\"reader\">An open DataReader that's in position to read</param>\n        /// <param name=\"propertiesToSkip\">Optional - comma delimited list of fields that you don't want to update</param>\n        /// <param name=\"piList\">\n        /// Optional - Cached PropertyInfo dictionary that holds property info data for this object.\n        /// Can be used for caching hte PropertyInfo structure for multiple operations to speed up\n        /// translation. If not passed automatically created.\n        /// </param>\n        /// <returns></returns>\n        public static IEnumerable<T> DataReaderToIEnumerable<T>(IDataReader reader, string propertiesToSkip = null, Dictionary<string, PropertyInfo> piList = null)\n            where T : new()\n        {\n            if (reader != null)\n            {\n                using (reader)\n                {\n                    // Get a list of PropertyInfo objects we can cache for looping            \n                    if (piList == null)\n                    {\n                        piList = new Dictionary<string, PropertyInfo>();\n                        var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);\n                        foreach (var prop in props)\n                            piList.Add(prop.Name.ToLower(), prop);\n                    }\n\n                    while (reader.Read())\n                    {\n                        T inst = new T();\n                        DataReaderToObject(reader, inst, propertiesToSkip, piList);\n                        yield return inst;\n                    }\n                }\n            }\n        }\n\n        public static List<T> DataReaderToList<T>(IDataReader reader, string propertiesToSkip = null, Dictionary<string, PropertyInfo> piList = null)\n          where T : new()\n        {\n            var list = new List<T>();\n\n            if (reader != null)\n            {\n                using (reader)\n                {\n                    // Get a list of PropertyInfo objects we can cache for looping            \n                    if (piList == null)\n                    {\n                        piList = new Dictionary<string, PropertyInfo>();\n                        var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);\n                        foreach (var prop in props)\n                            piList.Add(prop.Name.ToLower(), prop);\n                    }\n\n                    while (reader.Read())\n                    {\n                        T inst = new T();\n                        DataReaderToObject(reader, inst, propertiesToSkip, piList);\n                        list.Add(inst);\n                    }\n                }\n            }\n            return list;\n        }\n\n        /// <summary>\n        /// Populates the properties of an object from a single DataReader row using\n        /// Reflection by matching the DataReader fields to a public property on\n        /// the object passed in. Unmatched properties are left unchanged.\n        /// \n        /// You need to pass in a data reader located on the active row you want\n        /// to serialize.\n        /// \n        /// This routine works best for matching pure data entities and should\n        /// be used only in low volume environments where retrieval speed is not\n        /// critical due to its use of Reflection.\n        /// </summary>\n        /// <param name=\"reader\">Instance of the DataReader to read data from. Should be located on the correct record (Read() should have been called on it before calling this method)</param>\n        /// <param name=\"instance\">Instance of the object to populate properties on</param>\n        /// <param name=\"propertiesToSkip\">Optional - A comma delimited list of object properties that should not be updated</param>\n        /// <param name=\"piList\">Optional - Cached PropertyInfo dictionary that holds property info data for this object</param>\n        public static void DataReaderToObject(IDataReader reader, object instance,\n                                              string propertiesToSkip = null,\n                                              Dictionary<string, PropertyInfo> piList = null)\n        {\n            if (reader.IsClosed)\n                throw new InvalidOperationException(Resources.DataReaderPassedToDataReaderToObjectCannot);\n\n            if (string.IsNullOrEmpty(propertiesToSkip))\n                propertiesToSkip = string.Empty;\n            else\n                propertiesToSkip = \",\" + propertiesToSkip + \",\";\n\n            propertiesToSkip = propertiesToSkip.ToLower();\n\n            // create a dictionary of properties to look up\n            // we can pass this in so we can cache the list once \n            // for a list operation \n            if (piList == null || piList.Count < 1)\n            {\n                if (piList == null)\n                    piList = new Dictionary<string, PropertyInfo>();\n\n                var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);\n                foreach (var prop in props)\n                    piList.Add(prop.Name.ToLower(), prop);\n            }\n\n            for (int index = 0; index < reader.FieldCount; index++)\n            {\n                string name = reader.GetName(index).ToLower();\n                if (piList.ContainsKey(name))\n                {\n                    var prop = piList[name];\n\n                    // skip fields in skip list\n                    if (!string.IsNullOrEmpty(propertiesToSkip) && propertiesToSkip.Contains(\",\" + name + \",\"))\n                        continue;\n\n                    // find writable properties and assign\n                    if ((prop != null) && prop.CanWrite)\n                    {\n                        var val = reader.GetValue(index);\n\n                        if (val == DBNull.Value)\n                            val = null;\n                        // deal with data drivers return bit values as int64 or in\n                        else if (prop.PropertyType == typeof(bool) && (val is long || val is int))\n                            val = Convert.ToInt64(val) == 1 ? true : false;\n                        // int conversions when the value is not different type of number\n                        else if (prop.PropertyType == typeof(int) && (val is long || val is decimal))\n                            val = Convert.ToInt32(val);\n\n                        prop.SetValue(instance, val, null);\n                    }\n                }\n            }\n\n            return;\n        }\n\n        \n        /// <summary>\n        /// The default SQL date used by InitializeDataRowWithBlanks. Considered a blank date instead of null.\n        /// </summary>\n        internal static DateTime MinimumSqlDate = DateTime.Parse(\"01/01/1900\");\n\n        /// <summary>\n        /// Initializes a Datarow containing NULL values with 'empty' values instead.\n        /// Empty values are:\n        /// String - \"\"\n        /// all number types - 0 or 0.00\n        /// DateTime - Value of MinimumSqlData (1/1/1900 by default);\n        /// Boolean - false\n        /// Binary values and timestamps are left alone\n        /// </summary>\n        /// <param name=\"row\">DataRow to be initialized</param>\n        public static void InitializeDataRowWithBlanks(DataRow row)\n        {\n            DataColumnCollection loColumns = row.Table.Columns;\n\n            for (int x = 0; x < loColumns.Count; x++)\n            {\n                if (!row.IsNull(x))\n                    continue;\n\n                string lcRowType = loColumns[x].DataType.Name;\n\n                if (lcRowType == \"String\")\n                    row[x] = string.Empty;\n                else if (lcRowType.StartsWith(\"Int\"))\n                    row[x] = 0;\n                else if (lcRowType == \"Byte\")\n                    row[x] = 0;\n                else if (lcRowType == \"Decimal\")\n                    row[x] = 0.00M;\n                else if (lcRowType == \"Double\")\n                    row[x] = 0.00;\n                else if (lcRowType == \"Boolean\")\n                    row[x] = false;\n                else if (lcRowType == \"DateTime\")\n                    row[x] = DataUtils.MinimumSqlDate;\n\n                // Everything else isn't handled explicitly and left alone\n                // Byte[] most specifically\n\n            }\n        }\n\n        #endregion\n\n\n\n        #region Type Conversions\n        /// <summary>\n        /// Maps a SqlDbType to a .NET type\n        /// </summary>\n        /// <param name=\"sqlType\"></param>\n        /// <returns></returns>\n        public static Type SqlTypeToDotNetType(SqlDbType sqlType)\n        {\n            if (sqlType == SqlDbType.NText || sqlType == SqlDbType.Text ||\n                sqlType == SqlDbType.VarChar || sqlType == SqlDbType.NVarChar)\n                return typeof(string);\n            else if (sqlType == SqlDbType.Int)\n                return typeof(Int32);\n            else if (sqlType == SqlDbType.Decimal || sqlType == SqlDbType.Money)\n                return typeof(decimal);\n            else if (sqlType == SqlDbType.Bit)\n                return typeof(Boolean);\n            else if (sqlType == SqlDbType.DateTime || sqlType == SqlDbType.DateTime2)\n                return typeof(DateTime);\n            else if (sqlType == SqlDbType.Char || sqlType == SqlDbType.NChar)\n                return typeof(char);\n            else if (sqlType == SqlDbType.Float)\n                return typeof(Single);\n            else if (sqlType == SqlDbType.Real)\n                return typeof(Double);\n            else if (sqlType == SqlDbType.BigInt)\n                return typeof(Int64);\n            else if (sqlType == SqlDbType.Image)\n                return typeof(byte[]);\n            else if (sqlType == SqlDbType.SmallInt)\n                return typeof(byte);\n\n            throw new InvalidCastException(\"Unable to convert \" + sqlType.ToString() + \" to .NET type.\");\n        }\n\n        /// <summary>\n        /// Maps a DbType to a .NET native type\n        /// </summary>\n        /// <param name=\"sqlType\"></param>\n        /// <returns></returns>\n        public static Type DbTypeToDotNetType(DbType sqlType)\n        {\n            if (sqlType == DbType.String || sqlType == DbType.StringFixedLength || sqlType == DbType.AnsiString)\n                return typeof(string);\n            else if (sqlType == DbType.Int16 || sqlType == DbType.Int32)\n                return typeof(Int32);\n            else if (sqlType == DbType.Int64)\n                return typeof(Int64);\n            else if (sqlType == DbType.Decimal || sqlType == DbType.Currency)\n                return typeof(decimal);\n            else if (sqlType == DbType.Boolean)\n                return typeof(Boolean);\n            else if (sqlType == DbType.DateTime || sqlType == DbType.DateTime2 || sqlType == DbType.Date)\n                return typeof(DateTime);\n            else if (sqlType == DbType.Single)\n                return typeof(Single);\n            else if (sqlType == DbType.Double)\n                return typeof(Double);\n            else if (sqlType == DbType.Binary)\n                return typeof(byte[]);\n            else if (sqlType == DbType.SByte || sqlType == DbType.Byte)\n                return typeof(byte);\n            else if (sqlType == DbType.Guid)\n                return typeof(Guid);\n            else if (sqlType == DbType.Binary)\n                return typeof(byte[]);\n\n            throw new InvalidCastException(\"Unable to convert \" + sqlType.ToString() + \" to .NET type.\");\n        }\n\n        /// <summary>\n        /// Converts a .NET type into a DbType value\n        /// </summary>\n        /// <param name=\"type\"></param>\n        /// <returns></returns>\n        public static DbType DotNetTypeToDbType(Type type)\n        {\n            if (type == typeof(string))\n                return DbType.String;\n            else if (type == typeof(Int32))\n                return DbType.Int32;\n            else if (type == typeof(Int16))\n                return DbType.Int16;\n            else if (type == typeof(Int64))\n                return DbType.Int64;\n            else if (type == typeof(Guid))\n                return DbType.Guid;\n            else if (type == typeof(decimal))\n                return DbType.Decimal;\n            else if (type == typeof(double) || type == typeof(float))\n                return DbType.Double;\n            else if (type == typeof(Single))\n                return DbType.Single;\n            else if (type == typeof(bool) || type == typeof(Boolean))\n                return DbType.Boolean;\n            else if (type == typeof(DateTime))\n                return DbType.DateTime;\n            else if (type == typeof(DateTimeOffset))\n                return DbType.DateTimeOffset;\n            else if (type == typeof(byte))\n                return DbType.Byte;\n            else if (type == typeof(byte[]))\n                return DbType.Binary;\n\n            throw new InvalidCastException(string.Format(\"Unable to cast {0} to a DbType\", type.Name));\n        }\n\n        /// <summary>\n        /// Converts a .NET type into a SqlDbType.\n        /// </summary>\n        /// <param name=\"type\"></param>\n        /// <returns></returns>\n        public static SqlDbType DotNetTypeToSqlType(Type type)\n        {\n            if (type == typeof(string))\n                return SqlDbType.NVarChar;\n            else if (type == typeof(Int32))\n                return SqlDbType.Int;\n            else if (type == typeof(Int16))\n                return SqlDbType.SmallInt;\n            else if (type == typeof(Int64))\n                return SqlDbType.BigInt;\n            else if (type == typeof(Guid))\n                return SqlDbType.UniqueIdentifier;\n            else if (type == typeof(decimal))\n                return SqlDbType.Decimal;\n            else if (type == typeof(double) || type == typeof(float))\n                return SqlDbType.Float;\n            else if (type == typeof(Single))\n                return SqlDbType.Float;\n            else if (type == typeof(bool) || type == typeof(Boolean))\n                return SqlDbType.Bit;\n            else if (type == typeof(DateTime))\n                return SqlDbType.DateTime;\n            else if (type == typeof(DateTimeOffset))\n                return SqlDbType.DateTimeOffset;\n            else if (type == typeof(byte))\n                return SqlDbType.SmallInt;\n            else if (type == typeof(byte[]))\n                return SqlDbType.Image;\n\n            throw new InvalidCastException(string.Format(\"Unable to cast {0} to a DbType\", type.Name));\n        }\n\n        #endregion\n\n    }\n\n    public enum DataAccessProviderTypes\n    {\n        SqlServer,\n        SqLite,\n        MySql,\n        PostgreSql,\n\n#if NETFULL\n        OleDb,\n        SqlServerCompact\n#endif\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/DebugUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          (c) West Wind Technologies, 2011\n *          http://www.west-wind.com/\n * \n * Created: 06/12/2011\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n\t/// <summary>\n\t/// DebugUtils class contains various utility methods\n    /// for debugging and diagnostic tasks\n\t/// </summary>\n\tpublic  static class DebugUtils\n\t{\t\t\n        /// <summary>\n        /// Returns the innermost Exception for an object\n        /// </summary>\n        /// <param name=\"ex\"></param>\n        /// <returns></returns>\n        [Obsolete(\"Use Exception.GetBaseException() instead\")]\n        public static Exception GetInnerMostException(Exception ex)\n        {\n            Exception currentEx = ex;\n            while (currentEx.InnerException != null)\n            {\n                currentEx = currentEx.InnerException;\n            }\n\n            return currentEx;\n        }\n\n        /// <summary>\n        /// Returns an array of the entire exception list in reverse order\n        /// (innermost to outermost exception)\n        /// </summary>\n        /// <param name=\"ex\">The original exception to work off</param>\n        /// <returns>Array of Exceptions from innermost to outermost</returns>\n        public static Exception[] GetInnerExceptions(Exception ex)\n        {\n            List<Exception> exceptions = new List<Exception>();\n            exceptions.Add(ex);\n            \n            Exception currentEx = ex;\n            while (currentEx.InnerException != null)\n            {\n                currentEx = currentEx.InnerException;\n                exceptions.Add(currentEx);\n            }\n\n            // Reverse the order to the innermost is first\n            exceptions.Reverse();\n\n            return exceptions.ToArray();\n        }\n\n\n        /// <summary>\n        /// Returns the text with a prefix of line numbers\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"lineFormat\">Line format used to create the line. 0 is the line number, 1 is the text.</param>\n        /// <returns></returns>\n        public static string GetTextWithLineNumbers(string text, string lineFormat = \"{0}.  {1}\")\n        {\n            if (string.IsNullOrEmpty(text))\n                return text;\n\n            var sb = new StringBuilder();\n            var lines = text.GetLines();\n\n            var width = 2;\n            if (lines.Length > 9999)\n                width = 5;\n            else if (lines.Length > 999)\n                width = 4;\n            else if (lines.Length > 99)\n                width = 3;\n            else if (lines.Length < 10)\n                width = 1;\n\n            lineFormat += \"\\r\\n\";\n            for (var index = 1; index <= lines.Length; index++)\n            {\n                var lineNum = index.ToString().PadLeft(width, ' ');\n                sb.AppendFormat(lineFormat, lineNum, lines[index - 1]);\n            }\n\n            return sb.ToString();\n        }\n\n        /// <summary>\n        /// Parses a stack trace and tries to return the source code line that caused the exception in \n        /// a semi-formatted way if the stack trace returns line numbers (ie. Debug infor is available)\n        /// </summary>\n        /// <param name=\"stackTrace\">A string of a full stack trace</param>\n        /// <returns>A stacktrace with the code line from source if source is available via Debug. Otherwise the raw stacktrace is returned.</returns>\n        public static string ParseStackTrace(string stackTrace)\n        {\n            if (string.IsNullOrEmpty(stackTrace))\n                return null;\n\n            string source = stackTrace;\n            try\n            {\n                var firstLine = StringUtils.GetLines(stackTrace, 1)[0];\n\n                if (!string.IsNullOrEmpty(firstLine) && firstLine.Contains(\".cs:line \"))\n                {\n                    firstLine = StringUtils.ExtractString(firstLine, \" in \", \"xxx\", allowMissingEndDelimiter: true);\n\n                    var tokens = firstLine.Split(new[] { \":line \" }, StringSplitOptions.RemoveEmptyEntries);\n\n                    if (tokens.Length > 0 && System.IO.File.Exists(tokens[0]))\n                    {\n                        var line = int.Parse(tokens[1]);\n                        var fc = File.ReadAllText(tokens[0]);\n                        var lines = StringUtils.GetLines(fc);\n                        source = lines[line - 1].Trim();\n\n                        stackTrace = source + stackTrace;\n                    }\n                }\n            }\n            catch { }\n\n            return stackTrace;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ExensionMethods/DateTimeExtensions.cs",
    "content": "﻿using System;\n\nnamespace Westwind.Utilities.Extensions\n{\n    public static class DateTimeExtensions\n    {\n        /// <summary>\n        /// Returns true if the date is between or equal to one of the two values.\n        /// </summary>\n        /// <param name=\"date\">DateTime Base, from where the calculation will be preformed.</param>\n        /// <param name=\"startvalue\">Start date to check for</param>\n        /// <param name=\"endvalue\">End date to check for</param>\n        /// <returns>boolean value indicating if the date is between or equal to one of the two values</returns>\n        public static bool Between(this DateTime date, DateTime startDate, DateTime endDate)\n        {\n            var ticks = date.Ticks;\n            return ticks >= startDate.Ticks && ticks <= endDate.Ticks;\n        }\n\n\n        /// <summary>\n        /// Returns 12:59:59pm time for the date passed.\n        /// Useful for date only search ranges end value\n        /// </summary>\n        /// <param name=\"date\">Date to convert</param>\n        /// <returns></returns>\n        public static DateTime EndOfDay(this DateTime date)\n        {\n            return date.Date.AddDays(1).AddMilliseconds(-1);\n        }\n\n        /// <summary>\n        /// Returns 12:00am time for the date passed.\n        /// Useful for date only search ranges start value\n        /// </summary>\n        /// <param name=\"date\">Date to convert</param>\n        /// <returns></returns>\n        public static DateTime BeginningOfDay(this DateTime date)\n        {\n            return date.Date;\n        }\n\n        /// <summary>\n        /// Returns the very end of the given month (the last millisecond of the last hour for the given date)\n        /// </summary>\n        /// <param name=\"obj\">DateTime Base, from where the calculation will be preformed.</param>\n        /// <returns>Returns the very end of the given month (the last millisecond of the last hour for the given date)</returns>\n        public static DateTime EndOfMonth(this DateTime obj)\n        {\n            return new DateTime(obj.Year, obj.Month, DateTime.DaysInMonth(obj.Year, obj.Month), 23, 59, 59, 999);\n        }\n\n        /// <summary>\n        /// Returns the Start of the given month (the fist millisecond of the given date)\n        /// </summary>\n        /// <param name=\"obj\">DateTime Base, from where the calculation will be preformed.</param>\n        /// <returns>Returns the Start of the given month (the fist millisecond of the given date)</returns>\n        public static DateTime BeginningOfMonth(this DateTime obj)\n        {\n            return new DateTime(obj.Year, obj.Month, 1, 0, 0, 0, 0);\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ExensionMethods/DictionaryExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing System.Xml.Linq;\nusing Westwind.Utilities;\nusing System.Collections;\nusing System.Collections.Specialized;\nusing System.Xml.Serialization;\nusing System.Xml;\n\nnamespace Westwind.Utilities.Extensions\n{\n    /// <summary>\n    /// Extends dictionary classes with XML \n    /// </summary>\n    public static class DictionaryExtensions\n    {\n\n        /// <summary>\n        /// Serializes the dictionary to an XML string\n        /// </summary>\n        /// <returns></returns>\n        public static string ToXml(this IDictionary items, string root = \"root\")\n        {\n            var rootNode = new XElement(root);\n\n            foreach (DictionaryEntry item in items)\n            {\n                string xmlType = XmlUtils.MapTypeToXmlType(item.Value.GetType());\n                XAttribute typeAttr = null;\n\n                // if it's a simple type use it\n                if (!string.IsNullOrEmpty(xmlType))\n                {\n                    typeAttr = new XAttribute(\"type\", xmlType);\n                    rootNode.Add(\n                        new XElement(item.Key as string,\n                                     typeAttr,\n                                     item.Value)\n                        );\n                }\n                else\n                {\n                    // complex type use serialization\n                    string xmlString = null;\n                    if (SerializationUtils.SerializeObject(item.Value, out xmlString))\n                    {\n                        XElement el = XElement.Parse(xmlString);\n\n                        rootNode.Add(\n                            new XElement(item.Key as string,\n                            new XAttribute(\"type\", \"___\" + item.Value.GetType().FullName),\n                            el));\n                    }\n                }\n            }\n\n            return rootNode.ToString();\n        }\n\n        /// <summary>\n        /// Loads the dictionary from an Xml string\n        /// </summary>\n        /// <param name=\"xml\"></param>\n        public static void FromXml(this IDictionary items, string xml)\n        {\n            items.Clear();\n\n            var root = XElement.Parse(xml);\n\n            foreach (XElement el in root.Elements())\n            {\n                string typeString = null;\n\n                var typeAttr = el.Attribute(\"type\");\n                if (typeAttr != null)\n                    typeString = typeAttr.Value;\n\n                string val = el.Value;\n\n\n                if (!string.IsNullOrEmpty(typeString) && typeString != \"string\" && !typeString.StartsWith(\"__\"))\n                {\n                    // Simple type we know how to convert\n                    Type type = XmlUtils.MapXmlTypeToType(typeString);\n                    if (type != null)\n                        items.Add(el.Name.LocalName, ReflectionUtils.StringToTypedValue(val, type));\n                    else\n                        items.Add(el.Name.LocalName, val);\n                }\n                else if (typeString.StartsWith(\"___\"))\n                {\n                    Type type = ReflectionUtils.GetTypeFromName(typeString.Substring(3));\n                    object serializationUtilsDeSerializeObject = SerializationUtils.DeSerializeObject(el.Elements().First().CreateReader(), type);\n                    items.Add(el.Name.LocalName, serializationUtilsDeSerializeObject);\n                }\n                else\n                    // it's a string or unknown type\n                    items.Add(el.Name.LocalName, val);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ExensionMethods/LinqExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace Westwind.Utilities.Linq\n{\n    public static class LinqExtensions\n    {\n        /// <summary>\n        /// Recursively flattens a tree structure of nested same type enumerables\n        /// into a flat structure. \n        /// \n        /// Example:\n        /// Flattening a tree of documentation topics into a flat list of topics.\n        /// </summary>\n        /// <typeparam name=\"T\">Type to flatten</typeparam>\n        /// <param name=\"e\">Enumeration to work on</param>\n        /// <param name=\"f\">Expression that points at the element list to select from</param>\n        /// <returns>Flattened list or empty enumerable</returns>\n        /// <example>\n        /// var topics = topicTree.FlattenTree(t=&gt; t.Topics);\n        /// </example>\n        public static IEnumerable<T> FlattenTree<T>(\n            this IEnumerable<T> e,\n            Func<T, IEnumerable<T>> f)\n        {\n            if (f == null)\n                throw new ArgumentNullException(nameof(f));\n\n            var items = (e ?? Enumerable.Empty<T>()).ToList();\n\n            return items\n                .SelectMany(c => (f(c) ?? Enumerable.Empty<T>()).FlattenTree(f))\n                .Concat(items);\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ExensionMethods/MemoryStreamExtensions.cs",
    "content": "﻿using System.Text;\nusing System.IO;\nusing System.Threading.Tasks;\n\nnamespace System.IO\n{\n    /// <summary>\n    /// MemoryStream Extension Methods that provide conversions to and from strings\n    /// </summary>\n    public static class MemoryStreamExtensions\n    {\n        /// <summary>\n        /// Returns the content of the stream as a string\n        /// </summary>\n        /// <param name=\"ms\">Memory stream</param>\n        /// <param name=\"encoding\">Encoding to use - defaults to Unicode</param>\n        /// <returns></returns>\n        public static string AsString(this MemoryStream ms, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.Unicode;\n            ms.Position = 0;\n            return encoding.GetString(ms.ToArray());\n        }\n\n        /// <summary>\n        /// Writes the specified string into the memory stream\n        /// </summary>\n        /// <param name=\"ms\"></param>\n        /// <param name=\"inputString\"></param>\n        /// <param name=\"encoding\"></param>\n        public static void FromString(this MemoryStream ms, string inputString, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.Unicode;\n\n            byte[] buffer = encoding.GetBytes(inputString);\n            ms.Write(buffer, 0, buffer.Length);\n            ms.Position = 0;\n        }\n    }\n\n    /// <summary>\n    /// Stream Extensions\n    /// </summary>\n    public static class StreamExtensions\n    {\n        /// <summary>\n        /// Converts a stream by copying it to a memory stream and returning\n        /// as a string with encoding.\n        /// </summary>\n        /// <param name=\"s\">stream to turn into a string</param>\n        /// <param name=\"encoding\">Encoding of the stream. Defaults to Unicode</param>\n        /// <returns>string </returns>\n        public static string AsString(this Stream s, Encoding encoding = null)\n        {\n            using (var ms = new MemoryStream())\n            {\n                s.CopyTo(ms);\n                s.Position = 0;\n                return ms.AsString(encoding);\n            }\n        }\n\n        /// <summary>\n        /// Returns bytes from a stream\n        /// </summary>\n        /// <param name=\"s\"></param>\n        /// <returns></returns>\n        public static byte[] AsBytes(this Stream s)\n        {            \n            if (s is MemoryStream ms)\n            {                \n                ms.Position = 0;\n                return ms.ToArray();\n            }\n\n            using (ms = new MemoryStream() {  Capacity = Math.Max( 256, (int) s.Length ) })\n            {                                \n                s.CopyTo(ms);\n                s.Position = 0;\n                return ms.ToArray();\n            }\n        }\n\n        /// <summary>\n        /// Returns bytes from a stream\n        /// </summary>\n        /// <param name=\"s\"></param>\n        /// <returns></returns>\n        public static async Task<byte[]> AsBytesAsync(this Stream s)\n        {\n            if (s is MemoryStream ms)\n            {\n                ms.Position = 0;\n                return ms.ToArray();\n            }\n\n            using (ms = new MemoryStream() { Capacity = Math.Max(256, (int)s.Length) })\n            {\n                await s.CopyToAsync(ms);\n                s.Position = 0;\n                return ms.ToArray();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/FileUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\n//using System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities\n{\n\t/// <summary>\n\t/// wwUtils class which contains a set of common utility classes for \n\t/// Formatting strings\n\t/// Reflection Helpers\n\t/// Object Serialization\n    /// Stream Manipulation\n\t/// </summary>\n\tpublic static class FileUtils\n\t{\n        #region Path Segments and Path Names\n\n\n        /// <summary>\n        /// This function returns the actual filename of a file\n        /// that exists on disk. If you provide a path/file name\n        /// that is not proper cased as input, this function fixes\n        /// it up and returns the file using the path and file names\n        /// as they exist on disk.\n        /// \n        /// If the file doesn't exist the original filename is \n        /// returned.\n        /// </summary>\n        /// <param name=\"filename\">A filename to check</param>\n        /// <returns>On disk file name and path with the disk casing</returns>\n        public static string GetPhysicalPath(string filename)\n\t    {\n\t        try\n\t        {\n\t            return new FileInfo(filename).FullName;                \n\t        }\n            catch { }\n\n            return filename;\n        }\n\n\n        /// <summary>\n        /// Returns a relative path string from a full path based on a base path\n        /// provided.\n        /// </summary>\n        /// <param name=\"fullPath\">The path to convert. Can be either a file or a directory</param>\n        /// <param name=\"basePath\">The base path on which relative processing is based. Should be a directory!</param>\n        /// <returns>\n        /// String of the relative path. Path is returned in OS specific path format.\n        /// \n        /// Examples of returned values:\n        ///  test.txt, ..\\test.txt, ..\\..\\..\\test.txt, ., .., subdir\\test.txt\n        /// </returns>\n        public static string GetRelativePath(string fullPath, string basePath ) \n\t\t{\n            try\n            {\n                // ForceBasePath to a path\n                var pathChar = Path.DirectorySeparatorChar.ToString();\n                if (!basePath.EndsWith(value: pathChar))\n                    basePath += pathChar;\n\n                Uri baseUri = new Uri(uriString: basePath);\n                Uri fullUri = new Uri(uriString: fullPath);\n\n                string relativeUri = baseUri.MakeRelativeUri(uri: fullUri).ToString();\n\n                if (relativeUri.StartsWith(\"file://\"))\n                    return fullPath;  // invalid path that can't be made relative \n\n                // Uri's use forward slashes - convert back to backward slases if OS uses                \n                var path = relativeUri.Replace(oldValue: \"/\", newValue: pathChar);\n\n                return Uri.UnescapeDataString(path);\n            }\n            catch\n            {\n                return fullPath;\n            }\n        }\n\n        /// <summary>\n        /// Compares two files and returns a relative path to the second file.     \n        /// </summary>\n        /// <param name=\"filePath\">The path that is the current path you're working with</param>\n        /// <param name=\"compareToPath\">The path that that you want to reference</param>\n        /// <param name=\"noOsPathFixup\">If true won't fix up the path for current OS (ie. returns original path delimiters via Uri comparison)</param>\n        /// <returns>Relative path if possible otherwise original path</returns>\n        public static string GetRelativeFilePath(string filePath, string compareToPath, bool noOsPathFixup = false)\n        {\n            Uri fromUri = new Uri(\"file://\" + filePath);\n            Uri toUri = new Uri(\"file://\" + compareToPath);\n\n            string path = fromUri.MakeRelativeUri(toUri).ToString();\n\n            if (path.StartsWith(\"file://\"))\n                return filePath;  // invalid path that can't be made relative \n\n            if (!noOsPathFixup) { \n                var pathChar = Path.DirectorySeparatorChar.ToString();\n                path = path.Replace(oldValue: \"/\", newValue: pathChar);\n            }\n            return path;\n        }\n\n\n        /// <summary>\n        /// Resolves an absolute path from a relative file path to a base file or directory\n        /// </summary>\n        /// <param name=\"basePath\">Base file or folder which relativeFile is relative to.\n        /// If you pass a folder, terminate the folder with a path character!</param>\n        /// <param name=\"relativeFile\">The path to resolve against the base path</param>\n        /// <returns></returns>\n        public static string ResolvePath(string basePath, string relativeFile)\n        {\n            if (string.IsNullOrEmpty(basePath) ||\n                string.IsNullOrEmpty(relativeFile))\n                return relativeFile;\n\n            var baseUri = new Uri(basePath);\n            var relativeUri = new Uri(relativeFile, UriKind.Relative);\n            var relUri = new Uri(baseUri, relativeUri);\n\n            return relUri.LocalPath;\n        }\n        \n        /// <summary>\n        /// Checks to see if a given local file or directory is a relative path\n        /// </summary>\n        /// <param name=\"path\">path to check</param>\n        /// <returns></returns>\n        public static bool IsRelativePath(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return false;\n\n            if (path.StartsWith(\".\"))\n                return true;\n\n            if (path.StartsWith(\"/\") || path.StartsWith(\"\\\\\") || \n                path.Contains(\":\\\\\") || path.StartsWith(\"file:\"))\n                return false;\n\n            return true;            \n        }\n\n        public static string GetShortPath(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return null;\n\n            // allow for extended path syntax\n            bool addExtended = false;\n            if (path.Length > 240 && !path.StartsWith(@\"\\\\?\\\"))\n            {\n                path = @\"\\\\?\\\" + path;\n                addExtended = true;\n            }\n\n            var shortPath = new StringBuilder(1024);\n            int res = GetShortPathName(path, shortPath, 1024);\n            if (res < 1)\n                return null;\n\n            path = shortPath.ToString();\n\n            if (addExtended)\n                path = path.Substring(4);  // strip off \\\\?\\\n\n            return path;\n        }\n\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Auto)]\n        private static extern int GetShortPathName(\n            [MarshalAs(UnmanagedType.LPTStr)]\n            string path,\n            [MarshalAs(UnmanagedType.LPTStr)]\n            StringBuilder shortPath,\n            int shortPathLength\n        );\n\n\n        /// <summary>\n        /// Retrieves a long filename for a given path using\n        /// Extended Windows path syntax.        \n        /// \n        /// Fully qualified paths:\n        /// \\\\?\\C:\\path\\to\\file.txt\n        /// UNC Paths:\n        /// \\\\?\\UNC\\server\\share\\path\\to\\file.txt\n        /// </summary>\n        /// <remarks>       \n        /// Paths are resolved using GetFullPath() so preferably\n        /// you should pass in a fully qualified path for your \n        /// application to ensure that path is resolved correctly.\n        /// </remarks>\n        /// <param name=\"path\">Existing path</param>\n        /// <returns>Long path</returns>\n        public static string GetWindowsLongFilename(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return path;\n\n            string fullPath = System.IO.Path.GetFullPath(path);\n\n            if (fullPath.Length < 260)\n                return fullPath; // No need to convert\n\n            // Fully qualified path\n            if (fullPath.Length > 1 && fullPath[1] == ':')\n                fullPath = @\"\\\\?\\\" + fullPath;\n            // UNC Path\n            else if (fullPath.Length > 2 && fullPath.StartsWith(@\"\\\\\"))\n                fullPath = @\"\\\\?\\UNC\\\" + fullPath.Substring(2);\n\n            return fullPath;\n        }\n\n        //// To ensure that paths are not limited to MAX_PATH, use this signature within .NET\n        //[DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, EntryPoint = \"GetShortPathNameW\", SetLastError = true)]\n        //static extern int GetShortPathName_Internal(string pathName, StringBuilder shortName, int cbShortName);\n\n        ///// <summary>\n        ///// Returns a Windows short path (8 char path segments)\n        ///// for a long path.\n        ///// </summary>\n        ///// <param name=\"fullPath\"></param>\n        ///// <remarks>Throws on non-existant files</remarks>\n        ///// <returns></returns>\n        //public static string GetShortPath(string fullPath)\n        //{\n        //    if (string.IsNullOrEmpty(fullPath))\n        //        return fullPath;\n\n        //    StringBuilder sb = new StringBuilder();\n        //    GetShortPathName_Internal(fullPath, sb, 2048);\n        //    return sb.ToString();\n        //}\n\n\n        /// <summary>\n        /// Expands Path Environment Variables like %appdata% in paths. Also \n        /// expands ~ to the User Profile folder.\n        /// </summary>\n        /// <param name=\"path\">Path with potential environment variables.</param>\n        /// <returns></returns>\n        public static string ExpandPathEnvironmentVariables(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return path;\n\n            if (path.StartsWith(\"~\"))\n            {                \n                path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + path.TrimStart('~');                \n            }\n            return Environment.ExpandEnvironmentVariables(path);\n        }\n\n        /// <summary>\n        /// Changes any path that starts with the Windows user path into a ~ path instead\n        /// </summary>\n        /// <param name=\"path\">Any windows path</param>\n        /// <returns>~ replaces c:\\users\\someuser in path string, otherwise original path is returned</returns>\n        public static string TildefyUserPath(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return path;\n\n            string userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);\n\n            if (path.StartsWith(userPath, StringComparison.InvariantCultureIgnoreCase))\n#if NET6_0_OR_GREATER\n                return path.Replace(userPath, \"~\", true, null);\n#else\n                return StringUtils.ReplaceString(path, userPath, \"~\", true);\n#endif\n\n            return path;\n        }\n\n        /// <summary>\n        /// Returns a compact path with elipsis from a long path\n        /// </summary>\n        /// <param name=\"path\">Original path to potentially trim</param>\n        /// <param name=\"length\">Max length of the path string returned</param>\n        /// <returns></returns>\n        public static string GetCompactPath(string path, int length = 70)\n        {\n            if (string.IsNullOrEmpty(path))\n                return path;\n\n            var fnameOrDirectory = Path.GetFileName(path);\n            if (fnameOrDirectory.Length >= length)\n                return \"...\" + fnameOrDirectory;\n\n            if (path.Length <= length)\n                return path;\n\n            var index = Math.Max(path.LastIndexOf('\\\\'), path.LastIndexOf('/'));\n            if (index <= 0)\n                return path.Substring(0, length);\n\n            var end = path.Substring(index);\n            var maxStartLength = length - end.Length;\n\n            if (maxStartLength <= 0)\n                return \"...\" + end.Substring(Math.Max(0, end.Length - (length - 3)));\n\n            var start = path.Substring(0, index);\n\n            if (start.Length > maxStartLength)\n                start = start.Substring(0, Math.Max(0, maxStartLength - 3)) + \"...\";\n\n            return start + end;\n        }\n\n\n        /// <summary>\n        /// Creates a temporary file name with a specific extension. Optionall\n        /// provide the base path to create it in otherwise the TEMP path is used.\n        ///  Filename is generated as _ + 8 random characters/digits\n        /// </summary>\n        /// <param name=\"extension\">The extension to use (.png)</param>\n        /// <param name=\"tempPath\">Optional - path in which the file is created if it needs to override</param>\n        /// <param name=\"charCount\">Optional - character count - minus the prefix _ - of the generated temp filename. Between 8-16</param>\n        /// <returns></returns>\n        public static string GetTempFilenameWithExtension(string extension, string tempPath = null, int charCount = 8)\n        {\n            if (string.IsNullOrEmpty(extension))\n            {\n                extension = \".tmp\";\n            }\n            if (!extension.StartsWith(\".\"))\n                extension = \".\" + extension;\n\n            if (tempPath == null)\n            {\n                tempPath = Path.GetTempPath();\n            }\n\n            string filename = DataUtils.GenerateUniqueId(charCount);\n\n            return Path.Combine(tempPath, filename + extension);\n        }\n\n\n        /// <summary>\n        /// Attempts to break a file name into words that can be used for display\n        /// purposes. \n        /// * Replaces - and _ with spaces\n        /// * Converts from CamelCase (simplified - not perfect)        \n        /// </summary>\n        /// <param name=\"filename\">File name to break into words</param>\n        /// <returns></returns>\n        public static string BreakFilenameIntoWords(string filename)\n        {\n            if (string.IsNullOrEmpty(filename))\n                return filename;\n            return StringUtils.BreakIntoWords(Path.GetFileNameWithoutExtension(filename));\n        }\n\n        #endregion\n\n        #region File and Path Normalization\n\n        /// <summary>\n        /// Normalizes a file path to the operating system default\n        /// slashes.\n        /// </summary>\n        /// <param name=\"path\"></param>\n        public static string NormalizePath(string path)\n\t    {\n            //return Path.GetFullPath(path); // this always turns into a full OS path\n\n\t        if (string.IsNullOrEmpty(path))\n\t            return path;\n\n\t        char slash = Path.DirectorySeparatorChar;\n\t        path = path.Replace('/', slash).Replace('\\\\', slash);\n            string doubleSlash = string.Concat(slash, slash);\n            if (path.StartsWith(doubleSlash))\n                return string.Concat(doubleSlash, path.TrimStart(slash).Replace(doubleSlash, slash.ToString()));\n            else\n                return path.Replace(doubleSlash, slash.ToString());\n        }\n\n\n        /// <summary>\n        /// Normalizes path with slashes and forces a trailing slash \n        /// on the end of the path.\n        /// </summary>\n        /// <param name=\"Path\">Path to pass in</param>\n        /// <returns></returns>\n\t    public static string NormalizeDirectory(string path)\n        {\n            path = NormalizePath(path);\n            if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))\n                path += Path.DirectorySeparatorChar;\n            return path;\n        }\n\n        /// <summary>\n        /// Adds a trailing slash to a path if there isn't one.\n        /// \n        /// Uses the Operating System default path character (`/` or `\\`)\n        /// </summary>\n        /// <param name=\"path\">A file system path</param>\n        /// <returns></returns>\n        public static string AddTrailingSlash(string path)\n        {\n            string separator = Path.DirectorySeparatorChar.ToString();\n\n            path = path.TrimEnd();\n\n            if (path.EndsWith(separator) || path.EndsWith(Path.AltDirectorySeparatorChar.ToString()))\n                return path;\n\n            return path + separator;\n        }\n\n\n        /// <summary>\n        /// Adds a trailing slash to a path if there isn't one.\n        /// \n        /// Allows you to explicitly specify the path separator character\n        /// rather than using the default OS path separator.\n        /// </summary>\n        /// <param name=\"path\">A file system path</param>\n        /// <param name=\"slashchar\">Character to use as trailing character</param>\n        /// <returns></returns>\n        public static string AddTrailingSlash(string path, char slashChar)\n\t    {\n            var separator = slashChar.ToString();\n\t        path = path.TrimEnd();\n\n\t        if (path.EndsWith(separator))\n\t            return path;\n\n\t        return path + separator;\n\t    }\n\n\n        /// <summary>\n        /// Returns a path as a `file:///` url.\n        /// </summary>\n        /// <param name=\"path\">\n        /// A fully rooted path\n        /// or: a relative path that can be resolved to a\n        /// fully rooted path via GetFullPath().\n        /// </param>\n        /// <returns></returns>\n        public static string FilePathAsUrl(string path)\n        {\n            if (string.IsNullOrEmpty(path))\n                return path;\n\n            // try to resolve the path to a full path\n            path = Path.GetFullPath(path);\n            var url = new Uri(path);\n            return url.ToString();\n        }\n\n        #endregion\n\n        #region File Encoding and Checksums\n\n        /// <summary>\n        /// Detects the byte order mark of a file and returns\n        /// an appropriate encoding for the file.\n        /// </summary>\n        /// <param name=\"srcFile\"></param>\n        /// <returns></returns>\n        public static Encoding GetFileEncoding(string srcFile)\n        {\n            // Use Default of Encoding.Default (Ansi CodePage)\n            Encoding enc = Encoding.Default;\n\n            // Detect byte order mark if any - otherwise assume default\n\n            byte[] buffer = new byte[5];\n            FileStream file = new FileStream(srcFile, FileMode.Open);\n            _ = file.Read(buffer, 0, 5);\n            file.Close();\n\n            if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)\n                enc = Encoding.UTF8;\n            else if (buffer[0] == 0xfe && buffer[1] == 0xff)\n                enc = Encoding.Unicode;\n            else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)\n                enc = Encoding.UTF32;\n\n            return enc;\n        }\n\n\n        /// <summary>\n\t    /// Creates an MD5 checksum of a file\n\t    /// </summary>\n\t    /// <param name=\"file\"></param>        \n\t    /// <param name=\"hashAlgorithm\">SHA256, SHA512, SHA1, MD5</param>\n\t    /// <returns>BinHex file hash</returns>\n\t    public static string GetChecksumFromFile(string file, string hashAlgorithm = \"MD5\")\n\t    {           \n\t        if (!File.Exists(file))\n\t            return null;\n\n\t        try\n\t        {\n\t            byte[] checkSum;\n                using (FileStream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))\n                {\n                    HashAlgorithm md = null;\n\n                    if (hashAlgorithm == \"MD5\")\n                        md = MD5.Create();\n                    else if (hashAlgorithm == \"SHA256\")\n                        md = SHA256.Create();\n                    else if (hashAlgorithm == \"SHA512\")\n                        md = SHA512.Create();\n                    else if (hashAlgorithm == \"SHA1\")\n                        md = SHA1.Create();\n                    else\n                        md = MD5.Create();\n\n                    using (md)\n                    {\n                        checkSum = md.ComputeHash(stream);\n                    }\n\t            }\n\n\t            return StringUtils.BinaryToBinHex(checkSum);\n\t        }\n\t        catch\n\t        {\n\t            return null;\n\t        }\n\t    }\n\n#endregion\n\n#region Searching \n\n        /// <summary>\n        /// Searches for a file name based on a current file location up or down the\n        /// directory hierarchy including the current folder. First file match is returned.\n        /// </summary>\n        /// <param name=\"currentPath\">Current path or filename to determine start folder to search up from</param>\n        /// <param name=\"searchFile\">File name to search for. Should be a filename but can contain wildcards</param>\n        /// <param name=\"direction\">Search up or down the directory hierarchy including base path</param>\n         /// <returns></returns>\n        public static string FindFileInHierarchy( string currentPath, string searchFile,\n                                                  FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up)\n        {\n            string path = null;\n\n            var fi = new FileInfo(currentPath);\n            if (!fi.Exists)\n            {\n                var di = new DirectoryInfo(currentPath);\n                if (!di.Exists)\n                    return null;\n\n                path = di.FullName;\n            }\n            else\n            {\n                path = fi.DirectoryName;\n            }\n\n            return FindFileInHierarchyInternal(path, searchFile, direction);\n        }\n\n        /// <summary>\n        /// Recursive method to walk the hierarchy and find the file requested.\n        /// </summary>\n        /// <param name=\"path\">Base path</param>\n        /// <param name=\"searchFile\">Filename to search for</param>\n        /// <param name=\"direction\">Search up or down the tree including base path</param>\n        /// <returns></returns>\n        private static string FindFileInHierarchyInternal(string path, string searchFile,\n                                                          FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up)\n        {\n            if (path == null)\n                return null;\n\n            var dir = new DirectoryInfo(path);\n\n            var so = SearchOption.TopDirectoryOnly;\n\n            if (direction == FindFileInHierarchyDirection.Down)\n                so = SearchOption.AllDirectories;\n\n            FileInfo[] files;\n            try\n            {\n                files = dir.GetFiles(searchFile, so);\n            }\n            catch\n            {                \n                return null;  // permissions error most likely\n            }\n\n            if (files.Length > 0)\n                return files[0].FullName;  // closest match\n\n            if (direction == FindFileInHierarchyDirection.Down)\n                return null;\n\n            if (dir.Parent == null)\n                return null;\n\n            return FindFileInHierarchyInternal(dir.Parent.FullName, searchFile, FindFileInHierarchyDirection.Up);\n        }\n\n        /// <summary>\n        /// Returns a list of file matches searching up and down a file hierarchy\n        /// </summary>\n        /// <param name=\"startPath\">Path to start from</param>\n        /// <param name=\"searchFile\">Filename or Wildcard</param>\n        /// <param name=\"direction\">up or down the hiearchy</param>\n        /// <returns></returns>\n        public static string[] FindFilesInHierarchy(string startPath, string searchFile,\n            FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Down)\n        {\n            var list= new string[]{ };\n\n            var fi = new FileInfo(startPath);\n            if (!fi.Exists)\n            {\n                var di = new DirectoryInfo(startPath);\n                if (!di.Exists)\n                    return list;\n\n                startPath = di.FullName;\n            }\n            else\n            {\n                startPath = fi.DirectoryName;\n            }\n\n            return FindFilesInHierarchyInternal(startPath, searchFile, direction);\n        }\n\n        /// <summary>\n        /// Recursive method to walk the hierarchy and find the file requested.\n        /// </summary>\n        /// <param name=\"path\">Base path</param>\n        /// <param name=\"searchFile\">Filename to search for</param>\n        /// <param name=\"direction\">Search up or down the tree including base path</param>\n        /// <returns></returns>\n        private static string[] FindFilesInHierarchyInternal(string path, string searchFile,\n            FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up)\n        {\n            var list = new string[] { };\n\n            if (path == null)\n                return list;\n\n            var dir = new DirectoryInfo(path);\n\n            var so = SearchOption.TopDirectoryOnly;\n\n            if (direction == FindFileInHierarchyDirection.Down)\n                so = SearchOption.AllDirectories;\n\n            var files = dir.GetFiles(searchFile, so);\n            if (files.Length > 0)\n                return files.Select(fi=> fi.FullName).ToArray();\n\n            if (direction == FindFileInHierarchyDirection.Down)\n                return list;\n\n            if (dir.Parent == null)\n                return list;\n\n            return FindFilesInHierarchyInternal(dir.Parent.FullName, searchFile, FindFileInHierarchyDirection.Up);\n        }\n\n        public enum FindFileInHierarchyDirection\n        {\n            Up,\n            Down\n        }\n\n#endregion\n\n#region File and Path Naming\n\n        \n        /// <summary>\n        /// Returns a safe filename from a string by stripping out\n        /// illegal characters\n        /// </summary>\n        /// <param name=\"fileName\">Filename to fix up</param>\n        /// <param name=\"replacementString\">String value to replace illegal chars with. Defaults empty string</param>\n        /// <param name=\"spaceReplacement\">Optional - replace spaces with a specified string like a - or _. Optional, if not set leaves spaces which are legal for filenames</param>\n        /// <returns>Fixed up string</returns>\n        public static string SafeFilename(string fileName, string replacementString = \"\", string spaceReplacement = null)\n        {\n            if (string.IsNullOrEmpty(fileName))\n                return fileName;\n\n            string file = Path.GetInvalidFileNameChars()\n                .Aggregate(fileName.Trim(),\n                    (current, c) => current.Replace(c.ToString(), replacementString));\n\n            file = file.Replace(\"#\", \"\")                        \n                       .Replace(\"+\", \"\");\n\n            if (!string.IsNullOrEmpty(spaceReplacement))\n                file = file.Replace(\" \", spaceReplacement);\n\n            return file.Trim();\n        }\n\n        /// <summary>\n        /// Returns a safe filename in CamelCase\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <returns></returns>\n        public static string CamelCaseSafeFilename(string filename)\n        {\n            if (string.IsNullOrEmpty(filename))\n                return filename;\n\n            string fname = Path.GetFileNameWithoutExtension(filename);\n            string ext = Path.GetExtension(filename);\n\n            return StringUtils.ToCamelCase(SafeFilename(fname)) + ext;\n        }\n\n\n        /// <summary>\n        /// Checks to see if a file has invalid path characters. Use this\n        /// to check before using or manipulating paths with `Path` operations\n        /// that will fail if files or paths contain invalid characters.\n        /// </summary>\n        /// <param name=\"path\">Path to check</param>\n        /// <param name=\"additionalChars\">Optionally allows you to add additional invalid characters to the disallowed OS characters</param>\n        /// <returns></returns>\n        public static bool HasInvalidPathCharacters(string path, params char[] additionalChars)\n        {\n            if(string.IsNullOrEmpty(path)) return true; // no invalids\n\n            var invalids = Path.GetInvalidPathChars();\n            if (additionalChars != null)\n                invalids.Concat(additionalChars);\n\t\n            return (!string.IsNullOrEmpty(path) && path.IndexOfAny(invalids) >= 0);\n        }\n#endregion\n\n#region StreamFunctions\n        /// <summary>\n        /// Copies the content of the one stream to another.\n        /// Streams must be open and stay open.\n        /// </summary>\n        public static void CopyStream(Stream source, Stream dest, int bufferSize)\n        {\n            byte[] buffer = new byte[bufferSize];\n            int read;\n            while ( (read = source.Read(buffer, 0, buffer.Length)) > 0)\n            {\n                dest.Write(buffer, 0, read);\n            }\n        }\n\n        /// <summary>\n        /// Copies the content of one stream to another by appending to the target stream\n        /// Streams must be open when passed in.\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"dest\"></param>\n        /// <param name=\"bufferSize\"></param>\n        /// <param name=\"append\"></param>\n        public static void CopyStream(Stream source, Stream dest, int bufferSize, bool append)\n        {\n            if (append)\n                dest.Seek(0, SeekOrigin.End);\n\n            CopyStream(source, dest, bufferSize);\n            return;\n        }\n\n        /// <summary>\n        /// Opens a stream reader with the appropriate text encoding applied.\n        /// </summary>\n        /// <param name=\"srcFile\"></param>\n        public static StreamReader OpenStreamReaderWithEncoding(string srcFile)\n        {\n            Encoding enc = GetFileEncoding(srcFile);\n            return new StreamReader(srcFile, enc);\n        }\n\n        #endregion\n\n        #region Async File Access for NetFX\n        \n        /// <summary>\n        /// Asynchronously reads files. Use only with NetFx\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"encoding\"></param>\n        /// <returns></returns>\n        public static async Task<string> ReadAllTextAsync(string filename, Encoding encoding)\n        {\n            using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))\n            using (var reader = new StreamReader(stream, encoding))\n            {\n                return await reader.ReadToEndAsync();\n            }\n        }\n\n\n        /// <summary>\n        /// Async Read all bytes. Use only with NetFx\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <returns></returns>\n        /// <exception cref=\"ArgumentNullException\"></exception>\n        public static async Task<byte[]> ReadAllBytesAsync(string filename)\n        {\n            if (filename == null)\n                throw new ArgumentNullException(nameof(filename));\n\n            using (var sourceStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))\n            {\n                var buffer = new byte[sourceStream.Length];\n                int bytesRead = 0;\n                while (bytesRead < buffer.Length)\n                {\n                    int read = await sourceStream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);\n                    if (read == 0)\n                        break;\n                    bytesRead += read;\n                }\n                return buffer;\n            }\n        }\n\n        /// <summary>\n        /// Writes out text file content asynchronously.Use only with NetFx.\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"encoding\"></param>\n        /// <returns></returns>\n        public static async Task WriteAllTextAsync(string filename, string text, Encoding encoding)\n        {\n            if (filename == null)\n                throw new ArgumentNullException(nameof(filename));\n\n            using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))\n            {\n                var bytes = encoding.GetBytes(text ?? string.Empty);\n                await stream.WriteAsync(bytes, 0, bytes.Length);\n            }\n        }\n\n\n        /// <summary>\n        /// Async Write all bytes. Use only with NetFx\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"bytes\"></param>\n        /// <returns></returns>\n        /// <exception cref=\"ArgumentNullException\"></exception>\n        public static async Task WriteAllBytesAsync(string filename, byte[] bytes)\n        {\n            if (filename == null)\n                throw new ArgumentNullException(nameof(filename));\n            if (bytes == null)\n                throw new ArgumentNullException(nameof(bytes));\n\n            using (var destinationStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))\n            {\n                await destinationStream.WriteAsync(bytes, 0, bytes.Length);\n            }\n        }\n\n        #endregion\n\n        #region Folder Copying and Deleting\n\n        /// <summary>\n        /// Copies a file and creates the directory structure\n        /// if it doesn't exist.\n        /// </summary>\n        /// <param name=\"sourceFilePath\">Source file</param>\n        /// <param name=\"destinationFilePath\">Target file</param>\n        /// <param name=\"overwrite\">if true overwrites the file if possible. If the file is locked the method returns false</param>\n        /// <returns></returns>\n        public static bool CopyFileEnsureDirectory(string sourceFilePath, string destinationFilePath, bool overwrite = true)\n        {\n            try\n            {\n                // Ensure destination directory exists\n                var destinationDir = Path.GetDirectoryName(destinationFilePath);\n                if (string.IsNullOrWhiteSpace(destinationDir))\n                    return false;\n\n                Directory.CreateDirectory(destinationDir);\n\n                // Copy the file\n                File.Copy(sourceFilePath, destinationFilePath, overwrite);\n\n                return true;\n            }\n            catch (Exception)\n            {\n                // Optional: log or handle specific exceptions if needed\n                return false;\n            }\n        }\n\n\n        /// <summary>\n        /// Copies directories using either top level only or deep merge copy.\n        /// \n        /// Copies a directory by copying files from source folder to target folder.\n        /// If folder(s) don't exist they are created.\n        /// </summary>\n        /// <param name=\"sourceDirectory\">Source folder</param>\n        /// <param name=\"targetDirectory\">Target folder </param>\n        /// <param name=\"deleteFirst\">if set deletes the folder before copying</param>\n        /// <param name=\"recursive\">if set copies files recursively</param>\n        public static void CopyDirectory(string sourceDirectory, string targetDirectory, bool deleteFirst = false, bool recursive = true, bool ignoreErrors = false )\n        {\n            var diSource = new DirectoryInfo(sourceDirectory);\n            if (!diSource.Exists)\n                return;\n\n            var diTarget = new DirectoryInfo(targetDirectory);\n            CopyDirectory(diSource, diTarget, deleteFirst, recursive, ignoreErrors);\n        }\n\n        /// <summary>\n        /// Copies directories using either top level only or deep merge copy.\n        /// \n        /// Copies a directory by copying files from source folder to target folder.\n        /// If folder(s) don't exist they are created.\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"target\"></param>\n        /// <param name=\"deleteFirst\"></param>\n        /// <param name=\"recursive\"></param>\n        public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool deleteFirst = false, bool recursive = true, bool ignoreErrors =false )\n        {\n            if (!source.Exists)\n                return;\n\n            if (deleteFirst && target.Exists)\n                target.Delete(true);\n            \n            Directory.CreateDirectory(target.FullName);  // create if it doesn't exist\n \n            // Copy each file into the new directory.\n            foreach (FileInfo fi in source.GetFiles())\n            {                \n                if (ignoreErrors)\n                {\n                    try\n                    {\n                        fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);\n                    }\n                    catch { }\n                }\n                else\n                    fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);\n            }\n\n            if (recursive)\n            {\n                // Copy each subdirectory using recursion.\n                foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())\n                {\n                    DirectoryInfo nextTargetSubDir =\n                        target.CreateSubdirectory(diSourceSubDir.Name);\n                    CopyDirectory(diSourceSubDir, nextTargetSubDir, recursive: recursive, ignoreErrors: ignoreErrors);\n                }\n            }\n        }\n        /// <summary>\n        /// Deletes files in a folder based on a file spec recursively\n        /// </summary>\n        /// <param name=\"folder\"></param>\n        /// <param name=\"filespec\"></param>\n        /// <param name=\"recursive\"></param>\n        /// <returns>0 when no errors, otherwise number of files that have failed to delete (usually locked)</returns>\n        public static int DeleteFiles(string path, string filespec, bool recursive = false)\n        {\n            if (!Directory.Exists(path))\n                return 0;\n\n            int failed = 0;\n            path = Path.GetFullPath(path);\n            string spec = Path.GetFileName(filespec);\n            string[] files = Directory.GetFiles(path, spec);\n\n            foreach (string file in files)\n            {\n                try\n                {\n                    File.Delete(file);\n                }\n                catch\n                {\n                    failed++;\n                } // ignore locked files\n            }\n\n            if (recursive)\n            {\n                var dirs = Directory.GetDirectories(path);\n                foreach (string dir in dirs)\n                {\n                    failed =+ DeleteFiles(dir, filespec, recursive);\n                }\n            }\n\n\n            return failed;\n        }\n\n        /// <summary>\n        /// Deletes files based on a file spec and a given timeout.\n        /// This routine is useful for cleaning up temp files in \n        /// Web applications.\n        /// </summary>\n        /// <param name=\"filespec\">A filespec that includes path and/or wildcards to select files</param>\n        /// <param name=\"seconds\">The timeout - if files are older than this timeout they are deleted</param>\n        public static void DeleteTimedoutFiles(string filespec, int seconds)\n        {\n            string path = Path.GetDirectoryName(filespec);\n            string spec = Path.GetFileName(filespec);\n            string[] files = Directory.GetFiles(path, spec);\n\n            foreach (string file in files)\n            {\n                try\n                {\n                    if (File.GetLastWriteTimeUtc(file) < DateTime.UtcNow.AddSeconds(seconds * -1))\n                        File.Delete(file);\n                }\n                catch { }  // ignore locked files\n            }\n        }\n\n#endregion\n    }\n\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/GenericUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n    public static class GenericUtils\n    {\n        /// <summary>\n        /// Checks if an item is contained in a given list of values.\n        /// Equivalent to SQL's IN clause.\n        /// </summary>\n        public static bool InList<T>(this T item, params T[] list)\n        {\n            return list.Contains(item);\n        }\n\n        /// <summary>\n        /// Overload that takes any IEnumerable&lt;&lt;.\n        /// </summary>\n        public static bool InList<T>(this T item, IEnumerable<T> list)\n        {\n            return list.Contains(item);\n        }\n\n\n        /// <summary>\n        /// Determines whether an item is contained in a list of other items\n        /// </summary>\n        /// <example>\n        /// bool exists = Inlist&lt;string&gt;(\"Rick\",\"Mike\",\"Billy\",\"Rick\",\"Frank\"); // true;\n        /// </example>\n        /// <typeparam name=\"T\">Any type</typeparam>\n        /// <param name=\"item\">The item to look for</param>\n        /// <param name=\"list\">Any number of items to search (params)</param>\n        /// <returns></returns>\n        public static bool Inlist<T>(this T item, params T[] list)\n        {\n            return list.Contains(item);\n        }\n\n\n        /// <summary>\n        /// A string is contained in a list of strings\n        /// </summary>\n        /// <param name=\"item\">string to look for</param>\n        /// <param name=\"list\">list of strings</param>\n        /// <param name=\"caseSensitive\"></param>\n        /// <returns></returns>\n        public static bool Inlist(this string item,  bool caseSensitive, params string[] list)\n        {\n            if (caseSensitive)\n                return list.Contains(item);\n\n            foreach (var listItem in list)\n            {\n                if (listItem.Equals(item, StringComparison.OrdinalIgnoreCase))\n                    return true;\n            }\n\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/HtmlUtils.cs",
    "content": "using System;\nusing System.Text.RegularExpressions;\n\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Html string and formatting utilities\n    /// </summary>\n    public static class HtmlUtils\n    {\n        /// <summary>\n        /// Replaces and  and Quote characters to HTML safe equivalents.\n        /// </summary>\n        /// <param name=\"html\">HTML to convert</param>\n        /// <returns>Returns an HTML string of the converted text</returns>\n        public static string FixHTMLForDisplay(string html)\n        {\n            if (string.IsNullOrEmpty(html))\n                return html;\n\n            html = html.Replace(\"<\", \"&lt;\");\n            html = html.Replace(\">\", \"&gt;\");\n            html = html.Replace(\"\\\"\", \"&quot;\");\n            return html;\n        }\n\n        /// <summary>\n        /// Strips HTML tags out of an HTML string and returns just the text.\n        /// </summary>\n        /// <param name=\"html\">Html String</param>\n        /// <returns></returns>\n        public static string StripHtml(string html)\n        {\n            if (string.IsNullOrEmpty(html))\n                return html;\n\n            html = Regex.Replace(html, @\"<(.|\\n)*?>\", string.Empty);\n            html = html.Replace(\"\\t\", \" \");\n            html = html.Replace(\"\\r\\n\", string.Empty);\n            html = html.Replace(\"   \", \" \");\n            return html.Replace(\"  \", \" \");\n        }\n\n        /// <summary>\n        /// Fixes a plain text field for display as HTML by replacing carriage returns \n        /// with the appropriate br and p tags for breaks.\n        /// </summary>\n        /// <param name=\"htmlText\">Input string</param>\n        /// <returns>Fixed up string</returns>\n        public static string DisplayMemo(string htmlText)\n        {\n            if (htmlText == null)\n                return string.Empty;\n\n            htmlText = htmlText.Replace(\"\\r\\n\", \"\\r\");\n            htmlText = htmlText.Replace(\"\\n\", \"\\r\");\n            //HtmlText = HtmlText.Replace(\"\\r\\r\",\"<p>\");\n            htmlText = htmlText.Replace(\"\\r\", \"<br />\\r\\n\");\n            return htmlText;\n        }\n\n        /// <summary>\n        /// Method that handles handles display of text by breaking text.\n        /// Unlike the non-encoded version it encodes any embedded HTML text\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <returns></returns>\n        public static string DisplayMemoEncoded(string text)\n        {\n            if (text == null)\n                return string.Empty;\n\n            bool PreTag = false;\n            if (text.Contains(\"<pre>\"))\n            {\n                text = text.Replace(\"<pre>\", \"__pre__\");\n                text = text.Replace(\"</pre>\", \"__/pre__\");\n                PreTag = true;\n            }\n\n            // fix up line breaks into <br><p>\n            text = DisplayMemo(System.Net.WebUtility.HtmlEncode(text)); //HttpUtility.HtmlEncode(Text));\n\n            if (PreTag)\n            {\n                text = text.Replace(\"__pre__\", \"<pre>\");\n                text = text.Replace(\"__/pre__\", \"</pre>\");\n            }\n\n            return text;\n        }\n\n        /// <summary>\n        /// HTML-encodes a string and returns the encoded string.\n        /// </summary>\n        /// <param name=\"text\">The text string to encode. </param>\n        /// <returns>The HTML-encoded text.</returns>\n        [Obsolete(\"Use System.Net.WebUtility.HtmlEncode() instead.\")]\n        public static string HtmlEncode(string text)\n        {\n            return System.Net.WebUtility.HtmlEncode(text);\n            //if (text == null)\n            //    return string.Empty;\n\n            //StringBuilder sb = new StringBuilder(text.Length);\n\n            //int len = text.Length;\n            //for (int i = 0; i < len; i++)\n            //{\n            //    switch (text[i])\n            //    {\n\n            //        case '<':\n            //            sb.Append(\"&lt;\");\n            //            break;\n            //        case '>':\n            //            sb.Append(\"&gt;\");\n            //            break;\n            //        case '\"':\n            //            sb.Append(\"&quot;\");\n            //            break;\n            //        case '&':\n            //            sb.Append(\"&amp;\");\n            //            break;\n            //        case '\\'':\n            //            sb.Append(\"&#39;\");\n            //            break;\t\t\t\t\n            //        default:\n            //            if (text[i] > 159)\n            //            {\n            //                // decimal numeric entity\n            //                sb.Append(\"&#\");\n            //                sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture));\n            //                sb.Append(\";\");\n            //            }\n            //            else\n            //                sb.Append(text[i]);\n            //            break;\n            //    }\n            //}\n            //return sb.ToString();\n        }\n\n        /// <summary>\n        /// Create an embedded image url for binary data like images and media\n        /// </summary>\n        /// <param name=\"imageBytes\"></param>\n        /// <param name=\"mimeType\"></param>\n        /// <returns></returns>\n        public static string BinaryToEmbeddedBase64(byte[] imageBytes, string mimeType = \"image/png\")\n        {\n            if (imageBytes == null) return null;\n\n            var data = $\"data:{mimeType};base64,\" + Convert.ToBase64String(imageBytes, 0, imageBytes.Length);\n            return data;\n        }\n\n#if NET6_0_OR_GREATER\n        /// <summary>\n        /// Decoded an embedded base64 resource string into its binary content and mime type\n        /// </summary>\n        /// <param name=\"base64Data\">Embedded Base64 data (data:mime/type;b64data) </param>\n        /// <returns></returns>\n        public static (byte[] bytes, string mimeType) EmbeddedBase64ToBinary(string base64Data)\n        {\n            if (string.IsNullOrEmpty(base64Data))\n                return (null, null);\n\n            var parts = base64Data.Split(',');\n            if (parts.Length != 2)\n                return (null, null);\n\n            var mimeType = parts[0].Replace(\"data:\", \"\").Replace(\";base64\", \"\");\n            var data = parts[1];\n\n            var bytes = Convert.FromBase64String(data);\n\n            return (bytes, mimeType);\n        }\n#endif        \n\n        /// <summary>\n        /// Creates an Abstract from an HTML document. Strips the \n        /// HTML into plain text, then creates an abstract.\n        /// </summary>\n        /// <param name=\"html\"></param>\n        /// <returns></returns>\n        public static string HtmlAbstract(string html, int length)\n        {\n            if (string.IsNullOrEmpty(html))\n                return string.Empty;    \n            return StringUtils.TextAbstract(StripHtml(html), length);\n        }\n\n\n        static string DefaultHtmlSanitizeTagBlackList { get; } = \"script|iframe|object|embed|form\";\n\n        static Regex _RegExScript = new Regex($@\"(<({DefaultHtmlSanitizeTagBlackList})\\b[^<]*(?:(?!<\\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\\/({DefaultHtmlSanitizeTagBlackList})>)\",\n        RegexOptions.IgnoreCase | RegexOptions.Multiline);\n\n        // strip javascript: and unicode representation of javascript:\n        // href='javascript:alert(\\\"gotcha\\\")'\n        // href='&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:alert(\\\"gotcha\\\");'\n        static Regex _RegExJavaScriptHref = new Regex(\n            @\"<[^>]*?\\s(href|src|dynsrc|lowsrc)=.{0,20}((javascript:)|(&#)).*?>\",\n            RegexOptions.IgnoreCase | RegexOptions.Singleline);\n\n        static Regex _RegExOnEventAttributes = new Regex(\n            @\"<[^>]*?\\s(on[^\\s\\\\]{0,20}=([\"\"].*?[\"\"]|['].*?['])).*?(>|\\/>)\",\n            RegexOptions.IgnoreCase | RegexOptions.Singleline);\n\n        /// <summary>\n        /// Sanitizes HTML to some of the most of \n        /// </summary>\n        /// <remarks>\n        /// This provides rudimentary HTML sanitation catching the most obvious\n        /// XSS script attack vectors. For mroe complete HTML Sanitation please look into\n        /// a dedicated HTML Sanitizer.\n        /// </remarks>\n        /// <param name=\"html\">input html</param>\n        /// <param name=\"htmlTagBlacklist\">A list of HTML tags that are stripped.</param>\n        /// <returns>Sanitized HTML</returns>\n        public static string SanitizeHtml(string html, string htmlTagBlacklist = \"script|iframe|object|embed|form\")\n        {\n            if (string.IsNullOrEmpty(html))\n                return html;\n\n            if (string.IsNullOrEmpty(htmlTagBlacklist) || htmlTagBlacklist == DefaultHtmlSanitizeTagBlackList)\n            {\n                // Use the default list of tags Replace Script tags - reused expr is more efficient\n                html = _RegExScript.Replace(html, string.Empty);\n            }\n            else\n            {\n                // create a custom list including provided tags\n                html = Regex.Replace(html,\n                                        $@\"(<({htmlTagBlacklist})\\b[^<]*(?:(?!<\\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\\/({htmlTagBlacklist})>)\",\n                                        \"\", RegexOptions.IgnoreCase | RegexOptions.Multiline);\n            }\n\n            // Remove javascript: directives\n            var matches = _RegExJavaScriptHref.Matches(html);\n            foreach (Match match in matches)\n            {\n                if (match.Groups.Count > 2)\n                {\n                    var txt = match.Value.Replace(match.Groups[2].Value, \"unsupported:\");\n                    html = html.Replace(match.Value, txt);\n                }\n            }\n\n            // Remove onEvent handlers from elements\n            matches = _RegExOnEventAttributes.Matches(html);\n            foreach (Match match in matches)\n            {\n                var txt = match.Value;\n                if (match.Groups.Count > 1)\n                {\n                    var onEvent = match.Groups[1].Value;\n                    txt = txt.Replace(onEvent, string.Empty);\n                    if (!string.IsNullOrEmpty(txt))\n                        html = html.Replace(match.Value, txt);\n                }\n            }\n\n            return html;\n        }\n\n        /// <summary>\n        /// Create an Href HTML link.\n        /// \n        /// \n        /// <remarks>\n        /// For NetFx in ASPX applications ~ resolves to the application root\n        /// </remarks>\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"url\"></param>\n        /// <param name=\"target\"></param>\n        /// <param name=\"additionalMarkup\"></param>\n        /// <returns></returns>\n        public static string Href(string text, string url, string target = null, string additionalMarkup = null)\n        {\n\n#if NETFULL\n            if (url.StartsWith(\"~\"))\n                url = ResolveUrl(url);\n#endif\n\n            return \"<a href=\\\"\" + url + \"\\\" \" +\n                   (string.IsNullOrEmpty(target) ? string.Empty : \"target=\\\"\" + target + \"\\\" \") +\n                   (string.IsNullOrEmpty(additionalMarkup) ? string.Empty : additionalMarkup) +\n                   \">\" + text + \"</a>\";\n        }\n\n        /// <summary>\n        /// Creates an HREF HTML Link\n        /// </summary>\n        /// <param name=\"url\"></param>\n        public static string Href(string url)\n        {\n            return Href(url, url, null, null);\n        }\n\n        /// <summary>\n        /// Returns an IMG link as a string. If the image is null\n        /// or empty a blank string is returned.\n        /// </summary>\n        /// <param name=\"imageUrl\"></param>\n        /// <param name=\"additionalMarkup\">any additional attributes added to the element</param>\n        /// <returns></returns>\n        public static string ImgRef(string imageUrl, string additionalMarkup = null)\n        {\n            if (string.IsNullOrEmpty(imageUrl))\n                return string.Empty;\n\n#if NETFULL\n            if (imageUrl.StartsWith(\"~\"))\n                imageUrl = ResolveUrl(imageUrl);\n#endif\n\n            string img = \"<img src=\\\"\" + imageUrl + \"\\\" \";\n\n            if (!string.IsNullOrEmpty(\"additionalMarkup\"))\n                img += additionalMarkup + \" \";\n\n            img += \"/>\";\n            return img;\n        }\n\n#if NETFULL\n        /// <summary>\n        /// Resolves a URL based on the current HTTPContext\n        /// \n        /// Note this method is added here internally only\n        /// to support the HREF() method and ~ expansion\n        /// on urls.\n        /// </summary>\n        /// <param name=\"originalUrl\"></param>\n        /// <returns></returns>\n        public static string ResolveUrl(string originalUrl)\n        {\n            if (string.IsNullOrEmpty(originalUrl))\n                return string.Empty;\n\n            // Absolute path - just return\n            if (originalUrl.IndexOf(\"://\") != -1)\n                return originalUrl;\n\n            // Fix up image path for ~ root app dir directory\n            if (originalUrl.StartsWith(\"~\"))\n            {\n                //return VirtualPathUtility.ToAbsolute(originalUrl);\n                string newUrl = \"\";\n\n                // Avoid pulling in System.Web reference\n                dynamic context = ReflectionUtils.GetStaticProperty( \"System.Web.HttpContext\", \"Current\");\n                if (context != null)\n                {\n                    newUrl = context.Request.ApplicationPath +\n                          originalUrl.Substring(1);\n                    newUrl = newUrl.Replace(\"//\", \"/\"); // must fix up for root path\n                }\n                else\n                    // Not context: assume current directory is the base directory\n                    throw new ArgumentException(\"Invalid URL: Relative Urls are only available in classic ASP.NET Web applications.\");\n\n                // Just to be sure fix up any double slashes\n                return newUrl;\n            }\n\n            return originalUrl;\n        }\n#endif\n        #region Url Parsing\n\n        /// <summary>\n        /// Returns the base URL of a site from a full URL\n        /// </summary>\n        /// <param name=\"url\">An absolute scheme path (http:// or file:// or ftp:// etc) </param>\n        /// <returns></returns>\n        public static string GetSiteBasePath(string url)\n        {\n            var uri = new Uri(url);\n            return $\"{uri.Scheme}://{uri.Authority}/\";\n        }\n\n        /// <summary>\n        /// Returns the path portion of an absolute URL optionally with the query string and fragment\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"pathOptions\"></param>\n        /// <returns></returns>\n        public static string GetRelativeUrlPath(string url, PathReturnOptions pathOptions = PathReturnOptions.PathOnly )\n        {\n            var uri = new Uri(url);\n\n            switch (pathOptions)\n            {\n                case PathReturnOptions.PathOnly:\n                    return uri.AbsolutePath;\n                case PathReturnOptions.PathAndQuery:\n                    return uri.PathAndQuery;\n                case PathReturnOptions.PathAndHash:\n                    return uri.AbsolutePath + uri.Fragment;\n                case PathReturnOptions.PathAndQueryAndHash:\n                    return uri.PathAndQuery + uri.Fragment;\n            }\n\n            return uri.PathAndQuery;\n        }\n\n        #endregion\n    }\n\n    public enum PathReturnOptions\n    {\n        PathOnly,\n        PathAndQuery,\n        PathAndHash,\n        PathAndQueryAndHash,        \n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/HttpClientUtils.cs",
    "content": "﻿using Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Runtime.CompilerServices;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Http Client wrapper that provides single line access for common Http requests\n    /// that return string, Json or binary content. \n    /// \n    /// Most methods have dual versions using simple parameters, or an\n    /// `HttpClientRequestSettings` configuration and results object that is\n    /// passed through on requests.    \n    /// </summary>\n    public class HttpClientUtils\n    {\n        public const string STR_MultipartBoundary = \"----FormBoundary3vDSIXiW0WSTB551\";\n\n        #region String Download\n\n        /// <summary>\n        /// Runs an Http request and returns success results as a string or null\n        /// on failure or non-200/300 requests.\n        /// \n        /// Failed requests return null and set the settings.ErrorMessage property\n        /// but you can can access the `settings.Response` or use the \n        /// `settings.GetResponseStringAsync()` method or friends to retrieve \n        /// content despite the error.\n        /// </summary>\n        /// <remarks>\n        /// By default this method does not throw on errors or error status codes,\n        /// but returns null and an error message. For error requests you can check\n        /// settings.HasResponseContent and then either directly access settings.Response,\n        /// or use settings.GetResponseStringAsync() or friends to retrieve error content.\n        /// \n        /// If you want the method to throw exceptions on errors an > 400 status codes,\n        /// use `settings.ThrowExceptions = true`.\n        /// </remarks>\n        /// <param name=\"settings\">Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more</param>\n        /// <returns>string of HTTP response</returns>\n        public static async Task<string> DownloadStringAsync(HttpClientRequestSettings settings)\n        {\n            string content = null;\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = await client.SendAsync(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return null;\n                }\n\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var stream = await settings.Response.Content.ReadAsStreamAsync())\n                                {\n                                    var buffer = new byte[settings.MaxResponseSize];\n                                    _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);\n                                    content = settings.Encoding.GetString(buffer);\n                                }\n                            }\n                            else\n                            {\n                                content = await settings.Response.Content.ReadAsStringAsync();\n                            }\n                        }\n\n                        return content;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return null;\n                }\n                catch (Exception ex)\n                {\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n\n\n#if NET6_0_OR_GREATER\n\n#endif\n\n\n        /// <summary>\n        /// Runs an Http request and returns success results as a string or null\n        /// on failure or non-200/300 requests.\n        /// \n        /// Failed requests return null and set the settings.ErrorMessage property\n        /// but you can can access the `settings.Response` or use the \n        /// `settings.GetResponseStringAsync()` method or friends to retrieve \n        /// content despite the error.\n        /// </summary>\n        /// <remarks>\n        /// By default this method does not throw on errors or error status codes,\n        /// but returns null and an error message. For error requests you can check\n        /// settings.HasResponseContent and then either directly access settings.Response,\n        /// or use settings.GetResponseStringAsync() or friends to retrieve error content.\n        /// \n        /// If you want the method to throw exceptions on errors an > 400 status codes,\n        /// use `settings.ThrowExceptions = true`.\n        /// </remarks>        \n        /// <param name=\"url\">The url to access</param>      \n        /// <param name=\"data\">The data to send. String data is sent as is all other data is JSON encoded.</param>\n        /// <param name=\"contentType\">Optional Content type for the request</param>\n        /// <param name=\"verb\">The HTTP Verb to use (GET,POST,PUT,DELETE etc.)</param>\n        /// <returns>string of HTTP response</returns>\n        public static async Task<string> DownloadStringAsync(string url, object data = null, string contentType = null,\n            string verb = null)\n        {\n            if (string.IsNullOrEmpty(verb))\n            {\n                if (data != null)\n                    verb = \"POST\";\n                else\n                    verb = \"GET\";\n            }\n\n            return await DownloadStringAsync(new HttpClientRequestSettings\n            {\n                Url = url,\n                HttpVerb = verb,\n                RequestContent = data,\n                RequestContentType = contentType\n            });\n        }\n\n#if NET6_0_OR_GREATER\n\n        /// <summary>\n        /// Synchronous version of `DownloadStringAsync`.\n        /// </summary>\n        /// <param name=\"settings\">Request/Response settings instance</param>\n        /// <returns>string or null on failure</returns>\n        public static string DownloadString(HttpClientRequestSettings settings)\n        {\n            string content = null;\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = client.Send(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return null;\n                }\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var stream = settings.Response.Content.ReadAsStream())\n                                {\n                                    var buffer = new byte[settings.MaxResponseSize];\n                                    _ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize);\n                                    content = settings.Encoding.GetString(buffer);\n                                }\n                            }\n                            else\n                            {\n                                //content = await settings.Response.Content.ReadAsStringAsync();\n                                using (var stream = settings.Response.Content.ReadAsStream())\n                                {\n                                    var sr = new StreamReader(stream, true);\n                                    content = sr.ReadToEnd();\n                                }\n                            }\n                        }\n\n                        return content;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return null;\n                }\n                catch (Exception ex)\n                {\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n\n        /// <summary>\n        /// Runs an Http request and returns success results as a string or null\n        /// on failure or non-200/300 requests.\n        /// \n        /// Failed requests return null and set the settings.ErrorMessage property\n        /// but you can can access the `settings.Response` or use the \n        /// `settings.GetResponseStringAsync()` method or friends to retrieve \n        /// content despite the error.\n        /// </summary>\n        /// <remarks>\n        /// By default this method does not throw on errors or error status codes,\n        /// but returns null and an error message. For error requests you can check\n        /// settings.HasResponseContent and then either directly access settings.Response,\n        /// or use settings.GetResponseStringAsync() or friends to retrieve error content.\n        /// \n        /// If you want the method to throw exceptions on errors an > 400 status codes,\n        /// use `settings.ThrowExceptions = true`.\n        /// </remarks>        \n        /// <param name=\"url\">The url to access</param>      \n        /// <param name=\"data\">The data to send. String data is sent as is all other data is JSON encoded.</param>\n        /// <param name=\"contentType\">Optional Content type for the request</param>\n        /// <param name=\"verb\">The HTTP Verb to use (GET,POST,PUT,DELETE etc.)</param>\n        /// <returns>string of HTTP response</returns>\n        public static string DownloadString(string url, object data = null, string contentType = null,\n            string verb = null)\n        {\n            if (string.IsNullOrEmpty(verb))\n            {\n                if (data != null)\n                    verb = \"POST\";\n                else\n                    verb = \"GET\";\n            }\n\n            return DownloadString(new HttpClientRequestSettings\n            {\n                Url = url,\n                HttpVerb = verb,\n                RequestContent = data,\n                RequestContentType = contentType\n            });\n        }\n#endif\n\n\n        #endregion\n\n\n        #region File Download\n\n        /// <summary>\n        /// Downloads a Url to a file.\n        /// </summary>             \n        /// <param name=\"url\">Optional Url to download - settings.Url works too</param>\n        /// <param name=\"filename\">Option filename to download to - settings.OutputFilename works too</param>\n        /// <param name=\"settings\">Http request settings can be used in lieu of other parameters</param>\n        /// <remarks></remarks>\n        /// <returns>true or fals</returns>\n        public static async Task<bool> DownloadFileAsync(HttpClientRequestSettings settings, string filename = null)\n        {\n            if (settings == null)\n                return false;\n\n            if (string.IsNullOrEmpty(settings.OutputFilename))\n            {\n                settings.HasErrors = true;\n                settings.ErrorMessage = \"No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`.\";\n                return false;\n            }\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = await client.SendAsync(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return false;\n                }\n\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (File.Exists(settings.OutputFilename))\n                            File.Delete(settings.OutputFilename);\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,\n                                           FileAccess.Write))\n                                {\n                                    using (var stream = await settings.Response.Content.ReadAsStreamAsync())\n                                    {\n                                        var buffer = new byte[settings.MaxResponseSize];\n                                        _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);\n                                        await outputStream.WriteAsync(buffer, 0, buffer.Length);\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,\n                                           FileAccess.Write))\n                                {\n                                    using (var stream = await settings.Response.Content.ReadAsStreamAsync())\n                                    {\n                                        await stream.CopyToAsync(outputStream, 8 * 1024);\n                                    }\n                                }\n                            }\n                        }\n\n                        return true;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return false;\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return false;\n                }\n            }\n        }\n        /// <summary>\n        /// Downloads a Url to a file.\n        /// </summary>             \n        /// <param name=\"url\">Optional Url to download - settings.Url works too</param>\n        /// <param name=\"filename\">Option filename to download to - settings.OutputFilename works too</param>\n        /// <param name=\"settings\">Http request settings can be used in lieu of other parameters</param>\n        /// <remarks></remarks>\n        /// <returns>true or fals</returns>\n        public static Task<bool> DownloadFileAsync(string url, string filename)\n        {\n            var settings = new HttpClientRequestSettings();\n            if (!string.IsNullOrEmpty(url))\n                settings.Url = url;\n            if (!string.IsNullOrEmpty(filename))\n                settings.OutputFilename = filename;\n\n            return DownloadFileAsync(settings);\n        }\n\n#if NET6_0_OR_GREATER\n\n        /// <summary>\n        /// Downloads a Url to a file.\n        /// </summary>             \n        /// <param name=\"url\">Optional Url to download - settings.Url works too</param>\n        /// <param name=\"filename\">Option filename to download to - settings.OutputFilename works too</param>\n        /// <param name=\"settings\">Http request settings can be used in lieu of other parameters</param>\n        /// <remarks></remarks>\n        /// <returns>true or fals</returns>\n        public static bool DownloadFile(HttpClientRequestSettings settings, string filename = null)\n        {\n            if (settings == null)\n                return false;\n\n            if (string.IsNullOrEmpty(settings.OutputFilename))\n            {\n                settings.HasErrors = true;\n                settings.ErrorMessage = \"No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`.\";\n                return false;\n            }\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = client.Send(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return false;\n                }\n\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (File.Exists(settings.OutputFilename))\n                            File.Delete(settings.OutputFilename);\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,\n                                           FileAccess.Write))\n                                {\n                                    using (var stream = settings.Response.Content.ReadAsStream())\n                                    {\n                                        var buffer = new byte[settings.MaxResponseSize];\n                                        _ = stream.Read(buffer, 0, settings.MaxResponseSize);\n                                        outputStream.Write(buffer, 0, buffer.Length);\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate,\n                                           FileAccess.Write))\n                                {\n                                    using (var stream =settings.Response.Content.ReadAsStream())\n                                    {\n                                        stream.CopyTo(outputStream, 8 * 1024);\n                                    }\n                                }\n                            }\n                        }\n\n                        return true;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return false;\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return false;\n                }\n            }\n        }\n        /// <summary>\n        /// Downloads a Url to a file.\n        /// </summary>             \n        /// <param name=\"url\">Optional Url to download - settings.Url works too</param>\n        /// <param name=\"filename\">Option filename to download to - settings.OutputFilename works too</param>\n        /// <param name=\"settings\">Http request settings can be used in lieu of other parameters</param>\n        /// <remarks></remarks>\n        /// <returns>true or fals</returns>\n        public static bool DownloadFile(string url, string filename)\n        {\n            var settings = new HttpClientRequestSettings();\n            if (!string.IsNullOrEmpty(url))\n                settings.Url = url;\n            if (!string.IsNullOrEmpty(filename))\n                settings.OutputFilename = filename;\n\n            return DownloadFile(settings);\n        }\n\n#endif\n\n        /// <summary>\n        /// Downloads an image to a temporary file or a file you specify, automatically\n        /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)\n        /// Always use the return value to receive the final image file name.\n        ///\n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"imageUrl\">Url of image to download</param>\n        /// <param name=\"filename\">\n        /// Optional output image file name. Filename may change extension if the image format doesn't match the filename.\n        /// If not passed a temporary files file is created in the temp file location and you can move the file\n        /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.\n        /// </param>\n        /// <param name=\"settings\">Optional more detailed Http Settings for the request</param>\n        /// <returns>file name that was created or null</returns>\n        public static async Task<string> DownloadImageToFileAsync(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)\n        {\n            if (settings == null)\n            {\n                if (string.IsNullOrEmpty(imageUrl))\n                    return null;\n                settings = new();\n            }\n\n            if (!string.IsNullOrEmpty(imageUrl))\n                settings.Url = imageUrl;\n            imageUrl = settings.Url;\n            if (!string.IsNullOrEmpty(filename))\n                settings.OutputFilename = filename;\n            filename = settings.OutputFilename;\n\n\n            if (string.IsNullOrEmpty(imageUrl) ||\n                !imageUrl.StartsWith(\"http://\") && !imageUrl.StartsWith(\"https://\"))\n                return null;\n           \n            // if no filename is specified at all use a temp file\n            if (string.IsNullOrEmpty(filename))\n            {\n                filename = Path.Combine(Path.GetTempPath(), \"~img-\" + DataUtils.GenerateUniqueId());\n            }\n            // we download to a temp file\n            filename = Path.ChangeExtension(filename, \"bin\");\n\n            string newFilename;\n\n            try\n            {\n                settings.OutputFilename = filename;\n\n                await DownloadFileAsync(settings);\n\n                var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);\n                if (ext == null)\n                {\n                    if (File.Exists(filename))\n                        File.Delete(filename);\n                    return null; // invalid image type\n                }\n\n                newFilename = Path.ChangeExtension(filename, ext);\n\n\n                if (File.Exists(newFilename))\n                    File.Delete(newFilename);\n\n                // rename the file\n                File.Move(filename, newFilename);\n            }\n            catch\n            {\n                if (File.Exists(filename))\n                    File.Delete(filename);\n\n                return null;\n            }\n\n            return newFilename;\n        }\n\n\n        /// <summary>\n        /// Downloads an image to a temporary file or a file you specify, automatically\n        /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)\n        /// Always use the return value to receive the final image file name.\n        ///\n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"settings\">Must specify Url and optionally OutputFilename - see parametered version</param>\n        /// <returns>file name that was created or null</returns>\n        public static async Task<string> DownloadImageToFileAsync( HttpClientRequestSettings settings = null) => await DownloadImageToFileAsync(null, null, settings);\n\n#if NET6_0_OR_GREATER\n\n        /// <summary>\n        /// Downloads an image to a temporary file or a file you specify, automatically\n        /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)\n        /// Always use the return value to receive the final image file name.\n        ///\n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"imageUrl\">Url of image to download</param>\n        /// <param name=\"filename\">\n        /// Optional output image file name. Filename may change extension if the image format doesn't match the filename.\n        /// If not passed a temporary files file is created in the temp file location and you can move the file\n        /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.\n        /// </param>\n        /// <param name=\"settings\">Optional more detailed Http Settings for the request</param>\n        /// <returns>file name that was created or null</returns>\n        public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)\n        {\n            if (settings == null)\n            {\n                if (string.IsNullOrEmpty(imageUrl))\n                    return null;\n                settings = new();\n            }\n\n            if (!string.IsNullOrEmpty(imageUrl))\n                settings.Url = imageUrl;\n            imageUrl = settings.Url;\n            if (!string.IsNullOrEmpty(filename))\n                settings.OutputFilename = filename;\n            filename = settings.OutputFilename;\n\n\n            if (string.IsNullOrEmpty(imageUrl) ||\n                !imageUrl.StartsWith(\"http://\") && !imageUrl.StartsWith(\"https://\"))\n                return null;\n\n            // if no filename is specified at all use a temp file\n            if (string.IsNullOrEmpty(filename))\n            {\n                filename = Path.Combine(Path.GetTempPath(), \"~img-\" + DataUtils.GenerateUniqueId());\n            }\n            // we download to a temp file\n            filename = Path.ChangeExtension(filename, \"bin\");\n\n            string newFilename;\n\n            try\n            {\n                settings.OutputFilename = filename;\n\n                DownloadFile(settings);\n\n                var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);\n                if (ext == null)\n                {\n                    if (File.Exists(filename))\n                        File.Delete(filename);\n                    return null; // invalid image type\n                }\n\n                newFilename = Path.ChangeExtension(filename, ext);\n\n\n                if (File.Exists(newFilename))\n                    File.Delete(newFilename);\n\n                // rename the file\n                File.Move(filename, newFilename);\n            }\n            catch\n            {\n                if (File.Exists(filename))\n                    File.Delete(filename);\n\n                return null;\n            }\n\n            return newFilename;\n        }\n\n\n        /// <summary>\n        /// Downloads an image to a temporary file or a file you specify, automatically\n        /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)\n        /// Always use the return value to receive the final image file name.\n        ///\n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"settings\">Must specify Url and optionally OutputFilename - see parametered version</param>\n        /// <returns>file name that was created or null</returns>\n        public static string DownloadImageToFile(HttpClientRequestSettings settings = null) => DownloadImageToFile(null, null, settings);\n\n#endif\n\n#endregion\n\n\n        #region Byte Data Download\n\n\n        /// <summary>\n        /// Runs an Http Request and returns a byte array from the response or null on failure\n        /// </summary>\n        /// <param name=\"settings\">Pass in a settings object</param>\n        /// <returns>byte[] or null - if null check settings for errors</returns>\n        public static async Task<byte[]> DownloadBytesAsync(HttpClientRequestSettings settings)\n        {\n            byte[] content = null;\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = await client.SendAsync(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return null;\n                }\n\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var stream = await settings.Response.Content.ReadAsStreamAsync())\n                                {\n                                    var buffer = new byte[settings.MaxResponseSize];\n                                    _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize);\n                                    content = buffer;\n                                }\n                            }\n                            else\n                            {\n                                content = await settings.Response.Content.ReadAsByteArrayAsync();\n                            }\n                        }\n\n                        return content;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return null;\n                }\n                catch (Exception ex)\n                {\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n\n#if Net6_0_OR_GREATER\n        public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null)\n        {\n            if (settings == null)\n            {\n                if (string.IsNullOrEmpty(imageUrl))\n                    return null;\n                settings = new();\n            }\n\n            if (!string.IsNullOrEmpty(imageUrl))\n                settings.Url = imageUrl;\n            imageUrl = settings.Url;\n            if (!string.IsNullOrEmpty(filename))\n                settings.OutputFilename = filename;\n            filename = settings.OutputFilename;\n\n\n            if (string.IsNullOrEmpty(imageUrl) ||\n                !imageUrl.StartsWith(\"http://\") && !imageUrl.StartsWith(\"https://\"))\n                return null;\n\n            // if no filename is specified at all use a temp file\n            if (string.IsNullOrEmpty(filename))\n            {\n                filename = Path.Combine(Path.GetTempPath(), \"~img-\" + DataUtils.GenerateUniqueId());\n            }\n            // we download to a temp file\n            filename = Path.ChangeExtension(filename, \"bin\");\n\n            string newFilename;\n\n            try\n            {\n                settings.OutputFilename = filename;\n                \n                DownloadFile(settings);\n\n                var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType);\n                if (ext == null)\n                {\n                    if (File.Exists(filename))\n                        File.Delete(filename);\n                    return null; // invalid image type\n                }\n\n                newFilename = Path.ChangeExtension(filename, ext);\n\n\n                if (File.Exists(newFilename))\n                    File.Delete(newFilename);\n\n                // rename the file\n                File.Move(filename, newFilename);\n            }\n            catch\n            {\n                if (File.Exists(filename))\n                    File.Delete(filename);\n\n                return null;\n            }\n\n            return newFilename;\n        }\n\n#endif\n\n        public static async Task<byte[]> DownloadBytesAsync(string url, object data = null, string contentType = null,\n            string verb = null)\n        {\n            if (string.IsNullOrEmpty(verb))\n            {\n                if (data != null)\n                    verb = \"POST\";\n                else\n                    verb = \"GET\";\n            }\n\n            return await DownloadBytesAsync(new HttpClientRequestSettings\n            {\n                Url = url,\n                HttpVerb = verb,\n                RequestContent = data,\n                RequestContentType = contentType\n            });\n        }\n\n\n#if NET6_0_OR_GREATER\n\n        /// <summary>\n        /// Synchronous version of `DownloadBytesAsync`.\n        /// </summary>\n        /// <param name=\"settings\">Request/Response settings instance</param>\n        /// <returns>string or null on failure</returns>\n        public static byte[] DownloadBytes(HttpClientRequestSettings settings)\n        {\n            byte[] content = null;\n\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = client.Send(settings.Request);\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n                    return null;\n                }\n\n                // Capture the response content\n                try\n                {\n                    if (settings.Response.IsSuccessStatusCode)\n                    {\n                        // http 201 no content may return null and be success\n\n                        if (settings.HasResponseContent)\n                        {\n                            if (settings.MaxResponseSize > 0)\n                            {\n                                using (var stream = settings.Response.Content.ReadAsStream())\n                                {\n                                    var buffer = new byte[settings.MaxResponseSize];\n                                    _ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize);\n                                    content = buffer;\n                                }\n                            }\n                            else\n                            {\n                                using (var stream = settings.Response.Content.ReadAsStream())\n                                {\n                                    using (var ms = new MemoryStream())\n                                    {\n                                        stream.CopyTo(ms);\n                                        content = ms.ToArray();\n                                    }\n                                }\n                            }\n                        }\n\n                        return content;\n                    }\n\n                    settings.HasErrors = true;\n                    settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + \" \" +\n                                            settings.Response.StatusCode.ToString();\n\n                    if (settings.ThrowExceptions)\n                        throw new HttpRequestException(settings.ErrorMessage);\n\n                    // return null but allow for explicit response reading\n                    return null;\n                }\n                catch (Exception ex)\n                {\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n\n\n        /// <summary>\n        /// Synchronous version of `DownloadBytesAsync`.\n        /// </summary>       \n        /// <param name=\"url\">Request URL</param>\n        /// <param name=\"data\">Request data</param>        \n        /// <param name=\"contentType\">Request content type</param>\n        /// <param name=\"verb\">HTTP verb (GET, POST, etc.)</param>\n        /// <returns>string or null on failure</returns>\n        public static byte[] DownloadBytes(string url, object data = null, string contentType = null,\n            string verb = null)\n        {\n            if (string.IsNullOrEmpty(verb))\n            {\n                if (data != null)\n                    verb = \"POST\";\n                else\n                    verb = \"GET\";\n            }\n\n            return DownloadBytes(new HttpClientRequestSettings\n            {\n                Url = url,\n                HttpVerb = verb,\n                RequestContent = data,\n                RequestContentType = contentType\n            });\n        }\n#endif\n\n#endregion\n\n        #region Json\n\n        /// <summary>\n        /// Makes a JSON request that returns a JSON result.\n        /// </summary>\n        /// <typeparam name=\"TResult\">Result type to deserialize to</typeparam>\n        /// <param name=\"settings\">Configuration for this request</param>\n        /// <returns></returns>\n        public static async Task<TResult> DownloadJsonAsync<TResult>(HttpClientRequestSettings settings)\n{\n    settings.RequestContentType = \"application/json\";\n    settings.Encoding = Encoding.UTF8;\n\n    string json = await DownloadStringAsync(settings);\n\n    if (json == null)\n    {\n        return default;\n    }\n\n    try\n    {\n        return JsonConvert.DeserializeObject<TResult>(json);\n    }\n    catch (Exception ex)\n    {\n        // original error has priority\n        if (settings.HasErrors)\n            return default;\n\n        settings.HasErrors = true;\n        settings.ErrorMessage = ex.GetBaseException().Message;\n        settings.ErrorException = ex;\n    }\n\n    return default;\n}\n\npublic static async Task<TResult> DownloadJsonAsync<TResult>(string url, string verb = \"GET\",\n    object data = null)\n{\n    return await DownloadJsonAsync<TResult>(new HttpClientRequestSettings\n    {\n        Url = url,\n        HttpVerb = verb,\n        RequestContent = data,\n        RequestContentType = data != null ? \"application/json\" : null\n    });\n}\n\n#if NET6_0_OR_GREATER\n\n/// <summary>\n/// Makes a JSON request that returns a JSON result.\n/// </summary>\n/// <typeparam name=\"TResult\">Result type to deserialize to</typeparam>\n/// <param name=\"settings\">Configuration for this request</param>\n/// <returns>Result or null - check ErrorMessage in settings on failure</returns>\npublic static TResult DownloadJson<TResult>(HttpClientRequestSettings settings)\n{\n    settings.RequestContentType = \"application/json\";\n    settings.Encoding = Encoding.UTF8;\n\n    string json = DownloadString(settings);\n\n    if (json == null)\n    {\n        return default;\n    }\n\n    try\n    {\n        return JsonConvert.DeserializeObject<TResult>(json);\n    }\n    catch (Exception ex)\n    {\n        // original error has priority\n        if (settings.HasErrors)\n            return default;\n\n        settings.HasErrors = true;\n        settings.ErrorMessage = ex.GetBaseException().Message;\n        settings.ErrorException = ex;\n    }\n\n    return default;\n}\n\n\n/// <summary>\n/// Makes a JSON request that returns a JSON result.\n/// </summary>\n/// <param name=\"url\">Request URL</param>\n/// <param name=\"verb\">Http Verb to use. Defaults to GET on no data or POST when data is passed.</param>\n/// <param name=\"data\">Data to be serialized to JSON for sending</param>\n/// <returns>result or null</returns>\npublic static TResult DownloadJson<TResult>(string url, string verb = \"GET\", object data = null)\n{\n    return DownloadJson<TResult>(new HttpClientRequestSettings\n    {\n        Url = url,\n        HttpVerb = verb,\n        RequestContent = data,\n        RequestContentType = data != null ? \"application/json\" : null\n    });\n}\n\n#endif\n#endregion\n\n        #region Http Response\n        /// <summary>\n        /// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you \n        /// can read the response content from settings.Response.Content.ReadAsXXX() methods.\n        /// </summary>\n        /// <param name=\"settings\">Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more</param>\n        /// <returns>string of HTTP response</returns>\n        public static async Task<HttpResponseMessage> DownloadResponseMessageAsync(HttpClientRequestSettings settings)\n        {\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = await client.SendAsync(settings.Request);\n\n                    if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode)\n                        throw new HttpRequestException(settings.ResponseStatusCode + \" \" +\n                                                       settings.Response.ReasonPhrase);\n\n                    return settings.Response;\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n\n\n\n#if NET6_0_OR_GREATER\n\n        /// <summary>\n        /// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you \n        /// can read the response content from settings.Response.Content.ReadAsXXX() methods.\n        /// </summary>\n        /// <param name=\"settings\">Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more</param>\n        /// <returns>string of HTTP response</returns>\n        public static HttpResponseMessage DownloadResponseMessage(HttpClientRequestSettings settings)\n        {\n            using (var client = GetHttpClient(null, settings))\n            {\n                try\n                {\n                    settings.Response = client.Send(settings.Request);\n\n                    if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode)\n                        throw new HttpRequestException(settings.ResponseStatusCode + \" \" +\n                                                       settings.Response.ReasonPhrase);\n\n                    return settings.Response;\n                }\n                catch (Exception ex)\n                {\n                    settings.HasErrors = true;\n                    settings.ErrorException = ex;\n                    settings.ErrorMessage = ex.GetBaseException().Message;\n\n                    if (settings.ThrowExceptions)\n#pragma warning disable CA2200\n                        throw;\n#pragma warning restore CA2200\n\n                    return null;\n                }\n            }\n        }\n#endif\n\n        #endregion\n\n        #region Helpers\n        /// <summary>\n        /// Creates an instance of the HttpClient and sets the API Key\n        /// in the headers.\n        /// </summary>\n        /// <returns>Configured HttpClient instance</returns>\n        public static HttpClient GetHttpClient(HttpClientHandler handler = null,\n            HttpClientRequestSettings settings = null)\n        {\n            if (settings == null)\n                settings = new HttpClientRequestSettings();\n\n            handler = handler ?? new HttpClientHandler()\n            {\n                Proxy = settings.Proxy,\n                Credentials = settings.Credentials,\n            };\n\n#if NET6_0_OR_GREATER\n            if (settings.IgnoreCertificateErrors)\n                handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;\n#endif\n\n            var client = new HttpClient(handler);\n            client.DefaultRequestHeaders.UserAgent.ParseAdd(settings.UserAgent);\n\n            ApplySettingsToRequest(settings);\n\n            return client;\n        }\n\n        /// <summary>\n        /// Creates a new Request on the Settings object and assigns the settings values\n        /// to the request object.\n        /// </summary>\n        /// <param name=\"settings\">Settings instance</param>        \n        public static void ApplySettingsToRequest(HttpClientRequestSettings settings)\n        {\n            settings.Request = new HttpRequestMessage\n            {\n                RequestUri = new Uri(settings.Url),\n                Method = new HttpMethod(settings.HttpVerb ?? \"GET\"),\n                Version = new Version(settings.HttpVersion)\n            };\n\n\n            foreach (var header in settings.Headers)\n            {\n                SetHttpHeader(settings.Request, header.Key, header.Value);\n            }\n\n            if (settings.HasPostData)\n            {\n                settings.RequestContentType = settings.RequestFormPostMode == HttpFormPostMode.MultiPart\n                    ? \"multipart/form-data; boundary=\" + HttpClientUtils.STR_MultipartBoundary\n                    : \"application/x-www-form-urlencoded\";\n                settings.RequestContent = settings.GetPostBufferBytes();\n            }\n\n            if (settings.RequestContent != null &&\n                (settings.HttpVerb.Equals(\"POST\", StringComparison.OrdinalIgnoreCase) ||\n                 settings.HttpVerb.Equals(\"PUT\", StringComparison.OrdinalIgnoreCase) ||\n                 settings.HttpVerb.Equals(\"PATCH\", StringComparison.OrdinalIgnoreCase))\n               )\n            {\n                HttpContent content = null;\n\n\n                if (settings.RequestContent is string)\n                {\n                    if (!settings.IsRawData && settings.RequestContentType == \"application/json\")\n                    {\n                        var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent);\n                        content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType);\n                    }\n                    else\n                        content = new StringContent(settings.RequestContent as string, settings.Encoding,\n                            settings.RequestContentType);\n                }\n                else if (settings.RequestContent is byte[])\n                {\n                    content = new ByteArrayContent(settings.RequestContent as byte[]);\n                    settings.Request.Content = content;\n                    settings.Request.Content?.Headers.Add(\"Content-Type\", settings.RequestContentType);\n                }\n                else\n                {\n                    if (!settings.IsRawData)\n                    {\n                        var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent);\n                        content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType);\n                    }\n                }\n\n                if (content != null)\n                    settings.Request.Content = content;\n            }\n        }\n\n\n        private static void SetHttpHeader(HttpRequestMessage req, string header, string value)\n        {\n            if (string.IsNullOrEmpty(header) || string.IsNullOrEmpty(value))\n                return;\n\n\n            var lheader = header.ToLower();\n\n\n            if (lheader == \"content-length\")\n                return; // auto-generated\n\n            if (lheader == \"content-type\")\n            {\n                var contentType = value;\n                if (value == \"multipart/form\" && !value.Contains(\"boundary\"))\n                {\n                    contentType = \"multipart/form-data; boundary=\" + STR_MultipartBoundary;\n                }\n\n                req.Content?.Headers.Add(\"Content-Type\", contentType);\n                return;\n            }\n\n            // content-type, content-encoding etc.\n            if (lheader.StartsWith(\"content-\"))\n            {\n                req.Content?.Headers.Add(header, value);\n                return;\n            }\n\n            // set above view property\n            // not handled at the moment\n            if (lheader == \"proxy-connection\")\n                return;\n\n            req.Headers.Add(header, value);\n        }\n\n        #endregion\n    }\n\n    /// <summary>\n    /// Configuration object for Http Requests used by the HttpClientUtils\n    /// methods. Allows you to set the URL, verb, headers proxy and\n    /// credentials that are then passed to the HTTP client.\n    /// </summary>\n    public class HttpClientRequestSettings\n    {\n        /// <summary>\n        /// The URL to send the request to\n        /// </summary>\n        public string Url { get; set; }\n\n        /// <summary>\n        /// The HTTP verb to use when sending the request\n        /// </summary>\n        public string HttpVerb { get; set; }\n\n        /// <summary>\n        /// The Request content to send to the server.\n        /// Data can be either string or byte[] type\n        /// </summary>\n        public object RequestContent { get; set; }\n\n        /// <summary>\n        /// Content Encoding for the data sent to to server\n        /// </summary>\n        public Encoding Encoding { get; set; }\n\n        /// <summary>\n        /// When true data is not translated. For example\n        /// when using JSON Request if you want to send \n        /// raw POST data rather than a serialized object.\n        /// </summary>\n        public bool IsRawData { get; set; }\n\n        /// <summary>\n        /// The content type of any request data sent to the server\n        /// in the Data property.\n        /// </summary>\n        public string RequestContentType { get; set; } = \"application/json\";\n\n        /// <summary>\n        /// The request timeout in milliseconds. 0 for default (20 seconds typically)\n        /// </summary>\n        public int Timeout { get; set; }\n\n        /// <summary>\n        /// Any Http request headers you want to set for this request\n        /// </summary>\n        public Dictionary<string, string> Headers { get; set; }\n\n        /// <summary>\n        /// Authentication information for this request\n        /// </summary>\n        public NetworkCredential Credentials { get; set; }\n\n        /// <summary>\n        /// Determines whether credentials pre-authenticate\n        /// </summary>\n        public bool PreAuthenticate { get; set; }\n\n\n        /// <summary>\n        /// An optional proxy to set for this request\n        /// </summary>\n        public IWebProxy Proxy { get; set; }\n\n        /// <summary>\n        /// Capture request string data that was actually sent to the server.\n        /// </summary>\n        public string CapturedRequestContent { get; set; }\n\n        /// <summary>\n        /// Captured string Response Data from the server\n        /// </summary>\n        public string CapturedResponseContent { get; set; }\n\n\n        /// <summary>\n        /// Output file name for file download operations\n        /// </summary>\n        public string OutputFilename { get; set; }\n\n        /// <summary>\n        /// Capture binary Response data from the server when \n        /// using the Data methods rather than string methods.\n        /// </summary>\n        public byte[] ResponseByteData { get; set; }\n\n        /// <summary>\n        /// The HTTP Status code of the HTTP response\n        /// </summary>\n        public HttpStatusCode ResponseStatusCode\n        {\n            get\n            {\n                if (Response != null)\n                    return Response.StatusCode;\n\n                return HttpStatusCode.Unused;\n            }\n        }\n\n        /// <summary>\n        /// Content Type of the response - may be null if there is no content or the content type is not set by server\n        /// </summary>\n        public string ResponseContentType => Response?.Content?.Headers.ContentType?.MediaType;\n\n        /// <summary>\n        /// Content Length of the response. -1 if there is no result content\n        /// </summary>\n        public long ResponseContentLength => Response?.Content?.Headers?.ContentLength ?? -1;\n\n        /// <summary>\n        /// Response content headers (content type, size, charset etc.) - \n        /// check for null if request did not succeed or doesn't produce content\n        /// </summary>\n        public HttpContentHeaders ResponseContentHeaders => Response?.Content?.Headers;\n\n        /// <summary>\n        /// Non-Content Response headers - check for null if request did not succeed\n        /// </summary>\n        public HttpResponseHeaders ResponseHeaders => Response?.Headers;\n\n        public bool HasResponseContent\n        {\n            get\n            {\n                if (Response?.Content?.Headers == null)\n                    return false;\n\n                return Response.Content.Headers.ContentLength > 0;\n            }\n        }\n\n        /// <summary>\n        /// The User Agent string sent to the server\n        /// </summary>\n        public string UserAgent { get; set; }\n\n\n        /// <summary>\n        /// By default (false) throws a Web Exception on 500 and 400 repsonses.\n        /// This is the default WebClient behavior.\n        ///\n        /// If `true` doesn't throw, but instead returns the HTTP response.\n        /// Useful if you need to return error messages on 500 and 400 responses\n        /// from API requests.\n        /// </summary>\n        public bool ThrowExceptions { get; set; }\n\n        /// <summary>\n        /// Http Protocol Version 1.1\n        /// </summary>\n        public string HttpVersion { get; set; } = \"1.1\";\n\n        public HttpRequestMessage Request { get; set; }\n\n        public HttpResponseMessage Response { get; set; }\n\n        /// <summary>\n        /// Determines whether the request has errors or\n        /// didn't return a 200/300 result code\n        /// </summary>\n        public bool HasErrors { get; set; }\n\n        /// <summary>\n        /// Error message if one was set\n        /// </summary>\n        public string ErrorMessage { get; set; }\n\n\n        /// <summary>\n        /// The full Execption object if an error occurred\n        /// </summary>\n        public Exception ErrorException { get; set; }\n\n        public int MaxResponseSize { get; set; }\n\n        /// <summary>\n        /// if true ignores most certificate errors (expired, not trusted)\n        /// </summary>\n#if !NET6_0_OR_GREATER\n        [Obsolete(\"This property is not supported in .NET Framework.\")]\n#endif\n        public bool IgnoreCertificateErrors { get; set; }\n\n        public HttpClientRequestSettings()\n        {\n            HttpVerb = \"GET\";\n            Headers = new Dictionary<string, string>();\n            Encoding = Encoding.UTF8;\n            UserAgent = \"West Wind .NET Http Client\";\n        }\n\n\n        #region POST data\n\n        /// <summary>\n        /// Determines how data is POSTed when when using AddPostKey() and other methods\n        /// of posting data to the server. Support UrlEncoded, Multi-Part, XML and Raw modes.\n        /// </summary>\n        public HttpFormPostMode RequestFormPostMode { get; set; } = HttpFormPostMode.UrlEncoded;\n\n        // member properties\n        //string cPostBuffer = string.Empty;\n        internal MemoryStream PostStream;\n        internal BinaryWriter PostData;\n        internal bool HasPostData;\n\n        /// <summary>\n        /// Resets the Post buffer by clearing out all existing content\n        /// </summary>\n        public void ResetPostData()\n        {\n            PostStream = new MemoryStream();\n            PostData = new BinaryWriter(PostStream);\n        }\n\n        public void SetPostStream(Stream postStream)\n        {\n            MemoryStream ms = new MemoryStream(1024);\n            FileUtils.CopyStream(postStream, ms, 1024);\n            ms.Flush();\n            ms.Position = 0;\n            PostStream = ms;\n            PostData = new BinaryWriter(ms);\n        }\n\n        /// <summary>\n        /// Adds POST form variables to the request buffer.\n        /// PostMode determines how parms are handled.\n        /// </summary>\n        /// <param name=\"key\">Key value or raw buffer depending on post type</param>\n        /// <param name=\"value\">Value to store. Used only in key/value pair modes</param>\n        public void AddPostKey(string key, byte[] value)\n        {\n            if (value == null)\n                return;\n\n            if (key == \"RESET\")\n            {\n                ResetPostData();\n                return;\n            }\n\n            HasPostData = true;\n            if (PostData == null)\n            {\n                PostStream = new MemoryStream();\n                PostData = new BinaryWriter(PostStream);\n            }\n\n            if (string.IsNullOrEmpty(key))\n                PostData.Write(value);\n            else if (RequestFormPostMode == HttpFormPostMode.UrlEncoded)\n                PostData.Write(\n                    Encoding.Default.GetBytes(key + \"=\" +\n                                              StringUtils.UrlEncode(Encoding.Default.GetString(value)) +\n                                              \"&\"));\n            else if (RequestFormPostMode == HttpFormPostMode.MultiPart)\n            {\n                Encoding iso = Encoding.GetEncoding(\"ISO-8859-1\");\n                PostData.Write(iso.GetBytes(\n                    \"--\" + HttpClientUtils.STR_MultipartBoundary + \"\\r\\n\" +\n                    \"Content-Disposition: form-data; name=\\\"\" + key + \"\\\"\\r\\n\\r\\n\"));\n\n                PostData.Write(value);\n                PostData.Write(iso.GetBytes(\"\\r\\n\"));\n            }\n            else // Raw or Xml, JSON modes\n                PostData.Write(value);\n        }\n\n        /// <summary>\n        /// Adds POST form variables to the request buffer.\n        /// PostMode determines how parms are handled.\n        /// </summary>\n        /// <param name=\"key\">Key value or raw buffer depending on post type</param>\n        /// <param name=\"value\">Value to store. Used only in key/value pair modes</param>\n        public void AddPostKey(string key, string value)\n        {\n            if (value == null)\n                return;\n            AddPostKey(key, Encoding.Default.GetBytes(value));\n        }\n\n        /// <summary>\n        /// Adds a fully self contained POST buffer to the request.\n        /// Works for XML or previously encoded content.\n        /// </summary>\n        /// <param name=\"fullPostBuffer\">String based full POST buffer</param>\n        public void AddPostKey(string fullPostBuffer)\n        {\n            AddPostKey(null, fullPostBuffer);\n        }\n\n        /// <summary>\n        /// Adds a fully self contained POST buffer to the request.\n        /// Works for XML or previously encoded content.\n        /// </summary>\t    \n        /// <param name=\"fullPostBuffer\">Byte array of a full POST buffer</param>\n        public void AddPostKey(byte[] fullPostBuffer)\n        {\n            AddPostKey(null, fullPostBuffer);\n        }\n\n        /// <summary>\n        /// Allows posting a file to the Web Server. Make sure that you \n        /// set PostMode\n        /// </summary>\n        /// <param name=\"key\"></param>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"contentType\">Content type of the file to upload. Default is application/octet-stream</param>\n        /// <param name=\"contentFilename\">Optional filename to use in the Content-Disposition header. If not specified uses the file name of the file being uploaded.</param>\n        /// <returns>true or false. Fails if the file is not found or couldn't be encoded</returns>\n        public bool AddPostFile(string key, string filename, string contentType = \"application/octet-stream\",\n            string contentFilename = null)\n        {\n            byte[] lcFile;\n\n            if (RequestFormPostMode != HttpFormPostMode.MultiPart)\n            {\n                ErrorMessage = \"File upload allowed only with Multi-part forms\";\n                HasErrors = true;\n                return false;\n            }\n\n            HasPostData = true;\n            try\n            {\n                FileStream loFile = new FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read);\n\n                lcFile = new byte[loFile.Length];\n                _ = loFile.Read(lcFile, 0, (int)loFile.Length);\n                loFile.Close();\n            }\n            catch (Exception e)\n            {\n                ErrorMessage = e.Message;\n                HasErrors = true;\n                return false;\n            }\n\n            if (PostData == null)\n            {\n                PostStream = new MemoryStream();\n                PostData = new BinaryWriter(PostStream);\n            }\n\n            if (string.IsNullOrEmpty(contentFilename))\n                contentFilename = new FileInfo(filename).Name;\n\n            PostData.Write(Encoding.Default.GetBytes(\n                \"--\" + HttpClientUtils.STR_MultipartBoundary + \"\\r\\n\" +\n                \"Content-Disposition: form-data; name=\\\"\" + key + \"\\\"; filename=\\\"\" +\n                contentFilename + \"\\\"\\r\\n\" +\n                \"Content-Type: \" + contentType +\n                \"\\r\\n\\r\\n\"));\n\n            PostData.Write(lcFile);\n            PostData.Write(Encoding.Default.GetBytes(\"\\r\\n\"));\n\n            return true;\n        }\n\n        /// <summary>\n        /// Retrieves the accumulated postbuffer as a byte array\n        /// when AddPostKey() or AddPostFile() have been called.        \n        /// </summary>\n        /// <returns>the Post buffer or null if empty or not using\n        /// form post mode</returns>\n        public string GetPostBuffer()\n        {\n            var bytes = PostStream?.ToArray();\n            if (bytes == null)\n                return null;\n            var data = Encoding.Default.GetString(bytes);\n            if (RequestFormPostMode == HttpFormPostMode.MultiPart)\n            {\n                if (PostStream == null)\n                    return null;\n                // add final boundary\n                data += \"\\r\\n--\" + HttpClientUtils.STR_MultipartBoundary + \"--\\r\\n\";\n            }\n\n            return data;\n        }\n\n        /// <summary>\n        /// Retrieves the accumulated postbuffer as a byte array\n        /// when AddPostKey() or AddPostFile() have been called.\n        /// \n        /// For multi-part forms this buffer can only be returned\n        /// once as a footer needs to be appended and we don't want\n        /// copy the buffer and double memory usage.\n        /// </summary>\n        /// <returns>encoded POST buffer</returns>\n        public byte[] GetPostBufferBytes()\n        {\n            if (RequestFormPostMode == HttpFormPostMode.MultiPart)\n            {\n                if (PostStream == null)\n                    return null;\n                // add final boundary\n                PostData.Write(Encoding.Default.GetBytes(\"\\r\\n--\" + HttpClientUtils.STR_MultipartBoundary + \"--\\r\\n\"));\n                PostStream?.Flush();\n            }\n\n            return PostStream?.ToArray();\n        }\n\n        #endregion\n\n        /// <summary>\n        /// Retrieves the response as a \n        /// </summary>\n        /// <returns></returns>\n        public async Task<string> GetResponseStringAsync()\n        {\n            if (Response == null)\n                return null;\n\n            try\n            {\n                return await Response.Content.ReadAsStringAsync();\n            }\n            catch (Exception ex)\n            {\n                HasErrors = true;\n                ErrorMessage = ex.GetBaseException().Message;\n                ErrorException = ex;\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Returns deserialized JSON from the Response\n        /// </summary>\n        /// <typeparam name=\"TResult\"></typeparam>\n        /// <returns></returns>\n        public async Task<TResult> GetResponseJson<TResult>()\n        {\n            var json = await GetResponseStringAsync();\n\n            if (json == null)\n                return default;\n\n            return JsonSerializationUtils.Deserialize<TResult>(json);\n        }\n\n        /// <summary>\n        /// Returns an error message from a JSON error object that\n        /// contains a message or Message property.\n        /// </summary>\n        /// <returns></returns>\n        public async Task<string> GetResponseErrorMessage()\n        {\n            var obj = await GetResponseJson<JObject>();\n            if (obj == null)\n                return null;\n\n            if (obj.ContainsKey(\"message\"))\n                return obj[\"message\"].ToString();\n            if (obj.ContainsKey(\"Message\"))\n                return obj[\"Message\"].ToString();\n\n            return null;\n        }\n\n        /// <summary>\n        /// Returns byte data from the response\n        /// </summary>\n        /// <returns></returns>\n        public async Task<byte[]> GetResponseDataAsync()\n        {\n            if (Response == null)\n                return null;\n\n            try\n            {\n                return await Response.Content.ReadAsByteArrayAsync();\n            }\n            catch (Exception ex)\n            {\n                HasErrors = true;\n                ErrorMessage = ex.GetBaseException().Message;\n                ErrorException = ex;\n                return null;\n            }\n        }\n\n        public override string ToString()\n        {\n            return $\"{HttpVerb} {Url}   {ErrorMessage}\";\n        }\n    }\n\n    public enum HttpFormPostMode\n    {\n        UrlEncoded,\n        MultiPart\n    };\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/HttpUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Simple HTTP request helper to let you retrieve data from a Web\n    /// server and convert it to something useful.\n    /// </summary>\n    [Obsolete(\"Use HttpClientUtils if possible.\")]\n    public static class HttpUtils\n    {\n\n        /// <summary>\n        /// Retrieves and Http request and returns data as a string.\n        /// </summary>\n        /// <param name=\"url\">A url to call for a GET request without custom headers</param>\n        /// <returns>string of HTTP response</returns>\n        \n        public static string HttpRequestString(string url)\n        {            \n            return HttpRequestString(new HttpRequestSettings() { Url = url });\n        }\n\n        /// <summary>\n        /// Retrieves and Http request and returns data as a string.\n        /// </summary>\n        /// <param name=\"settings\">Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more</param>\n        /// <returns>string of HTTP response</returns>\n        public static string HttpRequestString(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);            \n          \n            if (settings.Content != null)\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n\n                if (settings.Content is string)\n                {\n                    settings.CapturedRequestContent = settings.Content as string;\n                    settings.CapturedResponseContent = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);\n                }\n                else if (settings.Content is byte[])\n                {\n                    settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]);\n                    settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData);\n                }\n                else\n                    throw new ArgumentException(\"Data must be either string or byte[].\");\n            }\n            else \n                settings.CapturedResponseContent = client.DownloadString(settings.Url);\n\n            settings.Response = client.Response;\n            \n\n            return settings.CapturedResponseContent;\n        }\n\n\n        /// <summary>\n        /// Retrieves bytes from the server without any request customizations\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>\n        /// <returns></returns>\n        public static byte[] HttpRequestBytes(string url)\n        {\n            return HttpRequestBytes(new HttpRequestSettings() { Url = url });\n        }\n\n\n        /// <summary>\n        /// Retrieves bytes from the server \n        /// </summary>\n        /// <param name=\"settings\"></param>\n        /// <returns></returns>\n        public static byte[] HttpRequestBytes(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);            \n          \n            if (settings.Content != null)\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n\n                if (settings.Content is string)\n                {\n                    settings.CapturedRequestContent = settings.Content as string;\n                    settings.ResponseByteData = client.UploadData(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent));\n                }\n                else if (settings.Content is byte[])\n                {\n                    settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]);\n                }\n                else\n                    throw new ArgumentException(\"Data must be either string or byte[].\");\n            }\n            else \n                settings.ResponseByteData = client.DownloadData(settings.Url);\n\n            settings.Response = client.Response;\n            \n            return settings.ResponseByteData;\n        }\n\n\n        /// <summary>\n        /// Retrieves and Http request and returns data as a string.\n        /// </summary>\n        /// <param name=\"url\">The Url to access</param>\n        /// <returns>string of HTTP response</returns>\n        public static async Task<string> HttpRequestStringAsync(string url)\n        {\n            return await HttpRequestStringAsync(new HttpRequestSettings() { Url = url });\n        }\n\n\n        /// <summary>\n        /// Retrieves and Http request and returns data as a string.\n        /// </summary>\n        /// <param name=\"settings\">Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more</param>\n        /// <returns>string of HTTP response</returns>\n        public static async Task<string> HttpRequestStringAsync(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);\n\n            if (settings.Content != null)\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n\n                if (settings.Content is string)\n                {\n                    settings.CapturedRequestContent = settings.Content as string;\n                    settings.CapturedResponseContent = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);\n                }\n                else if (settings.Content is byte[])\n                {\n                    settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]);\n                    settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData);\n                }\n                else\n                    throw new ArgumentException(\"Data must be either string or byte[].\");\n            }\n            else \n                settings.CapturedResponseContent = await client.DownloadStringTaskAsync(new Uri(settings.Url));\n\n            settings.Response = client.Response;\n\n            return settings.CapturedResponseContent;\n        }\n\n\n        /// <summary>\n        /// Retrieves bytes from the server without any request customizations\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>\n        /// <returns></returns>\n        public static async Task<byte[]> HttpRequestBytesAsync(string url)\n        {\n            return await HttpRequestBytesAsync(new HttpRequestSettings() { Url = url });\n        }\n\n        /// <summary>\n        /// Retrieves bytes from the server \n        /// </summary>\n        /// <param name=\"settings\"></param>\n        /// <returns></returns>\n        public static async Task<byte[]> HttpRequestBytesAsync(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);            \n          \n            if (settings.Content != null)\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n\n                if (settings.Content is string)\n                {\n                    settings.CapturedRequestContent = settings.Content as string;\n                    settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent));\n                }\n                else if (settings.Content is byte[])\n                {\n                    settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]);\n                }\n                else\n                    throw new ArgumentException(\"Data must be either string or byte[].\");\n            }\n            else \n                settings.ResponseByteData = await client.DownloadDataTaskAsync(settings.Url);\n\n            settings.Response = client.Response;\n            \n            return settings.ResponseByteData;\n        }\n\n        /// <summary>\n        /// Makes an HTTP with option JSON data serialized from an object\n        /// and parses the result from JSON back into an object.\n        /// Assumes that the service returns a JSON response\n        /// </summary>\n        /// <typeparam name=\"TResultType\">The type of the object returned</typeparam>\n        /// <param name=\"settings\"><see cref=\"HttpRequestSettings\"/>\n        /// Configuration object for the HTTP request made to the server.\n        /// </param>\n        /// <returns>deserialized value/object from returned JSON data</returns>\n        public static TResultType JsonRequest<TResultType>(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);            \n            client.Headers.Add(\"Accept\", \"application/json\");\n\n            string jsonResult;\n\n            if (settings.Content != null)\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n                else\n                    client.Headers[\"Content-type\"] = \"application/json;charset=utf-8;\";\n\n                if (!settings.IsRawData)\n                {\n                    settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content,\n                        throwExceptions: true);                    \n                }\n                else\n                    settings.CapturedRequestContent = settings.Content as string;\n                                \n                jsonResult = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);                \n\n                if (jsonResult == null)\n                    return default(TResultType);\n            }\n            else\n                jsonResult = client.DownloadString(settings.Url);\n\n            settings.CapturedResponseContent = jsonResult;\n            settings.Response = client.Response;\n\n            return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true);\n        }\n\n\n        /// <summary>\n\t\t/// Makes an HTTP with option JSON data serialized from an object\n\t\t/// and parses the result from JSON back into an object.\n\t\t/// Assumes that the service returns a JSON response and that\n\t\t/// any data sent is json.\n\t\t/// </summary>\n\t\t/// <typeparam name=\"TResultType\">The type of the object returned</typeparam>\n\t\t/// <param name=\"settings\"><see cref=\"HttpRequestSettings\"/>\n\t\t/// Configuration object for the HTTP request made to the server.\n\t\t/// </param>\n\t\t/// <returns>deserialized value/object from returned JSON data</returns>\n\t\tpublic static async Task<TResultType> JsonRequestAsync<TResultType>(HttpRequestSettings settings)\n        {\n            var client = new HttpUtilsWebClient(settings);       \n            client.Headers.Add(\"Accept\", \"application/json\");\n            \n            string jsonResult;\n\n            if (settings.HttpVerb == \"POST\" || settings.HttpVerb == \"PUT\" || settings.HttpVerb == \"PATCH\")\n            {\n                if (!string.IsNullOrEmpty(settings.ContentType))\n                    client.Headers[\"Content-type\"] = settings.ContentType;\n                else\n                    client.Headers[\"Content-type\"] = \"application/json\";\n\n                if (!settings.IsRawData)\n                    settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content,\n                        throwExceptions: true);\n                else\n                    settings.CapturedRequestContent = settings.Content as string;\n\n                jsonResult = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent);\n\n                if (jsonResult == null)\n                    return default(TResultType);\n            }\n            else\n                jsonResult = await client.DownloadStringTaskAsync(settings.Url);\n\n            settings.CapturedResponseContent = jsonResult;\n            settings.Response = client.Response;\n\n            return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true);\n        }\n\n\n        /// <summary>\n        /// Creates a temporary image file from a download from a URL\n        /// \n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"filename\">Url of image to download</param>\n        /// <param name=\"imageUrl\">Optional output image file. Filename may change extension if the image format doesn't match the filename.\n        /// If not passed a temporary files file is created. Caller is responsible for cleaning up this file.\n        /// </param>\n        /// <param name=\"settings\">Optional Http Settings for the request</param>\n        /// <returns>image filename or null on failure. Note that the filename may have a different extension than the request filename parameter.</returns>\n        public static string DownloadImageToFile(string imageUrl, string filename = null, HttpRequestSettings settings = null)\n        {\n            if (string.IsNullOrEmpty(imageUrl) || \n                !imageUrl.StartsWith(\"http://\") && !imageUrl.StartsWith(\"https://\") )\n                return null;\n\n            string newFilename;\n\n            if (string.IsNullOrEmpty(filename))\n            {\n                filename = Path.Combine(Path.GetTempPath(), \"~img-\" + DataUtils.GenerateUniqueId());\n            }\n            filename = Path.ChangeExtension(filename, \"bin\");\n\n            var client = new HttpUtilsWebClient(settings);\n\n            try\n            {\n                client.DownloadFile(imageUrl, filename);\n\n                var ct = client.Response.ContentType;   // works\n\n                if (string.IsNullOrEmpty(ct) || !ct.StartsWith(\"image/\"))\n                    return null;\n\n                var ext = ImageUtils.GetExtensionFromMediaType(ct);\n                if (ext == null)\n                    return null; // invalid image type\n\n                newFilename = Path.ChangeExtension(filename, ext);\n\n                if (File.Exists(newFilename))\n                    File.Delete(newFilename);\n\n                // rename the file\n                File.Move(filename, newFilename);\n            }\n            catch\n            {\n                if (File.Exists(filename))\n                    File.Delete(filename);\n\n                return null;\n            }\n\n            return newFilename;\n        }\n\n\n        /// <summary>\n        /// Downloads an image to a temporary file or a file you specify, automatically\n        /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc)\n        /// Always use the return value to receive the final image file name.\n        ///\n        /// If you don't pass a file a temporary file is created in Temp Files folder.\n        /// You're responsible for cleaning up the file after you are done with it.\n        /// \n        /// You should check the filename that is returned regardless of whether you\n        /// passed in a filename - if the file is of a different image type the\n        /// extension may be changed.\n        /// </summary>\n        /// <param name=\"imageUrl\">Url of image to download</param>\n        /// <param name=\"filename\">\n        /// Optional output image file name. Filename may change extension if the image format doesn't match the filename.\n        /// If not passed a temporary files file is created in the temp file location and you can move the file\n        /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation.\n        /// </param>\n        /// <param name=\"settings\">Optional more detailed Http Settings for the request</param>\n        /// <returns>file name that was created or null</returns>\n        public static async Task<string> DownloadImageToFileAsync(string imageUrl, string filename = null, HttpRequestSettings settings = null)\n        {\n            if (string.IsNullOrEmpty(imageUrl) || \n                !imageUrl.StartsWith(\"http://\") && !imageUrl.StartsWith(\"https://\") )\n                return null;\n\n            string newFilename;\n\n            if (string.IsNullOrEmpty(filename))\n            {\n                filename = Path.Combine(Path.GetTempPath(), \"~img-\" + DataUtils.GenerateUniqueId());\n            }\n            filename = Path.ChangeExtension(filename, \"bin\");\n\n            var client = new HttpUtilsWebClient(settings);\n\n            try\n            {\n                await client.DownloadFileTaskAsync(imageUrl, filename);\n\n                var ct = client.Response.ContentType;\n\n                var ext = ImageUtils.GetExtensionFromMediaType(ct);\n                if (ext == null)\n                {\n                    if(File.Exists(filename))\n                        File.Delete(filename);\n                    return null; // invalid image type\n                }\n\n                newFilename = Path.ChangeExtension(filename, ext);\n\n                \n                if (File.Exists(newFilename))\n                    File.Delete(newFilename);\n\n                // rename the file\n                File.Move(filename, newFilename);\n            }\n            catch\n            {\n                if (File.Exists(filename))\n                    File.Delete(filename);\n\n                return null;\n            }\n\n            return newFilename;\n        }\n\n\n        /// <summary>\n        /// Helper method that creates a proxy instance to store on the Proxy property\n        /// </summary>\n        /// <param name=\"proxyAddress\">\n        /// Proxy Address to create or \"default\" for Windows default proxy.\n        /// Null or empty means no proxy is set\n        /// </param>\n        /// <param name=\"byPassonLocal\">\n        /// Optional - bypass on local if you're specifying an explicit url\n        /// </param>\n        /// <param name=\"bypassList\">\n        /// Optional list of root domain Urls that are bypassed\n        /// </param>\n        /// <returns></returns>\n        public static IWebProxy CreateWebProxy(string proxyAddress = null, bool bypassonLocal = false, string[] bypassList = null)\n        {\n            IWebProxy proxy = null;\n\n            if (string.IsNullOrEmpty(proxyAddress))\n                return null;\n\n            if (proxyAddress.Equals(\"default\", StringComparison.OrdinalIgnoreCase))\n            {\n                proxy = WebRequest.GetSystemWebProxy();\n            }\n            else\n            {\n                proxy = new WebProxy(proxyAddress, bypassonLocal, bypassList);\n            }\n\n            return proxy;\n        }        \n    }\n\n\n    /// <summary>\n    /// Configuration object for Http Requests used by the HttpUtils\n    /// methods. Allows you to set the URL, verb, headers proxy and\n    /// credentials that are then passed to the HTTP client.\n    /// </summary>\n    public class HttpRequestSettings\n    {\n        /// <summary>\n        /// The URL to send the request to\n        /// </summary>\n        public string Url { get; set; }\n\n        /// <summary>\n        /// The HTTP verb to use when sending the request\n        /// </summary>\n        public string HttpVerb { get; set; }\n\n        /// <summary>\n        /// The Request content to send to the server.\n        /// Data can be either string or byte[] type\n        /// </summary>\n        public object Content { get; set; }\n\n        /// <summary>\n        /// Content Encoding for the data sent to to server\n        /// </summary>\n        public Encoding Encoding { get; set;  } \n\n        /// <summary>\n        /// When true data is not translated. For example\n        /// when using JSON Request if you want to send \n        /// raw POST data rather than a serialized object.\n        /// </summary>\n        public bool IsRawData { get; set; }\n\n        /// <summary>\n        /// The content type of any request data sent to the server\n        /// in the Data property.\n        /// </summary>\n        public string ContentType { get; set; }\n\n        /// <summary>\n        /// The request timeout in milliseconds. 0 for default (20 seconds typically)\n        /// </summary>\n        public int Timeout { get; set; }\n\n        /// <summary>\n        /// Any Http request headers you want to set for this request\n        /// </summary>\n        public Dictionary<string, string> Headers { get; set; }\n\n        /// <summary>\n        /// Authentication information for this request\n        /// </summary>\n        public NetworkCredential Credentials { get; set; }\n\n        /// <summary>\n        /// Determines whether credentials pre-authenticate\n        /// </summary>\n        public bool PreAuthenticate { get; set; }\n\n\n        /// <summary>\n        /// An optional proxy to set for this request\n        /// </summary>\n        public IWebProxy Proxy { get; set; }\n\n        /// <summary>\n        /// Capture request string data that was actually sent to the server.\n        /// </summary>\n        public string CapturedRequestContent { get; set; }\n\n        /// <summary>\n        /// Captured string Response Data from the server\n        /// </summary>\n        public string CapturedResponseContent { get; set; }\n\n        /// <summary>\n        /// Capture binary Response data from the server when \n        /// using the Data methods rather than string methods.\n        /// </summary>\n        public byte[] ResponseByteData { get; set; }\n\n        /// <summary>\n        /// The HTTP Status code of the HTTP response\n        /// </summary>\n        public HttpStatusCode ResponseStatusCode\n        {\n            get\n            {\n                if (Response != null)\n                    return Response.StatusCode;\n\n                return HttpStatusCode.OK;\n            }\n        }\n\n        /// <summary>\n        /// Instance of the full HttpResponse object that gives access\n        /// to the full HttpWebResponse object to provide things\n        /// like Response headers, status etc.\n        /// </summary>\n        public HttpWebResponse Response { get; set; }\n\n\n        /// <summary>\n        /// The User Agent string sent to the server\n        /// </summary>\n        public string UserAgent { get; set; }\n\n\n        /// <summary>\n        /// By default (false) throws a Web Exception on 500 and 400 repsonses.\n        /// This is the default WebClient behavior.\n        ///\n        /// If `true` doesn't throw, but instead returns the HTTP response.\n        /// Useful if you need to return error messages on 500 and 400 responses\n        /// from API requests.\n        /// </summary>\n        public bool DontThrowOnErrorStatusCodes { get; set; }\n\n        /// <summary>\n        /// Http Protocol Version 1.1\n        /// </summary>\n        public string HttpVersion { get; set; } = \"1.1\";\n\n\n        public HttpRequestSettings()\n        {\n            HttpVerb = \"GET\";\n            Headers = new Dictionary<string, string>();\n            Encoding = Encoding.UTF8;\n            UserAgent = \"West Wind .NET Http Client\";\n        }        \n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/HttpUtilsWebClient.cs",
    "content": "using System;\nusing System.Net;\nusing System.Web;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Customized version of WebClient that provides access\n    /// to the Response object so we can read result data \n    /// from the Response.\n    /// </summary>\n    public class HttpUtilsWebClient : WebClient\n    {\n#pragma warning disable SYSLIB0014\n        /// <summary>\n        /// Intializes this instance of WebClient with settings values\n        /// </summary>\n        /// <param name=\"settings\"></param>\n        public HttpUtilsWebClient(HttpRequestSettings settings = null)\n        {\n            Settings = settings;\n\n            if (settings != null)\n            {\n                if (settings.Credentials != null)\n                    Credentials = settings.Credentials;\n\n                if (settings.Proxy != null)\n                    Proxy = settings.Proxy;\n\n                if (settings.Encoding != null)\n                    Encoding = settings.Encoding;\n\n                if (settings.Headers != null)\n                {\n                    foreach (var header in settings.Headers)\n                    {\n                        Headers[header.Key] = header.Value;\n                    }\n                }\n\n            }            \n        }\n#pragma warning restore SYSLIB0014\n\n        internal HttpRequestSettings Settings { get; set; }\n        internal HttpWebResponse Response { get; set; }\n        internal HttpWebRequest Request { get; set; }\n\n        protected override WebRequest GetWebRequest(Uri address)\n        {\n            Request = base.GetWebRequest(address) as HttpWebRequest;\n            \n            if (Settings != null)\n            {\n                if (Settings.Timeout > 0)\n                {\n                    Request.Timeout = Settings.Timeout;\n                    Request.ReadWriteTimeout = Settings.Timeout;\n                    Request.PreAuthenticate = Settings.PreAuthenticate;                    \n                }\n\n                if (!string.IsNullOrEmpty(Settings.UserAgent))\n                    Request.UserAgent = Settings.UserAgent;\n\n                if (!string.IsNullOrEmpty(Settings.HttpVerb))\n                    Request.Method = Settings.HttpVerb;\n            }\n\n            return Request;\n        }\n\n        protected override WebResponse GetWebResponse(WebRequest request)\n        {\n            try\n            {\n                Response = base.GetWebResponse(request) as HttpWebResponse;\n            }\n            catch (WebException ex)\n            {\n                if(Settings.DontThrowOnErrorStatusCodes) {\n                    Response = ex?.Response as HttpWebResponse;\n                    return ex?.Response;\n                }\n\n#pragma warning disable CA2200\n                throw;\n#pragma warning restore CA2200\n            }\n\n            return Response;\n        }\n\n        protected override WebResponse GetWebResponse(WebRequest request, System.IAsyncResult result)\n        {\n            try\n            {\n                Response = base.GetWebResponse(request, result) as HttpWebResponse;\n            }\n            catch (WebException ex)\n            {\n                if (Settings.DontThrowOnErrorStatusCodes) {\n                    Response = ex?.Response as HttpWebResponse;\n                    return ex?.Response;\n                }\n\n#pragma warning disable CA2200\n                throw;\n#pragma warning restore CA2200\n            }\n\n            return Response;\n        }\n\n\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/ImageUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\n#if NETFULL\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Drawing.Drawing2D;\n#endif\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Summary description for wwImaging.\n    /// </summary>\n    public static class ImageUtils\n    {\n\n#if NETFULL\n        /// <summary>\n        /// Creates a resized bitmap from an existing image on disk. Resizes the image by \n        /// creating an aspect ratio safe image. Image is sized to the larger size of width\n        /// height and then smaller size is adjusted by aspect ratio.\n        /// \n        /// Image is returned as Bitmap - call Dispose() on the returned Bitmap object\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"width\"></param>\n        /// <param name=\"height\"></param>\n        /// <returns>Bitmap or null</returns>\n        public static Bitmap ResizeImage(string filename, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic)\n        {\n            try\n            {\n                using (Bitmap bmp = new Bitmap(filename))\n                {\n                    return ResizeImage(bmp, width, height, mode);\n                }\n            }\n            catch\n            {\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Resizes an image from byte array and returns a Bitmap.\n        /// Make sure you Dispose() the bitmap after you're done \n        /// with it!\n        /// </summary>\n        /// <param name=\"data\"></param>\n        /// <param name=\"width\"></param>\n        /// <param name=\"height\"></param>\n        /// <returns></returns>\n        public static Bitmap ResizeImage(byte[] data, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic)\n        {\n            try\n            {\n                using (Bitmap bmp = new Bitmap(new MemoryStream(data)))\n                {\n                    return ResizeImage(bmp, width, height, mode);\n                }\n            }\n            catch\n            {\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Resizes an image and saves the image to a file\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"outputFilename\"></param>\n        /// <param name=\"width\"></param>\n        /// <param name=\"height\"></param>\n        /// <param name=\"mode\"></param>\n        /// <param name=\"jpegCompressionMode\">\n        /// If using a jpeg image \n        /// </param>\n        /// <returns></returns>\n        public static bool ResizeImage(string filename, string outputFilename,\n                                        int width, int height,\n                                        InterpolationMode mode = InterpolationMode.HighQualityBicubic,\n                                        int jpegCompressionMode = 85)\n        {\n\n            using (var bmpOut = ResizeImage(filename, width, height, mode))\n            {\n                var imageFormat = GetImageFormatFromFilename(filename);\n                if (imageFormat == ImageFormat.Emf)\n                    imageFormat = bmpOut.RawFormat;\n\n                if(imageFormat == ImageFormat.Jpeg)\n                    SaveJpeg(bmpOut, outputFilename, jpegCompressionMode);\n                else\n                    bmpOut.Save(outputFilename, imageFormat);\n            }\n\n            return true;\n        }\n\n\n        /// <summary>\n        /// Resizes an image from a bitmap.\n        /// Note image will resize to the larger of the two sides\n        /// </summary>\n        /// <param name=\"bmp\">Bitmap to resize</param>\n        /// <param name=\"width\">new width</param>\n        /// <param name=\"height\">new height</param>\n        /// <returns>resized or original bitmap. Be sure to Dispose this bitmap</returns>\n        public static Bitmap ResizeImage(Bitmap bmp, int width, int height,\n                                         InterpolationMode mode = InterpolationMode.HighQualityBicubic)\n        {\n            Bitmap bmpOut = null;\n\n            try\n            {\n                decimal ratio;\n                int newWidth = 0;\n                int newHeight = 0;\n\n                // If the image is smaller than a thumbnail just return original size\n                if (bmp.Width < width && bmp.Height < height)\n                {\n                    newWidth = bmp.Width;\n                    newHeight = bmp.Height;\n                }\n                else\n                {\n                    if (bmp.Width == bmp.Height)\n                    {\n                        if (height > width)\n                        {\n                            newHeight = height;\n                            newWidth = height;\n                        }\n                        else\n                        {\n                            newHeight = width;\n                            newWidth = width;\n                        }\n                    }\n                    else if (bmp.Width >= bmp.Height)\n                    {\n                        ratio = (decimal)width / bmp.Width;\n                        newWidth = width;\n                        decimal lnTemp = bmp.Height * ratio;\n                        newHeight = (int)lnTemp;\n                    }\n                    else\n                    {\n                        ratio = (decimal)height / bmp.Height;\n                        newHeight = height;\n                        decimal lnTemp = bmp.Width * ratio;\n                        newWidth = (int)lnTemp;\n                    }\n                }\n                                    \n                bmpOut = new Bitmap(newWidth, newHeight);\n                bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);\n\n                using (Graphics g = Graphics.FromImage(bmpOut))\n                {\n                    g.InterpolationMode = mode;\n                    g.SmoothingMode = SmoothingMode.HighQuality;\n                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;\n\n                    g.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight);\n                    g.DrawImage(bmp, 0, 0, newWidth, newHeight);\n                }\n            }\n            catch\n            {\n                return null;\n            }\n\n            return bmpOut;\n        }\n\n        /// <summary>\n        /// Adjusts an image to a specific aspect ratio by clipping\n        /// from the center outward - essentially capturing the center\n        /// to fit the width/height of the aspect ratio.\n        /// </summary>\n        /// <param name=\"imageStream\">Stream to an image</param>\n        /// <param name=\"ratio\">Aspect ratio default is 16:9</param>\n        /// <param name=\"resizeWidth\">Optionally resize with to this width (if larger than height)</param>\n        /// <param name=\"resizeHeight\">Optionally resize to this height (if larger than width)</param>\n        /// <returns>Bitmap image - make sure to dispose this image</returns>\n        public static Bitmap AdjustImageToRatio(Stream imageStream, decimal ratio = 16M / 9M, int resizeWidth = 0,\n            int resizeHeight = 0)\n        {\n            if (imageStream == null)\n                return null;\n\n            decimal width = 0;\n            decimal height = 0;\n\n\n            Bitmap bmpOut = null;\n            Bitmap bitmap = null;\n\n            try\n            {\n                bitmap = new Bitmap(imageStream);\n\n                height = bitmap.Height;\n                width = bitmap.Width;\n\n                if (width >= height * ratio)\n                {\n                    // clip width\n                    decimal clipWidth = height * ratio;\n                    decimal clipX = (width - clipWidth) / 2;\n\n                    bmpOut = new Bitmap((int) clipWidth, (int) height);\n                    bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);\n\n                    using (Graphics g = Graphics.FromImage(bmpOut))\n                    {\n                        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;\n                        g.SmoothingMode = SmoothingMode.HighQuality;\n                        g.PixelOffsetMode = PixelOffsetMode.HighQuality;\n\n\n                        var sourceRect = new Rectangle((int) clipX, 0, (int) clipWidth, (int) height);\n                        var targetRect = new Rectangle(0, 0, (int) clipWidth, (int) height);\n\n                        g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel);\n                    }\n                }\n                else if (width < height * ratio)\n                {\n                    // clip height\n                    decimal clipHeight = width / ratio;\n                    decimal clipY = (height - clipHeight) / 2;\n\n                    bmpOut = new Bitmap((int) width, (int) clipHeight);\n                    bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);\n\n                    using (Graphics g = Graphics.FromImage(bmpOut))\n                    {\n                        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;\n                        g.SmoothingMode = SmoothingMode.HighQuality;\n                        g.PixelOffsetMode = PixelOffsetMode.HighQuality;\n\n                        var sourceRect = new Rectangle(0, (int) clipY, (int) width, (int) clipHeight);\n                        var targetRect = new Rectangle(0, 0, (int) width, (int) clipHeight);\n\n                        g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel);\n                    }\n                }\n                else\n                    bmpOut = bitmap;\n\n                if (resizeWidth == 0 || resizeWidth == 0)\n                    return bmpOut;\n\n                var resizedImage = ResizeImage(bmpOut, resizeWidth, resizeHeight);\n                return resizedImage;\n            }\n            finally\n            {\n                bitmap?.Dispose();\n                bmpOut?.Dispose();\n            }\n        }\n\n\n        /// <summary>\n        /// Adjusts an image to a specific aspect ratio by clipping\n        /// from the center outward - essentially capturing the center\n        /// to fit the width/height of the aspect ratio.\n        /// </summary>\n        /// <param name=\"imageContent\"></param>\n        /// <param name=\"ratio\"></param>\n        /// <param name=\"resizeWidth\"></param>\n        /// <param name=\"resizeHeight\"></param>\n        /// <returns></returns>\n        public static Bitmap AdjustImageToRatio(byte[] imageContent, decimal ratio = 16M / 9M, int resizeWidth = 0,\n            int resizeHeight = 0)\n        {\n            using (var ms = new MemoryStream(imageContent))\n            {\n                return AdjustImageToRatio(ms, ratio, resizeWidth, resizeHeight);\n            }\n        }\n\n\n        /// <summary>\n        /// Saves a jpeg BitMap  to disk with a jpeg quality setting.\n        /// Does not dispose the bitmap.\n        /// </summary>\n        /// <param name=\"bmp\">Bitmap to save</param>\n        /// <param name=\"outputFileName\">file to write it to</param>\n        /// <param name=\"jpegQuality\"></param>\n        /// <returns></returns>\n        public static bool SaveJpeg(Bitmap bmp, string outputFileName, long jpegQuality = 90)\n        {\n            try\n            {\n                //get the jpeg codec\n                ImageCodecInfo jpegCodec = null;\n                if (Encoders.ContainsKey(\"image/jpeg\"))\n                    jpegCodec = Encoders[\"image/jpeg\"];\n\n                EncoderParameters encoderParams = null;\n                if (jpegCodec != null)\n                {\n                    //create an encoder parameter for the image quality\n                    EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality);\n\n                    //create a collection of all parameters that we will pass to the encoder\n                    encoderParams = new EncoderParameters(1);\n                    encoderParams.Param[0] = qualityParam;\n                }\n                bmp.Save(outputFileName, jpegCodec, encoderParams);\n            }\n            catch\n            {\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Saves a jpeg BitMap  to disk with a jpeg quality setting.\n        /// Does not dispose the bitmap.\n        /// </summary>\n        /// <param name=\"bmp\">Bitmap to save</param>\n        /// <param name=\"outputStream\">Binary stream to write image data to</param>\n        /// <param name=\"jpegQuality\"></param>\n        /// <returns></returns>\n        public static bool SaveJpeg(Bitmap bmp, Stream imageStream, long jpegQuality = 90)\n        {\n            try\n            {\n                //get the jpeg codec\n                ImageCodecInfo jpegCodec = null;\n                if (Encoders.ContainsKey(\"image/jpeg\"))\n                    jpegCodec = Encoders[\"image/jpeg\"];\n\n                EncoderParameters encoderParams = null;\n                if (jpegCodec != null)\n                {\n                    //create an encoder parameter for the image quality\n                    EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality);\n\n                    //create a collection of all parameters that we will pass to the encoder\n                    encoderParams = new EncoderParameters(1);\n                    encoderParams.Param[0] = qualityParam;\n                }\n                bmp.Save(imageStream, jpegCodec, encoderParams);\n            }\n            catch\n            {\n                return false;\n            }\n\n            return true;\n        }\n\n\n        /// <summary>\n        /// Rotates an image and writes out the rotated image to a file.\n        /// </summary>\n        /// <param name=\"filename\">The original image to roatate</param>\n        /// <param name=\"outputFilename\">The output file of the rotated image file. If not passed the original file is overwritten</param>\n        /// <param name=\"type\">Type of rotation to perform</param>\n        /// <returns></returns>\n        public static bool RoateImage(string filename, string outputFilename = null,\n                                      RotateFlipType type = RotateFlipType.Rotate90FlipNone,\n                                      int jpegCompressionMode = 85)\n        {\n            Bitmap bmpOut = null;\n\n            if (string.IsNullOrEmpty(outputFilename))\n                outputFilename = filename;\n\n            try\n            {\n                ImageFormat imageFormat;\n                using (Bitmap bmp = new Bitmap(filename))\n                {\n                    imageFormat = GetImageFormatFromFilename(filename);\n                    if (imageFormat == ImageFormat.Emf)\n                        imageFormat = bmp.RawFormat;\n\n                    bmp.RotateFlip(type);\n\n                    using (bmpOut = new Bitmap(bmp.Width, bmp.Height))\n                    {\n                        bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);\n\n                        Graphics g = Graphics.FromImage(bmpOut);\n                        g.InterpolationMode = InterpolationMode.HighQualityBicubic;\n                        g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height);\n                        \n                        if (imageFormat == ImageFormat.Jpeg)\n                            SaveJpeg(bmpOut, outputFilename, jpegCompressionMode);\n                        else\n                            bmpOut.Save(outputFilename, imageFormat);                                \n                    }                    \n                }\n\n            }\n            catch (Exception ex)\n            {\n                var msg = ex.GetBaseException();\n                return false;\n            }\n\n            return true;\n        }\n\n        public static byte[] RoateImage(byte[] data, RotateFlipType type = RotateFlipType.Rotate90FlipNone)\n        {\n            Bitmap bmpOut = null;\n\n            try\n            {\n                Bitmap bmp = new Bitmap(new MemoryStream(data));\n\n                ImageFormat imageFormat;\n                imageFormat = bmp.RawFormat;\n                bmp.RotateFlip(type);\n\n                bmpOut = new Bitmap(bmp.Width, bmp.Height);\n                bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);\n\n                Graphics g = Graphics.FromImage(bmpOut);\n                g.InterpolationMode = InterpolationMode.HighQualityBicubic;\n                g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height);\n\n                bmp.Dispose();\n\n                using (var ms = new MemoryStream())\n                {\n                    bmpOut.Save(ms, imageFormat);\n                    bmpOut.Dispose();\n\n                    ms.Flush();\n                    return ms.ToArray();\n                }\n            }\n            catch (Exception ex)\n            {\n                var msg = ex.GetBaseException();\n                return null;\n            }\n\n        }\n\n\n        /// <summary>\n        /// Opens the image and writes it back out, stripping any Exif data\n        /// </summary>\n        /// <param name=\"imageFile\">Image to remove exif data from</param>\n        /// <param name=\"imageQuality\">image quality 0-100 (100 no compression)</param>\n        public static void StripJpgExifData(string imageFile, int imageQuality = 90)\n        {\n            using (var bmp = new Bitmap(imageFile))\n            {\n                using (var bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height))\n                {\n                    bmp.Dispose();\n                    SaveJpeg(bmp2, imageFile, imageQuality);\n                }\n            }\n        }\n\n\n        /// <summary>\n        /// If the image contains image rotation Exif data, apply the image rotation and\n        /// remove the Exif data. Optionally also allows for image resizing in the same\n        /// operation.\n        /// </summary>\n        /// <param name=\"imageFile\">Image file to work on</param>\n        /// <param name=\"imageQuality\">Jpg</param>\n        /// <param name=\"width\"></param>\n        /// <param name=\"height\"></param>\n        public static void NormalizeJpgImageRotation(string imageFile, int imageQuality = 90, int width = -1, int height = -1)\n        {\n            using (var bmp = new Bitmap(imageFile))\n            {\n                Bitmap bmp2;\n                using (bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height))\n                {\n                    if (bmp.PropertyItems != null)\n                    {\n                        foreach (var item in bmp.PropertyItems)\n                        {\n                            if (item.Id == 0x112)\n                            {\n                                int orientation = item.Value[0];\n                                if (orientation == 6)\n                                    bmp2.RotateFlip(RotateFlipType.Rotate90FlipNone);\n                                if (orientation == 8)\n                                    bmp2.RotateFlip(RotateFlipType.Rotate270FlipNone);\n                            }\n                        }\n                    }\n\n                    bmp.Dispose();\n\n                    if (width > 0 || height > 0)\n                        bmp2 = ResizeImage(bmp2, width, height);\n\n                    SaveJpeg(bmp2, imageFile, imageQuality);\n                }\n            }\n        }\n\n\n\n        /// <summary>\n        /// A quick lookup for getting image encoders\n        /// </summary>\n        public static Dictionary<string, ImageCodecInfo> Encoders\n        {\n            //get accessor that creates the dictionary on demand\n            get\n            {\n                //if the quick lookup isn't initialised, initialise it\n                if (_encoders != null)\n                    return _encoders;\n\n                _encoders = new Dictionary<string, ImageCodecInfo>();\n\n                //get all the codecs\n                foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())\n                {\n                    //add each codec to the quick lookup\n                    _encoders.Add(codec.MimeType.ToLower(), codec);\n                }\n\n                //return the lookup\n                return _encoders;\n            }\n        }\n        private static Dictionary<string, ImageCodecInfo> _encoders = null;\n\n\n        /// <summary>\n        /// Tries to return an image format \n        /// </summary>\n        /// <param name=\"filename\"></param>\n        /// <returns>Image format or ImageFormat.Emf if no match was found</returns>\n        public static ImageFormat GetImageFormatFromFilename(string filename)\n        {            \n            string ext = Path.GetExtension(filename).ToLower();\n\n            ImageFormat imageFormat;\n\n            if (ext == \".jpg\" || ext == \".jpeg\")\n                imageFormat = ImageFormat.Jpeg;\n            else if (ext == \".png\")\n                imageFormat = ImageFormat.Png;\n            else if (ext == \".gif\")\n                imageFormat = ImageFormat.Gif;\n            else if (ext == \".bmp\")\n                imageFormat = ImageFormat.Bmp;\n            else\n                imageFormat = ImageFormat.Emf;\n\n            return imageFormat;\n        }\n#endif\n\n        /// <summary>\n        /// Returns the image media type for a give file extension based\n        /// on a filename or url passed in.\n        /// </summary>\n        /// <param name=\"file\"></param>\n        /// <returns></returns>\n        public static string GetImageMediaTypeFromFilename(string file)\n        {\n            if (string.IsNullOrEmpty(file))\n                return file;\n\n            string ext = Path.GetExtension(file).ToLower();\n            if (ext == \".jpg\" || ext == \".jpeg\")\n                return \"image/jpeg\";\n            if (ext == \".png\")\n                return \"image/png\";\n            if (ext == \".apng\")\n                return \"image/apgn\";\n            if (ext == \".gif\")\n                return \"image/gif\";\n            if (ext == \".bmp\")\n                return \"image/bmp\";\n            if (ext == \".tif\" || ext == \".tiff\")\n                return \"image/tiff\";\n            if (ext == \".webp\")\n                return \"image/webp\";\n            if (ext == \".ico\")\n                return \"image/x-icon\";\n\n\n            return \"application/image\";\n        }\n\n\n        /// <summary>\n        /// Returns a file extension for a media type/content type.\n        /// </summary>\n        /// <param name=\"mediaType\">The media type to convert from</param>\n        /// <returns>A file extension or null if no image type is found</returns>\n        public static string GetExtensionFromMediaType(string mediaType)\n        {\n            if (mediaType == \"image/jpeg\")\n                return \"jpg\";\n            if (mediaType == \"image/png\")\n                return \"png\";\n            if (mediaType == \"image/apng\")\n                return \"apng\";\n            if (mediaType == \"image/bmp\")\n                return \"bmp\";\n            if (mediaType == \"image/gif\")\n                return \"gif\";\n            if (mediaType == \"image/tiff\")\n                return \"tif\";\n            if (mediaType == \"image/svg+xml\")\n                return \"svg\";\n            if (mediaType == \"image/webp\")\n                return \"webp\";\n            if (mediaType == \"image/x-icon\")\n                return \"ico\";\n\n\n            return null;\n        }\n\n        /// <summary>\n        /// Determines whether a filename has a typical image extension\n        /// </summary>\n        /// <param name=\"filename\">File name to check for image</param>\n        /// <returns>true or false</returns>\n        public static bool IsImage(string filename)\n        {\n            if (string.IsNullOrEmpty(filename))\n                return false;\n\n            string ext = Path.GetExtension(filename).ToLower();\n            return ext == \".jpg\" || ext == \".jpeg\" ||\n                   ext == \".png\" ||\n                   ext == \".apng\" ||\n                   ext == \".gif\" ||\n                   ext == \".bmp\" ||\n                   ext == \".tif\" || ext == \".tiff\" ||\n                   ext == \".webp\" ||\n                   ext == \".ico\";\n        }\n\n        /// <summary>\n        /// Determines whether a byte array  is an image based \n        /// on binary signature bytes.\n        /// Supports PNG, JPEG, GIF, BMP, WebP and AVIF formats.\n        /// </summary>\n        /// <param name=\"data\">binary file.</param>\n        /// <remarks>\n        /// The buffer only needs the first 15 bytes max for signature detection\n        /// </remarks>\n        /// <returns>true or false</returns>\n        public static bool IsImage(byte[] data)\n        {\n            if (data == null || data.Length < 4)\n                return false;\n\n            // PNG (also covers APNG, which is an extension of PNG)\n            if (data.Length >= 8 &&\n                data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E &&\n                data[3] == 0x47 && data[4] == 0x0D && data[5] == 0x0A &&\n                data[6] == 0x1A && data[7] == 0x0A)\n            {\n                return true;\n            }\n\n            // JPEG\n            if (data.Length >= 4 &&\n                data[0] == 0xFF && data[1] == 0xD8 &&\n                data[data.Length - 2] == 0xFF && data[data.Length - 1] == 0xD9)\n            {\n                return true;\n            }\n\n            // GIF\n            if (data.Length >= 6 &&\n                data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 &&\n                data[3] == 0x38 && (data[4] == 0x39 || data[4] == 0x37) && data[5] == 0x61)\n            {\n                return true;\n            }\n\n            // BMP\n            if (data.Length >= 2 &&\n                data[0] == 0x42 && data[1] == 0x4D)\n            {\n                return true;\n            }\n\n            // WebP: starts with 'RIFF' + 4 bytes + 'WEBP'\n            if (data.Length >= 12 &&\n                data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 &&\n                data[8] == 0x57 && data[9] == 0x45 && data[10] == 0x42 && data[11] == 0x50)\n            {\n                return true;\n            }\n\n            // AVIF: starts with 'ftyp' + 'avif' or 'avis' box brand\n            // ISO BMFF files start with 4-byte box size then 'ftyp'\n            if (data.Length >= 12 &&\n                data[4] == 0x66 && data[5] == 0x74 && data[6] == 0x79 && data[7] == 0x70)\n            {\n                // Check major brand\n                string brand = System.Text.Encoding.ASCII.GetString(data, 8, 4);\n                if (brand == \"avif\" || brand == \"avis\")\n                {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/JsonSerializationUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2008 - 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.IO;\nusing System.Text;\nusing System.Diagnostics;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Converters;\nusing Newtonsoft.Json.Linq;\nusing Newtonsoft.Json.Serialization;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// JSON Serialization helper class using Json.NET.\n    /// This class has simplified single line serialization\n    /// methods to serialize JSON to and from string and \n    /// files on disk with error handling and some common\n    /// optional configuration options.\n    /// </summary>   \n    public static class JsonSerializationUtils\n    {\n        //capture reused type instances\n        private static JsonSerializer JsonNet = null;\n\n        private static object SyncLock = new Object();\n  \t\n        private static UTF8Encoding UTF8Encoding = new UTF8Encoding(false);  \n\n        /// <summary>\n        /// Serializes an object to an XML string. Unlike the other SerializeObject overloads\n        /// this methods *returns a string* rather than a bool result!\n        /// </summary>\n        /// <param name=\"value\">Value to serialize</param>\n        /// <param name=\"throwExceptions\">Determines if a failure throws or returns null</param>\n        /// <returns>\n        /// null on error otherwise the Xml String.         \n        /// </returns>\n        /// <remarks>\n        /// If null is passed in null is also returned so you might want\n        /// to check for null before calling this method.\n        /// </remarks>\n        public static string Serialize(object value, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false)\n        {\n            if (value is null) return \"null\";\n\n            string jsonResult = null;\n            Type type = value.GetType();\n            JsonTextWriter writer = null;\n            try\n            {\n                var json = CreateJsonNet(throwExceptions, camelCase);\n\n                StringWriter sw = new StringWriter();\n\n\t\t\t\twriter = new JsonTextWriter(sw);\n\n\t\t\t\tif (formatJsonOutput)\n\t\t\t\t\twriter.Formatting = Formatting.Indented; \n\n                \n\n                writer.QuoteChar = '\"';\n                json.Serialize(writer, value);\n\n                jsonResult = sw.ToString();\n                writer.Close();\n            }\n            catch\n            {\n                if (throwExceptions)\n                    throw;\n\n                jsonResult = null;\n            }\n            finally\n            {\n                if (writer != null)\n                    writer.Close();\n            }\n\n            return jsonResult;\n        }\n\n        /// <summary>\n        /// Serializes an object instance to a JSON file.\n        /// </summary>\n        /// <param name=\"value\">the value to serialize</param>\n        /// <param name=\"fileName\">Full path to the file to write out with JSON.</param>\n        /// <param name=\"throwExceptions\">Determines whether exceptions are thrown or false is returned</param>\n        /// <param name=\"formatJsonOutput\">if true pretty-formats the JSON with line breaks</param>\n        /// <returns>true or false</returns>        \n        public static bool SerializeToFile(object value, string fileName, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false)\n        {\n            try\n            {\n                Type type = value.GetType();\n\n                var json = CreateJsonNet(throwExceptions, camelCase);\n                if (json == null)\n                    return false;\n\n                \n                using (FileStream fs = new FileStream(fileName, FileMode.Create))\n                {\n                    using (StreamWriter sw = new StreamWriter(fs, UTF8Encoding))\n                    {\n\t\t\t\t\t\tusing (var writer = new JsonTextWriter(sw))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (formatJsonOutput)\n\t\t\t\t\t\t\t\twriter.Formatting = Formatting.Indented;\n\n\t\t\t\t\t\t\twriter.QuoteChar = '\"';\n\t\t\t\t\t\t\tjson.Serialize(writer, value);\n\t\t\t\t\t\t}\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(\"JsonSerializer Serialize error: \" + ex.Message);\n                if (throwExceptions)\n                    throw;\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Deserializes an object, array or value from JSON string to an object or value\n        /// </summary>\n        /// <param name=\"jsonText\"></param>\n        /// <param name=\"type\"></param>\n        /// <param name=\"throwExceptions\"></param>\n        /// <returns></returns>\n        public static object Deserialize(string jsonText, Type type, bool throwExceptions = false)\n        {\n            var json = CreateJsonNet(throwExceptions);\n            if (json == null)\n                return null;\n\n            object result = null;\n\t\t\tJsonTextReader reader = null;\n            try\n            {\n                StringReader sr = new StringReader(jsonText);\n\t\t\t\treader = new JsonTextReader(sr);\n                result = json.Deserialize(reader, type);\n                reader.Close();\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(\"JsonSerializer Deserialize error: \" + ex.Message);\n                if (throwExceptions)\n                    throw;\n\n                return null;\n            }\n            finally\n            {\n                if (reader != null)\n                    reader.Close();\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Deserializes an object, array or value from JSON string to an object or value\n        /// </summary>\n        /// <param name=\"jsonText\"></param>\n        /// <param name=\"throwExceptions\"></param>\n        /// <returns></returns>\n        public static T Deserialize<T>(string jsonText, bool throwExceptions = false)\n        {\n            var res = Deserialize(jsonText, typeof(T), throwExceptions);\n            if (res == null)\n                return default(T);\n\n            return (T) res;\n        }\n\n\t\t/// <summary>\n        /// Deserializes an object from file and returns a reference.\n        /// </summary>\n        /// <param name=\"fileName\">name of the file to serialize to</param>\n        /// <param name=\"objectType\">The Type of the object. Use typeof(yourobject class)</param>\n        /// <param name=\"binarySerialization\">determines whether we use Xml or Binary serialization</param>\n        /// <param name=\"throwExceptions\">determines whether failure will throw rather than return null on failure</param>\n        /// <returns>Instance of the deserialized object or null. Must be cast to your object type</returns>\n        public static object DeserializeFromFile(string fileName, Type objectType, bool throwExceptions = false)\n        {\n            var json = CreateJsonNet(throwExceptions);\n            if (json == null)\n                return null;\n\n            object result;\n            JsonTextReader reader;\n            FileStream fs;\n\n            try\n            {\n                using (fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))\n                {\n                    using (var sr = new StreamReader(fs, Encoding.UTF8))\n                    {\n\t\t\t\t\t\tusing (reader = new JsonTextReader(sr))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tresult = json.Deserialize(reader, objectType);\n\t\t\t\t\t\t}\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Debug.WriteLine(\"JsonNetSerialization Deserialization Error: \" + ex.Message);\n                if (throwExceptions)\n                    throw;\n\n                return null;\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Deserializes an object from file and returns a reference.\n        /// </summary>\n        /// <param name=\"fileName\">name of the file to serialize to</param>\n        /// <param name=\"binarySerialization\">determines whether we use Xml or Binary serialization</param>\n        /// <param name=\"throwExceptions\">determines whether failure will throw rather than return null on failure</param>\n        /// <returns>Instance of the deserialized object or null. Must be cast to your object type</returns>\n        public static T DeserializeFromFile<T>(string fileName,  bool throwExceptions = false)\n        {\n            var res = DeserializeFromFile(fileName, typeof(T), throwExceptions);\n            if (res == null)\n                return default(T);\n            return (T) res;\n        }\n\n        /// <summary>\n        /// Takes a single line JSON string and pretty formats\n        /// it using indented formatting.\n        /// </summary>\n        /// <param name=\"json\"></param>\n        /// <returns></returns>\n        public static string FormatJsonString(string json)\n        {\n            return JToken.Parse(json).ToString(Formatting.Indented) as string;\t\t\t\n        }\n\n        /// <summary>\n        /// Dynamically creates an instance of JSON.NET\n        /// </summary>\n        /// <param name=\"throwExceptions\">If true throws exceptions otherwise returns null</param>\n        /// <returns>Dynamic JsonSerializer instance</returns>\n        public static JsonSerializer CreateJsonNet(bool throwExceptions = true, bool camelCase = false)\n        {\n            if (JsonNet != null)\n                return JsonNet;\n\n            lock (SyncLock)\n            {\n                if (JsonNet != null)\n                    return JsonNet;\n\n\t\t\t\t// Try to create instance\n\t\t\t\tJsonSerializer json;\n                try\n                {\n\t\t\t\t\tjson = new JsonSerializer();\n                }\n                catch\n                {\n                    if (throwExceptions)\n                        throw;\n                    return null;\n                }\n                \n                if (json == null)\n                    return null;\n\n\t\t\t\tjson.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;\n\n\t\t\t\t// Enums as strings in JSON\n\t\t\t\tvar enumConverter = new StringEnumConverter(); \n                json.Converters.Add(enumConverter);\n\n                if (camelCase)\n                    json.ContractResolver = new CamelCasePropertyNamesContractResolver();\n\n                JsonNet = json;\n            }\n\n            return JsonNet;\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/KnownFolders.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing static System.Environment;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Class that returns special Windows file system paths. Extends the folder list\n    /// beyond what Environment.GetFolderPath(Environment.SpecialFolder) provides\n    /// with additional Windows known folders like Library, Downloads etc.\n    /// </summary>\n    public static class KnownFolders\n    {\n        /// <summary>\n        /// Gets the current path to the specified known folder as currently configured. This does\n        /// not require the folder to be existent.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which current path will be returned.</param>\n        /// <returns>The default path of the known folder.</returns>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the path\n        ///     could not be retrieved.</exception>\n        public static string GetPath(KnownFolder knownFolder)\n        {\n            return GetPath(knownFolder, false);\n        }\n\n\n        /// <summary>\n        /// Gets the current path to the specified known folder as currently configured. This does\n        /// not require the folder to be existent.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which current path will be returned.</param>\n        /// <param name=\"defaultUser\">Specifies if the paths of the default user (user profile\n        ///     template) will be used. This requires administrative rights.</param>\n        /// <returns>The default path of the known folder.</returns>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the path\n        ///     could not be retrieved.</exception>\n        public static string GetPath(KnownFolder knownFolder, bool defaultUser)\n        {\n            return GetPath(knownFolder, KnownFolderFlags.DontVerify, defaultUser);\n        }\n\n        /// <summary>\n        /// Gets the default path to the specified known folder. This does not require the folder\n        /// to be existent.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which default path will be returned.</param>\n        /// <returns>The current (and possibly redirected) path of the known folder.</returns>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the path\n        ///     could not be retrieved.</exception>\n        public static string GetDefaultPath(KnownFolder knownFolder)\n        {\n            return GetDefaultPath(knownFolder, false);\n        }\n\n        /// <summary>\n        /// Gets the default path to the specified known folder. This does not require the folder\n        /// to be existent.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which default path will be returned.</param>\n        /// <param name=\"defaultUser\">Specifies if the paths of the default user (user profile\n        ///     template) will be used. This requires administrative rights.</param>\n        /// <returns>The current (and possibly redirected) path of the known folder.</returns>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the path\n        ///     could not be retrieved.</exception>\n        public static string GetDefaultPath(KnownFolder knownFolder, bool defaultUser)\n        {\n            return GetPath(knownFolder, KnownFolderFlags.DefaultPath | KnownFolderFlags.DontVerify,\n                defaultUser);\n        }\n\n        /// <summary>\n        /// Creates and initializes the known folder.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which will be initialized.</param>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the known\n        ///     folder could not be initialized.</exception>\n        public static void Initialize(KnownFolder knownFolder)\n        {\n            Initialize(knownFolder, false);\n        }\n\n        /// <summary>\n        /// Creates and initializes the known folder.\n        /// </summary>\n        /// <param name=\"knownFolder\">The known folder which will be initialized.</param>\n        /// <param name=\"defaultUser\">Specifies if the paths of the default user (user profile\n        ///     template) will be used. This requires administrative rights.</param>\n        /// <exception cref=\"System.Runtime.InteropServices.ExternalException\">Thrown if the known\n        ///     folder could not be initialized.</exception>\n        public static void Initialize(KnownFolder knownFolder, bool defaultUser)\n        {\n            GetPath(knownFolder, KnownFolderFlags.Create | KnownFolderFlags.Init, defaultUser);\n        }\n\n        private static string GetPath(KnownFolder knownFolder, KnownFolderFlags flags,\n            bool defaultUser)\n        {\n            // Handle SpecialFolder-mapped values\n            SpecialFolder? specialFolder = knownFolder switch\n            {\n                KnownFolder.UserProfile => SpecialFolder.UserProfile,\n                KnownFolder.ProgramFiles => SpecialFolder.ProgramFiles,\n                KnownFolder.ProgramFilesX86 => SpecialFolder.ProgramFilesX86,\n                KnownFolder.Programs => SpecialFolder.Programs,\n                KnownFolder.ApplicationData => SpecialFolder.ApplicationData,\n                KnownFolder.LocalApplicationData => SpecialFolder.LocalApplicationData,\n                KnownFolder.AdminTools => SpecialFolder.AdminTools,\n                KnownFolder.Startup => SpecialFolder.Startup,\n                KnownFolder.Recent => SpecialFolder.Recent,\n                KnownFolder.SendTo => SpecialFolder.SendTo,\n                KnownFolder.StartMenu => SpecialFolder.StartMenu,\n                KnownFolder.DesktopDirectory => SpecialFolder.DesktopDirectory,\n                KnownFolder.MyComputer => SpecialFolder.MyComputer,\n                KnownFolder.NetworkShortcuts => SpecialFolder.NetworkShortcuts,\n                KnownFolder.Fonts => SpecialFolder.Fonts,\n                KnownFolder.Templates => SpecialFolder.Templates,\n                KnownFolder.CommonStartMenu => SpecialFolder.CommonStartMenu,\n                KnownFolder.CommonPrograms => SpecialFolder.CommonPrograms,\n                KnownFolder.CommonStartup => SpecialFolder.CommonStartup,\n                KnownFolder.CommonDesktopDirectory => SpecialFolder.CommonDesktopDirectory,\n                KnownFolder.PrinterShortcuts => SpecialFolder.PrinterShortcuts,\n                KnownFolder.InternetCache => SpecialFolder.InternetCache,\n                KnownFolder.Cookies => SpecialFolder.Cookies,\n                KnownFolder.History => SpecialFolder.History,\n                KnownFolder.CommonApplicationData => SpecialFolder.CommonApplicationData,\n                KnownFolder.Windows => SpecialFolder.Windows,\n                KnownFolder.System => SpecialFolder.System,\n                KnownFolder.SystemX86 => SpecialFolder.SystemX86,\n                KnownFolder.CommonProgramFiles => SpecialFolder.CommonProgramFiles,\n                KnownFolder.CommonProgramFilesX86 => SpecialFolder.CommonProgramFilesX86,\n                KnownFolder.CommonTemplates => SpecialFolder.CommonTemplates,\n                KnownFolder.CommonDocuments => SpecialFolder.CommonDocuments,\n                KnownFolder.CommonAdminTools => SpecialFolder.CommonAdminTools,\n                KnownFolder.CommonMusic => SpecialFolder.CommonMusic,\n                KnownFolder.CommonPictures => SpecialFolder.CommonPictures,\n                KnownFolder.CommonVideos => SpecialFolder.CommonVideos,\n                KnownFolder.Resources => SpecialFolder.Resources,\n                KnownFolder.LocalizedResources => SpecialFolder.LocalizedResources,\n                KnownFolder.CommonOemLinks => SpecialFolder.CommonOemLinks,\n                KnownFolder.CDBurning => SpecialFolder.CDBurning,\n                _ => null\n            };\n\n            if (specialFolder != null)\n                return Environment.GetFolderPath(specialFolder.Value);\n\n#if NET60_OR_GREATER\n            if (!OperatingSystem.IsWindows())\n                return string.Empty;   // exit if not windows\n#endif\n\n            // these only work on Windows via PInvoke to Windows API\n            IntPtr outPath;\n            int result = SHGetKnownFolderPath(new Guid(_knownFolderGuids[(int)knownFolder]),\n                (uint)flags, new IntPtr(defaultUser ? -1 : 0), out outPath);\n            if (result >= 0)\n            {\n                return Marshal.PtrToStringUni(outPath);\n            }\n            else\n            {\n                throw new ExternalException(\"Unable to retrieve the known folder path. It may not \"\n                                            + \"be available on this system.\", result);\n            }\n        }\n\n        /// <summary>\n        /// Retrieves the full path of a known folder identified by the folder's KnownFolderID.\n        /// </summary>\n        /// <param name=\"rfid\">A KnownFolderID that identifies the folder.</param>\n        /// <param name=\"dwFlags\">Flags that specify special retrieval options. This value can be\n        ///     0; otherwise, one or more of the KnownFolderFlag values.</param>\n        /// <param name=\"hToken\">An access token that represents a particular user. If this\n        ///     parameter is NULL, which is the most common usage, the function requests the known\n        ///     folder for the current user. Assigning a value of -1 indicates the Default User.\n        ///     The default user profile is duplicated when any new user account is created.\n        ///     Note that access to the Default User folders requires administrator privileges.\n        ///     </param>\n        /// <param name=\"ppszPath\">When this method returns, contains the address of a string that\n        ///     specifies the path of the known folder. The returned path does not include a\n        ///     trailing backslash.</param>\n        /// <returns>Returns S_OK if successful, or an error value otherwise.</returns>\n        [DllImport(\"Shell32.dll\")]\n        private static extern int SHGetKnownFolderPath(\n            [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken,\n            out IntPtr ppszPath);\n\n        [Flags]\n        private enum KnownFolderFlags : uint\n        {\n            SimpleIDList = 0x00000100,\n            NotParentRelative = 0x00000200,\n            DefaultPath = 0x00000400,\n            Init = 0x00000800,\n            NoAlias = 0x00001000,\n            DontUnexpand = 0x00002000,\n            DontVerify = 0x00004000,\n            Create = 0x00008000,\n            NoAppcontainerRedirection = 0x00010000,\n            AliasOnly = 0x80000000\n        }\n\n        private static string[] _knownFolderGuids = new string[]\n        {            \n         //Folder Ids: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx\n         \"{56784854-C6CB-462B-8169-88E350ACB882}\", // Contacts\n         \"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}\", // Desktop\n         \"{FDD39AD0-238F-46AF-ADB4-6C85480369C7}\", // Documents\n         \"{374DE290-123F-4565-9164-39C4925E467B}\", // Downloads\n         \"{1777F761-68AD-4D8A-87BD-30B759FA33DD}\", // Favorites\n         \"{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}\", // Links\n         \"{4BD8D571-6D19-48D3-BE97-422220080E43}\", // Music\n         \"{33E28130-4E1E-4676-835A-98395C3BC3BB}\", // Pictures\n         \"{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}\", // SavedGames\n         \"{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}\", // SavedSearches\n         \"{18989B1D-99B5-455B-841C-AB7C74E4DDFC}\", // Videos\n         \"{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}\", // DocumentsLibrary\n         \"{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}\"  // Libraries\n        };\n    }\n\n\n\n    /// <summary>\n    /// Standard folders registered with the system. These folders are installed with Windows Vista\n    /// and later operating systems, and a computer will have only folders appropriate to it\n    /// installed.\n    /// </summary>\n    public enum KnownFolder\n    {\n        Contacts,\n        Desktop,\n        Documents,\n        Downloads,\n        Favorites,\n        Links,\n        Music,\n        Pictures,\n        SavedGames,\n        SavedSearches,\n        Videos,\n        DocumentsLibrary,\n        Libraries,\n\n        /* Separately handled */\n\n        UserProfile,\n        ProgramFiles,\n        ProgramFilesX86,\n        Programs,\n        ApplicationData,\n        LocalApplicationData,\n        AdminTools,\n\n        // Additional SpecialFolder values\n        Startup,\n        Recent,\n        SendTo,\n        StartMenu,\n        DesktopDirectory,\n        MyComputer,\n        NetworkShortcuts,\n        Fonts,\n        Templates,\n        CommonStartMenu,\n        CommonPrograms,\n        CommonStartup,\n        CommonDesktopDirectory,\n        PrinterShortcuts,\n        InternetCache,\n        Cookies,\n        History,\n        CommonApplicationData,\n        Windows,\n        System,\n        SystemX86,\n        CommonProgramFiles,\n        CommonProgramFilesX86,\n        CommonTemplates,\n        CommonDocuments,\n        CommonAdminTools,\n        CommonMusic,\n        CommonPictures,\n        CommonVideos,\n        Resources,\n        LocalizedResources,\n        CommonOemLinks,\n        CDBurning\n\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/LanguageUtils.cs",
    "content": "﻿using System;\n\nnamespace Westwind.Utilities\n{\n\n    public static class LanguageUtils\n    {\n        /// <summary>\n        /// Runs an operation and ignores any Exceptions that occur.\n        /// Returns true or falls depending on whether catch was\n        /// triggered\n        /// </summary>\n        /// <param name=\"operation\">lambda that performs an operation that might throw</param>\n        /// <returns></returns>\n        public static bool IgnoreErrors(Action operation)\n        {\n            if (operation == null)\n                return false;\n\n            try\n            {\n                operation.Invoke();\n            }\n            catch\n            {\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Runs an function that returns a value and ignores any Exceptions that occur.\n        /// Returns true or falls depending on whether catch was\n        /// triggered\n        /// </summary>\n        /// <param name=\"operation\">parameterless lamda that returns a value of T</param>\n        /// <param name=\"defaultValue\">Default value returned if operation fails</param>\n        public static T IgnoreErrors<T>(Func<T> operation, T defaultValue = default(T))\n        {\n            if (operation == null)\n                return defaultValue;\n\n            T result;\n            try\n            {\n                result = operation.Invoke();\n            }\n            catch\n            {\n                result = defaultValue;\n            }\n\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/NetworkUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Text;\n\nnamespace Westwind.Utilities\n{\n    public static class NetworkUtils\n    {\n\n        /// <summary>\n        /// Retrieves a base domain name from a full domain name.\n        /// For example: www.west-wind.com produces west-wind.com\n        /// </summary>\n        /// <param name=\"domainName\">Dns Domain name as a string</param>\n        /// <returns></returns>\n        public static string GetBaseDomain(string domainName)\n        {\n                var tokens = domainName.Split('.');\n\n                // only split 3 urls like www.west-wind.com\n                if (tokens == null || tokens.Length != 3)\n                    return domainName;\n\n\t            var tok  = new List<string>(tokens);\n                var remove = tokens.Length - 2;\n                tok.RemoveRange(0, remove);\n\n                return tok[0] + \".\" + tok[1]; ;                                \n        }\n    \n        /// <summary>\n        /// Returns the base domain from a domain name\n        /// Example: http://www.west-wind.com returns west-wind.com\n        /// </summary>\n        /// <param name=\"uri\"></param>\n        /// <returns></returns>\n        public static string GetBaseDomain(this Uri uri)\n        {\n            if (uri.HostNameType == UriHostNameType.Dns)            \t        \n                return GetBaseDomain(uri.DnsSafeHost);\n            \n            return uri.Host;\n        }\n\n        /// <summary>\n        /// Checks to see if an IP Address or Domain is a local address\n        /// </summary>\n        /// <remarks>\n        /// Do not use in high traffic situations, as this involves a\n        /// DNS lookup. If no local hostname is found it goes out to a\n        /// DNS server to retrieve IP Addresses.\n        /// </remarks>\n        /// <param name=\"hostOrIp\">Hostname or IP Address</param>\n        /// <returns>true or false</returns>\n        public static bool IsLocalIpAddress(string hostOrIp)\n        {\n            if(string.IsNullOrEmpty(hostOrIp))\n                return false;\n\n            try\n            {\n                // get IP Mapped to passed host\n                IPAddress[] hostIPs = Dns.GetHostAddresses(hostOrIp);\n                \n                // get local IP addresses\n                IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());\n\n                // check host ip addresses against local ip addresses for matches\n                foreach (IPAddress hostIP in hostIPs)\n                {\n                    // check for localhost/127.0.0.1\n                    if (IPAddress.IsLoopback(hostIP)) \n                        return true;\n\n                    // Check if IP Address matches a local IP\n                    foreach (IPAddress localIP in localIPs)\n                    {\n                        if (hostIP.Equals(localIP)) \n                            return true;\n                    }\n                }\n            }\n            catch {}\n\n            return false;\n        }\n\n        /// <summary>\n        /// Checks to see if an IP Address or Domain is a local address\n        /// </summary>\n        /// <remarks>\n        /// Do not use in high traffic situations, as this involves a\n        /// DNS lookup. If no local hostname is found it goes out to a\n        /// DNS server to retrieve IP Addresses.\n        /// </remarks>\n        /// <param name=\"uri\">Pass a full URL as an Uri</param>\n        /// <returns></returns>\n        public static bool IsLocalIpAddress(Uri uri)\n        {\n            if (uri == null)\n                return false;\n\n            var host = uri.Host;\n            return IsLocalIpAddress(host);\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/PasswordScrubber.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// A very basic password scrubber that can scrub passwords from\n    /// JSON and Sql Connection strings. \n    /// \n    /// This is a very basic implementation where you can provide the\n    /// keys to scrub or use default values.\n    /// </summary>\n    public class PasswordScrubber\n    {\n        /// <summary>\n        /// A static instance that can be used without instantiating first\n        /// </summary>\n        public static PasswordScrubber Instance = new PasswordScrubber();\n\n        /// <summary>\n        /// If set to a non-zero value displays the frist n characters\n        /// of the value that is being obscured.\n        /// </summary>\n        public int ShowUnobscuredCharacterCount = 2;\n\n        /// <summary>\n        /// Value displayed for obscured values. If choosing to display first characters\n        /// those are in addition to the obscured values.\n        /// </summary>\n        public string ObscuredValueBaseDisplay = \"****\";\n\n\n        public string ScrubJsonValues(string configString, params string[] jsonKeys)\n        {\n            if (jsonKeys == null || jsonKeys.Length < 1) jsonKeys = new string[1] { \"password\" };\n\n            foreach (var token in jsonKeys)\n            {\n                var matchValue = $@\"\"\"{token}\"\":\\s*\"\"(.*?)\"\"\";\n                var match = Regex.Match(configString, matchValue, RegexOptions.Multiline | RegexOptions.IgnoreCase);\n                if (match.Success)\n                {\n                    var group = match.Groups[1];\n                    configString = configString.Replace(group.Value, ObscureValue(group.Value));\n                }\n            }\n\n            return configString;\n        }\n\n        public string ScrubSqlConnectionStringValues(string configString, params string[] connKeys)\n        {\n            if (connKeys == null || connKeys.Length < 1) connKeys = new string[] { \"pwd\", \"password\" };\n\n            foreach (var key in connKeys)\n            {\n                // Sql Connection String pwd\n                var extract = StringUtils.ExtractString(configString, $\"{key}=\", \";\", allowMissingEndDelimiter: true, returnDelimiters: true);\n                if (!string.IsNullOrEmpty(extract))\n                {\n                    var only = StringUtils.ExtractString(extract, $\"{key}=\", \";\", allowMissingEndDelimiter: true, returnDelimiters: false);\n                    configString = configString.Replace(extract, $\"{key}=\" + ObscureValue(only) + \";\");\n                }\n            }\n\n            return configString;\n        }\n\n        public string ObscureValue(string value, int showUnobscuredCharacterCount = -1)\n        {\n            if (string.IsNullOrEmpty(value)) return value;\n\n            if (showUnobscuredCharacterCount < 0)\n                showUnobscuredCharacterCount = ShowUnobscuredCharacterCount;\n\n            // very short –just display the obscured value without any revealed characters\n            if (showUnobscuredCharacterCount > value.Length + 2)\n                return ObscuredValueBaseDisplay;\n\n            return value.Substring(0, showUnobscuredCharacterCount) + ObscuredValueBaseDisplay;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ReflectionUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2008 - 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Reflection;\nusing System.Collections;\nusing System.Globalization;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Runtime.Versioning;\n\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Collection of Reflection and type conversion related utility functions\n    /// </summary>\n    public static class ReflectionUtils\n    {\n\n        /// <summary>\n        /// Binding Flags constant to be reused for all Reflection access methods.\n        /// </summary>\n        public const BindingFlags MemberAccess =\n            BindingFlags.Public | BindingFlags.NonPublic |\n            BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase;\n\n        public const BindingFlags MemberAccessCom =\n            BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;\n\n        #region Type and Assembly Creation\n        /// <summary>\n        /// Creates an instance from a type by calling the parameterless constructor.\n        /// \n        /// Note this will not work with COM objects - continue to use the Activator.CreateInstance\n        /// for COM objects.\n        /// <seealso>Class wwUtils</seealso>\n        /// </summary>\n        /// <param name=\"typeToCreate\">\n        /// The type from which to create an instance.\n        /// </param>\n        /// <returns>object</returns>\n        public static object CreateInstanceFromType(Type typeToCreate, params object[] args)\n        {\n            if (args == null)\n            {\n                Type[] Parms = Type.EmptyTypes;\n                return typeToCreate.GetConstructor(Parms).Invoke(null);\n            }\n\n            return Activator.CreateInstance(typeToCreate, args);\n        }\n\n\n\n\n        /// <summary>\n        /// Creates an instance of a type based on a string. Assumes that the type's\n        /// </summary>\n        /// <param name=\"typeName\"></param>\n        /// <param name=\"args\"></param>\n        /// <returns></returns>\n        public static object CreateInstanceFromString(string typeName, params object[] args)\n        {\n            object instance = null;\n\n            try\n            {\n                var type = GetTypeFromName(typeName);\n                if (type == null)\n                    return null;\n\n                instance = Activator.CreateInstance(type, args);\n            }\n            catch\n            {\n                return null;\n            }\n\n            return instance;\n        }\n\n\n      \n\n        /// <summary>\n        /// Helper routine that looks up a type name and tries to retrieve the\n        /// full type reference using GetType() and if not found looking \n        /// in the actively executing assemblies and optionally loading\n        /// the specified assembly name.\n        /// </summary>\n        /// <param name=\"typeName\">type to load</param>\n        /// <param name=\"assemblyName\">\n        /// Optional assembly name to load from if type cannot be loaded initially. \n        /// Use for lazy loading of assemblies without taking a type dependency.\n        /// </param>\n        /// <returns>null</returns>\n        public static Type GetTypeFromName(string typeName, string assemblyName )\n        {\n            var type = Type.GetType(typeName, false);\n            if (type != null)\n                return type;\n\n            var assemblies = AppDomain.CurrentDomain.GetAssemblies();\n            // try to find manually\n            foreach (Assembly asm in assemblies)\n            {\n                type = asm.GetType(typeName, false);\n\n                if (type != null)\n                    break;\n            }\n            if (type != null)\n                return type;\n\n            // see if we can load the assembly\n            if (!string.IsNullOrEmpty(assemblyName))\n            {\n                var a = LoadAssembly(assemblyName);\n                if (a != null)\n                {\n                    type = Type.GetType(typeName, false);\n                    if (type != null)\n                        return type;\n                }\n            }\n\n            return null;\n        }\n\n        /// <summary>\n        /// Overload for backwards compatibility which only tries to load\n        /// assemblies that are already loaded in memory.\n        /// </summary>\n        /// <param name=\"typeName\"></param>\n        /// <returns></returns>        \n        public static Type GetTypeFromName(string typeName)\n        {\n            return GetTypeFromName(typeName, null);\n        }\n\n        /// <summary>\n        /// Creates a COM instance from a ProgID. Loads either\n        /// Exe or DLL servers.\n        /// </summary>\n        /// <param name=\"progId\"></param>\n        /// <returns></returns>\n#if NET6_0_OR_GREATER\n        [SupportedOSPlatform(\"windows\")]\n#endif\n        public static object CreateComInstance(string progId)\n        {\n            Type type = Type.GetTypeFromProgID(progId);\n            if (type == null)\n                return null;\n\n            return Activator.CreateInstance(type);\n        }\n\n        /// <summary>\n        /// Try to load an assembly into the application's app domain.\n        /// Loads by name first then checks for filename\n        /// </summary>\n        /// <param name=\"assemblyName\">Assembly name or full path</param>\n        /// <returns>null on failure</returns>\n        public static Assembly LoadAssembly(string assemblyName)\n        {\n            Assembly assembly = null;\n            try\n            {\n                assembly = Assembly.Load(assemblyName);\n            }\n            catch { }\n\n            if (assembly != null)\n                return assembly;\n\n            if (File.Exists(assemblyName))\n            {\n                assembly = Assembly.LoadFrom(assemblyName);\n                if (assembly != null)\n                    return assembly;\n            }\n            return null;\n        }\n\n        /// <summary>\n        /// Clones an object using a shallow cloning using private\n        /// MemberwiseClone operation.\n        /// </summary>\n        /// <param name=\"source\">Object to copy</param>\n        /// <returns>A shallow copy: Value types are copies, reference types are left as references</returns>\n        public static object ShallowClone(object source)\n        {\n            return source.GetType()\n                        .GetMethod(\"MemberwiseClone\",\n                                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod)\n                        .Invoke(source, null);\n        }\n        #endregion\n\n\n        #region Conversions\n\n        /// <summary>\n        /// Turns a string into a typed value generically.\n        /// Explicitly assigns common types and falls back\n        /// on using type converters for unhandled types.         \n        /// \n        /// Common uses: \n        /// * UI -&gt; to data conversions\n        /// * Parsers\n        /// <seealso>Class ReflectionUtils</seealso>\n        /// </summary>\n        /// <param name=\"sourceString\">\n        /// The string to convert from\n        /// </param>\n        /// <param name=\"targetType\">\n        /// The type to convert to\n        /// </param>\n        /// <param name=\"culture\">\n        /// Culture used for numeric and datetime values.\n        /// </param>\n        /// <returns>object. Throws exception if it cannot be converted.</returns>\n        public static object StringToTypedValue(string sourceString, Type targetType, CultureInfo culture = null)\n        {\n            object result = null;\n\n            bool isEmpty = string.IsNullOrEmpty(sourceString);\n\n            if (culture == null)\n                culture = CultureInfo.CurrentCulture;\n\n            if (targetType == typeof(string))\n                result = sourceString;\n            else if (targetType == typeof(Int32) || targetType == typeof(int))\n            {\n                if (isEmpty)\n                    result = 0;\n                else\n                    result = Int32.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Int64))\n            {\n                if (isEmpty)\n                    result = (Int64)0;\n                else\n                    result = Int64.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Int16))\n            {\n                if (isEmpty)\n                    result = (Int16)0;\n                else\n                    result = Int16.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(decimal))\n            {\n                if (isEmpty)\n                    result = 0M;\n                else\n                    result = decimal.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(DateTime))\n            {\n                if (isEmpty)\n                    result = DateTime.MinValue;\n                else\n                    result = Convert.ToDateTime(sourceString, culture.DateTimeFormat);\n            }\n            else if (targetType == typeof(byte))\n            {\n                if (isEmpty)\n                    result = 0;\n                else\n                    result = Convert.ToByte(sourceString);\n            }\n            else if (targetType == typeof(double))\n            {\n                if (isEmpty)\n                    result = 0F;\n                else\n                    result = Double.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(Single))\n            {\n                if (isEmpty)\n                    result = 0F;\n                else\n                    result = Single.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);\n            }\n            else if (targetType == typeof(bool))\n            {\n                sourceString = sourceString.ToLower();\n                if (!isEmpty &&\n                    sourceString == \"true\" || sourceString == \"on\" ||\n                    sourceString == \"1\" || sourceString == \"yes\")\n                    result = true;\n                else\n                    result = false;\n            }\n            else if (targetType == typeof(Guid))\n            {\n                if (isEmpty)\n                    result = Guid.Empty;\n                else\n                    result = new Guid(sourceString);\n            }\n            else if (targetType.IsEnum)\n                result = Enum.Parse(targetType, sourceString);\n            else if (targetType == typeof(byte[]))\n            {\n                // TODO: Convert HexBinary string to byte array\n                result = null;\n            }\n\n            // Handle nullables explicitly since type converter won't handle conversions\n            // properly for things like decimal separators currency formats etc.\n            // Grab underlying type and pass value to that\n            else if (targetType.Name.StartsWith(\"Nullable`\"))\n            {\n                if (sourceString.ToLower() == \"null\" || sourceString == string.Empty)\n                    result = null;\n                else\n                {\n                    targetType = Nullable.GetUnderlyingType(targetType);\n                    result = StringToTypedValue(sourceString, targetType);\n                }\n            }\n            else\n            {\n                TypeConverter converter = TypeDescriptor.GetConverter(targetType);\n                if (converter != null && converter.CanConvertFrom(typeof(string)))\n                    result = converter.ConvertFromString(null, culture, sourceString);\n                else\n                {\n                    Debug.Assert(false, string.Format(\"Type Conversion not handled in StringToTypedValue for {0} {1}\",\n                        targetType.Name, sourceString));\n                    throw (new InvalidCastException(\"Type Conversion failed for: \" + targetType.Name));\n                }\n            }\n\n            return result;\n        }\n\n\n\n        /// <summary>\n        /// Generic version allow for automatic type conversion without the explicit type\n        /// parameter\n        /// </summary>\n        /// <typeparam name=\"T\">Type to be converted to</typeparam>\n        /// <param name=\"sourceString\">input string value to be converted</param>\n        /// <param name=\"culture\">Culture applied to conversion</param>\n        /// <returns></returns>\n        public static T StringToTypedValue<T>(string sourceString, CultureInfo culture = null)\n        {\n            return (T)StringToTypedValue(sourceString, typeof(T), culture);\n        }\n\n        /// <summary>\n        /// Converts a type to string if possible. This method supports an optional culture generically on any value.\n        /// It calls the ToString() method on common types and uses a type converter on all other objects\n        /// if available\n        /// </summary>\n        /// <param name=\"rawValue\">The Value or Object to convert to a string</param>\n        /// <param name=\"culture\">Culture for numeric and DateTime values</param>\n        /// <param name=\"unsupportedReturn\">Return string for unsupported types</param>\n        /// <returns>string</returns>\n        public static string TypedValueToString(object rawValue, CultureInfo culture = null, string unsupportedReturn = null)\n        {\n            if (rawValue == null)\n                return string.Empty;\n\n            if (culture == null)\n                culture = CultureInfo.CurrentCulture;\n\n            Type valueType = rawValue.GetType();\n            string returnValue = null;\n\n            if (valueType == typeof(string))\n                returnValue = rawValue as string;\n            else if (valueType == typeof(int) || valueType == typeof(decimal) ||\n                     valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(Single))\n                returnValue = string.Format(culture.NumberFormat, \"{0}\", rawValue);\n            else if (valueType == typeof(DateTime))\n                returnValue = string.Format(culture.DateTimeFormat, \"{0}\", rawValue);\n            else if (valueType == typeof(bool) || valueType == typeof(Byte) || valueType.IsEnum)\n                returnValue = rawValue.ToString();\n            else if (valueType == typeof(Guid?))\n            {\n                if (rawValue == null)\n                    returnValue = string.Empty;\n                else\n                    return rawValue.ToString();\n            }\n            else\n            {\n                // Any type that supports a type converter\n                TypeConverter converter = TypeDescriptor.GetConverter(valueType);\n                if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))\n                    returnValue = converter.ConvertToString(null, culture, rawValue);\n                else\n                {\n                    // Last resort - just call ToString() on unknown type\n                    if (!string.IsNullOrEmpty(unsupportedReturn))\n                        returnValue = unsupportedReturn;\n                    else\n                        returnValue = rawValue.ToString();\n                }\n            }\n\n            return returnValue;\n        }\n\n        #endregion\n\n        #region Member Access\n\n        /// <summary>\n        /// Calls a method on an object dynamically. This version requires explicit\n        /// specification of the parameter type signatures.\n        /// </summary>\n        /// <param name=\"instance\">Instance of object to call method on</param>\n        /// <param name=\"method\">The method to call as a stringToTypedValue</param>\n        /// <param name=\"parameterTypes\">Specify each of the types for each parameter passed. \n        /// You can also pass null, but you may get errors for ambiguous methods signatures\n        /// when null parameters are passed</param>\n        /// <param name=\"parms\">any variable number of parameters.</param>        \n        /// <returns>object</returns>\n        public static object CallMethod(object instance, string method, Type[] parameterTypes, params object[] parms)\n        {\n            if (parameterTypes == null && parms.Length > 0)\n                // Call without explicit parameter types - might cause problems with overloads    \n                // occurs when null parameters were passed and we couldn't figure out the parm type\n                return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod).Invoke(instance, parms);\n            else\n                // Call with parameter types - works only if no null values were passed\n                return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, parameterTypes, null).Invoke(instance, parms);\n        }\n\n        /// <summary>   \n        /// Calls a method on an object dynamically. \n        /// \n        /// This version doesn't require specific parameter signatures to be passed. \n        /// Instead parameter types are inferred based on types passed. Note that if \n        /// you pass a null parameter, type inferrance cannot occur and if overloads\n        /// exist the call may fail. if so use the more detailed overload of this method.\n        /// </summary> \n        /// <param name=\"instance\">Instance of object to call method on</param>\n        /// <param name=\"method\">The method to call as a stringToTypedValue</param>\n        /// <param name=\"parameterTypes\">Specify each of the types for each parameter passed. \n        /// You can also pass null, but you may get errors for ambiguous methods signatures\n        /// when null parameters are passed</param>\n        /// <param name=\"parms\">any variable number of parameters.</param>        \n        /// <returns>object</returns>\n        public static object CallMethod(object instance, string method, params object[] parms)\n        {\n            // Pick up parameter types so we can match the method properly\n            Type[] parameterTypes = null;\n            if (parms != null)\n            {\n                parameterTypes = new Type[parms.Length];\n                for (int x = 0; x < parms.Length; x++)\n                {\n                    // if we have null parameters we can't determine parameter types - exit\n                    if (parms[x] == null)\n                    {\n                        parameterTypes = null;  // clear out - don't use types        \n                        break;\n                    }\n                    parameterTypes[x] = parms[x].GetType();\n                }\n            }\n            return CallMethod(instance, method, parameterTypes, parms);\n        }\n\n        /// <summary>\n        /// Allows invoking an event from an external classes where direct access\n        /// is not allowed (due to 'Can only assign to left hand side of operation')\n        /// </summary>\n        /// <param name=\"instance\">Instance of the object hosting the event</param>\n        /// <param name=\"eventName\">Name of the event to invoke</param>\n        /// <param name=\"parameters\">Optional parameters to the event handler to be invoked</param>\n        public static void InvokeEvent(object instance, string eventName, params object[] parameters)\n        {\n            MulticastDelegate del =\n                (MulticastDelegate)instance?.GetType().GetField(eventName,\n                    System.Reflection.BindingFlags.Instance |\n                    System.Reflection.BindingFlags.NonPublic)?.GetValue(instance);\n            \n            if (del == null)\n                return;\n\n            Delegate[] delegates = del.GetInvocationList();\n            foreach (Delegate dlg in delegates)\n            {\n                dlg.Method.Invoke(dlg.Target, parameters);\n            }\n        }\n\n        /// <summary>\n        /// Retrieve a property value from an object dynamically. This is a simple version\n        /// that uses Reflection calls directly. It doesn't support indexers.\n        /// </summary>\n        /// <param name=\"instance\">Object to make the call on</param>\n        /// <param name=\"property\">Property to retrieve</param>\n        /// <returns>Object - cast to proper type</returns>\n        public static object GetProperty(object instance, string property)\n        {\n            return instance.GetType().GetProperty(property).GetValue(instance, null);\n        }\n\n        /// <summary>\n        /// Parses Properties and Fields including Array and Collection references.\n        /// Used internally for the 'Ex' Reflection methods.\n        /// </summary>\n        /// <param name=\"Parent\"></param>\n        /// <param name=\"Property\"></param>\n        /// <returns></returns>\n        private static object GetPropertyInternal(object Parent, string Property)\n        {\n            if (Property == \"this\" || Property == \"me\")\n                return Parent;\n\n            object result = null;\n            string pureProperty = Property;\n            string indexes = null;\n            bool isArrayOrCollection = false;\n\n            // Deal with Array Property\n            if (Property.IndexOf(\"[\") > -1)\n            {\n                pureProperty = Property.Substring(0, Property.IndexOf(\"[\"));\n                indexes = Property.Substring(Property.IndexOf(\"[\"));\n                isArrayOrCollection = true;\n            }\n\n            var parentType = Parent.GetType();\n\n            if (string.IsNullOrEmpty(pureProperty))\n            {\n                // most likely an indexer\n                result = Parent;\n            }\n            else\n            {\n                // Get the member\n                MemberInfo member = Parent.GetType().GetMember(pureProperty, ReflectionUtils.MemberAccess)[0];\n                if (member.MemberType == MemberTypes.Property)\n                    result = ((PropertyInfo) member).GetValue(Parent, null);\n                else\n                    result = ((FieldInfo) member).GetValue(Parent);\n            }\n\n            if (isArrayOrCollection)\n            {\n                indexes = indexes.Replace(\"[\", string.Empty).Replace(\"]\", string.Empty);\n\n                if (result is Array)\n                {\n                    int Index = -1;\n                    int.TryParse(indexes, out Index);\n                    result = CallMethod(result, \"GetValue\", Index);\n                }\n                else if (result is ICollection || result is System.Data.DataRow || result is System.Data.DataTable)\n                {\n                    if (indexes.StartsWith(\"\\\"\"))\n                    {\n                        // String Index\n                        indexes = indexes.Trim('\\\"');\n                        result = CallMethod(result, \"get_Item\", indexes);\n                    }\n                    else\n                    {\n                        // assume numeric index\n                        int index = -1;\n                        int.TryParse(indexes, out index);\n                        result = CallMethod(result, \"get_Item\", index);\n                    }\n                }\n\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Returns a PropertyInfo structure from an extended Property reference\n        /// </summary>\n        /// <param name=\"Parent\"></param>\n        /// <param name=\"Property\"></param>\n        /// <returns></returns>\n        public static PropertyInfo GetPropertyInfoInternal(object Parent, string Property)\n        {\n            if (Property == \"this\" || Property == \"me\")\n                return null;\n\n            string propertyName = Property;\n\n            // Deal with Array Property - strip off array indexer\n            if (Property.IndexOf(\"[\") > -1)\n                propertyName = Property.Substring(0, Property.IndexOf(\"[\"));\n\n            // Get the member\n            return Parent.GetType().GetProperty(propertyName, ReflectionUtils.MemberAccess);\n        }\n\n        /// <summary>\n        /// Retrieve a field dynamically from an object. This is a simple implementation that's\n        /// straight Reflection and doesn't support indexers.\n        /// </summary>\n        /// <param name=\"Object\">Object to retreve Field from</param>\n        /// <param name=\"Property\">name of the field to retrieve</param>\n        /// <returns></returns>\n        public static object GetField(object Object, string Property)\n        {\n            return Object.GetType().GetField(Property, ReflectionUtils.MemberAccess | BindingFlags.GetField).GetValue(Object);\n        }\n\n\n        /// <summary>\n        /// Sets the property on an object. This is a simple method that uses straight Reflection \n        /// and doesn't support indexers.\n        /// </summary>\n        /// <param name=\"obj\">Object to set property on</param>\n        /// <param name=\"property\">Name of the property to set</param>\n        /// <param name=\"value\">value to set it to</param>\n        public static void SetProperty(object obj, string property, object value)\n        {\n            obj.GetType().GetProperty(property, ReflectionUtils.MemberAccess).SetValue(obj, value, null);\n        }\n\n        /// <summary>\n        /// Parses Properties and Fields including Array and Collection references.\n        /// </summary>\n        /// <param name=\"Parent\"></param>\n        /// <param name=\"Property\"></param>\n        /// <returns></returns>\n        private static object SetPropertyInternal(object Parent, string Property, object Value)\n        {\n            if (Property == \"this\" || Property == \"me\")\n                return Parent;\n\n            object Result = null;\n            string PureProperty = Property;\n            string Indexes = null;\n            bool IsArrayOrCollection = false;\n\n            // Deal with Array Property\n            if (Property.IndexOf(\"[\") > -1)\n            {\n                PureProperty = Property.Substring(0, Property.IndexOf(\"[\"));\n                Indexes = Property.Substring(Property.IndexOf(\"[\"));\n                IsArrayOrCollection = true;\n            }\n\n            if (!IsArrayOrCollection)\n            {\n                // Get the member\n                MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];\n                if (Member.MemberType == MemberTypes.Property)\n                {\n                    var prop = (PropertyInfo) Member;\n                    if (prop.CanWrite)\n                        prop.SetValue(Parent, Value, null);\n                }\n                else\n                    ((FieldInfo)Member).SetValue(Parent, Value);\n\n                return null;\n            }\n            else\n            {\n                // Get the member\n                MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];\n                if (Member.MemberType == MemberTypes.Property)\n                {\n                    var prop = (PropertyInfo) Member;\n                    if (prop.CanRead)\n                        Result = prop.GetValue(Parent, null);\n                }\n                else\n                    Result = ((FieldInfo)Member).GetValue(Parent);\n            }\n            if (IsArrayOrCollection)\n            {\n                Indexes = Indexes.Replace(\"[\", string.Empty).Replace(\"]\", string.Empty);\n\n                if (Result is Array)\n                {\n                    int Index = -1;\n                    int.TryParse(Indexes, out Index);\n                    Result = CallMethod(Result, \"SetValue\", Value, Index);\n                }\n                else if (Result is ICollection)\n                {\n                    if (Indexes.StartsWith(\"\\\"\"))\n                    {\n                        // String Index\n                        Indexes = Indexes.Trim('\\\"');\n                        Result = CallMethod(Result, \"set_Item\", Indexes, Value);\n                    }\n                    else\n                    {\n                        // assume numeric index\n                        int Index = -1;\n                        int.TryParse(Indexes, out Index);\n                        Result = CallMethod(Result, \"set_Item\", Index, Value);\n                    }\n                }\n            }\n\n            return Result;\n        }\n\n        /// <summary>\n        /// Sets the field on an object. This is a simple method that uses straight Reflection \n        /// and doesn't support indexers.\n        /// </summary>\n        /// <param name=\"obj\">Object to set property on</param>\n        /// <param name=\"property\">Name of the field to set</param>\n        /// <param name=\"value\">value to set it to</param>\n        public static void SetField(object obj, string property, object value)\n        {\n            obj.GetType().GetField(property, ReflectionUtils.MemberAccess).SetValue(obj, value);\n        }\n\n\n        /// <summary>\n        /// Returns a List of KeyValuePair object\n        /// </summary>\n        /// <param name=\"enumeration\"></param>\n        /// <returns></returns>\n        public static List<KeyValuePair<string, string>> GetEnumList(Type enumType, bool valueAsFieldValueNumber = false)\n        {\n            //string[] enumStrings = Enum.GetNames(enumType);\n            Array enumValues = Enum.GetValues(enumType);\n            List<KeyValuePair<string, string>> items = new List<KeyValuePair<string, string>>();\n\n            foreach (var enumValue in enumValues)\n            {\n                var strValue = enumValue.ToString();\n\n                if (!valueAsFieldValueNumber)\n                    items.Add(new KeyValuePair<string, string>(enumValue.ToString(), StringUtils.FromCamelCase(strValue)));\n                else\n                    items.Add(new KeyValuePair<string, string>(((int)enumValue).ToString(),\n                        StringUtils.FromCamelCase(strValue)\n                    ));\n            }\n            return items;\n        }\n\n        #endregion\n\n\n        #region EX processing for nested operations\n\n        /// <summary>\n        /// Calls a method on an object with extended . syntax (object: this Method: Entity.CalculateOrderTotal)\n        /// </summary>\n        /// <param name=\"parent\"></param>\n        /// <param name=\"method\"></param>\n        /// <param name=\"params\"></param>\n        /// <returns></returns>\n        public static object CallMethodEx(object parent, string method, params object[] parms)\n        {\n            Type Type = parent.GetType();\n\n            // no more .s - we got our final object\n            int lnAt = method.IndexOf(\".\");\n            if (lnAt < 0)\n            {\n                return ReflectionUtils.CallMethod(parent, method, parms);\n            }\n\n            // Walk the . syntax\n            string Main = method.Substring(0, lnAt);\n            string Subs = method.Substring(lnAt + 1);\n\n            object Sub = GetPropertyInternal(parent, Main);\n\n            // Recurse until we get the lowest ref\n            return CallMethodEx(Sub, Subs, parms);\n        }\n\n        /// <summary>\n        /// Returns a property or field value using a base object and sub members including . syntax.\n        /// For example, you can access: oCustomer.oData.Company with (this,\"oCustomer.oData.Company\")\n        /// This method also supports indexers in the Property value such as:\n        /// Customer.DataSet.Tables[\"Customers\"].Rows[0]\n        /// </summary>\n        /// <param name=\"Parent\">Parent object to 'start' parsing from. Typically this will be the Page.</param>\n        /// <param name=\"Property\">The property to retrieve. Example: 'Customer.Entity.Company'</param>\n        /// <returns></returns>\n        public static object GetPropertyEx(object Parent, string Property)\n        {\n            Type type = Parent.GetType();\n\n            int at = Property.IndexOf(\".\");\n            if (at < 0)\n            {\n                // Complex parse of the property    \n                return GetPropertyInternal(Parent, Property);\n            }\n\n            // Walk the . syntax - split into current object (Main) and further parsed objects (Subs)\n            string main = Property.Substring(0, at);\n            string subs = Property.Substring(at + 1);\n\n            // Retrieve the next . section of the property\n            object sub = GetPropertyInternal(Parent, main);\n\n            // Now go parse the left over sections\n            return GetPropertyEx(sub, subs);\n        }\n\n        /// <summary>\n        /// Returns a PropertyInfo object for a given dynamically accessed property\n        /// \n        /// Property selection can be specified using . syntax (\"Address.Street\" or \"DataTable[0].Rows[1]\") hence the 'Ex' name for this function.\n        /// </summary>\n        /// <param name=\"Parent\"></param>\n        /// <param name=\"Property\"></param>\n        /// <returns></returns>\n        public static PropertyInfo GetPropertyInfoEx(object Parent, string Property)\n        {\n            Type type = Parent.GetType();\n\n            int at = Property.IndexOf(\".\");\n            if (at < 0)\n            {\n                // Complex parse of the property    \n                return GetPropertyInfoInternal(Parent, Property);\n            }\n\n            // Walk the . syntax - split into current object (Main) and further parsed objects (Subs)\n            string main = Property.Substring(0, at);\n            string subs = Property.Substring(at + 1);\n\n            // Retrieve the next . section of the property\n            object sub = GetPropertyInternal(Parent, main);\n\n            // Now go parse the left over sections\n            return GetPropertyInfoEx(sub, subs);\n        }\n\n        /// <summary>\n        /// Sets a value on an object. Supports . syntax for named properties\n        /// (ie. Customer.Entity.Company) as well as indexers.\n        /// </summary>\n        /// <param name=\"Object Parent\">\n        /// Object to set the property on.\n        /// </param>\n        /// <param name=\"String Property\">\n        /// Property to set. Can be an object hierarchy with . syntax and can \n        /// include indexers. Examples: Customer.Entity.Company, \n        /// Customer.DataSet.Tables[\"Customers\"].Rows[0]\n        /// </param>\n        /// <param name=\"Object Value\">\n        /// Value to set the property to\n        /// </param>\n        public static object SetPropertyEx(object parent, string property, object value)\n        {\n            Type Type = parent.GetType();\n\n            // no more .s - we got our final object\n            int lnAt = property.IndexOf(\".\");\n            if (lnAt < 0)\n            {\n                SetPropertyInternal(parent, property, value);\n                return null;\n            }\n\n            // Walk the . syntax\n            string Main = property.Substring(0, lnAt);\n            string Subs = property.Substring(lnAt + 1);\n\n            object Sub = GetPropertyInternal(parent, Main);\n\n            SetPropertyEx(Sub, Subs, value);\n\n            return null;\n        }\n\n        #endregion\n\n        #region Static Methods and Properties\n        /// <summary>\n        /// Invokes a static method\n        /// </summary>\n        /// <param name=\"typeName\"></param>\n        /// <param name=\"method\"></param>\n        /// <param name=\"parms\"></param>\n        /// <returns></returns>\n        public static object CallStaticMethod(string typeName, string method, params object[] parms)\n        {\n\n            Type type = GetTypeFromName(typeName);\n            if (type == null)\n                throw new ArgumentException(\"Invalid type: \" + typeName);\n\n            try\n            {\n                return type.InvokeMember(method,\n                    BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod,\n                    null, type, parms);\n            }\n            catch (Exception ex)\n            {\n                if (ex.InnerException != null)\n                    throw ex.GetBaseException();\n\n                throw new ApplicationException(\"Failed to retrieve method signature or invoke method\");\n            }\n        }\n\n        /// <summary>\n        /// Retrieves a value from  a static property by specifying a type full name and property\n        /// </summary>\n        /// <param name=\"typeName\">Full type name (namespace.class)</param>\n        /// <param name=\"property\">Property to get value from</param>\n        /// <returns></returns>\n        public static object GetStaticProperty(string typeName, string property)\n        {\n            Type type = GetTypeFromName(typeName);\n            if (type == null)\n                return null;\n\n            return GetStaticProperty(type, property);\n        }\n\n        /// <summary>\n        /// Returns a static property from a given type\n        /// </summary>\n        /// <param name=\"type\">Type instance for the static property</param>\n        /// <param name=\"property\">Property name as a string</param>\n        /// <returns></returns>\n        public static object GetStaticProperty(Type type, string property)\n        {\n            object result = null;\n            try\n            {\n                result = type.InvokeMember(property, BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty, null, type, null);\n            }\n            catch\n            {\n                return null;\n            }\n\n            return result;\n        }\n\n        #endregion\n\n        #region COM Reflection Routines\n\n        /// <summary>\n        /// Retrieve a dynamic 'non-typelib' property\n        /// </summary>\n        /// <param name=\"instance\">Object to make the call on</param>\n        /// <param name=\"property\">Property to retrieve</param>\n        /// <returns></returns>\n        public static object GetPropertyCom(object instance, string property)\n        {\n                return instance.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null,\n                                                    instance, null);\n        }\n\n        /// <summary>\n        /// Returns a property or field value using a base object and sub members including . syntax.\n        /// For example, you can access: oCustomer.oData.Company with (this,\"oCustomer.oData.Company\")\n        /// </summary>\n        /// <param name=\"parent\">Parent object to 'start' parsing from.</param>\n        /// <param name=\"property\">The property to retrieve. Example: 'oBus.oData.Company'</param>\n        /// <returns></returns>\n        public static object GetPropertyExCom(object parent, string property)\n        {\n\n            Type Type = parent.GetType();\n\n            int lnAt = property.IndexOf(\".\");\n            if (lnAt < 0)\n            {\n                if (property == \"this\" || property == \"me\")\n                    return parent;\n\n                // Get the member\n                return parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,\n                    parent, null);\n            }\n\n            // Walk the . syntax - split into current object (Main) and further parsed objects (Subs)\n            string Main = property.Substring(0, lnAt);\n            string Subs = property.Substring(lnAt + 1);\n\n            object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,\n                parent, null);\n\n            // Recurse further into the sub-properties (Subs)\n            return ReflectionUtils.GetPropertyExCom(Sub, Subs);\n        }\n\n        /// <summary>\n        /// Sets the property on an object.\n        /// </summary>\n        /// <param name=\"inst\">Object to set property on</param>\n        /// <param name=\"Property\">Name of the property to set</param>\n        /// <param name=\"Value\">value to set it to</param>\n        public static void SetPropertyCom(object inst, string Property, object Value)\n        {\n            inst.GetType().InvokeMember(Property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty, null, inst, new object[1] { Value });\n        }\n\n        /// <summary>\n        /// Sets the value of a field or property via Reflection. This method alws \n        /// for using '.' syntax to specify objects multiple levels down.\n        /// \n        /// ReflectionUtils.SetPropertyEx(this,\"Invoice.LineItemsCount\",10)\n        /// \n        /// which would be equivalent of:\n        /// \n        /// Invoice.LineItemsCount = 10;\n        /// </summary>\n        /// <param name=\"Object Parent\">\n        /// Object to set the property on.\n        /// </param>\n        /// <param name=\"String Property\">\n        /// Property to set. Can be an object hierarchy with . syntax.\n        /// </param>\n        /// <param name=\"Object Value\">\n        /// Value to set the property to\n        /// </param>\n        public static object SetPropertyExCom(object parent, string property, object value)\n        {\n            Type Type = parent.GetType();\n\n            int lnAt = property.IndexOf(\".\");\n            if (lnAt < 0)\n            {\n                // Set the member\n                parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty , null,\n                    parent, new object[1] { value });\n\n                return null;\n            }\n\n            // Walk the . syntax - split into current object (Main) and further parsed objects (Subs)\n            string Main = property.Substring(0, lnAt);\n            string Subs = property.Substring(lnAt + 1);\n\n\n            object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null,\n                parent, null);\n\n            return SetPropertyExCom(Sub, Subs, value);\n        }\n\n\n        /// <summary>\n        /// Wrapper method to call a 'dynamic' (non-typelib) method\n        /// on a COM object\n        /// </summary>\n        /// <param name=\"params\"></param>\n        /// <param name=\"instance\"></param>\n        /// 1st - Method name, 2nd - 1st parameter, 3rd - 2nd parm etc.\n        /// <returns></returns>\n        public static object CallMethodCom(object instance, string method, params object[] parms)\n        {\n            return instance.GetType().InvokeMember(method, ReflectionUtils.MemberAccessCom | BindingFlags.InvokeMethod, null, instance, parms);\n        }\n\n        /// <summary>\n        /// Calls a method on a COM object with '.' syntax (Customer instance and Address.DoSomeThing method)\n        /// </summary>\n        /// <param name=\"parent\">the object instance on which to call method</param>\n        /// <param name=\"method\">The method or . syntax path to the method (Address.Parse)</param>\n        /// <param name=\"parms\">Any number of parameters</param>\n        /// <returns></returns>\n        public static object CallMethodExCom(object parent, string method, params object[] parms)\n        {\n            Type Type = parent.GetType();\n\n            // no more .s - we got our final object\n            int at = method.IndexOf(\".\");\n            if (at < 0)\n            {\n                return ReflectionUtils.CallMethodCom(parent, method, parms);\n            }\n\n            // Walk the . syntax - split into current object (Main) and further parsed objects (Subs)\n            string Main = method.Substring(0, at);\n            string Subs = method.Substring(at + 1);\n\n            object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null,\n                parent, null);\n\n            // Recurse until we get the lowest ref\n            return CallMethodExCom(Sub, Subs, parms);\n        }\n        #endregion\n\n\n        /// <summary>\n        /// Determines whether a given type passed is anonymous\n        /// </summary>\n        /// <param name=\"objectOrType\">Pass either an object instance or a Type object</param>\n        /// <returns></returns>\n        public static bool IsAnonoymousType(object objectOrType)\n        {\n            if (objectOrType == null)\n                return false;\n\n            if (objectOrType is Type type)\n            {\n                type = objectOrType as Type;\n            }\n            else\n            {\n                type = objectOrType.GetType();\n            }\n\n            if (!type.IsPublic &&\n                type.IsSealed &&\n                string.IsNullOrEmpty(type.Namespace))\n            {\n                return true;\n            }\n\n            return false;\n        }\n    }\n\n\n}\n\n\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/SecurityUtils.cs",
    "content": "#if NETFULL\n\n#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System.Runtime.InteropServices;\nusing System.Security.Principal;\nusing System;\n\nnamespace Westwind.Utilities\n{\n\n    /// <summary>\n    /// A set of utilities functions related to security.\n    /// </summary>\n    public static class SecurityUtils\n    {\n        const int LOGON32_LOGON_INTERACTIVE = 2;\n        const int LOGON32_LOGON_NETWORK = 3;\n        const int LOGON32_LOGON_BATCH = 4;\n        const int LOGON32_LOGON_SERVICE = 5;\n        const int LOGON32_LOGON_UNLOCK = 7;\n        const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;\n        const int LOGON32_LOGON_NEW_CREDENTIALS = 9;\n        const int LOGON32_PROVIDER_DEFAULT = 0;\n\n        [DllImport(\"advapi32.dll\", SetLastError = true)]\n        static extern int LogonUser(\n            string lpszUsername,\n            string lpszDomain,\n            string lpszPassword,\n            int dwLogonType,\n            int dwLogonProvider,\n            out IntPtr phToken\n            );\n\n        [DllImport(\"kernel32.dll\", SetLastError = true)]\n        static extern int CloseHandle(IntPtr hObject);\n\n        /// <summary>\n        /// Logs on a user and changes the current process impersonation to that user.\n        /// \n        /// IMPORTANT: Returns a WindowsImpersonationContext and you have to either\n        /// dispose this instance or call RevertImpersonation with it.\n        /// </summary>\n        /// <remarks>\n        /// Requires Full Trust permissions in ASP.NET Web Applications.\n        /// </remarks>\n        /// <param name=\"username\"></param>\n        /// <param name=\"password\"></param>\n        /// <param name=\"domain\"></param>\n        /// <returns>WindowsImpersonation Context. Call RevertImpersonation() to clear the impersonation or Dispose() instance.</returns>\n        public static WindowsImpersonationContext ImpersonateUser(string username, string password, string domain)\n        {\n            IntPtr token = IntPtr.Zero;\n            try\n            {\n                int TResult = LogonUser(username, domain, password,\n                                        LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT,\n                                        out token);\n\n                WindowsImpersonationContext context = null;\n                context = WindowsIdentity.Impersonate(token);\n                CloseHandle(token);\n\n                return context;\n            }\n            catch\n            {\n                return null;\n            }\n            finally\n            {\n                if (token != IntPtr.Zero)\n                    CloseHandle(token);\n            }\n        }\n\n        /// <summary>\n        /// Releases an impersonation context and releases associated resources\n        /// </summary>\n        /// <param name=\"context\">WindowsImpersonation context created with ImpersonateUser</param>\n        public static void RevertImpersonation(WindowsImpersonationContext context)\n        {\n            context.Undo();\n            context.Dispose();\n        }\n    }\n}\n#endif"
  },
  {
    "path": "Westwind.Utilities/Utilities/SerializationUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2008 - 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.IO;\nusing System.Text;\nusing System.Reflection;\n\nusing System.Xml;\nusing System.Xml.Serialization;\nusing System.Runtime.Serialization.Formatters.Binary;\nusing System.Diagnostics;\nusing System.Runtime.Serialization;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities\n{\n\n    // Serialization specific code\n\n    public static class SerializationUtils\n    {\n\n        /// <summary>\n        /// Serializes an object instance to a file.\n        /// </summary>\n        /// <param name=\"instance\">the object instance to serialize</param>\n        /// <param name=\"fileName\"></param>\n        /// <param name=\"binarySerialization\">determines whether XML serialization or binary serialization is used</param>\n        /// <returns></returns>\n        public static bool SerializeObject(object instance, string fileName, bool binarySerialization)\n        {\n            bool retVal = true;\n\n            if (!binarySerialization)\n            {\n                XmlTextWriter writer = null;\n                try\n                {\n                    XmlSerializer serializer =\n                        new XmlSerializer(instance.GetType());\n\n                    // Create an XmlTextWriter using a FileStream.\n                    Stream fs = new FileStream(fileName, FileMode.Create);\n                    writer = new XmlTextWriter(fs, new UTF8Encoding());\n                    writer.Formatting = Formatting.Indented;\n                    writer.IndentChar = ' ';\n                    writer.Indentation = 3;\n\n                    // Serialize using the XmlTextWriter.\n                    serializer.Serialize(writer, instance);\n                }\n                catch(Exception ex)\n                {\n                    Debug.Write(\"SerializeObject failed with : \" + ex.Message, \"West Wind\");\n                    retVal = false;\n                }\n                finally\n                {\n                    if (writer != null)\n                        writer.Close();\n                }\n            }\n            else\n            {\n#if NETFULL\n                Stream fs = null;\n                try\n                {\n                    BinaryFormatter serializer = new BinaryFormatter();\n                    fs = new FileStream(fileName, FileMode.Create);\n                    serializer.Serialize(fs, instance);\n                }\n                catch\n                {\n                    retVal = false;\n                }\n                finally\n                {\n                    if (fs != null)\n                        fs.Close();\n                }\n#else\n                throw new NotSupportedException( Resources.BinaryXmlSerializationNotSupported);\n#endif\n            }\n\n            return retVal;\n        }\n\n        /// <summary>\n        /// Overload that supports passing in an XML TextWriter. \n        /// </summary>\n        /// <remarks>\n        /// Note the Writer is not closed when serialization is complete \n        /// so the caller needs to handle closing.\n        /// </remarks>\n        /// <param name=\"instance\">object to serialize</param>\n        /// <param name=\"writer\">XmlTextWriter instance to write output to</param>       \n        /// <param name=\"throwExceptions\">Determines whether false is returned on failure or an exception is thrown</param>\n        /// <returns></returns>\n        public static bool SerializeObject(object instance, XmlTextWriter writer, bool throwExceptions)\n        {\n            bool retVal = true;\n\n            try\n            {\n                XmlSerializer serializer =\n                    new XmlSerializer(instance.GetType());\n\n                // Create an XmlTextWriter using a FileStream.\n                writer.Formatting = Formatting.Indented;\n                writer.IndentChar = ' ';\n                writer.Indentation = 3;\n\n                // Serialize using the XmlTextWriter.\n                serializer.Serialize(writer, instance);\n            }\n            catch (Exception ex)\n            {            \n                Debug.Write(\"SerializeObject failed with : \" + ex.GetBaseException().Message + \"\\r\\n\" + (ex.InnerException != null ? ex.InnerException.Message : \"\"), \"West Wind\");\n\n                if (throwExceptions)\n                    throw;\n\n                retVal = false;\n            }\n\n            return retVal;\n        }\n\n        \n        /// <summary>\n        /// Serializes an object into an XML string variable for easy 'manual' serialization\n        /// </summary>\n        /// <param name=\"instance\">object to serialize</param>\n        /// <param name=\"xmlResultString\">resulting XML string passed as an out parameter</param>\n        /// <returns>true or false</returns>\n        public static bool SerializeObject(object instance, out string xmlResultString)\n        {        \n            return SerializeObject(instance, out xmlResultString, false);\n        }       \n\n        /// <summary>\n        /// Serializes an object into a string variable for easy 'manual' serialization\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        /// <param name=\"xmlResultString\">Out parm that holds resulting XML string</param>\n        /// <param name=\"throwExceptions\">If true causes exceptions rather than returning false</param>\n        /// <returns></returns>\n        public static bool SerializeObject(object instance, out string xmlResultString, bool throwExceptions)\n        {            \n            xmlResultString = string.Empty;\n            MemoryStream ms = new MemoryStream();\n\n            XmlTextWriter writer = new XmlTextWriter(ms, new UTF8Encoding());\n\n            if (!SerializeObject(instance, writer,throwExceptions))\n            {\n                ms.Close();\n                return false;\n            }\n            \n            xmlResultString = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);\n\n            ms.Close();\n            writer.Close();\n\n            return true;\n        }\n\n#if NETFULL\n        /// <summary>\n        /// Serializes an object instance to a file.\n        /// </summary>\n        /// <param name=\"instance\">the object instance to serialize</param>\n        /// <param name=\"Filename\"></param>\n        /// <returns></returns>\n        public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false)\n        {\n            bool retVal = true;\n\n            using (var ms = new MemoryStream())\n            {\n                try\n                {\n                    BinaryFormatter serializer = new BinaryFormatter();\n                    serializer.Serialize(ms, instance);\n                }\n                catch (Exception ex)\n                {\n                    Debug.Write(\"SerializeObject failed with : \" + ex.GetBaseException().Message, \"West Wind\");\n                    retVal = false;\n\n                    if (throwExceptions)\n                        throw;\n                }\n             \n                ms.Position = 0;\n                resultBuffer = ms.ToArray();\n\n                return retVal;\n            }\n        }\n#endif\n\n        /// <summary>\n        /// Serializes an object to an XML string. Unlike the other SerializeObject overloads\n        /// this methods *returns a string* rather than a bool result!\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        /// <param name=\"throwExceptions\">Determines if a failure throws or returns null</param>\n        /// <returns>\n        /// null on error otherwise the Xml String.         \n        /// </returns>\n        /// <remarks>\n        /// If null is passed in null is also returned so you might want\n        /// to check for null before calling this method.\n        /// </remarks>\n        public static string SerializeObjectToString(object instance, bool throwExceptions = false)\n        {\n            string xmlResultString = string.Empty;\n\n            if (!SerializeObject(instance, out xmlResultString, throwExceptions))\n                return null;\n\n            return xmlResultString;\n        }\n\n#if NETFULL\n        public static byte[] SerializeObjectToByteArray(object instance, bool throwExceptions = false)\n        {\n            byte[] byteResult = null;\n\n            if ( !SerializeObject(instance, out byteResult) )\n                return null;\n                        \n            return byteResult;\n        }\n#endif\n\n\n                /// <summary>\n        /// Deserializes an object from file and returns a reference.\n        /// </summary>\n        /// <param name=\"fileName\">name of the file to serialize to</param>\n        /// <param name=\"objectType\">The Type of the object. Use typeof(yourobject class)</param>\n        /// <param name=\"binarySerialization\">determines whether we use Xml or Binary serialization</param>\n        /// <returns>Instance of the deserialized object or null. Must be cast to your object type</returns>\n        public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization)\n        {\n            return DeSerializeObject(fileName, objectType, binarySerialization, false);\n        }\n\n        /// <summary>\n        /// Deserializes an object from file and returns a reference.\n        /// </summary>\n        /// <param name=\"fileName\">name of the file to serialize to</param>\n        /// <param name=\"objectType\">The Type of the object. Use typeof(yourobject class)</param>\n        /// <param name=\"binarySerialization\">determines whether we use Xml or Binary serialization</param>\n        /// <param name=\"throwExceptions\">determines whether failure will throw rather than return null on failure</param>\n        /// <returns>Instance of the deserialized object or null. Must be cast to your object type</returns>\n        public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization, bool throwExceptions)\n        {\n            object instance = null;\n\n            if (!binarySerialization)\n            {\n\n                XmlReader reader = null;\n                XmlSerializer serializer = null;\n                FileStream fs = null;\n                try\n                {\n                    // Create an instance of the XmlSerializer specifying type and namespace.\n                    serializer = new XmlSerializer(objectType);\n\n                    // A FileStream is needed to read the XML document.\n                    fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);\n                    reader = new XmlTextReader(fs);\n\n                    instance = serializer.Deserialize(reader);\n                }\n                catch(Exception ex)\n                {\n                    if (throwExceptions)\n                        throw;\n\n                    string message = ex.Message;\n                    return null;\n                }\n                finally\n                {\n                    if (fs != null)\n                        fs.Close();\n\n                    if (reader != null)\n                        reader.Close();\n                }\n            }\n            else\n            {\n#if NETFULL\n                BinaryFormatter serializer = null;\n                FileStream fs = null;\n\n                try\n                {\n                    serializer = new BinaryFormatter();\n                    fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);\n                    instance = serializer.Deserialize(fs);\n                }\n                catch\n                {\n                    return null;\n                }\n                finally\n                {\n                    if (fs != null)\n                        fs.Close();\n                }\n#else\n                throw new NotSupportedException(Resources.BinaryXmlSerializationNotSupported);\n#endif\n            }\n\n            return instance;\n        }\n\n        /// <summary>\n        /// Deserialize an object from an XmlReader object.\n        /// </summary>\n        /// <param name=\"reader\"></param>\n        /// <param name=\"objectType\"></param>\n        /// <returns></returns>\n        public static object DeSerializeObject(XmlReader reader, Type objectType)\n        {\n            XmlSerializer serializer = new XmlSerializer(objectType);\n            object Instance = serializer.Deserialize(reader);\n            reader.Close();\n\n            return Instance;\n        }\n\n        public static object DeSerializeObject(string xml, Type objectType)\n        {\n            XmlTextReader reader = new XmlTextReader(xml, XmlNodeType.Document, null);\n            return DeSerializeObject(reader, objectType);\n        }\n\n#if NETFULL\n        /// <summary>\n        /// Deseializes a binary serialized object from  a byte array\n        /// </summary>\n        /// <param name=\"buffer\"></param>\n        /// <param name=\"objectType\"></param>\n        /// <param name=\"throwExceptions\"></param>\n        /// <returns></returns>\n        public static object DeSerializeObject(byte[] buffer, Type objectType, bool throwExceptions = false)\n        {\n            BinaryFormatter serializer = null;\n            MemoryStream ms = null;\n            object Instance = null;\n\n            try\n            {\n                serializer = new BinaryFormatter();\n                ms = new MemoryStream(buffer);\n                Instance = serializer.Deserialize(ms);\n\n            }\n            catch\n            {\n                if (throwExceptions)\n                    throw;\n\n                return null;\n            }\n            finally\n            {\n                if (ms != null)\n                    ms.Close();\n            }\n\n            return Instance;\n        }\n#endif\n\n        /// <summary>\n        /// Returns a string of all the field value pairs of a given object.\n        /// Works only on non-statics.\n        /// </summary>\n        /// <param name=\"instanc\"></param>\n        /// <param name=\"separator\"></param>\n        /// <returns></returns>\n        public static string ObjectToString(object instanc, string separator, ObjectToStringTypes type)\n        {\n            FieldInfo[] fi = instanc.GetType().GetFields();\n\n            string output = string.Empty;\n\n            if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields)\n            {\n                foreach (PropertyInfo property in instanc.GetType().GetProperties())\n                {\n                    try\n                    {\n                        output += property.Name + \":\" + property.GetValue(instanc, null).ToString() + separator;\n                    }\n                    catch\n                    {\n                        output += property.Name + \": n/a\" + separator;\n                    }\n                }\n            }\n\n            if (type == ObjectToStringTypes.Fields || type == ObjectToStringTypes.PropertiesAndFields)\n            {\n                foreach (FieldInfo field in fi)\n                {\n                    try\n                    {\n                        output = output + field.Name + \": \" + field.GetValue(instanc).ToString() + separator;\n                    }\n                    catch\n                    {\n                        output = output + field.Name + \": n/a\" + separator;\n                    }\n                }\n            }\n            return output;\n        }\n\n    }\n\n    public enum ObjectToStringTypes\n    {\n        Properties,\n        PropertiesAndFields,\n        Fields\n    }\n}\n\n\n\n\n\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/ShellUtils.cs",
    "content": "﻿#pragma warning disable SYSLIB0014\n\n#region \n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2011\n *          http://www.west-wind.com/\n * \n * Created: 6/19/2011\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\n\nusing System;\nusing System.Text;\nusing System.IO;\nusing System.Net;\nusing System.Diagnostics;\nusing System.Runtime.InteropServices;\n\nnamespace Westwind.Utilities\n{\n\n    /// <summary>\n    /// Windows specific shell utility functions \n    /// </summary>\n    public static class ShellUtils\n    {\n\n        #region Open in or Start Process\n\n        /// <summary>\n        /// Executes a Windows process with given command line parameters\n        /// </summary>\n        /// <param name=\"executable\">Executable to run</param>\n        /// <param name=\"arguments\">Command Line Parameters passed to executable</param>\n        /// <param name=\"timeoutMs\">Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.</param>\n        /// <param name=\"windowStyle\">Hidden, Normal etc.</param>\n        /// <returns>process exit code or 0 if run and forget. 1460 for time out. -1 on error</returns>\n        public static int ExecuteProcess(string executable, \n                                        string arguments = null, \n                                        int timeoutMs = 0, \n                                        ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal)\n        {\n            Process process;\n\n            try\n            {\n                using (process = new Process())\n                {\n                    process.StartInfo.FileName = executable;\n                    process.StartInfo.Arguments = arguments;\n                    process.StartInfo.WindowStyle = windowStyle;\n                    if (windowStyle == ProcessWindowStyle.Hidden)\n                        process.StartInfo.CreateNoWindow = true;\n\n                    process.StartInfo.UseShellExecute = false;\n                    process.Start();\n\n                    if (timeoutMs < 0)\n                        timeoutMs = 99999999; // indefinitely\n\n                    if (timeoutMs > 0)\n                    {\n                        if (!process.WaitForExit(timeoutMs))\n                        {\n                            Console.WriteLine(\"Process timed out.\");\n                            return 1460;\n                        }\n                    }\n                    else // run and don't wait - no exit code\n                        return 0;\n\n                    return process.ExitCode;\n                }\n            }\n            catch (Exception ex)\n            {\n                Console.WriteLine(\"Error executing process: \" + ex.Message);\n                return -1; // unhandled error\n            }\n        }\n\n        /// <summary>\n        /// Executes a Windows process with given command line parameters\n        /// and captures console output into a string.\n        ///\n        /// Writes command output to the output StringBuilder\n        /// from StdOut and StdError.\n        /// </summary>\n        /// <param name=\"executable\">Executable to run</param>\n        /// <param name=\"arguments\">Command Line Parameters passed to executable</param>\n        /// <param name=\"timeoutMs\">Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.</param>\n        /// <param name=\"output\">Pass in a string reference that will receive StdOut and StdError output</param>\n        /// <param name=\"windowStyle\">Hidden, Normal, etc.</param>\n        /// <returns>process exit code or 0 if run and forget. 1460 for time out. -1 on error</returns>\n        public static int ExecuteProcess(string executable,\n            string arguments,\n            int timeoutMs,\n            out StringBuilder output,\n            ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,\n            Action<bool> completionDelegate = null)\n        {\n            return ExecuteProcess(executable, arguments, timeoutMs, out output, null, windowStyle, completionDelegate);\n        }\n\n\n        /// <summary>\n        /// Executes a Windows process with given command line parameters\n        /// and captures console output into a string.\n        /// \n        /// Pass in a String Action that receives output from\n        /// StdOut and StdError as it is written (one line at a time).\n        /// </summary>\n        /// <param name=\"executable\">Executable to run</param>\n        /// <param name=\"arguments\">Command Line Parameters passed to executable</param>\n        /// <param name=\"timeoutMs\">Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.</param>\n        /// <param name=\"writeDelegate\">Delegate to let you capture streaming output of the executable to stdout and stderror.</param>\n        /// <param name=\"windowStyle\">Hidden, Normal etc.</param>\n        /// <param name=\"completionDelegate\">delegate that is called when execute completes. Passed true if success or false if timeout or failed</param>\n        /// <returns>process exit code or 0 if run and forget. 1460 for time out. -1 on error</returns>\n        public static int ExecuteProcess(string executable,\n            string arguments,\n            int timeoutMs,\n            Action<string> writeDelegate = null,\n            ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,\n            Action<bool> completionDelegate = null)\n        {\n            return ExecuteProcess(executable, arguments, timeoutMs, out StringBuilder output, writeDelegate, windowStyle, completionDelegate);\n        }\n\n        private static Process process = null;\n\n        /// <summary>\n        /// Executes a Windows process with given command line parameters\n        /// and captures console output into a string.\n        ///\n        /// Writes original output into the application Console which you can\n        /// optionally redirect to capture output from the command line\n        /// operation using `Console.SetOut` or `Console.SetError`.\n        /// </summary>\n        /// <param name=\"executable\">Executable to run</param>\n        /// <param name=\"arguments\"></param>\n        /// <param name=\"timeoutMs\">Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait.</param>\n        /// <param name=\"output\">StringBuilder that will receive StdOut and StdError output</param>\n        /// <param name=\"writeDelegate\">Action to capture stdout and stderror output for you to handle</param>\n        /// <param name=\"windowStyle\"></param>\n        /// <param name=\"completionDelegate\">If waiting for completion you can be notified when the exection is complete</param>\n        /// <returns>process exit code or 0 if run and forget. 1460 for time out. -1 on error</returns>\n        private static int ExecuteProcess(string executable,\n                                        string arguments,\n                                        int timeoutMs,\n                                        out StringBuilder output,\n                                        Action<string> writeDelegate = null,\n                                        ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden,\n                                        Action<bool> completionDelegate = null)\n        {\n            \n\n            try\n            {\n                using (process = new Process())\n                {\n                    process.StartInfo.FileName = executable;\n                    process.StartInfo.Arguments = arguments;\n                    process.StartInfo.WindowStyle = windowStyle;\n                    if (windowStyle == ProcessWindowStyle.Hidden)\n                        process.StartInfo.CreateNoWindow = true;\n\n                    process.StartInfo.UseShellExecute = false;\n\n                    process.StartInfo.RedirectStandardOutput = true;\n                    process.StartInfo.RedirectStandardError = true;\n\n                    var sb  = new StringBuilder();\n\n                    process.OutputDataReceived += (sender, args) =>\n                    {\n                        if (writeDelegate != null)\n                            writeDelegate.Invoke(args.Data);\n                        else\n                            sb.AppendLine(args.Data);\n                    };\n                    process.ErrorDataReceived += (sender, args) =>\n                    {\n                        if (writeDelegate != null)\n                            writeDelegate.Invoke(args.Data);\n                        else\n                            sb.AppendLine(args.Data);\n                    };\n\n                    if (completionDelegate != null)\n                    {\n                        process.Exited += (sender, args) =>\n                        {\n                            var proc = sender as Process;\n                            completionDelegate?.Invoke(proc.ExitCode == 0);\n                        };\n                    }\n\n                    process.Start();\n\n                    process.BeginErrorReadLine();\n                    process.BeginOutputReadLine();\n                    \n                    if (timeoutMs < 0)\n                        timeoutMs = 99999999; // indefinitely\n\n                    if (timeoutMs > 0)\n                    {                        \n                 \n                        if (!process.WaitForExit(timeoutMs))\n                        {\n                            Console.WriteLine(\"Process timed out.\");\n                            output = null;\n                            completionDelegate?.Invoke(false);\n                            return 1460;\n                        }\n                        completionDelegate?.Invoke(true);\n                    }\n                    else\n                    {\n                        // no exit code\n                        output = sb;\n                        return 0;\n                    }\n\n                    output = sb;\n                    return process.ExitCode;\n                }\n            }\n            catch (Exception ex)\n            {\n                Console.WriteLine($\"Error executing process: {ex.Message}\");\n                output = null;\n                return -1; // unhandled error\n            }\n        }\n\n        #endregion\n\n        #region Shell Execute Apis, URL Openening\n\n        /// <summary>\n        /// Uses the Shell Extensions to launch a program based on URL moniker or file name\n        /// Basically a wrapper around ShellExecute\n        /// </summary>\n        /// <param name=\"url\">Any URL Moniker that the Windows Shell understands (URL, Word Docs, PDF, Email links etc.)</param>\n        /// <returns></returns>\n        public static bool GoUrl(string url, string workingFolder = null)\n        {\n            if (string.IsNullOrEmpty(workingFolder))\n                return OpenUrl(url);\n\n            string TPath = Path.GetTempPath();\n\n            ProcessStartInfo info = new ProcessStartInfo();\n            info.UseShellExecute = true;\n            info.Verb = \"Open\";\n            info.WorkingDirectory = TPath;\n            info.FileName = url;\n\n            bool result;\n\n            using (Process process = new Process())\n            {\n                process.StartInfo = info;\n\n                try\n                {\n                    result = process.Start();\n                }\n                catch\n                {\n                    return false;\n                }\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Opens a URL in the browser. This version is specific to opening\n        /// a URL in a browser and it's cross platform enabled.\n        /// </summary>\n        /// <param name=\"url\"></param>\n        public static bool OpenUrl(string url)\n        {\n            bool success = true;\n\n#if NET6_0_OR_GREATER\n            bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.OSDescription.Contains(\"microsoft-standard\");\n#else\n            bool isWindows = true;\n#endif\n            Process p = null;\n            try\n            {\n                var psi = new ProcessStartInfo(url);\n                psi.UseShellExecute = isWindows;   // must be explicit -defaults changed in NETFX & NETCORE\n                p = Process.Start(psi);\n            }\n            catch\n            {\n#if NET6_0_OR_GREATER\n                // hack because of this: https://github.com/dotnet/corefx/issues/10361\n                if (isWindows)\n                {\n                    url = url.Replace(\"&\", \"^&\");\n                    try\n                    {\n                        Process.Start(new ProcessStartInfo(\"cmd.exe\", $\"/c start {url}\") {CreateNoWindow = true});\n                    }\n                    catch\n                    {\n                        success = false;\n                    }\n                }\n                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))\n                {\n                    p = Process.Start(\"xdg-open\", url);\n                }\n                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))\n                {\n                    p = Process.Start(\"open\", url);\n                }\n                else\n                {\n                    success = false;\n                }\n#else\n                success = false;\n#endif\n            }\n\n            p?.Dispose();\n\n            return success;\n        }\n\n\n\n        /// <summary>\n        /// Wrapper around the Shell Execute API. Windows specific.\n        /// </summary>\n        /// <param name=\"url\"></param>\n        /// <param name=\"arguments\"></param>\n        /// <param name=\"workingFolder\"></param>\n        /// <param name=\"verb\"></param>\n        /// <returns></returns>\n        public static int ShellExecute(string url, string arguments = null,\n            string workingFolder = null, string verb = \"open\")\n        {\n            ProcessStartInfo info = new ProcessStartInfo();\n            info.UseShellExecute = true;\n            info.Verb = verb;\n\n            info.FileName = url;\n            info.Arguments = arguments;\n            info.WorkingDirectory = workingFolder;\n\n            using (Process process = new Process())\n            {\n                process.StartInfo = info;\n                process.Start();\n            }\n\n            return 0;\n        }\n\n        /// <summary>\n        /// Opens a File or Folder in Explorer. If the path is a file\n        /// Explorer is opened in the parent folder with the file selected\n        /// </summary>\n        /// <param name=\"filename\"></param>\n        public static bool OpenFileInExplorer(string filename)\n        {\n            if (string.IsNullOrEmpty(filename))\n                return false;\n\n            // Is it a directory? Just open\n            if (Directory.Exists(filename))\n                ShellExecute(filename);\n            else\n            {\n                // required as command Explorer command line doesn't allow mixed slashes\n                filename = FileUtils.NormalizePath(filename); \n\n                if (!File.Exists(filename))\n                    filename = Path.GetDirectoryName(filename);\n\n                try\n                {\n                    Process.Start(\"explorer.exe\", $\"/select,\\\"{filename}\\\"\");\n                }\n                catch\n                {   \n                    return false;\n                }\n            }\n            return true;\n        }\n\n        /// <summary>\n        /// Opens a Terminal window in the specified folder\n        /// </summary>\n        /// <param name=\"folder\"></param>\n        /// <param  name=\"mode\">Powershell, Command or Bash</param>\n        /// <returns>false if process couldn't be started - most likely invalid link</returns>\n        public static bool OpenTerminal(string folder, TerminalModes mode = TerminalModes.Powershell)\n        {\n            try\n            {\n                string cmd = null, args = null;\n\n                if (mode == TerminalModes.Powershell)\n                {\n                    cmd = \"powershell.exe\";\n                    args = \"-noexit -command \\\"cd '{0}'\\\"\";\n                }\n                else if(mode == TerminalModes.Command)\n                {\n                    cmd = \"cmd.exe\";\n                    args = \"/k \\\"cd {0}\\\"\";\n                }\n                \n                Process.Start(cmd,string.Format(args, folder));\n            }\n            catch\n            {\n                return false;\n            }\n            return true;\n        }\n\n\n        /// <summary>\n        /// Executes a Windows Command Line using Shell Execute as a\n        /// single command line with parameters. This method handles\n        /// parsing out the executable from the parameters.\n        /// </summary>\n        /// <param name=\"fullCommandLine\">Full command line - Executable plus arguments.\n        /// If the executable contains paths with spaces **make sure to add quotes around the executable** otherwise the executable may not be found.\n        /// </param>\n        /// <param name=\"workingFolder\">Optional - the folder the executable runs in. If not specified uses current folder.</param>\n        /// <param name=\"waitForExitMs\">Optional - Number of milliseconds to wait for completion. 0 don't wait.</param>\n        /// <param name=\"verb\">Optional - Shell verb to apply. Defaults to \"Open\"</param>\n        /// <param name=\"windowStyle\">Optional - Windows style for the launched application. Default style is normal</param>\n        /// <remarks>\n        /// If the executable or parameters contain or **may contain spaces** make sure you use quotes (\") or (') around the exec or parameters.\n        ///\n        /// throws if the process fails to start or doesn't complete in time (if timeout is specified).\n        /// </remarks>\n        public static void ExecuteCommandLine(string fullCommandLine,\n            string workingFolder = null,\n            int waitForExitMs = 0,\n            string verb = \"OPEN\",\n            ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal,\n            bool useShellExecute = true)\n        {\n            string executable = fullCommandLine;\n            string args = null;\n\n            if (executable.StartsWith(\"\\\"\"))\n            {\n                int at = executable.IndexOf(\"\\\" \");\n                if (at > 0)\n                {\n                    // take the args as provided\n                    args = executable.Substring(at + 1);\n                    // plain executable\n                    executable = executable.Substring(0, at).Trim(' ', '\\\"');\n                }\n            }\n            else if (executable.StartsWith(\"\\'\"))\n            {\n                int at = executable.IndexOf(\"\\' \");\n                if (at > 0)\n                {\n                    // take the args as provided\n                    args = executable.Substring(at + 1);\n                    // plain executable\n                    executable = executable.Substring(0, at).Trim(' ', '\\'');\n                }\n            }\n            else\n            {\n                int at = executable.IndexOf(\" \");\n                if (at > 0)\n                {\n\n                    if (executable.Length > at + 1)\n                        args = executable.Substring(at + 1).Trim();\n                    executable = executable.Substring(0, at);\n                }\n            }\n            var pi = new ProcessStartInfo\n            {\n                Verb = verb,\n                WindowStyle = windowStyle,\n                FileName = executable,\n                WorkingDirectory = workingFolder,\n                Arguments = args,\n                UseShellExecute = true\n            };\n\n            Process p;\n            using (p = Process.Start(pi))\n            {\n                if (waitForExitMs > 0)\n                {\n                    if (!p.WaitForExit(waitForExitMs))\n                        throw new TimeoutException(\"Process failed to complete in time.\");\n                }\n            }\n        }\n\n        /// <summary>\n        /// Displays a string in in a browser as HTML. Optionally\n        /// provide an alternate extension to display in the appropriate\n        /// text viewer (ie. \"txt\" likely shows in NotePad)\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"extension\"></param>\n        /// <returns></returns>\n        public static bool ShowString(string text, string extension = null)\n        {\n            if (extension == null)\n                extension = \"htm\";\n\n            string File = Path.GetTempPath() + \"\\\\__preview.\" + extension;\n            StreamWriter sw = new StreamWriter(File, false, Encoding.Default);\n            sw.Write(text);\n            sw.Close();\n\n            return GoUrl(File);\n        }\n\n        /// <summary>\n        /// Shows a string as HTML\n        /// </summary>\n        /// <param name=\"htmlString\"></param>\n        /// <returns></returns>\n        public static bool ShowHtml(string htmlString)\n        {\n            return ShowString(htmlString, null);\n        }\n\n        /// <summary>\n        /// Displays a large Text string as a text file in the\n        /// systems' default text viewer (ie. NotePad)\n        /// </summary>\n        /// <param name=\"TextString\"></param>\n        /// <returns></returns>\n        public static bool ShowText(string TextString)\n        {\n            string File = Path.GetTempPath() + \"\\\\__preview.txt\";\n\n            StreamWriter sw = new StreamWriter(File, false);\n            sw.Write(TextString);\n            sw.Close();\n\n            return GoUrl(File);\n        }\n#endregion\n\n#region Simple HTTP Helpers\n        /// <summary>\n        /// Simple method to retrieve HTTP content from the Web quickly\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>        \n        /// <returns>Http response text or null</returns>\n        public static string HttpGet(string url)\n        {\n            string errorMessage;\n            return HttpGet(url, out errorMessage);\n        }\n\n        /// <summary>\n        /// Simple method to retrieve HTTP content from the Web quickly\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>\n        /// <param name=\"errorMessage\"></param>\n        /// <returns></returns>\n        public static string HttpGet(string url, out string errorMessage)\n        {\n            string responseText = string.Empty;\n            errorMessage = null;\n\n\n            using (WebClient Http = new WebClient())\n            {\n                try\n                {\n                    responseText = Http.DownloadString(url);                \n                }\n                catch (Exception ex)\n                {\n                    errorMessage = ex.Message;\n                    return null;\n                }\n            }\n\n            return responseText;\n        }\n\n\n        /// <summary>\n        /// Retrieves a buffer of binary data from a URL using\n        /// a plain HTTP Get.\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>\n        /// <returns>Response bytes or null on error</returns>\n        public static byte[] HttpGetBytes(string url)\n        {\n            string errorMessage;\n            return HttpGetBytes(url,out errorMessage);\n        }\n\n        /// <summary>\n        /// Retrieves a buffer of binary data from a URL using\n        /// a plain HTTP Get.\n        /// </summary>\n        /// <param name=\"url\">Url to access</param>\n        /// <param name=\"errorMessage\">ref parm to receive an error message</param>\n        /// <returns>response bytes or null on error</returns>\n        public static byte[] HttpGetBytes(string url, out string errorMessage)\n        {\n            byte[] result = null;\n            errorMessage = null;\n\n            using (var http = new WebClient())\n            {\n                try\n                {\n                    result = http.DownloadData(url);\n                }\n                catch (Exception ex)\n                {\n                    errorMessage = ex.Message;\n                    return null;\n                }\n            }\n\n            return result;\n        }\n#endregion\n    }\n\n    public enum TerminalModes\n    {\n        Powershell,\n        Command,\n        Bash\n    }\n}\n#pragma warning restore SYSLIB0014"
  },
  {
    "path": "Westwind.Utilities/Utilities/StringUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          (c) West Wind Technologies, 2008 - 2024\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Web;\nusing System.Globalization;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing Westwind.Utilities.Properties;\nusing static System.Net.Mime.MediaTypeNames;\nusing static System.Net.WebRequestMethods;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// String utility class that provides a host of string related operations\n    /// </summary>\n    public static class StringUtils\n    {\n        #region Basic String Tasks\n\n        /// <summary>\n        /// Trims the beginning of a string by a matching string.\n        /// \n        /// Overrides string behavior which only works with char.\n        /// </summary>\n        /// <param name=\"text\">Text to trim</param>\n        /// <param name=\"textToTrim\">Text to trim with</param>\n        /// <param name=\"caseInsensitive\">If true ignore case</param>\n        /// <returns>Trimmed string if match is found</returns>        \n        public static string TrimStart(this string text, string textToTrim, bool caseInsensitive = false)\n        {\n            if (string.IsNullOrEmpty(text) ||\n                string.IsNullOrEmpty(textToTrim) ||\n                text.Length < textToTrim.Length)\n                return text;\n\n            StringComparison comparison = caseInsensitive\n                ? StringComparison.OrdinalIgnoreCase\n                : StringComparison.Ordinal;\n\n            // account for multiple instances of the text to trim\n            while (text.Length >= textToTrim.Length &&\n                   text.Substring(0, textToTrim.Length).Equals(textToTrim, comparison))\n            {\n                text = text.Substring(textToTrim.Length);\n            }\n\n            return text;\n        }\n\n        /// <summary>\n        /// Trims the end of a string  with a matching string\n        /// </summary>\n        /// <param name=\"text\">Text to trim</param>\n        /// <param name=\"textToTrim\">Text to trim with</param>\n        /// <param name=\"caseInsensitive\">If true ignore case</param>\n        /// <returns>Trimmed string if match is found</returns>   \n        public static string TrimEnd(this string text, string textToTrim, bool caseInsensitive = false)\n        {\n            if (string.IsNullOrEmpty(text) || \n                string.IsNullOrEmpty(textToTrim) ||\n                text.Length < textToTrim.Length)\n                return text;\n\n            while (true)\n            {\n                var idx = text.LastIndexOf(textToTrim);\n                if (idx == -1)\n                    return text;\n\n                string match = text.Substring(text.Length - textToTrim.Length, textToTrim.Length);\n\n                if (match == textToTrim ||\n                    (!caseInsensitive && match.Equals(textToTrim, StringComparison.OrdinalIgnoreCase)))\n                {\n                    if (text.Length <= match.Length)\n                        text = \"\";\n                    else\n                        text = text.Substring(0, idx);\n                }\n                else\n                    break;\n            }\n            return text;\n        }\n\n\n        /// <summary>\n        /// Trims a string to a specific number of max characters\n        /// </summary>\n        /// <param name=\"value\"></param>\n        /// <param name=\"charCount\"></param>\n        /// <returns></returns>\n        [Obsolete(\"Please use the StringUtils.Truncate() method instead.\")]\n        public static string TrimTo(string value, int charCount)\n        {\n            if (value == null)\n                return value;\n\n            if (value.Length > charCount)\n                return value.Substring(0, charCount);\n\n            return value;\n        }\n\n        /// <summary>\n        /// Replicates an input string n number of times\n        /// </summary>\n        /// <param name=\"input\"></param>\n        /// <param name=\"charCount\"></param>\n        /// <returns></returns>\n        public static string Replicate(string input, int charCount)\n        {\n            StringBuilder sb = new StringBuilder(input.Length * charCount);\n            for (int i = 0; i < charCount; i++)\n                sb.Append(input);\n\n            return sb.ToString();\n        }\n\n        /// <summary>\n        /// Replicates a character n number of times and returns a string\n        /// You can use `new string(char, count)` directly though.\n        /// </summary>\n        /// <param name=\"charCount\"></param>\n        /// <param name=\"character\"></param>\n        /// <returns></returns>\n        public static string Replicate(char character, int charCount)\n        {\n            return new string(character, charCount);\n        }\n\n        /// <summary>\n        /// Finds the nth index of string in a string\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"matchString\"></param>\n        /// <param name=\"stringInstance\"></param>\n        /// <returns></returns>\n        public static int IndexOfNth(this string source, string matchString, int stringInstance, StringComparison stringComparison = StringComparison.CurrentCulture)\n        {\n            if (string.IsNullOrEmpty(source))\n                return -1;\n\n            int lastPos = 0;\n            int count = 0;\n\n            while (count < stringInstance)\n            {\n                var len = source.Length - lastPos;\n                lastPos = source.IndexOf(matchString, lastPos, len, stringComparison);\n                if (lastPos == -1)\n                    break;\n\n                count++;\n                if (count == stringInstance)\n                    return lastPos;\n\n                lastPos += matchString.Length;\n            }\n            return -1;\n        }\n\n        /// <summary>\n        /// Returns the nth Index of a character in a string\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"matchChar\"></param>\n        /// <param name=\"charInstance\"></param>\n        /// <returns></returns>\n        public static int IndexOfNth(this string source, char matchChar, int charInstance)\n        {\n            if (string.IsNullOrEmpty(source))\n                return -1;\n\n            if (charInstance < 1)\n                return -1;\n\n            int count = 0;\n            for (int i = 0; i < source.Length; i++)\n            {\n                if (source[i] == matchChar)\n                {\n                    count++;\n                    if (count == charInstance)\n                        return i;\n                }\n            }\n            return -1;\n        }\n\n\n\n        /// <summary>\n        /// Finds the nth index of strting in a string\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"matchString\"></param>\n        /// <param name=\"charInstance\"></param>\n        /// <returns></returns>\n        public static int LastIndexOfNth(this string source, string matchString, int charInstance, StringComparison stringComparison = StringComparison.CurrentCulture)\n        {\n            if (string.IsNullOrEmpty(source))\n                return -1;\n\n            int lastPos = source.Length;\n            int count = 0;\n\n            while (count < charInstance)\n            {\n                lastPos = source.LastIndexOf(matchString, lastPos, lastPos, stringComparison);\n                if (lastPos == -1)\n                    break;\n\n                count++;\n                if (count == charInstance)\n                    return lastPos;\n            }\n            return -1;\n        }\n\n        /// <summary>\n        /// Finds the nth index of in a string from the end.\n        /// </summary>\n        /// <param name=\"source\"></param>\n        /// <param name=\"matchChar\"></param>\n        /// <param name=\"charInstance\"></param>\n        /// <returns></returns>\n        public static int LastIndexOfNth(this string source, char matchChar, int charInstance)\n        {\n            if (string.IsNullOrEmpty(source))\n                return -1;\n\n            int count = 0;\n            for (int i = source.Length - 1; i > -1; i--)\n            {\n                if (source[i] == matchChar)\n                {\n                    count++;\n                    if (count == charInstance)\n                        return i;\n                }\n            }\n            return -1;\n        }\n        #endregion\n\n        #region String Casing\n\n\n        /// <summary>\n        /// Compares to strings for equality ignoring case.\n        /// Uses OrdinalIgnoreCase\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"compareTo\"></param>\n        /// <returns></returns>\n        public static bool EqualsNoCase(this string text, string compareTo)\n        {\n            if (text == null && compareTo == null)\n                return true;\n            if (text == null || compareTo == null)\n                return false;\n\n            return text.Equals(compareTo, StringComparison.OrdinalIgnoreCase);\n        }\n\n        /// <summary>\n        /// Return a string in proper Case format\n        /// </summary>\n        /// <param name=\"Input\"></param>\n        /// <returns></returns>\n        public static string ProperCase(string Input)\n        {\n            if (Input == null)\n                return null;\n            return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(Input);\n        }\n\n        /// <summary>\n        /// Takes a phrase and turns it into CamelCase text.\n        /// White Space, punctuation and separators are stripped\n        /// </summary>\n        /// <param name=\"phrase\">Text to convert to CamelCase</param>\n        public static string ToCamelCase(string phrase)\n        {\n            if (phrase == null)\n                return string.Empty;\n\n            StringBuilder sb = new StringBuilder(phrase.Length);\n\n            // First letter is always upper case\n            bool nextUpper = true;\n\n            foreach (char ch in phrase)\n            {\n                if (char.IsWhiteSpace(ch) || char.IsPunctuation(ch) || char.IsSeparator(ch) || ch > 32 && ch < 48)\n                {\n                    nextUpper = true;\n                    continue;\n                }\n                if (char.IsDigit(ch))\n                {\n                    sb.Append(ch);\n                    nextUpper = true;\n                    continue;\n                }\n\n                if (nextUpper)\n                    sb.Append(char.ToUpper(ch));\n                else\n                    sb.Append(char.ToLower(ch));\n\n                nextUpper = false;\n            }\n\n            return sb.ToString();\n        }\n\n        /// <summary>\n        /// Tries to create a phrase string from CamelCase text\n        /// into Proper Case text.  Will place spaces before capitalized\n        /// letters.\n        /// \n        /// Note that this method may not work for round tripping \n        /// ToCamelCase calls, since ToCamelCase strips more characters\n        /// than just spaces.\n        /// </summary>\n        /// <param name=\"camelCase\">Camel Case Text: firstName -> First Name</param>\n        /// <returns></returns>\n        public static string FromCamelCase(string camelCase)\n        {\n            if (string.IsNullOrEmpty(camelCase))\n                return camelCase;\n\n            StringBuilder sb = new StringBuilder(camelCase.Length + 10);\n            bool first = true;\n            char lastChar = '\\0';\n\n            foreach (char ch in camelCase)\n            {\n                if (!first &&\n                    lastChar != ' ' && !char.IsSymbol(lastChar) && !char.IsPunctuation(lastChar) &&\n                    ((char.IsUpper(ch) && !char.IsUpper(lastChar)) ||\n                     char.IsDigit(ch) && !char.IsDigit(lastChar)))\n                    sb.Append(' ');\n\n                sb.Append(ch);\n                first = false;\n                lastChar = ch;\n            }\n\n            return sb.ToString(); ;\n        }\n\n\n        /// <summary>\n        /// Attempts to convert a string that is encoded in camel or snake case or\n        /// and convert it into a proper case string. This is useful for converting\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <returns></returns>\n        public static string BreakIntoWords(string text)\n        {\n            // if the text contains spaces it's already real text     \n            if (string.IsNullOrEmpty(text) || text.Contains(\" \") || text.Contains(\"\\t\"))\n                return text;\n\n            if (text.Contains(\"-\"))\n                text = text.Replace(\"-\", \" \").Trim();\n\n            if (text.Contains(\"_\"))\n                text = text.Replace(\"_\", \" \").Trim();\n\n            char c = text[0];\n\n            // assume file name was valid as a 'title'     \n            if (char.IsUpper(c))\n            {\n                // Split before uppercase letters, but not if preceded by another uppercase letter\n                // and followed by a lowercase letter (to handle acronyms properly)\n                string[] words = Regex.Split(text, @\"(?<!^)(?<![A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])\");\n                return string.Join(\" \", words);\n            }\n\n            // lower case and no spaces - assume camel case     \n            if (char.IsLower(c) & !text.Contains(\" \"))\n            {\n                return StringUtils.FromCamelCase(text);\n            }\n\n            // just return as proper case     \n            return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(text);\n        }\n\n\n\n        #endregion\n\n        #region String Manipulation\n\n        /// <summary>\n        /// Extracts a string from between a pair of delimiters. Only the first \n        /// instance is found.\n        /// </summary>\n        /// <param name=\"source\">Input String to work on</param>\n        /// <param name=\"beginDelim\">Beginning delimiter</param>\n        /// <param name=\"endDelim\">ending delimiter</param>\n        /// <param name=\"caseSensitive\">Determines whether the search for delimiters is case sensitive</param>        \n        /// <param name=\"allowMissingEndDelimiter\"></param>\n        /// <param name=\"returnDelimiters\"></param>\n        /// <returns>Extracted string or string.Empty on no match</returns>\n        public static string ExtractString(this string source,\n            string beginDelim,\n            string endDelim,\n            bool caseSensitive = false,\n            bool allowMissingEndDelimiter = false,\n            bool returnDelimiters = false)\n        {\n            int at1, at2;\n\n            if (string.IsNullOrEmpty(source))\n                return string.Empty;\n\n            if (caseSensitive)\n            {\n                at1 = source.IndexOf(beginDelim, StringComparison.CurrentCulture);\n                if (at1 == -1)\n                    return string.Empty;\n\n                at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.CurrentCulture);\n            }\n            else\n            {\n                //string Lower = source.ToLower();\n                at1 = source.IndexOf(beginDelim, 0, source.Length, StringComparison.OrdinalIgnoreCase);\n                if (at1 == -1)\n                    return string.Empty;\n\n                at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.OrdinalIgnoreCase);\n            }\n\n            if (allowMissingEndDelimiter && at2 < 0)\n            {\n                if (!returnDelimiters)\n                    return source.Substring(at1 + beginDelim.Length);\n\n                return source.Substring(at1);\n            }\n\n            if (at1 > -1 && at2 > 1)\n            {\n                if (!returnDelimiters)\n                    return source.Substring(at1 + beginDelim.Length, at2 - at1 - beginDelim.Length);\n\n                return source.Substring(at1, at2 - at1 + endDelim.Length);\n            }\n\n            return string.Empty;\n        }\n\n        /// <summary>\n        /// Strips characters of a string that follow the specified delimiter\n        /// </summary>\n        /// <param name=\"value\">String to work with</param>\n        /// <param name=\"delimiter\">String to search for from end of string</param>\n        /// <param name=\"caseSensitive\">by default ignores case, set to true to care</param>\n        /// <returns>stripped string, or original string if delimiter was not found</returns>\n        public static string StripAfter(this string value, string delimiter, bool caseSensitive = false)\n        {\n            if (string.IsNullOrEmpty(value))\n                return value;\n\n            var pos = caseSensitive ?\n                         value.LastIndexOf(delimiter) : \n                         value.LastIndexOf(delimiter, StringComparison.OrdinalIgnoreCase);\n            if (pos < 0)\n                return value;\n\n            return value.Substring(0, pos);\n        }\n\n        /// <summary>\n        /// String replace function that supports replacing a specific instance with \n        /// case insensitivity\n        /// </summary>\n        /// <param name=\"origString\">Original input string</param>\n        /// <param name=\"findString\">The string that is to be replaced</param>\n        /// <param name=\"replaceWith\">The replacement string</param>\n        /// <param name=\"instance\">Instance of the FindString that is to be found. 1 based. If Instance = -1 all are replaced</param>\n        /// <param name=\"caseInsensitive\">Case insensitivity flag</param>\n        /// <returns>updated string or original string if no matches</returns>\n        public static string ReplaceStringInstance(string origString, string findString,\n            string replaceWith, int instance, bool caseInsensitive)\n        {\n            if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString))\n                return origString; // nothing to do\n\n            if (instance == -1) // all instances\n#if NET6_0_OR_GREATER\n                // use native if possible - can only replace all instances\n                return origString.Replace(findString, replaceWith, StringComparison.OrdinalIgnoreCase);\n#else\n                return ReplaceString(origString, findString, replaceWith, caseInsensitive);\n#endif\n            int at1 = 0;\n            for (int x = 0; x < instance; x++)\n            {\n                if (caseInsensitive)\n                    at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase);\n                else\n                    at1 = origString.IndexOf(findString, at1);\n\n                if (at1 == -1)\n                    return origString;\n\n                if (x < instance - 1)\n                    at1 += findString.Length;\n            }\n\n            return origString.Substring(0, at1) + replaceWith + origString.Substring(at1 + findString.Length);\n        }\n\n\n        /// <summary>\n        /// Replaces a substring within a string with another substring with optional case sensitivity turned off.\n        /// </summary>\n        /// <param name=\"origString\">String to do replacements on</param>\n        /// <param name=\"findString\">The string to find</param>\n        /// <param name=\"replaceString\">The string to replace found string wiht</param>\n        /// <param name=\"caseInsensitive\">If true case insensitive search is performed</param>\n        /// <returns>updated string or original string if no matches</returns>\n#if NET6_0_OR_GREATER\n        [Obsolete(\"You can use native `string.Replace()` with StringComparison in .NET Core\")]\n#endif\n        public static string ReplaceString(string origString, string findString, string replaceString, bool caseInsensitive)\n        {\n            if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString))\n                return origString; // nothing to do\n\n            int at1 = 0;\n            while (true)\n            {\n                if (caseInsensitive)\n                    at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase);\n                else\n                    at1 = origString.IndexOf(findString, at1);\n\n                if (at1 == -1)\n                    break;\n\n                origString = origString.Substring(0, at1) + replaceString + origString.Substring(at1 + findString.Length);\n\n                at1 += replaceString.Length;\n            }\n\n            return origString;\n        }\n\n        /// <summary>\n        /// Replaces the last nth occurrence of a string within a string with another string\n        /// </summary>\n        /// <param name=\"source\">Souce string</param>\n        /// <param name=\"oldValue\">Value to replace</param>\n        /// <param name=\"newValue\">Value to replace with</param>\n        /// <param name=\"instanceFromEnd\">The instance from the end to replace</param>\n        /// <param name=\"compare\">String comparison mode</param>\n        /// <returns>replaced string or original string if replacement is not found</returns>\n        public static string ReplaceLastNthInstance(string source, string oldValue, string newValue, int instanceFromEnd = 1, StringComparison compare = StringComparison.CurrentCulture)\n        {\n            if (instanceFromEnd <= 0 || source == null || oldValue == null)  return source; // Invalid n value\n\n            int lastIndex = source.Length;\n            \n            // Traverse the string backwards\n            while (instanceFromEnd > 0)\n            {\n                lastIndex = source.LastIndexOf(oldValue, lastIndex - 1, compare);\n                if (lastIndex == -1) return source; // If not found, return the original string\n                instanceFromEnd--;\n            }\n\n            // Replace the found occurrence\n            return source.Substring(0, lastIndex) + newValue + source.Substring(lastIndex + oldValue.Length);\n        }\n\n        /// <summary>\n        /// Truncate a string to maximum length.\n        /// </summary>\n        /// <param name=\"text\">Text to truncate</param>\n        /// <param name=\"maxLength\">Maximum length</param>\n        /// <returns>Trimmed string</returns>\n        public static string Truncate(this string text, int maxLength)\n        {\n            if (string.IsNullOrEmpty(text) || text.Length <= maxLength)\n                return text;\n\n            return text.Substring(0, maxLength);\n        }\n\n        /// <summary>\n        /// Returns an abstract of the provided text by returning up to Length characters\n        /// of a text string. If the text is truncated a ... is appended.\n        ///\n        /// Note: Linebreaks are converted into spaces.\n        /// </summary>\n        /// <param name=\"text\">Text to abstract</param>\n        /// <param name=\"length\">Number of characters to abstract to</param>\n        /// <returns>string</returns>\n        public static string TextAbstract(string text, int length)\n        {\n            if (string.IsNullOrEmpty(text))\n                return string.Empty;\n\n            if (text.Length > length)\n            {\n                text = text.Substring(0, length);\n                var idx = text.LastIndexOf(' ');\n                if (idx > -1)\n                    text = text.Substring(0, idx) + \"…\";              \n            }\n\n            if (!text.Contains(\"\\n\"))\n                return text;\n\n            // linebreaks to spaces\n            StringBuilder sb = new StringBuilder(text.Length);\n            foreach (var s in GetLines(text))\n                sb.Append(s.Trim() + \" \");\n            return sb.ToString().Trim();\n        }\n\n        /// <summary>\n        /// Terminates a string with the given end string/character, but only if the\n        /// text specified doesn't already exist and the string is not empty.\n        /// </summary>\n        /// <param name=\"value\">String to terminate</param>\n        /// <param name=\"terminatorString\">String to terminate the text string with</param>\n        /// <returns></returns>\n        public static string TerminateString(string value, string terminatorString)\n        {\n            if (string.IsNullOrEmpty(value))\n                return terminatorString;\n\n            if (value.EndsWith(terminatorString))\n                return value;\n\n            return value + terminatorString;\n        }\n\n\n\n        /// <summary>\n        /// Returns the number or right characters specified\n        /// </summary>\n        /// <param name=\"full\">full string to work with</param>\n        /// <param name=\"rightCharCount\">number of right characters to return</param>\n        /// <returns></returns>\n        public static string Right(string full, int rightCharCount)\n        {\n            if (string.IsNullOrEmpty(full) || full.Length < rightCharCount || full.Length - rightCharCount < 0)\n                return full;\n\n            return full.Substring(full.Length - rightCharCount);\n        }\n        #endregion\n\n        #region String 'Any' and 'Many' Operations\n\n        /// <summary>\n        /// Checks many a string for multiple string values to start with\n        /// </summary>\n        /// <param name=\"str\">String to check</param>\n        /// <param name=\"matchValues\">Values to check in string</param>\n        /// <returns></returns>\n        public static bool StartsWithAny(this string str, params string[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.StartsWith(value))\n                    return true;\n            }\n\n            return false;\n        }\n\n        /// <summary>\n        /// Checks many a string for multiple string values to start with\n        /// </summary>\n        /// <param name=\"str\">String to check</param>\n        /// <param name=\"compare\">Comparision mode</param>\n        /// <param name=\"matchValues\">Values to check in string</param>\n        /// <returns></returns>\n        public static bool StartsWithAny(this string str, StringComparison compare, params string[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)            \n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.StartsWith(value, compare))\n                    return true;\n            }\n            return false;\n        }\n\n\n        /// <summary>\n        /// Checks a string form multiple contained string values\n        /// </summary>\n        /// <param name=\"str\">String to match</param>\n        /// <param name=\"matchValues\">Matches to find in the string</param>\n        /// <returns></returns>\n        public static bool ContainsAny(this string str, params string[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.Contains(value))\n                    return true;\n            }\n\n            return false;\n        }\n\n        /// <summary>\n        /// Checks a string form multiple contained string values\n        /// </summary>\n        /// <param name=\"str\">String to match</param>\n        /// <param name=\"compare\">Type of comparison</param>\n        /// <param name=\"matchValues\">Matches to find in the string</param>\n        /// <returns></returns>\n        public static bool ContainsAny(this string str, StringComparison compare, params string[] matchValues)\n        {\n\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.Contains(value, compare))\n                    return true;\n            }\n\n            return false;\n        }\n\n\n        /// <summary>\n        /// Checks a string form multiple contained string values\n        /// </summary>\n        /// <param name=\"str\">String to match</param>\n        /// <param name=\"matchValues\">Matches to find in the string</param>\n        /// <returns></returns>\n        public static bool ContainsAny(this string str, params char[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.Contains(value))\n                    return true;\n            }\n\n            return false;\n        }\n\n#if NET6_0_OR_GREATER\n        /// <summary>\n        /// Checks a string form multiple contained string values\n        /// </summary>\n        /// <param name=\"str\">String to match</param>\n        /// <param name=\"compare\">Type of comparison</param>\n        /// <param name=\"matchValues\">Matches to find in the string</param>\n        /// <returns></returns>\n        public static bool ContainsAny(this string str, StringComparison compare, params char[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                if (str.Contains(value, compare))\n                    return true;\n            }\n\n            return false;\n        }\n#endif\n\n\n        /// <summary>\n        /// Checks to see if a string contains any of a set of values.\n        /// </summary>\n        /// <param name=\"str\">String to compare</param>        \n        /// <param name=\"matchValues\">String values to compare to</param>\n        /// <remarks>null values in matchValues are ignored</remarks>\n        /// <returns></returns>\n        public static bool EqualsAny(this string str, params string[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                // null values are ignored\n                if (value == null) continue;\n\n                if (str.Equals(value))\n                    return true;\n            }\n\n            return false;\n        }\n\n\n        /// <summary>\n        /// Checks to see if a string contains any of a set of values\n        /// </summary>\n        /// <param name=\"str\">String to check</param>\n        /// <param name=\"compare\">Comparison mode</param>\n        /// <param name=\"matchValues\">Strings to check for</param>\n        /// <returns></returns>\n        public static bool EqualsAny(this string str, StringComparison compare, params string[] matchValues)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return false;\n\n            foreach (var value in matchValues)\n            {\n                // null values are ignored\n                if (value == null) continue;\n\n                if (str.Equals(value, compare))\n                    return true;\n            }\n\n            return false;\n        }\n\n\n        /// <summary>\n        /// Replaces multiple matches with a single new value\n        ///         \n        /// This version takes an array of strings as input\n        /// </summary>\n        /// <param name=\"str\">String to work on</param>\n        /// <param name=\"matchValues\">String values to match</param>\n        /// <param name=\"replaceWith\">String to replace with</param>\n        /// <returns></returns>\n        public static string ReplaceMany(this string str, string[] matchValues, string replaceWith)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return str;\n\n            foreach (var value in matchValues)\n            {\n                // null values are ignored\n                if (value == null) continue;\n\n                str = str.Replace(value, replaceWith);\n            }\n\n            return str;\n        }\n\n\n        /// <summary>\n        /// Replaces multiple matches with a single new value. \n        /// \n        /// This version takes a comma delimited list of strings\n        /// </summary>\n        /// <param name=\"str\">String to work on</param>\n        /// <param name=\"valuesToMatch\">Comma delimited list of values. Values are start and end trimmed</param>\n        /// <param name=\"replaceWith\">String to replace with</param>\n        /// <returns></returns>\n        public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith)\n        {\n            if (string.IsNullOrEmpty(valuesToMatch))\n                return str;\n#if NET6_0_OR_GREATER\n            var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);\n#else\n            var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)\n                .Select(v => v.Trim())\n                .ToArray();            \n#endif\n\n            return ReplaceMany(str, matchValues, replaceWith);\n        }\n\n#if NET6_0_OR_GREATER\n        /// <summary>\n        /// Replaces multiple matches with a single new value\n        ///         \n        /// This version takes an array of strings as input\n        /// </summary>\n        /// <param name=\"str\">String to work on</param>\n        /// <param name=\"matchValues\">String values to match</param>\n        /// <param name=\"replaceWith\">String to replace with</param>\n        /// <param name=\"compare\">String comparison mode</param>\n        /// <returns></returns>\n        public static string ReplaceMany(this string str, string[] matchValues, string replaceWith, StringComparison compare)\n        {\n            if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1)\n                return str;\n\n            foreach (var value in matchValues)\n            {\n                str = str.Replace(value, replaceWith, compare);\n            }\n\n            return str;\n        }\n\n        /// <summary>\n        /// Replaces multiple matches with a single new value. \n        /// \n        /// This version takes a comma delimited list of strings\n        /// </summary>\n        /// <param name=\"str\">String to work on</param>\n        /// <param name=\"valuesToMatch\">Comma delimited list of values. Values are start and end trimmed</param>\n        /// <param name=\"replaceWith\">String to replace with</param>\n        /// <param name=\"compare\">String comparison mode</param>\n        /// <returns></returns>\n        public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith, StringComparison compare)\n        {\n            if (string.IsNullOrEmpty(valuesToMatch))\n                return str;\n\n#if NET6_0_OR_GREATER\n            var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);\n#else\n            var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)\n                .Select(v => v.Trim())\n                .ToArray();            \n#endif\n\n            return ReplaceMany(str, matchValues, replaceWith, compare);\n        }\n#endif\n\n#endregion\n\n\n        #region String Parsing\n\n        /// <summary>\n        /// Determines if a string is contained in a list of other strings\n        /// </summary>\n        /// <param name=\"s\"></param>\n        /// <param name=\"list\"></param>\n        /// <returns></returns>\n        public static bool Inlist(this string s, params string[] list)\n        {\n            if (string.IsNullOrEmpty(s) || list == null || list.Length < 1)\n                return false;\n\n            return list.Contains(s);\n        }\n\n#if NET6_0_OR_GREATER\n        /// <summary>\n        /// Determines if a string is contained in a list of other strings\n        /// </summary>\n        /// <param name=\"s\"></param>\n        /// <param name=\"list\"></param>\n        /// <returns></returns>\n        public static bool Inlist(string s, StringComparison compare, params string[] list)\n        {\n            if (string.IsNullOrEmpty(s) || list == null || list.Length < 1)\n                return false;\n\n            foreach (var item in list)\n            {\n                if (item.Equals(s, compare))\n                    return true;\n            }\n            return false;\n        }\n#endif\n\n        /// <summary>\n        /// Checks to see if value is part of a delimited list of values.\n        /// Example: IsStringInList(\"value1,value2,value3\",\"value3\");\n        /// </summary>\n        /// <param name=\"stringList\">A list of delimited strings (ie. value1, value2, value3) with or without spaces (values are trimmed)</param>\n        /// <param name=\"valueToFind\">value to match against the list</param>\n        /// <param name=\"separator\">Character that separates the list values</param>\n        /// <param name=\"ignoreCase\">If true ignores case for the list value matches</param>\n        public static bool IsStringInList(string stringList, string valueToFind, char separator = ',', bool ignoreCase = false)\n        {\n            var tokens = stringList.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);\n            if (tokens.Length == 0)\n                return false;\n\n            var comparer = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;\n            foreach (var tok in tokens)\n            {\n                if (tok.Trim().Equals(valueToFind, comparer))\n                    return true;\n            }\n            return false;\n        }\n\n        /// <summary>\n        /// String.Contains() extension method that allows to specify case\n        /// </summary>\n        /// <param name=\"text\">Input text</param>\n        /// <param name=\"searchFor\">text to search for</param>\n        /// <param name=\"stringComparison\">Case sensitivity options</param>\n        /// <returns></returns>\n        public static bool Contains(this string text, string searchFor, StringComparison stringComparison)\n        {\n            return text.IndexOf(searchFor, stringComparison) > -1;\n        }\n\n\n        /// <summary>\n        /// Parses a string into an array of lines broken\n        /// by \\r\\n or \\n\n        /// </summary>\n        /// <param name=\"s\">String to check for lines</param>\n        /// <param name=\"maxLines\">Optional - max number of lines to return</param>\n        /// <returns>array of strings, or null if the string passed was a null</returns>\n        public static string[] GetLines(this string s, int maxLines = 0)\n        {\n            if (s == null)\n                return new string[] { };\n\n            s = s.Replace(\"\\r\\n\", \"\\n\").Replace(\"\\r\", \"\\n\");\n\n            if (maxLines < 1)\n                return s.Split(new char[] { '\\n' });\n\n            return s.Split(new char[] { '\\n' }).Take(maxLines).ToArray();\n        }\n\n        /// <summary>\n        /// Returns a line count for a string\n        /// </summary>\n        /// <param name=\"s\">string to count lines for</param>\n        /// <returns></returns>\n        public static int CountLines(this string s)\n        {\n            if (string.IsNullOrEmpty(s))\n                return 0;\n\n            return s.Split('\\n').Length;\n        }\n\n\n        /// <summary>\n        /// Counts the number of times a character occurs\n        /// in a given string\n        /// </summary>\n        /// <param name=\"source\">input string</param>\n        /// <param name=\"match\">character to match</param>\n        /// <returns></returns>\n        public static int Occurs(string source, char match)\n        {\n            if (string.IsNullOrEmpty(source)) return 0;\n\n            int count = 0;\n            foreach (char c in source)\n                if (c == match)\n                    count++;\n\n            return count;\n        }\n\n        /// <summary>\n        /// Counts the number of times a sub string occurs\n        /// in a given string\n        /// </summary>\n        /// <param name=\"source\">input string</param>\n        /// <param name=\"match\">string to match</param>\n        /// <returns></returns>\n        public static int Occurs(string source, string match)\n        {\n            if (string.IsNullOrEmpty(source)) return 0;\n            return source.Split(new[] { match }, StringSplitOptions.None).Length - 1;\n        }\n\n        /// <summary>\n        /// Returns a string that has the max amount of characters of the source string.\n        /// If the string is shorter than the max length the entire string is returned.\n        /// If the string is longer it's truncated.\n        /// If empty the original value is returned (null or string.Empty)\n        /// If the startPosition is greater than the length of the string null is returned\n        /// </summary>\n        /// <param name=\"s\">source string to work on</param>\n        /// <param name=\"maxCharacters\">Maximum number of characters to return</param>\n        /// <param name=\"startPosition\">Optional start position. If not specified uses entire string (0)</param>\n        /// <returns></returns>\n        public static string GetMaxCharacters(this string s, int maxCharacters, int startPosition = 0)\n        {\n            if (string.IsNullOrEmpty(s) || startPosition == 0 && maxCharacters > s.Length)\n                return s;\n\n            if (startPosition > s.Length - 1)\n                return null;\n\n            var available = s.Length - startPosition;\n\n            return s.Substring(startPosition, Math.Min(available, maxCharacters));\n        }\n\n        /// <summary>\n        /// Retrieves the last n characters from the end of a string up to the\n        /// number of characters specified. If there are fewer characters\n        /// the original string is returned otherwise the last n characters\n        /// are returned.\n        /// </summary>\n        /// <param name=\"s\">input string</param>\n        /// <param name=\"characterCount\">number of characters to retrieve from end of string</param>\n        /// <returns>Up to the last n characters of the string. Empty string on empty or null</returns>\n        public static string GetLastCharacters(this string s, int characterCount)\n        {\n            if (string.IsNullOrEmpty(s) || s.Length < characterCount)\n                return s ?? string.Empty;\n\n            return s.Substring(s.Length - characterCount);\n        }\n\n        /// <summary>\n        /// Strips all non digit values from a string and only\n        /// returns the numeric string.\n        /// </summary>\n        /// <param name=\"input\"></param>\n        /// <returns></returns>\n        public static string StripNonNumber(string input)\n        {\n            var chars = input.ToCharArray();\n            StringBuilder sb = new StringBuilder();\n            foreach (var chr in chars)\n            {\n                if (char.IsNumber(chr) || char.IsSeparator(chr))\n                    sb.Append(chr);\n            }\n\n            return sb.ToString();\n        }\n\n        static Regex tokenizeRegex = new Regex(\"{{.*?}}\");\n\n        /// <summary>\n        /// Tokenizes a string based on a start and end string. Replaces the values with a token\n        /// text (#@#1#@# for example).\n        /// \n        /// You can use Detokenize to get the original values back using DetokenizeString\n        /// using the same token replacement text.\n        /// </summary>\n        /// <param name=\"text\">Text to search</param>\n        /// <param name=\"startMatch\">starting match string</param>\n        /// <param name=\"endMatch\">ending match string</param>\n        /// <param name=\"replaceDelimiter\">token replacement text - make sure this string is a value that is unique and **doesn't occur in the document**</param>\n        /// <returns>A list of extracted string tokens that have been replaced in `ref text` with the replace delimiter</returns>\n        public static List<string> TokenizeString(ref string text, string startMatch, string endMatch, string replaceDelimiter = \"#@#\")\n        {\n            var strings = new List<string>();\n            var matches = tokenizeRegex.Matches(text);\n\n            int i = 0;\n            foreach (Match match in matches)\n            {\n                tokenizeRegex = new Regex(Regex.Escape(match.Value));\n                text = tokenizeRegex.Replace(text, $\"{replaceDelimiter}{i}{replaceDelimiter}\", 1);\n                strings.Add(match.Value);\n                i++;\n            }\n\n            return strings;\n        }\n\n\n        /// <summary>\n        /// Detokenizes a string tokenized with TokenizeString. Requires the collection created\n        /// by detokenization\n        /// </summary>\n        /// <param name=\"text\">Text to work with</param>\n        /// <param name=\"tokens\">list of previously extracted tokens</param>\n        /// <param name=\"replaceDelimiter\">the token replacement string that replaced the captured tokens</param>\n        /// <returns></returns>\n        public static string DetokenizeString(string text, IEnumerable<string> tokens, string replaceDelimiter = \"#@#\")\n        {\n            int i = 0;\n            foreach (string token in tokens)\n            {\n                text = text.Replace($\"{replaceDelimiter}{i}{replaceDelimiter}\", token);\n                i++;\n            }\n            return text;\n        }\n\n        /// <summary>\n        /// Parses an string into an integer. If the text can't be parsed\n        /// a default text is returned instead\n        /// </summary>\n        /// <param name=\"input\">Input numeric string to be parsed</param>\n        /// <param name=\"defaultValue\">Optional default text if parsing fails</param>\n        /// <param name=\"formatProvider\">Optional NumberFormat provider. Defaults to current culture's number format</param>\n        /// <returns></returns>\n        public static int ParseInt(string input, int defaultValue = 0, IFormatProvider numberFormat = null)\n        {\n\n            if (numberFormat == null)\n                numberFormat = CultureInfo.CurrentCulture.NumberFormat;\n\n            int val = defaultValue;\n\n            if (input == null)\n                return defaultValue;\n\n            if (!int.TryParse(input, NumberStyles.Any, numberFormat, out val))\n                return defaultValue;\n            return val;\n        }\n\n\n\n        /// <summary>\n        /// Parses an string into an decimal. If the text can't be parsed\n        /// a default text is returned instead\n        /// </summary>\n        /// <param name=\"input\"></param>\n        /// <param name=\"defaultValue\"></param>\n        /// <returns></returns>\n        public static decimal ParseDecimal(string input, decimal defaultValue = 0M, IFormatProvider numberFormat = null)\n        {\n            numberFormat = numberFormat ?? CultureInfo.CurrentCulture.NumberFormat;\n            decimal val = defaultValue;\n\n            if (input == null)\n                return defaultValue;\n\n            if (!decimal.TryParse(input, NumberStyles.Any, numberFormat, out val))\n                return defaultValue;\n            return val;\n        }\n\n        #endregion\n\n        #region String Ids\n        /// <summary>\n        /// Creates short string id based on a GUID hashcode.\n        /// Not guaranteed to be unique across machines, but unlikely\n        /// to duplicate in medium volume situations.\n        /// </summary>\n        /// <returns></returns>\n        public static string NewStringId()\n        {\n            return Guid.NewGuid().ToString().GetHashCode().ToString(\"x\");\n        }\n\n        /// <summary>\n        /// Creates a new random string of upper, lower case letters and digits.\n        /// Very useful for generating random data for storage in test data.\n        /// </summary>\n        /// <param name=\"size\">The number of characters of the string to generate</param>\n        /// <returns>randomized string</returns>\n        public static string RandomString(int size, bool includeNumbers = false)\n        {\n            StringBuilder builder = new StringBuilder(size);\n            char ch;\n            int num;\n\n            for (int i = 0; i < size; i++)\n            {\n                if (includeNumbers)\n                    num = Convert.ToInt32(Math.Floor(62 * random.NextDouble()));\n                else\n                    num = Convert.ToInt32(Math.Floor(52 * random.NextDouble()));\n\n                if (num < 26)\n                    ch = Convert.ToChar(num + 65);\n                // lower case\n                else if (num > 25 && num < 52)\n                    ch = Convert.ToChar(num - 26 + 97);\n                // numbers\n                else\n                    ch = Convert.ToChar(num - 52 + 48);\n\n                builder.Append(ch);\n            }\n\n            return builder.ToString();\n        }\n        private static Random random = new Random((int)DateTime.Now.Ticks);\n\n        #endregion\n\n        #region Encodings\n\n        /// <summary>\n        /// UrlEncodes a string without the requirement for System.Web\n        /// </summary>\n        /// <param name=\"String\"></param>\n        /// <returns></returns>\n        // [Obsolete(\"Use System.Uri.EscapeDataString instead\")]\n        public static string UrlEncode(string text)\n        {\n            if (string.IsNullOrEmpty(text))\n                return string.Empty;\n\n            return Uri.EscapeDataString(text);\n        }\n\n        /// <summary>\n        /// Encodes a few additional characters for use in paths\n        /// Encodes: . #\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <returns></returns>\n        public static string UrlEncodePathSafe(string text)\n        {\n            string escaped = UrlEncode(text);\n            return escaped.Replace(\".\", \"%2E\").Replace(\"#\", \"%23\");\n        }\n\n        /// <summary>\n        /// UrlDecodes a string without requiring System.Web\n        /// </summary>\n        /// <param name=\"text\">String to decode.</param>\n        /// <returns>decoded string</returns>\n        public static string UrlDecode(string text)\n        {\n            if (string.IsNullOrEmpty(text)) return text;\n\n            // pre-process for + sign space formatting since System.Uri doesn't handle it\n            // plus literals are encoded as %2b normally so this should be safe\n            text = text.Replace(\"+\", \" \");\n            string decoded = Uri.UnescapeDataString(text);\n            return decoded;\n        }\n\n        /// <summary>\n        /// Retrieves a text by key from a UrlEncoded string.\n        /// </summary>\n        /// <param name=\"urlEncoded\">UrlEncoded String</param>\n        /// <param name=\"key\">Key to retrieve text for</param>\n        /// <returns>returns the text or \"\" if the key is not found or the text is blank</returns>\n        public static string GetUrlEncodedKey(string urlEncoded, string key)\n        {\n            if (string.IsNullOrEmpty(urlEncoded) || string.IsNullOrEmpty(key) ) return string.Empty;\n\n            urlEncoded = \"&\" + urlEncoded + \"&\"; \n\n            int Index = urlEncoded.IndexOf(\"&\" + key + \"=\", StringComparison.OrdinalIgnoreCase);\n            if (Index < 0)\n                return string.Empty;\n\n            int lnStart = Index + 2 + key.Length;\n\n            int Index2 = urlEncoded.IndexOf(\"&\", lnStart);\n            if (Index2 < 0)\n                return string.Empty;\n\n            return UrlDecode(urlEncoded.Substring(lnStart, Index2 - lnStart));\n        }\n\n        /// <summary>\n        /// Allows setting of a text in a UrlEncoded string. If the key doesn't exist\n        /// a new one is set, if it exists it's replaced with the new text.\n        /// </summary>\n        /// <param name=\"urlEncoded\">A UrlEncoded string of key text pairs</param>\n        /// <param name=\"key\"></param>\n        /// <param name=\"value\"></param>\n        /// <returns></returns>\n        public static string SetUrlEncodedKey(string urlEncoded, string key, string value)\n        {\n            if (!urlEncoded.EndsWith(\"?\") && !urlEncoded.EndsWith(\"&\"))\n                urlEncoded += \"&\";\n\n            Match match = Regex.Match(urlEncoded, \"[?|&]\" + key + \"=.*?&\");\n\n            if (match == null || string.IsNullOrEmpty(match.Value))\n                urlEncoded = urlEncoded + key + \"=\" + UrlEncode(value) + \"&\";\n            else\n                urlEncoded = urlEncoded.Replace(match.Value, match.Value.Substring(0, 1) + key + \"=\" + UrlEncode(value) + \"&\");\n\n            return urlEncoded.TrimEnd('&');\n        }\n        #endregion\n\n        #region Binary Encoding\n\n        /// <summary>\n        /// Turns a BinHex string that contains raw byte values\n        /// into a byte array\n        /// </summary>\n        /// <param name=\"hex\">BinHex string (just two byte hex digits strung together)</param>\n        /// <returns></returns>\n        public static byte[] BinHexToBinary(string hex)\n        {\n            int offset = hex.StartsWith(\"0x\") ? 2 : 0;\n            if ((hex.Length % 2) != 0)\n                throw new ArgumentException(string.Format(Resources.InvalidHexStringLength, hex.Length));\n\n            byte[] ret = new byte[(hex.Length - offset) / 2];\n\n            for (int i = 0; i < ret.Length; i++)\n            {\n                ret[i] = (byte)((ParseHexChar(hex[offset]) << 4)\n                                | ParseHexChar(hex[offset + 1]));\n                offset += 2;\n            }\n            return ret;\n        }\n\n        /// <summary>\n        /// Converts a byte array into a BinHex string.\n        /// BinHex is two digit hex byte values squished together\n        /// into a string.\n        /// </summary>\n        /// <param name=\"data\">Raw data to send</param>\n        /// <returns>BinHex string or null if input is null</returns>\n        public static string BinaryToBinHex(byte[] data)\n        {\n            if (data == null)\n                return null;\n\n            char[] c = new char[data.Length * 2];\n            int b;\n            for (int i = 0; i < data.Length; i++)\n            {\n                b = data[i] >> 4;\n                c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));\n                b = data[i] & 0xF;\n                c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));\n            }\n            return new string(c).ToLower();\n        }\n\n        /// <summary>\n        /// Converts a string into bytes for storage in any byte[] types\n        /// buffer or stream format (like MemoryStream).\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"encoding\">The character encoding to use. Defaults to Unicode</param>\n        /// <returns></returns>\n        public static byte[] StringToBytes(string text, Encoding encoding = null)\n        {\n            if (text == null)\n                return null;\n\n            if (encoding == null)\n                encoding = Encoding.Unicode;\n\n            return encoding.GetBytes(text);\n        }\n\n        /// <summary>\n        /// Converts a byte array to a stringUtils\n        /// </summary>\n        /// <param name=\"buffer\">raw string byte data</param>\n        /// <param name=\"encoding\">Character encoding to use. Defaults to Unicode</param>\n        /// <returns></returns>\n        public static string BytesToString(byte[] buffer, Encoding encoding = null)\n        {\n            if (buffer == null)\n                return null;\n\n            if (encoding == null)\n                encoding = Encoding.Unicode;\n\n            return encoding.GetString(buffer);\n        }\n\n        /// <summary>\n        /// Converts a string to a Base64 string\n        /// </summary>\n        /// <param name=\"text\">A string to convert to base64</param>\n        /// <param name=\"encoding\">Optional encoding - if not passed assumed to be Unicode</param>\n        /// <returns>Base 64 or null</returns>\n        public static string ToBase64String(string text, Encoding encoding = null)\n        {\n            var bytes = StringToBytes(text, encoding);\n            if (bytes == null)\n                return null;\n\n            return Convert.ToBase64String(bytes);\n        }\n\n        /// <summary>\n        /// Converts a base64 string back to a string\n        /// </summary>\n        /// <param name=\"base64\">A base 64 string</param>\n        /// <param name=\"encoding\">Optional encoding - if not passed assumed to be Unicode</param>\n        /// <returns></returns>\n        public static string FromBase64String(string base64, Encoding encoding = null)\n        {\n            var bytes = Convert.FromBase64String(base64);\n            if (bytes == null)\n                return null;\n\n            return BytesToString(bytes, encoding);\n        }\n\n        static int ParseHexChar(char c)\n        {\n            if (c >= '0' && c <= '9')\n                return c - '0';\n            if (c >= 'A' && c <= 'F')\n                return c - 'A' + 10;\n            if (c >= 'a' && c <= 'f')\n                return c - 'a' + 10;\n\n            throw new ArgumentException(Resources.InvalidHexDigit + c);\n        }\n\n        static char[] base36CharArray = \"0123456789abcdefghijklmnopqrstuvwxyz\".ToCharArray();\n        static string base36Chars = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n\n        /// <summary>\n        /// Encodes an integer into a string by mapping to alpha and digits (36 chars)\n        /// chars are embedded as lower case\n        /// \n        /// Example: 4zx12ss\n        /// </summary>\n        /// <param name=\"value\"></param>\n        /// <returns></returns>\n        public static string Base36Encode(long value)\n        {\n            string returnValue = \"\";\n            bool isNegative = value < 0;\n            if (isNegative)\n                value = value * -1;\n\n            do\n            {\n                returnValue = base36CharArray[value % base36CharArray.Length] + returnValue;\n                value /= 36;\n            } while (value != 0);\n\n            return isNegative ? returnValue + \"-\" : returnValue;\n        }\n\n        /// <summary>\n        /// Decodes a base36 encoded string to an integer\n        /// </summary>\n        /// <param name=\"input\"></param>\n        /// <returns></returns>\n        public static long Base36Decode(string input)\n        {\n            bool isNegative = false;\n            if (input.EndsWith(\"-\"))\n            {\n                isNegative = true;\n                input = input.Substring(0, input.Length - 1);\n            }\n\n            char[] arrInput = input.ToCharArray();\n            Array.Reverse(arrInput);\n            long returnValue = 0;\n            for (long i = 0; i < arrInput.Length; i++)\n            {\n                long valueindex = base36Chars.IndexOf(arrInput[i]);\n                returnValue += Convert.ToInt64(valueindex * Math.Pow(36, i));\n            }\n            return isNegative ? returnValue * -1 : returnValue;\n        }\n        #endregion\n\n        #region Miscellaneous\n\n        /// <summary>\n        /// Normalizes linefeeds to the appropriate \n        /// </summary>\n        /// <param name=\"text\">The text to fix up</param>\n        /// <param name=\"type\">Type of linefeed to fix up to</param>\n        /// <returns></returns>\n        public static string NormalizeLineFeeds(string text, LineFeedTypes type = LineFeedTypes.Auto)\n        {\n            if (string.IsNullOrEmpty(text))\n                return text;\n\n            if (type == LineFeedTypes.Auto)\n            {\n                if (Environment.NewLine.Contains('\\r'))\n                    type = LineFeedTypes.CrLf;\n                else\n                    type = LineFeedTypes.Lf;\n            }\n\n            if (type == LineFeedTypes.Lf)\n                return text.Replace(\"\\r\\n\", \"\\n\");\n\n            return text.Replace(\"\\r\\n\", \"\\n\").Replace(\"\\n\", \"\\r\\n\");\n        }\n\n        /// <summary>\n        /// Strips any common white space from all lines of text that have the same\n        /// common white space text. Effectively removes common code indentation from\n        /// code blocks for example so you can get a left aligned code snippet.\n        /// </summary>\n        /// <param name=\"code\">Text to normalize</param>\n        /// <returns></returns>\n        public static string NormalizeIndentation(string code)\n        {\n            if (string.IsNullOrEmpty(code))\n                return string.Empty;\n\n            // normalize tabs to 3 spaces\n            string text = code.Replace(\"\\t\", \"   \");\n\n            string[] lines = text.Split(new string[3] { \"\\r\\n\", \"\\r\", \"\\n\" }, StringSplitOptions.None);\n\n            // keep track of the smallest indent\n            int minPadding = 1000;\n\n            foreach (var line in lines)\n            {\n                if (line.Length == 0)  // ignore blank lines\n                    continue;\n\n                int count = 0;\n                foreach (char chr in line)\n                {\n                    if (chr == ' ' && count < minPadding)\n                        count++;\n                    else\n                        break;\n                }\n                if (count == 0)\n                    return code;\n\n                minPadding = count;\n            }\n\n            string strip = new String(' ', minPadding);\n\n            StringBuilder sb = new StringBuilder();\n            foreach (var line in lines)\n            {\n                sb.AppendLine(StringUtils.ReplaceStringInstance(line, strip, \"\", 1, false));\n            }\n\n            return sb.ToString();\n        }\n\n\n\n        /// <summary>\n        /// Simple Logging method that allows quickly writing a string to a file\n        /// </summary>\n        /// <param name=\"output\"></param>\n        /// <param name=\"filename\"></param>\n        /// <param name=\"encoding\">if not specified used UTF-8</param>\n        public static void LogString(string output, string filename, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.UTF8;\n\n            lock (_logLock)\n            {\n                var writer = new StreamWriter(filename, true, encoding);\n                writer.WriteLine(DateTime.Now + \" - \" + output);\n                writer.Close();\n            }\n        }\n        private static object _logLock = new object();\n\n        /// <summary>\n        /// Creates a Stream from a string. Internally creates\n        /// a memory stream and returns that.\n        ///\n        /// Note: stream returned should be disposed!\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"encoding\"></param>\n        /// <returns></returns>\n        public static Stream StringToStream(string text, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.Default;\n\n            var ms = new MemoryStream(text.Length * 2);\n            byte[] data = encoding.GetBytes(text);\n            ms.Write(data, 0, data.Length);\n            ms.Position = 0;\n            return ms;\n        }\n\n        /// <summary>\n        /// Creates a string from a text based stream\n        /// </summary>\n        /// <param name=\"stream\">input stream (not closed by operation)</param>\n        /// <param name=\"encoding\">Optional encoding - if not specified assumes 'Encoding.Default'</param>\n        /// <returns></returns>\n        /// <exception cref=\"InvalidOleVariantTypeException\"></exception>\n        public static string StreamToString(Stream stream, Encoding encoding = null)\n        {\n            if (encoding == null)\n                encoding = Encoding.Default;\n\n            if (!stream.CanRead)\n                throw new InvalidOleVariantTypeException(\"Stream cannot be read.\");\n\n            using (var reader = new StreamReader(stream))\n            {\n                return reader.ReadToEnd();\n            }\n        }\n\n        /// <summary>\n        /// Retrieves a string value from an XML-like string collection that was stored via SetProperty()\n        /// </summary>\n        /// <param name=\"propertyString\">String of XML like values (not proper XML)</param>\n        /// <param name=\"key\">The key of the property to return or empty string</param>\n        /// <returns></returns>\n        public static string GetProperty(string propertyString, string key)\n        {\n            var value = StringUtils.ExtractString(propertyString, \"<\" + key + \">\", \"</\" + key + \">\");\n            return value;\n        }\n\n\n        /// <summary>\n        /// Sets a property value in an XML-like structure that can be used to store properties\n        /// in a string.\n        /// </summary>\n        /// <param name=\"propertyString\">String of XML like values (not proper XML)</param>\n        /// <param name=\"key\">a key in that string</param>\n        /// <param name=\"value\">the string value to store</param>\n        /// <returns></returns>\n        public static string SetProperty(string propertyString, string key, string value)\n        {\n            string extract = StringUtils.ExtractString(propertyString, \"<\" + key + \">\", \"</\" + key + \">\");\n\n            if (string.IsNullOrEmpty(value) && extract != string.Empty)\n            {\n                return propertyString.Replace(extract, \"\");\n            }\n\n            // NOTE: Value is not XML encoded - we only retrieve based on named nodes so no conflict\n            string xmlLine = \"<\" + key + \">\" + value + \"</\" + key + \">\";\n\n            // replace existing\n            if (extract != string.Empty)\n                return propertyString.Replace(extract, xmlLine);\n\n            // add new\n            return propertyString + xmlLine + \"\\r\\n\";\n        }\n\n\n        /// <summary>\n        /// A helper to generate a JSON string from a string value\n        /// \n        /// Use this to avoid bringing in a full JSON Serializer for\n        /// scenarios of string serialization.\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <returns>JSON encoded string (\"text\"), empty (\"\") or \"null\".</returns>\n        public static string ToJsonString(string text)\n        {\n            if (text is null)\n                return \"null\";\n\n            var sb = new StringBuilder(text.Length);\n            sb.Append(\"\\\"\");\n            var ct = text.Length;\n\n            for (int x = 0; x < ct; x++)\n            {\n                var c = text[x];\n\n                switch (c)\n                {\n                    case '\\\"':\n                        sb.Append(\"\\\\\\\"\");\n                        break;\n                    case '\\\\':\n                        sb.Append(\"\\\\\\\\\");\n                        break;\n                    case '\\b':\n                        sb.Append(\"\\\\b\");\n                        break;\n                    case '\\f':\n                        sb.Append(\"\\\\f\");\n                        break;\n                    case '\\n':\n                        sb.Append(\"\\\\n\");\n                        break;\n                    case '\\r':\n                        sb.Append(\"\\\\r\");\n                        break;\n                    case '\\t':\n                        sb.Append(\"\\\\t\");\n                        break;\n                    default:\n                        uint i = c;\n                        if (i < 32)  // || i > 255\n                            sb.Append($\"\\\\u{i:x4}\");\n                        else\n                            sb.Append(c);\n                        break;\n                }\n            }\n            sb.Append(\"\\\"\");\n\n            return sb.ToString();\n        }\n        #endregion\n    }\n\n    public enum LineFeedTypes\n    {\n        // Linefeed \\n only\n        Lf,\n        // Carriage Return and Linefeed \\r\\n\n        CrLf,\n        // Platform default Environment.NewLine\n        Auto\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Utilities/TimeUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2008 - 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Globalization;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities\n{\n    /// <summary>\n    /// Time Utilities class provides date and time related routines.\n    /// </summary>\n    public static class TimeUtils\n    {\n\n        public static DateTime MIN_DATE_VALUE = new DateTime(1900, 1, 1, 0, 0, 0, 0, CultureInfo.InvariantCulture.Calendar, DateTimeKind.Utc);\n\n        /// <summary>\n        /// Displays a date in friendly format.\n        /// </summary>\n        /// <param name=\"date\"></param>\n        /// <param name=\"showTime\"></param>\n        /// <returns>Today,Yesterday,Day of week or a string day (Jul 15, 2008)</returns>\n        public static string FriendlyDateString(DateTime date, bool showTime = false, string timeSeparator = \"-\")\n        {\n            if (date < TimeUtils.MIN_DATE_VALUE)\n                return string.Empty;\n\n            string FormattedDate = string.Empty;\n            if (date.Date.Equals(DateTime.Today))\n                FormattedDate = Resources.Today; \n            else if (date.Date == DateTime.Today.AddDays(-1))\n                FormattedDate = Resources.Yesterday;\n            else if (date.Date > DateTime.Today.AddDays(-6))\n                // Show the Day of the week\n                FormattedDate = date.ToString(\"dddd\").ToString();\n            else\n                FormattedDate = date.ToString(\"MMMM dd, yyyy\");\n\n            if (showTime)\n                FormattedDate += \" \" + timeSeparator + \"  \" + date.ToString(\"t\").ToLower().Replace(\" \",\"\");\n            \n            return FormattedDate;\n        }\n\n\n        /// <summary>\n        /// Returns a short date time string \n        /// </summary>\n        /// <param name=\"date\">The date to format</param>\n        /// <param name=\"showTime\">If true displays simpe time (hour and minutes)</param>\n        /// <param name=\"separator\">Date and time separator character</param>\n        /// <returns></returns>\n        public static string ShortDateString(DateTime date, bool showTime=false, string separator = \"-\")\n        {\n            if (date < TimeUtils.MIN_DATE_VALUE)\n                return string.Empty;\n\n            string dateString = date.ToString(\"MMM dd, yyyy\");\n            if (!showTime)\n                return dateString;\n\n            return dateString + \" \" + separator + \" \" + date.ToString(\"t\").Replace(\" \",\"\").ToLower();\n        }\n\n        /// <summary>\n        /// Returns a short date time string \n        /// </summary>\n        /// <param name=\"date\"></param>\n        /// <param name=\"ShowTime\"></param>\n        /// <returns></returns>\n        public static string ShortDateString(DateTime? date, bool ShowTime)\n        {\n            if (date == null || !date.HasValue)\n                return string.Empty;\n\n            return ShortDateString(date.Value, ShowTime);\n        }\n\n        /// <summary>\n        /// Short date time format that shows hours and minutes.\n        /// Culture adjusted but packs down US dates.\n        /// </summary>\n        /// <returns>Formatted date</returns>\n        public static string ShortTimeString(DateTime date)\n        {\n            return date.ToString(\"t\").Replace(\" \", \"\").ToLower();\n        }\n\n        /// <summary>\n        /// Displays a number of milliseconds as friendly seconds, hours, minutes \n        /// Pass -1 to get a blank date.\n        ///\n        /// Note: English only!\n        /// </summary>\n        /// <param name=\"milliSeconds\">the elapsed milliseconds to display time for</param>           \n        /// <returns>string in format of just now or 1m ago, 2h ago</returns>\n        public static string FriendlyElapsedTimeString(int milliSeconds)\n        {\n            return FriendlyElapsedTimeString(Convert.ToDouble( milliSeconds));            \n        }\n\n        /// <summary>\n        /// Displays a number of milliseconds as friendly seconds, hours, minutes \n        /// Pass -1 to get a blank date.\n        ///\n        /// Note: English only!\n        /// </summary>\n        /// <param name=\"milliSeconds\">the elapsed milliseconds to display time for</param>           \n        /// <returns>string in format of just now or 1m ago, 2h ago</returns>\n        public static string FriendlyElapsedTimeString(double milliSeconds)\n        {\n            if (milliSeconds == 0)\n                return string.Empty;\n\n            milliSeconds = Math.Abs(milliSeconds);\n\n            if (milliSeconds < 20000)\n                return \"just now\";\n\n            if (milliSeconds < 60000)\n                return ((int)(milliSeconds / 1000)) + \"s ago\"; \n\n            if (milliSeconds < 3600000)\n                return ((int)(milliSeconds / 60000)) + \"m ago\";\n\n            if (milliSeconds < 86400000 * 2) // 2 days\n                return ((int)(milliSeconds / 3600000)) + \"h ago\";  \n            \n            if (milliSeconds < 86400000F * 30F) // 30 days\n                return ((int)(milliSeconds / 86400000 )) + \"d ago\";\n\n            if (milliSeconds < 86400000F * 365F ) // 365 days\n                return (Math.Round(milliSeconds / 2592000000)) + \"mo ago\";\n\n            return (Math.Round(milliSeconds / (60000F * 60F * 24F * 365F)) ) + \"y ago\";            \n        }\n\n\n        /// <summary>\n        /// Displays the elapsed time  friendly seconds, hours, minutes \n        /// </summary>\n        /// <param name=\"elapsed\">Timespan of elapsed time</param>\n        /// <returns>string in format of just now or 1m ago, 2h ago</returns>\n        public static string FriendlyElapsedTimeString(TimeSpan elapsed)\n        {\n            return FriendlyElapsedTimeString(elapsed.TotalMilliseconds);\n        }\n\n        /// <summary>\n        /// Converts a fractional hour value like 1.25 to 1:15  hours:minutes format\n        /// </summary>\n        /// <param name=\"hours\">Decimal hour value</param>\n        /// <param name=\"format\">An optional format string where {0} is hours and {1} is minutes (ie: \"{0}h:{1}m\").</param>\n        /// <returns></returns>\n        public static string FractionalHoursToString(decimal hours, string format)\n        {\n            if (string.IsNullOrEmpty(format))\n                format = \"{0}:{1}\";\n\n            TimeSpan tspan = TimeSpan.FromHours((double)hours);\n\n            // Account for rounding error\n            int minutes = tspan.Minutes;\n            if (tspan.Seconds > 29)\n                minutes++;\n\n            return string.Format(format, tspan.Hours + tspan.Days * 24, minutes);\n        }\n        /// <summary>\n        /// Converts a fractional hour value like 1.25 to 1:15  hours:minutes format\n        /// </summary>\n        /// <param name=\"hours\">Decimal hour value</param>\n        public static string FractionalHoursToString(decimal hours)\n        {\n            return FractionalHoursToString(hours, null);\n        }\n\n        /// <summary>\n        /// Rounds an hours value to a minute interval\n        /// 0 means no rounding\n        /// </summary>\n        /// <param name=\"minuteInterval\">Minutes to round up or down to</param>\n        /// <returns></returns>\n        public static decimal RoundDateToMinuteInterval(decimal hours, int minuteInterval,\n                                                        RoundingDirection direction)\n        {\n            if (minuteInterval == 0)\n                return hours;\n\n            decimal fraction = 60 / minuteInterval;\n\n            switch (direction)\n            {\n                case RoundingDirection.Round:\n                    return Math.Round(hours * fraction, 0) / fraction;\n                case RoundingDirection.RoundDown:\n                    return Math.Truncate(hours * fraction) / fraction;\n\n            }\n            return Math.Ceiling(hours * fraction) / fraction;\n        }\n\n        /// <summary>\n        /// Rounds a date value to a given minute interval\n        /// </summary>\n        /// <param name=\"time\">Original time value</param>\n        /// <param name=\"minuteInterval\">Number of minutes to round up or down to</param>\n        /// <returns></returns>\n        public static DateTime RoundDateToMinuteInterval(DateTime time, int minuteInterval,\n                                                         RoundingDirection direction)\n        {\n            if (minuteInterval == 0)\n                return time;\n\n            decimal interval = (decimal)minuteInterval;\n            decimal actMinute = (decimal)time.Minute;\n\n            if (actMinute == 0.00M)\n                return time;\n\n            int newMinutes = 0;\n\n            switch (direction)\n            {\n                case RoundingDirection.Round:\n                    newMinutes = (int)(Math.Round(actMinute / interval, 0) * interval);\n                    break;\n                case RoundingDirection.RoundDown:\n                    newMinutes = (int)(Math.Truncate(actMinute / interval) * interval);\n                    break;\n                case RoundingDirection.RoundUp:\n                    newMinutes = (int)(Math.Ceiling(actMinute / interval) * interval);\n                    break;\n            }\n\n            // strip time \n            time = time.AddMinutes(time.Minute * -1);\n            time = time.AddSeconds(time.Second * -1);\n            time = time.AddMilliseconds(time.Millisecond * -1);\n\n            // add new minutes back on            \n            return time.AddMinutes(newMinutes);\n        }\n\n        /// <summary>\n        /// Creates a DateTime value from date and time input values\n        /// </summary>\n        /// <param name=\"Date\"></param>\n        /// <param name=\"Time\"></param>\n        /// <returns></returns>\n        public static DateTime DateTimeFromDateAndTime(string Date, string Time)\n        {\n            return DateTime.Parse(Date + \" \" + Time);\n        }\n\n        /// <summary>\n        /// Creates a DateTime Value from a DateTime date and a string time value.\n        /// </summary>\n        /// <param name=\"Date\"></param>\n        /// <param name=\"Time\"></param>\n        /// <returns></returns>\n        public static DateTime DateTimeFromDateAndTime(DateTime Date, string Time)\n        {\n            return DateTime.Parse(Date.ToShortDateString() + \" \" + Time);\n        }\n\n        /// <summary>\n        /// Converts the passed date time value to Mime formatted time string\n        /// </summary>\n        /// <param name=\"Time\"></param>\n        public static string MimeDateTime(DateTime Time)\n        {\n\n\t\t\tTimeSpan Offset = TimeZoneInfo.Local.GetUtcOffset(Time);\n            //TimeSpan Offset = TimeZone.CurrentTimeZone.GetUtcOffset(Time);\n\n            string sOffset = null;\n            if (Offset.Hours < 0)\n                sOffset = \"-\" + (Offset.Hours * -1).ToString().PadLeft(2, '0');\n            else\n                sOffset = \"+\" + Offset.Hours.ToString().PadLeft(2, '0');\n\n            sOffset += Offset.Minutes.ToString().PadLeft(2, '0');\n\n            return \"Date: \" + Time.ToString(\"ddd, dd MMM yyyy HH:mm:ss\",\n                                                          System.Globalization.CultureInfo.InvariantCulture) +\n                                                          \" \" + sOffset;\n        }\n\n        /// <summary>\n        /// Returns whether a date time is between two other dates. Optionally\n        /// can compare date only or date and time.\n        /// </summary>\n        /// <param name=\"date\"></param>\n        /// <param name=\"startDate\"></param>\n        /// <param name=\"endDate\"></param>\n        /// <param name=\"includeTime\">If true compare date and time, otherwise just date</param>\n        /// <returns>true or false</returns>\n        public static bool IsBetween(this DateTime date, DateTime startDate, DateTime endDate, bool includeTime = true)\n        {\n            return includeTime ?\n                date >= startDate && date <= endDate :\n                date.Date >= startDate.Date && date.Date <= endDate.Date;\n        }\n\n        /// <summary>\n        /// Returns whether a timespan is between two dates\n        /// </summary>\n        /// <param name=\"date\"></param>\n        /// <param name=\"startDate\"></param>\n        /// <param name=\"endDate\"></param>\n        /// <returns>true or false</returns>\n        public static bool IsBetween(this TimeSpan date, DateTime startDate, DateTime endDate)\n        {\n            return date.CompareTo(endDate) <= 0 && date.CompareTo(endDate) >= 0;\n        }\n\n        /// <summary>\n        /// Truncates a DateTime value to the nearest partial value.\n        /// </summary>\n        /// <remarks>\n        /// From: http://stackoverflow.com/questions/1004698/how-to-truncate-milliseconds-off-of-a-net-datetime        \n        /// </remarks>\n        /// <param name=\"date\"></param>\n        /// <param name=\"resolution\"></param>\n        /// <returns></returns>\n        public static DateTime Truncate(DateTime date, DateTimeResolution resolution = DateTimeResolution.Second)\n        {\n            switch (resolution)\n            {\n                case DateTimeResolution.Year:\n                    return new DateTime(date.Year, 1, 1, 0, 0, 0, 0, date.Kind);\n                case DateTimeResolution.Month:\n                    return new DateTime(date.Year, date.Month, 1, 0, 0, 0, date.Kind);\n                case DateTimeResolution.Day:\n                    return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, date.Kind);\n                case DateTimeResolution.Hour:\n                    return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerHour));\n                case DateTimeResolution.Minute:\n                    return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMinute));\n                case DateTimeResolution.Second:\n                    return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerSecond));\n                case DateTimeResolution.Millisecond:\n                    return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMillisecond));\n                case DateTimeResolution.Tick:\n                    return date.AddTicks(0);\n                default:\n                    throw new ArgumentException(\"unrecognized resolution\", \"resolution\");\n            }\n        }\n\n        /// <summary>\n        /// Returns TimeZone adjusted time for a given from a Utc or local time.\n        /// Date is first converted to UTC then adjusted.\n        /// </summary>\n        /// <param name=\"time\"></param>\n        /// <param name=\"timeZoneId\"></param>\n        /// <returns></returns>\n        public static DateTime ToTimeZoneTime(this DateTime time, string timeZoneId = \"Pacific Standard Time\")\n        {\n            TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);\n            return time.ToTimeZoneTime(tzi);\n        }\n\n        /// <summary>\n        /// Returns TimeZone adjusted time for a given from a Utc or local time.\n        /// Date is first converted to UTC then adjusted.\n        /// </summary>\n        /// <param name=\"time\"></param>\n        /// <param name=\"timeZoneId\"></param>\n        /// <returns></returns>\n        public static DateTime ToTimeZoneTime(this DateTime time, TimeZoneInfo tzi)\n        {\n            return TimeZoneInfo.ConvertTimeFromUtc(time, tzi);\n        }\n\n\n    }\n\n    /// <summary>\n    /// Determines how date time values are rounded\n    /// </summary>\n    public enum RoundingDirection\n    {\n        RoundUp,\n        RoundDown,\n        Round\n    }\n\n    public enum DateTimeResolution\n    {\n        Year, Month, Day, Hour, Minute, Second, Millisecond, Tick\n    }\n\n\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/VersionUtils.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Net;\n\nnamespace Westwind.Utilities\n{\n\n    public static class VersionExtensions\n    {\n        /// <summary>\n        /// Formats a version by stripping all zero values\n        /// up to the trimTokens count provided. By default\n        /// displays Major.Minor and then displays any\n        /// Build and/or revision if non-zero\t\n        /// \n        /// More info: https://weblog.west-wind.com/posts/2024/Jun/13/C-Version-Formatting\n        /// </summary>\n        /// <param name=\"version\">Version to format</param>\n        /// <param name=\"minTokens\">Minimum number of component tokens of the version to display</param>\n        /// <param name=\"maxTokens\">Maximum number of component tokens of the version to display</param>\n        public static string FormatVersion(this Version version, int minTokens = 2, int maxTokens = 2)\n        {\n            if (minTokens < 1)\n                minTokens = 1;\n            if (minTokens > 4)\n                minTokens = 4;\n            if (maxTokens < minTokens)\n                maxTokens = minTokens;\n            if (maxTokens > 4)\n                maxTokens = 4;\n\n            var items = new [] { version.Major, version.Minor, version.Build, version.Revision };\n\n            int tokens = maxTokens;\n            while (tokens > minTokens && items[tokens - 1] == 0)\n            {\n                tokens--;\n            }\n            return version.ToString(tokens);\n        }\n\n\n        /// <summary>\n        /// Formats a version by stripping all zero values\n        /// up to the trimTokens count provided. By default\n        /// displays Major.Minor and then displays any\n        /// Build and/or revision if non-zero\t\n        /// </summary>\n        /// <param name=\"version\">Version to format</param>\n        /// <param name=\"minTokens\">Minimum number of component tokens to display</param>\n        /// <param name=\"maxTokens\">Maximum number of component tokens to display</param>\n        public static string FormatVersion(string version, int minTokens = 2, int maxTokens = 2)\n        {\n            var ver = new Version(version);\n            return ver.FormatVersion(minTokens, maxTokens);\n        }\n\n\n        /// <summary>\n        /// Compare two version strings.\n        /// </summary>\n        /// <param name=\"versionToCompare\">Semantic Version string</param>\n        /// <param name=\"versionToCompareAgainst\">Semantic Version string</param>\n        /// <returns>0 - equal, 1 - greater than compareAgainst,  -1 - smaller than, -2  - Version Format error </returns>\n        public static int CompareVersions(string versionToCompare, string versionToCompareAgainst)\n        {\n            try\n            {\n                var v1 = new Version(versionToCompare);\n                var v2 = new Version(versionToCompareAgainst);\n                return v1.CompareTo(v2);\n            }\n            catch\n            {\n                return -2;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities/Utilities/XmlUtils.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2008 - 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/08/2008\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Globalization;\nusing System.Text;\nusing System.Xml;\nusing System.Xml.Linq;\nusing Westwind.Utilities.Properties;\n\n\nnamespace Westwind.Utilities\n{   \n    /// <summary>\n    /// String utility class that provides a host of string related operations\n    /// </summary>\n    public static class XmlUtils\n    {\n\n\n        ///  <summary>\n        ///  Turns a string into a properly XML Encoded string.\n        ///  Uses simple string replacement.\n        /// \n        ///  Also see XmlUtils.XmlString() which uses XElement\n        ///  to handle additional extended characters.\n        ///  </summary>\n        ///  <param name=\"text\">Plain text to convert to XML Encoded string</param>\n        /// <param name=\"isAttribute\">\n        /// If true encodes single and double quotes.\n        /// When embedding element values quotes don't need to be encoded.\n        /// When embedding attributes quotes need to be encoded.\n        /// </param>\n        /// <returns>XML encoded string</returns>\n        ///  <exception cref=\"InvalidOperationException\">Invalid character in XML string</exception>\n        public static string XmlString(string text, bool isAttribute = false)\n        {\n            if (string.IsNullOrEmpty(text))\n                return text;\n\n            var sb = new StringBuilder(text.Length);\n            foreach (var chr in text)\n            {\n                if (chr == '<')\n                    sb.Append(\"&lt;\");\n                else if (chr == '>')\n                    sb.Append(\"&gt;\");\n                else if (chr == '&')\n                    sb.Append(\"&amp;\");\n\n                if (isAttribute)\n                {\n                    // special handling for quotes\n                    if (chr == '\\\"')\n                        sb.Append(\"&quot;\");\n                    else if (chr == '\\'')\n                        sb.Append(\"&apos;\");\n\n                    // Legal sub-chr32 characters\n                    else if (chr == '\\n')\n                        sb.Append(\"&#xA;\");\n                    else if (chr == '\\r')\n                        sb.Append(\"&#xD;\");\n                    else if (chr == '\\t')\n                        sb.Append(\"&#x9;\");\n                }\n                else\n                {\n                    if (chr < 32)\n                        throw new InvalidOperationException(\"Invalid character in Xml String. Chr \" +\n                                                            Convert.ToInt16(chr) + \" is illegal.\");\n                    sb.Append(chr);\n                }\n            }\n\n            return sb.ToString();\n        }\n\n\n        /// <summary>\n        /// Retrieves a result string from an XPATH query. Null if not found.\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <param name=\"xPath\"></param>\n        /// <param name=\"ns\"></param>\n        /// <returns></returns>\n        public static XmlNode GetXmlNode(XmlNode node, string xPath, XmlNamespaceManager ns = null)\n\t    {\n\t\t    return  node.SelectSingleNode(xPath, ns);\t\t    \n\t    }\n\n\t\t/// <summary>\n\t\t/// Retrieves a result string from an XPATH query. Null if not found.\n\t\t/// </summary>\n\t\t/// <param name=\"node\">The base node to search from</param>\n\t\t/// <param name=\"xPath\">XPath to drill into to find the target node. if not provided or null, returns current node</param>\n\t\t/// <param name=\"ns\">namespace to search in (optional)</param>\n\t\t/// <returns>node text</returns>\n\t\tpublic static string GetXmlString(XmlNode node, string xPath = null, XmlNamespaceManager ns=null)\n        {\n            if (node == null)\n                return null;\n\n            if (string.IsNullOrEmpty(xPath))\n                return node?.InnerText;\n\n            XmlNode selNode = node.SelectSingleNode(xPath,ns);\n            return selNode?.InnerText;\n        }\n\n\n\t\t/// <summary>\n\t\t/// Gets an Enum value from an xml node. Returns enum\n\t\t/// type value. Either flag or string based keys will work\n\t\t/// </summary>\n\t\t/// <typeparam name=\"T\"></typeparam>\n\t\t/// <param name=\"node\"></param>\n\t\t/// <param name=\"xPath\"></param>\n\t\t/// <param name=\"ns\"></param>\n\t\t/// <returns></returns>\n\t    public static T GetXmlEnum<T>(XmlNode node, string xPath, XmlNamespaceManager ns = null)\n\t    {\n\t\t    string val = GetXmlString(node, xPath,ns);\n\t\t    if (!string.IsNullOrEmpty(val))\n\t\t\t    return (T)Enum.Parse(typeof(T), val, true);\n\n\t\t    return default(T);\n\t    }\n\n\t\t/// <summary>\n\t\t/// Retrieves a result int value from an XPATH query. 0 if not found.\n\t\t/// </summary>\n\t\t/// <param name=\"node\"></param>\n\t\t/// <param name=\"XPath\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static int GetXmlInt(XmlNode node, string XPath, XmlNamespaceManager ns = null)\n        {\n            string val = GetXmlString(node, XPath, ns);\n            if (val == null)\n                return 0;\n\n            int result = 0;\n            int.TryParse(val, out result);\n\n            return result;\n        }\n\n\t    /// <summary>\n\t    /// Retrieves a result decimal value from an XPATH query. 0 if not found.\n\t    /// </summary>\n\t    /// <param name=\"node\"></param>\n\t    /// <param name=\"XPath\"></param>\n\t    /// <returns></returns>\n\t    public static decimal GetXmlDecimal(XmlNode node, string XPath, XmlNamespaceManager ns = null)\n\t    {\n\t\t    string val = GetXmlString(node, XPath, ns);\n\t\t    if (val == null)\n\t\t\t    return 0;\n\n\t\t    decimal result = 0;\n\t\t    decimal.TryParse(val, NumberStyles.Any, CultureInfo.InvariantCulture, out result);\n\n\t\t    return result;\n\t    }\n\n\t\t/// <summary>\n\t\t/// Retrieves a result bool from an XPATH query. false if not found.\n\t\t/// </summary>\n\t\t/// <param name=\"node\"></param>\n\t\t/// <param name=\"xPath\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static bool GetXmlBool(XmlNode node, string xPath,XmlNamespaceManager ns = null)\n        {\n            string val = GetXmlString(node, xPath, ns);\n            if (val == null)\n                return false;\n\n            if (val == \"1\" || val == \"true\" || val == \"True\")\n                return true;\n\n            return false;\n        }\n\n\t    /// <summary>\n\t    /// Retrieves a result DateTime from an XPATH query. 1/1/1900  if not found.\n\t    /// </summary>\n\t    /// <param name=\"node\"></param>\n\t    /// <param name=\"xPath\"></param>\n\t    /// <param name=\"ns\"></param>\n\t    /// <returns></returns>\n\t    public static DateTime GetXmlDateTime(XmlNode node, string xPath, XmlNamespaceManager ns = null)\n        {\n            DateTime dtVal = new DateTime(1900, 1, 1, 0, 0, 0);\n\n            string val = GetXmlString(node, xPath, ns);\n            if (val == null)\n                return dtVal;\n\n            try\n            {\n                dtVal = XmlConvert.ToDateTime(val,XmlDateTimeSerializationMode.Utc);\n            }\n            catch { }\n\n            return dtVal;\n        }\n\n        /// <summary>\n        /// Gets an attribute by name\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <param name=\"attributeName\"></param>\n        /// <returns>value or null if not available</returns>\n        public static string GetXmlAttributeString(XmlNode node, string attributeName)\n        {\n            XmlAttribute att = node.Attributes[attributeName];\n            if (att == null)\n                return null;\n\n            return att.InnerText;\n        }\n\n        /// <summary>\n        /// Returns an integer value from an attribute\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <param name=\"attributeName\"></param>\n        /// <param name=\"defaultValue\"></param>\n        /// <returns></returns>\n        public static int GetXmlAttributeInt(XmlNode node, string attributeName, int defaultValue)\n        {\n            string val = GetXmlAttributeString(node, attributeName);\n            if (val == null)\n                return defaultValue;\n\n            return XmlConvert.ToInt32(val);\n        }\n\n\n\t\t/// <summary>\n\t\t/// Returns an bool value from an attribute\n\t\t/// </summary>\n\t\t/// <param name=\"node\"></param>\n\t\t/// <param name=\"attributeName\"></param>\n\t\t/// <param name=\"defaultValue\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static bool? GetXmlAttributeBool(XmlNode node, string attributeName)\n\t\t{\n\t\t\tstring val = GetXmlAttributeString(node, attributeName);\n\t\t\tif (val == null)\n\t\t\t\treturn null;\n\n\t\t\treturn XmlConvert.ToBoolean(val);\n\t\t}\n\n\n\t\t/// <summary>\n\t\t/// Converts a .NET type into an XML compatible type - roughly\n\t\t/// </summary>\n\t\t/// <param name=\"type\"></param>\n\t\t/// <returns></returns>\n\t\tpublic static string MapTypeToXmlType(Type type)\n        {\n            if (type == null)\n                return null;\n\n            if (type == typeof(string) || type == typeof(char) )\n                return \"string\";\n            if (type == typeof(int) || type== typeof(Int32) )\n                return \"integer\";\n            if (type == typeof(Int16) || type == typeof(byte) )\n                return \"short\";\n            if (type == typeof(long) || type == typeof(Int64) )\n                return \"long\";\n            if (type == typeof(bool))\n                return \"boolean\";\n            if (type == typeof(DateTime))\n                return \"datetime\";\n            \n            if (type == typeof(float))\n                return \"float\";\n            if (type == typeof(decimal))\n                return \"decimal\";\n            if (type == typeof(double))\n                return \"double\";\n            if (type == typeof(Single))\n                return \"single\";\n\n            if (type == typeof(byte))\n                return \"byte\";\n\n            if (type == typeof(byte[]))\n                return \"base64Binary\";        \n\n            return null;\n\n            // *** hope for the best\n            //return type.ToString().ToLower();\n        }\n\n\n        public static Type MapXmlTypeToType(string xmlType)\n        {\n            xmlType = xmlType.ToLower();\n\n            if (xmlType == \"string\")\n                return typeof(string);\n            if (xmlType == \"integer\")\n                return typeof(int);\n            if (xmlType == \"long\")\n                return typeof(long);\n            if (xmlType == \"boolean\")\n                return typeof(bool);\n            if (xmlType == \"datetime\")\n                return typeof(DateTime);\n            if (xmlType == \"float\")\n                return typeof(float);\n            if (xmlType == \"decimal\")\n                return typeof(decimal);\n            if (xmlType == \"double\")\n                return typeof(Double);\n            if (xmlType == \"single\")\n                return typeof(Single);\n               \n            if (xmlType == \"byte\")\n                return typeof(byte);                \n            if (xmlType == \"base64binary\")\n                return typeof(byte[]);\n      \n\n            // return null if no match is found\n            // don't throw so the caller can decide more efficiently what to do \n            // with this error result\n            return null;\n        }\n\n        \n        /// <summary>\n        /// Creates an Xml NamespaceManager for an XML document by looking\n        /// at all of the namespaces defined on the document root element.\n        /// </summary>\n        /// <param name=\"doc\">The XmlDom instance to attach the namespacemanager to</param>\n        /// <param name=\"defaultNamespace\">The prefix to use for prefix-less nodes (which are not supported if any namespaces are used in XmlDoc).</param>\n        /// <returns></returns>\n        public static XmlNamespaceManager CreateXmlNamespaceManager(XmlDocument doc, string defaultNamespace)\n        {\n            XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);\n            foreach (XmlAttribute attr in doc.DocumentElement.Attributes)\n            {\n                if (attr.Prefix == \"xmlns\")\n                    nsmgr.AddNamespace(attr.LocalName, attr.Value);\n                if (attr.Name == \"xmlns\")\n                    // default namespace MUST use a prefix\n                    nsmgr.AddNamespace(defaultNamespace, attr.Value);\n            }\n\n            return nsmgr;\n        }\n\n    }\n}"
  },
  {
    "path": "Westwind.Utilities/Westwind.Utilities.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\t<PropertyGroup>\n\t\t<TargetFrameworks>net10.0;net8.0;net472;netstandard2.0</TargetFrameworks>\n\t\t<Version>5.2.8.1</Version>\n\t\t<Authors>Rick Strahl</Authors>\n\t\t<RequireLicenseAcceptance>false</RequireLicenseAcceptance>\n\t\t<Language>en-US</Language>\n\t\t<AssemblyName>Westwind.Utilities</AssemblyName>\n\t\t<AssemblyTitle>Westwind.Utilities</AssemblyTitle>\n\t\t<NeutralLanguage>en-US</NeutralLanguage>\n\t\t<PackageId>Westwind.Utilities</PackageId>\n\t\t<RootNamespace>Westwind.Utilities</RootNamespace>\n\t\t<Title>West Wind Utilities</Title>\n\t\t<Description>.NET utility library that includes Application Configuration, lightweight ADO.NET Data Access Layer, logging, utility classes include: StringUtils, ReflectionUtils, FileUtils, DataUtils, SerializationUtils, TimeUtils, SecurityUtils and XmlUtils. These classes are useful in any kind of .NET project.</Description>\n\t\t<Summary>Small library of general purpose utilities for .NET development that almost every application can use. Used as a core reference library for other West Wind libraries.</Summary>\n\t\t<PackageCopyright>Rick Strahl, West Wind Technologies 2007-2026</PackageCopyright>\n\t\t<PackageTags>Westwind ApplicationConfiguration StringUtils ReflectionUtils DataUtils FileUtils TimeUtils SerializationUtils ImageUtils Logging DAL Sql ADO.NET</PackageTags>\n\t\t<PackageProjectUrl>http://github.com/rickstrahl/westwind.utilities</PackageProjectUrl>\n\n\t\t<PackageIcon>icon.png</PackageIcon>\n\t\t<PackageLicenseFile>LICENSE.MD</PackageLicenseFile>\n\n\t\t<AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n\t\t<Copyright>Rick Strahl, West Wind Technologies, 2010-2026</Copyright>\n\t\t<Company>West Wind Technologies</Company>\n\t\t<LangVersion>latest</LangVersion>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(Configuration)'=='Debug'\">\n\t\t<DefineConstants>TRACE;DEBUG;</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<DebugType>embedded</DebugType>\n\t\t<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<PackageOutputPath>./nupkg</PackageOutputPath>\n\t\t<PublishRepositoryUrl>true</PublishRepositoryUrl>\n\t\t<DefineConstants>RELEASE</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(TargetFramework)' != 'net472'\">\n\t\t<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\" '$(TargetFramework)' == 'net472'\">\n\t\t<DefineConstants>NETFULL</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net472|AnyCPU'\">\n\t\t<DebugType>embedded</DebugType>\n\t\t<DebugSymbols>true</DebugSymbols>\n\t</PropertyGroup>\n\t\n\n\t<ItemGroup>\t\t\n\t\t<PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.4\" />\n\t</ItemGroup>\n\t\n\t<ItemGroup Condition=\" '$(TargetFramework)' != 'net472'\">\t\n\t\t\n\t</ItemGroup>\n\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'netstandard2.0'\">\n\t\t<PackageReference Include=\"Microsoft.CSharp\" Version=\"4.7.0\" />\n\t\t<!--<PackageReference Include=\"Microsoft.Win32.Registry\" Version=\"5.0.0\" />-->\n\t</ItemGroup>\t\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net472' \">\n\t\t<!-- explicitly required netFX dependencies -->\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t\n\t\t<Reference Include=\"System.Security\" />\n\t\t<Reference Include=\"System.Configuration\" />\n\t\t<Reference Include=\"System.Net.Http\" />\n\t</ItemGroup>\n\n\t\t\n\t<ItemGroup>\n\t\t<Compile Update=\"Properties\\Resources.Designer.cs\">\n\t\t\t<DesignTime>True</DesignTime>\n\t\t\t<AutoGen>True</AutoGen>\n\t\t\t<DependentUpon>Resources.resx</DependentUpon>\n\t\t</Compile>\n\t</ItemGroup>\n\n\n\t<ItemGroup>\n\t\t<EmbeddedResource Update=\"Properties\\Resources.resx\">\n\t\t\t<Generator>PublicResXFileCodeGenerator</Generator>\n\t\t\t<LastGenOutput>Resources.Designer.cs</LastGenOutput>\n\t\t</EmbeddedResource>\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<None Include=\"icon.png\" Pack=\"true\" PackagePath=\"\" />\n\t\t<None Include=\"LICENSE.MD\" Pack=\"true\" PackagePath=\"\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Westwind.Utilities/Westwind.Utilities.sln",
    "content": "Microsoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.5.2.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Westwind.Utilities\", \"Westwind.Utilities.csproj\", \"{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {3A29F986-14FE-4001-B3F7-3470BDE9EDB0}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Westwind.Utilities/publish-nuget.ps1",
    "content": "﻿if (test-path ./nupkg) {\n    remove-item ./nupkg -Force -Recurse\n}   \n\ndotnet build -c Release\n\n$filename = Get-ChildItem \"./nupkg/*.nupkg\" | sort LastWriteTime | select -last 1 | select -ExpandProperty \"Name\"\nWrite-host $filename\n$len = $filename.length\n\nif ($len -gt 0) {\n    Write-Host \"signing... $filename\"\n    #nuget sign  \".\\nupkg\\$filename\"   -CertificateSubject \"West Wind Technologies\" -timestamper \" http://timestamp.digicert.com\"    \n    nuget push  \".\\nupkg\\$filename\" -source \"https://nuget.org\"    \n\n    Write-Host \"Done.\"\n}"
  },
  {
    "path": "Westwind.Utilities.Data/Configuration/SqlServerConfigurationProvider.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009-2013\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\n\n// TODO: Doesn't work due to missing SqlDbClientFactories which should be added later\n\n\n#if NETCORE\n    using Microsoft.Data.SqlClient;\n#else\n    using System.Data.SqlClient;\n#endif\n\nusing Westwind.Utilities.Data;\nusing System.Data.Common;\nusing System;\n//using System.Data.SqlServerCe;\nnamespace Westwind.Utilities.Configuration\n{\n\n    /// <summary>\n    /// Reads and Writes configuration settings in .NET config files and \n    /// sections. Allows reading and writing to default or external files \n    /// and specification of the configuration section that settings are\n    /// applied to.\n    /// \n    /// This implementation doesn't support Read and Write operation that\n    /// don't return a string value. Only Read(string) and WriteAsString()\n    /// should be used to read and write string values.\n    /// </summary>\n    public class SqlServerConfigurationProvider<TAppConfiguration> : ConfigurationProviderBase<TAppConfiguration>\n        where TAppConfiguration : AppConfiguration, new()\n    {\n\n        /// <summary>\n        /// The raw SQL connection string or connectionstrings name\n        /// for the database connection.\n        /// </summary>\n        public string ConnectionString\n        {\n            get { return _ConnectionString; }\n            set { _ConnectionString = value; }\n        }\n        private string _ConnectionString = string.Empty;\n\n#if NETFULL\n        /// <summary>\n        /// The data provider used to access the database\n        /// </summary>\n        public string ProviderName { get; set; } = \"System.Data.SqlClient\";\n#else\n        /// <summary>\n        /// If ProviderName is missing\n        /// </summary>\n        public DbProviderFactory ProviderFactory { get; set; } = SqlClientFactory.Instance;\n#endif        \n        /// <summary>\n        /// Table in the database that holds configuration data\n        /// Table must have ID(int) and ConfigData (nText) fields\n        /// </summary>\n        public string Tablename\n        {\n            get { return _Tablename; }\n            set { _Tablename = value; }\n        }\n        private string _Tablename = \"ConfigurationSettings\";\n\n\n        /// <summary>\n        /// The key of the record into which the config\n        /// data is written. Defaults to 1.\n        /// \n        /// If you need to read or write multiple different\n        /// configuration records you have to change it on\n        /// this provider before calling the Read()/Write()\n        /// methods.\n        /// </summary>\n        public int Key\n        {\n            get { return _Key; }\n            set { _Key = value; }\n        }\n        private int _Key = 1;\n      \n\n        /// <summary>\n        /// Reads configuration data into a new instance from SQL Server\n        /// that is returned.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <returns></returns>\n        public override T Read<T>()\n        {\n#if NETFULL\n\t\t\tusing (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderName) )\n#else\n\t\t\tusing (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderFactory))\n#endif\n\n\t\t\t{\n                string sql = \"select * from [\" + Tablename + \"] where id=\" + Key.ToString();\n\n                DbDataReader reader = null;\n                try\n                {\n                    DbCommand command = data.CreateCommand(sql);\n                    if (command == null)\n                    {\n                        SetError(data.ErrorMessage);\n                        return null;\n                    }\n                    reader = command.ExecuteReader();\n                    if (reader == null)\n                    {\n                        SetError(data.ErrorMessage);\n                        return null;\n                    }\n                }\n                catch (SqlException ex)\n                {\n                    if (ex.Number == 208)\n                    {\n\n                        sql =\n    @\"CREATE TABLE [\" + Tablename + @\"]  \n( [id] [int] , [ConfigData] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS)\";\n                        try\n                        {\n                            data.ExecuteNonQuery(sql);\n                        }\n                        catch\n                        {\n                            return null;\n                        }\n\n                        // try again if we were able to create the table \n                        return Read<T>();\n                    }\n\n                }\n                catch (DbException dbEx)\n                {\n                    // SQL CE Table doesn't exist\n                    if (dbEx.ErrorCode == -2147467259)\n                    {\n                        sql = String.Format(\n                            @\"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )\",\n                            Tablename);\n                        try\n                        {\n                            data.ExecuteNonQuery(sql);\n                        }\n                        catch\n                        {\n                            return null;\n                        }\n\n                        // try again if we were able to create the table \n                        var inst = Read<T>();\n\n                        // if we got it write it to the db\n                        Write(inst);\n\n                        return inst;\n                    }\n                    return null;\n                }\n                catch (Exception ex)\n                {\n                    this.SetError(ex);\n                    \n                    if (reader != null)\n                        reader.Close();\n                    \n                    data.CloseConnection();\n                    return null;\n                }\n\n\n                string xmlConfig = null;\n\n                if (reader.Read())\n                    xmlConfig = (string)reader[\"ConfigData\"];\n\n                reader.Close();\n                data.CloseConnection();\n\n                if (string.IsNullOrEmpty(xmlConfig))\n                {\n                    T newInstance = new T();\n                    newInstance.Provider = this;\n                    return newInstance;\n                }\n\n                T instance = Read<T>(xmlConfig);\n\n                return instance;\n            }\n        }\n\n        /// <summary>\n        /// Reads configuration data from Sql Server into an existing \n        /// instance updating its fields.\n        /// </summary> \n        /// <param name=\"config\"></param>\n        /// <returns></returns>\n        public override bool Read(AppConfiguration config)\n        {\n            TAppConfiguration newConfig = Read<TAppConfiguration>();\n            if (newConfig == null)\n                return false;\n\n            DataUtils.CopyObjectData(newConfig, config,\"Provider,ErrorMessage\");\n            return true;\n        }\n\n\n        public override bool Write(AppConfiguration config)\n        {\n#if NETFULL\n\t\t\tSqlDataAccess data = new SqlDataAccess(ConnectionString,ProviderName);\n#else\n\t\t\tSqlDataAccess data = new SqlDataAccess(ConnectionString, SqlClientFactory.Instance);\n#endif\n\n            string sql = String.Format(\n                \"Update [{0}] set ConfigData=@ConfigData where id={1}\", \n                Tablename, Key);\n            \n            string xml = WriteAsString(config);\n\n            int result = 0;\n            try\n            {\n                result = data.ExecuteNonQuery(sql, data.CreateParameter(\"@ConfigData\", xml));\n            }\n            catch\n            {\n                result = -1;\n            }\n\n            // try to create the table\n            if (result == -1)\n            {\n                sql = String.Format(\n            @\"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )\",\n            Tablename);\n                try\n                {\n                    result = data.ExecuteNonQuery(sql);\n                    if (result > -1)\n                        result = 0;\n                }\n                catch (Exception ex)\n                {\n                    SetError(ex);\n                    return false;\n                }\n            }\n\n            // Check for missing record\n            if (result == 0)\n            {\n                sql = \"Insert [\" + Tablename + \"] (id,configdata) values (\" + Key.ToString() + \",@ConfigData)\";\n\n                try\n                {\n                    result = data.ExecuteNonQuery(sql, data.CreateParameter(\"@ConfigData\", xml));\n                }\n                catch (Exception ex)\n                {\n                    SetError(ex);\n                    return false;\n                }\n                if (result == 0)\n                {                   \n                    return false;\n                }\n            }\n\n            if (result < 0)\n                return false;\n            \n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/ConnectionStringInfo.cs",
    "content": "using System;\nusing System.Configuration;\nusing System.Data.Common;\n\n#if NETCORE\n    using Microsoft.Data.SqlClient;\n#else\n    using System.Data.SqlClient;\n#endif\n\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Data\n{\n    /// <summary>\n    /// Used to parse a connection string or connection string name \n    /// into a the base connection  string and dbProvider.\n    /// \n    /// If a connection string is passed that's just used.\n    /// If a ConnectionString entry name is passed the connection \n    /// string is extracted and the provider parsed.\n    /// </summary>\n    public class ConnectionStringInfo\n    {\n        /// <summary>\n        /// The default connection string provider\n        /// </summary>\n        public static string DefaultProviderName = \"System.Data.SqlClient\";\n\n        /// <summary>\n        /// The connection string parsed\n        /// </summary>\n        public string ConnectionString { get; set; }\n\n        /// <summary>\n        /// The DbProviderFactory parsed from the connection string\n        /// or default provider\n        /// </summary>\n        public DbProviderFactory Provider { get; set; }\n\n\n        /// <summary>\n        /// Figures out the Provider and ConnectionString from either a connection string\n        /// name in a config file or full  ConnectionString and provider.         \n        /// </summary>\n        /// <param name=\"connectionString\">Config file connection name or full connection string</param>\n        /// <param name=\"providerName\">optional provider name. If not passed with a connection string is considered Sql Server</param>\n        /// <param name=\"factory\">optional provider factory. Use for .NET Core to pass actual provider instance since DbproviderFactories doesn't exist</param> \n        public static ConnectionStringInfo GetConnectionStringInfo(string connectionString, string providerName = null, DbProviderFactory factory = null)\n        {            \n            var info = new ConnectionStringInfo();\n\n            if (string.IsNullOrEmpty(connectionString))\n                throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor);\n\n            if (!connectionString.Contains(\"=\"))\n\t\t\t{\n#if NETFULL\n\t\t\t\tconnectionString = RetrieveConnectionStringFromConfig(connectionString, info);\n#else\n\t\t\t\tthrow new ArgumentException(\"Connection string names are not supported with .NET Standard. Please use a full connectionstring.\");\n#endif\n\t\t\t}\n\t\t\telse\n            {\n\t\t\t\tinfo.Provider = factory;\n\n\t\t\t\tif (factory == null)\n\t\t\t\t{\n\t\t\t\t\tif (providerName == null)\n\t\t\t\t\t\tproviderName = DefaultProviderName;\n\n                    // TODO: DbProviderFactories This should get fixed by release of .NET 2.0\n#if NETFULL\n\t\t\t\t\tinfo.Provider = DbProviderFactories.GetFactory(providerName);\n#else                    \n                    info.Provider = SqlClientFactory.Instance;\n#endif\n\t\t\t\t}\n            }\n\n            info.ConnectionString = connectionString;\n\n            return info;\n        }\n\n\n\n#if NETFULL\n\t\t/// <summary>\n\t\t/// Retrieves a connection string from the Connection Strings configuration settings\n\t\t/// </summary>\n\t\t/// <param name=\"connectionStringName\"></param>\n\t\t/// <param name=\"info\"></param>\n\t\t/// <exception cref=\"InvalidOperationException\">Throws when connection string doesn't exist</exception>\n\t\t/// <returns></returns>\n\t\tpublic static string RetrieveConnectionStringFromConfig(string connectionStringName, ConnectionStringInfo info)\n\t\t{\n\t\t\t// it's a connection string entry\n\t\t\tvar connInfo = ConfigurationManager.ConnectionStrings[connectionStringName];\n\t\t\tif (connInfo != null)\n\t\t\t{\n\t\t\t\tif (!string.IsNullOrEmpty(connInfo.ProviderName))\n\t\t\t\t\tinfo.Provider = DbProviderFactories.GetFactory(connInfo.ProviderName);\n\t\t\t\telse\n\t\t\t\t\tinfo.Provider = DbProviderFactories.GetFactory(DefaultProviderName);\n\n\t\t\t\tconnectionStringName = connInfo.ConnectionString;\n\t\t\t}\n\t\t\telse\n\t\t\t\tthrow new InvalidOperationException(Resources.InvalidConnectionStringName + \": \" + connectionStringName);\n\t\t\treturn connectionStringName;\n\t\t}\n#endif\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/DataAccessBase.cs",
    "content": "﻿#region License\n//#define SupportWebRequestProvider\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.Data;\nusing System.Data.Common;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Reflection;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing Westwind.Utilities.Properties;\n\n\n#if NETCORE\n    using Microsoft.Data.SqlClient;\n#else\n    using System.Data.SqlClient;\n#endif\n\nnamespace Westwind.Utilities.Data\n{\n    /// <summary>\n    /// Base Data Access Layer (DAL) for ADO.NET SQL operations.\n    /// Provides easy, single method operations to retrieve DataReader,\n    /// DataTable, DataSet and Entities, perform non-query operations,\n    /// call stored procedures.\n    /// \n    /// This abstract class implements most data operations using a\n    /// configured DbProvider. Subclasses implement specific database\n    /// providers and override a few methods that might have provider\n    /// specific SQL Syntax.\n    /// </summary>\n    [DebuggerDisplay(\"{ ErrorMessage } {ConnectionString} {LastSql}\")]\n    public abstract class DataAccessBase : IDisposable\n    {        \n\n\t\t/// <summary>\n\t\t/// Default constructor that should be called back to \n\t\t/// by subclasses. Parameterless assumes default provider\n\t\t/// and no connection string which must be explicitly set.\n\t\t/// </summary>\n\t\tprotected DataAccessBase()\n\t\t{\n\t\t    dbProvider = SqlClientFactory.Instance;\n        }\n\n        /// <summary>\n        /// Most common constructor that expects a connection string or \n        /// connection string name from a .config file. If a connection\n        /// string is provided the default provider is used.\n        /// </summary>\n        /// <param name=\"connectionString\"></param>\n        protected DataAccessBase(string connectionString)\n        {\n            if (string.IsNullOrEmpty(connectionString))\n                throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor);\n\n            // sets dbProvider and ConnectionString properties\n            // based on connectionString Name or full connection string\n            GetConnectionInfo(connectionString,null);            \n        }\n\n#if NETFULL\n\t\t/// <summary>\n\t\t/// Constructor that expects a full connection string and provider\n\t\t/// for creating a SQL instance. To be called by the same implementation\n\t\t/// on a subclass.\n\t\t/// </summary>\n\t\t/// <param name=\"connectionString\"></param>\n\t\t/// <param name=\"providerName\"></param>\n\t\tprotected DataAccessBase(string connectionString, string providerName)\n        {\n            ConnectionString = connectionString;\n            dbProvider = DbProviderFactories.GetFactory(providerName);\n        }\n#endif\n\n        /// <summary>\n        /// Constructor that expects a full connection string and provider\n        /// for creating a SQL instance. To be called by the same implementation\n        /// on a subclass.\n        /// </summary>\n        /// <param name=\"connectionString\"></param>\n        /// <param name=\"providerName\"></param>\n        protected DataAccessBase(string connectionString, DbProviderFactory provider)\n\t\t{\n\t\t\tConnectionString = connectionString;\n\t\t\tdbProvider = provider;\t\t\t\n\t\t}\n\n        /// <summary>\n        /// Holds the last Identity Key if running insert statements that return\n        /// the identity key (save entity)\n        /// </summary>\n        public object LastIdentityResult { get; set; }\n\n\n        /// <summary>\n        /// Command to retrieve the last identity key from the database - \n        /// appended to auto-generated insert commands\n        /// </summary>\n        public string GetIdentityKeySqlCommand { get; set; } = \"select SCOPE_IDENTITY()\";\n\n\n        /// <summary>\n        /// Create a DataAccess component with a specific database provider\n        /// </summary>\n        /// <param name=\"connectionString\"></param>\n        /// <param name=\"providerType\"></param>\n        public DataAccessBase(string connectionString, DataAccessProviderTypes providerType)\n        {\n            ConnectionString = connectionString;\n            DbProviderFactory instance = SqlUtils.GetDbProviderFactory(providerType);\n            dbProvider = instance ?? throw new InvalidOperationException(\"Can't load database provider: \" + providerType.ToString());\n        }\n\n\n\n\n        /// <summary>\n        /// Figures out the dbProvider and Connection string from a \n        /// connectionString name in a config file or explicit \n        /// ConnectionString and provider.         \n        /// </summary>\n        /// <param name=\"connectionString\">Config file connection name or full connection string</param>\n        /// <param name=\"providerName\">optional provider name. If not passed with a connection string is considered Sql Server</param>\n        public void GetConnectionInfo(string connectionString, string providerName = null)\n        {\n            // throws if connection string is invalid or missing\n            var connInfo = ConnectionStringInfo.GetConnectionStringInfo(connectionString, providerName);\n\n            ConnectionString = connInfo.ConnectionString;\n            dbProvider = connInfo.Provider;                       \n        }\n\n        /// <summary>\n        /// The internally used dbProvider\n        /// </summary>\n        public DbProviderFactory dbProvider = null;\n        \n        /// <summary>\n        /// An error message if a method fails\n        /// </summary>\n        public virtual string ErrorMessage { get; set; } = string.Empty;\n\n        /// <summary>\n        /// Optional error number returned by failed SQL commands\n        /// </summary>\n        public int ErrorNumber { get; set; } = 0;\n\n        public Exception ErrorException { get; set; }\n\n        public bool ThrowExceptions { get; set; } = false;\n\n\n        /// <summary>\n        /// The prefix used by the provider\n        /// </summary>\n        public string ParameterPrefix { get; set; } = \"@\";\n\n        /// <summary>\n        /// Determines whether parameters are positional or named. Positional\n        /// parameters are added without adding the name using just the ParameterPrefix\n        /// </summary>\n        public bool UsePositionalParameters { get; set; } = false;\n\n        /// <summary>\n        /// Character used for the left bracket on field names. Can be empty or null to use none\n        /// </summary>\n        public string LeftFieldBracket { get; set; } = \"[\";\n\n        /// <summary>\n        /// Character used for the right bracket on field names. Can be empty or null to use none\n        /// </summary>\n        public string RightFieldBracket { get; set; } = \"]\";\n\n        /// <summary>\n        /// ConnectionString for the data access component\n        /// </summary>\n        public virtual string ConnectionString { get; set; } = string.Empty;\n\n\n\n        /// <summary>\n        /// A SQL Transaction object that may be active. You can \n        /// also set this object explcitly\n        /// </summary>\n        public virtual DbTransaction Transaction { get; set; }\n\n\n        /// <summary>\n        /// The SQL Connection object used for connections\n        /// </summary>\n        public virtual DbConnection Connection\n        {\n            get { return _Connection; }\n            set { _Connection = value; }\n        }\n        protected DbConnection _Connection = null;\n\n        /// <summary>\n        /// The Sql Command execution Timeout in seconds.\n        /// Set to -1 for whatever the system default is.\n        /// Set to 0 to never timeout (not recommended).\n        /// </summary>\n        public int Timeout { get; set; } = -1;\n\n\n        /// <summary>\n        /// Determines whether extended schema information is returned for \n        /// queries from the server. Useful if schema needs to be returned\n        /// as part of DataSet XML creation \n        /// </summary>\n        public virtual bool ExecuteWithSchema { get; set; } = false;\n\n        /// <summary>\n        /// Holds the last SQL string executed\n        /// </summary>\n        public string LastSql { get; set; }\n\n#region Connection Operations\n        /// <summary>\n        /// Opens a Sql Connection based on the connection string.\n        /// Called internally but externally accessible. Sets the internal\n        /// _Connection property.\n        /// </summary>\n        /// <returns></returns>\n        /// <summary>\n        /// Opens a Sql Connection based on the connection string.\n        /// Called internally but externally accessible. Sets the internal\n        /// _Connection property.\n        /// </summary>\n        /// <returns></returns>\n        public virtual bool OpenConnection()\n        {\n            try\n            {\n                if (_Connection == null)\n                {\n                    if (ConnectionString.Contains(\"=\"))\n                    {\n                        _Connection = dbProvider.CreateConnection();\n                        _Connection.ConnectionString = ConnectionString;                        \n                    }\n                    else\n                    {\n                        var connInfo = ConnectionStringInfo.GetConnectionStringInfo(ConnectionString);\n                        if (connInfo == null)\n                        {\n                            SetError(Resources.InvalidConnectionString);\n\n                            if (ThrowExceptions)\n                                throw new ApplicationException(ErrorMessage);\n\n                            return false;\n                        }\n\n                        dbProvider = connInfo.Provider;\n                        ConnectionString = connInfo.ConnectionString;\n                        \n                        _Connection = dbProvider.CreateConnection();\n                        _Connection.ConnectionString = ConnectionString;\n                    }\n                }\n                \n                if (_Connection.State != ConnectionState.Open)\n                    _Connection.Open();\n            }\n            catch (SqlException ex)\n            {              \n                SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message));\n                ErrorException = ex;\n                return false;\n            }\n            catch (DbException ex)\n            {\n                SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message));\n                ErrorException = ex;\n                return false;\n            }\n            catch (Exception ex)\n            {\n                SetError(string.Format(Resources.ConnectionOpeningFailure, ex.GetBaseException().Message));\n                ErrorException = ex;\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Closes an active connection. If a transaction is pending the \n        /// connection is held open.\n        /// </summary>\n        public virtual void CloseConnection()\n        {\n            if (Transaction != null)\n                return;\n\n            if (_Connection != null &&\n                _Connection.State != ConnectionState.Closed)\n                _Connection.Close();\n\n            _Connection = null;\n        }\n\n\n        /// <summary>\n        /// Closes a connection on a command\n        /// </summary>\n        /// <param name=\"Command\"></param>\n        public virtual void CloseConnection(DbCommand Command)\n        {\n            if (Transaction != null)\n                return;\n\n            if (Command.Connection != null &&\n                Command.Connection.State != ConnectionState.Closed)\n                Command.Connection.Close();\n            \n            _Connection = null;\n        }\n\n#endregion\n\n#region Core Operations\n\n        /// <summary>\n        /// Creates a Command object and opens a connection\n        /// </summary>        \n        /// <param name=\"sql\">Sql string to execute</param>\n        /// <param name=\"parameters\">Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter()</param>\n        /// <returns>Command object or null on error</returns>\n        public virtual DbCommand CreateCommand(string sql, CommandType commandType, params object[] parameters)\n        {\n            SetError();\n\n            DbCommand command = dbProvider.CreateCommand();\n            command.CommandType = commandType;\n            command.CommandText = sql;\n            if (Timeout > -1)\n                command.CommandTimeout = Timeout;\n\n            try\n            {\n                if (Transaction != null)\n                {\n                    command.Transaction = Transaction;\n                    command.Connection = Transaction.Connection;\n                }\n                else\n                {\n                    if (!OpenConnection())\n                        return null;\n\n                    command.Connection = _Connection;\n                }\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n\n            if (parameters != null)\n                AddParameters(command,parameters);\n            \n\n            return command;\n        }\n\n        /// <summary>\n        /// Creates a Command object and opens a connection\n        /// </summary>        \n        /// <param name=\"sql\">Sql string to execute</param>\n        /// <param name=\"parameters\">Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter()</param>\n        /// <returns>command object</returns>\n        public virtual DbCommand CreateCommand(string sql, params object[] parameters)\n        {\n            return CreateCommand(sql, CommandType.Text, parameters);\n        }\n\n        /// <summary>\n        /// Adds parameters to a DbCommand instance. Parses value and DbParameter parameters\n        /// properly into the command's Parameters collection.\n        /// </summary>\n        /// <param name=\"command\">A preconfigured DbCommand object that should have all connection information set</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        protected void AddParameters(DbCommand command, object[] parameters)\n        {\n            if (parameters != null && parameters.Length > 0)\n            {          \n                // check for anonymous type\n                if (parameters.Length == 1 &&  ReflectionUtils.IsAnonoymousType(parameters[0]))\n                {\n                    ParseObjectParameters(command, parameters[0]);\n                    return;\n                }\n\n                var parmCount = 0;\n                foreach (var parameter in parameters)\n                {\n                    if (parameter is DbParameter)\n                        command.Parameters.Add(parameter);\n                    else\n                    {                        \n                        var parm = CreateParameter(ParameterPrefix + parmCount, parameter);\n                        command.Parameters.Add(parm);\n                        parmCount++;                        \n                    }\n                }\n            }\n\n        }\n\n        /// <summary>\n        /// Parses an anonymous object map into a set of DbParameters\n        /// </summary>\n        /// <param name=\"\"></param>\n        /// <param name=\"parameter\"></param>\n        /// <exception cref=\"NotImplementedException\"></exception>\n        public DbParameterCollection ParseObjectParameters(DbCommand command, object parameter)\n        {\n            var props = parameter.GetType().GetProperties();\n\n            var parmCount = 0;\n            foreach (var prop in props)\n            {\n                object value = prop.GetValue(parameter, null);\n                if (value is DbParameter)\n                    command.Parameters.Add(value);\n                else\n                {\n                    var parm = CreateParameter(ParameterPrefix + prop.Name, value);\n                    command.Parameters.Add(parm);\n                    parmCount++;\n                }\n            }\n\n            return command.Parameters;\n        }\n        \n\n        /// <summary>\n        /// Used to create named parameters to pass to commands or the various\n        /// methods of this class.\n        /// </summary>\n        /// <param name=\"parameterName\"></param>\n        /// <param name=\"value\"></param>\n        /// <param name=\"dbType\"></param>\n        /// <returns></returns>\n        public virtual DbParameter CreateParameter(string parameterName, object value)\n        {\n            DbParameter parm = dbProvider.CreateParameter();\n            parm.ParameterName = parameterName;\n            if (value == null)\n                value = DBNull.Value;\n            parm.Value = value;\n            return parm;\n        }\n\n\n        /// <summary>\n        /// Used to create named parameters to pass to commands or the various\n        /// methods of this class.\n        /// </summary>\n        /// <param name=\"parameterName\"></param>\n        /// <param name=\"value\"></param>\n        /// <param name=\"dbType\"></param>\n        /// <returns></returns>\n        public virtual DbParameter CreateParameter(string parameterName, object value, ParameterDirection parameterDirection = ParameterDirection.Input)\n        {\n            DbParameter parm = CreateParameter(parameterName, value);\n            parm.Direction = parameterDirection;\n            return parm;\n        }\n\n        /// <summary>\n        /// Used to create named parameters to pass to commands or the various\n        /// methods of this class.\n        /// </summary>\n        /// <param name=\"parameterName\"></param>\n        /// <param name=\"value\"></param>\n        /// <param name=\"size\"></param>\n        /// <returns></returns>\n        public virtual DbParameter CreateParameter(string parameterName, object value, int size)\n        {\n            DbParameter parm = CreateParameter(parameterName, value);\n            parm.Size = size;\n            return parm;\n        }\n\n        /// <summary>\n        /// Used to create named parameters to pass to commands or the various\n        /// methods of this class.\n        /// </summary>\n        /// <param name=\"parameterName\"></param>\n        /// <param name=\"value\"></param>\n        /// <param name=\"dbType\"></param>\n        /// <returns></returns>\n        public virtual DbParameter CreateParameter(string parameterName, object value, DbType type)\n        {\n            DbParameter parm = CreateParameter(parameterName, value);\n            parm.DbType = type;\n            return parm;\n        }\n\n        /// <summary>\n        /// Used to create named parameters to pass to commands or the various\n        /// methods of this class.\n        /// </summary>\n        /// <param name=\"parameterName\"></param>\n        /// <param name=\"value\"></param>\n        /// <param name=\"type\"></param>\n        /// <param name=\"size\"></param>\n        /// <returns></returns>\n        public virtual DbParameter CreateParameter(string parameterName, object value, DbType type, int size)\n        {\n            DbParameter parm = CreateParameter(parameterName, value);\n            parm.DbType = type;\n            parm.Size = size;\n            return parm;\n        }\n\n        #endregion\n\n        #region Transactions\n        /// <summary>\n        /// Starts a new transaction on this connection/instance\n        /// </summary>\n        /// <remarks>Opens a Connection and keeps it open for the duration of the transaction. Calls to `.CloseConnection` while the transaction is active have no effect.</remarks>\n        /// <returns></returns>\n        public virtual bool BeginTransaction()\n        {\n            if (_Connection == null)\n            {\n                if (!OpenConnection())\n                    return false;\n            }            \n\n            Transaction = _Connection.BeginTransaction();\n            if (Transaction == null)\n                return false;\n\n            return true;\n        }\n\n        /// <summary>\n        /// Commits all changes to the database and ends the transaction\n        /// </summary>\n        /// <remarks>Closes Connection</remarks>\n        /// <returns></returns>\n        public virtual bool CommitTransaction()\n        {\n            if (Transaction == null)\n            {\n                SetError(Resources.NoActiveTransactionToCommit);\n                if (ThrowExceptions)\n                    new InvalidOperationException(Resources.NoActiveTransactionToCommit);\n                return false;\n            }\n\n            Transaction.Commit();\n            Transaction = null;\n\n            CloseConnection();\n\n            return true;\n        }\n\n        /// <summary>\n        /// Rolls back a transaction\n        /// </summary>\n        /// <remarks>Closes Connection</remarks>\n        /// <returns></returns>\n        public virtual bool RollbackTransaction()\n        {\n            if (Transaction == null)\n                return true;\n\n            Transaction.Rollback();\n            Transaction = null;\n\n            CloseConnection();\n\n            return true;\n        }\n\n        #endregion\n\n        #region Non-list Sql Commands \n        /// <summary>\n        /// Executes a non-query command and returns the affected records\n        /// </summary>\n        /// <param name=\"Command\">Command should be created with GetSqlCommand to have open connection</param>       \n        /// <returns>Affected Record count or -1 on error</returns>\n        public virtual int ExecuteNonQuery(DbCommand Command)\n        {\n            SetError();\n\n            int RecordCount = 0;\n\n            try\n            {\n                LastSql = Command.CommandText;\n\n                RecordCount = Command.ExecuteNonQuery();\n                if (RecordCount == -1)\n                    RecordCount = 0;\n            }\n            catch (DbException ex)\n            {\n                RecordCount = -1;\n                SetError(ex);;\n            }\n            catch (Exception ex)\n            {\n                RecordCount = -1;\n                SetError(ex);\n            }\n            finally\n            {\n                CloseConnection();\n            }\n\n            return RecordCount;\n        }\n\n\n        /// <summary>\n        /// Executes a command that doesn't return any data. The result\n        /// returns the number of records affected or -1 on error.\n        /// </summary>\n        /// <param name=\"sql\">SQL statement as a string</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// or new { parm1=value, parm2 = value2}\n        /// </param>\n        /// <returns></returns>\n        /// <summary>\n        /// Executes a command that doesn't return a data result. You can return\n        /// output parameters and you do receive an AffectedRecords counter.\n        /// .setItem(\"list_html\", JSON.stringify(data));\n        /// </summary>        \n        public virtual int ExecuteNonQuery(string sql, params object[] parameters)\n        {\n            DbCommand command = CreateCommand(sql,parameters);\n            if (command == null)\n                return -1;\n\n            return ExecuteNonQuery(command);\n        }\n\n        \n        /// <summary>\n        /// Executes a non-query command and returns the affected records\n        /// </summary>\n        /// <param name=\"Command\">Command should be created with GetSqlCommand to have open connection</param>       \n        /// <returns>Affected Record count or -1 on error</returns>\n        public virtual async Task<int> ExecuteNonQueryAsync(DbCommand Command)\n        {\n            SetError();\n\n            int RecordCount = 0;\n\n            try\n            {\n                LastSql = Command.CommandText;\n\n                RecordCount = await Command.ExecuteNonQueryAsync();\n                if (RecordCount == -1)\n                    RecordCount = 0;\n            }\n            catch (DbException ex)\n            {\n                RecordCount = -1;\n                SetError(ex); ;\n            }\n            catch (Exception ex)\n            {\n                RecordCount = -1;\n                SetError(ex);\n            }\n            finally\n            {\n                CloseConnection();\n            }\n\n            return RecordCount;\n        }\n\n\n        /// <summary>\n        /// Executes a command that doesn't return any data. The result\n        /// returns the number of records affected or -1 on error.\n        /// </summary>\n        /// <param name=\"sql\">SQL statement as a string</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        /// <summary>\n        /// Executes a command that doesn't return a data result. You can return\n        /// output parameters and you do receive an AffectedRecords counter.\n        /// .setItem(\"list_html\", JSON.stringify(data));\n        /// </summary>        \n        public virtual async Task<int> ExecuteNonQueryAsync(string sql, params object[] parameters)\n        {\n            DbCommand command = CreateCommand(sql, parameters);\n            if (command == null)\n                return -1;\n\n            int result = await ExecuteNonQueryAsync(command);\n            return result;\n        }\n\n\n        /// <summary>\n        /// Executes a command and returns a scalar value from it\n        /// </summary>\n        /// <param name=\"command\">DbCommand containing command to run</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>value or null on failure</returns>        \n        public virtual object ExecuteScalar(DbCommand command, params object[] parameters)\n        {\n            SetError();\n\n            AddParameters(command, parameters);\n\n            object Result = null;\n            try\n            {\n                LastSql = command.CommandText;\n                Result = command.ExecuteScalar();\n            }\n            catch (Exception ex)\n            {\n                SetError(ex.GetBaseException());\n            }\n            finally\n            {\n                CloseConnection();\n            }\n\n            return Result;\n        }\n        /// <summary>\n        /// Executes a Sql command and returns a single value from it.\n        /// </summary>\n        /// <param name=\"Sql\">Sql string to execute</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>Result value or null. Check ErrorMessage on Null if unexpected</returns>\n        public virtual object ExecuteScalar(string sql, params object[] parameters)\n        {\n            SetError();\n\n            DbCommand command = CreateCommand(sql, parameters);\n            if (command == null)\n                return null;\n\n            return ExecuteScalar(command, null);\n        }\n\n\n        /// <summary>\n        /// Executes a command and returns a scalar value from it\n        /// </summary>\n        /// <param name=\"command\">DbCommand containing command to run</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>value or null on failure</returns>        \n        public virtual async Task<object> ExecuteScalarAsync(DbCommand command, params object[] parameters)\n        {\n            SetError();\n\n            AddParameters(command, parameters);\n\n            object Result = null;\n            try\n            {\n                LastSql = command.CommandText;\n                Result = await command.ExecuteScalarAsync();\n            }\n            catch (Exception ex)\n            {\n                SetError(ex.GetBaseException());\n            }\n            finally\n            {\n                CloseConnection();\n            }\n\n            return Result;\n        }\n\n        /// <summary>\n        /// Executes a Sql command and returns a single value from it.\n        /// </summary>\n        /// <param name=\"Sql\">Sql string to execute</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>Result value or null. Check ErrorMessage on Null if unexpected</returns>\n        public virtual async Task<object> ExecuteScalarAsync(string sql, params object[] parameters)\n        {\n            SetError();\n\n            DbCommand command = CreateCommand(sql, parameters);\n            if (command == null)\n                return null;\n\n            return await ExecuteScalarAsync(command, null);\n        }\n\n        /// <summary>\n        /// Executes a long SQL script that contains batches (GO commands). This code\n        /// breaks the script into individual commands and captures all execution errors.\n        /// \n        /// If ContinueOnError is false, operations are run inside of a transaction and\n        /// changes are rolled back. If true commands are accepted even if failures occur\n        /// and are not rolled back.\n        /// </summary>\n        /// <param name=\"script\"></param>\n        /// <param name=\"continueOnError\"></param>\n        /// <param name=\"scriptIsFile\"></param>\n        /// <returns></returns>\n        public bool RunSqlScript(string script, bool continueOnError = false, bool scriptIsFile = false)\n        {\n            SetError();\n\n            if (scriptIsFile)\n            {\n                try\n                {\n                    script = File.ReadAllText(script);\n                }\n                catch (Exception ex)\n                {\n                    SetError(ex.GetBaseException());\n                    return false;\n                }\n            }\n\n            // Normalize line endings to \\n\n            string scriptNormal = script.Replace(\"\\r\\n\", \"\\n\").Replace(\"\\r\", \"\\n\");\n            string[] scriptBlocks = Regex.Split(scriptNormal + \"\\n\", \"GO\\n\");\n\n            string errors = \"\";\n\n            if (!continueOnError)\n                BeginTransaction();\n\n            foreach (string block in scriptBlocks)\n            {\n                if (string.IsNullOrEmpty(block.TrimEnd()))\n                    continue;\n\n                if (ExecuteNonQuery(block) == -1)\n                {\n                    errors = ErrorMessage +  block;\n                    if (!continueOnError)\n                    {\n                        RollbackTransaction();\n                        return false;\n                    }\n                }\n            }\n\n            if (!continueOnError)\n                CommitTransaction();\n\n            if (string.IsNullOrEmpty(errors))\n                return true;\n\n            ErrorMessage = errors;\n            return false;\n        }\n\n\n        /// <summary>\n        /// Determines whether a table exists\n        /// </summary>\n        /// <param name=\"tablename\"></param>\n        /// <param name=\"schema\"></param>\n        /// <returns></returns>\n        public virtual bool DoesTableExist(string tablename, string schema = null)\n        {\n            var sql = @\"SELECT TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @0\";\n\n            if (!string.IsNullOrEmpty(schema))\n                sql += \" AND TABLE_SCHEMA = @1\";\n\n            var cat = ExecuteScalar(sql, tablename, schema);\n\n            return !(cat is null);\n        }\n\n        #endregion \n\n        #region Sql Execution\n\n        /// <summary>\n        /// Executes a SQL Command object and returns a SqlDataReader object\n        /// </summary>\n        /// <param name=\"command\">Command should be created with GetSqlCommand and open connection</param>\n        /// <param name=\"parameters\"></param>\n        /// <returns></returns>\n        /// <returns>A SqlDataReader. Make sure to call Close() to close the underlying connection.</returns>\n        //public abstract DbDataReader ExecuteReader(DbCommand Command, params DbParameter[] Parameters)\n        public virtual DbDataReader ExecuteReader(DbCommand command, params object[] parameters)\n        {\n            SetError();\n\n            if (command.Connection == null || command.Connection.State != ConnectionState.Open)\n            {\n                if (!OpenConnection())\n                    return null;\n\n                command.Connection = _Connection;\n            }\n\n            AddParameters(command, parameters);\n\n            DbDataReader Reader = null;\n            try\n            {\n                LastSql = command.CommandText;\n                Reader = command.ExecuteReader();\n            }\n            catch (Exception ex)\n            {\n                SetError(ex.GetBaseException());\n                CloseConnection(command);\n                return null;\n            }\n\n            return Reader;\n        }\n\n        /// <summary>\n        /// Executes a SQL command against the server and returns a DbDataReader\n        /// </summary>\n        /// <param name=\"sql\">Sql String</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual DbDataReader ExecuteReader(string sql, params object[] parameters)\n        {\n            DbCommand command = CreateCommand(sql, parameters);\n            if (command == null)\n                return null;\n\n            return ExecuteReader(command);\n        }\n\n\n\t    /// <summary>\n\t    /// Executes a Sql statement and returns a dynamic DataReader instance \n\t    /// that exposes each field as a property\n\t    /// </summary>\n\t    /// <param name=\"sql\">Sql String to executeTable</param>\n\t    /// <param name=\"parameters\">\n\t    /// DbParameters (CreateParameter()) for named parameters\n\t    /// or use @0,@1 parms in SQL and plain values\n\t    /// </param>\n\t    /// <returns></returns>\n\t    public virtual dynamic ExecuteDynamicDataReader(string sql, params object[] parameters)\n\t    {\n\t\t    var reader = ExecuteReader(sql, parameters);\n\t\t    return new DynamicDataReader(reader);\n\t    }\n\n\t    /// <summary>\n\t    /// Return a list of entities that are matched to an object\n\t    /// </summary>\n\t    /// <typeparam name=\"T\">Type of object to create from data record</typeparam>\n\t    /// <param name=\"sql\">Sql string</param>\n\t    /// <param name=\"parameters\">\n\t    ///  DbParameters (CreateParameter()) for named parameters\n\t    ///  or use @0,@1 parms in SQL and plain values\n\t    /// </param> \n\t    /// <returns>An enumerated list of objects or null</returns>\n\t    [Obsolete(\"Use the Query method instead with the same syntax\")]\n\t    public virtual IEnumerable<T> ExecuteReader<T>(string sql, params object[] parameters)\n\t\t    where T: class, new()\n\t    {\n\t\t    return Query<T>(sql, null, parameters);\n\t    }\n\n\t    /// <summary>\n\t    /// Allows querying and return a list of entities.\n\t    /// </summary>\t    \n\t    /// <typeparam name=\"T\"></typeparam>\n\t    /// <param name=\"command\"></param>\n\t    /// <param name=\"parameters\">\n\t    /// DbParameters (CreateParameter()) for named parameters\n\t    ///  or use @0,@1 parms in SQL and plain values\n\t    /// </param>\n\t    /// <returns></returns>\n\t    [Obsolete(\"Use the Query method instead with the same syntax\")]\n\t    public virtual IEnumerable<T> ExecuteReader<T>(DbCommand command, params object[] parameters)\n\t\t    where T : class, new()\n\t    {\n\t\t    return Query<T>(command, parameters);\n\t    }\n\n        /// <summary>\n        /// Executes a SQL statement and creates an object list using\n        /// optimized Reflection.\n        /// \n        /// Not very efficient but provides an easy way to retrieve \n        /// an object list from query.\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to create from DataReader data</typeparam>\n        /// <param name=\"sql\">Sql string to execute</param>        \n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>List of objects or null. Null is returned if there are no matches</returns>       \n        public virtual IEnumerable<T> Query<T>(string sql, params object[] parameters)            \n            where T : class, new()\n        {\n            var reader = ExecuteReader(sql, parameters);\n            \n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToIEnumerable<T>(reader, null);                \n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }                            \n        }\n\n        /// <summary>\n        /// Returns list of objects from a query.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"sql\"></param>\n        /// <param name=\"parameters\"></param>\n        /// <returns></returns>\n        public virtual List<T> QueryList<T>(string sql, params object[] parameters)\n                where T : class, new()\n        {\n            var reader = ExecuteReader(sql, parameters);\n\n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToObjectList<T>(reader,null);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n\n        /// <summary>\n        /// Returns list of objects from a query.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"sql\"></param>\n        /// <param name=\"parameters\"></param>\n        /// <returns></returns>\n        public virtual List<T> QueryList<T>(DbCommand command, params object[] parameters)\n            where T : class, new()\n        {\n            var reader = ExecuteReader(command, parameters);\n\n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToObjectList<T>(reader, null);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Returns list of objects from a query.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"sql\">Sql Statement string</param>\n        /// <param name=\"propertiesToSkip\">Comma delimited list of property names to skip</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual List<T> QueryListWithExclusions<T>(string sql, string propertiesToSkip, params object[] parameters)\n                where T : class, new()\n        {\n            var reader = ExecuteReader(sql, parameters);\n\n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToObjectList<T>(reader, propertiesToSkip);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Returns list of objects from a query.\n        /// </summary>\n        /// <typeparam name=\"T\"></typeparam>\n        /// <param name=\"sql\">Sql Statement string</param>\n        /// <param name=\"propertiesToSkip\">Comma delimited list of property names to skip</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual List<T> QueryListWithExclusions<T>(DbCommand command, string propertiesToSkip, params object[] parameters)\n            where T : class, new()\n        {\n            var reader = ExecuteReader(command, parameters);\n\n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToObjectList<T>(reader, propertiesToSkip);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Executes a SQL command and creates an object list using\n        /// Reflection.\n        /// \n        /// Not very efficient but provides an easy way to retrieve\n        /// object lists from queries.\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to create from DataReader data</typeparam>\n        /// <param name=\"command\">Command object containing configured SQL command to execute</param>        \n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>List of objects or null. Null is returned if there are no matches</returns>   \n        public virtual IEnumerable<T> Query<T>(DbCommand command, params object[] parameters)\n            where T : class, new()\n        {\n            var reader = ExecuteReader(command, parameters);\n\n            if (reader == null)\n                return null;\n\n            try\n            {\n                return DataUtils.DataReaderToIEnumerable<T>(reader, null);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n        /// <summary>\n        /// Executes a SQL statement and creates an object list using\n        /// Reflection.\n        /// \n        /// Not very efficient but provides an easy way to retrieve\n        /// object lists from queries\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to create from DataReader data</typeparam>\n        /// <param name=\"sql\">Sql string to execute</param>        \n        /// <param name=\"propertiesToExclude\">Comma delimited list of properties that are not to be updated</param>\n        /// <param name=\"parameters\">\n        ///  DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns>List of objects</returns>        \n        public virtual IEnumerable<T> QueryWithExclusions<T>(string sql, string propertiesToExclude, params object[] parameters)            \n            where T: class, new()\n        {\n            IEnumerable<T> result;\n\n            var reader = ExecuteReader(sql, parameters);\n            \n            if (reader == null)\n                return null;\n\n            try\n            {\n                result = DataUtils.DataReaderToIEnumerable<T>(reader, propertiesToExclude);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n            \n            return result;\n        }\n\n\n        /// <summary>\n        /// Executes a SQL statement and creates an object list using\n        /// Reflection.\n        /// \n        /// Not very efficient but provides an easy way to retrieve\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to create from DataReader data</typeparam>\n        /// <param name=\"sql\">Sql string to execute</param>        \n        /// <param name=\"parameters\">DbParameters to fill the SQL statement</param>\n        /// <returns>List of objects</returns>\n        public virtual IEnumerable<T> QueryWithExclusions<T>(DbCommand sqlCommand, string propertiesToExclude, params object[] parameters)\n            where T : class, new()\n        {\n            var reader = ExecuteReader(sqlCommand, parameters);\n\n            try\n            {\n                return DataUtils.DataReaderToIEnumerable<T>(reader, propertiesToExclude);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n        }\n\n\n        /// <summary>\n        /// Calls a stored procedure that returns a cursor results\n        /// The result is returned as a DataReader\n        /// </summary>\n        /// <param name=\"storedProc\">Name of the Stored Procedure to call</param>\n        /// <param name=\"parameters\">\n        /// Parameters to pass. Note that if you need to pass out/inout/return parameters\n        /// you need to pass DbParameter instances or use the CreateParameter() method\n        /// </param>\n        /// <returns>A DataReader or null on failure</returns>\n        public virtual DbDataReader ExecuteStoredProcedureReader(string storedProc, params object[] parameters)\n        {\n            var command = CreateCommand(storedProc, parameters);\n            if (command == null)\n                return null;\n           \n            command.CommandType = CommandType.StoredProcedure;\n\n            return ExecuteReader(command);\n        }\n\n\n\t\t/// <summary>\n\t\t/// Calls a stored procedure that returns a cursor results\n\t\t/// The result is returned as an IEnumerable&lt;T&gt;> list\n\t\t/// </summary>\n\t\t/// <example>\n\t\t///\tIEnumerable&lt;Customer%gt; customers = context.Db.ExecuteStoredProcedureReader&lt;Customer&gt;('GetCustomers',         \n\t\t///              context.Db.CreateParameter('@cCompany','W%'));\n\t\t/// </example>\n\t\t/// <param name=\"storedProc\">Name of the Stored Procedure to call</param>\n\t\t/// <param name=\"parameters\">\n\t\t/// Use CreateParameter() for named, output or return parameters. Plain values for others.\n\t\t/// </param>\n\t\t/// <returns>A DataReader or null on failure</returns>\n\t\tpublic virtual IEnumerable<T> ExecuteStoredProcedureReader<T>(string storedProc, params object[] parameters)\n            where T : class, new()\n        {\n            var command = CreateCommand(storedProc, parameters);\n            if (command == null)\n                return null;\n\n            command.CommandType = CommandType.StoredProcedure;\n\n            return Query<T>(command,null);\n        }\n\n        /// <summary>\n        /// Executes a stored procedure that doesn't return a result set.\n        /// </summary>\n        /// <param name=\"storedProc\">The Stored Procedure to call</param>\n        /// <param name=\"parameters\">\n        /// Parameters to pass. Note that if you need to pass out/inout/return parameters\n        /// you need to pass DbParameter instances or use the CreateParameter() method\n        /// </param>\n        /// <returns>> 0 or greater on success, -1 on failure</returns>\n        public virtual int ExecuteStoredProcedureNonQuery(string storedProc, params object[] parameters)\n        {\n            var command = CreateCommand(storedProc, parameters);\n            if (command == null)\n                return -1;\n\n            command.CommandType = CommandType.StoredProcedure;\n\n            return ExecuteNonQuery(command);\n        }\n\n\n\t    /// <summary>\n        /// Returns a DataTable from a Sql Command string passed in.\n        /// </summary>\n        /// <param name=\"tablename\"></param>\n        /// <param name=\"command\"></param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual DataTable ExecuteTable(string tablename, DbCommand command, params object[] parameters)\n        {\n\t        SetError();\n\n            AddParameters(command, parameters);\n\n            DbDataAdapter adapter = dbProvider.CreateDataAdapter();\n\t        if (adapter == null)\n\t        {\n\t\t        SetError(\"Failed to create data adapter.\");\n\t\t\t\treturn null;\n\t        }\n\t        adapter.SelectCommand = command;\n\t\t\t\n\t\t\tLastSql = command.CommandText;\n\n            DataTable dt = new DataTable(tablename);\n\n            try\n            {\n                adapter.Fill(dt);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex.GetBaseException());\n                return null;\n            }\n            finally\n            {\n                CloseConnection(command);\n            }\n\n            return dt;\n        }\n\n        /// <summary>\n        /// Returns a DataTable from a Sql Command string passed in.\n        /// </summary>\n        /// <param name=\"Tablename\"></param>\n        /// <param name=\"ConnectionString\"></param>\n        /// <param name=\"Sql\"></param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual DataTable ExecuteTable(string Tablename, string Sql, params object[] Parameters)\n        {\n            SetError();\n\n            DbCommand command = CreateCommand(Sql, Parameters);\n            if (command == null)\n                return null;\n\n            return ExecuteTable(Tablename, command);\n        }\n\n\n        /// <summary>\n        /// Returns a DataSet/DataTable from a Sql Command string passed in. \n        /// </summary>\n        /// <param name=\"Tablename\">The name for the table generated or the base names</param>\n        /// <param name=\"Command\"></param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        ///  or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual DataSet ExecuteDataSet(string Tablename, DbCommand Command, params object[] Parameters)\n        {\n            return ExecuteDataSet(null, Tablename, Command, Parameters);\n        }\n\n        /// <summary>\n        /// Executes a SQL command against the server and returns a DataSet of the result\n        /// </summary>\n        /// <param name=\"command\"></param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual DataSet ExecuteDataSet(string tablename, string sql, params object[] parameters)\n        {\n            return ExecuteDataSet(tablename, CreateCommand(sql), parameters);\n        }\n\n\n        /// <summary>\n        /// Returns a DataSet from a Sql Command string passed in.\n        /// </summary>\n        /// <param name=\"tableName\"></param>\n        /// <param name=\"command\"></param>\n        /// <param name=\"parameters\"></param>\n        /// <returns></returns>        \n        public virtual DataSet ExecuteDataSet(DataSet dataSet, string tableName, DbCommand command, params object[] parameters)\n        {\n            SetError();\n\n            if (dataSet == null)\n                dataSet = new DataSet();\n\n            DbDataAdapter adapter = dbProvider.CreateDataAdapter();\n            adapter.SelectCommand = command;\n            LastSql = command.CommandText;\n\n\n            if (ExecuteWithSchema)\n                adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;\n\n            AddParameters(command, parameters);\n\n            DataTable dt = new DataTable(tableName);\n\n            if (dataSet.Tables.Contains(tableName))\n                dataSet.Tables.Remove(tableName);\n\n            try\n            {\n                adapter.Fill(dataSet, tableName);\n            }\n            catch (Exception ex)\n            {\n                SetError(ex);\n                return null;\n            }\n            finally\n            {\n                CloseConnection(command);\n            }\n\n            return dataSet;\n        }\n\n        /// <summary>\n        /// Returns a DataTable from a Sql Command string passed in.\n        /// </summary>\n        /// <param name=\"tablename\"></param>\n        /// <param name=\"Command\"></param>\n        /// <param name=\"parameters\"></param>\n        /// <returns></returns>\n        public virtual DataSet ExecuteDataSet(DataSet dataSet, string tablename, string sql, params object[] parameters)\n        {\n            DbCommand Command = CreateCommand(sql, parameters);\n            if (Command == null)\n                return null;\n\n            return ExecuteDataSet(dataSet, tablename, Command);\n        }\n\n\n        /// <summary>\n        /// Sql 2005 specific semi-generic paging routine\n        /// </summary>\n        /// <param name=\"sql\"></param>\n        /// <param name=\"pageSize\"></param>\n        /// <param name=\"page\"></param>\n        /// <param name=\"sortOrderFields\"></param>\n        /// <param name=\"Parameters\"></param>\n        /// <returns></returns>\n        public virtual DbCommand CreatePagingCommand(string sql, int pageSize, int page, string sortOrderFields, params object[] Parameters)\n        {\n            int pos = sql.IndexOf(\"select \", 0, StringComparison.OrdinalIgnoreCase);\n            if (pos == -1)\n            {\n                SetError(\"Invalid Command for paging. Must start with select and followed by field list\");\n                return null;\n            }\n            sql = StringUtils.ReplaceStringInstance(sql, \"select\", string.Empty, 1, true);\n\n            string newSql = string.Format(\n            @\"\nselect * FROM \n   (SELECT ROW_NUMBER() OVER (ORDER BY @OrderByFields) as __No,{0}) __TQuery\nwhere __No > (@Page-1) * @PageSize and __No < (@Page * @PageSize + 1)\n\", sql);\n\n            return CreateCommand(newSql,\n                            CreateParameter(\"@PageSize\", pageSize),\n                            CreateParameter(\"@Page\", page),\n                            CreateParameter(\"@OrderByFields\", sortOrderFields));\n\n        }\n\n        #endregion\n\n#region Generic Entity features\n        /// <summary>\n        /// Generic routine to retrieve an object from a database record\n        /// The object properties must match the database fields.\n        /// </summary>\n        /// <param name=\"entity\">The object to update</param>\n        /// <param name=\"command\">Database command object</param>\n        /// <param name=\"propertiesToSkip\"></param>\n        /// <returns></returns>\n        public virtual bool GetEntity(object entity, DbCommand command, string propertiesToSkip = null)\n        {\n            SetError();\n\n            if (string.IsNullOrEmpty(propertiesToSkip))\n                propertiesToSkip = string.Empty;\n\n            DbDataReader reader = ExecuteReader(command);\n            if (reader == null)\n                return false;\n\n            if (!reader.Read())\n            {\n                reader.Close();\n                CloseConnection(command);\n                return false;\n            }\n\n            DataUtils.DataReaderToObject(reader, entity, propertiesToSkip);\n\n            reader.Close();\n            CloseConnection();\n\n            return true;\n        }\n\n        /// <summary>\n        /// Retrieves a single record and returns it as an entity\n        /// </summary>\n        /// <param name=\"entity\"></param>\n        /// <param name=\"sql\"></param>        \n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public bool GetEntity(object entity, string sql, object[] parameters)\n        {\n            var command = CreateCommand(sql, parameters);\n            if (command == null)\n                return false;\n            \n            return GetEntity(entity, command, null);\n        }\n\n\n\n        /// <summary>\n        /// Generic routine to return an Entity that matches the field names of a \n        /// table exactly.\n        /// </summary>\n        /// <param name=\"entity\"></param>\n        /// <param name=\"table\"></param>\n        /// <param name=\"keyField\"></param>\n        /// <param name=\"keyValue\"></param>\n        /// <param name=\"propertiesToSkip\"></param>\n        /// <returns></returns>\n        public virtual bool GetEntity(object entity, string table, string keyField, object keyValue, string propertiesToSkip = null)\n        {\n            SetError();\n\n            DbCommand Command = CreateCommand(\"select * from \" + table + \" where \" + LeftFieldBracket + keyField + RightFieldBracket + \"=\" + ParameterPrefix + \"Key\",\n                                                    CreateParameter(ParameterPrefix + \"Key\", keyValue));\n            if (Command == null)\n                return false;\n\n            return GetEntity(entity, Command, propertiesToSkip);\n        }\n\n        /// <summary>\n        /// Finds the first matching entity based on a keyfield and key value.\n        /// Note the keyfield can be any field that is used in a WHERE clause.\n        ///\n        /// This method has been renamed from Find() to avoid ambiguous\n        /// overload errors.\n        /// </summary>\n        /// <typeparam name=\"T\">Type of entity to populate</typeparam>\n        /// <param name=\"keyValue\">Value to look up in keyfield</param>\n        /// <param name=\"tableName\">Name of the table to work on</param>\n        /// <param name=\"keyField\">Field that is used for the key lookup</param>\n        /// <returns></returns>\n        public virtual T FindKey<T>(object keyValue, string tableName,string keyField)\n            where T: class,new()\n        {\n            T obj = new T();\n            if (obj == null)\n                return null;\n\n            if (!GetEntity(obj, tableName, keyField, keyValue, null))\n                return null;\n\n            return obj;\n        }\n\n        /// <summary>\n        /// Returns the first matching record retrieved from data based on a SQL statement\n        /// as an entity or null if no match was found.\n        ///\n        /// NOTE: Key based method has been renamed to:\n        /// `FindKey`\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to fill</typeparam>\n        /// <param name=\"sql\">SQL string to execute. Use @0,@1,@2 for parameters.\n        /// \n        /// Recommend you use `TOP1` in your SQL statements to limit the \n        /// amount of data returned from the underlying query even though\n        /// a full list returns the same result.\n        /// </param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual T Find<T>(string sql, params object[] parameters)\n            where T : class,new()\n        {\n            T obj = new T();\n            if (!GetEntity(obj, sql, parameters))\n                return null;\n\n            return obj;\n        }\n\n        /// <summary>\n        /// Returns an entity that is the first match from a sql statement string.\n        /// </summary>\n        /// <typeparam name=\"T\">Entity type to return</typeparam>\n        /// <param name=\"sql\">Sql string to execute. Use @0,@1,@2 for positional parameters</param>\n        /// <param name=\"propertiesToSkip\">fields to not update from the resultset</param>\n        /// <param name=\"parameters\">\n        /// DbParameters (CreateParameter()) for named parameters\n        /// or use @0,@1 parms in SQL and plain values\n        /// </param>\n        /// <returns></returns>\n        public virtual T FindEx<T>(string sql, string propertiesToSkip, params object[] parameters)\n            where T : class,new()\n        {\n            T obj = new T();\n            if (!GetEntity(obj, CreateCommand( sql, parameters),propertiesToSkip))\n                return null;\n\n            return obj;\n        }\n\n        \n        /// <summary>\n        /// Updates an entity object that has matching fields in the database for each\n        /// public property. Kind of a poor man's quick entity update mechanism.\n        /// \n        /// Note this method will not save if the record doesn't already exist in the db.\n        /// </summary>        \n        /// <param name=\"entity\">entity to update</param>\n        /// <param name=\"table\">the table name to update</param>\n        /// <param name=\"keyField\">keyfield used to find entity</param>\n        /// <param name=\"propertiesToSkip\"></param>\n        /// <returns></returns>\n        public virtual bool UpdateEntity(object entity, string table, string keyField, string propertiesToSkip = null)\n        {\n            SetError();\n\n            var command = GetUpdateEntityCommand(entity, table, keyField, propertiesToSkip);\n            if (command == null)\n                return false;\n\n            bool result;\n            using (command)\n            {\n                result = ExecuteNonQuery(command) > -1;\n                CloseConnection(command);\n            }\n\n            return result;\n        }\n\n\n\n        /// <summary>\n        /// Updates an entity object that has matching fields in the database for each\n        /// public property. Kind of a poor man's quick entity update mechanism.\n        /// \n        /// Note this method will not save if the record doesn't already exist in the db.\n        /// </summary>        \n        /// <param name=\"entity\">entity to update</param>\n        /// <param name=\"table\">the table name to update</param>\n        /// <param name=\"keyField\">keyfield used to find entity</param>\n        /// <param name=\"propertiesToSkip\"></param>\n        /// <returns></returns>\n        public virtual DbCommand GetUpdateEntityCommand(object entity, string table, string keyField, string propertiesToSkip = null)\n        {\n            SetError();\n\n            if (string.IsNullOrEmpty(propertiesToSkip))\n                propertiesToSkip = string.Empty;\n            else\n                propertiesToSkip = \",\" + propertiesToSkip.ToLower() + \",\";\n\n            var command = CreateCommand(string.Empty);\n\n            var objType = entity.GetType();\n\n            StringBuilder sb = new StringBuilder();\n            sb.Append(\"update \" + table + \" set \");\n\n            PropertyInfo[] Properties = objType.GetProperties(BindingFlags.Instance | BindingFlags.Public);\n            foreach (PropertyInfo property in Properties)\n            {\n                if (!property.CanRead || !property.CanWrite)\n                    continue;\n       \n                if (!property.PropertyType.IsValueType && \n                    property.PropertyType.Name != \"String\" &&\n                    property.PropertyType.Name != \"Byte[]\")\n                    continue;\n\n                string name = property.Name;\n\n                if (propertiesToSkip.IndexOf(\",\" + name.ToLower() + \",\") > -1)\n                    continue;\n                \n                object value = property.GetValue(entity, null);\n\n                string parmString = UsePositionalParameters ? ParameterPrefix : ParameterPrefix + name;\n                sb.Append(\" \" + LeftFieldBracket + name + RightFieldBracket + \"=\" + parmString + \",\");\n\n                if (value == null && property.PropertyType == typeof(byte[]))\n                {\n                    command.Parameters.Add(\n                        CreateParameter(ParameterPrefix + name, DBNull.Value, DataUtils.DotNetTypeToDbType(property.PropertyType))\n                    );\n                }\n                else\n                    command.Parameters.Add(CreateParameter(ParameterPrefix + name, value ?? DBNull.Value));\n            }\n\n            object pkValue = ReflectionUtils.GetProperty(entity, keyField);\n\n            String commandText = sb.ToString().TrimEnd(',') + \" where \" + keyField + \"=\" + ParameterPrefix + \"__PK\";\n\n            command.Parameters.Add(CreateParameter(ParameterPrefix + \"__PK\", pkValue));\n            command.CommandText = commandText;\n\n            return command;\n        }\n\n\n        /// <summary>\n        /// Gets a DbCommand that creates an update statement.\n        /// that allows you to specify which fields to update and\n        /// so is a bit more efficient as it only checks for specific fields in the database\n        /// and the underlying table.\n        /// </summary>\n        /// <seealso cref=\"SaveEntity\"/>\n        /// <seealso cref=\"InsertEntity\"/>\n        /// <param name=\"entity\">Entity to update</param>\n        /// <param name=\"table\">DB Table to udpate</param>\n        /// <param name=\"keyField\">The keyfield to query on</param>\n        /// <param name=\"propertiesToSkip\">fields to skip in update</param>\n        /// <param name=\"fieldsToUpdate\">fields that should be updated</param>\n        /// <returns></returns>\n        public virtual DbCommand GetUpdateEntityCommand(object entity, string table, \n            string keyField, string propertiesToSkip, string fieldsToUpdate)\n        {\n            SetError();\n\n            if (propertiesToSkip == null)\n                propertiesToSkip = string.Empty;\n            else\n                propertiesToSkip = \",\" + propertiesToSkip.ToLower() + \",\";\n\n\n            DbCommand command = CreateCommand(string.Empty);\n            if (command == null)\n            {\n                SetError(\"Unable to create command.\");\n                return null;\n            }\n\n            Type objType = entity.GetType();\n\n            StringBuilder sb = new StringBuilder();\n            sb.Append(\"update \" + table + \" set \");\n\n            string[] fields = fieldsToUpdate.Split(',');\n            foreach (string Name in fields)\n            {\n           \n                if (propertiesToSkip.IndexOf(\",\" + Name.ToLower() + \",\") > -1)\n                    continue;\n\n                PropertyInfo property = objType.GetProperty(Name);\n                if (property == null)\n                    continue;\n\n                if (!property.CanWrite)\n                    continue;\n\n                if (!property.PropertyType.IsValueType &&\n                    property.PropertyType.Name != \"String\" &&\n                    property.PropertyType.Name != \"Byte[]\")\n                    continue;\n\n                object Value = property.GetValue(entity, null);\n\n                string parmString = UsePositionalParameters ? ParameterPrefix : ParameterPrefix + Name;\n                sb.Append(\" \" + LeftFieldBracket + Name + RightFieldBracket + \"=\" + parmString + \",\");\n\n                if (Value == null && property.PropertyType == typeof(byte[]))\n                    command.Parameters.Add(CreateParameter(ParameterPrefix + Name, DBNull.Value,\n                        DataUtils.DotNetTypeToDbType(property.PropertyType)));\n                else\n                    command.Parameters.Add(CreateParameter(ParameterPrefix + Name, Value ?? DBNull.Value));\n            }\n\n            object pkValue = ReflectionUtils.GetProperty(entity, keyField);\n\n            // check to see if \n            string commandText = sb.ToString().TrimEnd(',') +\n                                 \" where \" + LeftFieldBracket + keyField + RightFieldBracket + \"=\" + ParameterPrefix +\n                                 (UsePositionalParameters ? \"\" : \"__PK\");\n            command.Parameters.Add(CreateParameter(ParameterPrefix + \"__PK\", pkValue));\n            command.CommandText = commandText;\n\n            return command;\n        }\n\n        /// <summary>\n        /// This version of UpdateEntity allows you to specify which fields to update and\n        /// so is a bit more efficient as it only checks for specific fields in the database\n        /// and the underlying table.\n        /// </summary>\n        /// <seealso cref=\"SaveEntity\"/>\n        /// <seealso cref=\"InsertEntity\"/>\n        /// <param name=\"entity\">Entity to update</param>\n        /// <param name=\"table\">DB Table to udpate</param>\n        /// <param name=\"keyField\">The keyfield to query on</param>\n        /// <param name=\"propertiesToSkip\">fields to skip in update</param>\n        /// <param name=\"fieldsToUpdate\">fields that should be updated</param>\n        /// <returns></returns>\n        public virtual bool UpdateEntity(object entity, string table, string keyField, string propertiesToSkip, string fieldsToUpdate)\n        {\n            SetError();\n\n            var command = GetUpdateEntityCommand(entity, table, keyField, propertiesToSkip, fieldsToUpdate);\n            if (command == null)\n                return false;\n\n            bool result;\n            using (command) {\n                result = ExecuteNonQuery(command) > -1;\n                CloseConnection(command);\n            }\n\n            return result;\n        }\n\n\n\n        /// <summary>\n        /// Inserts an object into the database based on its type information.\n        /// The properties must match the database structure and you can skip\n        /// over fields in the propertiesToSkip list.        \n        /// </summary>        \n        /// <seealso cref=\"SaveEntity\" />        \n        /// <param name=\"entity\">Entity data to insert into table</param>\n        /// <param name=\"table\">Name of the table to update</param>        \n        /// <param name=\"propertiesToSkip\">Comma delimited list of fields to skip in the entity update</param>\n        /// <param name=\"returnIdentityKey\"></param>\n        /// <returns>\n        /// Null if the insert failed\n        /// Scope Identity (when returnIdentityKey is true or null if that fails)\n        /// Otherwise affected records\n        /// </returns>\n        public object InsertEntity(object entity, string table, string propertiesToSkip = null, bool returnIdentityKey = true)\n        {\n            SetError();\n            using (DbCommand command = GetInsertEntityCommand(entity, table, propertiesToSkip))\n            {\n                if (command == null)\n                    return null;\n\n                if (returnIdentityKey)\n                {\n                    command.CommandText += \";\\r\\n\" + GetIdentityKeySqlCommand;\n                    LastIdentityResult = ExecuteScalar(command);\n                    return LastIdentityResult;\n                }\n\n                int res = ExecuteNonQuery(command);\n                if (res < 0)\n                    return null;\n\n                return res;\n            }\n        }\n\n        \n  /// <summary>\n        /// Inserts an object into the database based on its type information.\n        /// The properties must match the database structure and you can skip\n        /// over fields in the propertiesToSkip list.        \n        /// </summary>        \n        /// <seealso cref=\"SaveEntity\" />        \n        /// <param name=\"entity\">Entity data to insert into table</param>\n        /// <param name=\"table\">Name of the table to update</param>        \n        /// <param name=\"propertiesToSkip\">Comma delimited list of fields to skip in the entity update</param>\n        /// <param name=\"returnIdentityKey\"></param>\n        /// <returns>\n        /// Null if the insert failed\n        /// Scope Identity (when returnIdentityKey is true or null if that fails)\n        /// Otherwise affected records\n        /// </returns>\n        public async Task<object> InsertEntityAsync(object entity, string table, string propertiesToSkip = null, bool returnIdentityKey = true)\n        {\n            SetError();\n            DbCommand command = GetInsertEntityCommand(entity, table, propertiesToSkip);\n\n            using (command)\n            {\n                if (returnIdentityKey)\n                {\n                    command.CommandText += \";\\r\\n\" + GetIdentityKeySqlCommand;\n                    LastIdentityResult = await ExecuteScalarAsync(command);\n                    return LastIdentityResult;\n                }\n\n                int res = await ExecuteNonQueryAsync(command);\n                if (res < 0)\n                    return null;\n\n                return res;\n            }\n        }\n\n\n        /// <summary>\n        /// Gets the DbCommand used to insert an object into the database based on its type information.\n        /// The properties must match the database structure and you can skip\n        /// over fields in the propertiesToSkip list.        \n        /// </summary>        \n        /// <seealso cref=\"SaveEntity\" />        \n        /// <param name=\"entity\">Entity object to use for Insert</param>\n        /// <param name=\"table\">Table name to insert to</param>\n        /// <param name=\"propertiesToSkip\">Comma delimited list of fields not to include in insert statement</param>\n        /// <returns>\n        /// Scope Identity or Null (when returnIdentityKey is true\n        /// Otherwise affected records\n        /// </returns>\n        public DbCommand GetInsertEntityCommand(object entity, string table, string propertiesToSkip = null)\n        {\n            SetError();\n\n            if (string.IsNullOrEmpty(propertiesToSkip))\n                propertiesToSkip = string.Empty;\n            else\n                propertiesToSkip = \",\" + propertiesToSkip.ToLower() + \",\";\n\n\n            DbCommand command = CreateCommand(string.Empty);\n            if (command == null)\n            {\n                return null;\n            }\n\n            Type objType = entity.GetType();\n\n            StringBuilder fieldList = new StringBuilder();\n            StringBuilder dataList = new StringBuilder();\n            fieldList.Append(\"insert into \" + table + \" (\");\n            dataList.Append(\" values (\");\n\n            PropertyInfo[] properties = objType.GetProperties(BindingFlags.Instance | BindingFlags.Public);\n            foreach (PropertyInfo property in properties)\n            {\n                if (!property.CanRead || !property.CanWrite)\n                    continue;\n                if (!property.PropertyType.IsValueType &&\n                    property.PropertyType.Name != \"String\" && \n                    property.PropertyType.Name != \"Byte[]\")\n                    continue;\n\n                string name = property.Name; \n                \n                if (propertiesToSkip.IndexOf(\",\" + name.ToLower() + \",\") > -1 )\n                    continue;\n                \n                object value = property.GetValue(entity, null);\n\n                fieldList.Append(\" \" + LeftFieldBracket + name + RightFieldBracket + \",\");\n\n                string parmString = ParameterPrefix;\n                if (!UsePositionalParameters)\n                    parmString += name;\n\n                dataList.Append(parmString + \",\");\n\n                if (value == null && property.PropertyType == typeof(byte[]))\n                    command.Parameters.Add(CreateParameter(ParameterPrefix + name, DBNull.Value,\n                        DataUtils.DotNetTypeToDbType(property.PropertyType)));\n                else\n                    command.Parameters.Add(CreateParameter(ParameterPrefix + name, value ?? DBNull.Value));\n            }\n\n            command.CommandText = fieldList.ToString().TrimEnd(',') + \") \" +\n                                 dataList.ToString().TrimEnd(',') + \")\";\n\n            return command;\n        }\n\n\n        /// <summary>\n        /// Saves an entity into the database using insert or update as required.\n        /// Requires a key field that exists on both the entity and the database.\n        /// </summary>\n        /// <param name=\"entity\">entity to save</param>\n        /// <param name=\"table\">table to save to</param>\n        /// <param name=\"keyField\">keyfield to update</param>\n        /// <param name=\"propertiesToSkip\">optional fields to skip when updating (keys related items etc)</param>\n        /// <param name=\"returnScopeId\"></param>\n        /// <param name=\"fieldsToUpdate\">optional - specify fields to update</param>\n        /// <returns></returns>\n        public virtual bool SaveEntity(object entity, string table, string keyField, string propertiesToSkip = null, bool returnScopeId = false)\n        {\n            object pkValue = ReflectionUtils.GetProperty(entity, keyField);\n            object res = null;\n            if (pkValue != null)\n                res = ExecuteScalar(\"select \" + LeftFieldBracket + keyField + RightFieldBracket + \" from \" + \n                                    LeftFieldBracket + table + RightFieldBracket +\n                                    \" where \" + LeftFieldBracket + keyField + RightFieldBracket + \"=\" + ParameterPrefix + \"id\",\n                                         CreateParameter(ParameterPrefix + \"id\", pkValue));\n            if (res == null)\n            {\n                LastIdentityResult  = InsertEntity(entity, table, propertiesToSkip, returnScopeId);\n                if (!string.IsNullOrEmpty(ErrorMessage))\n                    return false;\n            }\n            else\n            {\n                return UpdateEntity(entity, table, keyField, propertiesToSkip);\n            }\n\n            return true;\n        }\n\n\n        #endregion\n\n#region Error Handling\n\n        /// <summary>\n        /// Sets the error message for the failure operations\n        /// </summary>\n        /// <param name=\"message\"></param>\n        protected virtual void SetError(string message, int errorNumber)\n        {\n            if (string.IsNullOrEmpty(message))\n            {\n                ErrorMessage = string.Empty;\n                ErrorNumber = 0;\n                ErrorException = null;\n                return;\n            }\n\n            ErrorMessage = message;\n            ErrorNumber = errorNumber;            \n        }\n\n        /// <summary>\n        /// Sets the error message and error number.\n        /// </summary>\n        /// <param name=\"message\">Message for the ErrorMessage</param>\n        /// <param name=\"ex\">Optional exception that sets LastError</param>\n        protected virtual void SetError(string message, Exception ex = null)\n        {\n            SetError(message,0);\n            if (ex != null)\n                ErrorException = ex;            \n        }\n\n        protected virtual void SetError(DbException ex)\n        {\n            SetError(ex.Message, ex.ErrorCode);\n            ErrorException = ex;\n\n            if (ThrowExceptions)\n                throw ex;\n        }\n        protected virtual void SetError(SqlException ex)\n        {\n            SetError(ex.Message, ex.Number);\n            ErrorException = ex;\n\n            if (ThrowExceptions)\n                throw ex;\n        }\n\n        protected virtual void SetError(Exception ex)\n        {\n            if (ex is SqlException sqlEx)\n            {\n                SetError(sqlEx);\n                return;\n            }\n            if (ex is DbException)\n            {\n                SetError(ex as DbException);\n                return;\n            }\n            ErrorMessage = ex.Message;\n            ErrorException = ex;\n            ErrorNumber = 0;\n\n            if (ThrowExceptions)\n                throw ex;\n        }\n\n        /// <summary>\n        /// Sets the error message for failure operations.\n        /// </summary>\n        protected virtual void SetError()\n        {\n            SetError(null,0);\n        }\n#endregion\n\n#region IDisposable Members\n\n        public void Dispose()\n        {\n            if (_Connection != null)\n                CloseConnection();\n        }\n\n\n#endregion\n    }\n\n\n\n}"
  },
  {
    "path": "Westwind.Utilities.Data/DataTableExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Data;\nusing System.Collections;\nusing System.Dynamic;\n\nnamespace Westwind.Utilities.Data\n{\n    /// <summary>\n    /// Extends the DataTable to provide access to DynamicDataRow \n    /// data.\n    /// </summary>\n    public static class DataTableDynamicExtensions\n    {\n        /// <summary>\n        /// Returns a dynamic DataRow instance that can be accessed\n        /// with the field name as a property\n        /// </summary>\n        /// <param name=\"index\"></param>\n        /// <returns></returns>taTab\n        public static dynamic DynamicRow(this DataTable dt, int index)\n        {\n            var row = dt.Rows[index];            \n            return new DynamicDataRow(row);\n        }\n\n        /// <summary>\n        /// Returns a dynamic list of rows so you can reference them with\n        /// row.fieldName\n        /// </summary>\n        /// <param name=\"dt\"></param>\n        /// <returns></returns>\n        public static DynamicDataRows DynamicRows(this DataTable dt)\n        {\n            DynamicDataRows drows = new DynamicDataRows(dt.Rows);\n            return drows;\n        }\n\n    }\n\n    /// <summary>\n    /// Helper class that extends a DataRow collection to \n    /// be exposed as individual <see cref=\"Westwind.Utilities.Data.DynamicDataRow\"/>  objects\n    /// </summary>\n    public class DynamicDataRows : IEnumerator<DynamicDataRow>, IEnumerable<DynamicDataRow>\n    {\n        DataRowCollection Rows;\n        IEnumerator RowsEnumerator;\n\n        public DynamicDataRow this[int index]\n        {\n            get\n            {\n                return new DynamicDataRow(Rows[index]);\n            }\n        }\n\n        DynamicDataRow IEnumerator<DynamicDataRow>.Current\n        {\n            get\n            {\n                return new DynamicDataRow(RowsEnumerator.Current as DataRow);\n            }\n        }\n\n        public object Current\n        {\n            get\n            {\n                return new DynamicDataRow(RowsEnumerator.Current as DataRow);\n            }\n        }\n\n        public DynamicDataRows(DataRowCollection rows)\n        {\n            Rows = rows;\n            RowsEnumerator = rows.GetEnumerator();\n        }\n\n        IEnumerator<DynamicDataRow> IEnumerable<DynamicDataRow>.GetEnumerator()\n        {\n           foreach (DataRow row in Rows)\n            yield return new DynamicDataRow(row);\n        }\n\n\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            foreach (DataRow row in Rows)\n                yield return new DynamicDataRow(row);\n        }\n       \n        public void Dispose()\n        {\n            Rows = null;\n            RowsEnumerator = null;\n        }\n\n\n\n        public bool MoveNext()\n        {\n            return RowsEnumerator.MoveNext();\n        }\n\n        public void Reset()\n        {\n            RowsEnumerator.Reset();\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/DynamicDataReader.cs",
    "content": "﻿using System;\nusing System.Dynamic;\nusing System.Data;\nusing System.Data.Common;\n\nnamespace Westwind.Utilities.Data\n{\n\n    /// <summary>\n    /// This class provides an easy way to use object.property\n    /// syntax with a DataReader by wrapping a DataReader into\n    /// a dynamic object.\n    /// \n    /// The class also automatically fixes up DbNull values\n    /// (null into .NET and DbNUll)\n    /// </summary>\n    public class DynamicDataReader : DynamicObject\n    {\n        /// <summary>\n        /// Cached Instance of DataReader passed in\n        /// </summary>\n        IDataReader DataReader;\n        \n        /// <summary>\n        /// Pass in a loaded DataReader\n        /// </summary>\n        /// <param name=\"dataReader\">DataReader instance to work off</param>\n        public DynamicDataReader(IDataReader dataReader)\n        {\n            DataReader = dataReader;\n        }\n\n       /// <summary>\n       /// Returns a value from the current DataReader record\n       /// If the field doesn't exist null is returned.\n       /// DbNull values are turned into .NET nulls.\n       /// </summary>\n       /// <param name=\"binder\"></param>\n       /// <param name=\"result\"></param>\n       /// <returns></returns>\n        public override bool TryGetMember(GetMemberBinder binder, out object result)\n        {\n            result = null;\n\n            // 'Implement' common reader properties directly\n            if (binder.Name == \"IsClosed\")            \n                result = DataReader.IsClosed;                            \n            else if (binder.Name == \"RecordsAffected\")            \n                result = DataReader.RecordsAffected;                         \n            // lookup column names as fields\n            else\n            {\n                try\n                {\n                    result = DataReader[binder.Name];\n                    if (result == DBNull.Value)\n                        result = null;                    \n                }\n                catch \n                {\n                    result = null;\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n        {\n\t\t\t// Implement most commonly used method\n\t\t\tif (binder.Name == \"Read\")\n\t\t\t\tresult = DataReader.Read();\n\t\t\telse if (binder.Name == \"Close\")\n\t\t\t{\n\t\t\t\tDataReader.Close();\n\t\t\t\tresult = null;\n\t\t\t}\n\t\t\telse\t\t\t\n                // call other DataReader methods using Reflection (slow - not recommended)\n                // recommend you use full DataReader instance\n                result = ReflectionUtils.CallMethod(DataReader, binder.Name, args);\n\n            return true;            \n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/DynamicDataRow.cs",
    "content": "﻿using System;\nusing System.Dynamic;\nusing System.Data;\n\nnamespace Westwind.Utilities.Data\n{\n    /// <summary>\n    /// This class provides an easy way to turn a DataRow \n    /// into a Dynamic object that supports direct property\n    /// access to the DataRow fields.\n    /// \n    /// The class also automatically fixes up DbNull values\n    /// (null into .NET and DbNUll to DataRow)\n    /// </summary>\n    public class DynamicDataRow : DynamicObject\n    {\n        /// <summary>\n        /// Instance of object passed in\n        /// </summary>\n        DataRow DataRow;\n        \n        /// <summary>\n        /// Pass in a DataRow to work off\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        public DynamicDataRow(DataRow dataRow)\n        {\n            DataRow = dataRow;\n        }\n\n       /// <summary>\n       /// Returns a value from a DataRow items array.\n       /// If the field doesn't exist null is returned.\n       /// DbNull values are turned into .NET nulls.\n       /// \n       /// </summary>\n       /// <param name=\"binder\"></param>\n       /// <param name=\"result\"></param>\n       /// <returns></returns>\n        public override bool TryGetMember(GetMemberBinder binder, out object result)\n        {\n            result = null;\n\n            try\n            {\n                result = DataRow[binder.Name];\n\n                if (result == DBNull.Value)\n                    result = null;\n                \n                return true;\n            }\n            catch { }\n\n            result = null;\n            return false;\n        }\n\n\n        /// <summary>\n        /// Property setter implementation tries to retrieve value from instance \n        /// first then into this object\n        /// </summary>\n        /// <param name=\"binder\"></param>\n        /// <param name=\"value\"></param>\n        /// <returns></returns>\n        public override bool TrySetMember(SetMemberBinder binder, object value)\n        {\n            try\n            {\n                if (value == null)\n                    value = DBNull.Value;\n\n                DataRow[binder.Name] = value;\n                return true;\n            }\n            catch {}\n\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/LICENSE.MD",
    "content": "West Wind Utilities Data Support Library\n========================================\n\n\nMIT License\n-----------\n\nCopyright (c) 2019-2023 West Wind Technologies\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Westwind.Utilities.Data/Security/UserTokenManager.cs",
    "content": "﻿using System;\nusing Westwind.Utilities.Properties;\n\nnamespace Westwind.Utilities.Data.Security\n{\n\n    /// <summary>\n    /// Generic SQL Token generator class that can be used to\n    /// generate, store and validation user tokens for use in APIs.\n    ///\n    /// Methods can create tokens, store them and then can be validated\n    /// based on existance and timeout status. Service cleans up expired\n    /// tokens as part of operations.\n    ///\n    /// Works with SQL Server but can be customized\n    /// </summary>\n    public class UserTokenManager\n    {\n        /// <summary>\n        /// Sql Connection String (Sql Server)\n        /// </summary>\n        public string TokenServerConnectionString { get; set; }\n\n        public string Tablename { get; set; } = \"UserTokens\";\n\n        /// <summary>\n        /// The duration that a token is valid for in seconds\n        /// </summary>\n        public int TokenTimeoutSeconds { get; set; } = 1800;\n\n        /// <summary>\n        /// If true won't check if a token already exists for a given\n        /// user. If false only one token per user is allowed.\n        /// </summary>\n        public bool AllowMultipleTokensPerUser { get; set; }\n\n        public string ErrorMessage { get; set; }\n\n       \n\n        public UserTokenManager(string connectionString = null)\n        {\n            TokenServerConnectionString = connectionString;\n        }\n\n        /// <summary>\n        /// Check to see if a token is valid\n        /// </summary>\n        /// <param name=\"tokenId\">A string token id. Can also contain \"Bearer xxxx\" which strips the preamble</param>\n        /// <param name=\"renewLease\">If true updates the Updated property and moves the expiration window out</param>\n        /// <returns></returns>\n        public bool IsTokenValid(string tokenId, bool renewLease = true)\n        {\n            if (tokenId.StartsWith(\"Bearer \", StringComparison.OrdinalIgnoreCase))\n                tokenId = tokenId.Substring(7);\n\n            var token = GetToken(tokenId, checkForExpiration: true, renewLease: renewLease);\n\n            if (token == null)\n                return false;\n\n            return true;\n        }\n\n        /// <summary>\n        /// Retrieves the full token based on the id\n        /// </summary>\n        /// <param name=\"tokenId\"></param>\n        /// <param name=\"checkForExpiration\">if true doesn't retrieve token if it's expired</param>\n        /// <param name=\"renewLease\">If true updates the updated property so expiration window moves out</param>\n        /// <returns></returns>\n        public UserToken GetToken(string tokenId, bool checkForExpiration = true, bool renewLease = true)\n        {\n            UserToken token;\n            using (var data = GetSqlData())\n            {\n                string sql = $@\"select  Top 1 * from [{Tablename}] where Id = @0 Order by Updated Desc\";\n\n                token = data.Find<UserToken>(sql, tokenId);\n                if (token == null)\n                {\n                    SetError(data.ErrorMessage);\n                    return null;\n                }\n\n                if (checkForExpiration && token.Updated.AddSeconds(TokenTimeoutSeconds) < DateTime.UtcNow)\n                {\n                    SetError(Resources.UserTokenHasExpired);\n                    if (!string.IsNullOrEmpty(token.Id))\n                        DeleteToken(token.Id);\n                    return null;\n                }\n\n                if (renewLease)\n                {\n                    sql = $@\"update [{Tablename}] set updated = GetUtcDate() where Id = @0\";\n                    data.ExecuteNonQuery(sql, tokenId);\n                }\n            }\n\n            token.TokenIdentifier = null;\n            return token;\n        }\n\n\n        /// <summary>\n        /// Returns a token based on a Token Identifier. This is useful in non-Web\n        /// auth scenarios where you can check for the token being validated based on\n        /// a token identifier.\n        /// \n        /// Expired tokens are not returned and automatically deleted.\n        ///\n        /// By default the token identifier is removed after reading it and you can\n        /// prevent by passing the `\n        /// </summary>\n        /// <param name=\"tokenIdentifier\"></param>\n        /// <param name=\"dontRemoveTokenIdentifier\">If true doesn't remove the token identifier after reading it</param>\n        /// <returns></returns>\n        public UserToken GetTokenByTokenIdentifier(string tokenIdentifier, bool dontRemoveTokenIdentifier = false)\n        {\n            if (string.IsNullOrEmpty(tokenIdentifier) || tokenIdentifier.Length < 8)\n            {\n                SetError(Resources.MissingOrInvalidTokenIdentifier);\n                return null;\n            }\n\n            UserToken token;\n            using (var data = GetSqlData())\n            {\n                string sql = $@\"select  Top 1 * from [{Tablename}] where TokenIdentifier = @0 Order by Updated Desc\";\n\n                token = data.Find<UserToken>(sql, tokenIdentifier);\n                if (token == null)\n                {\n                    SetError(Resources.UserTokenNotFound);\n                    return null;\n                }\n\n                // expired token\n                if (token.Updated.AddSeconds(TokenTimeoutSeconds) < DateTime.UtcNow)\n                {\n                    SetError(Resources.UserTokenHasExpired);\n                    if (!string.IsNullOrEmpty(token.Id))\n                        DeleteToken(token.Id);\n                    return null;\n                }\n\n                // clear out the token identifier if requested\n                if (!dontRemoveTokenIdentifier && !string.IsNullOrEmpty(token.TokenIdentifier))\n                {\n                    sql = $@\"update [{Tablename}] set TokenIdentifier = null where TokenIdentifier = @0\";\n                    data.ExecuteNonQuery(sql, tokenIdentifier);\n                    token.TokenIdentifier = null;\n                }\n            }\n\n            return token;\n        }\n\n        /// <summary>\n        /// Adds a new token record into the db and returns the new token id.\n        /// \n        /// Parameters map to the modifiable user token table fields, so you can provide initial values.\n        /// </summary>\n        /// <param name=\"userId\">A mapping user id that maps into a user/customer table of the application</param>\n        /// <param name=\"referenceId\">An optional reference id</param>\n        /// <param name=\"tokenIdentifier\">An optional token identifier that can be used to retrieve a token after creation. Must be 8 or more chars long or null if not provided</param>\n        /// <param name=\"scope\">Optional scope identifier</param>\n        /// <param name=\"data\">Optional additional data that can be stored</param>\n        /// <returns>A new token Id</returns>\n        public string CreateNewToken(string userId, string referenceId = null, string tokenIdentifier = null, string scope = null, string data = null)\n        {\n            var dt = DateTime.UtcNow;\n\n            if (tokenIdentifier != null && tokenIdentifier.Length < 8 )\n            {\n                SetError(Resources.InvalidUserTokenIdentifier);\n                return null;\n            }\n\n            string tokenId;\n            int result;\n            using (var db = GetSqlData())\n            {\n                tokenId = null;\n                string sql;\n                result = -1;\n\n                var token = db.Find<UserToken>($\"select Top 1 * from [{Tablename}] where userId=@0\", userId);\n\n                // error - table doesn't exist?\n                if (!string.IsNullOrEmpty(db.ErrorMessage))\n                {\n                    if (!IsUserTokenTable() && CreateUserTokenSqlTable())\n                    {\n                        return CreateNewToken(userId, referenceId); // retry\n                    }\n                }\n\n\n                if (AllowMultipleTokensPerUser || token == null)\n                {\n                    tokenId = DataUtils.GenerateUniqueId(15);\n\n                    sql = $@\"\ninsert into [{Tablename}]\n            (Id,UserId,ReferenceId,TokenIdentifier,scope, data,Updated,Created,IsValidated) Values\n            (@0,@1,@2, @3,@4,@5,@6,@7,@8)\n\";\n                    result = db.ExecuteNonQuery(sql, tokenId, userId, referenceId, tokenIdentifier, scope, data, dt, dt, false );\n                }\n                else\n                {\n                    // replace an existing token for this user id\n                    tokenId = DataUtils.GenerateUniqueId(15);\n                    sql = $@\"update [{Tablename}] set Id=@0, UserId=@1, ReferenceId=@2, TokenIdentifier=@3, Scope=@4, Data=@5, Updated=@6, IsValidated=@7  where Id=@8\";\n                    result = db.ExecuteNonQuery(sql, tokenId, userId, referenceId ?? token.ReferenceId, tokenIdentifier, scope, data, dt, token.IsValidated, token.Id);\n                }\n\n                if (result == -1)\n                {\n                    SetError(Resources.CouldntCreateUserToken +\": \" + db.ErrorMessage);\n                    return null;\n                }\n            }\n\n            \n\n            return tokenId;\n        }\n\n\n        /// <summary>\n        /// Deletes a token by its token id\n        /// </summary>\n        /// <param name=\"userTokenId\"></param>\n        public bool DeleteToken(string userTokenId)\n        {\n            using (var data = GetSqlData())\n            {\n                int result = data.ExecuteNonQuery($\"delete from [{Tablename}] where id=@0\", userTokenId);\n                if (result == -1)\n                {\n                    SetError(data.ErrorMessage);\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Deletes a token by its token id\n        /// </summary>\n        /// <param name=\"userId\">The userid for which do delete tokens</param>\n        public bool DeleteTokenForUserId(string userId)\n        {\n            var data = GetSqlData();\n            int result = data.ExecuteNonQuery($\"delete from [{Tablename}] where userId=@0\", userId);\n            if (result == -1)\n            {\n                SetError(data.ErrorMessage);\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Deletes a token by its token id\n        /// </summary>\n        public bool DeleteExpiredTokens()\n        {\n            var time = DateTime.UtcNow.AddSeconds((TokenTimeoutSeconds * 1.5) * -1);\n\n            var data = GetSqlData();\n            int result = data.ExecuteNonQuery($\"delete from [{Tablename}] where updated < @0\", time);\n            if (result == -1)\n            {\n                SetError(data.ErrorMessage);\n                return false;\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Creates the UserToken Sql Server table in the specified connection.\n        /// Uses the current 'Tablename' property to determine the table\n        ///\n        /// Default implementation creates table for SQL Server\n        /// </summary>\n        /// <returns></returns>\n        public virtual bool CreateUserTokenSqlTable()\n        {\n            using (var data = GetSqlData())\n            {\n                string sql = $@\"\nBegin Transaction T1\nCREATE TABLE [{Tablename}]\n(\n    Id              nvarchar(20) not null Primary Key,\n    UserId          nvarchar(100),\n    ReferenceId     nvarchar(255),\n    TokenIdentifier nvarchar(100) ,\n    Scope           nvarchar(100),\n    Data            nvarchar(max),\n    IsValidated     bit,\n    Created         datetime not null,\n    Updated         datetime not null\n)\nCommit Transaction T1\n\";\n\n                if (data.ExecuteNonQuery(sql) < 0)\n                {\n                    SetError(data.ErrorMessage);\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// Check to see if hte user token table exists\n        /// </summary>\n        /// <returns></returns>\n        public bool IsUserTokenTable()\n        {\n            using (var data = GetSqlData())\n            {\n                data.ThrowExceptions = false;\n                object result = data.ExecuteScalar($\"select count(*) from  [{Tablename}]\");\n\n                if (result == null)\n                {\n                    SetError(data.ErrorMessage);\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        #region Helpers\n\n        SqlDataAccess GetSqlData()\n        {\n            var data = new SqlDataAccess(TokenServerConnectionString) { ThrowExceptions = false };\n            return data;\n        }\n\n        #endregion\n\n        #region Error Handling\n\n        protected void SetError()\n        {\n            SetError(\"CLEAR\");\n        }\n\n        protected void SetError(string message)\n        {\n            if (message == null || message == \"CLEAR\")\n            {\n                ErrorMessage = string.Empty;\n                return;\n            }\n            ErrorMessage += message;\n        }\n\n        protected void SetError(Exception ex, bool checkInner = false)\n        {\n            if (ex == null)\n                ErrorMessage = string.Empty;\n\n            Exception e = ex;\n            if (checkInner)\n                e = e.GetBaseException();\n\n            ErrorMessage = e.Message;\n        }\n\n        #endregion\n    }\n\n\n    /// <summary>\n    /// User token entity that maps the user token db table data\n    /// </summary>\n    public class UserToken\n    {\n        public UserToken()\n        {\n            Id = DataUtils.GenerateUniqueId(15);\n            Updated = DateTime.UtcNow;\n        }\n\n        /// <summary>\n        /// This is the token's unique Id\n        /// </summary>\n        public string Id { get; set; }\n\n        /// <summary>\n        /// Time the token was last updated. Used to determine\n        /// when the token expires\n        /// </summary>\n        public DateTime Updated { get; set; }\n\n\n        /// <summary>\n        /// A user id that maps this token to a given user\n        /// id or other user identifier\n        /// </summary>\n        public string UserId { get; set; }\n\n        /// <summary>\n        /// An additional reference string value that can be set\n        /// and stored with the key token.\n        /// </summary>\n        public string ReferenceId { get; set; }\n\n        /// <summary>\n        /// An Application specific identifier that can be passed in to act as a token\n        /// identifier for an external application. Used in token validation\n        /// and retrieving a token from a local/desktop app that allows for querying\n        /// for the Token Identifier rather than the token.        \n        /// </summary>\n        public string TokenIdentifier { get; set; }\n\n        /// <summary>\n        /// An Application specific scope or other identifier that allows you\n        /// specify additional information about the token with the a token query.\n        /// </summary>\n        public string Scope { get; set; }\n\n\n        /// <summary>\n        /// Application specific extra data field\n        /// </summary>\n        public string Data { get; set; }\n\n        /// <summary>\n        /// Application specific flag that can be used to indicate\n        /// that this user token have been validated.\n        ///\n        /// Note: Not explicitly set by the UserTokenManager.\n        /// </summary>\n        public bool IsValidated { get; set; }\n\n        public override string ToString() => Id ?? \"no id set\";\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/SqlDataAccess.cs",
    "content": "#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *           West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\n//#define SupportWebRequestProvider\n\nusing System;\nusing System.Data.Common;\n\n#if NETCORE\n    using Microsoft.Data.SqlClient;\n#else\n    using System.Data.SqlClient;\n#endif\n\n\nnamespace Westwind.Utilities.Data\n{\n    /// <summary>\n    /// Sql Server specific implementation of the DataAccessBase class to\n    /// provide an easy to use Data Access Layer (DAL) with single line\n    /// operations for most data retrieval and non-query operations.\n    /// </summary>\n    public class SqlDataAccess : DataAccessBase\n    {\n\n        public SqlDataAccess()\n        {\n            dbProvider = SqlClientFactory.Instance;\n        }\n\n\n        public SqlDataAccess(string connectionString)\n        : base(connectionString, SqlClientFactory.Instance)\n        { }\n\n#if NETFULL\n        public SqlDataAccess(string connectionString, string providerName)\n            : base(connectionString, providerName)\n        {\n\n        }\n#endif\n\n\n        public SqlDataAccess(string connectionString, DbProviderFactory provider)\n            : base(connectionString, provider)\n        { }\n\n        public SqlDataAccess(string connectionString, DataAccessProviderTypes providerType)\n            : base(connectionString,providerType)\n        { }\n\n        /// <summary>\n        /// Sql 2005 and later specific semi-generic paging routine\n        /// </summary>\n        /// <param name=\"sql\"></param>\n        /// <param name=\"pageSize\"></param>\n        /// <param name=\"page\"></param>\n        /// <param name=\"sortOrderFields\"></param>\n        /// <param name=\"Parameters\"></param>\n        /// <returns></returns>\n        public override DbCommand CreatePagingCommand(string sql, int pageSize, int page, string sortOrderFields, params object[] Parameters)\n        {\n            int pos = sql.IndexOf(\"select \", 0, StringComparison.OrdinalIgnoreCase);\n            if (pos == -1)\n            {\n                SetError(\"Invalid Command for paging. Must start with select and followed by field list\");\n                return null;\n            }\n            sql = StringUtils.ReplaceStringInstance(sql, \"select\", string.Empty, 1, true);\n\n            string NewSql = string.Format(\n            @\"\nselect * FROM \n   (SELECT ROW_NUMBER() OVER (ORDER BY @OrderByFields) as __No,{0}) __TQuery\nwhere __No > (@Page-1) * @PageSize and __No < (@Page * @PageSize + 1)\n\", sql);\n\n            return CreateCommand(NewSql,\n                            CreateParameter(\"@PageSize\", pageSize),\n                            CreateParameter(\"@Page\", page),\n                            CreateParameter(\"@OrderByFields\", sortOrderFields));\n\n        }\n\n    }\n\n}\n\n"
  },
  {
    "path": "Westwind.Utilities.Data/SqlUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Configuration;\nusing System.Data;\nusing System.Data.Common;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing Westwind.Utilities.Properties;\n\n\n#if NETCORE\n    using Microsoft.Data.SqlClient;\n#else\n    using System.Data.SqlClient;\n#endif\n\nnamespace Westwind.Utilities.Data\n{\n    public  class SqlUtils\n    {\n\n        #region Provider Factories\n\n        /// <summary>\n        /// Loads a SQL Provider factory based on the DbFactory type name and assembly.       \n        /// </summary>\n        /// <param name=\"dbProviderFactoryTypename\">Type name of the DbProviderFactory</param>\n        /// <param name=\"assemblyName\">Short assembly name of the provider factory. Note: Host project needs to have a reference to this assembly</param>\n        /// <returns></returns>\n        public static DbProviderFactory GetDbProviderFactory(string dbProviderFactoryTypename, string assemblyName)\n        {\n            var instance = ReflectionUtils.GetStaticProperty(dbProviderFactoryTypename, \"Instance\");\n            if (instance == null)\n            {\n                var a = ReflectionUtils.LoadAssembly(assemblyName);\n                if (a != null)\n                    instance = ReflectionUtils.GetStaticProperty(dbProviderFactoryTypename, \"Instance\");\n            }\n\n            if (instance == null)\n                throw new InvalidOperationException(string.Format(Resources.UnableToRetrieveDbProviderFactoryForm, dbProviderFactoryTypename));\n\n            return instance as DbProviderFactory;\n        }\n\n        /// <summary>\n        /// This method loads various providers dynamically similar to the \n        /// way that DbProviderFactories.GetFactory() works except that\n        /// this API is not available on .NET Standard 2.0\n        /// </summary>\n        /// <param name=\"type\"></param>\n        /// <returns></returns>\n        public static DbProviderFactory GetDbProviderFactory(DataAccessProviderTypes type)\n        {\n            if (type == DataAccessProviderTypes.SqlServer)\n                return SqlClientFactory.Instance; // this library has a ref to SqlClient so this works\n\n            if (type == DataAccessProviderTypes.SqLite)\n                return GetDbProviderFactory(\"System.Data.SQLite.SQLiteFactory\", \"System.Data.SQLite\");\n            if (type == DataAccessProviderTypes.MySql)\n                return GetDbProviderFactory(\"MySql.Data.MySqlClient.MySqlClientFactory\", \"MySql.Data\");\n            if (type == DataAccessProviderTypes.PostgreSql)\n                return GetDbProviderFactory(\"Npgsql.NpgsqlFactory\", \"Npgsql\");\n#if NETFULL\n            if (type == DataAccessProviderTypes.OleDb)\n                return System.Data.OleDb.OleDbFactory.Instance;\n            if (type == DataAccessProviderTypes.SqlServerCompact)\n                return DbProviderFactories.GetFactory(\"System.Data.SqlServerCe.4.0\");                \n#endif\n\n            throw new NotSupportedException(string.Format(Resources.UnsupportedProviderFactory, type.ToString()));\n        }\n\n\n\n        /// <summary>\n        /// Returns a provider factory using the old Provider Model names from full framework .NET.\n        /// Simply calls DbProviderFactories.\n        /// </summary>\n        /// <param name=\"providerName\"></param>\n        /// <returns></returns>\n        public static DbProviderFactory GetDbProviderFactory(string providerName)\n        {\n#if NETFULL\n            return DbProviderFactories.GetFactory(providerName);\n#else\n            var lowerProvider = providerName.ToLower();\n\n            if (lowerProvider == \"system.data.sqlclient\")\n                return GetDbProviderFactory(DataAccessProviderTypes.SqlServer);\n            if (lowerProvider == \"system.data.sqlite\" || lowerProvider == \"microsoft.data.sqlite\")\n                return GetDbProviderFactory(DataAccessProviderTypes.SqLite);\n            if (lowerProvider == \"mysql.data.mysqlclient\" || lowerProvider == \"mysql.data\")\n                return GetDbProviderFactory(DataAccessProviderTypes.MySql);\n            if (lowerProvider == \"npgsql\")\n                return GetDbProviderFactory(DataAccessProviderTypes.PostgreSql);\n\n            throw new NotSupportedException(string.Format(Resources.UnsupportedProviderFactory, providerName));\n#endif\n        }\n\n        #endregion\n\n\n        #region Minimal Sql Data Access Function\n\n        /// <summary>\n        /// Creates a Command object and opens a connection\n        /// </summary>\n        /// <param name=\"ConnectionString\"></param>\n        /// <param name=\"Sql\"></param>\n        /// <returns></returns>\n        public static SqlCommand GetSqlCommand(string ConnectionString, string Sql, params SqlParameter[] Parameters)\n        {\n            SqlCommand Command = new SqlCommand();\n            Command.CommandText = Sql;\n\n            try\n            {\n#if NETFULL\n\t\t\t\tif (!ConnectionString.Contains(';'))\n                    ConnectionString =  ConfigurationManager.ConnectionStrings[ConnectionString].ConnectionString;\n#endif\n\n                Command.Connection = new SqlConnection(ConnectionString);\n                Command.Connection.Open();\n            }\n            catch\n            {\n                return null;\n            }\n\n\n            if (Parameters != null)\n            {\n                foreach (SqlParameter Parm in Parameters)\n                {\n                    Command.Parameters.Add(Parm);\n                }\n            }\n\n            return Command;\n        }\n\n        /// <summary>\n        /// Returns a SqlDataReader object from a SQL string.\n        /// \n        /// Please ensure you close the Reader object\n        /// </summary>\n        /// <param name=\"ConnectionString\"></param>\n        /// <param name=\"Sql\"></param>\n        /// <param name=\"Parameters\"></param>\n        /// <returns></returns>\n        public static SqlDataReader GetSqlDataReader(string ConnectionString, string Sql, params SqlParameter[] Parameters)\n        {\n            SqlCommand Command = GetSqlCommand(ConnectionString, Sql, Parameters);\n            if (Command == null)\n                return null;\n\n            SqlDataReader Reader = null;\n            try\n            {\n                Reader = Command.ExecuteReader();\n            }\n            catch\n            {\n                CloseConnection(Command);\n                return null;\n            }\n\n            return Reader;\n        }\n\n        /// <summary>\n        /// Returns a DataTable from a Sql Command string passed in.\n        /// </summary>\n        /// <param name=\"Tablename\"></param>\n        /// <param name=\"ConnectionString\"></param>\n        /// <param name=\"Sql\"></param>\n        /// <param name=\"Parameters\"></param>\n        /// <returns></returns>\n        public static DataTable GetDataTable(string Tablename, string ConnectionString, string Sql, params SqlParameter[] Parameters)\n        {\n            SqlCommand Command = GetSqlCommand(ConnectionString, Sql, Parameters);\n            if (Command == null)\n                return null;\n\n            SqlDataAdapter Adapter = new SqlDataAdapter(Command);\n\n            DataTable dt = new DataTable(Tablename);\n\n            try\n            {\n                Adapter.Fill(dt);\n            }\n            catch\n            {\n                return null;\n            }\n            finally\n            {\n                CloseConnection(Command);\n            }\n\n            return dt;\n        }\n\n\n        /// <summary>\n        /// Closes a connection\n        /// </summary>\n        /// <param name=\"Command\"></param>\n        public static void CloseConnection(SqlCommand Command)\n        {\n            if (Command.Connection != null &&\n                Command.Connection.State == ConnectionState.Open)\n                Command.Connection.Close();\n        }\n        #endregion\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Data/Westwind.Utilities.Data.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\t<PropertyGroup>\n\t\t<TargetFrameworks>net10.0;net8.0;net472</TargetFrameworks>\n\t\t<Version>5.2.8</Version>\n\t\t<Authors>Rick Strahl</Authors>\n\t\t<RequireLicenseAcceptance>false</RequireLicenseAcceptance>\n\t\t<Language>en-US</Language>\n\t\t<AssemblyName>Westwind.Utilities.Data</AssemblyName>\n\t\t<AssemblyTitle>Westwind.Utilities.Data</AssemblyTitle>\n\t\t<NeutralLanguage>en-US</NeutralLanguage>\n\t\t<PackageId>Westwind.Utilities.Data</PackageId>\n\t\t<RootNamespace>Westwind.Utilities.Data</RootNamespace>\n\t\t<Title>West Wind Utilities Data</Title>\n\t\t<Description>.NET utility library that includes Application Configuration, lightweight ADO.NET Data Access Layer, logging, utility classes include: StringUtils, ReflectionUtils, FileUtils, DataUtils, SerializationUtils, TimeUtils, SecurityUtils and XmlUtils. These classes are useful in any kind of .NET project.</Description>\n\t\t<Description>Data access support library for Westwind.Utilities that provides a small lightweight data access providerand and a host of data utilities.</Description>\n\t\t<Summary>Data access support library for Westwind.Utilities.</Summary>\n\t\t<PackageCopyright>Rick Strahl, West Wind Technologies 2007-2026</PackageCopyright>\n\t\t<PackageTags>Westwind DataAccess DataUtils DAL Sql ADO.NET</PackageTags>\n\t\t<PackageReleaseNotes></PackageReleaseNotes>\n\n\t\t<PackageProjectUrl>http://github.com/rickstrahl/westwind.utilities</PackageProjectUrl>\n\n\t\t<PackageIcon>icon.png</PackageIcon>\n\t\t<PackageLicenseFile>LICENSE.MD</PackageLicenseFile>\n\n\t\t<AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n\t\t<Copyright>Rick Strahl, West Wind Technologies, 2010-2026</Copyright>\n\t\t<RepositoryType>Github</RepositoryType>\n\t\t<Company>West Wind Technologies</Company>\n\t\t<RepositoryUrl>https://github.com/RickStrahl/Westwind.Utilities</RepositoryUrl>\n\t\t<RepositoryType>git</RepositoryType>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(Configuration)'=='Debug'\">\n\t\t<DefineConstants>TRACE;DEBUG;</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<DebugType>embedded</DebugType>\n\t\t<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<PackageOutputPath>./nupkg</PackageOutputPath>\n\t\t<PublishRepositoryUrl>true</PublishRepositoryUrl>\n\t\t<DefineConstants>RELEASE</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(TargetFramework)' != 'net472'\">\n\t\t<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\" '$(TargetFramework)' == 'net472'\">\n\t\t<DefineConstants>NETFULL</DefineConstants>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\"'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net472|AnyCPU'\">\n\t\t<DebugType>embedded</DebugType>\n\t\t<DebugSymbols>true</DebugSymbols>\n\t</PropertyGroup>\n\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Microsoft.Data.SqlClient\" Version=\"6.1.4\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net472' \">\n\t\t<Reference Include=\"mscorlib\" />\n\t\t<Reference Include=\"System\" />\n\t\t<Reference Include=\"System.Core\" />\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t\t<Reference Include=\"System.Configuration\" />\t\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<None Include=\"icon.png\" Pack=\"true\" PackagePath=\"\" />\n\t\t<None Include=\"LICENSE.MD\" Pack=\"true\" PackagePath=\"\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <ProjectReference Include=\"..\\Westwind.Utilities\\Westwind.Utilities.csproj\" />\n\t</ItemGroup>\n</Project>\n"
  },
  {
    "path": "Westwind.Utilities.Data/publish-nuget.ps1",
    "content": "﻿if (test-path ./nupkg) {\n    remove-item ./nupkg -Force -Recurse\n}   \n\ndotnet build -c Release\n\n$filename = Get-ChildItem \"./nupkg/*.nupkg\" | sort LastWriteTime | select -last 1 | select -ExpandProperty \"Name\"\nWrite-host $filename\n$len = $filename.length\n\nif ($len -gt 0) {\n    Write-Host \"signing... $filename\"\n    #nuget sign  \".\\nupkg\\$filename\"   -CertificateSubject \"West Wind Technologies\" -timestamper \" http://timestamp.digicert.com\"    \n    nuget push  \".\\nupkg\\$filename\" -source \"https://nuget.org\"    \n\n    Write-Host \"Done.\"\n}"
  },
  {
    "path": "Westwind.Utilities.Test/App.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <configSections>\n    <section name=\"LogManager\" requirePermission=\"false\" type=\"System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\"/>\n    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->\n  </configSections>\n  <connectionStrings>\n    <add name=\"WebStoreContext\" connectionString=\"server=.;database=WestwindToolkitSamples;integrated security=true;MultipleActiveResultSets=true;\" providerName=\"System.Data.SqlClient\"/>\n    <add name=\"WestwindToolkitSamples\" connectionString=\"server=.;database=WestwindToolkitSamples;integrated security=true;MultipleActiveResultSets=true;\" providerName=\"System.Data.SqlClient\"/>\n  </connectionStrings>\n  <LogManager>\n    <add key=\"ConnectionString\" text=\"WestwindToolkitSamples\"/>\n    <add key=\"LogFilename\" text=\"ApplicationLog\"/>\n    <add key=\"LogAdapter\" text=\"Sql\"/>\n    <add key=\"LogWebRequests\" text=\"True\"/>\n    <add key=\"LogErrors\" text=\"True\"/>\n  </LogManager>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <dependentAssembly>\n        <assemblyIdentity name=\"Newtonsoft.Json\" publicKeyToken=\"30ad4fe6b2a6aeed\" culture=\"neutral\"/>\n        <bindingRedirect oldVersion=\"0.0.0.0-10.0.0.0\" newVersion=\"10.0.0.0\"/>\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.Net.Http.Formatting\" publicKeyToken=\"31bf3856ad364e35\" culture=\"neutral\"/>\n        <bindingRedirect oldVersion=\"0.0.0.0-5.2.3.0\" newVersion=\"5.2.3.0\"/>\n      </dependentAssembly>\n    </assemblyBinding>\n  </runtime>  \n</configuration>\n"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/AutoConfigFileConfigurationTests.cs",
    "content": "﻿#if NETFULL\nusing System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing System.Configuration;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests default config file implementation that uses\n    /// only base constructor behavior - (config file and section config only)    \n    /// </summary>\n    [TestClass]\n    public class AutoConfigFileConfigurationTests\n    {\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        [TestMethod]\n        public void DefaultConstructorInstanceTest()\n        {         \n            var config = new AutoConfigFileConfiguration();\n            \n            // gets .config file, AutoConfigFileConfiguration section\n            config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n            Assert.AreEqual(config.MaxDisplayListItems, 15);\n\n\t        string outputfile = TestHelpers.GetTestConfigFilePath();\n\n\t\t\tstring text = File.ReadAllText(outputfile);\n            Console.WriteLine(text);\n        }\n\n        [TestMethod]\n        public void DefaultConstructorWithCustomProviderTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n\n            // Create a customized provider to set provider options\n            var provider = new ConfigurationFileConfigurationProvider<AutoConfigFileConfiguration>()\n            {\n                ConfigurationSection = \"CustomConfiguration\",\n                EncryptionKey = \"seekrit123\",\n                PropertiesToEncrypt = \"MailServer,MailServerPassword\"                \n            };\n\n            config.Initialize(provider);  \n            \n            // Config File and custom section should have been created in config file\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n\n            Assert.IsFalse(string.IsNullOrEmpty(text));\n            Assert.IsTrue(text.Contains(\"<CustomConfiguration>\"));\n\n            // MailServer/MailServerPassword value should be encrypted\n            Console.WriteLine(text);\n        }\n\n\n        [TestMethod]\n        public void DefaultConstructorWithAppSettingsProviderTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n\n            // Create a customized provider to set provider options\n            var provider = new ConfigurationFileConfigurationProvider<AutoConfigFileConfiguration>()\n            {\n                ConfigurationSection = null, // forces to AppSettings\n                EncryptionKey = \"seekrit123\",\n                PropertiesToEncrypt = \"MailServer,MailServerPassword\"\n            };\n\n            config.Initialize(provider);\n\n            // Config File and custom section should have been created in config file\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n\n            Assert.IsFalse(string.IsNullOrEmpty(text));\n            Assert.IsTrue(text.Contains(\"<appSettings>\"));\n\n            // MailServer/MailServerPassword value should be encrypted\n            Console.WriteLine(text);\n\n            config.ApplicationName = \"Updated Configuration\";\n            config.Write();\n\n            config = null;\n            config = new AutoConfigFileConfiguration();\n            config.Initialize(provider);\n\n            config.Initialize(); // should reload, reread\n\n            Console.WriteLine(\"Application Name: \" + config.ApplicationName);\n\n            Assert.IsTrue(config.ApplicationName == \"Updated Configuration\");\n\n        }\n\n        [TestMethod]\n        public void AutoConfigWriteConfigurationTest()\n        {            \n            var config = new AutoConfigFileConfiguration();\n            config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n            Assert.AreEqual(config.MaxDisplayListItems, 15);\n\n            config.MaxDisplayListItems = 17;\n            config.Write();\n\n            var config2 = new AutoConfigFileConfiguration();\n            config2.Initialize();\n\n            Assert.AreEqual(config2.MaxDisplayListItems, 17);\n\n            // reset to default val\n            config2.MaxDisplayListItems = 15;\n            config2.Write();\n        }\n\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n            config.Initialize();\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n            \n            // update complex type\n            config.License.Company = \"Updated Company\";\n            config.License.Name = \"New User\";\n            config.License.LicenseKey = \"UpdatedCompanyNewUser-5331231\";\n\n            config.Write();\n\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"DebugMode\"\" value=\"\"DeveloperErrorMessage\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"12\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"SendAdminEmailConfirmations\"\" value=\"\"True\"\" />\"));\n\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"License\"\" value=\"\"New User,Updated Company,UpdatedCompanyNewUser-5331231\"\" />\"));\n\n            var config2 = new AutoConfigFileConfiguration();\n            config2.Initialize();\n\n            Assert.AreEqual(config2.MaxDisplayListItems, 12);\n            Assert.AreEqual(config2.ApplicationName, \"Changed\");\n            Assert.AreEqual(\"Updated Company\",config2.License.Company);\n\n            // reset to default val\n            config2.MaxDisplayListItems = 15;\n            config2.Write();\n        }\n\n\n        /// <summary>\n        /// Test without explicit constructor parameter \n        /// </summary>\n        [TestMethod]\n        public void DefaultConstructor2InstanceTest()\n        {\n            var config = new AutoConfigFile2Configuration();\n            \n            // Not required since custom constructor calls this\n            //config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n            Assert.AreEqual(config.MaxDisplayListItems, 15);\n\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n        }\n\n        /// <summary>\n        /// Write test without explicit constructor\n        /// </summary>\n        [TestMethod]\n        public void WriteConfiguration2Test()\n        {\n            var config = new AutoConfigFile2Configuration();\n            \n            // not necesary since constructor calls internally\n            //config.Initialize();\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n            config.Write();\n\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"DebugMode\"\" value=\"\"DeveloperErrorMessage\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"12\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"SendAdminEmailConfirmations\"\" value=\"\"True\"\" />\"));\n\n            // reset to default val\n            config.MaxDisplayListItems = 15;\n            config.Write();\n        }\n\n        [TestMethod]\n        public void NoConstructorWriteConfiguration2Test2()\n        {\n            var config = new NoConstructorConfiguration();\n            Assert.IsNotNull(config, \"Configuration object is null\");\n\n            config = NoConstructorConfiguration.New();\n            Assert.IsNotNull(config, \"Static New(): Configuration object is null\");\n        }\n\n        public class NoConstructorConfiguration : AppConfiguration\n        {\n            public string ApplicationName { get; set; }\n            public DebugModes DebugMode { get; set; }\n            public int MaxDisplayListItems { get; set; }\n            public bool SendAdminEmailConfirmations { get; set; }\n\n            public static NoConstructorConfiguration New()\n            {\n                var config = new NoConstructorConfiguration();\n                config.Initialize();\n                return config;\n            }\n        }\n\n    }\n}\n#endif"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/AutoConfigFileConfiguration.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Default implementation that uses only base constructors\n    /// for configuration.\n    /// \n    /// Default setup allows for no configuration of the provider\n    /// since we're just calling back to the base constructors\n    /// \n    /// Note: for config files ONLY you can implement the default \n    /// constructor automatically since no serialization is used.\n    /// When using XML, String, Database the default constructor \n    /// needs to be left at default to avoid recursive loading\n    /// </summary>\n    public class AutoConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n        public string MailServer { get; set; }\n        public string MailServerPassword { get; set; }\n\n        /// <summary>\n        /// Type that has ToString() and FromString() methods\n        /// to allow serialization\n        /// </summary>\n        public LicenseInformation License { get; set; }\n        \n        public AutoConfigFileConfiguration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            MailServer = \"mail.MyWickedServer.com:334\";\n            MailServerPassword = \"seekrity\";\n            License = new LicenseInformation()\n            {\n                Name = \"Rick\",\n                Company = \"West Wind\",\n                LicenseKey = \"WestWind-5333121\"\n            };\n        }\n\n\n    }\n\n    /// <summary>\n    /// This version of the class internally calls Initialize\n    /// to read configuration information immediately from\n    /// itself so no explicit call to Initialize is required\n    /// </summary>\n    public class AutoConfigFile2Configuration : AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n\n        public AutoConfigFile2Configuration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n\n            // Automatically initialize this one\n            this.Initialize();\n        }\n    }\n\n    public class NoConstructorConfiguration : AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n\n        public static NoConstructorConfiguration New()\n        {\n            var config = new NoConstructorConfiguration();\n            config.Initialize();\n            return config;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/CustomConfigFileConfiguration.cs",
    "content": "﻿#if NETFULL\n\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Xml.Serialization;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Custom Configuration Provider implementation that allows\n    /// uses a different section and encrypts a couple of properties\n    /// </summary>\npublic class CustomConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n{\n    public string ConfigFile  {get; set; }\n\n    public string ApplicationName { get; set; }\n    public DebugModes DebugMode { get; set; }\n    public int MaxDisplayListItems { get; set; }\n    public bool SendAdminEmailConfirmations { get; set; }\n    public string Password { get; set; }\n    public string AppConnectionString { get; set; }\n\n    public LicenseInformation License { get; set; }\n\n\n    [XmlIgnore]\n    public string IgnoredProperty {get; set;}\n\n    public List<string> ServerList { get; set;  }\n\n    \n        public CustomConfigFileConfiguration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            Password = \"seekrit\";\n            AppConnectionString = \"server=.;database=hosers;uid=bozo;pwd=seekrit;\";\n            License = new LicenseInformation()\n            {\n                Company = \"West Wind\",\n                Name = \"Rick\", \n                LicenseKey = \"westwindrick-51123\"\n            };\n            ServerList = new List<string>()\n            {\n                \"DevServer\",\n                \"Maximus\",\n                \"Tempest\"\n            };\n        }\n\n        /// <summary>\n        /// Override to provide a custom default provider (created when Initialize() is\n        /// called with no parameters).\n        /// </summary>\n        /// <param name=\"sectionName\"></param>\n        /// <param name=\"configData\"></param>\n        /// <returns></returns>\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            var provider = new ConfigurationFileConfigurationProvider<CustomConfigFileConfiguration>()\n            {\n                ConfigurationFile = ConfigFile,\n                ConfigurationSection = sectionName,\n                EncryptionKey = \"ultra-seekrit\",  // use a generated value here\n                PropertiesToEncrypt = \"Password,AppConnectionString,License.LicenseKey\"\n            };\n\n            return provider;\n        }\n    }\n}\n#endif"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/DatabaseConfiguration.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Custom Configuration Provider implementation that allows\n    /// uses a different section and encrypts a couple of properties\n    /// </summary>\n    public class DatabaseConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n    {\n        // Configuration store values \n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n        public string Password { get; set; }\n        public string AppConnectionString { get; set; }\n\n        // Must implement public default constructor\n        public DatabaseConfiguration()\n        {            \n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            Password = \"seekrit\";\n            AppConnectionString = \"server=.;database=hosers;uid=bozo;pwd=seekrit;\";\n        }\n\n    \n        ///// <summary>\n        ///// Override this method to create the custom default provider - in this case a database\n        ///// provider with a few options.\n        ///// </summary>\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            string connectionString = TestConfigurationSettings.WestwindToolkitConnectionString;\n            string tableName = \"ConfigurationData\";\n\n            // ConfigData: new { ConnectionString = \"...\", Tablename = \"...\" }\n            if (configData != null)\n            {\n                dynamic data = configData;\n                connectionString = data.ConnectionString;\n                tableName = data.Tablename;\n            }\n\n            var provider = new SqlServerConfigurationProvider<DatabaseConfiguration>()\n                {\n                    Key = 0,\n                    ConnectionString = connectionString,\n                    Tablename = tableName,\n                    //ProviderName = \"System.Data.SqlServerCe.4.0\",\n                    EncryptionKey = \"ultra-seekrit\", // use a generated value here\n                    PropertiesToEncrypt = \"Password,AppConnectionString\"\n                };\n\n            return provider;\n        }\n\n        /// <summary>\n        /// Optional - Create a custom overload with required parameters\n        /// </summary>\n        public void Initialize(string connectionString, string tableName = null)\n        {\n            // pass in anonymous object with parameters we're interested in\n            // the OnCreateDefaultProvider reads the anonymous object values\n            // and uses them for the SQL access object\n            base.Initialize(configData: new { ConnectionString = connectionString, Tablename = tableName });\n        }\n\n    }\n\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/JsonFileConfiguration.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Web;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Custom Configuration Provider implementation that allows\n    /// uses a different section and encrypts a couple of properties\n    /// </summary>\n    public class JsonFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n        public string Password { get; set; }\n        public string AppConnectionString { get; set; }\n        public LicenseInformation License { get; set; }\n\n        // Must implement public default constructor\n        public JsonFileConfiguration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            Password = \"seekrit\";\n            AppConnectionString = \"server=.;database=hosers;uid=bozo;pwd=seekrit;\";\n            License = new LicenseInformation\n            {\n                Name = \"Rick\",\n                Company = \"West Wind\",\n                LicenseKey = \"RickWestWind-533112\"\n            };\n        }\n\n\n        // Automatically initialize with default config and config file\n        public void Initialize(string configFile)\n        {\n            base.Initialize(configData: configFile);\n        }\n\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            string jsonFile = \"JsonConfiguration.txt\";\n            if (configData != null)\n                jsonFile = configData as string;\n\n            var provider = new JsonFileConfigurationProvider<JsonFileConfiguration>()\n            {\n                JsonConfigurationFile = jsonFile,\n                EncryptionKey = \"ultra-seekrit\",  // use a generated value here\n                PropertiesToEncrypt = \"Password,AppConnectionString,License.LicenseKey\"\n            };\n\n            return provider;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/LicenseInformation.cs",
    "content": "﻿namespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Demonstration class for a complex type\n    /// </summary>\npublic class LicenseInformation\n{\n    public string Name { get; set; }\n    public string Company { get; set; }\n    public string LicenseKey { get; set; }\n        \n    public static LicenseInformation FromString(string data)\n    {        \n        return StringSerializer.Deserialize<LicenseInformation>(data,\",\");\n    }\n\n    public override string ToString()\n    {        \n        return StringSerializer.SerializeObject(this, \",\");\n    }\n}\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/StringConfigFileConfiguration.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Custom Configuration Provider implementation that allows\n    /// uses a different section and encrypts a couple of properties\n    /// </summary>\n    public class StringConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n        public string Password { get; set; }\n        public string AppConnectionString { get; set; }\n\n        // Must implement public default constructor\n        public StringConfiguration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            Password = \"seekrit\";\n            AppConnectionString = \"server=.;database=hosers;uid=bozo;pwd=seekrit;\";\n        }\n\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            var provider = new StringConfigurationProvider<StringConfiguration>()\n            {\n                InitialStringData = configData as String,\n                EncryptionKey = \"ultra-seekrit\",  // use a generated value here\n                PropertiesToEncrypt = \"Password,AppConnectionString\",\n            };\n\n            return provider;\n        }\n\n        /// <summary>\n        /// Optional - easier overload for xml string loading\n        /// </summary>\n        /// <param name=\"xml\"></param>\n        public void Initialize(string xml)\n        {\n            base.Initialize(configData: xml);\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/XmlFileConfiguration.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Web;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Custom Configuration Provider implementation that allows\n    /// uses a different section and encrypts a couple of properties\n    /// </summary>\n    public class XmlFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration\n    {\n        public string ApplicationName { get; set; }\n        public DebugModes DebugMode { get; set; }\n        public int MaxDisplayListItems { get; set; }\n        public bool SendAdminEmailConfirmations { get; set; }\n        public string Password { get; set; }\n        public string AppConnectionString { get; set; }\n        public LicenseInformation License {get; set; }\n\n\n        // Must implement public default constructor\n        public XmlFileConfiguration()\n        {\n            ApplicationName = \"Configuration Tests\";\n            DebugMode = DebugModes.Default;\n            MaxDisplayListItems = 15;\n            SendAdminEmailConfirmations = false;\n            Password = \"seekrit\";\n            License = new LicenseInformation()\n            {\n                Company = \"West Wind\",\n                Name = \"Rick\",\n                LicenseKey = \"westwindrick-4123122\"\n            };\n            AppConnectionString = \"server=.;database=hosers;uid=bozo;pwd=seekrit;\";\n        }\n\n\n        // Automatically initialize with default config and config file\n        public void Initialize(string configFile)\n        {\n            base.Initialize(configData: configFile);\n        }\n\n        protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData)\n        {\n            string xmlFile = \"XmlConfiguration.xml\";\n            if (configData != null)\n                xmlFile = configData as string;\n\n            var provider = new XmlFileConfigurationProvider<XmlFileConfiguration>()\n            {\n                XmlConfigurationFile = xmlFile,\n                EncryptionKey = \"ultra-seekrit\",  // use a generated value here\n                PropertiesToEncrypt = \"Password,AppConnectionString,License.LicenseKey\"\n                // UseBinarySerialization = true                     \n            };\n\n            return provider;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/CustomConfigFileConfigurationTests.cs",
    "content": "﻿#if NETFULL\n\nusing System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests default config file implementation that uses\n    /// only base constructor behavior - (config file and section config only)    \n    /// </summary>\n    [TestClass]\n    public class CustomConfigurationTests\n    {\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        [TestMethod]\n        public void DefaultConstructorInstanceTest()\n        {\n            var configFile  = Path.Combine(Path.GetTempPath(),\"testconfig.config\");\n\n            var config = new CustomConfigFileConfiguration();\n            config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"15\"\" />\"));\n            Console.WriteLine(text);          \n        }\n\n        [TestMethod]\n        public void CustomFileLocationInstanceTest()\n        {\n            var configFile  = Path.Combine(Path.GetTempPath(),\"testconfig.config\");\n\n            var config = new CustomConfigFileConfiguration();\n            config.ConfigFile = configFile; // custom property to pass in the path\n            config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n\n            string text = File.ReadAllText(configFile); // TestHelpers.GetTestConfigFilePath());\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"15\"\" />\"));\n            Console.WriteLine(text);\n\n            File.Delete(configFile);\n        }\n\n\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            File.Delete(TestHelpers.GetTestConfigFilePath());\n\n            var config = new CustomConfigFileConfiguration();\n            config.Initialize();\n            \n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            // Complex Types\n            config.License.Company = \"Updated Company\";\n            config.ServerList[0] = \"UpdatedServerName\";\n\n            config.License.Name = \"Rick\";\n            config.License.Company = \"West Wind 2\";\n            config.License.LicenseKey = \"RickWestWind2-51231223\";\n\n            config.Write();\n\n            config = null;\n            config = new CustomConfigFileConfiguration();\n            config.Initialize();\n\n            Console.WriteLine(config.License.LicenseKey);\n            Assert.IsTrue(config.License.LicenseKey == \"RickWestWind2-51231223\");\n            \n            \n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"DebugMode\"\" value=\"\"DeveloperErrorMessage\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"12\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"SendAdminEmailConfirmations\"\" value=\"\"True\"\" />\"));\n\n            // Password and AppSettings  should be encrypted in config file\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"Password\"\" value=\"\"ADoCNO6L1HIm8V7TyI4deg==\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"AppConnectionString\"\" value=\"\"z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=\"\" />\"));\n\n            // Complex Value\n            Assert.IsTrue(text.Contains(@\"West Wind 2\"));\n\n            // List values\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"ServerList1\"\"\"));\n            Assert.IsTrue(text.Contains(@\"UpdatedServerName\"));\n\n\n\n        }\n\n        [TestMethod]\n        public void WriteAndReadConfigurationTest()\n        {\n            var config = new CustomConfigFileConfiguration();\n            config.Initialize();\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.License.Company = \"Updated Company\";\n            config.ServerList[0] = \"UpdatedServerName\";\n\n            config.Write();\n\n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"DebugMode\"\" value=\"\"DeveloperErrorMessage\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"MaxDisplayListItems\"\" value=\"\"12\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"SendAdminEmailConfirmations\"\" value=\"\"True\"\" />\"));\n\n            // Password and AppSettings  should be encrypted in config file\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"Password\"\" value=\"\"ADoCNO6L1HIm8V7TyI4deg==\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"AppConnectionString\"\" value=\"\"z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=\"\" />\"));\n\n            // Complex Value\n            Assert.IsTrue(text.Contains(@\"Updated Company\"));\n\n            // List values\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"ServerList1\"\"\"));\n            Assert.IsTrue(text.Contains(@\"UpdatedServerName\"));\n\n            \n            var config2 = new CustomConfigFileConfiguration();\n            config2.Initialize();\n            config2.Read();\n\n            Assert.IsTrue(config2.License.Company == \"Updated Company\");\n            Assert.IsTrue(config2.ServerList[0] == \"UpdatedServerName\");\n\n        }\n\n        [TestMethod]\n        public void WriteEncryptedConfigurationTest()\n        {\n            var config = new CustomConfigFileConfiguration();\n            config.Initialize();\n\n            // write secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.Write();\n            \n            string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath());\n            Console.WriteLine(text);\n            \n            // Password and AppSettings  should be encrypted in config file\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"Password\"\" value=\"\"ADoCNO6L1HIm8V7TyI4deg==\"\" />\"));\n            Assert.IsTrue(text.Contains(@\"<add key=\"\"AppConnectionString\"\" value=\"\"z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=\"\" />\"));\n            \n            // now re-read settings into a new object\n            var config2 = new CustomConfigFileConfiguration();\n            config2.Initialize();\n            \n            // check secure properties\n            Assert.IsTrue(config.Password == \"seekrit2\");\n            Assert.IsTrue(config.AppConnectionString == \"server=.;database=unsecured\");\n        }\n    }\n}\n\n#endif"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/DatabaseConfigurationTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing Westwind.Data.Test;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests default config file implementation that uses\n    /// only base constructor behavior - (config file and section config only)    \n    /// </summary>\n    [TestClass]\n    public class DatabaseConfigurationTests\n    {\n\n\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        //[ClassInitialize()]\n        //public static void TestClassInitialize(TestContext testContext)\n        //{\n        //    DatabaseInitializer.InitializeDatabase();\n        //}\n\n\n        /// <summary>\n        /// Note: For Web Apps this should be a complete path.\n        /// Here the filename references the current directory\n        /// </summary>        \n        [TestMethod]\n        public void DefaultConstructorInstanceTest()\n        {\n            // this should create the database table and add default\n            // data into it if it doesn't exist - otherwise\n            // the values are read\n            var config = new DatabaseConfiguration();\n            config.Initialize();\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n        }\n\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            var config = new DatabaseConfiguration();\n            // connection string and table are provided in OnInitialize()\n            config.Initialize();\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // encrypted properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=HRDatabase\";\n\n            Assert.IsTrue(config.Write(), \"Write Failed: \" + config.ErrorMessage);\n\n            // create a new instance and read the values from the database\n            var config2 = new DatabaseConfiguration();\n            config2.Initialize();\n\n            Assert.IsNotNull(config2);\n            Assert.AreEqual(config.MaxDisplayListItems, config2.MaxDisplayListItems);\n            Assert.AreEqual(config.DebugMode, config2.DebugMode);\n\n            // Encrypted values\n            Assert.AreEqual(config.Password, config2.Password);\n            Assert.AreEqual(config.AppConnectionString, config2.AppConnectionString);\n\n            // reset to default val\n            config.MaxDisplayListItems = 15;\n            config.Write();\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/JsonFileConfigurationTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing Newtonsoft.Json;\nusing System.Security.Cryptography.X509Certificates;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests default config file implementation that uses\n    /// only base constructor behavior - (config file and section config only)    \n    /// </summary>\n    [TestClass]\n    public class JsonFileConfigurationTests\n    {\n        /// <summary>\n        /// Note: For Web Apps this should be a complete path.\n        /// Here the filename references the current directory\n        /// </summary>\n        public string STR_JSONCONFIGFILE = \"JsonConfiguration.txt\";\n\n        public JsonFileConfigurationTests()\n        {\n            // force Json ref to load since we dynamically load\n            var json = new JsonSerializerSettings();\n\n\t\t\t// explicitly write file location since tests are in indeterminate folder\n\t\t\t// especially running .NET Core\n\t        STR_JSONCONFIGFILE = Path.Combine(Path.GetTempPath(), STR_JSONCONFIGFILE);\n        }\n\t\t\n        [TestMethod]\n        public void DefaultConstructorInstanceTest()\n        {\n            var config = new JsonFileConfiguration();            \n            config.Initialize(configData: STR_JSONCONFIGFILE);\n            \n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n\n            string text = File.ReadAllText(STR_JSONCONFIGFILE);\n            Console.WriteLine(text);\n        }\n\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            var config = new JsonFileConfiguration();\n            config.Initialize(STR_JSONCONFIGFILE);\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.Write();\n\n            string jsonConfig = File.ReadAllText(STR_JSONCONFIGFILE);\n            Console.WriteLine(jsonConfig);\n\n            Assert.IsTrue(jsonConfig.Contains(@\"\"\"DebugMode\"\": \"\"DeveloperErrorMessage\"\"\"));\n            Assert.IsTrue(jsonConfig.Contains(@\"\"\"MaxDisplayListItems\"\": 12\") );\n            Assert.IsTrue(jsonConfig.Contains(@\"\"\"SendAdminEmailConfirmations\"\": true\"));\n\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(!jsonConfig.Contains($\"\\\"Password\\\": \\\"seekrit\\\"\"));\t\t\t\n\t\t}\n\n        [TestMethod]\n        public void WriteEncryptedConfigurationTest()\n        {\n            File.Delete(STR_JSONCONFIGFILE);\n\n            var config = new JsonFileConfiguration();\n            config.Initialize(STR_JSONCONFIGFILE);\n\n            // write secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.License.Company = \"West Wind 2\";\n            config.License.LicenseKey = \"RickWestWind2-51123222\";\n\n            config.Write();\n\n            // completely reload settings\n            config = new JsonFileConfiguration();\n            config.Initialize(STR_JSONCONFIGFILE);\n\n            Assert.IsTrue(config.Password == \"seekrit2\");\n            Assert.IsTrue(config.License.Company == \"West Wind 2\");\n            Assert.IsTrue(config.License.LicenseKey == \"RickWestWind2-51123222\");\n\n            string jsonConfig = File.ReadAllText(STR_JSONCONFIGFILE);\n            Console.WriteLine(jsonConfig);\n\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(!jsonConfig.Contains(\"\\\"Password\\\": \\\"seekrit2\\\"\"));\n\n\t\t\t// now re-read settings into a new object\n\t\t\tvar config2 = new JsonFileConfiguration();\n            config2.Initialize(STR_JSONCONFIGFILE);\n\n            // check secure properties\n            Assert.IsTrue(config.Password == \"seekrit2\");\n            Assert.IsTrue(config.AppConnectionString == \"server=.;database=unsecured\");\n        }\n\n        [TestMethod]\n        public void RawJsonFileWithSingletonTest()\n        {\n            var config = MyJsonConfiguration.Current;\n\n            Assert.IsNotNull(config);\n            \n            Console.WriteLine(JsonSerializationUtils.Serialize(config, false, true));            \n\n        }\n    }\n\n    public class MyJsonConfiguration : AppConfiguration \n    { \n        public static MyJsonConfiguration Current { get; set; }\n\n        static MyJsonConfiguration()\n        {\n            var config = new MyJsonConfiguration();\n            // assign a provider explicitly\n            config.Provider = new JsonFileConfigurationProvider<MyJsonConfiguration>()\n            {\n                JsonConfigurationFile = \"./SupportFiles/_MyJsonConfiguration.json\"\n            };\n\n            // load and read the configuration\n            //config.Initialize();\n            config.Read();\n\n            Current = config;\n        }\n\n\n        public string MyString { get; set; } = \"Default String\";\n        public int SomeValue { get; set; } = -1;\n\n        \n    }\n\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/StringConfigurationTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests implementation of the string provider.\n    /// \n    /// String providers make it easy to read and write\n    /// configuration values and store them in any source\n    /// that can store strings. For example you can store\n    /// the xml generated in a database field of your choice.\n    /// (there is a database field provider that does that\n    ///  however).\n    /// \n    /// Use App.Configuration.Read(xmlString)\n    /// and App.Configuration.WriteAsString() to read and\n    /// write the XML configuration data for storage in \n    /// any non-existing format you like.\n    /// \n    /// Light weight alternative to creating your own\n    /// configuration provider (although it's easy to \n    /// create one).\n    /// </summary>\n    [TestClass]\n    public class StringConfigurationTests\n    {\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        /// <summary>\n        /// Write out configuration settings to string\n        /// </summary>\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            var config = new StringConfiguration();\n            config.Initialize(null);\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            string xmlConfig = config.WriteAsString();\n\n            Console.WriteLine(xmlConfig);\n\n            Assert.IsTrue(xmlConfig.Contains(@\"<DebugMode>DeveloperErrorMessage</DebugMode>\"));\n            Assert.IsTrue(xmlConfig.Contains(@\"<MaxDisplayListItems>12</MaxDisplayListItems>\"));\n            Assert.IsTrue(xmlConfig.Contains(@\"<SendAdminEmailConfirmations>true</SendAdminEmailConfirmations>\"));\n\n\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(!xmlConfig.Contains(@\"<Password>seekrit2</Password>\"));\n\t\t\tAssert.IsTrue(!xmlConfig.Contains(@\"<AppConnectionString>server=.;database=unsecured</AppConnectionString>\"));\n\t\t}\n\n\t\t/// <summary>\n\t\t/// Unlike other providers the string provider has \n\t\t/// no 'automatic' read mode - it requires a string \n\t\t/// as input to work.\n\t\t/// </summary>\n\t\t[TestMethod]\n        public void ReadConfigurationFromStringTest()\n        {\n\n\t\t\tstring xmlConfig = @\"<?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\"?>\n<StringConfiguration xmlns:xsi=\"\"http://www.w3.org/2001/XMLSchema-instance\"\" xmlns:xsd=\"\"http://www.w3.org/2001/XMLSchema\"\">\n   <ApplicationName>Configuration Tests</ApplicationName>\n   <DebugMode>Default</DebugMode>\n   <MaxDisplayListItems>12</MaxDisplayListItems>\n   <SendAdminEmailConfirmations>false</SendAdminEmailConfirmations>\n   <Password>ADoCNO6L1HIm8V7TyI4deg==</Password>\n   <AppConnectionString>z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=</AppConnectionString>\n</StringConfiguration>\n\";\n            var config = new StringConfiguration();\n\n            // Initialize with configData as parameter to load from\n            config.Initialize(configData: xmlConfig);\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n            Assert.IsTrue(config.MaxDisplayListItems == 12);\n            //Assert.AreEqual(config.Password, \"seekrit2\");                        \n        }\n\n        \n        [TestMethod]\n        public void WriteEncryptedConfigurationTest()\n        {\n            var config = new StringConfiguration();\n            config.Initialize();\n\n            // write secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            var xml = config.WriteAsString(); \n                        \n            Console.WriteLine(xml);\n\n\n\n            // Password and AppSettings  should be encrypted in config file\n            Assert.IsTrue(!xml.Contains(@\"<Password>seekrit2</Password>\"));\n            Assert.IsTrue(!xml.Contains(@\"<AppConnectionString>server=.;database=unsecured</AppConnectionString>\"));\n\n\n\n\t\t\t// now re-read settings into a new object\n\t\t\tvar config2 = new StringConfiguration();\n            // pass XML to deserialize from - OnInitialize() implements this logic\n            //config2.Initialize(xml);  // same as below\n            config2.Initialize(configData: xml);\n                        \n\n            // check secure properties\n            Assert.IsTrue(config.Password == \"seekrit2\");\n            Assert.IsTrue(config.AppConnectionString == \"server=.;database=unsecured\");\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AppConfiguration/XmlFileConfigurationTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    /// <summary>\n    /// Tests default config file implementation that uses\n    /// only base constructor behavior - (config file and section config only)    \n    /// </summary>\n    [TestClass]\n    public class XmlConfigurationTests\n    {\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        /// <summary>\n        /// Note: For Web Apps this should be a complete path.\n        /// Here the filename references the current directory\n        /// </summary>\n        public const string STR_XMLCONFIGFILE = \"XmlConfiguration.xml\";\n\n\n        [TestMethod]\n        public void DefaultConstructorInstanceTest()\n        {\n            var config = new XmlFileConfiguration();\n            config.Initialize(configData: STR_XMLCONFIGFILE);\n\n            Assert.IsNotNull(config);\n            Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName));\n\n            string text = File.ReadAllText(STR_XMLCONFIGFILE);\n            Console.WriteLine(text);\n        }\n\n        [TestMethod]\n        public void WriteConfigurationTest()\n        {\n            var config = new XmlFileConfiguration();\n            config.Initialize(STR_XMLCONFIGFILE);\n\n            config.MaxDisplayListItems = 12;\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            config.ApplicationName = \"Changed\";\n            config.SendAdminEmailConfirmations = true;\n\n            // secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.License.LicenseKey = \"asdss\";\n\n            config.Write();\n\n            string xmlConfig = File.ReadAllText(STR_XMLCONFIGFILE);\n            Console.WriteLine(xmlConfig);\n\n            Assert.IsTrue(xmlConfig.Contains(@\"<DebugMode>DeveloperErrorMessage</DebugMode>\"));\n            Assert.IsTrue(xmlConfig.Contains(@\"<MaxDisplayListItems>12</MaxDisplayListItems>\"));\n            Assert.IsTrue(xmlConfig.Contains(@\"<SendAdminEmailConfirmations>true</SendAdminEmailConfirmations>\"));\n\n#if NETFULL\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(xmlConfig.Contains(@\"<Password>ADoCNO6L1HIm8V7TyI4deg==</Password>\"));\n            Assert.IsTrue(xmlConfig.Contains(@\"<AppConnectionString>z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=</AppConnectionString>\"));\n#else\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(xmlConfig.Contains(@\"<Password>Odou0/8LMaGT52eE0DDA2g==</Password>\"));\n\t\t\tAssert.IsTrue(xmlConfig.Contains(@\"<AppConnectionString>tLde067qv6r54CVKxse7MtgZXnWDdgSH0CBUWL9CHwc=</AppConnectionString>\"));\t\t\t   \n#endif\n\n\t\t}\n\n        [TestMethod]\n        public void WriteEncryptedConfigurationTest()\n        {\n            var config = new XmlFileConfiguration();\n            config.Initialize(STR_XMLCONFIGFILE);\n\n            // write secure properties\n            config.Password = \"seekrit2\";\n            config.AppConnectionString = \"server=.;database=unsecured\";\n\n            config.Write();\n\n            string xmlConfig = File.ReadAllText(STR_XMLCONFIGFILE);\n            Console.WriteLine(xmlConfig);\n\n\n\n\t\t\t// Password and AppSettings  should be encrypted in config file\n\t\t\tAssert.IsTrue(!xmlConfig.Contains(@\"<Password>seekrit2</Password>\"));\n            Assert.IsTrue(!xmlConfig.Contains(@\"<AppConnectionString>server=.;database=unsecured</AppConnectionString>\"));\n\n            // now re-read settings into a new object\n\t\t\tvar config2 = new XmlFileConfiguration();\n            config2.Initialize(STR_XMLCONFIGFILE);\n\n            // check secure properties\n            Assert.IsTrue(config.Password == \"seekrit2\");\n            Assert.IsTrue(config.AppConnectionString == \"server=.;database=unsecured\");\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities.Test/AsyncUtilsTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class AsyncUtilsTests\n    {\n        [TestMethod]\n        public async Task TimeoutAfter2SecondsTest()\n        {\n            var taskToComplete = Task.Run(async () =>\n            {\n                await Task.Delay(7000);\n            });\n\n            bool isComplete = await taskToComplete.Timeout(2000);\n\n            // should time out after 2 seconds\n            Assert.IsFalse(isComplete, \"Task did not complete in time\");\n        }\n\n        [TestMethod]\n        public async Task SucceedTimeoutAfter2SecondsTest()\n        {\n            var taskToComplete = DoSomething(1000);\n\n            bool isComplete = false;\n            try\n            {\n                isComplete = await taskToComplete.Timeout(2000);\n            }\n            catch (Exception ex)\n            {\n                Console.WriteLine(ex.Message);\n            }\n\n            // should time out after 2 seconds\n            Assert.IsTrue(isComplete, \"Task did not complete in time\");\n\n            // Retrieve task result\n            int result = taskToComplete.Result;\n            Assert.AreEqual(result, 100);\n        }\n\n\n\n        [TestMethod]\n        public async Task ExceptionTimeoutAfter2SecondsTest()\n        {\n            var taskToComplete = DoThrow(1000);\n\n            bool isComplete = false;\n            try\n            {\n                // Shouldn't timeout, but throw an exception\n                isComplete = await taskToComplete.Timeout(2000);\n            }\n            catch (Exception ex)\n            {\n                // regular exception handling\n                Console.WriteLine(ex.Message);  // message here\n            }\n\n                        \n            Assert.IsTrue(taskToComplete.IsFaulted, \"Task threw an exception: \" + taskToComplete.Exception.GetBaseException().Message);\n\n            // Or we can pull the exception off the task (aggregate exception so we need base Exception)\n            Console.WriteLine(taskToComplete.Exception.GetBaseException().Message);\n        }\n\n\n\n        [TestMethod]\n        public async Task TimeoutWithResultAfter2SecondsTest()\n        {\n            var taskToComplete = DoSomething(10_000);\n\n            int result = 0;\n            try\n            {\n                // returns int - 100 on success and 0 (default) on failure\n                result = await taskToComplete.TimeoutWithResult<int>(1000);\n            }\n            catch(TimeoutException)\n            {\n                Assert.IsTrue(true);\n                return;\n            }\n            catch (Exception)\n            {\n                Assert.Fail(\"This task should have timed out and there should be no execution exception\");\n            }\n\n            // fails: should return 0. timed out after 2 seconds\n            Assert.Fail(\"This task should have timed out\");\n        }\n\n        [TestMethod]\n        public async Task NoTimeoutWithResultAfter2SecondsTest()\n        {\n            int result = 0;\n            try\n            {\n                // no timeout no exception\n                result = await DoSomething(1500).TimeoutWithResult(2000);\n            }\n            catch(TimeoutException ex)\n            {\n                Assert.Fail(\"Task should not have timed out: \" + ex.Message);\n            }\n            catch(Exception ex)\n            {\n                Assert.Fail(\"Task should not have thrown an exception: \" + ex.Message);\n            }\n            \n            // No timeout since task completes in 1.5 seconds  \n            Assert.IsTrue(result==100, \"Task did not return the correct result\");\n        }\n\n\n        [TestMethod]\n        public async Task TaskExceptionWithResultAfter2SecondsTest()\n        {\n            int result = 0;\n            try\n            {\n                // no timeout no exception\n                result = await DoThrow(1500).TimeoutWithResult<int>(2000);\n            }\n            catch (TimeoutException ex)\n            {\n                Assert.Fail(\"Task should not have timed out: \" + ex.Message);\n            }\n            catch (Exception)\n            {\n                Assert.IsTrue(true);\n                return;\n            }\n\n            // No timeout since task completes in 1.5 seconds  \n            Assert.Fail(\"Task should have failed with an exception.\");\n        }\n\n#if NETCORE\n        [TestMethod]\n        public async Task SucceedTimeoutAfter2SecondsWithTaskWaitTest()\n        {\n\n            int result = 0;\n            try\n            {\n                result = await DoSomething(1000).WaitAsync(TimeSpan.FromSeconds(2));\n            }\n            // Capture Timeout\n            catch (TimeoutException)\n            {\n                Assert.Fail(\"This task method should not have timed out.\");\n            }\n            // Fires on Exception in taskToComplete\n            catch (Exception)\n            {\n                Assert.Fail(\"This task method should not fail with exceptions.\");\n            }\n\n            Assert.AreEqual(100, result);\n        }\n#endif\n\n        private async Task<int> DoSomething(int timeout)\n        {\n            await Task.Delay(timeout);\n       \n            return 100;\n        }\n        private async Task<int> DoThrow(int timeout)\n        {\n            await Task.Delay(timeout);\n            throw new Exception(\"Exception thrown in task method DoThrow\");            \n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/DataUtilsTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Data;\n\nusing System.Diagnostics;\nusing System.Data.Common;\nusing Westwind.Data.Test.Models;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n\t/// <summary>\n\t/// Summary description for DataUtilsTests\n\t/// </summary>\n\t[TestClass]\n    public class DataUtilsTests\n    {\n\t\t//private const string STR_TestDataConnection = \"WestwindToolkitSamples\";\n\t\tprivate static string STR_TestDataConnection = TestConfigurationSettings.WestwindToolkitConnectionString;\n\n\n\t\t[TestMethod]\n\t\tpublic void DataTableToListTest()\n\t\t{\n\t\t\tvar sql = new SqlDataAccess(STR_TestDataConnection);\n\t\t\tvar dt = sql.ExecuteTable(\"items\", \"select * from ApplicationLog\");\n\n\t\t\tAssert.IsNotNull(dt, \"Failed to load test data: \" + sql.ErrorMessage);\n\n\t\t\tvar items = DataUtils.DataTableToObjectList<Customer>(dt);\n\n\t\t\tAssert.IsNotNull(items);\n\t\t\tAssert.IsTrue(items.Count > 0);\n\t\t\tConsole.WriteLine(items.Count);\n\t\t}\n\n        #region Byte Operations\n\n        [TestMethod]\n        public void IndexOfByteArrayTest()\n        {\n            var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80, 0x0a, 0x31, 0x33, 0x20};\n            var bytesToFind = new byte[] {0xe2, 0x80};\n\n            Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1);\n        }\n\n        [TestMethod]\n        public void IndexOfByteArrayEndTest()\n        {\n            var bytes = new byte[] {0x31, 0x33, 0x20, 0x0a, 0x31, 0x33, 0x20, 0xe2, 0x80};\n            var bytesToFind = new byte[] {0xe2, 0x80};\n\n            Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1);\n        }\n\n        [TestMethod]\n        public void IndexOfByteArrayBeginTest()\n        {\n            var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20, 0x0a, 0x31, 0x33, 0x20};\n            var bytesToFind = new byte[] {0xe2, 0x80};\n\n            Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1);\n        }\n\n        [TestMethod]\n        public void ReplaceBytesTest()\n        {\n            var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80, 0x0a, 0x31, 0x33, 0x20};\n            var bytesToRemove = new byte[] {0xe2, 0x80};\n            byte[] results = DataUtils.RemoveBytes(bytes, bytesToRemove );\n\n            Console.WriteLine(\"-- Replace Middle --\");\n            Console.WriteLine(\"Before: \" + StringUtils.BinaryToBinHex(bytes));\n            Console.WriteLine(\"After : \" + StringUtils.BinaryToBinHex(results));\n\n            Assert.IsTrue(results.Length == bytes.Length - 2);\n            Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1);\n            \n        }\n\n        [TestMethod]\n        public void ReplaceBytesEndTest()\n        {\n           Console.WriteLine(\"-- Replace End --\");\n            var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80 };\n            var bytesToRemove = new byte[] {0xe2, 0x80};\n            var results = DataUtils.RemoveBytes(bytes, bytesToRemove );\n\n            Console.WriteLine(\"Before: \" + StringUtils.BinaryToBinHex(bytes));\n            Console.WriteLine(\"After : \" + StringUtils.BinaryToBinHex(results));\n\n            Assert.IsTrue(results.Length == bytes.Length - 2);\n            Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1);\n        }\n\n        [TestMethod]\n        public void ReplaceBytesBeginningTest()\n        {\n            Console.WriteLine(\"-- Replace Beginning --\");\n            var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20 };\n            var bytesToRemove = new byte[] {0xe2, 0x80};\n            var results = DataUtils.RemoveBytes(bytes, bytesToRemove );\n\n            Console.WriteLine(\"Before: \" + StringUtils.BinaryToBinHex(bytes));\n            Console.WriteLine(\"After : \" + StringUtils.BinaryToBinHex(results));\n\n            Assert.IsTrue(results.Length == bytes.Length - 2);\n            Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1);\n        }\n\n        [TestMethod]\n        public void ReplaceBytesBeginningAndEndTest()\n        {\n            Console.WriteLine(\"-- Replace Beginning and End --\");\n            var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20, 0xe2, 0x80 };\n            var bytesToRemove = new byte[] {0xe2, 0x80};\n            var results = DataUtils.RemoveBytes(bytes, bytesToRemove );\n\n            Console.WriteLine(\"Before: \" + StringUtils.BinaryToBinHex(bytes));\n            Console.WriteLine(\"After : \" + StringUtils.BinaryToBinHex(results));\n\n            Assert.IsTrue(results.Length == bytes.Length - 4);\n            Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1);\n        }\n        #endregion\n\n#if false\n        // Add Npgsql package\n        [TestMethod]\n        public void GetPostGreSqlProviderTest()\n        {\n            var provider = DataUtils.GetDbProviderFactory(DataAccessProviderTypes.PostgreSql);\n            Assert.IsNotNull(provider);            \n        }\n#endif\n\n\n#if false\n        // Add MySql.Data Package   \n        [TestMethod]\n        public void GetMySqlProviderTest()\n        {\n            var provider = DataUtils.GetDbProviderFactory(DataAccessProviderTypes.MySql);\n            Assert.IsNotNull(provider);            \n        }\n#endif\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/DynamicDataReaderTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Data;\nusing System.Data.Common;\nusing Westwind.Utilities;\nusing Microsoft.CSharp.RuntimeBinder;\nusing Westwind.Utilities.Data;\nusing System.Diagnostics;\nusing System.Threading;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n\n    /// <summary>\n    /// Summary description for DynamicDataRowTests\n    /// </summary>\n    [TestClass]\n    public class DynamicDataReaderTests\n    {\n\t\tprivate static string STR_ConnectionString = TestConfigurationSettings.WestwindToolkitConnectionString;\n    \n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        [ClassInitialize()]\n        public static void MyClassInitialize(TestContext testContext) {\n\n\t\t\t// warm up dynamic runtime\n            dynamic test = new List<int>();\n            var val = test.Count;\n\n\n            //DatabaseInitializer.InitializeDatabase();\n\n            // warm up data connection\n\n            using (SqlDataAccess data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var readr = data.ExecuteReader(\"select top 1 * from Customers\");\n                var x = readr.Read();\n            }\n        }\n\n        [TestMethod]\n        public void BasicDataReaderTimerTests()\n        {\n            DbDataReader reader;\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                reader = data.ExecuteReader(\"select * from Customers\");\n                Assert.IsNotNull(reader, \"Query Failure: \" + data.ErrorMessage);\n\n\n                StringBuilder sb = new StringBuilder();\n\n                Stopwatch watch = new Stopwatch();\n                watch.Start();\n\n                while (reader.Read())\n                {\n                    string firstName = reader[\"FirstName\"] as string;\n                    string lastName = reader[\"LastName\"] as string;\n                    string company = reader[\"Company\"] as string;\n\n                    DateTime? entered = reader[\"Entered\"] as DateTime?;\n                    string d = entered.HasValue ? entered.Value.ToString(\"d\") : string.Empty;\n\n                    sb.AppendLine(firstName + \" \" + lastName + \" \" + company + \" - \" + entered.Value.ToString(\"d\"));\n                }\n\n                watch.Stop();\n\n\n                Console.WriteLine(watch.ElapsedMilliseconds.ToString());\n                Console.WriteLine(sb.ToString());\n            }\n        }\n\n\n        [TestMethod]\n        public void BasicDynamicDataReaderTimerTest()\n        {\n            dynamic reader;\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                reader = data.ExecuteDynamicDataReader(\"select * from customers\");\n\n                Assert.IsNotNull(reader, \"Query Failure: \" + data.ErrorMessage);\n\n                StringBuilder sb = new StringBuilder();\n\n                Stopwatch watch = new Stopwatch();\n                watch.Start();\n\n\n                while (reader.Read())\n                {\n\n                    string firstName = reader.FirstName;\n                    string lastName = reader.LastName;\n                    string company = reader.Company;\n\n                    DateTime? entered = reader.Entered as DateTime?;\n                    string d = entered.HasValue ? entered.Value.ToString(\"d\") : string.Empty;\n\n                    sb.AppendLine(firstName + \" \" + lastName + \" \" + company + \" - \" + entered.Value.ToString(\"d\"));\n                }\n\n                watch.Stop();\n\n                reader.Close();\n\n                Console.WriteLine(watch.ElapsedMilliseconds.ToString());\n                Console.WriteLine(sb.ToString());\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/DynamicDataRowTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Data;\nusing Westwind.Utilities;\nusing Microsoft.CSharp.RuntimeBinder;\nusing Westwind.Utilities.Data;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n    /// <summary>\n    /// Summary description for DynamicDataRowTests\n    /// </summary>\n    [TestClass]\n    public class DynamicDataRowTests\n    {\n        public DynamicDataRowTests()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n        [TestMethod]\n        [ExpectedException(typeof(RuntimeBinderException))]\n        public void BasicDataRowTests()\n        {\n            DataTable table = new DataTable(\"table\");\n            table.Columns.Add( new DataColumn() { ColumnName = \"Name\", DataType = typeof(string) });\n            table.Columns.Add( new DataColumn() { ColumnName = \"Entered\", DataType = typeof(DateTime) });\n            table.Columns.Add(new DataColumn() { ColumnName = \"NullValue\", DataType = typeof(string) });\n\n            DataRow row = table.NewRow();\n\n            DateTime now = DateTime.Now;\n\n            row[\"Name\"] = \"Rick\";\n            row[\"Entered\"] = now;\n            row[\"NullValue\"] = null; // converted in DbNull\n\n            dynamic drow = new DynamicDataRow(row);\n\n            string name = drow.Name;\n            DateTime entered = drow.Entered;\n            string nulled = drow.NullValue;\n\n            Assert.AreEqual(name, \"Rick\");\n            Assert.AreEqual(entered, now);\n            Assert.IsNull(nulled);\n            // this should throw a RuntimeBinderException\n            Assert.AreEqual(entered, drow.enteredd);\n        }\n\n        [TestMethod]\n        public void DynamicDataTableTest()\n        {\n            DataTable table = new DataTable(\"table\");\n            table.Columns.Add(new DataColumn() { ColumnName = \"Name\", DataType = typeof(string) });\n            table.Columns.Add(new DataColumn() { ColumnName = \"Entered\", DataType = typeof(DateTime) });\n            table.Columns.Add(new DataColumn() { ColumnName = \"NullValue\", DataType = typeof(string) });\n\n            DataRow row = table.NewRow();\n\n            DateTime now = DateTime.Now;\n\n            row[\"Name\"] = \"Rick\";\n            row[\"Entered\"] = now;\n            row[\"NullValue\"] = null; // converted in DbNull\n\n            table.Rows.Add(row);\n\n            row = table.NewRow();\n            row[\"Name\"] = \"Don\";\n            row[\"Entered\"] = now.AddDays(1);\n            row[\"NullValue\"] = \"Not Null\"; // converted in DbNull\n            table.Rows.Add(row);\n\n            foreach (dynamic drow in table.DynamicRows())\n            {\n                Console.WriteLine(drow.Name + \" \" + drow.Entered.ToString(\"d\") + \" \" + drow.NullValue);\n            }\n\n            dynamic drow2 = table.DynamicRows()[1];\n            Console.WriteLine(drow2.Name + \" \" + drow2.Entered.ToString(\"d\") + \" \" + drow2.NullValue);\n            \n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/EncryptionTests.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Security.Cryptography;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class EncryptionTests\n    {\n        /// <summary>\n\t\t/// 16 byte (or anything less than 24 bytes) are no longer supported\n\t\t/// by .NET Core Triple DES. Encrypt/Decrypt pad out or trim \n\t\t/// the buffer to fit 24 bytes, so the following should work.\n\t\t/// </summary>\n\t\t[TestMethod]\n\t    public void SimpleEncryptDecryptWith16ByteKey()\n\t    {\n\t\t    string data = \"Seekrit!Password\";\n\t\t    byte[] key = new byte[16] {3, 166, 3, 5, 222, 13, 155, 55, 122, 123, 165, 187, 188, 1,11, 133};\n\t\t\tstring encrypted = Encryption.EncryptString(data, key);\n\t\t\tstring decrypted = Encryption.DecryptString(encrypted, key);\n\n\t\t\tAssert.AreEqual(data, decrypted);\n\t\t}\n\n\t\t[TestMethod]\n\t\tpublic void SimpleEncryptWith24ByteKey()\n\t\t{\n\t\t\tstring data = \"Seekrit!Password\";\n\t\t\tbyte[] key = new byte[24] { 3, 166, 3, 5, 222, 13, 155, 55, 122, 123, 165, 187, 188, 1, 11, 133,1,2,3,4,5,6,7,8 };\n\t\t\tstring encrypted = Encryption.EncryptString(data, key);\n\t\t\tstring decrypted = Encryption.DecryptString(encrypted, key);\n\n\t\t\tAssert.AreEqual(data, decrypted);\n\t\t}\n\n\t\t[TestMethod]\n        public void EncryptDecryptString()\n        {\n            string data = \"Seekrit!Password\";\n            string key = \"my+keeper\";\n\n            string encrypted = Encryption.EncryptString(data,key);\n            string decrypted = Encryption.DecryptString(encrypted,key);\n\n            Assert.AreNotEqual(data, encrypted);\n            Assert.AreEqual(data, decrypted);\n\n            Console.WriteLine(encrypted);            \n        }\n\n        [TestMethod]\n        public void EncryptDecryptWithExtendedCharacterString()\n        {            \n            string data = \"Seekrit°!Password\";\n            string key = \"my+keeper\";\n\n            string encrypted = Encryption.EncryptString(data, key);\n            string decrypted = Encryption.DecryptString(encrypted, key);\n\n            Assert.AreNotEqual(data, encrypted);\n            Assert.AreEqual(data, decrypted);\n\n            Console.WriteLine(encrypted);\n        }\n\n\n        [TestMethod]\n        public void EncryptDecryptWithExtendedCharacterStringByteKey()\n        {\n            string data = \"Seekrit°!Password\";\n            byte[] key = new byte[] {10, 20, 88, 223, 132, 1, 55, 32};\n\n            string encrypted = Encryption.EncryptString(data, key);\n            string decrypted = Encryption.DecryptString(encrypted, key);\n\n            Assert.AreNotEqual(data, encrypted);\n            Assert.AreEqual(data, decrypted);\n\n            Console.WriteLine(encrypted);\n        }\n\n        [TestMethod]\n        public void EncryptDecryptWithExtendedCharacterByteData()\n        {\n            byte[] data = new byte[] {1, 3, 22, 224, 113, 53, 31, 6, 12, 44, 49, 66};\n            byte[] key = new byte[] { 2, 3, 4, 5, 6};\n\n            byte[] encrypted = Encryption.EncryptBytes(data, key);\n            byte[] decrypted = Encryption.DecryptBytes(encrypted, key);\n\n            Assert.IsTrue(decrypted.SequenceEqual(data));\n\n            Console.WriteLine(encrypted);\n        }\n\n\n        [TestMethod]\n        public void HashValues()\n        {             \n            string data = \"Seekrit!Password\";\n            byte[] salt = new byte[] { 10, 22, 144, 51, 55, 61};\n            string algo = \"SHA1\";\n\n            string encrypted = Encryption.ComputeHash(data, algo, salt,useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"test\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n            \n\n            data = \"t\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n\n            data = \"testa\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"testa\";\n            var encrypted2 = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n        }\n\n\n        [TestMethod]\n        public void HashValuesHMAC()\n        {\n            string data = \"Seekrit!Password\";\n            byte[] salt = new byte[] { 10, 22, 144, 51, 55, 61 };\n            string saltString = \"bogus\";\n            string algo = \"SHA256\";\n\n            string encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"test\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"testa\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"t\";\n            encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            var binData = new byte[] { 10, 20, 21, 44, 55, 233, 122, 193 };\n            encrypted = Encryption.ComputeHash(binData, algo, salt, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n            data = \"test\";\n            encrypted = Encryption.ComputeHash(data, algo, saltString, useBinHex: true);\n            Console.WriteLine(encrypted);\n\n\n            data = \"test\";\n            var encrypted2 = Encryption.ComputeHash(data, algo, saltString, useBinHex: false);\n            Console.WriteLine(encrypted);\n\n            byte[] decryptBytes1 = Encryption.BinHexToBinary(encrypted);\n            byte[] decryptBytes2 = Convert.FromBase64String(encrypted2);\n\n            Assert.IsTrue( decryptBytes1.SequenceEqual(decryptBytes2));\n        }\n\n        [TestMethod]\n        public void BinHexBase64CompareTest()\n        {\n\n            string algo = \"HMACSHA256\";\n            string saltString = \"bogus\";\n\n            string data = \"test\";\n            string encrypted = Encryption.ComputeHash(data, algo, saltString, useBinHex: true);\n            Console.WriteLine(encrypted);\n            \n            string encrypted2 = Encryption.ComputeHash(data, algo, saltString, useBinHex: false);\n            Console.WriteLine(encrypted2);\n            \n            byte[] decryptBytes1 = Encryption.BinHexToBinary(encrypted);\n            byte[] decryptBytes2 = Convert.FromBase64String(encrypted2);\n\n            Assert.IsTrue(decryptBytes1.SequenceEqual(decryptBytes2));\n        }\n\n\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ExpandUrlsParserTest.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for UnitTest1\n    /// </summary>\n    [TestClass]\n    public class ExpandUrlsParserTest\n    {\n        public ExpandUrlsParserTest()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n        [TestMethod]\n        public void ParseExpandedLinks()\n        {\n            string text = \"This is a test with an [embedded link|www.west-wind.com] that is formatted and one to www.west-wind.com that is not.\";\n\n            string parsed = UrlParser.ExpandUrls(text, null, true);\n\n            TestContext.WriteLine(\"{0}\", parsed);\n\n            Assert.IsTrue(parsed.Contains(\"<a href='http://www.west-wind.com'>embedded link</a>\"), \"link parsing failed.\");\n            Assert.IsTrue(parsed.Contains(\"<a href='http://www.west-wind.com'>www.west-wind.com</a>\"), \"link parsing failed.\");\n        }\n\n        [TestMethod]\n        public void ParseOddExpandedLinks()\n        {\n            string text = \"This is a test with an [embedded link|jquery.ui.com] that is formatted and one to http://skodia.name.com that is not.\";\n\n            string parsed = UrlParser.ExpandUrls(text, null, true);\n\n            TestContext.WriteLine(\"{0}\", parsed);\n\n            Assert.IsTrue(parsed.Contains(\"<a href='http://jquery.ui.com'>embedded link</a>\"), \"link parsing failed.\");\n            Assert.IsTrue(parsed.Contains(\"<a href='http://skodia.name.com'>http://skodia.name.com</a>\"), \"link parsing failed.\");\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ExpandoTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\nusing System.Dynamic;\nusing Microsoft.CSharp.RuntimeBinder;\nusing Newtonsoft.Json;\n\nnamespace ExpandoTests\n{\n\n    /// <summary>\n    /// Summary description for ExpandoTests\n    /// </summary>\n    [TestClass]\n    public class ExpandoTests\n    {\n        public ExpandoTests()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        #region Additional test attributes\n\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n\n        #endregion\n\n        /// <summary>\n        /// Summary method that demonstrates some\n        /// of the basic behaviors.\n        /// \n        /// More specific tests are provided below\n        /// </summary>\n        [TestMethod]\n        public void ExandoBasicTests()\n        {\n            // Set standard properties\n            var ex = new User()\n            {\n                Name = \"Rick\",\n                Email = \"rstrahl@whatsa.com\",\n                Active = true\n            };\n            \n            // set dynamic properties that don't exist on type\n            dynamic exd = ex;\n            exd.Entered = DateTime.Now; \n            exd.Company = \"West Wind\";\n            exd.Accesses = 10;\n\n            // set dynamic properties as dictionary\n            ex[\"Address\"] = \"32 Kaiea\";\n            ex[\"Email\"] = \"rick@west-wind.com\";\n            ex[\"TotalOrderAmounts\"] = 51233.99M;\n\n            // iterate over all properties dynamic and native\n            foreach (var prop in ex.GetProperties(true))\n            {\n                Console.WriteLine(prop.Key + \" \" + prop.Value);\n            }\n\n            // you can access plain properties both as explicit or dynamic\n            Assert.AreEqual(ex.Name, exd.Name, \"Name doesn't match\");\n\n            // You can access dynamic properties either as dynamic or via IDictionary\n            Assert.AreEqual(exd.Company, ex[\"Company\"] as string, \"Company doesn't match\");\n            Assert.AreEqual(exd.Address, ex[\"Address\"] as string, \"Name doesn't match\");\n\n            // You can access strong type properties via the collection as well (inefficient though)\n            Assert.AreEqual(ex.Name, ex[\"Name\"] as string);\n\n            // dynamic can access everything\n            Assert.AreEqual(ex.Name, exd.Name); // native property\n            Assert.AreEqual(ex[\"TotalOrderAmounts\"], exd.TotalOrderAmounts); // dictionary property\n        }\n\n\n        [TestMethod]\n        public void AddAndReadDynamicPropertiesTest()\n        {\n            // strong typing first\n            var ex = new User();\n            ex.Name = \"Rick\";\n            ex.Email = \"rick@whatsa.com\";\n\n            // create dynamic and create new props\n            dynamic exd = ex;\n            \n            string company = \"West Wind\";\n            int count = 10;\n\n            exd.entered = DateTime.Now; \n            exd.Company = company;\n            exd.Accesses = count;\n\n            Assert.AreEqual(exd.Company, company);\n            Assert.AreEqual(exd.Accesses, count);\n        }\n\n        [TestMethod]\n        public void AddAndReadDynamicIndexersTest()\n        {\n            var ex = new ExpandoInstance();\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            string address = \"32 Kaiea\";\n\n            ex[\"Address\"] = address;\n            ex[\"Contacted\"] = true;\n\n            dynamic exd = ex;\n\n            Assert.AreEqual(exd.Address, ex[\"Address\"]);\n            Assert.AreEqual(exd.Contacted, true);\n        }\n\n\n        [TestMethod]\n        public void PropertyAsIndexerTest()\n        {\n            // Set standard properties\n            var ex = new ExpandoInstance();\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            Assert.AreEqual(ex.Name, ex[\"Name\"]);\n            Assert.AreEqual(ex.Entered, ex[\"Entered\"]);\n        }\n\n        [TestMethod]\n        public void DynamicAccessToPropertyTest()\n        {\n            // Set standard properties\n            var ex = new ExpandoInstance();\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            // turn into dynamic\n            dynamic exd = ex;\n\n            // Dynamic can access \n            Assert.AreEqual(ex.Name, exd.Name);\n            Assert.AreEqual(ex.Entered, exd.Entered);\n\n        }\n\n        [TestMethod]\n        public void IterateOverDynamicPropertiesTest()\n        {\n            var ex = new ExpandoInstance();\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            dynamic exd = ex;\n            exd.Company = \"West Wind\";\n            exd.Accesses = 10;\n\n            // Dictionary pseudo implementation\n            ex[\"Count\"] = 10;\n            ex[\"Type\"] = \"NEWAPP\";\n\n            // Dictionary Count - 2 dynamic props added\n            Assert.IsTrue(ex.Properties.Count == 4);\n\n            // iterate over all properties\n            foreach (KeyValuePair<string, object> prop in exd.GetProperties(true))\n            {\n                Console.WriteLine(prop.Key + \" \" + prop.Value);\n            }\n        }\n\n        [TestMethod]\n        public void MixInObjectInstanceTest()\n        {\n            // Create expando an mix-in second objectInstanceTest\n            var ex = new ExpandoInstance(new Address());\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            // create dynamic\n            dynamic exd = ex;\n\n            // values should show Addresses initialized values (not null)\n            Console.WriteLine(exd.FullAddress);\n            Console.WriteLine(exd.Email);\n            Console.WriteLine(exd.Phone);\n        }\n\n        [TestMethod]\n        public void TwoWayJsonSerializeExpandoTyped()\n        {\n            // Set standard properties\n            var ex = new User()\n            {\n                Name = \"Rick\",\n                Email = \"rstrahl@whatsa.com\",\n                Password = \"Seekrit23\",\n                Active = true\n            };\n\n            // set dynamic properties\n            dynamic exd = ex;\n            exd.Entered = DateTime.Now;\n            exd.Company = \"West Wind\";\n            exd.Accesses = 10;\n\n            // set dynamic properties as dictionary\n            ex[\"Address\"] = \"32 Kaiea\";\n            ex[\"Email\"] = \"rick@west-wind.com\";\n            ex[\"TotalOrderAmounts\"] = 51233.99M;\n\n            // *** Should serialize both static properties dynamic properties\n            var json = JsonConvert.SerializeObject(ex, Formatting.Indented);\n            Console.WriteLine(\"*** Serialized Native object:\");\n            Console.WriteLine(json);\n\n            Assert.IsTrue(json.Contains(\"Name\")); // static\n            Assert.IsTrue(json.Contains(\"Company\")); // dynamic\n\n\n            // *** Now deserialize the JSON back into object to \n            // *** check for two-way serialization\n            var user2 = JsonConvert.DeserializeObject<User>(json);\n            json = JsonConvert.SerializeObject(user2, Formatting.Indented);\n            Console.WriteLine(\"*** De-Serialized User object:\");\n            Console.WriteLine(json);\n\n            Assert.IsTrue(json.Contains(\"Name\")); // static\n            Assert.IsTrue(json.Contains(\"Company\")); // dynamic\n        }\n\n#if SupportXmlSerialization\n        [TestMethod]\n        public void TwoWayXmlSerializeExpandoTyped()\n        {\n            // Set standard properties\n            var ex = new User();\n            ex.Name = \"Rick\";\n            ex.Active = true;\n\n\n            // set dynamic properties\n            dynamic exd = ex;\n            exd.Entered = DateTime.Now;\n            exd.Company = \"West Wind\";\n            exd.Accesses = 10;\n\n            // set dynamic properties as dictionary\n            ex[\"Address\"] = \"32 Kaiea\";\n            ex[\"Email\"] = \"rick@west-wind.com\";\n            ex[\"TotalOrderAmounts\"] = 51233.99M;\n            \n            // Serialize creates both static and dynamic properties\n            // dynamic properties are serialized as a 'collection'\n            string xml;\n            SerializationUtils.SerializeObject(exd, out xml);\n            Console.WriteLine(\"*** Serialized Dynamic object:\");\n            Console.WriteLine(xml);\n\n            Assert.IsTrue(xml.Contains(\"Name\")); // static\n            Assert.IsTrue(xml.Contains(\"Company\")); // dynamic\n\n            // Serialize\n            var user2 = SerializationUtils.DeSerializeObject(xml,typeof(User));\n            SerializationUtils.SerializeObject(exd, out xml);\n            Console.WriteLine(xml);\n\n            Assert.IsTrue(xml.Contains(\"Rick\")); // static\n            Assert.IsTrue(xml.Contains(\"West Wind\")); // dynamic\n        }\n#endif\n\n\n        [TestMethod]\n        public void ExpandoObjectJsonTest()\n        {\n            dynamic ex = new ExpandoObject();\n            ex.Name = \"Rick\";\n            ex.Entered = DateTime.Now;\n\n            string address = \"32 Kaiea\";\n\n            ex.Address = address;\n            ex.Contacted = true;\n\n            ex.Count = 10;\n            ex.Completed = DateTime.Now.AddHours(2);\n            \n            string json = JsonConvert.SerializeObject(ex,Formatting.Indented);\n            Console.WriteLine(json);\n        }\n\n       \n\n        [TestMethod]\n        public void UserExampleTest()\n        {\n            var user = new User();\n\n            // Set strongly typed properties\n            user.Email = \"rick@west-wind.com\";\n            user.Password = \"nonya123\";\n            user.Name = \"Rickochet\";\n            user.Active = true;\n\n            // Now add dynamic properties\n            dynamic duser = user;\n            duser.Entered = DateTime.Now;\n            duser.Accesses = 1;\n\n            // you can also add dynamic props via indexer \n            user[\"NickName\"] = \"Wreck\";\n            duser[\"WebSite\"] = \"http://www.west-wind.com/weblog\";\n\n            // Access strong type through dynamic ref\n            Assert.AreEqual(user.Name, duser.Name);\n\n            // Access strong type through indexer \n            Assert.AreEqual(user.Password, user[\"Password\"]);\n\n\n            // access dyanmically added value through indexer\n            Assert.AreEqual(duser.Entered, user[\"Entered\"]);\n\n            // access index added value through dynamic\n            Assert.AreEqual(user[\"NickName\"], duser.NickName);\n\n\n            // loop through all properties dynamic AND strong type properties (true)\n            foreach (var prop in user.GetProperties(true))\n            {\n                object val = prop.Value;\n                if (val == null)\n                    val = \"null\";\n\n                Console.WriteLine(prop.Key + \": \" + val.ToString());\n            }\n        }\n\n        [TestMethod]\n        public void ExpandoMixinTest()\n        {\n            // have Expando work on Addresses\n            var user = new User(new Address());\n\n            // cast to dynamicAccessToPropertyTest\n            dynamic duser = user;\n\n            // Set strongly typed properties\n            duser.Email = \"rick@west-wind.com\";\n            user.Password = \"nonya123\";\n\n            // Set properties on address object\n            duser.Address = \"32 Kaiea\";\n            //duser.Phone = \"808-123-2131\";\n\n            // set dynamic properties\n            duser.NonExistantProperty = \"This works too\";\n\n            // shows default value Address.Phone value\n            Console.WriteLine(duser.Phone);\n        }\n\n\n        public class ObjWithProp : Expando\n        {\n            public string SomeProp { get; set; }\n        }\n\n        [TestMethod]\n        public void GivenPropWhenSetWithIndexThenPropsValue()\n        {\n            //arrange\n            ObjWithProp obj = new ObjWithProp();\n            //act\n            obj.SomeProp = \"value1\";\n            obj[\"SomeProp\"] = \"value2\";\n\n            //assert\n            Assert.AreEqual(\"value2\", obj.SomeProp);\n\n        }\n\n        [TestMethod]\n        [ExpectedException(typeof(RuntimeBinderException))]\n        public void InvalidAssignmentErrorOnStaticProperty()\n        {\n            dynamic dynUser = new User();\n            dynUser.Name = 100;  // RuntimeBinderException\n\n            // this should never run\n            var user = dynUser as User;\n            user.Name = \"Rick\";\n            Console.WriteLine(user.Name);                        \n            Console.WriteLine(user[\"Name\"]);\n            \n\n            Assert.Fail(\"Invalid Assignment should have thrown exception\");\n            //>> 100\n        }\n\n        [TestMethod]\n        public void ExpandoFromDictionary()\n        {\n            var dict = new Dictionary<string, object>()\n            {\n                {\"Name\", \"Rick\"},\n                {\"Company\", \"West Wind\"},\n                {\"Accesses\", 2}\n            };\n\n            dynamic expando = new Expando(dict);\n\n            Console.WriteLine(expando.Name);\n            Console.WriteLine(expando.Company);\n            Console.WriteLine(expando.Accesses);\n\n            Assert.AreEqual(dict[\"Name\"], expando.Name);\n            Assert.AreEqual(dict[\"Company\"], expando.Company);\n            Assert.AreEqual(dict[\"Accesses\"], expando.Accesses);\n        }\n\n        [TestMethod]\n        public void ExpandoFromList()\n        {\n            var props = new Dictionary<string, object>();\n            props.Add(\"Name\", \"Rick\");\n            props.Add(\"Contains\", false);\n            props.Add(\"Remove\", false);\n            props.Add(\"InList\", false);\n            props.Add(\"Count\", 1);\n            \n            var expando = new Expando(props);\n            dynamic obj = expando;\n\n            object val = expando[\"Contains\"];\n\n            Console.WriteLine(val);\n        }\n\n    }\n    \n    public class ExpandoInstance : Expando\n    {\n        public string Name { get; set; }\n        public DateTime Entered { get; set; }\n\n        public ExpandoInstance()\n        { }\n\n        /// <summary>\n        /// Allow passing in of an instance\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        public ExpandoInstance(object instance)\n            : base(instance)\n        { }\n    }\n\n    public class Address\n    {\n        public string FullAddress { get; set; }\n        public string Email { get; set; }\n        public string Phone { get; set; }\n\n        public Address()\n        {\n            FullAddress = \"32 Kaiea\";\n            Phone = \"808 132-3456\";\n            Email = \"rick@whatsa.com\";\n        }\n    }\n\n    public class User : Expando\n    {\n        public string Email { get; set; }\n        public string Password { get; set; }\n        public string Name { get; set; }\n        public bool Active { get; set; }\n        public DateTime? ExpiresOn { get; set; }\n\n        public User() : base()\n        {\n        }\n\n        // only required if you want to mix in seperate instance\n        public User(object instance)\n            : base(instance)\n        {\n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/FileUtilsTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing System.Linq;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class FileUtilsTest\n    {\n        [TestMethod]\n        public void SafeFilenameTest()\n        {\n            string file = \"This: iS this file really invalid?\";\n\n            string result = FileUtils.SafeFilename(file);\n\n            Assert.AreEqual(result, \"This iS this file really invalid\");\n            Console.WriteLine(result);\n        }\n\n        [TestMethod]\n        public void NormalizePathTest()\n        {\n            var path = @\"c:\\temp\\test/work/play.txt\";\n            string normal = FileUtils.NormalizePath(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n            path = @\"\\temp\\test/work/play.txt\";\n            normal = FileUtils.NormalizePath(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n            path = @\"temp\\test/work/play.txt\";\n            normal = FileUtils.NormalizePath(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 3);\n        }\n\n\n        [TestMethod]\n        public void NormalizeDirectoryTest()\n        {\n            var path = @\"c:\\temp\\test\\work\";\n            string normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n            path = @\"c:\\temp\\test\\work\\\";\n            normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n            path = @\"\\temp\\test\\work\\\";\n            normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n            path = @\"\\temp\\test/work/\";\n            normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n\n\n            path = @\"\\temp\\test/work/play.txt\";\n            normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 5);\n\n            path = @\"temp\\test/work/bogus\";\n            normal = FileUtils.NormalizeDirectory(path);\n            Console.WriteLine(normal);\n            Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4);\n        }\n\n        [TestMethod]\n        public void CompactPathTest()\n        {\n            var path = @\"c:\\temp\\test\\node_modules\\SomeVeryLongComponentNameSpaceAndName\\SomeLongComponentName.md\";\n            Console.WriteLine(\"Orig: \" + path);\n            var result = FileUtils.GetCompactPath(path);\n            Console.WriteLine($\"{result.Length} {result}\");\n            Assert.IsTrue(result.Length == 70);\n\n\n            path = @\"c:\\temp\\SomeLongComponentName.md\";\n            Console.WriteLine(\"Orig: \" + path);\n            result = FileUtils.GetCompactPath(path);\n            Console.WriteLine($\"{result.Length} {result}\");\n            Assert.IsTrue(result.Length < 70);\n\n            path = @\"\\\\temp\\test\\node_modules\\SomeVeryLongComponentNameSpaceAndName\\SomeLongComponentName.md\";\n            Console.WriteLine(\"Orig: \" + path);\n            result = FileUtils.GetCompactPath(path);\n            Console.WriteLine($\"{result.Length} {result}\");\n            Assert.IsTrue(result.Length == 70);\n\n        }\n\n        [TestMethod]\n        public void TildefyPathTest()\n        {\n\n            var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), \"Projects\\\\Markdown Monster\");\n            var result = FileUtils.TildefyUserPath(path);\n\n            Assert.IsTrue(result.StartsWith(\"~\"));\n            Assert.IsTrue(result.Contains(\"Projects\\\\Markdown Monster\"));\n            Console.WriteLine(result);\n\n            path = null;\n            result = FileUtils.TildefyUserPath(path);\n            Assert.IsTrue(result == null);\n\n            path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);\n            result = FileUtils.TildefyUserPath(path);\n            Assert.IsTrue(result == \"~\");\n\n\n            path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + \"\\\\\";\n            result = FileUtils.TildefyUserPath(path);\n            Assert.IsTrue(result == \"~\\\\\");\n        }\n\n\n\n        [TestMethod]\n        public void CopyDirectory()\n        {\n            string target = Path.Combine(Path.GetTempPath(), \"_TestFolders2\");\n            string source = target.TrimEnd('2');\n\n            try\n            {\n                Directory.Delete(target, true);\n            }\n            catch { }\n            try\n            {\n                Directory.Delete(source, true);\n            }\n            catch { }\n\n            Directory.CreateDirectory(source);\n            Directory.CreateDirectory(Path.Combine(source, \"SubFolder1\"));\n            Directory.CreateDirectory(Path.Combine(source, \"SubFolder2\"));\n            File.WriteAllText(Path.Combine(source, \"test.txt\"), \"Hello cruel world\");\n            File.WriteAllText(Path.Combine(source, \"SubFolder1\", \"test.txt\"), \"Hello cruel world\");\n            File.WriteAllText(Path.Combine(source, \"SubFolder2\", \"test.txt\"), \"Hello cruel world\");\n\n            FileUtils.CopyDirectory(source, target);\n\n            Assert.IsTrue(Directory.Exists(target));\n            Assert.IsTrue(File.Exists(Path.Combine(source, \"test.txt\")));\n            Assert.IsTrue(File.Exists(Path.Combine(source, \"SubFolder1\", \"test.txt\")));\n            Assert.IsTrue(File.Exists(Path.Combine(source, \"SubFolder2\", \"test.txt\")));\n\n            Directory.Delete(target, true);\n            Directory.Delete(source, true);\n        }\n\n        [TestMethod]\n        public void ExpandPathEnvironmentVariablesTest()\n        {\n            string path = \"%appdata%\\\\Markdown Monster\";\n            string result = FileUtils.ExpandPathEnvironmentVariables(path);\n\n            Console.WriteLine(result);\n            Assert.IsFalse(result.Contains(\"%appdata%\"));\n            Assert.IsTrue(result.ToLower().Contains(\"appdata\"));\n        }\n\n        [TestMethod]\n        public void ExpandPathEnvironmentVariablesUserFolderTest()\n        {\n            string path = \"~\\\\Projects\";\n            string result = FileUtils.ExpandPathEnvironmentVariables(path);\n\n            Console.WriteLine(result);\n            Assert.AreNotEqual(path, result);\n            Assert.IsFalse(result.Contains(\"~\\\\\"));\n\n        }\n\n        [TestMethod]\n        public void DeleteFilesTest()\n        {\n            string source = Path.Combine(Path.GetTempPath(), \"_TestFolders\");\n            try\n            {\n                Directory.Delete(source, true);\n            }\n            catch { }\n\n            Directory.CreateDirectory(source);\n            Directory.CreateDirectory(Path.Combine(source, \"SubFolder1\"));\n            Directory.CreateDirectory(Path.Combine(source, \"SubFolder2\"));\n            File.WriteAllText(Path.Combine(source, \"test.txt\"), \"Hello cruel world\");\n            File.WriteAllText(Path.Combine(source, \"SubFolder1\", \"test.txt\"), \"Hello cruel world\");\n            File.WriteAllText(Path.Combine(source, \"SubFolder2\", \"test.txt\"), \"Hello cruel world\");\n\n            Console.WriteLine(source);\n            int fails = FileUtils.DeleteFiles(source, \"test.txt\", recursive: true);\n\n            Directory.Delete(source, true);\n\n            Assert.IsTrue(fails == 0, \"Failed to delete all files\");\n        }\n\n        //[TestMethod]\n        //public void ShortPathTest()\n        //{\n        //    var path = FileUtils.ExpandPathEnvironmentVariables(@\"%appdata%\\Markdown Monster\\MarkdownMonster.json\");\n        //    var shortPath = FileUtils.GetShortPath(path);\n\n        //    Assert.IsFalse(path == shortPath);\n        //    Console.WriteLine(shortPath);\n\n        //    //path = path.Replace(\".json\", \".json-bogus\");\n        //    //shortPath = FileUtils.GetShortPath(path);\n        //    //Assert.IsFalse(path == shortPath);\n        //    //Console.WriteLine(shortPath);\n\n        //}\n\n        [TestMethod]\n        public void FindFileHierarchicalUp()\n        {\n\n            var basePath = Path.GetFullPath(\"./SupportFiles/SquareImage.jpg\");\n            var matchedFile = FileUtils.FindFileInHierarchy(basePath, \"Westwind.Utilities.dll\", FileUtils.FindFileInHierarchyDirection.Up);\n\n            Assert.IsNotNull(matchedFile);\n            Console.WriteLine(matchedFile);\n        }\n\n\n        [TestMethod]\n        public void FindFileHierarchicalDown()\n        {\n            var basePath = Path.GetFullPath(\"./SupportFiles\");\n            var matchedFile = FileUtils.FindFileInHierarchy(basePath, \"SquareImage.jpg\", FileUtils.FindFileInHierarchyDirection.Down);\n\n            Assert.IsNotNull(matchedFile);\n            Console.WriteLine(matchedFile);\n        }\n\n        [TestMethod]\n        public void FindFilesHierarchal()\n        {\n            var basePath = Path.GetFullPath(\"./SupportFiles\");\n            var matches = FileUtils.FindFilesInHierarchy(basePath, \"*.jpg\", FileUtils.FindFileInHierarchyDirection.Down);\n\n            Assert.IsNotNull(matches);\n            Assert.IsTrue(matches.Length > 0);\n\n            foreach (var file in matches)\n            {\n                Console.WriteLine(file);\n            }\n\n        }\n\n\n        [TestMethod]\n        public void GetRelativePathTest()\n        {\n            string basePath = @\"C:\\Users\\johndoe\\Documents\";\n            string fullPath = @\"c:\\Users\\Johndoe\\Documents\\Visual Studio #$%*!(# 2022\\Code Snippets\\Visual C#\\My Code Snippets\\clw.snippet\";\n\n            var relative = FileUtils.GetRelativePath(fullPath, basePath);\n\n            Assert.AreNotEqual(relative, fullPath);\n            Assert.IsFalse(relative.StartsWith(\"\\\\\"));\n\n            Console.WriteLine(relative);\n        }\n\n\n        /// <summary>\n        /// Checks to see if a path is a relative path based on \n        /// how it is formatted. \n        /// Note this method DOES NOT check against any other path\n        /// it merely checks to see if it's a path that is relative\n        /// based on how the path starts.\n        /// </summary>\n        [TestMethod]\n        public void IsRelativePathTest()\n        {\n            string path = \"test.txt\";\n            Assert.IsTrue(FileUtils.IsRelativePath(path), \"Single path should be relative\");\n\n            path = \"..\\\\test.txt\";\n            Assert.IsTrue(FileUtils.IsRelativePath(path), \"..\\\\ path should be relative\");\n\n            path = \"\\\\test.txt\";\n            Assert.IsFalse(FileUtils.IsRelativePath(path), \"\\\\ path should not be relative\");\n\n\n            path = \"d:\\\\temp\\\\test.txt\";\n            Assert.IsFalse(FileUtils.IsRelativePath(path), \"Absolute path should not be relative\");\n\n            path = \"\\\\\\\\RASMSI\\\\temp\\\\test.txt\";\n            Assert.IsFalse(FileUtils.IsRelativePath(path), \"Network path should not be relative\");\n\n\n            path = \"file:///d:/temp/test.txt\";\n            Assert.IsFalse(FileUtils.IsRelativePath(path), \"file:// path  should not be relative\");\n\n        }\n\n        [TestMethod]\n        public void ResolvePathTest()\n        {\n            var basePath = @\"c:\\temp\\subfolder\\test.txt\";\n\n            string path = \"..\\\\test.txt\";\n            var result = FileUtils.ResolvePath(basePath, path);\n            Assert.AreEqual(result, @\"c:\\temp\\test.txt\");\n\n            path = \"test.txt\";\n            result = FileUtils.ResolvePath(basePath, path);\n            Assert.AreEqual(result, @\"c:\\temp\\subfolder\\test.txt\");\n\n            path = \".\\\\test.txt\";\n            result = FileUtils.ResolvePath(basePath, path);\n            Assert.AreEqual(result, @\"c:\\temp\\subfolder\\test.txt\");\n\n\n            basePath = @\"c:\\temp\\subfolder\\\";  // folder - !IMPORTANT add backslash to folders!\n            path = \"..\\\\test.txt\";\n            result = FileUtils.ResolvePath(basePath, path);\n            Assert.AreEqual(result, @\"c:\\temp\\test.txt\");\n\n\n            path = \"\\\\test.txt\";\n            result = FileUtils.ResolvePath(basePath, path);\n            Console.WriteLine(result);\n            Assert.AreEqual(result, @\"c:\\test.txt\");\n\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/HttpClientTests.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Westwind.Utilities.InternetTools;\n\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace Westwind.Utilities.InternetTools\n{\n    [TestClass]\n    public class HttpClientTests\n    { \n\n        [TestMethod]\n        public void InvalidUrlTest()\n        {\n            var client = new HttpClient();\n\n            var html = client.DownloadString(\"http://weblog.west-wind.com/nonexistantpage.htm\");\n\n            Assert.IsTrue(client.WebResponse.StatusCode == System.Net.HttpStatusCode.NotFound);            \n            Console.WriteLine(client.WebResponse.StatusCode);\n        }\n\n\n      \n        [TestMethod]\n        public void HttpTimingsTest()\n        {\n            var client = new HttpClient();\n\n            var html = client.DownloadString(\"http://weblog.west-wind.com/posts/2015/Jan/06/Using-Cordova-and-Visual-Studio-to-build-iOS-Mobile-Apps\");\n\n            Console.WriteLine(client.WebResponse.ContentLength);\n            Console.WriteLine(client.HttpTimings.StartedTime);\n            Console.WriteLine(\"First Byte: \" + client.HttpTimings.TimeToFirstByteMs);\n            Console.WriteLine(\"Last Byte: \" + client.HttpTimings.TimeToLastByteMs);\n        }\n\n\n        [TestMethod]\n        public async Task HttpTimingsTestsAsync()\n        {\n            var client = new HttpClient();\n\n            var html = await client.DownloadStringAsync(\"http://weblog.west-wind.com/posts/2015/Jan/06/Using-Cordova-and-Visual-Studio-to-build-iOS-Mobile-Apps\");\n\n            Console.WriteLine(client.WebResponse.ContentLength);\n            Console.WriteLine(client.HttpTimings.StartedTime);\n            Console.WriteLine(\"First Byte: \" + client.HttpTimings.TimeToFirstByteMs);\n            Console.WriteLine(\"Last Byte: \" + client.HttpTimings.TimeToLastByteMs);\n        }\n\n        [TestMethod]\n        public void AddPostKeyGetPostBufferTest()\n        {\n            var client = new HttpClient();\n            client.ContentType = \"application/x-www-form-urlencoded\";\n\n            client.AddPostKey(\"ctl00_Content_Username\", \"Rick\");\n            client.AddPostKey(\"ctl00_Content_Password\", \"seekrit\");\n\n            string post = client.GetPostBuffer();\n            Console.WriteLine(post);\n\n            Assert.IsTrue(post.Contains(\"&ctl00_Content_Password=\"));\n        }\n\n        [TestMethod]\n        public void AddPostKeyRawGetPostBufferTest()\n        {\n            var client = new HttpClient();\n            client.ContentType = \"application/x-www-form-urlencoded\";\n\n            // raw POST buffer\n            client.AddPostKey(\"name=Rick&company=West%20Wind\");\n            \n            string post = client.GetPostBuffer();\n            Console.WriteLine(post);\n\n            Assert.IsTrue(post == \"name=Rick&company=West%20Wind\");\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/HttpClientUtilsTests.cs",
    "content": "﻿\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Net;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class HttpClientUtilsTests\n    {\n        public HttpClientUtilsTests()\n        {\n            // force Json.NET to load\n            //var f = Newtonsoft.Json.Formatting.Indented;\n        }\n\n        [TestMethod]\n        public async Task HttpRequestStringWithUrlTest()\n        {\n            string html = await HttpClientUtils.DownloadStringAsync(new HttpClientRequestSettings\n            {\n                Url = \"https://west-wind.com\"\n            });\n            Assert.IsNotNull(html);\n            Console.WriteLine(html);\n        }\n\n\n        [TestMethod]\n        public async Task HttpRequestStringWithBadUrlTest()\n        {\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"https://west-wind.com/bogus.html\"\n            };\n            string html = await HttpClientUtils.DownloadStringAsync(settings);\n\n\n            // result is a 404 with content but success has no result (null)\n            Assert.IsNull(html);\n\n            Assert.IsTrue(settings.HasErrors);\n            Assert.IsTrue(settings.ResponseStatusCode == HttpStatusCode.NotFound);\n\n            var content = await settings.GetResponseStringAsync();\n\n            Assert.IsNotNull(content);\n            Console.WriteLine(content);\n        }\n\n\n        [TestMethod]\n        public async Task ASynchBytesDownloadTest()\n        {\n            byte[] html = await HttpClientUtils.DownloadBytesAsync(\"https://west-wind.com/images/wwtoolbarlogo.png\");\n            Assert.IsNotNull(html);\n            Console.WriteLine(html.Length);\n        }\n\n        [TestMethod]\n        public async Task DownloadAsFileAsyncTest()\n        {\n            var dlFile = Path.Combine(Path.GetTempPath(), \"~wwtoolbarlogo.png\");\n            if (File.Exists(dlFile))\n                File.Delete(dlFile);\n\n            bool result = await HttpClientUtils.DownloadFileAsync(\"https://west-wind.com/images/wwtoolbarlogo.png\",dlFile);\n            Assert.IsTrue(result);\n            Assert.IsTrue(File.Exists(dlFile));\n            File.Delete(dlFile);\n        }\n\n\n\n        [TestMethod]\n        public async Task DownloadAsFileWithSettingsAsyncTest()\n        {\n            var dlFile = Path.Combine(Path.GetTempPath(), \"~wwtoolbarlogo.png\");\n            if (File.Exists(dlFile))\n                File.Delete(dlFile);\n\n            var settings = new HttpClientRequestSettings { \n                Url = \"https://west-wind.com/images/wwtoolbarlogo.png\",\n                OutputFilename = dlFile\n            };\n            bool result = await HttpClientUtils.DownloadFileAsync(settings: settings);\n\n            Assert.IsTrue(result);\n\n            // helper for header\n            Assert.AreEqual(settings.ResponseContentType,\"image/png\");\n            // raw header access\n            Assert.AreEqual(settings.ResponseContentHeaders.ContentType?.MediaType, \"image/png\");\n            Assert.IsTrue(File.Exists(dlFile));\n            File.Delete(dlFile);\n        }\n\n\n#if NET6_0_OR_GREATER\n        [TestMethod]\n        public void SynchronousStringDownloadTest()\n        {            \n            string html = HttpClientUtils.DownloadString(\"https://west-wind.com\");\n            Assert.IsNotNull(html);\n            Console.WriteLine(html.GetMaxCharacters(1000));\n        }\n\n        [TestMethod]\n        public void SynchronousStringSettingsDownload()\n        {\n            string html = HttpClientUtils.DownloadString(new HttpClientRequestSettings\n            {\n                Url = \"https://west-wind.com\"\n            });\n            Assert.IsNotNull(html);\n            Console.WriteLine(html.GetMaxCharacters(1000));\n        }\n\n        [TestMethod]\n        public void SynchronousBytesDownload()\n        {\n            byte[] html = HttpClientUtils.DownloadBytes(\"https://west-wind.com/images/wwtoolbarlogo.png\");\n            Assert.IsNotNull(html);\n            Console.WriteLine(html.Length);\n        }\n#endif\n\n        [TestMethod]\n        public async Task HttpRequestJsonStringWithUrlTest()\n        {\n            // returns a 404 so bad request\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"http://websurgeapi.west-wind.com/api/authentication/usertokenvalidation/1234\"\n            };\n            string json = await HttpClientUtils.DownloadStringAsync(settings);\n            Assert.IsNull(json);\n            Assert.IsTrue(settings.HasErrors);\n\n\n            json = await settings.GetResponseStringAsync();\n            Console.WriteLine(json);\n            Console.WriteLine(settings.ErrorMessage);\n        }\n\n        [TestMethod]\n        public async Task HttpRequestHtmlPostTest()\n        {\n            string html = await HttpClientUtils.DownloadStringAsync(new HttpClientRequestSettings\n            {\n                Url = \"https://west-wind.com/wconnect/Testpage.wwd\",\n                HttpVerb = \"POST\",\n                RequestContent = \"FirstName=Rick&Company=West+Windx\",\n                RequestContentType = \"application/x-www-form-urlencoded\"\n            });\n\n            Assert.IsNotNull(html);\n            Console.WriteLine(html);\n        }\n\n\n        [TestMethod]\n        public async Task HttpRequestBadJsonPostTest()\n        {\n            string json = \"\"\"\n{\n    \"username\": \"test@test.com\",\n    \"password\": \"kqm3ube0jnm!QKC9wcx\"\n}\n\"\"\";\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"https://store.west-wind.com/api/account/authenticate\",\n                HttpVerb = \"POST\",\n                RequestContent = json,\n                RequestContentType = \"application/json\"\n            };\n\n            json = await HttpClientUtils.DownloadStringAsync(settings);\n\n            Assert.IsNull(json, settings.ErrorMessage);\n\n            json = await settings.GetResponseStringAsync();\n\n            Assert.IsNotNull(json, \"Error content not retrieved.\");\n\n            Console.WriteLine(json);\n\n            // \n            var jobj = await settings.GetResponseJson<JObject>();\n            Assert.IsNotNull(jobj);\n\n            Console.WriteLine(jobj);\n            Console.WriteLine(((dynamic)jobj).message);\n        }\n\n\n\n        [TestMethod]\n        public async Task HttpRequestBadJsonPostWithExceptionsTest()\n        {\n            string json = \"\"\"\n                          {\n                              \"username\": \"test@test.com\",\n                              \"password\": \"kqm3ube0jnm!QKC9wcx\"\n                          }\n                          \"\"\";\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"https://albumviewer.west-wind.com/api/account/bogus\",\n                HttpVerb = \"POST\",\n                RequestContent = json,\n                RequestContentType = \"application/json\",\n                ThrowExceptions = true\n            };\n\n            try\n            {\n                json = await HttpClientUtils.DownloadStringAsync(settings);\n                Console.WriteLine(json);\n            }\n            catch (Exception ex)\n            {\n                Console.WriteLine(ex.Message);  // 404\n\n                var htmlErrorPage = await settings.GetResponseStringAsync();\n                Assert.IsNotNull(htmlErrorPage);\n                Assert.IsTrue(htmlErrorPage.StartsWith(\"<!DOCTYPE\"));\n                //Console.WriteLine(htmlErrorPage);\n            }\n\n        }\n\n\n        [TestMethod]\n        public async Task HttpMaxResponseSizeTest()\n        {\n            int maxSize = 200;\n            string html = await HttpClientUtils.DownloadStringAsync(new HttpClientRequestSettings\n            {\n                Url = \"http://west-wind.com/wconnect/Testpage.wwd\",\n                HttpVerb = \"HEAD\",                \n                MaxResponseSize = maxSize\n            });\n\n\n            Assert.IsNotNull(html);\n            Assert.IsTrue(html.Length <= maxSize);\n            Console.WriteLine(html);\n        }\n\n        [TestMethod]\n        public async Task HttpPostRequest()\n        {\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"http://west-wind.com/wconnect/Testpage.wwd\",\n                HttpVerb = \"POST\",\n                RequestFormPostMode = HttpFormPostMode.UrlEncoded,         \n            };\n            settings.AddPostKey(\"FirstName\", \"Rick\");\n            settings.AddPostKey(\"LastName\", \"Strahl\");\n            settings.AddPostKey(\"Company\", \"West Windx\");\n             \n            string html = await HttpClientUtils.DownloadStringAsync(settings);         \n\n            Assert.IsNotNull(html);\n            Assert.IsTrue(html.Contains(\"lcFirstname=Rick\"));\n\n            ShellUtils.ShowHtml(html);\n        }\n\n        [TestMethod]\n        public async Task HttpUploadFileRequest()\n        {\n            var settings = new HttpClientRequestSettings\n            {\n                Url = \"http://west-wind.com/wconnect/wcscripts/FileUpload.wwd\",\n                HttpVerb = \"POST\",\n                RequestFormPostMode = HttpFormPostMode.MultiPart\n            };\n            settings.AddPostKey(\"reference\", \"Sail Big\");\n            settings.AddPostFile(\"upload\",\"SupportFiles/images/SailBig.jpg\", \"image/jpeg\");\n            string html = await HttpClientUtils.DownloadStringAsync(settings);\n\n            Assert.IsNotNull(html);\n\n            ShellUtils.GoUrl(\"https://west-wind.com/wconnect/temp/SailBig.jpg\");\n\n            Assert.IsTrue(html.Contains(\"SailBig.jpg\"));\n\n        }\n\n        [TestMethod]\n        public async Task DownloadImageAsyncTest()\n        {\n            string filename = await HttpClientUtils.DownloadImageToFileAsync(\"https://markdownmonster.west-wind.com/Images/MarkdownMonster_Icon_32.png\", @\"c:\\temp\\test.png\");\n            Assert.IsNotNull(filename);\n            Assert.IsTrue(File.Exists(filename));\n            Console.WriteLine(filename);\n\n            //ShellUtils.ShellExecute(filename);\n            //await Task.Delay(250);\n\n            if (File.Exists(filename))\n                File.Delete(filename);\n        }\n\n        [TestMethod]\n        public async Task DownloadImageAsyncSettingsTest()\n        {\n            string filename = await HttpClientUtils.DownloadImageToFileAsync(new HttpClientRequestSettings\n            {\n                Url = \"https://markdownmonster.west-wind.com/Images/MarkdownMonster_Icon_32.png\",\n                OutputFilename = @\"c:\\temp\\test.png\"\n            });\n            Assert.IsNotNull(filename);\n            Assert.IsTrue(File.Exists(filename));\n            Console.WriteLine(filename);\n\n            //ShellUtils.ShellExecute(filename);\n            //await Task.Delay(250);\n\n            if (File.Exists(filename))\n                File.Delete(filename);\n        }\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/HttpUtilsTests.cs",
    "content": "﻿using Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class HttpUtilsTests\n    {\n        public HttpUtilsTests()\n        {\n            // force Json.NET to load\n            //var f = Newtonsoft.Json.Formatting.Indented;\n        }\n\n        [TestMethod]\n        public void HttpRequestStringWithUrlTest()\n        {\n            string html = HttpUtils.HttpRequestString(\"http://microsoft.com\");\n            Assert.IsNotNull(html);\n        }\n\n        [TestMethod]\n        public async Task HttpRequestStringWithUrlAsyncTest()\n        {\n            string html = await HttpUtils.HttpRequestStringAsync(\"http://microsoft.com\");\n            Assert.IsNotNull(html);\n        }\n\n        [TestMethod]\n        [ExpectedException(typeof(WebException))]\n        public void InvalidUrlTest()\n        {\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"http://west-wind.com/invalidurl.html\",\n            };\n\n            string html = HttpUtils.HttpRequestString(settings);                       \n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.NotFound);\n\n        }\n\n        \n        \n        [TestMethod]\n        public void HttpRequestStringWithSettingsTest()\n        {\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"http://microsoft.com\",                 \n            };\n\n            string html = HttpUtils.HttpRequestString(settings);\n            Assert.IsNotNull(html);\n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);\n        }\n\n#if false      // Web Site has gone away\n\n        [TestMethod]\n        public void JsonRequestTest()\n        {\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"http://codepaste.net/recent?format=json\",\n            };\n\n            var snippets = HttpUtils.JsonRequest<List<CodeSnippet>>(settings);\n\n            Assert.IsNotNull(snippets);\n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);\n            Assert.IsTrue(snippets.Count > 0);\n            Console.WriteLine(snippets.Count);\n        }\n\n        [TestMethod]\n        public async Task JsonRequestAsyncTest()\n        {\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"https://albumviewer.west-wind.com/album/37\"\n\n            };\n\n            var snippets = await HttpUtils.JsonRequestAsync<List<CodeSnippet>>(settings);\n\n            Assert.IsNotNull(snippets);\n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);\n            Assert.IsTrue(snippets.Count > 0);\n            Console.WriteLine(snippets.Count);\n            Console.WriteLine(settings.CapturedResponseContent);\n        }\n\n        [TestMethod]\n        public void JsonRequestPostTest()\n        {\n            var postSnippet = new CodeSnippet()\n            {\n                UserId = \"Bogus\",\n                Code = \"string.Format('die Bären sind süss und sauer.');\",\n                Comment = \"World domination imminent\"\n            };\n\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"http://codepaste.net/recent?format=json\",\n                Content = postSnippet,\n                HttpVerb = \"POST\"\n            };\n\n            var snippets = HttpUtils.JsonRequest<List<CodeSnippet>>(settings);\n\n            Assert.IsNotNull(snippets);\n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);\n            Assert.IsTrue(snippets.Count > 0);\n\n            Console.WriteLine(snippets.Count);\n            Console.WriteLine(settings.CapturedRequestContent);\n            Console.WriteLine();\n            Console.WriteLine(settings.CapturedResponseContent);\n\n            foreach (var snippet in snippets)\n            {\n                if (string.IsNullOrEmpty(snippet.Code))\n                    continue;\n                Console.WriteLine(snippet.Code.Substring(0, Math.Min(snippet.Code.Length, 200)));\n                Console.WriteLine(\"--\");\n            }\n            \n            Console.WriteLine(\"Status Code: \" + settings.Response.StatusCode);\n\n            foreach (var header in settings.Response.Headers)\n            {\n                Console.WriteLine(header + \": \" + settings.Response.Headers[header.ToString()]);\n            }\n        }\n\n        [TestMethod]\n        public async Task JsonRequestPostAsyncTest()\n        {\n            var postSnippet = new CodeSnippet()\n            {\n                UserId = \"Bogus\",\n                Code = \"string.Format('Hello World, I will own you!');\",\n                Comment = \"World domination imminent\"\n            };\n\n            var settings = new HttpRequestSettings()\n            {\n                Url = \"http://codepaste.net/recent?format=json\",\n                Content = postSnippet,\n                HttpVerb = \"POST\"\n            };\n\n            var snippets = await HttpUtils.JsonRequestAsync<List<CodeSnippet>>(settings);\n\n            Assert.IsNotNull(snippets);\n            Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK);\n            Assert.IsTrue(snippets.Count > 0);\n\n            Console.WriteLine(snippets.Count);\n            Console.WriteLine(settings.CapturedRequestContent);\n            Console.WriteLine();\n            Console.WriteLine(settings.CapturedResponseContent);\n\n            foreach (var snippet in snippets)\n            {\n                if (string.IsNullOrEmpty(snippet.Code))\n                    continue;\n                Console.WriteLine(snippet.Code.Substring(0, Math.Min(snippet.Code.Length, 200)));\n                Console.WriteLine(\"--\");\n            }\n\n            // This doesn't work for the async version - Response is never set by the base class\n            Console.WriteLine(\"Status Code: \" + settings.Response.StatusCode);\n\n            foreach (var header in settings.Response.Headers)\n            {\n                Console.WriteLine(header + \": \" + settings.Response.Headers[header.ToString()]);\n            }\n        }\n#endif\n\n        [TestMethod]\n        public void DownloadImageFile()\n        {\n            string url = \"https://markdownmonster.west-wind.com/Images/MarkdownMonster_Icon_32.png\";\n\n            string fname = null;\n            try\n            {\n                fname = HttpUtils.DownloadImageToFile(url);\n\n                Console.WriteLine(fname);\n\n                Assert.IsNotNull(fname);\n                Assert.IsTrue(File.Exists(fname));\n\n                //ShellUtils.ShellExecute(fname,null);\n                //Thread.Sleep(500);\n            }\n            finally\n            {\n                if (!string.IsNullOrEmpty(fname))\n                   File.Delete(fname);\n\n                Assert.IsFalse(File.Exists(fname));\n            }\n        }\n\n\n        [ExpectedException(typeof(WebException))]\n        [TestMethod()]\n        public void HttpTimeout()\n        {\n            string result = HttpUtils.HttpRequestString(new HttpRequestSettings()\n            {\n                Url=\"http://west-wind.com/files/wconnect.exe\",\n                Timeout = 100\n            });\n\t\t\t\n            Assert.IsNotNull(result);\n        }\n    }\n\n    public class CodeSnippet\n    {\n        public int CommentCount { get; set; }\n        public string Id { get; set; }\n        public string UserId { get; set; }\n        public string Language { get; set; }\n        public int Views { get; set; }\n        public string Code { get; set; }\n        public string Comment { get; set; }\n        public DateTime Entered { get; set; }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ImagingTests.cs",
    "content": "#if NETFULL\nusing System;\nusing System.Drawing.Imaging;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Drawing;\nusing System.IO;\n\nnamespace Westwind.Utilities.Tests\n{\n    [TestClass]\n    public class ImagingTests\n    {\n        private string ImageFile = @\"supportfiles\\sailbig.jpg\";\n        private string ImageFileWork = @\"supportfiles\\sailbigWork.jpg\";\n        private string ImageFileRotated = @\"supportfiles\\sailbigRotated.jpg\";\n        private string SquareImageFile = @\"supportfiles\\SquareImage.jpg\";\n        private string HighQualityImageFile = @\"supportfiles\\HighQuality.jpg\";\n        private string HighQualityImageFileWork = @\"supportfiles\\HighQualityWork.jpg\";\n\n\n        [TestMethod]\n        public void RotateFileToFileTest()\n        {\n            string orig = ImageFile;\n            string work = ImageFileWork;\n            string rotated = ImageFileRotated;\n\n            File.Copy(orig,work,true);\n            ImageUtils.RoateImage(work,rotated,RotateFlipType.Rotate270FlipNone);            \n            File.Copy(rotated,work,true);\n\n        }\n\n        [TestMethod]\n        public void RotateFileToSelfFileTest()\n        {\n            string orig = ImageFile;\n\n            // work on sailbig3 and write output to sailbig3\n            string work = ImageFileWork;\n            string rotated = ImageFileRotated;\n\n            File.Copy(orig, work, true);\n            ImageUtils.RoateImage(work, rotated, RotateFlipType.Rotate270FlipNone);            \n        }\n\n        [TestMethod]\n        public void RotateFileInMemory()\n        {\n            string orig = ImageFile;\n\n            // work on sailbig3 and write output to sailbig3\n            string work = ImageFileWork;\n            string rotated = ImageFileRotated;\n\n            var imgData = File.ReadAllBytes(orig); \n            var rotatedData = ImageUtils.RoateImage(imgData, RotateFlipType.Rotate270FlipNone);\n\n            Assert.IsNotNull(rotatedData);\n\n            File.WriteAllBytes(rotated, rotatedData);\n        }\n\n\n\n        [TestMethod]\n        public void ResizeBitMap()\n        {\n            string orig = ImageFile;\n            string copied = ImageFileWork;\n\n            using (var bmp = new Bitmap(orig))\n            {\n                var bmp2 = ImageUtils.ResizeImage(bmp, 150, 150);\n                bmp2.Save(copied);\n                bmp2.Dispose();\n            }\n        }\n\n        [TestMethod] \n        public void ResizeBitMapFile()\n        {\n            string orig = ImageFile;\n            string copied = ImageFileWork;\n\n            bool res = ImageUtils.ResizeImage(orig,copied, 150, 150);\n\n            Assert.IsTrue(res);\n        }\n\n        [TestMethod]\n        public void ResizeSquareBitMap()\n        {\n            string orig = SquareImageFile;\n            string copied = ImageFileWork;\n\n            using (var bmp = new Bitmap(orig))\n            {\n                var bmp2 = ImageUtils.ResizeImage(bmp, 100, 150);\n\n                Assert.IsTrue(bmp2.Width == 150, \"Image was not resized correctly.\");\n\n                Console.WriteLine(bmp2.RawFormat.Guid + \" (New)\");\n                Console.WriteLine(bmp.RawFormat.Guid + \" (File)\");\n                Console.WriteLine(ImageFormat.Jpeg.Guid + \" (Jpeg)\");\n                               \n                bmp2.Save(copied,ImageFormat.Jpeg);\n                bmp2.Dispose();\n            }\n        }\n\n        [TestMethod]\n        public void ResizeHighQualityBitMap()\n        {\n            string orig = HighQualityImageFile;\n            string copied = HighQualityImageFileWork;\n\n            Assert.IsTrue(ImageUtils.ResizeImage(orig, copied, 2000, 2000));\n\n            Console.WriteLine(\"Orig file: \" + (new FileInfo(orig).Length/1000).ToString(\"n2\") + \"kb\");\n            Console.WriteLine(\"Updated file: \" + (new FileInfo(copied).Length / 1000).ToString(\"n2\") + \"kb\");\n        }\n\n    }\n}\n#endif"
  },
  {
    "path": "Westwind.Utilities.Test/Models/Entities/Customer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\n\nusing System.Linq;\nusing System.Text;\nusing Westwind.Utilities;\n\nnamespace Westwind.Data.Test.Models\n{\n    public class Customer\n    {\n        public int Id { get; set; }\n\n        \n        public string FirstName { get; set; }\n        \n        public string LastName { get; set; }\n        \n        public string Company { get; set; }\n        \n        public string Address { get; set; }\n\n        public string Password { get; set; }\n\n        public DateTime? LastOrder { get; set; }\n\n        public DateTime Entered { get; set; }\n        public DateTime Updated { get; set; }\n\n        public byte[] Binary { get; set;  }\n\n        public List<Order> Orders { get; set; }\n\n        public Customer()\n        {\n            Id = (int) Math.Abs(DataUtils.GenerateUniqueNumericId());\n            Entered = DateTime.Now;\n            Updated = DateTime.Now;\n            Orders = new List<Order>();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/Models/Entities/LineItem.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Text;\n\nnamespace Westwind.Data.Test.Models\n{\n    public class LineItem\n    {\n        public int Id { get; set; }\n        public int OrderId { get; set; }\n\n        \n        public string Sku { get; set; }\n        \n        public string Description { get; set; }\n        public decimal Quantity { get; set; }\n        public decimal Price { get; set; }\n\n        public decimal Total { get; set; }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/Models/Entities/Order.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text;\n\nnamespace Westwind.Data.Test.Models\n{\n    public class Order\n    {\n        public int Id { get; set; }\n        \n        \n        public int CustomerPk { get; set; }\n\n        public string OrderId { get; set; }\n        public DateTime Entered { get; set; }\n        public DateTime? Shipped { get; set; }\n\n        public List<LineItem> LineItems { get; set; }\n        public Customer Customer { get; set; }\n\n        public Order()\n        {\n            Entered = DateTime.Now;\n            Shipped = null;\n            LineItems = new List<LineItem>();\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/Models/Entities/WebLogEntry.cs",
    "content": "﻿#region License\n/*\n **************************************************************\n *  Author: Rick Strahl \n *          © West Wind Technologies, 2009\n *          http://www.west-wind.com/\n * \n * Created: 09/12/2009\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use,\n * copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following\n * conditions:\n * \n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n * OTHER DEALINGS IN THE SOFTWARE.\n **************************************************************  \n*/\n#endregion\n\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Web;\nusing System.Net;\n\nnamespace Westwind.Utilities.Test.Models.Entities\n\n{\n    /// <summary>\n    /// A Web specific Log entry that includes information about the current Web Request\n    /// </summary>\n    public class WebLogEntry : LogEntry\n    {\n        public WebLogEntry() { }\n        public WebLogEntry(Exception ex) : base(ex) { }\n\n#if NETFULL\n        public WebLogEntry(Exception ex, HttpContext context) : base(ex)\n        {\n            UpdateFromRequest(context);\n        }\n#endif\n\n        /// <summary>\n        /// The Url without the query string for the current request\n        /// </summary>\n        public string Url\n        {\n            get { return _Url; }\n            set { _Url = value; }\n        }\n        private string _Url = \"\";\n\n\n        /// <summary>\n        /// The query string of the current request\n        /// </summary>\n        public string QueryString\n        {\n            get { return _QueryString; }\n            set { _QueryString = value; }\n        }\n        private string _QueryString = \"\";\n\n        /// <summary>\n        /// The IP Address of the client that called this URL\n        /// </summary>\n        public string IpAddress\n        {\n            get { return _IpAddress; }\n            set { _IpAddress = value; }\n        }\n        private string _IpAddress = \"\";\n\n\n        /// <summary>\n        /// The POST data if available\n        /// </summary>\n        public string PostData\n        {\n            get { return _PostData; }\n            set { _PostData = value; }\n        }\n        private string _PostData = \"\";\n\n        /// <summary>\n        /// The Referring url\n        /// </summary>\n        public string Referrer\n        {\n            get { return _Referrer; }\n            set { _Referrer = value; }\n        }\n        private string _Referrer = \"\";\n\n\n        public string UserAgent\n        {\n            get { return _UserAgent; }\n            set { _UserAgent = value; }\n        }\n        private string _UserAgent = \"\";\n\n\n        /// <summary>\n        /// Optional duration of the current request\n        /// </summary>\n        public decimal RequestDuration\n        {\n            get { return _RequestDuration; }\n            set { _RequestDuration = value; }\n        }\n        private decimal _RequestDuration = 0M;\n\n\n#if NETFULL\n        public bool UpdateFromRequest()\n        {\n            return UpdateFromRequest(HttpContext.Current);\n        }\n\n\n        /// <summary>\n        /// Updates the Web specific properties of this entry from the \n        /// supplied HttpContext object.\n        /// </summary>\n        /// <param name=\"context\"></param>\n        /// <returns></returns>\n        public bool UpdateFromRequest(HttpContext context)\n        {\n            if (context == null)\n                context = HttpContext.Current;\n\n            if (context == null)\n                return false;\n\n            HttpRequest request = context.Request;\n\n            IpAddress = request.UserHostAddress;\n            Url = request.FilePath;\n            QueryString = request.QueryString.ToString();\n\n            if (request.UrlReferrer != null)\n                Referrer = request.UrlReferrer.ToString();\n            UserAgent = request.UserAgent;\n\n            if (request.TotalBytes > 0 && request.TotalBytes < 2048)\n            {\n                PostData = Encoding.Default.GetString(request.BinaryRead(request.TotalBytes));\n            }\n            else if (request.TotalBytes > 2048)  // strip the result\n            {\n                PostData = Encoding.Default.GetString(request.BinaryRead(2040)) + \"...\";\n            }\n\n            return true;\n        }\n#endif\n\n\n    }\n\n    /// <summary>\n    /// Message object that contains information about the current error information\n    /// \n    /// Note: a WebLogEntry specific to Web applications lives in the\n    /// Westwind.Web assembly.\n    /// </summary>\n    public class LogEntry\n    {\n\n        public LogEntry()\n        {\n        }\n\n        public LogEntry(Exception ex)\n        {\n            UpdateFromException(ex);\n        }\n\n        /// <summary>\n        /// The unique ID for this LogEntry\n        /// </summary>\n        public int Id\n        {\n            get { return _Id; }\n            set { _Id = value; }\n        }\n        private int _Id = 0;\n\n        /// <summary>\n        /// When this error occurred.\n        /// </summary>\n        public DateTime Entered\n        {\n            get { return _Entered; }\n            set { _Entered = value; }\n        }\n        private DateTime _Entered = DateTime.UtcNow;\n\n        /// <summary>\n        /// The Actual Error Message\n        /// </summary>\n        public string Message\n        {\n            get { return _Message; }\n            set { _Message = value; }\n        }\n        private string _Message = String.Empty;\n\n\n        /// <summary>\n        /// Determines the error level of the messages\n        /// </summary>\n        public ErrorLevels ErrorLevel\n        {\n            get { return _ErrorLevel; }\n            set { _ErrorLevel = value; }\n        }\n        private ErrorLevels _ErrorLevel = ErrorLevels.Error;\n\n\n        /// <summary>\n        /// Free form text field that contains extra data to display\n        /// </summary>\n        public string Details\n        {\n            get { return _Details; }\n            set { _Details = value; }\n        }\n        private string _Details = String.Empty;\n\n        /// <summary>\n        /// The type of exception that was thrown if an error occurred\n        /// </summary>\n        public string ErrorType\n        {\n            get { return _ErrorType; }\n            set { _ErrorType = value; }\n        }\n        private string _ErrorType = String.Empty;\n\n        /// <summary>\n        /// StackTrace in event of an exception\n        /// </summary>\n        public string StackTrace\n        {\n            get { return _StackTrace; }\n            set { _StackTrace = value; }\n        }\n        private string _StackTrace = String.Empty;\n\n\n        /// <summary>\n        /// Updates the current request as an error log entry and \n        /// sets the ErrorType, Message and StackTrace properties\n        /// from the content of the passed exception\n        /// </summary>\n        /// <param name=\"ex\"></param>\n        public void UpdateFromException(Exception ex)\n        {\n            ErrorLevel = ErrorLevels.Error;\n            ErrorType = ex.GetType().Name.Replace(\"Exception\", \"\");\n            Message = ex.Message;\n            StackTrace = ex.StackTrace != null && ex.StackTrace.Length > 1490 ?\n                              ex.StackTrace.Substring(0, 1500) :\n                              ex.StackTrace;\n            Details = ex.Source;\n        }\n    }\n\n\n    [Flags]\n    public enum ErrorLevels\n    {\n        /// <summary>\n        /// A critical error occurred\n        /// </summary>          \n        Error = 1,\n        /// <summary>\n        /// A warning type message that drives attention to potential problems\n        /// </summary>\n        Warning = 2,\n        /// <summary>\n        /// Log Entry\n        /// </summary>\n        Info = 4,\n        /// <summary>\n        /// Debug message\n        /// </summary>\n        Debug = 8,\n        /// <summary>\n        /// Application level information log entries\n        /// </summary>\n        ApplicationInfo = 16,\n        /// <summary>\n        /// Application Level Error entries\n        /// </summary>\n        ApplicationError = 32,\n        /// <summary>\n        /// Empty - not assigned\n        /// </summary>        \n        None = 0,\n        /// <summary>\n        /// All ErrorLevels - used only for querying\n        /// </summary>            \n        All = 256\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/NetworkUtilsTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for NetworkUtilsTests\n    /// </summary>\n    [TestClass]\n    public class NetworkUtilsTests\n    {\n        public NetworkUtilsTests()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n        \n\n        /// <summary>\n        ///Gets or sets the test context which provides\n        ///information about and functionality for the current test run.\n        ///</summary>\n        public TestContext TestContext { get; set; }\n\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n        [TestMethod]\n        public void GetBaseDomain()\n        {\n            Assert.IsTrue(new Uri(\"http://www.west-wind.com\").GetBaseDomain() == \"west-wind.com\");\n            Assert.IsTrue(new Uri(\"http://127.0.0.1\").GetBaseDomain() == \"127.0.0.1\");\n            Assert.IsTrue(NetworkUtils.GetBaseDomain(\"localhost\") == \"localhost\");\n            Assert.IsTrue(NetworkUtils.GetBaseDomain(\"classifieds4.gorge.net\") == \"gorge.net\");\n            Assert.IsTrue(NetworkUtils.GetBaseDomain(\"classifieds5.gorge.net\") == \"gorge.net\");\n            Assert.IsTrue(NetworkUtils.GetBaseDomain(string.Empty) == string.Empty);\n        }\n\n        [TestMethod]\n        public void IsLocalIpAddress()\n        {\n            Assert.IsTrue(NetworkUtils.IsLocalIpAddress(\"localhost\"));\n            Assert.IsTrue(NetworkUtils.IsLocalIpAddress(\"127.0.0.1\"));\n\n            var domain = \"dev.west-wind.com\";\n            bool result =  NetworkUtils.IsLocalIpAddress(domain);\n            Console.WriteLine($\"{domain} is local: {result}\");\n\n            domain = \"bogus.west-wind.com\";\n            result =  NetworkUtils.IsLocalIpAddress(domain);\n            Console.WriteLine($\"{domain} is local: {result}\");\n\n\n\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ObjectFactoryTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for ObjectFactoryTests\n    /// </summary>\n    [TestClass]\n    public class ObjectFactoryTests\n    {\n        public ObjectFactoryTests()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n        [TestMethod]\n        public void GetUniqueObjectKeyTest()\n        {\n            var obj1 = ObjectFactory<Information>.GetUniqueObjectKey();\n            var obj2 = ObjectFactory<Information>.GetUniqueObjectKey();\n\n            var name = \"Rick\";\n            var obj3 = ObjectFactory<Information>.GetUniqueObjectKey(name);\n            var obj4 = ObjectFactory<Information>.GetUniqueObjectKey(name);\n\n            TestContext.WriteLine(obj1 + Environment.NewLine +\n            obj2 + Environment.NewLine +\n            obj3 + Environment.NewLine +\n            obj4 + Environment.NewLine );\n\n            Assert.AreEqual(obj1, obj2);\n            Assert.AreNotEqual(obj1, obj3);\n            Assert.AreEqual(obj3, obj4);\n        }\n\n        [TestMethod]\n        public void ThreadScopedObjectTest()\n        {\n            var inst1 = ObjectFactory<Information>.CreateThreadScopedObject();\n            var inst2 = ObjectFactory<Information>.CreateThreadScopedObject();\n\n            var inst3 = ObjectFactory<Information>.CreateThreadScopedObject(\"rick\");\n            var inst4 = ObjectFactory<Information>.CreateThreadScopedObject(\"rick\");\n\n            var inst5 = ObjectFactory<Information>.CreateThreadScopedObject(\"rick5\");\n\n\n            Assert.IsNotNull(inst1);\n            Assert.AreEqual(inst1, inst2);\n\n            Assert.IsNotNull(inst3);\n\n            Assert.AreNotEqual(inst1, inst3);\n            Assert.AreEqual(inst3, inst4);\n\n            Assert.IsNotNull(inst5);\n            Assert.AreNotEqual(inst3, inst5);\n        }\n    }\n\n    class Information\n    {\n        public string Name { get; set; }\n        public DateTime Entered { get; set; }\n\n        public Information()\n        {\n        }\n        public Information(string name)\n        {\n            Name = name;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/PasswordScrubberTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StringUtilsTests\n    /// </summary>\n    [TestClass]\n    public class PasswordScrubberTests\n    {\n        public PasswordScrubberTests()\n        {\n\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        #region Additional test attributes\n\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n\n        #endregion\n\n        [TestMethod]\n        public void SqlConnectionStringTest()\n        {\n            var scrubber = new PasswordScrubber()\n            {\n                ObscuredValueBaseDisplay = \"*******\",\n                ShowUnobscuredCharacterCount = 2\n            };\n\n            string conn = @\"server=.;database=WebStore;uid=WebStoreUser;pwd=superSeekrit#1;encrypt=false;\";\n            string result = scrubber.ScrubSqlConnectionStringValues(conn, \"pwd\", \"uid\");\n\n            Console.WriteLine(result);\n\n            Assert.IsTrue(result.Contains(\"pwd=su*******\"));\n            Assert.IsTrue(result.Contains(\"uid=We*******\"));\n\n        }\n\n        [TestMethod]\n        public void JsonScrubTest()\n        {\n            var scrubber = new PasswordScrubber()\n            {\n                ObscuredValueBaseDisplay = \"*******\",\n                ShowUnobscuredCharacterCount = 2\n            };\n\n            var json = \"\"\"\n                       { \n                       \t\"Name: \"Rick\",\n                       \t\"Password\": \"SuperSeekrit56\",\n                       \t\"UserId\": \"rickochet2\",                       \t\n                       }\n                       \"\"\";\n            \n            var result = scrubber.ScrubJsonValues(json, \"Password\", \"UserId\");\n\n            Console.WriteLine(result);\n\n            Assert.IsTrue(result.Contains(\"\\\"Password\\\": \\\"Su*******\\\"\"));\n            Assert.IsTrue(result.Contains(\"ri*******\"));\n        }\n\n        [TestMethod]\n        public void JsonAndSqlConnectionScrubTest()\n        {\n            var scrubber = new PasswordScrubber()\n            {\n                ObscuredValueBaseDisplay = \"*******\",\n                ShowUnobscuredCharacterCount = 2\n            };\n\n            var json = \"\"\"\n                   { \n                   \t\"Name: \"Rick\",\n                   \t\"Password\": \"SuperSeekrit56\",\n                   \t\"UserId\": \"rickochet2\",\n                   \t\"ConnectionString\": \"server=.;database=WebStore;uid=WebStoreUser;pwd=superSeekrit#1;encrypt=false;\"\n                   }\n                   \"\"\";\n\n            var result = scrubber.ScrubSqlConnectionStringValues(json, \"pwd\", \"uid\");\n            result = scrubber.ScrubJsonValues(result, \"Password\", \"UserId\");\n\n            Console.WriteLine(result);\n\n            Assert.IsTrue(result.Contains(\"\\\"Password\\\": \\\"Su*******\\\"\"));\n            Assert.IsTrue(result.Contains(\"ri*******\"));\n\n            Assert.IsTrue(result.Contains(\"pwd=su*******\"));\n            Assert.IsTrue(result.Contains(\"uid=We*******\"));\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/PropertyBagTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Xml.Serialization;\nusing System.IO;\nusing Newtonsoft.Json;\n\n\nnamespace Westwind.Utilities.Tests\n{\n\t/// <summary>\n\t/// Summary description for UnitTest1\n\t/// </summary>\n\t[TestClass]\n    public class PropertyBagTest\n    {\n        public PropertyBagTest()\n        {\n            //\n            // TODO: Add constructor logic here\n            //\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n\n\n        [TestMethod]\n        public void PropertyBagTwoWayObjectSerializationTest()\n        {\n            var bag = new PropertyBag();\n\n            bag.Add(\"key\", \"Value\");\n            bag.Add(\"Key2\", 100.10M);\n            bag.Add(\"Key3\", Guid.NewGuid());\n            bag.Add(\"Key4\", DateTime.Now);\n            bag.Add(\"Key5\", true);\n            bag.Add(\"Key7\", new byte[3] { 42, 45, 66 } );\n            bag.Add(\"Key8\", null);\n            bag.Add(\"Key9\", new ComplexObject() { Name = \"Rick\",\n            Entered = DateTime.Now,\n            Count = 10 });\n\n            string xml = bag.ToXml();\n\n            TestContext.WriteLine(bag.ToXml());\n\n            bag.Clear();\n\n            bag.FromXml(xml);\n\n            Assert.IsTrue(bag[\"key\"] as string == \"Value\");\n            Assert.IsInstanceOfType( bag[\"Key3\"], typeof(Guid));\n            Assert.IsNull(bag[\"Key8\"]);\n            //Assert.IsNull(bag[\"Key10\"]);\n\n            Assert.IsInstanceOfType(bag[\"Key9\"], typeof(ComplexObject));\n        }\n\n \n        [TestMethod]\n        public void PropertyBagInContainerTwoWayObjectSerializationTest()\n        {\n            var bag = new PropertyBag();\n\n            bag.Add(\"key\", \"Value\");\n            bag.Add(\"Key2\", 100.10M);\n            bag.Add(\"Key3\", Guid.NewGuid());\n            bag.Add(\"Key4\", DateTime.Now);\n            bag.Add(\"Key5\", true);\n            bag.Add(\"Key7\", new byte[3] { 42, 45, 66 });\n            bag.Add(\"Key8\", null);\n            bag.Add(\"Key9\", new ComplexObject() { Name = \"Rick\",\n            Entered = DateTime.Now,\n            Count = 10 });\n\n            ContainerObject cont = new ContainerObject();\n            cont.Name = \"Rick\";\n            cont.Items = bag;\n\n\n            string xml = SerializationUtils.SerializeObjectToString(cont);\n\n            TestContext.WriteLine(xml);\n\n            ContainerObject cont2 = SerializationUtils.DeSerializeObject(xml,\n            typeof(ContainerObject)) as ContainerObject;\n\n            Assert.IsTrue(cont2.Items[\"key\"] as string == \"Value\");\n            Assert.IsTrue(cont2.Items[\"Key3\"].GetType() == typeof(Guid));\n\n            Assert.IsNull(cont2.Items[\"Key8\"]);\n\n            //Assert.IsNull(bag[\"Key10\"]);\n\n            TestContext.WriteLine(cont.Items[\"Key3\"].ToString());\n            TestContext.WriteLine(cont.Items[\"Key4\"].ToString());\n        }\n\n\n        [TestMethod]\n        public void PropertyBagTwoWayValueTypeSerializationTest()\n        {\n            var bag = new PropertyBag<decimal>();\n\n            bag.Add(\"key\", 10M);\n            bag.Add(\"Key1\", 100.10M);\n            bag.Add(\"Key2\", 200.10M);\n            bag.Add(\"Key3\", 300.10M);\n            string xml = bag.ToXml();\n\n            TestContext.WriteLine(xml);\n\n            bag.Clear();\n\n            bag.FromXml(xml);\n\n            Assert.IsTrue(bag[\"Key1\"] == 100.10M);\n            Assert.IsTrue(bag[\"Key3\"] == 300.10M);\n        }\n\n\n        [TestMethod]\n        public void SerializationToJson()\n        {\n            var bag = new PropertyBag<string>();\n\n            bag.Add(\"Value1\", \"asldkjalsk dalksdj\");\n            bag.Add(\"Value2\", \"bbasdasdklajsdalksdja\");\n\n            string json = JsonConvert.SerializeObject(bag,Formatting.Indented);\n            Console.WriteLine(json);\n\n            var bag2 = JsonConvert.DeserializeObject(json, typeof(PropertyBag<string>)) as PropertyBag<string>;\n\n            Assert.IsNotNull(bag2);\n            Assert.IsNotNull(bag[\"Value2\"] as string);\n            Console.WriteLine(bag[\"Value1\"] as string);            \n        }\n\n\n        [TestMethod]\n        public void SerializationToXml()\n        {\n            var bag = new PropertyBag<string>();\n\n            bag.Add(\"Value1\", \"asldkjalsk dalksdj\");\n            bag.Add(\"Value2\", \"bbasdasdklajsdalksdja\");\n\n            string xml = null;\n            SerializationUtils.SerializeObject(bag, out xml);\n            Console.WriteLine(xml);\n\n            var bag2 = SerializationUtils.DeSerializeObject(xml, typeof(PropertyBag<string>)) as PropertyBag<string>;\n\n            Assert.IsNotNull(bag2);\n            Assert.IsNotNull(bag[\"Value2\"] as string);\n            Console.WriteLine(bag[\"Value1\"] as string);\n        }\n\n        #region StandardSerializerTests\n\n        [TestMethod]\n        [ExpectedException(typeof(NotSupportedException))]\n        public void DictionaryXmlSerializerTest()\n        {\n            var bag = new Dictionary<string, object>();\n\n            bag.Add(\"key\", \"Value\");\n            bag.Add(\"Key2\", 100.10M);\n            bag.Add(\"Key3\", Guid.NewGuid());\n            bag.Add(\"Key4\", DateTime.Now);\n            bag.Add(\"Key5\", true);\n            bag.Add(\"Key7\", new byte[3] { 42, 45, 66 });\n\n            // this should fail with NotSupported as Dictionaries \n            // can't be serialized\n            TestContext.WriteLine(this.ToXml(bag));\n        }\n\n        string ToXml(object obj)\n        {\n            if (obj == null)\n                return null;\n\n            StringWriter sw = new StringWriter();\n            XmlSerializer ser = new XmlSerializer(obj.GetType());\n            ser.Serialize(sw, obj);\n            return sw.ToString();\n        }\n\n\n        #endregion\n        public class ContainerObject\n        {\n            public string Name { get; set; }\n            public PropertyBag Items { get; set; }\n        }\n\n        public class ComplexObject\n        {\n            public string Name { get; set; }\n            public DateTime Entered { get; set; }\n            public int Count { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ReflectionUtilsTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Reflection;\n\nnamespace Westwind.Utilities.Tests\n{\n    [TestClass]\n    public class ReflectionUtilsTests\n    {\n        [TestMethod]\n        public void TypedValueToStringTest()\n        {\n            // Guid\n            object val = Guid.NewGuid();            \n            string res = ReflectionUtils.TypedValueToString(val);\n\n            Assert.IsTrue(res.Contains(\"-\"));\n            Console.WriteLine(res);\n\n            object val2 = ReflectionUtils.StringToTypedValue<Guid>(res);\n            Assert.AreEqual(val, val2);\n\n            // Single \n            val = (Single) 10.342F;            \n            res = ReflectionUtils.TypedValueToString(val);\n            Console.WriteLine(res);\n\n            Assert.AreEqual(res, val.ToString());\n\n            val2 = ReflectionUtils.StringToTypedValue<Single>(res);\n            Assert.AreEqual(val, val2);\n\n            // Single \n            val = (Single)10.342F;\n            res = ReflectionUtils.TypedValueToString(val);\n            Console.WriteLine(res);\n\n            Assert.AreEqual(res, val.ToString());\n\n            val2 = ReflectionUtils.StringToTypedValue<Single>(res);\n            Assert.AreEqual(val, val2);\n        }\n\n        //[TestMethod]\n        //public void ComAccessReflectionCoreAnd45Test()\n        //{\n        //    // this works with both .NET 4.5+ and .NET Core 2.0+\n\n        //    string progId = \"InternetExplorer.Application\";\n        //    Type type = Type.GetTypeFromProgID(progId);\n        //    object inst = Activator.CreateInstance(type);\n\n\n        //    inst.GetType().InvokeMember(\"Visible\", ReflectionUtils.MemberAccess | BindingFlags.SetProperty, null, inst,\n        //        new object[1]\n        //        {\n        //            true\n        //        });\n\n        //    inst.GetType().InvokeMember(\"Navigate\", ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null,\n        //        inst, new object[]\n        //        {\n        //            \"https://markdownmonster.west-wind.com\",\n        //        });\n\n        //    //result = ReflectionUtils.GetPropertyCom(inst, \"cAppStartPath\");\n        //    bool result = (bool)inst.GetType().InvokeMember(\"Visible\",\n        //        ReflectionUtils.MemberAccess | BindingFlags.GetProperty, null, inst, null);\n        //    Console.WriteLine(result); // path             \n        //}\n\n        //[TestMethod]\n        //public void ComAccessDynamicCoreAnd45Test()\n        //{\n        //    // this does not work with .NET Core 2.0\n\n        //    string progId = \"InternetExplorer.Application\";\n        //    Type type = Type.GetTypeFromProgID(progId);\n\n        //    dynamic inst = Activator.CreateInstance(type);\n\n        //    inst.Visible = true;\n        //    inst.Navigate(\"https://markdownmonster.west-wind.com\");\n\n        //    bool result = inst.Visible;\n        //    Assert.IsTrue(result);\n        //}\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SanitizeHtmlTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    [TestClass]\n    public class SanitizeHtmlTests\n    {\n        [TestMethod]\n        public void HtmlSanitizeScriptTags()\n        {\n            string html = \"<div>User input with <ScRipt>alert('Gotcha');</ScRipt></div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"<ScRipt>\"));\n        }\n\n        [TestMethod]\n        public void HtmlSanitizeJavaScriptTags()\n        {\n            string html = \"<div>User input with <a href=\\\"javascript: alert('Gotcha')\\\">Don't hurt me!<a/></div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"javascript:\"));\n        }\n\n        [TestMethod]\n        public void HtmlSanitizeJavaScriptTagsSingleQuotes()\n        {\n            string html = \"<div>User input with <a href='javascript: alert(\\\"Gotcha\\\");'>Don't hurt me!<a/></div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"javascript:\"));\n        }\n\n        [TestMethod]\n        public void HtmlSanitizeJavaScriptTagsSingleQuotesWithLineBreaksInTag()\n        {\n            string html = \"<div>User input with <a \\r\\n  href='javascript: alert(\\\"Gotcha\\\");'>Don't hurt me!<a/></div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n            \n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"javascript:\"));\n        }\n\n        [TestMethod]\n        public void HtmlSanitizeJavaScriptTagsWithUnicodeQuotes()\n        {\n            string html = \"<div>User input with <a href='&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:alert(\\\"javascript active\\\");'>Don't hurt me!<a/></div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"&#106;&#97;&#118\"));\n        }\n\n\n        [TestMethod]\n        public void HtmlSanitizeEventAttributes()\n        {\n            string html = \"<div onmouseover=\\\"alert('Gotcha!')\\\">User input with \" +\n                          \"<div onclick='alert(\\\"Gotcha!\\\");'>Don't hurt me!<div/>\" +\n                          \"</div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"onmouseover:\") && !result.Contains(\"onclick\"));\n        }\n\n\n        [TestMethod]\n        public void HtmlSanitizeEventAttributesWithLineBreaks()\n        {\n            string html = \"<div\\r\\n   onmouseover=\\\"alert('Gotcha!')\\\">User input with \" +\n                          \"<div \\r\\n  onclick='alert(\\\"Gotcha!\\\");'>Don't hurt me!<div/>\" +\n                          \"</div>\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(!result.Contains(\"onmouseover:\") && !result.Contains(\"onclick\"));\n        }\n\n        [TestMethod]\n        public void IncorrectOnParsingBugTest()\n        {\n            string html = \"<div><a href=\\\"https://west-wind.com\\\">This</a> is on <a href=\\\"https://markdownmonster.west-wind.com\\\">time</a> train.\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(result.Contains(\"on <\"), \"On shouldn't be transformed or removed: \" + result);\n        }\n\n\n        [TestMethod]\n        public void IncorrectOnEventParsingWithFakeEventInBodyTest()\n        {\n            string html = \"<div><a href=\\\"https://west-wind.com\\\">This</a> is onchange=\\\"this should stay\\\" <a href=\\\"https://markdownmonster.west-wind.com\\\">time</a> train.\";\n\n            var result = HtmlUtils.SanitizeHtml(html);\n\n            Console.WriteLine(result);\n            Assert.IsTrue(result.Contains(\"onchange=\"), \"OnChange should not  be removed: \" + result);\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/ShellUtilsTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class ShellUtilsTests\n    {\n        [TestMethod]\n        public void ExecuteProcessAndGetOutput()\n        {\n            int result = ShellUtils.ExecuteProcess(\"ipconfig.exe\", null, 3000,\n                out StringBuilder output);\n\n            Assert.IsTrue(result == 0, \"Process should exit with 0\");\n            Assert.IsNotNull(output.Length > 0,\"Output should not be empty\");\n\n            Console.WriteLine(\"Complete output:\");\n            Console.WriteLine(\"----------------\");\n            Console.WriteLine(output.ToString());\n        }\n\n        [TestMethod]\n        public void ExecuteProcessAndCaptureOutputAction()\n        {\n            var action = new Action<string>((s) => Console.WriteLine(s));\n\n            Console.WriteLine(\"Output Captured from Action:\");\n            Console.WriteLine(\"----------------------------\");\n\n            int result = ShellUtils.ExecuteProcess(\"ipconfig.exe\", null,\n                3000,\n                action);\n\n            Assert.IsTrue(result == 0, \"Process should exit with 0\");\n        }\n\n        [TestMethod]\n        public void ExecuteProcess()\n        {\n            int result = ShellUtils.ExecuteProcess(\"ipconfig.exe\", null, 3000, ProcessWindowStyle.Normal);\n            Assert.IsTrue(result == 0, \"Process should exit with 0\");\n        }\n    }\n}\n\n"
  },
  {
    "path": "Westwind.Utilities.Test/SmtpClientNativeTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities.InternetTools;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class SmtpClientNativeTests\n    {\n        bool isMailServerAvailable = false;\n\n        /// <summary>\n        /// For this test to run make sure Email Server is available\n        /// </summary>\n        [TestMethod]\n        public void SendMailTest()\n        {            \n            SmtpClientNative smtp = new SmtpClientNative();\n            smtp.MailServer = TestConfigurationSettings.Mailserver;\n            smtp.Username = TestConfigurationSettings.MailServerUsername;\n            smtp.Password = TestConfigurationSettings.MailServerPassword;\n            smtp.UseSsl = TestConfigurationSettings.MailServerUseSsl;\n\n            \n            smtp.SenderEmail = \"admin@test.com\";\n\n            smtp.Recipient = \"test@test.com\";\n            smtp.Subject = \"Test Message\";\n            smtp.Message = \"Test Message Content from <b>Test SMTP Client</b>\";\n            smtp.ContentType = \"text/html\";\n            \n            bool result = smtp.SendMail();\n            \n            if (isMailServerAvailable)                \n                Assert.IsTrue(result, smtp.ErrorMessage);\n        }\n\n        /// <summary>\n        /// For this test to run make sure Email Server is available\n        /// </summary>\n        [TestMethod]\n        public void SendMailAsyncTest()\n        {\n            SmtpClientNative smtp = new SmtpClientNative();\n            smtp.MailServer = TestConfigurationSettings.Mailserver;\n            smtp.Username = TestConfigurationSettings.MailServerUsername;\n            smtp.Password = TestConfigurationSettings.MailServerPassword;\n            smtp.UseSsl = TestConfigurationSettings.MailServerUseSsl;\n\n\n            smtp.SenderEmail = \"admin@test.com\";\n\n            smtp.Recipient = \"test@test.com\";\n            smtp.Subject = \"Async Test Message\";\n            smtp.Message = \"Test Message Content from <b>Async Test SMTP Client</b>\";\n            smtp.ContentType = \"text/html\";\n\n            smtp.SendMailAsync();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SqlDataAccessFoxProTests.cs",
    "content": "﻿// #define TEST_FOXPRO_DATA\n// IMPORTANT: For this to work the host project has to be targeting 32 bit Windows\n\nusing System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Data.Test;\nusing Westwind.Data.Test.Models;\nusing Westwind.Utilities.Data;\nusing System.Data;\nusing Westwind.Utilities;\nusing System.Diagnostics;\nusing System.IO;\nusing Westwind.Utilities.Test;\n\n#if NETFULL && TEST_FOXPRO_DATA\n\n// Remove TestFoxProDriver from Compiler Options if you don't have the FoxPro OleDb Driver installed\n\nnamespace Westwind.Utilities.Data.Tests\n{\n    /// <summary>\n    /// Summary description for DataUtilsTests    \n    /// </summary>\n    /// <remarks>\n    /// Requires that the FoxPro Visual FoxPro database driver is installed\n    /// </remarks>\n    [TestClass]\n    public class SqlDataAccessFoxProTests\n    {\n        string connString =\n                @\"Provider=vfpoledb.1;Data Source={0};Exclusive=false;Deleted=true;Nulls=false;\";\n\n        public SqlDataAccessFoxProTests()\n        {\n            connString = string.Format(connString, Path.Combine(Environment.CurrentDirectory, \"supportFiles\\\\\"));\n\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        [ClassInitialize()]\n        public static void Initialize(TestContext testContext)\n        {\n        }\n\n        [TestMethod]\n        public void SimpleSelectOleDbTest()\n        {\n            Console.WriteLine(connString);\n            using (var data = new SqlDataAccess(connString, DataAccessProviderTypes.OleDb)\n                   {\n                       ParameterPrefix = \"?\",\n                       UsePositionalParameters = true,\n                       LeftFieldBracket = \"\",\n                       RightFieldBracket = \"\"\n                   })\n            {\n                var dt = data.ExecuteTable(\"TCustomers\",\"select * from customers\");\n\n                Assert.IsNotNull(dt, data.ErrorMessage);\n                Assert.IsTrue(dt.Rows.Count > 0);\n\n            }\n\n            \n\n        }\n\n            [TestMethod]\n        public void InsertEntityOleDbTest()\n        {\n            Console.WriteLine(connString);\n            using (var data = new SqlDataAccess(connString, \"System.Data.OleDb\")\n            {\n                ParameterPrefix = \"?\",\n                UsePositionalParameters = true,\n                LeftFieldBracket = \"\",\n                RightFieldBracket = \"\"\n            })\n            {\n                var customer = new\n                {\n                    Id = StringUtils.NewStringId(),\n                    FirstName = \"Mike\",\n                    LastName = \"Smith\",\n                    Company = \"Smith & Smith\",\n                    Entered = DateTime.UtcNow                    \n                };\n\n                // insert into customers and skip Id,Order properties and return id\n                object newId = data.InsertEntity(customer, \"Customers\", null, false);\n\n                Console.WriteLine(data.LastSql);\n                Assert.IsNotNull(newId, data.ErrorMessage);\n                Console.WriteLine(newId);\n            }\n        }\n\n        [TestMethod]\n        public void UpdateEntityOleDbTest()\n        {\n            using (var data = new SqlDataAccess(connString, \"System.Data.OleDb\")\n            {\n                LeftFieldBracket = \"\",\n                RightFieldBracket = \"\",\n                ParameterPrefix = \"?\",\n                UsePositionalParameters = true\n            })\n            {\n                var customer = new\n                {\n                    Id = \"_adasdasd  \",\n                    FirstName = \"Mike Updated\",\n                    LastName = \"Smith\",\n                    Company = \"Smith & Smith\",\n                    Entered = DateTime.UtcNow,                    \n                };\n\n                // insert into customers and skip Id,Order properties and return id\n                object result= data.UpdateEntity(customer, \"Customers\", \"Id\", null, \"FirstName,Entered,Address\");\n                \n                Console.WriteLine(data.LastSql);\n                Assert.IsNotNull(result, data.ErrorMessage);\n                Console.WriteLine(result);\n                Console.WriteLine(data.ErrorMessage);\n                Assert.IsNotNull(result,data.ErrorMessage);\n            }\n        }\n\n    }\n\n}\n#endif"
  },
  {
    "path": "Westwind.Utilities.Test/SqlDataAccessMySqlTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n    /// <summary>\n    /// \n    /// </summary>\n    [TestClass]\n    public class SqlDataAccessMySqlTests\n    {\n        //private string STR_ConnectionString = \"WestwindToolkitSamples\";\n        static string STR_ConnectionString = TestConfigurationSettings.MySqlConnectionString;\n\n        public SqlDataAccessMySqlTests()\n        {\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        [ClassInitialize()]\n        public static void Initialize(TestContext testContext)\n        {\n            //DatabaseInitializer.InitializeDatabase();\n\n            // warm up data connection\n            //#if NETCORE\n            //            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString, Microsoft.Data.Sqlite.SqliteFactory.Instance);\n            //#else\n            //            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString, System.Data.SQLite.SQLiteFactory.Instance);\n            //#endif\n            //            var readr = data.ExecuteReader(\"select * from artists\");\n\n            //            readr.Read();\n            //            readr.Close();\n\n            //            // warm up DLR load time\n            //            dynamic ddata = data;\n            //            string err = ddata.ErrorMessage;\n        }\n\n\n        [TestMethod]\n        public void GetMySqlInstance()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.MySql))\n            {\n                Assert.IsNotNull(data, data.ErrorMessage);\n                Assert.IsNotNull(data.dbProvider, data.ErrorMessage);\n            }\n        }\n\n        [TestMethod]\n        public void ExecuteReaderTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.MySql))\n            {\n                var reader = data.ExecuteReader(\"select * from Localizations\");\n\n                Assert.IsTrue(reader.HasRows,data.ErrorMessage);\n\n                while (reader.Read())\n                {\n                    string id = reader[\"ResourceId\"].ToString();\n                    string val = reader[\"ResourceId\"].ToString();\n                    Console.WriteLine($\"{id} - {val}\");\n                }\n            }            \n        }\n\n        [TestMethod]\n        public void CreateTable()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.MySql))\n            {\n                var script = @\"CREATE TABLE `{0}` (\n   pk int(11) NOT NULL AUTO_INCREMENT,\n  ResourceId varchar(1024) DEFAULT NULL,\n  Value varchar(2048) DEFAULT NULL,\n  LocaleId varchar(10) DEFAULT NULL,\n  ResourceSet varchar(512) DEFAULT NULL,\n  Type varchar(512) DEFAULT NULL,\n  BinFile blob,\n  TextFile text,\n  Filename varchar(128) DEFAULT NULL,\n  Comment varchar(512) DEFAULT NULL,\n  ValueType int(2) DEFAULT 0,\n  Updated datetime DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY(`pk`)\n) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;\n\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('HelloWorld','Hello Cruel World (MySql)','','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('HelloWorld','Hallo schnöde Welt (MySql)','de','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('HelloWorld','Bonjour tout le monde','fr','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Yesterday','Yesterday (invariant)','','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Yesterday','Gestern','de','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Yesterday','Hier','fr','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Today','Today (invariant)','','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Today','Heute','de','Resources');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('Today','Aujourd''hui','fr','Resources');\n\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet, ValueType) VALUES('MarkdownText','This is **MarkDown** formatted *HTML Text*','','Resources',2);\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet, ValueType) VALUES('MarkdownText','Hier ist **MarkDown** formatierter *HTML Text*','de','Resources',2);\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet, ValueType) VALUES('MarkdownText','Ceci est **MarkDown** formaté *HTML Texte*','fr','Resources',2);\n\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('lblHelloWorldLabel.Text','Hello Cruel World (local)','','ResourceTest.aspx');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('lblHelloWorldLabel.Text','Hallo Welt (lokal)','de','ResourceTest.aspx');\n                INSERT INTO `{0}` (ResourceId, Value, LocaleId, ResourceSet) VALUES('lblHelloWorldLabel.Text','Bonjour monde (local)','fr','ResourceTest.aspx');\n\";\n\n                var result = data.RunSqlScript(string.Format(script,\"Localizations\"));\n                Assert.IsTrue(result, data.ErrorMessage);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SqlDataAccessSqlLiteTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities.Test;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n    /// <summary>\n    /// Summary description for DataUtilsTests\n    /// </summary>\n    [TestClass]\n    public class SqlDataAccessSqliteTests\n    {\n        //private string STR_ConnectionString = \"WestwindToolkitSamples\";\n        static string STR_ConnectionString = TestConfigurationSettings.SqliteConnectionString;\n\n        public SqlDataAccessSqliteTests()\n        {\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        [ClassInitialize()]\n        public static void Initialize(TestContext testContext)\n        {\n            //DatabaseInitializer.InitializeDatabase();\n\n            // warm up data connection\n            //#if NETCORE\n            //            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString, Microsoft.Data.Sqlite.SqliteFactory.Instance);\n            //#else\n            //            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString, System.Data.SQLite.SQLiteFactory.Instance);\n            //#endif\n            //            var readr = data.ExecuteReader(\"select * from artists\");\n\n            //            readr.Read();\n            //            readr.Close();\n\n            //            // warm up DLR load time\n            //            dynamic ddata = data;\n            //            string err = ddata.ErrorMessage;\n        }\n\n\n        [TestMethod]\n        public void GetSqliteInstance()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.SqLite))\n            {\n                Assert.IsNotNull(data, data.ErrorMessage);\n                Assert.IsNotNull(data.dbProvider, data.ErrorMessage);\n            }\n        }\n\n        [TestMethod]\n        public void ExecuteReaderTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.SqLite))\n            {\n                var reader = data.ExecuteReader(\"select * from artists\");\n\n                Assert.IsTrue(reader.HasRows, data.ErrorMessage);\n\n                while (reader.Read())\n                {\n                    string txt = reader[\"ArtistName\"].ToString();\n                    Console.WriteLine(txt);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void CreateTable()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString, DataAccessProviderTypes.SqLite))\n            {\n                var script = @\"CREATE TABLE [Localizations] (\n [Pk] INTEGER PRIMARY KEY \n, [ResourceId] nvarchar(1024) COLLATE NOCASE NOT NULL\n, [Value] ntext  NULL\n, [LocaleId] nvarchar(10) COLLATE NOCASE DEFAULT '' NULL\n, [ResourceSet] nvarchar(512) COLLATE NOCASE DEFAULT ''  NULL\n, [Type] nvarchar(512) DEFAULT '' NULL\n, [BinFile] image NULL\n, [TextFile] ntext NULL\n, [Filename] nvarchar(128) NULL\n, [Comment] nvarchar(512) NULL\n, [ValueType] unsigned integer(2) DEFAULT 0\n, [Updated] datetime NULL\n);\n\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('HelloWorld','Hello Cruel World (SqlLite)','','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('HelloWorld','Hallo schnöde Welt (SqlLite)','de','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('HelloWorld','Bonjour tout le monde (SqlLite)','fr','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Yesterday','Yesterday (invariant)','','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Yesterday','Gestern','de','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Yesterday','Hier','fr','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Today','Today (invariant)','','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Today','Heute','de','Resources');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('Today','Aujourd''hui','fr','Resources');\n\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet,ValueType) VALUES ('MarkdownText','This is **MarkDown** formatted *HTML Text*','','Resources',2);\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet,ValueType) VALUES ('MarkdownText','Hier ist **MarkDown** formatierter *HTML Text*','de','Resources',2);\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet,ValueType) VALUES ('MarkdownText','Ceci est **MarkDown** formaté *HTML Texte*','fr','Resources',2);\n\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('lblHelloWorldLabel.Text','Hello Cruel World (local)','','ResourceTest.aspx');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('lblHelloWorldLabel.Text','Hallo Welt (lokal)','de','ResourceTest.aspx');\nINSERT INTO [Localizations] (ResourceId,Value,LocaleId,ResourceSet) VALUES ('lblHelloWorldLabel.Text','Bonjour monde (local)','fr','ResourceTest.aspx');\n\";\n                var result = data.RunSqlScript(script);\n                Assert.IsTrue(result, data.ErrorMessage);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SqlDataAccessTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Data.Test.Models;\nusing System.Data;\nusing System.Diagnostics;\nusing Westwind.Utilities.Test;\nusing Westwind.Utilities.Test.Models.Entities;\n\nnamespace Westwind.Utilities.Data.Tests\n{\n    /// <summary>\n    /// Summary description for DataUtilsTests\n    /// </summary>\n    [TestClass]\n    public class SqlDataAccessTests\n    {\n\t\t//private string STR_ConnectionString = \"WestwindToolkitSamples\";\n\t\tstatic string STR_ConnectionString = TestConfigurationSettings.WestwindToolkitConnectionString;\n\n        public SqlDataAccessTests()\n        {\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        [ClassInitialize()]\n        public static void Initialize(TestContext testContext)\n        {\n            //DatabaseInitializer.InitializeDatabase();\n\n            // warm up data connection\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n            var readr = data.ExecuteReader(\"select top 1 * from Customers\");\n            readr.Read();\n            readr.Close();\n\n            // warm up DLR load time\n            dynamic ddata = data;\n            string err = ddata.ErrorMessage;\n        }\n\n\t\t[TestMethod]\n\t\tpublic void ExecuteReaderTest()\n\t\t{\n\t\t\tusing (var data = new SqlDataAccess(STR_ConnectionString))\n\t\t\t{\n\t\t\t\tvar reader = data.ExecuteReader(\"select * from customers\");\n\n\t\t\t\tAssert.IsTrue(reader.HasRows);\n\n\t\t\t\twhile (reader.Read())\n\t\t\t\t{\n\t\t\t\t\tstring txt = reader[\"LastName\"] + \" \" + (DateTime)reader[\"Entered\"];\n\t\t\t\t\t//Console.WriteLine(txt);\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n        [TestMethod]\n        public void ExecuteNonQueryTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var count = data.ExecuteNonQuery(\"update Customers set Updated=@1 where id=@0\",\n                    1, DateTime.Now);\n\n                Assert.IsTrue(count > -1, data.ErrorMessage);\n                Assert.IsTrue(count > 0, \"No record found to update\");\n\n                Assert.IsTrue(count == 1, \"Invalid number of records updated.\");\n            }\n        }\n\n        [TestMethod]\n        public void InsertEntityTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n\n                Customer customer = new Customer()\n                {\n                    FirstName = \"Mike\",\n                    LastName = \"Smith\",\n                    Company = \"Smith & Smith\",\n                    Entered = DateTime.UtcNow,\n                    Updated = DateTime.UtcNow\n                };\n\n                // insert into customers and skip Id,Order properties and return id\n                object newId = data.InsertEntity(customer, \"Customers\", \"Id,Orders\");\n\n                Assert.IsNotNull(newId, data.ErrorMessage);\n                Console.WriteLine(newId);\n            }\n        }\n\n        [TestMethod]\n        public void UdateEntityTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                int id = (int) data.ExecuteScalar(\"select TOP 1 id from customers order by entered\");\n                Console.WriteLine(id);\n\n                Customer customer = new Customer()\n                {\n                    Id = id,\n                    FirstName = \"Updated Entry \" + DateTime.UtcNow,\n                    Entered = DateTime.UtcNow,\n                    Updated = DateTime.UtcNow\n                };\n\n                // insert into customers and skip Id,Order properties and return id\n                object newId = data.UpdateEntity(customer, \"Customers\", \"Id\", null, \"Id,Orders\");\n\n                Assert.IsNotNull(newId, data.ErrorMessage);\n                Console.WriteLine(newId);\n            }\n        }\n\n\n\n\n        [TestMethod]\n        public void FindTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var customer = data.Find<Customer>(\"select * from customers where id=@0\", 1);\n                Assert.IsNotNull(customer, data.ErrorMessage);\n                Console.WriteLine(customer.Company);\n            }\n        }\n\n        [TestMethod]\n        public void FindByKeyTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                // Notice you have to explicitly specify parameter names\n                var customer = data.FindKey<Customer>(keyValue: \"Strahl\", tableName: \"Customers\",keyField: \"LastName\" );\n                Assert.IsNotNull(customer, data.ErrorMessage);\n                Console.WriteLine(customer.Company);\n            }\n        }\n\n\n\n        [TestMethod]\n        public void DataReaderToObjectTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            IDataReader reader = data.ExecuteReader(\"select top 1 * from ApplicationLog\");\n\n            Assert.IsNotNull(reader, \"Couldn't access Data reader. \" + data.ErrorMessage);\n\n            Assert.IsTrue(reader.Read(), \"Couldn't read from Data Reader\");\n\n            WebLogEntry entry = new WebLogEntry();\n\n            DataUtils.DataReaderToObject(reader, entry, null);\n\n            Assert.IsNotNull(entry.Message, \"Entry Message should not be null\");\n            Assert.IsTrue(entry.ErrorLevel != ErrorLevels.None, \"Entry Error level should not be None (error)\");\n        }\n\n        [TestMethod]\n        public void ExecuteDataReaderToListTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            var swatch = new Stopwatch();\n            swatch.Start();\n\n            var recs = data.Query<WebLogEntry>(\"select * from ApplicationLog\").ToList();\n\n            swatch.Stop();\n\n            Assert.IsNotNull(recs, \"Null\");\n            Assert.IsTrue(recs.Count > 0, \"Count < 1\");\n            Assert.IsTrue(recs[0].Entered > DateTime.MinValue);\n\n            Console.WriteLine(swatch.ElapsedMilliseconds);\n            Console.WriteLine(recs.Count);\n        }\n\n\n\n        [TestMethod]\n        public void ExecuteDataReaderWithNoMatchingDataTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            // no records returned from query\n            var reader = data.ExecuteReader(\"select * from ApplicationLog where 1=2\");\n            Assert.IsNotNull(reader, \"Reader is null and shouldn't be\");\n        }\n\n        [TestMethod]\n        public void QueryWithNoMatchingDataTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            // no records returned from query\n            var entries = data.Query<WebLogEntry>(\"select * from ApplicationLog where 1=2\");\n\n            var ent = entries.ToList();\n            Console.WriteLine(ent.Count);\n\n            Assert.IsNotNull(entries, \"IEnumerable should not be null - only null on failure.\");\n\n        }\n\n        [TestMethod]\n        public void QueryToIEnumerableTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            var swatch = new Stopwatch();\n            swatch.Start();\n\n            var enumerable = data.Query<WebLogEntry>(\"select * from ApplicationLog\");\n\n            var recs = new List<WebLogEntry>();\n            foreach (var entry in enumerable)\n            {\n                recs.Add(entry);\n            }\n\n            swatch.Stop();\n\n            Assert.IsNotNull(recs, \"Null\");\n            Assert.IsTrue(recs.Count > 0, \"Count < 1\");\n            Assert.IsTrue(recs[0].Entered > DateTime.MinValue);\n\n            Console.WriteLine(swatch.ElapsedMilliseconds);\n            Console.WriteLine(recs.Count);\n        }\n\n        [TestMethod]\n        public void QueryToListTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            var swatch = new Stopwatch();\n            swatch.Start();\n\n            var recs = data.QueryList<WebLogEntry>(\"select * from ApplicationLog\");\n\n            swatch.Stop();\n\n            Assert.IsNotNull(recs, \"Null\");\n            Assert.IsTrue(recs.Count > 0, \"Count < 1\");\n            Assert.IsTrue(recs[0].Entered > DateTime.MinValue);\n\n            Console.WriteLine(swatch.ElapsedMilliseconds);\n            Console.WriteLine(recs.Count);\n        }\n\n        [TestMethod]\n        public void QueryToCustomer()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var custList = data.Query<Customer>(\"select * from customers where LastName like @0\", \"S%\");\n\n                Assert.IsNotNull(custList, data.ErrorMessage);\n\n                foreach (var customer in custList)\n                {\n                    Console.WriteLine(customer.Company + \" \" + customer.Entered);\n                }\n            }\n        }\n\n\n        [TestMethod]\n        public void ExecuteDataReaderToListManualTest()\n        {\n            SqlDataAccess data = new SqlDataAccess(STR_ConnectionString);\n\n            var swatch = new Stopwatch();\n            swatch.Start();\n\n            var entries = new List<WebLogEntry>();\n            var reader = data.ExecuteReader(\"select * from ApplicationLog\");\n\n            while (reader.Read())\n            {\n                WebLogEntry entry = new WebLogEntry();\n                entry.Details = reader[\"Details\"] as string;\n                entry.Entered = (DateTime) reader[\"Entered\"];\n                entry.ErrorLevel = (ErrorLevels) reader[\"ErrorLevel\"];\n                entry.Id = (int) reader[\"id\"];\n                entry.IpAddress = reader[\"IpAddress\"] as string;\n                entry.Message = reader[\"Message\"] as string;\n                entry.PostData = reader[\"PostData\"] as string;\n                entry.QueryString = reader[\"QueryString\"] as string;\n                entry.Referrer = reader[\"Referrer\"] as string;\n                entry.RequestDuration = (decimal) reader[\"RequestDuration\"];\n                entry.Url = reader[\"Url\"] as string;\n                entry.UserAgent = reader[\"UserAgent\"] as string;\n\n                entries.Add(entry);\n            }\n            reader.Close();\n\n            swatch.Stop();\n\n            Console.WriteLine(swatch.ElapsedMilliseconds);\n            Console.WriteLine(entries.Count);\n        }\n\n        [TestMethod]\n        public void NewParametersReaderTest()\n        {\n            var data = new SqlDataAccess(STR_ConnectionString);\n\n\n            var swatch = Stopwatch.StartNew();\n\n            var reader =\n                data.ExecuteReader(\"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n                    DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1));\n\n            Assert.IsNotNull(reader, data.ErrorMessage);\n\n            int readerCount = 0;\n            while (reader.Read())\n            {\n                string Message = reader[\"Message\"] as string;\n                string Details = reader[\"Details\"] as string;\n\n                Console.WriteLine(((DateTime) reader[\"Entered\"]));\n                readerCount++;\n            }\n\n            swatch.Stop();\n            Console.WriteLine(readerCount);\n            Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n        }\n\n\t\t// TODO: Table Queryies don't work in .NET Core yet due to missing CreateAdapter method\n\n\t\t[TestMethod]\n        public void NewParametersTableTest()\n        {\n            var data = new SqlDataAccess(STR_ConnectionString);\n\n            // warmup\n            data.ExecuteScalar(\"select top1 id from ApplicationLog\");\n\n            //var cmd = data.CreateCommand(\"select * from ApplicationLog where entered > @0 and entered > @1\",CommandType.Text, DateTime.Now.AddYears(-10), DateTime.Now.AddYears(-));\n            //var table = data.ExecuteTable(\"TLogs\", cmd);\n\n            var swatch = Stopwatch.StartNew();\n\n            var table = data.ExecuteTable(\"TLogs\",\n                \"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n                DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1));\n\n            Assert.IsNotNull(table, data.ErrorMessage);\n\n            Console.WriteLine(table.Rows.Count);\n            foreach (DataRow row in table.Rows)\n            {\n                Console.WriteLine(((DateTime) row[\"Entered\"]));\n            }\n            swatch.Stop();\n            Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n\n        }\n\n        [TestMethod]\n        public void NewParametersExecuteEntityTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                //var cmd = data.CreateCommand(\"select * from ApplicationLog where entered > @0 and entered > @1\",CommandType.Text, DateTime.Now.AddYears(-10), DateTime.Now.AddYears(-));\n                //var table = data.ExecuteTable(\"TLogs\", cmd);\n                var swatch = Stopwatch.StartNew();\n                var entries =\n                    data.Query<WebLogEntry>(\n                        \"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n                        DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1));\n                var logEntries = entries.ToList();\n                Assert.IsNotNull(logEntries, data.ErrorMessage);\n                Console.WriteLine(logEntries.Count);\n                foreach (var logEntry in logEntries)\n                {\n                    Console.WriteLine(logEntry.Entered);\n                }\n                swatch.Stop();\n                Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n            }\n        }\n\n        [TestMethod]\n        public void NewParametersxecuteDynamicTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var swatch = Stopwatch.StartNew();\n                var reader =\n                    data.ExecuteReader(\n                        \"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n                        DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1));\n                dynamic dreader = new DynamicDataReader(reader);\n                Assert.IsNotNull(reader, data.ErrorMessage);\n                int readerCount = 0;\n                while (reader.Read())\n                {\n                    DateTime date = (DateTime) dreader.Entered; // reader.Entered;\n                    Console.WriteLine(date);\n                    readerCount++;\n                }\n                swatch.Stop();\n                Console.WriteLine(readerCount);\n                Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n            }\n        }\n\n        [TestMethod]\n        public void FindByIdTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var entry = data.FindKey<WebLogEntry>(1, \"ApplicationLog\", \"Id\");\n                Assert.IsNotNull(entry, data.ErrorMessage);\n                Console.WriteLine(entry.Entered + \" \" + entry.Message);\n                var entry2 = new WebLogEntry();\n                data.GetEntity(entry2, \"ApplicationLog\", \"Id\", 1);\n                Assert.IsNotNull(entry2);\n                Assert.AreEqual(entry2.Message, entry.Message);\n                Console.WriteLine(entry2.Entered + \" \" + entry2.Message);\n            }\n        }\n\n\n        [TestMethod]\n        public void FindBySqlTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var entry = data.Find<WebLogEntry>(\"select * from ApplicationLog where id=@0\", 1);\n                Assert.IsNotNull(entry, data.ErrorMessage);\n                Console.WriteLine(entry.Entered + \" \" + entry.Message);\n            }\n\n\n        }\n\n        [TestMethod]\n        public void QueryTest()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString))\n            {\n                var swatch = Stopwatch.StartNew();\n                var logEntries =\n                    data.Query<WebLogEntry>(\n                        \"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n                        DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1)).ToList();\n                Assert.IsNotNull(logEntries, data.ErrorMessage);\n                Console.WriteLine(logEntries.Count);\n                foreach (var logEntry in logEntries)\n                {\n                    Console.WriteLine(logEntry.Entered);\n                }\n                swatch.Stop();\n                Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n            }\n        }\n\n\t    [TestMethod]\n\t    public void ExecuteDynamicDataReaderTest()\n\t    {\n\t\t    using (var data = new SqlDataAccess(STR_ConnectionString))\n\t\t    {\n\t\t\t    var swatch = Stopwatch.StartNew();\n\t\t\t    var reader =\n\t\t\t\t    data.ExecuteDynamicDataReader(\n\t\t\t\t\t    \"select * from ApplicationLog where entered > @0 and entered < @1 order by Entered\",\n\t\t\t\t\t    DateTime.Now.AddYears(-115), DateTime.Now.AddYears(-1));\n\t\t\t    Assert.IsNotNull(reader, data.ErrorMessage);\n\n\t\t\t\twhile(reader.Read())\n\t\t\t    {\n\t\t\t\t    Console.WriteLine(reader.Entered);\n\t\t\t    }\n\t\t\t    swatch.Stop();\n\t\t\t    Console.WriteLine(swatch.ElapsedMilliseconds + \"ms\");\n\t\t    }\n\t    }\n\n\n\t    [TestMethod]\n        public void QueryException()\n        {\n            using (var data = new SqlDataAccess(STR_ConnectionString)\n            {\n                ThrowExceptions = true\n            })\n            {\n                try\n                {\n                    var logEntries = data.Query<WebLogEntry>(\"select * from ApplicationLogggg\");\n                    Assert.Fail(\"Invalid Sql Statement should not continue\");\n                }\n                catch (Exception ex)\n                {\n                    Console.WriteLine(\"Error caught correctly: \" + ex.Message);\n                }\n            }\n        }\n\n        [TestMethod]\n        public void DoesTableExist()\n        {\n            using (var db = new SqlDataAccess(STR_ConnectionString))\n            {\n                Assert.IsFalse(db.DoesTableExist(\"Bogus\"));\n                Assert.IsTrue(db.DoesTableExist(\"customers\"));\n            }\n        }\n\n        [TestMethod]\n        public void BinaryDataTest()\n        {\n            using (var db = new SqlDataAccess(STR_ConnectionString))\n            {\n                var customer = new Customer();\n                Assert.IsTrue(db.GetEntity(customer, \"customers\", \"Id\", 1), db.ErrorMessage);\n\n                customer.Binary = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };\n                Assert.IsTrue(db.UpdateEntity(customer, \"customers\", \"Id\", \"Id\"), db.ErrorMessage);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/StrExtractTest.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Text.RegularExpressions;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StrExtractTest\n    /// </summary>\n    [TestClass]\n    public class StrExtractTest\n    {\n        public StrExtractTest()\n        {\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\n        #region Additional test attributes\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n        #endregion\n\n        [TestMethod]\n        public void ExtractStringTest()\n        {\n            string sourceString = \"<root><data>value</data></root>\";\n            string expected = \"value\";\n\n            // Case sensitive\n            string res = StringUtils.ExtractString(sourceString, \"<data>\", \"</data>\", true, false);\n            Assert.AreEqual(expected, res, \"Failed to extract string properly\");\n\n            // Case Insensitive\n            res = StringUtils.ExtractString(sourceString, \"<Data>\", \"</Data>\", false, false);\n            Assert.AreEqual(expected, res, \"Failed to extract string properly with case insensitive values\");\n\n            // Missing end delimiter - should read until end of the string\n            res = StringUtils.ExtractString(sourceString, \"</Data>\", \"<Data>\", false, true);\n            expected = \"</root>\";\n            Assert.AreEqual(expected, res, \"Failed to extract string with missing end parameter\");\n        }\n\n        [TestMethod]\n        public void ExtractStringWithDelimetersTest()\n        {\n            string sourceString = \"<root><data>value</data></root>\";\n            string expected = \"<data>value</data>\";\n\n            // Case sensitive\n            string res = StringUtils.ExtractString(sourceString, \"<data>\", \"</data>\",\n                                                   returnDelimiters: true);\n            Assert.AreEqual(expected, res, \"Failed to extract string properly\");\n\n            // Case Insensitive\n            res = StringUtils.ExtractString(sourceString, \"<Data>\", \"</Data>\",\n                caseSensitive: false,\n                returnDelimiters: true);\n            Assert.AreEqual(expected, res, \"Failed to extract string properly with case insensitive values\");\n\n            // Missing end delimiter - should read until end of the string\n            res = StringUtils.ExtractString(sourceString, \"</Data>\", \"<Data>\", \n                caseSensitive: false,\n                returnDelimiters: true, \n                allowMissingEndDelimiter: true);\n            \n            expected = \"</data></root>\";\n            Assert.AreEqual(expected, res, \"Failed to extract string with missing end parameter\");\n        }\n\n\n        [TestMethod]\n        public void RegExExtractionTest()\n        {\n            string sourceString = \"ScriptCompression.ahx?r=ww.jquery.js\";\n            Match match = Regex.Match(sourceString, @\"r=(.*)(&|$)\", RegexOptions.IgnoreCase);\n            string res = match.Groups[1].Value;\n\n            string expected = \"ww.jquery.js\";\n            Assert.AreEqual(expected, res, \"Failed to extract string with missing end parameter\");\n        }\n\n        [TestMethod]\n        public void ReplaceStringTest()\n        {\n        }\n    }\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Westwind.Utilities.Test/StringSerializerTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Newtonsoft.Json;\nusing System.Diagnostics;\nusing System.IO;\n\n#if NETFULL\nusing System.Web.UI;\n#endif\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class StringSerializerTests\n    {\n\t\tprivate const int INT_ProfileLoop = 1;\n\n        [TestMethod]\n        public void StringSerializerTest()\n        {\n            var state = new UserState();\n            state.Email = \"rstrahl@west-wind.com\";\n            state.UserId = \"1\";\n            state.IsAdmin = true;\n            state.Name = \"Rick Strahl | Markus Egger\";\n            state.Date = DateTime.Now; \n            //state.Role = new Role() { Level = 10, Name = \"Rick\" };            \n            \n            string ser = null;\n            ser = StringSerializer.SerializeObject(state);\n\n            Stopwatch watch = new Stopwatch();\n            watch.Start();\n            \n            for (int i = 0; i < INT_ProfileLoop; i++)\n            {\n                ser = StringSerializer.SerializeObject(state);\n            }\n\n            watch.Stop();\n\n            Console.WriteLine(\"StringSerializer: \" + ser.Length + \"  elapsed: \" + watch.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(ser);\n\n            var state2 = StringSerializer.Deserialize<UserState>(ser);\n\n            Assert.AreEqual(state.Email, state2.Email);\n            Assert.AreEqual(state.UserId, state2.UserId);\n            Assert.AreEqual(state.Name, state2.Name);\n            Assert.IsNull(state.NullString);\n\n            // exact date is not lined up to ticks so compare minutes\n            Assert.AreEqual(state.Date.Minute, state2.Date.Minute);\n\n            // Computed property\n            Assert.AreEqual(state.UserIdInt, state2.UserIdInt);\n            \n            // Role is an unsupported type so it should come back as null\n            //Assert.IsNull(state2.Role);\n        }\n\n        [TestMethod]\n        public void StringSerializerNullTest()\n        {\n            UserState state = null;\n\n            string ser = StringSerializer.SerializeObject(state);\n\n            Console.WriteLine(ser.Length);\n            Console.WriteLine(ser);\n\n            var state2 = StringSerializer.Deserialize<UserState>(ser);\n\n            Assert.IsNull(state2);\n        }\n\n        [TestMethod]\n        public void StringSerializerWithNullStringValuesTest()\n        {\n            UserState state = new UserState()\n            {\n                 Email = \"email@west-wind.com\",\n                 UserId = \"rstrahl\",\n                 NullString = null\n            };\n\n            string ser = StringSerializer.SerializeObject(state);\n\n            Console.WriteLine(ser.Length);\n            Console.WriteLine(ser);\n\n            var state2 = StringSerializer.Deserialize<UserState>(ser);\n\n            Assert.IsNotNull(state2);\n            Assert.AreEqual(null, state2.NullString);\n\n\n        }\n\n#if NETFULL\n        [TestMethod]\n        public void XmlSerializerSizeTest()\n        {\n            var state = new UserState();\n            state.Email = \"rstrahl@west-wind.com\";\n            state.UserId = \"1\";\n            state.IsAdmin = true;\n            state.Name = \"Rick Strahl | Markus Egger\";\n            state.Date = DateTime.Now;\n            //state.Role = null;\n\n            \n            string xml = null; \n            var bytes = SerializationUtils.SerializeObjectToByteArray(state, true);\n\n            Stopwatch watch = new Stopwatch();\n            watch.Start();\n\n\n            for (int i = 0; i < INT_ProfileLoop; i++)\n            {\n                bytes = SerializationUtils.SerializeObjectToByteArray(state, true);\n            }\n\n            \n            watch.Stop();\n\n            xml = SerializationUtils.SerializeObjectToString(state, true);\n            Console.WriteLine(\"Xml: \" + xml.Length + \"  elapsed: \" + watch.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(xml);\n        }\n#endif\n\n        [TestMethod]\n        public void JsonSerializerSizeTest()\n        {\n            var state = new UserState();\n            state.Email = \"rstrahl@west-wind.com\";\n            state.UserId = \"1\";\n            state.IsAdmin = true;\n            state.Name = \"Rick Strahl | Markus Egger\";\n            state.Date = DateTime.Now;\n            //state.Role = null;\n\n\n            string json = null;\n            json = JsonConvert.SerializeObject(state);\n\n            Stopwatch watch = new Stopwatch();\n            watch.Start();\n\n            for (int i = 0; i < INT_ProfileLoop; i++)\n            {\n                json = JsonConvert.SerializeObject(state);\n            }\n\n            watch.Stop();\n\n            Console.WriteLine(\"Json: \" + json.Length  + \"  time: \" + watch.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(json);\n        }\n\n\n#if NETFULL\n\t\t[TestMethod]        \n        public void LosSerializerSizeTest()\n        {\n            var state = new UserState();\n            state.Email = \"rstrahl@west-wind.com\";\n            state.UserId = \"1\";\n            state.IsAdmin = true;\n            state.Name = \"Rick Strahl | Markus Egger\";\n            state.Date = DateTime.Now;\n            \n\n\n            var los = new LosFormatter();\n            var writer = new StringWriter();\n\n            los.Serialize(writer, state);\n            string json = writer.ToString();\n\n\n            Stopwatch watch = new Stopwatch();\n            watch.Start();\n\n            for (int i = 0; i < INT_ProfileLoop; i++)\n            {\n                writer = new StringWriter();\n                los.Serialize(writer, state);\n                json = writer.ToString();\n            }\n\n            watch.Stop();\n\n            Console.WriteLine(\"LosFormatter: \" + json.Length + \"  time: \" + watch.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(json);\n        }\n#endif\n\n        [Serializable]\n        public class UserState\n        {\n\n            /// <summary>\n            /// The display name for the userId\n            /// </summary>\n            public string Name { get; set; }\n\n            /// <summary>\n            /// The user's email address or login acount\n            /// </summary>\n            public string Email { get; set; }\n\n            /// <summary>\n            /// The user's user Id as a string\n            /// </summary>\n            public string UserId { get; set; }\n\n            /// <summary>\n            /// The users admin status\n            /// </summary>\n            public bool IsAdmin { get; set; }\n\n            /// <summary>\n            /// Null string\n            /// </summary>\n            public string NullString { get; set; }\n\n            /// <summary>\n            /// \n            /// </summary>\n            public DateTime Date { get; set; }\n\n            /// <summary>\n            /// Returns the User Id as an int if convertiable\n            /// </summary>\n            public int UserIdInt\n            {\n                get\n                {\n                    if (string.IsNullOrEmpty(UserId))\n                        return 0;\n\n                    if (int.TryParse(UserId, out int result))\n                        return result;\n\n                    return 0;\n                }\n                set\n                {\n                    UserId = value.ToString();\n                }\n            }\n\n            //public Role Role { get; set; } = new Role();\n        }\n\n        [Serializable]\n        public class Role\n        {\n            public string Name { get; set; }\n            public int Level { get; set; }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/StringUtilsTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StringUtilsTests\n    /// </summary>\n    [TestClass]\n    public class StringUtilsTests\n    {\n        public StringUtilsTests()\n        {\n        }\n\n        private TestContext testContextInstance;\n\n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get { return testContextInstance; }\n            set { testContextInstance = value; }\n        }\n\n        #region Additional test attributes\n\n        //\n        // You can use the following additional attributes as you write your tests:\n        //\n        // Use ClassInitialize to run code before running the first test in the class\n        // [ClassInitialize()]\n        // public static void MyClassInitialize(TestContext testContext) { }\n        //\n        // Use ClassCleanup to run code after all tests in a class have run\n        // [ClassCleanup()]\n        // public static void MyClassCleanup() { }\n        //\n        // Use TestInitialize to run code before running each test \n        // [TestInitialize()]\n        // public void MyTestInitialize() { }\n        //\n        // Use TestCleanup to run code after each test has run\n        // [TestCleanup()]\n        // public void MyTestCleanup() { }\n        //\n\n        #endregion\n\n        [TestMethod]\n        public void ToCamelCaseTest()\n        {\n            string original = \"This is a test\";\n            string expected = \"ThisIsATest\";\n            string actual = StringUtils.ToCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Simple Test\");\n\n            original = null;\n            expected = \"\";\n            actual = StringUtils.ToCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Null Test\");\n\n            original = \"Pronto 123\";\n            expected = \"Pronto123\";\n            actual = StringUtils.ToCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Embedded Numbers Test\");\n\n            original = \"None\";\n            expected = \"None\";\n            actual = StringUtils.ToCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Null Test\");\n        }\n\n        [TestMethod]\n        public void FromCamelCaseTest()\n        {\n            string original = \"NoProblem\";\n            string expected = \"No Problem\";\n            string actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Simple Test\");\n\n            expected = null;\n            original = null;\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed null test - exception should have been thrown.\");\n\n            expected = \"Pronto 123\";\n            original = \"Pronto123\";\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Embedded Numbers Test\");\n\n\n            expected = \"The Mountains Are Beautiful\";\n            original = \"TheMountainsAreBeautiful\";\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed Long String Of Text\");\n\n\n            expected = \"None\";\n            original = \"None\";\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed No CamelCase Test\");\n\n            expected = \"OK\"; // multiple upper case letters don't split\n            original = \"OK\";\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed No CamelCase Test\");\n\n            expected = \"IISAdmin\";   // multiple upper case letters don't split\n            original = \"IISAdmin\";\n            actual = StringUtils.FromCamelCase(original);\n            Assert.AreEqual(expected, actual, \"Failed No CamelCase Test\");\n\n\n        }\n\n        [TestMethod]\n        public void ToCamelCaseUpperCasingTest()\n        {\n\n            var original = \"ABCCompany\";\n            var expected = \"ABCCompany\";\n            var actual = StringUtils.FromCamelCase(original);\n            Console.WriteLine(actual);\n            Assert.AreEqual(expected, actual, \"Failed UpperCase Letters CamelCase Test\");\n\n            original = \"ThisIsATest\";\n            expected = \"This Is ATest\";\n            actual = StringUtils.FromCamelCase(original);\n            Console.WriteLine(actual);\n            Assert.AreEqual(expected, actual, \"Failed UpperCase Letters CamelCase Test\");\n\n            original = \"ABCdef\";\n            expected = \"ABCdef\";\n            actual = StringUtils.FromCamelCase(original);\n            Console.WriteLine(actual);\n            Assert.AreEqual(expected, actual, \"Failed UpperCase Letters CamelCase Test\");\n\n        }\n\n        [TestMethod]\n        public void NormalizeIndentationTest()\n        {\n            string code = @\"\n            try\n            {\n                // null should throw ArgumentException\n                actual = StringUtils.FromCamelCase(original);\n            }\";\n\n            string result = StringUtils.NormalizeIndentation(code).Trim();\n\n            Assert.IsTrue(result.Substring(0, 3) == \"try\", \"Not indented\");\n\n            code = @\"\n\t\t************************************************************************\n\t\tFUNCTION IsWinnt\n\t\t*****************\n\t\t***      Pass: llReturnVersionNumber\n\t\t***    Return: .t. or .f.   or Version Number or -1 if not NT\n\t\t*************************************************************************\n\t\tLPARAMETER llReturnVersionNumber\n\n\t\tloAPI=CREATE(\"\"wwAPI\"\")\n\t\tlcVersion = loAPI.ReadRegistryString(HKEY_LOCAL_MACHINE,;\n\t\t           \"\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\"\",;\n\t\t           \"\"CurrentVersion\"\")\n\t\t                          \n\t\tIF !llReturnVersionNumber\n\t\t  IF ISNULL(lcVersion)\n\t\t     RETURN .F.\n\t\t  ELSE\n\t\t     RETURN .T.\n\t\t  ENDIF\n\t\tENDIF\";\n\n            result = StringUtils.NormalizeIndentation(code).Trim();\n\n            Assert.IsTrue(result.Substring(0, 3) == \"***\", \"Not indented\");\n        }\n\n        [TestMethod]\n        public void ReplicateStringTest()\n        {\n            Assert.IsTrue(StringUtils.Replicate(\"123\", 3) == \"123123123\");\n            Assert.IsTrue(StringUtils.Replicate(\"1\", 2) == \"11\");\n        }\n\n        [TestMethod]\n        public void ReplicateCharTest()\n        {            \n            Assert.IsTrue(StringUtils.Replicate('1', 4) == \"1111\");\n        }\n\n        [TestMethod]\n        public void Base36EncodeTest()\n        {\n            // positive number\n            long inputNumber = 512344131333132;\n            long result = 0;\n            string base36 = \"\";\n\n            base36 = StringUtils.Base36Encode(inputNumber);\n            Assert.IsTrue(!string.IsNullOrEmpty(base36), \"Base36 number resulted in empty or null\");\n            result = StringUtils.Base36Decode(base36);\n            Assert.AreEqual(inputNumber, result, \"Base36 conversion failed.\");\n\n            // negative number\n            inputNumber = -512344131333132;\n\n            base36 = StringUtils.Base36Encode(inputNumber);\n            Assert.IsTrue(!string.IsNullOrEmpty(base36), \"Base36 number resulted in empty or null\");\n            result = StringUtils.Base36Decode(base36);\n            Assert.AreEqual(inputNumber, result, \"Base36 conversion failed.\");\n\n            inputNumber = 0;\n\n            base36 = StringUtils.Base36Encode(inputNumber);\n            Assert.IsTrue(!string.IsNullOrEmpty(base36), \"Base36 number resulted in empty or null\");\n            result = StringUtils.Base36Decode(base36);\n            Assert.AreEqual(inputNumber, result, \"Base36 conversion failed.\");\n        }\n\n        [TestMethod]\n        public void GenerateUniqueId()\n        {\n            List<string> list = new List<string>();\n            for (int i = 0; i < 100; i++)\n            {\n                list.Add(DataUtils.GenerateUniqueId());\n            }\n\n            int maxLength = list.Max(str => str.Length);\n            int minLength = list.Min(str => str.Length);\n            int negVals = list.Where(str => str.StartsWith(\"-\")).Count();\n            this.TestContext.WriteLine(\"Min Length: {0}, Max: {1}, Neg: {2}\", minLength, maxLength, negVals);\n\n            Assert.IsTrue(list.Distinct().Count() == 100, \"Didn't create 100 unique entries\");\n        }\n\n        [TestMethod]\n        public void RandomStringTest()\n        {\n            for (int i = 0; i < 20; i++)\n            {\n                string random = StringUtils.RandomString(20);\n                foreach (var ch in random)\n                    Assert.IsTrue(char.IsLetter(ch));\n            }\n        }\n\n        [TestMethod]\n        public void RandomStringWithNumbersTest()\n        {\n            for (int i = 0; i < 20; i++)\n            {\n                string random = StringUtils.RandomString(20, true);\n                foreach (var ch in random)\n                    Assert.IsTrue(char.IsLetterOrDigit(ch));\n            }\n        }\n\n        [TestMethod]\n        public void ExtractStringTest()\n        {\n            string source = \"Hello: <rant />\";\n            string extract = StringUtils.ExtractString(source, \"<\", \"/>\", false, false, true);\n\n\n\n            Console.WriteLine(extract);\n            Assert.AreEqual(extract, \"<rant />\");\n        }\n\n        [TestMethod]\n        public void ExtractStringWithDelimitersTest()\n        {\n            string source = @\"\n# Another Test Blog Post\n\nSo this is a new test blog post. I can read this and can do some cool stuff with this.\n\n\n\n<!-- Post Configuration -->\n---\n```xml\n<abstract>\nThis is the abstract ofr this blog post.\n</abstract>\n<categories>\n</categories>\n<postid>1420322</postid>\n<keywords>\n</keywords>\n<weblog>\nRick Strahl's Weblog\n</weblog>\n```\n<!-- End Post Configuration -->\n\";\n\n            string extract = StringUtils.ExtractString(source, \"<!-- Post Configuration -->\",\n                \"<!-- End Post Configuration -->\", false, true, true);\n\n            Console.WriteLine(extract);\n            Assert.IsTrue(extract.Contains(\"<!-- Post Configuration -->\"));\n            Assert.IsTrue(extract.Contains(\"<!-- End Post Configuration -->\"));\n        }\n\n\n        [TestMethod]\n        public void GetLinesTest()\n        {\n            string s =\n                @\"this is test\nwith\nmultiple lines\";\n\n            var strings = StringUtils.GetLines(s);\n            Assert.IsNotNull(strings);\n            Assert.IsTrue(strings.Length == 3);\n\n\n            s = string.Empty;\n            strings = StringUtils.GetLines(s);\n            Assert.IsNotNull(strings);\n            Assert.IsTrue(strings.Length == 1);\n\n            s = null;\n            strings = StringUtils.GetLines(s);\n            Assert.IsTrue(strings.Length == 0);\n        }\n\n        [TestMethod]\n        public void CountLinesTest()\n        {\n            string s =\n                \"this is test\\r\\n\" +\n                \"with\\n\" +\n                \"multiple lines\";\n\n            int count = StringUtils.CountLines(s);\n            Assert.IsTrue(count == 3);\n\n            s = string.Empty;\n            count = StringUtils.CountLines(s);\n            Assert.IsTrue(count == 0);\n\n            s = null;\n            count = StringUtils.CountLines(s);\n            Assert.IsTrue(count == 0);\n        }\n\n        [TestMethod]\n        public void IndexOfNthCharTest()\n        {\n            string version = \"1.11.13.2\";\n\n            var idx = StringUtils.IndexOfNth(version, '.', 1);\n            Assert.IsTrue(idx == 1);\n\n\n            idx = StringUtils.IndexOfNth(version, '.', 3);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\");\n\n            idx = StringUtils.IndexOfNth(version, '.', 2);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\");\n\n            idx = StringUtils.IndexOfNth(version, '.', 1);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\");\n\n            idx = StringUtils.IndexOfNth(version, '.', 4);\n            Assert.IsTrue(idx == -1,\"\");\n\n            version = string.Empty;\n            idx = StringUtils.IndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1);\n\n            version = null;\n            idx = StringUtils.IndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1);\n        }\n\n        [TestMethod]\n        public void IndexOfNthStringTest()\n        {\n            string version = \"11.11.11.11\";\n\n            var idx = StringUtils.IndexOfNth(version, \".11\", 1);\n            Assert.IsTrue(idx == 2,\"position should be 3\");\n\n            idx = StringUtils.IndexOfNth(version, \".11\", 2);                   \n            Assert.IsTrue(idx == 5,\"position should be 5\");\n\n            idx = StringUtils.IndexOfNth(version, \".11\", 3);            \n            Assert.IsTrue(idx == 8, \"position should be 8\");\n            \n            idx = StringUtils.IndexOfNth(version, \".11\", 4);\n            Assert.IsTrue(idx == -1, \"no 4th item\");\n            \n            idx = StringUtils.IndexOfNth(version, \"11.\", 1);\n            Assert.IsTrue(idx == 0, \"should be at start of string\");\n\n            version = string.Empty;\n            idx = StringUtils.IndexOfNth(version, \".11\", 2);\n            Assert.IsTrue(idx == -1,\"not empty\");\n\n            version = null;\n            idx = StringUtils.IndexOfNth(version, \".11\", 2);\n            Assert.IsTrue(idx == -1,\"not null\");\n        }\n\n\n        [TestMethod]\n        public void LastIndexOfNthStringTest()\n        {\n            string version = \"1.11.11.11\";\n\n            var idx = StringUtils.LastIndexOfNth(version, \".11\", 3);\n            Assert.IsTrue(idx == 1, \"Should be index of 1\");\n            \n            idx = StringUtils.LastIndexOfNth(version, \".11\", 1);            \n            Assert.IsTrue(idx == 7, \"should be . at index 7\");\n            Assert.IsTrue(version.Substring(idx, 3) == \".11\", \"should be . at index 7\");\n            \n            idx = StringUtils.LastIndexOfNth(version, \".11\", 2);\n            Assert.IsTrue(idx == 4, \"should be . at index 4\");\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 3);           \n            Assert.IsTrue(idx == 1,\"index should be at 1\");\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 4);\n            Assert.IsTrue(idx == -1, \"should not match 4th .\");\n\n            version = string.Empty;\n            idx = StringUtils.LastIndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1, \"\");\n\n            version = null;\n            idx = StringUtils.LastIndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1);\n        }\n\n        [TestMethod]\n        public void LastIndexOfNthCharTest()\n        {\n            string version = \"1.11.13.2\";\n\n            var idx = StringUtils.LastIndexOfNth(version, '.', 3);\n            Assert.IsTrue(idx == 1,\"Should be index of 1\");\n\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 1);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\",\"should be . at index 1\");\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 2);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\",\"should be . at index 2\");\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 3);\n            Assert.IsTrue(version.Substring(idx, 1) == \".\",\"should be . at index 3\");\n            Assert.IsTrue(idx == 1);\n\n            idx = StringUtils.LastIndexOfNth(version, '.', 4);\n            Assert.IsTrue(idx == -1,\"should not match 4th .\");\n\n            version = string.Empty;\n            idx = StringUtils.LastIndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1,\"\");\n\n            version = null;\n            idx = StringUtils.LastIndexOfNth(version, '.', 2);\n            Assert.IsTrue(idx == -1);\n        }\n\n\n\n        [TestMethod]\n        public void StringTokenization()\n        {\n            string code = \"This is a test {{DateTime.Now}}  and another {{System.Environment.CurrentDirectory}}\";\n\n            string tokenString = code;\n\n            var tokens = StringUtils.TokenizeString(ref tokenString, \"{{\", \"}}\");\n            Assert.IsTrue(tokens.Count > 0, \"No tokens found\");\n\n\n            Console.WriteLine(\"Tokenized Code String:\");\n            Console.WriteLine(tokenString);\n\n            Console.WriteLine(\"Tokens:\");\n            foreach (var token in tokens)\n            {\n                Console.WriteLine(token);\n            }\n\n            string returnedCode = StringUtils.DetokenizeString(tokenString, tokens);\n\n            Console.WriteLine(\"--- returned ---\");\n\n\n            Console.WriteLine(returnedCode);\n            Console.WriteLine(code);\n\n            Assert.IsTrue(returnedCode == code, \"Code doesn't match\");\n        }\n\n        [TestMethod]\n        public void NormalizeLineFeedTest()\n        {\n            string text = \"Hello World!\\r\\nMy name is Harold.\\nWhat do you want?\";\n            string converted = StringUtils.NormalizeLineFeeds(text, LineFeedTypes.Lf);\n\n            Assert.IsNotNull(converted);\n            Assert.IsFalse(converted.Contains(\"\\r\"));\n            Assert.IsTrue(converted.Count(c => c == '\\n') == 2);\n\n            converted = StringUtils.NormalizeLineFeeds(text, LineFeedTypes.CrLf);\n            Assert.IsNotNull(converted);\n            Assert.IsTrue(converted.Count(c => c == '\\n') == 2 && converted.Count(c => c == '\\r') == 2);\n\n\n            text = null;\n            converted = StringUtils.NormalizeLineFeeds(text, LineFeedTypes.CrLf);\n            Assert.IsNull(converted);\n\n            text = String.Empty;\n            converted = StringUtils.NormalizeLineFeeds(text, LineFeedTypes.CrLf);\n            Assert.IsTrue(converted == string.Empty);\n        }\n\n        [TestMethod]\n        public void TextAbstractTest()\n        {\n            var text = \"This is a long block of text that needs to be truncated into a text abstract by ending in an elipses.\\r\\n\" +\n            \"Multiple lines are converted to spaces to make the text cleaner to work with. This is some pretty long text to truncate\\n\\nand more lines.\";\n\n            string result = StringUtils.TextAbstract(text,200);\n\n            Console.WriteLine($\"{result.Length} {result}\");\n\n            // breaks on word boundaries so size may not be exact\n            Assert.IsTrue(result.Length > 180 && result.Length < 204);\n        }\n\n        [TestMethod]\n        public void IsStringInListTest()\n        {\n            var list = \"value1, value2, value3, value4, Value5\";\n\n            Assert.IsTrue(StringUtils.IsStringInList(list, \"value3\"),\"value3 not matched\");\n            Assert.IsFalse(StringUtils.IsStringInList(list, \"value10\"),\"value10 shouldn't match\");\n            Assert.IsTrue(StringUtils.IsStringInList(list, \"value5\", ignoreCase: true),\"value5 not matched\");\n            Assert.IsFalse(StringUtils.IsStringInList(list, \"value5\", ignoreCase: false),\"value5 shouldn't match\");\n            Assert.IsFalse(StringUtils.IsStringInList(list, \"value4\", ignoreCase: true, separator: ';'), \"value4 shouldn't match\");\n\n        }\n\n        [TestMethod]\n        public void OccursCharTest()\n        {\n            var s = \"3211-32123-1233-123331\";\n\n            var count = StringUtils.Occurs(s, '-');\n            Assert.IsTrue(count == 3);\n\n            s = \"3211321231233123331\";\n            count = StringUtils.Occurs(s, '-');\n            Assert.IsTrue(count == 0);\n\n            s = \"\";\n            count = StringUtils.Occurs(s, '-');\n            Assert.IsTrue(count == 0);\n\n            s = null;\n            count = StringUtils.Occurs(s, '-');\n            Assert.IsTrue(count == 0);\n        }\n\n        [TestMethod]\n        public void OccursStringTest()\n        {\n            var s = \"3211-32123-1233-123331\";\n\n            var count = StringUtils.Occurs(s, \"-\");\n            Assert.IsTrue(count == 3);\n\n            s = \"-3211-32123-1233-123331-\";\n            count = StringUtils.Occurs(s, \"-\");\n            Assert.IsTrue(count == 5);\n\n\n            s = \"3211321231233123331\";\n            count = StringUtils.Occurs(s, \"-\");\n            Assert.IsTrue(count == 0);\n\n            s = \"\";\n            count = StringUtils.Occurs(s, \"-\");\n            Assert.IsTrue(count == 0);\n\n            s = null;\n            count = StringUtils.Occurs(s, \"-\");\n            Assert.IsTrue(count == 0);\n        }\n\n\n        [TestMethod]\n        public void GetMaxCharactersTest()\n        {\n            var s=\"1234567890\";\n\n            var result = StringUtils.GetMaxCharacters(s, 5);\n            Assert.AreEqual(result.Length, 5);\n\n            result = StringUtils.GetMaxCharacters(s, 15);\n            Assert.AreEqual(result.Length, 10, \"should be 10 lengths\");\n            \n            result = StringUtils.GetMaxCharacters(s, 10);\n            Assert.AreEqual(result.Length,10, \"should be 10 lengths\");\n\n            s = null;\n            result = StringUtils.GetMaxCharacters(s, 10);\n            Assert.IsNull(result, \"result should be null\");\n\n            s = string.Empty;\n            result = StringUtils.GetMaxCharacters(s, 4);\n            Assert.AreEqual(result.Length, 0,\"result should be empty\");\n        }\n\n        [TestMethod]\n        public void GetLastCharactersTest()\n        {\n            string s = \"1234567890\";\n            \n            var res = s.GetLastCharacters(4);\n            Assert.AreEqual(res, \"7890\");\n\n            res = StringUtils.GetLastCharacters(null, 4);\n            Assert.AreEqual(res, string.Empty);\n\n            s = string.Empty;\n            res = s.GetLastCharacters(4);\n            Assert.AreEqual(res, string.Empty);\n\n            s = \"123\";\n            res = s.GetLastCharacters(4);\n            Assert.AreEqual(res, \"123\");\n        }\n\n        [TestMethod]\n        public void ToAndFromBase64StringTest() \n        { \n            var text = \"This is a test string\";\n\n            var encoded = StringUtils.ToBase64String(text);\n            Console.WriteLine(text);\n            Console.WriteLine(encoded);\n            var decoded = StringUtils.FromBase64String(encoded);\n\n            Assert.AreEqual(decoded, text, \"Text doesn't match\");        \n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SupportClasses/DebugModes.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Westwind.Utilities.Configuration\n{\n    public enum DebugModes\n    {\n        Default,\n        ApplicationErrorMessage,\n        DeveloperErrorMessage\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SupportClasses/JsonNetSerializationUtilsTests.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Converters;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n\t[TestClass]\n    public class JsonNetSerializationUtilsTests\n    {\n        [TestMethod]\n        public void JsonStringSerializeTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n\n            string json = JsonSerializationUtils.Serialize(config, true, true);\n\n            Console.WriteLine(json);\n            Assert.IsNotNull(json);\n            Assert.IsTrue(json.Contains(\"\\\"ApplicationName\\\": \"));\n        }\n\n        [TestMethod]\n        public void JsonStringSerializeCamelCaseTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n\n            string json = JsonSerializationUtils.Serialize(config, true, true, true);\n\n            Console.WriteLine(json);\n            Assert.IsNotNull(json);\n            Assert.IsTrue(json.Contains(\"\\\"applicationName\\\": \"));\n        }\n\n\n        [TestMethod]\n        public void JsonSerializeToFile()\n        {\n            var config = new AutoConfigFileConfiguration();\n\n            bool result = JsonSerializationUtils.SerializeToFile(config, \"serialized.config\", true, true);\n            string filetext = File.ReadAllText(\"serialized.config\");\n            Console.WriteLine(filetext);\n        }\n\n\n        [TestMethod]\n        public void JsonDeserializeStringTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            string json = JsonSerializationUtils.Serialize(config, true, true);\n\n            config = null;\n\n            config = JsonSerializationUtils.Deserialize(json, typeof(AutoConfigFileConfiguration), true) as AutoConfigFileConfiguration;\n\n            Assert.IsNotNull(config);\n            Assert.IsTrue(config.ApplicationName == \"New App\");\n            Assert.IsTrue(config.DebugMode == DebugModes.DeveloperErrorMessage);\n\n        }\n\n        [TestMethod]\n        public void DeserializeFromFileTest()\n        {\n            string fname = \"serialized.config\";\n\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n            bool result = JsonSerializationUtils.SerializeToFile(config, fname, true, true);\n\n            Assert.IsTrue(result);\n\n            config = null;\n\n            config = JsonSerializationUtils.DeserializeFromFile(fname, typeof(AutoConfigFileConfiguration)) as\n                AutoConfigFileConfiguration;\n\n            Assert.IsNotNull(config);\n            Assert.IsTrue(config.ApplicationName == \"New App\");\n            Assert.IsTrue(config.DebugMode == DebugModes.DeveloperErrorMessage);\n        }\n\n        [TestMethod]\n        public void JsonNativeInstantiation()\n        {\n            // Try to create instance\n            var ser = new JsonSerializer();\n\n            //ser.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;\n            //ser.ObjectCreationHandling = ObjectCreationHandling.Auto;\n            //ser.MissingMemberHandling = MissingMemberHandling.Ignore;\n            ser.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;\n            ser.Converters.Add(new StringEnumConverter());\n\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n\n            var writer = new StringWriter();\n            var jtw = new JsonTextWriter(writer);\n            jtw.Formatting = Formatting.Indented;\n\n            ser.Serialize(jtw, config);\n\n            string result = writer.ToString();\n            jtw.Close();\n\n            Console.WriteLine(result);\n\n            dynamic json = ReflectionUtils.CreateInstanceFromString(\"Newtonsoft.Json.JsonSerializer\");\n            dynamic enumConverter = ReflectionUtils.CreateInstanceFromString(\"Newtonsoft.Json.Converters.StringEnumConverter\");\n            json.Converters.Add(enumConverter);\n\n            writer = new StringWriter();\n            jtw = new JsonTextWriter(writer);\n            jtw.Formatting = Formatting.Indented;\n\n            json.Serialize(jtw, config);\n\n            result = writer.ToString();\n            jtw.Close();\n\n            Console.WriteLine(result);\n        }\n\n        [TestMethod]\n        public void NativePerfTest()\n        {\n            // Try to create instance\n            var ser = new JsonSerializer();\n            ser.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;\n            ser.Converters.Add(new StringEnumConverter());\n\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n\n            string result = null;\n\n            var sw = new Stopwatch();\n            sw.Start();\n\n            for (int i = 0; i < 10000; i++)\n            {\n                var writer = new StringWriter();\n                var jtw = new JsonTextWriter(writer);\n                jtw.Formatting = Formatting.Indented;\n                ser.Serialize(jtw, config);\n                result = writer.ToString();\n                jtw.Close();\n            }\n\n            sw.Stop();\n            Console.WriteLine(\"Native Serialize: \" + sw.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(result);\n        }\n        [TestMethod]\n        public void Native2PerfTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n\n            string result = JsonSerializationUtils.Serialize(config, true, true);\n\n            var sw = new Stopwatch();\n            sw.Start();\n\n            for (int i = 0; i < 10000; i++)\n            {\n                result = JsonSerializationUtils.Serialize(config, true, true);\n            }\n\n            sw.Stop();\n            Console.WriteLine(\"Utils Serialize: \" + sw.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(result);\n        }\n\n        [TestMethod]\n        public void UtilsPerfTest()\n        {\n            var config = new AutoConfigFileConfiguration();\n            config.ApplicationName = \"New App\";\n            config.DebugMode = DebugModes.DeveloperErrorMessage;\n\n            string result = JsonSerializationUtils.Serialize(config, true, true);\n            result = JsonSerializationUtils.Serialize(config, true, true);\n\n            var sw = new Stopwatch();\n            sw.Start();\n\n            for (int i = 0; i < 10000; i++)\n            {\n                result = JsonSerializationUtils.Serialize(config, true, true);\n            }\n\n            sw.Stop();\n            Console.WriteLine(\"Utils Serialize: \" + sw.ElapsedMilliseconds + \"ms\");\n            Console.WriteLine(result);\n        }\n\n        [TestMethod]\n        public void PrettifyJsonStringTest()\n        {\n            var test = new\n            {\n                name = \"rick\",\n                company = \"Westwind\",\n                entered = DateTime.UtcNow\n            };\n\n            string json = JsonConvert.SerializeObject(test);\n            Console.WriteLine(json);\n\n            \n            string jsonFormatted = JsonSerializationUtils.FormatJsonString(json);\n            //string jsonFormatted = JValue.Parse(json).ToString(Formatting.Indented);\n\n            //JValue.Parse()\n            Console.WriteLine(jsonFormatted);\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SupportClasses/TestHelpers.cs",
    "content": "using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\n\nnamespace Westwind.Utilities.Configuration.Tests\n{\n    public class TestHelpers\n    {\n        public static string GetTestConfigFilePath()\n        {\n            return (typeof(TestHelpers).Assembly.Location + \".config\");\n        }\n\n#if NETFULL\n\t\tpublic static string GetApplicationConfigFile()\n\t    {\n\t\t\treturn AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;\n\t\t}\n#endif\n\n        public static void DeleteTestConfigFile()\n        {\n            string configFile = GetTestConfigFilePath();\n            try\n            {\n                File.Delete(configFile);\n            }\n            catch { }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/SupportFiles/_MyJsonConfiguration.json",
    "content": "{\n  \"MyString\": \"WhatEver!!\",\n  \"SomeValue\": 0\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/TimeUtilsTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing System.Linq;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class TimeUtilsTest\n    {\n        /// <summary>\n        /// Should get a date\n        /// </summary>\n        [TestMethod]\n        public void FriendlyElapsedTimeOverflowTest()\n        {\n            var ts = TimeSpan.FromHours(1);\n            Assert.AreEqual(\"1h ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromMinutes(2);            \n            Assert.AreEqual(\"2m ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromSeconds(77);            \n            Assert.AreEqual(\"1m ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromSeconds(130);\n            Assert.AreEqual(\"2m ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromSeconds(30 * 60);            \n            Assert.AreEqual(\"30m ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n\n            ts = TimeSpan.FromSeconds(77 * 60);\n            Console.WriteLine(TimeUtils.FriendlyElapsedTimeString(ts));\n            Assert.AreEqual(\"1h ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromHours((24 * 60));            \n            Assert.AreEqual(\"2mo ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n\n            ts = TimeSpan.FromHours(25);            \n            Assert.AreEqual(\"25h ago\", TimeUtils.FriendlyElapsedTimeString(ts));\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/UrlEncodingParserTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\n#if NETFRAMEWORK\nusing System.Web;\n#endif\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StringUtilsTests\n    /// </summary>\n    [TestClass]\n    public class UrlEncodingParserTests\n    {\n        [TestMethod]\n        public void QueryStringTest()\n        {\n            string str = \"http://mysite.com/page1?id=3123&format=json&action=edit&text=It's%20a%20brave%20new%20world!\";\n\n            var query = new UrlEncodingParser(str);\n            Console.WriteLine(query);\n\n            Assert.IsTrue(query[\"id\"] == \"3123\");\n            Assert.IsTrue(query[\"format\"] == \"json\", \"wrong format \" + query[\"format\"]);\n            Assert.IsTrue(query[\"action\"] == \"edit\");\n\n            Console.WriteLine(query[\"text\"]);\n            // It's a brave new world!\n\n            query[\"id\"] = \"4123\";\n            query[\"format\"] = \"xml\";\n            query[\"name\"] = \"<< It's a brave new world! say what?\";\n\n            var url = query.ToString();\n\n            Console.WriteLine(url);            \n            Console.Write(query.ToString());\n            //http://mysite.com/page1?id=4123&format=xml&action=edit&\n            //text=It's%20a%20brave%20new%20world!&name=%3C%3C%20It's%20a%20brave%20new%20world!\n        }\n\n        [TestMethod]\n        public void QueryStringNullTest()\n        {\n            var query = new UrlEncodingParser(null);                        \n            Assert.IsTrue(query.Count == 0);\n\n            query.Set(\"id\", \"3123\");\n            Assert.IsTrue(query.Count == 1);\n            \n            Assert.IsTrue(query[\"id\"] == \"3123\");\n\n            query = new UrlEncodingParser(null);\n            var nvCol = query.Parse(null);\n\n            Assert.IsTrue(nvCol != null);\n            Assert.IsTrue(nvCol.Count == 0);\n        }\n\n        [TestMethod]\n        public void QueryValuesNullTest()\n        {\n            var query = new UrlEncodingParser(null);\n            query[\"test\"] = \"1234\";\n            query[\"id\"] = null;\n            query[\"name\"] = \"Anita\";\n\n            var c = query[\"id\"];\n            Assert.IsTrue(c == string.Empty);\n\n            var qs= query.ToString();\n            Console.WriteLine(qs);\n\n            // id should not be in the query\n            Assert.IsTrue(!qs.Contains(\"id=&\"));\n        }\n\n\n        [TestMethod]\n        public void QueryStringMultipleTest()\n        {\n            string str = \"http://mysite.com/page1?id=3123&format=json&format=xml\";\n\n            var query = new UrlEncodingParser(str);\n\n            Assert.IsTrue(query[\"id\"] == \"3123\");\n            Assert.IsTrue(query[\"format\"] == \"json,xml\", \"wrong format \" + query[\"format\"]);\n\n            // multiple format strings\n            string[] formats = query.GetValues(\"format\");\n            Assert.IsTrue(formats.Length == 2);\n\n            query.SetValues(\"multiple\", new[]\n            {\n                \"1\",\n                \"2\",\n                \"3\"\n            });\n\n            var url = query.ToString();\n\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url ==\n                          \"http://mysite.com/page1?id=3123&format=json&format=xml&multiple=1&multiple=2&multiple=3\");\n        }\n\n        [TestMethod]\n        public void QueryStringPlusSigns()\n        {\n            string str = \"http://mysite.com/page1?text=It's+a+depressing+world+out+there\";\n\n            var query = new UrlEncodingParser(str, true);\n           \n            string text = query[\"text\"];\n            Console.WriteLine(text);\n\n            Assert.IsFalse(text.Contains(\"+\") );\n            Assert.IsTrue(text.Contains(\" \"));;\n        }\n\n        [TestMethod]\n        public void WriteUrlTest()\n        {\n            // URL only\n            string url = \"http://test.com/page\";\n\n            var query = new UrlEncodingParser(url);\n            query[\"id\"] = \"321312\";\n            query[\"name\"] = \"rick\";\n\n            url = query.ToString();\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url.Contains(\"name=\"));\n            Assert.IsTrue(url.Contains(\"http://\"));\n\n            // URL with ? but no query\n            url = \"http://test.com/page?\";\n\n            query = new UrlEncodingParser(url);\n            query[\"id\"] = \"321312\";\n            query[\"name\"] = \"rick\";\n\n            url = query.ToString();\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url.Contains(\"name=\"));\n\n\n            // URL with query\n            url = \"http://test.com/page?q=search\";\n\n            query = new UrlEncodingParser(url);\n            query[\"id\"] = \"321312\";\n            query[\"name\"] = \"rick\";\n\n            url = query.ToString();\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url.Contains(\"name=\"));\n            Assert.IsTrue(url.Contains(\"http://\"));\n\n\n            // Raw query data\n            url = \"q=search&name=james\";\n\n            query = new UrlEncodingParser(url);\n            query[\"id\"] = \"321312\";\n            query[\"name\"] = \"rick\";\n\n            url = query.ToString();\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url.Contains(\"name=\"));\n            Assert.IsTrue(!url.Contains(\"http://\"));\n\n\n            // No data at all\n            url = null;\n\n            query = new UrlEncodingParser();\n            query[\"id\"] = \"321312\";\n            query[\"name\"] = \"rick\";\n\n            url = query.ToString();\n            Console.WriteLine(url);\n\n            Assert.IsTrue(url.Contains(\"name=\"));\n            Assert.IsTrue(!url.Contains(\"http://\"));\n        }\n\n#if NETFULL\n\t\t[TestMethod]\n        public void HttpUtilityTest()\n        {\n            var nv = HttpUtility.ParseQueryString(\"\");\n            nv[\"id\"] = \"Rick\";\n            nv[\"format\"] = \"json\";\n            Console.WriteLine(nv);\n        }\n#endif\n\t}\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/UserTokenManagerTests.cs",
    "content": "﻿using System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.IO;\nusing System.Linq;\nusing Westwind.Utilities.Data.Security;\n\n#if true // disabled for now - config not set up. You can manually remove and set the connection string\n\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class UserTokenManagerTests\n    {\n        public const string ConnectionString = \"server=.;database=WestwindWebStore;integrated security=true;encrypt=false\";\n\n        [TestInitialize]\n        public void Initialize()\n        {\n            // *** Set up the connection string\n            var manager = new UserTokenManager(ConnectionString);\n            if (!manager.IsUserTokenTable())\n                manager.CreateUserTokenSqlTable();\n        }\n\n        [TestMethod]\n        public void CreateTokenTest()\n        {\n            var manager = new UserTokenManager(ConnectionString);\n            var token = manager.CreateNewToken(\"1111\", \"Reference #1\", \"12345678\", data: \"Additional Data\", scope: \"AppName\" );\n\n            Assert.IsNotNull(token, manager.ErrorMessage);\n            Console.WriteLine(token);\n        }\n\n        [TestMethod]\n        public void UpdateTokenTest()\n        {\n            // Create a token\n            var manager = new UserTokenManager(ConnectionString);\n            var token = manager.CreateNewToken(\"4444\", \"Reference #4\", \"12345678\", data: \"Additional Data\", scope: \"AppName\");\n\n            Assert.IsNotNull(token, manager.ErrorMessage);\n      \n\n            // Update the token \n            token = manager.CreateNewToken(\"4444\", \"Reference #5\", \"12345678\", data: \"Additional Data\", scope: \"AppName\");\n            \n            Assert.IsNotNull(token, manager.ErrorMessage);\n            \n\n            // Retrieve the token and check the reference\n            var userToken = manager.GetToken(token);\n            Assert.IsNotNull(userToken, manager.ErrorMessage);\n            Assert.AreEqual(\"Reference #5\", userToken.ReferenceId);\n\n            // remove the token\n            manager.DeleteToken(token);\n        }\n\n        [TestMethod]\n        public void ValidateTokenTest()\n        {\n            var manager = new UserTokenManager(ConnectionString);\n            var token = manager.CreateNewToken(\"2222\", \"Reference #1\", \"12345678\");\n\n            Assert.IsNotNull(token, manager.ErrorMessage);\n            Console.WriteLine(token);\n\n\n            Assert.IsTrue(manager.IsTokenValid(token, false), manager.ErrorMessage);\n        }\n\n        [TestMethod]\n        public void GetTokenByTokenIdentifierTest()\n        {\n            var tokenIdentifier = DataUtils.GenerateUniqueId();\n\n            var manager = new UserTokenManager(ConnectionString);\n            var token = manager.CreateNewToken(\"3333\", \"Reference #3\", tokenIdentifier);\n\n            Assert.IsNotNull(token, manager.ErrorMessage);\n            Console.WriteLine(token);\n\n            var userToken = manager.GetTokenByTokenIdentifier(tokenIdentifier);\n            Assert.IsNotNull(userToken, manager.ErrorMessage);\n            Console.WriteLine(JsonSerializationUtils.Serialize(userToken));\n        }\n\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Westwind.Utilities.Test/VersionExtensionsTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StringUtilsTests\n    /// </summary>\n    [TestClass]\n    public class VersionExtensionsTests\n    {\n        [TestMethod]\n        public void DefaultVersionStringTest()\n        {\n            // using defaults  2 min, 2 max\n            var version = new Version(\"8.0.0.2\");\n            string verString = version.FormatVersion();\n            Assert.AreEqual(verString, \"8.0\");\n\n\n            version = new Version(\"8.0.1.2\");\n            verString = version.FormatVersion();\n            Assert.AreEqual(verString, \"8.0\");\n\n            version = new Version(\"8.3.1.2\");\n            verString = version.FormatVersion();\n            Assert.AreEqual(verString, \"8.3\");\n        }\n\n        [TestMethod]\n        public void MinTokenCountProvidedTest()\n        {\n            // using defaults  2 min, 2 max\n            var version = new Version(\"8.0.0.2\");\n            string verString = version.FormatVersion(3);\n            Assert.AreEqual(verString, \"8.0.0\");\n\n\n            version = new Version(\"8.0.1.2\");\n            verString = version.FormatVersion(3);\n            Assert.AreEqual(verString, \"8.0.1\");\n\n            version = new Version(\"8.3.1.2\");\n            verString = version.FormatVersion(3);\n            Assert.AreEqual(verString, \"8.3.1\");\n        }\n\n        [TestMethod]\n        public void MaxTokenProvidedTest()\n        {           \n            var version = new Version(\"8.0.0.2\");\n            string verString = version.FormatVersion(3, 4);\n            Assert.AreEqual(verString, \"8.0.0.2\");\n\n            // trim .0\n            version = new Version(\"8.0.1.0\");\n            verString = version.FormatVersion(3, 4);\n            Assert.AreEqual(verString, \"8.0.1\");\n\n            // all 4\n            version = new Version(\"8.3.1.2\");\n            verString = version.FormatVersion(3, 4);\n            Assert.AreEqual(verString, \"8.3.1.2\");\n\n            version = new Version(\"8.3.1.2\");\n            verString = version.FormatVersion(2, 3);\n            Assert.AreEqual(verString, \"8.3.1\");\n\n            // trim of 3rd 0  8.3.0 -> 8.3\n            version = new Version(\"8.3.0.2\");\n            verString = version.FormatVersion(2, 3);\n            Console.WriteLine(verString);\n            Assert.AreEqual(verString, \"8.3\");\n\n            // no trim because we look at all 4\n            version = new Version(\"8.3.0.2\");\n            verString = version.FormatVersion(2, 4);\n            Assert.AreEqual(verString, \"8.3.0.2\");\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Test/Westwind.Utilities.Test.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<TargetFrameworks>net10.0;net472</TargetFrameworks>\n\t\t<LangVersion>latest</LangVersion>\n\t\t<IsPackable>false</IsPackable>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Microsoft.CSharp\" Version=\"4.7.0\" />\n\t\t\n\t\t<PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.14.1\" />\n\t\t<PackageReference Include=\"MSTest.TestAdapter\" Version=\"3.10.4\" />\n\t\t<PackageReference Include=\"MSTest.TestFramework\" Version=\"3.10.4\" />\n\t  <PackageReference Include=\"System.Data.SQLite\" Version=\"2.0.2\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\Westwind.Utilities.Data\\Westwind.Utilities.Data.csproj\" />\n\t\t<ProjectReference Include=\"..\\Westwind.Utilities\\Westwind.Utilities.csproj\" />\n\t\t<ProjectReference Include=\"..\\Westwind.Utilities.Windows\\Westwind.Utilities.Windows.csproj\" />\n\t</ItemGroup>\n\n\t<PropertyGroup Condition=\" '$(TargetFramework)' == 'net8.0'\">\n\t\t<DefineConstants>NETSTANDARD2_0;NETCORE;NETSTANDARD</DefineConstants>\n\t  \n\t</PropertyGroup>\n\n\n\n  <ItemGroup Condition=\" '$(TargetFramework)' == 'net472' \">\n\t\t<Reference Include=\"mscorlib\" />\n\t\t<Reference Include=\"System\" />\n\t\t<Reference Include=\"System.Core\" />\t\t\n\t\t<Reference Include=\"System.Data\" />\n\t\t<Reference Include=\"System.Web\" />\n\t\t<Reference Include=\"System.Drawing\" />\n\t\t<Reference Include=\"System.Security\" />\n\t\t<Reference Include=\"System.Xml\" />\n\t\t<Reference Include=\"System.Web\" />\t\t\n\t\t<Reference Include=\"System.Configuration\" />\n\t\t<Reference Include=\"System.ComponentModel.Annotations\" />\n\t\n    <!--<PackageReference Include=\"MySql.Data\" Version=\"6.10.4\" />-->\n  </ItemGroup>\n\t<PropertyGroup Condition=\" '$(TargetFramework)' == 'net472'\">\n\t\t<DefineConstants>NET45;NETFULL</DefineConstants>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t  <Service Include=\"{82a7f48d-3b50-4b1e-b82e-3ada8210c358}\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <Folder Include=\"Properties\\\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <None Update=\"SupportFiles\\AlbumViewerData.sqlite\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\customers.DBF\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\HighQuality.jpg\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\Images\\SailBig.jpg\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\Images\\SquareImage.jpg\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\SailBig.jpg\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\SquareImage.jpg\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t  <None Update=\"SupportFiles\\_MyJsonConfiguration.json\">\n\t    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n\t  </None>\n\t</ItemGroup>\n</Project>\n"
  },
  {
    "path": "Westwind.Utilities.Test/Westwind.Utilities.Test.dll.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n  <configSections>\n    <section name=\"LogManager\" requirePermission=\"false\" type=\"System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  </configSections>\n  <LogManager>\n    <add key=\"ConnectionString\" text=\"TestDataConnection\" />\n    <add key=\"LogFilename\" text=\"TestLogFile\" />\n    <add key=\"LogAdapter\" text=\"Sql\" />\n    <add key=\"LogWebRequests\" text=\"True\" />\n    <add key=\"LogErrors\" text=\"True\" />\n  </LogManager>\n\n  <connectionStrings>\n    <add name=\"TestDataConnection\" connectionString=\"server=.;database=TestData;integrated security=true\" />        \n    <add name=\"LocalDatabaseConnection\" connectionString=\"Data Source=Database.sdf;Persist Security Info=False;\" providerName=\"System.Data.SqlServerCe.4.0\" />\n  </connectionStrings>\n</configuration>"
  },
  {
    "path": "Westwind.Utilities.Test/WindowsUtilsTests.cs",
    "content": "﻿\nusing System;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing Westwind.Utilities.Windows;\n\nnamespace Westwind.Utilities.Test\n{\n    [TestClass]\n    public class WindowsUtilsTests\n    {\n        [TestMethod]\n        public void GetDotnetVersionTest()\n        {\n            var version = WindowsUtils.GetDotnetVersion();\n            Console.WriteLine(version);\n#if NETFULL\n            StringAssert.StartsWith(version, \"4.\");            \n#else\n            StringAssert.StartsWith(version, \".NET\");            \n#endif\n        }\n\n        [TestMethod]\n        public void GetWindowsVersionTest()\n        {\n            var version = WindowsUtils.GetWindowsVersion();\n            Console.WriteLine(version);\n            StringAssert.StartsWith(version, \"10.\");\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities.Test/XmlUtilsTest.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Text.RegularExpressions;\nusing System.Xml;\nusing Westwind.Utilities;\n\nnamespace Westwind.Utilities.Tests\n{\n    /// <summary>\n    /// Summary description for StrExtractTest\n    /// </summary>\n    [TestClass]\n    public class XmlUtilsTest\n    {\n\t    public XmlUtilsTest()\n\t    {\n\t\t    \n\t    }\n\n        \n        /// <summary>\n        /// Gets or sets the test context which provides\n        /// information about and functionality for the current test run.\n        /// </summary>\n        public TestContext TestContext\n        {\n            get\n            {\n                return testContextInstance;\n            }\n            set\n            {\n                testContextInstance = value;\n            }\n        }\n\t\tprivate TestContext testContextInstance;\n\n\n\t    const string XmlTest = @\"<docRoot>\n\t<name>Bill Bunsen</name>\n\t<count>32</count>\n\t<price>122.22</price>\n\t<discount>1,30</discount>\n\t<keyValue>value2</keyValue>\n\t<isActive>true</isActive>\n\t<attributeNode type=\"\"data\"\" count=\"\"21\"\"  isLive=\"\"true\"\" />\n</docRoot>\";\n\n\t\t[TestMethod]\n\t\tpublic void ElementValuesTest()\n\t\t{\n\t\t\tvar dom = new XmlDocument();\n\t\t\tdom.LoadXml(XmlTest);\n\n\t\t\tXmlNode node = dom.DocumentElement;\n\n\t\t\tstring name = XmlUtils.GetXmlString(node,\"name\");\n\t\t\tAssert.IsNotNull(name);\n\t\t\tAssert.AreEqual(\"Bill Bunsen\",name);\n\n\t\t\tint count = XmlUtils.GetXmlInt(node, \"count\");\n\t\t\tAssert.AreEqual(32, count);\n\n\t\t\tdecimal price = XmlUtils.GetXmlDecimal(node, \"price\");\n\t\t\tAssert.AreEqual(122.22M, price);\n\n\t\t\tdecimal discount = XmlUtils.GetXmlDecimal(node, \"discount\");\n\t\t\tAssert.AreEqual(130M, discount);\n\n\t\t\tbool isActive = XmlUtils.GetXmlBool(node, \"isActive\");\n\t\t\tAssert.IsTrue(isActive);\n\n\t\t\tvar keyValue = XmlUtils.GetXmlEnum<TestKeyValues>(node, \"keyValue\");\n\t\t\tAssert.IsTrue(TestKeyValues.value2 == keyValue);\n\t\t}\n\n\t\t[TestMethod]\n\t\tpublic void AttributeValuesTest()\n\t\t{\n\t\t\tvar dom = new XmlDocument();\n\t\t\tdom.LoadXml(XmlTest);\n\n\t\t\tXmlNode node = dom.DocumentElement;\n\t\t\t\n\t\t\tvar attributes = XmlUtils.GetXmlNode(node, \"attributeNode\");\n\n\t\t\tstring type = XmlUtils.GetXmlAttributeString(attributes, \"type\");\n\t\t\tAssert.IsNotNull(type);\n\t\t\tAssert.AreEqual(\"data\", type);\n\n\t\t\tint count = XmlUtils.GetXmlAttributeInt(attributes, \"count\", -1);\n\t\t\tAssert.AreEqual(21, count);\n\n\t\t\tbool? isLive = XmlUtils.GetXmlAttributeBool(attributes, \"isLive\");\n\t\t\tAssert.IsNotNull(isLive);\n\t\t\tAssert.IsTrue(isLive.Value);\n\n\t\t}\n\n        [TestMethod]\n        public void XmlStringForElement()\n        {\n            var text = \"Characters: <doc> \\\" ' & \\r\\n break and a \\t tab too.\";\n\n            var xmlString = XmlUtils.XmlString(text);\n\n            Assert.IsTrue(xmlString.Contains(\"&lt;doc&gt;\"));\n            Assert.IsTrue(xmlString.Contains(\"&amp;\"));\n            Assert.IsFalse(xmlString.Contains(\"&apos;\") || xmlString.Contains(\"&quot;\"));\n        }\n\n        [TestMethod]\n        public void XmlStringForAttributes()\n        {\n            var text = \"Characters: <doc> \\\" ' & \\r\\n break and a \\t tab too.\";\n\n            var xmlString = XmlUtils.XmlString(text,true);\n\n            Assert.IsTrue(xmlString.Contains(\"&lt;doc&gt;\"));\n            Assert.IsTrue(xmlString.Contains(\"&amp;\"));\n            Assert.IsTrue(xmlString.Contains(\"&apos;\") || xmlString.Contains(\"&quot;\"),\"Missing quotes.\");\n\n            Console.WriteLine(xmlString);\n        }\n    }\n\n\tpublic enum TestKeyValues\n\t{\n\t\tvalue1,\n\t\tvalue2\n\t}\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Westwind.Utilities.Test/_TestConfigurationSettings.cs",
    "content": "﻿\nnamespace Westwind.Utilities.Test\n{\n\tpublic class TestConfigurationSettings\n\t{\n\t\tpublic static string WestwindToolkitConnectionString { get; set; } =\n\t\t\t\"server=.;database=WestwindToolkitSamples;integrated security=true;MultipleActiveResultSets=true;encrypt=false\";\n\n\t    public static string SqliteConnectionString = \"Data Source=./SupportFiles/AlbumViewerData.sqlite\";\n\n\t    public static string MySqlConnectionString = \"server=localhost;uid=testuser;pwd=super10seekrit;database=Localizations\";\n\n        public static string Mailserver { get; set;  } = \"localhost\";\n\n        public static string MailServerUsername { get; set;  } \n\n        public static string MailServerPassword { get; set;  }\n\n        public static bool MailServerUseSsl { get; set; }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Windows/LICENSE.MD",
    "content": "West Wind Utilities Library\n===========================\n\nMIT License\n-----------\nCopyright (c) 2019-2023 West Wind Technologies\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Westwind.Utilities.Windows/Utilities/ComObject.cs",
    "content": "﻿using System;\nusing System.Dynamic;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\n\nnamespace Westwind.Utilities.Windows\n{\n\n    /// <summary>\n    /// Wrapper around a COM object that allows 'dynamic' like behavior to\n    /// work in .NET Core where dynamic with COM objects is not working. This\n    ///\n    /// Credit to: https://github.com/bubibubi/EntityFrameworkCore.Jet/blob/3.1-preview/src/System.Data.Jet/ComObject.cs\n    /// Added here with slight interface modifications\n    /// </summary>\n#if NET6_0_OR_GREATER\n    [SupportedOSPlatform(\"windows\")]\n#endif\n    public class ComObject : DynamicObject, IDisposable\n    {\n        internal object _instance;\n\n#if DEBUG\n        private readonly Guid Id = Guid.NewGuid();\n#endif\n\n        /// <summary>\n        /// Pass a COM Object reference to create this COM Object wrapper\n        /// </summary>\n        /// <param name=\"instance\"></param>\n        public ComObject(object instance)\n        {\n            if (instance is ComObject)\n                _instance = ((ComObject)instance)._instance;\n\n            _instance = instance;\n        }\n\n        /// <summary>\n        /// Create a new instance based on ProgId\n        /// </summary>\n        /// <param name=\"progid\"></param>\n        /// <returns></returns>\n        public static ComObject CreateFromProgId(string progid)\n        {\n            var type = Type.GetTypeFromProgID(progid, false);\n            if (type != null)\n            {\n                var instance = Activator.CreateInstance(type);\n                if (instance != null)\n                {\n                    return new ComObject(instance);\n                }\n            }\n\n            throw new TypeLoadException(\"Couldn't create COM Wrapper for: \" + progid);\n        }\n\n        /// <summary>\n        /// Create a new instance based on ClassId\n        /// </summary>\n        /// <param name=\"clsid\">Guid class Id</param>\n        /// <returns></returns>\n        public static ComObject CreateFirstFrom(Guid clsid)\n        {\n            var type = Type.GetTypeFromCLSID(clsid, false);\n            if (type != null)\n            {\n                var instance = Activator.CreateInstance(type);\n                if (instance != null)\n                {\n                    return new ComObject(instance);\n                }\n            }\n\n            throw new TypeLoadException(\"Couldn't create COM Wrapper for: \" + clsid);\n        }\n\n\n        /// <summary>\n        /// Creates a new instance based on a ProgId\n        /// </summary>\n        /// <param name=\"progid\"></param>\n        public ComObject(string progid)\n            : this(Activator.CreateInstance(Type.GetTypeFromProgID(progid, true)))\n        {\n        }\n\n        /// <summary>\n        /// Creates an instance based on a class Id\n        /// </summary>\n        /// <param name=\"clsid\"></param>\n        public ComObject(Guid clsid)\n            : this(Activator.CreateInstance(Type.GetTypeFromCLSID(clsid, true)))\n        {\n        }\n\n        /// <summary>\n        /// Removes the COM reference linkage from this object\n        /// </summary>\n        /// <returns></returns>\n        public object Detach()\n        {\n            var instance = _instance;\n            _instance = null;\n            return instance;\n        }\n\n\n        #region DynamicObject implementation\n\n        public override bool TryGetMember(GetMemberBinder binder, out object result)\n        {\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        binder.Name,\n                        BindingFlags.GetProperty,\n                        Type.DefaultBinder,\n                        _instance,\n                        new object[0]\n                    ));\n            return true;\n        }\n\n\n        public override bool TrySetMember(SetMemberBinder binder, object value)\n        {\n            _instance.GetType()\n                .InvokeMember(\n                    binder.Name,\n                    BindingFlags.SetProperty,\n                    Type.DefaultBinder,\n                    _instance,\n                    new[]\n                    {\n                            value is ComObject comObject\n                                ? comObject._instance\n                                : value\n                    }\n                );\n            return true;\n        }\n\n        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\n        {\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        binder.Name,\n                        BindingFlags.InvokeMethod,\n                        Type.DefaultBinder,\n                        _instance,\n                        args\n                    ));\n            return true;\n        }\n\n        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\n        {\n            // This should work for all specific interfaces derived from `_Collection` (like `_Tables`) in ADOX.\n            result = WrapIfRequired(\n                _instance.GetType()\n                    .InvokeMember(\n                        \"Item\",\n                        BindingFlags.GetProperty,\n                        Type.DefaultBinder,\n                        _instance,\n                        indexes\n                    ));\n            return true;\n        }\n\n        #endregion\n\n\n\n        // See https://github.com/dotnet/runtime/issues/12587#issuecomment-578431424\n\n        /// <summary>\n        /// Wrap any embedded raw COM objects in a new ComObject wrapper as well\n        /// when returned from members or method results.\n        /// </summary>\n        /// <param name=\"obj\"></param>\n        /// <returns></returns>\n        private static object WrapIfRequired(object obj)\n            => obj != null && obj.GetType().IsCOMObject ? new ComObject(obj) : obj;\n\n        public void Dispose()\n        {\n            // The RCW is a .NET object and cannot be released from the finalizer,\n            // because it might not exist anymore.\n            if (_instance != null)\n            {\n                Marshal.ReleaseComObject(_instance);\n                _instance = null;\n            }\n\n            GC.SuppressFinalize(this);\n        }\n    }\n}"
  },
  {
    "path": "Westwind.Utilities.Windows/Utilities/HtmlUtils.cs",
    "content": "﻿using System;\nusing System.Runtime.Versioning;\nusing System.Text.RegularExpressions;\nusing System.Web;\n\nnamespace Westwind.Utilities.Windows\n{\n    /// <summary>\n    /// Html string and formatting utilities\n    /// \n    /// Use this class for backwards compatibility and support\n    /// for NETFX Windows .NET specific features. For .NET Core\n    /// use the HtmlUtils class in the main Westwind.Utilities namespace.\n    /// </summary>\n#if NET6_0_OR_GREATER\n    [SupportedOSPlatform(\"windows\")]\n#endif\n    public static class HtmlUtils\n    {\n        /// <summary>\n        /// Replaces and  and Quote characters to HTML safe equivalents.\n        /// </summary>\n        /// <param name=\"html\">HTML to convert</param>\n        /// <returns>Returns an HTML string of the converted text</returns>\n        public static string FixHTMLForDisplay(string html)\n        {\n            html = html.Replace(\"<\", \"&lt;\");\n            html = html.Replace(\">\", \"&gt;\");\n            html = html.Replace(\"\\\"\", \"&quot;\");\n            return html;\n        }\n\n        /// <summary>\n        /// Strips HTML tags out of an HTML string and returns just the text.\n        /// </summary>\n        /// <param name=\"html\">Html String</param>\n        /// <returns></returns>\n        public static string StripHtml(string html)\n        {\n            html = Regex.Replace(html, @\"<(.|\\n)*?>\", string.Empty);\n            html = html.Replace(\"\\t\", \" \");\n            html = html.Replace(\"\\r\\n\", string.Empty);\n            html = html.Replace(\"   \", \" \");\n            return html.Replace(\"  \", \" \");\n        }\n\n        /// <summary>\n        /// Fixes a plain text field for display as HTML by replacing carriage returns \n        /// with the appropriate br and p tags for breaks.\n        /// </summary>\n        /// <param name=\"htmlText\">Input string</param>\n        /// <returns>Fixed up string</returns>\n        public static string DisplayMemo(string htmlText)\n        {\n            if (htmlText == null)\n                return string.Empty;\n\n            htmlText = htmlText.Replace(\"\\r\\n\", \"\\r\");\n            htmlText = htmlText.Replace(\"\\n\", \"\\r\");\n            //HtmlText = HtmlText.Replace(\"\\r\\r\",\"<p>\");\n            htmlText = htmlText.Replace(\"\\r\", \"<br />\\r\\n\");\n            return htmlText;\n        }\n\n        /// <summary>\n        /// Method that handles handles display of text by breaking text.\n        /// Unlike the non-encoded version it encodes any embedded HTML text\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <returns></returns>\n        public static string DisplayMemoEncoded(string text)\n        {\n            if (text == null)\n                return string.Empty;\n\n            bool PreTag = false;\n            if (text.Contains(\"<pre>\"))\n            {\n                text = text.Replace(\"<pre>\", \"__pre__\");\n                text = text.Replace(\"</pre>\", \"__/pre__\");\n                PreTag = true;\n            }\n\n\n            // fix up line breaks into <br><p>\n            text = DisplayMemo(System.Net.WebUtility.HtmlEncode(text)); //HttpUtility.HtmlEncode(Text));\n\n            if (PreTag)\n            {\n                text = text.Replace(\"__pre__\", \"<pre>\");\n                text = text.Replace(\"__/pre__\", \"</pre>\");\n            }\n\n            return text;\n        }\n\n        /// <summary>\n        /// HTML-encodes a string and returns the encoded string.\n        /// </summary>\n        /// <param name=\"text\">The text string to encode. </param>\n        /// <returns>The HTML-encoded text.</returns>\n        [Obsolete(\"Use System.Net.WebUtility.HtmlEncode() instead.\")]\n        public static string HtmlEncode(string text)\n        {\n            return System.Net.WebUtility.HtmlEncode(text);\n            //if (text == null)\n            //    return string.Empty;\n\n            //StringBuilder sb = new StringBuilder(text.Length);\n\n            //int len = text.Length;\n            //for (int i = 0; i < len; i++)\n            //{\n            //    switch (text[i])\n            //    {\n\n            //        case '<':\n            //            sb.Append(\"&lt;\");\n            //            break;\n            //        case '>':\n            //            sb.Append(\"&gt;\");\n            //            break;\n            //        case '\"':\n            //            sb.Append(\"&quot;\");\n            //            break;\n            //        case '&':\n            //            sb.Append(\"&amp;\");\n            //            break;\n            //        case '\\'':\n            //            sb.Append(\"&#39;\");\n            //            break;\t\t\t\t\n            //        default:\n            //            if (text[i] > 159)\n            //            {\n            //                // decimal numeric entity\n            //                sb.Append(\"&#\");\n            //                sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture));\n            //                sb.Append(\";\");\n            //            }\n            //            else\n            //                sb.Append(text[i]);\n            //            break;\n            //    }\n            //}\n            //return sb.ToString();\n        }\n\n\n\n        /// <summary>\n        /// Create an Href HTML link\n        /// </summary>\n        /// <param name=\"text\"></param>\n        /// <param name=\"url\"></param>\n        /// <param name=\"target\"></param>\n        /// <param name=\"additionalMarkup\"></param>\n        /// <returns></returns>\n        public static string Href(string text, string url, string target = null, string additionalMarkup = null)\n        {\n#if NETFULL\n            if (url.StartsWith(\"~\"))\n                url = ResolveUrl(url);\n#endif\n            return \"<a href=\\\"\" + url + \"\\\" \" +\n                (string.IsNullOrEmpty(target) ? string.Empty : \"target=\\\"\" + target + \"\\\" \") +\n                (string.IsNullOrEmpty(additionalMarkup) ? string.Empty : additionalMarkup) +\n                \">\" + text + \"</a>\";\n        }\n\n        /// <summary>\n        /// Creates an HREF HTML Link\n        /// </summary>\n        /// <param name=\"url\"></param>\n        public static string Href(string url)\n        {\n            return Href(url, url, null, null);\n        }\n\n        /// <summary>\n        /// Returns an IMG link as a string. If the image is null\n        /// or empty a blank string is returned.\n        /// </summary>\n        /// <param name=\"imageUrl\"></param>\n        /// <param name=\"additionalMarkup\">any additional attributes added to the element</param>\n        /// <returns></returns>\n        public static string ImgRef(string imageUrl, string additionalMarkup = null)\n        {\n            if (string.IsNullOrEmpty(imageUrl))\n                return string.Empty;\n\n#if NETFULL\n            if (imageUrl.StartsWith(\"~\"))\n                imageUrl = ResolveUrl(imageUrl);\n#endif\n\n            string img = \"<img src=\\\"\" + imageUrl + \"\\\" \";\n\n            if (!string.IsNullOrEmpty(\"additionalMarkup\"))\n                img += additionalMarkup + \" \";\n\n            img += \"/>\";\n            return img;\n        }\n\n\n        /// <summary>\n        /// Resolves a URL based on the current HTTPContext\n        /// \n        /// Note this method is added here internally only\n        /// to support the HREF() method and ~ expansion\n        /// on urls.\n        /// </summary>\n        /// <param name=\"originalUrl\"></param>\n        /// <returns></returns>\n        internal static string ResolveUrl(string originalUrl)\n        {\n            if (string.IsNullOrEmpty(originalUrl))\n                return string.Empty;\n\n            // Absolute path - just return\n            if (originalUrl.IndexOf(\"://\") != -1)\n                return originalUrl;\n\n            // Fix up image path for ~ root app dir directory\n            if (originalUrl.StartsWith(\"~\"))\n            {\n\n#if NETFULL\n                //return VirtualPathUtility.ToAbsolute(originalUrl);\n                string newUrl = \"\";\n\n                if (HttpContext.Current != null)\n                {\n                    newUrl = HttpContext.Current.Request.ApplicationPath +\n                             originalUrl.Substring(1);\n                    newUrl = newUrl.Replace(\"//\", \"/\"); // must fix up for root path\n                }\n                else\n                    // Not context: assume current directory is the base directory\n                    throw new ArgumentException(\"Invalid URL: Relative URL not allowed.\");\n\n                // Just to be sure fix up any double slashes\n                return newUrl;\n#else\n                throw new ArgumentException(\"Invalid URL: Relative URL not allowed.\");\n#endif\n            }\n\n            return originalUrl;\n        }\n\n\n        /// <summary>\n        /// Create an embedded image url for binary data like images and media\n        /// </summary>\n        /// <param name=\"imageBytes\"></param>\n        /// <param name=\"mimeType\"></param>\n        /// <returns></returns>\n        public static string BinaryToEmbeddedBase64(byte[] imageBytes, string mimeType = \"image/png\")\n        {\n            var data = $\"data:{mimeType};base64,\" + Convert.ToBase64String(imageBytes, 0, imageBytes.Length);\n            return data;\n        }\n\n#if NETCORE\n\n        /// <summary>\n        /// Decoded an embedded base64 resource string into its binary content and mime type\n        /// </summary>\n        /// <param name=\"base64Data\">Embedded Base64 data (data:mime/type;b64data) </param>\n        /// <returns></returns>\n        public static (byte[] bytes, string mimeType) EmbeddedBase64ToBinary(string base64Data)\n        {\n            if (string.IsNullOrEmpty(base64Data))\n                return (null, null);\n\n            var parts = base64Data.Split(',');\n            if (parts.Length != 2)\n                return (null, null);\n\n            var mimeType = parts[0].Replace(\"data:\", \"\").Replace(\";base64\", \"\");\n            var data = parts[1];\n\n            var bytes = Convert.FromBase64String(data);\n\n            return (bytes, mimeType);\n        }\n#endif        \n\n        /// <summary>\n        /// Creates an Abstract from an HTML document. Strips the \n        /// HTML into plain text, then creates an abstract.\n        /// </summary>\n        /// <param name=\"html\"></param>\n        /// <returns></returns>\n        public static string HtmlAbstract(string html, int length)\n        {\n            return StringUtils.TextAbstract(StripHtml(html), length);\n        }\n\n\n        static string DefaultHtmlSanitizeTagBlackList { get; } = \"script|iframe|object|embed|form\";\n\n        static Regex _RegExScript = new Regex($@\"(<({DefaultHtmlSanitizeTagBlackList})\\b[^<]*(?:(?!<\\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\\/({DefaultHtmlSanitizeTagBlackList})>)\",\n        RegexOptions.IgnoreCase | RegexOptions.Multiline);\n\n        // strip javascript: and unicode representation of javascript:\n        // href='javascript:alert(\\\"gotcha\\\")'\n        // href='&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:alert(\\\"gotcha\\\");'\n        static Regex _RegExJavaScriptHref = new Regex(\n            @\"<[^>]*?\\s(href|src|dynsrc|lowsrc)=.{0,20}((javascript:)|(&#)).*?>\",\n            RegexOptions.IgnoreCase | RegexOptions.Singleline);\n\n        static Regex _RegExOnEventAttributes = new Regex(\n            @\"<[^>]*?\\s(on[^\\s\\\\]{0,20}=([\"\"].*?[\"\"]|['].*?['])).*?(>|\\/>)\",\n            RegexOptions.IgnoreCase | RegexOptions.Singleline);\n\n        /// <summary>\n        /// Sanitizes HTML to some of the most of \n        /// </summary>\n        /// <remarks>\n        /// This provides rudimentary HTML sanitation catching the most obvious\n        /// XSS script attack vectors. For mroe complete HTML Sanitation please look into\n        /// a dedicated HTML Sanitizer.\n        /// </remarks>\n        /// <param name=\"html\">input html</param>\n        /// <param name=\"htmlTagBlacklist\">A list of HTML tags that are stripped.</param>\n        /// <returns>Sanitized HTML</returns>\n        public static string SanitizeHtml(string html, string htmlTagBlacklist = \"script|iframe|object|embed|form\")\n        {\n            if (string.IsNullOrEmpty(html))\n                return html;\n\n            if (string.IsNullOrEmpty(htmlTagBlacklist) || htmlTagBlacklist == DefaultHtmlSanitizeTagBlackList)\n            {\n                // Use the default list of tags Replace Script tags - reused expr is more efficient\n                html = _RegExScript.Replace(html, string.Empty);\n            }\n            else\n            {\n                // create a custom list including provided tags\n                html = Regex.Replace(html,\n                                        $@\"(<({htmlTagBlacklist})\\b[^<]*(?:(?!<\\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\\/({htmlTagBlacklist})>)\",\n                                        \"\", RegexOptions.IgnoreCase | RegexOptions.Multiline);\n            }\n\n            // Remove javascript: directives\n            var matches = _RegExJavaScriptHref.Matches(html);\n            foreach (Match match in matches)\n            {\n                if (match.Groups.Count > 2)\n                {\n                    var txt = match.Value.Replace(match.Groups[2].Value, \"unsupported:\");\n                    html = html.Replace(match.Value, txt);\n                }\n            }\n\n            // Remove onEvent handlers from elements\n            matches = _RegExOnEventAttributes.Matches(html);\n            foreach (Match match in matches)\n            {\n                var txt = match.Value;\n                if (match.Groups.Count > 1)\n                {\n                    var onEvent = match.Groups[1].Value;\n                    txt = txt.Replace(onEvent, string.Empty);\n                    if (!string.IsNullOrEmpty(txt))\n                        html = html.Replace(match.Value, txt);\n                }\n            }\n\n            return html;\n        }\n    }\n}\n"
  },
  {
    "path": "Westwind.Utilities.Windows/Utilities/WindowsUtils.cs",
    "content": "﻿\nusing System;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing Microsoft.Win32;\n\nnamespace Westwind.Utilities.Windows\n{\n\n    /// <summary>\n    /// Windows specific system and information helpers\n    /// Helper class that provides Windows and .NET Version numbers.    \n    /// </summary>\n    #if NET6_0_OR_GREATER\n    [SupportedOSPlatform(\"windows\")]\n    #endif\n    public static class WindowsUtils\n    {\n        /// <summary>\n        /// Returns the Windows major version number for this computer.\n        /// based on this: http://stackoverflow.com/questions/21737985/windows-version-in-c-sharp/37716269#37716269\n        /// </summary>\n        public static uint WinMajorVersion\n        {\n            get\n            {\n                dynamic major;\n                // The 'CurrentMajorVersionNumber' string value in the CurrentVersion key is new for Windows 10,\n                // and will most likely (hopefully) be there for some time before MS decides to change this - again...\n                if (TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentMajorVersionNumber\",\n                    out major, Registry.LocalMachine))\n                {\n                    return (uint)major;\n                }\n\n                // When the 'CurrentMajorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'\n                dynamic version;\n                if (!TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentVersion\",\n                    out version, Registry.LocalMachine))\n                    return 0;\n\n                var versionParts = ((string)version).Split('.');\n                if (versionParts.Length != 2) return 0;\n                uint majorAsUInt;\n                return uint.TryParse(versionParts[0], out majorAsUInt) ? majorAsUInt : 0;\n            }\n        }\n\n        /// <summary>\n        ///     Returns the Windows minor version number for this computer.\n        /// </summary>\n        public static uint WinMinorVersion\n        {\n            get\n            {\n                dynamic minor;\n                // The 'CurrentMinorVersionNumber' string value in the CurrentVersion key is new for Windows 10,\n                // and will most likely (hopefully) be there for some time before MS decides to change this - again...\n                if (TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentMinorVersionNumber\",\n                    out minor, Registry.LocalMachine))\n                {\n                    return (uint)minor;\n                }\n\n                // When the 'CurrentMinorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'\n                dynamic version;\n                if (!TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentVersion\",\n                    out version, Registry.LocalMachine))\n                    return 0;\n\n                var versionParts = ((string)version).Split('.');\n                if (versionParts.Length != 2) return 0;\n                uint minorAsUInt;\n                return uint.TryParse(versionParts[1], out minorAsUInt) ? minorAsUInt : 0;\n            }\n        }\n\n        /// <summary>\n        ///     Returns the Windows minor version number for this computer.\n        /// </summary>\n        public static uint WinBuildVersion\n        {\n            get\n            {\n                dynamic buildNumber;\n                // The 'CurrentMinorVersionNumber' string value in the CurrentVersion key is new for Windows 10,\n                // and will most likely (hopefully) be there for some time before MS decides to change this - again...\n                if (TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentBuildNumber\",\n                    out buildNumber, Registry.LocalMachine))\n                {\n                    return Convert.ToUInt32(buildNumber);\n                }\n\n\n                if (!TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"CurrentBuild\",\n                    out buildNumber, Registry.LocalMachine))\n                    return 0;\n\n                return Convert.ToUInt32(buildNumber);\n            }\n        }\n\n        /// <summary>\n        ///     Returns the Windows minor version number for this computer.\n        /// </summary>\n        public static string WinBuildLabVersion\n        {\n            get\n            {\n                dynamic buildNumber;\n                // The 'CurrentMinorVersionNumber' string value in the CurrentVersion key is new for Windows 10,\n                // and will most likely (hopefully) be there for some time before MS decides to change this - again...\n                if (TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"BuildLabEx\",\n                    out buildNumber, Registry.LocalMachine))\n                {\n                    return buildNumber;\n                }\n\n                return WinBuildVersion.ToString();\n            }\n        }\n\n        /// <summary>\n        /// Returns whether or not the current computer is a server or not.\n        /// </summary>\n        public static uint IsServer\n        {\n            get\n            {\n                dynamic installationType;\n                if (TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\", \"InstallationType\",\n                    out installationType))\n                {\n                    return (uint)(installationType.Equals(\"Client\") ? 0 : 1);\n                }\n\n                return 0;\n            }\n        }\n\n        static string DotnetVersion = null;\n\n        /// <summary> \n        /// Returns the .NET version installed on the machine\n        /// </summary>             \n        /// <returns>\n        /// Full Framework: number (ie `4.8.1`)\n        /// .NET Core: FrameworkVersion (ie. `.NET 7.0.7`)\n        /// </returns>\n        public static string GetDotnetVersion()\n        {\n#if NETCORE\n            if (!string.IsNullOrEmpty(DotnetVersion))\n                return DotnetVersion;\n\n            DotnetVersion = RuntimeInformation.FrameworkDescription;\n            return DotnetVersion;\n#else\n\n            if (!string.IsNullOrEmpty(DotnetVersion))\n                return DotnetVersion;\n\n            dynamic value;\n            TryGetRegistryKey(@\"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\\", \"Release\", out value);\n\n            if (value == null)\n            {\n                DotnetVersion = \"4.0\";\n                return DotnetVersion;\n            }\n\n            int releaseKey = value;\n\n            // https://msdn.microsoft.com/en-us/library/hh925568(v=vs.110).aspx\n            // RegEdit paste: HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\n            if (releaseKey >= 533320)\n                DotnetVersion = \"4.8.1\";\n            else if (releaseKey >= 528040)\n                DotnetVersion = \"4.8\";\n            else if (releaseKey >= 461808)\n                DotnetVersion = \"4.7.2\";\n            else if (releaseKey >= 461308)\n                DotnetVersion = \"4.7.1\";\n            else if (releaseKey >= 460798)\n                DotnetVersion = \"4.7\";\n            else if (releaseKey >= 394802)\n                DotnetVersion = \"4.6.2\";\n            else if (releaseKey >= 394254)\n                DotnetVersion = \"4.6.1\";\n            else if (releaseKey >= 393295)\n                DotnetVersion = \"4.6\";\n            else if (releaseKey >= 379893)\n                DotnetVersion = \"4.5.2\";\n            else if (releaseKey >= 378675)\n                DotnetVersion = \"4.5.1\";\n            else if (releaseKey >= 378389)\n                DotnetVersion = \"4.5\";\n\n            // This line should never execute. A non-null release key should mean \n            // that 4.5 or later is installed. \n            else\n                DotnetVersion = \"4.0\";\n\n            return DotnetVersion;\n#endif\n        }\n\n        static string _WindowsVersion = null;\n\n        /// <summary>\n        /// Returns a Windows Version string including build number\n        /// </summary>\n        /// <returns></returns>\n        public static string GetWindowsVersion()\n        {\n\n            if (string.IsNullOrEmpty(_WindowsVersion))\n                _WindowsVersion = WinMajorVersion + \".\" + WinMinorVersion + \".\" +\n                                  WinBuildLabVersion;\n\n            return _WindowsVersion;\n        }\n\n\n        /// <summary>\n        /// Retrieves a registry value into a dynamic value based on path and key.\n        /// </summary>\n        /// <param name=\"path\">base key relative path (starts without slash)</param>\n        /// <param name=\"key\">The keyname to retrieve. Use string.Empty for the default key</param>\n        /// <param name=\"value\">Out value result as a dynamic value</param>\n        /// <param name=\"baseKey\">Base key like HKLM, HKCU, HKCR</param>\n        /// <returns>true or false</returns>\n        public static bool TryGetRegistryKey(string path, string key, out dynamic value, RegistryKey baseKey = null)\n        {\n            if (baseKey == null)\n                baseKey = Registry.CurrentUser;\n\n            value = null;\n            try\n            {\n                RegistryKey rk = baseKey.OpenSubKey(path);\n                if (rk == null) return false;\n                value = rk.GetValue(key);\n                return value != null;\n            }\n            catch\n            {\n                return false;\n            }\n        }\n\n\n        /// <summary>\n        /// Determine whether the user is an Administrator\n        /// </summary>\n        /// <returns></returns>\n        [DllImport(\"shell32.dll\", SetLastError = true)]\n        [return: MarshalAs(UnmanagedType.Bool)]\n        static extern bool IsUserAnAdmin();\n    }\n\n\n\n}\n"
  },
  {
    "path": "Westwind.Utilities.Windows/Westwind.Utilities.Windows.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\t<PropertyGroup>\n\t\t<TargetFrameworks>net10.0;net8.0;net6.0;net472;netstandard2.0</TargetFrameworks>\n\t\t<Version>5.2.6</Version>\n\t\t<Authors>Rick Strahl</Authors>\n\t\t<RequireLicenseAcceptance>false</RequireLicenseAcceptance>\n\t\t<Language>en-US</Language>\n\t\t<AssemblyName>Westwind.Utilities.Windows</AssemblyName>\n\t\t<AssemblyTitle>Westwind.Utilities.Windows</AssemblyTitle>\n\t\t<NeutralLanguage>en-US</NeutralLanguage>\n\t\t<PackageId>Westwind.Utilities.Windows</PackageId>\n\t\t<RootNamespace>Westwind.Utilities.Windows</RootNamespace>\n\t\t<Title>West Wind Utilities Windows Specific Features</Title>\n\t\t<Description>This sub-component of Westwind.Utilities extracts the Windows Specific features into a separate library to remove the Windows specific dependencies in the core library.</Description>\n\t\t<Summary>Sub-Component of Westwind.Utilities to provide Windows specific features. Provided for backwards compatibility.</Summary>\n\t\t<PackageCopyright>Rick Strahl, West Wind Technologies 2007-2024</PackageCopyright>\n\t\t<PackageTags>Westwind ApplicationConfiguration StringUtils ReflectionUtils DataUtils FileUtils TimeUtils SerializationUtils ImageUtils Logging DAL Sql ADO.NET</PackageTags>\n\t\t<PackageReleaseNotes></PackageReleaseNotes>\n\t\t<PackageProjectUrl>http://github.com/rickstrahl/westwind.utilities</PackageProjectUrl>\n\t\t<PackageIcon>icon.png</PackageIcon>\n\t\t<PackageLicenseFile>LICENSE.MD</PackageLicenseFile>\n\t\t<AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n\t\t<Copyright>Rick Strahl, West Wind Technologies, 2010-2026</Copyright>\n\t\t<Company>West Wind Technologies</Company>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(Configuration)'=='Debug'\">\n\t\t<DefineConstants>TRACE;DEBUG;</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\" '$(Configuration)' == 'Release' \">\n\t\t<DebugType>embedded</DebugType>\n\t\t<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>\n\t\t<GenerateDocumentationFile>true</GenerateDocumentationFile>\n\t\t<GeneratePackageOnBuild>True</GeneratePackageOnBuild>\n\t\t<PackageOutputPath>./nupkg</PackageOutputPath>\n\t\t<PublishRepositoryUrl>true</PublishRepositoryUrl>\n\t\t<DefineConstants>RELEASE</DefineConstants>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(TargetFramework)' != 'net472'\">\n\t\t<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\" '$(TargetFramework)' == 'net472'\">\n\t\t<DefineConstants>NETFULL</DefineConstants>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\"'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net472|AnyCPU'\">\n\t\t<DebugType>embedded</DebugType>\n\t\t<DebugSymbols>true</DebugSymbols>\n\t</PropertyGroup>\n\n\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' == 'net472' \">\n\t\t<!-- explicitly required netFX dependencies -->\n\t\t<Reference Include=\"Microsoft.CSharp\" />\n\t\t<Reference Include=\"System.Web\" />\n\t\t<Reference Include=\"System.Security\" />\n\t\t<Reference Include=\"System.Configuration\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\" '$(TargetFramework)' != 'net472' \">\n\t\t<!-- explicitly required netFX dependencies -->\n\t\t<PackageReference Include=\"Microsoft.Win32.Registry\" Version=\"5.0.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<None Include=\"icon.png\" Pack=\"true\" PackagePath=\"\" />\n\t\t<None Include=\"LICENSE.MD\" Pack=\"true\" PackagePath=\"\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <ProjectReference Include=\"..\\Westwind.Utilities\\Westwind.Utilities.csproj\" />\n\t</ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Westwind.Utilities.Windows/Westwind.Utilities.Windows.sln",
    "content": "Microsoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.5.2.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Westwind.Utilities.Windows\", \"Westwind.Utilities.Windows.csproj\", \"{CF17AAF1-F926-0AB9-0E12-FDC79F47739D}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{CF17AAF1-F926-0AB9-0E12-FDC79F47739D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{CF17AAF1-F926-0AB9-0E12-FDC79F47739D}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{CF17AAF1-F926-0AB9-0E12-FDC79F47739D}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{CF17AAF1-F926-0AB9-0E12-FDC79F47739D}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {280DCD4C-A918-4465-91DE-33E1156A69F8}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Westwind.Utilities.Windows/publish-nuget.ps1",
    "content": "﻿if (test-path ./nupkg) {\n    remove-item ./nupkg -Force -Recurse\n}   \n\ndotnet build -c Release\n\n$filename = Get-ChildItem \"./nupkg/*.nupkg\" | sort LastWriteTime | select -last 1 | select -ExpandProperty \"Name\"\nWrite-host $filename\n$len = $filename.length\n\nif ($len -gt 0) {\n    Write-Host \"signing... $filename\"\n    #nuget sign  \".\\nupkg\\$filename\"   -CertificateSubject \"West Wind Technologies\" -timestamper \" http://timestamp.digicert.com\"    \n    nuget push  \".\\nupkg\\$filename\" -source \"https://nuget.org\"    \n\n    Write-Host \"Done.\"\n}"
  },
  {
    "path": "Westwind.Utilities.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.5.33516.290\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Westwind.Utilities\", \"Westwind.Utilities\\Westwind.Utilities.csproj\", \"{16881D93-0458-4DD0-BAB6-111C44864607}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Tests\", \"Tests\", \"{80CD1636-8E36-4BC3-85E6-80F25C070095}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Westwind.Utilities.Test\", \"Westwind.Utilities.Test\\Westwind.Utilities.Test.csproj\", \"{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{FE23F8DD-98B3-464D-AB7C-AEB259A8A5F9}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t.gitignore = .gitignore\n\t\tChangelog.md = Changelog.md\n\t\tReadme.md = Readme.md\n\tEndProjectSection\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Westwind.Utilities.Data\", \"Westwind.Utilities.Data\\Westwind.Utilities.Data.csproj\", \"{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Westwind.Utilities.Windows\", \"Westwind.Utilities.Windows\\Westwind.Utilities.Windows.csproj\", \"{A45F8462-8C5C-4903-9074-3F38BEC9286C}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tnet40|Any CPU = net40|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.net40|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.net40|Any CPU.Build.0 = Release|Any CPU\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{16881D93-0458-4DD0-BAB6-111C44864607}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.net40|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.net40|Any CPU.Build.0 = Release|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.net40|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.net40|Any CPU.Build.0 = Debug|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{04AA52BB-B4EB-4ECA-962D-52EA79168AA9}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.net40|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.net40|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{A45F8462-8C5C-4903-9074-3F38BEC9286C}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{097C1E6F-8AAD-47B9-8A7F-64B2D7343033} = {80CD1636-8E36-4BC3-85E6-80F25C070095}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {76FF4B12-41A6-4CCD-AACE-C84738B9D72C}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Westwind.Utilities.slnx",
    "content": "<Solution>\n  <Folder Name=\"/Solution Items/\">\n    <File Path=\".gitignore\" />\n    <File Path=\"Changelog.md\" />\n    <File Path=\"Readme.md\" />\n  </Folder>\n  <Folder Name=\"/Tests/\">\n    <Project Path=\"Westwind.Utilities.Test/Westwind.Utilities.Test.csproj\" />\n  </Folder>\n  <Project Path=\"Westwind.Utilities.Data/Westwind.Utilities.Data.csproj\" />\n  <Project Path=\"Westwind.Utilities.Windows/Westwind.Utilities.Windows.csproj\" />\n  <Project Path=\"Westwind.Utilities/Westwind.Utilities.csproj\" />\n</Solution>\n"
  }
]