Repository: RickStrahl/Westwind.Utilities Branch: master Commit: a0fa76021cb9 Files: 142 Total size: 1.2 MB Directory structure: gitextract_wnw4ww1a/ ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── Changelog.md ├── LICENSE.md ├── Readme.md ├── Westwind.Utilities/ │ ├── .gitignore │ ├── Configuration/ │ │ ├── AppConfiguration.cd │ │ ├── AppConfiguration.cs │ │ └── Providers/ │ │ ├── ConfigurationFileConfigurationProvider.cs │ │ ├── ConfigurationProviderBase.cs │ │ ├── IConfigurationProvider.cs │ │ ├── JsonFileConfigurationProvider.cs │ │ ├── StringConfigurationProvider.cs │ │ └── XmlFileConfigurationProvider.cs │ ├── Data/ │ │ ├── ValidationError.cs │ │ └── ValidationErrorCollection.cs │ ├── InternetTools/ │ │ ├── HttpClient.cs │ │ └── SmtpClientNative.cs │ ├── LICENSE.MD │ ├── Properties/ │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── SupportClasses/ │ │ ├── DelegateFactory.cs │ │ ├── Encryption.cs │ │ ├── Expando.cs │ │ ├── ObjectFactory.cs │ │ ├── PropertyBag.cs │ │ ├── Scheduler.cs │ │ ├── StringSerializer.cs │ │ ├── UrlEncodingParser.cs │ │ └── UrlParser.cs │ ├── Utilities/ │ │ ├── AsyncUtils.cs │ │ ├── ComObject.cs │ │ ├── DataUtils.cs │ │ ├── DebugUtils.cs │ │ ├── ExensionMethods/ │ │ │ ├── DateTimeExtensions.cs │ │ │ ├── DictionaryExtensions.cs │ │ │ ├── LinqExtensions.cs │ │ │ └── MemoryStreamExtensions.cs │ │ ├── FileUtils.cs │ │ ├── GenericUtils.cs │ │ ├── HtmlUtils.cs │ │ ├── HttpClientUtils.cs │ │ ├── HttpUtils.cs │ │ ├── HttpUtilsWebClient.cs │ │ ├── ImageUtils.cs │ │ ├── JsonSerializationUtils.cs │ │ ├── KnownFolders.cs │ │ ├── LanguageUtils.cs │ │ ├── NetworkUtils.cs │ │ ├── PasswordScrubber.cs │ │ ├── ReflectionUtils.cs │ │ ├── SecurityUtils.cs │ │ ├── SerializationUtils.cs │ │ ├── ShellUtils.cs │ │ ├── StringUtils.cs │ │ ├── TimeUtils.cs │ │ ├── VersionUtils.cs │ │ └── XmlUtils.cs │ ├── Westwind.Utilities.csproj │ ├── Westwind.Utilities.sln │ └── publish-nuget.ps1 ├── Westwind.Utilities.Data/ │ ├── Configuration/ │ │ └── SqlServerConfigurationProvider.cs │ ├── ConnectionStringInfo.cs │ ├── DataAccessBase.cs │ ├── DataTableExtensions.cs │ ├── DynamicDataReader.cs │ ├── DynamicDataRow.cs │ ├── LICENSE.MD │ ├── Security/ │ │ └── UserTokenManager.cs │ ├── SqlDataAccess.cs │ ├── SqlUtils.cs │ ├── Westwind.Utilities.Data.csproj │ └── publish-nuget.ps1 ├── Westwind.Utilities.Test/ │ ├── App.config │ ├── AppConfiguration/ │ │ ├── AutoConfigFileConfigurationTests.cs │ │ ├── ConfigurationClasses/ │ │ │ ├── AutoConfigFileConfiguration.cs │ │ │ ├── CustomConfigFileConfiguration.cs │ │ │ ├── DatabaseConfiguration.cs │ │ │ ├── JsonFileConfiguration.cs │ │ │ ├── LicenseInformation.cs │ │ │ ├── StringConfigFileConfiguration.cs │ │ │ └── XmlFileConfiguration.cs │ │ ├── CustomConfigFileConfigurationTests.cs │ │ ├── DatabaseConfigurationTests.cs │ │ ├── JsonFileConfigurationTests.cs │ │ ├── StringConfigurationTests.cs │ │ └── XmlFileConfigurationTests.cs │ ├── AsyncUtilsTests.cs │ ├── DataUtilsTests.cs │ ├── DynamicDataReaderTests.cs │ ├── DynamicDataRowTests.cs │ ├── EncryptionTests.cs │ ├── ExpandUrlsParserTest.cs │ ├── ExpandoTests.cs │ ├── FileUtilsTests.cs │ ├── HttpClientTests.cs │ ├── HttpClientUtilsTests.cs │ ├── HttpUtilsTests.cs │ ├── ImagingTests.cs │ ├── Models/ │ │ └── Entities/ │ │ ├── Customer.cs │ │ ├── LineItem.cs │ │ ├── Order.cs │ │ └── WebLogEntry.cs │ ├── NetworkUtilsTests.cs │ ├── ObjectFactoryTests.cs │ ├── PasswordScrubberTests.cs │ ├── PropertyBagTest.cs │ ├── ReflectionUtilsTests.cs │ ├── SanitizeHtmlTests.cs │ ├── ShellUtilsTests.cs │ ├── SmtpClientNativeTests.cs │ ├── SqlDataAccessFoxProTests.cs │ ├── SqlDataAccessMySqlTests.cs │ ├── SqlDataAccessSqlLiteTests.cs │ ├── SqlDataAccessTests.cs │ ├── StrExtractTest.cs │ ├── StringSerializerTests.cs │ ├── StringUtilsTests.cs │ ├── SupportClasses/ │ │ ├── DebugModes.cs │ │ ├── JsonNetSerializationUtilsTests.cs │ │ └── TestHelpers.cs │ ├── SupportFiles/ │ │ ├── _MyJsonConfiguration.json │ │ └── customers.DBF │ ├── TimeUtilsTests.cs │ ├── UrlEncodingParserTests.cs │ ├── UserTokenManagerTests.cs │ ├── VersionExtensionsTests.cs │ ├── Westwind.Utilities.Test.csproj │ ├── Westwind.Utilities.Test.dll.config │ ├── WindowsUtilsTests.cs │ ├── XmlUtilsTest.cs │ └── _TestConfigurationSettings.cs ├── Westwind.Utilities.Windows/ │ ├── LICENSE.MD │ ├── Utilities/ │ │ ├── ComObject.cs │ │ ├── HtmlUtils.cs │ │ └── WindowsUtils.cs │ ├── Westwind.Utilities.Windows.csproj │ ├── Westwind.Utilities.Windows.sln │ └── publish-nuget.ps1 ├── Westwind.Utilities.sln └── Westwind.Utilities.slnx ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/FUNDING.yml ================================================ github: RickStrahl custom: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K677THUA2MJSE&source=url" custom: "https://store.west-wind.com/product/donation" ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. Test/ UnitTestProject2/ # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg *.snupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc *.saved.bak ================================================ FILE: Changelog.md ================================================ # Westwind.Utilities Changelog ## 5.3 * **HttpClientUtils.DownloadBytes/Async** Added routine to easily download bytes from an Http server. * **HttpClientUtils.DownloadFile/Async** Download content directly to file from an Http Server. * **Additional HttpClientUtils Settings Object Response Properties** Provided several helper properties to facilitate common header access. Explicitly exposed new Response properties: `ResponseContentType`, `ResponseContentLength`, `ResponseContentHeaders` and `ResponseHeaders`. ### 5.2 * **Add .NET 10 Target** Change Targets to .NET 10 and .NET 8.0 (and .NET Standard and net472). Dropped .NET 9 target (handled by .NET 8.0). * **Add HttpClientRequestSettings.AddPostKey() and AddPostFile() Methods** You 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. * **ImageUtils.IsImage()** Two versions one for filename extension check, and one for binary data image signature check. ### 5.1.10 * **FileUtils.CopyFileEnsureDirectory()** Added method that copies a file and creates the corresponding directory structure if it doesn't exist. * **StringUtils.StripAfter()** Method that strips text after a matched delimiter at the end of the string or returns the original string. * **StringUtils.ReplaceLastNthInstance()** Method that replaces the last nth instance of a sub string in an existing string. ### 5.1.6 * **FileUtils.GetRelativeFilePath()** Compares 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). * **FileUtils.ResolvePath()** Returns an absolute file path for a relative path and a base file or folder. * **FileUtils.IsRelativePath()** Checks 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. * **FileUtils.TildefyUserPath()** Replaces 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. * **Add Sync Methods to HttpClientUtils (Core only)** Add 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. * **Basic PasswordScrubber Class for Json and Sql Connections** Added 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()`. * **Option on FileUtils.CopyDirectory() to ignore errors** Added 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. ### 5.0.9 * **Add HttpClientUtils support for .NET Framework** The HttpClientUtils class now works on .NET Framework. It was previously only supported on .NET Core. * **Add FileUtils.ReadAllBytesAsync() and WriteAllBytesAsync()** Support functions for .NET Framework to support missing `System.IO.File` async methods. * **Update Microsoft.Data.SqlClient** Microsoft finally fixed the vulnerability related Azure dependencies in Microsoft.Data.SqlClient. ### 5.0.7 * **StringUtils 'Many' Operations** String extension methods that operate on 'many' values for various string operations: `EqualsMany()`, `ContainsMany()`, `StartsWithMany()`, `ReplaceMany()`. The finds * **AsyncUtils.DelayExecution()** Added 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. * **AsyncUtils.Timeout() and TimeoutWithResult()** Task 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). * **VersionUtils.FormatVersion()** A 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). * **Fix: UrlEncodingParser Null Value Handling** Fix 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. ### 5.0 **Breaking Changes** * **New Westwind.utilities.Windows Package** Added 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: * HtmlUtils.ResolveUrl() * HtmlUtils.ImageRef() * HtmlUtils.Href() * **Westwind.Utilities.Data now uses `Microsoft.Data.SqlClient` for NETFX** Switched 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. * **Removed Westwind Logging** The 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. ### 4.0.21 * **StringUtils.GetLastCharacters()** Retrieves up to the last n characters from the end of a string. * **WindowsUtils.IsUserAnAdmin()** Determines if user is an Administrator on Windows. * **StringUtils.ToBase64String()/FromBase64String()** Helper method that simplifies converting strings to and from Base64 without having to go through the intermediary byte conversion explicitly. * **Fix: HttpClientUtils HasErrors Result** Fix `HttpClientRequestSettings.HasErrors` when requests hard fail due to network issues. ### 4.0 * **Refactor Data Utilities into separate Westwind.Utilities.Data Library** Due 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. * **StringUtils.Occurs()** Counts the number of times a character or substring occurs in a given string. * **StringUtils.StringToStream() and StringUtils.StreamToString()** Converts a string to a `MemoryStream` with Encoding, or reads a string from an input stream. * **StringUtils.GetMaxCharacters()** String extension method that retrieves a string with n characters max optionally from a specified start position. * **Fix StringSerializer Null Values** Fix null values for string properties that were returning the string `"null"` instead of an actual `null`. * **Fix: AppConfiguration.Write() if Provider is not configured** Fix `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. * **Remove .NET 4.0 Target** We've removed the .NET 4.0 target, leaving `net462` and `netstandard2.0` as the two targets for this library. * **Update Newtonsoft.Json** Update to latest JSON.NET Nuget package. * **ReflectionUtils.ShallowClone()** Helper 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. * **FileUtils.GetShortPath()** Returns 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. * **TimeUtils.IsBetween** Add helper extension method to DateTime and TimeSpan for checking for date between a high and low date. * **Fix TimeUtils ShortDate Formatting** Make short date formatting locale aware. Add optional date separator parameter. * **AsyncUtils.FireAndForget() / Task.FireAndForget()** Added 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. * **JsonSerializationUtils CamelCase Support for Serialization** You 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. * **StreamExtensions.AsString()** Returns the contents of a stream as a string with optional encoding. * **MemoryStreamExtensions.AsString()** Helper extension method that lets you convert a memory stream to a string with optional Encoding. ### 3.0.55 March 12, 2021 * **NetworkUtils.IsLocalIpAddress** Checks 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. * **Fix: Replace Timeout with TimeoutMs** Replace 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. * **ValidationErrors.HasErrors Property** Added a `HasErrors` property to make the check for errors more explicit than `ValidationErrors.Count > 0`. ### 3.0.51 February 17, 2021 * **Switch Data Access to Microsoft.Data.SqlClient** Switch 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. * **Add HttpUtils.DownloadImageToFile\Async()** Added 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. * **ImageUtils.GetExtensionFromMediaType()** This 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. * **HttpUtils.DownloadBytes\Async() for Binary data** Http helper to download HTTP contents into a `byte[]`. * **ComObject Wrapper for .NET Core** Added `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. * **Fix issues with StringUtils.TextAbstract() & Line Breaks** Fix behavior of text abstract with line breaks not being turned into spaces consistently. Also check for null. * **ShellUtils.OpenUrl() - Platform agnostic Browser Opening** This method opens a URL in the system browser on Windows, Mac and Linux. * **WindowsUtils.TryGetRegistryKey() Signature Change** Change 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. Note: this is a potentially breaking change. * **FileUtils.HasInvalidPathCharacters()** Added a function that checks paths for invalid characters. Uses default invalid character list, plus you can pass in additional characters to be deemed invalid. > ### Breaking Changes for 3.0.50 > * **WindowUtilities.TryGetRegistryKey() parameter Change** The 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. > > * **Renamed `DataAccessBase.Find()` KeyLookup overload** > Renamed this method to `FindKey()` 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()` when `FindKey()` is desired. ### 3.0.40 *May 22nd, 2020* * **ReflectionUtils.InvokeEvent()** Method 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. * **XmlIgnore for XML Configuration** You 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. * **FileUtils.ShortFilename** Turn a Windows long filename into a short filename. Use to get around 256 MAX_PATH limitations for some operations. * **SqlDataAccess.DoesTableExist()** Added a method that checks to see if a table exist in the current database. * **DataUtils.RemoveBytes()** Removes a sequence of bytes from a byte array. * **Fix: TextAbstract() remove Line Breaks** `TextAbstract()` now removes line breaks when creating the the abstract and replaces them with spaces. * **Fix: FileUtils.SafeFilename trailing Spaces** Fix trailing spaces issue in SafeFilename, when last character is a replacement character. * **Fix: Missing resources** Project conversion appears to have removed resx resources for localization resulting in empty messages for some operations. * **Fix: Xml Logging Adapter with Empty File** Fix error when creating a new XML error log and appending the XML closing tag. ### 3.0.30 *August 28th, 2019* * **Add some Async Methods to DataAccessBase** Add `ExecuteNonQueryAsync`, `ExecuteScalarAsync`, `InsertEntityAsync` methods. More to come in future updates. * **StringUtils.Right() Method** Add method to retrieve the rightmost characters from a string. * **Add TextLogAdapter** Add a log adapter for plain text output. * **Update: ShellUtils.ExecuteProcess with Output Capture** Add 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` that can intercept output as it's generated and fire your own capture code. ### 3.0.28 *June 23rd, 2019* * **DataUtils.IndexOfByteArray()** Small 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. * **Fix: HtmlUtils.HtmlEncode()** Handle 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). * **Fix: ReflectionUtils.CallMethodExCom()** Fix bug when traversing object hierarchy. * **Add .NET 4.8 to WindowsUtilities.GetDotnetVersion()** Add support for .NET 4.8 and also fix an errant `Debug.Writeline()` in this method. ### 3.0.24 *February 28th, 2019* * **Fix: HtmlUtils.SanitizeHtml() for multi-line** Fix SanitizeHtml() function to work across line breaks for a tag. * **XmlUtils.XmlString()** Create an XML encode string for elements or attributes. * **FileUtils.DeleteFiles()** Added routine that deletes files in a folder based on a path spec, optionally recursively. * **DebugUtils.GetCodeWithLineNumbers()** Add method that creates lines with line numbers appended. Useful for displaying source code. * **StringUtils.Truncate()** Added method that truncates a string if it exceeds a certain number of characters. ### 3.0.20 *September 6th, 2018* * **HtmlUtils.SanitizeHtml()** RegEx based HTML sanitation that handles the most common script injection scenarios for `"; sb.AppendLine(script); return sb.ToString(); } } } ================================================ FILE: Westwind.Utilities/InternetTools/HttpClient.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * � West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Net; using System.IO; using System.Text; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.IO.Compression; using System.Threading.Tasks; using Westwind.Utilities; namespace Westwind.Utilities.InternetTools { /// /// An HTTP wrapper class that abstracts away the common needs for adding post keys /// and firing update events as data is received. This class is real easy to use /// with many common operations requiring single method calls. /// /// The class also provides automated cookie and state handling, GZip compression /// decompression, simplified proxy and authentication mechanisms to provide a /// simple single level class interface. The underlying WebRequest is also /// exposed so you will not loose any functionality from the .NET BCL class. /// #if NET6_0_OR_GREATER [Obsolete("Westwind.HttpClient is obsolete in .NET Core as it uses the old HttpWebRequest class. Please use System.Net.HttpClient or " + "`Westwind.Utilities.HttpClientUtils` or in Net4.x for sync Operations `Westwind.Utilities.HttpUtils`")] #endif public class HttpClient : IDisposable { /// /// Determines how data is POSTed when when using AddPostKey() and other methods /// of posting data to the server. Support UrlEncoded, Multi-Part, XML and Raw modes. /// public HttpPostMode PostMode { get; set; } = HttpPostMode.UrlEncoded; /// /// User name used for Authentication. /// To use the currently logged in user when accessing an NTLM resource you can use "AUTOLOGIN". /// public string Username { get; set; } /// /// Password for Authentication. /// public string Password { get; set; } /// /// Address of the Proxy Server to be used. /// Use optional DEFAULTPROXY value to specify that you want to IE's Proxy Settings /// public string ProxyAddress { get; set; } /// /// Semicolon separated Address list of the servers the proxy is not used for. /// public string ProxyBypass { get; set; } /// /// Username for a password validating Proxy. Only used if the proxy info is set. /// public string ProxyUsername { get; set; } /// /// Password for a password validating Proxy. Only used if the proxy info is set. /// public string ProxyPassword { get; set; } /// /// Timeout for the Web request in seconds. Times out on connection, read and send operations. /// Default is 30 seconds. /// [Obsolete("Please use TimeoutMs instead")] public int Timeout { get { if (_timeoutMs < 0) return -1; return _timeoutMs * 1000; } set { if(value >= 1) _timeoutMs = value * 1000; else if (value == -1) _timeoutMs = -1; { if (value > 0) _timeoutMs = 1; else if (value < 0) _timeoutMs = -1; else _timeoutMs = 0; } } } /// /// Timeout for the Web request in seconds. Times out on connection, read and send operations. /// Default is 30 seconds (30,000ms). /// public int TimeoutMs { get => _timeoutMs; set => _timeoutMs = value; } private int _timeoutMs = 30_000; /// /// Returns whether the last request was cancelled through one of the /// events. /// public bool Cancelled { get; set; } /// /// Use this option to set a custom content type. /// If possible use PostMode to specify a predefined /// content type as it will ensure that Post data is /// appropriately formatted. /// /// If setting the content type manually POST data /// public string ContentType { get { return _ContentType; } set { _ContentType = value; if (_ContentType == null) return; if (_ContentType.StartsWith("application/x-www-form-urlencoded")) PostMode = HttpPostMode.UrlEncoded; else if (_ContentType.StartsWith("multipart/form-data")) PostMode = HttpPostMode.MultiPart; else PostMode = HttpPostMode.Raw; } } private string _ContentType; // this doesn't seem necessary - . NET will automatically decode common encodings like UTF-8 ///// ///// The Encoding used to decode the response data ///// //public Encoding ResponseEncoding //{ // get { return _ResponseEncoding; } // set { _ResponseEncoding = value; } //} //private Encoding _ResponseEncoding = Encoding.Default; /// /// When true will automatically add Accept-Encoding: gzip,deflate header /// and automatically decompress gzip and deflate content /// public bool UseGZip { get; set; } /// /// Keeps track of request timings for the last executed request. Tracks started, /// firstbyte and lastbyte times as well as ms to first byte /// (actually first 'buffer' loaded) and last byte. /// /// Does not work with DownloadStream() since you control the stream's operations. public HttpTimings HttpTimings { get; set; } /// /// Error Message if the Error Flag is set or an error value is returned from a method. /// public string ErrorMessage { get { return _ErrorMessage; } set { _ErrorMessage = value; } } /// /// Error flag if an error occurred. /// public bool Error { get; set; } /// /// Determines whether errors cause exceptions to be thrown. By default errors /// are handled in the class and the Error property is set for error conditions. /// (not implemented at this time). /// public bool ThrowExceptions { get; set; } /// /// If set to true will automatically track cookies /// between multiple successive requests on this /// instance. Uses the CookieCollection property /// to persist cookie status. /// /// When set posts values in the CookieCollection, /// and on return fills the CookieCollection with /// cookies from the Response. /// public bool HandleCookies { get; set; } /// /// Holds the internal Cookie collection before or after a request. This /// collection is used only if HandleCookies is set to .t. which also causes it /// to capture cookies and repost them on the next request. /// public CookieCollection Cookies { get { if (_Cookies == null) Cookies = new CookieCollection(); return _Cookies; } set { _Cookies = value; } } /// /// WebResponse object that is accessible after the request is complete and /// allows you to retrieve additional information about the completed request. /// /// The Response Stream is already closed after the GetUrl methods complete /// (except GetUrlResponse()) but you can access the Response object members /// and collections to retrieve more detailed information about the current /// request that completed. /// public HttpWebResponse WebResponse { get; set; } /// /// WebRequest object that can be manipulated and set up for the request if you /// called . /// /// Note: This object must be recreated and reset for each request, since a /// request's life time is tied to a single request. This object is not used if /// you specify a URL on any of the GetUrl methods since this causes a default /// WebRequest to be created. /// public HttpWebRequest WebRequest { get; set; } /// /// The buffersize used for the Send and Receive operations /// public int BufferSize { get; set; } = 100; /// /// Lets you specify the User Agent browser string that is sent to the server. /// This allows you to simulate a specific browser if necessary. /// public string UserAgent { get; set; } = "West Wind HTTP .NET Client"; public string HttpVerb { get; set; } = "GET"; // member properties //string cPostBuffer = string.Empty; private MemoryStream _PostStream; private BinaryWriter _PostData; private string _ErrorMessage = string.Empty; private CookieCollection _Cookies; private string _MultiPartBoundary = "-----------------------------" + DateTime.Now.Ticks.ToString("x"); /// /// The HttpClient Default Constructor /// public HttpClient() { HttpTimings = new HttpTimings(); } /// /// Creates a new WebRequest instance that can be set prior to calling the /// various Get methods. You can then manipulate the WebRequest property, to /// custom configure the request. /// /// Instead of passing a URL you can then pass null. /// /// Note - You need a new Web Request for each and every request so you need to /// set this object for every call if you manually customize it. /// /// /// The Url to access with this WebRequest /// /// Boolean public bool CreateWebRequestObject(string Url) { try { #pragma warning disable SYSLIB0014 WebRequest = (HttpWebRequest) System.Net.WebRequest.Create(Url); #pragma warning restore SYSLIB0014 } catch (Exception ex) { ErrorMessage = ex.Message; return false; } return true; } #region POST data /// /// Resets the Post buffer by clearing out all existing content /// public void ResetPostData() { _PostStream = new MemoryStream(); _PostData = new BinaryWriter(_PostStream); } public void SetPostStream(Stream postStream) { MemoryStream ms = new MemoryStream(1024); FileUtils.CopyStream(postStream, ms, 1024); ms.Flush(); ms.Position = 0; _PostStream = ms; _PostData = new BinaryWriter(ms); } /// /// Adds POST form variables to the request buffer. /// PostMode determines how parms are handled. /// /// Key value or raw buffer depending on post type /// Value to store. Used only in key/value pair modes public void AddPostKey(string key, byte[] value) { if (value == null) return; if (key == "RESET") { ResetPostData(); return; } if (_PostData == null) { _PostStream = new MemoryStream(); _PostData = new BinaryWriter(_PostStream); } if (string.IsNullOrEmpty(key)) _PostData.Write(value); else if(PostMode == HttpPostMode.UrlEncoded) _PostData.Write( Encoding.Default.GetBytes(key + "=" + StringUtils.UrlEncode(Encoding.Default.GetString(value)) + "&") ); else if (PostMode == HttpPostMode.MultiPart) { Encoding iso = Encoding.GetEncoding("ISO-8859-1"); _PostData.Write(iso.GetBytes( "--" + _MultiPartBoundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\"\r\n\r\n")); _PostData.Write(value); _PostData.Write(iso.GetBytes("\r\n")); } else // Raw or Xml, JSON modes _PostData.Write( value ); } /// /// Adds POST form variables to the request buffer. /// PostMode determines how parms are handled. /// /// Key value or raw buffer depending on post type /// Value to store. Used only in key/value pair modes public void AddPostKey(string key, string value) { if (value == null) return; AddPostKey(key,Encoding.Default.GetBytes(value)); } /// /// Adds a fully self contained POST buffer to the request. /// Works for XML or previously encoded content. /// /// String based full POST buffer public void AddPostKey(string fullPostBuffer) { AddPostKey(null,fullPostBuffer ); } /// /// Adds a fully self contained POST buffer to the request. /// Works for XML or previously encoded content. /// /// Byte array of a full POST buffer public void AddPostKey(byte[] fullPostBuffer) { AddPostKey(null,fullPostBuffer); } /// /// Allows posting a file to the Web Server. Make sure that you /// set PostMode /// /// /// /// public bool AddPostFile(string key,string fileName, string contentType = "application/octet-stream") { byte[] lcFile; if (PostMode != HttpPostMode.MultiPart) { _ErrorMessage = "File upload allowed only with Multi-part forms"; Error = true; return false; } try { FileStream loFile = new FileStream(fileName,System.IO.FileMode.Open,System.IO.FileAccess.Read); lcFile = new byte[loFile.Length]; _ = loFile.Read(lcFile,0,(int) loFile.Length); loFile.Close(); } catch(Exception e) { _ErrorMessage = e.Message; Error = true; return false; } if (_PostData == null) { _PostStream = new MemoryStream(); _PostData = new BinaryWriter(_PostStream); } _PostData.Write( Encoding.Default.GetBytes( "--" + _MultiPartBoundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + new FileInfo(fileName).Name + "\"\r\n" + "Content-Type: " + contentType + "\r\n\r\n" ) ); _PostData.Write( lcFile ); _PostData.Write( Encoding.Default.GetBytes("\r\n")) ; return true; } /// /// Returns the contents of the post buffer. Useful for debugging /// /// public string GetPostBuffer() { var bytes = _PostStream?.ToArray(); if (bytes == null) return null; return Encoding.Default.GetString(bytes); } #endregion #region Run Requests /// /// Return a the result from an HTTP Url into a StreamReader. /// Client code should call Close() on the returned object when done reading. /// /// Url to retrieve. /// public StreamReader DownloadStream(string url) { try { Encoding enc; HttpWebResponse Response = DownloadResponse(url); if (Response == null) return null; try { if (!string.IsNullOrEmpty(Response.CharacterSet)) enc = Encoding.GetEncoding(Response.CharacterSet); else enc = Encoding.Default; } catch { // Invalid encoding passed enc = Encoding.Default; } Stream responseStream = Response.GetResponseStream(); //if (Response.ContentEncoding.ToLower().Contains("gzip")) // responseStream = new GZipStream(Response.GetResponseStream(), CompressionMode.Decompress); //else if (Response.ContentEncoding.ToLower().Contains("deflate")) // responseStream = new DeflateStream(Response.GetResponseStream(), CompressionMode.Decompress); // drag to a stream StreamReader strResponse = new StreamReader(responseStream, enc); return strResponse; } catch (Exception ex) { Error = true; ErrorMessage = "Unable to read response: " + ex.Message; return null; } } /// /// Return an HttpWebResponse object for a request. You can use the Response to /// read the result as needed. This is a low level method. Most of the other 'Get' /// methods call this method and process the results further. /// /// Important: The Response object's Close() method must be called when you are done with the object. /// Url to retrieve. /// An HttpWebResponse Object [Obsolete("Use DownloadResponse instead.")] public HttpWebResponse GetUrlResponse(string url) { return DownloadResponse(url); } public async Task DownloadResponseAsync(string url) { Cancelled = false; //try //{ Error = false; _ErrorMessage = string.Empty; Cancelled = false; if (WebRequest == null) { #pragma warning disable SYSLIB0014 WebRequest = (HttpWebRequest)System.Net.WebRequest.Create(url); #pragma warning restore SYSLIB0014 //WebRequest.Headers.Add("Cache","no-cache"); } WebRequest.UserAgent = UserAgent; WebRequest.Timeout = TimeoutMs; WebRequest.Method = HttpVerb; #if NETFULL WebRequest.ReadWriteTimeout = TimeoutMs; #endif // Handle Security for the request if (!string.IsNullOrEmpty(Username)) { if (Username == "AUTOLOGIN" || Username == "AutoLogin") WebRequest.Credentials = CredentialCache.DefaultCredentials; else WebRequest.Credentials = new NetworkCredential(Username, Password); } // Handle Proxy Server configuration if (!string.IsNullOrEmpty(ProxyAddress)) { if (ProxyAddress == "DEFAULTPROXY") { WebRequest.Proxy = HttpWebRequest.DefaultWebProxy; } else { WebProxy Proxy = new WebProxy(ProxyAddress, true); if (ProxyBypass.Length > 0) { Proxy.BypassList = ProxyBypass.Split(';'); } if (ProxyUsername.Length > 0) Proxy.Credentials = new NetworkCredential(ProxyUsername, ProxyPassword); WebRequest.Proxy = Proxy; } } if (UseGZip) { // TODO: Check if already set WebRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate"); WebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } // Handle cookies - automatically re-assign if (HandleCookies || (_Cookies != null && _Cookies.Count > 0)) { WebRequest.CookieContainer = new CookieContainer(); if (_Cookies != null && _Cookies.Count > 0) { WebRequest.CookieContainer.Add(_Cookies); } } HttpTimings.StartRequest(); // Deal with the POST buffer if any if (_PostData != null) { if (WebRequest.Method == "GET") WebRequest.Method = "POST"; switch (PostMode) { case HttpPostMode.UrlEncoded: WebRequest.ContentType = "application/x-www-form-urlencoded"; break; case HttpPostMode.MultiPart: WebRequest.ContentType = "multipart/form-data; boundary=" + _MultiPartBoundary; _PostData.Write(Encoding.GetEncoding(1252).GetBytes("--" + _MultiPartBoundary + "--\r\n")); break; case HttpPostMode.Xml: WebRequest.ContentType = "text/xml"; break; case HttpPostMode.Json: WebRequest.ContentType = "application/json"; break; case HttpPostMode.Raw: //WebRequest.ContentType = "application/octet-stream"; break; default: goto case HttpPostMode.UrlEncoded; } if (!string.IsNullOrEmpty(ContentType)) WebRequest.ContentType = ContentType; // TODO: Make POSTing async using (Stream requestStream = WebRequest.GetRequestStream()) { if (SendData == null) _PostStream.WriteTo(requestStream); // Simplest version - no events else StreamPostBuffer(requestStream); // Send in chunks and fire events //*** Close the memory stream _PostStream.Close(); _PostStream = null; //*** Close the Binary Writer if (_PostData != null) { _PostData.Dispose(); _PostData = null; } } // clear out the Post buffer ResetPostData(); // If user cancelled the 'upload' exit if (Cancelled) { ErrorMessage = "HTTP Request was cancelled."; Error = true; return null; } } // Retrieve the response headers HttpWebResponse Response; try { Response = await WebRequest.GetResponseAsync() as HttpWebResponse; } catch (WebException ex) { // Check for 500 error return - if so we still want to return a response // Client can check oHttp.WebResponse.StatusCode if (ex.Status == WebExceptionStatus.ProtocolError) { Response = (HttpWebResponse)ex.Response; } else { if(ThrowExceptions) throw; Error = true; ErrorMessage = ex.Message + "." + url; return null; } } WebResponse = Response; // Close out the request - it cannot be reused WebRequest = null; // ** Save cookies the server sends if (HandleCookies) { if (Response.Cookies.Count > 0) { if (_Cookies == null) _Cookies = Response.Cookies; else { // ** If we already have cookies update the list foreach (Cookie oRespCookie in Response.Cookies) { bool bMatch = false; foreach (Cookie oReqCookie in _Cookies) { if (oReqCookie.Name == oRespCookie.Name) { oReqCookie.Value = oRespCookie.Value; bMatch = true; break; // } } // for each ReqCookies if (!bMatch) _Cookies.Add(oRespCookie); } } } } return Response; } /// /// Return an HttpWebResponse object for a request. You can use the Response to /// read the result as needed. This is a low level method. Most of the other 'Get' /// methods call this method and process the results further. /// /// Important: The Response object's Close() method must be called when you are done with the object. /// Url to retrieve. /// An HttpWebResponse Object public HttpWebResponse DownloadResponse(string url) { Cancelled = false; //try //{ Error = false; _ErrorMessage = string.Empty; Cancelled = false; if (WebRequest == null) { #pragma warning disable SYSLIB0014 WebRequest = (HttpWebRequest) System.Net.WebRequest.Create(url); #pragma warning restore SYSLIB0014 //WebRequest.Headers.Add("Cache","no-cache"); } WebRequest.UserAgent = UserAgent; WebRequest.Timeout = TimeoutMs; WebRequest.Method = HttpVerb; #if NETFULL WebRequest.ReadWriteTimeout = TimeoutMs; #endif // Handle Security for the request if (!string.IsNullOrEmpty(Username)) { if (Username == "AUTOLOGIN" || Username == "AutoLogin") WebRequest.Credentials = CredentialCache.DefaultCredentials; else WebRequest.Credentials = new NetworkCredential(Username,Password); } // Handle Proxy Server configuration if (!string.IsNullOrEmpty(ProxyAddress)) { if (ProxyAddress == "DEFAULTPROXY") { WebRequest.Proxy = HttpWebRequest.DefaultWebProxy; } else { WebProxy Proxy = new WebProxy(ProxyAddress,true); if (ProxyBypass.Length > 0) { Proxy.BypassList = ProxyBypass.Split(';'); } if (ProxyUsername.Length > 0) Proxy.Credentials = new NetworkCredential(ProxyUsername,ProxyPassword); WebRequest.Proxy = Proxy; } } if (UseGZip) { // TODO: Check if already set WebRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate"); WebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } // Handle cookies - automatically re-assign if (HandleCookies || (_Cookies != null && _Cookies.Count > 0) ) { WebRequest.CookieContainer = new CookieContainer(); if (_Cookies != null && _Cookies.Count > 0) { WebRequest.CookieContainer.Add(_Cookies); } } HttpTimings.StartRequest(); // Deal with the POST buffer if any if (_PostData != null) { if (WebRequest.Method == "GET") WebRequest.Method = "POST"; switch (PostMode) { case HttpPostMode.UrlEncoded: WebRequest.ContentType = "application/x-www-form-urlencoded"; break; case HttpPostMode.MultiPart: WebRequest.ContentType = "multipart/form-data; boundary=" + _MultiPartBoundary; _PostData.Write(Encoding.GetEncoding(1252).GetBytes("--" + _MultiPartBoundary + "--\r\n")); break; case HttpPostMode.Xml: WebRequest.ContentType = "text/xml"; break; case HttpPostMode.Json: WebRequest.ContentType = "application/json"; break; case HttpPostMode.Raw: //WebRequest.ContentType = "application/octet-stream"; break; default: goto case HttpPostMode.UrlEncoded; } if (!string.IsNullOrEmpty(ContentType)) WebRequest.ContentType = ContentType; using (Stream requestStream = WebRequest.GetRequestStream()) { if (SendData == null) _PostStream.WriteTo(requestStream); // Simplest version - no events else StreamPostBuffer(requestStream); // Send in chunks and fire events //*** Close the memory stream _PostStream.Close(); _PostStream = null; //*** Close the Binary Writer if (_PostData != null) { _PostData.Dispose(); _PostData = null; } } // clear out the Post buffer ResetPostData(); // If user cancelled the 'upload' exit if (Cancelled) { ErrorMessage = "HTTP Request was cancelled."; Error = true; return null; } } if ((WebRequest.Method == "POST" || WebRequest.Method == "PUT" || WebRequest.Method == "PATCH") && _PostData == null) { WebRequest.ContentLength = 0; } // Retrieve the response headers HttpWebResponse Response; try { Response = (HttpWebResponse) WebRequest.GetResponse(); } catch(WebException ex) { // Check for 500 error return - if so we still want to return a response // Client can check oHttp.WebResponse.StatusCode if (ex.Status == WebExceptionStatus.ProtocolError) { Response = (HttpWebResponse) ex.Response; } else { if (ThrowExceptions) throw; Error = true; ErrorMessage = ex.GetBaseException().Message + " " + url; return null; } } WebResponse = Response; // Close out the request - it cannot be reused WebRequest = null; // ** Save cookies the server sends if (HandleCookies) { if (Response.Cookies.Count > 0) { if (_Cookies == null) _Cookies = Response.Cookies; else { // ** If we already have cookies update the list foreach (Cookie oRespCookie in Response.Cookies) { bool bMatch = false; foreach (Cookie oReqCookie in _Cookies) { if (oReqCookie.Name == oRespCookie.Name) { oReqCookie.Value = oRespCookie.Value; bMatch = true; break; // } } // for each ReqCookies if (!bMatch) _Cookies.Add(oRespCookie); } } } } return Response; } /// /// Sends the Postbuffer to the server /// /// protected void StreamPostBuffer(Stream PostData) { if (_PostStream.Length < BufferSize) { _PostStream.WriteTo(PostData); // Handle Send Data Even // Here just let it know we're done if (SendData != null) { ReceiveDataEventArgs Args = new ReceiveDataEventArgs(); Args.CurrentByteCount = _PostStream.Length; Args.Done = true; SendData(this, Args); } } else { // Send data up in 8k blocks byte[] Buffer = _PostStream.GetBuffer(); int lnSent = 0; int lnToSend = (int)_PostStream.Length; int lnCurrent = 1; while (true) { if (lnToSend < 1 || lnCurrent < 1) { if (SendData != null) { ReceiveDataEventArgs Args = new ReceiveDataEventArgs(); Args.CurrentByteCount = lnSent; Args.TotalBytes = Buffer.Length; Args.Done = true; SendData(this, Args); } break; } lnCurrent = lnToSend; if (lnCurrent > BufferSize) { lnCurrent = BufferSize; lnToSend = lnToSend - lnCurrent; } else { lnToSend = lnToSend - lnCurrent; } PostData.Write(Buffer, lnSent, lnCurrent); lnSent = lnSent + lnCurrent; if (SendData != null) { ReceiveDataEventArgs Args = new ReceiveDataEventArgs(); Args.CurrentByteCount = lnSent; Args.TotalBytes = Buffer.Length; if (Buffer.Length == lnSent) { Args.Done = true; SendData(this, Args); break; } SendData(this, Args); if (Args.Cancel) { Cancelled = true; break; } } } } } /// /// Returns the content of a URL as a string /// /// /// The intermediate download buffer used /// A .NET Encoding scheme or null to attempt sniffing from Charset. /// [Obsolete("Use the DownloadString() method instead.")] public string GetUrl(string url, long bufferSize = 8192, Encoding encoding = null) { return DownloadString(url, bufferSize, encoding); } /// /// Returns the content of a URL as a string using a specified Encoding /// /// /// Internal download buffer size used to hold data chunks. /// A .NET Encoding scheme or null to attempt sniffing from Charset. /// public string DownloadString(string url, long bufferSize = 8192, Encoding encoding = null) { byte[] bytes = DownloadBytes(url, bufferSize); if (bytes == null) return null; if (encoding == null) { encoding = Encoding.Default; try { if (!string.IsNullOrEmpty(WebResponse.CharacterSet)) { string charset = WebResponse.CharacterSet.ToLower(); // special case UTF-8 since it's most common if (charset.Contains("utf-8")) encoding = Encoding.UTF8; else if (charset.Contains("utf-16")) encoding = Encoding.Unicode; else if (charset.Contains("utf-32")) encoding = Encoding.UTF32; else encoding = Encoding.GetEncoding(WebResponse.CharacterSet); } } catch { } // ignore encoding assignment failures } return encoding.GetString(bytes); } /// /// Returns the content of a URL as a string using a specified Encoding /// /// /// Internal download buffer size used to hold data chunks. /// A .NET Encoding scheme or null to attempt sniffing from Charset. /// public async Task DownloadStringAsync(string url, long bufferSize = 8192, Encoding encoding = null) { byte[] bytes = await DownloadBytesAsync(url, bufferSize); if (bytes == null) return null; if (encoding == null) { encoding = Encoding.Default; try { if (!string.IsNullOrEmpty(WebResponse.CharacterSet)) { string charset = WebResponse.CharacterSet.ToLower(); // special case UTF-8 since it's most common if (charset.Contains("utf-8")) encoding = Encoding.UTF8; else if (charset.Contains("utf-16")) encoding = Encoding.Unicode; else if (charset.Contains("utf-32")) encoding = Encoding.UTF32; else encoding = Encoding.GetEncoding(WebResponse.CharacterSet); } } catch { } // ignore encoding assignment failures } return encoding.GetString(bytes); } /// /// Returns a partial response from the URL by specifying only /// given number of bytes to retrieve. This can reduce network /// traffic and keep string formatting down if you are only /// interested a small port at the top of the page. Also /// returns full headers. /// /// /// /// [Obsolete("Use DownloadStringPartial() instead.")] public string GetUrlPartial(string url, int size) { return GetUrlPartial(url, size); } /// /// Returns a partial response from the URL by specifying only /// given number of bytes to retrieve. This can reduce network /// traffic and keep string formatting down if you are only /// interested a small port at the top of the page. Also /// returns full headers. /// /// /// /// public string DownloadStringPartial(string url, int size) { char[] buffer; using (StreamReader sr = DownloadStream(url)) { if (sr == null) return null; buffer = new char[size]; sr.Read(buffer, 0, size); } return new string(buffer); } /// /// Retrieves URL into an Byte Array. /// /// Fires the ReceiveData Event /// Url to read /// Size of the buffer for each read. 0 = 8192 /// public byte[] DownloadBytes(string url, long bufferSize = 8192) { HttpWebResponse response = DownloadResponse(url); if (response == null) return null; long responseSize = bufferSize; if (response.ContentLength > 0) responseSize = WebResponse.ContentLength; else // No content size provided responseSize = -1; Stream responseStream = responseStream = response.GetResponseStream(); //if (response.ContentEncoding.ToLower().Contains("gzip")) //{ // responseStream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress); // responseSize = -1; // we don't have a size //} //else if (response.ContentEncoding.ToLower().Contains("deflate")) //{ // responseStream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress); // responseSize = -1; // we don't have a size //} //else if (responseStream == null) { Error = true; ErrorMessage = "Failed to retrieve a response from " + url; return null; } using (responseStream) { if (bufferSize < 1) bufferSize = 4096; var ms = new MemoryStream((int) bufferSize); byte[] buffer = new byte[bufferSize]; var args = new ReceiveDataEventArgs(); args.TotalBytes = responseSize; long bytesRead = 1; int count = 0; long totalBytes = 0; while (bytesRead > 0) { if (responseSize != -1 && totalBytes + bufferSize > responseSize) bufferSize = responseSize - totalBytes; bytesRead = responseStream.Read(buffer, 0, (int) bufferSize); if (bytesRead > 0) { if (totalBytes == 0) HttpTimings.FirstByteTime = DateTime.UtcNow; // write to stream ms.Write(buffer, 0, (int) bytesRead); count++; totalBytes += bytesRead; // Raise an event if hooked up if (ReceiveData != null) { // Update the event handler args.CurrentByteCount = totalBytes; args.NumberOfReads = count; args.CurrentChunk = null; // don't send anything here ReceiveData(this, args); // Check for cancelled flag if (args.Cancel) { Cancelled = true; break; } } } } // while HttpTimings.LastByteTime = DateTime.UtcNow; // Send Done notification if (ReceiveData != null && !args.Cancel) { // Update the event handler args.Done = true; ReceiveData(this, args); } //ms.Flush(); ms.Position = 0; return ms.ToArray(); } } /// /// Retrieves URL into an Byte Array. /// /// Fires the ReceiveData Event /// Url to read /// Size of the buffer for each read. 0 = 8192 /// public async Task DownloadBytesAsync(string url, long bufferSize = 8192) { HttpWebResponse response = await DownloadResponseAsync(url); if (response == null) return null; long responseSize = bufferSize; if (response.ContentLength > 0) responseSize = WebResponse.ContentLength; else // No content size provided responseSize = -1; Stream responseStream = response.GetResponseStream(); if (responseStream == null) { Error = true; ErrorMessage = "Failed to retrieve a response from " + url; return null; } if (response.ContentEncoding != null) { if(response.ContentEncoding.ToLower().Contains("gzip")) { responseStream = new GZipStream(responseStream, CompressionMode.Decompress); responseSize = -1; // we don't have a size } else if (response.ContentEncoding.ToLower().Contains("deflate")) { responseStream = new DeflateStream(responseStream, CompressionMode.Decompress); responseSize = -1; // we don't have a size } } using (responseStream) { if (bufferSize < 1) bufferSize = 4096; var ms = new MemoryStream((int)bufferSize); byte[] buffer = new byte[bufferSize]; var args = new ReceiveDataEventArgs(); args.TotalBytes = responseSize; long bytesRead = 1; int count = 0; long totalBytes = 0; while (bytesRead > 0) { if (responseSize != -1 && totalBytes + bufferSize > responseSize) bufferSize = responseSize - totalBytes; bytesRead = await responseStream.ReadAsync(buffer, 0, (int)bufferSize); if (bytesRead > 0) { if (totalBytes == 0) HttpTimings.FirstByteTime = DateTime.UtcNow; // write to stream ms.Write(buffer, 0, (int)bytesRead); count++; totalBytes += bytesRead; // Raise an event if hooked up if (ReceiveData != null) { // Update the event handler args.CurrentByteCount = totalBytes; args.NumberOfReads = count; args.CurrentChunk = null; // don't send anything here ReceiveData(this, args); // Check for cancelled flag if (args.Cancel) { Cancelled = true; break; } } } } // while HttpTimings.LastByteTime = DateTime.UtcNow; // Send Done notification if (ReceiveData != null && !args.Cancel) { // Update the event handler args.Done = true; ReceiveData(this, args); } ms.Position = 0; var bytes = ms.ToArray(); ms.Dispose(); return bytes; } } /// /// Writes the output from the URL request to a file firing events. /// /// Url to fire /// Buffersize - how often to fire events /// File to write response to /// true or false [Obsolete("Use DownloadFile() instead.")] public bool GetUrlFile(string Url, long BufferSize, string OutputFile) { return DownloadFile(Url, BufferSize, OutputFile); } /// /// Writes the output from the URL request to a file firing events. /// /// Url to fire /// Buffersize - how often to fire events /// File to write response to /// true or false public bool DownloadFile(string url,long bufferSize,string outputFile) { byte[] result = DownloadBytes(url,bufferSize); if (result == null) return false; File.Delete(outputFile); File.WriteAllBytes(outputFile, result); return File.Exists(outputFile); } #endregion #region Certificates /// /// Sets the certificate policy. /// /// Note this is a global setting and affects the entire application. /// It's recommended you set this for the application and not on /// a per request basis. /// /// public static bool IgnoreCertificateErrors { set { if (value) #pragma warning disable SYSLIB0014 ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(CheckCertificateCallback); #pragma warning restore SYSLIB0014 else #pragma warning disable SYSLIB0014 ServicePointManager.ServerCertificateValidationCallback -= new RemoteCertificateValidationCallback(CheckCertificateCallback); #pragma warning restore SYSLIB0014 } } /// /// Handles the Certificate check /// /// /// /// /// /// private static bool CheckCertificateCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors) { return true; } #endregion #region Events and Event Delegates and Arguments /// /// Fires progress events when receiving data from the server /// public event ReceiveDataDelegate ReceiveData; public delegate void ReceiveDataDelegate(object sender, ReceiveDataEventArgs e); /// /// Fires progress events when using GetUrlEvents() to retrieve a URL. /// public event ReceiveDataDelegate SendData; /// /// Event arguments passed to the ReceiveData event handler on each block of data sent /// public class ReceiveDataEventArgs { /// /// Size of the cumulative bytes read in this request /// public long CurrentByteCount=0; /// /// The number of total bytes of this request /// public long TotalBytes = 0; /// /// The number of reads that have occurred - how often has this event been called. /// public int NumberOfReads = 0; /// /// The current chunk of data being read /// public char[] CurrentChunk; /// /// Flag set if the request is currently done. /// public bool Done = false; /// /// Flag to specify that you want the current request to cancel. This is a write-only flag /// public bool Cancel = false; } #endregion /// /// Releases response and request data /// /// 2 public void Dispose() { if (WebResponse != null) { WebResponse.Dispose(); // introduced in 4.5 WebResponse = null; } if (WebRequest != null) WebRequest = null; } } /// /// Enumeration of the various HTTP POST modes supported by HttpClient /// public enum HttpPostMode { UrlEncoded, MultiPart, Xml, Json, Raw }; #if NETFULL /// /// Internal object used to allow setting WebRequest.CertificatePolicy to /// not fail on Cert errors /// internal class AcceptAllCertificatePolicy : ICertificatePolicy { public AcceptAllCertificatePolicy() { } public bool CheckValidationResult(ServicePoint sPoint, X509Certificate cert, WebRequest wRequest, int certProb) { // Always accept return true; } } #endif public class HttpTimings { public DateTime StartedTime { get; set; } public DateTime FirstByteTime { get; set; } public DateTime LastByteTime { get; set; } public void StartRequest() { StartedTime = DateTime.UtcNow; FirstByteTime = DateTime.UtcNow; LastByteTime = DateTime.UtcNow; } public int TimeToFirstByteMs { get { return (int) FirstByteTime.Subtract(StartedTime).TotalMilliseconds; } } public int TimeToLastByteMs { get { return (int)LastByteTime.Subtract(StartedTime).TotalMilliseconds; } } public bool IsEmpty() { return StartedTime < new DateTime(2010, 1, 1); } } } ================================================ FILE: Westwind.Utilities/InternetTools/SmtpClientNative.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Text; using System.Threading; using System.IO; using System.Net.Mail; using System.Net; using System.Security; using System.Collections.Generic; namespace Westwind.Utilities.InternetTools { /// /// SMTP Wrapper around System.Net.Email.SmtpClient. Provided /// here mainly to provide compatibility with existing wwSmtp code /// and to provide a slightly more user friendly front end interface /// on a single object. /// public class SmtpClientNative : IDisposable { /// /// Mail Server to send message through. Should be a domain name /// (mail.yourserver.net) or IP Address (211.123.123.123). /// /// You can also provide a port number as part of the string which will /// override the ServerPort (yourserver.net:211) /// Class wwSmtp /// public string MailServer = string.Empty; /// /// Port on the mail server to send through. Defaults to port 25. /// public int ServerPort = 25; /// /// Use Tls Security /// public bool UseSsl = false; /// /// Email address or addresses of the Recipient. Comma delimit multiple addresses. To have formatted names use /// "Rick Strahl" <rstrahl@west-wind.com> /// public string Recipient = string.Empty; /// /// Carbon Copy Recipients /// public string CC = string.Empty; /// /// Blind Copy Recipients /// public string BCC = string.Empty; /// /// Email address of the sender /// public string SenderEmail = string.Empty; /// /// Display name of the sender (optional) /// public string SenderName = String.Empty; /// /// The ReplyTo address /// public string ReplyTo = String.Empty; /// /// Message Subject. /// public string Subject = String.Empty; /// /// The body of the message. /// public string Message = String.Empty; /// /// Username to connect to the mail server. /// public string Username = String.Empty; /// /// Password to connect to the mail server. /// public string Password = String.Empty; /// /// Any attachments you'd like to send /// public string Attachments = String.Empty; /// /// List of attachment objects /// public List AttachmentList = new List(); /// /// The content type of the message. text/plain default or you can set to any other type like text/html /// public string ContentType = "text/plain"; /// /// Character Encoding for the message. /// public string CharacterEncoding = "8bit"; /// /// The character Encoding used to write the stream out to disk /// Defaults to the default Locale used on the server. /// public System.Text.Encoding Encoding = Encoding.Default; /// /// /// public string AlternateText = string.Empty; /// /// The content type for the alternate /// public string AlternateTextContentType = "text/plain"; /// /// The user agent for the x-mailer /// public string UserAgent = ""; /// /// Determines the priority of the message /// public string Priority = "Normal"; /// /// Determines whether a return receipt is sent /// public bool ReturnReceipt = false; /// /// /// /// protected internal List AlternateViews = new List(); /// /// An optional file name that appends logging information for the TCP/IP messaging /// to the specified file. /// public string LogFile = string.Empty; /// /// Determines whether wwSMTP passes back errors as exceptions or /// whether it sets error properties. Right now only error properties /// work reliably. /// public bool HandleExceptions = true; /// /// An Error Message if the result is negative or Error is set to true; /// public string ErrorMessage = string.Empty; /// /// Error Flag set when an error occurs. /// public bool Error = false; /// /// Connection timeouts for the mail server in seconds. If this timeout is exceeded waiting for a connection /// or for receiving or sending data the request is aborted and fails. /// public int Timeout = 30; /// /// SMTP headers for this email request /// public Dictionary Headers = new Dictionary(); /// /// Event fired when sending of a message or multiple messages /// is complete and the connection is to be closed. This event /// occurs only once per connection as opposed to the MessageSendComplete /// event which fires after each message is sent regardless of the /// number of SendMessage operations. /// public event delSmtpNativeEvent SendComplete; /// /// Event fired when an error occurs during processing and before /// the connection is closed down. /// public event delSmtpNativeEvent SendError; /// /// Internal instance of SmtpClient that holds the 'connection' /// effectively. /// private SmtpClient smtp = null; /// /// Adds an Smtp header to this email request. Headers are /// always cleared after a message has been sent or failed. /// /// /// public void AddHeader(string headerName, string value) { if (headerName.ToLower() == "clear" || headerName.ToLower() == "reset") this.Headers.Clear(); else { if (!Headers.ContainsKey(headerName)) this.Headers.Add(headerName, value); else this.Headers[headerName] = value; } } /// /// Adds headers from a CR/LF separate string that has key:value header pairs /// defined. /// /// public void AddHeadersFromString(string headers) { string[] lines = headers.Split(new char[2] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { string[] tokens = line.Split(':'); if (tokens.Length != 2) continue; this.AddHeader(tokens[0].Trim(), tokens[1].Trim()); } } /// /// Lets you load the actual SMTP client instance /// prior to use so you can manipulate the actual /// Smtp instance. /// /// public SmtpClient LoadSmtpClient() { int serverPort = this.ServerPort; string server = this.MailServer; // if there's a port we need to split the address string[] parts = server.Split(':'); if (parts.Length > 1) { server = parts[0]; serverPort = int.Parse(parts[1]); } if (server == null || server == string.Empty) { this.SetError("No Mail Server specified."); this.Headers.Clear(); return null; } smtp = null; try { smtp = new SmtpClient(server, serverPort); if (this.UseSsl) smtp.EnableSsl = true; } catch (SecurityException) { 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."); this.Headers.Clear(); return null; } // This is a Total Send Timeout not a Connection timeout! smtp.Timeout = this.Timeout * 1000; if (!string.IsNullOrEmpty(this.Username)) smtp.Credentials = new NetworkCredential(this.Username, this.Password); return smtp; } /// /// Starts a new SMTP session. Note this doesn't actually open a connection /// but just configures and sets up the SMTP session. The actual connection /// is opened only when a message is actually sent /// /// public bool Connect() { if (smtp == null) smtp = LoadSmtpClient(); if (smtp == null) return false; return true; } /// /// Cleans up and closes the connection /// /// public bool Close() { this.smtp = null; // clear all existing headers this.Headers.Clear(); return true; } /// /// Fully self contained mail sending method. Sends an email message by connecting /// and disconnecting from the email server. /// /// true or false public bool SendMail() { if (!this.Connect()) return false; try { // Create and configure the message using (MailMessage msg = this.GetMessage()) { smtp.Send(msg); if (this.SendComplete != null) this.SendComplete(this); } } catch (Exception ex) { string msg = ex.Message; if (ex.InnerException != null) msg = ex.InnerException.Message; this.SetError(msg); if (this.SendError != null) this.SendError(this); return false; } finally { // close connection and clear out headers this.Close(); } return true; } /// /// Run mail sending operation on a separate thread and asynchronously /// Operation does not return any information about completion. /// /// public void SendMailAsync() { //ThreadStart delSendMail = new ThreadStart(this.SendMailRun); //delSendMail.BeginInvoke(null, null); Thread mailThread = new Thread(this.SendMailRun); mailThread.Start(); } protected void SendMailRun() { // Create an extra reference to insure GC doesn't collect // the reference from the caller SmtpClientNative Email = this; Email.SendMail(); } /// /// Sends an individual message. Allows sending several messages /// on the same SMTP session without having to reconnect each time. /// /// This version assigns default properties assigned from the main /// mail object and allows overriding only of recipients /// /// Call after Connect() has been called and call Close() to /// close the connection afterwards /// /// public bool SendMessage(string recipient, string ccList, string bccList) { try { // Create and configure the message using (MailMessage msg = this.GetMessage()) { this.AssignMailAddresses(msg.To, recipient); this.AssignMailAddresses(msg.CC, ccList); this.AssignMailAddresses(msg.Bcc, bccList); smtp.Send(msg); } if (this.SendComplete != null) this.SendComplete(this); } catch (Exception ex) { this.SetError(ex.Message); if (this.SendError != null) this.SendError(this); return false; } return true; } /// /// Configures the message interface /// /// protected virtual MailMessage GetMessage() { MailMessage msg = new MailMessage(); msg.Body = this.Message; msg.Subject = this.Subject; msg.From = new MailAddress(this.SenderEmail, this.SenderName); if (!string.IsNullOrEmpty(this.ReplyTo)) msg.ReplyToList.Add(new MailAddress(this.ReplyTo)); // Send all the different recipients this.AssignMailAddresses(msg.To, this.Recipient); this.AssignMailAddresses(msg.CC, this.CC); this.AssignMailAddresses(msg.Bcc, this.BCC); // add string attachments from comma delimited list if (!string.IsNullOrEmpty(this.Attachments)) { string[] files = this.Attachments.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string file in files) { msg.Attachments.Add(new Attachment(file)); } } // add actual attachment objects foreach(var att in this.AttachmentList) { msg.Attachments.Add(att); } if (this.ContentType.StartsWith("text/html")) msg.IsBodyHtml = true; else msg.IsBodyHtml = false; msg.BodyEncoding = this.Encoding; msg.Priority = (MailPriority)Enum.Parse(typeof(MailPriority), this.Priority); if (!string.IsNullOrEmpty(this.ReplyTo)) msg.ReplyToList.Add(new MailAddress(this.ReplyTo)); if (this.ReturnReceipt) msg.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess; if (!string.IsNullOrEmpty(this.UserAgent)) this.AddHeader("x-mailer", this.UserAgent); if (!string.IsNullOrEmpty(this.AlternateText)) { byte[] alternateBytes = Encoding.Default.GetBytes(this.AlternateText); MemoryStream ms = new MemoryStream(alternateBytes); ms.Position = 0; msg.AlternateViews.Add(new AlternateView(ms)); //ms.Close(); } if (this.AlternateViews.Count > 0) { foreach (var view in this.AlternateViews) { msg.AlternateViews.Add(view); } } foreach (var header in this.Headers) { msg.Headers[header.Key] = header.Value; } return msg; } /// /// Assigns mail addresses from a string or comma delimited string list. /// Facilitates /// /// /// private void AssignMailAddresses(MailAddressCollection address, string recipients) { if (string.IsNullOrEmpty(recipients)) return; string[] recips = recipients.Split(',', ';'); for (int x = 0; x < recips.Length; x++) { address.Add(new MailAddress(recips[x])); } } /// /// Strips out just the email address from a full email address that might contain a display name /// in the format of: "Web Monitor" <rstrahl@west-wind.com> /// /// Full email address to parse. Note currently only < and > tags are recognized as message delimiters /// only the email address string GetEmailFromFullAddress(string fullEmail) { if (fullEmail.IndexOf("<") > 0) { int lnIndex = fullEmail.IndexOf("<"); int lnIndex2 = fullEmail.IndexOf(">"); string lcEmail = fullEmail.Substring(lnIndex + 1, lnIndex2 - lnIndex - 1); return lcEmail; } return fullEmail; } /// /// Adds a new Alternate view to the request. Passed from FoxPro /// which sets up this object. /// /// /// /// public void AddAlternateView(AlternateView view) { this.AlternateViews.Add(view); } /// /// Logs a message to the specified LogFile /// /// /// protected void LogString(string message) { if (string.IsNullOrEmpty(this.LogFile)) return; if (!message.EndsWith("\r\n")) message += "\r\n"; using (StreamWriter sw = new StreamWriter(this.LogFile, true)) { sw.Write(message); } } /// /// Internally used to set errors /// /// private void SetError(string errorMessage) { if (errorMessage == null || errorMessage.Length == 0) { this.ErrorMessage = string.Empty; this.Error = false; return; } ErrorMessage = errorMessage; Error = true; } #region IDisposable Members public void Dispose() { if (this.smtp != null) this.smtp = null; } #endregion } /// /// Delegate used to handle Completion and failure events /// /// public delegate void delSmtpNativeEvent(SmtpClientNative Smtp); } ================================================ FILE: Westwind.Utilities/LICENSE.MD ================================================ West Wind Utilities Library =========================== MIT License ----------- Copyright (c) 2019-2023 West Wind Technologies Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: Westwind.Utilities/Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Westwind.Utilities.Properties { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Westwind.Utilities.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to A connection string must be passed to the constructor. /// public static string AConnectionStringMustBePassedToTheConstructor { get { return ResourceManager.GetString("AConnectionStringMustBePassedToTheConstructor", resourceCulture); } } /// /// Looks up a localized string similar to An error occurred in your application. /// public static string An_error_occurred_in_your_Application { get { return ResourceManager.GetString("An_error_occurred_in_your_Application", resourceCulture); } } /// /// Looks up a localized string similar to Binary XML Serialization is not supported in .NET Core. /// public static string BinaryXmlSerializationNotSupported { get { return ResourceManager.GetString("BinaryXmlSerializationNotSupported", resourceCulture); } } /// /// Looks up a localized string similar to Byte value greater than 20mb are not supported. /// public static string ByteValueGreaterThan20megsAreNotSupported { get { return ResourceManager.GetString("ByteValueGreaterThan20megsAreNotSupported", resourceCulture); } } /// /// Looks up a localized string similar to Configuration Initialization Error. /// public static string ConfigurationInitializationError { get { return ResourceManager.GetString("ConfigurationInitializationError", resourceCulture); } } /// /// Looks up a localized string similar to Configuration method no longer supported. /// public static string ConfigurationMethodNoLongerSupported { get { return ResourceManager.GetString("ConfigurationMethodNoLongerSupported", resourceCulture); } } /// /// Looks up a localized string similar to Connection opening failure: {0}. /// public static string ConnectionOpeningFailure { get { return ResourceManager.GetString("ConnectionOpeningFailure", resourceCulture); } } /// /// Looks up a localized string similar to Couldn't create user token. /// public static string CouldntCreateUserToken { get { return ResourceManager.GetString("CouldntCreateUserToken", resourceCulture); } } /// /// Looks up a localized string similar to Couldn't load entity. Invalid key provided.. /// public static string CouldntLoadEntityInvalidKeyProvided { get { return ResourceManager.GetString("CouldntLoadEntityInvalidKeyProvided", resourceCulture); } } /// /// Looks up a localized string similar to Data reader passed to object failure.. /// public static string DataReaderPassedToDataReaderToObjectCannot { get { return ResourceManager.GetString("DataReaderPassedToDataReaderToObjectCannot", resourceCulture); } } /// /// Looks up a localized string similar to Error:. /// public static string ErrorColon { get { return ResourceManager.GetString("ErrorColon", resourceCulture); } } /// /// Looks up a localized string similar to Invalid Connection String. /// public static string InvalidConnectionString { get { return ResourceManager.GetString("InvalidConnectionString", resourceCulture); } } /// /// Looks up a localized string similar to Invalid connection string name.. /// public static string InvalidConnectionStringName { get { return ResourceManager.GetString("InvalidConnectionStringName", resourceCulture); } } /// /// Looks up a localized string similar to Invalid encryption property name. /// public static string InvalidEncryptionPropertyName { get { return ResourceManager.GetString("InvalidEncryptionPropertyName", resourceCulture); } } /// /// Looks up a localized string similar to Invalid hex digit. /// public static string InvalidHexDigit { get { return ResourceManager.GetString("InvalidHexDigit", resourceCulture); } } /// /// Looks up a localized string similar to Invalid hex string length.. /// public static string InvalidHexStringLength { get { return ResourceManager.GetString("InvalidHexStringLength", resourceCulture); } } /// /// Looks up a localized string similar to Invalid type for Xml type to .NET object conversion.. /// public static string InvalidTypeForXmlTypeToNETTypeConversion { get { return ResourceManager.GetString("InvalidTypeForXmlTypeToNETTypeConversion", resourceCulture); } } /// /// Looks up a localized string similar to Invalid token identifier - token must be at least 8 characters. /// public static string InvalidUserTokenIdentifier { get { return ResourceManager.GetString("InvalidUserTokenIdentifier", resourceCulture); } } /// /// Looks up a localized string similar to Invalid Xml configuration file format.. /// public static string InvalidXMLConfigurationFileFormat { get { return ResourceManager.GetString("InvalidXMLConfigurationFileFormat", resourceCulture); } } /// /// Looks up a localized string similar to JSON.NET library not available.. /// public static string JSON_NET_library_not_avaiable { get { return ResourceManager.GetString("JSON_NET_library_not_avaiable", resourceCulture); } } /// /// Looks up a localized string similar to Missing encryption key.. /// public static string MissingEncryptionKey { get { return ResourceManager.GetString("MissingEncryptionKey", resourceCulture); } } /// /// Looks up a localized string similar to Missing or invalid token identifier.. /// public static string MissingOrInvalidTokenIdentifier { get { return ResourceManager.GetString("MissingOrInvalidTokenIdentifier", resourceCulture); } } /// /// Looks up a localized string similar to No active transaction to commit.. /// public static string NoActiveTransactionToCommit { get { return ResourceManager.GetString("NoActiveTransactionToCommit", resourceCulture); } } /// /// Looks up a localized string similar to Object could not be deserialized from Xml. /// public static string ObjectCouldNotBeDeserializedFromXml { get { return ResourceManager.GetString("ObjectCouldNotBeDeserializedFromXml", resourceCulture); } } /// /// Looks up a localized string similar to SqlServer Compact Data Provider not supported on .NET Core.. /// public static string SqlServerCompactDataProviderNotSupportedOnNetCore { get { return ResourceManager.GetString("SqlServerCompactDataProviderNotSupportedOnNetCore", resourceCulture); } } /// /// Looks up a localized string similar to String to typed value type conversion failed.. /// public static string StringToTypedValueValueTypeConversionFailed { get { return ResourceManager.GetString("StringToTypedValueValueTypeConversionFailed", resourceCulture); } } /// /// Looks up a localized string similar to Today. /// public static string Today { get { return ResourceManager.GetString("Today", resourceCulture); } } /// /// Looks up a localized string similar to Unable to extract config data from string. /// public static string UnableToExtractKeys { get { return ResourceManager.GetString("UnableToExtractKeys", resourceCulture); } } /// /// Looks up a localized string similar to Unable to read configuration data from string.. /// public static string UnableToReadConfigDataFromString { get { return ResourceManager.GetString("UnableToReadConfigDataFromString", resourceCulture); } } /// /// Looks up a localized string similar to Unable to retrieve DbProvider Factory Form. /// public static string UnableToRetrieveDbProviderFactoryForm { get { return ResourceManager.GetString("UnableToRetrieveDbProviderFactoryForm", resourceCulture); } } /// /// Looks up a localized string similar to Unsupported Provider Factory. /// public static string UnsupportedProviderFactory { get { return ResourceManager.GetString("UnsupportedProviderFactory", resourceCulture); } } /// /// Looks up a localized string similar to Token has expired.. /// public static string UserTokenHasExpired { get { return ResourceManager.GetString("UserTokenHasExpired", resourceCulture); } } /// /// Looks up a localized string similar to Token not found.. /// public static string UserTokenNotFound { get { return ResourceManager.GetString("UserTokenNotFound", resourceCulture); } } /// /// Looks up a localized string similar to Yesterday. /// public static string Yesterday { get { return ResourceManager.GetString("Yesterday", resourceCulture); } } } } ================================================ FILE: Westwind.Utilities/Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Unable to read configuration data from string. Unable to extract config data from string Configuration method no longer supported String to typed value type conversion failed. Invalid type for Xml type to .NET object conversion. Data reader passed to object failure. Byte value greater than 20mb are not supported A connection string must be passed to the constructor Invalid connection string name. Invalid hex string length. Invalid hex digit Invalid Connection String Object could not be deserialized from Xml An error occurred in your application Error: Couldn't load entity. Invalid key provided. Invalid encryption property name . Invalid Xml configuration file format. JSON.NET library not available. No active transaction to commit. Missing encryption key. Connection opening failure: {0} SqlServer Compact Data Provider not supported on .NET Core. Unable to retrieve DbProvider Factory Form Unsupported Provider Factory Today Yesterday Couldn't create user token Invalid token identifier - token must be at least 8 characters Missing or invalid token identifier. Token has expired. Token not found. Configuration Initialization Error Binary XML Serialization is not supported in .NET Core ================================================ FILE: Westwind.Utilities/SupportClasses/DelegateFactory.cs ================================================ using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Westwind.Utilities { /// /// This class creates a generic method delegate from a MethodInfo signature /// converting the method call into a LateBoundMethod delegate call. Using /// this class allows making repeated calls very quickly. /// /// Note: this class will be very inefficient for individual dynamic method /// calls - compilation of the expression is very expensive up front, so using /// this delegate factory makes sense only if you re-use the dynamicly loaded /// method repeatedly. /// /// Entirely based on Nate Kohari's blog post: /// http://kohari.org/2009/03/06/fast-late-bound-invocation-with-expression-trees/ /// public static class DelegateFactory { /// /// Creates a LateBoundMethod delegate from a MethodInfo structure /// Basically creates a dynamic delegate instance (code) on the fly. /// /// /// public static LateBoundMethod Create(MethodInfo method) { ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target"); ParameterExpression argumentsParameter = Expression.Parameter(typeof(object[]), "arguments"); MethodCallExpression call = Expression.Call( Expression.Convert(instanceParameter, method.DeclaringType), method, CreateParameterExpressions(method, argumentsParameter)); Expression lambda = Expression.Lambda( Expression.Convert(call, typeof(object)), instanceParameter, argumentsParameter); return lambda.Compile(); } private static Expression[] CreateParameterExpressions(MethodInfo method, Expression argumentsParameter) { return method.GetParameters().Select((parameter, index) => Expression.Convert( Expression.ArrayIndex(argumentsParameter, Expression.Constant(index)), parameter.ParameterType)).ToArray(); } /// /// Creates a LateBoundMethod from type methodname and parameter signature that /// is turned into a MethodInfo structure and then parsed into a dynamic delegate /// /// /// /// /// public static LateBoundMethod Create(Type type, string methodName, params Type[] parameterTypes) { return Create(type.GetMethod(methodName, parameterTypes)); } } /// /// LateBoundMethod is a generic method signature that is passed an instance /// and an array of parameters and returns an object. It basically can be /// used to call any method. /// /// /// The instance that the dynamic method is called on /// /// public delegate object LateBoundMethod(object target, object[] arguments); } ================================================ FILE: Westwind.Utilities/SupportClasses/Encryption.cs ================================================ using System; using System.IO; using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; using System.Text; using Westwind.Utilities.Properties; namespace Westwind.Utilities { /// /// Class that provides a number of encryption helper utilities to /// make it easier to create hashes and two-way encryption, create /// checksums and more. /// /// /// For best compatibility across platforms of the Encrypt/Decrypt methods use /// overloads that use the key as a byte[24] value rather than string or other sized buffers. /// public static class Encryption { /// /// Replace this value with some unique key of your own /// Best set this in your App start up in a Static constructor /// public static string EncryptionKey = "41a3f131dd91"; /// /// Global configuration property that can be overridden to /// set the key size used for Encrypt/Decrypt operations. /// Choose between 16 bytes (not recommended except for /// backwards compatibility) or 24 bytes (works both in /// NET Full and NET Core) /// #if NETFRAMEWORK public static int EncryptionKeySize = 16; // set for full framework compatibility with previous version #else public static int EncryptionKeySize = 24; // this is the default for .NET Core (doesn't support 16) #endif #region Two-way Encryption /// /// Encodes a stream of bytes using DES encryption with a pass key. Lowest level method that /// handles all work. /// /// /// /// public static byte[] EncryptBytes(byte[] inputBytes, string encryptionKey) { if (encryptionKey == null) encryptionKey = Encryption.EncryptionKey; return EncryptBytes(inputBytes, Encoding.UTF8.GetBytes(encryptionKey)); } /// /// Encodes a stream of bytes using DES encryption with a pass key. Lowest level method that /// handles all work. /// /// /// /// public static byte[] EncryptBytes(byte[] inputBytes, SecureString encryptionKey) { if (encryptionKey == null) throw new ArgumentException(Resources.MissingEncryptionKey); return EncryptBytes(inputBytes, Encoding.UTF8.GetBytes(encryptionKey.GetString())); } /// /// Encrypts a byte buffer with a byte encryption key. /// /// Bytes to convert /// The key bytes used to encode the data. Use a 24 byte key for best compatibility /// Optional CipherMode used. Defaults to older ECB for backwards compatibility /// public static byte[] EncryptBytes(byte[] inputBytes, byte[] encryptionKey, CipherMode cipherMode = CipherMode.ECB) { using (var des = TripleDES.Create()) //new TripleDESCryptoServiceProvider(); { des.Mode = cipherMode; if (EncryptionKeySize == 16) { using (var hash = MD5.Create()) { des.Key = hash.ComputeHash(encryptionKey); } } else { using (var hash = SHA256.Create()) { des.Key = hash.ComputeHash(encryptionKey) .Take(EncryptionKeySize) .ToArray(); } } var transform = des.CreateEncryptor(); byte[] buffer = inputBytes; return transform.TransformFinalBlock(buffer, 0, buffer.Length); } } /// /// Encrypts a string into bytes using DES encryption with a Passkey. /// /// /// /// public static byte[] EncryptBytes(string inputString, string encryptionKey) { return EncryptBytes(Encoding.UTF8.GetBytes(inputString), encryptionKey); } /// /// Encrypts a string using Triple DES encryption with a two way encryption key.String is returned as Base64 or BinHex /// encoded value rather than binary. /// /// /// /// if true returns bin hex rather than base64 /// public static string EncryptString(string inputString, byte[] encryptionKey, bool useBinHex = false) { byte[] bytes = Encoding.UTF8.GetBytes(inputString); if (useBinHex) return BinaryToBinHex(EncryptBytes(bytes, encryptionKey)); return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey)); } /// /// Encrypts a string using Triple DES encryption with a two way encryption key. /// String is returned as Base64 or BinHex encoded string. /// /// /// /// /// public static string EncryptString(string inputString, string encryptionKey, bool useBinHex = false) { byte[] bytes = Encoding.UTF8.GetBytes(inputString); if (useBinHex) return BinaryToBinHex(EncryptBytes(bytes, encryptionKey)); return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey)); } /// /// Encrypts a string using Triple DES encryption with a two way encryption key. /// String is returned as Base64 or BinHex encoded string. /// /// /// /// /// public static string EncryptString(string inputString,SecureString encryptionKey, bool useBinHex = false) { if (encryptionKey == null) throw new ArgumentException(Resources.MissingEncryptionKey); byte[] bytes = Encoding.UTF8.GetBytes(inputString); if (useBinHex) return BinaryToBinHex(EncryptBytes(bytes, encryptionKey.GetString())); return Convert.ToBase64String(EncryptBytes(bytes, encryptionKey.GetString())); } /// /// Decrypts a Byte array from DES with an Encryption Key. /// /// /// /// public static byte[] DecryptBytes(byte[] decryptBuffer, string encryptionKey) { if (decryptBuffer == null || decryptBuffer.Length == 0) return null; if (encryptionKey == null) encryptionKey = Encryption.EncryptionKey; return DecryptBytes(decryptBuffer, Encoding.UTF8.GetBytes(encryptionKey)); } /// /// Decrypts a Byte array from DES with an Encryption Key. /// /// /// /// public static byte[] DecryptBytes(byte[] decryptBuffer, SecureString encryptionKey) { if (decryptBuffer == null || decryptBuffer.Length == 0) return null; return DecryptBytes(decryptBuffer, Encoding.UTF8.GetBytes(encryptionKey.GetString())); } /// /// Decrypts a byte buffer with a byte based encryption key /// /// Data to encrypt /// The key bytes used to encode the data. Use a 24 byte key for best compatibility /// Optional CipherMode used. Defaults to older ECB for backwards compatibility /// public static byte[] DecryptBytes(byte[] decryptBuffer, byte[] encryptionKey, CipherMode cipherMode = CipherMode.ECB) { if (decryptBuffer == null || decryptBuffer.Length == 0) return null; using (var des = TripleDES.Create()) { des.Mode = cipherMode; if (EncryptionKeySize == 16) { using (var hash = MD5.Create()) { des.Key = hash.ComputeHash(encryptionKey); } } else { using (var hash = SHA256.Create()) { des.Key = hash.ComputeHash(encryptionKey) .Take(EncryptionKeySize) .ToArray(); } } var transform = des.CreateDecryptor(); return transform.TransformFinalBlock(decryptBuffer, 0, decryptBuffer.Length); } } /// /// Decrypts a string using DES encryption and a pass key that was used for /// encryption and returns a byte buffer. /// /// /// /// Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned. /// String public static byte[] DecryptBytes(string decryptString, string encryptionKey, bool useBinHex = false) { if (useBinHex) return DecryptBytes(BinHexToBinary(decryptString), encryptionKey); return DecryptBytes(Convert.FromBase64String(decryptString), encryptionKey); } /// /// Decrypts a string using DES encryption and a pass key that was used for /// encryption and returns a byte buffer. /// /// /// /// Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned. /// String public static byte[] DecryptBytes(string decryptString, SecureString encryptionKey, bool useBinHex = false) { if (encryptionKey == null) throw new ArgumentException(Resources.MissingEncryptionKey); if (useBinHex) return DecryptBytes(BinHexToBinary(decryptString), encryptionKey.GetString()); return DecryptBytes(Convert.FromBase64String(decryptString), encryptionKey.GetString()); } /// /// Decrypts a string using DES encryption and a pass key that was used for /// encryption. /// Class wwEncrypt /// /// /// /// Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned. /// String public static string DecryptString(string decryptString, string encryptionKey, bool useBinHex = false) { try { var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString); byte[] decrypted = DecryptBytes(data, encryptionKey); return Encoding.UTF8.GetString(decrypted); } catch { // undecryptable data return string.Empty; } } /// /// Decrypts a string using DES encryption and a pass key that was used for /// encryption. /// Class wwEncrypt /// /// /// /// Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned. /// String public static string DecryptString(string decryptString, SecureString encryptionKey, bool useBinHex = false) { if (encryptionKey == null) throw new ArgumentException(Resources.MissingEncryptionKey); var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString); try { byte[] decrypted = DecryptBytes(data, encryptionKey.GetString()); return Encoding.UTF8.GetString(decrypted); } catch { return string.Empty; } } /// /// Decrypts a string using DES encryption and a pass key that was used for /// encryption. /// Class wwEncrypt /// /// /// /// Returns data in useBinHex format (12afb1c3f1). Otherwise base64 is returned /// String public static string DecryptString(string decryptString, byte[] encryptionKey, bool useBinHex = false) { var data = useBinHex ? BinHexToBinary(decryptString) : Convert.FromBase64String(decryptString); try { byte[] decrypted = DecryptBytes(data, encryptionKey); return Encoding.UTF8.GetString(decrypted); } catch { return string.Empty; } } #if NETFULL /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// public static byte[] ProtectBytes(byte[] encryptBytes, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine) { return ProtectedData.Protect(encryptBytes,key,scope); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// public static byte[] ProtectBytes(byte[] encryptBytes, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine) { return ProtectedData.Protect(encryptBytes, Encoding.UTF8.GetBytes(key),scope); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// returns bin hex data when set (010A0D10AF) /// public static string ProtectString(string encryptString, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false) { var encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(encryptString), Encoding.UTF8.GetBytes(key), scope); if (useBinHex) return BinaryToBinHex(encryptedBytes); return Convert.ToBase64String(encryptedBytes); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// returns bin hex data when set (010A0D10AF) /// public static string ProtectString(string encryptString, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false) { var encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(encryptString), key, scope); if (useBinHex) return BinaryToBinHex(encryptedBytes); return Convert.ToBase64String(encryptedBytes); } /// /// Decrypts bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// public static byte[] UnprotectBytes(byte[] encryptBytes, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine) { return ProtectedData.Unprotect(encryptBytes, key, scope); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// public static byte[] UnprotectBytes(byte[] encryptBytes, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine) { return ProtectedData.Unprotect(encryptBytes, Encoding.UTF8.GetBytes(key), scope); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// returns bin hex data when set (010A0D10AF) /// public static string UnprotectString(string encryptString, string key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false) { byte[] buffer; if (useBinHex) buffer = BinHexToBinary(encryptString); else buffer = Convert.FromBase64String(encryptString); buffer = ProtectedData.Unprotect(buffer, Encoding.UTF8.GetBytes(key), scope); return Encoding.UTF8.GetString(buffer); } /// /// Encrypt bytes using the Data Protection API on Windows. This API /// uses internal keys to encrypt data which is valid for decryption only /// on the same machine. /// /// This is an idea storage mechanism for application registraions, /// service passwords and other semi-transient data that is specific /// to the software used on the current machine /// /// /// DO NOT USE FOR DATA THAT WILL CROSS MACHINE BOUNDARIES /// /// /// /// /// returns bin hex data when set (010A0D10AF) /// public static string UnprotectString(string encryptString, byte[] key, DataProtectionScope scope = DataProtectionScope.LocalMachine, bool useBinHex = false) { byte[] buffer; if (useBinHex) buffer = BinHexToBinary(encryptString); else buffer = Convert.FromBase64String(encryptString); buffer = ProtectedData.Unprotect(buffer, key, scope); return Encoding.UTF8.GetString(buffer); } #endif #endregion #region Hashes /// /// Generates a hash for the given plain text value and returns a /// base64-encoded result. Before the hash is computed, a random salt /// is generated and appended to the plain text. This salt is stored at /// the end of the hash value, so it can be used later for hash /// verification. /// /// /// Plaintext value to be hashed. /// /// /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", "SHA512", "HMACMD5", "HMACSHA1", "HMACSHA256", /// "HMACSHA512" (if any other value is specified MD5 will be used). /// /// HMAC algorithms uses Hash-based Message Authentication Code. /// The HMAC process mixes a secret key with the message data, hashes /// the result with the hash function, mixes that hash value with /// the secret key again, and then applies the hash function /// a second time. HMAC hashes are fixed lenght and generally /// much longer than non-HMAC hashes of the same type. /// /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx /// /// This value is case-insensitive. /// /// /// Optional but recommended salt string to apply to the hash. If not passed the /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for /// file hashes etc.) HMAC versions REQUIRE that salt is passed. /// /// if true returns the data as BinHex byte pair string. Otherwise Base64 is returned. /// /// Hash value formatted as a base64-encoded or BinHex stringstring. /// public static string ComputeHash(string plainText, string hashAlgorithm, byte[] saltBytes, bool useBinHex = false) { if (string.IsNullOrEmpty(plainText)) return plainText; return ComputeHash(Encoding.UTF8.GetBytes(plainText), hashAlgorithm, saltBytes, useBinHex); } /// /// Generates a hash for the given plain text value and returns a /// base64-encoded result. Before the hash is computed, a random salt /// is generated and appended to the plain text. This salt is stored at /// the end of the hash value, so it can be used later for hash /// verification. /// /// /// Plaintext value to be hashed. /// /// /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", "SHA512", "HMACMD5", "HMACSHA1", "HMACSHA256", /// "HMACSHA512" (if any other value is specified MD5 will be used). /// /// HMAC algorithms uses Hash-based Message Authentication Code. /// The HMAC process mixes a secret key with the message data, hashes /// the result with the hash function, mixes that hash value with /// the secret key again, and then applies the hash function /// a second time. HMAC hashes are fixed lenght and generally /// much longer than non-HMAC hashes of the same type. /// /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx /// /// This value is case-insensitive. /// /// /// Optional but recommended salt string to apply to the hash. If not passed the /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for /// file hashes etc.) HMAC versions REQUIRE that salt is passed. /// /// if true returns the data as BinHex byte pair string. Otherwise Base64 is returned. /// /// Hash value formatted as a base64-encoded or BinHex stringstring. /// public static string ComputeHash(string plainText, string hashAlgorithm, string salt, bool useBinHex = false) { if (string.IsNullOrEmpty(plainText)) return plainText; return ComputeHash(Encoding.UTF8.GetBytes(plainText), hashAlgorithm, Encoding.UTF8.GetBytes(salt), useBinHex); } /// /// Generates a hash for the given plain text value and returns a /// base64-encoded result. Before the hash is computed, a random salt /// is generated and appended to the plain text. This salt is stored at /// the end of the hash value, so it can be used later for hash /// verification. /// /// /// Plaintext value to be hashed. /// /// /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", /// "SHA256", "SHA384", "SHA512", "HMACMD5", "HMACSHA1", "HMACSHA256", /// "HMACSHA512" (if any other value is specified MD5 will be used). /// /// HMAC algorithms uses Hash-based Message Authentication Code. /// The HMAC process mixes a secret key with the message data, hashes /// the result with the hash function, mixes that hash value with /// the secret key again, and then applies the hash function /// a second time. HMAC hashes are fixed lenght and generally /// much longer than non-HMAC hashes of the same type. /// /// https://msdn.microsoft.com/en-us/library/system.security.cryptography.hmacsha256(v=vs.110).aspx /// /// This value is case-insensitive. /// /// /// Optional but recommended salt bytes to apply to the hash. If not passed the /// raw encoding is used. If salt is nullthe raw algorithm is used (useful for /// file hashes etc.) HMAC versions REQUIRE that salt is passed. /// /// if true returns the data as BinHex byte pair string. Otherwise Base64 is returned. /// /// Hash value formatted as a base64-encoded or BinHex stringstring. /// public static string ComputeHash(byte[] byteData, string hashAlgorithm, byte[] saltBytes, bool useBinHex = false) { if (byteData == null) return null; // Convert plain text into a byte array. byte[] plainTextWithSaltBytes; if (saltBytes != null && !hashAlgorithm.StartsWith("HMAC")) { // Allocate array, which will hold plain text and salt. plainTextWithSaltBytes = new byte[byteData.Length + saltBytes.Length]; // Copy plain text bytes into resulting array. for (int i = 0; i < byteData.Length; i++) plainTextWithSaltBytes[i] = byteData[i]; // Append salt bytes to the resulting array. for (int i = 0; i < saltBytes.Length; i++) plainTextWithSaltBytes[byteData.Length + i] = saltBytes[i]; } else plainTextWithSaltBytes = byteData; HashAlgorithm hash; // Make sure hashing algorithm name is specified. if (hashAlgorithm == null) hashAlgorithm = ""; // Initialize appropriate hashing algorithm class. switch (hashAlgorithm.ToUpper()) { case "SHA1": hash = SHA1.Create(); break; case "SHA256": hash = SHA256.Create(); break; case "SHA384": hash = SHA384.Create(); break; case "SHA512": hash = SHA512.Create(); break; case "HMACMD5": hash = new HMACMD5(saltBytes); break; case "HMACSHA1": hash = new HMACSHA1(saltBytes); break; case "HMACSHA256": hash = new HMACSHA256(saltBytes); break; case "HMACSHA512": hash = new HMACSHA512(saltBytes); break; default: // default to MD5 hash = MD5.Create(); break; } using (hash) { byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); if (useBinHex) return BinaryToBinHex(hashBytes); return Convert.ToBase64String(hashBytes); } } #endregion #region Gzip /// /// GZip encodes a memory buffer to a compressed memory buffer /// /// /// public static byte[] GZipMemory(byte[] buffer) { MemoryStream ms = new MemoryStream(); GZipStream gZip = new GZipStream(ms, CompressionMode.Compress); gZip.Write(buffer, 0, buffer.Length); gZip.Close(); byte[] result = ms.ToArray(); ms.Close(); return result; } /// /// Encodes a string to a gzip compressed memory buffer /// /// /// public static byte[] GZipMemory(string input) { return GZipMemory(Encoding.UTF8.GetBytes(input)); } /// /// Encodes a file to a gzip memory buffer /// /// /// /// public static byte[] GZipMemory(string filename, bool isFile) { byte[] buffer = File.ReadAllBytes(filename); return GZipMemory(buffer); } /// /// Encodes one file to another file that is gzip compressed. /// File is overwritten if it exists and not locked. /// /// /// /// public static bool GZipFile(string filename, string outputFile) { byte[] Buffer = File.ReadAllBytes(filename); FileStream fs = new FileStream(outputFile, FileMode.OpenOrCreate, FileAccess.Write); GZipStream GZip = new GZipStream(fs, CompressionMode.Compress); GZip.Write(Buffer, 0, Buffer.Length); GZip.Close(); fs.Close(); return true; } #endregion #region CheckSum /// /// Creates an SHA256 or MD5 checksum of a file /// /// /// SHA256,SHA512,MD5 /// A binhex string public static string GetChecksumFromFile(string file, string mode = "SHA256") { if (!File.Exists(file)) return null; try { using (FileStream stream = File.OpenRead(file)) { if (mode == "SHA256") { byte[] checksum; using (var sha = SHA256.Create()) { checksum = sha.ComputeHash(stream); } return BinaryToBinHex(checksum); } if (mode == "SHA512") { byte[] checksum; using (var sha = SHA512.Create()) { checksum = sha.ComputeHash(stream); } return BinaryToBinHex(checksum); } if (mode == "MD5") { byte[] checkSum; using (var md = MD5.Create()) { checkSum = md.ComputeHash(stream); } return BinaryToBinHex(checkSum); } } } catch { return null; } return null; } /// /// Create a SHA256 or MD5 checksum from a bunch of bytes /// /// /// SHA256,SHA512,MD5 /// public static string GetChecksumFromBytes(byte[] fileData, string mode = "SHA256") { using (MemoryStream stream = new MemoryStream(fileData)) { if (mode == "SHA256") { byte[] checksum; using (var sha = SHA256.Create()) { checksum = sha.ComputeHash(stream); } return BinaryToBinHex(checksum); } if (mode == "SHA512") { byte[] checksum; using (var sha = SHA512.Create()) { checksum = sha.ComputeHash(stream); } return BinaryToBinHex(checksum); } if (mode == "MD5") { byte[] checkSum; using (var md = MD5.Create()) { checkSum = md.ComputeHash(stream); } return BinaryToBinHex(checkSum); } } return null; } #endregion #region BinHex Helpers /// /// Converts a byte array into a BinHex string. /// Example: 01552233 /// where the numbers are packed /// byte values. /// /// Raw data to send /// string or null if input is null public static string BinaryToBinHex(byte[] data) { if (data == null) return null; StringBuilder sb = new StringBuilder(data.Length * 2); foreach (byte val in data) { sb.AppendFormat("{0:x2}", val); } return sb.ToString().ToUpper(); } /// /// Turns a BinHex string that contains raw byte values /// into a byte array /// /// BinHex string (011a031f) just two byte hex digits strung together) /// public static byte[] BinHexToBinary(string hex) { int offset = hex.StartsWith("0x") ? 2 : 0; if ((hex.Length % 2) != 0) throw new ArgumentException("Invalid String Length"); byte[] ret = new byte[(hex.Length - offset) / 2]; for (int i = 0; i < ret.Length; i++) { ret[i] = (byte)((ParseHexChar(hex[offset]) << 4) | ParseHexChar(hex[offset + 1])); offset += 2; } return ret; } static int ParseHexChar(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; throw new ArgumentException("Invalid character"); } #endregion } public static class SecureStringExtensions { public static string GetString(this SecureString source) { string result = null; int length = source.Length; IntPtr pointer = IntPtr.Zero; char[] chars = new char[length]; try { pointer = Marshal.SecureStringToBSTR(source); Marshal.Copy(pointer, chars, 0, length); result = string.Join("", chars); } finally { if (pointer != IntPtr.Zero) { Marshal.ZeroFreeBSTR(pointer); } } return result; } } } ================================================ FILE: Westwind.Utilities/SupportClasses/Expando.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2012 * http://www.west-wind.com/ * * Created: Feb 2, 2012 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Dynamic; using System.Reflection; using ICollection = System.Collections.ICollection; namespace Westwind.Utilities { /// /// Class that provides extensible properties and methods to an /// existing object when cast to dynamic. This /// dynamic object stores 'extra' properties in a dictionary or /// checks the actual properties of the instance passed via /// constructor. /// /// This class can be subclassed to extend an existing type or /// you can pass in an instance to extend. Properties (both /// dynamic and strongly typed) can be accessed through an /// indexer. /// /// This type allows you three ways to access its properties: /// /// Directly: any explicitly declared properties are accessible /// Dynamic: dynamic cast allows access to dictionary and native properties/methods /// Dictionary: Any of the extended properties are accessible via IDictionary interface /// [Serializable] public class Expando : DynamicObject, IDynamicMetaObjectProvider { /// /// Instance of object passed in /// protected object Instance; /// /// Cached type of the instance /// protected Type InstanceType; PropertyInfo[] InstancePropertyInfo { get { if (_InstancePropertyInfo == null && Instance != null) _InstancePropertyInfo = Instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) .Where(a => a.GetIndexParameters().Length == 0).ToArray(); return _InstancePropertyInfo; } } PropertyInfo[] _InstancePropertyInfo; /// /// String Dictionary that contains the extra dynamic values /// stored on this object/instance /// /// Using PropertyBag to support XML Serialization of the dictionary public PropertyBag Properties = new PropertyBag(); //public Dictionary Properties = new Dictionary(); /// /// This constructor just works off the internal dictionary and any /// public properties of this object. /// /// Note you can subclass Expando. /// public Expando() { Initialize(this); } /// /// Allows passing in an existing instance variable to 'extend'. /// /// /// You can pass in null here if you don't want to /// check native properties and only check the Dictionary! /// /// public Expando(object instance) { var dictionary = instance as IDictionary; if (dictionary == null) { Initialize(instance); return; } var expando = this; this. Initialize(expando); InitializeAsDictionary(expando, dictionary); } /// /// Create an Expando from a dictionary /// /// A dictionary of property/value pairs public Expando(IDictionary dict) { var expando = this; Initialize(expando); InitializeAsDictionary(expando, dict); } private void InitializeAsDictionary(Expando expando, IDictionary dict) { Properties = new PropertyBag(); foreach (var kvp in dict) { var kvpValue = kvp.Value; if (kvpValue is IDictionary) { var expandoVal = new Expando(kvpValue); expando[kvp.Key] = expandoVal; } else if (kvp.Value is ICollection) { // iterate through the collection and convert any string-object dictionaries // along the way into expando objects var objList = new List(); foreach (var item in (ICollection) kvp.Value) { var itemVals = item as IDictionary; if (itemVals != null) { var expandoItem = new Expando(itemVals); objList.Add(expandoItem); } else { objList.Add(item); } } expando[kvp.Key] = objList; } else { expando[kvp.Key] = kvpValue; } } } protected void Initialize(object instance) { Instance = instance; if (instance != null) InstanceType = instance.GetType(); } /// /// Return both instance and dynamic names. /// /// Important to return both so JSON serialization with /// Json.NET works. /// /// public override IEnumerable GetDynamicMemberNames() { foreach (var prop in GetProperties(true)) yield return prop.Key; } /// /// Try to retrieve a member by name first from instance properties /// followed by the collection entries. /// /// /// /// public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; // first check the Properties collection for member if (Properties.Keys.Contains(binder.Name)) { result = Properties[binder.Name]; return true; } // Next check for Public properties via Reflection if (Instance != null) { try { return GetProperty(Instance, binder.Name, out result); } catch { } } // failed to retrieve a property result = null; return false; } /// /// Property setter implementation tries to retrieve value from instance /// first then into this object /// /// /// /// public override bool TrySetMember(SetMemberBinder binder, object value) { // first check to see if there's a native property to set if (Instance != null) { try { bool result = SetProperty(Instance, binder.Name, value); if (result) return true; } catch { return false; } } // no match - set or add to dictionary Properties[binder.Name] = value; return true; } /// /// Dynamic invocation method. Currently allows only for Reflection based /// operation (no ability to add methods dynamically). /// /// /// /// /// public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (Instance != null) { try { // check instance passed in for methods to invoke if (InvokeMethod(Instance, binder.Name, args, out result)) return true; } catch { } } result = null; return false; } /// /// Reflection Helper method to retrieve a property /// /// /// /// /// protected bool GetProperty(object instance, string name, out object result) { if (instance == null) instance = this; var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance); if (miArray != null && miArray.Length > 0) { var mi = miArray[0]; if (mi.MemberType == MemberTypes.Property) { result = ((PropertyInfo)mi).GetValue(instance,null); return true; } } result = null; return false; } /// /// Reflection helper method to set a property value /// /// /// /// /// protected bool SetProperty(object instance, string name, object value) { if (instance == null) instance = this; var miArray = InstanceType.GetMember(name, BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.Instance); if (miArray != null && miArray.Length > 0) { var mi = miArray[0]; if (mi.MemberType == MemberTypes.Property) { ((PropertyInfo)mi).SetValue(Instance, value, null); return true; } } return false; } /// /// Reflection helper method to invoke a method /// /// /// /// /// /// protected bool InvokeMethod(object instance, string name, object[] args, out object result) { if (instance == null) instance = this; // Look at the instanceType var miArray = InstanceType.GetMember(name, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance); if (miArray != null && miArray.Length > 0) { var mi = miArray[0] as MethodInfo; result = mi.Invoke(Instance, args); return true; } result = null; return false; } /// /// Convenience method that provides a string Indexer /// to the Properties collection AND the strongly typed /// properties of the object by name. /// /// // dynamic /// exp["Address"] = "112 nowhere lane"; /// // strong /// var name = exp["StronglyTypedProperty"] as string; /// /// /// The getter checks the Properties dictionary first /// then looks in PropertyInfo for properties. /// The setter checks the instance properties before /// checking the Properties dictionary. /// /// /// /// public object this[string key] { get { try { // try to get from properties collection first return Properties[key]; } catch (KeyNotFoundException) { // try reflection on instanceType object result = null; if (GetProperty(Instance, key, out result)) return result; // nope doesn't exist throw; } } set { if (Properties.ContainsKey(key)) { Properties[key] = value; return; } // check instance for existance of type first var miArray = InstanceType.GetMember(key, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance); if (miArray != null && miArray.Length > 0) SetProperty(Instance, key, value); else Properties[key] = value; } } /// /// Returns and the properties of /// /// /// public IEnumerable> GetProperties(bool includeInstanceProperties = false) { if (includeInstanceProperties && Instance != null) { foreach (var prop in this.InstancePropertyInfo) yield return new KeyValuePair(prop.Name, prop.GetValue(Instance, null)); } foreach (var key in this.Properties.Keys) yield return new KeyValuePair(key, this.Properties[key]); } /// /// Checks whether a property exists in the Property collection /// or as a property on the instance /// /// /// public bool Contains(KeyValuePair item, bool includeInstanceProperties = false) { bool res = Properties.ContainsKey(item.Key); if (res) return true; if (includeInstanceProperties && Instance != null) { foreach (var prop in this.InstancePropertyInfo) { if (prop.Name == item.Key) return true; } } return false; } } } ================================================ FILE: Westwind.Utilities/SupportClasses/ObjectFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Threading; namespace Westwind.Utilities { /// /// An object factory that can create instances of types /// for Http Web Request and Thread Scoped object objects /// and value types. /// /// public class ObjectFactory where T: class, new() { /// /// Internal locking for collection storage /// static object _syncLock = new object(); /// /// Returns a standard instance of an object. /// /// /// public static T CreateObject(params object[] args) { return (T) Activator.CreateInstance(typeof(T), args); } /// /// Create an instance scoped to a current thread. /// /// Optional reusable key of the TLS item created /// /// public static T CreateThreadScopedObject(params object[] args) { string key = GetUniqueObjectKey(args) + "_" + Thread.CurrentThread.ManagedThreadId.ToString(); LocalDataStoreSlot threadData = Thread.GetNamedDataSlot(key); T item; if (threadData != null) item = (T)Thread.GetData(threadData); else item = null; // no item - create and store if ( item == null ) { lock (_syncLock) { // check again inside of lock threadData = Thread.GetNamedDataSlot(key); if (threadData == null) threadData = Thread.GetNamedDataSlot(key); if (item == null) item = CreateObject(args); Thread.SetData(threadData, item); } } return item; } /// /// Returns a unique ID for a given type and parameter signature /// /// /// public static string GetUniqueObjectKey(params object[] args) { Type type = typeof(T); StringBuilder sb = new StringBuilder("_" + type.GetHashCode().ToString("x")); sb.Append( "_" + type.Name); foreach (var arg in args) { if (arg == null) sb.Append("_null"); else sb.Append("_" + arg.GetHashCode().ToString("x")); } return sb.ToString(); } } } ================================================ FILE: Westwind.Utilities/SupportClasses/PropertyBag.cs ================================================ using System; using System.Collections.Generic; using System.Xml.Serialization; using System.Xml; using Westwind.Utilities.Properties; namespace Westwind.Utilities { /// /// Creates a serializable string/object dictionary that is XML serializable /// Encodes keys as element names and values as simple values with a type /// attribute that contains an XML type name. Complex names encode the type /// name with type='___namespace.classname' format followed by a standard xml /// serialized format. The latter serialization can be slow so it's not recommended /// to pass complex types if performance is critical. /// [XmlRoot("properties")] public class PropertyBag : PropertyBag { /// /// Creates an instance of a propertybag from an Xml string /// /// Serialize /// public new static PropertyBag CreateFromXml(string xml) { var bag = new PropertyBag(); bag.FromXml(xml); return bag; } } /// /// Creates a serializable string for generic types that is XML serializable. /// /// Encodes keys as element names and values as simple values with a type /// attribute that contains an XML type name. Complex names encode the type /// name with type='___namespace.classname' format followed by a standard xml /// serialized format. The latter serialization can be slow so it's not recommended /// to pass complex types if performance is critical. /// /// Must be a reference type. For value types use type object [XmlRoot("properties")] public class PropertyBag : Dictionary, IXmlSerializable { /// /// Not implemented - this means no schema information is passed /// so this won't work with ASMX/WCF services. /// /// public System.Xml.Schema.XmlSchema GetSchema() { return null; } /// /// Serializes the dictionary to XML. Keys are /// serialized to element names and values as /// element values. An xml type attribute is embedded /// for each serialized element - a .NET type /// element is embedded for each complex type and /// prefixed with three underscores. /// /// public void WriteXml(System.Xml.XmlWriter writer) { foreach (string key in this.Keys) { TValue value = this[key]; Type type = null; if (value != null) type = value.GetType(); writer.WriteStartElement("item"); writer.WriteStartElement("key"); writer.WriteString(key as string); writer.WriteEndElement(); writer.WriteStartElement("value"); string xmlType = XmlUtils.MapTypeToXmlType(type); bool isCustom = false; // Type information attribute if not string if (value == null) { writer.WriteAttributeString("type", "nil"); } else if (!string.IsNullOrEmpty(xmlType)) { if (xmlType != "string") { writer.WriteStartAttribute("type"); writer.WriteString(xmlType); writer.WriteEndAttribute(); } } else { isCustom = true; xmlType = "___" + value.GetType().FullName; writer.WriteStartAttribute("type"); writer.WriteString(xmlType); writer.WriteEndAttribute(); } // Serialize simple types with WriteValue if (!isCustom) { if (value != null) writer.WriteValue(value); } else { // Complex types require custom XmlSerializer XmlSerializer ser = new XmlSerializer(value.GetType()); ser.Serialize(writer, value); } writer.WriteEndElement(); // value writer.WriteEndElement(); // item } } /// /// Reads the custom serialized format /// /// public void ReadXml(System.Xml.XmlReader reader) { this.Clear(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "key") { string xmlType = null; string name = reader.ReadElementContentAsString(); // item element reader.ReadToNextSibling("value"); if (reader.MoveToNextAttribute()) xmlType = reader.Value; if (string.IsNullOrEmpty(xmlType)) xmlType = "string"; reader.MoveToContent(); TValue value; string strval = String.Empty; if (xmlType == "nil") value = default(TValue); // null // .NET types that don't map to XML we have to manually // deserialize else if (xmlType.StartsWith("___")) { // skip ahead to serialized value element while (reader.Read() && reader.NodeType != XmlNodeType.Element) { } Type type = ReflectionUtils.GetTypeFromName(xmlType.Substring(3)); XmlSerializer ser = new XmlSerializer(type); value = (TValue)ser.Deserialize(reader); } else value = (TValue) reader.ReadElementContentAs(XmlUtils.MapXmlTypeToType(xmlType), null); this.Add(name, value); } } } /// /// Serializes this dictionary to an XML string /// /// XML String or Null if it fails public string ToXml() { string xml = null; SerializationUtils.SerializeObject(this, out xml); return xml; } /// /// Deserializes from an XML string /// /// /// true or false public bool FromXml(string xml) { this.Clear(); // if xml string is empty we return an empty dictionary if (string.IsNullOrEmpty(xml)) return true; var result = SerializationUtils.DeSerializeObject(xml, this.GetType()) as PropertyBag; if (result != null) { foreach (var item in result) { this.Add(item.Key, item.Value); } } else // null is a failure return false; return true; } /// /// Creates an instance of a propertybag from an Xml string /// /// /// public static PropertyBag CreateFromXml(string xml) { var bag = new PropertyBag(); bag.FromXml(xml); return bag; } } } ================================================ FILE: Westwind.Utilities/SupportClasses/Scheduler.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Net; namespace Westwind.Utilities { /// /// A generic scheduling service that runs on a background /// thread and fires events in a given check frequency. /// /// /// public class Scheduler : IDisposable { /// /// Determines the status the Scheduler /// public bool Cancelled { get { return _Cancelled; } private set { _Cancelled = value; } } private bool _Cancelled = true; /// /// The frequency in how often the main method is executed. /// Given in milli-seconds. /// public int CheckFrequency { get { return _CheckFrequency; } set { _CheckFrequency = value; } } private int _CheckFrequency = 60000; /// /// Optional URL that is pinged occasionally to /// ensure the server stays alive. /// /// If empty hits root web page (~/yourapp/) /// public string WebServerPingUrl { get { return _WebServerPingUrl; } set { _WebServerPingUrl = value; } } private string _WebServerPingUrl = ""; /// /// Event that is fired when /// public event EventHandler ExecuteScheduledEvent; AutoResetEvent _WaitHandle = new AutoResetEvent(false); /// /// Internal property used for cross thread locking /// object _SyncLock = new Object(); /// /// Optionally usable local memory based queue that /// contains can be used to add items to a queue /// ordered retrieval. /// /// If message persistence is important your scheduling store /// should be a database. You can use the QueueMessageManager /// object for example. /// /// /// Note memory based! This means if app crashses /// or is shut down messages might get lost. /// public virtual Queue Items { get { return _Items; } set { _Items = value; } } private Queue _Items = new Queue(); /// /// Starts the background thread processing /// /// Frequency that checks are performed in seconds public void Start(int checkFrequency) { // Ensure that any waiting instances are shut down //this.WaitHandle.Set(); CheckFrequency = checkFrequency; Cancelled = false; Thread t = new Thread(Run); t.Start(); } /// /// Starts the background Thread processing /// public void Start() { Cancelled = false; IsRunning = false; Start(CheckFrequency); } /// /// Causes the processing to stop. If the operation is still /// active it will stop after the current message processing /// completes /// public void Stop() { lock (_SyncLock) { if (!IsRunning || Cancelled) return; IsRunning = false; _WaitHandle.Set(); } } /// /// Causes the processing to stop. If the operation is still /// active it will stop after the current message processing /// completes /// public void Cancel() { lock (_SyncLock) { if (Cancelled) return; IsRunning = false; Cancelled = true; _WaitHandle.Set(); } } public bool IsRunning { get; set; } /// /// Runs the actual processing loop by checking the mail box /// private void Run() { // Start out waiting for the timeout period defined // on the scheduler _WaitHandle.WaitOne(CheckFrequency, true); IsRunning = true; while (!Cancelled && IsRunning) { try { // Call whatever logic is attached to the scheduler OnExecuteScheduledEvent(); ExecuteScheduledEventAction?.Invoke(this); } // always eat the exception and notify listener catch (Exception ex) { OnError(ex); ErrorAction?.Invoke(this, ex); } // If execution caused cancelling we want to exit now if (Cancelled || !IsRunning) break; // if a keep alive ping is required fire it if (!string.IsNullOrEmpty(WebServerPingUrl)) PingServer(); // Wait for the specified time out _WaitHandle.WaitOne(CheckFrequency, true); } IsRunning = false; } /// /// Handles a scheduled operation. Checks to see if an event handler /// is set up and if so calls it. /// /// This method can also be overrriden in a subclass to implemnent /// custom functionality. /// protected virtual void OnExecuteScheduledEvent() { if (ExecuteScheduledEvent != null) ExecuteScheduledEvent(this, EventArgs.Empty); } /// /// Event handler you can hook up to handle scheduled events /// public virtual ActionExecuteScheduledEventAction { get; set; } /// /// This method is called if an error occurs during processing /// of the OnExecuteScheduledEvent request /// /// Override this method in your own implementation to provide /// for error logging or other handling of an error that occurred /// in processing. /// /// Ideally this shouldn't be necessary - your OnexecuteScheduledEvent /// code should handle any errors internally and provide for its own /// logging mechanism but this is here as an additional point of /// control. /// /// Exception occurred during item execution protected virtual void OnError(Exception ex) { } /// /// Event handler Action you can hook up to respond to errors. /// Receives the Scheduler Exception that occurred during processing /// as a parameter in addition to the scheduler instance. /// public virtual Action ErrorAction { get; set; } /// /// Adds an item to the queue. /// /// Any data you want to add to the local queue public void AddItem(object item) { lock (_SyncLock) { Items.Enqueue(item); } } /// /// Adds an item to the queue. /// /// A specific Scheduler Item to add to the local queue public void AddItem(SchedulerItem item) { AddItem(item); } /// /// Adds a text item to the queue with a specific identification type /// /// /// public void AddItem(string textData, string type = null) { SchedulerItem item = new SchedulerItem(); item.TextData = textData; item.Type = type; AddItem(item as object); } /// /// Adds a binary item to the queue with a specific identification type /// /// /// public void AddItem(byte[] data, string type = null) { SchedulerItem item = new SchedulerItem { Data = data, Type = type }; AddItem(item as object); } /// /// Returns the next queued item or null on failure. /// /// public object GetNextItem() { lock (_SyncLock) { if (Items.Count > 0) return Items.Dequeue(); } return null; } /// /// Optional routine that pings a Url on the server /// to keep the server alive. /// /// Use this to avoid IIS shutting down your AppPools /// public void PingServer(string url = null) { if (string.IsNullOrEmpty(url)) url = WebServerPingUrl; //if (Url.StartsWith("~") && HttpContext.Current != null) // Url = wwUtils.ResolveUrl(Url); try { #pragma warning disable SYSLIB0014 WebClient http = new WebClient(); #pragma warning restore SYSLIB0014 string Result = http.DownloadString(url); } catch {} } #region IDisposable Members public void Dispose() { Stop(); } #endregion } /// /// A simple item wrapper that allows separating items /// by type. /// public class SchedulerItem { /// /// Allows identifying items by type /// public string Type { get { return _Type; } set { _Type = value; } } private string _Type = ""; /// /// Any text data you want to submit /// public string TextData { get { return _TextData; } set { _TextData = value; } } private string _TextData = ""; /// /// Any binary data you want to submit /// public byte[] Data { get { return _Data; } set { _Data = value; } } private byte[] _Data = null; /// /// The initial date when the item was /// created or submitted. /// public DateTime Entered { get { return _Entered; } set { _Entered = value; } } private DateTime _Entered = DateTime.UtcNow; } } ================================================ FILE: Westwind.Utilities/SupportClasses/StringSerializer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace Westwind.Utilities { /// /// A very simple flat object serializer that can be used /// for intra application serialization. It creates a very compact /// positional string of properties. /// Only serializes top level properties, with no nesting support /// and only simple properties or those with a type converter are /// supported. Complex properties or non-two way type convertered /// values are ignored. /// /// Creates strings in the format of: /// Rick|rstrahl@west-wind.com|1|True|3/29/2013 1:32:31 PM|1 /// /// /// This component is meant for intra application serialization of /// very compact objects. A common use case is for state serialization /// for cookies or a Forms Authentication ticket to minimize the amount /// of space used - the output produced here contains only the actual /// data, no property info or validation like other serialization formats. /// Use only on small objects when size and speed matter otherwise use /// a JSON/XML/Binary serializer or the ASP.NET LosFormatter object. /// public static class StringSerializer { private const string Seperator_Replace_String = "-@-"; /// /// Serializes a flat object's properties into a String /// separated by a separator character/string. Only /// top level properties are serialized. /// /// /// Only serializes top level properties, with no nesting support /// and only simple properties or those with a type converter are /// 'serialized'. All other property types use ToString(). /// /// The object to serialize /// Optional separator character or string. Default is | /// public static string SerializeObject(object objectToSerialize, string separator = null) { if (separator == null) separator = "|"; if (objectToSerialize == null) return "null"; var properties = objectToSerialize.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public); //.OrderBy(prop => prop.Name.ToLower()) //.ToArray(); var values = new List(); for (int i = 0; i < properties.Length; i++) { var pi = properties[i]; // don't store read/write-only data if (!pi.CanRead && !pi.CanWrite) continue; object value = pi.GetValue(objectToSerialize, null); string stringValue = "null"; if (value != null) { if (value is string) { stringValue = (string)value; if (stringValue.Contains(separator)) stringValue = stringValue.Replace(separator, Seperator_Replace_String); } else stringValue = ReflectionUtils.TypedValueToString(value, unsupportedReturn: "null"); } values.Add(stringValue); } if (values.Count < 0) // empty object (no properties) return string.Empty; return string.Join(separator, values.ToArray()); } /// /// Deserializes an object previously serialized by SerializeObject. /// /// /// /// /// public static object DeserializeObject(string serialized, Type type, string separator = null) { if (serialized == "null") return null; if (separator == null) separator = "|"; object inst = ReflectionUtils.CreateInstanceFromType(type); var properties = inst.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public); //.OrderBy(prop => prop.Name.ToLower()) //.ToArray(); string[] tokens = serialized.Split(new string[] { separator }, StringSplitOptions.None); if (tokens == null || tokens.Length < 1) return null; for (int i = 0; i < properties.Length; i++) { string token = tokens[i]; var prop = properties[i]; // don't store read/write-only data if (!prop.CanRead && !prop.CanWrite) continue; if (token == null || token == "null") token = null; else token = token.Replace(Seperator_Replace_String, separator); object value = null; if (token != null) { try { value = ReflectionUtils.StringToTypedValue(token, prop.PropertyType); } catch (InvalidCastException) { // DANGER: skip over unsupported types continue; // leave value at default } } prop.SetValue(inst, value, null); } return inst; } /// /// Deserializes an object serialized with SerializeObject. /// /// /// /// /// public static T Deserialize(string serialized, string separator = null) where T : class, new() { return DeserializeObject(serialized, typeof(T), separator) as T; } } } ================================================ FILE: Westwind.Utilities/SupportClasses/UrlEncodingParser.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Specialized; namespace Westwind.Utilities { /// /// A query string or UrlEncoded form parser and editor /// class that allows reading and writing of urlencoded /// key value pairs used for query string and HTTP /// form data. /// /// Useful for parsing and editing querystrings inside /// of non-Web code that doesn't have easy access to /// the HttpUtility class. /// /// /// Supports multiple values per key /// public class UrlEncodingParser : NameValueCollection { /// /// Holds the original Url that was assigned if any /// Url must contain // to be considered a url /// private string Url { get; set; } /// /// Determines whether plus signs in the UrlEncoded content /// are treated as spaces. /// public bool DecodePlusSignsAsSpaces { get; set; } /// /// override indexer to ensure we always non-null value /// public new string this[string key] { get { return base[key] ?? string.Empty; } set { base[key] = value ?? string.Empty; } } /// /// Always pass in a UrlEncoded data or a URL to parse from /// unless you are creating a new one from scratch. /// /// /// Pass a query string or raw Form data, or a full URL. /// If a URL is parsed the part prior to the ? is stripped /// but saved. Then when you write the original URL is /// re-written with the new query string. /// public UrlEncodingParser(string queryStringOrUrl = null, bool decodeSpacesAsPlusSigns = false) { Url = string.Empty; DecodePlusSignsAsSpaces = decodeSpacesAsPlusSigns; if (!string.IsNullOrEmpty(queryStringOrUrl)) { Parse(queryStringOrUrl); } } /// /// Assigns multiple values to the same key /// /// /// public void SetValues(string key, IEnumerable values) { foreach (var val in values) Add(key, val); } /// /// Parses the query string into the internal dictionary /// and optionally also returns this dictionary /// /// /// Query string key value pairs or a full URL. If URL is /// passed the URL is re-written in Write operation /// /// public NameValueCollection Parse(string query) { if (string.IsNullOrEmpty(query)) { Clear(); return this; } if (Uri.IsWellFormedUriString(query, UriKind.Absolute)) Url = query; int index = query.IndexOf('?'); if (index > -1) { if (query.Length >= index + 1) query = query.Substring(index + 1); } var pairs = query.Split('&'); foreach (var pair in pairs) { int index2 = pair.IndexOf('='); if (index2 > 0) { var val = pair.Substring(index2 + 1); if (!string.IsNullOrEmpty(val)) { if (DecodePlusSignsAsSpaces) val = val.Replace("+", " "); val = Uri.UnescapeDataString(val); } Add(pair.Substring(0, index2), val); } } return this; } /// /// Writes out the urlencoded data/query string or full URL based /// on the internally set values. /// /// urlencoded data or url public override string ToString() { string query = string.Empty; foreach (string key in Keys) { string[] values = GetValues(key); if (values == null) continue; foreach (var val in values) { if (string.IsNullOrEmpty(val)) continue; query += key + "=" + Uri.EscapeDataString(val) + "&"; } } query = query.Trim('&'); if (!string.IsNullOrEmpty(Url)) { if (Url.Contains("?")) query = Url.Substring(0, Url.IndexOf('?') + 1) + query; else query = Url + "?" + query; } return query; } } } ================================================ FILE: Westwind.Utilities/SupportClasses/UrlParser.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Web; using System.Globalization; namespace Westwind.Utilities { /// /// Internally used class that is used to expand links in text /// strings. /// public class UrlParser { internal string Target = string.Empty; internal bool ParseFormattedLinks = false; /// /// Expands links into HTML hyperlinks inside of text or HTML. /// /// The text to expand /// Target frame where links are displayed /// Allows parsing of links in the following format [text|www.site.com] /// public static string ExpandUrls(string text, string target = null, bool parseFormattedLinks = false) { if (target == null) target = string.Empty; UrlParser Parser = new UrlParser(); Parser.Target = target; Parser.ParseFormattedLinks = parseFormattedLinks; return Parser.ExpandUrlsInternal(text); } /// /// Expands links into HTML hyperlinks inside of text or HTML. /// /// The text to expand /// private string ExpandUrlsInternal(string text) { MatchEvaluator matchEval = null; string pattern = null; string updated = null; // Expand embedded hyperlinks System.Text.RegularExpressions.RegexOptions options = RegexOptions.Multiline | RegexOptions.IgnoreCase; if (ParseFormattedLinks) { pattern = @"\[(.*?)\|(.*?)]"; matchEval = new MatchEvaluator(ExpandFormattedLinks); updated = Regex.Replace(text, pattern, matchEval, options); } else updated = text; pattern = @"([""'=]|")?(http://|ftp://|https://|www\.|ftp\.[\w]+)([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])"; matchEval = new MatchEvaluator(ExpandUrlsRegExEvaluator); updated = Regex.Replace(updated, pattern, matchEval, options); return updated; } /// /// Internal RegExEvaluator callback /// /// /// private string ExpandUrlsRegExEvaluator(System.Text.RegularExpressions.Match M) { string Href = M.Value; // M.Groups[0].Value; // if string starts within an HREF don't expand it if (Href.StartsWith("=") || Href.StartsWith("'") || Href.StartsWith("\"") || Href.StartsWith(""")) return Href; string Text = Href; if (Href.IndexOf("://") < 0) { if (Href.StartsWith("www.")) Href = "http://" + Href; else if (Href.StartsWith("ftp")) Href = "ftp://" + Href; else if (Href.IndexOf("@") > -1) Href = "mailto:" + Href; } string Targ = !string.IsNullOrEmpty(Target) ? " target='" + Target + "'" : string.Empty; return "" + Text + ""; } private string ExpandFormattedLinks(System.Text.RegularExpressions.Match M) { //string Href = M.Value; // M.Groups[0].Value; string Text = M.Groups[1].Value; string Href = M.Groups[2].Value; if (Href.IndexOf("://") < 0) { if (Href.StartsWith("www.")) Href = "http://" + Href; else if (Href.StartsWith("ftp")) Href = "ftp://" + Href; else if (Href.IndexOf("@") > -1) Href = "mailto:" + Href; else Href = "http://" + Href; } string Targ = !string.IsNullOrEmpty(Target) ? " target='" + Target + "'" : string.Empty; return "" + Text + ""; } } } ================================================ FILE: Westwind.Utilities/Utilities/AsyncUtils.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Westwind.Utilities { /// /// Helper class to run async methods within a sync process. /// Source: https://www.ryadel.com/en/asyncutil-c-helper-class-async-method-sync-result-wait/ /// public static class AsyncUtils { private static readonly TaskFactory _taskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); /// /// Executes an async Task method which has a void return value synchronously /// USAGE: AsyncUtil.RunSync(() => AsyncMethod()); /// /// Task method to execute public static void RunSync(Func task) => _taskFactory .StartNew(task) .Unwrap() .GetAwaiter() .GetResult(); /// /// Executes an async Task method which has a void return value synchronously /// USAGE: AsyncUtil.RunSync(() => AsyncMethod()); /// /// Task method to execute public static void RunSync(Func task, CancellationToken cancellationToken, TaskCreationOptions taskCreation = TaskCreationOptions.None, TaskContinuationOptions taskContinuation = TaskContinuationOptions.None, TaskScheduler taskScheduler = null) { if (taskScheduler == null) taskScheduler = TaskScheduler.Default; new TaskFactory(cancellationToken, taskCreation, taskContinuation, taskScheduler) .StartNew(task) .Unwrap() .GetAwaiter() .GetResult(); } /// /// Executes an async Task<T> method which has a T return type synchronously /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>()); /// /// Return Type /// Task<T> method to execute /// public static TResult RunSync(Func> task) => _taskFactory .StartNew(task) .Unwrap() .GetAwaiter() .GetResult(); /// /// Executes an async Task<T> method which has a T return type synchronously /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>()); /// /// Return Type /// Task<T> method to execute /// public static TResult RunSync(Func> func, CancellationToken cancellationToken, TaskCreationOptions taskCreation = TaskCreationOptions.None, TaskContinuationOptions taskContinuation = TaskContinuationOptions.None, TaskScheduler taskScheduler = null) { if (taskScheduler == null) taskScheduler = TaskScheduler.Default; return new TaskFactory(cancellationToken, taskCreation, taskContinuation, taskScheduler) .StartNew(func, cancellationToken) .Unwrap() .GetAwaiter() .GetResult(); } /// /// Ensures safe operation of a task without await even if /// an execution fails with an exception. This forces the /// exception to be cleared unlike a non-continued task. /// /// Exceptions are silently ignored. /// /// Task Instance public static void FireAndForget(this Task t) { t.ContinueWith(tsk => tsk.Exception, TaskContinuationOptions.OnlyOnFaulted); } /// /// Ensures safe operation of a task without await even if /// an execution fails with an exception. This forces the /// exception to be cleared unlike a non-continued task. /// /// This version allows you to capture and respond to any /// exceptions caused by the Task code executing. /// /// /// Action delegate that receives an Exception parameter you can use to log or otherwise handle (or ignore) any exceptions public static void FireAndForget(this Task t, Action del) { t.ContinueWith((tsk) => del?.Invoke(tsk.Exception), TaskContinuationOptions.OnlyOnFaulted); } /// /// Wait for a task to complete with a timeout /// /// Exceptions are thrown as normal if the task fails as /// /// Task to wait on /// timeout to allow /// True if completed in time, false if timed out. If true task is completed and you can read the result /// Any exceptions thrown by the task code" public static async Task Timeout(this Task task, int timeoutMs) { var completed = await Task.WhenAny(task, Task.Delay(timeoutMs)); if (task.IsFaulted) throw task.Exception.GetBaseException(); return completed == task && task.IsCompleted; } /// /// Wait for a task with a result and a timeout. If the task times out an /// the `default` value is returned. /// /// Task to wait on /// timeout to allow /// Task result on success. Or exceptions for timeout or task operation exception /// Thrown if the task times out /// Any exceptions thrown by the task code public static async Task TimeoutWithResult(this Task task, int timeoutMs) { var completed = await Task.WhenAny(task, Task.Delay(timeoutMs)); if (task.IsFaulted) throw task.Exception.GetBaseException(); if (completed == task && task.IsCompleted) { return task.Result; } throw new TimeoutException(); } /// /// Executes an Action after a delay /// /// /// Code is executed on a background thread, so if UI code is executed /// make sure you marshal back to the UI thread using a Dispatcher or Control.Invoke(). /// /// delay in Milliseconds /// Action to execute after delay public static void DelayExecution(int delayMs, Action action, Action errorHandler = null) { var t = new System.Timers.Timer(); t.Interval = delayMs; t.AutoReset = false; t.Elapsed += (s, e) => { t.Stop(); try { action.Invoke(); } catch(Exception ex) { errorHandler?.Invoke(ex); } t.Dispose(); }; t.Start(); } /// /// Executes an Action after a delay with a parameter /// /// /// Code is executed on a background thread, so if UI code is executed /// make sure you marshal back to the UI thread using a Dispatcher or Control.Invoke(). /// /// delay in Milliseconds /// Action to execute after delay public static void DelayExecution(int delayMs, Action action, T parm = default, Action errorHandler = null) { var t = new System.Timers.Timer(); t.Interval = delayMs; t.AutoReset = false; t.Elapsed += (s, e) => { t.Stop(); try { action.Invoke(parm); } catch(Exception ex) { errorHandler?.Invoke(ex); } t.Dispose(); }; t.Start(); return; } } } ================================================ FILE: Westwind.Utilities/Utilities/ComObject.cs ================================================ using System; using System.Dynamic; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace Westwind.Utilities { /// /// Wrapper around a COM object that allows 'dynamic' like behavior to /// work in .NET Core where dynamic with COM objects is not working. This /// /// Credit to: https://github.com/bubibubi/EntityFrameworkCore.Jet/blob/3.1-preview/src/System.Data.Jet/ComObject.cs /// Added here with slight interface modifications /// #if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] #endif public class ComObject : DynamicObject, IDisposable { internal object _instance; #if DEBUG private readonly Guid Id = Guid.NewGuid(); #endif /// /// Pass a COM Object reference to create this COM Object wrapper /// /// public ComObject(object instance) { if (instance is ComObject) _instance = ((ComObject)instance)._instance; _instance = instance; } /// /// Create a new instance based on ProgId /// /// /// public static ComObject CreateFromProgId(string progid) { var type = Type.GetTypeFromProgID(progid, false); if (type != null) { var instance = Activator.CreateInstance(type); if (instance != null) { return new ComObject(instance); } } throw new TypeLoadException("Couldn't create COM Wrapper for: " + progid); } /// /// Create a new instance based on ClassId /// /// Guid class Id /// public static ComObject CreateFirstFrom(Guid clsid) { var type = Type.GetTypeFromCLSID(clsid, false); if (type != null) { var instance = Activator.CreateInstance(type); if (instance != null) { return new ComObject(instance); } } throw new TypeLoadException("Couldn't create COM Wrapper for: " + clsid); } /// /// Creates a new instance based on a ProgId /// /// public ComObject(string progid) : this(Activator.CreateInstance(Type.GetTypeFromProgID(progid, true))) { } /// /// Creates an instance based on a class Id /// /// public ComObject(Guid clsid) : this(Activator.CreateInstance(Type.GetTypeFromCLSID(clsid, true))) { } /// /// Removes the COM reference linkage from this object /// /// public object Detach() { var instance = _instance; _instance = null; return instance; } #region DynamicObject implementation public override bool TryGetMember(GetMemberBinder binder, out object result) { result = WrapIfRequired( _instance.GetType() .InvokeMember( binder.Name, BindingFlags.GetProperty, Type.DefaultBinder, _instance, new object[0] )); return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { _instance.GetType() .InvokeMember( binder.Name, BindingFlags.SetProperty, Type.DefaultBinder, _instance, new[] { value is ComObject comObject ? comObject._instance : value } ); return true; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { result = WrapIfRequired( _instance.GetType() .InvokeMember( binder.Name, BindingFlags.InvokeMethod, Type.DefaultBinder, _instance, args )); return true; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { // This should work for all specific interfaces derived from `_Collection` (like `_Tables`) in ADOX. result = WrapIfRequired( _instance.GetType() .InvokeMember( "Item", BindingFlags.GetProperty, Type.DefaultBinder, _instance, indexes )); return true; } #endregion // See https://github.com/dotnet/runtime/issues/12587#issuecomment-578431424 /// /// Wrap any embedded raw COM objects in a new ComObject wrapper as well /// when returned from members or method results. /// /// /// private static object WrapIfRequired(object obj) => obj != null && obj.GetType().IsCOMObject ? new ComObject(obj) : obj; public void Dispose() { // The RCW is a .NET object and cannot be released from the finalizer, // because it might not exist anymore. if (_instance != null) { Marshal.ReleaseComObject(_instance); _instance = null; } GC.SuppressFinalize(this); } } } ================================================ FILE: Westwind.Utilities/Utilities/DataUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * � West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Data; using System.Linq; using System.Reflection; using System.Collections.Generic; using System.Text; using System.Configuration; using Westwind.Utilities.Properties; using System.Data.Common; using System.IO; namespace Westwind.Utilities { /// /// Utility library for common data operations. /// public static class DataUtils { const BindingFlags MemberAccess = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase; //const BindingFlags MemberPublicInstanceAccess = // BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; #region Unique Ids and Random numbers /// /// Generates a unique Id as a string of up to 16 characters. /// Based on a GUID and the size takes that subset of a the /// Guid's 16 bytes to create a string id. /// /// String Id contains numbers and lower case alpha chars 36 total. /// /// Sizes: 6 gives roughly 99.97% uniqueness. /// 8 gives less than 1 in a million doubles. /// 16 will give near full GUID strength uniqueness /// /// Number of characters to generate between 5 and 16 /// Any additional characters you allow in the string. /// You can add upper case letters and symbols which are not included in the default /// which includes only digits and lower case letters. /// /// public static string GenerateUniqueId(int stringSize = 8, string additionalCharacters = null) { string chars = "abcdefghijkmnopqrstuvwxyz1234567890" + additionalCharacters; StringBuilder result = new StringBuilder(stringSize); int count = 0; if (stringSize < 5) stringSize = 5; byte[] array = Guid.NewGuid().ToByteArray(); for (int i = array.Length - 1; i >= 0; i--) { byte b = array[i]; result.Append(chars[b % (chars.Length)]); count++; if (count >= stringSize) break; } return result.ToString(); } /// /// Generates a unique numeric ID. Generated off a GUID and /// returned as a 64 bit long value /// /// public static long GenerateUniqueNumericId() { byte[] bytes = Guid.NewGuid().ToByteArray(); return (long)BitConverter.ToUInt64(bytes, 0); } private static Random rnd = new Random(); /// /// Returns a random integer in a range of numbers /// a single seed value. /// /// The minimum value /// The max value *including* this value (unlike Random.Next() which doesn't include it) /// public static int GetRandomNumber(int min, int max) { return rnd.Next(min, max + 1); } #endregion #region Byte Data /// /// Returns an index into a byte array to find sequence of /// of bytes. /// Note: You can use Span.IndexOf() where available instead. /// /// byte array to be searched /// bytes to find /// public static int IndexOfByteArray(byte[] buffer, byte[] bufferToFind) { if (buffer.Length == 0 || bufferToFind.Length == 0) return -1; for (int i = 0; i < buffer.Length; i++) { if (buffer[i] == bufferToFind[0]) { bool innerMatch = true; for (int j = 1; j < bufferToFind.Length; j++) { if (buffer[i + j] != bufferToFind[j]) { innerMatch = false; break; } } if (innerMatch) return i; } } return -1; } /// /// Returns an index into a byte array to find a string in the byte array. /// Exact match using the encoding provided or UTF-8 by default. /// /// Source buffer to look for string /// string to search for (case sensitive) /// Optional encoding to use - defaults to UTF-8 if null /// public static int IndexOfByteArray(byte[] buffer, string stringToFind, Encoding encoding = null) { if (encoding == null) encoding = Encoding.UTF8; if (buffer.Length == 0 || string.IsNullOrEmpty(stringToFind)) return -1; var bytes = encoding.GetBytes(stringToFind); return IndexOfByteArray(buffer, bytes); } /// /// Removes a sequence of bytes from a byte array /// /// /// /// public static byte[] RemoveBytes(byte[] buffer, byte[] bytesToRemove) { if (buffer == null || buffer.Length == 0 || bytesToRemove == null || bytesToRemove.Length == 0) return buffer; using (var ms = new MemoryStream()) using(var bw = new BinaryWriter(ms)) { var firstByte = bytesToRemove[0]; for (int i = 0; i < buffer.Length; i++) { var current = buffer[i]; if (current == firstByte && buffer.Length >= i + bytesToRemove.Length) { bool found = true; for (int y = 1; y < bytesToRemove.Length; y++) { if (buffer[i + y] != bytesToRemove[y]) { found = false; break; } } if (found) i += bytesToRemove.Length -1; // skip over else bw.Write(current); } else bw.Write(current); } return ms.ToArray(); } } #endregion #region Copying Objects and Data /// /// Copies the content of a data row to another. Runs through the target's fields /// and looks for fields of the same name in the source row. Structure must mathc /// or fields are skipped. /// /// /// /// public static bool CopyDataRow(DataRow source, DataRow target) { DataColumnCollection columns = target.Table.Columns; for (int x = 0; x < columns.Count; x++) { string fieldname = columns[x].ColumnName; try { target[x] = source[fieldname]; } catch {; } // skip any errors } return true; } /// /// Populates an object passed in from values in /// a data row that's passed in. /// /// Data row with values to fill from /// Object to file values from data row public static void CopyObjectFromDataRow(DataRow row, object targetObject, MemberInfo[] cachedMemberInfo = null) { if (cachedMemberInfo == null) { cachedMemberInfo = targetObject.GetType() .FindMembers(MemberTypes.Field | MemberTypes.Property, ReflectionUtils.MemberAccess, null, null); } foreach (MemberInfo Field in cachedMemberInfo) { string Name = Field.Name; if (!row.Table.Columns.Contains(Name)) continue; object value = row[Name]; if (value == DBNull.Value) value = null; if (Field.MemberType == MemberTypes.Field) { ((FieldInfo)Field).SetValue(targetObject, value); } else if (Field.MemberType == MemberTypes.Property) { ((PropertyInfo)Field).SetValue(targetObject, value, null); } } } /// /// Copies the content of an object to a DataRow with matching field names. /// Both properties and fields are copied. If a field copy fails due to a /// type mismatch copying continues but the method returns false /// /// /// /// public static bool CopyObjectToDataRow(DataRow row, object target) { bool result = true; MemberInfo[] miT = target.GetType().FindMembers(MemberTypes.Field | MemberTypes.Property, MemberAccess, null, null); foreach (MemberInfo Field in miT) { string name = Field.Name; if (!row.Table.Columns.Contains(name)) continue; try { if (Field.MemberType == MemberTypes.Field) { row[name] = ((FieldInfo)Field).GetValue(target) ?? DBNull.Value; } else if (Field.MemberType == MemberTypes.Property) { row[name] = ((PropertyInfo)Field).GetValue(target, null) ?? DBNull.Value; } } catch { result = false; } } return result; } /// /// Coverts a DataTable to a typed list of items /// /// Type to /// /// public static List DataTableToTypedList(DataTable dsTable) where T : class, new() { var objectList = new List(); MemberInfo[] cachedMemberInfo = null; foreach (DataRow dr in dsTable.Rows) { var obj = default(T); // Activator.CreateInstance(); CopyObjectFromDataRow(dr, obj, cachedMemberInfo); objectList.Add(obj); } return objectList; } /// /// Copies the content of one object to another. The target object 'pulls' properties of the first. /// /// /// public static void CopyObjectData(object source, Object target) { CopyObjectData(source, target, MemberAccess); } /// /// Copies the content of one object to another. The target object 'pulls' properties of the first. /// /// /// /// public static void CopyObjectData(object source, Object target, BindingFlags memberAccess) { CopyObjectData(source, target, null, memberAccess); } /// /// Copies the content of one object to another. The target object 'pulls' properties of the first. /// /// /// /// public static void CopyObjectData(object source, Object target, string excludedProperties) { CopyObjectData(source, target, excludedProperties, MemberAccess); } /// /// Copies the data of one object to another. The target object 'pulls' properties of the first. /// This any matching properties are written to the target. /// /// The object copy is a shallow copy only. Any nested types will be copied as /// whole values rather than individual property assignments (ie. via assignment) /// /// The source object to copy from /// The object to copy to /// A comma delimited list of properties that should not be copied /// Reflection binding access public static void CopyObjectData(object source, object target, string excludedProperties = null, BindingFlags memberAccess = MemberAccess) { string[] excluded = null; if (!string.IsNullOrEmpty(excludedProperties)) excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); MemberInfo[] miT = target.GetType().GetMembers(memberAccess); foreach (MemberInfo Field in miT) { string name = Field.Name; // Skip over any property exceptions if (!string.IsNullOrEmpty(excludedProperties) && excluded.Contains(name)) continue; if (Field.MemberType == MemberTypes.Field) { FieldInfo SourceField = source.GetType().GetField(name); if (SourceField == null) continue; object SourceValue = SourceField.GetValue(source); ((FieldInfo)Field).SetValue(target, SourceValue); } else if (Field.MemberType == MemberTypes.Property) { PropertyInfo piTarget = Field as PropertyInfo; PropertyInfo SourceField = source.GetType().GetProperty(name, memberAccess); if (SourceField == null) continue; if (piTarget.CanWrite && SourceField.CanRead) { object SourceValue = SourceField.GetValue(source, null); piTarget.SetValue(target, SourceValue, null); } } } } #endregion #region DataTable and DataReader /// /// Coverts a DataTable to a typed list of items /// /// /// /// public static List DataTableToObjectList(DataTable dsTable) where T : class, new() { var objectList = new List(); foreach (DataRow dr in dsTable.Rows) { var obj = Activator.CreateInstance(); CopyObjectFromDataRow(dr, obj); objectList.Add(obj); } return objectList; } /// /// Creates a list of a given type from all the rows in a DataReader. /// /// Note this method uses Reflection so this isn't a high performance /// operation, but it can be useful for generic data reader to entity /// conversions on the fly and with anonymous types. /// /// /// An open DataReader that's in position to read /// Optional - comma delimited list of fields that you don't want to update /// /// Optional - Cached PropertyInfo dictionary that holds property info data for this object. /// Can be used for caching hte PropertyInfo structure for multiple operations to speed up /// translation. If not passed automatically created. /// /// /// DataReader is not closed by this method. Make sure you call reader.close() afterwards public static List DataReaderToObjectList(IDataReader reader, string propertiesToSkip = null, Dictionary piList = null) where T : new() { List list = new List(); using (reader) { // Get a list of PropertyInfo objects we can cache for looping if (piList == null) { piList = new Dictionary(); var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var prop in props) piList.Add(prop.Name.ToLower(), prop); } while (reader.Read()) { T inst = new T(); DataReaderToObject(reader, inst, propertiesToSkip, piList); list.Add(inst); } } return list; } /// /// Creates an IEnumerable of T from an open DataReader instance. /// /// Note this method uses Reflection so this isn't a high performance /// operation, but it can be useful for generic data reader to entity /// conversions on the fly and with anonymous types. /// /// An open DataReader that's in position to read /// Optional - comma delimited list of fields that you don't want to update /// /// Optional - Cached PropertyInfo dictionary that holds property info data for this object. /// Can be used for caching hte PropertyInfo structure for multiple operations to speed up /// translation. If not passed automatically created. /// /// public static IEnumerable DataReaderToIEnumerable(IDataReader reader, string propertiesToSkip = null, Dictionary piList = null) where T : new() { if (reader != null) { using (reader) { // Get a list of PropertyInfo objects we can cache for looping if (piList == null) { piList = new Dictionary(); var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var prop in props) piList.Add(prop.Name.ToLower(), prop); } while (reader.Read()) { T inst = new T(); DataReaderToObject(reader, inst, propertiesToSkip, piList); yield return inst; } } } } public static List DataReaderToList(IDataReader reader, string propertiesToSkip = null, Dictionary piList = null) where T : new() { var list = new List(); if (reader != null) { using (reader) { // Get a list of PropertyInfo objects we can cache for looping if (piList == null) { piList = new Dictionary(); var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var prop in props) piList.Add(prop.Name.ToLower(), prop); } while (reader.Read()) { T inst = new T(); DataReaderToObject(reader, inst, propertiesToSkip, piList); list.Add(inst); } } } return list; } /// /// Populates the properties of an object from a single DataReader row using /// Reflection by matching the DataReader fields to a public property on /// the object passed in. Unmatched properties are left unchanged. /// /// You need to pass in a data reader located on the active row you want /// to serialize. /// /// This routine works best for matching pure data entities and should /// be used only in low volume environments where retrieval speed is not /// critical due to its use of Reflection. /// /// 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) /// Instance of the object to populate properties on /// Optional - A comma delimited list of object properties that should not be updated /// Optional - Cached PropertyInfo dictionary that holds property info data for this object public static void DataReaderToObject(IDataReader reader, object instance, string propertiesToSkip = null, Dictionary piList = null) { if (reader.IsClosed) throw new InvalidOperationException(Resources.DataReaderPassedToDataReaderToObjectCannot); if (string.IsNullOrEmpty(propertiesToSkip)) propertiesToSkip = string.Empty; else propertiesToSkip = "," + propertiesToSkip + ","; propertiesToSkip = propertiesToSkip.ToLower(); // create a dictionary of properties to look up // we can pass this in so we can cache the list once // for a list operation if (piList == null || piList.Count < 1) { if (piList == null) piList = new Dictionary(); var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var prop in props) piList.Add(prop.Name.ToLower(), prop); } for (int index = 0; index < reader.FieldCount; index++) { string name = reader.GetName(index).ToLower(); if (piList.ContainsKey(name)) { var prop = piList[name]; // skip fields in skip list if (!string.IsNullOrEmpty(propertiesToSkip) && propertiesToSkip.Contains("," + name + ",")) continue; // find writable properties and assign if ((prop != null) && prop.CanWrite) { var val = reader.GetValue(index); if (val == DBNull.Value) val = null; // deal with data drivers return bit values as int64 or in else if (prop.PropertyType == typeof(bool) && (val is long || val is int)) val = Convert.ToInt64(val) == 1 ? true : false; // int conversions when the value is not different type of number else if (prop.PropertyType == typeof(int) && (val is long || val is decimal)) val = Convert.ToInt32(val); prop.SetValue(instance, val, null); } } } return; } /// /// The default SQL date used by InitializeDataRowWithBlanks. Considered a blank date instead of null. /// internal static DateTime MinimumSqlDate = DateTime.Parse("01/01/1900"); /// /// Initializes a Datarow containing NULL values with 'empty' values instead. /// Empty values are: /// String - "" /// all number types - 0 or 0.00 /// DateTime - Value of MinimumSqlData (1/1/1900 by default); /// Boolean - false /// Binary values and timestamps are left alone /// /// DataRow to be initialized public static void InitializeDataRowWithBlanks(DataRow row) { DataColumnCollection loColumns = row.Table.Columns; for (int x = 0; x < loColumns.Count; x++) { if (!row.IsNull(x)) continue; string lcRowType = loColumns[x].DataType.Name; if (lcRowType == "String") row[x] = string.Empty; else if (lcRowType.StartsWith("Int")) row[x] = 0; else if (lcRowType == "Byte") row[x] = 0; else if (lcRowType == "Decimal") row[x] = 0.00M; else if (lcRowType == "Double") row[x] = 0.00; else if (lcRowType == "Boolean") row[x] = false; else if (lcRowType == "DateTime") row[x] = DataUtils.MinimumSqlDate; // Everything else isn't handled explicitly and left alone // Byte[] most specifically } } #endregion #region Type Conversions /// /// Maps a SqlDbType to a .NET type /// /// /// public static Type SqlTypeToDotNetType(SqlDbType sqlType) { if (sqlType == SqlDbType.NText || sqlType == SqlDbType.Text || sqlType == SqlDbType.VarChar || sqlType == SqlDbType.NVarChar) return typeof(string); else if (sqlType == SqlDbType.Int) return typeof(Int32); else if (sqlType == SqlDbType.Decimal || sqlType == SqlDbType.Money) return typeof(decimal); else if (sqlType == SqlDbType.Bit) return typeof(Boolean); else if (sqlType == SqlDbType.DateTime || sqlType == SqlDbType.DateTime2) return typeof(DateTime); else if (sqlType == SqlDbType.Char || sqlType == SqlDbType.NChar) return typeof(char); else if (sqlType == SqlDbType.Float) return typeof(Single); else if (sqlType == SqlDbType.Real) return typeof(Double); else if (sqlType == SqlDbType.BigInt) return typeof(Int64); else if (sqlType == SqlDbType.Image) return typeof(byte[]); else if (sqlType == SqlDbType.SmallInt) return typeof(byte); throw new InvalidCastException("Unable to convert " + sqlType.ToString() + " to .NET type."); } /// /// Maps a DbType to a .NET native type /// /// /// public static Type DbTypeToDotNetType(DbType sqlType) { if (sqlType == DbType.String || sqlType == DbType.StringFixedLength || sqlType == DbType.AnsiString) return typeof(string); else if (sqlType == DbType.Int16 || sqlType == DbType.Int32) return typeof(Int32); else if (sqlType == DbType.Int64) return typeof(Int64); else if (sqlType == DbType.Decimal || sqlType == DbType.Currency) return typeof(decimal); else if (sqlType == DbType.Boolean) return typeof(Boolean); else if (sqlType == DbType.DateTime || sqlType == DbType.DateTime2 || sqlType == DbType.Date) return typeof(DateTime); else if (sqlType == DbType.Single) return typeof(Single); else if (sqlType == DbType.Double) return typeof(Double); else if (sqlType == DbType.Binary) return typeof(byte[]); else if (sqlType == DbType.SByte || sqlType == DbType.Byte) return typeof(byte); else if (sqlType == DbType.Guid) return typeof(Guid); else if (sqlType == DbType.Binary) return typeof(byte[]); throw new InvalidCastException("Unable to convert " + sqlType.ToString() + " to .NET type."); } /// /// Converts a .NET type into a DbType value /// /// /// public static DbType DotNetTypeToDbType(Type type) { if (type == typeof(string)) return DbType.String; else if (type == typeof(Int32)) return DbType.Int32; else if (type == typeof(Int16)) return DbType.Int16; else if (type == typeof(Int64)) return DbType.Int64; else if (type == typeof(Guid)) return DbType.Guid; else if (type == typeof(decimal)) return DbType.Decimal; else if (type == typeof(double) || type == typeof(float)) return DbType.Double; else if (type == typeof(Single)) return DbType.Single; else if (type == typeof(bool) || type == typeof(Boolean)) return DbType.Boolean; else if (type == typeof(DateTime)) return DbType.DateTime; else if (type == typeof(DateTimeOffset)) return DbType.DateTimeOffset; else if (type == typeof(byte)) return DbType.Byte; else if (type == typeof(byte[])) return DbType.Binary; throw new InvalidCastException(string.Format("Unable to cast {0} to a DbType", type.Name)); } /// /// Converts a .NET type into a SqlDbType. /// /// /// public static SqlDbType DotNetTypeToSqlType(Type type) { if (type == typeof(string)) return SqlDbType.NVarChar; else if (type == typeof(Int32)) return SqlDbType.Int; else if (type == typeof(Int16)) return SqlDbType.SmallInt; else if (type == typeof(Int64)) return SqlDbType.BigInt; else if (type == typeof(Guid)) return SqlDbType.UniqueIdentifier; else if (type == typeof(decimal)) return SqlDbType.Decimal; else if (type == typeof(double) || type == typeof(float)) return SqlDbType.Float; else if (type == typeof(Single)) return SqlDbType.Float; else if (type == typeof(bool) || type == typeof(Boolean)) return SqlDbType.Bit; else if (type == typeof(DateTime)) return SqlDbType.DateTime; else if (type == typeof(DateTimeOffset)) return SqlDbType.DateTimeOffset; else if (type == typeof(byte)) return SqlDbType.SmallInt; else if (type == typeof(byte[])) return SqlDbType.Image; throw new InvalidCastException(string.Format("Unable to cast {0} to a DbType", type.Name)); } #endregion } public enum DataAccessProviderTypes { SqlServer, SqLite, MySql, PostgreSql, #if NETFULL OleDb, SqlServerCompact #endif } } ================================================ FILE: Westwind.Utilities/Utilities/DebugUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * (c) West Wind Technologies, 2011 * http://www.west-wind.com/ * * Created: 06/12/2011 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Westwind.Utilities { /// /// DebugUtils class contains various utility methods /// for debugging and diagnostic tasks /// public static class DebugUtils { /// /// Returns the innermost Exception for an object /// /// /// [Obsolete("Use Exception.GetBaseException() instead")] public static Exception GetInnerMostException(Exception ex) { Exception currentEx = ex; while (currentEx.InnerException != null) { currentEx = currentEx.InnerException; } return currentEx; } /// /// Returns an array of the entire exception list in reverse order /// (innermost to outermost exception) /// /// The original exception to work off /// Array of Exceptions from innermost to outermost public static Exception[] GetInnerExceptions(Exception ex) { List exceptions = new List(); exceptions.Add(ex); Exception currentEx = ex; while (currentEx.InnerException != null) { currentEx = currentEx.InnerException; exceptions.Add(currentEx); } // Reverse the order to the innermost is first exceptions.Reverse(); return exceptions.ToArray(); } /// /// Returns the text with a prefix of line numbers /// /// /// Line format used to create the line. 0 is the line number, 1 is the text. /// public static string GetTextWithLineNumbers(string text, string lineFormat = "{0}. {1}") { if (string.IsNullOrEmpty(text)) return text; var sb = new StringBuilder(); var lines = text.GetLines(); var width = 2; if (lines.Length > 9999) width = 5; else if (lines.Length > 999) width = 4; else if (lines.Length > 99) width = 3; else if (lines.Length < 10) width = 1; lineFormat += "\r\n"; for (var index = 1; index <= lines.Length; index++) { var lineNum = index.ToString().PadLeft(width, ' '); sb.AppendFormat(lineFormat, lineNum, lines[index - 1]); } return sb.ToString(); } /// /// Parses a stack trace and tries to return the source code line that caused the exception in /// a semi-formatted way if the stack trace returns line numbers (ie. Debug infor is available) /// /// A string of a full stack trace /// A stacktrace with the code line from source if source is available via Debug. Otherwise the raw stacktrace is returned. public static string ParseStackTrace(string stackTrace) { if (string.IsNullOrEmpty(stackTrace)) return null; string source = stackTrace; try { var firstLine = StringUtils.GetLines(stackTrace, 1)[0]; if (!string.IsNullOrEmpty(firstLine) && firstLine.Contains(".cs:line ")) { firstLine = StringUtils.ExtractString(firstLine, " in ", "xxx", allowMissingEndDelimiter: true); var tokens = firstLine.Split(new[] { ":line " }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length > 0 && System.IO.File.Exists(tokens[0])) { var line = int.Parse(tokens[1]); var fc = File.ReadAllText(tokens[0]); var lines = StringUtils.GetLines(fc); source = lines[line - 1].Trim(); stackTrace = source + stackTrace; } } } catch { } return stackTrace; } } } ================================================ FILE: Westwind.Utilities/Utilities/ExensionMethods/DateTimeExtensions.cs ================================================ using System; namespace Westwind.Utilities.Extensions { public static class DateTimeExtensions { /// /// Returns true if the date is between or equal to one of the two values. /// /// DateTime Base, from where the calculation will be preformed. /// Start date to check for /// End date to check for /// boolean value indicating if the date is between or equal to one of the two values public static bool Between(this DateTime date, DateTime startDate, DateTime endDate) { var ticks = date.Ticks; return ticks >= startDate.Ticks && ticks <= endDate.Ticks; } /// /// Returns 12:59:59pm time for the date passed. /// Useful for date only search ranges end value /// /// Date to convert /// public static DateTime EndOfDay(this DateTime date) { return date.Date.AddDays(1).AddMilliseconds(-1); } /// /// Returns 12:00am time for the date passed. /// Useful for date only search ranges start value /// /// Date to convert /// public static DateTime BeginningOfDay(this DateTime date) { return date.Date; } /// /// Returns the very end of the given month (the last millisecond of the last hour for the given date) /// /// DateTime Base, from where the calculation will be preformed. /// Returns the very end of the given month (the last millisecond of the last hour for the given date) public static DateTime EndOfMonth(this DateTime obj) { return new DateTime(obj.Year, obj.Month, DateTime.DaysInMonth(obj.Year, obj.Month), 23, 59, 59, 999); } /// /// Returns the Start of the given month (the fist millisecond of the given date) /// /// DateTime Base, from where the calculation will be preformed. /// Returns the Start of the given month (the fist millisecond of the given date) public static DateTime BeginningOfMonth(this DateTime obj) { return new DateTime(obj.Year, obj.Month, 1, 0, 0, 0, 0); } } } ================================================ FILE: Westwind.Utilities/Utilities/ExensionMethods/DictionaryExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Text; using System.Linq; using System.Xml.Linq; using Westwind.Utilities; using System.Collections; using System.Collections.Specialized; using System.Xml.Serialization; using System.Xml; namespace Westwind.Utilities.Extensions { /// /// Extends dictionary classes with XML /// public static class DictionaryExtensions { /// /// Serializes the dictionary to an XML string /// /// public static string ToXml(this IDictionary items, string root = "root") { var rootNode = new XElement(root); foreach (DictionaryEntry item in items) { string xmlType = XmlUtils.MapTypeToXmlType(item.Value.GetType()); XAttribute typeAttr = null; // if it's a simple type use it if (!string.IsNullOrEmpty(xmlType)) { typeAttr = new XAttribute("type", xmlType); rootNode.Add( new XElement(item.Key as string, typeAttr, item.Value) ); } else { // complex type use serialization string xmlString = null; if (SerializationUtils.SerializeObject(item.Value, out xmlString)) { XElement el = XElement.Parse(xmlString); rootNode.Add( new XElement(item.Key as string, new XAttribute("type", "___" + item.Value.GetType().FullName), el)); } } } return rootNode.ToString(); } /// /// Loads the dictionary from an Xml string /// /// public static void FromXml(this IDictionary items, string xml) { items.Clear(); var root = XElement.Parse(xml); foreach (XElement el in root.Elements()) { string typeString = null; var typeAttr = el.Attribute("type"); if (typeAttr != null) typeString = typeAttr.Value; string val = el.Value; if (!string.IsNullOrEmpty(typeString) && typeString != "string" && !typeString.StartsWith("__")) { // Simple type we know how to convert Type type = XmlUtils.MapXmlTypeToType(typeString); if (type != null) items.Add(el.Name.LocalName, ReflectionUtils.StringToTypedValue(val, type)); else items.Add(el.Name.LocalName, val); } else if (typeString.StartsWith("___")) { Type type = ReflectionUtils.GetTypeFromName(typeString.Substring(3)); object serializationUtilsDeSerializeObject = SerializationUtils.DeSerializeObject(el.Elements().First().CreateReader(), type); items.Add(el.Name.LocalName, serializationUtilsDeSerializeObject); } else // it's a string or unknown type items.Add(el.Name.LocalName, val); } } } } ================================================ FILE: Westwind.Utilities/Utilities/ExensionMethods/LinqExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Westwind.Utilities.Linq { public static class LinqExtensions { /// /// Recursively flattens a tree structure of nested same type enumerables /// into a flat structure. /// /// Example: /// Flattening a tree of documentation topics into a flat list of topics. /// /// Type to flatten /// Enumeration to work on /// Expression that points at the element list to select from /// Flattened list or empty enumerable /// /// var topics = topicTree.FlattenTree(t=> t.Topics); /// public static IEnumerable FlattenTree( this IEnumerable e, Func> f) { if (f == null) throw new ArgumentNullException(nameof(f)); var items = (e ?? Enumerable.Empty()).ToList(); return items .SelectMany(c => (f(c) ?? Enumerable.Empty()).FlattenTree(f)) .Concat(items); } } } ================================================ FILE: Westwind.Utilities/Utilities/ExensionMethods/MemoryStreamExtensions.cs ================================================ using System.Text; using System.IO; using System.Threading.Tasks; namespace System.IO { /// /// MemoryStream Extension Methods that provide conversions to and from strings /// public static class MemoryStreamExtensions { /// /// Returns the content of the stream as a string /// /// Memory stream /// Encoding to use - defaults to Unicode /// public static string AsString(this MemoryStream ms, Encoding encoding = null) { if (encoding == null) encoding = Encoding.Unicode; ms.Position = 0; return encoding.GetString(ms.ToArray()); } /// /// Writes the specified string into the memory stream /// /// /// /// public static void FromString(this MemoryStream ms, string inputString, Encoding encoding = null) { if (encoding == null) encoding = Encoding.Unicode; byte[] buffer = encoding.GetBytes(inputString); ms.Write(buffer, 0, buffer.Length); ms.Position = 0; } } /// /// Stream Extensions /// public static class StreamExtensions { /// /// Converts a stream by copying it to a memory stream and returning /// as a string with encoding. /// /// stream to turn into a string /// Encoding of the stream. Defaults to Unicode /// string public static string AsString(this Stream s, Encoding encoding = null) { using (var ms = new MemoryStream()) { s.CopyTo(ms); s.Position = 0; return ms.AsString(encoding); } } /// /// Returns bytes from a stream /// /// /// public static byte[] AsBytes(this Stream s) { if (s is MemoryStream ms) { ms.Position = 0; return ms.ToArray(); } using (ms = new MemoryStream() { Capacity = Math.Max( 256, (int) s.Length ) }) { s.CopyTo(ms); s.Position = 0; return ms.ToArray(); } } /// /// Returns bytes from a stream /// /// /// public static async Task AsBytesAsync(this Stream s) { if (s is MemoryStream ms) { ms.Position = 0; return ms.ToArray(); } using (ms = new MemoryStream() { Capacity = Math.Max(256, (int)s.Length) }) { await s.CopyToAsync(ms); s.Position = 0; return ms.ToArray(); } } } } ================================================ FILE: Westwind.Utilities/Utilities/FileUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Runtime.InteropServices; using System.Security.Cryptography; //using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace Westwind.Utilities { /// /// wwUtils class which contains a set of common utility classes for /// Formatting strings /// Reflection Helpers /// Object Serialization /// Stream Manipulation /// public static class FileUtils { #region Path Segments and Path Names /// /// This function returns the actual filename of a file /// that exists on disk. If you provide a path/file name /// that is not proper cased as input, this function fixes /// it up and returns the file using the path and file names /// as they exist on disk. /// /// If the file doesn't exist the original filename is /// returned. /// /// A filename to check /// On disk file name and path with the disk casing public static string GetPhysicalPath(string filename) { try { return new FileInfo(filename).FullName; } catch { } return filename; } /// /// Returns a relative path string from a full path based on a base path /// provided. /// /// The path to convert. Can be either a file or a directory /// The base path on which relative processing is based. Should be a directory! /// /// String of the relative path. Path is returned in OS specific path format. /// /// Examples of returned values: /// test.txt, ..\test.txt, ..\..\..\test.txt, ., .., subdir\test.txt /// public static string GetRelativePath(string fullPath, string basePath ) { try { // ForceBasePath to a path var pathChar = Path.DirectorySeparatorChar.ToString(); if (!basePath.EndsWith(value: pathChar)) basePath += pathChar; Uri baseUri = new Uri(uriString: basePath); Uri fullUri = new Uri(uriString: fullPath); string relativeUri = baseUri.MakeRelativeUri(uri: fullUri).ToString(); if (relativeUri.StartsWith("file://")) return fullPath; // invalid path that can't be made relative // Uri's use forward slashes - convert back to backward slases if OS uses var path = relativeUri.Replace(oldValue: "/", newValue: pathChar); return Uri.UnescapeDataString(path); } catch { return fullPath; } } /// /// Compares two files and returns a relative path to the second file. /// /// The path that is the current path you're working with /// The path that that you want to reference /// If true won't fix up the path for current OS (ie. returns original path delimiters via Uri comparison) /// Relative path if possible otherwise original path public static string GetRelativeFilePath(string filePath, string compareToPath, bool noOsPathFixup = false) { Uri fromUri = new Uri("file://" + filePath); Uri toUri = new Uri("file://" + compareToPath); string path = fromUri.MakeRelativeUri(toUri).ToString(); if (path.StartsWith("file://")) return filePath; // invalid path that can't be made relative if (!noOsPathFixup) { var pathChar = Path.DirectorySeparatorChar.ToString(); path = path.Replace(oldValue: "/", newValue: pathChar); } return path; } /// /// Resolves an absolute path from a relative file path to a base file or directory /// /// Base file or folder which relativeFile is relative to. /// If you pass a folder, terminate the folder with a path character! /// The path to resolve against the base path /// public static string ResolvePath(string basePath, string relativeFile) { if (string.IsNullOrEmpty(basePath) || string.IsNullOrEmpty(relativeFile)) return relativeFile; var baseUri = new Uri(basePath); var relativeUri = new Uri(relativeFile, UriKind.Relative); var relUri = new Uri(baseUri, relativeUri); return relUri.LocalPath; } /// /// Checks to see if a given local file or directory is a relative path /// /// path to check /// public static bool IsRelativePath(string path) { if (string.IsNullOrEmpty(path)) return false; if (path.StartsWith(".")) return true; if (path.StartsWith("/") || path.StartsWith("\\") || path.Contains(":\\") || path.StartsWith("file:")) return false; return true; } public static string GetShortPath(string path) { if (string.IsNullOrEmpty(path)) return null; // allow for extended path syntax bool addExtended = false; if (path.Length > 240 && !path.StartsWith(@"\\?\")) { path = @"\\?\" + path; addExtended = true; } var shortPath = new StringBuilder(1024); int res = GetShortPathName(path, shortPath, 1024); if (res < 1) return null; path = shortPath.ToString(); if (addExtended) path = path.Substring(4); // strip off \\?\ return path; } [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern int GetShortPathName( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength ); /// /// Retrieves a long filename for a given path using /// Extended Windows path syntax. /// /// Fully qualified paths: /// \\?\C:\path\to\file.txt /// UNC Paths: /// \\?\UNC\server\share\path\to\file.txt /// /// /// Paths are resolved using GetFullPath() so preferably /// you should pass in a fully qualified path for your /// application to ensure that path is resolved correctly. /// /// Existing path /// Long path public static string GetWindowsLongFilename(string path) { if (string.IsNullOrEmpty(path)) return path; string fullPath = System.IO.Path.GetFullPath(path); if (fullPath.Length < 260) return fullPath; // No need to convert // Fully qualified path if (fullPath.Length > 1 && fullPath[1] == ':') fullPath = @"\\?\" + fullPath; // UNC Path else if (fullPath.Length > 2 && fullPath.StartsWith(@"\\")) fullPath = @"\\?\UNC\" + fullPath.Substring(2); return fullPath; } //// To ensure that paths are not limited to MAX_PATH, use this signature within .NET //[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError = true)] //static extern int GetShortPathName_Internal(string pathName, StringBuilder shortName, int cbShortName); ///// ///// Returns a Windows short path (8 char path segments) ///// for a long path. ///// ///// ///// Throws on non-existant files ///// //public static string GetShortPath(string fullPath) //{ // if (string.IsNullOrEmpty(fullPath)) // return fullPath; // StringBuilder sb = new StringBuilder(); // GetShortPathName_Internal(fullPath, sb, 2048); // return sb.ToString(); //} /// /// Expands Path Environment Variables like %appdata% in paths. Also /// expands ~ to the User Profile folder. /// /// Path with potential environment variables. /// public static string ExpandPathEnvironmentVariables(string path) { if (string.IsNullOrEmpty(path)) return path; if (path.StartsWith("~")) { path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + path.TrimStart('~'); } return Environment.ExpandEnvironmentVariables(path); } /// /// Changes any path that starts with the Windows user path into a ~ path instead /// /// Any windows path /// ~ replaces c:\users\someuser in path string, otherwise original path is returned public static string TildefyUserPath(string path) { if (string.IsNullOrEmpty(path)) return path; string userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); if (path.StartsWith(userPath, StringComparison.InvariantCultureIgnoreCase)) #if NET6_0_OR_GREATER return path.Replace(userPath, "~", true, null); #else return StringUtils.ReplaceString(path, userPath, "~", true); #endif return path; } /// /// Returns a compact path with elipsis from a long path /// /// Original path to potentially trim /// Max length of the path string returned /// public static string GetCompactPath(string path, int length = 70) { if (string.IsNullOrEmpty(path)) return path; var fnameOrDirectory = Path.GetFileName(path); if (fnameOrDirectory.Length >= length) return "..." + fnameOrDirectory; if (path.Length <= length) return path; var index = Math.Max(path.LastIndexOf('\\'), path.LastIndexOf('/')); if (index <= 0) return path.Substring(0, length); var end = path.Substring(index); var maxStartLength = length - end.Length; if (maxStartLength <= 0) return "..." + end.Substring(Math.Max(0, end.Length - (length - 3))); var start = path.Substring(0, index); if (start.Length > maxStartLength) start = start.Substring(0, Math.Max(0, maxStartLength - 3)) + "..."; return start + end; } /// /// Creates a temporary file name with a specific extension. Optionall /// provide the base path to create it in otherwise the TEMP path is used. /// Filename is generated as _ + 8 random characters/digits /// /// The extension to use (.png) /// Optional - path in which the file is created if it needs to override /// Optional - character count - minus the prefix _ - of the generated temp filename. Between 8-16 /// public static string GetTempFilenameWithExtension(string extension, string tempPath = null, int charCount = 8) { if (string.IsNullOrEmpty(extension)) { extension = ".tmp"; } if (!extension.StartsWith(".")) extension = "." + extension; if (tempPath == null) { tempPath = Path.GetTempPath(); } string filename = DataUtils.GenerateUniqueId(charCount); return Path.Combine(tempPath, filename + extension); } /// /// Attempts to break a file name into words that can be used for display /// purposes. /// * Replaces - and _ with spaces /// * Converts from CamelCase (simplified - not perfect) /// /// File name to break into words /// public static string BreakFilenameIntoWords(string filename) { if (string.IsNullOrEmpty(filename)) return filename; return StringUtils.BreakIntoWords(Path.GetFileNameWithoutExtension(filename)); } #endregion #region File and Path Normalization /// /// Normalizes a file path to the operating system default /// slashes. /// /// public static string NormalizePath(string path) { //return Path.GetFullPath(path); // this always turns into a full OS path if (string.IsNullOrEmpty(path)) return path; char slash = Path.DirectorySeparatorChar; path = path.Replace('/', slash).Replace('\\', slash); string doubleSlash = string.Concat(slash, slash); if (path.StartsWith(doubleSlash)) return string.Concat(doubleSlash, path.TrimStart(slash).Replace(doubleSlash, slash.ToString())); else return path.Replace(doubleSlash, slash.ToString()); } /// /// Normalizes path with slashes and forces a trailing slash /// on the end of the path. /// /// Path to pass in /// public static string NormalizeDirectory(string path) { path = NormalizePath(path); if (!path.EndsWith(Path.DirectorySeparatorChar.ToString())) path += Path.DirectorySeparatorChar; return path; } /// /// Adds a trailing slash to a path if there isn't one. /// /// Uses the Operating System default path character (`/` or `\`) /// /// A file system path /// public static string AddTrailingSlash(string path) { string separator = Path.DirectorySeparatorChar.ToString(); path = path.TrimEnd(); if (path.EndsWith(separator) || path.EndsWith(Path.AltDirectorySeparatorChar.ToString())) return path; return path + separator; } /// /// Adds a trailing slash to a path if there isn't one. /// /// Allows you to explicitly specify the path separator character /// rather than using the default OS path separator. /// /// A file system path /// Character to use as trailing character /// public static string AddTrailingSlash(string path, char slashChar) { var separator = slashChar.ToString(); path = path.TrimEnd(); if (path.EndsWith(separator)) return path; return path + separator; } /// /// Returns a path as a `file:///` url. /// /// /// A fully rooted path /// or: a relative path that can be resolved to a /// fully rooted path via GetFullPath(). /// /// public static string FilePathAsUrl(string path) { if (string.IsNullOrEmpty(path)) return path; // try to resolve the path to a full path path = Path.GetFullPath(path); var url = new Uri(path); return url.ToString(); } #endregion #region File Encoding and Checksums /// /// Detects the byte order mark of a file and returns /// an appropriate encoding for the file. /// /// /// public static Encoding GetFileEncoding(string srcFile) { // Use Default of Encoding.Default (Ansi CodePage) Encoding enc = Encoding.Default; // Detect byte order mark if any - otherwise assume default byte[] buffer = new byte[5]; FileStream file = new FileStream(srcFile, FileMode.Open); _ = file.Read(buffer, 0, 5); file.Close(); if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) enc = Encoding.UTF8; else if (buffer[0] == 0xfe && buffer[1] == 0xff) enc = Encoding.Unicode; else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) enc = Encoding.UTF32; return enc; } /// /// Creates an MD5 checksum of a file /// /// /// SHA256, SHA512, SHA1, MD5 /// BinHex file hash public static string GetChecksumFromFile(string file, string hashAlgorithm = "MD5") { if (!File.Exists(file)) return null; try { byte[] checkSum; using (FileStream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { HashAlgorithm md = null; if (hashAlgorithm == "MD5") md = MD5.Create(); else if (hashAlgorithm == "SHA256") md = SHA256.Create(); else if (hashAlgorithm == "SHA512") md = SHA512.Create(); else if (hashAlgorithm == "SHA1") md = SHA1.Create(); else md = MD5.Create(); using (md) { checkSum = md.ComputeHash(stream); } } return StringUtils.BinaryToBinHex(checkSum); } catch { return null; } } #endregion #region Searching /// /// Searches for a file name based on a current file location up or down the /// directory hierarchy including the current folder. First file match is returned. /// /// Current path or filename to determine start folder to search up from /// File name to search for. Should be a filename but can contain wildcards /// Search up or down the directory hierarchy including base path /// public static string FindFileInHierarchy( string currentPath, string searchFile, FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up) { string path = null; var fi = new FileInfo(currentPath); if (!fi.Exists) { var di = new DirectoryInfo(currentPath); if (!di.Exists) return null; path = di.FullName; } else { path = fi.DirectoryName; } return FindFileInHierarchyInternal(path, searchFile, direction); } /// /// Recursive method to walk the hierarchy and find the file requested. /// /// Base path /// Filename to search for /// Search up or down the tree including base path /// private static string FindFileInHierarchyInternal(string path, string searchFile, FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up) { if (path == null) return null; var dir = new DirectoryInfo(path); var so = SearchOption.TopDirectoryOnly; if (direction == FindFileInHierarchyDirection.Down) so = SearchOption.AllDirectories; FileInfo[] files; try { files = dir.GetFiles(searchFile, so); } catch { return null; // permissions error most likely } if (files.Length > 0) return files[0].FullName; // closest match if (direction == FindFileInHierarchyDirection.Down) return null; if (dir.Parent == null) return null; return FindFileInHierarchyInternal(dir.Parent.FullName, searchFile, FindFileInHierarchyDirection.Up); } /// /// Returns a list of file matches searching up and down a file hierarchy /// /// Path to start from /// Filename or Wildcard /// up or down the hiearchy /// public static string[] FindFilesInHierarchy(string startPath, string searchFile, FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Down) { var list= new string[]{ }; var fi = new FileInfo(startPath); if (!fi.Exists) { var di = new DirectoryInfo(startPath); if (!di.Exists) return list; startPath = di.FullName; } else { startPath = fi.DirectoryName; } return FindFilesInHierarchyInternal(startPath, searchFile, direction); } /// /// Recursive method to walk the hierarchy and find the file requested. /// /// Base path /// Filename to search for /// Search up or down the tree including base path /// private static string[] FindFilesInHierarchyInternal(string path, string searchFile, FindFileInHierarchyDirection direction = FindFileInHierarchyDirection.Up) { var list = new string[] { }; if (path == null) return list; var dir = new DirectoryInfo(path); var so = SearchOption.TopDirectoryOnly; if (direction == FindFileInHierarchyDirection.Down) so = SearchOption.AllDirectories; var files = dir.GetFiles(searchFile, so); if (files.Length > 0) return files.Select(fi=> fi.FullName).ToArray(); if (direction == FindFileInHierarchyDirection.Down) return list; if (dir.Parent == null) return list; return FindFilesInHierarchyInternal(dir.Parent.FullName, searchFile, FindFileInHierarchyDirection.Up); } public enum FindFileInHierarchyDirection { Up, Down } #endregion #region File and Path Naming /// /// Returns a safe filename from a string by stripping out /// illegal characters /// /// Filename to fix up /// String value to replace illegal chars with. Defaults empty string /// Optional - replace spaces with a specified string like a - or _. Optional, if not set leaves spaces which are legal for filenames /// Fixed up string public static string SafeFilename(string fileName, string replacementString = "", string spaceReplacement = null) { if (string.IsNullOrEmpty(fileName)) return fileName; string file = Path.GetInvalidFileNameChars() .Aggregate(fileName.Trim(), (current, c) => current.Replace(c.ToString(), replacementString)); file = file.Replace("#", "") .Replace("+", ""); if (!string.IsNullOrEmpty(spaceReplacement)) file = file.Replace(" ", spaceReplacement); return file.Trim(); } /// /// Returns a safe filename in CamelCase /// /// /// public static string CamelCaseSafeFilename(string filename) { if (string.IsNullOrEmpty(filename)) return filename; string fname = Path.GetFileNameWithoutExtension(filename); string ext = Path.GetExtension(filename); return StringUtils.ToCamelCase(SafeFilename(fname)) + ext; } /// /// Checks to see if a file has invalid path characters. Use this /// to check before using or manipulating paths with `Path` operations /// that will fail if files or paths contain invalid characters. /// /// Path to check /// Optionally allows you to add additional invalid characters to the disallowed OS characters /// public static bool HasInvalidPathCharacters(string path, params char[] additionalChars) { if(string.IsNullOrEmpty(path)) return true; // no invalids var invalids = Path.GetInvalidPathChars(); if (additionalChars != null) invalids.Concat(additionalChars); return (!string.IsNullOrEmpty(path) && path.IndexOfAny(invalids) >= 0); } #endregion #region StreamFunctions /// /// Copies the content of the one stream to another. /// Streams must be open and stay open. /// public static void CopyStream(Stream source, Stream dest, int bufferSize) { byte[] buffer = new byte[bufferSize]; int read; while ( (read = source.Read(buffer, 0, buffer.Length)) > 0) { dest.Write(buffer, 0, read); } } /// /// Copies the content of one stream to another by appending to the target stream /// Streams must be open when passed in. /// /// /// /// /// public static void CopyStream(Stream source, Stream dest, int bufferSize, bool append) { if (append) dest.Seek(0, SeekOrigin.End); CopyStream(source, dest, bufferSize); return; } /// /// Opens a stream reader with the appropriate text encoding applied. /// /// public static StreamReader OpenStreamReaderWithEncoding(string srcFile) { Encoding enc = GetFileEncoding(srcFile); return new StreamReader(srcFile, enc); } #endregion #region Async File Access for NetFX /// /// Asynchronously reads files. Use only with NetFx /// /// /// /// public static async Task ReadAllTextAsync(string filename, Encoding encoding) { using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan)) using (var reader = new StreamReader(stream, encoding)) { return await reader.ReadToEndAsync(); } } /// /// Async Read all bytes. Use only with NetFx /// /// /// /// public static async Task ReadAllBytesAsync(string filename) { if (filename == null) throw new ArgumentNullException(nameof(filename)); using (var sourceStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true)) { var buffer = new byte[sourceStream.Length]; int bytesRead = 0; while (bytesRead < buffer.Length) { int read = await sourceStream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead); if (read == 0) break; bytesRead += read; } return buffer; } } /// /// Writes out text file content asynchronously.Use only with NetFx. /// /// /// /// public static async Task WriteAllTextAsync(string filename, string text, Encoding encoding) { if (filename == null) throw new ArgumentNullException(nameof(filename)); using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan)) { var bytes = encoding.GetBytes(text ?? string.Empty); await stream.WriteAsync(bytes, 0, bytes.Length); } } /// /// Async Write all bytes. Use only with NetFx /// /// /// /// /// public static async Task WriteAllBytesAsync(string filename, byte[] bytes) { if (filename == null) throw new ArgumentNullException(nameof(filename)); if (bytes == null) throw new ArgumentNullException(nameof(bytes)); using (var destinationStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) { await destinationStream.WriteAsync(bytes, 0, bytes.Length); } } #endregion #region Folder Copying and Deleting /// /// Copies a file and creates the directory structure /// if it doesn't exist. /// /// Source file /// Target file /// if true overwrites the file if possible. If the file is locked the method returns false /// public static bool CopyFileEnsureDirectory(string sourceFilePath, string destinationFilePath, bool overwrite = true) { try { // Ensure destination directory exists var destinationDir = Path.GetDirectoryName(destinationFilePath); if (string.IsNullOrWhiteSpace(destinationDir)) return false; Directory.CreateDirectory(destinationDir); // Copy the file File.Copy(sourceFilePath, destinationFilePath, overwrite); return true; } catch (Exception) { // Optional: log or handle specific exceptions if needed return false; } } /// /// Copies directories using either top level only or deep merge copy. /// /// Copies a directory by copying files from source folder to target folder. /// If folder(s) don't exist they are created. /// /// Source folder /// Target folder /// if set deletes the folder before copying /// if set copies files recursively public static void CopyDirectory(string sourceDirectory, string targetDirectory, bool deleteFirst = false, bool recursive = true, bool ignoreErrors = false ) { var diSource = new DirectoryInfo(sourceDirectory); if (!diSource.Exists) return; var diTarget = new DirectoryInfo(targetDirectory); CopyDirectory(diSource, diTarget, deleteFirst, recursive, ignoreErrors); } /// /// Copies directories using either top level only or deep merge copy. /// /// Copies a directory by copying files from source folder to target folder. /// If folder(s) don't exist they are created. /// /// /// /// /// public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool deleteFirst = false, bool recursive = true, bool ignoreErrors =false ) { if (!source.Exists) return; if (deleteFirst && target.Exists) target.Delete(true); Directory.CreateDirectory(target.FullName); // create if it doesn't exist // Copy each file into the new directory. foreach (FileInfo fi in source.GetFiles()) { if (ignoreErrors) { try { fi.CopyTo(Path.Combine(target.FullName, fi.Name), true); } catch { } } else fi.CopyTo(Path.Combine(target.FullName, fi.Name), true); } if (recursive) { // Copy each subdirectory using recursion. foreach (DirectoryInfo diSourceSubDir in source.GetDirectories()) { DirectoryInfo nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name); CopyDirectory(diSourceSubDir, nextTargetSubDir, recursive: recursive, ignoreErrors: ignoreErrors); } } } /// /// Deletes files in a folder based on a file spec recursively /// /// /// /// /// 0 when no errors, otherwise number of files that have failed to delete (usually locked) public static int DeleteFiles(string path, string filespec, bool recursive = false) { if (!Directory.Exists(path)) return 0; int failed = 0; path = Path.GetFullPath(path); string spec = Path.GetFileName(filespec); string[] files = Directory.GetFiles(path, spec); foreach (string file in files) { try { File.Delete(file); } catch { failed++; } // ignore locked files } if (recursive) { var dirs = Directory.GetDirectories(path); foreach (string dir in dirs) { failed =+ DeleteFiles(dir, filespec, recursive); } } return failed; } /// /// Deletes files based on a file spec and a given timeout. /// This routine is useful for cleaning up temp files in /// Web applications. /// /// A filespec that includes path and/or wildcards to select files /// The timeout - if files are older than this timeout they are deleted public static void DeleteTimedoutFiles(string filespec, int seconds) { string path = Path.GetDirectoryName(filespec); string spec = Path.GetFileName(filespec); string[] files = Directory.GetFiles(path, spec); foreach (string file in files) { try { if (File.GetLastWriteTimeUtc(file) < DateTime.UtcNow.AddSeconds(seconds * -1)) File.Delete(file); } catch { } // ignore locked files } } #endregion } } ================================================ FILE: Westwind.Utilities/Utilities/GenericUtils.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Westwind.Utilities { public static class GenericUtils { /// /// Checks if an item is contained in a given list of values. /// Equivalent to SQL's IN clause. /// public static bool InList(this T item, params T[] list) { return list.Contains(item); } /// /// Overload that takes any IEnumerable<<. /// public static bool InList(this T item, IEnumerable list) { return list.Contains(item); } /// /// Determines whether an item is contained in a list of other items /// /// /// bool exists = Inlist<string>("Rick","Mike","Billy","Rick","Frank"); // true; /// /// Any type /// The item to look for /// Any number of items to search (params) /// public static bool Inlist(this T item, params T[] list) { return list.Contains(item); } /// /// A string is contained in a list of strings /// /// string to look for /// list of strings /// /// public static bool Inlist(this string item, bool caseSensitive, params string[] list) { if (caseSensitive) return list.Contains(item); foreach (var listItem in list) { if (listItem.Equals(item, StringComparison.OrdinalIgnoreCase)) return true; } return false; } } } ================================================ FILE: Westwind.Utilities/Utilities/HtmlUtils.cs ================================================ using System; using System.Text.RegularExpressions; namespace Westwind.Utilities { /// /// Html string and formatting utilities /// public static class HtmlUtils { /// /// Replaces and and Quote characters to HTML safe equivalents. /// /// HTML to convert /// Returns an HTML string of the converted text public static string FixHTMLForDisplay(string html) { if (string.IsNullOrEmpty(html)) return html; html = html.Replace("<", "<"); html = html.Replace(">", ">"); html = html.Replace("\"", """); return html; } /// /// Strips HTML tags out of an HTML string and returns just the text. /// /// Html String /// public static string StripHtml(string html) { if (string.IsNullOrEmpty(html)) return html; html = Regex.Replace(html, @"<(.|\n)*?>", string.Empty); html = html.Replace("\t", " "); html = html.Replace("\r\n", string.Empty); html = html.Replace(" ", " "); return html.Replace(" ", " "); } /// /// Fixes a plain text field for display as HTML by replacing carriage returns /// with the appropriate br and p tags for breaks. /// /// Input string /// Fixed up string public static string DisplayMemo(string htmlText) { if (htmlText == null) return string.Empty; htmlText = htmlText.Replace("\r\n", "\r"); htmlText = htmlText.Replace("\n", "\r"); //HtmlText = HtmlText.Replace("\r\r","

"); htmlText = htmlText.Replace("\r", "
\r\n"); return htmlText; } ///

/// Method that handles handles display of text by breaking text. /// Unlike the non-encoded version it encodes any embedded HTML text /// /// /// public static string DisplayMemoEncoded(string text) { if (text == null) return string.Empty; bool PreTag = false; if (text.Contains("
"))
            {
                text = text.Replace("
", "__pre__");
                text = text.Replace("
", "__/pre__"); PreTag = true; } // fix up line breaks into

text = DisplayMemo(System.Net.WebUtility.HtmlEncode(text)); //HttpUtility.HtmlEncode(Text)); if (PreTag) { text = text.Replace("__pre__", "

");
                text = text.Replace("__/pre__", "
"); } return text; } /// /// HTML-encodes a string and returns the encoded string. /// /// The text string to encode. /// The HTML-encoded text. [Obsolete("Use System.Net.WebUtility.HtmlEncode() instead.")] public static string HtmlEncode(string text) { return System.Net.WebUtility.HtmlEncode(text); //if (text == null) // return string.Empty; //StringBuilder sb = new StringBuilder(text.Length); //int len = text.Length; //for (int i = 0; i < len; i++) //{ // switch (text[i]) // { // case '<': // sb.Append("<"); // break; // case '>': // sb.Append(">"); // break; // case '"': // sb.Append("""); // break; // case '&': // sb.Append("&"); // break; // case '\'': // sb.Append("'"); // break; // default: // if (text[i] > 159) // { // // decimal numeric entity // sb.Append("&#"); // sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture)); // sb.Append(";"); // } // else // sb.Append(text[i]); // break; // } //} //return sb.ToString(); } /// /// Create an embedded image url for binary data like images and media /// /// /// /// public static string BinaryToEmbeddedBase64(byte[] imageBytes, string mimeType = "image/png") { if (imageBytes == null) return null; var data = $"data:{mimeType};base64," + Convert.ToBase64String(imageBytes, 0, imageBytes.Length); return data; } #if NET6_0_OR_GREATER /// /// Decoded an embedded base64 resource string into its binary content and mime type /// /// Embedded Base64 data (data:mime/type;b64data) /// public static (byte[] bytes, string mimeType) EmbeddedBase64ToBinary(string base64Data) { if (string.IsNullOrEmpty(base64Data)) return (null, null); var parts = base64Data.Split(','); if (parts.Length != 2) return (null, null); var mimeType = parts[0].Replace("data:", "").Replace(";base64", ""); var data = parts[1]; var bytes = Convert.FromBase64String(data); return (bytes, mimeType); } #endif /// /// Creates an Abstract from an HTML document. Strips the /// HTML into plain text, then creates an abstract. /// /// /// public static string HtmlAbstract(string html, int length) { if (string.IsNullOrEmpty(html)) return string.Empty; return StringUtils.TextAbstract(StripHtml(html), length); } static string DefaultHtmlSanitizeTagBlackList { get; } = "script|iframe|object|embed|form"; static Regex _RegExScript = new Regex($@"(<({DefaultHtmlSanitizeTagBlackList})\b[^<]*(?:(?!<\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\/({DefaultHtmlSanitizeTagBlackList})>)", RegexOptions.IgnoreCase | RegexOptions.Multiline); // strip javascript: and unicode representation of javascript: // href='javascript:alert(\"gotcha\")' // href='javascript:alert(\"gotcha\");' static Regex _RegExJavaScriptHref = new Regex( @"<[^>]*?\s(href|src|dynsrc|lowsrc)=.{0,20}((javascript:)|(&#)).*?>", RegexOptions.IgnoreCase | RegexOptions.Singleline); static Regex _RegExOnEventAttributes = new Regex( @"<[^>]*?\s(on[^\s\\]{0,20}=([""].*?[""]|['].*?['])).*?(>|\/>)", RegexOptions.IgnoreCase | RegexOptions.Singleline); /// /// Sanitizes HTML to some of the most of /// /// /// This provides rudimentary HTML sanitation catching the most obvious /// XSS script attack vectors. For mroe complete HTML Sanitation please look into /// a dedicated HTML Sanitizer. /// /// input html /// A list of HTML tags that are stripped. /// Sanitized HTML public static string SanitizeHtml(string html, string htmlTagBlacklist = "script|iframe|object|embed|form") { if (string.IsNullOrEmpty(html)) return html; if (string.IsNullOrEmpty(htmlTagBlacklist) || htmlTagBlacklist == DefaultHtmlSanitizeTagBlackList) { // Use the default list of tags Replace Script tags - reused expr is more efficient html = _RegExScript.Replace(html, string.Empty); } else { // create a custom list including provided tags html = Regex.Replace(html, $@"(<({htmlTagBlacklist})\b[^<]*(?:(?!<\/({DefaultHtmlSanitizeTagBlackList}))<[^<]*)*<\/({htmlTagBlacklist})>)", "", RegexOptions.IgnoreCase | RegexOptions.Multiline); } // Remove javascript: directives var matches = _RegExJavaScriptHref.Matches(html); foreach (Match match in matches) { if (match.Groups.Count > 2) { var txt = match.Value.Replace(match.Groups[2].Value, "unsupported:"); html = html.Replace(match.Value, txt); } } // Remove onEvent handlers from elements matches = _RegExOnEventAttributes.Matches(html); foreach (Match match in matches) { var txt = match.Value; if (match.Groups.Count > 1) { var onEvent = match.Groups[1].Value; txt = txt.Replace(onEvent, string.Empty); if (!string.IsNullOrEmpty(txt)) html = html.Replace(match.Value, txt); } } return html; } /// /// Create an Href HTML link. /// /// /// /// For NetFx in ASPX applications ~ resolves to the application root /// /// /// /// /// /// /// public static string Href(string text, string url, string target = null, string additionalMarkup = null) { #if NETFULL if (url.StartsWith("~")) url = ResolveUrl(url); #endif return "" + text + ""; } /// /// Creates an HREF HTML Link /// /// public static string Href(string url) { return Href(url, url, null, null); } /// /// Returns an IMG link as a string. If the image is null /// or empty a blank string is returned. /// /// /// any additional attributes added to the element /// public static string ImgRef(string imageUrl, string additionalMarkup = null) { if (string.IsNullOrEmpty(imageUrl)) return string.Empty; #if NETFULL if (imageUrl.StartsWith("~")) imageUrl = ResolveUrl(imageUrl); #endif string img = " /// Resolves a URL based on the current HTTPContext /// /// Note this method is added here internally only /// to support the HREF() method and ~ expansion /// on urls. /// /// /// public static string ResolveUrl(string originalUrl) { if (string.IsNullOrEmpty(originalUrl)) return string.Empty; // Absolute path - just return if (originalUrl.IndexOf("://") != -1) return originalUrl; // Fix up image path for ~ root app dir directory if (originalUrl.StartsWith("~")) { //return VirtualPathUtility.ToAbsolute(originalUrl); string newUrl = ""; // Avoid pulling in System.Web reference dynamic context = ReflectionUtils.GetStaticProperty( "System.Web.HttpContext", "Current"); if (context != null) { newUrl = context.Request.ApplicationPath + originalUrl.Substring(1); newUrl = newUrl.Replace("//", "/"); // must fix up for root path } else // Not context: assume current directory is the base directory throw new ArgumentException("Invalid URL: Relative Urls are only available in classic ASP.NET Web applications."); // Just to be sure fix up any double slashes return newUrl; } return originalUrl; } #endif #region Url Parsing /// /// Returns the base URL of a site from a full URL /// /// An absolute scheme path (http:// or file:// or ftp:// etc) /// public static string GetSiteBasePath(string url) { var uri = new Uri(url); return $"{uri.Scheme}://{uri.Authority}/"; } /// /// Returns the path portion of an absolute URL optionally with the query string and fragment /// /// /// /// public static string GetRelativeUrlPath(string url, PathReturnOptions pathOptions = PathReturnOptions.PathOnly ) { var uri = new Uri(url); switch (pathOptions) { case PathReturnOptions.PathOnly: return uri.AbsolutePath; case PathReturnOptions.PathAndQuery: return uri.PathAndQuery; case PathReturnOptions.PathAndHash: return uri.AbsolutePath + uri.Fragment; case PathReturnOptions.PathAndQueryAndHash: return uri.PathAndQuery + uri.Fragment; } return uri.PathAndQuery; } #endregion } public enum PathReturnOptions { PathOnly, PathAndQuery, PathAndHash, PathAndQueryAndHash, } } ================================================ FILE: Westwind.Utilities/Utilities/HttpClientUtils.cs ================================================ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace Westwind.Utilities { /// /// Http Client wrapper that provides single line access for common Http requests /// that return string, Json or binary content. /// /// Most methods have dual versions using simple parameters, or an /// `HttpClientRequestSettings` configuration and results object that is /// passed through on requests. /// public class HttpClientUtils { public const string STR_MultipartBoundary = "----FormBoundary3vDSIXiW0WSTB551"; #region String Download /// /// Runs an Http request and returns success results as a string or null /// on failure or non-200/300 requests. /// /// Failed requests return null and set the settings.ErrorMessage property /// but you can can access the `settings.Response` or use the /// `settings.GetResponseStringAsync()` method or friends to retrieve /// content despite the error. /// /// /// By default this method does not throw on errors or error status codes, /// but returns null and an error message. For error requests you can check /// settings.HasResponseContent and then either directly access settings.Response, /// or use settings.GetResponseStringAsync() or friends to retrieve error content. /// /// If you want the method to throw exceptions on errors an > 400 status codes, /// use `settings.ThrowExceptions = true`. /// /// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more /// string of HTTP response public static async Task DownloadStringAsync(HttpClientRequestSettings settings) { string content = null; using (var client = GetHttpClient(null, settings)) { try { settings.Response = await client.SendAsync(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return null; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var stream = await settings.Response.Content.ReadAsStreamAsync()) { var buffer = new byte[settings.MaxResponseSize]; _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize); content = settings.Encoding.GetString(buffer); } } else { content = await settings.Response.Content.ReadAsStringAsync(); } } return content; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return null; } catch (Exception ex) { settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } #if NET6_0_OR_GREATER #endif /// /// Runs an Http request and returns success results as a string or null /// on failure or non-200/300 requests. /// /// Failed requests return null and set the settings.ErrorMessage property /// but you can can access the `settings.Response` or use the /// `settings.GetResponseStringAsync()` method or friends to retrieve /// content despite the error. /// /// /// By default this method does not throw on errors or error status codes, /// but returns null and an error message. For error requests you can check /// settings.HasResponseContent and then either directly access settings.Response, /// or use settings.GetResponseStringAsync() or friends to retrieve error content. /// /// If you want the method to throw exceptions on errors an > 400 status codes, /// use `settings.ThrowExceptions = true`. /// /// The url to access /// The data to send. String data is sent as is all other data is JSON encoded. /// Optional Content type for the request /// The HTTP Verb to use (GET,POST,PUT,DELETE etc.) /// string of HTTP response public static async Task DownloadStringAsync(string url, object data = null, string contentType = null, string verb = null) { if (string.IsNullOrEmpty(verb)) { if (data != null) verb = "POST"; else verb = "GET"; } return await DownloadStringAsync(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = contentType }); } #if NET6_0_OR_GREATER /// /// Synchronous version of `DownloadStringAsync`. /// /// Request/Response settings instance /// string or null on failure public static string DownloadString(HttpClientRequestSettings settings) { string content = null; using (var client = GetHttpClient(null, settings)) { try { settings.Response = client.Send(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return null; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var stream = settings.Response.Content.ReadAsStream()) { var buffer = new byte[settings.MaxResponseSize]; _ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize); content = settings.Encoding.GetString(buffer); } } else { //content = await settings.Response.Content.ReadAsStringAsync(); using (var stream = settings.Response.Content.ReadAsStream()) { var sr = new StreamReader(stream, true); content = sr.ReadToEnd(); } } } return content; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return null; } catch (Exception ex) { settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } /// /// Runs an Http request and returns success results as a string or null /// on failure or non-200/300 requests. /// /// Failed requests return null and set the settings.ErrorMessage property /// but you can can access the `settings.Response` or use the /// `settings.GetResponseStringAsync()` method or friends to retrieve /// content despite the error. /// /// /// By default this method does not throw on errors or error status codes, /// but returns null and an error message. For error requests you can check /// settings.HasResponseContent and then either directly access settings.Response, /// or use settings.GetResponseStringAsync() or friends to retrieve error content. /// /// If you want the method to throw exceptions on errors an > 400 status codes, /// use `settings.ThrowExceptions = true`. /// /// The url to access /// The data to send. String data is sent as is all other data is JSON encoded. /// Optional Content type for the request /// The HTTP Verb to use (GET,POST,PUT,DELETE etc.) /// string of HTTP response public static string DownloadString(string url, object data = null, string contentType = null, string verb = null) { if (string.IsNullOrEmpty(verb)) { if (data != null) verb = "POST"; else verb = "GET"; } return DownloadString(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = contentType }); } #endif #endregion #region File Download /// /// Downloads a Url to a file. /// /// Optional Url to download - settings.Url works too /// Option filename to download to - settings.OutputFilename works too /// Http request settings can be used in lieu of other parameters /// /// true or fals public static async Task DownloadFileAsync(HttpClientRequestSettings settings, string filename = null) { if (settings == null) return false; if (string.IsNullOrEmpty(settings.OutputFilename)) { settings.HasErrors = true; settings.ErrorMessage = "No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`."; return false; } using (var client = GetHttpClient(null, settings)) { try { settings.Response = await client.SendAsync(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return false; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (File.Exists(settings.OutputFilename)) File.Delete(settings.OutputFilename); if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate, FileAccess.Write)) { using (var stream = await settings.Response.Content.ReadAsStreamAsync()) { var buffer = new byte[settings.MaxResponseSize]; _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize); await outputStream.WriteAsync(buffer, 0, buffer.Length); } } } else { using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate, FileAccess.Write)) { using (var stream = await settings.Response.Content.ReadAsStreamAsync()) { await stream.CopyToAsync(outputStream, 8 * 1024); } } } } return true; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return false; } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return false; } } } /// /// Downloads a Url to a file. /// /// Optional Url to download - settings.Url works too /// Option filename to download to - settings.OutputFilename works too /// Http request settings can be used in lieu of other parameters /// /// true or fals public static Task DownloadFileAsync(string url, string filename) { var settings = new HttpClientRequestSettings(); if (!string.IsNullOrEmpty(url)) settings.Url = url; if (!string.IsNullOrEmpty(filename)) settings.OutputFilename = filename; return DownloadFileAsync(settings); } #if NET6_0_OR_GREATER /// /// Downloads a Url to a file. /// /// Optional Url to download - settings.Url works too /// Option filename to download to - settings.OutputFilename works too /// Http request settings can be used in lieu of other parameters /// /// true or fals public static bool DownloadFile(HttpClientRequestSettings settings, string filename = null) { if (settings == null) return false; if (string.IsNullOrEmpty(settings.OutputFilename)) { settings.HasErrors = true; settings.ErrorMessage = "No ouput file provided. Provide `filename` parameter or `settings.OutputFilename`."; return false; } using (var client = GetHttpClient(null, settings)) { try { settings.Response = client.Send(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return false; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (File.Exists(settings.OutputFilename)) File.Delete(settings.OutputFilename); if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate, FileAccess.Write)) { using (var stream = settings.Response.Content.ReadAsStream()) { var buffer = new byte[settings.MaxResponseSize]; _ = stream.Read(buffer, 0, settings.MaxResponseSize); outputStream.Write(buffer, 0, buffer.Length); } } } else { using (var outputStream = new FileStream(settings.OutputFilename, FileMode.OpenOrCreate, FileAccess.Write)) { using (var stream =settings.Response.Content.ReadAsStream()) { stream.CopyTo(outputStream, 8 * 1024); } } } } return true; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return false; } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return false; } } } /// /// Downloads a Url to a file. /// /// Optional Url to download - settings.Url works too /// Option filename to download to - settings.OutputFilename works too /// Http request settings can be used in lieu of other parameters /// /// true or fals public static bool DownloadFile(string url, string filename) { var settings = new HttpClientRequestSettings(); if (!string.IsNullOrEmpty(url)) settings.Url = url; if (!string.IsNullOrEmpty(filename)) settings.OutputFilename = filename; return DownloadFile(settings); } #endif /// /// Downloads an image to a temporary file or a file you specify, automatically /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc) /// Always use the return value to receive the final image file name. /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Url of image to download /// /// Optional output image file name. Filename may change extension if the image format doesn't match the filename. /// If not passed a temporary files file is created in the temp file location and you can move the file /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation. /// /// Optional more detailed Http Settings for the request /// file name that was created or null public static async Task DownloadImageToFileAsync(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null) { if (settings == null) { if (string.IsNullOrEmpty(imageUrl)) return null; settings = new(); } if (!string.IsNullOrEmpty(imageUrl)) settings.Url = imageUrl; imageUrl = settings.Url; if (!string.IsNullOrEmpty(filename)) settings.OutputFilename = filename; filename = settings.OutputFilename; if (string.IsNullOrEmpty(imageUrl) || !imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://")) return null; // if no filename is specified at all use a temp file if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId()); } // we download to a temp file filename = Path.ChangeExtension(filename, "bin"); string newFilename; try { settings.OutputFilename = filename; await DownloadFileAsync(settings); var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType); if (ext == null) { if (File.Exists(filename)) File.Delete(filename); return null; // invalid image type } newFilename = Path.ChangeExtension(filename, ext); if (File.Exists(newFilename)) File.Delete(newFilename); // rename the file File.Move(filename, newFilename); } catch { if (File.Exists(filename)) File.Delete(filename); return null; } return newFilename; } /// /// Downloads an image to a temporary file or a file you specify, automatically /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc) /// Always use the return value to receive the final image file name. /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Must specify Url and optionally OutputFilename - see parametered version /// file name that was created or null public static async Task DownloadImageToFileAsync( HttpClientRequestSettings settings = null) => await DownloadImageToFileAsync(null, null, settings); #if NET6_0_OR_GREATER /// /// Downloads an image to a temporary file or a file you specify, automatically /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc) /// Always use the return value to receive the final image file name. /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Url of image to download /// /// Optional output image file name. Filename may change extension if the image format doesn't match the filename. /// If not passed a temporary files file is created in the temp file location and you can move the file /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation. /// /// Optional more detailed Http Settings for the request /// file name that was created or null public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null) { if (settings == null) { if (string.IsNullOrEmpty(imageUrl)) return null; settings = new(); } if (!string.IsNullOrEmpty(imageUrl)) settings.Url = imageUrl; imageUrl = settings.Url; if (!string.IsNullOrEmpty(filename)) settings.OutputFilename = filename; filename = settings.OutputFilename; if (string.IsNullOrEmpty(imageUrl) || !imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://")) return null; // if no filename is specified at all use a temp file if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId()); } // we download to a temp file filename = Path.ChangeExtension(filename, "bin"); string newFilename; try { settings.OutputFilename = filename; DownloadFile(settings); var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType); if (ext == null) { if (File.Exists(filename)) File.Delete(filename); return null; // invalid image type } newFilename = Path.ChangeExtension(filename, ext); if (File.Exists(newFilename)) File.Delete(newFilename); // rename the file File.Move(filename, newFilename); } catch { if (File.Exists(filename)) File.Delete(filename); return null; } return newFilename; } /// /// Downloads an image to a temporary file or a file you specify, automatically /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc) /// Always use the return value to receive the final image file name. /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Must specify Url and optionally OutputFilename - see parametered version /// file name that was created or null public static string DownloadImageToFile(HttpClientRequestSettings settings = null) => DownloadImageToFile(null, null, settings); #endif #endregion #region Byte Data Download /// /// Runs an Http Request and returns a byte array from the response or null on failure /// /// Pass in a settings object /// byte[] or null - if null check settings for errors public static async Task DownloadBytesAsync(HttpClientRequestSettings settings) { byte[] content = null; using (var client = GetHttpClient(null, settings)) { try { settings.Response = await client.SendAsync(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return null; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var stream = await settings.Response.Content.ReadAsStreamAsync()) { var buffer = new byte[settings.MaxResponseSize]; _ = await stream.ReadAsync(buffer, 0, settings.MaxResponseSize); content = buffer; } } else { content = await settings.Response.Content.ReadAsByteArrayAsync(); } } return content; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return null; } catch (Exception ex) { settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } #if Net6_0_OR_GREATER public static string DownloadImageToFile(string imageUrl = null, string filename = null, HttpClientRequestSettings settings = null) { if (settings == null) { if (string.IsNullOrEmpty(imageUrl)) return null; settings = new(); } if (!string.IsNullOrEmpty(imageUrl)) settings.Url = imageUrl; imageUrl = settings.Url; if (!string.IsNullOrEmpty(filename)) settings.OutputFilename = filename; filename = settings.OutputFilename; if (string.IsNullOrEmpty(imageUrl) || !imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://")) return null; // if no filename is specified at all use a temp file if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId()); } // we download to a temp file filename = Path.ChangeExtension(filename, "bin"); string newFilename; try { settings.OutputFilename = filename; DownloadFile(settings); var ext = ImageUtils.GetExtensionFromMediaType(settings.ResponseContentType); if (ext == null) { if (File.Exists(filename)) File.Delete(filename); return null; // invalid image type } newFilename = Path.ChangeExtension(filename, ext); if (File.Exists(newFilename)) File.Delete(newFilename); // rename the file File.Move(filename, newFilename); } catch { if (File.Exists(filename)) File.Delete(filename); return null; } return newFilename; } #endif public static async Task DownloadBytesAsync(string url, object data = null, string contentType = null, string verb = null) { if (string.IsNullOrEmpty(verb)) { if (data != null) verb = "POST"; else verb = "GET"; } return await DownloadBytesAsync(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = contentType }); } #if NET6_0_OR_GREATER /// /// Synchronous version of `DownloadBytesAsync`. /// /// Request/Response settings instance /// string or null on failure public static byte[] DownloadBytes(HttpClientRequestSettings settings) { byte[] content = null; using (var client = GetHttpClient(null, settings)) { try { settings.Response = client.Send(settings.Request); } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; return null; } // Capture the response content try { if (settings.Response.IsSuccessStatusCode) { // http 201 no content may return null and be success if (settings.HasResponseContent) { if (settings.MaxResponseSize > 0) { using (var stream = settings.Response.Content.ReadAsStream()) { var buffer = new byte[settings.MaxResponseSize]; _ = stream.ReadAsync(buffer, 0, settings.MaxResponseSize); content = buffer; } } else { using (var stream = settings.Response.Content.ReadAsStream()) { using (var ms = new MemoryStream()) { stream.CopyTo(ms); content = ms.ToArray(); } } } } return content; } settings.HasErrors = true; settings.ErrorMessage = ((int)settings.Response.StatusCode).ToString() + " " + settings.Response.StatusCode.ToString(); if (settings.ThrowExceptions) throw new HttpRequestException(settings.ErrorMessage); // return null but allow for explicit response reading return null; } catch (Exception ex) { settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } /// /// Synchronous version of `DownloadBytesAsync`. /// /// Request URL /// Request data /// Request content type /// HTTP verb (GET, POST, etc.) /// string or null on failure public static byte[] DownloadBytes(string url, object data = null, string contentType = null, string verb = null) { if (string.IsNullOrEmpty(verb)) { if (data != null) verb = "POST"; else verb = "GET"; } return DownloadBytes(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = contentType }); } #endif #endregion #region Json /// /// Makes a JSON request that returns a JSON result. /// /// Result type to deserialize to /// Configuration for this request /// public static async Task DownloadJsonAsync(HttpClientRequestSettings settings) { settings.RequestContentType = "application/json"; settings.Encoding = Encoding.UTF8; string json = await DownloadStringAsync(settings); if (json == null) { return default; } try { return JsonConvert.DeserializeObject(json); } catch (Exception ex) { // original error has priority if (settings.HasErrors) return default; settings.HasErrors = true; settings.ErrorMessage = ex.GetBaseException().Message; settings.ErrorException = ex; } return default; } public static async Task DownloadJsonAsync(string url, string verb = "GET", object data = null) { return await DownloadJsonAsync(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = data != null ? "application/json" : null }); } #if NET6_0_OR_GREATER /// /// Makes a JSON request that returns a JSON result. /// /// Result type to deserialize to /// Configuration for this request /// Result or null - check ErrorMessage in settings on failure public static TResult DownloadJson(HttpClientRequestSettings settings) { settings.RequestContentType = "application/json"; settings.Encoding = Encoding.UTF8; string json = DownloadString(settings); if (json == null) { return default; } try { return JsonConvert.DeserializeObject(json); } catch (Exception ex) { // original error has priority if (settings.HasErrors) return default; settings.HasErrors = true; settings.ErrorMessage = ex.GetBaseException().Message; settings.ErrorException = ex; } return default; } /// /// Makes a JSON request that returns a JSON result. /// /// Request URL /// Http Verb to use. Defaults to GET on no data or POST when data is passed. /// Data to be serialized to JSON for sending /// result or null public static TResult DownloadJson(string url, string verb = "GET", object data = null) { return DownloadJson(new HttpClientRequestSettings { Url = url, HttpVerb = verb, RequestContent = data, RequestContentType = data != null ? "application/json" : null }); } #endif #endregion #region Http Response /// /// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you /// can read the response content from settings.Response.Content.ReadAsXXX() methods. /// /// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more /// string of HTTP response public static async Task DownloadResponseMessageAsync(HttpClientRequestSettings settings) { using (var client = GetHttpClient(null, settings)) { try { settings.Response = await client.SendAsync(settings.Request); if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode) throw new HttpRequestException(settings.ResponseStatusCode + " " + settings.Response.ReasonPhrase); return settings.Response; } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } #if NET6_0_OR_GREATER /// /// Calls a URL and returns the raw, unretrieved HttpResponse. Also set on settings.Response and you /// can read the response content from settings.Response.Content.ReadAsXXX() methods. /// /// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more /// string of HTTP response public static HttpResponseMessage DownloadResponseMessage(HttpClientRequestSettings settings) { using (var client = GetHttpClient(null, settings)) { try { settings.Response = client.Send(settings.Request); if (settings.ThrowExceptions && !settings.Response.IsSuccessStatusCode) throw new HttpRequestException(settings.ResponseStatusCode + " " + settings.Response.ReasonPhrase); return settings.Response; } catch (Exception ex) { settings.HasErrors = true; settings.ErrorException = ex; settings.ErrorMessage = ex.GetBaseException().Message; if (settings.ThrowExceptions) #pragma warning disable CA2200 throw; #pragma warning restore CA2200 return null; } } } #endif #endregion #region Helpers /// /// Creates an instance of the HttpClient and sets the API Key /// in the headers. /// /// Configured HttpClient instance public static HttpClient GetHttpClient(HttpClientHandler handler = null, HttpClientRequestSettings settings = null) { if (settings == null) settings = new HttpClientRequestSettings(); handler = handler ?? new HttpClientHandler() { Proxy = settings.Proxy, Credentials = settings.Credentials, }; #if NET6_0_OR_GREATER if (settings.IgnoreCertificateErrors) handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; #endif var client = new HttpClient(handler); client.DefaultRequestHeaders.UserAgent.ParseAdd(settings.UserAgent); ApplySettingsToRequest(settings); return client; } /// /// Creates a new Request on the Settings object and assigns the settings values /// to the request object. /// /// Settings instance public static void ApplySettingsToRequest(HttpClientRequestSettings settings) { settings.Request = new HttpRequestMessage { RequestUri = new Uri(settings.Url), Method = new HttpMethod(settings.HttpVerb ?? "GET"), Version = new Version(settings.HttpVersion) }; foreach (var header in settings.Headers) { SetHttpHeader(settings.Request, header.Key, header.Value); } if (settings.HasPostData) { settings.RequestContentType = settings.RequestFormPostMode == HttpFormPostMode.MultiPart ? "multipart/form-data; boundary=" + HttpClientUtils.STR_MultipartBoundary : "application/x-www-form-urlencoded"; settings.RequestContent = settings.GetPostBufferBytes(); } if (settings.RequestContent != null && (settings.HttpVerb.Equals("POST", StringComparison.OrdinalIgnoreCase) || settings.HttpVerb.Equals("PUT", StringComparison.OrdinalIgnoreCase) || settings.HttpVerb.Equals("PATCH", StringComparison.OrdinalIgnoreCase)) ) { HttpContent content = null; if (settings.RequestContent is string) { if (!settings.IsRawData && settings.RequestContentType == "application/json") { var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent); content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType); } else content = new StringContent(settings.RequestContent as string, settings.Encoding, settings.RequestContentType); } else if (settings.RequestContent is byte[]) { content = new ByteArrayContent(settings.RequestContent as byte[]); settings.Request.Content = content; settings.Request.Content?.Headers.Add("Content-Type", settings.RequestContentType); } else { if (!settings.IsRawData) { var jsonString = JsonSerializationUtils.Serialize(settings.RequestContent); content = new StringContent(jsonString, settings.Encoding, settings.RequestContentType); } } if (content != null) settings.Request.Content = content; } } private static void SetHttpHeader(HttpRequestMessage req, string header, string value) { if (string.IsNullOrEmpty(header) || string.IsNullOrEmpty(value)) return; var lheader = header.ToLower(); if (lheader == "content-length") return; // auto-generated if (lheader == "content-type") { var contentType = value; if (value == "multipart/form" && !value.Contains("boundary")) { contentType = "multipart/form-data; boundary=" + STR_MultipartBoundary; } req.Content?.Headers.Add("Content-Type", contentType); return; } // content-type, content-encoding etc. if (lheader.StartsWith("content-")) { req.Content?.Headers.Add(header, value); return; } // set above view property // not handled at the moment if (lheader == "proxy-connection") return; req.Headers.Add(header, value); } #endregion } /// /// Configuration object for Http Requests used by the HttpClientUtils /// methods. Allows you to set the URL, verb, headers proxy and /// credentials that are then passed to the HTTP client. /// public class HttpClientRequestSettings { /// /// The URL to send the request to /// public string Url { get; set; } /// /// The HTTP verb to use when sending the request /// public string HttpVerb { get; set; } /// /// The Request content to send to the server. /// Data can be either string or byte[] type /// public object RequestContent { get; set; } /// /// Content Encoding for the data sent to to server /// public Encoding Encoding { get; set; } /// /// When true data is not translated. For example /// when using JSON Request if you want to send /// raw POST data rather than a serialized object. /// public bool IsRawData { get; set; } /// /// The content type of any request data sent to the server /// in the Data property. /// public string RequestContentType { get; set; } = "application/json"; /// /// The request timeout in milliseconds. 0 for default (20 seconds typically) /// public int Timeout { get; set; } /// /// Any Http request headers you want to set for this request /// public Dictionary Headers { get; set; } /// /// Authentication information for this request /// public NetworkCredential Credentials { get; set; } /// /// Determines whether credentials pre-authenticate /// public bool PreAuthenticate { get; set; } /// /// An optional proxy to set for this request /// public IWebProxy Proxy { get; set; } /// /// Capture request string data that was actually sent to the server. /// public string CapturedRequestContent { get; set; } /// /// Captured string Response Data from the server /// public string CapturedResponseContent { get; set; } /// /// Output file name for file download operations /// public string OutputFilename { get; set; } /// /// Capture binary Response data from the server when /// using the Data methods rather than string methods. /// public byte[] ResponseByteData { get; set; } /// /// The HTTP Status code of the HTTP response /// public HttpStatusCode ResponseStatusCode { get { if (Response != null) return Response.StatusCode; return HttpStatusCode.Unused; } } /// /// Content Type of the response - may be null if there is no content or the content type is not set by server /// public string ResponseContentType => Response?.Content?.Headers.ContentType?.MediaType; /// /// Content Length of the response. -1 if there is no result content /// public long ResponseContentLength => Response?.Content?.Headers?.ContentLength ?? -1; /// /// Response content headers (content type, size, charset etc.) - /// check for null if request did not succeed or doesn't produce content /// public HttpContentHeaders ResponseContentHeaders => Response?.Content?.Headers; /// /// Non-Content Response headers - check for null if request did not succeed /// public HttpResponseHeaders ResponseHeaders => Response?.Headers; public bool HasResponseContent { get { if (Response?.Content?.Headers == null) return false; return Response.Content.Headers.ContentLength > 0; } } /// /// The User Agent string sent to the server /// public string UserAgent { get; set; } /// /// By default (false) throws a Web Exception on 500 and 400 repsonses. /// This is the default WebClient behavior. /// /// If `true` doesn't throw, but instead returns the HTTP response. /// Useful if you need to return error messages on 500 and 400 responses /// from API requests. /// public bool ThrowExceptions { get; set; } /// /// Http Protocol Version 1.1 /// public string HttpVersion { get; set; } = "1.1"; public HttpRequestMessage Request { get; set; } public HttpResponseMessage Response { get; set; } /// /// Determines whether the request has errors or /// didn't return a 200/300 result code /// public bool HasErrors { get; set; } /// /// Error message if one was set /// public string ErrorMessage { get; set; } /// /// The full Execption object if an error occurred /// public Exception ErrorException { get; set; } public int MaxResponseSize { get; set; } /// /// if true ignores most certificate errors (expired, not trusted) /// #if !NET6_0_OR_GREATER [Obsolete("This property is not supported in .NET Framework.")] #endif public bool IgnoreCertificateErrors { get; set; } public HttpClientRequestSettings() { HttpVerb = "GET"; Headers = new Dictionary(); Encoding = Encoding.UTF8; UserAgent = "West Wind .NET Http Client"; } #region POST data /// /// Determines how data is POSTed when when using AddPostKey() and other methods /// of posting data to the server. Support UrlEncoded, Multi-Part, XML and Raw modes. /// public HttpFormPostMode RequestFormPostMode { get; set; } = HttpFormPostMode.UrlEncoded; // member properties //string cPostBuffer = string.Empty; internal MemoryStream PostStream; internal BinaryWriter PostData; internal bool HasPostData; /// /// Resets the Post buffer by clearing out all existing content /// public void ResetPostData() { PostStream = new MemoryStream(); PostData = new BinaryWriter(PostStream); } public void SetPostStream(Stream postStream) { MemoryStream ms = new MemoryStream(1024); FileUtils.CopyStream(postStream, ms, 1024); ms.Flush(); ms.Position = 0; PostStream = ms; PostData = new BinaryWriter(ms); } /// /// Adds POST form variables to the request buffer. /// PostMode determines how parms are handled. /// /// Key value or raw buffer depending on post type /// Value to store. Used only in key/value pair modes public void AddPostKey(string key, byte[] value) { if (value == null) return; if (key == "RESET") { ResetPostData(); return; } HasPostData = true; if (PostData == null) { PostStream = new MemoryStream(); PostData = new BinaryWriter(PostStream); } if (string.IsNullOrEmpty(key)) PostData.Write(value); else if (RequestFormPostMode == HttpFormPostMode.UrlEncoded) PostData.Write( Encoding.Default.GetBytes(key + "=" + StringUtils.UrlEncode(Encoding.Default.GetString(value)) + "&")); else if (RequestFormPostMode == HttpFormPostMode.MultiPart) { Encoding iso = Encoding.GetEncoding("ISO-8859-1"); PostData.Write(iso.GetBytes( "--" + HttpClientUtils.STR_MultipartBoundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\"\r\n\r\n")); PostData.Write(value); PostData.Write(iso.GetBytes("\r\n")); } else // Raw or Xml, JSON modes PostData.Write(value); } /// /// Adds POST form variables to the request buffer. /// PostMode determines how parms are handled. /// /// Key value or raw buffer depending on post type /// Value to store. Used only in key/value pair modes public void AddPostKey(string key, string value) { if (value == null) return; AddPostKey(key, Encoding.Default.GetBytes(value)); } /// /// Adds a fully self contained POST buffer to the request. /// Works for XML or previously encoded content. /// /// String based full POST buffer public void AddPostKey(string fullPostBuffer) { AddPostKey(null, fullPostBuffer); } /// /// Adds a fully self contained POST buffer to the request. /// Works for XML or previously encoded content. /// /// Byte array of a full POST buffer public void AddPostKey(byte[] fullPostBuffer) { AddPostKey(null, fullPostBuffer); } /// /// Allows posting a file to the Web Server. Make sure that you /// set PostMode /// /// /// /// Content type of the file to upload. Default is application/octet-stream /// Optional filename to use in the Content-Disposition header. If not specified uses the file name of the file being uploaded. /// true or false. Fails if the file is not found or couldn't be encoded public bool AddPostFile(string key, string filename, string contentType = "application/octet-stream", string contentFilename = null) { byte[] lcFile; if (RequestFormPostMode != HttpFormPostMode.MultiPart) { ErrorMessage = "File upload allowed only with Multi-part forms"; HasErrors = true; return false; } HasPostData = true; try { FileStream loFile = new FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read); lcFile = new byte[loFile.Length]; _ = loFile.Read(lcFile, 0, (int)loFile.Length); loFile.Close(); } catch (Exception e) { ErrorMessage = e.Message; HasErrors = true; return false; } if (PostData == null) { PostStream = new MemoryStream(); PostData = new BinaryWriter(PostStream); } if (string.IsNullOrEmpty(contentFilename)) contentFilename = new FileInfo(filename).Name; PostData.Write(Encoding.Default.GetBytes( "--" + HttpClientUtils.STR_MultipartBoundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + contentFilename + "\"\r\n" + "Content-Type: " + contentType + "\r\n\r\n")); PostData.Write(lcFile); PostData.Write(Encoding.Default.GetBytes("\r\n")); return true; } /// /// Retrieves the accumulated postbuffer as a byte array /// when AddPostKey() or AddPostFile() have been called. /// /// the Post buffer or null if empty or not using /// form post mode public string GetPostBuffer() { var bytes = PostStream?.ToArray(); if (bytes == null) return null; var data = Encoding.Default.GetString(bytes); if (RequestFormPostMode == HttpFormPostMode.MultiPart) { if (PostStream == null) return null; // add final boundary data += "\r\n--" + HttpClientUtils.STR_MultipartBoundary + "--\r\n"; } return data; } /// /// Retrieves the accumulated postbuffer as a byte array /// when AddPostKey() or AddPostFile() have been called. /// /// For multi-part forms this buffer can only be returned /// once as a footer needs to be appended and we don't want /// copy the buffer and double memory usage. /// /// encoded POST buffer public byte[] GetPostBufferBytes() { if (RequestFormPostMode == HttpFormPostMode.MultiPart) { if (PostStream == null) return null; // add final boundary PostData.Write(Encoding.Default.GetBytes("\r\n--" + HttpClientUtils.STR_MultipartBoundary + "--\r\n")); PostStream?.Flush(); } return PostStream?.ToArray(); } #endregion /// /// Retrieves the response as a /// /// public async Task GetResponseStringAsync() { if (Response == null) return null; try { return await Response.Content.ReadAsStringAsync(); } catch (Exception ex) { HasErrors = true; ErrorMessage = ex.GetBaseException().Message; ErrorException = ex; return null; } } /// /// Returns deserialized JSON from the Response /// /// /// public async Task GetResponseJson() { var json = await GetResponseStringAsync(); if (json == null) return default; return JsonSerializationUtils.Deserialize(json); } /// /// Returns an error message from a JSON error object that /// contains a message or Message property. /// /// public async Task GetResponseErrorMessage() { var obj = await GetResponseJson(); if (obj == null) return null; if (obj.ContainsKey("message")) return obj["message"].ToString(); if (obj.ContainsKey("Message")) return obj["Message"].ToString(); return null; } /// /// Returns byte data from the response /// /// public async Task GetResponseDataAsync() { if (Response == null) return null; try { return await Response.Content.ReadAsByteArrayAsync(); } catch (Exception ex) { HasErrors = true; ErrorMessage = ex.GetBaseException().Message; ErrorException = ex; return null; } } public override string ToString() { return $"{HttpVerb} {Url} {ErrorMessage}"; } } public enum HttpFormPostMode { UrlEncoded, MultiPart }; } ================================================ FILE: Westwind.Utilities/Utilities/HttpUtils.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; namespace Westwind.Utilities { /// /// Simple HTTP request helper to let you retrieve data from a Web /// server and convert it to something useful. /// [Obsolete("Use HttpClientUtils if possible.")] public static class HttpUtils { /// /// Retrieves and Http request and returns data as a string. /// /// A url to call for a GET request without custom headers /// string of HTTP response public static string HttpRequestString(string url) { return HttpRequestString(new HttpRequestSettings() { Url = url }); } /// /// Retrieves and Http request and returns data as a string. /// /// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more /// string of HTTP response public static string HttpRequestString(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); if (settings.Content != null) { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; if (settings.Content is string) { settings.CapturedRequestContent = settings.Content as string; settings.CapturedResponseContent = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent); } else if (settings.Content is byte[]) { settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]); settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData); } else throw new ArgumentException("Data must be either string or byte[]."); } else settings.CapturedResponseContent = client.DownloadString(settings.Url); settings.Response = client.Response; return settings.CapturedResponseContent; } /// /// Retrieves bytes from the server without any request customizations /// /// Url to access /// public static byte[] HttpRequestBytes(string url) { return HttpRequestBytes(new HttpRequestSettings() { Url = url }); } /// /// Retrieves bytes from the server /// /// /// public static byte[] HttpRequestBytes(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); if (settings.Content != null) { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; if (settings.Content is string) { settings.CapturedRequestContent = settings.Content as string; settings.ResponseByteData = client.UploadData(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent)); } else if (settings.Content is byte[]) { settings.ResponseByteData = client.UploadData(settings.Url, settings.Content as byte[]); } else throw new ArgumentException("Data must be either string or byte[]."); } else settings.ResponseByteData = client.DownloadData(settings.Url); settings.Response = client.Response; return settings.ResponseByteData; } /// /// Retrieves and Http request and returns data as a string. /// /// The Url to access /// string of HTTP response public static async Task HttpRequestStringAsync(string url) { return await HttpRequestStringAsync(new HttpRequestSettings() { Url = url }); } /// /// Retrieves and Http request and returns data as a string. /// /// Pass HTTP request configuration parameters object to set the URL, Verb, Headers, content and more /// string of HTTP response public static async Task HttpRequestStringAsync(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); if (settings.Content != null) { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; if (settings.Content is string) { settings.CapturedRequestContent = settings.Content as string; settings.CapturedResponseContent = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent); } else if (settings.Content is byte[]) { settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]); settings.CapturedResponseContent = Encoding.UTF8.GetString(settings.ResponseByteData); } else throw new ArgumentException("Data must be either string or byte[]."); } else settings.CapturedResponseContent = await client.DownloadStringTaskAsync(new Uri(settings.Url)); settings.Response = client.Response; return settings.CapturedResponseContent; } /// /// Retrieves bytes from the server without any request customizations /// /// Url to access /// public static async Task HttpRequestBytesAsync(string url) { return await HttpRequestBytesAsync(new HttpRequestSettings() { Url = url }); } /// /// Retrieves bytes from the server /// /// /// public static async Task HttpRequestBytesAsync(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); if (settings.Content != null) { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; if (settings.Content is string) { settings.CapturedRequestContent = settings.Content as string; settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.HttpVerb, settings.Encoding.GetBytes(settings.CapturedRequestContent)); } else if (settings.Content is byte[]) { settings.ResponseByteData = await client.UploadDataTaskAsync(settings.Url, settings.Content as byte[]); } else throw new ArgumentException("Data must be either string or byte[]."); } else settings.ResponseByteData = await client.DownloadDataTaskAsync(settings.Url); settings.Response = client.Response; return settings.ResponseByteData; } /// /// Makes an HTTP with option JSON data serialized from an object /// and parses the result from JSON back into an object. /// Assumes that the service returns a JSON response /// /// The type of the object returned /// /// Configuration object for the HTTP request made to the server. /// /// deserialized value/object from returned JSON data public static TResultType JsonRequest(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); client.Headers.Add("Accept", "application/json"); string jsonResult; if (settings.Content != null) { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; else client.Headers["Content-type"] = "application/json;charset=utf-8;"; if (!settings.IsRawData) { settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content, throwExceptions: true); } else settings.CapturedRequestContent = settings.Content as string; jsonResult = client.UploadString(settings.Url, settings.HttpVerb, settings.CapturedRequestContent); if (jsonResult == null) return default(TResultType); } else jsonResult = client.DownloadString(settings.Url); settings.CapturedResponseContent = jsonResult; settings.Response = client.Response; return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true); } /// /// Makes an HTTP with option JSON data serialized from an object /// and parses the result from JSON back into an object. /// Assumes that the service returns a JSON response and that /// any data sent is json. /// /// The type of the object returned /// /// Configuration object for the HTTP request made to the server. /// /// deserialized value/object from returned JSON data public static async Task JsonRequestAsync(HttpRequestSettings settings) { var client = new HttpUtilsWebClient(settings); client.Headers.Add("Accept", "application/json"); string jsonResult; if (settings.HttpVerb == "POST" || settings.HttpVerb == "PUT" || settings.HttpVerb == "PATCH") { if (!string.IsNullOrEmpty(settings.ContentType)) client.Headers["Content-type"] = settings.ContentType; else client.Headers["Content-type"] = "application/json"; if (!settings.IsRawData) settings.CapturedRequestContent = JsonSerializationUtils.Serialize(settings.Content, throwExceptions: true); else settings.CapturedRequestContent = settings.Content as string; jsonResult = await client.UploadStringTaskAsync(settings.Url, settings.HttpVerb, settings.CapturedRequestContent); if (jsonResult == null) return default(TResultType); } else jsonResult = await client.DownloadStringTaskAsync(settings.Url); settings.CapturedResponseContent = jsonResult; settings.Response = client.Response; return (TResultType)JsonSerializationUtils.Deserialize(jsonResult, typeof(TResultType), true); } /// /// Creates a temporary image file from a download from a URL /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Url of image to download /// Optional output image file. Filename may change extension if the image format doesn't match the filename. /// If not passed a temporary files file is created. Caller is responsible for cleaning up this file. /// /// Optional Http Settings for the request /// image filename or null on failure. Note that the filename may have a different extension than the request filename parameter. public static string DownloadImageToFile(string imageUrl, string filename = null, HttpRequestSettings settings = null) { if (string.IsNullOrEmpty(imageUrl) || !imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://") ) return null; string newFilename; if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId()); } filename = Path.ChangeExtension(filename, "bin"); var client = new HttpUtilsWebClient(settings); try { client.DownloadFile(imageUrl, filename); var ct = client.Response.ContentType; // works if (string.IsNullOrEmpty(ct) || !ct.StartsWith("image/")) return null; var ext = ImageUtils.GetExtensionFromMediaType(ct); if (ext == null) return null; // invalid image type newFilename = Path.ChangeExtension(filename, ext); if (File.Exists(newFilename)) File.Delete(newFilename); // rename the file File.Move(filename, newFilename); } catch { if (File.Exists(filename)) File.Delete(filename); return null; } return newFilename; } /// /// Downloads an image to a temporary file or a file you specify, automatically /// adjusting the file extension to match the image content type (ie. .png, jpg, .gif etc) /// Always use the return value to receive the final image file name. /// /// If you don't pass a file a temporary file is created in Temp Files folder. /// You're responsible for cleaning up the file after you are done with it. /// /// You should check the filename that is returned regardless of whether you /// passed in a filename - if the file is of a different image type the /// extension may be changed. /// /// Url of image to download /// /// Optional output image file name. Filename may change extension if the image format doesn't match the filename. /// If not passed a temporary files file is created in the temp file location and you can move the file /// manually. If using the temporary file, caller is responsible for cleaning up the file after creation. /// /// Optional more detailed Http Settings for the request /// file name that was created or null public static async Task DownloadImageToFileAsync(string imageUrl, string filename = null, HttpRequestSettings settings = null) { if (string.IsNullOrEmpty(imageUrl) || !imageUrl.StartsWith("http://") && !imageUrl.StartsWith("https://") ) return null; string newFilename; if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(Path.GetTempPath(), "~img-" + DataUtils.GenerateUniqueId()); } filename = Path.ChangeExtension(filename, "bin"); var client = new HttpUtilsWebClient(settings); try { await client.DownloadFileTaskAsync(imageUrl, filename); var ct = client.Response.ContentType; var ext = ImageUtils.GetExtensionFromMediaType(ct); if (ext == null) { if(File.Exists(filename)) File.Delete(filename); return null; // invalid image type } newFilename = Path.ChangeExtension(filename, ext); if (File.Exists(newFilename)) File.Delete(newFilename); // rename the file File.Move(filename, newFilename); } catch { if (File.Exists(filename)) File.Delete(filename); return null; } return newFilename; } /// /// Helper method that creates a proxy instance to store on the Proxy property /// /// /// Proxy Address to create or "default" for Windows default proxy. /// Null or empty means no proxy is set /// /// /// Optional - bypass on local if you're specifying an explicit url /// /// /// Optional list of root domain Urls that are bypassed /// /// public static IWebProxy CreateWebProxy(string proxyAddress = null, bool bypassonLocal = false, string[] bypassList = null) { IWebProxy proxy = null; if (string.IsNullOrEmpty(proxyAddress)) return null; if (proxyAddress.Equals("default", StringComparison.OrdinalIgnoreCase)) { proxy = WebRequest.GetSystemWebProxy(); } else { proxy = new WebProxy(proxyAddress, bypassonLocal, bypassList); } return proxy; } } /// /// Configuration object for Http Requests used by the HttpUtils /// methods. Allows you to set the URL, verb, headers proxy and /// credentials that are then passed to the HTTP client. /// public class HttpRequestSettings { /// /// The URL to send the request to /// public string Url { get; set; } /// /// The HTTP verb to use when sending the request /// public string HttpVerb { get; set; } /// /// The Request content to send to the server. /// Data can be either string or byte[] type /// public object Content { get; set; } /// /// Content Encoding for the data sent to to server /// public Encoding Encoding { get; set; } /// /// When true data is not translated. For example /// when using JSON Request if you want to send /// raw POST data rather than a serialized object. /// public bool IsRawData { get; set; } /// /// The content type of any request data sent to the server /// in the Data property. /// public string ContentType { get; set; } /// /// The request timeout in milliseconds. 0 for default (20 seconds typically) /// public int Timeout { get; set; } /// /// Any Http request headers you want to set for this request /// public Dictionary Headers { get; set; } /// /// Authentication information for this request /// public NetworkCredential Credentials { get; set; } /// /// Determines whether credentials pre-authenticate /// public bool PreAuthenticate { get; set; } /// /// An optional proxy to set for this request /// public IWebProxy Proxy { get; set; } /// /// Capture request string data that was actually sent to the server. /// public string CapturedRequestContent { get; set; } /// /// Captured string Response Data from the server /// public string CapturedResponseContent { get; set; } /// /// Capture binary Response data from the server when /// using the Data methods rather than string methods. /// public byte[] ResponseByteData { get; set; } /// /// The HTTP Status code of the HTTP response /// public HttpStatusCode ResponseStatusCode { get { if (Response != null) return Response.StatusCode; return HttpStatusCode.OK; } } /// /// Instance of the full HttpResponse object that gives access /// to the full HttpWebResponse object to provide things /// like Response headers, status etc. /// public HttpWebResponse Response { get; set; } /// /// The User Agent string sent to the server /// public string UserAgent { get; set; } /// /// By default (false) throws a Web Exception on 500 and 400 repsonses. /// This is the default WebClient behavior. /// /// If `true` doesn't throw, but instead returns the HTTP response. /// Useful if you need to return error messages on 500 and 400 responses /// from API requests. /// public bool DontThrowOnErrorStatusCodes { get; set; } /// /// Http Protocol Version 1.1 /// public string HttpVersion { get; set; } = "1.1"; public HttpRequestSettings() { HttpVerb = "GET"; Headers = new Dictionary(); Encoding = Encoding.UTF8; UserAgent = "West Wind .NET Http Client"; } } } ================================================ FILE: Westwind.Utilities/Utilities/HttpUtilsWebClient.cs ================================================ using System; using System.Net; using System.Web; namespace Westwind.Utilities { /// /// Customized version of WebClient that provides access /// to the Response object so we can read result data /// from the Response. /// public class HttpUtilsWebClient : WebClient { #pragma warning disable SYSLIB0014 /// /// Intializes this instance of WebClient with settings values /// /// public HttpUtilsWebClient(HttpRequestSettings settings = null) { Settings = settings; if (settings != null) { if (settings.Credentials != null) Credentials = settings.Credentials; if (settings.Proxy != null) Proxy = settings.Proxy; if (settings.Encoding != null) Encoding = settings.Encoding; if (settings.Headers != null) { foreach (var header in settings.Headers) { Headers[header.Key] = header.Value; } } } } #pragma warning restore SYSLIB0014 internal HttpRequestSettings Settings { get; set; } internal HttpWebResponse Response { get; set; } internal HttpWebRequest Request { get; set; } protected override WebRequest GetWebRequest(Uri address) { Request = base.GetWebRequest(address) as HttpWebRequest; if (Settings != null) { if (Settings.Timeout > 0) { Request.Timeout = Settings.Timeout; Request.ReadWriteTimeout = Settings.Timeout; Request.PreAuthenticate = Settings.PreAuthenticate; } if (!string.IsNullOrEmpty(Settings.UserAgent)) Request.UserAgent = Settings.UserAgent; if (!string.IsNullOrEmpty(Settings.HttpVerb)) Request.Method = Settings.HttpVerb; } return Request; } protected override WebResponse GetWebResponse(WebRequest request) { try { Response = base.GetWebResponse(request) as HttpWebResponse; } catch (WebException ex) { if(Settings.DontThrowOnErrorStatusCodes) { Response = ex?.Response as HttpWebResponse; return ex?.Response; } #pragma warning disable CA2200 throw; #pragma warning restore CA2200 } return Response; } protected override WebResponse GetWebResponse(WebRequest request, System.IAsyncResult result) { try { Response = base.GetWebResponse(request, result) as HttpWebResponse; } catch (WebException ex) { if (Settings.DontThrowOnErrorStatusCodes) { Response = ex?.Response as HttpWebResponse; return ex?.Response; } #pragma warning disable CA2200 throw; #pragma warning restore CA2200 } return Response; } } } ================================================ FILE: Westwind.Utilities/Utilities/ImageUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion #if NETFULL using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; #endif using System; using System.Collections.Generic; using System.IO; namespace Westwind.Utilities { /// /// Summary description for wwImaging. /// public static class ImageUtils { #if NETFULL /// /// Creates a resized bitmap from an existing image on disk. Resizes the image by /// creating an aspect ratio safe image. Image is sized to the larger size of width /// height and then smaller size is adjusted by aspect ratio. /// /// Image is returned as Bitmap - call Dispose() on the returned Bitmap object /// /// /// /// /// Bitmap or null public static Bitmap ResizeImage(string filename, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic) { try { using (Bitmap bmp = new Bitmap(filename)) { return ResizeImage(bmp, width, height, mode); } } catch { return null; } } /// /// Resizes an image from byte array and returns a Bitmap. /// Make sure you Dispose() the bitmap after you're done /// with it! /// /// /// /// /// public static Bitmap ResizeImage(byte[] data, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic) { try { using (Bitmap bmp = new Bitmap(new MemoryStream(data))) { return ResizeImage(bmp, width, height, mode); } } catch { return null; } } /// /// Resizes an image and saves the image to a file /// /// /// /// /// /// /// /// If using a jpeg image /// /// public static bool ResizeImage(string filename, string outputFilename, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic, int jpegCompressionMode = 85) { using (var bmpOut = ResizeImage(filename, width, height, mode)) { var imageFormat = GetImageFormatFromFilename(filename); if (imageFormat == ImageFormat.Emf) imageFormat = bmpOut.RawFormat; if(imageFormat == ImageFormat.Jpeg) SaveJpeg(bmpOut, outputFilename, jpegCompressionMode); else bmpOut.Save(outputFilename, imageFormat); } return true; } /// /// Resizes an image from a bitmap. /// Note image will resize to the larger of the two sides /// /// Bitmap to resize /// new width /// new height /// resized or original bitmap. Be sure to Dispose this bitmap public static Bitmap ResizeImage(Bitmap bmp, int width, int height, InterpolationMode mode = InterpolationMode.HighQualityBicubic) { Bitmap bmpOut = null; try { decimal ratio; int newWidth = 0; int newHeight = 0; // If the image is smaller than a thumbnail just return original size if (bmp.Width < width && bmp.Height < height) { newWidth = bmp.Width; newHeight = bmp.Height; } else { if (bmp.Width == bmp.Height) { if (height > width) { newHeight = height; newWidth = height; } else { newHeight = width; newWidth = width; } } else if (bmp.Width >= bmp.Height) { ratio = (decimal)width / bmp.Width; newWidth = width; decimal lnTemp = bmp.Height * ratio; newHeight = (int)lnTemp; } else { ratio = (decimal)height / bmp.Height; newHeight = height; decimal lnTemp = bmp.Width * ratio; newWidth = (int)lnTemp; } } bmpOut = new Bitmap(newWidth, newHeight); bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); using (Graphics g = Graphics.FromImage(bmpOut)) { g.InterpolationMode = mode; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight); g.DrawImage(bmp, 0, 0, newWidth, newHeight); } } catch { return null; } return bmpOut; } /// /// Adjusts an image to a specific aspect ratio by clipping /// from the center outward - essentially capturing the center /// to fit the width/height of the aspect ratio. /// /// Stream to an image /// Aspect ratio default is 16:9 /// Optionally resize with to this width (if larger than height) /// Optionally resize to this height (if larger than width) /// Bitmap image - make sure to dispose this image public static Bitmap AdjustImageToRatio(Stream imageStream, decimal ratio = 16M / 9M, int resizeWidth = 0, int resizeHeight = 0) { if (imageStream == null) return null; decimal width = 0; decimal height = 0; Bitmap bmpOut = null; Bitmap bitmap = null; try { bitmap = new Bitmap(imageStream); height = bitmap.Height; width = bitmap.Width; if (width >= height * ratio) { // clip width decimal clipWidth = height * ratio; decimal clipX = (width - clipWidth) / 2; bmpOut = new Bitmap((int) clipWidth, (int) height); bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution); using (Graphics g = Graphics.FromImage(bmpOut)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; var sourceRect = new Rectangle((int) clipX, 0, (int) clipWidth, (int) height); var targetRect = new Rectangle(0, 0, (int) clipWidth, (int) height); g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel); } } else if (width < height * ratio) { // clip height decimal clipHeight = width / ratio; decimal clipY = (height - clipHeight) / 2; bmpOut = new Bitmap((int) width, (int) clipHeight); bmpOut.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution); using (Graphics g = Graphics.FromImage(bmpOut)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; var sourceRect = new Rectangle(0, (int) clipY, (int) width, (int) clipHeight); var targetRect = new Rectangle(0, 0, (int) width, (int) clipHeight); g.DrawImage(bitmap, targetRect, sourceRect, GraphicsUnit.Pixel); } } else bmpOut = bitmap; if (resizeWidth == 0 || resizeWidth == 0) return bmpOut; var resizedImage = ResizeImage(bmpOut, resizeWidth, resizeHeight); return resizedImage; } finally { bitmap?.Dispose(); bmpOut?.Dispose(); } } /// /// Adjusts an image to a specific aspect ratio by clipping /// from the center outward - essentially capturing the center /// to fit the width/height of the aspect ratio. /// /// /// /// /// /// public static Bitmap AdjustImageToRatio(byte[] imageContent, decimal ratio = 16M / 9M, int resizeWidth = 0, int resizeHeight = 0) { using (var ms = new MemoryStream(imageContent)) { return AdjustImageToRatio(ms, ratio, resizeWidth, resizeHeight); } } /// /// Saves a jpeg BitMap to disk with a jpeg quality setting. /// Does not dispose the bitmap. /// /// Bitmap to save /// file to write it to /// /// public static bool SaveJpeg(Bitmap bmp, string outputFileName, long jpegQuality = 90) { try { //get the jpeg codec ImageCodecInfo jpegCodec = null; if (Encoders.ContainsKey("image/jpeg")) jpegCodec = Encoders["image/jpeg"]; EncoderParameters encoderParams = null; if (jpegCodec != null) { //create an encoder parameter for the image quality EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality); //create a collection of all parameters that we will pass to the encoder encoderParams = new EncoderParameters(1); encoderParams.Param[0] = qualityParam; } bmp.Save(outputFileName, jpegCodec, encoderParams); } catch { return false; } return true; } /// /// Saves a jpeg BitMap to disk with a jpeg quality setting. /// Does not dispose the bitmap. /// /// Bitmap to save /// Binary stream to write image data to /// /// public static bool SaveJpeg(Bitmap bmp, Stream imageStream, long jpegQuality = 90) { try { //get the jpeg codec ImageCodecInfo jpegCodec = null; if (Encoders.ContainsKey("image/jpeg")) jpegCodec = Encoders["image/jpeg"]; EncoderParameters encoderParams = null; if (jpegCodec != null) { //create an encoder parameter for the image quality EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, jpegQuality); //create a collection of all parameters that we will pass to the encoder encoderParams = new EncoderParameters(1); encoderParams.Param[0] = qualityParam; } bmp.Save(imageStream, jpegCodec, encoderParams); } catch { return false; } return true; } /// /// Rotates an image and writes out the rotated image to a file. /// /// The original image to roatate /// The output file of the rotated image file. If not passed the original file is overwritten /// Type of rotation to perform /// public static bool RoateImage(string filename, string outputFilename = null, RotateFlipType type = RotateFlipType.Rotate90FlipNone, int jpegCompressionMode = 85) { Bitmap bmpOut = null; if (string.IsNullOrEmpty(outputFilename)) outputFilename = filename; try { ImageFormat imageFormat; using (Bitmap bmp = new Bitmap(filename)) { imageFormat = GetImageFormatFromFilename(filename); if (imageFormat == ImageFormat.Emf) imageFormat = bmp.RawFormat; bmp.RotateFlip(type); using (bmpOut = new Bitmap(bmp.Width, bmp.Height)) { bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); Graphics g = Graphics.FromImage(bmpOut); g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height); if (imageFormat == ImageFormat.Jpeg) SaveJpeg(bmpOut, outputFilename, jpegCompressionMode); else bmpOut.Save(outputFilename, imageFormat); } } } catch (Exception ex) { var msg = ex.GetBaseException(); return false; } return true; } public static byte[] RoateImage(byte[] data, RotateFlipType type = RotateFlipType.Rotate90FlipNone) { Bitmap bmpOut = null; try { Bitmap bmp = new Bitmap(new MemoryStream(data)); ImageFormat imageFormat; imageFormat = bmp.RawFormat; bmp.RotateFlip(type); bmpOut = new Bitmap(bmp.Width, bmp.Height); bmpOut.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); Graphics g = Graphics.FromImage(bmpOut); g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(bmp, 0, 0, bmpOut.Width, bmpOut.Height); bmp.Dispose(); using (var ms = new MemoryStream()) { bmpOut.Save(ms, imageFormat); bmpOut.Dispose(); ms.Flush(); return ms.ToArray(); } } catch (Exception ex) { var msg = ex.GetBaseException(); return null; } } /// /// Opens the image and writes it back out, stripping any Exif data /// /// Image to remove exif data from /// image quality 0-100 (100 no compression) public static void StripJpgExifData(string imageFile, int imageQuality = 90) { using (var bmp = new Bitmap(imageFile)) { using (var bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height)) { bmp.Dispose(); SaveJpeg(bmp2, imageFile, imageQuality); } } } /// /// If the image contains image rotation Exif data, apply the image rotation and /// remove the Exif data. Optionally also allows for image resizing in the same /// operation. /// /// Image file to work on /// Jpg /// /// public static void NormalizeJpgImageRotation(string imageFile, int imageQuality = 90, int width = -1, int height = -1) { using (var bmp = new Bitmap(imageFile)) { Bitmap bmp2; using (bmp2 = new Bitmap(bmp, bmp.Width, bmp.Height)) { if (bmp.PropertyItems != null) { foreach (var item in bmp.PropertyItems) { if (item.Id == 0x112) { int orientation = item.Value[0]; if (orientation == 6) bmp2.RotateFlip(RotateFlipType.Rotate90FlipNone); if (orientation == 8) bmp2.RotateFlip(RotateFlipType.Rotate270FlipNone); } } } bmp.Dispose(); if (width > 0 || height > 0) bmp2 = ResizeImage(bmp2, width, height); SaveJpeg(bmp2, imageFile, imageQuality); } } } /// /// A quick lookup for getting image encoders /// public static Dictionary Encoders { //get accessor that creates the dictionary on demand get { //if the quick lookup isn't initialised, initialise it if (_encoders != null) return _encoders; _encoders = new Dictionary(); //get all the codecs foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()) { //add each codec to the quick lookup _encoders.Add(codec.MimeType.ToLower(), codec); } //return the lookup return _encoders; } } private static Dictionary _encoders = null; /// /// Tries to return an image format /// /// /// Image format or ImageFormat.Emf if no match was found public static ImageFormat GetImageFormatFromFilename(string filename) { string ext = Path.GetExtension(filename).ToLower(); ImageFormat imageFormat; if (ext == ".jpg" || ext == ".jpeg") imageFormat = ImageFormat.Jpeg; else if (ext == ".png") imageFormat = ImageFormat.Png; else if (ext == ".gif") imageFormat = ImageFormat.Gif; else if (ext == ".bmp") imageFormat = ImageFormat.Bmp; else imageFormat = ImageFormat.Emf; return imageFormat; } #endif /// /// Returns the image media type for a give file extension based /// on a filename or url passed in. /// /// /// public static string GetImageMediaTypeFromFilename(string file) { if (string.IsNullOrEmpty(file)) return file; string ext = Path.GetExtension(file).ToLower(); if (ext == ".jpg" || ext == ".jpeg") return "image/jpeg"; if (ext == ".png") return "image/png"; if (ext == ".apng") return "image/apgn"; if (ext == ".gif") return "image/gif"; if (ext == ".bmp") return "image/bmp"; if (ext == ".tif" || ext == ".tiff") return "image/tiff"; if (ext == ".webp") return "image/webp"; if (ext == ".ico") return "image/x-icon"; return "application/image"; } /// /// Returns a file extension for a media type/content type. /// /// The media type to convert from /// A file extension or null if no image type is found public static string GetExtensionFromMediaType(string mediaType) { if (mediaType == "image/jpeg") return "jpg"; if (mediaType == "image/png") return "png"; if (mediaType == "image/apng") return "apng"; if (mediaType == "image/bmp") return "bmp"; if (mediaType == "image/gif") return "gif"; if (mediaType == "image/tiff") return "tif"; if (mediaType == "image/svg+xml") return "svg"; if (mediaType == "image/webp") return "webp"; if (mediaType == "image/x-icon") return "ico"; return null; } /// /// Determines whether a filename has a typical image extension /// /// File name to check for image /// true or false public static bool IsImage(string filename) { if (string.IsNullOrEmpty(filename)) return false; string ext = Path.GetExtension(filename).ToLower(); return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".apng" || ext == ".gif" || ext == ".bmp" || ext == ".tif" || ext == ".tiff" || ext == ".webp" || ext == ".ico"; } /// /// Determines whether a byte array is an image based /// on binary signature bytes. /// Supports PNG, JPEG, GIF, BMP, WebP and AVIF formats. /// /// binary file. /// /// The buffer only needs the first 15 bytes max for signature detection /// /// true or false public static bool IsImage(byte[] data) { if (data == null || data.Length < 4) return false; // PNG (also covers APNG, which is an extension of PNG) if (data.Length >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47 && data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A) { return true; } // JPEG if (data.Length >= 4 && data[0] == 0xFF && data[1] == 0xD8 && data[data.Length - 2] == 0xFF && data[data.Length - 1] == 0xD9) { return true; } // GIF if (data.Length >= 6 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38 && (data[4] == 0x39 || data[4] == 0x37) && data[5] == 0x61) { return true; } // BMP if (data.Length >= 2 && data[0] == 0x42 && data[1] == 0x4D) { return true; } // WebP: starts with 'RIFF' + 4 bytes + 'WEBP' if (data.Length >= 12 && data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 && data[8] == 0x57 && data[9] == 0x45 && data[10] == 0x42 && data[11] == 0x50) { return true; } // AVIF: starts with 'ftyp' + 'avif' or 'avis' box brand // ISO BMFF files start with 4-byte box size then 'ftyp' if (data.Length >= 12 && data[4] == 0x66 && data[5] == 0x74 && data[6] == 0x79 && data[7] == 0x70) { // Check major brand string brand = System.Text.Encoding.ASCII.GetString(data, 8, 4); if (brand == "avif" || brand == "avis") { return true; } } return false; } } } ================================================ FILE: Westwind.Utilities/Utilities/JsonSerializationUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2008 - 2009 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.IO; using System.Text; using System.Diagnostics; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; namespace Westwind.Utilities { /// /// JSON Serialization helper class using Json.NET. /// This class has simplified single line serialization /// methods to serialize JSON to and from string and /// files on disk with error handling and some common /// optional configuration options. /// public static class JsonSerializationUtils { //capture reused type instances private static JsonSerializer JsonNet = null; private static object SyncLock = new Object(); private static UTF8Encoding UTF8Encoding = new UTF8Encoding(false); /// /// Serializes an object to an XML string. Unlike the other SerializeObject overloads /// this methods *returns a string* rather than a bool result! /// /// Value to serialize /// Determines if a failure throws or returns null /// /// null on error otherwise the Xml String. /// /// /// If null is passed in null is also returned so you might want /// to check for null before calling this method. /// public static string Serialize(object value, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false) { if (value is null) return "null"; string jsonResult = null; Type type = value.GetType(); JsonTextWriter writer = null; try { var json = CreateJsonNet(throwExceptions, camelCase); StringWriter sw = new StringWriter(); writer = new JsonTextWriter(sw); if (formatJsonOutput) writer.Formatting = Formatting.Indented; writer.QuoteChar = '"'; json.Serialize(writer, value); jsonResult = sw.ToString(); writer.Close(); } catch { if (throwExceptions) throw; jsonResult = null; } finally { if (writer != null) writer.Close(); } return jsonResult; } /// /// Serializes an object instance to a JSON file. /// /// the value to serialize /// Full path to the file to write out with JSON. /// Determines whether exceptions are thrown or false is returned /// if true pretty-formats the JSON with line breaks /// true or false public static bool SerializeToFile(object value, string fileName, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false) { try { Type type = value.GetType(); var json = CreateJsonNet(throwExceptions, camelCase); if (json == null) return false; using (FileStream fs = new FileStream(fileName, FileMode.Create)) { using (StreamWriter sw = new StreamWriter(fs, UTF8Encoding)) { using (var writer = new JsonTextWriter(sw)) { if (formatJsonOutput) writer.Formatting = Formatting.Indented; writer.QuoteChar = '"'; json.Serialize(writer, value); } } } } catch (Exception ex) { Debug.WriteLine("JsonSerializer Serialize error: " + ex.Message); if (throwExceptions) throw; return false; } return true; } /// /// Deserializes an object, array or value from JSON string to an object or value /// /// /// /// /// public static object Deserialize(string jsonText, Type type, bool throwExceptions = false) { var json = CreateJsonNet(throwExceptions); if (json == null) return null; object result = null; JsonTextReader reader = null; try { StringReader sr = new StringReader(jsonText); reader = new JsonTextReader(sr); result = json.Deserialize(reader, type); reader.Close(); } catch (Exception ex) { Debug.WriteLine("JsonSerializer Deserialize error: " + ex.Message); if (throwExceptions) throw; return null; } finally { if (reader != null) reader.Close(); } return result; } /// /// Deserializes an object, array or value from JSON string to an object or value /// /// /// /// public static T Deserialize(string jsonText, bool throwExceptions = false) { var res = Deserialize(jsonText, typeof(T), throwExceptions); if (res == null) return default(T); return (T) res; } /// /// Deserializes an object from file and returns a reference. /// /// name of the file to serialize to /// The Type of the object. Use typeof(yourobject class) /// determines whether we use Xml or Binary serialization /// determines whether failure will throw rather than return null on failure /// Instance of the deserialized object or null. Must be cast to your object type public static object DeserializeFromFile(string fileName, Type objectType, bool throwExceptions = false) { var json = CreateJsonNet(throwExceptions); if (json == null) return null; object result; JsonTextReader reader; FileStream fs; try { using (fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { using (var sr = new StreamReader(fs, Encoding.UTF8)) { using (reader = new JsonTextReader(sr)) { result = json.Deserialize(reader, objectType); } } } } catch (Exception ex) { Debug.WriteLine("JsonNetSerialization Deserialization Error: " + ex.Message); if (throwExceptions) throw; return null; } return result; } /// /// Deserializes an object from file and returns a reference. /// /// name of the file to serialize to /// determines whether we use Xml or Binary serialization /// determines whether failure will throw rather than return null on failure /// Instance of the deserialized object or null. Must be cast to your object type public static T DeserializeFromFile(string fileName, bool throwExceptions = false) { var res = DeserializeFromFile(fileName, typeof(T), throwExceptions); if (res == null) return default(T); return (T) res; } /// /// Takes a single line JSON string and pretty formats /// it using indented formatting. /// /// /// public static string FormatJsonString(string json) { return JToken.Parse(json).ToString(Formatting.Indented) as string; } /// /// Dynamically creates an instance of JSON.NET /// /// If true throws exceptions otherwise returns null /// Dynamic JsonSerializer instance public static JsonSerializer CreateJsonNet(bool throwExceptions = true, bool camelCase = false) { if (JsonNet != null) return JsonNet; lock (SyncLock) { if (JsonNet != null) return JsonNet; // Try to create instance JsonSerializer json; try { json = new JsonSerializer(); } catch { if (throwExceptions) throw; return null; } if (json == null) return null; json.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // Enums as strings in JSON var enumConverter = new StringEnumConverter(); json.Converters.Add(enumConverter); if (camelCase) json.ContractResolver = new CamelCasePropertyNamesContractResolver(); JsonNet = json; } return JsonNet; } } } ================================================ FILE: Westwind.Utilities/Utilities/KnownFolders.cs ================================================ using System; using System.Runtime.InteropServices; using static System.Environment; namespace Westwind.Utilities { /// /// Class that returns special Windows file system paths. Extends the folder list /// beyond what Environment.GetFolderPath(Environment.SpecialFolder) provides /// with additional Windows known folders like Library, Downloads etc. /// public static class KnownFolders { /// /// Gets the current path to the specified known folder as currently configured. This does /// not require the folder to be existent. /// /// The known folder which current path will be returned. /// The default path of the known folder. /// Thrown if the path /// could not be retrieved. public static string GetPath(KnownFolder knownFolder) { return GetPath(knownFolder, false); } /// /// Gets the current path to the specified known folder as currently configured. This does /// not require the folder to be existent. /// /// The known folder which current path will be returned. /// Specifies if the paths of the default user (user profile /// template) will be used. This requires administrative rights. /// The default path of the known folder. /// Thrown if the path /// could not be retrieved. public static string GetPath(KnownFolder knownFolder, bool defaultUser) { return GetPath(knownFolder, KnownFolderFlags.DontVerify, defaultUser); } /// /// Gets the default path to the specified known folder. This does not require the folder /// to be existent. /// /// The known folder which default path will be returned. /// The current (and possibly redirected) path of the known folder. /// Thrown if the path /// could not be retrieved. public static string GetDefaultPath(KnownFolder knownFolder) { return GetDefaultPath(knownFolder, false); } /// /// Gets the default path to the specified known folder. This does not require the folder /// to be existent. /// /// The known folder which default path will be returned. /// Specifies if the paths of the default user (user profile /// template) will be used. This requires administrative rights. /// The current (and possibly redirected) path of the known folder. /// Thrown if the path /// could not be retrieved. public static string GetDefaultPath(KnownFolder knownFolder, bool defaultUser) { return GetPath(knownFolder, KnownFolderFlags.DefaultPath | KnownFolderFlags.DontVerify, defaultUser); } /// /// Creates and initializes the known folder. /// /// The known folder which will be initialized. /// Thrown if the known /// folder could not be initialized. public static void Initialize(KnownFolder knownFolder) { Initialize(knownFolder, false); } /// /// Creates and initializes the known folder. /// /// The known folder which will be initialized. /// Specifies if the paths of the default user (user profile /// template) will be used. This requires administrative rights. /// Thrown if the known /// folder could not be initialized. public static void Initialize(KnownFolder knownFolder, bool defaultUser) { GetPath(knownFolder, KnownFolderFlags.Create | KnownFolderFlags.Init, defaultUser); } private static string GetPath(KnownFolder knownFolder, KnownFolderFlags flags, bool defaultUser) { // Handle SpecialFolder-mapped values SpecialFolder? specialFolder = knownFolder switch { KnownFolder.UserProfile => SpecialFolder.UserProfile, KnownFolder.ProgramFiles => SpecialFolder.ProgramFiles, KnownFolder.ProgramFilesX86 => SpecialFolder.ProgramFilesX86, KnownFolder.Programs => SpecialFolder.Programs, KnownFolder.ApplicationData => SpecialFolder.ApplicationData, KnownFolder.LocalApplicationData => SpecialFolder.LocalApplicationData, KnownFolder.AdminTools => SpecialFolder.AdminTools, KnownFolder.Startup => SpecialFolder.Startup, KnownFolder.Recent => SpecialFolder.Recent, KnownFolder.SendTo => SpecialFolder.SendTo, KnownFolder.StartMenu => SpecialFolder.StartMenu, KnownFolder.DesktopDirectory => SpecialFolder.DesktopDirectory, KnownFolder.MyComputer => SpecialFolder.MyComputer, KnownFolder.NetworkShortcuts => SpecialFolder.NetworkShortcuts, KnownFolder.Fonts => SpecialFolder.Fonts, KnownFolder.Templates => SpecialFolder.Templates, KnownFolder.CommonStartMenu => SpecialFolder.CommonStartMenu, KnownFolder.CommonPrograms => SpecialFolder.CommonPrograms, KnownFolder.CommonStartup => SpecialFolder.CommonStartup, KnownFolder.CommonDesktopDirectory => SpecialFolder.CommonDesktopDirectory, KnownFolder.PrinterShortcuts => SpecialFolder.PrinterShortcuts, KnownFolder.InternetCache => SpecialFolder.InternetCache, KnownFolder.Cookies => SpecialFolder.Cookies, KnownFolder.History => SpecialFolder.History, KnownFolder.CommonApplicationData => SpecialFolder.CommonApplicationData, KnownFolder.Windows => SpecialFolder.Windows, KnownFolder.System => SpecialFolder.System, KnownFolder.SystemX86 => SpecialFolder.SystemX86, KnownFolder.CommonProgramFiles => SpecialFolder.CommonProgramFiles, KnownFolder.CommonProgramFilesX86 => SpecialFolder.CommonProgramFilesX86, KnownFolder.CommonTemplates => SpecialFolder.CommonTemplates, KnownFolder.CommonDocuments => SpecialFolder.CommonDocuments, KnownFolder.CommonAdminTools => SpecialFolder.CommonAdminTools, KnownFolder.CommonMusic => SpecialFolder.CommonMusic, KnownFolder.CommonPictures => SpecialFolder.CommonPictures, KnownFolder.CommonVideos => SpecialFolder.CommonVideos, KnownFolder.Resources => SpecialFolder.Resources, KnownFolder.LocalizedResources => SpecialFolder.LocalizedResources, KnownFolder.CommonOemLinks => SpecialFolder.CommonOemLinks, KnownFolder.CDBurning => SpecialFolder.CDBurning, _ => null }; if (specialFolder != null) return Environment.GetFolderPath(specialFolder.Value); #if NET60_OR_GREATER if (!OperatingSystem.IsWindows()) return string.Empty; // exit if not windows #endif // these only work on Windows via PInvoke to Windows API IntPtr outPath; int result = SHGetKnownFolderPath(new Guid(_knownFolderGuids[(int)knownFolder]), (uint)flags, new IntPtr(defaultUser ? -1 : 0), out outPath); if (result >= 0) { return Marshal.PtrToStringUni(outPath); } else { throw new ExternalException("Unable to retrieve the known folder path. It may not " + "be available on this system.", result); } } /// /// Retrieves the full path of a known folder identified by the folder's KnownFolderID. /// /// A KnownFolderID that identifies the folder. /// Flags that specify special retrieval options. This value can be /// 0; otherwise, one or more of the KnownFolderFlag values. /// An access token that represents a particular user. If this /// parameter is NULL, which is the most common usage, the function requests the known /// folder for the current user. Assigning a value of -1 indicates the Default User. /// The default user profile is duplicated when any new user account is created. /// Note that access to the Default User folders requires administrator privileges. /// /// When this method returns, contains the address of a string that /// specifies the path of the known folder. The returned path does not include a /// trailing backslash. /// Returns S_OK if successful, or an error value otherwise. [DllImport("Shell32.dll")] private static extern int SHGetKnownFolderPath( [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr ppszPath); [Flags] private enum KnownFolderFlags : uint { SimpleIDList = 0x00000100, NotParentRelative = 0x00000200, DefaultPath = 0x00000400, Init = 0x00000800, NoAlias = 0x00001000, DontUnexpand = 0x00002000, DontVerify = 0x00004000, Create = 0x00008000, NoAppcontainerRedirection = 0x00010000, AliasOnly = 0x80000000 } private static string[] _knownFolderGuids = new string[] { //Folder Ids: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx "{56784854-C6CB-462B-8169-88E350ACB882}", // Contacts "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", // Desktop "{FDD39AD0-238F-46AF-ADB4-6C85480369C7}", // Documents "{374DE290-123F-4565-9164-39C4925E467B}", // Downloads "{1777F761-68AD-4D8A-87BD-30B759FA33DD}", // Favorites "{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}", // Links "{4BD8D571-6D19-48D3-BE97-422220080E43}", // Music "{33E28130-4E1E-4676-835A-98395C3BC3BB}", // Pictures "{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}", // SavedGames "{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}", // SavedSearches "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}", // Videos "{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}", // DocumentsLibrary "{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}" // Libraries }; } /// /// Standard folders registered with the system. These folders are installed with Windows Vista /// and later operating systems, and a computer will have only folders appropriate to it /// installed. /// public enum KnownFolder { Contacts, Desktop, Documents, Downloads, Favorites, Links, Music, Pictures, SavedGames, SavedSearches, Videos, DocumentsLibrary, Libraries, /* Separately handled */ UserProfile, ProgramFiles, ProgramFilesX86, Programs, ApplicationData, LocalApplicationData, AdminTools, // Additional SpecialFolder values Startup, Recent, SendTo, StartMenu, DesktopDirectory, MyComputer, NetworkShortcuts, Fonts, Templates, CommonStartMenu, CommonPrograms, CommonStartup, CommonDesktopDirectory, PrinterShortcuts, InternetCache, Cookies, History, CommonApplicationData, Windows, System, SystemX86, CommonProgramFiles, CommonProgramFilesX86, CommonTemplates, CommonDocuments, CommonAdminTools, CommonMusic, CommonPictures, CommonVideos, Resources, LocalizedResources, CommonOemLinks, CDBurning } } ================================================ FILE: Westwind.Utilities/Utilities/LanguageUtils.cs ================================================ using System; namespace Westwind.Utilities { public static class LanguageUtils { /// /// Runs an operation and ignores any Exceptions that occur. /// Returns true or falls depending on whether catch was /// triggered /// /// lambda that performs an operation that might throw /// public static bool IgnoreErrors(Action operation) { if (operation == null) return false; try { operation.Invoke(); } catch { return false; } return true; } /// /// Runs an function that returns a value and ignores any Exceptions that occur. /// Returns true or falls depending on whether catch was /// triggered /// /// parameterless lamda that returns a value of T /// Default value returned if operation fails public static T IgnoreErrors(Func operation, T defaultValue = default(T)) { if (operation == null) return defaultValue; T result; try { result = operation.Invoke(); } catch { result = defaultValue; } return result; } } } ================================================ FILE: Westwind.Utilities/Utilities/NetworkUtils.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; namespace Westwind.Utilities { public static class NetworkUtils { /// /// Retrieves a base domain name from a full domain name. /// For example: www.west-wind.com produces west-wind.com /// /// Dns Domain name as a string /// public static string GetBaseDomain(string domainName) { var tokens = domainName.Split('.'); // only split 3 urls like www.west-wind.com if (tokens == null || tokens.Length != 3) return domainName; var tok = new List(tokens); var remove = tokens.Length - 2; tok.RemoveRange(0, remove); return tok[0] + "." + tok[1]; ; } /// /// Returns the base domain from a domain name /// Example: http://www.west-wind.com returns west-wind.com /// /// /// public static string GetBaseDomain(this Uri uri) { if (uri.HostNameType == UriHostNameType.Dns) return GetBaseDomain(uri.DnsSafeHost); return uri.Host; } /// /// Checks to see if an IP Address or Domain is a local address /// /// /// Do not use in high traffic situations, as this involves a /// DNS lookup. If no local hostname is found it goes out to a /// DNS server to retrieve IP Addresses. /// /// Hostname or IP Address /// true or false public static bool IsLocalIpAddress(string hostOrIp) { if(string.IsNullOrEmpty(hostOrIp)) return false; try { // get IP Mapped to passed host IPAddress[] hostIPs = Dns.GetHostAddresses(hostOrIp); // get local IP addresses IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName()); // check host ip addresses against local ip addresses for matches foreach (IPAddress hostIP in hostIPs) { // check for localhost/127.0.0.1 if (IPAddress.IsLoopback(hostIP)) return true; // Check if IP Address matches a local IP foreach (IPAddress localIP in localIPs) { if (hostIP.Equals(localIP)) return true; } } } catch {} return false; } /// /// Checks to see if an IP Address or Domain is a local address /// /// /// Do not use in high traffic situations, as this involves a /// DNS lookup. If no local hostname is found it goes out to a /// DNS server to retrieve IP Addresses. /// /// Pass a full URL as an Uri /// public static bool IsLocalIpAddress(Uri uri) { if (uri == null) return false; var host = uri.Host; return IsLocalIpAddress(host); } } } ================================================ FILE: Westwind.Utilities/Utilities/PasswordScrubber.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Westwind.Utilities { /// /// A very basic password scrubber that can scrub passwords from /// JSON and Sql Connection strings. /// /// This is a very basic implementation where you can provide the /// keys to scrub or use default values. /// public class PasswordScrubber { /// /// A static instance that can be used without instantiating first /// public static PasswordScrubber Instance = new PasswordScrubber(); /// /// If set to a non-zero value displays the frist n characters /// of the value that is being obscured. /// public int ShowUnobscuredCharacterCount = 2; /// /// Value displayed for obscured values. If choosing to display first characters /// those are in addition to the obscured values. /// public string ObscuredValueBaseDisplay = "****"; public string ScrubJsonValues(string configString, params string[] jsonKeys) { if (jsonKeys == null || jsonKeys.Length < 1) jsonKeys = new string[1] { "password" }; foreach (var token in jsonKeys) { var matchValue = $@"""{token}"":\s*""(.*?)"""; var match = Regex.Match(configString, matchValue, RegexOptions.Multiline | RegexOptions.IgnoreCase); if (match.Success) { var group = match.Groups[1]; configString = configString.Replace(group.Value, ObscureValue(group.Value)); } } return configString; } public string ScrubSqlConnectionStringValues(string configString, params string[] connKeys) { if (connKeys == null || connKeys.Length < 1) connKeys = new string[] { "pwd", "password" }; foreach (var key in connKeys) { // Sql Connection String pwd var extract = StringUtils.ExtractString(configString, $"{key}=", ";", allowMissingEndDelimiter: true, returnDelimiters: true); if (!string.IsNullOrEmpty(extract)) { var only = StringUtils.ExtractString(extract, $"{key}=", ";", allowMissingEndDelimiter: true, returnDelimiters: false); configString = configString.Replace(extract, $"{key}=" + ObscureValue(only) + ";"); } } return configString; } public string ObscureValue(string value, int showUnobscuredCharacterCount = -1) { if (string.IsNullOrEmpty(value)) return value; if (showUnobscuredCharacterCount < 0) showUnobscuredCharacterCount = ShowUnobscuredCharacterCount; // very short –just display the obscured value without any revealed characters if (showUnobscuredCharacterCount > value.Length + 2) return ObscuredValueBaseDisplay; return value.Substring(0, showUnobscuredCharacterCount) + ObscuredValueBaseDisplay; } } } ================================================ FILE: Westwind.Utilities/Utilities/ReflectionUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2008 - 2009 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Reflection; using System.Collections; using System.Globalization; using System.ComponentModel; using System.Diagnostics; using System.Collections.Generic; using System.IO; using System.Runtime.Versioning; namespace Westwind.Utilities { /// /// Collection of Reflection and type conversion related utility functions /// public static class ReflectionUtils { /// /// Binding Flags constant to be reused for all Reflection access methods. /// public const BindingFlags MemberAccess = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase; public const BindingFlags MemberAccessCom = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; #region Type and Assembly Creation /// /// Creates an instance from a type by calling the parameterless constructor. /// /// Note this will not work with COM objects - continue to use the Activator.CreateInstance /// for COM objects. /// Class wwUtils /// /// /// The type from which to create an instance. /// /// object public static object CreateInstanceFromType(Type typeToCreate, params object[] args) { if (args == null) { Type[] Parms = Type.EmptyTypes; return typeToCreate.GetConstructor(Parms).Invoke(null); } return Activator.CreateInstance(typeToCreate, args); } /// /// Creates an instance of a type based on a string. Assumes that the type's /// /// /// /// public static object CreateInstanceFromString(string typeName, params object[] args) { object instance = null; try { var type = GetTypeFromName(typeName); if (type == null) return null; instance = Activator.CreateInstance(type, args); } catch { return null; } return instance; } /// /// Helper routine that looks up a type name and tries to retrieve the /// full type reference using GetType() and if not found looking /// in the actively executing assemblies and optionally loading /// the specified assembly name. /// /// type to load /// /// Optional assembly name to load from if type cannot be loaded initially. /// Use for lazy loading of assemblies without taking a type dependency. /// /// null public static Type GetTypeFromName(string typeName, string assemblyName ) { var type = Type.GetType(typeName, false); if (type != null) return type; var assemblies = AppDomain.CurrentDomain.GetAssemblies(); // try to find manually foreach (Assembly asm in assemblies) { type = asm.GetType(typeName, false); if (type != null) break; } if (type != null) return type; // see if we can load the assembly if (!string.IsNullOrEmpty(assemblyName)) { var a = LoadAssembly(assemblyName); if (a != null) { type = Type.GetType(typeName, false); if (type != null) return type; } } return null; } /// /// Overload for backwards compatibility which only tries to load /// assemblies that are already loaded in memory. /// /// /// public static Type GetTypeFromName(string typeName) { return GetTypeFromName(typeName, null); } /// /// Creates a COM instance from a ProgID. Loads either /// Exe or DLL servers. /// /// /// #if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] #endif public static object CreateComInstance(string progId) { Type type = Type.GetTypeFromProgID(progId); if (type == null) return null; return Activator.CreateInstance(type); } /// /// Try to load an assembly into the application's app domain. /// Loads by name first then checks for filename /// /// Assembly name or full path /// null on failure public static Assembly LoadAssembly(string assemblyName) { Assembly assembly = null; try { assembly = Assembly.Load(assemblyName); } catch { } if (assembly != null) return assembly; if (File.Exists(assemblyName)) { assembly = Assembly.LoadFrom(assemblyName); if (assembly != null) return assembly; } return null; } /// /// Clones an object using a shallow cloning using private /// MemberwiseClone operation. /// /// Object to copy /// A shallow copy: Value types are copies, reference types are left as references public static object ShallowClone(object source) { return source.GetType() .GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) .Invoke(source, null); } #endregion #region Conversions /// /// Turns a string into a typed value generically. /// Explicitly assigns common types and falls back /// on using type converters for unhandled types. /// /// Common uses: /// * UI -> to data conversions /// * Parsers /// Class ReflectionUtils /// /// /// The string to convert from /// /// /// The type to convert to /// /// /// Culture used for numeric and datetime values. /// /// object. Throws exception if it cannot be converted. public static object StringToTypedValue(string sourceString, Type targetType, CultureInfo culture = null) { object result = null; bool isEmpty = string.IsNullOrEmpty(sourceString); if (culture == null) culture = CultureInfo.CurrentCulture; if (targetType == typeof(string)) result = sourceString; else if (targetType == typeof(Int32) || targetType == typeof(int)) { if (isEmpty) result = 0; else result = Int32.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(Int64)) { if (isEmpty) result = (Int64)0; else result = Int64.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(Int16)) { if (isEmpty) result = (Int16)0; else result = Int16.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(decimal)) { if (isEmpty) result = 0M; else result = decimal.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(DateTime)) { if (isEmpty) result = DateTime.MinValue; else result = Convert.ToDateTime(sourceString, culture.DateTimeFormat); } else if (targetType == typeof(byte)) { if (isEmpty) result = 0; else result = Convert.ToByte(sourceString); } else if (targetType == typeof(double)) { if (isEmpty) result = 0F; else result = Double.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(Single)) { if (isEmpty) result = 0F; else result = Single.Parse(sourceString, NumberStyles.Any, culture.NumberFormat); } else if (targetType == typeof(bool)) { sourceString = sourceString.ToLower(); if (!isEmpty && sourceString == "true" || sourceString == "on" || sourceString == "1" || sourceString == "yes") result = true; else result = false; } else if (targetType == typeof(Guid)) { if (isEmpty) result = Guid.Empty; else result = new Guid(sourceString); } else if (targetType.IsEnum) result = Enum.Parse(targetType, sourceString); else if (targetType == typeof(byte[])) { // TODO: Convert HexBinary string to byte array result = null; } // Handle nullables explicitly since type converter won't handle conversions // properly for things like decimal separators currency formats etc. // Grab underlying type and pass value to that else if (targetType.Name.StartsWith("Nullable`")) { if (sourceString.ToLower() == "null" || sourceString == string.Empty) result = null; else { targetType = Nullable.GetUnderlyingType(targetType); result = StringToTypedValue(sourceString, targetType); } } else { TypeConverter converter = TypeDescriptor.GetConverter(targetType); if (converter != null && converter.CanConvertFrom(typeof(string))) result = converter.ConvertFromString(null, culture, sourceString); else { Debug.Assert(false, string.Format("Type Conversion not handled in StringToTypedValue for {0} {1}", targetType.Name, sourceString)); throw (new InvalidCastException("Type Conversion failed for: " + targetType.Name)); } } return result; } /// /// Generic version allow for automatic type conversion without the explicit type /// parameter /// /// Type to be converted to /// input string value to be converted /// Culture applied to conversion /// public static T StringToTypedValue(string sourceString, CultureInfo culture = null) { return (T)StringToTypedValue(sourceString, typeof(T), culture); } /// /// Converts a type to string if possible. This method supports an optional culture generically on any value. /// It calls the ToString() method on common types and uses a type converter on all other objects /// if available /// /// The Value or Object to convert to a string /// Culture for numeric and DateTime values /// Return string for unsupported types /// string public static string TypedValueToString(object rawValue, CultureInfo culture = null, string unsupportedReturn = null) { if (rawValue == null) return string.Empty; if (culture == null) culture = CultureInfo.CurrentCulture; Type valueType = rawValue.GetType(); string returnValue = null; if (valueType == typeof(string)) returnValue = rawValue as string; else if (valueType == typeof(int) || valueType == typeof(decimal) || valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(Single)) returnValue = string.Format(culture.NumberFormat, "{0}", rawValue); else if (valueType == typeof(DateTime)) returnValue = string.Format(culture.DateTimeFormat, "{0}", rawValue); else if (valueType == typeof(bool) || valueType == typeof(Byte) || valueType.IsEnum) returnValue = rawValue.ToString(); else if (valueType == typeof(Guid?)) { if (rawValue == null) returnValue = string.Empty; else return rawValue.ToString(); } else { // Any type that supports a type converter TypeConverter converter = TypeDescriptor.GetConverter(valueType); if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) returnValue = converter.ConvertToString(null, culture, rawValue); else { // Last resort - just call ToString() on unknown type if (!string.IsNullOrEmpty(unsupportedReturn)) returnValue = unsupportedReturn; else returnValue = rawValue.ToString(); } } return returnValue; } #endregion #region Member Access /// /// Calls a method on an object dynamically. This version requires explicit /// specification of the parameter type signatures. /// /// Instance of object to call method on /// The method to call as a stringToTypedValue /// Specify each of the types for each parameter passed. /// You can also pass null, but you may get errors for ambiguous methods signatures /// when null parameters are passed /// any variable number of parameters. /// object public static object CallMethod(object instance, string method, Type[] parameterTypes, params object[] parms) { if (parameterTypes == null && parms.Length > 0) // Call without explicit parameter types - might cause problems with overloads // occurs when null parameters were passed and we couldn't figure out the parm type return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod).Invoke(instance, parms); else // Call with parameter types - works only if no null values were passed return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, parameterTypes, null).Invoke(instance, parms); } /// /// Calls a method on an object dynamically. /// /// This version doesn't require specific parameter signatures to be passed. /// Instead parameter types are inferred based on types passed. Note that if /// you pass a null parameter, type inferrance cannot occur and if overloads /// exist the call may fail. if so use the more detailed overload of this method. /// /// Instance of object to call method on /// The method to call as a stringToTypedValue /// Specify each of the types for each parameter passed. /// You can also pass null, but you may get errors for ambiguous methods signatures /// when null parameters are passed /// any variable number of parameters. /// object public static object CallMethod(object instance, string method, params object[] parms) { // Pick up parameter types so we can match the method properly Type[] parameterTypes = null; if (parms != null) { parameterTypes = new Type[parms.Length]; for (int x = 0; x < parms.Length; x++) { // if we have null parameters we can't determine parameter types - exit if (parms[x] == null) { parameterTypes = null; // clear out - don't use types break; } parameterTypes[x] = parms[x].GetType(); } } return CallMethod(instance, method, parameterTypes, parms); } /// /// Allows invoking an event from an external classes where direct access /// is not allowed (due to 'Can only assign to left hand side of operation') /// /// Instance of the object hosting the event /// Name of the event to invoke /// Optional parameters to the event handler to be invoked public static void InvokeEvent(object instance, string eventName, params object[] parameters) { MulticastDelegate del = (MulticastDelegate)instance?.GetType().GetField(eventName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)?.GetValue(instance); if (del == null) return; Delegate[] delegates = del.GetInvocationList(); foreach (Delegate dlg in delegates) { dlg.Method.Invoke(dlg.Target, parameters); } } /// /// Retrieve a property value from an object dynamically. This is a simple version /// that uses Reflection calls directly. It doesn't support indexers. /// /// Object to make the call on /// Property to retrieve /// Object - cast to proper type public static object GetProperty(object instance, string property) { return instance.GetType().GetProperty(property).GetValue(instance, null); } /// /// Parses Properties and Fields including Array and Collection references. /// Used internally for the 'Ex' Reflection methods. /// /// /// /// private static object GetPropertyInternal(object Parent, string Property) { if (Property == "this" || Property == "me") return Parent; object result = null; string pureProperty = Property; string indexes = null; bool isArrayOrCollection = false; // Deal with Array Property if (Property.IndexOf("[") > -1) { pureProperty = Property.Substring(0, Property.IndexOf("[")); indexes = Property.Substring(Property.IndexOf("[")); isArrayOrCollection = true; } var parentType = Parent.GetType(); if (string.IsNullOrEmpty(pureProperty)) { // most likely an indexer result = Parent; } else { // Get the member MemberInfo member = Parent.GetType().GetMember(pureProperty, ReflectionUtils.MemberAccess)[0]; if (member.MemberType == MemberTypes.Property) result = ((PropertyInfo) member).GetValue(Parent, null); else result = ((FieldInfo) member).GetValue(Parent); } if (isArrayOrCollection) { indexes = indexes.Replace("[", string.Empty).Replace("]", string.Empty); if (result is Array) { int Index = -1; int.TryParse(indexes, out Index); result = CallMethod(result, "GetValue", Index); } else if (result is ICollection || result is System.Data.DataRow || result is System.Data.DataTable) { if (indexes.StartsWith("\"")) { // String Index indexes = indexes.Trim('\"'); result = CallMethod(result, "get_Item", indexes); } else { // assume numeric index int index = -1; int.TryParse(indexes, out index); result = CallMethod(result, "get_Item", index); } } } return result; } /// /// Returns a PropertyInfo structure from an extended Property reference /// /// /// /// public static PropertyInfo GetPropertyInfoInternal(object Parent, string Property) { if (Property == "this" || Property == "me") return null; string propertyName = Property; // Deal with Array Property - strip off array indexer if (Property.IndexOf("[") > -1) propertyName = Property.Substring(0, Property.IndexOf("[")); // Get the member return Parent.GetType().GetProperty(propertyName, ReflectionUtils.MemberAccess); } /// /// Retrieve a field dynamically from an object. This is a simple implementation that's /// straight Reflection and doesn't support indexers. /// /// Object to retreve Field from /// name of the field to retrieve /// public static object GetField(object Object, string Property) { return Object.GetType().GetField(Property, ReflectionUtils.MemberAccess | BindingFlags.GetField).GetValue(Object); } /// /// Sets the property on an object. This is a simple method that uses straight Reflection /// and doesn't support indexers. /// /// Object to set property on /// Name of the property to set /// value to set it to public static void SetProperty(object obj, string property, object value) { obj.GetType().GetProperty(property, ReflectionUtils.MemberAccess).SetValue(obj, value, null); } /// /// Parses Properties and Fields including Array and Collection references. /// /// /// /// private static object SetPropertyInternal(object Parent, string Property, object Value) { if (Property == "this" || Property == "me") return Parent; object Result = null; string PureProperty = Property; string Indexes = null; bool IsArrayOrCollection = false; // Deal with Array Property if (Property.IndexOf("[") > -1) { PureProperty = Property.Substring(0, Property.IndexOf("[")); Indexes = Property.Substring(Property.IndexOf("[")); IsArrayOrCollection = true; } if (!IsArrayOrCollection) { // Get the member MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0]; if (Member.MemberType == MemberTypes.Property) { var prop = (PropertyInfo) Member; if (prop.CanWrite) prop.SetValue(Parent, Value, null); } else ((FieldInfo)Member).SetValue(Parent, Value); return null; } else { // Get the member MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0]; if (Member.MemberType == MemberTypes.Property) { var prop = (PropertyInfo) Member; if (prop.CanRead) Result = prop.GetValue(Parent, null); } else Result = ((FieldInfo)Member).GetValue(Parent); } if (IsArrayOrCollection) { Indexes = Indexes.Replace("[", string.Empty).Replace("]", string.Empty); if (Result is Array) { int Index = -1; int.TryParse(Indexes, out Index); Result = CallMethod(Result, "SetValue", Value, Index); } else if (Result is ICollection) { if (Indexes.StartsWith("\"")) { // String Index Indexes = Indexes.Trim('\"'); Result = CallMethod(Result, "set_Item", Indexes, Value); } else { // assume numeric index int Index = -1; int.TryParse(Indexes, out Index); Result = CallMethod(Result, "set_Item", Index, Value); } } } return Result; } /// /// Sets the field on an object. This is a simple method that uses straight Reflection /// and doesn't support indexers. /// /// Object to set property on /// Name of the field to set /// value to set it to public static void SetField(object obj, string property, object value) { obj.GetType().GetField(property, ReflectionUtils.MemberAccess).SetValue(obj, value); } /// /// Returns a List of KeyValuePair object /// /// /// public static List> GetEnumList(Type enumType, bool valueAsFieldValueNumber = false) { //string[] enumStrings = Enum.GetNames(enumType); Array enumValues = Enum.GetValues(enumType); List> items = new List>(); foreach (var enumValue in enumValues) { var strValue = enumValue.ToString(); if (!valueAsFieldValueNumber) items.Add(new KeyValuePair(enumValue.ToString(), StringUtils.FromCamelCase(strValue))); else items.Add(new KeyValuePair(((int)enumValue).ToString(), StringUtils.FromCamelCase(strValue) )); } return items; } #endregion #region EX processing for nested operations /// /// Calls a method on an object with extended . syntax (object: this Method: Entity.CalculateOrderTotal) /// /// /// /// /// public static object CallMethodEx(object parent, string method, params object[] parms) { Type Type = parent.GetType(); // no more .s - we got our final object int lnAt = method.IndexOf("."); if (lnAt < 0) { return ReflectionUtils.CallMethod(parent, method, parms); } // Walk the . syntax string Main = method.Substring(0, lnAt); string Subs = method.Substring(lnAt + 1); object Sub = GetPropertyInternal(parent, Main); // Recurse until we get the lowest ref return CallMethodEx(Sub, Subs, parms); } /// /// Returns a property or field value using a base object and sub members including . syntax. /// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company") /// This method also supports indexers in the Property value such as: /// Customer.DataSet.Tables["Customers"].Rows[0] /// /// Parent object to 'start' parsing from. Typically this will be the Page. /// The property to retrieve. Example: 'Customer.Entity.Company' /// public static object GetPropertyEx(object Parent, string Property) { Type type = Parent.GetType(); int at = Property.IndexOf("."); if (at < 0) { // Complex parse of the property return GetPropertyInternal(Parent, Property); } // Walk the . syntax - split into current object (Main) and further parsed objects (Subs) string main = Property.Substring(0, at); string subs = Property.Substring(at + 1); // Retrieve the next . section of the property object sub = GetPropertyInternal(Parent, main); // Now go parse the left over sections return GetPropertyEx(sub, subs); } /// /// Returns a PropertyInfo object for a given dynamically accessed property /// /// Property selection can be specified using . syntax ("Address.Street" or "DataTable[0].Rows[1]") hence the 'Ex' name for this function. /// /// /// /// public static PropertyInfo GetPropertyInfoEx(object Parent, string Property) { Type type = Parent.GetType(); int at = Property.IndexOf("."); if (at < 0) { // Complex parse of the property return GetPropertyInfoInternal(Parent, Property); } // Walk the . syntax - split into current object (Main) and further parsed objects (Subs) string main = Property.Substring(0, at); string subs = Property.Substring(at + 1); // Retrieve the next . section of the property object sub = GetPropertyInternal(Parent, main); // Now go parse the left over sections return GetPropertyInfoEx(sub, subs); } /// /// Sets a value on an object. Supports . syntax for named properties /// (ie. Customer.Entity.Company) as well as indexers. /// /// /// Object to set the property on. /// /// /// Property to set. Can be an object hierarchy with . syntax and can /// include indexers. Examples: Customer.Entity.Company, /// Customer.DataSet.Tables["Customers"].Rows[0] /// /// /// Value to set the property to /// public static object SetPropertyEx(object parent, string property, object value) { Type Type = parent.GetType(); // no more .s - we got our final object int lnAt = property.IndexOf("."); if (lnAt < 0) { SetPropertyInternal(parent, property, value); return null; } // Walk the . syntax string Main = property.Substring(0, lnAt); string Subs = property.Substring(lnAt + 1); object Sub = GetPropertyInternal(parent, Main); SetPropertyEx(Sub, Subs, value); return null; } #endregion #region Static Methods and Properties /// /// Invokes a static method /// /// /// /// /// public static object CallStaticMethod(string typeName, string method, params object[] parms) { Type type = GetTypeFromName(typeName); if (type == null) throw new ArgumentException("Invalid type: " + typeName); try { return type.InvokeMember(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, type, parms); } catch (Exception ex) { if (ex.InnerException != null) throw ex.GetBaseException(); throw new ApplicationException("Failed to retrieve method signature or invoke method"); } } /// /// Retrieves a value from a static property by specifying a type full name and property /// /// Full type name (namespace.class) /// Property to get value from /// public static object GetStaticProperty(string typeName, string property) { Type type = GetTypeFromName(typeName); if (type == null) return null; return GetStaticProperty(type, property); } /// /// Returns a static property from a given type /// /// Type instance for the static property /// Property name as a string /// public static object GetStaticProperty(Type type, string property) { object result = null; try { result = type.InvokeMember(property, BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty, null, type, null); } catch { return null; } return result; } #endregion #region COM Reflection Routines /// /// Retrieve a dynamic 'non-typelib' property /// /// Object to make the call on /// Property to retrieve /// public static object GetPropertyCom(object instance, string property) { return instance.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null, instance, null); } /// /// Returns a property or field value using a base object and sub members including . syntax. /// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company") /// /// Parent object to 'start' parsing from. /// The property to retrieve. Example: 'oBus.oData.Company' /// public static object GetPropertyExCom(object parent, string property) { Type Type = parent.GetType(); int lnAt = property.IndexOf("."); if (lnAt < 0) { if (property == "this" || property == "me") return parent; // Get the member return parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null, parent, null); } // Walk the . syntax - split into current object (Main) and further parsed objects (Subs) string Main = property.Substring(0, lnAt); string Subs = property.Substring(lnAt + 1); object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null, parent, null); // Recurse further into the sub-properties (Subs) return ReflectionUtils.GetPropertyExCom(Sub, Subs); } /// /// Sets the property on an object. /// /// Object to set property on /// Name of the property to set /// value to set it to public static void SetPropertyCom(object inst, string Property, object Value) { inst.GetType().InvokeMember(Property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty, null, inst, new object[1] { Value }); } /// /// Sets the value of a field or property via Reflection. This method alws /// for using '.' syntax to specify objects multiple levels down. /// /// ReflectionUtils.SetPropertyEx(this,"Invoice.LineItemsCount",10) /// /// which would be equivalent of: /// /// Invoice.LineItemsCount = 10; /// /// /// Object to set the property on. /// /// /// Property to set. Can be an object hierarchy with . syntax. /// /// /// Value to set the property to /// public static object SetPropertyExCom(object parent, string property, object value) { Type Type = parent.GetType(); int lnAt = property.IndexOf("."); if (lnAt < 0) { // Set the member parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccessCom | BindingFlags.SetProperty , null, parent, new object[1] { value }); return null; } // Walk the . syntax - split into current object (Main) and further parsed objects (Subs) string Main = property.Substring(0, lnAt); string Subs = property.Substring(lnAt + 1); object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty , null, parent, null); return SetPropertyExCom(Sub, Subs, value); } /// /// Wrapper method to call a 'dynamic' (non-typelib) method /// on a COM object /// /// /// /// 1st - Method name, 2nd - 1st parameter, 3rd - 2nd parm etc. /// public static object CallMethodCom(object instance, string method, params object[] parms) { return instance.GetType().InvokeMember(method, ReflectionUtils.MemberAccessCom | BindingFlags.InvokeMethod, null, instance, parms); } /// /// Calls a method on a COM object with '.' syntax (Customer instance and Address.DoSomeThing method) /// /// the object instance on which to call method /// The method or . syntax path to the method (Address.Parse) /// Any number of parameters /// public static object CallMethodExCom(object parent, string method, params object[] parms) { Type Type = parent.GetType(); // no more .s - we got our final object int at = method.IndexOf("."); if (at < 0) { return ReflectionUtils.CallMethodCom(parent, method, parms); } // Walk the . syntax - split into current object (Main) and further parsed objects (Subs) string Main = method.Substring(0, at); string Subs = method.Substring(at + 1); object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccessCom | BindingFlags.GetProperty, null, parent, null); // Recurse until we get the lowest ref return CallMethodExCom(Sub, Subs, parms); } #endregion /// /// Determines whether a given type passed is anonymous /// /// Pass either an object instance or a Type object /// public static bool IsAnonoymousType(object objectOrType) { if (objectOrType == null) return false; if (objectOrType is Type type) { type = objectOrType as Type; } else { type = objectOrType.GetType(); } if (!type.IsPublic && type.IsSealed && string.IsNullOrEmpty(type.Namespace)) { return true; } return false; } } } ================================================ FILE: Westwind.Utilities/Utilities/SecurityUtils.cs ================================================ #if NETFULL #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System.Runtime.InteropServices; using System.Security.Principal; using System; namespace Westwind.Utilities { /// /// A set of utilities functions related to security. /// public static class SecurityUtils { const int LOGON32_LOGON_INTERACTIVE = 2; const int LOGON32_LOGON_NETWORK = 3; const int LOGON32_LOGON_BATCH = 4; const int LOGON32_LOGON_SERVICE = 5; const int LOGON32_LOGON_UNLOCK = 7; const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8; const int LOGON32_LOGON_NEW_CREDENTIALS = 9; const int LOGON32_PROVIDER_DEFAULT = 0; [DllImport("advapi32.dll", SetLastError = true)] static extern int LogonUser( string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken ); [DllImport("kernel32.dll", SetLastError = true)] static extern int CloseHandle(IntPtr hObject); /// /// Logs on a user and changes the current process impersonation to that user. /// /// IMPORTANT: Returns a WindowsImpersonationContext and you have to either /// dispose this instance or call RevertImpersonation with it. /// /// /// Requires Full Trust permissions in ASP.NET Web Applications. /// /// /// /// /// WindowsImpersonation Context. Call RevertImpersonation() to clear the impersonation or Dispose() instance. public static WindowsImpersonationContext ImpersonateUser(string username, string password, string domain) { IntPtr token = IntPtr.Zero; try { int TResult = LogonUser(username, domain, password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, out token); WindowsImpersonationContext context = null; context = WindowsIdentity.Impersonate(token); CloseHandle(token); return context; } catch { return null; } finally { if (token != IntPtr.Zero) CloseHandle(token); } } /// /// Releases an impersonation context and releases associated resources /// /// WindowsImpersonation context created with ImpersonateUser public static void RevertImpersonation(WindowsImpersonationContext context) { context.Undo(); context.Dispose(); } } } #endif ================================================ FILE: Westwind.Utilities/Utilities/SerializationUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2008 - 2009 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.IO; using System.Text; using System.Reflection; using System.Xml; using System.Xml.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Diagnostics; using System.Runtime.Serialization; using Westwind.Utilities.Properties; namespace Westwind.Utilities { // Serialization specific code public static class SerializationUtils { /// /// Serializes an object instance to a file. /// /// the object instance to serialize /// /// determines whether XML serialization or binary serialization is used /// public static bool SerializeObject(object instance, string fileName, bool binarySerialization) { bool retVal = true; if (!binarySerialization) { XmlTextWriter writer = null; try { XmlSerializer serializer = new XmlSerializer(instance.GetType()); // Create an XmlTextWriter using a FileStream. Stream fs = new FileStream(fileName, FileMode.Create); writer = new XmlTextWriter(fs, new UTF8Encoding()); writer.Formatting = Formatting.Indented; writer.IndentChar = ' '; writer.Indentation = 3; // Serialize using the XmlTextWriter. serializer.Serialize(writer, instance); } catch(Exception ex) { Debug.Write("SerializeObject failed with : " + ex.Message, "West Wind"); retVal = false; } finally { if (writer != null) writer.Close(); } } else { #if NETFULL Stream fs = null; try { BinaryFormatter serializer = new BinaryFormatter(); fs = new FileStream(fileName, FileMode.Create); serializer.Serialize(fs, instance); } catch { retVal = false; } finally { if (fs != null) fs.Close(); } #else throw new NotSupportedException( Resources.BinaryXmlSerializationNotSupported); #endif } return retVal; } /// /// Overload that supports passing in an XML TextWriter. /// /// /// Note the Writer is not closed when serialization is complete /// so the caller needs to handle closing. /// /// object to serialize /// XmlTextWriter instance to write output to /// Determines whether false is returned on failure or an exception is thrown /// public static bool SerializeObject(object instance, XmlTextWriter writer, bool throwExceptions) { bool retVal = true; try { XmlSerializer serializer = new XmlSerializer(instance.GetType()); // Create an XmlTextWriter using a FileStream. writer.Formatting = Formatting.Indented; writer.IndentChar = ' '; writer.Indentation = 3; // Serialize using the XmlTextWriter. serializer.Serialize(writer, instance); } catch (Exception ex) { Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message + "\r\n" + (ex.InnerException != null ? ex.InnerException.Message : ""), "West Wind"); if (throwExceptions) throw; retVal = false; } return retVal; } /// /// Serializes an object into an XML string variable for easy 'manual' serialization /// /// object to serialize /// resulting XML string passed as an out parameter /// true or false public static bool SerializeObject(object instance, out string xmlResultString) { return SerializeObject(instance, out xmlResultString, false); } /// /// Serializes an object into a string variable for easy 'manual' serialization /// /// /// Out parm that holds resulting XML string /// If true causes exceptions rather than returning false /// public static bool SerializeObject(object instance, out string xmlResultString, bool throwExceptions) { xmlResultString = string.Empty; MemoryStream ms = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(ms, new UTF8Encoding()); if (!SerializeObject(instance, writer,throwExceptions)) { ms.Close(); return false; } xmlResultString = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length); ms.Close(); writer.Close(); return true; } #if NETFULL /// /// Serializes an object instance to a file. /// /// the object instance to serialize /// /// public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false) { bool retVal = true; using (var ms = new MemoryStream()) { try { BinaryFormatter serializer = new BinaryFormatter(); serializer.Serialize(ms, instance); } catch (Exception ex) { Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message, "West Wind"); retVal = false; if (throwExceptions) throw; } ms.Position = 0; resultBuffer = ms.ToArray(); return retVal; } } #endif /// /// Serializes an object to an XML string. Unlike the other SerializeObject overloads /// this methods *returns a string* rather than a bool result! /// /// /// Determines if a failure throws or returns null /// /// null on error otherwise the Xml String. /// /// /// If null is passed in null is also returned so you might want /// to check for null before calling this method. /// public static string SerializeObjectToString(object instance, bool throwExceptions = false) { string xmlResultString = string.Empty; if (!SerializeObject(instance, out xmlResultString, throwExceptions)) return null; return xmlResultString; } #if NETFULL public static byte[] SerializeObjectToByteArray(object instance, bool throwExceptions = false) { byte[] byteResult = null; if ( !SerializeObject(instance, out byteResult) ) return null; return byteResult; } #endif /// /// Deserializes an object from file and returns a reference. /// /// name of the file to serialize to /// The Type of the object. Use typeof(yourobject class) /// determines whether we use Xml or Binary serialization /// Instance of the deserialized object or null. Must be cast to your object type public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization) { return DeSerializeObject(fileName, objectType, binarySerialization, false); } /// /// Deserializes an object from file and returns a reference. /// /// name of the file to serialize to /// The Type of the object. Use typeof(yourobject class) /// determines whether we use Xml or Binary serialization /// determines whether failure will throw rather than return null on failure /// Instance of the deserialized object or null. Must be cast to your object type public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization, bool throwExceptions) { object instance = null; if (!binarySerialization) { XmlReader reader = null; XmlSerializer serializer = null; FileStream fs = null; try { // Create an instance of the XmlSerializer specifying type and namespace. serializer = new XmlSerializer(objectType); // A FileStream is needed to read the XML document. fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); reader = new XmlTextReader(fs); instance = serializer.Deserialize(reader); } catch(Exception ex) { if (throwExceptions) throw; string message = ex.Message; return null; } finally { if (fs != null) fs.Close(); if (reader != null) reader.Close(); } } else { #if NETFULL BinaryFormatter serializer = null; FileStream fs = null; try { serializer = new BinaryFormatter(); fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); instance = serializer.Deserialize(fs); } catch { return null; } finally { if (fs != null) fs.Close(); } #else throw new NotSupportedException(Resources.BinaryXmlSerializationNotSupported); #endif } return instance; } /// /// Deserialize an object from an XmlReader object. /// /// /// /// public static object DeSerializeObject(XmlReader reader, Type objectType) { XmlSerializer serializer = new XmlSerializer(objectType); object Instance = serializer.Deserialize(reader); reader.Close(); return Instance; } public static object DeSerializeObject(string xml, Type objectType) { XmlTextReader reader = new XmlTextReader(xml, XmlNodeType.Document, null); return DeSerializeObject(reader, objectType); } #if NETFULL /// /// Deseializes a binary serialized object from a byte array /// /// /// /// /// public static object DeSerializeObject(byte[] buffer, Type objectType, bool throwExceptions = false) { BinaryFormatter serializer = null; MemoryStream ms = null; object Instance = null; try { serializer = new BinaryFormatter(); ms = new MemoryStream(buffer); Instance = serializer.Deserialize(ms); } catch { if (throwExceptions) throw; return null; } finally { if (ms != null) ms.Close(); } return Instance; } #endif /// /// Returns a string of all the field value pairs of a given object. /// Works only on non-statics. /// /// /// /// public static string ObjectToString(object instanc, string separator, ObjectToStringTypes type) { FieldInfo[] fi = instanc.GetType().GetFields(); string output = string.Empty; if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields) { foreach (PropertyInfo property in instanc.GetType().GetProperties()) { try { output += property.Name + ":" + property.GetValue(instanc, null).ToString() + separator; } catch { output += property.Name + ": n/a" + separator; } } } if (type == ObjectToStringTypes.Fields || type == ObjectToStringTypes.PropertiesAndFields) { foreach (FieldInfo field in fi) { try { output = output + field.Name + ": " + field.GetValue(instanc).ToString() + separator; } catch { output = output + field.Name + ": n/a" + separator; } } } return output; } } public enum ObjectToStringTypes { Properties, PropertiesAndFields, Fields } } ================================================ FILE: Westwind.Utilities/Utilities/ShellUtils.cs ================================================ #pragma warning disable SYSLIB0014 #region /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2011 * http://www.west-wind.com/ * * Created: 6/19/2011 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Text; using System.IO; using System.Net; using System.Diagnostics; using System.Runtime.InteropServices; namespace Westwind.Utilities { /// /// Windows specific shell utility functions /// public static class ShellUtils { #region Open in or Start Process /// /// Executes a Windows process with given command line parameters /// /// Executable to run /// Command Line Parameters passed to executable /// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait. /// Hidden, Normal etc. /// process exit code or 0 if run and forget. 1460 for time out. -1 on error public static int ExecuteProcess(string executable, string arguments = null, int timeoutMs = 0, ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal) { Process process; try { using (process = new Process()) { process.StartInfo.FileName = executable; process.StartInfo.Arguments = arguments; process.StartInfo.WindowStyle = windowStyle; if (windowStyle == ProcessWindowStyle.Hidden) process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.Start(); if (timeoutMs < 0) timeoutMs = 99999999; // indefinitely if (timeoutMs > 0) { if (!process.WaitForExit(timeoutMs)) { Console.WriteLine("Process timed out."); return 1460; } } else // run and don't wait - no exit code return 0; return process.ExitCode; } } catch (Exception ex) { Console.WriteLine("Error executing process: " + ex.Message); return -1; // unhandled error } } /// /// Executes a Windows process with given command line parameters /// and captures console output into a string. /// /// Writes command output to the output StringBuilder /// from StdOut and StdError. /// /// Executable to run /// Command Line Parameters passed to executable /// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait. /// Pass in a string reference that will receive StdOut and StdError output /// Hidden, Normal, etc. /// process exit code or 0 if run and forget. 1460 for time out. -1 on error public static int ExecuteProcess(string executable, string arguments, int timeoutMs, out StringBuilder output, ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden, Action completionDelegate = null) { return ExecuteProcess(executable, arguments, timeoutMs, out output, null, windowStyle, completionDelegate); } /// /// Executes a Windows process with given command line parameters /// and captures console output into a string. /// /// Pass in a String Action that receives output from /// StdOut and StdError as it is written (one line at a time). /// /// Executable to run /// Command Line Parameters passed to executable /// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait. /// Delegate to let you capture streaming output of the executable to stdout and stderror. /// Hidden, Normal etc. /// delegate that is called when execute completes. Passed true if success or false if timeout or failed /// process exit code or 0 if run and forget. 1460 for time out. -1 on error public static int ExecuteProcess(string executable, string arguments, int timeoutMs, Action writeDelegate = null, ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden, Action completionDelegate = null) { return ExecuteProcess(executable, arguments, timeoutMs, out StringBuilder output, writeDelegate, windowStyle, completionDelegate); } private static Process process = null; /// /// Executes a Windows process with given command line parameters /// and captures console output into a string. /// /// Writes original output into the application Console which you can /// optionally redirect to capture output from the command line /// operation using `Console.SetOut` or `Console.SetError`. /// /// Executable to run /// /// Timeout of the process in milliseconds. Pass -1 to wait forever. Pass 0 to not wait. /// StringBuilder that will receive StdOut and StdError output /// Action to capture stdout and stderror output for you to handle /// /// If waiting for completion you can be notified when the exection is complete /// process exit code or 0 if run and forget. 1460 for time out. -1 on error private static int ExecuteProcess(string executable, string arguments, int timeoutMs, out StringBuilder output, Action writeDelegate = null, ProcessWindowStyle windowStyle = ProcessWindowStyle.Hidden, Action completionDelegate = null) { try { using (process = new Process()) { process.StartInfo.FileName = executable; process.StartInfo.Arguments = arguments; process.StartInfo.WindowStyle = windowStyle; if (windowStyle == ProcessWindowStyle.Hidden) process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; var sb = new StringBuilder(); process.OutputDataReceived += (sender, args) => { if (writeDelegate != null) writeDelegate.Invoke(args.Data); else sb.AppendLine(args.Data); }; process.ErrorDataReceived += (sender, args) => { if (writeDelegate != null) writeDelegate.Invoke(args.Data); else sb.AppendLine(args.Data); }; if (completionDelegate != null) { process.Exited += (sender, args) => { var proc = sender as Process; completionDelegate?.Invoke(proc.ExitCode == 0); }; } process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); if (timeoutMs < 0) timeoutMs = 99999999; // indefinitely if (timeoutMs > 0) { if (!process.WaitForExit(timeoutMs)) { Console.WriteLine("Process timed out."); output = null; completionDelegate?.Invoke(false); return 1460; } completionDelegate?.Invoke(true); } else { // no exit code output = sb; return 0; } output = sb; return process.ExitCode; } } catch (Exception ex) { Console.WriteLine($"Error executing process: {ex.Message}"); output = null; return -1; // unhandled error } } #endregion #region Shell Execute Apis, URL Openening /// /// Uses the Shell Extensions to launch a program based on URL moniker or file name /// Basically a wrapper around ShellExecute /// /// Any URL Moniker that the Windows Shell understands (URL, Word Docs, PDF, Email links etc.) /// public static bool GoUrl(string url, string workingFolder = null) { if (string.IsNullOrEmpty(workingFolder)) return OpenUrl(url); string TPath = Path.GetTempPath(); ProcessStartInfo info = new ProcessStartInfo(); info.UseShellExecute = true; info.Verb = "Open"; info.WorkingDirectory = TPath; info.FileName = url; bool result; using (Process process = new Process()) { process.StartInfo = info; try { result = process.Start(); } catch { return false; } } return result; } /// /// Opens a URL in the browser. This version is specific to opening /// a URL in a browser and it's cross platform enabled. /// /// public static bool OpenUrl(string url) { bool success = true; #if NET6_0_OR_GREATER bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.OSDescription.Contains("microsoft-standard"); #else bool isWindows = true; #endif Process p = null; try { var psi = new ProcessStartInfo(url); psi.UseShellExecute = isWindows; // must be explicit -defaults changed in NETFX & NETCORE p = Process.Start(psi); } catch { #if NET6_0_OR_GREATER // hack because of this: https://github.com/dotnet/corefx/issues/10361 if (isWindows) { url = url.Replace("&", "^&"); try { Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}") {CreateNoWindow = true}); } catch { success = false; } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { p = Process.Start("xdg-open", url); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { p = Process.Start("open", url); } else { success = false; } #else success = false; #endif } p?.Dispose(); return success; } /// /// Wrapper around the Shell Execute API. Windows specific. /// /// /// /// /// /// public static int ShellExecute(string url, string arguments = null, string workingFolder = null, string verb = "open") { ProcessStartInfo info = new ProcessStartInfo(); info.UseShellExecute = true; info.Verb = verb; info.FileName = url; info.Arguments = arguments; info.WorkingDirectory = workingFolder; using (Process process = new Process()) { process.StartInfo = info; process.Start(); } return 0; } /// /// Opens a File or Folder in Explorer. If the path is a file /// Explorer is opened in the parent folder with the file selected /// /// public static bool OpenFileInExplorer(string filename) { if (string.IsNullOrEmpty(filename)) return false; // Is it a directory? Just open if (Directory.Exists(filename)) ShellExecute(filename); else { // required as command Explorer command line doesn't allow mixed slashes filename = FileUtils.NormalizePath(filename); if (!File.Exists(filename)) filename = Path.GetDirectoryName(filename); try { Process.Start("explorer.exe", $"/select,\"{filename}\""); } catch { return false; } } return true; } /// /// Opens a Terminal window in the specified folder /// /// /// Powershell, Command or Bash /// false if process couldn't be started - most likely invalid link public static bool OpenTerminal(string folder, TerminalModes mode = TerminalModes.Powershell) { try { string cmd = null, args = null; if (mode == TerminalModes.Powershell) { cmd = "powershell.exe"; args = "-noexit -command \"cd '{0}'\""; } else if(mode == TerminalModes.Command) { cmd = "cmd.exe"; args = "/k \"cd {0}\""; } Process.Start(cmd,string.Format(args, folder)); } catch { return false; } return true; } /// /// Executes a Windows Command Line using Shell Execute as a /// single command line with parameters. This method handles /// parsing out the executable from the parameters. /// /// Full command line - Executable plus arguments. /// If the executable contains paths with spaces **make sure to add quotes around the executable** otherwise the executable may not be found. /// /// Optional - the folder the executable runs in. If not specified uses current folder. /// Optional - Number of milliseconds to wait for completion. 0 don't wait. /// Optional - Shell verb to apply. Defaults to "Open" /// Optional - Windows style for the launched application. Default style is normal /// /// If the executable or parameters contain or **may contain spaces** make sure you use quotes (") or (') around the exec or parameters. /// /// throws if the process fails to start or doesn't complete in time (if timeout is specified). /// public static void ExecuteCommandLine(string fullCommandLine, string workingFolder = null, int waitForExitMs = 0, string verb = "OPEN", ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal, bool useShellExecute = true) { string executable = fullCommandLine; string args = null; if (executable.StartsWith("\"")) { int at = executable.IndexOf("\" "); if (at > 0) { // take the args as provided args = executable.Substring(at + 1); // plain executable executable = executable.Substring(0, at).Trim(' ', '\"'); } } else if (executable.StartsWith("\'")) { int at = executable.IndexOf("\' "); if (at > 0) { // take the args as provided args = executable.Substring(at + 1); // plain executable executable = executable.Substring(0, at).Trim(' ', '\''); } } else { int at = executable.IndexOf(" "); if (at > 0) { if (executable.Length > at + 1) args = executable.Substring(at + 1).Trim(); executable = executable.Substring(0, at); } } var pi = new ProcessStartInfo { Verb = verb, WindowStyle = windowStyle, FileName = executable, WorkingDirectory = workingFolder, Arguments = args, UseShellExecute = true }; Process p; using (p = Process.Start(pi)) { if (waitForExitMs > 0) { if (!p.WaitForExit(waitForExitMs)) throw new TimeoutException("Process failed to complete in time."); } } } /// /// Displays a string in in a browser as HTML. Optionally /// provide an alternate extension to display in the appropriate /// text viewer (ie. "txt" likely shows in NotePad) /// /// /// /// public static bool ShowString(string text, string extension = null) { if (extension == null) extension = "htm"; string File = Path.GetTempPath() + "\\__preview." + extension; StreamWriter sw = new StreamWriter(File, false, Encoding.Default); sw.Write(text); sw.Close(); return GoUrl(File); } /// /// Shows a string as HTML /// /// /// public static bool ShowHtml(string htmlString) { return ShowString(htmlString, null); } /// /// Displays a large Text string as a text file in the /// systems' default text viewer (ie. NotePad) /// /// /// public static bool ShowText(string TextString) { string File = Path.GetTempPath() + "\\__preview.txt"; StreamWriter sw = new StreamWriter(File, false); sw.Write(TextString); sw.Close(); return GoUrl(File); } #endregion #region Simple HTTP Helpers /// /// Simple method to retrieve HTTP content from the Web quickly /// /// Url to access /// Http response text or null public static string HttpGet(string url) { string errorMessage; return HttpGet(url, out errorMessage); } /// /// Simple method to retrieve HTTP content from the Web quickly /// /// Url to access /// /// public static string HttpGet(string url, out string errorMessage) { string responseText = string.Empty; errorMessage = null; using (WebClient Http = new WebClient()) { try { responseText = Http.DownloadString(url); } catch (Exception ex) { errorMessage = ex.Message; return null; } } return responseText; } /// /// Retrieves a buffer of binary data from a URL using /// a plain HTTP Get. /// /// Url to access /// Response bytes or null on error public static byte[] HttpGetBytes(string url) { string errorMessage; return HttpGetBytes(url,out errorMessage); } /// /// Retrieves a buffer of binary data from a URL using /// a plain HTTP Get. /// /// Url to access /// ref parm to receive an error message /// response bytes or null on error public static byte[] HttpGetBytes(string url, out string errorMessage) { byte[] result = null; errorMessage = null; using (var http = new WebClient()) { try { result = http.DownloadData(url); } catch (Exception ex) { errorMessage = ex.Message; return null; } } return result; } #endregion } public enum TerminalModes { Powershell, Command, Bash } } #pragma warning restore SYSLIB0014 ================================================ FILE: Westwind.Utilities/Utilities/StringUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * (c) West Wind Technologies, 2008 - 2024 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Web; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using Westwind.Utilities.Properties; using static System.Net.Mime.MediaTypeNames; using static System.Net.WebRequestMethods; namespace Westwind.Utilities { /// /// String utility class that provides a host of string related operations /// public static class StringUtils { #region Basic String Tasks /// /// Trims the beginning of a string by a matching string. /// /// Overrides string behavior which only works with char. /// /// Text to trim /// Text to trim with /// If true ignore case /// Trimmed string if match is found public static string TrimStart(this string text, string textToTrim, bool caseInsensitive = false) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(textToTrim) || text.Length < textToTrim.Length) return text; StringComparison comparison = caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; // account for multiple instances of the text to trim while (text.Length >= textToTrim.Length && text.Substring(0, textToTrim.Length).Equals(textToTrim, comparison)) { text = text.Substring(textToTrim.Length); } return text; } /// /// Trims the end of a string with a matching string /// /// Text to trim /// Text to trim with /// If true ignore case /// Trimmed string if match is found public static string TrimEnd(this string text, string textToTrim, bool caseInsensitive = false) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(textToTrim) || text.Length < textToTrim.Length) return text; while (true) { var idx = text.LastIndexOf(textToTrim); if (idx == -1) return text; string match = text.Substring(text.Length - textToTrim.Length, textToTrim.Length); if (match == textToTrim || (!caseInsensitive && match.Equals(textToTrim, StringComparison.OrdinalIgnoreCase))) { if (text.Length <= match.Length) text = ""; else text = text.Substring(0, idx); } else break; } return text; } /// /// Trims a string to a specific number of max characters /// /// /// /// [Obsolete("Please use the StringUtils.Truncate() method instead.")] public static string TrimTo(string value, int charCount) { if (value == null) return value; if (value.Length > charCount) return value.Substring(0, charCount); return value; } /// /// Replicates an input string n number of times /// /// /// /// public static string Replicate(string input, int charCount) { StringBuilder sb = new StringBuilder(input.Length * charCount); for (int i = 0; i < charCount; i++) sb.Append(input); return sb.ToString(); } /// /// Replicates a character n number of times and returns a string /// You can use `new string(char, count)` directly though. /// /// /// /// public static string Replicate(char character, int charCount) { return new string(character, charCount); } /// /// Finds the nth index of string in a string /// /// /// /// /// public static int IndexOfNth(this string source, string matchString, int stringInstance, StringComparison stringComparison = StringComparison.CurrentCulture) { if (string.IsNullOrEmpty(source)) return -1; int lastPos = 0; int count = 0; while (count < stringInstance) { var len = source.Length - lastPos; lastPos = source.IndexOf(matchString, lastPos, len, stringComparison); if (lastPos == -1) break; count++; if (count == stringInstance) return lastPos; lastPos += matchString.Length; } return -1; } /// /// Returns the nth Index of a character in a string /// /// /// /// /// public static int IndexOfNth(this string source, char matchChar, int charInstance) { if (string.IsNullOrEmpty(source)) return -1; if (charInstance < 1) return -1; int count = 0; for (int i = 0; i < source.Length; i++) { if (source[i] == matchChar) { count++; if (count == charInstance) return i; } } return -1; } /// /// Finds the nth index of strting in a string /// /// /// /// /// public static int LastIndexOfNth(this string source, string matchString, int charInstance, StringComparison stringComparison = StringComparison.CurrentCulture) { if (string.IsNullOrEmpty(source)) return -1; int lastPos = source.Length; int count = 0; while (count < charInstance) { lastPos = source.LastIndexOf(matchString, lastPos, lastPos, stringComparison); if (lastPos == -1) break; count++; if (count == charInstance) return lastPos; } return -1; } /// /// Finds the nth index of in a string from the end. /// /// /// /// /// public static int LastIndexOfNth(this string source, char matchChar, int charInstance) { if (string.IsNullOrEmpty(source)) return -1; int count = 0; for (int i = source.Length - 1; i > -1; i--) { if (source[i] == matchChar) { count++; if (count == charInstance) return i; } } return -1; } #endregion #region String Casing /// /// Compares to strings for equality ignoring case. /// Uses OrdinalIgnoreCase /// /// /// /// public static bool EqualsNoCase(this string text, string compareTo) { if (text == null && compareTo == null) return true; if (text == null || compareTo == null) return false; return text.Equals(compareTo, StringComparison.OrdinalIgnoreCase); } /// /// Return a string in proper Case format /// /// /// public static string ProperCase(string Input) { if (Input == null) return null; return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(Input); } /// /// Takes a phrase and turns it into CamelCase text. /// White Space, punctuation and separators are stripped /// /// Text to convert to CamelCase public static string ToCamelCase(string phrase) { if (phrase == null) return string.Empty; StringBuilder sb = new StringBuilder(phrase.Length); // First letter is always upper case bool nextUpper = true; foreach (char ch in phrase) { if (char.IsWhiteSpace(ch) || char.IsPunctuation(ch) || char.IsSeparator(ch) || ch > 32 && ch < 48) { nextUpper = true; continue; } if (char.IsDigit(ch)) { sb.Append(ch); nextUpper = true; continue; } if (nextUpper) sb.Append(char.ToUpper(ch)); else sb.Append(char.ToLower(ch)); nextUpper = false; } return sb.ToString(); } /// /// Tries to create a phrase string from CamelCase text /// into Proper Case text. Will place spaces before capitalized /// letters. /// /// Note that this method may not work for round tripping /// ToCamelCase calls, since ToCamelCase strips more characters /// than just spaces. /// /// Camel Case Text: firstName -> First Name /// public static string FromCamelCase(string camelCase) { if (string.IsNullOrEmpty(camelCase)) return camelCase; StringBuilder sb = new StringBuilder(camelCase.Length + 10); bool first = true; char lastChar = '\0'; foreach (char ch in camelCase) { if (!first && lastChar != ' ' && !char.IsSymbol(lastChar) && !char.IsPunctuation(lastChar) && ((char.IsUpper(ch) && !char.IsUpper(lastChar)) || char.IsDigit(ch) && !char.IsDigit(lastChar))) sb.Append(' '); sb.Append(ch); first = false; lastChar = ch; } return sb.ToString(); ; } /// /// Attempts to convert a string that is encoded in camel or snake case or /// and convert it into a proper case string. This is useful for converting /// /// /// public static string BreakIntoWords(string text) { // if the text contains spaces it's already real text if (string.IsNullOrEmpty(text) || text.Contains(" ") || text.Contains("\t")) return text; if (text.Contains("-")) text = text.Replace("-", " ").Trim(); if (text.Contains("_")) text = text.Replace("_", " ").Trim(); char c = text[0]; // assume file name was valid as a 'title' if (char.IsUpper(c)) { // Split before uppercase letters, but not if preceded by another uppercase letter // and followed by a lowercase letter (to handle acronyms properly) string[] words = Regex.Split(text, @"(? /// Extracts a string from between a pair of delimiters. Only the first /// instance is found. /// /// Input String to work on /// Beginning delimiter /// ending delimiter /// Determines whether the search for delimiters is case sensitive /// /// /// Extracted string or string.Empty on no match public static string ExtractString(this string source, string beginDelim, string endDelim, bool caseSensitive = false, bool allowMissingEndDelimiter = false, bool returnDelimiters = false) { int at1, at2; if (string.IsNullOrEmpty(source)) return string.Empty; if (caseSensitive) { at1 = source.IndexOf(beginDelim, StringComparison.CurrentCulture); if (at1 == -1) return string.Empty; at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.CurrentCulture); } else { //string Lower = source.ToLower(); at1 = source.IndexOf(beginDelim, 0, source.Length, StringComparison.OrdinalIgnoreCase); if (at1 == -1) return string.Empty; at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.OrdinalIgnoreCase); } if (allowMissingEndDelimiter && at2 < 0) { if (!returnDelimiters) return source.Substring(at1 + beginDelim.Length); return source.Substring(at1); } if (at1 > -1 && at2 > 1) { if (!returnDelimiters) return source.Substring(at1 + beginDelim.Length, at2 - at1 - beginDelim.Length); return source.Substring(at1, at2 - at1 + endDelim.Length); } return string.Empty; } /// /// Strips characters of a string that follow the specified delimiter /// /// String to work with /// String to search for from end of string /// by default ignores case, set to true to care /// stripped string, or original string if delimiter was not found public static string StripAfter(this string value, string delimiter, bool caseSensitive = false) { if (string.IsNullOrEmpty(value)) return value; var pos = caseSensitive ? value.LastIndexOf(delimiter) : value.LastIndexOf(delimiter, StringComparison.OrdinalIgnoreCase); if (pos < 0) return value; return value.Substring(0, pos); } /// /// String replace function that supports replacing a specific instance with /// case insensitivity /// /// Original input string /// The string that is to be replaced /// The replacement string /// Instance of the FindString that is to be found. 1 based. If Instance = -1 all are replaced /// Case insensitivity flag /// updated string or original string if no matches public static string ReplaceStringInstance(string origString, string findString, string replaceWith, int instance, bool caseInsensitive) { if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString)) return origString; // nothing to do if (instance == -1) // all instances #if NET6_0_OR_GREATER // use native if possible - can only replace all instances return origString.Replace(findString, replaceWith, StringComparison.OrdinalIgnoreCase); #else return ReplaceString(origString, findString, replaceWith, caseInsensitive); #endif int at1 = 0; for (int x = 0; x < instance; x++) { if (caseInsensitive) at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase); else at1 = origString.IndexOf(findString, at1); if (at1 == -1) return origString; if (x < instance - 1) at1 += findString.Length; } return origString.Substring(0, at1) + replaceWith + origString.Substring(at1 + findString.Length); } /// /// Replaces a substring within a string with another substring with optional case sensitivity turned off. /// /// String to do replacements on /// The string to find /// The string to replace found string wiht /// If true case insensitive search is performed /// updated string or original string if no matches #if NET6_0_OR_GREATER [Obsolete("You can use native `string.Replace()` with StringComparison in .NET Core")] #endif public static string ReplaceString(string origString, string findString, string replaceString, bool caseInsensitive) { if (string.IsNullOrEmpty(origString) || string.IsNullOrEmpty(findString)) return origString; // nothing to do int at1 = 0; while (true) { if (caseInsensitive) at1 = origString.IndexOf(findString, at1, origString.Length - at1, StringComparison.OrdinalIgnoreCase); else at1 = origString.IndexOf(findString, at1); if (at1 == -1) break; origString = origString.Substring(0, at1) + replaceString + origString.Substring(at1 + findString.Length); at1 += replaceString.Length; } return origString; } /// /// Replaces the last nth occurrence of a string within a string with another string /// /// Souce string /// Value to replace /// Value to replace with /// The instance from the end to replace /// String comparison mode /// replaced string or original string if replacement is not found public static string ReplaceLastNthInstance(string source, string oldValue, string newValue, int instanceFromEnd = 1, StringComparison compare = StringComparison.CurrentCulture) { if (instanceFromEnd <= 0 || source == null || oldValue == null) return source; // Invalid n value int lastIndex = source.Length; // Traverse the string backwards while (instanceFromEnd > 0) { lastIndex = source.LastIndexOf(oldValue, lastIndex - 1, compare); if (lastIndex == -1) return source; // If not found, return the original string instanceFromEnd--; } // Replace the found occurrence return source.Substring(0, lastIndex) + newValue + source.Substring(lastIndex + oldValue.Length); } /// /// Truncate a string to maximum length. /// /// Text to truncate /// Maximum length /// Trimmed string public static string Truncate(this string text, int maxLength) { if (string.IsNullOrEmpty(text) || text.Length <= maxLength) return text; return text.Substring(0, maxLength); } /// /// Returns an abstract of the provided text by returning up to Length characters /// of a text string. If the text is truncated a ... is appended. /// /// Note: Linebreaks are converted into spaces. /// /// Text to abstract /// Number of characters to abstract to /// string public static string TextAbstract(string text, int length) { if (string.IsNullOrEmpty(text)) return string.Empty; if (text.Length > length) { text = text.Substring(0, length); var idx = text.LastIndexOf(' '); if (idx > -1) text = text.Substring(0, idx) + "…"; } if (!text.Contains("\n")) return text; // linebreaks to spaces StringBuilder sb = new StringBuilder(text.Length); foreach (var s in GetLines(text)) sb.Append(s.Trim() + " "); return sb.ToString().Trim(); } /// /// Terminates a string with the given end string/character, but only if the /// text specified doesn't already exist and the string is not empty. /// /// String to terminate /// String to terminate the text string with /// public static string TerminateString(string value, string terminatorString) { if (string.IsNullOrEmpty(value)) return terminatorString; if (value.EndsWith(terminatorString)) return value; return value + terminatorString; } /// /// Returns the number or right characters specified /// /// full string to work with /// number of right characters to return /// public static string Right(string full, int rightCharCount) { if (string.IsNullOrEmpty(full) || full.Length < rightCharCount || full.Length - rightCharCount < 0) return full; return full.Substring(full.Length - rightCharCount); } #endregion #region String 'Any' and 'Many' Operations /// /// Checks many a string for multiple string values to start with /// /// String to check /// Values to check in string /// public static bool StartsWithAny(this string str, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.StartsWith(value)) return true; } return false; } /// /// Checks many a string for multiple string values to start with /// /// String to check /// Comparision mode /// Values to check in string /// public static bool StartsWithAny(this string str, StringComparison compare, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.StartsWith(value, compare)) return true; } return false; } /// /// Checks a string form multiple contained string values /// /// String to match /// Matches to find in the string /// public static bool ContainsAny(this string str, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.Contains(value)) return true; } return false; } /// /// Checks a string form multiple contained string values /// /// String to match /// Type of comparison /// Matches to find in the string /// public static bool ContainsAny(this string str, StringComparison compare, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.Contains(value, compare)) return true; } return false; } /// /// Checks a string form multiple contained string values /// /// String to match /// Matches to find in the string /// public static bool ContainsAny(this string str, params char[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.Contains(value)) return true; } return false; } #if NET6_0_OR_GREATER /// /// Checks a string form multiple contained string values /// /// String to match /// Type of comparison /// Matches to find in the string /// public static bool ContainsAny(this string str, StringComparison compare, params char[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { if (str.Contains(value, compare)) return true; } return false; } #endif /// /// Checks to see if a string contains any of a set of values. /// /// String to compare /// String values to compare to /// null values in matchValues are ignored /// public static bool EqualsAny(this string str, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { // null values are ignored if (value == null) continue; if (str.Equals(value)) return true; } return false; } /// /// Checks to see if a string contains any of a set of values /// /// String to check /// Comparison mode /// Strings to check for /// public static bool EqualsAny(this string str, StringComparison compare, params string[] matchValues) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return false; foreach (var value in matchValues) { // null values are ignored if (value == null) continue; if (str.Equals(value, compare)) return true; } return false; } /// /// Replaces multiple matches with a single new value /// /// This version takes an array of strings as input /// /// String to work on /// String values to match /// String to replace with /// public static string ReplaceMany(this string str, string[] matchValues, string replaceWith) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return str; foreach (var value in matchValues) { // null values are ignored if (value == null) continue; str = str.Replace(value, replaceWith); } return str; } /// /// Replaces multiple matches with a single new value. /// /// This version takes a comma delimited list of strings /// /// String to work on /// Comma delimited list of values. Values are start and end trimmed /// String to replace with /// public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith) { if (string.IsNullOrEmpty(valuesToMatch)) return str; #if NET6_0_OR_GREATER var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); #else var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(v => v.Trim()) .ToArray(); #endif return ReplaceMany(str, matchValues, replaceWith); } #if NET6_0_OR_GREATER /// /// Replaces multiple matches with a single new value /// /// This version takes an array of strings as input /// /// String to work on /// String values to match /// String to replace with /// String comparison mode /// public static string ReplaceMany(this string str, string[] matchValues, string replaceWith, StringComparison compare) { if (string.IsNullOrEmpty(str) || matchValues == null || matchValues.Length < 1) return str; foreach (var value in matchValues) { str = str.Replace(value, replaceWith, compare); } return str; } /// /// Replaces multiple matches with a single new value. /// /// This version takes a comma delimited list of strings /// /// String to work on /// Comma delimited list of values. Values are start and end trimmed /// String to replace with /// String comparison mode /// public static string ReplaceMany(this string str, string valuesToMatch, string replaceWith, StringComparison compare) { if (string.IsNullOrEmpty(valuesToMatch)) return str; #if NET6_0_OR_GREATER var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); #else var matchValues = valuesToMatch.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(v => v.Trim()) .ToArray(); #endif return ReplaceMany(str, matchValues, replaceWith, compare); } #endif #endregion #region String Parsing /// /// Determines if a string is contained in a list of other strings /// /// /// /// public static bool Inlist(this string s, params string[] list) { if (string.IsNullOrEmpty(s) || list == null || list.Length < 1) return false; return list.Contains(s); } #if NET6_0_OR_GREATER /// /// Determines if a string is contained in a list of other strings /// /// /// /// public static bool Inlist(string s, StringComparison compare, params string[] list) { if (string.IsNullOrEmpty(s) || list == null || list.Length < 1) return false; foreach (var item in list) { if (item.Equals(s, compare)) return true; } return false; } #endif /// /// Checks to see if value is part of a delimited list of values. /// Example: IsStringInList("value1,value2,value3","value3"); /// /// A list of delimited strings (ie. value1, value2, value3) with or without spaces (values are trimmed) /// value to match against the list /// Character that separates the list values /// If true ignores case for the list value matches public static bool IsStringInList(string stringList, string valueToFind, char separator = ',', bool ignoreCase = false) { var tokens = stringList.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length == 0) return false; var comparer = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; foreach (var tok in tokens) { if (tok.Trim().Equals(valueToFind, comparer)) return true; } return false; } /// /// String.Contains() extension method that allows to specify case /// /// Input text /// text to search for /// Case sensitivity options /// public static bool Contains(this string text, string searchFor, StringComparison stringComparison) { return text.IndexOf(searchFor, stringComparison) > -1; } /// /// Parses a string into an array of lines broken /// by \r\n or \n /// /// String to check for lines /// Optional - max number of lines to return /// array of strings, or null if the string passed was a null public static string[] GetLines(this string s, int maxLines = 0) { if (s == null) return new string[] { }; s = s.Replace("\r\n", "\n").Replace("\r", "\n"); if (maxLines < 1) return s.Split(new char[] { '\n' }); return s.Split(new char[] { '\n' }).Take(maxLines).ToArray(); } /// /// Returns a line count for a string /// /// string to count lines for /// public static int CountLines(this string s) { if (string.IsNullOrEmpty(s)) return 0; return s.Split('\n').Length; } /// /// Counts the number of times a character occurs /// in a given string /// /// input string /// character to match /// public static int Occurs(string source, char match) { if (string.IsNullOrEmpty(source)) return 0; int count = 0; foreach (char c in source) if (c == match) count++; return count; } /// /// Counts the number of times a sub string occurs /// in a given string /// /// input string /// string to match /// public static int Occurs(string source, string match) { if (string.IsNullOrEmpty(source)) return 0; return source.Split(new[] { match }, StringSplitOptions.None).Length - 1; } /// /// Returns a string that has the max amount of characters of the source string. /// If the string is shorter than the max length the entire string is returned. /// If the string is longer it's truncated. /// If empty the original value is returned (null or string.Empty) /// If the startPosition is greater than the length of the string null is returned /// /// source string to work on /// Maximum number of characters to return /// Optional start position. If not specified uses entire string (0) /// public static string GetMaxCharacters(this string s, int maxCharacters, int startPosition = 0) { if (string.IsNullOrEmpty(s) || startPosition == 0 && maxCharacters > s.Length) return s; if (startPosition > s.Length - 1) return null; var available = s.Length - startPosition; return s.Substring(startPosition, Math.Min(available, maxCharacters)); } /// /// Retrieves the last n characters from the end of a string up to the /// number of characters specified. If there are fewer characters /// the original string is returned otherwise the last n characters /// are returned. /// /// input string /// number of characters to retrieve from end of string /// Up to the last n characters of the string. Empty string on empty or null public static string GetLastCharacters(this string s, int characterCount) { if (string.IsNullOrEmpty(s) || s.Length < characterCount) return s ?? string.Empty; return s.Substring(s.Length - characterCount); } /// /// Strips all non digit values from a string and only /// returns the numeric string. /// /// /// public static string StripNonNumber(string input) { var chars = input.ToCharArray(); StringBuilder sb = new StringBuilder(); foreach (var chr in chars) { if (char.IsNumber(chr) || char.IsSeparator(chr)) sb.Append(chr); } return sb.ToString(); } static Regex tokenizeRegex = new Regex("{{.*?}}"); /// /// Tokenizes a string based on a start and end string. Replaces the values with a token /// text (#@#1#@# for example). /// /// You can use Detokenize to get the original values back using DetokenizeString /// using the same token replacement text. /// /// Text to search /// starting match string /// ending match string /// token replacement text - make sure this string is a value that is unique and **doesn't occur in the document** /// A list of extracted string tokens that have been replaced in `ref text` with the replace delimiter public static List TokenizeString(ref string text, string startMatch, string endMatch, string replaceDelimiter = "#@#") { var strings = new List(); var matches = tokenizeRegex.Matches(text); int i = 0; foreach (Match match in matches) { tokenizeRegex = new Regex(Regex.Escape(match.Value)); text = tokenizeRegex.Replace(text, $"{replaceDelimiter}{i}{replaceDelimiter}", 1); strings.Add(match.Value); i++; } return strings; } /// /// Detokenizes a string tokenized with TokenizeString. Requires the collection created /// by detokenization /// /// Text to work with /// list of previously extracted tokens /// the token replacement string that replaced the captured tokens /// public static string DetokenizeString(string text, IEnumerable tokens, string replaceDelimiter = "#@#") { int i = 0; foreach (string token in tokens) { text = text.Replace($"{replaceDelimiter}{i}{replaceDelimiter}", token); i++; } return text; } /// /// Parses an string into an integer. If the text can't be parsed /// a default text is returned instead /// /// Input numeric string to be parsed /// Optional default text if parsing fails /// Optional NumberFormat provider. Defaults to current culture's number format /// public static int ParseInt(string input, int defaultValue = 0, IFormatProvider numberFormat = null) { if (numberFormat == null) numberFormat = CultureInfo.CurrentCulture.NumberFormat; int val = defaultValue; if (input == null) return defaultValue; if (!int.TryParse(input, NumberStyles.Any, numberFormat, out val)) return defaultValue; return val; } /// /// Parses an string into an decimal. If the text can't be parsed /// a default text is returned instead /// /// /// /// public static decimal ParseDecimal(string input, decimal defaultValue = 0M, IFormatProvider numberFormat = null) { numberFormat = numberFormat ?? CultureInfo.CurrentCulture.NumberFormat; decimal val = defaultValue; if (input == null) return defaultValue; if (!decimal.TryParse(input, NumberStyles.Any, numberFormat, out val)) return defaultValue; return val; } #endregion #region String Ids /// /// Creates short string id based on a GUID hashcode. /// Not guaranteed to be unique across machines, but unlikely /// to duplicate in medium volume situations. /// /// public static string NewStringId() { return Guid.NewGuid().ToString().GetHashCode().ToString("x"); } /// /// Creates a new random string of upper, lower case letters and digits. /// Very useful for generating random data for storage in test data. /// /// The number of characters of the string to generate /// randomized string public static string RandomString(int size, bool includeNumbers = false) { StringBuilder builder = new StringBuilder(size); char ch; int num; for (int i = 0; i < size; i++) { if (includeNumbers) num = Convert.ToInt32(Math.Floor(62 * random.NextDouble())); else num = Convert.ToInt32(Math.Floor(52 * random.NextDouble())); if (num < 26) ch = Convert.ToChar(num + 65); // lower case else if (num > 25 && num < 52) ch = Convert.ToChar(num - 26 + 97); // numbers else ch = Convert.ToChar(num - 52 + 48); builder.Append(ch); } return builder.ToString(); } private static Random random = new Random((int)DateTime.Now.Ticks); #endregion #region Encodings /// /// UrlEncodes a string without the requirement for System.Web /// /// /// // [Obsolete("Use System.Uri.EscapeDataString instead")] public static string UrlEncode(string text) { if (string.IsNullOrEmpty(text)) return string.Empty; return Uri.EscapeDataString(text); } /// /// Encodes a few additional characters for use in paths /// Encodes: . # /// /// /// public static string UrlEncodePathSafe(string text) { string escaped = UrlEncode(text); return escaped.Replace(".", "%2E").Replace("#", "%23"); } /// /// UrlDecodes a string without requiring System.Web /// /// String to decode. /// decoded string public static string UrlDecode(string text) { if (string.IsNullOrEmpty(text)) return text; // pre-process for + sign space formatting since System.Uri doesn't handle it // plus literals are encoded as %2b normally so this should be safe text = text.Replace("+", " "); string decoded = Uri.UnescapeDataString(text); return decoded; } /// /// Retrieves a text by key from a UrlEncoded string. /// /// UrlEncoded String /// Key to retrieve text for /// returns the text or "" if the key is not found or the text is blank public static string GetUrlEncodedKey(string urlEncoded, string key) { if (string.IsNullOrEmpty(urlEncoded) || string.IsNullOrEmpty(key) ) return string.Empty; urlEncoded = "&" + urlEncoded + "&"; int Index = urlEncoded.IndexOf("&" + key + "=", StringComparison.OrdinalIgnoreCase); if (Index < 0) return string.Empty; int lnStart = Index + 2 + key.Length; int Index2 = urlEncoded.IndexOf("&", lnStart); if (Index2 < 0) return string.Empty; return UrlDecode(urlEncoded.Substring(lnStart, Index2 - lnStart)); } /// /// Allows setting of a text in a UrlEncoded string. If the key doesn't exist /// a new one is set, if it exists it's replaced with the new text. /// /// A UrlEncoded string of key text pairs /// /// /// public static string SetUrlEncodedKey(string urlEncoded, string key, string value) { if (!urlEncoded.EndsWith("?") && !urlEncoded.EndsWith("&")) urlEncoded += "&"; Match match = Regex.Match(urlEncoded, "[?|&]" + key + "=.*?&"); if (match == null || string.IsNullOrEmpty(match.Value)) urlEncoded = urlEncoded + key + "=" + UrlEncode(value) + "&"; else urlEncoded = urlEncoded.Replace(match.Value, match.Value.Substring(0, 1) + key + "=" + UrlEncode(value) + "&"); return urlEncoded.TrimEnd('&'); } #endregion #region Binary Encoding /// /// Turns a BinHex string that contains raw byte values /// into a byte array /// /// BinHex string (just two byte hex digits strung together) /// public static byte[] BinHexToBinary(string hex) { int offset = hex.StartsWith("0x") ? 2 : 0; if ((hex.Length % 2) != 0) throw new ArgumentException(string.Format(Resources.InvalidHexStringLength, hex.Length)); byte[] ret = new byte[(hex.Length - offset) / 2]; for (int i = 0; i < ret.Length; i++) { ret[i] = (byte)((ParseHexChar(hex[offset]) << 4) | ParseHexChar(hex[offset + 1])); offset += 2; } return ret; } /// /// Converts a byte array into a BinHex string. /// BinHex is two digit hex byte values squished together /// into a string. /// /// Raw data to send /// BinHex string or null if input is null public static string BinaryToBinHex(byte[] data) { if (data == null) return null; char[] c = new char[data.Length * 2]; int b; for (int i = 0; i < data.Length; i++) { b = data[i] >> 4; c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); b = data[i] & 0xF; c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); } return new string(c).ToLower(); } /// /// Converts a string into bytes for storage in any byte[] types /// buffer or stream format (like MemoryStream). /// /// /// The character encoding to use. Defaults to Unicode /// public static byte[] StringToBytes(string text, Encoding encoding = null) { if (text == null) return null; if (encoding == null) encoding = Encoding.Unicode; return encoding.GetBytes(text); } /// /// Converts a byte array to a stringUtils /// /// raw string byte data /// Character encoding to use. Defaults to Unicode /// public static string BytesToString(byte[] buffer, Encoding encoding = null) { if (buffer == null) return null; if (encoding == null) encoding = Encoding.Unicode; return encoding.GetString(buffer); } /// /// Converts a string to a Base64 string /// /// A string to convert to base64 /// Optional encoding - if not passed assumed to be Unicode /// Base 64 or null public static string ToBase64String(string text, Encoding encoding = null) { var bytes = StringToBytes(text, encoding); if (bytes == null) return null; return Convert.ToBase64String(bytes); } /// /// Converts a base64 string back to a string /// /// A base 64 string /// Optional encoding - if not passed assumed to be Unicode /// public static string FromBase64String(string base64, Encoding encoding = null) { var bytes = Convert.FromBase64String(base64); if (bytes == null) return null; return BytesToString(bytes, encoding); } static int ParseHexChar(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; throw new ArgumentException(Resources.InvalidHexDigit + c); } static char[] base36CharArray = "0123456789abcdefghijklmnopqrstuvwxyz".ToCharArray(); static string base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"; /// /// Encodes an integer into a string by mapping to alpha and digits (36 chars) /// chars are embedded as lower case /// /// Example: 4zx12ss /// /// /// public static string Base36Encode(long value) { string returnValue = ""; bool isNegative = value < 0; if (isNegative) value = value * -1; do { returnValue = base36CharArray[value % base36CharArray.Length] + returnValue; value /= 36; } while (value != 0); return isNegative ? returnValue + "-" : returnValue; } /// /// Decodes a base36 encoded string to an integer /// /// /// public static long Base36Decode(string input) { bool isNegative = false; if (input.EndsWith("-")) { isNegative = true; input = input.Substring(0, input.Length - 1); } char[] arrInput = input.ToCharArray(); Array.Reverse(arrInput); long returnValue = 0; for (long i = 0; i < arrInput.Length; i++) { long valueindex = base36Chars.IndexOf(arrInput[i]); returnValue += Convert.ToInt64(valueindex * Math.Pow(36, i)); } return isNegative ? returnValue * -1 : returnValue; } #endregion #region Miscellaneous /// /// Normalizes linefeeds to the appropriate /// /// The text to fix up /// Type of linefeed to fix up to /// public static string NormalizeLineFeeds(string text, LineFeedTypes type = LineFeedTypes.Auto) { if (string.IsNullOrEmpty(text)) return text; if (type == LineFeedTypes.Auto) { if (Environment.NewLine.Contains('\r')) type = LineFeedTypes.CrLf; else type = LineFeedTypes.Lf; } if (type == LineFeedTypes.Lf) return text.Replace("\r\n", "\n"); return text.Replace("\r\n", "\n").Replace("\n", "\r\n"); } /// /// Strips any common white space from all lines of text that have the same /// common white space text. Effectively removes common code indentation from /// code blocks for example so you can get a left aligned code snippet. /// /// Text to normalize /// public static string NormalizeIndentation(string code) { if (string.IsNullOrEmpty(code)) return string.Empty; // normalize tabs to 3 spaces string text = code.Replace("\t", " "); string[] lines = text.Split(new string[3] { "\r\n", "\r", "\n" }, StringSplitOptions.None); // keep track of the smallest indent int minPadding = 1000; foreach (var line in lines) { if (line.Length == 0) // ignore blank lines continue; int count = 0; foreach (char chr in line) { if (chr == ' ' && count < minPadding) count++; else break; } if (count == 0) return code; minPadding = count; } string strip = new String(' ', minPadding); StringBuilder sb = new StringBuilder(); foreach (var line in lines) { sb.AppendLine(StringUtils.ReplaceStringInstance(line, strip, "", 1, false)); } return sb.ToString(); } /// /// Simple Logging method that allows quickly writing a string to a file /// /// /// /// if not specified used UTF-8 public static void LogString(string output, string filename, Encoding encoding = null) { if (encoding == null) encoding = Encoding.UTF8; lock (_logLock) { var writer = new StreamWriter(filename, true, encoding); writer.WriteLine(DateTime.Now + " - " + output); writer.Close(); } } private static object _logLock = new object(); /// /// Creates a Stream from a string. Internally creates /// a memory stream and returns that. /// /// Note: stream returned should be disposed! /// /// /// /// public static Stream StringToStream(string text, Encoding encoding = null) { if (encoding == null) encoding = Encoding.Default; var ms = new MemoryStream(text.Length * 2); byte[] data = encoding.GetBytes(text); ms.Write(data, 0, data.Length); ms.Position = 0; return ms; } /// /// Creates a string from a text based stream /// /// input stream (not closed by operation) /// Optional encoding - if not specified assumes 'Encoding.Default' /// /// public static string StreamToString(Stream stream, Encoding encoding = null) { if (encoding == null) encoding = Encoding.Default; if (!stream.CanRead) throw new InvalidOleVariantTypeException("Stream cannot be read."); using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } /// /// Retrieves a string value from an XML-like string collection that was stored via SetProperty() /// /// String of XML like values (not proper XML) /// The key of the property to return or empty string /// public static string GetProperty(string propertyString, string key) { var value = StringUtils.ExtractString(propertyString, "<" + key + ">", ""); return value; } /// /// Sets a property value in an XML-like structure that can be used to store properties /// in a string. /// /// String of XML like values (not proper XML) /// a key in that string /// the string value to store /// public static string SetProperty(string propertyString, string key, string value) { string extract = StringUtils.ExtractString(propertyString, "<" + key + ">", ""); if (string.IsNullOrEmpty(value) && extract != string.Empty) { return propertyString.Replace(extract, ""); } // NOTE: Value is not XML encoded - we only retrieve based on named nodes so no conflict string xmlLine = "<" + key + ">" + value + ""; // replace existing if (extract != string.Empty) return propertyString.Replace(extract, xmlLine); // add new return propertyString + xmlLine + "\r\n"; } /// /// A helper to generate a JSON string from a string value /// /// Use this to avoid bringing in a full JSON Serializer for /// scenarios of string serialization. /// /// /// JSON encoded string ("text"), empty ("") or "null". public static string ToJsonString(string text) { if (text is null) return "null"; var sb = new StringBuilder(text.Length); sb.Append("\""); var ct = text.Length; for (int x = 0; x < ct; x++) { var c = text[x]; switch (c) { case '\"': sb.Append("\\\""); break; case '\\': sb.Append("\\\\"); break; case '\b': sb.Append("\\b"); break; case '\f': sb.Append("\\f"); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; default: uint i = c; if (i < 32) // || i > 255 sb.Append($"\\u{i:x4}"); else sb.Append(c); break; } } sb.Append("\""); return sb.ToString(); } #endregion } public enum LineFeedTypes { // Linefeed \n only Lf, // Carriage Return and Linefeed \r\n CrLf, // Platform default Environment.NewLine Auto } } ================================================ FILE: Westwind.Utilities/Utilities/TimeUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2008 - 2009 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Globalization; using Westwind.Utilities.Properties; namespace Westwind.Utilities { /// /// Time Utilities class provides date and time related routines. /// public static class TimeUtils { public static DateTime MIN_DATE_VALUE = new DateTime(1900, 1, 1, 0, 0, 0, 0, CultureInfo.InvariantCulture.Calendar, DateTimeKind.Utc); /// /// Displays a date in friendly format. /// /// /// /// Today,Yesterday,Day of week or a string day (Jul 15, 2008) public static string FriendlyDateString(DateTime date, bool showTime = false, string timeSeparator = "-") { if (date < TimeUtils.MIN_DATE_VALUE) return string.Empty; string FormattedDate = string.Empty; if (date.Date.Equals(DateTime.Today)) FormattedDate = Resources.Today; else if (date.Date == DateTime.Today.AddDays(-1)) FormattedDate = Resources.Yesterday; else if (date.Date > DateTime.Today.AddDays(-6)) // Show the Day of the week FormattedDate = date.ToString("dddd").ToString(); else FormattedDate = date.ToString("MMMM dd, yyyy"); if (showTime) FormattedDate += " " + timeSeparator + " " + date.ToString("t").ToLower().Replace(" ",""); return FormattedDate; } /// /// Returns a short date time string /// /// The date to format /// If true displays simpe time (hour and minutes) /// Date and time separator character /// public static string ShortDateString(DateTime date, bool showTime=false, string separator = "-") { if (date < TimeUtils.MIN_DATE_VALUE) return string.Empty; string dateString = date.ToString("MMM dd, yyyy"); if (!showTime) return dateString; return dateString + " " + separator + " " + date.ToString("t").Replace(" ","").ToLower(); } /// /// Returns a short date time string /// /// /// /// public static string ShortDateString(DateTime? date, bool ShowTime) { if (date == null || !date.HasValue) return string.Empty; return ShortDateString(date.Value, ShowTime); } /// /// Short date time format that shows hours and minutes. /// Culture adjusted but packs down US dates. /// /// Formatted date public static string ShortTimeString(DateTime date) { return date.ToString("t").Replace(" ", "").ToLower(); } /// /// Displays a number of milliseconds as friendly seconds, hours, minutes /// Pass -1 to get a blank date. /// /// Note: English only! /// /// the elapsed milliseconds to display time for /// string in format of just now or 1m ago, 2h ago public static string FriendlyElapsedTimeString(int milliSeconds) { return FriendlyElapsedTimeString(Convert.ToDouble( milliSeconds)); } /// /// Displays a number of milliseconds as friendly seconds, hours, minutes /// Pass -1 to get a blank date. /// /// Note: English only! /// /// the elapsed milliseconds to display time for /// string in format of just now or 1m ago, 2h ago public static string FriendlyElapsedTimeString(double milliSeconds) { if (milliSeconds == 0) return string.Empty; milliSeconds = Math.Abs(milliSeconds); if (milliSeconds < 20000) return "just now"; if (milliSeconds < 60000) return ((int)(milliSeconds / 1000)) + "s ago"; if (milliSeconds < 3600000) return ((int)(milliSeconds / 60000)) + "m ago"; if (milliSeconds < 86400000 * 2) // 2 days return ((int)(milliSeconds / 3600000)) + "h ago"; if (milliSeconds < 86400000F * 30F) // 30 days return ((int)(milliSeconds / 86400000 )) + "d ago"; if (milliSeconds < 86400000F * 365F ) // 365 days return (Math.Round(milliSeconds / 2592000000)) + "mo ago"; return (Math.Round(milliSeconds / (60000F * 60F * 24F * 365F)) ) + "y ago"; } /// /// Displays the elapsed time friendly seconds, hours, minutes /// /// Timespan of elapsed time /// string in format of just now or 1m ago, 2h ago public static string FriendlyElapsedTimeString(TimeSpan elapsed) { return FriendlyElapsedTimeString(elapsed.TotalMilliseconds); } /// /// Converts a fractional hour value like 1.25 to 1:15 hours:minutes format /// /// Decimal hour value /// An optional format string where {0} is hours and {1} is minutes (ie: "{0}h:{1}m"). /// public static string FractionalHoursToString(decimal hours, string format) { if (string.IsNullOrEmpty(format)) format = "{0}:{1}"; TimeSpan tspan = TimeSpan.FromHours((double)hours); // Account for rounding error int minutes = tspan.Minutes; if (tspan.Seconds > 29) minutes++; return string.Format(format, tspan.Hours + tspan.Days * 24, minutes); } /// /// Converts a fractional hour value like 1.25 to 1:15 hours:minutes format /// /// Decimal hour value public static string FractionalHoursToString(decimal hours) { return FractionalHoursToString(hours, null); } /// /// Rounds an hours value to a minute interval /// 0 means no rounding /// /// Minutes to round up or down to /// public static decimal RoundDateToMinuteInterval(decimal hours, int minuteInterval, RoundingDirection direction) { if (minuteInterval == 0) return hours; decimal fraction = 60 / minuteInterval; switch (direction) { case RoundingDirection.Round: return Math.Round(hours * fraction, 0) / fraction; case RoundingDirection.RoundDown: return Math.Truncate(hours * fraction) / fraction; } return Math.Ceiling(hours * fraction) / fraction; } /// /// Rounds a date value to a given minute interval /// /// Original time value /// Number of minutes to round up or down to /// public static DateTime RoundDateToMinuteInterval(DateTime time, int minuteInterval, RoundingDirection direction) { if (minuteInterval == 0) return time; decimal interval = (decimal)minuteInterval; decimal actMinute = (decimal)time.Minute; if (actMinute == 0.00M) return time; int newMinutes = 0; switch (direction) { case RoundingDirection.Round: newMinutes = (int)(Math.Round(actMinute / interval, 0) * interval); break; case RoundingDirection.RoundDown: newMinutes = (int)(Math.Truncate(actMinute / interval) * interval); break; case RoundingDirection.RoundUp: newMinutes = (int)(Math.Ceiling(actMinute / interval) * interval); break; } // strip time time = time.AddMinutes(time.Minute * -1); time = time.AddSeconds(time.Second * -1); time = time.AddMilliseconds(time.Millisecond * -1); // add new minutes back on return time.AddMinutes(newMinutes); } /// /// Creates a DateTime value from date and time input values /// /// /// /// public static DateTime DateTimeFromDateAndTime(string Date, string Time) { return DateTime.Parse(Date + " " + Time); } /// /// Creates a DateTime Value from a DateTime date and a string time value. /// /// /// /// public static DateTime DateTimeFromDateAndTime(DateTime Date, string Time) { return DateTime.Parse(Date.ToShortDateString() + " " + Time); } /// /// Converts the passed date time value to Mime formatted time string /// /// public static string MimeDateTime(DateTime Time) { TimeSpan Offset = TimeZoneInfo.Local.GetUtcOffset(Time); //TimeSpan Offset = TimeZone.CurrentTimeZone.GetUtcOffset(Time); string sOffset = null; if (Offset.Hours < 0) sOffset = "-" + (Offset.Hours * -1).ToString().PadLeft(2, '0'); else sOffset = "+" + Offset.Hours.ToString().PadLeft(2, '0'); sOffset += Offset.Minutes.ToString().PadLeft(2, '0'); return "Date: " + Time.ToString("ddd, dd MMM yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture) + " " + sOffset; } /// /// Returns whether a date time is between two other dates. Optionally /// can compare date only or date and time. /// /// /// /// /// If true compare date and time, otherwise just date /// true or false public static bool IsBetween(this DateTime date, DateTime startDate, DateTime endDate, bool includeTime = true) { return includeTime ? date >= startDate && date <= endDate : date.Date >= startDate.Date && date.Date <= endDate.Date; } /// /// Returns whether a timespan is between two dates /// /// /// /// /// true or false public static bool IsBetween(this TimeSpan date, DateTime startDate, DateTime endDate) { return date.CompareTo(endDate) <= 0 && date.CompareTo(endDate) >= 0; } /// /// Truncates a DateTime value to the nearest partial value. /// /// /// From: http://stackoverflow.com/questions/1004698/how-to-truncate-milliseconds-off-of-a-net-datetime /// /// /// /// public static DateTime Truncate(DateTime date, DateTimeResolution resolution = DateTimeResolution.Second) { switch (resolution) { case DateTimeResolution.Year: return new DateTime(date.Year, 1, 1, 0, 0, 0, 0, date.Kind); case DateTimeResolution.Month: return new DateTime(date.Year, date.Month, 1, 0, 0, 0, date.Kind); case DateTimeResolution.Day: return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, date.Kind); case DateTimeResolution.Hour: return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerHour)); case DateTimeResolution.Minute: return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMinute)); case DateTimeResolution.Second: return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerSecond)); case DateTimeResolution.Millisecond: return date.AddTicks(-(date.Ticks % TimeSpan.TicksPerMillisecond)); case DateTimeResolution.Tick: return date.AddTicks(0); default: throw new ArgumentException("unrecognized resolution", "resolution"); } } /// /// Returns TimeZone adjusted time for a given from a Utc or local time. /// Date is first converted to UTC then adjusted. /// /// /// /// public static DateTime ToTimeZoneTime(this DateTime time, string timeZoneId = "Pacific Standard Time") { TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); return time.ToTimeZoneTime(tzi); } /// /// Returns TimeZone adjusted time for a given from a Utc or local time. /// Date is first converted to UTC then adjusted. /// /// /// /// public static DateTime ToTimeZoneTime(this DateTime time, TimeZoneInfo tzi) { return TimeZoneInfo.ConvertTimeFromUtc(time, tzi); } } /// /// Determines how date time values are rounded /// public enum RoundingDirection { RoundUp, RoundDown, Round } public enum DateTimeResolution { Year, Month, Day, Hour, Minute, Second, Millisecond, Tick } } ================================================ FILE: Westwind.Utilities/Utilities/VersionUtils.cs ================================================ using System; using System.Linq; using System.Net; namespace Westwind.Utilities { public static class VersionExtensions { /// /// Formats a version by stripping all zero values /// up to the trimTokens count provided. By default /// displays Major.Minor and then displays any /// Build and/or revision if non-zero /// /// More info: https://weblog.west-wind.com/posts/2024/Jun/13/C-Version-Formatting /// /// Version to format /// Minimum number of component tokens of the version to display /// Maximum number of component tokens of the version to display public static string FormatVersion(this Version version, int minTokens = 2, int maxTokens = 2) { if (minTokens < 1) minTokens = 1; if (minTokens > 4) minTokens = 4; if (maxTokens < minTokens) maxTokens = minTokens; if (maxTokens > 4) maxTokens = 4; var items = new [] { version.Major, version.Minor, version.Build, version.Revision }; int tokens = maxTokens; while (tokens > minTokens && items[tokens - 1] == 0) { tokens--; } return version.ToString(tokens); } /// /// Formats a version by stripping all zero values /// up to the trimTokens count provided. By default /// displays Major.Minor and then displays any /// Build and/or revision if non-zero /// /// Version to format /// Minimum number of component tokens to display /// Maximum number of component tokens to display public static string FormatVersion(string version, int minTokens = 2, int maxTokens = 2) { var ver = new Version(version); return ver.FormatVersion(minTokens, maxTokens); } /// /// Compare two version strings. /// /// Semantic Version string /// Semantic Version string /// 0 - equal, 1 - greater than compareAgainst, -1 - smaller than, -2 - Version Format error public static int CompareVersions(string versionToCompare, string versionToCompareAgainst) { try { var v1 = new Version(versionToCompare); var v2 = new Version(versionToCompareAgainst); return v1.CompareTo(v2); } catch { return -2; } } } } ================================================ FILE: Westwind.Utilities/Utilities/XmlUtils.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2008 - 2009 * http://www.west-wind.com/ * * Created: 09/08/2008 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Globalization; using System.Text; using System.Xml; using System.Xml.Linq; using Westwind.Utilities.Properties; namespace Westwind.Utilities { /// /// String utility class that provides a host of string related operations /// public static class XmlUtils { /// /// Turns a string into a properly XML Encoded string. /// Uses simple string replacement. /// /// Also see XmlUtils.XmlString() which uses XElement /// to handle additional extended characters. /// /// Plain text to convert to XML Encoded string /// /// If true encodes single and double quotes. /// When embedding element values quotes don't need to be encoded. /// When embedding attributes quotes need to be encoded. /// /// XML encoded string /// Invalid character in XML string public static string XmlString(string text, bool isAttribute = false) { if (string.IsNullOrEmpty(text)) return text; var sb = new StringBuilder(text.Length); foreach (var chr in text) { if (chr == '<') sb.Append("<"); else if (chr == '>') sb.Append(">"); else if (chr == '&') sb.Append("&"); if (isAttribute) { // special handling for quotes if (chr == '\"') sb.Append("""); else if (chr == '\'') sb.Append("'"); // Legal sub-chr32 characters else if (chr == '\n') sb.Append(" "); else if (chr == '\r') sb.Append(" "); else if (chr == '\t') sb.Append(" "); } else { if (chr < 32) throw new InvalidOperationException("Invalid character in Xml String. Chr " + Convert.ToInt16(chr) + " is illegal."); sb.Append(chr); } } return sb.ToString(); } /// /// Retrieves a result string from an XPATH query. Null if not found. /// /// /// /// /// public static XmlNode GetXmlNode(XmlNode node, string xPath, XmlNamespaceManager ns = null) { return node.SelectSingleNode(xPath, ns); } /// /// Retrieves a result string from an XPATH query. Null if not found. /// /// The base node to search from /// XPath to drill into to find the target node. if not provided or null, returns current node /// namespace to search in (optional) /// node text public static string GetXmlString(XmlNode node, string xPath = null, XmlNamespaceManager ns=null) { if (node == null) return null; if (string.IsNullOrEmpty(xPath)) return node?.InnerText; XmlNode selNode = node.SelectSingleNode(xPath,ns); return selNode?.InnerText; } /// /// Gets an Enum value from an xml node. Returns enum /// type value. Either flag or string based keys will work /// /// /// /// /// /// public static T GetXmlEnum(XmlNode node, string xPath, XmlNamespaceManager ns = null) { string val = GetXmlString(node, xPath,ns); if (!string.IsNullOrEmpty(val)) return (T)Enum.Parse(typeof(T), val, true); return default(T); } /// /// Retrieves a result int value from an XPATH query. 0 if not found. /// /// /// /// public static int GetXmlInt(XmlNode node, string XPath, XmlNamespaceManager ns = null) { string val = GetXmlString(node, XPath, ns); if (val == null) return 0; int result = 0; int.TryParse(val, out result); return result; } /// /// Retrieves a result decimal value from an XPATH query. 0 if not found. /// /// /// /// public static decimal GetXmlDecimal(XmlNode node, string XPath, XmlNamespaceManager ns = null) { string val = GetXmlString(node, XPath, ns); if (val == null) return 0; decimal result = 0; decimal.TryParse(val, NumberStyles.Any, CultureInfo.InvariantCulture, out result); return result; } /// /// Retrieves a result bool from an XPATH query. false if not found. /// /// /// /// public static bool GetXmlBool(XmlNode node, string xPath,XmlNamespaceManager ns = null) { string val = GetXmlString(node, xPath, ns); if (val == null) return false; if (val == "1" || val == "true" || val == "True") return true; return false; } /// /// Retrieves a result DateTime from an XPATH query. 1/1/1900 if not found. /// /// /// /// /// public static DateTime GetXmlDateTime(XmlNode node, string xPath, XmlNamespaceManager ns = null) { DateTime dtVal = new DateTime(1900, 1, 1, 0, 0, 0); string val = GetXmlString(node, xPath, ns); if (val == null) return dtVal; try { dtVal = XmlConvert.ToDateTime(val,XmlDateTimeSerializationMode.Utc); } catch { } return dtVal; } /// /// Gets an attribute by name /// /// /// /// value or null if not available public static string GetXmlAttributeString(XmlNode node, string attributeName) { XmlAttribute att = node.Attributes[attributeName]; if (att == null) return null; return att.InnerText; } /// /// Returns an integer value from an attribute /// /// /// /// /// public static int GetXmlAttributeInt(XmlNode node, string attributeName, int defaultValue) { string val = GetXmlAttributeString(node, attributeName); if (val == null) return defaultValue; return XmlConvert.ToInt32(val); } /// /// Returns an bool value from an attribute /// /// /// /// /// public static bool? GetXmlAttributeBool(XmlNode node, string attributeName) { string val = GetXmlAttributeString(node, attributeName); if (val == null) return null; return XmlConvert.ToBoolean(val); } /// /// Converts a .NET type into an XML compatible type - roughly /// /// /// public static string MapTypeToXmlType(Type type) { if (type == null) return null; if (type == typeof(string) || type == typeof(char) ) return "string"; if (type == typeof(int) || type== typeof(Int32) ) return "integer"; if (type == typeof(Int16) || type == typeof(byte) ) return "short"; if (type == typeof(long) || type == typeof(Int64) ) return "long"; if (type == typeof(bool)) return "boolean"; if (type == typeof(DateTime)) return "datetime"; if (type == typeof(float)) return "float"; if (type == typeof(decimal)) return "decimal"; if (type == typeof(double)) return "double"; if (type == typeof(Single)) return "single"; if (type == typeof(byte)) return "byte"; if (type == typeof(byte[])) return "base64Binary"; return null; // *** hope for the best //return type.ToString().ToLower(); } public static Type MapXmlTypeToType(string xmlType) { xmlType = xmlType.ToLower(); if (xmlType == "string") return typeof(string); if (xmlType == "integer") return typeof(int); if (xmlType == "long") return typeof(long); if (xmlType == "boolean") return typeof(bool); if (xmlType == "datetime") return typeof(DateTime); if (xmlType == "float") return typeof(float); if (xmlType == "decimal") return typeof(decimal); if (xmlType == "double") return typeof(Double); if (xmlType == "single") return typeof(Single); if (xmlType == "byte") return typeof(byte); if (xmlType == "base64binary") return typeof(byte[]); // return null if no match is found // don't throw so the caller can decide more efficiently what to do // with this error result return null; } /// /// Creates an Xml NamespaceManager for an XML document by looking /// at all of the namespaces defined on the document root element. /// /// The XmlDom instance to attach the namespacemanager to /// The prefix to use for prefix-less nodes (which are not supported if any namespaces are used in XmlDoc). /// public static XmlNamespaceManager CreateXmlNamespaceManager(XmlDocument doc, string defaultNamespace) { XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); foreach (XmlAttribute attr in doc.DocumentElement.Attributes) { if (attr.Prefix == "xmlns") nsmgr.AddNamespace(attr.LocalName, attr.Value); if (attr.Name == "xmlns") // default namespace MUST use a prefix nsmgr.AddNamespace(defaultNamespace, attr.Value); } return nsmgr; } } } ================================================ FILE: Westwind.Utilities/Westwind.Utilities.csproj ================================================  net10.0;net8.0;net472;netstandard2.0 5.2.8.1 Rick Strahl false en-US Westwind.Utilities Westwind.Utilities en-US Westwind.Utilities Westwind.Utilities West Wind Utilities .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. 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. Rick Strahl, West Wind Technologies 2007-2026 Westwind ApplicationConfiguration StringUtils ReflectionUtils DataUtils FileUtils TimeUtils SerializationUtils ImageUtils Logging DAL Sql ADO.NET http://github.com/rickstrahl/westwind.utilities icon.png LICENSE.MD true Rick Strahl, West Wind Technologies, 2010-2026 West Wind Technologies latest TRACE;DEBUG; embedded $(NoWarn);CS1591;CS1572;CS1573 true True ./nupkg true RELEASE NETCORE;NETSTANDARD;NETSTANDARD2_0 NETFULL embedded true True True Resources.resx PublicResXFileCodeGenerator Resources.Designer.cs ================================================ FILE: Westwind.Utilities/Westwind.Utilities.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Westwind.Utilities", "Westwind.Utilities.csproj", "{203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Debug|Any CPU.Build.0 = Debug|Any CPU {203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.ActiveCfg = Release|Any CPU {203DEFFC-8FA8-9E5E-05BD-136AE8F2055D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3A29F986-14FE-4001-B3F7-3470BDE9EDB0} EndGlobalSection EndGlobal ================================================ FILE: Westwind.Utilities/publish-nuget.ps1 ================================================ if (test-path ./nupkg) { remove-item ./nupkg -Force -Recurse } dotnet build -c Release $filename = Get-ChildItem "./nupkg/*.nupkg" | sort LastWriteTime | select -last 1 | select -ExpandProperty "Name" Write-host $filename $len = $filename.length if ($len -gt 0) { Write-Host "signing... $filename" #nuget sign ".\nupkg\$filename" -CertificateSubject "West Wind Technologies" -timestamper " http://timestamp.digicert.com" nuget push ".\nupkg\$filename" -source "https://nuget.org" Write-Host "Done." } ================================================ FILE: Westwind.Utilities.Data/Configuration/SqlServerConfigurationProvider.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009-2013 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion // TODO: Doesn't work due to missing SqlDbClientFactories which should be added later #if NETCORE using Microsoft.Data.SqlClient; #else using System.Data.SqlClient; #endif using Westwind.Utilities.Data; using System.Data.Common; using System; //using System.Data.SqlServerCe; namespace Westwind.Utilities.Configuration { /// /// Reads and Writes configuration settings in .NET config files and /// sections. Allows reading and writing to default or external files /// and specification of the configuration section that settings are /// applied to. /// /// This implementation doesn't support Read and Write operation that /// don't return a string value. Only Read(string) and WriteAsString() /// should be used to read and write string values. /// public class SqlServerConfigurationProvider : ConfigurationProviderBase where TAppConfiguration : AppConfiguration, new() { /// /// The raw SQL connection string or connectionstrings name /// for the database connection. /// public string ConnectionString { get { return _ConnectionString; } set { _ConnectionString = value; } } private string _ConnectionString = string.Empty; #if NETFULL /// /// The data provider used to access the database /// public string ProviderName { get; set; } = "System.Data.SqlClient"; #else /// /// If ProviderName is missing /// public DbProviderFactory ProviderFactory { get; set; } = SqlClientFactory.Instance; #endif /// /// Table in the database that holds configuration data /// Table must have ID(int) and ConfigData (nText) fields /// public string Tablename { get { return _Tablename; } set { _Tablename = value; } } private string _Tablename = "ConfigurationSettings"; /// /// The key of the record into which the config /// data is written. Defaults to 1. /// /// If you need to read or write multiple different /// configuration records you have to change it on /// this provider before calling the Read()/Write() /// methods. /// public int Key { get { return _Key; } set { _Key = value; } } private int _Key = 1; /// /// Reads configuration data into a new instance from SQL Server /// that is returned. /// /// /// public override T Read() { #if NETFULL using (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderName) ) #else using (SqlDataAccess data = new SqlDataAccess(ConnectionString, ProviderFactory)) #endif { string sql = "select * from [" + Tablename + "] where id=" + Key.ToString(); DbDataReader reader = null; try { DbCommand command = data.CreateCommand(sql); if (command == null) { SetError(data.ErrorMessage); return null; } reader = command.ExecuteReader(); if (reader == null) { SetError(data.ErrorMessage); return null; } } catch (SqlException ex) { if (ex.Number == 208) { sql = @"CREATE TABLE [" + Tablename + @"] ( [id] [int] , [ConfigData] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS)"; try { data.ExecuteNonQuery(sql); } catch { return null; } // try again if we were able to create the table return Read(); } } catch (DbException dbEx) { // SQL CE Table doesn't exist if (dbEx.ErrorCode == -2147467259) { sql = String.Format( @"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )", Tablename); try { data.ExecuteNonQuery(sql); } catch { return null; } // try again if we were able to create the table var inst = Read(); // if we got it write it to the db Write(inst); return inst; } return null; } catch (Exception ex) { this.SetError(ex); if (reader != null) reader.Close(); data.CloseConnection(); return null; } string xmlConfig = null; if (reader.Read()) xmlConfig = (string)reader["ConfigData"]; reader.Close(); data.CloseConnection(); if (string.IsNullOrEmpty(xmlConfig)) { T newInstance = new T(); newInstance.Provider = this; return newInstance; } T instance = Read(xmlConfig); return instance; } } /// /// Reads configuration data from Sql Server into an existing /// instance updating its fields. /// /// /// public override bool Read(AppConfiguration config) { TAppConfiguration newConfig = Read(); if (newConfig == null) return false; DataUtils.CopyObjectData(newConfig, config,"Provider,ErrorMessage"); return true; } public override bool Write(AppConfiguration config) { #if NETFULL SqlDataAccess data = new SqlDataAccess(ConnectionString,ProviderName); #else SqlDataAccess data = new SqlDataAccess(ConnectionString, SqlClientFactory.Instance); #endif string sql = String.Format( "Update [{0}] set ConfigData=@ConfigData where id={1}", Tablename, Key); string xml = WriteAsString(config); int result = 0; try { result = data.ExecuteNonQuery(sql, data.CreateParameter("@ConfigData", xml)); } catch { result = -1; } // try to create the table if (result == -1) { sql = String.Format( @"CREATE TABLE [{0}] ( [id] [int] , [ConfigData] [ntext] )", Tablename); try { result = data.ExecuteNonQuery(sql); if (result > -1) result = 0; } catch (Exception ex) { SetError(ex); return false; } } // Check for missing record if (result == 0) { sql = "Insert [" + Tablename + "] (id,configdata) values (" + Key.ToString() + ",@ConfigData)"; try { result = data.ExecuteNonQuery(sql, data.CreateParameter("@ConfigData", xml)); } catch (Exception ex) { SetError(ex); return false; } if (result == 0) { return false; } } if (result < 0) return false; return true; } } } ================================================ FILE: Westwind.Utilities.Data/ConnectionStringInfo.cs ================================================ using System; using System.Configuration; using System.Data.Common; #if NETCORE using Microsoft.Data.SqlClient; #else using System.Data.SqlClient; #endif using Westwind.Utilities.Properties; namespace Westwind.Utilities.Data { /// /// Used to parse a connection string or connection string name /// into a the base connection string and dbProvider. /// /// If a connection string is passed that's just used. /// If a ConnectionString entry name is passed the connection /// string is extracted and the provider parsed. /// public class ConnectionStringInfo { /// /// The default connection string provider /// public static string DefaultProviderName = "System.Data.SqlClient"; /// /// The connection string parsed /// public string ConnectionString { get; set; } /// /// The DbProviderFactory parsed from the connection string /// or default provider /// public DbProviderFactory Provider { get; set; } /// /// Figures out the Provider and ConnectionString from either a connection string /// name in a config file or full ConnectionString and provider. /// /// Config file connection name or full connection string /// optional provider name. If not passed with a connection string is considered Sql Server /// optional provider factory. Use for .NET Core to pass actual provider instance since DbproviderFactories doesn't exist public static ConnectionStringInfo GetConnectionStringInfo(string connectionString, string providerName = null, DbProviderFactory factory = null) { var info = new ConnectionStringInfo(); if (string.IsNullOrEmpty(connectionString)) throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor); if (!connectionString.Contains("=")) { #if NETFULL connectionString = RetrieveConnectionStringFromConfig(connectionString, info); #else throw new ArgumentException("Connection string names are not supported with .NET Standard. Please use a full connectionstring."); #endif } else { info.Provider = factory; if (factory == null) { if (providerName == null) providerName = DefaultProviderName; // TODO: DbProviderFactories This should get fixed by release of .NET 2.0 #if NETFULL info.Provider = DbProviderFactories.GetFactory(providerName); #else info.Provider = SqlClientFactory.Instance; #endif } } info.ConnectionString = connectionString; return info; } #if NETFULL /// /// Retrieves a connection string from the Connection Strings configuration settings /// /// /// /// Throws when connection string doesn't exist /// public static string RetrieveConnectionStringFromConfig(string connectionStringName, ConnectionStringInfo info) { // it's a connection string entry var connInfo = ConfigurationManager.ConnectionStrings[connectionStringName]; if (connInfo != null) { if (!string.IsNullOrEmpty(connInfo.ProviderName)) info.Provider = DbProviderFactories.GetFactory(connInfo.ProviderName); else info.Provider = DbProviderFactories.GetFactory(DefaultProviderName); connectionStringName = connInfo.ConnectionString; } else throw new InvalidOperationException(Resources.InvalidConnectionStringName + ": " + connectionStringName); return connectionStringName; } #endif } } ================================================ FILE: Westwind.Utilities.Data/DataAccessBase.cs ================================================ #region License //#define SupportWebRequestProvider /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Westwind.Utilities.Properties; #if NETCORE using Microsoft.Data.SqlClient; #else using System.Data.SqlClient; #endif namespace Westwind.Utilities.Data { /// /// Base Data Access Layer (DAL) for ADO.NET SQL operations. /// Provides easy, single method operations to retrieve DataReader, /// DataTable, DataSet and Entities, perform non-query operations, /// call stored procedures. /// /// This abstract class implements most data operations using a /// configured DbProvider. Subclasses implement specific database /// providers and override a few methods that might have provider /// specific SQL Syntax. /// [DebuggerDisplay("{ ErrorMessage } {ConnectionString} {LastSql}")] public abstract class DataAccessBase : IDisposable { /// /// Default constructor that should be called back to /// by subclasses. Parameterless assumes default provider /// and no connection string which must be explicitly set. /// protected DataAccessBase() { dbProvider = SqlClientFactory.Instance; } /// /// Most common constructor that expects a connection string or /// connection string name from a .config file. If a connection /// string is provided the default provider is used. /// /// protected DataAccessBase(string connectionString) { if (string.IsNullOrEmpty(connectionString)) throw new InvalidOperationException(Resources.AConnectionStringMustBePassedToTheConstructor); // sets dbProvider and ConnectionString properties // based on connectionString Name or full connection string GetConnectionInfo(connectionString,null); } #if NETFULL /// /// Constructor that expects a full connection string and provider /// for creating a SQL instance. To be called by the same implementation /// on a subclass. /// /// /// protected DataAccessBase(string connectionString, string providerName) { ConnectionString = connectionString; dbProvider = DbProviderFactories.GetFactory(providerName); } #endif /// /// Constructor that expects a full connection string and provider /// for creating a SQL instance. To be called by the same implementation /// on a subclass. /// /// /// protected DataAccessBase(string connectionString, DbProviderFactory provider) { ConnectionString = connectionString; dbProvider = provider; } /// /// Holds the last Identity Key if running insert statements that return /// the identity key (save entity) /// public object LastIdentityResult { get; set; } /// /// Command to retrieve the last identity key from the database - /// appended to auto-generated insert commands /// public string GetIdentityKeySqlCommand { get; set; } = "select SCOPE_IDENTITY()"; /// /// Create a DataAccess component with a specific database provider /// /// /// public DataAccessBase(string connectionString, DataAccessProviderTypes providerType) { ConnectionString = connectionString; DbProviderFactory instance = SqlUtils.GetDbProviderFactory(providerType); dbProvider = instance ?? throw new InvalidOperationException("Can't load database provider: " + providerType.ToString()); } /// /// Figures out the dbProvider and Connection string from a /// connectionString name in a config file or explicit /// ConnectionString and provider. /// /// Config file connection name or full connection string /// optional provider name. If not passed with a connection string is considered Sql Server public void GetConnectionInfo(string connectionString, string providerName = null) { // throws if connection string is invalid or missing var connInfo = ConnectionStringInfo.GetConnectionStringInfo(connectionString, providerName); ConnectionString = connInfo.ConnectionString; dbProvider = connInfo.Provider; } /// /// The internally used dbProvider /// public DbProviderFactory dbProvider = null; /// /// An error message if a method fails /// public virtual string ErrorMessage { get; set; } = string.Empty; /// /// Optional error number returned by failed SQL commands /// public int ErrorNumber { get; set; } = 0; public Exception ErrorException { get; set; } public bool ThrowExceptions { get; set; } = false; /// /// The prefix used by the provider /// public string ParameterPrefix { get; set; } = "@"; /// /// Determines whether parameters are positional or named. Positional /// parameters are added without adding the name using just the ParameterPrefix /// public bool UsePositionalParameters { get; set; } = false; /// /// Character used for the left bracket on field names. Can be empty or null to use none /// public string LeftFieldBracket { get; set; } = "["; /// /// Character used for the right bracket on field names. Can be empty or null to use none /// public string RightFieldBracket { get; set; } = "]"; /// /// ConnectionString for the data access component /// public virtual string ConnectionString { get; set; } = string.Empty; /// /// A SQL Transaction object that may be active. You can /// also set this object explcitly /// public virtual DbTransaction Transaction { get; set; } /// /// The SQL Connection object used for connections /// public virtual DbConnection Connection { get { return _Connection; } set { _Connection = value; } } protected DbConnection _Connection = null; /// /// The Sql Command execution Timeout in seconds. /// Set to -1 for whatever the system default is. /// Set to 0 to never timeout (not recommended). /// public int Timeout { get; set; } = -1; /// /// Determines whether extended schema information is returned for /// queries from the server. Useful if schema needs to be returned /// as part of DataSet XML creation /// public virtual bool ExecuteWithSchema { get; set; } = false; /// /// Holds the last SQL string executed /// public string LastSql { get; set; } #region Connection Operations /// /// Opens a Sql Connection based on the connection string. /// Called internally but externally accessible. Sets the internal /// _Connection property. /// /// /// /// Opens a Sql Connection based on the connection string. /// Called internally but externally accessible. Sets the internal /// _Connection property. /// /// public virtual bool OpenConnection() { try { if (_Connection == null) { if (ConnectionString.Contains("=")) { _Connection = dbProvider.CreateConnection(); _Connection.ConnectionString = ConnectionString; } else { var connInfo = ConnectionStringInfo.GetConnectionStringInfo(ConnectionString); if (connInfo == null) { SetError(Resources.InvalidConnectionString); if (ThrowExceptions) throw new ApplicationException(ErrorMessage); return false; } dbProvider = connInfo.Provider; ConnectionString = connInfo.ConnectionString; _Connection = dbProvider.CreateConnection(); _Connection.ConnectionString = ConnectionString; } } if (_Connection.State != ConnectionState.Open) _Connection.Open(); } catch (SqlException ex) { SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message)); ErrorException = ex; return false; } catch (DbException ex) { SetError(string.Format(Resources.ConnectionOpeningFailure, ex.Message)); ErrorException = ex; return false; } catch (Exception ex) { SetError(string.Format(Resources.ConnectionOpeningFailure, ex.GetBaseException().Message)); ErrorException = ex; return false; } return true; } /// /// Closes an active connection. If a transaction is pending the /// connection is held open. /// public virtual void CloseConnection() { if (Transaction != null) return; if (_Connection != null && _Connection.State != ConnectionState.Closed) _Connection.Close(); _Connection = null; } /// /// Closes a connection on a command /// /// public virtual void CloseConnection(DbCommand Command) { if (Transaction != null) return; if (Command.Connection != null && Command.Connection.State != ConnectionState.Closed) Command.Connection.Close(); _Connection = null; } #endregion #region Core Operations /// /// Creates a Command object and opens a connection /// /// Sql string to execute /// Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter() /// Command object or null on error public virtual DbCommand CreateCommand(string sql, CommandType commandType, params object[] parameters) { SetError(); DbCommand command = dbProvider.CreateCommand(); command.CommandType = commandType; command.CommandText = sql; if (Timeout > -1) command.CommandTimeout = Timeout; try { if (Transaction != null) { command.Transaction = Transaction; command.Connection = Transaction.Connection; } else { if (!OpenConnection()) return null; command.Connection = _Connection; } } catch (Exception ex) { SetError(ex); return null; } if (parameters != null) AddParameters(command,parameters); return command; } /// /// Creates a Command object and opens a connection /// /// Sql string to execute /// Either values mapping to @0,@1,@2 etc. or DbParameter objects created with CreateParameter() /// command object public virtual DbCommand CreateCommand(string sql, params object[] parameters) { return CreateCommand(sql, CommandType.Text, parameters); } /// /// Adds parameters to a DbCommand instance. Parses value and DbParameter parameters /// properly into the command's Parameters collection. /// /// A preconfigured DbCommand object that should have all connection information set /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// protected void AddParameters(DbCommand command, object[] parameters) { if (parameters != null && parameters.Length > 0) { // check for anonymous type if (parameters.Length == 1 && ReflectionUtils.IsAnonoymousType(parameters[0])) { ParseObjectParameters(command, parameters[0]); return; } var parmCount = 0; foreach (var parameter in parameters) { if (parameter is DbParameter) command.Parameters.Add(parameter); else { var parm = CreateParameter(ParameterPrefix + parmCount, parameter); command.Parameters.Add(parm); parmCount++; } } } } /// /// Parses an anonymous object map into a set of DbParameters /// /// /// /// public DbParameterCollection ParseObjectParameters(DbCommand command, object parameter) { var props = parameter.GetType().GetProperties(); var parmCount = 0; foreach (var prop in props) { object value = prop.GetValue(parameter, null); if (value is DbParameter) command.Parameters.Add(value); else { var parm = CreateParameter(ParameterPrefix + prop.Name, value); command.Parameters.Add(parm); parmCount++; } } return command.Parameters; } /// /// Used to create named parameters to pass to commands or the various /// methods of this class. /// /// /// /// /// public virtual DbParameter CreateParameter(string parameterName, object value) { DbParameter parm = dbProvider.CreateParameter(); parm.ParameterName = parameterName; if (value == null) value = DBNull.Value; parm.Value = value; return parm; } /// /// Used to create named parameters to pass to commands or the various /// methods of this class. /// /// /// /// /// public virtual DbParameter CreateParameter(string parameterName, object value, ParameterDirection parameterDirection = ParameterDirection.Input) { DbParameter parm = CreateParameter(parameterName, value); parm.Direction = parameterDirection; return parm; } /// /// Used to create named parameters to pass to commands or the various /// methods of this class. /// /// /// /// /// public virtual DbParameter CreateParameter(string parameterName, object value, int size) { DbParameter parm = CreateParameter(parameterName, value); parm.Size = size; return parm; } /// /// Used to create named parameters to pass to commands or the various /// methods of this class. /// /// /// /// /// public virtual DbParameter CreateParameter(string parameterName, object value, DbType type) { DbParameter parm = CreateParameter(parameterName, value); parm.DbType = type; return parm; } /// /// Used to create named parameters to pass to commands or the various /// methods of this class. /// /// /// /// /// /// public virtual DbParameter CreateParameter(string parameterName, object value, DbType type, int size) { DbParameter parm = CreateParameter(parameterName, value); parm.DbType = type; parm.Size = size; return parm; } #endregion #region Transactions /// /// Starts a new transaction on this connection/instance /// /// Opens a Connection and keeps it open for the duration of the transaction. Calls to `.CloseConnection` while the transaction is active have no effect. /// public virtual bool BeginTransaction() { if (_Connection == null) { if (!OpenConnection()) return false; } Transaction = _Connection.BeginTransaction(); if (Transaction == null) return false; return true; } /// /// Commits all changes to the database and ends the transaction /// /// Closes Connection /// public virtual bool CommitTransaction() { if (Transaction == null) { SetError(Resources.NoActiveTransactionToCommit); if (ThrowExceptions) new InvalidOperationException(Resources.NoActiveTransactionToCommit); return false; } Transaction.Commit(); Transaction = null; CloseConnection(); return true; } /// /// Rolls back a transaction /// /// Closes Connection /// public virtual bool RollbackTransaction() { if (Transaction == null) return true; Transaction.Rollback(); Transaction = null; CloseConnection(); return true; } #endregion #region Non-list Sql Commands /// /// Executes a non-query command and returns the affected records /// /// Command should be created with GetSqlCommand to have open connection /// Affected Record count or -1 on error public virtual int ExecuteNonQuery(DbCommand Command) { SetError(); int RecordCount = 0; try { LastSql = Command.CommandText; RecordCount = Command.ExecuteNonQuery(); if (RecordCount == -1) RecordCount = 0; } catch (DbException ex) { RecordCount = -1; SetError(ex);; } catch (Exception ex) { RecordCount = -1; SetError(ex); } finally { CloseConnection(); } return RecordCount; } /// /// Executes a command that doesn't return any data. The result /// returns the number of records affected or -1 on error. /// /// SQL statement as a string /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// or new { parm1=value, parm2 = value2} /// /// /// /// Executes a command that doesn't return a data result. You can return /// output parameters and you do receive an AffectedRecords counter. /// .setItem("list_html", JSON.stringify(data)); /// public virtual int ExecuteNonQuery(string sql, params object[] parameters) { DbCommand command = CreateCommand(sql,parameters); if (command == null) return -1; return ExecuteNonQuery(command); } /// /// Executes a non-query command and returns the affected records /// /// Command should be created with GetSqlCommand to have open connection /// Affected Record count or -1 on error public virtual async Task ExecuteNonQueryAsync(DbCommand Command) { SetError(); int RecordCount = 0; try { LastSql = Command.CommandText; RecordCount = await Command.ExecuteNonQueryAsync(); if (RecordCount == -1) RecordCount = 0; } catch (DbException ex) { RecordCount = -1; SetError(ex); ; } catch (Exception ex) { RecordCount = -1; SetError(ex); } finally { CloseConnection(); } return RecordCount; } /// /// Executes a command that doesn't return any data. The result /// returns the number of records affected or -1 on error. /// /// SQL statement as a string /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// /// /// Executes a command that doesn't return a data result. You can return /// output parameters and you do receive an AffectedRecords counter. /// .setItem("list_html", JSON.stringify(data)); /// public virtual async Task ExecuteNonQueryAsync(string sql, params object[] parameters) { DbCommand command = CreateCommand(sql, parameters); if (command == null) return -1; int result = await ExecuteNonQueryAsync(command); return result; } /// /// Executes a command and returns a scalar value from it /// /// DbCommand containing command to run /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// value or null on failure public virtual object ExecuteScalar(DbCommand command, params object[] parameters) { SetError(); AddParameters(command, parameters); object Result = null; try { LastSql = command.CommandText; Result = command.ExecuteScalar(); } catch (Exception ex) { SetError(ex.GetBaseException()); } finally { CloseConnection(); } return Result; } /// /// Executes a Sql command and returns a single value from it. /// /// Sql string to execute /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// Result value or null. Check ErrorMessage on Null if unexpected public virtual object ExecuteScalar(string sql, params object[] parameters) { SetError(); DbCommand command = CreateCommand(sql, parameters); if (command == null) return null; return ExecuteScalar(command, null); } /// /// Executes a command and returns a scalar value from it /// /// DbCommand containing command to run /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// value or null on failure public virtual async Task ExecuteScalarAsync(DbCommand command, params object[] parameters) { SetError(); AddParameters(command, parameters); object Result = null; try { LastSql = command.CommandText; Result = await command.ExecuteScalarAsync(); } catch (Exception ex) { SetError(ex.GetBaseException()); } finally { CloseConnection(); } return Result; } /// /// Executes a Sql command and returns a single value from it. /// /// Sql string to execute /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// Result value or null. Check ErrorMessage on Null if unexpected public virtual async Task ExecuteScalarAsync(string sql, params object[] parameters) { SetError(); DbCommand command = CreateCommand(sql, parameters); if (command == null) return null; return await ExecuteScalarAsync(command, null); } /// /// Executes a long SQL script that contains batches (GO commands). This code /// breaks the script into individual commands and captures all execution errors. /// /// If ContinueOnError is false, operations are run inside of a transaction and /// changes are rolled back. If true commands are accepted even if failures occur /// and are not rolled back. /// /// /// /// /// public bool RunSqlScript(string script, bool continueOnError = false, bool scriptIsFile = false) { SetError(); if (scriptIsFile) { try { script = File.ReadAllText(script); } catch (Exception ex) { SetError(ex.GetBaseException()); return false; } } // Normalize line endings to \n string scriptNormal = script.Replace("\r\n", "\n").Replace("\r", "\n"); string[] scriptBlocks = Regex.Split(scriptNormal + "\n", "GO\n"); string errors = ""; if (!continueOnError) BeginTransaction(); foreach (string block in scriptBlocks) { if (string.IsNullOrEmpty(block.TrimEnd())) continue; if (ExecuteNonQuery(block) == -1) { errors = ErrorMessage + block; if (!continueOnError) { RollbackTransaction(); return false; } } } if (!continueOnError) CommitTransaction(); if (string.IsNullOrEmpty(errors)) return true; ErrorMessage = errors; return false; } /// /// Determines whether a table exists /// /// /// /// public virtual bool DoesTableExist(string tablename, string schema = null) { var sql = @"SELECT TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @0"; if (!string.IsNullOrEmpty(schema)) sql += " AND TABLE_SCHEMA = @1"; var cat = ExecuteScalar(sql, tablename, schema); return !(cat is null); } #endregion #region Sql Execution /// /// Executes a SQL Command object and returns a SqlDataReader object /// /// Command should be created with GetSqlCommand and open connection /// /// /// A SqlDataReader. Make sure to call Close() to close the underlying connection. //public abstract DbDataReader ExecuteReader(DbCommand Command, params DbParameter[] Parameters) public virtual DbDataReader ExecuteReader(DbCommand command, params object[] parameters) { SetError(); if (command.Connection == null || command.Connection.State != ConnectionState.Open) { if (!OpenConnection()) return null; command.Connection = _Connection; } AddParameters(command, parameters); DbDataReader Reader = null; try { LastSql = command.CommandText; Reader = command.ExecuteReader(); } catch (Exception ex) { SetError(ex.GetBaseException()); CloseConnection(command); return null; } return Reader; } /// /// Executes a SQL command against the server and returns a DbDataReader /// /// Sql String /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual DbDataReader ExecuteReader(string sql, params object[] parameters) { DbCommand command = CreateCommand(sql, parameters); if (command == null) return null; return ExecuteReader(command); } /// /// Executes a Sql statement and returns a dynamic DataReader instance /// that exposes each field as a property /// /// Sql String to executeTable /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual dynamic ExecuteDynamicDataReader(string sql, params object[] parameters) { var reader = ExecuteReader(sql, parameters); return new DynamicDataReader(reader); } /// /// Return a list of entities that are matched to an object /// /// Type of object to create from data record /// Sql string /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// An enumerated list of objects or null [Obsolete("Use the Query method instead with the same syntax")] public virtual IEnumerable ExecuteReader(string sql, params object[] parameters) where T: class, new() { return Query(sql, null, parameters); } /// /// Allows querying and return a list of entities. /// /// /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// [Obsolete("Use the Query method instead with the same syntax")] public virtual IEnumerable ExecuteReader(DbCommand command, params object[] parameters) where T : class, new() { return Query(command, parameters); } /// /// Executes a SQL statement and creates an object list using /// optimized Reflection. /// /// Not very efficient but provides an easy way to retrieve /// an object list from query. /// /// Entity type to create from DataReader data /// Sql string to execute /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// List of objects or null. Null is returned if there are no matches public virtual IEnumerable Query(string sql, params object[] parameters) where T : class, new() { var reader = ExecuteReader(sql, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToIEnumerable(reader, null); } catch (Exception ex) { SetError(ex); return null; } } /// /// Returns list of objects from a query. /// /// /// /// /// public virtual List QueryList(string sql, params object[] parameters) where T : class, new() { var reader = ExecuteReader(sql, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToObjectList(reader,null); } catch (Exception ex) { SetError(ex); return null; } } /// /// Returns list of objects from a query. /// /// /// /// /// public virtual List QueryList(DbCommand command, params object[] parameters) where T : class, new() { var reader = ExecuteReader(command, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToObjectList(reader, null); } catch (Exception ex) { SetError(ex); return null; } } /// /// Returns list of objects from a query. /// /// /// Sql Statement string /// Comma delimited list of property names to skip /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual List QueryListWithExclusions(string sql, string propertiesToSkip, params object[] parameters) where T : class, new() { var reader = ExecuteReader(sql, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToObjectList(reader, propertiesToSkip); } catch (Exception ex) { SetError(ex); return null; } } /// /// Returns list of objects from a query. /// /// /// Sql Statement string /// Comma delimited list of property names to skip /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual List QueryListWithExclusions(DbCommand command, string propertiesToSkip, params object[] parameters) where T : class, new() { var reader = ExecuteReader(command, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToObjectList(reader, propertiesToSkip); } catch (Exception ex) { SetError(ex); return null; } } /// /// Executes a SQL command and creates an object list using /// Reflection. /// /// Not very efficient but provides an easy way to retrieve /// object lists from queries. /// /// Entity type to create from DataReader data /// Command object containing configured SQL command to execute /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// List of objects or null. Null is returned if there are no matches public virtual IEnumerable Query(DbCommand command, params object[] parameters) where T : class, new() { var reader = ExecuteReader(command, parameters); if (reader == null) return null; try { return DataUtils.DataReaderToIEnumerable(reader, null); } catch (Exception ex) { SetError(ex); return null; } } /// /// Executes a SQL statement and creates an object list using /// Reflection. /// /// Not very efficient but provides an easy way to retrieve /// object lists from queries /// /// Entity type to create from DataReader data /// Sql string to execute /// Comma delimited list of properties that are not to be updated /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// List of objects public virtual IEnumerable QueryWithExclusions(string sql, string propertiesToExclude, params object[] parameters) where T: class, new() { IEnumerable result; var reader = ExecuteReader(sql, parameters); if (reader == null) return null; try { result = DataUtils.DataReaderToIEnumerable(reader, propertiesToExclude); } catch (Exception ex) { SetError(ex); return null; } return result; } /// /// Executes a SQL statement and creates an object list using /// Reflection. /// /// Not very efficient but provides an easy way to retrieve /// /// Entity type to create from DataReader data /// Sql string to execute /// DbParameters to fill the SQL statement /// List of objects public virtual IEnumerable QueryWithExclusions(DbCommand sqlCommand, string propertiesToExclude, params object[] parameters) where T : class, new() { var reader = ExecuteReader(sqlCommand, parameters); try { return DataUtils.DataReaderToIEnumerable(reader, propertiesToExclude); } catch (Exception ex) { SetError(ex); return null; } } /// /// Calls a stored procedure that returns a cursor results /// The result is returned as a DataReader /// /// Name of the Stored Procedure to call /// /// Parameters to pass. Note that if you need to pass out/inout/return parameters /// you need to pass DbParameter instances or use the CreateParameter() method /// /// A DataReader or null on failure public virtual DbDataReader ExecuteStoredProcedureReader(string storedProc, params object[] parameters) { var command = CreateCommand(storedProc, parameters); if (command == null) return null; command.CommandType = CommandType.StoredProcedure; return ExecuteReader(command); } /// /// Calls a stored procedure that returns a cursor results /// The result is returned as an IEnumerable<T>> list /// /// /// IEnumerable<Customer%gt; customers = context.Db.ExecuteStoredProcedureReader<Customer>('GetCustomers', /// context.Db.CreateParameter('@cCompany','W%')); /// /// Name of the Stored Procedure to call /// /// Use CreateParameter() for named, output or return parameters. Plain values for others. /// /// A DataReader or null on failure public virtual IEnumerable ExecuteStoredProcedureReader(string storedProc, params object[] parameters) where T : class, new() { var command = CreateCommand(storedProc, parameters); if (command == null) return null; command.CommandType = CommandType.StoredProcedure; return Query(command,null); } /// /// Executes a stored procedure that doesn't return a result set. /// /// The Stored Procedure to call /// /// Parameters to pass. Note that if you need to pass out/inout/return parameters /// you need to pass DbParameter instances or use the CreateParameter() method /// /// > 0 or greater on success, -1 on failure public virtual int ExecuteStoredProcedureNonQuery(string storedProc, params object[] parameters) { var command = CreateCommand(storedProc, parameters); if (command == null) return -1; command.CommandType = CommandType.StoredProcedure; return ExecuteNonQuery(command); } /// /// Returns a DataTable from a Sql Command string passed in. /// /// /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual DataTable ExecuteTable(string tablename, DbCommand command, params object[] parameters) { SetError(); AddParameters(command, parameters); DbDataAdapter adapter = dbProvider.CreateDataAdapter(); if (adapter == null) { SetError("Failed to create data adapter."); return null; } adapter.SelectCommand = command; LastSql = command.CommandText; DataTable dt = new DataTable(tablename); try { adapter.Fill(dt); } catch (Exception ex) { SetError(ex.GetBaseException()); return null; } finally { CloseConnection(command); } return dt; } /// /// Returns a DataTable from a Sql Command string passed in. /// /// /// /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual DataTable ExecuteTable(string Tablename, string Sql, params object[] Parameters) { SetError(); DbCommand command = CreateCommand(Sql, Parameters); if (command == null) return null; return ExecuteTable(Tablename, command); } /// /// Returns a DataSet/DataTable from a Sql Command string passed in. /// /// The name for the table generated or the base names /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual DataSet ExecuteDataSet(string Tablename, DbCommand Command, params object[] Parameters) { return ExecuteDataSet(null, Tablename, Command, Parameters); } /// /// Executes a SQL command against the server and returns a DataSet of the result /// /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual DataSet ExecuteDataSet(string tablename, string sql, params object[] parameters) { return ExecuteDataSet(tablename, CreateCommand(sql), parameters); } /// /// Returns a DataSet from a Sql Command string passed in. /// /// /// /// /// public virtual DataSet ExecuteDataSet(DataSet dataSet, string tableName, DbCommand command, params object[] parameters) { SetError(); if (dataSet == null) dataSet = new DataSet(); DbDataAdapter adapter = dbProvider.CreateDataAdapter(); adapter.SelectCommand = command; LastSql = command.CommandText; if (ExecuteWithSchema) adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; AddParameters(command, parameters); DataTable dt = new DataTable(tableName); if (dataSet.Tables.Contains(tableName)) dataSet.Tables.Remove(tableName); try { adapter.Fill(dataSet, tableName); } catch (Exception ex) { SetError(ex); return null; } finally { CloseConnection(command); } return dataSet; } /// /// Returns a DataTable from a Sql Command string passed in. /// /// /// /// /// public virtual DataSet ExecuteDataSet(DataSet dataSet, string tablename, string sql, params object[] parameters) { DbCommand Command = CreateCommand(sql, parameters); if (Command == null) return null; return ExecuteDataSet(dataSet, tablename, Command); } /// /// Sql 2005 specific semi-generic paging routine /// /// /// /// /// /// /// public virtual DbCommand CreatePagingCommand(string sql, int pageSize, int page, string sortOrderFields, params object[] Parameters) { int pos = sql.IndexOf("select ", 0, StringComparison.OrdinalIgnoreCase); if (pos == -1) { SetError("Invalid Command for paging. Must start with select and followed by field list"); return null; } sql = StringUtils.ReplaceStringInstance(sql, "select", string.Empty, 1, true); string newSql = string.Format( @" select * FROM (SELECT ROW_NUMBER() OVER (ORDER BY @OrderByFields) as __No,{0}) __TQuery where __No > (@Page-1) * @PageSize and __No < (@Page * @PageSize + 1) ", sql); return CreateCommand(newSql, CreateParameter("@PageSize", pageSize), CreateParameter("@Page", page), CreateParameter("@OrderByFields", sortOrderFields)); } #endregion #region Generic Entity features /// /// Generic routine to retrieve an object from a database record /// The object properties must match the database fields. /// /// The object to update /// Database command object /// /// public virtual bool GetEntity(object entity, DbCommand command, string propertiesToSkip = null) { SetError(); if (string.IsNullOrEmpty(propertiesToSkip)) propertiesToSkip = string.Empty; DbDataReader reader = ExecuteReader(command); if (reader == null) return false; if (!reader.Read()) { reader.Close(); CloseConnection(command); return false; } DataUtils.DataReaderToObject(reader, entity, propertiesToSkip); reader.Close(); CloseConnection(); return true; } /// /// Retrieves a single record and returns it as an entity /// /// /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public bool GetEntity(object entity, string sql, object[] parameters) { var command = CreateCommand(sql, parameters); if (command == null) return false; return GetEntity(entity, command, null); } /// /// Generic routine to return an Entity that matches the field names of a /// table exactly. /// /// /// /// /// /// /// public virtual bool GetEntity(object entity, string table, string keyField, object keyValue, string propertiesToSkip = null) { SetError(); DbCommand Command = CreateCommand("select * from " + table + " where " + LeftFieldBracket + keyField + RightFieldBracket + "=" + ParameterPrefix + "Key", CreateParameter(ParameterPrefix + "Key", keyValue)); if (Command == null) return false; return GetEntity(entity, Command, propertiesToSkip); } /// /// Finds the first matching entity based on a keyfield and key value. /// Note the keyfield can be any field that is used in a WHERE clause. /// /// This method has been renamed from Find() to avoid ambiguous /// overload errors. /// /// Type of entity to populate /// Value to look up in keyfield /// Name of the table to work on /// Field that is used for the key lookup /// public virtual T FindKey(object keyValue, string tableName,string keyField) where T: class,new() { T obj = new T(); if (obj == null) return null; if (!GetEntity(obj, tableName, keyField, keyValue, null)) return null; return obj; } /// /// Returns the first matching record retrieved from data based on a SQL statement /// as an entity or null if no match was found. /// /// NOTE: Key based method has been renamed to: /// `FindKey` /// /// Entity type to fill /// SQL string to execute. Use @0,@1,@2 for parameters. /// /// Recommend you use `TOP1` in your SQL statements to limit the /// amount of data returned from the underlying query even though /// a full list returns the same result. /// /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual T Find(string sql, params object[] parameters) where T : class,new() { T obj = new T(); if (!GetEntity(obj, sql, parameters)) return null; return obj; } /// /// Returns an entity that is the first match from a sql statement string. /// /// Entity type to return /// Sql string to execute. Use @0,@1,@2 for positional parameters /// fields to not update from the resultset /// /// DbParameters (CreateParameter()) for named parameters /// or use @0,@1 parms in SQL and plain values /// /// public virtual T FindEx(string sql, string propertiesToSkip, params object[] parameters) where T : class,new() { T obj = new T(); if (!GetEntity(obj, CreateCommand( sql, parameters),propertiesToSkip)) return null; return obj; } /// /// Updates an entity object that has matching fields in the database for each /// public property. Kind of a poor man's quick entity update mechanism. /// /// Note this method will not save if the record doesn't already exist in the db. /// /// entity to update /// the table name to update /// keyfield used to find entity /// /// public virtual bool UpdateEntity(object entity, string table, string keyField, string propertiesToSkip = null) { SetError(); var command = GetUpdateEntityCommand(entity, table, keyField, propertiesToSkip); if (command == null) return false; bool result; using (command) { result = ExecuteNonQuery(command) > -1; CloseConnection(command); } return result; } /// /// Updates an entity object that has matching fields in the database for each /// public property. Kind of a poor man's quick entity update mechanism. /// /// Note this method will not save if the record doesn't already exist in the db. /// /// entity to update /// the table name to update /// keyfield used to find entity /// /// public virtual DbCommand GetUpdateEntityCommand(object entity, string table, string keyField, string propertiesToSkip = null) { SetError(); if (string.IsNullOrEmpty(propertiesToSkip)) propertiesToSkip = string.Empty; else propertiesToSkip = "," + propertiesToSkip.ToLower() + ","; var command = CreateCommand(string.Empty); var objType = entity.GetType(); StringBuilder sb = new StringBuilder(); sb.Append("update " + table + " set "); PropertyInfo[] Properties = objType.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo property in Properties) { if (!property.CanRead || !property.CanWrite) continue; if (!property.PropertyType.IsValueType && property.PropertyType.Name != "String" && property.PropertyType.Name != "Byte[]") continue; string name = property.Name; if (propertiesToSkip.IndexOf("," + name.ToLower() + ",") > -1) continue; object value = property.GetValue(entity, null); string parmString = UsePositionalParameters ? ParameterPrefix : ParameterPrefix + name; sb.Append(" " + LeftFieldBracket + name + RightFieldBracket + "=" + parmString + ","); if (value == null && property.PropertyType == typeof(byte[])) { command.Parameters.Add( CreateParameter(ParameterPrefix + name, DBNull.Value, DataUtils.DotNetTypeToDbType(property.PropertyType)) ); } else command.Parameters.Add(CreateParameter(ParameterPrefix + name, value ?? DBNull.Value)); } object pkValue = ReflectionUtils.GetProperty(entity, keyField); String commandText = sb.ToString().TrimEnd(',') + " where " + keyField + "=" + ParameterPrefix + "__PK"; command.Parameters.Add(CreateParameter(ParameterPrefix + "__PK", pkValue)); command.CommandText = commandText; return command; } /// /// Gets a DbCommand that creates an update statement. /// that allows you to specify which fields to update and /// so is a bit more efficient as it only checks for specific fields in the database /// and the underlying table. /// /// /// /// Entity to update /// DB Table to udpate /// The keyfield to query on /// fields to skip in update /// fields that should be updated /// public virtual DbCommand GetUpdateEntityCommand(object entity, string table, string keyField, string propertiesToSkip, string fieldsToUpdate) { SetError(); if (propertiesToSkip == null) propertiesToSkip = string.Empty; else propertiesToSkip = "," + propertiesToSkip.ToLower() + ","; DbCommand command = CreateCommand(string.Empty); if (command == null) { SetError("Unable to create command."); return null; } Type objType = entity.GetType(); StringBuilder sb = new StringBuilder(); sb.Append("update " + table + " set "); string[] fields = fieldsToUpdate.Split(','); foreach (string Name in fields) { if (propertiesToSkip.IndexOf("," + Name.ToLower() + ",") > -1) continue; PropertyInfo property = objType.GetProperty(Name); if (property == null) continue; if (!property.CanWrite) continue; if (!property.PropertyType.IsValueType && property.PropertyType.Name != "String" && property.PropertyType.Name != "Byte[]") continue; object Value = property.GetValue(entity, null); string parmString = UsePositionalParameters ? ParameterPrefix : ParameterPrefix + Name; sb.Append(" " + LeftFieldBracket + Name + RightFieldBracket + "=" + parmString + ","); if (Value == null && property.PropertyType == typeof(byte[])) command.Parameters.Add(CreateParameter(ParameterPrefix + Name, DBNull.Value, DataUtils.DotNetTypeToDbType(property.PropertyType))); else command.Parameters.Add(CreateParameter(ParameterPrefix + Name, Value ?? DBNull.Value)); } object pkValue = ReflectionUtils.GetProperty(entity, keyField); // check to see if string commandText = sb.ToString().TrimEnd(',') + " where " + LeftFieldBracket + keyField + RightFieldBracket + "=" + ParameterPrefix + (UsePositionalParameters ? "" : "__PK"); command.Parameters.Add(CreateParameter(ParameterPrefix + "__PK", pkValue)); command.CommandText = commandText; return command; } /// /// This version of UpdateEntity allows you to specify which fields to update and /// so is a bit more efficient as it only checks for specific fields in the database /// and the underlying table. /// /// /// /// Entity to update /// DB Table to udpate /// The keyfield to query on /// fields to skip in update /// fields that should be updated /// public virtual bool UpdateEntity(object entity, string table, string keyField, string propertiesToSkip, string fieldsToUpdate) { SetError(); var command = GetUpdateEntityCommand(entity, table, keyField, propertiesToSkip, fieldsToUpdate); if (command == null) return false; bool result; using (command) { result = ExecuteNonQuery(command) > -1; CloseConnection(command); } return result; } /// /// Inserts an object into the database based on its type information. /// The properties must match the database structure and you can skip /// over fields in the propertiesToSkip list. /// /// /// Entity data to insert into table /// Name of the table to update /// Comma delimited list of fields to skip in the entity update /// /// /// Null if the insert failed /// Scope Identity (when returnIdentityKey is true or null if that fails) /// Otherwise affected records /// public object InsertEntity(object entity, string table, string propertiesToSkip = null, bool returnIdentityKey = true) { SetError(); using (DbCommand command = GetInsertEntityCommand(entity, table, propertiesToSkip)) { if (command == null) return null; if (returnIdentityKey) { command.CommandText += ";\r\n" + GetIdentityKeySqlCommand; LastIdentityResult = ExecuteScalar(command); return LastIdentityResult; } int res = ExecuteNonQuery(command); if (res < 0) return null; return res; } } /// /// Inserts an object into the database based on its type information. /// The properties must match the database structure and you can skip /// over fields in the propertiesToSkip list. /// /// /// Entity data to insert into table /// Name of the table to update /// Comma delimited list of fields to skip in the entity update /// /// /// Null if the insert failed /// Scope Identity (when returnIdentityKey is true or null if that fails) /// Otherwise affected records /// public async Task InsertEntityAsync(object entity, string table, string propertiesToSkip = null, bool returnIdentityKey = true) { SetError(); DbCommand command = GetInsertEntityCommand(entity, table, propertiesToSkip); using (command) { if (returnIdentityKey) { command.CommandText += ";\r\n" + GetIdentityKeySqlCommand; LastIdentityResult = await ExecuteScalarAsync(command); return LastIdentityResult; } int res = await ExecuteNonQueryAsync(command); if (res < 0) return null; return res; } } /// /// Gets the DbCommand used to insert an object into the database based on its type information. /// The properties must match the database structure and you can skip /// over fields in the propertiesToSkip list. /// /// /// Entity object to use for Insert /// Table name to insert to /// Comma delimited list of fields not to include in insert statement /// /// Scope Identity or Null (when returnIdentityKey is true /// Otherwise affected records /// public DbCommand GetInsertEntityCommand(object entity, string table, string propertiesToSkip = null) { SetError(); if (string.IsNullOrEmpty(propertiesToSkip)) propertiesToSkip = string.Empty; else propertiesToSkip = "," + propertiesToSkip.ToLower() + ","; DbCommand command = CreateCommand(string.Empty); if (command == null) { return null; } Type objType = entity.GetType(); StringBuilder fieldList = new StringBuilder(); StringBuilder dataList = new StringBuilder(); fieldList.Append("insert into " + table + " ("); dataList.Append(" values ("); PropertyInfo[] properties = objType.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo property in properties) { if (!property.CanRead || !property.CanWrite) continue; if (!property.PropertyType.IsValueType && property.PropertyType.Name != "String" && property.PropertyType.Name != "Byte[]") continue; string name = property.Name; if (propertiesToSkip.IndexOf("," + name.ToLower() + ",") > -1 ) continue; object value = property.GetValue(entity, null); fieldList.Append(" " + LeftFieldBracket + name + RightFieldBracket + ","); string parmString = ParameterPrefix; if (!UsePositionalParameters) parmString += name; dataList.Append(parmString + ","); if (value == null && property.PropertyType == typeof(byte[])) command.Parameters.Add(CreateParameter(ParameterPrefix + name, DBNull.Value, DataUtils.DotNetTypeToDbType(property.PropertyType))); else command.Parameters.Add(CreateParameter(ParameterPrefix + name, value ?? DBNull.Value)); } command.CommandText = fieldList.ToString().TrimEnd(',') + ") " + dataList.ToString().TrimEnd(',') + ")"; return command; } /// /// Saves an entity into the database using insert or update as required. /// Requires a key field that exists on both the entity and the database. /// /// entity to save /// table to save to /// keyfield to update /// optional fields to skip when updating (keys related items etc) /// /// optional - specify fields to update /// public virtual bool SaveEntity(object entity, string table, string keyField, string propertiesToSkip = null, bool returnScopeId = false) { object pkValue = ReflectionUtils.GetProperty(entity, keyField); object res = null; if (pkValue != null) res = ExecuteScalar("select " + LeftFieldBracket + keyField + RightFieldBracket + " from " + LeftFieldBracket + table + RightFieldBracket + " where " + LeftFieldBracket + keyField + RightFieldBracket + "=" + ParameterPrefix + "id", CreateParameter(ParameterPrefix + "id", pkValue)); if (res == null) { LastIdentityResult = InsertEntity(entity, table, propertiesToSkip, returnScopeId); if (!string.IsNullOrEmpty(ErrorMessage)) return false; } else { return UpdateEntity(entity, table, keyField, propertiesToSkip); } return true; } #endregion #region Error Handling /// /// Sets the error message for the failure operations /// /// protected virtual void SetError(string message, int errorNumber) { if (string.IsNullOrEmpty(message)) { ErrorMessage = string.Empty; ErrorNumber = 0; ErrorException = null; return; } ErrorMessage = message; ErrorNumber = errorNumber; } /// /// Sets the error message and error number. /// /// Message for the ErrorMessage /// Optional exception that sets LastError protected virtual void SetError(string message, Exception ex = null) { SetError(message,0); if (ex != null) ErrorException = ex; } protected virtual void SetError(DbException ex) { SetError(ex.Message, ex.ErrorCode); ErrorException = ex; if (ThrowExceptions) throw ex; } protected virtual void SetError(SqlException ex) { SetError(ex.Message, ex.Number); ErrorException = ex; if (ThrowExceptions) throw ex; } protected virtual void SetError(Exception ex) { if (ex is SqlException sqlEx) { SetError(sqlEx); return; } if (ex is DbException) { SetError(ex as DbException); return; } ErrorMessage = ex.Message; ErrorException = ex; ErrorNumber = 0; if (ThrowExceptions) throw ex; } /// /// Sets the error message for failure operations. /// protected virtual void SetError() { SetError(null,0); } #endregion #region IDisposable Members public void Dispose() { if (_Connection != null) CloseConnection(); } #endregion } } ================================================ FILE: Westwind.Utilities.Data/DataTableExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Collections; using System.Dynamic; namespace Westwind.Utilities.Data { /// /// Extends the DataTable to provide access to DynamicDataRow /// data. /// public static class DataTableDynamicExtensions { /// /// Returns a dynamic DataRow instance that can be accessed /// with the field name as a property /// /// /// taTab public static dynamic DynamicRow(this DataTable dt, int index) { var row = dt.Rows[index]; return new DynamicDataRow(row); } /// /// Returns a dynamic list of rows so you can reference them with /// row.fieldName /// /// /// public static DynamicDataRows DynamicRows(this DataTable dt) { DynamicDataRows drows = new DynamicDataRows(dt.Rows); return drows; } } /// /// Helper class that extends a DataRow collection to /// be exposed as individual objects /// public class DynamicDataRows : IEnumerator, IEnumerable { DataRowCollection Rows; IEnumerator RowsEnumerator; public DynamicDataRow this[int index] { get { return new DynamicDataRow(Rows[index]); } } DynamicDataRow IEnumerator.Current { get { return new DynamicDataRow(RowsEnumerator.Current as DataRow); } } public object Current { get { return new DynamicDataRow(RowsEnumerator.Current as DataRow); } } public DynamicDataRows(DataRowCollection rows) { Rows = rows; RowsEnumerator = rows.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { foreach (DataRow row in Rows) yield return new DynamicDataRow(row); } IEnumerator IEnumerable.GetEnumerator() { foreach (DataRow row in Rows) yield return new DynamicDataRow(row); } public void Dispose() { Rows = null; RowsEnumerator = null; } public bool MoveNext() { return RowsEnumerator.MoveNext(); } public void Reset() { RowsEnumerator.Reset(); } } } ================================================ FILE: Westwind.Utilities.Data/DynamicDataReader.cs ================================================ using System; using System.Dynamic; using System.Data; using System.Data.Common; namespace Westwind.Utilities.Data { /// /// This class provides an easy way to use object.property /// syntax with a DataReader by wrapping a DataReader into /// a dynamic object. /// /// The class also automatically fixes up DbNull values /// (null into .NET and DbNUll) /// public class DynamicDataReader : DynamicObject { /// /// Cached Instance of DataReader passed in /// IDataReader DataReader; /// /// Pass in a loaded DataReader /// /// DataReader instance to work off public DynamicDataReader(IDataReader dataReader) { DataReader = dataReader; } /// /// Returns a value from the current DataReader record /// If the field doesn't exist null is returned. /// DbNull values are turned into .NET nulls. /// /// /// /// public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; // 'Implement' common reader properties directly if (binder.Name == "IsClosed") result = DataReader.IsClosed; else if (binder.Name == "RecordsAffected") result = DataReader.RecordsAffected; // lookup column names as fields else { try { result = DataReader[binder.Name]; if (result == DBNull.Value) result = null; } catch { result = null; return false; } } return true; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { // Implement most commonly used method if (binder.Name == "Read") result = DataReader.Read(); else if (binder.Name == "Close") { DataReader.Close(); result = null; } else // call other DataReader methods using Reflection (slow - not recommended) // recommend you use full DataReader instance result = ReflectionUtils.CallMethod(DataReader, binder.Name, args); return true; } } } ================================================ FILE: Westwind.Utilities.Data/DynamicDataRow.cs ================================================ using System; using System.Dynamic; using System.Data; namespace Westwind.Utilities.Data { /// /// This class provides an easy way to turn a DataRow /// into a Dynamic object that supports direct property /// access to the DataRow fields. /// /// The class also automatically fixes up DbNull values /// (null into .NET and DbNUll to DataRow) /// public class DynamicDataRow : DynamicObject { /// /// Instance of object passed in /// DataRow DataRow; /// /// Pass in a DataRow to work off /// /// public DynamicDataRow(DataRow dataRow) { DataRow = dataRow; } /// /// Returns a value from a DataRow items array. /// If the field doesn't exist null is returned. /// DbNull values are turned into .NET nulls. /// /// /// /// /// public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; try { result = DataRow[binder.Name]; if (result == DBNull.Value) result = null; return true; } catch { } result = null; return false; } /// /// Property setter implementation tries to retrieve value from instance /// first then into this object /// /// /// /// public override bool TrySetMember(SetMemberBinder binder, object value) { try { if (value == null) value = DBNull.Value; DataRow[binder.Name] = value; return true; } catch {} return false; } } } ================================================ FILE: Westwind.Utilities.Data/LICENSE.MD ================================================ West Wind Utilities Data Support Library ======================================== MIT License ----------- Copyright (c) 2019-2023 West Wind Technologies Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: Westwind.Utilities.Data/Security/UserTokenManager.cs ================================================ using System; using Westwind.Utilities.Properties; namespace Westwind.Utilities.Data.Security { /// /// Generic SQL Token generator class that can be used to /// generate, store and validation user tokens for use in APIs. /// /// Methods can create tokens, store them and then can be validated /// based on existance and timeout status. Service cleans up expired /// tokens as part of operations. /// /// Works with SQL Server but can be customized /// public class UserTokenManager { /// /// Sql Connection String (Sql Server) /// public string TokenServerConnectionString { get; set; } public string Tablename { get; set; } = "UserTokens"; /// /// The duration that a token is valid for in seconds /// public int TokenTimeoutSeconds { get; set; } = 1800; /// /// If true won't check if a token already exists for a given /// user. If false only one token per user is allowed. /// public bool AllowMultipleTokensPerUser { get; set; } public string ErrorMessage { get; set; } public UserTokenManager(string connectionString = null) { TokenServerConnectionString = connectionString; } /// /// Check to see if a token is valid /// /// A string token id. Can also contain "Bearer xxxx" which strips the preamble /// If true updates the Updated property and moves the expiration window out /// public bool IsTokenValid(string tokenId, bool renewLease = true) { if (tokenId.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) tokenId = tokenId.Substring(7); var token = GetToken(tokenId, checkForExpiration: true, renewLease: renewLease); if (token == null) return false; return true; } /// /// Retrieves the full token based on the id /// /// /// if true doesn't retrieve token if it's expired /// If true updates the updated property so expiration window moves out /// public UserToken GetToken(string tokenId, bool checkForExpiration = true, bool renewLease = true) { UserToken token; using (var data = GetSqlData()) { string sql = $@"select Top 1 * from [{Tablename}] where Id = @0 Order by Updated Desc"; token = data.Find(sql, tokenId); if (token == null) { SetError(data.ErrorMessage); return null; } if (checkForExpiration && token.Updated.AddSeconds(TokenTimeoutSeconds) < DateTime.UtcNow) { SetError(Resources.UserTokenHasExpired); if (!string.IsNullOrEmpty(token.Id)) DeleteToken(token.Id); return null; } if (renewLease) { sql = $@"update [{Tablename}] set updated = GetUtcDate() where Id = @0"; data.ExecuteNonQuery(sql, tokenId); } } token.TokenIdentifier = null; return token; } /// /// Returns a token based on a Token Identifier. This is useful in non-Web /// auth scenarios where you can check for the token being validated based on /// a token identifier. /// /// Expired tokens are not returned and automatically deleted. /// /// By default the token identifier is removed after reading it and you can /// prevent by passing the ` /// /// /// If true doesn't remove the token identifier after reading it /// public UserToken GetTokenByTokenIdentifier(string tokenIdentifier, bool dontRemoveTokenIdentifier = false) { if (string.IsNullOrEmpty(tokenIdentifier) || tokenIdentifier.Length < 8) { SetError(Resources.MissingOrInvalidTokenIdentifier); return null; } UserToken token; using (var data = GetSqlData()) { string sql = $@"select Top 1 * from [{Tablename}] where TokenIdentifier = @0 Order by Updated Desc"; token = data.Find(sql, tokenIdentifier); if (token == null) { SetError(Resources.UserTokenNotFound); return null; } // expired token if (token.Updated.AddSeconds(TokenTimeoutSeconds) < DateTime.UtcNow) { SetError(Resources.UserTokenHasExpired); if (!string.IsNullOrEmpty(token.Id)) DeleteToken(token.Id); return null; } // clear out the token identifier if requested if (!dontRemoveTokenIdentifier && !string.IsNullOrEmpty(token.TokenIdentifier)) { sql = $@"update [{Tablename}] set TokenIdentifier = null where TokenIdentifier = @0"; data.ExecuteNonQuery(sql, tokenIdentifier); token.TokenIdentifier = null; } } return token; } /// /// Adds a new token record into the db and returns the new token id. /// /// Parameters map to the modifiable user token table fields, so you can provide initial values. /// /// A mapping user id that maps into a user/customer table of the application /// An optional reference id /// 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 /// Optional scope identifier /// Optional additional data that can be stored /// A new token Id public string CreateNewToken(string userId, string referenceId = null, string tokenIdentifier = null, string scope = null, string data = null) { var dt = DateTime.UtcNow; if (tokenIdentifier != null && tokenIdentifier.Length < 8 ) { SetError(Resources.InvalidUserTokenIdentifier); return null; } string tokenId; int result; using (var db = GetSqlData()) { tokenId = null; string sql; result = -1; var token = db.Find($"select Top 1 * from [{Tablename}] where userId=@0", userId); // error - table doesn't exist? if (!string.IsNullOrEmpty(db.ErrorMessage)) { if (!IsUserTokenTable() && CreateUserTokenSqlTable()) { return CreateNewToken(userId, referenceId); // retry } } if (AllowMultipleTokensPerUser || token == null) { tokenId = DataUtils.GenerateUniqueId(15); sql = $@" insert into [{Tablename}] (Id,UserId,ReferenceId,TokenIdentifier,scope, data,Updated,Created,IsValidated) Values (@0,@1,@2, @3,@4,@5,@6,@7,@8) "; result = db.ExecuteNonQuery(sql, tokenId, userId, referenceId, tokenIdentifier, scope, data, dt, dt, false ); } else { // replace an existing token for this user id tokenId = DataUtils.GenerateUniqueId(15); sql = $@"update [{Tablename}] set Id=@0, UserId=@1, ReferenceId=@2, TokenIdentifier=@3, Scope=@4, Data=@5, Updated=@6, IsValidated=@7 where Id=@8"; result = db.ExecuteNonQuery(sql, tokenId, userId, referenceId ?? token.ReferenceId, tokenIdentifier, scope, data, dt, token.IsValidated, token.Id); } if (result == -1) { SetError(Resources.CouldntCreateUserToken +": " + db.ErrorMessage); return null; } } return tokenId; } /// /// Deletes a token by its token id /// /// public bool DeleteToken(string userTokenId) { using (var data = GetSqlData()) { int result = data.ExecuteNonQuery($"delete from [{Tablename}] where id=@0", userTokenId); if (result == -1) { SetError(data.ErrorMessage); return false; } } return true; } /// /// Deletes a token by its token id /// /// The userid for which do delete tokens public bool DeleteTokenForUserId(string userId) { var data = GetSqlData(); int result = data.ExecuteNonQuery($"delete from [{Tablename}] where userId=@0", userId); if (result == -1) { SetError(data.ErrorMessage); return false; } return true; } /// /// Deletes a token by its token id /// public bool DeleteExpiredTokens() { var time = DateTime.UtcNow.AddSeconds((TokenTimeoutSeconds * 1.5) * -1); var data = GetSqlData(); int result = data.ExecuteNonQuery($"delete from [{Tablename}] where updated < @0", time); if (result == -1) { SetError(data.ErrorMessage); return false; } return true; } /// /// Creates the UserToken Sql Server table in the specified connection. /// Uses the current 'Tablename' property to determine the table /// /// Default implementation creates table for SQL Server /// /// public virtual bool CreateUserTokenSqlTable() { using (var data = GetSqlData()) { string sql = $@" Begin Transaction T1 CREATE TABLE [{Tablename}] ( Id nvarchar(20) not null Primary Key, UserId nvarchar(100), ReferenceId nvarchar(255), TokenIdentifier nvarchar(100) , Scope nvarchar(100), Data nvarchar(max), IsValidated bit, Created datetime not null, Updated datetime not null ) Commit Transaction T1 "; if (data.ExecuteNonQuery(sql) < 0) { SetError(data.ErrorMessage); return false; } } return true; } /// /// Check to see if hte user token table exists /// /// public bool IsUserTokenTable() { using (var data = GetSqlData()) { data.ThrowExceptions = false; object result = data.ExecuteScalar($"select count(*) from [{Tablename}]"); if (result == null) { SetError(data.ErrorMessage); return false; } } return true; } #region Helpers SqlDataAccess GetSqlData() { var data = new SqlDataAccess(TokenServerConnectionString) { ThrowExceptions = false }; return data; } #endregion #region Error Handling protected void SetError() { SetError("CLEAR"); } protected void SetError(string message) { if (message == null || message == "CLEAR") { ErrorMessage = string.Empty; return; } ErrorMessage += message; } protected void SetError(Exception ex, bool checkInner = false) { if (ex == null) ErrorMessage = string.Empty; Exception e = ex; if (checkInner) e = e.GetBaseException(); ErrorMessage = e.Message; } #endregion } /// /// User token entity that maps the user token db table data /// public class UserToken { public UserToken() { Id = DataUtils.GenerateUniqueId(15); Updated = DateTime.UtcNow; } /// /// This is the token's unique Id /// public string Id { get; set; } /// /// Time the token was last updated. Used to determine /// when the token expires /// public DateTime Updated { get; set; } /// /// A user id that maps this token to a given user /// id or other user identifier /// public string UserId { get; set; } /// /// An additional reference string value that can be set /// and stored with the key token. /// public string ReferenceId { get; set; } /// /// An Application specific identifier that can be passed in to act as a token /// identifier for an external application. Used in token validation /// and retrieving a token from a local/desktop app that allows for querying /// for the Token Identifier rather than the token. /// public string TokenIdentifier { get; set; } /// /// An Application specific scope or other identifier that allows you /// specify additional information about the token with the a token query. /// public string Scope { get; set; } /// /// Application specific extra data field /// public string Data { get; set; } /// /// Application specific flag that can be used to indicate /// that this user token have been validated. /// /// Note: Not explicitly set by the UserTokenManager. /// public bool IsValidated { get; set; } public override string ToString() => Id ?? "no id set"; } } ================================================ FILE: Westwind.Utilities.Data/SqlDataAccess.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion //#define SupportWebRequestProvider using System; using System.Data.Common; #if NETCORE using Microsoft.Data.SqlClient; #else using System.Data.SqlClient; #endif namespace Westwind.Utilities.Data { /// /// Sql Server specific implementation of the DataAccessBase class to /// provide an easy to use Data Access Layer (DAL) with single line /// operations for most data retrieval and non-query operations. /// public class SqlDataAccess : DataAccessBase { public SqlDataAccess() { dbProvider = SqlClientFactory.Instance; } public SqlDataAccess(string connectionString) : base(connectionString, SqlClientFactory.Instance) { } #if NETFULL public SqlDataAccess(string connectionString, string providerName) : base(connectionString, providerName) { } #endif public SqlDataAccess(string connectionString, DbProviderFactory provider) : base(connectionString, provider) { } public SqlDataAccess(string connectionString, DataAccessProviderTypes providerType) : base(connectionString,providerType) { } /// /// Sql 2005 and later specific semi-generic paging routine /// /// /// /// /// /// /// public override DbCommand CreatePagingCommand(string sql, int pageSize, int page, string sortOrderFields, params object[] Parameters) { int pos = sql.IndexOf("select ", 0, StringComparison.OrdinalIgnoreCase); if (pos == -1) { SetError("Invalid Command for paging. Must start with select and followed by field list"); return null; } sql = StringUtils.ReplaceStringInstance(sql, "select", string.Empty, 1, true); string NewSql = string.Format( @" select * FROM (SELECT ROW_NUMBER() OVER (ORDER BY @OrderByFields) as __No,{0}) __TQuery where __No > (@Page-1) * @PageSize and __No < (@Page * @PageSize + 1) ", sql); return CreateCommand(NewSql, CreateParameter("@PageSize", pageSize), CreateParameter("@Page", page), CreateParameter("@OrderByFields", sortOrderFields)); } } } ================================================ FILE: Westwind.Utilities.Data/SqlUtils.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.Common; using System.Linq; using System.Text; using System.Threading.Tasks; using Westwind.Utilities.Properties; #if NETCORE using Microsoft.Data.SqlClient; #else using System.Data.SqlClient; #endif namespace Westwind.Utilities.Data { public class SqlUtils { #region Provider Factories /// /// Loads a SQL Provider factory based on the DbFactory type name and assembly. /// /// Type name of the DbProviderFactory /// Short assembly name of the provider factory. Note: Host project needs to have a reference to this assembly /// public static DbProviderFactory GetDbProviderFactory(string dbProviderFactoryTypename, string assemblyName) { var instance = ReflectionUtils.GetStaticProperty(dbProviderFactoryTypename, "Instance"); if (instance == null) { var a = ReflectionUtils.LoadAssembly(assemblyName); if (a != null) instance = ReflectionUtils.GetStaticProperty(dbProviderFactoryTypename, "Instance"); } if (instance == null) throw new InvalidOperationException(string.Format(Resources.UnableToRetrieveDbProviderFactoryForm, dbProviderFactoryTypename)); return instance as DbProviderFactory; } /// /// This method loads various providers dynamically similar to the /// way that DbProviderFactories.GetFactory() works except that /// this API is not available on .NET Standard 2.0 /// /// /// public static DbProviderFactory GetDbProviderFactory(DataAccessProviderTypes type) { if (type == DataAccessProviderTypes.SqlServer) return SqlClientFactory.Instance; // this library has a ref to SqlClient so this works if (type == DataAccessProviderTypes.SqLite) return GetDbProviderFactory("System.Data.SQLite.SQLiteFactory", "System.Data.SQLite"); if (type == DataAccessProviderTypes.MySql) return GetDbProviderFactory("MySql.Data.MySqlClient.MySqlClientFactory", "MySql.Data"); if (type == DataAccessProviderTypes.PostgreSql) return GetDbProviderFactory("Npgsql.NpgsqlFactory", "Npgsql"); #if NETFULL if (type == DataAccessProviderTypes.OleDb) return System.Data.OleDb.OleDbFactory.Instance; if (type == DataAccessProviderTypes.SqlServerCompact) return DbProviderFactories.GetFactory("System.Data.SqlServerCe.4.0"); #endif throw new NotSupportedException(string.Format(Resources.UnsupportedProviderFactory, type.ToString())); } /// /// Returns a provider factory using the old Provider Model names from full framework .NET. /// Simply calls DbProviderFactories. /// /// /// public static DbProviderFactory GetDbProviderFactory(string providerName) { #if NETFULL return DbProviderFactories.GetFactory(providerName); #else var lowerProvider = providerName.ToLower(); if (lowerProvider == "system.data.sqlclient") return GetDbProviderFactory(DataAccessProviderTypes.SqlServer); if (lowerProvider == "system.data.sqlite" || lowerProvider == "microsoft.data.sqlite") return GetDbProviderFactory(DataAccessProviderTypes.SqLite); if (lowerProvider == "mysql.data.mysqlclient" || lowerProvider == "mysql.data") return GetDbProviderFactory(DataAccessProviderTypes.MySql); if (lowerProvider == "npgsql") return GetDbProviderFactory(DataAccessProviderTypes.PostgreSql); throw new NotSupportedException(string.Format(Resources.UnsupportedProviderFactory, providerName)); #endif } #endregion #region Minimal Sql Data Access Function /// /// Creates a Command object and opens a connection /// /// /// /// public static SqlCommand GetSqlCommand(string ConnectionString, string Sql, params SqlParameter[] Parameters) { SqlCommand Command = new SqlCommand(); Command.CommandText = Sql; try { #if NETFULL if (!ConnectionString.Contains(';')) ConnectionString = ConfigurationManager.ConnectionStrings[ConnectionString].ConnectionString; #endif Command.Connection = new SqlConnection(ConnectionString); Command.Connection.Open(); } catch { return null; } if (Parameters != null) { foreach (SqlParameter Parm in Parameters) { Command.Parameters.Add(Parm); } } return Command; } /// /// Returns a SqlDataReader object from a SQL string. /// /// Please ensure you close the Reader object /// /// /// /// /// public static SqlDataReader GetSqlDataReader(string ConnectionString, string Sql, params SqlParameter[] Parameters) { SqlCommand Command = GetSqlCommand(ConnectionString, Sql, Parameters); if (Command == null) return null; SqlDataReader Reader = null; try { Reader = Command.ExecuteReader(); } catch { CloseConnection(Command); return null; } return Reader; } /// /// Returns a DataTable from a Sql Command string passed in. /// /// /// /// /// /// public static DataTable GetDataTable(string Tablename, string ConnectionString, string Sql, params SqlParameter[] Parameters) { SqlCommand Command = GetSqlCommand(ConnectionString, Sql, Parameters); if (Command == null) return null; SqlDataAdapter Adapter = new SqlDataAdapter(Command); DataTable dt = new DataTable(Tablename); try { Adapter.Fill(dt); } catch { return null; } finally { CloseConnection(Command); } return dt; } /// /// Closes a connection /// /// public static void CloseConnection(SqlCommand Command) { if (Command.Connection != null && Command.Connection.State == ConnectionState.Open) Command.Connection.Close(); } #endregion } } ================================================ FILE: Westwind.Utilities.Data/Westwind.Utilities.Data.csproj ================================================  net10.0;net8.0;net472 5.2.8 Rick Strahl false en-US Westwind.Utilities.Data Westwind.Utilities.Data en-US Westwind.Utilities.Data Westwind.Utilities.Data West Wind Utilities Data .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. Data access support library for Westwind.Utilities that provides a small lightweight data access providerand and a host of data utilities. Data access support library for Westwind.Utilities. Rick Strahl, West Wind Technologies 2007-2026 Westwind DataAccess DataUtils DAL Sql ADO.NET http://github.com/rickstrahl/westwind.utilities icon.png LICENSE.MD true Rick Strahl, West Wind Technologies, 2010-2026 Github West Wind Technologies https://github.com/RickStrahl/Westwind.Utilities git TRACE;DEBUG; embedded $(NoWarn);CS1591;CS1572;CS1573 true True ./nupkg true RELEASE NETCORE;NETSTANDARD;NETSTANDARD2_0 NETFULL embedded true ================================================ FILE: Westwind.Utilities.Data/publish-nuget.ps1 ================================================ if (test-path ./nupkg) { remove-item ./nupkg -Force -Recurse } dotnet build -c Release $filename = Get-ChildItem "./nupkg/*.nupkg" | sort LastWriteTime | select -last 1 | select -ExpandProperty "Name" Write-host $filename $len = $filename.length if ($len -gt 0) { Write-Host "signing... $filename" #nuget sign ".\nupkg\$filename" -CertificateSubject "West Wind Technologies" -timestamper " http://timestamp.digicert.com" nuget push ".\nupkg\$filename" -source "https://nuget.org" Write-Host "Done." } ================================================ FILE: Westwind.Utilities.Test/App.config ================================================ 
================================================ FILE: Westwind.Utilities.Test/AppConfiguration/AutoConfigFileConfigurationTests.cs ================================================ #if NETFULL using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using System.Configuration; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests default config file implementation that uses /// only base constructor behavior - (config file and section config only) /// [TestClass] public class AutoConfigFileConfigurationTests { private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } [TestMethod] public void DefaultConstructorInstanceTest() { var config = new AutoConfigFileConfiguration(); // gets .config file, AutoConfigFileConfiguration section config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); Assert.AreEqual(config.MaxDisplayListItems, 15); string outputfile = TestHelpers.GetTestConfigFilePath(); string text = File.ReadAllText(outputfile); Console.WriteLine(text); } [TestMethod] public void DefaultConstructorWithCustomProviderTest() { var config = new AutoConfigFileConfiguration(); // Create a customized provider to set provider options var provider = new ConfigurationFileConfigurationProvider() { ConfigurationSection = "CustomConfiguration", EncryptionKey = "seekrit123", PropertiesToEncrypt = "MailServer,MailServerPassword" }; config.Initialize(provider); // Config File and custom section should have been created in config file string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Assert.IsFalse(string.IsNullOrEmpty(text)); Assert.IsTrue(text.Contains("")); // MailServer/MailServerPassword value should be encrypted Console.WriteLine(text); } [TestMethod] public void DefaultConstructorWithAppSettingsProviderTest() { var config = new AutoConfigFileConfiguration(); // Create a customized provider to set provider options var provider = new ConfigurationFileConfigurationProvider() { ConfigurationSection = null, // forces to AppSettings EncryptionKey = "seekrit123", PropertiesToEncrypt = "MailServer,MailServerPassword" }; config.Initialize(provider); // Config File and custom section should have been created in config file string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Assert.IsFalse(string.IsNullOrEmpty(text)); Assert.IsTrue(text.Contains("")); // MailServer/MailServerPassword value should be encrypted Console.WriteLine(text); config.ApplicationName = "Updated Configuration"; config.Write(); config = null; config = new AutoConfigFileConfiguration(); config.Initialize(provider); config.Initialize(); // should reload, reread Console.WriteLine("Application Name: " + config.ApplicationName); Assert.IsTrue(config.ApplicationName == "Updated Configuration"); } [TestMethod] public void AutoConfigWriteConfigurationTest() { var config = new AutoConfigFileConfiguration(); config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); Assert.AreEqual(config.MaxDisplayListItems, 15); config.MaxDisplayListItems = 17; config.Write(); var config2 = new AutoConfigFileConfiguration(); config2.Initialize(); Assert.AreEqual(config2.MaxDisplayListItems, 17); // reset to default val config2.MaxDisplayListItems = 15; config2.Write(); } [TestMethod] public void WriteConfigurationTest() { var config = new AutoConfigFileConfiguration(); config.Initialize(); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // update complex type config.License.Company = "Updated Company"; config.License.Name = "New User"; config.License.LicenseKey = "UpdatedCompanyNewUser-5331231"; config.Write(); string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Console.WriteLine(text); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); var config2 = new AutoConfigFileConfiguration(); config2.Initialize(); Assert.AreEqual(config2.MaxDisplayListItems, 12); Assert.AreEqual(config2.ApplicationName, "Changed"); Assert.AreEqual("Updated Company",config2.License.Company); // reset to default val config2.MaxDisplayListItems = 15; config2.Write(); } /// /// Test without explicit constructor parameter /// [TestMethod] public void DefaultConstructor2InstanceTest() { var config = new AutoConfigFile2Configuration(); // Not required since custom constructor calls this //config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); Assert.AreEqual(config.MaxDisplayListItems, 15); string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Console.WriteLine(text); } /// /// Write test without explicit constructor /// [TestMethod] public void WriteConfiguration2Test() { var config = new AutoConfigFile2Configuration(); // not necesary since constructor calls internally //config.Initialize(); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; config.Write(); string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Console.WriteLine(text); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // reset to default val config.MaxDisplayListItems = 15; config.Write(); } [TestMethod] public void NoConstructorWriteConfiguration2Test2() { var config = new NoConstructorConfiguration(); Assert.IsNotNull(config, "Configuration object is null"); config = NoConstructorConfiguration.New(); Assert.IsNotNull(config, "Static New(): Configuration object is null"); } public class NoConstructorConfiguration : AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public static NoConstructorConfiguration New() { var config = new NoConstructorConfiguration(); config.Initialize(); return config; } } } } #endif ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/AutoConfigFileConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Westwind.Utilities.Configuration.Tests { /// /// Default implementation that uses only base constructors /// for configuration. /// /// Default setup allows for no configuration of the provider /// since we're just calling back to the base constructors /// /// Note: for config files ONLY you can implement the default /// constructor automatically since no serialization is used. /// When using XML, String, Database the default constructor /// needs to be left at default to avoid recursive loading /// public class AutoConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string MailServer { get; set; } public string MailServerPassword { get; set; } /// /// Type that has ToString() and FromString() methods /// to allow serialization /// public LicenseInformation License { get; set; } public AutoConfigFileConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; MailServer = "mail.MyWickedServer.com:334"; MailServerPassword = "seekrity"; License = new LicenseInformation() { Name = "Rick", Company = "West Wind", LicenseKey = "WestWind-5333121" }; } } /// /// This version of the class internally calls Initialize /// to read configuration information immediately from /// itself so no explicit call to Initialize is required /// public class AutoConfigFile2Configuration : AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public AutoConfigFile2Configuration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; // Automatically initialize this one this.Initialize(); } } public class NoConstructorConfiguration : AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public static NoConstructorConfiguration New() { var config = new NoConstructorConfiguration(); config.Initialize(); return config; } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/CustomConfigFileConfiguration.cs ================================================ #if NETFULL using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace Westwind.Utilities.Configuration.Tests { /// /// Custom Configuration Provider implementation that allows /// uses a different section and encrypts a couple of properties /// public class CustomConfigFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration { public string ConfigFile {get; set; } public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string Password { get; set; } public string AppConnectionString { get; set; } public LicenseInformation License { get; set; } [XmlIgnore] public string IgnoredProperty {get; set;} public List ServerList { get; set; } public CustomConfigFileConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; Password = "seekrit"; AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;"; License = new LicenseInformation() { Company = "West Wind", Name = "Rick", LicenseKey = "westwindrick-51123" }; ServerList = new List() { "DevServer", "Maximus", "Tempest" }; } /// /// Override to provide a custom default provider (created when Initialize() is /// called with no parameters). /// /// /// /// protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) { var provider = new ConfigurationFileConfigurationProvider() { ConfigurationFile = ConfigFile, ConfigurationSection = sectionName, EncryptionKey = "ultra-seekrit", // use a generated value here PropertiesToEncrypt = "Password,AppConnectionString,License.LicenseKey" }; return provider; } } } #endif ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/DatabaseConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Westwind.Utilities.Test; namespace Westwind.Utilities.Configuration.Tests { /// /// Custom Configuration Provider implementation that allows /// uses a different section and encrypts a couple of properties /// public class DatabaseConfiguration : Westwind.Utilities.Configuration.AppConfiguration { // Configuration store values public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string Password { get; set; } public string AppConnectionString { get; set; } // Must implement public default constructor public DatabaseConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; Password = "seekrit"; AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;"; } ///// ///// Override this method to create the custom default provider - in this case a database ///// provider with a few options. ///// protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) { string connectionString = TestConfigurationSettings.WestwindToolkitConnectionString; string tableName = "ConfigurationData"; // ConfigData: new { ConnectionString = "...", Tablename = "..." } if (configData != null) { dynamic data = configData; connectionString = data.ConnectionString; tableName = data.Tablename; } var provider = new SqlServerConfigurationProvider() { Key = 0, ConnectionString = connectionString, Tablename = tableName, //ProviderName = "System.Data.SqlServerCe.4.0", EncryptionKey = "ultra-seekrit", // use a generated value here PropertiesToEncrypt = "Password,AppConnectionString" }; return provider; } /// /// Optional - Create a custom overload with required parameters /// public void Initialize(string connectionString, string tableName = null) { // pass in anonymous object with parameters we're interested in // the OnCreateDefaultProvider reads the anonymous object values // and uses them for the SQL access object base.Initialize(configData: new { ConnectionString = connectionString, Tablename = tableName }); } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/JsonFileConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; namespace Westwind.Utilities.Configuration.Tests { /// /// Custom Configuration Provider implementation that allows /// uses a different section and encrypts a couple of properties /// public class JsonFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string Password { get; set; } public string AppConnectionString { get; set; } public LicenseInformation License { get; set; } // Must implement public default constructor public JsonFileConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; Password = "seekrit"; AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;"; License = new LicenseInformation { Name = "Rick", Company = "West Wind", LicenseKey = "RickWestWind-533112" }; } // Automatically initialize with default config and config file public void Initialize(string configFile) { base.Initialize(configData: configFile); } protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) { string jsonFile = "JsonConfiguration.txt"; if (configData != null) jsonFile = configData as string; var provider = new JsonFileConfigurationProvider() { JsonConfigurationFile = jsonFile, EncryptionKey = "ultra-seekrit", // use a generated value here PropertiesToEncrypt = "Password,AppConnectionString,License.LicenseKey" }; return provider; } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/LicenseInformation.cs ================================================ namespace Westwind.Utilities.Configuration.Tests { /// /// Demonstration class for a complex type /// public class LicenseInformation { public string Name { get; set; } public string Company { get; set; } public string LicenseKey { get; set; } public static LicenseInformation FromString(string data) { return StringSerializer.Deserialize(data,","); } public override string ToString() { return StringSerializer.SerializeObject(this, ","); } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/StringConfigFileConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Westwind.Utilities.Configuration.Tests { /// /// Custom Configuration Provider implementation that allows /// uses a different section and encrypts a couple of properties /// public class StringConfiguration : Westwind.Utilities.Configuration.AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string Password { get; set; } public string AppConnectionString { get; set; } // Must implement public default constructor public StringConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; Password = "seekrit"; AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;"; } protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) { var provider = new StringConfigurationProvider() { InitialStringData = configData as String, EncryptionKey = "ultra-seekrit", // use a generated value here PropertiesToEncrypt = "Password,AppConnectionString", }; return provider; } /// /// Optional - easier overload for xml string loading /// /// public void Initialize(string xml) { base.Initialize(configData: xml); } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/ConfigurationClasses/XmlFileConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; namespace Westwind.Utilities.Configuration.Tests { /// /// Custom Configuration Provider implementation that allows /// uses a different section and encrypts a couple of properties /// public class XmlFileConfiguration : Westwind.Utilities.Configuration.AppConfiguration { public string ApplicationName { get; set; } public DebugModes DebugMode { get; set; } public int MaxDisplayListItems { get; set; } public bool SendAdminEmailConfirmations { get; set; } public string Password { get; set; } public string AppConnectionString { get; set; } public LicenseInformation License {get; set; } // Must implement public default constructor public XmlFileConfiguration() { ApplicationName = "Configuration Tests"; DebugMode = DebugModes.Default; MaxDisplayListItems = 15; SendAdminEmailConfirmations = false; Password = "seekrit"; License = new LicenseInformation() { Company = "West Wind", Name = "Rick", LicenseKey = "westwindrick-4123122" }; AppConnectionString = "server=.;database=hosers;uid=bozo;pwd=seekrit;"; } // Automatically initialize with default config and config file public void Initialize(string configFile) { base.Initialize(configData: configFile); } protected override IConfigurationProvider OnCreateDefaultProvider(string sectionName, object configData) { string xmlFile = "XmlConfiguration.xml"; if (configData != null) xmlFile = configData as string; var provider = new XmlFileConfigurationProvider() { XmlConfigurationFile = xmlFile, EncryptionKey = "ultra-seekrit", // use a generated value here PropertiesToEncrypt = "Password,AppConnectionString,License.LicenseKey" // UseBinarySerialization = true }; return provider; } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/CustomConfigFileConfigurationTests.cs ================================================ #if NETFULL using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests default config file implementation that uses /// only base constructor behavior - (config file and section config only) /// [TestClass] public class CustomConfigurationTests { private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } [TestMethod] public void DefaultConstructorInstanceTest() { var configFile = Path.Combine(Path.GetTempPath(),"testconfig.config"); var config = new CustomConfigFileConfiguration(); config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Assert.IsTrue(text.Contains(@"")); Console.WriteLine(text); } [TestMethod] public void CustomFileLocationInstanceTest() { var configFile = Path.Combine(Path.GetTempPath(),"testconfig.config"); var config = new CustomConfigFileConfiguration(); config.ConfigFile = configFile; // custom property to pass in the path config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); string text = File.ReadAllText(configFile); // TestHelpers.GetTestConfigFilePath()); Assert.IsTrue(text.Contains(@"")); Console.WriteLine(text); File.Delete(configFile); } [TestMethod] public void WriteConfigurationTest() { File.Delete(TestHelpers.GetTestConfigFilePath()); var config = new CustomConfigFileConfiguration(); config.Initialize(); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; // Complex Types config.License.Company = "Updated Company"; config.ServerList[0] = "UpdatedServerName"; config.License.Name = "Rick"; config.License.Company = "West Wind 2"; config.License.LicenseKey = "RickWestWind2-51231223"; config.Write(); config = null; config = new CustomConfigFileConfiguration(); config.Initialize(); Console.WriteLine(config.License.LicenseKey); Assert.IsTrue(config.License.LicenseKey == "RickWestWind2-51231223"); string text = File.ReadAllText(TestHelpers.GetTestConfigFilePath()); Console.WriteLine(text); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // Password and AppSettings should be encrypted in config file Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // Complex Value Assert.IsTrue(text.Contains(@"West Wind 2")); // List values Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // Password and AppSettings should be encrypted in config file Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // Complex Value Assert.IsTrue(text.Contains(@"Updated Company")); // List values Assert.IsTrue(text.Contains(@"")); Assert.IsTrue(text.Contains(@"")); // now re-read settings into a new object var config2 = new CustomConfigFileConfiguration(); config2.Initialize(); // check secure properties Assert.IsTrue(config.Password == "seekrit2"); Assert.IsTrue(config.AppConnectionString == "server=.;database=unsecured"); } } } #endif ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/DatabaseConfigurationTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using Westwind.Data.Test; using Westwind.Utilities.Test; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests default config file implementation that uses /// only base constructor behavior - (config file and section config only) /// [TestClass] public class DatabaseConfigurationTests { private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } //[ClassInitialize()] //public static void TestClassInitialize(TestContext testContext) //{ // DatabaseInitializer.InitializeDatabase(); //} /// /// Note: For Web Apps this should be a complete path. /// Here the filename references the current directory /// [TestMethod] public void DefaultConstructorInstanceTest() { // this should create the database table and add default // data into it if it doesn't exist - otherwise // the values are read var config = new DatabaseConfiguration(); config.Initialize(); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); } [TestMethod] public void WriteConfigurationTest() { var config = new DatabaseConfiguration(); // connection string and table are provided in OnInitialize() config.Initialize(); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // encrypted properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=HRDatabase"; Assert.IsTrue(config.Write(), "Write Failed: " + config.ErrorMessage); // create a new instance and read the values from the database var config2 = new DatabaseConfiguration(); config2.Initialize(); Assert.IsNotNull(config2); Assert.AreEqual(config.MaxDisplayListItems, config2.MaxDisplayListItems); Assert.AreEqual(config.DebugMode, config2.DebugMode); // Encrypted values Assert.AreEqual(config.Password, config2.Password); Assert.AreEqual(config.AppConnectionString, config2.AppConnectionString); // reset to default val config.MaxDisplayListItems = 15; config.Write(); } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/JsonFileConfigurationTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests default config file implementation that uses /// only base constructor behavior - (config file and section config only) /// [TestClass] public class JsonFileConfigurationTests { /// /// Note: For Web Apps this should be a complete path. /// Here the filename references the current directory /// public string STR_JSONCONFIGFILE = "JsonConfiguration.txt"; public JsonFileConfigurationTests() { // force Json ref to load since we dynamically load var json = new JsonSerializerSettings(); // explicitly write file location since tests are in indeterminate folder // especially running .NET Core STR_JSONCONFIGFILE = Path.Combine(Path.GetTempPath(), STR_JSONCONFIGFILE); } [TestMethod] public void DefaultConstructorInstanceTest() { var config = new JsonFileConfiguration(); config.Initialize(configData: STR_JSONCONFIGFILE); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); string text = File.ReadAllText(STR_JSONCONFIGFILE); Console.WriteLine(text); } [TestMethod] public void WriteConfigurationTest() { var config = new JsonFileConfiguration(); config.Initialize(STR_JSONCONFIGFILE); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; config.Write(); string jsonConfig = File.ReadAllText(STR_JSONCONFIGFILE); Console.WriteLine(jsonConfig); Assert.IsTrue(jsonConfig.Contains(@"""DebugMode"": ""DeveloperErrorMessage""")); Assert.IsTrue(jsonConfig.Contains(@"""MaxDisplayListItems"": 12") ); Assert.IsTrue(jsonConfig.Contains(@"""SendAdminEmailConfirmations"": true")); // Password and AppSettings should be encrypted in config file Assert.IsTrue(!jsonConfig.Contains($"\"Password\": \"seekrit\"")); } [TestMethod] public void WriteEncryptedConfigurationTest() { File.Delete(STR_JSONCONFIGFILE); var config = new JsonFileConfiguration(); config.Initialize(STR_JSONCONFIGFILE); // write secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; config.License.Company = "West Wind 2"; config.License.LicenseKey = "RickWestWind2-51123222"; config.Write(); // completely reload settings config = new JsonFileConfiguration(); config.Initialize(STR_JSONCONFIGFILE); Assert.IsTrue(config.Password == "seekrit2"); Assert.IsTrue(config.License.Company == "West Wind 2"); Assert.IsTrue(config.License.LicenseKey == "RickWestWind2-51123222"); string jsonConfig = File.ReadAllText(STR_JSONCONFIGFILE); Console.WriteLine(jsonConfig); // Password and AppSettings should be encrypted in config file Assert.IsTrue(!jsonConfig.Contains("\"Password\": \"seekrit2\"")); // now re-read settings into a new object var config2 = new JsonFileConfiguration(); config2.Initialize(STR_JSONCONFIGFILE); // check secure properties Assert.IsTrue(config.Password == "seekrit2"); Assert.IsTrue(config.AppConnectionString == "server=.;database=unsecured"); } [TestMethod] public void RawJsonFileWithSingletonTest() { var config = MyJsonConfiguration.Current; Assert.IsNotNull(config); Console.WriteLine(JsonSerializationUtils.Serialize(config, false, true)); } } public class MyJsonConfiguration : AppConfiguration { public static MyJsonConfiguration Current { get; set; } static MyJsonConfiguration() { var config = new MyJsonConfiguration(); // assign a provider explicitly config.Provider = new JsonFileConfigurationProvider() { JsonConfigurationFile = "./SupportFiles/_MyJsonConfiguration.json" }; // load and read the configuration //config.Initialize(); config.Read(); Current = config; } public string MyString { get; set; } = "Default String"; public int SomeValue { get; set; } = -1; } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/StringConfigurationTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests implementation of the string provider. /// /// String providers make it easy to read and write /// configuration values and store them in any source /// that can store strings. For example you can store /// the xml generated in a database field of your choice. /// (there is a database field provider that does that /// however). /// /// Use App.Configuration.Read(xmlString) /// and App.Configuration.WriteAsString() to read and /// write the XML configuration data for storage in /// any non-existing format you like. /// /// Light weight alternative to creating your own /// configuration provider (although it's easy to /// create one). /// [TestClass] public class StringConfigurationTests { private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } /// /// Write out configuration settings to string /// [TestMethod] public void WriteConfigurationTest() { var config = new StringConfiguration(); config.Initialize(null); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; string xmlConfig = config.WriteAsString(); Console.WriteLine(xmlConfig); Assert.IsTrue(xmlConfig.Contains(@"DeveloperErrorMessage")); Assert.IsTrue(xmlConfig.Contains(@"12")); Assert.IsTrue(xmlConfig.Contains(@"true")); // Password and AppSettings should be encrypted in config file Assert.IsTrue(!xmlConfig.Contains(@"seekrit2")); Assert.IsTrue(!xmlConfig.Contains(@"server=.;database=unsecured")); } /// /// Unlike other providers the string provider has /// no 'automatic' read mode - it requires a string /// as input to work. /// [TestMethod] public void ReadConfigurationFromStringTest() { string xmlConfig = @" Configuration Tests Default 12 false ADoCNO6L1HIm8V7TyI4deg== z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE= "; var config = new StringConfiguration(); // Initialize with configData as parameter to load from config.Initialize(configData: xmlConfig); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); Assert.IsTrue(config.MaxDisplayListItems == 12); //Assert.AreEqual(config.Password, "seekrit2"); } [TestMethod] public void WriteEncryptedConfigurationTest() { var config = new StringConfiguration(); config.Initialize(); // write secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; var xml = config.WriteAsString(); Console.WriteLine(xml); // Password and AppSettings should be encrypted in config file Assert.IsTrue(!xml.Contains(@"seekrit2")); Assert.IsTrue(!xml.Contains(@"server=.;database=unsecured")); // now re-read settings into a new object var config2 = new StringConfiguration(); // pass XML to deserialize from - OnInitialize() implements this logic //config2.Initialize(xml); // same as below config2.Initialize(configData: xml); // check secure properties Assert.IsTrue(config.Password == "seekrit2"); Assert.IsTrue(config.AppConnectionString == "server=.;database=unsecured"); } } } ================================================ FILE: Westwind.Utilities.Test/AppConfiguration/XmlFileConfigurationTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; namespace Westwind.Utilities.Configuration.Tests { /// /// Tests default config file implementation that uses /// only base constructor behavior - (config file and section config only) /// [TestClass] public class XmlConfigurationTests { private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } /// /// Note: For Web Apps this should be a complete path. /// Here the filename references the current directory /// public const string STR_XMLCONFIGFILE = "XmlConfiguration.xml"; [TestMethod] public void DefaultConstructorInstanceTest() { var config = new XmlFileConfiguration(); config.Initialize(configData: STR_XMLCONFIGFILE); Assert.IsNotNull(config); Assert.IsFalse(string.IsNullOrEmpty(config.ApplicationName)); string text = File.ReadAllText(STR_XMLCONFIGFILE); Console.WriteLine(text); } [TestMethod] public void WriteConfigurationTest() { var config = new XmlFileConfiguration(); config.Initialize(STR_XMLCONFIGFILE); config.MaxDisplayListItems = 12; config.DebugMode = DebugModes.DeveloperErrorMessage; config.ApplicationName = "Changed"; config.SendAdminEmailConfirmations = true; // secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; config.License.LicenseKey = "asdss"; config.Write(); string xmlConfig = File.ReadAllText(STR_XMLCONFIGFILE); Console.WriteLine(xmlConfig); Assert.IsTrue(xmlConfig.Contains(@"DeveloperErrorMessage")); Assert.IsTrue(xmlConfig.Contains(@"12")); Assert.IsTrue(xmlConfig.Contains(@"true")); #if NETFULL // Password and AppSettings should be encrypted in config file Assert.IsTrue(xmlConfig.Contains(@"ADoCNO6L1HIm8V7TyI4deg==")); Assert.IsTrue(xmlConfig.Contains(@"z6+T5mzXbtJBEgWqpQNYbBss0csbtw2b/qdge7PUixE=")); #else // Password and AppSettings should be encrypted in config file Assert.IsTrue(xmlConfig.Contains(@"Odou0/8LMaGT52eE0DDA2g==")); Assert.IsTrue(xmlConfig.Contains(@"tLde067qv6r54CVKxse7MtgZXnWDdgSH0CBUWL9CHwc=")); #endif } [TestMethod] public void WriteEncryptedConfigurationTest() { var config = new XmlFileConfiguration(); config.Initialize(STR_XMLCONFIGFILE); // write secure properties config.Password = "seekrit2"; config.AppConnectionString = "server=.;database=unsecured"; config.Write(); string xmlConfig = File.ReadAllText(STR_XMLCONFIGFILE); Console.WriteLine(xmlConfig); // Password and AppSettings should be encrypted in config file Assert.IsTrue(!xmlConfig.Contains(@"seekrit2")); Assert.IsTrue(!xmlConfig.Contains(@"server=.;database=unsecured")); // now re-read settings into a new object var config2 = new XmlFileConfiguration(); config2.Initialize(STR_XMLCONFIGFILE); // check secure properties Assert.IsTrue(config.Password == "seekrit2"); Assert.IsTrue(config.AppConnectionString == "server=.;database=unsecured"); } } } ================================================ FILE: Westwind.Utilities.Test/AsyncUtilsTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Westwind.Utilities.Test { [TestClass] public class AsyncUtilsTests { [TestMethod] public async Task TimeoutAfter2SecondsTest() { var taskToComplete = Task.Run(async () => { await Task.Delay(7000); }); bool isComplete = await taskToComplete.Timeout(2000); // should time out after 2 seconds Assert.IsFalse(isComplete, "Task did not complete in time"); } [TestMethod] public async Task SucceedTimeoutAfter2SecondsTest() { var taskToComplete = DoSomething(1000); bool isComplete = false; try { isComplete = await taskToComplete.Timeout(2000); } catch (Exception ex) { Console.WriteLine(ex.Message); } // should time out after 2 seconds Assert.IsTrue(isComplete, "Task did not complete in time"); // Retrieve task result int result = taskToComplete.Result; Assert.AreEqual(result, 100); } [TestMethod] public async Task ExceptionTimeoutAfter2SecondsTest() { var taskToComplete = DoThrow(1000); bool isComplete = false; try { // Shouldn't timeout, but throw an exception isComplete = await taskToComplete.Timeout(2000); } catch (Exception ex) { // regular exception handling Console.WriteLine(ex.Message); // message here } Assert.IsTrue(taskToComplete.IsFaulted, "Task threw an exception: " + taskToComplete.Exception.GetBaseException().Message); // Or we can pull the exception off the task (aggregate exception so we need base Exception) Console.WriteLine(taskToComplete.Exception.GetBaseException().Message); } [TestMethod] public async Task TimeoutWithResultAfter2SecondsTest() { var taskToComplete = DoSomething(10_000); int result = 0; try { // returns int - 100 on success and 0 (default) on failure result = await taskToComplete.TimeoutWithResult(1000); } catch(TimeoutException) { Assert.IsTrue(true); return; } catch (Exception) { Assert.Fail("This task should have timed out and there should be no execution exception"); } // fails: should return 0. timed out after 2 seconds Assert.Fail("This task should have timed out"); } [TestMethod] public async Task NoTimeoutWithResultAfter2SecondsTest() { int result = 0; try { // no timeout no exception result = await DoSomething(1500).TimeoutWithResult(2000); } catch(TimeoutException ex) { Assert.Fail("Task should not have timed out: " + ex.Message); } catch(Exception ex) { Assert.Fail("Task should not have thrown an exception: " + ex.Message); } // No timeout since task completes in 1.5 seconds Assert.IsTrue(result==100, "Task did not return the correct result"); } [TestMethod] public async Task TaskExceptionWithResultAfter2SecondsTest() { int result = 0; try { // no timeout no exception result = await DoThrow(1500).TimeoutWithResult(2000); } catch (TimeoutException ex) { Assert.Fail("Task should not have timed out: " + ex.Message); } catch (Exception) { Assert.IsTrue(true); return; } // No timeout since task completes in 1.5 seconds Assert.Fail("Task should have failed with an exception."); } #if NETCORE [TestMethod] public async Task SucceedTimeoutAfter2SecondsWithTaskWaitTest() { int result = 0; try { result = await DoSomething(1000).WaitAsync(TimeSpan.FromSeconds(2)); } // Capture Timeout catch (TimeoutException) { Assert.Fail("This task method should not have timed out."); } // Fires on Exception in taskToComplete catch (Exception) { Assert.Fail("This task method should not fail with exceptions."); } Assert.AreEqual(100, result); } #endif private async Task DoSomething(int timeout) { await Task.Delay(timeout); return 100; } private async Task DoThrow(int timeout) { await Task.Delay(timeout); throw new Exception("Exception thrown in task method DoThrow"); } } } ================================================ FILE: Westwind.Utilities.Test/DataUtilsTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data; using System.Diagnostics; using System.Data.Common; using Westwind.Data.Test.Models; using Westwind.Utilities.Test; namespace Westwind.Utilities.Data.Tests { /// /// Summary description for DataUtilsTests /// [TestClass] public class DataUtilsTests { //private const string STR_TestDataConnection = "WestwindToolkitSamples"; private static string STR_TestDataConnection = TestConfigurationSettings.WestwindToolkitConnectionString; [TestMethod] public void DataTableToListTest() { var sql = new SqlDataAccess(STR_TestDataConnection); var dt = sql.ExecuteTable("items", "select * from ApplicationLog"); Assert.IsNotNull(dt, "Failed to load test data: " + sql.ErrorMessage); var items = DataUtils.DataTableToObjectList(dt); Assert.IsNotNull(items); Assert.IsTrue(items.Count > 0); Console.WriteLine(items.Count); } #region Byte Operations [TestMethod] public void IndexOfByteArrayTest() { var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80, 0x0a, 0x31, 0x33, 0x20}; var bytesToFind = new byte[] {0xe2, 0x80}; Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1); } [TestMethod] public void IndexOfByteArrayEndTest() { var bytes = new byte[] {0x31, 0x33, 0x20, 0x0a, 0x31, 0x33, 0x20, 0xe2, 0x80}; var bytesToFind = new byte[] {0xe2, 0x80}; Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1); } [TestMethod] public void IndexOfByteArrayBeginTest() { var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20, 0x0a, 0x31, 0x33, 0x20}; var bytesToFind = new byte[] {0xe2, 0x80}; Assert.IsTrue(DataUtils.IndexOfByteArray(bytes, bytesToFind) > -1); } [TestMethod] public void ReplaceBytesTest() { var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80, 0x0a, 0x31, 0x33, 0x20}; var bytesToRemove = new byte[] {0xe2, 0x80}; byte[] results = DataUtils.RemoveBytes(bytes, bytesToRemove ); Console.WriteLine("-- Replace Middle --"); Console.WriteLine("Before: " + StringUtils.BinaryToBinHex(bytes)); Console.WriteLine("After : " + StringUtils.BinaryToBinHex(results)); Assert.IsTrue(results.Length == bytes.Length - 2); Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1); } [TestMethod] public void ReplaceBytesEndTest() { Console.WriteLine("-- Replace End --"); var bytes = new byte[] {0x31, 0x33, 0x20, 0xe2, 0x80 }; var bytesToRemove = new byte[] {0xe2, 0x80}; var results = DataUtils.RemoveBytes(bytes, bytesToRemove ); Console.WriteLine("Before: " + StringUtils.BinaryToBinHex(bytes)); Console.WriteLine("After : " + StringUtils.BinaryToBinHex(results)); Assert.IsTrue(results.Length == bytes.Length - 2); Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1); } [TestMethod] public void ReplaceBytesBeginningTest() { Console.WriteLine("-- Replace Beginning --"); var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20 }; var bytesToRemove = new byte[] {0xe2, 0x80}; var results = DataUtils.RemoveBytes(bytes, bytesToRemove ); Console.WriteLine("Before: " + StringUtils.BinaryToBinHex(bytes)); Console.WriteLine("After : " + StringUtils.BinaryToBinHex(results)); Assert.IsTrue(results.Length == bytes.Length - 2); Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1); } [TestMethod] public void ReplaceBytesBeginningAndEndTest() { Console.WriteLine("-- Replace Beginning and End --"); var bytes = new byte[] {0xe2, 0x80, 0x31, 0x33, 0x20, 0xe2, 0x80 }; var bytesToRemove = new byte[] {0xe2, 0x80}; var results = DataUtils.RemoveBytes(bytes, bytesToRemove ); Console.WriteLine("Before: " + StringUtils.BinaryToBinHex(bytes)); Console.WriteLine("After : " + StringUtils.BinaryToBinHex(results)); Assert.IsTrue(results.Length == bytes.Length - 4); Assert.IsTrue(DataUtils.IndexOfByteArray(results, bytesToRemove) == -1); } #endregion #if false // Add Npgsql package [TestMethod] public void GetPostGreSqlProviderTest() { var provider = DataUtils.GetDbProviderFactory(DataAccessProviderTypes.PostgreSql); Assert.IsNotNull(provider); } #endif #if false // Add MySql.Data Package [TestMethod] public void GetMySqlProviderTest() { var provider = DataUtils.GetDbProviderFactory(DataAccessProviderTypes.MySql); Assert.IsNotNull(provider); } #endif } } ================================================ FILE: Westwind.Utilities.Test/DynamicDataReaderTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data; using System.Data.Common; using Westwind.Utilities; using Microsoft.CSharp.RuntimeBinder; using Westwind.Utilities.Data; using System.Diagnostics; using System.Threading; using Westwind.Utilities.Test; namespace Westwind.Utilities.Data.Tests { /// /// Summary description for DynamicDataRowTests /// [TestClass] public class DynamicDataReaderTests { private static string STR_ConnectionString = TestConfigurationSettings.WestwindToolkitConnectionString; private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class [ClassInitialize()] public static void MyClassInitialize(TestContext testContext) { // warm up dynamic runtime dynamic test = new List(); var val = test.Count; //DatabaseInitializer.InitializeDatabase(); // warm up data connection using (SqlDataAccess data = new SqlDataAccess(STR_ConnectionString)) { var readr = data.ExecuteReader("select top 1 * from Customers"); var x = readr.Read(); } } [TestMethod] public void BasicDataReaderTimerTests() { DbDataReader reader; using (var data = new SqlDataAccess(STR_ConnectionString)) { reader = data.ExecuteReader("select * from Customers"); Assert.IsNotNull(reader, "Query Failure: " + data.ErrorMessage); StringBuilder sb = new StringBuilder(); Stopwatch watch = new Stopwatch(); watch.Start(); while (reader.Read()) { string firstName = reader["FirstName"] as string; string lastName = reader["LastName"] as string; string company = reader["Company"] as string; DateTime? entered = reader["Entered"] as DateTime?; string d = entered.HasValue ? entered.Value.ToString("d") : string.Empty; sb.AppendLine(firstName + " " + lastName + " " + company + " - " + entered.Value.ToString("d")); } watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds.ToString()); Console.WriteLine(sb.ToString()); } } [TestMethod] public void BasicDynamicDataReaderTimerTest() { dynamic reader; using (var data = new SqlDataAccess(STR_ConnectionString)) { reader = data.ExecuteDynamicDataReader("select * from customers"); Assert.IsNotNull(reader, "Query Failure: " + data.ErrorMessage); StringBuilder sb = new StringBuilder(); Stopwatch watch = new Stopwatch(); watch.Start(); while (reader.Read()) { string firstName = reader.FirstName; string lastName = reader.LastName; string company = reader.Company; DateTime? entered = reader.Entered as DateTime?; string d = entered.HasValue ? entered.Value.ToString("d") : string.Empty; sb.AppendLine(firstName + " " + lastName + " " + company + " - " + entered.Value.ToString("d")); } watch.Stop(); reader.Close(); Console.WriteLine(watch.ElapsedMilliseconds.ToString()); Console.WriteLine(sb.ToString()); } } } } ================================================ FILE: Westwind.Utilities.Test/DynamicDataRowTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data; using Westwind.Utilities; using Microsoft.CSharp.RuntimeBinder; using Westwind.Utilities.Data; namespace Westwind.Utilities.Data.Tests { /// /// Summary description for DynamicDataRowTests /// [TestClass] public class DynamicDataRowTests { public DynamicDataRowTests() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] [ExpectedException(typeof(RuntimeBinderException))] public void BasicDataRowTests() { DataTable table = new DataTable("table"); table.Columns.Add( new DataColumn() { ColumnName = "Name", DataType = typeof(string) }); table.Columns.Add( new DataColumn() { ColumnName = "Entered", DataType = typeof(DateTime) }); table.Columns.Add(new DataColumn() { ColumnName = "NullValue", DataType = typeof(string) }); DataRow row = table.NewRow(); DateTime now = DateTime.Now; row["Name"] = "Rick"; row["Entered"] = now; row["NullValue"] = null; // converted in DbNull dynamic drow = new DynamicDataRow(row); string name = drow.Name; DateTime entered = drow.Entered; string nulled = drow.NullValue; Assert.AreEqual(name, "Rick"); Assert.AreEqual(entered, now); Assert.IsNull(nulled); // this should throw a RuntimeBinderException Assert.AreEqual(entered, drow.enteredd); } [TestMethod] public void DynamicDataTableTest() { DataTable table = new DataTable("table"); table.Columns.Add(new DataColumn() { ColumnName = "Name", DataType = typeof(string) }); table.Columns.Add(new DataColumn() { ColumnName = "Entered", DataType = typeof(DateTime) }); table.Columns.Add(new DataColumn() { ColumnName = "NullValue", DataType = typeof(string) }); DataRow row = table.NewRow(); DateTime now = DateTime.Now; row["Name"] = "Rick"; row["Entered"] = now; row["NullValue"] = null; // converted in DbNull table.Rows.Add(row); row = table.NewRow(); row["Name"] = "Don"; row["Entered"] = now.AddDays(1); row["NullValue"] = "Not Null"; // converted in DbNull table.Rows.Add(row); foreach (dynamic drow in table.DynamicRows()) { Console.WriteLine(drow.Name + " " + drow.Entered.ToString("d") + " " + drow.NullValue); } dynamic drow2 = table.DynamicRows()[1]; Console.WriteLine(drow2.Name + " " + drow2.Entered.ToString("d") + " " + drow2.NullValue); } } } ================================================ FILE: Westwind.Utilities.Test/EncryptionTests.cs ================================================ using System; using System.Linq; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Security.Cryptography; namespace Westwind.Utilities.Test { [TestClass] public class EncryptionTests { /// /// 16 byte (or anything less than 24 bytes) are no longer supported /// by .NET Core Triple DES. Encrypt/Decrypt pad out or trim /// the buffer to fit 24 bytes, so the following should work. /// [TestMethod] public void SimpleEncryptDecryptWith16ByteKey() { string data = "Seekrit!Password"; byte[] key = new byte[16] {3, 166, 3, 5, 222, 13, 155, 55, 122, 123, 165, 187, 188, 1,11, 133}; string encrypted = Encryption.EncryptString(data, key); string decrypted = Encryption.DecryptString(encrypted, key); Assert.AreEqual(data, decrypted); } [TestMethod] public void SimpleEncryptWith24ByteKey() { string data = "Seekrit!Password"; byte[] 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 }; string encrypted = Encryption.EncryptString(data, key); string decrypted = Encryption.DecryptString(encrypted, key); Assert.AreEqual(data, decrypted); } [TestMethod] public void EncryptDecryptString() { string data = "Seekrit!Password"; string key = "my+keeper"; string encrypted = Encryption.EncryptString(data,key); string decrypted = Encryption.DecryptString(encrypted,key); Assert.AreNotEqual(data, encrypted); Assert.AreEqual(data, decrypted); Console.WriteLine(encrypted); } [TestMethod] public void EncryptDecryptWithExtendedCharacterString() { string data = "Seekrit°!Password"; string key = "my+keeper"; string encrypted = Encryption.EncryptString(data, key); string decrypted = Encryption.DecryptString(encrypted, key); Assert.AreNotEqual(data, encrypted); Assert.AreEqual(data, decrypted); Console.WriteLine(encrypted); } [TestMethod] public void EncryptDecryptWithExtendedCharacterStringByteKey() { string data = "Seekrit°!Password"; byte[] key = new byte[] {10, 20, 88, 223, 132, 1, 55, 32}; string encrypted = Encryption.EncryptString(data, key); string decrypted = Encryption.DecryptString(encrypted, key); Assert.AreNotEqual(data, encrypted); Assert.AreEqual(data, decrypted); Console.WriteLine(encrypted); } [TestMethod] public void EncryptDecryptWithExtendedCharacterByteData() { byte[] data = new byte[] {1, 3, 22, 224, 113, 53, 31, 6, 12, 44, 49, 66}; byte[] key = new byte[] { 2, 3, 4, 5, 6}; byte[] encrypted = Encryption.EncryptBytes(data, key); byte[] decrypted = Encryption.DecryptBytes(encrypted, key); Assert.IsTrue(decrypted.SequenceEqual(data)); Console.WriteLine(encrypted); } [TestMethod] public void HashValues() { string data = "Seekrit!Password"; byte[] salt = new byte[] { 10, 22, 144, 51, 55, 61}; string algo = "SHA1"; string encrypted = Encryption.ComputeHash(data, algo, salt,useBinHex: true); Console.WriteLine(encrypted); data = "test"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "t"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "testa"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "testa"; var encrypted2 = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); } [TestMethod] public void HashValuesHMAC() { string data = "Seekrit!Password"; byte[] salt = new byte[] { 10, 22, 144, 51, 55, 61 }; string saltString = "bogus"; string algo = "SHA256"; string encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "test"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "testa"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "t"; encrypted = Encryption.ComputeHash(data, algo, salt, useBinHex: true); Console.WriteLine(encrypted); var binData = new byte[] { 10, 20, 21, 44, 55, 233, 122, 193 }; encrypted = Encryption.ComputeHash(binData, algo, salt, useBinHex: true); Console.WriteLine(encrypted); data = "test"; encrypted = Encryption.ComputeHash(data, algo, saltString, useBinHex: true); Console.WriteLine(encrypted); data = "test"; var encrypted2 = Encryption.ComputeHash(data, algo, saltString, useBinHex: false); Console.WriteLine(encrypted); byte[] decryptBytes1 = Encryption.BinHexToBinary(encrypted); byte[] decryptBytes2 = Convert.FromBase64String(encrypted2); Assert.IsTrue( decryptBytes1.SequenceEqual(decryptBytes2)); } [TestMethod] public void BinHexBase64CompareTest() { string algo = "HMACSHA256"; string saltString = "bogus"; string data = "test"; string encrypted = Encryption.ComputeHash(data, algo, saltString, useBinHex: true); Console.WriteLine(encrypted); string encrypted2 = Encryption.ComputeHash(data, algo, saltString, useBinHex: false); Console.WriteLine(encrypted2); byte[] decryptBytes1 = Encryption.BinHexToBinary(encrypted); byte[] decryptBytes2 = Convert.FromBase64String(encrypted2); Assert.IsTrue(decryptBytes1.SequenceEqual(decryptBytes2)); } } } ================================================ FILE: Westwind.Utilities.Test/ExpandUrlsParserTest.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; namespace Westwind.Utilities.Tests { /// /// Summary description for UnitTest1 /// [TestClass] public class ExpandUrlsParserTest { public ExpandUrlsParserTest() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] public void ParseExpandedLinks() { 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."; string parsed = UrlParser.ExpandUrls(text, null, true); TestContext.WriteLine("{0}", parsed); Assert.IsTrue(parsed.Contains("embedded link"), "link parsing failed."); Assert.IsTrue(parsed.Contains("www.west-wind.com"), "link parsing failed."); } [TestMethod] public void ParseOddExpandedLinks() { 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."; string parsed = UrlParser.ExpandUrls(text, null, true); TestContext.WriteLine("{0}", parsed); Assert.IsTrue(parsed.Contains("embedded link"), "link parsing failed."); Assert.IsTrue(parsed.Contains("http://skodia.name.com"), "link parsing failed."); } } } ================================================ FILE: Westwind.Utilities.Test/ExpandoTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; using System.Dynamic; using Microsoft.CSharp.RuntimeBinder; using Newtonsoft.Json; namespace ExpandoTests { /// /// Summary description for ExpandoTests /// [TestClass] public class ExpandoTests { public ExpandoTests() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion /// /// Summary method that demonstrates some /// of the basic behaviors. /// /// More specific tests are provided below /// [TestMethod] public void ExandoBasicTests() { // Set standard properties var ex = new User() { Name = "Rick", Email = "rstrahl@whatsa.com", Active = true }; // set dynamic properties that don't exist on type dynamic exd = ex; exd.Entered = DateTime.Now; exd.Company = "West Wind"; exd.Accesses = 10; // set dynamic properties as dictionary ex["Address"] = "32 Kaiea"; ex["Email"] = "rick@west-wind.com"; ex["TotalOrderAmounts"] = 51233.99M; // iterate over all properties dynamic and native foreach (var prop in ex.GetProperties(true)) { Console.WriteLine(prop.Key + " " + prop.Value); } // you can access plain properties both as explicit or dynamic Assert.AreEqual(ex.Name, exd.Name, "Name doesn't match"); // You can access dynamic properties either as dynamic or via IDictionary Assert.AreEqual(exd.Company, ex["Company"] as string, "Company doesn't match"); Assert.AreEqual(exd.Address, ex["Address"] as string, "Name doesn't match"); // You can access strong type properties via the collection as well (inefficient though) Assert.AreEqual(ex.Name, ex["Name"] as string); // dynamic can access everything Assert.AreEqual(ex.Name, exd.Name); // native property Assert.AreEqual(ex["TotalOrderAmounts"], exd.TotalOrderAmounts); // dictionary property } [TestMethod] public void AddAndReadDynamicPropertiesTest() { // strong typing first var ex = new User(); ex.Name = "Rick"; ex.Email = "rick@whatsa.com"; // create dynamic and create new props dynamic exd = ex; string company = "West Wind"; int count = 10; exd.entered = DateTime.Now; exd.Company = company; exd.Accesses = count; Assert.AreEqual(exd.Company, company); Assert.AreEqual(exd.Accesses, count); } [TestMethod] public void AddAndReadDynamicIndexersTest() { var ex = new ExpandoInstance(); ex.Name = "Rick"; ex.Entered = DateTime.Now; string address = "32 Kaiea"; ex["Address"] = address; ex["Contacted"] = true; dynamic exd = ex; Assert.AreEqual(exd.Address, ex["Address"]); Assert.AreEqual(exd.Contacted, true); } [TestMethod] public void PropertyAsIndexerTest() { // Set standard properties var ex = new ExpandoInstance(); ex.Name = "Rick"; ex.Entered = DateTime.Now; Assert.AreEqual(ex.Name, ex["Name"]); Assert.AreEqual(ex.Entered, ex["Entered"]); } [TestMethod] public void DynamicAccessToPropertyTest() { // Set standard properties var ex = new ExpandoInstance(); ex.Name = "Rick"; ex.Entered = DateTime.Now; // turn into dynamic dynamic exd = ex; // Dynamic can access Assert.AreEqual(ex.Name, exd.Name); Assert.AreEqual(ex.Entered, exd.Entered); } [TestMethod] public void IterateOverDynamicPropertiesTest() { var ex = new ExpandoInstance(); ex.Name = "Rick"; ex.Entered = DateTime.Now; dynamic exd = ex; exd.Company = "West Wind"; exd.Accesses = 10; // Dictionary pseudo implementation ex["Count"] = 10; ex["Type"] = "NEWAPP"; // Dictionary Count - 2 dynamic props added Assert.IsTrue(ex.Properties.Count == 4); // iterate over all properties foreach (KeyValuePair prop in exd.GetProperties(true)) { Console.WriteLine(prop.Key + " " + prop.Value); } } [TestMethod] public void MixInObjectInstanceTest() { // Create expando an mix-in second objectInstanceTest var ex = new ExpandoInstance(new Address()); ex.Name = "Rick"; ex.Entered = DateTime.Now; // create dynamic dynamic exd = ex; // values should show Addresses initialized values (not null) Console.WriteLine(exd.FullAddress); Console.WriteLine(exd.Email); Console.WriteLine(exd.Phone); } [TestMethod] public void TwoWayJsonSerializeExpandoTyped() { // Set standard properties var ex = new User() { Name = "Rick", Email = "rstrahl@whatsa.com", Password = "Seekrit23", Active = true }; // set dynamic properties dynamic exd = ex; exd.Entered = DateTime.Now; exd.Company = "West Wind"; exd.Accesses = 10; // set dynamic properties as dictionary ex["Address"] = "32 Kaiea"; ex["Email"] = "rick@west-wind.com"; ex["TotalOrderAmounts"] = 51233.99M; // *** Should serialize both static properties dynamic properties var json = JsonConvert.SerializeObject(ex, Formatting.Indented); Console.WriteLine("*** Serialized Native object:"); Console.WriteLine(json); Assert.IsTrue(json.Contains("Name")); // static Assert.IsTrue(json.Contains("Company")); // dynamic // *** Now deserialize the JSON back into object to // *** check for two-way serialization var user2 = JsonConvert.DeserializeObject(json); json = JsonConvert.SerializeObject(user2, Formatting.Indented); Console.WriteLine("*** De-Serialized User object:"); Console.WriteLine(json); Assert.IsTrue(json.Contains("Name")); // static Assert.IsTrue(json.Contains("Company")); // dynamic } #if SupportXmlSerialization [TestMethod] public void TwoWayXmlSerializeExpandoTyped() { // Set standard properties var ex = new User(); ex.Name = "Rick"; ex.Active = true; // set dynamic properties dynamic exd = ex; exd.Entered = DateTime.Now; exd.Company = "West Wind"; exd.Accesses = 10; // set dynamic properties as dictionary ex["Address"] = "32 Kaiea"; ex["Email"] = "rick@west-wind.com"; ex["TotalOrderAmounts"] = 51233.99M; // Serialize creates both static and dynamic properties // dynamic properties are serialized as a 'collection' string xml; SerializationUtils.SerializeObject(exd, out xml); Console.WriteLine("*** Serialized Dynamic object:"); Console.WriteLine(xml); Assert.IsTrue(xml.Contains("Name")); // static Assert.IsTrue(xml.Contains("Company")); // dynamic // Serialize var user2 = SerializationUtils.DeSerializeObject(xml,typeof(User)); SerializationUtils.SerializeObject(exd, out xml); Console.WriteLine(xml); Assert.IsTrue(xml.Contains("Rick")); // static Assert.IsTrue(xml.Contains("West Wind")); // dynamic } #endif [TestMethod] public void ExpandoObjectJsonTest() { dynamic ex = new ExpandoObject(); ex.Name = "Rick"; ex.Entered = DateTime.Now; string address = "32 Kaiea"; ex.Address = address; ex.Contacted = true; ex.Count = 10; ex.Completed = DateTime.Now.AddHours(2); string json = JsonConvert.SerializeObject(ex,Formatting.Indented); Console.WriteLine(json); } [TestMethod] public void UserExampleTest() { var user = new User(); // Set strongly typed properties user.Email = "rick@west-wind.com"; user.Password = "nonya123"; user.Name = "Rickochet"; user.Active = true; // Now add dynamic properties dynamic duser = user; duser.Entered = DateTime.Now; duser.Accesses = 1; // you can also add dynamic props via indexer user["NickName"] = "Wreck"; duser["WebSite"] = "http://www.west-wind.com/weblog"; // Access strong type through dynamic ref Assert.AreEqual(user.Name, duser.Name); // Access strong type through indexer Assert.AreEqual(user.Password, user["Password"]); // access dyanmically added value through indexer Assert.AreEqual(duser.Entered, user["Entered"]); // access index added value through dynamic Assert.AreEqual(user["NickName"], duser.NickName); // loop through all properties dynamic AND strong type properties (true) foreach (var prop in user.GetProperties(true)) { object val = prop.Value; if (val == null) val = "null"; Console.WriteLine(prop.Key + ": " + val.ToString()); } } [TestMethod] public void ExpandoMixinTest() { // have Expando work on Addresses var user = new User(new Address()); // cast to dynamicAccessToPropertyTest dynamic duser = user; // Set strongly typed properties duser.Email = "rick@west-wind.com"; user.Password = "nonya123"; // Set properties on address object duser.Address = "32 Kaiea"; //duser.Phone = "808-123-2131"; // set dynamic properties duser.NonExistantProperty = "This works too"; // shows default value Address.Phone value Console.WriteLine(duser.Phone); } public class ObjWithProp : Expando { public string SomeProp { get; set; } } [TestMethod] public void GivenPropWhenSetWithIndexThenPropsValue() { //arrange ObjWithProp obj = new ObjWithProp(); //act obj.SomeProp = "value1"; obj["SomeProp"] = "value2"; //assert Assert.AreEqual("value2", obj.SomeProp); } [TestMethod] [ExpectedException(typeof(RuntimeBinderException))] public void InvalidAssignmentErrorOnStaticProperty() { dynamic dynUser = new User(); dynUser.Name = 100; // RuntimeBinderException // this should never run var user = dynUser as User; user.Name = "Rick"; Console.WriteLine(user.Name); Console.WriteLine(user["Name"]); Assert.Fail("Invalid Assignment should have thrown exception"); //>> 100 } [TestMethod] public void ExpandoFromDictionary() { var dict = new Dictionary() { {"Name", "Rick"}, {"Company", "West Wind"}, {"Accesses", 2} }; dynamic expando = new Expando(dict); Console.WriteLine(expando.Name); Console.WriteLine(expando.Company); Console.WriteLine(expando.Accesses); Assert.AreEqual(dict["Name"], expando.Name); Assert.AreEqual(dict["Company"], expando.Company); Assert.AreEqual(dict["Accesses"], expando.Accesses); } [TestMethod] public void ExpandoFromList() { var props = new Dictionary(); props.Add("Name", "Rick"); props.Add("Contains", false); props.Add("Remove", false); props.Add("InList", false); props.Add("Count", 1); var expando = new Expando(props); dynamic obj = expando; object val = expando["Contains"]; Console.WriteLine(val); } } public class ExpandoInstance : Expando { public string Name { get; set; } public DateTime Entered { get; set; } public ExpandoInstance() { } /// /// Allow passing in of an instance /// /// public ExpandoInstance(object instance) : base(instance) { } } public class Address { public string FullAddress { get; set; } public string Email { get; set; } public string Phone { get; set; } public Address() { FullAddress = "32 Kaiea"; Phone = "808 132-3456"; Email = "rick@whatsa.com"; } } public class User : Expando { public string Email { get; set; } public string Password { get; set; } public string Name { get; set; } public bool Active { get; set; } public DateTime? ExpiresOn { get; set; } public User() : base() { } // only required if you want to mix in seperate instance public User(object instance) : base(instance) { } } } ================================================ FILE: Westwind.Utilities.Test/FileUtilsTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using System.Linq; namespace Westwind.Utilities.Test { [TestClass] public class FileUtilsTest { [TestMethod] public void SafeFilenameTest() { string file = "This: iS this file really invalid?"; string result = FileUtils.SafeFilename(file); Assert.AreEqual(result, "This iS this file really invalid"); Console.WriteLine(result); } [TestMethod] public void NormalizePathTest() { var path = @"c:\temp\test/work/play.txt"; string normal = FileUtils.NormalizePath(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"\temp\test/work/play.txt"; normal = FileUtils.NormalizePath(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"temp\test/work/play.txt"; normal = FileUtils.NormalizePath(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 3); } [TestMethod] public void NormalizeDirectoryTest() { var path = @"c:\temp\test\work"; string normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"c:\temp\test\work\"; normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"\temp\test\work\"; normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"\temp\test/work/"; normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); path = @"\temp\test/work/play.txt"; normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 5); path = @"temp\test/work/bogus"; normal = FileUtils.NormalizeDirectory(path); Console.WriteLine(normal); Assert.IsTrue(normal.ToArray().Count(c => c == Path.DirectorySeparatorChar) == 4); } [TestMethod] public void CompactPathTest() { var path = @"c:\temp\test\node_modules\SomeVeryLongComponentNameSpaceAndName\SomeLongComponentName.md"; Console.WriteLine("Orig: " + path); var result = FileUtils.GetCompactPath(path); Console.WriteLine($"{result.Length} {result}"); Assert.IsTrue(result.Length == 70); path = @"c:\temp\SomeLongComponentName.md"; Console.WriteLine("Orig: " + path); result = FileUtils.GetCompactPath(path); Console.WriteLine($"{result.Length} {result}"); Assert.IsTrue(result.Length < 70); path = @"\\temp\test\node_modules\SomeVeryLongComponentNameSpaceAndName\SomeLongComponentName.md"; Console.WriteLine("Orig: " + path); result = FileUtils.GetCompactPath(path); Console.WriteLine($"{result.Length} {result}"); Assert.IsTrue(result.Length == 70); } [TestMethod] public void TildefyPathTest() { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Projects\\Markdown Monster"); var result = FileUtils.TildefyUserPath(path); Assert.IsTrue(result.StartsWith("~")); Assert.IsTrue(result.Contains("Projects\\Markdown Monster")); Console.WriteLine(result); path = null; result = FileUtils.TildefyUserPath(path); Assert.IsTrue(result == null); path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); result = FileUtils.TildefyUserPath(path); Assert.IsTrue(result == "~"); path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\"; result = FileUtils.TildefyUserPath(path); Assert.IsTrue(result == "~\\"); } [TestMethod] public void CopyDirectory() { string target = Path.Combine(Path.GetTempPath(), "_TestFolders2"); string source = target.TrimEnd('2'); try { Directory.Delete(target, true); } catch { } try { Directory.Delete(source, true); } catch { } Directory.CreateDirectory(source); Directory.CreateDirectory(Path.Combine(source, "SubFolder1")); Directory.CreateDirectory(Path.Combine(source, "SubFolder2")); File.WriteAllText(Path.Combine(source, "test.txt"), "Hello cruel world"); File.WriteAllText(Path.Combine(source, "SubFolder1", "test.txt"), "Hello cruel world"); File.WriteAllText(Path.Combine(source, "SubFolder2", "test.txt"), "Hello cruel world"); FileUtils.CopyDirectory(source, target); Assert.IsTrue(Directory.Exists(target)); Assert.IsTrue(File.Exists(Path.Combine(source, "test.txt"))); Assert.IsTrue(File.Exists(Path.Combine(source, "SubFolder1", "test.txt"))); Assert.IsTrue(File.Exists(Path.Combine(source, "SubFolder2", "test.txt"))); Directory.Delete(target, true); Directory.Delete(source, true); } [TestMethod] public void ExpandPathEnvironmentVariablesTest() { string path = "%appdata%\\Markdown Monster"; string result = FileUtils.ExpandPathEnvironmentVariables(path); Console.WriteLine(result); Assert.IsFalse(result.Contains("%appdata%")); Assert.IsTrue(result.ToLower().Contains("appdata")); } [TestMethod] public void ExpandPathEnvironmentVariablesUserFolderTest() { string path = "~\\Projects"; string result = FileUtils.ExpandPathEnvironmentVariables(path); Console.WriteLine(result); Assert.AreNotEqual(path, result); Assert.IsFalse(result.Contains("~\\")); } [TestMethod] public void DeleteFilesTest() { string source = Path.Combine(Path.GetTempPath(), "_TestFolders"); try { Directory.Delete(source, true); } catch { } Directory.CreateDirectory(source); Directory.CreateDirectory(Path.Combine(source, "SubFolder1")); Directory.CreateDirectory(Path.Combine(source, "SubFolder2")); File.WriteAllText(Path.Combine(source, "test.txt"), "Hello cruel world"); File.WriteAllText(Path.Combine(source, "SubFolder1", "test.txt"), "Hello cruel world"); File.WriteAllText(Path.Combine(source, "SubFolder2", "test.txt"), "Hello cruel world"); Console.WriteLine(source); int fails = FileUtils.DeleteFiles(source, "test.txt", recursive: true); Directory.Delete(source, true); Assert.IsTrue(fails == 0, "Failed to delete all files"); } //[TestMethod] //public void ShortPathTest() //{ // var path = FileUtils.ExpandPathEnvironmentVariables(@"%appdata%\Markdown Monster\MarkdownMonster.json"); // var shortPath = FileUtils.GetShortPath(path); // Assert.IsFalse(path == shortPath); // Console.WriteLine(shortPath); // //path = path.Replace(".json", ".json-bogus"); // //shortPath = FileUtils.GetShortPath(path); // //Assert.IsFalse(path == shortPath); // //Console.WriteLine(shortPath); //} [TestMethod] public void FindFileHierarchicalUp() { var basePath = Path.GetFullPath("./SupportFiles/SquareImage.jpg"); var matchedFile = FileUtils.FindFileInHierarchy(basePath, "Westwind.Utilities.dll", FileUtils.FindFileInHierarchyDirection.Up); Assert.IsNotNull(matchedFile); Console.WriteLine(matchedFile); } [TestMethod] public void FindFileHierarchicalDown() { var basePath = Path.GetFullPath("./SupportFiles"); var matchedFile = FileUtils.FindFileInHierarchy(basePath, "SquareImage.jpg", FileUtils.FindFileInHierarchyDirection.Down); Assert.IsNotNull(matchedFile); Console.WriteLine(matchedFile); } [TestMethod] public void FindFilesHierarchal() { var basePath = Path.GetFullPath("./SupportFiles"); var matches = FileUtils.FindFilesInHierarchy(basePath, "*.jpg", FileUtils.FindFileInHierarchyDirection.Down); Assert.IsNotNull(matches); Assert.IsTrue(matches.Length > 0); foreach (var file in matches) { Console.WriteLine(file); } } [TestMethod] public void GetRelativePathTest() { string basePath = @"C:\Users\johndoe\Documents"; string fullPath = @"c:\Users\Johndoe\Documents\Visual Studio #$%*!(# 2022\Code Snippets\Visual C#\My Code Snippets\clw.snippet"; var relative = FileUtils.GetRelativePath(fullPath, basePath); Assert.AreNotEqual(relative, fullPath); Assert.IsFalse(relative.StartsWith("\\")); Console.WriteLine(relative); } /// /// Checks to see if a path is a relative path based on /// how it is formatted. /// Note this method DOES NOT check against any other path /// it merely checks to see if it's a path that is relative /// based on how the path starts. /// [TestMethod] public void IsRelativePathTest() { string path = "test.txt"; Assert.IsTrue(FileUtils.IsRelativePath(path), "Single path should be relative"); path = "..\\test.txt"; Assert.IsTrue(FileUtils.IsRelativePath(path), "..\\ path should be relative"); path = "\\test.txt"; Assert.IsFalse(FileUtils.IsRelativePath(path), "\\ path should not be relative"); path = "d:\\temp\\test.txt"; Assert.IsFalse(FileUtils.IsRelativePath(path), "Absolute path should not be relative"); path = "\\\\RASMSI\\temp\\test.txt"; Assert.IsFalse(FileUtils.IsRelativePath(path), "Network path should not be relative"); path = "file:///d:/temp/test.txt"; Assert.IsFalse(FileUtils.IsRelativePath(path), "file:// path should not be relative"); } [TestMethod] public void ResolvePathTest() { var basePath = @"c:\temp\subfolder\test.txt"; string path = "..\\test.txt"; var result = FileUtils.ResolvePath(basePath, path); Assert.AreEqual(result, @"c:\temp\test.txt"); path = "test.txt"; result = FileUtils.ResolvePath(basePath, path); Assert.AreEqual(result, @"c:\temp\subfolder\test.txt"); path = ".\\test.txt"; result = FileUtils.ResolvePath(basePath, path); Assert.AreEqual(result, @"c:\temp\subfolder\test.txt"); basePath = @"c:\temp\subfolder\"; // folder - !IMPORTANT add backslash to folders! path = "..\\test.txt"; result = FileUtils.ResolvePath(basePath, path); Assert.AreEqual(result, @"c:\temp\test.txt"); path = "\\test.txt"; result = FileUtils.ResolvePath(basePath, path); Console.WriteLine(result); Assert.AreEqual(result, @"c:\test.txt"); } } } ================================================ FILE: Westwind.Utilities.Test/HttpClientTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Westwind.Utilities.InternetTools; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Westwind.Utilities.InternetTools { [TestClass] public class HttpClientTests { [TestMethod] public void InvalidUrlTest() { var client = new HttpClient(); var html = client.DownloadString("http://weblog.west-wind.com/nonexistantpage.htm"); Assert.IsTrue(client.WebResponse.StatusCode == System.Net.HttpStatusCode.NotFound); Console.WriteLine(client.WebResponse.StatusCode); } [TestMethod] public void HttpTimingsTest() { var client = new HttpClient(); var html = client.DownloadString("http://weblog.west-wind.com/posts/2015/Jan/06/Using-Cordova-and-Visual-Studio-to-build-iOS-Mobile-Apps"); Console.WriteLine(client.WebResponse.ContentLength); Console.WriteLine(client.HttpTimings.StartedTime); Console.WriteLine("First Byte: " + client.HttpTimings.TimeToFirstByteMs); Console.WriteLine("Last Byte: " + client.HttpTimings.TimeToLastByteMs); } [TestMethod] public async Task HttpTimingsTestsAsync() { var client = new HttpClient(); var html = await client.DownloadStringAsync("http://weblog.west-wind.com/posts/2015/Jan/06/Using-Cordova-and-Visual-Studio-to-build-iOS-Mobile-Apps"); Console.WriteLine(client.WebResponse.ContentLength); Console.WriteLine(client.HttpTimings.StartedTime); Console.WriteLine("First Byte: " + client.HttpTimings.TimeToFirstByteMs); Console.WriteLine("Last Byte: " + client.HttpTimings.TimeToLastByteMs); } [TestMethod] public void AddPostKeyGetPostBufferTest() { var client = new HttpClient(); client.ContentType = "application/x-www-form-urlencoded"; client.AddPostKey("ctl00_Content_Username", "Rick"); client.AddPostKey("ctl00_Content_Password", "seekrit"); string post = client.GetPostBuffer(); Console.WriteLine(post); Assert.IsTrue(post.Contains("&ctl00_Content_Password=")); } [TestMethod] public void AddPostKeyRawGetPostBufferTest() { var client = new HttpClient(); client.ContentType = "application/x-www-form-urlencoded"; // raw POST buffer client.AddPostKey("name=Rick&company=West%20Wind"); string post = client.GetPostBuffer(); Console.WriteLine(post); Assert.IsTrue(post == "name=Rick&company=West%20Wind"); } } } ================================================ FILE: Westwind.Utilities.Test/HttpClientUtilsTests.cs ================================================  using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; using Westwind.Utilities; namespace Westwind.Utilities.Test { [TestClass] public class HttpClientUtilsTests { public HttpClientUtilsTests() { // force Json.NET to load //var f = Newtonsoft.Json.Formatting.Indented; } [TestMethod] public async Task HttpRequestStringWithUrlTest() { string html = await HttpClientUtils.DownloadStringAsync(new HttpClientRequestSettings { Url = "https://west-wind.com" }); Assert.IsNotNull(html); Console.WriteLine(html); } [TestMethod] public async Task HttpRequestStringWithBadUrlTest() { var settings = new HttpClientRequestSettings { Url = "https://west-wind.com/bogus.html" }; string html = await HttpClientUtils.DownloadStringAsync(settings); // result is a 404 with content but success has no result (null) Assert.IsNull(html); Assert.IsTrue(settings.HasErrors); Assert.IsTrue(settings.ResponseStatusCode == HttpStatusCode.NotFound); var content = await settings.GetResponseStringAsync(); Assert.IsNotNull(content); Console.WriteLine(content); } [TestMethod] public async Task ASynchBytesDownloadTest() { byte[] html = await HttpClientUtils.DownloadBytesAsync("https://west-wind.com/images/wwtoolbarlogo.png"); Assert.IsNotNull(html); Console.WriteLine(html.Length); } [TestMethod] public async Task DownloadAsFileAsyncTest() { var dlFile = Path.Combine(Path.GetTempPath(), "~wwtoolbarlogo.png"); if (File.Exists(dlFile)) File.Delete(dlFile); bool result = await HttpClientUtils.DownloadFileAsync("https://west-wind.com/images/wwtoolbarlogo.png",dlFile); Assert.IsTrue(result); Assert.IsTrue(File.Exists(dlFile)); File.Delete(dlFile); } [TestMethod] public async Task DownloadAsFileWithSettingsAsyncTest() { var dlFile = Path.Combine(Path.GetTempPath(), "~wwtoolbarlogo.png"); if (File.Exists(dlFile)) File.Delete(dlFile); var settings = new HttpClientRequestSettings { Url = "https://west-wind.com/images/wwtoolbarlogo.png", OutputFilename = dlFile }; bool result = await HttpClientUtils.DownloadFileAsync(settings: settings); Assert.IsTrue(result); // helper for header Assert.AreEqual(settings.ResponseContentType,"image/png"); // raw header access Assert.AreEqual(settings.ResponseContentHeaders.ContentType?.MediaType, "image/png"); Assert.IsTrue(File.Exists(dlFile)); File.Delete(dlFile); } #if NET6_0_OR_GREATER [TestMethod] public void SynchronousStringDownloadTest() { string html = HttpClientUtils.DownloadString("https://west-wind.com"); Assert.IsNotNull(html); Console.WriteLine(html.GetMaxCharacters(1000)); } [TestMethod] public void SynchronousStringSettingsDownload() { string html = HttpClientUtils.DownloadString(new HttpClientRequestSettings { Url = "https://west-wind.com" }); Assert.IsNotNull(html); Console.WriteLine(html.GetMaxCharacters(1000)); } [TestMethod] public void SynchronousBytesDownload() { byte[] html = HttpClientUtils.DownloadBytes("https://west-wind.com/images/wwtoolbarlogo.png"); Assert.IsNotNull(html); Console.WriteLine(html.Length); } #endif [TestMethod] public async Task HttpRequestJsonStringWithUrlTest() { // returns a 404 so bad request var settings = new HttpClientRequestSettings { Url = "http://websurgeapi.west-wind.com/api/authentication/usertokenvalidation/1234" }; string json = await HttpClientUtils.DownloadStringAsync(settings); Assert.IsNull(json); Assert.IsTrue(settings.HasErrors); json = await settings.GetResponseStringAsync(); Console.WriteLine(json); Console.WriteLine(settings.ErrorMessage); } [TestMethod] public async Task HttpRequestHtmlPostTest() { string html = await HttpClientUtils.DownloadStringAsync(new HttpClientRequestSettings { Url = "https://west-wind.com/wconnect/Testpage.wwd", HttpVerb = "POST", RequestContent = "FirstName=Rick&Company=West+Windx", RequestContentType = "application/x-www-form-urlencoded" }); Assert.IsNotNull(html); Console.WriteLine(html); } [TestMethod] public async Task HttpRequestBadJsonPostTest() { string json = """ { "username": "test@test.com", "password": "kqm3ube0jnm!QKC9wcx" } """; var settings = new HttpClientRequestSettings { Url = "https://store.west-wind.com/api/account/authenticate", HttpVerb = "POST", RequestContent = json, RequestContentType = "application/json" }; json = await HttpClientUtils.DownloadStringAsync(settings); Assert.IsNull(json, settings.ErrorMessage); json = await settings.GetResponseStringAsync(); Assert.IsNotNull(json, "Error content not retrieved."); Console.WriteLine(json); // var jobj = await settings.GetResponseJson(); Assert.IsNotNull(jobj); Console.WriteLine(jobj); Console.WriteLine(((dynamic)jobj).message); } [TestMethod] public async Task HttpRequestBadJsonPostWithExceptionsTest() { string json = """ { "username": "test@test.com", "password": "kqm3ube0jnm!QKC9wcx" } """; var settings = new HttpClientRequestSettings { Url = "https://albumviewer.west-wind.com/api/account/bogus", HttpVerb = "POST", RequestContent = json, RequestContentType = "application/json", ThrowExceptions = true }; try { json = await HttpClientUtils.DownloadStringAsync(settings); Console.WriteLine(json); } catch (Exception ex) { Console.WriteLine(ex.Message); // 404 var htmlErrorPage = await settings.GetResponseStringAsync(); Assert.IsNotNull(htmlErrorPage); Assert.IsTrue(htmlErrorPage.StartsWith(">(settings); Assert.IsNotNull(snippets); Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.IsTrue(snippets.Count > 0); Console.WriteLine(snippets.Count); } [TestMethod] public async Task JsonRequestAsyncTest() { var settings = new HttpRequestSettings() { Url = "https://albumviewer.west-wind.com/album/37" }; var snippets = await HttpUtils.JsonRequestAsync>(settings); Assert.IsNotNull(snippets); Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.IsTrue(snippets.Count > 0); Console.WriteLine(snippets.Count); Console.WriteLine(settings.CapturedResponseContent); } [TestMethod] public void JsonRequestPostTest() { var postSnippet = new CodeSnippet() { UserId = "Bogus", Code = "string.Format('die Bären sind süss und sauer.');", Comment = "World domination imminent" }; var settings = new HttpRequestSettings() { Url = "http://codepaste.net/recent?format=json", Content = postSnippet, HttpVerb = "POST" }; var snippets = HttpUtils.JsonRequest>(settings); Assert.IsNotNull(snippets); Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.IsTrue(snippets.Count > 0); Console.WriteLine(snippets.Count); Console.WriteLine(settings.CapturedRequestContent); Console.WriteLine(); Console.WriteLine(settings.CapturedResponseContent); foreach (var snippet in snippets) { if (string.IsNullOrEmpty(snippet.Code)) continue; Console.WriteLine(snippet.Code.Substring(0, Math.Min(snippet.Code.Length, 200))); Console.WriteLine("--"); } Console.WriteLine("Status Code: " + settings.Response.StatusCode); foreach (var header in settings.Response.Headers) { Console.WriteLine(header + ": " + settings.Response.Headers[header.ToString()]); } } [TestMethod] public async Task JsonRequestPostAsyncTest() { var postSnippet = new CodeSnippet() { UserId = "Bogus", Code = "string.Format('Hello World, I will own you!');", Comment = "World domination imminent" }; var settings = new HttpRequestSettings() { Url = "http://codepaste.net/recent?format=json", Content = postSnippet, HttpVerb = "POST" }; var snippets = await HttpUtils.JsonRequestAsync>(settings); Assert.IsNotNull(snippets); Assert.IsTrue(settings.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.IsTrue(snippets.Count > 0); Console.WriteLine(snippets.Count); Console.WriteLine(settings.CapturedRequestContent); Console.WriteLine(); Console.WriteLine(settings.CapturedResponseContent); foreach (var snippet in snippets) { if (string.IsNullOrEmpty(snippet.Code)) continue; Console.WriteLine(snippet.Code.Substring(0, Math.Min(snippet.Code.Length, 200))); Console.WriteLine("--"); } // This doesn't work for the async version - Response is never set by the base class Console.WriteLine("Status Code: " + settings.Response.StatusCode); foreach (var header in settings.Response.Headers) { Console.WriteLine(header + ": " + settings.Response.Headers[header.ToString()]); } } #endif [TestMethod] public void DownloadImageFile() { string url = "https://markdownmonster.west-wind.com/Images/MarkdownMonster_Icon_32.png"; string fname = null; try { fname = HttpUtils.DownloadImageToFile(url); Console.WriteLine(fname); Assert.IsNotNull(fname); Assert.IsTrue(File.Exists(fname)); //ShellUtils.ShellExecute(fname,null); //Thread.Sleep(500); } finally { if (!string.IsNullOrEmpty(fname)) File.Delete(fname); Assert.IsFalse(File.Exists(fname)); } } [ExpectedException(typeof(WebException))] [TestMethod()] public void HttpTimeout() { string result = HttpUtils.HttpRequestString(new HttpRequestSettings() { Url="http://west-wind.com/files/wconnect.exe", Timeout = 100 }); Assert.IsNotNull(result); } } public class CodeSnippet { public int CommentCount { get; set; } public string Id { get; set; } public string UserId { get; set; } public string Language { get; set; } public int Views { get; set; } public string Code { get; set; } public string Comment { get; set; } public DateTime Entered { get; set; } } } ================================================ FILE: Westwind.Utilities.Test/ImagingTests.cs ================================================ #if NETFULL using System; using System.Drawing.Imaging; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Drawing; using System.IO; namespace Westwind.Utilities.Tests { [TestClass] public class ImagingTests { private string ImageFile = @"supportfiles\sailbig.jpg"; private string ImageFileWork = @"supportfiles\sailbigWork.jpg"; private string ImageFileRotated = @"supportfiles\sailbigRotated.jpg"; private string SquareImageFile = @"supportfiles\SquareImage.jpg"; private string HighQualityImageFile = @"supportfiles\HighQuality.jpg"; private string HighQualityImageFileWork = @"supportfiles\HighQualityWork.jpg"; [TestMethod] public void RotateFileToFileTest() { string orig = ImageFile; string work = ImageFileWork; string rotated = ImageFileRotated; File.Copy(orig,work,true); ImageUtils.RoateImage(work,rotated,RotateFlipType.Rotate270FlipNone); File.Copy(rotated,work,true); } [TestMethod] public void RotateFileToSelfFileTest() { string orig = ImageFile; // work on sailbig3 and write output to sailbig3 string work = ImageFileWork; string rotated = ImageFileRotated; File.Copy(orig, work, true); ImageUtils.RoateImage(work, rotated, RotateFlipType.Rotate270FlipNone); } [TestMethod] public void RotateFileInMemory() { string orig = ImageFile; // work on sailbig3 and write output to sailbig3 string work = ImageFileWork; string rotated = ImageFileRotated; var imgData = File.ReadAllBytes(orig); var rotatedData = ImageUtils.RoateImage(imgData, RotateFlipType.Rotate270FlipNone); Assert.IsNotNull(rotatedData); File.WriteAllBytes(rotated, rotatedData); } [TestMethod] public void ResizeBitMap() { string orig = ImageFile; string copied = ImageFileWork; using (var bmp = new Bitmap(orig)) { var bmp2 = ImageUtils.ResizeImage(bmp, 150, 150); bmp2.Save(copied); bmp2.Dispose(); } } [TestMethod] public void ResizeBitMapFile() { string orig = ImageFile; string copied = ImageFileWork; bool res = ImageUtils.ResizeImage(orig,copied, 150, 150); Assert.IsTrue(res); } [TestMethod] public void ResizeSquareBitMap() { string orig = SquareImageFile; string copied = ImageFileWork; using (var bmp = new Bitmap(orig)) { var bmp2 = ImageUtils.ResizeImage(bmp, 100, 150); Assert.IsTrue(bmp2.Width == 150, "Image was not resized correctly."); Console.WriteLine(bmp2.RawFormat.Guid + " (New)"); Console.WriteLine(bmp.RawFormat.Guid + " (File)"); Console.WriteLine(ImageFormat.Jpeg.Guid + " (Jpeg)"); bmp2.Save(copied,ImageFormat.Jpeg); bmp2.Dispose(); } } [TestMethod] public void ResizeHighQualityBitMap() { string orig = HighQualityImageFile; string copied = HighQualityImageFileWork; Assert.IsTrue(ImageUtils.ResizeImage(orig, copied, 2000, 2000)); Console.WriteLine("Orig file: " + (new FileInfo(orig).Length/1000).ToString("n2") + "kb"); Console.WriteLine("Updated file: " + (new FileInfo(copied).Length / 1000).ToString("n2") + "kb"); } } } #endif ================================================ FILE: Westwind.Utilities.Test/Models/Entities/Customer.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using Westwind.Utilities; namespace Westwind.Data.Test.Models { public class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Company { get; set; } public string Address { get; set; } public string Password { get; set; } public DateTime? LastOrder { get; set; } public DateTime Entered { get; set; } public DateTime Updated { get; set; } public byte[] Binary { get; set; } public List Orders { get; set; } public Customer() { Id = (int) Math.Abs(DataUtils.GenerateUniqueNumericId()); Entered = DateTime.Now; Updated = DateTime.Now; Orders = new List(); } } } ================================================ FILE: Westwind.Utilities.Test/Models/Entities/LineItem.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; namespace Westwind.Data.Test.Models { public class LineItem { public int Id { get; set; } public int OrderId { get; set; } public string Sku { get; set; } public string Description { get; set; } public decimal Quantity { get; set; } public decimal Price { get; set; } public decimal Total { get; set; } } } ================================================ FILE: Westwind.Utilities.Test/Models/Entities/Order.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; namespace Westwind.Data.Test.Models { public class Order { public int Id { get; set; } public int CustomerPk { get; set; } public string OrderId { get; set; } public DateTime Entered { get; set; } public DateTime? Shipped { get; set; } public List LineItems { get; set; } public Customer Customer { get; set; } public Order() { Entered = DateTime.Now; Shipped = null; LineItems = new List(); } } } ================================================ FILE: Westwind.Utilities.Test/Models/Entities/WebLogEntry.cs ================================================ #region License /* ************************************************************** * Author: Rick Strahl * © West Wind Technologies, 2009 * http://www.west-wind.com/ * * Created: 09/12/2009 * * Permission 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: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE 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. ************************************************************** */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.Net; namespace Westwind.Utilities.Test.Models.Entities { /// /// A Web specific Log entry that includes information about the current Web Request /// public class WebLogEntry : LogEntry { public WebLogEntry() { } public WebLogEntry(Exception ex) : base(ex) { } #if NETFULL public WebLogEntry(Exception ex, HttpContext context) : base(ex) { UpdateFromRequest(context); } #endif /// /// The Url without the query string for the current request /// public string Url { get { return _Url; } set { _Url = value; } } private string _Url = ""; /// /// The query string of the current request /// public string QueryString { get { return _QueryString; } set { _QueryString = value; } } private string _QueryString = ""; /// /// The IP Address of the client that called this URL /// public string IpAddress { get { return _IpAddress; } set { _IpAddress = value; } } private string _IpAddress = ""; /// /// The POST data if available /// public string PostData { get { return _PostData; } set { _PostData = value; } } private string _PostData = ""; /// /// The Referring url /// public string Referrer { get { return _Referrer; } set { _Referrer = value; } } private string _Referrer = ""; public string UserAgent { get { return _UserAgent; } set { _UserAgent = value; } } private string _UserAgent = ""; /// /// Optional duration of the current request /// public decimal RequestDuration { get { return _RequestDuration; } set { _RequestDuration = value; } } private decimal _RequestDuration = 0M; #if NETFULL public bool UpdateFromRequest() { return UpdateFromRequest(HttpContext.Current); } /// /// Updates the Web specific properties of this entry from the /// supplied HttpContext object. /// /// /// public bool UpdateFromRequest(HttpContext context) { if (context == null) context = HttpContext.Current; if (context == null) return false; HttpRequest request = context.Request; IpAddress = request.UserHostAddress; Url = request.FilePath; QueryString = request.QueryString.ToString(); if (request.UrlReferrer != null) Referrer = request.UrlReferrer.ToString(); UserAgent = request.UserAgent; if (request.TotalBytes > 0 && request.TotalBytes < 2048) { PostData = Encoding.Default.GetString(request.BinaryRead(request.TotalBytes)); } else if (request.TotalBytes > 2048) // strip the result { PostData = Encoding.Default.GetString(request.BinaryRead(2040)) + "..."; } return true; } #endif } /// /// Message object that contains information about the current error information /// /// Note: a WebLogEntry specific to Web applications lives in the /// Westwind.Web assembly. /// public class LogEntry { public LogEntry() { } public LogEntry(Exception ex) { UpdateFromException(ex); } /// /// The unique ID for this LogEntry /// public int Id { get { return _Id; } set { _Id = value; } } private int _Id = 0; /// /// When this error occurred. /// public DateTime Entered { get { return _Entered; } set { _Entered = value; } } private DateTime _Entered = DateTime.UtcNow; /// /// The Actual Error Message /// public string Message { get { return _Message; } set { _Message = value; } } private string _Message = String.Empty; /// /// Determines the error level of the messages /// public ErrorLevels ErrorLevel { get { return _ErrorLevel; } set { _ErrorLevel = value; } } private ErrorLevels _ErrorLevel = ErrorLevels.Error; /// /// Free form text field that contains extra data to display /// public string Details { get { return _Details; } set { _Details = value; } } private string _Details = String.Empty; /// /// The type of exception that was thrown if an error occurred /// public string ErrorType { get { return _ErrorType; } set { _ErrorType = value; } } private string _ErrorType = String.Empty; /// /// StackTrace in event of an exception /// public string StackTrace { get { return _StackTrace; } set { _StackTrace = value; } } private string _StackTrace = String.Empty; /// /// Updates the current request as an error log entry and /// sets the ErrorType, Message and StackTrace properties /// from the content of the passed exception /// /// public void UpdateFromException(Exception ex) { ErrorLevel = ErrorLevels.Error; ErrorType = ex.GetType().Name.Replace("Exception", ""); Message = ex.Message; StackTrace = ex.StackTrace != null && ex.StackTrace.Length > 1490 ? ex.StackTrace.Substring(0, 1500) : ex.StackTrace; Details = ex.Source; } } [Flags] public enum ErrorLevels { /// /// A critical error occurred /// Error = 1, /// /// A warning type message that drives attention to potential problems /// Warning = 2, /// /// Log Entry /// Info = 4, /// /// Debug message /// Debug = 8, /// /// Application level information log entries /// ApplicationInfo = 16, /// /// Application Level Error entries /// ApplicationError = 32, /// /// Empty - not assigned /// None = 0, /// /// All ErrorLevels - used only for querying /// All = 256 } } ================================================ FILE: Westwind.Utilities.Test/NetworkUtilsTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; namespace Westwind.Utilities.Tests { /// /// Summary description for NetworkUtilsTests /// [TestClass] public class NetworkUtilsTests { public NetworkUtilsTests() { // // TODO: Add constructor logic here // } /// ///Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get; set; } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] public void GetBaseDomain() { Assert.IsTrue(new Uri("http://www.west-wind.com").GetBaseDomain() == "west-wind.com"); Assert.IsTrue(new Uri("http://127.0.0.1").GetBaseDomain() == "127.0.0.1"); Assert.IsTrue(NetworkUtils.GetBaseDomain("localhost") == "localhost"); Assert.IsTrue(NetworkUtils.GetBaseDomain("classifieds4.gorge.net") == "gorge.net"); Assert.IsTrue(NetworkUtils.GetBaseDomain("classifieds5.gorge.net") == "gorge.net"); Assert.IsTrue(NetworkUtils.GetBaseDomain(string.Empty) == string.Empty); } [TestMethod] public void IsLocalIpAddress() { Assert.IsTrue(NetworkUtils.IsLocalIpAddress("localhost")); Assert.IsTrue(NetworkUtils.IsLocalIpAddress("127.0.0.1")); var domain = "dev.west-wind.com"; bool result = NetworkUtils.IsLocalIpAddress(domain); Console.WriteLine($"{domain} is local: {result}"); domain = "bogus.west-wind.com"; result = NetworkUtils.IsLocalIpAddress(domain); Console.WriteLine($"{domain} is local: {result}"); } } } ================================================ FILE: Westwind.Utilities.Test/ObjectFactoryTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; namespace Westwind.Utilities.Tests { /// /// Summary description for ObjectFactoryTests /// [TestClass] public class ObjectFactoryTests { public ObjectFactoryTests() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] public void GetUniqueObjectKeyTest() { var obj1 = ObjectFactory.GetUniqueObjectKey(); var obj2 = ObjectFactory.GetUniqueObjectKey(); var name = "Rick"; var obj3 = ObjectFactory.GetUniqueObjectKey(name); var obj4 = ObjectFactory.GetUniqueObjectKey(name); TestContext.WriteLine(obj1 + Environment.NewLine + obj2 + Environment.NewLine + obj3 + Environment.NewLine + obj4 + Environment.NewLine ); Assert.AreEqual(obj1, obj2); Assert.AreNotEqual(obj1, obj3); Assert.AreEqual(obj3, obj4); } [TestMethod] public void ThreadScopedObjectTest() { var inst1 = ObjectFactory.CreateThreadScopedObject(); var inst2 = ObjectFactory.CreateThreadScopedObject(); var inst3 = ObjectFactory.CreateThreadScopedObject("rick"); var inst4 = ObjectFactory.CreateThreadScopedObject("rick"); var inst5 = ObjectFactory.CreateThreadScopedObject("rick5"); Assert.IsNotNull(inst1); Assert.AreEqual(inst1, inst2); Assert.IsNotNull(inst3); Assert.AreNotEqual(inst1, inst3); Assert.AreEqual(inst3, inst4); Assert.IsNotNull(inst5); Assert.AreNotEqual(inst3, inst5); } } class Information { public string Name { get; set; } public DateTime Entered { get; set; } public Information() { } public Information(string name) { Name = name; } } } ================================================ FILE: Westwind.Utilities.Test/PasswordScrubberTests.cs ================================================ using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; namespace Westwind.Utilities.Tests { /// /// Summary description for StringUtilsTests /// [TestClass] public class PasswordScrubberTests { public PasswordScrubberTests() { } private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] public void SqlConnectionStringTest() { var scrubber = new PasswordScrubber() { ObscuredValueBaseDisplay = "*******", ShowUnobscuredCharacterCount = 2 }; string conn = @"server=.;database=WebStore;uid=WebStoreUser;pwd=superSeekrit#1;encrypt=false;"; string result = scrubber.ScrubSqlConnectionStringValues(conn, "pwd", "uid"); Console.WriteLine(result); Assert.IsTrue(result.Contains("pwd=su*******")); Assert.IsTrue(result.Contains("uid=We*******")); } [TestMethod] public void JsonScrubTest() { var scrubber = new PasswordScrubber() { ObscuredValueBaseDisplay = "*******", ShowUnobscuredCharacterCount = 2 }; var json = """ { "Name: "Rick", "Password": "SuperSeekrit56", "UserId": "rickochet2", } """; var result = scrubber.ScrubJsonValues(json, "Password", "UserId"); Console.WriteLine(result); Assert.IsTrue(result.Contains("\"Password\": \"Su*******\"")); Assert.IsTrue(result.Contains("ri*******")); } [TestMethod] public void JsonAndSqlConnectionScrubTest() { var scrubber = new PasswordScrubber() { ObscuredValueBaseDisplay = "*******", ShowUnobscuredCharacterCount = 2 }; var json = """ { "Name: "Rick", "Password": "SuperSeekrit56", "UserId": "rickochet2", "ConnectionString": "server=.;database=WebStore;uid=WebStoreUser;pwd=superSeekrit#1;encrypt=false;" } """; var result = scrubber.ScrubSqlConnectionStringValues(json, "pwd", "uid"); result = scrubber.ScrubJsonValues(result, "Password", "UserId"); Console.WriteLine(result); Assert.IsTrue(result.Contains("\"Password\": \"Su*******\"")); Assert.IsTrue(result.Contains("ri*******")); Assert.IsTrue(result.Contains("pwd=su*******")); Assert.IsTrue(result.Contains("uid=We*******")); } } } ================================================ FILE: Westwind.Utilities.Test/PropertyBagTest.cs ================================================ using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Xml.Serialization; using System.IO; using Newtonsoft.Json; namespace Westwind.Utilities.Tests { /// /// Summary description for UnitTest1 /// [TestClass] public class PropertyBagTest { public PropertyBagTest() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; /// /// Gets or sets the test context which provides /// information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // // You can use the following additional attributes as you write your tests: // // Use ClassInitialize to run code before running the first test in the class // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Use ClassCleanup to run code after all tests in a class have run // [ClassCleanup()] // public static void MyClassCleanup() { } // // Use TestInitialize to run code before running each test // [TestInitialize()] // public void MyTestInitialize() { } // // Use TestCleanup to run code after each test has run // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [TestMethod] public void PropertyBagTwoWayObjectSerializationTest() { var bag = new PropertyBag(); bag.Add("key", "Value"); bag.Add("Key2", 100.10M); bag.Add("Key3", Guid.NewGuid()); bag.Add("Key4", DateTime.Now); bag.Add("Key5", true); bag.Add("Key7", new byte[3] { 42, 45, 66 } ); bag.Add("Key8", null); bag.Add("Key9", new ComplexObject() { Name = "Rick", Entered = DateTime.Now, Count = 10 }); string xml = bag.ToXml(); TestContext.WriteLine(bag.ToXml()); bag.Clear(); bag.FromXml(xml); Assert.IsTrue(bag["key"] as string == "Value"); Assert.IsInstanceOfType( bag["Key3"], typeof(Guid)); Assert.IsNull(bag["Key8"]); //Assert.IsNull(bag["Key10"]); Assert.IsInstanceOfType(bag["Key9"], typeof(ComplexObject)); } [TestMethod] public void PropertyBagInContainerTwoWayObjectSerializationTest() { var bag = new PropertyBag(); bag.Add("key", "Value"); bag.Add("Key2", 100.10M); bag.Add("Key3", Guid.NewGuid()); bag.Add("Key4", DateTime.Now); bag.Add("Key5", true); bag.Add("Key7", new byte[3] { 42, 45, 66 }); bag.Add("Key8", null); bag.Add("Key9", new ComplexObject() { Name = "Rick", Entered = DateTime.Now, Count = 10 }); ContainerObject cont = new ContainerObject(); cont.Name = "Rick"; cont.Items = bag; string xml = SerializationUtils.SerializeObjectToString(cont); TestContext.WriteLine(xml); ContainerObject cont2 = SerializationUtils.DeSerializeObject(xml, typeof(ContainerObject)) as ContainerObject; Assert.IsTrue(cont2.Items["key"] as string == "Value"); Assert.IsTrue(cont2.Items["Key3"].GetType() == typeof(Guid)); Assert.IsNull(cont2.Items["Key8"]); //Assert.IsNull(bag["Key10"]); TestContext.WriteLine(cont.Items["Key3"].ToString()); TestContext.WriteLine(cont.Items["Key4"].ToString()); } [TestMethod] public void PropertyBagTwoWayValueTypeSerializationTest() { var bag = new PropertyBag(); bag.Add("key", 10M); bag.Add("Key1", 100.10M); bag.Add("Key2", 200.10M); bag.Add("Key3", 300.10M); string xml = bag.ToXml(); TestContext.WriteLine(xml); bag.Clear(); bag.FromXml(xml); Assert.IsTrue(bag["Key1"] == 100.10M); Assert.IsTrue(bag["Key3"] == 300.10M); } [TestMethod] public void SerializationToJson() { var bag = new PropertyBag(); bag.Add("Value1", "asldkjalsk dalksdj"); bag.Add("Value2", "bbasdasdklajsdalksdja"); string json = JsonConvert.SerializeObject(bag,Formatting.Indented); Console.WriteLine(json); var bag2 = JsonConvert.DeserializeObject(json, typeof(PropertyBag)) as PropertyBag; Assert.IsNotNull(bag2); Assert.IsNotNull(bag["Value2"] as string); Console.WriteLine(bag["Value1"] as string); } [TestMethod] public void SerializationToXml() { var bag = new PropertyBag(); bag.Add("Value1", "asldkjalsk dalksdj"); bag.Add("Value2", "bbasdasdklajsdalksdja"); string xml = null; SerializationUtils.SerializeObject(bag, out xml); Console.WriteLine(xml); var bag2 = SerializationUtils.DeSerializeObject(xml, typeof(PropertyBag)) as PropertyBag; Assert.IsNotNull(bag2); Assert.IsNotNull(bag["Value2"] as string); Console.WriteLine(bag["Value1"] as string); } #region StandardSerializerTests [TestMethod] [ExpectedException(typeof(NotSupportedException))] public void DictionaryXmlSerializerTest() { var bag = new Dictionary(); bag.Add("key", "Value"); bag.Add("Key2", 100.10M); bag.Add("Key3", Guid.NewGuid()); bag.Add("Key4", DateTime.Now); bag.Add("Key5", true); bag.Add("Key7", new byte[3] { 42, 45, 66 }); // this should fail with NotSupported as Dictionaries // can't be serialized TestContext.WriteLine(this.ToXml(bag)); } string ToXml(object obj) { if (obj == null) return null; StringWriter sw = new StringWriter(); XmlSerializer ser = new XmlSerializer(obj.GetType()); ser.Serialize(sw, obj); return sw.ToString(); } #endregion public class ContainerObject { public string Name { get; set; } public PropertyBag Items { get; set; } } public class ComplexObject { public string Name { get; set; } public DateTime Entered { get; set; } public int Count { get; set; } } } } ================================================ FILE: Westwind.Utilities.Test/ReflectionUtilsTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Reflection; namespace Westwind.Utilities.Tests { [TestClass] public class ReflectionUtilsTests { [TestMethod] public void TypedValueToStringTest() { // Guid object val = Guid.NewGuid(); string res = ReflectionUtils.TypedValueToString(val); Assert.IsTrue(res.Contains("-")); Console.WriteLine(res); object val2 = ReflectionUtils.StringToTypedValue(res); Assert.AreEqual(val, val2); // Single val = (Single) 10.342F; res = ReflectionUtils.TypedValueToString(val); Console.WriteLine(res); Assert.AreEqual(res, val.ToString()); val2 = ReflectionUtils.StringToTypedValue(res); Assert.AreEqual(val, val2); // Single val = (Single)10.342F; res = ReflectionUtils.TypedValueToString(val); Console.WriteLine(res); Assert.AreEqual(res, val.ToString()); val2 = ReflectionUtils.StringToTypedValue(res); Assert.AreEqual(val, val2); } //[TestMethod] //public void ComAccessReflectionCoreAnd45Test() //{ // // this works with both .NET 4.5+ and .NET Core 2.0+ // string progId = "InternetExplorer.Application"; // Type type = Type.GetTypeFromProgID(progId); // object inst = Activator.CreateInstance(type); // inst.GetType().InvokeMember("Visible", ReflectionUtils.MemberAccess | BindingFlags.SetProperty, null, inst, // new object[1] // { // true // }); // inst.GetType().InvokeMember("Navigate", ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, // inst, new object[] // { // "https://markdownmonster.west-wind.com", // }); // //result = ReflectionUtils.GetPropertyCom(inst, "cAppStartPath"); // bool result = (bool)inst.GetType().InvokeMember("Visible", // ReflectionUtils.MemberAccess | BindingFlags.GetProperty, null, inst, null); // Console.WriteLine(result); // path //} //[TestMethod] //public void ComAccessDynamicCoreAnd45Test() //{ // // this does not work with .NET Core 2.0 // string progId = "InternetExplorer.Application"; // Type type = Type.GetTypeFromProgID(progId); // dynamic inst = Activator.CreateInstance(type); // inst.Visible = true; // inst.Navigate("https://markdownmonster.west-wind.com"); // bool result = inst.Visible; // Assert.IsTrue(result); //} } } ================================================ FILE: Westwind.Utilities.Test/SanitizeHtmlTests.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Westwind.Utilities; namespace Westwind.Utilities.Tests { [TestClass] public class SanitizeHtmlTests { [TestMethod] public void HtmlSanitizeScriptTags() { string html = "
User input with
"; var result = HtmlUtils.SanitizeHtml(html); Console.WriteLine(result); Assert.IsTrue(!result.Contains("