From 55ad086c2838a6ef915e319147dab0a94528052f Mon Sep 17 00:00:00 2001 From: axunonb Date: Thu, 29 Jun 2023 22:35:26 +0200 Subject: [PATCH] Integrate SmartFormat v3.2.1 (#38) * Update of SmartFormat.NET to v3.2.1 with at least the same extensions enabled as in prior MailMergeLib 5.x versions * Add NET6.0 as a target framework, reducing number of dependencies * Update YAXLib to v4.1.0 * Update AngeSharp to v1.0.4 * Update Microsoft.NET.Test.Sdk to v17.6.3" * Unit test run with net462, netcoreapp3.1 and net6.0 * Disable unit tests console output * After installation is completed, the above "Heads up" message is displayed * Add Sandcastle Help File Builder project folder * Resolves #23 * Enable Nullable Reference Types (NRT) (#30) * Removed obsolete SmartObjects as data source (#31) * Change setup for SMTP Integration tests (#32) * New method to get a free TCP port * SslProtocols: let operating system select the best * Improved Sender integration tests * Fix: SmtpSever trying to use an unpermitted port (#33) * Add method trying to bind an available port * Removed console message for CanBindPort(int port) * Update MailSmartFormatter to load at least the same SmartFormat extensions as before * MailMergeMessage: A new MailSmartFormatter instance is created after properties of SmartFormatterConfig are changed. This is because SmartFormatter does not allow for changes to the settings, after the instance is created * Run unit test for linux under netstandard2.1 * Code refactoring from NET6.0 analytics * Update readme and license files * SmartFormatterConfig change retains existing SmartSettings * Disable warnings for unused private variables in MessageConfig * Add Sandcastle Help File Builder project folder No breaking MailMergeLib API changes from version 5.9.10 However SmartFormat v3 brings breaking changes compared to v2 (e.g. for formatter names, where name "template" changed to "t" and others) --- LICENSE.md | 22 +- License.txt | 21 - PkgReadMe.md | 10 +- README.md | 10 +- ReleaseNotes.md | 120 ----- Src/.editorconfig | 264 +++++++++++ Src/Directory.Build.props | 8 +- Src/Documentation/MailMergeLib.hmxz | Bin 0 -> 37008 bytes Src/Documentation/MailMergeLib.png | Bin 0 -> 25243 bytes Src/Documentation/MailMergeLib.shfbproj | 135 ++++++ Src/Documentation/MergedEmail.png | Bin 0 -> 63224 bytes Src/Documentation/Migration-4-to-5.docx | Bin 0 -> 20017 bytes Src/Documentation/icons/MailMergeLib.png | Bin 0 -> 25243 bytes Src/MailMergeLib.Tests/EmailValidatorTest.cs | 10 +- Src/MailMergeLib.Tests/FakeSmtpClient.cs | 22 +- Src/MailMergeLib.Tests/FileMessageInfo.cs | 4 +- .../FileMessageStore_Serialization.cs | 6 +- Src/MailMergeLib.Tests/Helper.cs | 50 +- Src/MailMergeLib.Tests/HtmlBodyBuilderTest.cs | 14 +- .../MailMergeLib.Tests.csproj | 13 +- Src/MailMergeLib.Tests/MessageFactory.cs | 6 +- Src/MailMergeLib.Tests/MessageInfo.cs | 2 - Src/MailMergeLib.Tests/Message_Config.cs | 28 +- Src/MailMergeLib.Tests/Message_Equality.cs | 18 +- Src/MailMergeLib.Tests/Message_Html.cs | 14 +- .../Message_Serialization.cs | 12 +- .../Message_SmartFormatter.cs | 40 +- Src/MailMergeLib.Tests/Message_Templates.cs | 24 +- .../Message_VariableExceptions.cs | 22 +- Src/MailMergeLib.Tests/Message_Variables.cs | 24 +- Src/MailMergeLib.Tests/Sender_Config.cs | 2 - .../Sender_EventsAndSend.cs | 158 +++---- .../Sender_SendMimeMessage.cs | 22 +- .../Settings_Serialization.cs | 44 +- Src/MailMergeLib.Tests/SmtpClient_Config.cs | 12 +- Src/MailMergeLib.Tests/TestFileFolders.cs | 3 +- Src/MailMergeLib.Tests/TestSetup.cs | 22 + Src/MailMergeLib.Tests/Tools.cs | 12 +- Src/MailMergeLib/AngleSharpHtmlConverter.cs | 22 +- Src/MailMergeLib/CaseSensitivityType.cs | 15 + Src/MailMergeLib/Credential.cs | 8 +- Src/MailMergeLib/EmailValidator.cs | 26 +- Src/MailMergeLib/ErrorAction.cs | 22 + Src/MailMergeLib/FileAttachment.cs | 11 +- Src/MailMergeLib/HtmlBodyBuilder.cs | 46 +- Src/MailMergeLib/IHtmlConverter.cs | 1 - Src/MailMergeLib/MailMergeAddress.cs | 36 +- .../MailMergeAddressCollection.cs | 19 +- Src/MailMergeLib/MailMergeLib.csproj | 17 +- Src/MailMergeLib/MailMergeMessage.cs | 183 ++++---- .../MailMergeMessage_Exception.cs | 58 ++- Src/MailMergeLib/MailMergeSender.cs | 57 ++- Src/MailMergeLib/MailSender_EventArgs.cs | 82 +++- Src/MailMergeLib/MailSmartFormatter.cs | 79 ++-- Src/MailMergeLib/MessageConfig.cs | 35 +- .../MessageStore/FileMessageInfo.cs | 10 +- .../MessageStore/FileMessageStore.cs | 10 +- Src/MailMergeLib/MessageStore/IMessageInfo.cs | 11 +- Src/MailMergeLib/MessageStore/MessageInfo.cs | 21 +- .../MessageStore/MessageInfoBase.cs | 41 +- Src/MailMergeLib/PlainBodyBuilder.cs | 3 +- Src/MailMergeLib/SenderConfig.cs | 9 +- .../Serialization/HeaderListSerializer.cs | 3 +- .../Serialization/IPEndPointSerializer.cs | 10 +- .../Serialization/SerializationFactory.cs | 34 +- Src/MailMergeLib/Settings.cs | 8 +- Src/MailMergeLib/SmartFormatterConfig.cs | 17 +- Src/MailMergeLib/SmtpClientConfig.cs | 436 +++++++++--------- Src/MailMergeLib/StringAttachment.cs | 11 +- Src/MailMergeLib/Templates/Part.cs | 10 +- Src/MailMergeLib/Templates/Parts.cs | 6 +- Src/MailMergeLib/Templates/Template.cs | 28 +- .../Templates/TemplateException.cs | 10 +- Src/MailMergeLib/Templates/Templates.cs | 15 +- Src/MailMergeLib/Tools.cs | 26 +- readme.txt | 25 + 76 files changed, 1553 insertions(+), 1082 deletions(-) delete mode 100644 License.txt delete mode 100644 ReleaseNotes.md create mode 100644 Src/.editorconfig create mode 100644 Src/Documentation/MailMergeLib.hmxz create mode 100644 Src/Documentation/MailMergeLib.png create mode 100644 Src/Documentation/MailMergeLib.shfbproj create mode 100644 Src/Documentation/MergedEmail.png create mode 100644 Src/Documentation/Migration-4-to-5.docx create mode 100644 Src/Documentation/icons/MailMergeLib.png create mode 100644 Src/MailMergeLib.Tests/TestSetup.cs create mode 100644 Src/MailMergeLib/CaseSensitivityType.cs create mode 100644 Src/MailMergeLib/ErrorAction.cs create mode 100644 readme.txt diff --git a/LICENSE.md b/LICENSE.md index 99d7fe8..eb3c6ce 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,9 @@ -## MIT License Information +MIT License -MailMergeLib is Copyright (C) 2007-2019 by axuno gGmbH and is licensed under the MIT license: +Copyright (c) axuno, MailMergeLib Project maintainers and contributors - 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: +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 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. +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. diff --git a/License.txt b/License.txt deleted file mode 100644 index 1029078..0000000 --- a/License.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License Information - -MailMergeLib is Copyright (C) 2007-2019 by axuno gGmbH and is licensed under the MIT license: - -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. diff --git a/PkgReadMe.md b/PkgReadMe.md index 0069e55..afa9d6d 100644 --- a/PkgReadMe.md +++ b/PkgReadMe.md @@ -9,9 +9,9 @@ * HTML text may contain images from local hard disk, which will be automatically inserted as inline attachments. * For HTML text ```MailMergeLib ``` can generate a plain text representation. * Attachment sources can be files, streams or strings. -* The data source for email merge messages to a number of recipients and be any ```IEnumerable``` object as well as ```DataTable```s. The data source for single emails can be any of the following types: ```Dictionary```, ```ExpandoObject```, ```DataRow```, any class instances or anonymous types. For class instances it's even allowed to use the name of parameter less methods. -* Placeholders in the email can be formatted with any of the features known from string.Format by using [SmartFormat.NET](https://github.com/scottrippey/SmartFormat.NET/wiki). SmartFormat is a parser coming close to string.Format's speed, but bringing a lot of additional options like easy pluralization for many languages. -* Resulting emails are MimeMessages from [MimeKit](https://github.com/jstedfast/MimeKit), an outstanding tool for creating and parsing emails, covering all relevant MIME standards making sure that emails are not qualified as SPAM. +* The data source for email merge messages to a number of recipients and be any ```IEnumerable``` object as well as ```DataTable```s. The data source for single emails can be any of the following types: ```Dictionary```, ```ExpandoObject```, ```DataRow```, any class instance or anonymous types. For class instances it's even allowed to use the name of parameter less methods in placeholders. +* Placeholders in the email can be formatted much like the features known from `string.Format` by using [SmartFormat.NET](https://github.com/axuno/MailMergeLib/wiki). SmartFormat is a fast and lean string parser and formatter, bringing a lot of additional options like conditional output depending on input data. +* Resulting emails are MimeMessages from [MimeKit](https://github.com/jstedfast/MimeKit), an outstanding tool for creating and parsing emails, covering all relevant MIME standards. * Support for international email address format. ### Sending email messages @@ -28,12 +28,12 @@ ### Both * Fine grained control over the whole process of email message generation and distribution. -* Clearly out-performs .NET ```System.Net.Mail```. * RFC standards compliant. * We aks you not to use ```MailMergeLib``` for sending unsolicited bulk email. ### Supported Frameworks * .Net Framework 4.6.2 and later -* .Net Standard 2.1 and later +* .Net Standard 2.1 +* NET 6.0 and later [![Paypal-Donations](https://img.shields.io/badge/Donate-PayPal-important.svg?style=flat-square)](https://www.paypal.com/donate?hosted_button_id=KSC3LRAR26AHN) diff --git a/README.md b/README.md index 49db477..24e9521 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ * HTML text may contain images from local hard disk, which will be automatically inserted as inline attachments. * For HTML text ```MailMergeLib ``` can generate a plain text representation. * Attachment sources can be files, streams or strings. -* The data source for email merge messages to a number of recipients and be any ```IEnumerable``` object as well as ```DataTable```s. The data source for single emails can be any of the following types: ```Dictionary```, ```ExpandoObject```, ```DataRow```, any class instances or anonymous types. For class instances it's even allowed to use the name of parameter less methods. -* Placeholders in the email can be formatted with any of the features known from string.Format by using [SmartFormat.NET](https://github.com/scottrippey/SmartFormat.NET/wiki). SmartFormat is a parser coming close to string.Format's speed, but bringing a lot of additional options like easy pluralization for many languages. -* Resulting emails are MimeMessages from [MimeKit](https://github.com/jstedfast/MimeKit), an outstanding tool for creating and parsing emails, covering all relevant MIME standards making sure that emails are not qualified as SPAM. +* The data source for email merge messages to a number of recipients and be any ```IEnumerable``` object as well as ```DataTable```s. The data source for single emails can be any of the following types: ```Dictionary```, ```ExpandoObject```, ```DataRow```, any class instance or anonymous types. For class instances it's even allowed to use the name of parameter less methods in placeholders. +* Placeholders in the email can be formatted much like the features known from `string.Format` by using [SmartFormat.NET](https://github.com/axuno/MailMergeLib/wiki). SmartFormat is a fast and lean string parser and formatter, bringing a lot of additional options like conditional output depending on input data. +* Resulting emails are MimeMessages from [MimeKit](https://github.com/jstedfast/MimeKit), an outstanding tool for creating and parsing emails, covering all relevant MIME standards. * Support for international email address format. ### Sending email messages @@ -36,13 +36,13 @@ ### Both * Fine grained control over the whole process of email message generation and distribution. -* Clearly out-performs .NET ```System.Net.Mail```. * RFC standards compliant. * We aks you not to use ```MailMergeLib``` for sending unsolicited bulk email. ### Supported Frameworks * .Net Framework 4.6.2 and later -* .Net Standard 2.1 and later +* .Net Standard 2.1 +* NET 6.0 and later [![Paypal-Donations](https://img.shields.io/badge/Donate-PayPal-important.svg?style=flat-square)](https://www.paypal.com/donate?hosted_button_id=KSC3LRAR26AHN) diff --git a/ReleaseNotes.md b/ReleaseNotes.md deleted file mode 100644 index 051ae34..0000000 --- a/ReleaseNotes.md +++ /dev/null @@ -1,120 +0,0 @@ -# 5.9.1 - -#### MailMergLib: -* **Minimum .Net Framework version is 4.6.2** -* Update package references - * AngleSharp 1.0.0 - * MailKit 3.4.3 - * MimeKit 3.4.3 - * YAXLib 4.0.0 - * SmartFormat.NET >=2.7.3 && < 3.0.0 -#### MailMergLib.Tests -* Update package references - -# 5.8.2 - -* Package references: Set highest major version of compatible dependencies for `MailKit`, `MimeKit`, `SmartFormat.NET`, `YAXLib` - -# 5.8.1 - -* Fixed: Large embedded images could throw `UriFormatException` (thanks to [PhnXnhP](https://github.com/PhnXnhP)) -* Package references: Set version range of compatible dependencies for `MailKit`, `MimeKit`, `SmartFormat.NET`, `YAXLib` - -# 5.8.0 - -* Updated dependencies to latest versions - * Fixed compatibility issues with updated dependencies (specifically `MailKit`, `MimeKit` and `YAXLib` with new major versions). - * Now referencing updated [SmartFormat.Net v2.7.2](https://github.com/axuno/SmartFormat) for formatting. - * No impact on `MailMergeLib` public API. -* Supported frameworks - * .Net Framework: 4.6.1 and later - * NetStandard: 2.1 and later - -# 5.7.1 -* Formatting (parse and format variables) can be switched off with ```MailMergeLib.EnableFormatter = false ```. Default is ```true``` -* Enabled SourceLink - -# 5.7.0.0 and 5.7.0.1 -* Encryption of Credential in Settings can now be disabled. **Breaking change**: disabled is the default. To enable, set ```Settings.CryptoEnabled = true```. -* SMTP settings can now be read from web.config as well as app.config -* Path checks (e.g. inline images, attachments) now respect Linux and MacOsX platform rules beside Windows. Linux tests run on Ubuntu. -* Classes in namespace 'MailMergLib.SmartFormatMail' were obsolete and are now removed. -* Updated string parser/formatter to **[SmartFormat.Net v2.5](https://github.com/axuno/SmartFormat)** - * **Data Sources** - * *New:* Added ```ValueTupleSource``` for ```ValueTuple```s - * *Changed:* ```SmartObjects``` and ```SmartObjectsSource``` are depreciated in favor of ```ValueTupleSource``` - * **Settings** - * *Breaking Change:* Internal string comparisons (i.e. for placeholder names) are no more culture-specific, but ```Ordinal``` or ```OrdinalIgnoreCase``` respectively. See discussion [under this issue](https://github.com/axuno/SmartFormat/issues/122). - * *Breaking Change:* Default ```ErrorAction``` is now ```ThrowError``` for parser and formatter, instead of ```Ignore``` - * **Other** - * *Changed:* Removed all members which were flagged obsolete since more than a year. -* Updated versions of other dependencies -* Dropped support of .NetFramework 4.5 (because of dependency to AngleSharp package). Minimum now is **4.6**. -* Dropped support of NetStandard1.6 (as announced) - -**Note**: v5.7.0.0 referenced MailKit/MimeKit 2.4.0 assemblies, which were not strongly signed for .NetFramework 4.6 in the NuGet packages - -# 5.6.1.0 -* Reverted back to v5.5.0 behavior: MessageConfig.FileBaseDirectory must be a full path only before the MailMergeMessage is processed (not already, when the property is set). -* Closes https://github.com/axuno/MailMergeLib/issues/18 -* Classes in namespace 'MailMergLib.SmartFormatMail' are obsolete. Use namespace 'SmartFormat' from dependency 'SmartFormat.Net' instead. -* This is the last minor version which supports netstandard1.6 -* Updated versions of dependencies -* More unit tests - -# 5.6.0.0 -* Classes in namespace 'MailMergLib.SmartFormatMail' are obsolete. Use namespace 'SmartFormat' from new dependency 'SmartFormat.Net' instead. -* This is the last version which supports netstandard1.6 -* Updated versions of dependencies - -# 5.5.0.0 -* [Support for JSON](https://github.com/scottrippey/SmartFormat.NET/wiki/Data-Sources) (JObject, JArray) -* [IsMatchFormatter](https://github.com/scottrippey/SmartFormat.NET/wiki/IsMatch) for evaluation of regular expressions -* Support for NetStandard 2.0 - -# 5.4.1.0 -New feature: Within a MailMergeSender.OnMessageFailure delegate the cause of the failure can be removed, so that the message can still be sent successfully. See details in https://github.com/axuno/MailMergeLib/wiki/Message-Error-Handling - -# 5.4.0.0 -**Changes:** -* Refactored raising events in ```MailMergeSender``` and ```MailMergeMessage``` -* Integrated [SmartFormat.NET v.2.2.0](https://github.com/scottrippey/SmartFormat.NET/) which resolves a rare issue, when ```null``` is a parameter of ```Send...``` methods of ```MailMergeSender```. -* Exceptions in ```MailSmartFormatter``` are all caught (i.e. not propagated to ```MailMergeSender```), never mind the exception settings in SmartFormatMail modules. This way they do no more have influence when generating the email message. Format and parse errors always end up in a ```MailMergeMessageException```. -* Added new exception of type ```ParseException``` for parsing issues in the template. ```ParseException``` are included as inner exceptions of ```MailMergeMessageException``` when building a message fails. -* Updated dependency: MailKit v2.0,1 MimeKit v2.0.1 (.NetStandard, .Net 4.5) and v1.22.0 (.Net 4.0) -* Updated dependency: AngleSharp 0.9.9.1 -* Updated dependencies in UnitTests: NUnit 3.9.0 and NUnit3TestAdapter 3.9.0 -* Added unit tests for raising events in ```MailMergeSender``` and ```MailMergeMessage``` -* Updated the [API documentation](https://axuno.net/mailmergelib/docs/) - -**Note:** -As MailKit and MileKit no longer support .Net 4.0, this is expected to become the last release which supplies packages for .Net 4.0. - -# 5.3.0.0 -**New components:** -* All relevant classes can be serialized and deserialized -* ```MessageStore``` for saving and loading ```MailMergeMessages``` -* Integrated SmartFormat.Net 2.1.0.2 for handling templates and ```{placeholders}```. Character string literals read from files or other resources (outside the code) are now treated like with ```string.Format``` inside of code. However, backslashes in filenames are not treated as escape characters. -* Templates: ```MailMergeMessage```s may contain text and/or html Templates which are inserted depending on certain conditions. ```Template```s may contain ```{placeholders}``` -* ```Credential``` class implementing already existing ```ICredentials``` -* New unit test for added and changed components - -**Changes:** -* Classes contain Equality methods -* Moved from System.Xml.Serialization to YAXLib v2.15 -* Updated dependency for MailKit v1.18.0, MimeKit v1.18.0 -* Moved solution to Visual Studio 2017 -* Migrated MailMergeLib.NetCore to the VS2017 xml project file format -* Added method ```MailMergeMessage.GetMimeMessages(IEnumerable data)``` -* ```MailMergeMessage.ConvertHtmlToPlainText()``` writes directly to ```MailMergeMessage.PlainText``` - -**Removed obsolete components:** -* HtmlTagHelper -* TextVariableManager -* HtmlAgilityPackHtmlConverter -* RegExHtmlConverter -* HtmlBodyBuilderRegEx - -**Documentation;** -* Wiki substantially extended -* API documentation updated diff --git a/Src/.editorconfig b/Src/.editorconfig new file mode 100644 index 0000000..058c1e9 --- /dev/null +++ b/Src/.editorconfig @@ -0,0 +1,264 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +[*.{cs,xml,csproj,sln}] + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space +indent_size = 4 +tab_width = 4 + +#Formatting - new line options + +trim_trailing_whitespace = true +insert_final_newline = true +#require members of anonymous types to be on separate lines +csharp_new_line_before_members_in_anonymous_types = true + +[*.cs] + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require members of object initializers to be on the same line +csharp_new_line_before_members_in_object_initializers = false +#require braces to be on a new line for object_collection_array_initializers, accessors, types, control_blocks, methods, lambdas, and properties (also known as "Allman" style) +csharp_new_line_before_open_brace = object_collection_array_initializers, accessors, types, control_blocks, methods, lambdas, properties + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +#require a space between a cast and the value +csharp_space_after_cast = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false +#use space after cast and colon and comma and dot and semicolon +csharp_space_after_cast = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_colon_for_base_or_interface_in_type_declaration = true +csharp_space_before_comma = false +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +# use space between brackets +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# use space before brackets +csharp_space_before_open_square_brackets = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true +#leave statements and member declarations on the same line +csharp_preserve_single_line_statements = true + +#Style - Code block preferences + +#prefer no curly braces if allowed +csharp_prefer_braces = false:suggestion + +#Style - expression bodied member options + +#prefer block bodies for accessors +csharp_style_expression_bodied_accessors = false:suggestion +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties when they will be a single line +csharp_style_expression_bodied_properties = when_on_single_line:suggestion + +#Style - expression level options + +#prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer events not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_event = false:suggestion +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion + +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_new_line_before_finally = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_parentheses = false +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Copyright file header +file_header_template = \nCopyright MailMergeLib Project maintainers and contributors.\nLicensed under the MIT license.\n diff --git a/Src/Directory.Build.props b/Src/Directory.Build.props index f8b3c39..efdc980 100644 --- a/Src/Directory.Build.props +++ b/Src/Directory.Build.props @@ -5,12 +5,12 @@ axuno gGmbH axuno gGmbH, project maintainers and contributors. $([System.DateTime]::Now.ToString(yyyy)) - Copyright 2011-$(CurrentYear) MailMergeLib Project + Copyright 2011-$(CurrentYear) axuno, MailMergeLib Project maintainers and contributers https://github.com/axuno/MailMergeLib.git true - 5.10.0 - 5.10.0 - 5.10.0 + 5.11.0 + 5.11.0 + 5.0.0.0 latest true Default diff --git a/Src/Documentation/MailMergeLib.hmxz b/Src/Documentation/MailMergeLib.hmxz new file mode 100644 index 0000000000000000000000000000000000000000..7d3dbb98ea23864569d00f59d822fe904979e2b6 GIT binary patch literal 37008 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≥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 + + + + Debug + AnyCPU + 2.0 + {2f90ef97-21c0-4ae8-8888-d7101da1f560} + 2017.9.26.0 + + Documentation + Documentation + Documentation + + .NET Framework 4.6.2 + Help\ + API Documentation + en-US + 100 + OnlyWarningsAndErrors + Website + False + False + False + True + C:\Program Files (x86)\HTML Help Workshop\ + 5.11.0 + 2 + False + C#, Visual Basic + Blank + False + Default2022 + False + Guid + MailMergeLib + + + %28C%29 2007-2023 axuno gGmbH + AboveNamespaces + + + + -1 + 100 + 100 + -1 + MailMergeLib 5.11 + Msdn + 100 + VS + AutoDocumentCtors, AutoDocumentDispose + + + + + + + + + + + + + + + + + + + + + + + Attributes, ExplicitInterfaceImplementations, InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected, EditorBrowsableNever + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OnBuildSuccess + + + + + + + MailMergeLib + Mail Merge Lib + + + + + ..\MailMergeLib\bin\Debug\net462\AngleSharp.dll + + + ..\MailMergeLib\bin\Debug\net462\MailKit.dll + + + ..\MailMergeLib\bin\Debug\net462\MimeKit.dll + + + ..\MailMergeLib\bin\Debug\net462\SmartFormat.Net.dll + + + ..\MailMergeLib\bin\Debug\net462\YAXLib.dll + + + \ No newline at end of file diff --git a/Src/Documentation/MergedEmail.png b/Src/Documentation/MergedEmail.png new file mode 100644 index 0000000000000000000000000000000000000000..b1deae0c8888aa6cfed764d618c198195854251b GIT binary patch literal 63224 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 LfhknV8^i+uCHIHF literal 0 HcmV?d00001 diff --git a/Src/Documentation/icons/MailMergeLib.png b/Src/Documentation/icons/MailMergeLib.png new file mode 100644 index 0000000000000000000000000000000000000000..309b41052dff480e4cc8f553def3c9c7c1687826 GIT binary patch literal 25243 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≥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 => EmailValidator.Validate(null, true, true), "Null Address"); + Assert.Throws(() => EmailValidator.Validate(null!, true, true), "Null Address"); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/FakeSmtpClient.cs b/Src/MailMergeLib.Tests/FakeSmtpClient.cs index bfdf1b0..df62cb1 100644 --- a/Src/MailMergeLib.Tests/FakeSmtpClient.cs +++ b/Src/MailMergeLib.Tests/FakeSmtpClient.cs @@ -23,9 +23,9 @@ public FakeSmtpClient() prop?.SetValue(this, SmtpCapabilities.Authentication); } - public Exception AuthenticateException { get; set; } = null; - public Exception ConnectException { get; set; } = null; - public Exception SendException { get; set; } = null; + public Exception? AuthenticateException { get; set; } = null; + public Exception? ConnectException { get; set; } = null; + public Exception? SendException { get; set; } = null; public override Task AuthenticateAsync(SaslMechanism mechanism, CancellationToken cancellationToken = new CancellationToken()) { @@ -77,13 +77,13 @@ public override Task ConnectAsync(Stream stream, string host, int port = 0, Secu } public override Task SendAsync(FormatOptions options, MimeMessage message, - CancellationToken cancellationToken = new CancellationToken(), ITransferProgress progress = null) + CancellationToken cancellationToken = new CancellationToken(), ITransferProgress? progress = null) { throw new NotImplementedException(); } public override Task SendAsync(FormatOptions options, MimeMessage message, MailboxAddress sender, IEnumerable recipients, - CancellationToken cancellationToken = new CancellationToken(), ITransferProgress progress = null) + CancellationToken cancellationToken = new CancellationToken(), ITransferProgress? progress = null) { throw new NotImplementedException(); } @@ -163,14 +163,14 @@ protected override void OnRecipientNotAccepted(MimeMessage message, MailboxAddre } public override string Send(FormatOptions options, MimeMessage message, CancellationToken cancellationToken = new CancellationToken(), - ITransferProgress progress = null) + ITransferProgress? progress = null) { if (SendException != null) throw SendException; return string.Empty; } public override string Send(FormatOptions options, MimeMessage message, MailboxAddress sender, IEnumerable recipients, - CancellationToken cancellationToken = new CancellationToken(), ITransferProgress progress = null) + CancellationToken cancellationToken = new CancellationToken(), ITransferProgress? progress = null) { if (SendException != null) throw SendException; return string.Empty; @@ -187,14 +187,14 @@ public override string Send(FormatOptions options, MimeMessage message, MailboxA */ public override string Send(MimeMessage message, CancellationToken cancellationToken = new CancellationToken(), - ITransferProgress progress = null) + ITransferProgress? progress = null) { if (SendException != null) throw SendException; // in use return string.Empty; } public override Task SendAsync(MimeMessage message, CancellationToken cancellationToken = new CancellationToken(), - ITransferProgress progress = null) + ITransferProgress? progress = null) { if (SendException != null) throw SendException; @@ -202,14 +202,14 @@ public override string Send(FormatOptions options, MimeMessage message, MailboxA } public override string Send(MimeMessage message, MailboxAddress sender, IEnumerable recipients, - CancellationToken cancellationToken = new CancellationToken(), ITransferProgress progress = null) + CancellationToken cancellationToken = new CancellationToken(), ITransferProgress? progress = null) { if (SendException != null) throw SendException; return string.Empty; } public override Task SendAsync(MimeMessage message, MailboxAddress sender, IEnumerable recipients, - CancellationToken cancellationToken = new CancellationToken(), ITransferProgress progress = null) + CancellationToken cancellationToken = new CancellationToken(), ITransferProgress? progress = null) { if (SendException != null) throw SendException; diff --git a/Src/MailMergeLib.Tests/FileMessageInfo.cs b/Src/MailMergeLib.Tests/FileMessageInfo.cs index 28a25b7..8627bb0 100644 --- a/Src/MailMergeLib.Tests/FileMessageInfo.cs +++ b/Src/MailMergeLib.Tests/FileMessageInfo.cs @@ -23,8 +23,8 @@ public void CompareFileMessageInfo() [Test] public void CompareNullFileMessageInfo() { - var info = new MailMergeLib.MessageStore.FileMessageInfo { Id = 1, Category = null, Comments = null, Description = "No description", Data = "Some data hint", MessageEncoding = null, MessageFile = null }; - var otherInfo = new MailMergeLib.MessageStore.FileMessageInfo { Id = 1, Category = null, Comments = null, Description = "No description", Data = "Some data hint", MessageEncoding = null, MessageFile = null }; + var info = new MailMergeLib.MessageStore.FileMessageInfo { Id = 1, Category = null, Comments = null, Description = "No description", Data = "Some data hint", MessageEncoding = null!, MessageFile = null! }; + var otherInfo = new MailMergeLib.MessageStore.FileMessageInfo { Id = 1, Category = null, Comments = null, Description = "No description", Data = "Some data hint", MessageEncoding = null!, MessageFile = null! }; Assert.AreEqual(info, otherInfo); Assert.IsFalse(info.Equals(null)); diff --git a/Src/MailMergeLib.Tests/FileMessageStore_Serialization.cs b/Src/MailMergeLib.Tests/FileMessageStore_Serialization.cs index 419bb9d..bc21ec2 100644 --- a/Src/MailMergeLib.Tests/FileMessageStore_Serialization.cs +++ b/Src/MailMergeLib.Tests/FileMessageStore_Serialization.cs @@ -28,7 +28,7 @@ public void GetMessageInfosFromFiles() foreach (var info in messageInfos) { // messageInfos come from fast xml scan in MessageInfoBase, Info of the Messsage comes from YAXLib deserialization - Assert.AreEqual(info, info.LoadMessage().Info); + Assert.AreEqual(info, info.LoadMessage()!.Info); } } @@ -60,12 +60,10 @@ public void StreamSerialization() var stream = new MemoryStream(); fms.Serialize(stream, Encoding.UTF8); stream.Position = 0; - var restoredFms = FileMessageStore.Deserialize(stream, Encoding.UTF8); + var restoredFms = FileMessageStore.Deserialize(stream, Encoding.UTF8)!; Assert.True(fms.Equals(restoredFms)); Assert.AreEqual(fms.GetHashCode(), restoredFms.GetHashCode()); - Assert.False(fms.Equals(null)); - Assert.True(fms.Equals(fms)); Assert.False(fms.Equals(new object())); } } \ No newline at end of file diff --git a/Src/MailMergeLib.Tests/Helper.cs b/Src/MailMergeLib.Tests/Helper.cs index 7986f0c..96c3839 100644 --- a/Src/MailMergeLib.Tests/Helper.cs +++ b/Src/MailMergeLib.Tests/Helper.cs @@ -1,5 +1,9 @@ using System; using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; using System.Reflection; namespace MailMergeLib.Tests; @@ -16,10 +20,10 @@ internal class Helper /// public static string GetCodeBaseDirectory() { - return Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().Location).LocalPath); + return Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().Location).LocalPath)!; } - internal static int Compare(Stream a, Stream b) + internal static int Compare(Stream? a, Stream? b) { if (a == null && b == null) return 0; @@ -40,4 +44,46 @@ internal static int Compare(Stream a, Stream b) } return 0; } + + /// + /// The method will select the first free port after and + /// including the given . + /// + /// The first free TCP port found. + /// If no free port could be found. + + internal static int GetFreeTcpPort(int startPort = 2000) + { + for (var i = startPort; i <= 0xFFFF; i++) + { + if (IsFreePort(i) && CanBindPort(i)) return i; + } + + throw new InvalidOperationException("No free TCP port found"); + } + + private static bool IsFreePort(int port) + { + var properties = IPGlobalProperties.GetIPGlobalProperties(); + var listeners = properties.GetActiveTcpListeners(); + var openPorts = listeners.Select(item => item.Port).ToArray(); + return openPorts.All(openPort => openPort != port); + } + + private static bool CanBindPort(int port) + { + try + { + var localEndPoint = new IPEndPoint(IPAddress.Any, port); + using var listener = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + listener.Bind(localEndPoint); + } + catch + { + // e.g. because of "Permission denied" or other reason + return false; + } + + return true; + } } \ No newline at end of file diff --git a/Src/MailMergeLib.Tests/HtmlBodyBuilderTest.cs b/Src/MailMergeLib.Tests/HtmlBodyBuilderTest.cs index 3d7a441..58d0abd 100644 --- a/Src/MailMergeLib.Tests/HtmlBodyBuilderTest.cs +++ b/Src/MailMergeLib.Tests/HtmlBodyBuilderTest.cs @@ -13,7 +13,7 @@ public void SetHtmlBuilderDocBaseUri_UriFormatException(string baseUri) { var mmm = new MailMergeMessage("subject", "plain text", ""); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); Assert.Throws(() => hbb.DocBaseUri = baseUri); } @@ -26,7 +26,7 @@ public void SetHtmlBuilderDocBaseUri_NoException(string baseUri) { var mmm = new MailMergeMessage("subject", "plain text", ""); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); Assert.DoesNotThrow(() => hbb.DocBaseUri = baseUri); } @@ -35,7 +35,7 @@ public void ScriptTagRemoved() { var mmm = new MailMergeMessage("subject_to_set", "plain text", "some body"); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); var html = hbb.GetBodyPart(); Assert.IsTrue(html.ToString().Contains("some body")); Assert.IsTrue(!html.ToString().Contains("script")); @@ -47,7 +47,7 @@ public void ExistingTitleTagSetWithSubject() var subjectToSet = "subject_to_set"; var mmm = new MailMergeMessage(subjectToSet, "plain text", "abc"); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); var html = hbb.GetBodyPart(); Assert.IsTrue(html.ToString().Contains(subjectToSet)); } @@ -57,7 +57,7 @@ public void NonExistingTitleTagSetWithSubject() { var subjectToSet = "subject_to_set"; var mmm = new MailMergeMessage(subjectToSet, "plain text", ""); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); var html = hbb.GetBodyPart(); Assert.IsTrue(!html.ToString().Contains(subjectToSet)); } @@ -69,7 +69,7 @@ public void EmbeddedDataImage_ShouldNotBeTouched() var imageTag = $"\"1Pixel\""; var mmm = new MailMergeMessage("", "plain text", $"{imageTag}"); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); var html = hbb.GetBodyPart(); Assert.That(html.ToString(), Does.Contain(imageTag)); } @@ -85,7 +85,7 @@ public void LargeEmbeddedDataImage_ShouldNotThrow() var imageTag = $"\"1Pixel\""; var mmm = new MailMergeMessage("", "plain text", $"{imageTag}"); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); Assert.That(code: () => hbb.GetBodyPart(), Throws.Nothing); } } \ No newline at end of file diff --git a/Src/MailMergeLib.Tests/MailMergeLib.Tests.csproj b/Src/MailMergeLib.Tests/MailMergeLib.Tests.csproj index 28207d6..c323b82 100644 --- a/Src/MailMergeLib.Tests/MailMergeLib.Tests.csproj +++ b/Src/MailMergeLib.Tests/MailMergeLib.Tests.csproj @@ -3,17 +3,22 @@ Test project for MailMergeLib MailMergeLib.UnitTest - net6.0;net462 + netcoreapp3.1;net462;net6.0 false MailMergeLib.Tests ../MailMergeLib/MailMergeLib.snk true true true - disable + enable latest + + + true + + prompt 4 @@ -30,10 +35,10 @@ - + - + diff --git a/Src/MailMergeLib.Tests/MessageFactory.cs b/Src/MailMergeLib.Tests/MessageFactory.cs index d78dee6..5b9a7ba 100644 --- a/Src/MailMergeLib.Tests/MessageFactory.cs +++ b/Src/MailMergeLib.Tests/MessageFactory.cs @@ -117,8 +117,8 @@ public static MailMergeMessage GetHtmlAndPlainMessage_WithTemplates(out Dictiona Comments = "Comments to the message", Data = "Data hint" }, - HtmlText = "{:template(Salutation)}and so on", - PlainText = "{:template(Salutation)}and so on...", + HtmlText = "{:t(Salutation)}and so on", + PlainText = "{:t(Salutation)}and so on...", Templates = { new Template("Salutation", @@ -136,7 +136,7 @@ public static MailMergeMessage GetHtmlAndPlainMessage_WithTemplates(out Dictiona Config = { Organization = "MailMergeLib Inc.", - CharacterEncoding = Encoding.UTF8, + CharacterEncoding = Encoding.UTF8 } }; mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.From, "{FromAddr}")); diff --git a/Src/MailMergeLib.Tests/MessageInfo.cs b/Src/MailMergeLib.Tests/MessageInfo.cs index bdf488d..bbfeaf6 100644 --- a/Src/MailMergeLib.Tests/MessageInfo.cs +++ b/Src/MailMergeLib.Tests/MessageInfo.cs @@ -24,7 +24,6 @@ public void ReadAndCompareInfo() // MessageInfo var deserializedInfo = MailMergeLib.MessageStore.MessageInfoBase.Read(xml); Assert.AreEqual(info, deserializedInfo); - Assert.IsFalse(info.Equals(null)); Assert.IsTrue(info.Equals(info)); Assert.IsFalse(info.Equals(new object())); Assert.AreEqual(info.GetHashCode(), deserializedInfo.GetHashCode()); @@ -44,7 +43,6 @@ public void ReadAndCompareEmptyInfo() // MessageInfo var deserializedInfo = MailMergeLib.MessageStore.MessageInfoBase.Read(xml); Assert.AreEqual(info, deserializedInfo); - Assert.IsFalse(info.Equals(null)); Assert.IsTrue(info.Equals(info)); Assert.IsFalse(info.Equals(new object())); Assert.AreEqual(info.GetHashCode(), deserializedInfo.GetHashCode()); diff --git a/Src/MailMergeLib.Tests/Message_Config.cs b/Src/MailMergeLib.Tests/Message_Config.cs index e601595..b4eec7e 100644 --- a/Src/MailMergeLib.Tests/Message_Config.cs +++ b/Src/MailMergeLib.Tests/Message_Config.cs @@ -2,13 +2,14 @@ using System.IO; using MailMergeLib.Tests.NUnit; using NUnit.Framework; +using SmartFormat.Core.Settings; namespace MailMergeLib.Tests; [TestFixture] public class Message_Config { - private MessageConfig _msgConfig = new MessageConfig(); + private readonly MessageConfig _msgConfig = new(); [TestCase(" \t", "")] [TestCase(" ", "")] @@ -69,7 +70,7 @@ public void FileBaseDirectory_must_be_full_path_when_processing_the_message(stri [TestCase("noFullPath", null)] [TestCase("..\\..\\relativePath", null, ExcludePlatform = nameof(OpSys.Linux) + "," + nameof(OpSys.MacOsX))] [TestCase("../../relativePath", null, IncludePlatform = nameof(OpSys.Linux) + "," + nameof(OpSys.MacOsX))] - public void HtmlBodyBuilderDocBaseUri_vs_MessageConfig_FileBaseDirectory(string path, string expected) + public void HtmlBodyBuilderDocBaseUri_vs_MessageConfig_FileBaseDirectory(string path, string? expected) { var mmm = new MailMergeMessage("subject", "plain text", ""); mmm.Config.FileBaseDirectory = path; @@ -77,11 +78,11 @@ public void HtmlBodyBuilderDocBaseUri_vs_MessageConfig_FileBaseDirectory(string HtmlBodyBuilder hbb; if (expected == null) { - Assert.Throws(() => { hbb = new HtmlBodyBuilder(mmm, (object) null); }); + Assert.Throws(() => { hbb = new HtmlBodyBuilder(mmm, null); }); } else { - hbb = new HtmlBodyBuilder(mmm, (object) null); + hbb = new HtmlBodyBuilder(mmm, null); Assert.AreEqual(expected, hbb.DocBaseUri); } } @@ -93,7 +94,7 @@ public void MessageConfig_FileBaseDirectory_cannot_be_changed_by_Html_Base_Tag() ""); mmm.Config.FileBaseDirectory = Path.GetTempPath(); - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); Assert.AreEqual( new Uri(mmm.Config.FileBaseDirectory), hbb.DocBaseUri); } @@ -105,7 +106,7 @@ public void Empty_MessageConfig_FileBaseDirectory_is_changed_by_Html_Base_Tag() $""); mmm.Config.FileBaseDirectory = string.Empty; - var hbb = new HtmlBodyBuilder(mmm, (object) null); + var hbb = new HtmlBodyBuilder(mmm, null); hbb.GetBodyPart(); Assert.AreEqual(baseTagHref, hbb.DocBaseUri); } @@ -120,4 +121,17 @@ public void HashCode() Assert.AreEqual(mc1.GetHashCode(), mc1.GetHashCode()); Assert.AreEqual(mc2.GetHashCode(), mc2.GetHashCode()); } -} \ No newline at end of file + + [Test] + public void SmartFormatterConfig_Change_Retains_Existing_SmartSettings() + { + var mmm = new MailMergeMessage("subject", "plain text"); + const char alignmentFillCharacter = '#'; // defaults to blank + mmm.SmartFormatter.Settings.Formatter.AlignmentFillCharacter = alignmentFillCharacter; + // Change the setting triggers creation of a new SmartFormatter instance + mmm.Config.SmartFormatterConfig.CaseSensitivity = CaseSensitivityType.CaseInsensitive; + + // Setting should persist + Assert.That(mmm.SmartFormatter.Settings.Formatter.AlignmentFillCharacter, Is.EqualTo(alignmentFillCharacter)); + } +} diff --git a/Src/MailMergeLib.Tests/Message_Equality.cs b/Src/MailMergeLib.Tests/Message_Equality.cs index 654efb4..60ef5e0 100644 --- a/Src/MailMergeLib.Tests/Message_Equality.cs +++ b/Src/MailMergeLib.Tests/Message_Equality.cs @@ -7,13 +7,13 @@ namespace MailMergeLib.Tests; [TestFixture] public class Message_Equality { - private MailMergeAddress _addr1a = new MailMergeAddress(MailAddressType.To, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; - private MailMergeAddress _addr2a = new MailMergeAddress(MailAddressType.Bcc, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; + private readonly MailMergeAddress _addr1a = new(MailAddressType.To, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; + private readonly MailMergeAddress _addr2a = new(MailAddressType.Bcc, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; - private MailMergeAddress _addr3 = new MailMergeAddress(MailAddressType.From, "display name 3", "address3@test.com") { DisplayNameCharacterEncoding = Encoding.UTF8 }; + private readonly MailMergeAddress _addr3 = new(MailAddressType.From, "display name 3", "address3@test.com") { DisplayNameCharacterEncoding = Encoding.UTF8 }; - private MailMergeAddress _addr1b = new MailMergeAddress(MailAddressType.To, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; - private MailMergeAddress _addr2b = new MailMergeAddress(MailAddressType.Bcc, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; + private readonly MailMergeAddress _addr1b = new(MailAddressType.To, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; + private readonly MailMergeAddress _addr2b = new(MailAddressType.Bcc, "display name", "address@test.com") { DisplayNameCharacterEncoding = Encoding.UTF32 }; [Test] public void MailMergeAddressEquality() @@ -35,7 +35,6 @@ public void MailMergeAddressCollectionEquality() addrColl2.Add(_addr3); Assert.False(addrColl1.Equals(addrColl2)); - Assert.False(addrColl1.Equals(null)); } [TestCase(MailAddressType.To)] @@ -115,9 +114,9 @@ public void StringAttachments() [Test] public void MailMergeMessage() { - var mmm1 = MessageFactory.GetMessageWithAllPropertiesSet(); - var mmm2 = MailMergeLib.MailMergeMessage.Deserialize(mmm1.Serialize()); - var mmm3 = MessageFactory.GetMessageWithAllPropertiesSet(); + var mmm1 = MessageFactory.GetMessageWithAllPropertiesSet()!; + var mmm2 = MailMergeLib.MailMergeMessage.Deserialize(mmm1.Serialize())!; + var mmm3 = MessageFactory.GetMessageWithAllPropertiesSet()!; Assert.IsTrue(mmm1.Equals(mmm2)); Assert.IsTrue(mmm1.Equals(mmm3)); @@ -128,7 +127,6 @@ public void MailMergeMessage() mmm3.MailMergeAddresses.RemoveAt(0); Assert.IsFalse(mmm1.Equals(mmm3)); - Assert.IsFalse(mmm1.Equals(default(object))); Assert.IsTrue(mmm1.Equals(mmm1)); Assert.IsFalse(mmm1.Equals(new object())); } diff --git a/Src/MailMergeLib.Tests/Message_Html.cs b/Src/MailMergeLib.Tests/Message_Html.cs index 7dcfa57..4a09c02 100644 --- a/Src/MailMergeLib.Tests/Message_Html.cs +++ b/Src/MailMergeLib.Tests/Message_Html.cs @@ -6,7 +6,6 @@ using AngleSharp.Html.Parser; using MimeKit; using NUnit.Framework; -using SmartFormat.Core.Settings; namespace MailMergeLib.Tests; @@ -52,7 +51,7 @@ public void EmptyContent() catch (Exception e) { Assert.IsTrue(e is MailMergeMessage.MailMergeMessageException); - Assert.IsTrue(e.InnerException is MailMergeMessage.EmtpyContentException); + Assert.IsTrue(e.InnerException is MailMergeMessage.EmptyContentException); } } @@ -126,8 +125,7 @@ public void HtmlStreamAttachments() Assert.IsTrue(mmm.StreamAttachments.Count == 1); mmm.StreamAttachments.Clear(); Assert.IsTrue(mmm.StreamAttachments.Count == 0); - mmm.StreamAttachments = null; - Assert.IsTrue(mmm.StreamAttachments != null && mmm.StreamAttachments.Count == 0); + mmm.StreamAttachments = streamAttachments; Assert.IsTrue(mmm.StreamAttachments.Count == 2); } @@ -154,7 +152,7 @@ public void HtmlMailMergeWithStreamAttachment() mmm.StreamAttachments.Clear(); mmm.StringAttachments.Clear(); - using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(text ?? string.Empty)); + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(text)); mmm.StreamAttachments.Add(new StreamAttachment(stream, streamAttFilename, "text/plain")); var msg = mmm.GetMimeMessage(dataItem); @@ -291,7 +289,7 @@ public void ConvertHtmlToPlainText() [TestCase("{Name} {SenderAddr}", "John test@specimen.com")] [TestCase("{Name {SenderAddr}", "{Name {SenderAddr}")] // parsing error [TestCase("{NotExisting}", "{NotExisting}")] // formatting error - [TestCase(null, null)] + [TestCase("", "")] public void SearchAndReplace(string text, string expected) { var dataItem = new @@ -310,7 +308,7 @@ public void SearchAndReplace(string text, string expected) [TestCase("{Name} {SenderAddr}", "John test@specimen.com")] [TestCase("{Name {SenderAddr}", "{Name {SenderAddr}")] // parsing error [TestCase("{NotExisting}", "{NotExisting}")] // formatting error - [TestCase(null, null)] + [TestCase("", "")] public void SearchAndReplaceFilename(string text, string expected) { var dataItem = new @@ -325,4 +323,4 @@ public void SearchAndReplaceFilename(string text, string expected) var result = mmm.SearchAndReplaceVarsInFilename(text, dataItem); Assert.AreEqual(expected, result); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Message_Serialization.cs b/Src/MailMergeLib.Tests/Message_Serialization.cs index 582f674..615f470 100644 --- a/Src/MailMergeLib.Tests/Message_Serialization.cs +++ b/Src/MailMergeLib.Tests/Message_Serialization.cs @@ -15,7 +15,7 @@ public void SerializationFromToString() { var mmm = MessageFactory.GetMessageWithAllPropertiesSet(); var result = mmm.Serialize(); - var back = MailMergeMessage.Deserialize(result); + var back = MailMergeMessage.Deserialize(result)!; Assert.True(mmm.Equals(back)); Assert.AreEqual(mmm.Serialize(), back.Serialize()); @@ -27,7 +27,7 @@ public void SerializationFromToFile() var filename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var mmm = MessageFactory.GetMessageWithAllPropertiesSet(); mmm.Serialize(filename, Encoding.Unicode); - var back = MailMergeMessage.Deserialize(filename, Encoding.Unicode); + var back = MailMergeMessage.Deserialize(filename, Encoding.Unicode)!; Assert.True(mmm.Equals(back)); Assert.AreEqual(mmm.Serialize(), back.Serialize()); @@ -41,7 +41,7 @@ public void SerializationFromToStream() mmm.Serialize(msOut, Encoding.UTF8); msOut.Position = 0; - var back = MailMergeMessage.Deserialize(msOut, Encoding.UTF8); + var back = MailMergeMessage.Deserialize(msOut, Encoding.UTF8)!; msOut.Close(); msOut.Dispose(); @@ -69,7 +69,7 @@ public void MessageClearExternalInlineAttachments() public void DeserializeMinimalisticXml() { // an empty deserialized message and new message must be equal - var mmm = MailMergeMessage.Deserialize(""); + var mmm = MailMergeMessage.Deserialize("")!; Assert.True(new MailMergeMessage().Equals(mmm)); } @@ -96,9 +96,9 @@ public void SerializeTemplates() }; var result = templates.Serialize(); var back = new MailMergeMessage(); - back.Templates.AddRange(Templates.Templates.Deserialize(result)); + back.Templates.AddRange(Templates.Templates.Deserialize(result)!); Assert.True(templates.Equals(back.Templates)); Assert.AreEqual(templates.Serialize(), back.Templates.Serialize()); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Message_SmartFormatter.cs b/Src/MailMergeLib.Tests/Message_SmartFormatter.cs index 8820f15..f5abed2 100644 --- a/Src/MailMergeLib.Tests/Message_SmartFormatter.cs +++ b/Src/MailMergeLib.Tests/Message_SmartFormatter.cs @@ -16,16 +16,19 @@ public class Message_SmartFormatter private class TestClass { public string Email { set; get; } = "test@example.com"; +#pragma warning disable CA1822 public string GetContinent() { return "Europe"; } +#pragma warning restore CA1822 private TestClass2 NewTestClass { get; set; } = new TestClass2(); public TestClass2 GetNewTestClass() { return NewTestClass; } } + private class TestClass2 { public string City { get; set; } = "New York"; @@ -41,15 +44,15 @@ public void PlaceholderCaseSensitivityTest() var mmm = new MailMergeMessage(); mmm.Config.SmartFormatterConfig.CaseSensitivity = CaseSensitivityType.CaseSensitive; - mmm.Config.SmartFormatterConfig.FormatErrorAction = - mmm.Config.SmartFormatterConfig.ParseErrorAction = ErrorAction.OutputErrorInResult; + mmm.Config.SmartFormatterConfig.FormatErrorAction = ErrorAction.OutputErrorInResult; + mmm.Config.SmartFormatterConfig.ParseErrorAction = ErrorAction.OutputErrorInResult; - var smf = mmm.SmartFormatter; - Assert.AreEqual(dataItem.Email, smf.Format("{Email}", dataItem)); - Assert.AreNotEqual(dataItem.Email, smf.Format("{EmAiL}", dataItem)); - // The following is the same as smf.Settings.CaseSensitivity = CaseSensitivityType.CaseInsensitive + Assert.AreEqual(dataItem.Email, mmm.SmartFormatter.Format("{Email}", dataItem)); + Assert.AreNotEqual(dataItem.Email, mmm.SmartFormatter.Format("{EmAiL}", dataItem)); + // Changing the the SmartFormatterConfig settings creates a new instance of + // the SmartFormatter inside MailMergeMessage mmm.Config.SmartFormatterConfig.CaseSensitivity = CaseSensitivityType.CaseInsensitive; - var actual = smf.Format("{EmAiL}", dataItem); + var actual = mmm.SmartFormatter.Format("{EmAiL}", dataItem); Assert.AreEqual(dataItem.Email, actual); } @@ -58,20 +61,15 @@ public void PlaceholderCaseSensitivityTest() public void DataTypeTests() { // ******** Initialize ******** - string result; - string expected; - object dataItem; var culture = CultureInfo.GetCultureInfo("en-US"); - var smf = new MailSmartFormatter - { - Settings = { FormatErrorAction = ErrorAction.Ignore, ParseErrorAction = ErrorAction.ThrowError } - }; - + var smf = new MailSmartFormatter(new SmartFormatterConfig(), new SmartSettings()); + smf.Settings.Formatter.ErrorAction = FormatErrorAction.Ignore; + smf.Settings.Parser.ErrorAction = ParseErrorAction.ThrowError; // ******** Class instances ******** - dataItem = new TestClass(); + object dataItem = new TestClass(); var text = "Lorem ipsum dolor. Email={Email}, Continent={GetContinent}, City={GetNewTestClass.City}."; - result = smf.Format(culture, text, dataItem); - expected = string.Format($"Lorem ipsum dolor. Email={((TestClass)dataItem).Email}, Continent={((TestClass)dataItem).GetContinent()}, City={((TestClass)dataItem).GetNewTestClass().City}."); + var result = smf.Format(culture, text, dataItem); + var expected = string.Format($"Lorem ipsum dolor. Email={((TestClass)dataItem).Email}, Continent={((TestClass)dataItem).GetContinent()}, City={((TestClass)dataItem).GetNewTestClass().City}."); Assert.AreEqual(expected, result); Console.WriteLine("Class instances: passed"); @@ -171,7 +169,7 @@ public void DataTypeTests() catch (MailMergeMessage.MailMergeMessageException ex) { // will throw because of incomplete mail addresses, but Subject should contain placeholders replaced with content - result = ex.MimeMessage.Subject; + result = ex.MimeMessage?.Subject; } expected = text.Replace("{Email}", "test@example.com").Replace("{Continent}", "Europe"); @@ -204,9 +202,9 @@ public void DataTypeTests() Console.WriteLine("Format error (missing variable): passed"); // ******** Culture ******** - result = smf.Format(culture, "{Date:MMMM}", new DateTime(2018,01,01)); + result = smf.Format(culture, "{Date:d:MMMM}", new DateTime(2018,01,01)); Assert.AreEqual("January", result); Console.WriteLine("Culture: passed"); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Message_Templates.cs b/Src/MailMergeLib.Tests/Message_Templates.cs index 720e7b6..7322586 100644 --- a/Src/MailMergeLib.Tests/Message_Templates.cs +++ b/Src/MailMergeLib.Tests/Message_Templates.cs @@ -13,7 +13,7 @@ class Message_Templates [Test] public void Template() { - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out Dictionary variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var variables); var msg = mmm.GetMimeMessage(variables); @@ -37,7 +37,7 @@ public void Template() .First(t => t.Type == PartType.Plain) .Value.Replace("{FirstName}", variables["FirstName"]))); - // Neither DefaultKey nore Key of the template are set: gets the first part + // Neither DefaultKey nor Key of the template are set: gets the first part mmm.Templates[0].DefaultKey = null; mmm.Templates[0].Key = null; // Remove so that only max. 2 parts for 1 key are left @@ -58,7 +58,7 @@ public void Template() [Test] public void Part_in_Template_Changes() { - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out Dictionary variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var variables); Assert.Throws(() => mmm.Templates["This-is-definitely-an-illegal-Key-not-existing-in-any-Part"] = new Template()); @@ -76,23 +76,23 @@ public void Part_in_Template_Changes() [Test] public void FileSerialization() { - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out Dictionary variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var _); var templates = mmm.Templates; var tempFilename = Path.GetTempFileName(); templates.Serialize(tempFilename, Encoding.UTF8); - Assert.True(templates.Equals(Templates.Templates.Deserialize(tempFilename, Encoding.UTF8))); + Assert.True(templates.Equals(Templates.Templates.Deserialize(tempFilename, Encoding.UTF8)!)); File.Delete(tempFilename); } [Test] public void StreamSerialization() { - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out Dictionary variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var _); var templates = mmm.Templates; var stream = new MemoryStream(); templates.Serialize(stream, Encoding.UTF8); stream.Position = 0; - var restoredTemplates = Templates.Templates.Deserialize(stream, Encoding.UTF8); + var restoredTemplates = Templates.Templates.Deserialize(stream, Encoding.UTF8)!; Assert.True(templates.Equals(restoredTemplates)); Assert.True(templates.Equals(templates)); @@ -102,7 +102,7 @@ public void StreamSerialization() [Test] public void TemplateTest() { - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out Dictionary variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var variables); var t = mmm.Templates[0]; Assert.DoesNotThrow(() => { @@ -110,14 +110,10 @@ public void TemplateTest() }); Assert.Throws(() => t.Key = "This-is-definitely-an-illegal-Key-not-existing-in-any-Part"); - Assert.False(t.Equals(null)); - Assert.True(t.Equals(t)); Assert.False(t.Equals(new object())); // Equality with null members - t.Name = t.Key = t.DefaultKey = null; - Assert.False(t.Equals(null)); - Assert.True(t.Equals(t)); + t.Key = t.DefaultKey = null; Assert.False(t.Equals(new object())); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Message_VariableExceptions.cs b/Src/MailMergeLib.Tests/Message_VariableExceptions.cs index 8bf5fc3..085ad08 100644 --- a/Src/MailMergeLib.Tests/Message_VariableExceptions.cs +++ b/Src/MailMergeLib.Tests/Message_VariableExceptions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using MailMergeLib.Templates; using NUnit.Framework; @@ -46,22 +45,22 @@ public void MissingVariableAndAttachmentsExceptions() foreach (var ex in exceptions.InnerExceptions.Where(ex => !(ex is MailMergeMessage .AttachmentException))) { - if (ex is MailMergeMessage.VariableException) + if (ex is MailMergeMessage.VariableException exception) { - Assert.AreEqual(9, (ex as MailMergeMessage.VariableException).MissingVariable.Count); + Assert.AreEqual(9, exception.MissingVariable.Count); Console.WriteLine($"{nameof(MailMergeMessage.VariableException)} thrown successfully:"); Console.WriteLine("Missing variables: " + string.Join(", ", - (ex as MailMergeMessage.VariableException).MissingVariable)); + exception.MissingVariable)); Console.WriteLine(); } - if (ex is MailMergeMessage.AddressException) + if (ex is MailMergeMessage.AddressException addressException) { Console.WriteLine($"{nameof(MailMergeMessage.AddressException)} thrown successfully:"); - Console.WriteLine((ex as MailMergeMessage.AddressException).Message); + Console.WriteLine(addressException.Message); Console.WriteLine(); - Assert.That((ex as MailMergeMessage.AddressException).Message == "No recipients." || - (ex as MailMergeMessage.AddressException).Message == "No from address."); + Assert.That(addressException.Message == "No recipients." || + addressException.Message == "No from address."); } } @@ -73,9 +72,9 @@ public void MissingVariableAndAttachmentsExceptions() { Console.WriteLine($"{nameof(MailMergeMessage.AttachmentException)} thrown successfully:"); Console.WriteLine("Missing files: " + - string.Join(", ", (ex as MailMergeMessage.AttachmentException).BadAttachment)); + string.Join(", ", (ex as MailMergeMessage.AttachmentException)?.BadAttachment!)); Console.WriteLine(); - Assert.AreEqual(1, (ex as MailMergeMessage.AttachmentException).BadAttachment.Count); + Assert.AreEqual(1, (ex as MailMergeMessage.AttachmentException)?.BadAttachment.Count); } } @@ -106,8 +105,7 @@ public void MissingVariableAndAttachmentsExceptions() [Test] public void Template() { - Dictionary variables; - var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out variables); + var mmm = MessageFactory.GetHtmlAndPlainMessage_WithTemplates(out var variables); var msg = mmm.GetMimeMessage(variables); diff --git a/Src/MailMergeLib.Tests/Message_Variables.cs b/Src/MailMergeLib.Tests/Message_Variables.cs index 914b1dd..0a4c7e5 100644 --- a/Src/MailMergeLib.Tests/Message_Variables.cs +++ b/Src/MailMergeLib.Tests/Message_Variables.cs @@ -4,7 +4,6 @@ using System.Linq; using Newtonsoft.Json.Linq; using NUnit.Framework; -using SmartFormat; namespace MailMergeLib.Tests; @@ -76,16 +75,16 @@ public void MissingVariableAndAttachmentsExceptions() // Inline file missing Console.WriteLine($"{nameof(MailMergeMessage.AttachmentException)} thrown successfully:"); Console.WriteLine("Missing inline attachment files: " + - string.Join(", ", (attExceptions[0] as MailMergeMessage.AttachmentException).BadAttachment)); + string.Join(", ", (attExceptions[0] as MailMergeMessage.AttachmentException)?.BadAttachment!)); Console.WriteLine(); - Assert.AreEqual(1, (attExceptions[0] as MailMergeMessage.AttachmentException).BadAttachment.Count); + Assert.AreEqual(1, (attExceptions[0] as MailMergeMessage.AttachmentException)?.BadAttachment.Count); // 2 file attachments missing Console.WriteLine($"{nameof(MailMergeMessage.AttachmentException)} thrown successfully:"); Console.WriteLine("Missing attachment files: " + - string.Join(", ", (attExceptions[1] as MailMergeMessage.AttachmentException).BadAttachment)); + string.Join(", ", (attExceptions[1] as MailMergeMessage.AttachmentException)?.BadAttachment!)); Console.WriteLine(); - Assert.AreEqual(2, (attExceptions[1] as MailMergeMessage.AttachmentException).BadAttachment.Count); + Assert.AreEqual(2, (attExceptions[1] as MailMergeMessage.AttachmentException)?.BadAttachment.Count); } // **************** Part 2: @@ -131,10 +130,11 @@ public void MessagesFromDataRows() var i = 0; foreach (var mimeMessage in mmm.GetMimeMessages(tbl.Rows.OfType())) { - Assert.IsTrue(mimeMessage.To.ToString().Contains(tbl.Rows[i]["Email"].ToString())); + var row = tbl.Rows[i]; + Assert.IsTrue(mimeMessage.To.ToString().Contains(row["Email"].ToString()!)); Assert.IsTrue(mimeMessage.TextBody.Contains(text - .Replace("{Email}", tbl.Rows[i]["Email"].ToString()) - .Replace("{Continent}", tbl.Rows[i]["Continent"].ToString()))); + .Replace("{Email}", row["Email"].ToString()) + .Replace("{Continent}", row["Continent"].ToString()))); MailMergeMessage.DisposeFileStreams(mimeMessage); i++; } @@ -188,8 +188,8 @@ public void SingleMessageFromValueTuple() private class Recipient { - public string Name { get; set; } - public string Email { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; } @@ -255,7 +255,7 @@ public void MessagesFromJsonArray() { Assert.IsTrue(mimeMessage.TextBody == string.Format($"This is the plain text part for {recipients[cnt]["Name"]} ({recipients[cnt]["Email"]})")); Assert.IsTrue(mimeMessage.HtmlBody.Contains(string.Format($"This is the plain text part for {recipients[cnt]["Name"]} ({recipients[cnt]["Email"]})"))); - Assert.IsTrue(mimeMessage.To.ToString().Contains(recipients[cnt]["Name"].ToString()) && mimeMessage.To.ToString().Contains(recipients[cnt]["Email"].ToString())); + Assert.IsTrue(mimeMessage.To.ToString().Contains(recipients[cnt]["Name"]!.ToString()) && mimeMessage.To.ToString().Contains(recipients[cnt]["Email"]!.ToString())); MailMergeMessage.DisposeFileStreams(mimeMessage); cnt++; } @@ -283,4 +283,4 @@ public void Disabled_Formatter_Should_Maintain_Variable_Placeholders() Assert.IsTrue(mimeMessage.TextBody.Contains(text)); MailMergeMessage.DisposeFileStreams(mimeMessage); } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Sender_Config.cs b/Src/MailMergeLib.Tests/Sender_Config.cs index b396e7f..7d5c733 100644 --- a/Src/MailMergeLib.Tests/Sender_Config.cs +++ b/Src/MailMergeLib.Tests/Sender_Config.cs @@ -12,7 +12,6 @@ public void Equality() var sc2 = new SenderConfig(); Assert.IsTrue(sc1.Equals(sc2)); - Assert.IsFalse(sc1.Equals(null)); Assert.IsFalse(sc1.Equals(new object())); } @@ -23,7 +22,6 @@ public void NotEqual() var sc2 = new SenderConfig {MaxNumOfSmtpClients = 99999 }; Assert.IsFalse(sc1.Equals(sc2)); - Assert.IsFalse(sc1.Equals(null)); Assert.IsFalse(sc1.Equals(new object())); } } \ No newline at end of file diff --git a/Src/MailMergeLib.Tests/Sender_EventsAndSend.cs b/Src/MailMergeLib.Tests/Sender_EventsAndSend.cs index 9e768c2..409acc4 100644 --- a/Src/MailMergeLib.Tests/Sender_EventsAndSend.cs +++ b/Src/MailMergeLib.Tests/Sender_EventsAndSend.cs @@ -4,26 +4,21 @@ using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Net; -using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using MailKit.Security; using MimeKit; using netDumbster.smtp; using NUnit.Framework; -using SmartFormat.Core.Settings; namespace MailMergeLib.Tests; [TestFixture] public class Sender_EventsAndSend { - private static readonly object _locker = new object(); - private static SimpleSmtpServer _server; - private int? _simpleSmtpServerPort; - private readonly Random _rnd = new Random(); - private Settings _settings = new Settings(); + private static readonly object _locker = new(); + private static SimpleSmtpServer? _server; + private Settings _settings = new(); public Sender_EventsAndSend() { @@ -31,10 +26,10 @@ public Sender_EventsAndSend() // netDumbster.smtp.Logging.LogManager.GetLogger = type => new netDumbster.smtp.Logging.ConsoleLogger(type); } - private void SendMail(EventHandler onAfterSend = null, - EventHandler onSmtpConnected = null, - EventHandler onSmtpDisconnected = null, - EventHandler onSendFailure = null) + private void SendMail(EventHandler? onAfterSend = null, + EventHandler? onSmtpConnected = null, + EventHandler? onSmtpDisconnected = null, + EventHandler? onSendFailure = null) { var data = new Dictionary { @@ -86,13 +81,13 @@ public void TryToSendWithNullMessage() using var mms = new MailMergeSender(); // single mail - Assert.Throws(() => mms.Send(null, new object())); - Assert.ThrowsAsync(async () => await mms.SendAsync(null, new object())); + Assert.Throws(() => mms.Send(null!, new object())); + Assert.ThrowsAsync(async () => await mms.SendAsync(null!, new object())); // several mails - Assert.Throws(() => mms.Send(null, new Dictionary())); + Assert.Throws(() => mms.Send(null!, new Dictionary())); Assert.ThrowsAsync(async () => - await mms.SendAsync(null, new Dictionary())); + await mms.SendAsync(null!, new Dictionary())); } [Test] @@ -101,9 +96,9 @@ public void TryToSendWithNullAsEnumerable() using var mms = new MailMergeSender(); using var mmm = new MailMergeMessage(); // no need to fully prepare for this test - Assert.Throws(() => mms.Send(null, (Dictionary) null)); + Assert.Throws(() => mms.Send(new MailMergeMessage(), (Dictionary) null!)); Assert.ThrowsAsync(async () => - await mms.SendAsync(mmm, (Dictionary) null)); + await mms.SendAsync(mmm, (Dictionary) null!)); } [Test] @@ -125,7 +120,7 @@ public void CancelSendOperationWithDelay() mms.SendCancel(500); Assert.ThrowsAsync(() => mms.SendAsync(mmm, anyData)); - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } [Test] @@ -151,7 +146,7 @@ await Task.Factory.StartNew(async () => await mms.SendAsync(mmm, anyData)), Assert.Throws(() => { Task.WaitAll(tasks); }); - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } [Test] @@ -159,9 +154,9 @@ public void SendMailWithStandardConfig() { var connectedCounter = 0; var disconnectedCounter = 0; - SmtpClientConfig usedClientConfig = null; + SmtpClientConfig? usedClientConfig = null; - void OnAfterSend(object sender, MailSenderAfterSendEventArgs args) + void OnAfterSend(object? sender, MailSenderAfterSendEventArgs args) { lock (_locker) { @@ -169,7 +164,7 @@ void OnAfterSend(object sender, MailSenderAfterSendEventArgs args) } } - void OnSmtpConnected(object sender, MailSenderSmtpClientEventArgs args) + void OnSmtpConnected(object? sender, MailSenderSmtpClientEventArgs args) { lock (_locker) { @@ -177,7 +172,7 @@ void OnSmtpConnected(object sender, MailSenderSmtpClientEventArgs args) } } - void OnSmtpDisconnected(object sender, MailSenderSmtpClientEventArgs args) + void OnSmtpDisconnected(object? sender, MailSenderSmtpClientEventArgs args) { lock (_locker) { @@ -189,19 +184,19 @@ void OnSmtpDisconnected(object sender, MailSenderSmtpClientEventArgs args) Assert.AreEqual(1, connectedCounter); Assert.AreEqual(1, disconnectedCounter); - Assert.AreEqual(1, _server.ReceivedEmailCount); - Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[0].Name, usedClientConfig.Name); + Assert.AreEqual(1, _server?.ReceivedEmailCount); + Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[0].Name, usedClientConfig?.Name); - Console.WriteLine($"Sending mail with smtp config name '{usedClientConfig.Name}' passed.\n\n"); - Console.WriteLine(_server.ReceivedEmail[0].Data); + Console.WriteLine($"Sending mail with smtp config name '{usedClientConfig?.Name}' passed.\n\n"); + Console.WriteLine(_server?.ReceivedEmail[0].Data); } [Test] public void SendMailWithBackupConfig() { - SmtpClientConfig usedClientConfig = null; + SmtpClientConfig? usedClientConfig = null; - void OnAfterSend(object sender, MailSenderAfterSendEventArgs args) + void OnAfterSend(object? sender, MailSenderAfterSendEventArgs args) { usedClientConfig = args.SmtpClientConfig; } @@ -209,20 +204,20 @@ void OnAfterSend(object sender, MailSenderAfterSendEventArgs args) _settings.SenderConfig.SmtpClientConfig[0] .SmtpPort++; // set wrong server port, so that backup config should be taken SendMail(OnAfterSend); - Assert.AreEqual(1, _server.ReceivedEmailCount); - Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[1].Name, usedClientConfig.Name); + Assert.AreEqual(1, _server?.ReceivedEmailCount); + Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[1].Name, usedClientConfig?.Name); - Console.WriteLine($"Sending mail with smtp config name '{usedClientConfig.Name}' passed.\n\n"); - Console.WriteLine(_server.ReceivedEmail[0].Data); + Console.WriteLine($"Sending mail with smtp config name '{usedClientConfig?.Name}' passed.\n\n"); + Console.WriteLine(_server?.ReceivedEmail[0].Data); } [Test] public void SendMailWithSendFailure() { - SmtpClientConfig usedClientConfig = null; - Exception sendFailure = null; + SmtpClientConfig? usedClientConfig = null; + Exception? sendFailure = null; - void OnSendFailure(object sender, MailSenderSendFailureEventArgs args) + void OnSendFailure(object? sender, MailSenderSendFailureEventArgs args) { lock (_locker) { @@ -235,14 +230,14 @@ void OnSendFailure(object sender, MailSenderSendFailureEventArgs args) .SmtpPort++; // set wrong server port, so that backup config should be taken _settings.SenderConfig.SmtpClientConfig[1].SmtpPort++; // set wrong server port, so that send will fail Assert.Catch(() => SendMail(onSendFailure: OnSendFailure)); - Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[1].Name, usedClientConfig.Name); - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(_settings.SenderConfig.SmtpClientConfig[1].Name, usedClientConfig?.Name); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } private class Recipient { - public string Name { get; set; } - public string Email { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; } @@ -333,8 +328,8 @@ public async Task AllSenderEventsSingleMail(string somePlaceholder, bool withPar // sequence of sync sending is predefined while (actualEvents.Count > 0) { - expectedEvents.TryPop(out string expected); - actualEvents.TryPop(out string actual); + expectedEvents.TryPop(out var expected); + actualEvents.TryPop(out var actual); Assert.AreEqual(expected, actual); } @@ -475,8 +470,8 @@ public async Task AllSenderEventsMailMerge(string somePlaceholder, bool withPars // sequence of sync sending is predefined while (actualEvents.Count > 0) { - expectedEvents.TryPop(out string expected); - actualEvents.TryPop(out string actual); + expectedEvents.TryPop(out var expected); + actualEvents.TryPop(out var actual); Assert.AreEqual(expected, actual); } @@ -593,21 +588,21 @@ public void Send_With_And_Without_MailMergeMessageException(bool throwException, if (throwException) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { if (setMimeMessageToNull) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { - Assert.AreEqual(recipients.Count, _server.ReceivedEmailCount); + Assert.AreEqual(recipients.Count, _server?.ReceivedEmailCount); } } - _server.ClearReceivedEmail(); + _server?.ClearReceivedEmail(); // send single data item mmm.PlainText = plainText; // set text from constant @@ -636,21 +631,21 @@ public void Send_With_And_Without_MailMergeMessageException(bool throwException, if (throwException) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { if (setMimeMessageToNull) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { - Assert.AreEqual(1, _server.ReceivedEmailCount); + Assert.AreEqual(1, _server?.ReceivedEmailCount); } } - _server.ClearReceivedEmail(); + _server?.ClearReceivedEmail(); #endregion @@ -685,21 +680,21 @@ public void Send_With_And_Without_MailMergeMessageException(bool throwException, if (throwException) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { if (setMimeMessageToNull) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { - Assert.AreEqual(recipients.Count, _server.ReceivedEmailCount); + Assert.AreEqual(recipients.Count, _server?.ReceivedEmailCount); } } - _server.ClearReceivedEmail(); + _server?.ClearReceivedEmail(); // send single data item mmm.PlainText = plainText; // set text from constant @@ -730,17 +725,17 @@ public void Send_With_And_Without_MailMergeMessageException(bool throwException, if (throwException) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { if (setMimeMessageToNull) { - Assert.AreEqual(0, _server.ReceivedEmailCount); + Assert.AreEqual(0, _server?.ReceivedEmailCount); } else { - Assert.AreEqual(1, _server.ReceivedEmailCount); + Assert.AreEqual(1, _server?.ReceivedEmailCount); } } @@ -773,11 +768,11 @@ public async Task SendSyncAndAsyncPerformance(int numOfRecipients) sw.Stop(); Console.WriteLine($"Time to send {recipients.Count} messages sync: {sw.ElapsedMilliseconds} milliseconds."); Console.WriteLine(); - Assert.AreEqual(recipients.Count, _server.ReceivedEmail.Length); + Assert.AreEqual(recipients.Count, _server?.ReceivedEmail.Length); Assert.IsFalse(mms.IsBusy); sw.Reset(); - _server.ClearReceivedEmail(); + _server?.ClearReceivedEmail(); sw.Start(); @@ -793,7 +788,7 @@ public async Task SendSyncAndAsyncPerformance(int numOfRecipients) Console.WriteLine( $"{numOfSmtpClientsUsed} tasks (and SmtpClients) used for sending async\n(max {mms.Config.MaxNumOfSmtpClients} were configured)."); - Assert.AreEqual(recipients.Count, _server.ReceivedEmail.Length); + Assert.AreEqual(recipients.Count, _server?.ReceivedEmail.Length); Assert.IsFalse(mms.IsBusy); } @@ -802,40 +797,21 @@ public async Task SendSyncAndAsyncPerformance(int numOfRecipients) [OneTimeSetUp] public void FixtureSetUp() { - _server = SimpleSmtpServer.Start(_rnd.Next(50000, 60000)); } [OneTimeTearDown] public void FixtureTearDown() { - _server.Stop(); - } - - /// - /// Initialize with port zero. - /// In this case, the system will select a random free port - /// from the dynamic port range. - /// We can get the number of this port from the LocalEndpoint property. - /// - /// The first free TCP port found. - private static int GetFreeTcpPort() - { - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - var freePort = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return freePort; } [SetUp] public void SetUp() { - _server.ClearReceivedEmail(); - _server.Stop(); + _server?.ClearReceivedEmail(); + _server?.Stop(); - _simpleSmtpServerPort ??= GetFreeTcpPort(); - - _server = SimpleSmtpServer.Start(_simpleSmtpServerPort.Value); + // Server frequently hangs, if not initialized before each test + _server = SimpleSmtpServer.Start(Helper.GetFreeTcpPort()); _settings = GetSettings(); } @@ -862,19 +838,19 @@ private static Settings GetSettings() SmtpClientConfig = new[] { - new SmtpClientConfig() + new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, SmtpHost = "localhost", - SmtpPort = _server.Configuration.Port, + SmtpPort = (int)_server?.Configuration.Port!, NetworkCredential = new Credential("user", "pwd"), // not used for netDumbster - SecureSocketOptions = SecureSocketOptions.None, + SecureSocketOptions = SecureSocketOptions.Auto, Name = "Standard", MaxFailures = 3, DelayBetweenMessages = 0, ClientDomain = "mail.mailmergelib.net" }, - new SmtpClientConfig() + new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, SmtpHost = "localhost", @@ -892,4 +868,4 @@ private static Settings GetSettings() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/Sender_SendMimeMessage.cs b/Src/MailMergeLib.Tests/Sender_SendMimeMessage.cs index 5f0d042..06e5f3b 100644 --- a/Src/MailMergeLib.Tests/Sender_SendMimeMessage.cs +++ b/Src/MailMergeLib.Tests/Sender_SendMimeMessage.cs @@ -16,9 +16,9 @@ public class Sender_SendMimeMessage [TestCase(typeof(Exception))] public void With_ConnectException(Type connectException) { - var sender = GetMailMergeSender(); - var msg = GetMimeMessage(); - var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential()}; + var sender = Sender_SendMimeMessage.GetMailMergeSender(); + var msg = Sender_SendMimeMessage.GetMimeMessage(); + var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential(string.Empty, string.Empty)}; sender.Config.SmtpClientConfig[0] = config; Exception exception; @@ -53,9 +53,9 @@ public void With_ConnectException(Type connectException) [TestCase(typeof(Exception))] public void With_AuthenticateException(Type authenticateException) { - var sender = GetMailMergeSender(); - var msg = GetMimeMessage(); - var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential() }; + var sender = Sender_SendMimeMessage.GetMailMergeSender(); + var msg = Sender_SendMimeMessage.GetMimeMessage(); + var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential(string.Empty, string.Empty) }; sender.Config.SmtpClientConfig[0] = config; Exception exception; @@ -99,9 +99,9 @@ public void With_AuthenticateException(Type authenticateException) [TestCase(typeof(Exception))] public void With_SendException(Type sendException, SmtpErrorCode smtpErrorCode = SmtpErrorCode.UnexpectedStatusCode) { - var sender = GetMailMergeSender(); - var msg = GetMimeMessage(); - var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential() }; + var sender = Sender_SendMimeMessage.GetMailMergeSender(); + var msg = Sender_SendMimeMessage.GetMimeMessage(); + var config = new SmtpClientConfig { MessageOutput = MessageOutput.SmtpServer, NetworkCredential = new Credential(string.Empty, string.Empty) }; sender.Config.SmtpClientConfig[0] = config; Exception exception; @@ -137,13 +137,13 @@ public void With_SendException(Type sendException, SmtpErrorCode smtpErrorCode = Assert.ThrowsAsync(sendException, async () => await sender.SendMimeMessageAsync(smtpClient, msg, config)); } - private MailMergeSender GetMailMergeSender() + private static MailMergeSender GetMailMergeSender() { var sender = new MailMergeSender {GetInitializedSmtpClientDelegate = config => new FakeSmtpClient()}; return sender; } - private MimeMessage GetMimeMessage() + private static MimeMessage GetMimeMessage() { var mmm = new MailMergeMessage("subject", "plain text"); mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.To, "test@example.org")); diff --git a/Src/MailMergeLib.Tests/Settings_Serialization.cs b/Src/MailMergeLib.Tests/Settings_Serialization.cs index 0c23920..d569907 100644 --- a/Src/MailMergeLib.Tests/Settings_Serialization.cs +++ b/Src/MailMergeLib.Tests/Settings_Serialization.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -7,7 +8,6 @@ using MailKit.Security; using MimeKit; using NUnit.Framework; -using SmartFormat.Core.Settings; namespace MailMergeLib.Tests; @@ -15,7 +15,7 @@ namespace MailMergeLib.Tests; public class Settings_Serialization { private const string _settingsFilename = "TestSettings.xml"; - private Settings _outSettings; + private Settings _outSettings = new(); [OneTimeSetUp] public void Setup() @@ -98,20 +98,20 @@ public void CryptoKey() [Test] public void Credential_Without_Domain() { - var credential = (Credential)_outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; - var networkCredential = credential.GetCredential(null, null); - Assert.AreEqual(credential.Username, networkCredential.UserName); - Assert.AreEqual(credential.Password, networkCredential.Password); + var credential = (Credential?)_outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; + var networkCredential = credential?.GetCredential(new Uri("file:///"), ""); + Assert.AreEqual(credential?.Username, networkCredential?.UserName); + Assert.AreEqual(credential?.Password, networkCredential?.Password); } [Test] public void Credential_With_Domain() { - var credential = (Credential)_outSettings.SenderConfig.SmtpClientConfig.Last().NetworkCredential; - var networkCredential = credential.GetCredential(null, null); - Assert.AreEqual(credential.Username, networkCredential.UserName); - Assert.AreEqual(credential.Password, networkCredential.Password); - Assert.AreEqual(credential.Domain, networkCredential.Domain); + var credential = (Credential?)_outSettings.SenderConfig.SmtpClientConfig.Last().NetworkCredential; + var networkCredential = credential?.GetCredential(new Uri("file:///"), ""); + Assert.AreEqual(credential?.Username, networkCredential?.UserName); + Assert.AreEqual(credential?.Password, networkCredential?.Password); + Assert.AreEqual(credential?.Domain, networkCredential?.Domain); } [Test] @@ -125,11 +125,11 @@ public void Settings_Save_and_Restore(bool cryptoEnabled) _outSettings.Serialize(outMs, Encoding.UTF8); var inSettings = Settings.Deserialize(outMs, Encoding.UTF8); - Assert.IsTrue(inSettings.SenderConfig.Equals(_outSettings.SenderConfig)); + Assert.IsTrue(inSettings?.SenderConfig.Equals(_outSettings.SenderConfig)); outMs.Dispose(); - var smtpCredential = (Credential) _outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; - Assert.AreEqual(cryptoEnabled, smtpCredential.Password != smtpCredential.PasswordEncrypted && smtpCredential.Username != smtpCredential.UsernameEncrypted); + var smtpCredential = (Credential?) _outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; + Assert.AreEqual(cryptoEnabled, smtpCredential?.Password != smtpCredential?.PasswordEncrypted && smtpCredential?.Username != smtpCredential?.UsernameEncrypted); } [Test] @@ -140,12 +140,12 @@ public void Settings_Save_and_Restore_With_String(bool cryptoEnabled) Settings.CryptoEnabled = cryptoEnabled; var serialized = _outSettings.Serialize(); - var restored = Settings.Deserialize(serialized); + var restored = Settings.Deserialize(serialized)!; Assert.IsTrue(restored.SenderConfig.Equals(_outSettings.SenderConfig)); - var smtpCredential = (Credential)_outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; - Assert.AreEqual(cryptoEnabled, smtpCredential.Password != smtpCredential.PasswordEncrypted && smtpCredential.Username != smtpCredential.UsernameEncrypted); + var smtpCredential = (Credential?)_outSettings.SenderConfig.SmtpClientConfig.First().NetworkCredential; + Assert.AreEqual(cryptoEnabled, smtpCredential?.Password != smtpCredential?.PasswordEncrypted && smtpCredential?.Username != smtpCredential?.UsernameEncrypted); } [Test] @@ -157,15 +157,15 @@ public void Settings_Restore_From_File(bool cryptoEnabled) if (!cryptoEnabled) { var restored = - Settings.Deserialize(Path.Combine(TestFileFolders.FilesAbsPath, _settingsFilename), null); - Assert.IsTrue(restored.SenderConfig.Equals(_outSettings.SenderConfig)); + Settings.Deserialize(Path.Combine(TestFileFolders.FilesAbsPath, _settingsFilename), Encoding.UTF8)!; + Assert.That(restored.SenderConfig, Is.EqualTo(_outSettings.SenderConfig)); } else { // An exception is thrown because username / password are saved as plain text, // while with encryption enabled, both should be encrypted. Assert.That(() => - Settings.Deserialize(Path.Combine(TestFileFolders.FilesAbsPath, _settingsFilename), null), Throws.Exception.InstanceOf()); + Settings.Deserialize(Path.Combine(TestFileFolders.FilesAbsPath, _settingsFilename), Encoding.UTF8), Throws.Exception.InstanceOf()); } } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/SmtpClient_Config.cs b/Src/MailMergeLib.Tests/SmtpClient_Config.cs index 01817ec..dd5f96c 100644 --- a/Src/MailMergeLib.Tests/SmtpClient_Config.cs +++ b/Src/MailMergeLib.Tests/SmtpClient_Config.cs @@ -1,6 +1,4 @@ -using System; -using System.Net.Mail; -using System.Reflection; +using System.Net.Mail; using NUnit.Framework; #if NETFRAMEWORK using System.Configuration; @@ -58,8 +56,8 @@ public void Read_SmtpConfig_From_ConfigFile(string username, string password, bo Assert.AreEqual(credentialSet, smtpConfig.NetworkCredential != null); if (credentialSet) { - Assert.AreEqual(username, ((Credential) smtpConfig.NetworkCredential)?.Username); - Assert.AreEqual(password, ((Credential) smtpConfig.NetworkCredential)?.Password); + Assert.AreEqual(username, ((Credential?) smtpConfig.NetworkCredential)?.Username); + Assert.AreEqual(password, ((Credential?) smtpConfig.NetworkCredential)?.Password); } } @@ -85,7 +83,7 @@ public void Read_SmtpConfig_From_ConfigFile(string host, int port, string client /// If the app doesn't have a .config, it will be created. /// /// The SMTP settings to write to the .config file. - private void ChangeSmtpConfigFile(SmtpSection newSmtpSettings) + private static void ChangeSmtpConfigFile(SmtpSection newSmtpSettings) { var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.SectionGroups["system.net"]?.SectionGroups["mailSettings"]?.Sections.Clear(); @@ -133,7 +131,6 @@ public void Equality() var sc2 = new SmtpClientConfig(); Assert.IsTrue(sc1.Equals(sc2)); - Assert.IsFalse(sc1.Equals(null)); Assert.IsFalse(sc1.Equals(new object())); } @@ -144,7 +141,6 @@ public void NotEqual() var sc2 = new SmtpClientConfig { SmtpPort = 12345 }; Assert.IsFalse(sc1.Equals(sc2)); - Assert.IsFalse(sc1.Equals(null)); Assert.IsFalse(sc1.Equals(new object())); } } diff --git a/Src/MailMergeLib.Tests/TestFileFolders.cs b/Src/MailMergeLib.Tests/TestFileFolders.cs index 68bc76f..35ef0da 100644 --- a/Src/MailMergeLib.Tests/TestFileFolders.cs +++ b/Src/MailMergeLib.Tests/TestFileFolders.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Runtime; namespace MailMergeLib.Tests; @@ -20,4 +19,4 @@ private static string Get_TestFiles_Path_From_CodeBase() } public static string FilesAbsPath = Get_TestFiles_Path_From_CodeBase(); -} \ No newline at end of file +} diff --git a/Src/MailMergeLib.Tests/TestSetup.cs b/Src/MailMergeLib.Tests/TestSetup.cs new file mode 100644 index 0000000..6ec8027 --- /dev/null +++ b/Src/MailMergeLib.Tests/TestSetup.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using NUnit.Framework; + +namespace YAXLibTests; + +[SetUpFixture] +public class TestSetup +{ + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + // Disable console output from test methods + Console.SetOut(TextWriter.Null); + } + + [OneTimeTearDown] + public void RunAfterAnyTests() + { + // Nothing defined here + } +} diff --git a/Src/MailMergeLib.Tests/Tools.cs b/Src/MailMergeLib.Tests/Tools.cs index 45a321c..1c79b7a 100644 --- a/Src/MailMergeLib.Tests/Tools.cs +++ b/Src/MailMergeLib.Tests/Tools.cs @@ -101,20 +101,16 @@ public void IsSevenBit(bool expected, string toTest) [TestCase(false, "abcdeföäü")] public void IsSevenBitStream(bool expected, string toTest) { - using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(toTest ?? string.Empty))) - { - Assert.AreEqual(expected, MailMergeLib.Tools.IsSevenBit(stream)); - } + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(toTest ?? string.Empty)); + Assert.AreEqual(expected, MailMergeLib.Tools.IsSevenBit(stream)); } [Test] [TestCase("Some Text")] public void StreamToString(string toTest) { - using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(toTest ?? string.Empty))) - { - Assert.AreEqual(toTest, MailMergeLib.Tools.Stream2String(stream)); - } + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(toTest ?? string.Empty)); + Assert.AreEqual(toTest, MailMergeLib.Tools.Stream2String(stream)); } [Test] diff --git a/Src/MailMergeLib/AngleSharpHtmlConverter.cs b/Src/MailMergeLib/AngleSharpHtmlConverter.cs index 7d2637c..938a886 100644 --- a/Src/MailMergeLib/AngleSharpHtmlConverter.cs +++ b/Src/MailMergeLib/AngleSharpHtmlConverter.cs @@ -63,7 +63,7 @@ private void ConvertToText(INode node, TextWriter outText) break; case NodeType.Text: - var parentName = node.ParentElement.NodeName.ToLower(); + var parentName = node.ParentElement?.NodeName.ToLower(); // script, style and title text is ignored if ((parentName == "script") || (parentName == "style") || parentName == "head" || parentName == "title" || parentName == "meta") @@ -105,21 +105,21 @@ private void ConvertToText(INode node, TextWriter outText) case "img": // images toWrite.Clear(); - if (element.Attributes["src"] != null && element.Attributes["src"].Value.Trim() != string.Empty) - toWrite.Add("[" + element.Attributes["src"].Value + "]"); - if (element.Attributes["alt"] != null && element.Attributes["alt"].Value.Trim() != string.Empty) - toWrite.Add("[" + element.Attributes["alt"].Value + "]"); - if (element.Attributes["title"] != null && element.Attributes["title"].Value.Trim() != string.Empty) - toWrite.Add("(\"" + element.Attributes["title"].Value + "\")"); + if (!string.IsNullOrEmpty(element.Attributes["src"]?.Value.Trim())) + toWrite.Add("[" + element.Attributes["src"]?.Value + "]"); + if (!string.IsNullOrEmpty(element.Attributes["alt"]?.Value.Trim())) + toWrite.Add("[" + element.Attributes["alt"]?.Value + "]"); + if (!string.IsNullOrEmpty(element.Attributes["title"]?.Value.Trim())) + toWrite.Add("(\"" + element.Attributes["title"]?.Value + "\")"); outText.Write("[" + string.Join(" ", toWrite.ToArray()) + "] "); break; case "a": // links toWrite.Clear(); - if (element.Attributes["href"] != null && element.Attributes["href"].Value.Trim() != string.Empty) - toWrite.Add("[" + element.Attributes["href"].Value + "]"); - if (element.Attributes["title"] != null && element.Attributes["title"].Value.Trim() != string.Empty) - toWrite.Add("(\"" + element.Attributes["title"].Value + "\")"); + if (!string.IsNullOrEmpty(element.Attributes["href"]?.Value.Trim())) + toWrite.Add("[" + element.Attributes["href"]?.Value + "]"); + if (!string.IsNullOrEmpty(element.Attributes["title"]?.Value.Trim())) + toWrite.Add("(\"" + element.Attributes["title"]?.Value + "\")"); outText.Write(string.Join(" ", toWrite.ToArray()) + " "); break; case "hr": diff --git a/Src/MailMergeLib/CaseSensitivityType.cs b/Src/MailMergeLib/CaseSensitivityType.cs new file mode 100644 index 0000000..3a8dcfb --- /dev/null +++ b/Src/MailMergeLib/CaseSensitivityType.cs @@ -0,0 +1,15 @@ +namespace MailMergeLib; + +/// +/// An enumeration of types defining whether string will be processed case-sensitive. +/// +/// +/// Enum added for API compatibility when migrating from SmartFormat 2.7.3 to 3.2.1 +/// +public enum CaseSensitivityType +{ + /// String are processed case-sensitive. + CaseSensitive, + /// String are not processed case-sensitive. + CaseInsensitive, +} \ No newline at end of file diff --git a/Src/MailMergeLib/Credential.cs b/Src/MailMergeLib/Credential.cs index 8e859b2..36436db 100644 --- a/Src/MailMergeLib/Credential.cs +++ b/Src/MailMergeLib/Credential.cs @@ -18,7 +18,9 @@ public class Credential : ICredentials /// Initializes a new instance of the Credential class. /// public Credential() - {} + { + Username = Password = string.Empty; + } /// /// Initializes a new instance of the Credential class. @@ -26,7 +28,7 @@ public Credential() /// /// /// - public Credential(string username, string password, string domain = null) + public Credential(string username, string password, string? domain = null) { Username = username; Password = password; @@ -89,7 +91,7 @@ internal string PasswordEncrypted /// [YAXAttributeForClass] [YAXSerializableField] - public string Domain { get; set; } + public string? Domain { get; set; } #endregion } \ No newline at end of file diff --git a/Src/MailMergeLib/EmailValidator.cs b/Src/MailMergeLib/EmailValidator.cs index df2970d..e2ceaf0 100644 --- a/Src/MailMergeLib/EmailValidator.cs +++ b/Src/MailMergeLib/EmailValidator.cs @@ -128,7 +128,7 @@ static bool IsDomainStart(char c, bool allowInternational, out SubDomainType typ static bool SkipAtom(string text, ref int index, bool allowInternational) { - int startIndex = index; + var startIndex = index; while (index < text.Length && IsAtom(text[index], allowInternational)) index++; @@ -138,7 +138,7 @@ static bool SkipAtom(string text, ref int index, bool allowInternational) static bool SkipSubDomain(string text, ref int index, bool allowInternational, out SubDomainType type) { - int startIndex = index; + var startIndex = index; if (!IsDomainStart(text[index], allowInternational, out type)) return false; @@ -154,7 +154,7 @@ static bool SkipSubDomain(string text, ref int index, bool allowInternational, o static bool SkipDomain(string text, ref int index, bool allowTopLevelDomains, bool allowInternational) { - if (!SkipSubDomain(text, ref index, allowInternational, out SubDomainType type)) + if (!SkipSubDomain(text, ref index, allowInternational, out var type)) return false; if (index < text.Length && text[index] == '.') @@ -184,7 +184,7 @@ static bool SkipDomain(string text, ref int index, bool allowTopLevelDomains, bo static bool SkipQuoted(string text, ref int index, bool allowInternational) { - bool escaped = false; + var escaped = false; // skip over leading '"' index++; @@ -221,12 +221,12 @@ static bool SkipQuoted(string text, ref int index, bool allowInternational) static bool SkipIPv4Literal(string text, ref int index) { - int groups = 0; + var groups = 0; while (index < text.Length && groups < 4) { - int startIndex = index; - int value = 0; + var startIndex = index; + var value = 0; while (index < text.Length && text[index] >= '0' && text[index] <= '9') { @@ -268,12 +268,12 @@ static bool IsHexDigit(char c) // ; IPv4-address-literal may be present static bool SkipIPv6Literal(string text, ref int index) { - bool compact = false; - int colons = 0; + var compact = false; + var colons = 0; while (index < text.Length) { - int startIndex = index; + var startIndex = index; while (index < text.Length && IsHexDigit(text[index])) index++; @@ -292,7 +292,7 @@ static bool SkipIPv6Literal(string text, ref int index) return compact ? colons < 6 : colons == 6; } - int count = index - startIndex; + var count = index - startIndex; if (count > 4) return false; @@ -346,7 +346,7 @@ static bool SkipIPv6Literal(string text, ref int index) /// public static bool Validate(string email, bool allowTopLevelDomains = false, bool allowInternational = false) { - int index = 0; + var index = 0; if (email == null) throw new ArgumentNullException(nameof(email)); @@ -422,4 +422,4 @@ public static bool Validate(string email, bool allowTopLevelDomains = false, boo return index == email.Length; } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/ErrorAction.cs b/Src/MailMergeLib/ErrorAction.cs new file mode 100644 index 0000000..eb31864 --- /dev/null +++ b/Src/MailMergeLib/ErrorAction.cs @@ -0,0 +1,22 @@ +namespace MailMergeLib; + +/// +/// Determines how format and parsing errors are handled. +/// +/// +/// Enum added for API compatibility when migrating from SmartFormat 2.7.3 to 3.2.1 +/// +public enum ErrorAction +{ + /// Throws an exception. This is only recommended for debugging, so that formatting errors can be easily found. + ThrowError, + + /// Includes an issue message in the output + OutputErrorInResult, + + /// Ignores errors and tries to output the data anyway + Ignore, + + /// Leaves invalid tokens unmodified in the text. + MaintainTokens +} \ No newline at end of file diff --git a/Src/MailMergeLib/FileAttachment.cs b/Src/MailMergeLib/FileAttachment.cs index 04351be..37c7bc8 100644 --- a/Src/MailMergeLib/FileAttachment.cs +++ b/Src/MailMergeLib/FileAttachment.cs @@ -9,7 +9,10 @@ public class FileAttachment /// Creates a new file attachment for a /// public FileAttachment() - {} + { + DisplayName = Filename = string.Empty; + MimeType = MimeKit.MimeTypes.GetMimeType( "application/octet-stream"); + } /// /// Creates a new file attachment for a @@ -62,11 +65,11 @@ public string Filename /// E.g. necessary for HashSet<FileAttachment>. /// /// Returns true, if both FileAttachments are equal, else false. - public override bool Equals(object fa) + public override bool Equals(object? fa) { if (fa is null) return false; if (ReferenceEquals(this, fa)) return true; - if (fa.GetType() != this.GetType()) return false; + if (fa.GetType() != GetType()) return false; return Equals((FileAttachment) fa); } @@ -92,4 +95,4 @@ public override int GetHashCode() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/HtmlBodyBuilder.cs b/Src/MailMergeLib/HtmlBodyBuilder.cs index 875129f..0bc98a9 100644 --- a/Src/MailMergeLib/HtmlBodyBuilder.cs +++ b/Src/MailMergeLib/HtmlBodyBuilder.cs @@ -22,8 +22,8 @@ internal class HtmlBodyBuilder : BodyBuilderBase { private readonly MailMergeMessage _mailMergeMessage; private readonly IHtmlDocument _htmlDocument; - private Uri _docBaseUri; - private readonly object _dataItem; + private Uri _docBaseUri = new(string.Concat(UriScheme.File, UriScheme.SchemeDelimiter)); + private readonly object? _dataItem; private readonly string _defaultDocBaseUri = new Uri(string.Concat(UriScheme.File, UriScheme.SchemeDelimiter)).ToString(); /// @@ -31,7 +31,7 @@ internal class HtmlBodyBuilder : BodyBuilderBase /// /// The parent MailMergeMessage, where HtmlBodyBuilder processes HtmlText and Subject properties. /// - public HtmlBodyBuilder(MailMergeMessage mailMergeMessage, object dataItem) + public HtmlBodyBuilder(MailMergeMessage mailMergeMessage, object? dataItem) { DocBaseUri = mailMergeMessage.Config.FileBaseDirectory; _mailMergeMessage = mailMergeMessage; @@ -70,7 +70,7 @@ public override MimeEntity GetBodyPart() // set the HTML title tag from email subject if (_htmlDocument.All.FirstOrDefault(m => m is IHtmlTitleElement) is IHtmlTitleElement titleEle) { - titleEle.Text = _mailMergeMessage.SearchAndReplaceVars(_mailMergeMessage.Subject, _dataItem); + titleEle.Text = _mailMergeMessage.SearchAndReplaceVars(_mailMergeMessage.Subject, _dataItem) ?? string.Empty; } // read the tag in order to find the embedded image files later on @@ -91,7 +91,9 @@ public override MimeEntity GetBodyPart() // replace placeholders only in the HTML Body, because e.g. // in the header there may be CSS definitions with curly brace which collide with SmartFormat {placeholders} - _htmlDocument.Body.InnerHtml = _mailMergeMessage.SearchAndReplaceVars(_htmlDocument.Body.InnerHtml, _dataItem); + if (_htmlDocument.Body != null) + _htmlDocument.Body.InnerHtml = + _mailMergeMessage.SearchAndReplaceVars(_htmlDocument.Body.InnerHtml, _dataItem) ?? string.Empty; var htmlTextPart = new TextPart("html") { @@ -195,7 +197,7 @@ private void ReplaceImgSrcByCid() } // replace any placeholders with variables - srcAttrValue = _mailMergeMessage.SearchAndReplaceVars(srcAttrValue, _dataItem); + srcAttrValue = _mailMergeMessage.SearchAndReplaceVars(srcAttrValue, _dataItem) ?? string.Empty; // Note: if srcAttrValue is a rooted path, _docBaseUrl will be ignored var srcUri = new Uri(_docBaseUri, srcAttrValue); @@ -212,25 +214,29 @@ private void ReplaceImgSrcByCid() var filename = _mailMergeMessage.SearchAndReplaceVarsInFilename(srcUri.LocalPath, _dataItem); try { - if (!fileList.ContainsKey(filename)) + if (filename != null) { - var fileInfo = new FileInfo(filename); - var contentType = MimeTypes.GetMimeType(filename); - var cid = MimeUtils.GenerateMessageId(); - InlineAtt.Add(new FileAttachment(fileInfo.FullName, MakeCid(string.Empty, cid, fileInfo.Extension), contentType)); - srcAttr.Value = MakeCid("cid:", cid, fileInfo.Extension); - fileList.Add(fileInfo.FullName, cid); - } - else - { - var cidForExistingFile = fileList[filename]; - var fileInfo = new FileInfo(filename); - srcAttr.Value = MakeCid("cid:", cidForExistingFile, fileInfo.Extension); + if (!fileList.ContainsKey(filename)) + { + var fileInfo = new FileInfo(filename); + var contentType = MimeTypes.GetMimeType(filename); + var cid = MimeUtils.GenerateMessageId(); + InlineAtt.Add(new FileAttachment(fileInfo.FullName, + MakeCid(string.Empty, cid, fileInfo.Extension), contentType)); + srcAttr.Value = MakeCid("cid:", cid, fileInfo.Extension); + fileList.Add(fileInfo.FullName, cid); + } + else + { + var cidForExistingFile = fileList[filename]; + var fileInfo = new FileInfo(filename); + srcAttr.Value = MakeCid("cid:", cidForExistingFile, fileInfo.Extension); + } } } catch { - BadInlineFiles.Add(filename); + BadInlineFiles.Add(filename ?? "(null)"); continue; } } diff --git a/Src/MailMergeLib/IHtmlConverter.cs b/Src/MailMergeLib/IHtmlConverter.cs index 6c7533e..eb74bff 100644 --- a/Src/MailMergeLib/IHtmlConverter.cs +++ b/Src/MailMergeLib/IHtmlConverter.cs @@ -1,5 +1,4 @@ namespace MailMergeLib; - /// /// Convert HTML to plain text. /// diff --git a/Src/MailMergeLib/MailMergeAddress.cs b/Src/MailMergeLib/MailMergeAddress.cs index c368b2f..a34ea2f 100644 --- a/Src/MailMergeLib/MailMergeAddress.cs +++ b/Src/MailMergeLib/MailMergeAddress.cs @@ -15,7 +15,10 @@ public class MailMergeAddress /// /// Represents the address of a mail sender or recipient for use with a MailMergeMessage. /// - public MailMergeAddress() { } + public MailMergeAddress() + { + Address = DisplayName = string.Empty; + } /// /// Represents the address of a mail sender or recipient for use with a MailMergeMessage. @@ -33,7 +36,7 @@ public MailMergeAddress(MailAddressType addrType, string address) /// MailAddressType of the e-mail address. /// A string that contains an e-mail address. /// A string that contains the display name associated with address. This parameter can be null. - public MailMergeAddress(MailAddressType addrType, string displayName, string address) + public MailMergeAddress(MailAddressType addrType, string? displayName, string address) { AddrType = addrType; Address = address; @@ -51,8 +54,7 @@ public MailMergeAddress(MailAddressType addrType, string fullMailAddress, Encodi { AddrType = addrType; DisplayNameCharacterEncoding = displayNameCharacterEncoding; - MailboxAddress mba; - if (MailboxAddress.TryParse(displayNameCharacterEncoding?.GetBytes(fullMailAddress), out mba)) + if (MailboxAddress.TryParse(displayNameCharacterEncoding?.GetBytes(fullMailAddress), out var mba)) { Address = mba.Address; DisplayName = mba.Name; @@ -79,13 +81,13 @@ public MailMergeAddress(MailAddressType addrType, string fullMailAddress, Encodi /// Gets or sets the display name of the recipient. /// [YAXSerializableField] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } /// /// Gets or sets the Encoding that defines the character set used for displayName. /// [YAXDontSerialize] - internal Encoding DisplayNameCharacterEncoding { get; set; } + internal Encoding? DisplayNameCharacterEncoding { get; set; } /// /// Character encoding for the display name. @@ -93,10 +95,13 @@ public MailMergeAddress(MailAddressType addrType, string fullMailAddress, Encodi /// [YAXSerializableField] [YAXSerializeAs("DisplayNameCharacterEncoding")] - internal string DisplayNameCharacterEncodingName + internal string? DisplayNameCharacterEncodingName { - get { return DisplayNameCharacterEncoding.WebName; } - set { DisplayNameCharacterEncoding = Encoding.GetEncoding(value); } + get { return DisplayNameCharacterEncoding?.WebName; } + set + { + if (value != null) DisplayNameCharacterEncoding = Encoding.GetEncoding(value); + } } /// @@ -105,10 +110,10 @@ internal string DisplayNameCharacterEncodingName /// Returns a MailAddress ready to be used for a MailAddress, or Null if no address part exists. /// Throws a NullReferenceException if TextVariableManager is null. /// Throws a FormatException if the computed MailAddress is not valid. - internal MailboxAddress GetMailAddress(MailMergeMessage mmm, object dataItem) + internal MailboxAddress? GetMailAddress(MailMergeMessage mmm, object? dataItem) { var address = mmm.SearchAndReplaceVars(Address, dataItem); - var displayName = mmm.SearchAndReplaceVars(DisplayName, dataItem); + var displayName = DisplayName != null ? mmm.SearchAndReplaceVars(DisplayName, dataItem) : null; if (string.IsNullOrEmpty(displayName)) displayName = null; // Exclude invalid address from further process @@ -117,7 +122,7 @@ internal MailboxAddress GetMailAddress(MailMergeMessage mmm, object dataItem) return null; } - return displayName != null + return displayName != null ? new MailboxAddress(DisplayNameCharacterEncoding, displayName, address) : new MailboxAddress(DisplayNameCharacterEncoding, address, address); } @@ -129,14 +134,17 @@ internal MailboxAddress GetMailAddress(MailMergeMessage mmm, object dataItem) /// /// /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((MailMergeAddress) obj); } + /// + /// Compares for equality + /// protected bool Equals(MailMergeAddress other) { return AddrType == other.AddrType && string.Equals(Address, other.Address) && string.Equals(DisplayName, other.DisplayName) && Equals(DisplayNameCharacterEncoding, other.DisplayNameCharacterEncoding); diff --git a/Src/MailMergeLib/MailMergeAddressCollection.cs b/Src/MailMergeLib/MailMergeAddressCollection.cs index 7fbf839..eec60cc 100644 --- a/Src/MailMergeLib/MailMergeAddressCollection.cs +++ b/Src/MailMergeLib/MailMergeAddressCollection.cs @@ -54,9 +54,12 @@ public enum MailAddressType public class MailMergeAddressCollection : Collection { /// - /// Property MailMergeMessage must be set after creating the instance! + /// Property MailMergeMessage will be initialized as 'empty'. /// - internal MailMergeAddressCollection() {} + internal MailMergeAddressCollection() + { + MailMergeMessage = new(); + } /// /// Constructor. @@ -114,9 +117,9 @@ public IEnumerable Get(MailAddressType addrType) /// /// /// The string representation of the collection of mailbox addresses - public string ToString(MailAddressType addrType, object dataItem) + public string ToString(MailAddressType addrType, object? dataItem) { - return string.Join(", ", Get(addrType).Select(at => at.GetMailAddress(MailMergeMessage, dataItem).ToString())); + return string.Join(", ", Get(addrType).Select(at => at?.GetMailAddress(MailMergeMessage, dataItem)?.ToString())); } #region *** Equality *** @@ -126,11 +129,11 @@ public string ToString(MailAddressType addrType, object dataItem) /// /// /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((MailMergeAddressCollection)obj); } @@ -153,4 +156,4 @@ public override int GetHashCode() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MailMergeLib.csproj b/Src/MailMergeLib/MailMergeLib.csproj index 530ddbd..9d4e4f4 100644 --- a/Src/MailMergeLib/MailMergeLib.csproj +++ b/Src/MailMergeLib/MailMergeLib.csproj @@ -4,7 +4,7 @@ MailMergeLib is a mail message client library which provides comfortable mail merge capabilities for text, inline images and attachments, as well as good throughput and fault tolerance for sending mail messages. MailMergeLib MailMergeLib - netstandard2.1;net462 + netstandard2.1;net462;net6.0 true MailMergeLib MailMergeLib.snk @@ -14,9 +14,9 @@ MailMergeLib_64x64.png MIT - See the change log with links to the Wiki for details of this release: -https://github.com/axuno/MailMergeLib/blob/main/ReleaseNotes.md - disable + See the release notes for all versions with links to the Wiki for details of this release: +https://github.com/axuno/MailMergeLib/releases + enable latest smtp mime mail email merge template netcore netstandard netframework c# Git @@ -55,11 +55,11 @@ https://github.com/axuno/MailMergeLib/blob/main/ReleaseNotes.md - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -74,7 +74,10 @@ https://github.com/axuno/MailMergeLib/blob/main/ReleaseNotes.md + + + diff --git a/Src/MailMergeLib/MailMergeMessage.cs b/Src/MailMergeLib/MailMergeMessage.cs index a99cb2e..58a8ea4 100644 --- a/Src/MailMergeLib/MailMergeMessage.cs +++ b/Src/MailMergeLib/MailMergeMessage.cs @@ -9,6 +9,7 @@ using SmartFormat.Extensions; using MailMergeLib.Templates; using MimeKit; +using SmartFormat.Core.Settings; using YAXLib.Attributes; using YAXLib.Enums; @@ -22,37 +23,37 @@ public partial class MailMergeMessage : IDisposable { #region *** Private fields *** - private MimeEntity _textMessagePart; // plain text and/or html text, maybe with inline attachments - private List _attachmentParts; + private MimeEntity? _textMessagePart; // plain text and/or html text, maybe with inline attachments + private List _attachmentParts = new(); // backing fields for properties are necessary for private setters used in deserialization! private string _subject = string.Empty; private string _plainText = string.Empty; private string _htmlText = string.Empty; private MailMergeAddressCollection _mailMergeAddresses; - private HashSet _fileAttachments = new HashSet(); - private List _streamAttachments = new List(); - private HashSet _inlineAttachments = new HashSet(); - private HashSet _stringAttachments = new HashSet(); - private HashSet _externalInlineAttachments = new HashSet(); - private HeaderList _headers = new HeaderList(); - private MessageInfo _info = new MessageInfo(); - private MessageConfig _config = new MessageConfig(); - private Templates.Templates _templates = new MailMergeLib.Templates.Templates(); + private HashSet _fileAttachments = new(); + private List _streamAttachments = new(); + private HashSet _inlineAttachments = new(); + private HashSet _stringAttachments = new(); + private HashSet _externalInlineAttachments = new(); + private HeaderList _headers = new(); + private MessageInfo _info = new(); + private MessageConfig _config = new(); + private Templates.Templates _templates = new(); // disposal and sync private bool _disposed; - private static readonly object SyncRoot = new object(); + private static readonly object SyncRoot = new(); #endregion #region *** Private lists for tracking errors when generating a MimeMessage *** - private readonly HashSet _badAttachmentFiles = new HashSet(); - private readonly HashSet _badMailAddr = new HashSet(); - private readonly HashSet _badInlineFiles = new HashSet(); - private readonly HashSet _badVariableNames = new HashSet(); - private readonly List _parseExceptions = new List(); + private readonly HashSet _badAttachmentFiles = new(); + private readonly HashSet _badMailAddr = new(); + private readonly HashSet _badInlineFiles = new(); + private readonly HashSet _badVariableNames = new(); + private readonly List _parseExceptions = new(); #endregion @@ -65,8 +66,8 @@ public MailMergeMessage() { Config.IgnoreIllegalRecipientAddresses = true; Config.Priority = MessagePriority.Normal; - SmartFormatter = GetConfiguredMailSmartFormatter(); - Config.SmartFormatterConfig.OnConfigChanged += SmartFormatter.SetConfig; + SmartFormatter = GetConfiguredMailSmartFormatter(true); + Config.SmartFormatterConfig.OnConfigChanged += RecreateMailSmartFormatter; // SmartFormatter.SetConfig; _mailMergeAddresses = new MailMergeAddressCollection(this); } @@ -233,14 +234,13 @@ public MessageConfig Config get => _config; set { + // Note: Keep null checks are for deserialization _config = value ?? new MessageConfig(); - if (_config.SmartFormatterConfig == null) _config.SmartFormatterConfig = new SmartFormatterConfig(); - - SmartFormatter.SetConfig(_config.SmartFormatterConfig); - _config.SmartFormatterConfig.OnConfigChanged += SmartFormatter.SetConfig; + _config.SmartFormatterConfig ??= new SmartFormatterConfig(); + + RecreateMailSmartFormatter(); } } - #endregion #region *** Attachment properties *** @@ -254,6 +254,7 @@ public MessageConfig Config public HashSet FileAttachments { get => _fileAttachments; + // Note: null checks are for deserialization private set => _fileAttachments = value ?? new HashSet(); } @@ -264,6 +265,7 @@ public HashSet FileAttachments public List StreamAttachments { get => _streamAttachments; + // Note: null checks are for deserialization internal set => _streamAttachments = value ?? new List(); } @@ -276,6 +278,7 @@ public List StreamAttachments public HashSet InlineAttachments { get => _inlineAttachments; + // Note: null checks are for deserialization private set => _inlineAttachments = value ?? new HashSet(); } @@ -288,6 +291,7 @@ public HashSet InlineAttachments public HashSet StringAttachments { get => _stringAttachments; + // Note: null checks are for deserialization private set => _stringAttachments = value ?? new HashSet(); } @@ -296,6 +300,7 @@ public HashSet StringAttachments private HashSet ExternalInlineAttachments { get => _externalInlineAttachments; + // Note: null checks are for deserialization set => _externalInlineAttachments = value ?? new HashSet(); } @@ -323,13 +328,19 @@ public void ClearExternalInlineAttachment() #endregion #region *** SmartFormat *** - private MailSmartFormatter GetConfiguredMailSmartFormatter() - { - if (Config.SmartFormatterConfig == null) Config.SmartFormatterConfig = new SmartFormatterConfig(); - var smartFormatter = new MailSmartFormatter(Config.SmartFormatterConfig); - // Smart.Format("{Name:choose(null|):N/A|empty|{Name}}", variable), where abc.Name NULL, string.Emtpy or a string + private void RecreateMailSmartFormatter() + { + SmartFormatter = GetConfiguredMailSmartFormatter(); + } + private MailSmartFormatter GetConfiguredMailSmartFormatter(bool invokedFromConstructor = false) + { + // Take over SmartSettings from existing SmartFormatter instance + var currentSmartSettings = invokedFromConstructor + ? new SmartSettings() + : SmartFormatter.Settings; + var smartFormatter = new MailSmartFormatter(Config.SmartFormatterConfig, currentSmartSettings); smartFormatter.OnFormattingFailure += (sender, args) => { _badVariableNames.Add(args.Placeholder); }; smartFormatter.Parser.OnParsingFailure += (sender, args) => { _parseExceptions.Add(new ParseException(args.Errors.MessageShort, args.Errors)); }; return smartFormatter; @@ -348,15 +359,14 @@ private MailSmartFormatter GetConfiguredMailSmartFormatter() /// we simple catch the exception and simulate setting ErrorAction.MaintainTokens. /// Note: We track such errors by subscribing to Parser.OnParsingFailure and Formatter.OnFormattingFailure. /// - internal string SearchAndReplaceVars(string text, object dataItem) + internal string SearchAndReplaceVars(string text, object? dataItem) { if (!EnableFormatter) return text; - - if (text == null) return null; - SmartFormatter.SetConfig(Config?.SmartFormatterConfig); // make sure we use the latest settings + + //SmartFormatter.SetConfig(Config.SmartFormatterConfig); // make sure we use the latest settings try { - return SmartFormatter.Format(Config?.CultureInfo, text, dataItem); + return SmartFormatter.Format(Config.CultureInfo, text, dataItem ?? new object()); } catch (SmartFormat.Core.Parsing.ParsingErrors) { @@ -383,16 +393,15 @@ internal string SearchAndReplaceVars(string text, object dataItem) /// we simple catch the exception and simulate setting ErrorAction.MaintainTokens. /// Note: We track such errors by subscribing to Parser.OnParsingFailure and Formatter.OnFormattingFailure. /// - internal string SearchAndReplaceVarsInFilename(string text, object dataItem) + internal string SearchAndReplaceVarsInFilename(string text, object? dataItem) { if (!EnableFormatter) return text; - if (text == null) return null; try { var filenameSmartFormatter = GetConfiguredMailSmartFormatter(); - filenameSmartFormatter.Settings.ConvertCharacterStringLiterals = false; - return filenameSmartFormatter.Format(Config?.CultureInfo, text, dataItem); + filenameSmartFormatter.Settings.Parser.ConvertCharacterStringLiterals = false; + return filenameSmartFormatter.Format(Config?.CultureInfo, text, dataItem ?? new object()); } catch (SmartFormat.Core.Parsing.ParsingErrors) { @@ -412,10 +421,10 @@ internal string SearchAndReplaceVarsInFilename(string text, object dataItem) /// Prepares the mail message subject: /// Replacing placeholders with their values and setting correct encoding. /// - private void AddSubjectToMailMessage(MimeMessage msg, object dataItem) + private void AddSubjectToMailMessage(MimeMessage msg, object? dataItem) { var subject = SearchAndReplaceVars(Subject, dataItem); - msg.Subject = subject ?? string.Empty; + msg.Subject = subject; msg.Headers.Replace(HeaderId.Subject, Config.CharacterEncoding, msg.Subject); } @@ -423,22 +432,21 @@ private void AddSubjectToMailMessage(MimeMessage msg, object dataItem) /// Prepares the mail message part (plain text and/or HTML: /// Replacing placeholders with their values and setting correct encoding. /// - private void BuildTextMessagePart(object dataIteam) + private void BuildTextMessagePart(object? dataItem) { _badInlineFiles.Clear(); _textMessagePart = null; - MultipartAlternative alternative = null; + MultipartAlternative? alternative = null; // create the plain text body part - TextPart plainTextPart = null; if (!string.IsNullOrEmpty(PlainText)) { RegisterPlainTextTemplates(); - var plainText = SearchAndReplaceVars(PlainText, dataIteam); + var plainText = SearchAndReplaceVars(PlainText, dataItem); - plainTextPart = (TextPart)new PlainBodyBuilder(plainText) + var plainTextPart = (TextPart)new PlainBodyBuilder(plainText) { TextTransferEncoding = Config.TextTransferEncoding, CharacterEncoding = Config.CharacterEncoding @@ -463,7 +471,7 @@ private void BuildTextMessagePart(object dataIteam) // create the HTML text body part with any linked resources // replacing any placeholders in the text or files with variable values - var htmlBody = new HtmlBodyBuilder(this, dataIteam) + var htmlBody = new HtmlBodyBuilder(this, dataItem) { DocBaseUri = Config.FileBaseDirectory, TextTransferEncoding = Config.TextTransferEncoding, @@ -487,7 +495,7 @@ private void BuildTextMessagePart(object dataIteam) InlineAttachments = htmlBody.InlineAtt; // expose all resolved inline attachments in MailMergeMessage htmlBody.BadInlineFiles.ToList().ForEach(f => _badInlineFiles.Add(f)); - SmartFormatter.Templates.Clear(); + SmartFormatter.Templates?.Clear(); } else { @@ -502,11 +510,11 @@ private void BuildTextMessagePart(object dataIteam) private void RegisterHtmlTemplates() { if (SmartFormatter.Templates == null) - SmartFormatter.Templates = new TemplateFormatter(SmartFormatter); + SmartFormatter.Templates = new TemplateFormatter(); SmartFormatter.Templates.Clear(); - foreach (var template in this.Templates) + foreach (var template in Templates) { var parts = template.GetParts(); var htmlPart = parts.FirstOrDefault(p => p.Type == PartType.Html); @@ -527,9 +535,10 @@ private void RegisterHtmlTemplates() /// private void RegisterPlainTextTemplates() { - SmartFormatter.Templates.Clear(); + SmartFormatter.Templates?.Clear(); + SmartFormatter.Templates ??= new TemplateFormatter(); - foreach (var template in this.Templates) + foreach (var template in Templates) { var plainPart = template.GetParts().FirstOrDefault(p => p.Type == PartType.Plain); SmartFormatter.Templates.Register(template.Name, plainPart?.Value ?? string.Empty); @@ -540,7 +549,7 @@ private void RegisterPlainTextTemplates() /// Prepares the mail message file and string attachments: /// Replacing placeholders with their values and setting correct encoding. /// - private void BuildAttachmentPartsForMessage(object dataItem) + private void BuildAttachmentPartsForMessage(object? dataItem) { _badAttachmentFiles.Clear(); _attachmentParts = new List(); @@ -610,7 +619,7 @@ private string MakeFullPath(string filename) /// The IHtmlConverter to be used for converting. If the converter is null, the /// will be used. /// - public void ConvertHtmlToPlainText(IHtmlConverter converter = null) + public void ConvertHtmlToPlainText(IHtmlConverter? converter = null) { PlainText = converter == null ? new AngleSharpHtmlConverter().ToPlainText(HtmlText) @@ -642,7 +651,7 @@ public IEnumerable GetMimeMessages(IEnumerable dataSource) /// /// Returns a MailMessage ready to be sent by an SmtpClient. /// Throws a general , which contains a list of exceptions giving more details. - public MimeMessage GetMimeMessage(object dataItem = default) + public MimeMessage GetMimeMessage(object? dataItem = default) { lock (SyncRoot) { @@ -692,7 +701,7 @@ public MimeMessage GetMimeMessage(object dataItem = default) exceptions.Add(new AddressException("No from address.", _badMailAddr, null)); if (string.IsNullOrEmpty(HtmlText) && string.IsNullOrEmpty(PlainText) && string.IsNullOrEmpty(Subject) && !FileAttachments.Any() && !InlineAttachments.Any() && !StringAttachments.Any() && !StreamAttachments.Any()) - exceptions.Add(new EmtpyContentException("Message is empty.", null)); + exceptions.Add(new EmptyContentException("Message is empty.", null)); if (_badMailAddr.Count > 0) exceptions.Add( new AddressException($"Bad mail address(es): {string.Join(", ", _badMailAddr.ToArray())}", @@ -730,10 +739,7 @@ public MimeMessage GetMimeMessage(object dataItem = default) mimeMessage.Body = mixed; } - if (mimeMessage.Body == null) - { - mimeMessage.Body = _textMessagePart ?? new TextPart("plain") { Text = string.Empty }; - } + mimeMessage.Body ??= _textMessagePart ?? new TextPart("plain") { Text = string.Empty }; // Throw a general exception in case of any exceptions // Note: The MimeMessage, as far as it could completed, is one of the parameters of the exception @@ -772,8 +778,8 @@ private void Dispose(bool disposing) if (disposing) { _textMessagePart = null; - _attachmentParts = null; - StreamAttachments = null; + _attachmentParts.Clear(); + StreamAttachments.Clear(); } } _disposed = true; @@ -786,14 +792,14 @@ private void Dispose(bool disposing) /// /// Prepares all recipient address and the corresponding header fields of a mail message. /// - private void AddAddressesToMailMessage(MimeMessage mimeMessage, object dataItem) + private void AddAddressesToMailMessage(MimeMessage mimeMessage, object? dataItem) { _badMailAddr.Clear(); - MailMergeAddress testAddress = null; - foreach (MailMergeAddress mmAddr in MailMergeAddresses.Where(mmAddr => mmAddr.AddrType == MailAddressType.TestAddress)) + MailMergeAddress? testAddress = null; + foreach (var mmAddr in MailMergeAddresses.Where(mmAddr => mmAddr.AddrType == MailAddressType.TestAddress)) { - testAddress = new MailMergeAddress(MailAddressType.TestAddress, mmAddr.Address, mmAddr.DisplayName); + testAddress = new MailMergeAddress(MailAddressType.TestAddress, mmAddr.DisplayName, mmAddr.Address); } if (Config.StandardFromAddress != null) @@ -807,7 +813,7 @@ private void AddAddressesToMailMessage(MimeMessage mimeMessage, object dataItem) { try { - MailboxAddress mailboxAddr; + MailboxAddress? mailboxAddr; // use the address part the test mail address (if set) but use the original display name if (testAddress != null) { @@ -838,11 +844,11 @@ private void AddAddressesToMailMessage(MimeMessage mimeMessage, object dataItem) break; case MailAddressType.ConfirmReadingTo: mimeMessage.Headers.RemoveAll(HeaderId.DispositionNotificationTo); - mimeMessage.Headers.Add(HeaderId.DispositionNotificationTo, mailboxAddr.Address); + mimeMessage.Headers.Add(HeaderId.DispositionNotificationTo, mailboxAddr?.Address); break; case MailAddressType.ReturnReceiptTo: mimeMessage.Headers.RemoveAll(HeaderId.ReturnReceiptTo); - mimeMessage.Headers.Add(HeaderId.ReturnReceiptTo, mailboxAddr.Address); + mimeMessage.Headers.Add(HeaderId.ReturnReceiptTo, mailboxAddr?.Address); break; case MailAddressType.Sender: mimeMessage.Sender = mailboxAddr; @@ -854,7 +860,7 @@ private void AddAddressesToMailMessage(MimeMessage mimeMessage, object dataItem) } catch (FormatException) { - _badMailAddr.Add(mmAddr.ToString()); + _badMailAddr.Add(mmAddr.ToString()!); } } } @@ -862,7 +868,7 @@ private void AddAddressesToMailMessage(MimeMessage mimeMessage, object dataItem) /// /// Sets all attributes of a mail message. /// - private void AddAttributesToMailMessage(MimeMessage mimeMessage, object dataItem) + private void AddAttributesToMailMessage(MimeMessage mimeMessage, object? dataItem) { mimeMessage.Priority = Config.Priority; @@ -928,7 +934,7 @@ public void Serialize(string filename, Encoding encoding) /// /// /// - public static MailMergeMessage Deserialize(string xml) + public static MailMergeMessage? Deserialize(string xml) { return SerializationFactory.Deserialize(xml); } @@ -938,11 +944,10 @@ public static MailMergeMessage Deserialize(string xml) /// /// /// - public static MailMergeMessage Deserialize(Stream stream, Encoding encoding) + public static MailMergeMessage? Deserialize(Stream stream, Encoding encoding) { -#pragma warning disable IDE0068 // Use recommended dispose pattern - return Deserialize(new StreamReader(stream, encoding), true); -#pragma warning restore IDE0068 // Use recommended dispose pattern + using var sr = new StreamReader(stream, encoding); + return Deserialize(sr, true); } /// @@ -950,7 +955,7 @@ public static MailMergeMessage Deserialize(Stream stream, Encoding encoding) /// /// /// - public static MailMergeMessage Deserialize(string filename, Encoding encoding) + public static MailMergeMessage? Deserialize(string filename, Encoding encoding) { return SerializationFactory.Deserialize(filename, encoding); } @@ -961,7 +966,7 @@ public static MailMergeMessage Deserialize(string filename, Encoding encoding) /// /// Returns a instance. /// If true, the writer will not be closed and disposed, so that the underlying stream can be used on return. - private static MailMergeMessage Deserialize(StreamReader reader, bool isStream) + private static MailMergeMessage? Deserialize(StreamReader reader, bool isStream) { return SerializationFactory.Deserialize(reader, isStream); } @@ -975,11 +980,11 @@ private static MailMergeMessage Deserialize(StreamReader reader, bool isStream) /// /// /// Returns true, if both instances are equal, else false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((MailMergeMessage)obj); } @@ -1004,9 +1009,9 @@ public bool Equals(MailMergeMessage mmm) { return Info.Equals(mmm.Info) && MailMergeAddresses.Equals(mmm.MailMergeAddresses) && - Equals(FileAttachments, mmm.FileAttachments) && - Equals(ExternalInlineAttachments, mmm.ExternalInlineAttachments) && - Equals(StringAttachments, mmm.StringAttachments) && + MailMergeMessage.Equals(FileAttachments, mmm.FileAttachments) && + MailMergeMessage.Equals(ExternalInlineAttachments, mmm.ExternalInlineAttachments) && + MailMergeMessage.Equals(StringAttachments, mmm.StringAttachments) && string.Equals(Subject, mmm.Subject) && string.Equals(PlainText, mmm.PlainText) && string.Equals(HtmlText, mmm.HtmlText) && @@ -1014,7 +1019,7 @@ public bool Equals(MailMergeMessage mmm) Config.Equals(mmm.Config); } - private bool Equals(HeaderList hl1, HeaderList hl2) + private static bool Equals(HeaderList hl1, HeaderList hl2) { var hl1Dict = hl1.ToDictionary(header => header.Id, header => header.Value); var h21Dict = hl2.ToDictionary(header => header.Id, header => header.Value); @@ -1023,13 +1028,13 @@ private bool Equals(HeaderList hl1, HeaderList hl2) return !hl1Dict.Except(h21Dict).Union(h21Dict.Except(hl1Dict)).Any(); } - private bool Equals(HashSet fl1, HashSet fl2) + private static bool Equals(HashSet fl1, HashSet fl2) { // not any entry missing in fl1, nor in the other list return !fl1.Except(fl2).Union(fl2.Except(fl1)).Any(); } - private bool Equals(HashSet sa1, HashSet sa2) + private static bool Equals(HashSet sa1, HashSet sa2) { // not any entry missing in sa11, nor in the other list return !sa1.Except(sa2).Union(sa2.Except(sa1)).Any(); @@ -1046,20 +1051,18 @@ private bool Equals(HashSet sa1, HashSet sa2 /// public static void DisposeFileStreams(MimeMessage mimeMessage) { - if (mimeMessage == null) return; - // Dispose the streams of file attachments - foreach (var mimePart in mimeMessage.Attachments?.Where(mp => mp is MimePart)?.Cast()) + foreach (var mimePart in mimeMessage.Attachments.Where(mp => mp is MimePart).Cast()) { mimePart?.Content?.Stream?.Dispose(); } // Dispose the streams of HTML inline file attachments - foreach (var mimePart in mimeMessage.BodyParts?.Where(mp => mp is MimePart)?.Cast()) + foreach (var mimePart in mimeMessage.BodyParts.Where(mp => mp is MimePart).Cast()) { mimePart?.Content?.Stream?.Dispose(); } } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MailMergeMessage_Exception.cs b/Src/MailMergeLib/MailMergeMessage_Exception.cs index d7b9ee4..1e1e47a 100644 --- a/Src/MailMergeLib/MailMergeMessage_Exception.cs +++ b/Src/MailMergeLib/MailMergeMessage_Exception.cs @@ -4,7 +4,7 @@ namespace MailMergeLib; -partial class MailMergeMessage +public partial class MailMergeMessage { #region Nested type: AddressException @@ -13,13 +13,19 @@ partial class MailMergeMessage /// public class AddressException : Exception { - public AddressException(string message, HashSet badAddress, Exception innerException) + /// + /// CTOR + /// + public AddressException(string message, HashSet badAddress, Exception? innerException) : base(message, innerException) { BadAddress = badAddress; } - public HashSet BadAddress { get; } = new HashSet(); + /// + /// Gets the bad email address(es) leading to the exception. + /// + public HashSet BadAddress { get; } } #endregion @@ -31,13 +37,19 @@ public AddressException(string message, HashSet badAddress, Exception in /// public class AttachmentException : Exception { - public AttachmentException(string message, HashSet badAttachment, Exception innerException) + /// + /// CTOR. + /// + public AttachmentException(string message, HashSet badAttachment, Exception? innerException) : base(message, innerException) { BadAttachment = badAttachment; } - public HashSet BadAttachment { get; } = new HashSet(); + /// + /// Gets the list of bad attachments that caused the exception. + /// + public HashSet BadAttachment { get; } } #endregion @@ -47,9 +59,12 @@ public AttachmentException(string message, HashSet badAttachment, Except /// /// Mail merge empty content exception. /// - public class EmtpyContentException : Exception + public class EmptyContentException : Exception { - public EmtpyContentException(string message, Exception innerException) + /// + /// CTOR. + /// + public EmptyContentException(string message, Exception? innerException) : base(message, innerException) { } @@ -64,15 +79,25 @@ public EmtpyContentException(string message, Exception innerException) /// public class MailMergeMessageException : AggregateException { - public MailMergeMessageException(string message, IEnumerable exceptions, MimeMessage mimeMessage) + /// + /// CTOR. + /// + public MailMergeMessageException(string message, IEnumerable exceptions, MimeMessage? mimeMessage) : base(message, exceptions) { MimeMessage = mimeMessage; } - public AggregateException Exception { get; } + /// + /// Gets all exceptions that were thrown when the message was created. + /// Check for more details. + /// + public AggregateException? Exception { get; } - public MimeMessage MimeMessage { get; } + /// + /// Gets the where the exception was thrown. + /// + public MimeMessage? MimeMessage { get; } } #endregion @@ -84,12 +109,18 @@ public MailMergeMessageException(string message, IEnumerable exceptio /// public class VariableException : Exception { - public VariableException(string message, HashSet missingVariable, Exception innerException) + /// + /// CTOR. + /// + public VariableException(string message, HashSet missingVariable, Exception? innerException) : base(message, innerException) { MissingVariable = missingVariable; } + /// + /// Gets the missing variables that caused the exception. + /// public HashSet MissingVariable { get; } } @@ -102,10 +133,13 @@ public VariableException(string message, HashSet missingVariable, Except /// public class ParseException : Exception { + /// + /// CTOR. + /// public ParseException(string message, Exception innerException) : base(message, innerException) { } } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MailMergeSender.cs b/Src/MailMergeLib/MailMergeSender.cs index f50de09..a6b48ed 100644 --- a/Src/MailMergeLib/MailMergeSender.cs +++ b/Src/MailMergeLib/MailMergeSender.cs @@ -16,7 +16,7 @@ namespace MailMergeLib; public class MailMergeSender : IDisposable { private bool _disposed; - private CancellationTokenSource _cancellationTokenSource; + private CancellationTokenSource _cancellationTokenSource = new(); /// /// CTOR @@ -25,7 +25,6 @@ public MailMergeSender() { IsBusy = false; GetInitializedSmtpClientDelegate = GetInitializedSmtpClient; - RenewCancellationTokenSource(); } /// @@ -74,10 +73,10 @@ private void RenewCancellationTokenSource() public async Task SendAsync(MailMergeMessage mailMergeMessage, IEnumerable dataSource) { if (mailMergeMessage == null) - throw new ArgumentNullException($"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null."); + throw new ArgumentNullException(nameof(mailMergeMessage),$"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null."); if (dataSource == null) - throw new ArgumentNullException($"{nameof(dataSource)} is null."); + throw new ArgumentNullException(nameof(dataSource),$"{nameof(dataSource)} is null."); if (IsBusy) throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}."); @@ -88,7 +87,7 @@ public async Task SendAsync(MailMergeMessage mailMergeMessage, IEnumerable var tasksUsed = new HashSet(); - void AfterSend(object obj, MailSenderAfterSendEventArgs args) + void AfterSend(object? obj, MailSenderAfterSendEventArgs args) { if (args.Error == null) Interlocked.Increment(ref sentMsgCount); @@ -130,7 +129,7 @@ void AfterSend(object obj, MailSenderAfterSendEventArgs args) await Task.Delay(smtpConfigForTask[taskNo].DelayBetweenMessages, _cancellationTokenSource.Token).ConfigureAwait(false); var localDataItem = dataItem; // no modified enclosure - MimeMessage mimeMessage = null; + MimeMessage? mimeMessage = null; try { mimeMessage = await Task.Run(() => mailMergeMessage.GetMimeMessage(localDataItem), _cancellationTokenSource.Token).ConfigureAwait(false); @@ -175,7 +174,7 @@ void AfterSend(object obj, MailSenderAfterSendEventArgs args) } smtpClient.ProtocolLogger?.Dispose(); - smtpClient.Disconnect(true, _cancellationTokenSource.Token); + await smtpClient.DisconnectAsync(true, _cancellationTokenSource.Token); }, _cancellationTokenSource.Token); } @@ -218,7 +217,7 @@ void AfterSend(object obj, MailSenderAfterSendEventArgs args) public async Task SendAsync(MailMergeMessage mailMergeMessage, object dataItem) { if (mailMergeMessage == null) - throw new ArgumentNullException($"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null."); + throw new ArgumentNullException(nameof(mailMergeMessage),$"{nameof(SendAsync)}: {nameof(mailMergeMessage)} is null."); if (IsBusy) throw new InvalidOperationException($"{nameof(SendAsync)}: A send operation is pending in this instance of {nameof(MailMergeSender)}."); @@ -231,7 +230,7 @@ await Task.Run(async () => { var smtpClientConfig = Config.SmtpClientConfig[0]; // use the standard configuration using var smtpClient = GetInitializedSmtpClientDelegate(smtpClientConfig); - MimeMessage mimeMessage = null; + MimeMessage? mimeMessage = null; try { mimeMessage = mailMergeMessage.GetMimeMessage(dataItem); @@ -288,7 +287,7 @@ await Task.Run(async () => internal async Task SendMimeMessageAsync(SmtpClient smtpClient, MimeMessage mimeMsg, SmtpClientConfig config) { var startTime = DateTime.Now; - Exception sendException; + Exception? sendException; // the client can rely on the sequence of events: OnBeforeSend, OnSendFailure (if any), OnAfterSend OnBeforeSend?.Invoke(smtpClient, new MailSenderBeforeSendEventArgs(config, mimeMsg, startTime, null, _cancellationTokenSource.Token.IsCancellationRequested)); @@ -304,7 +303,7 @@ internal async Task SendMimeMessageAsync(SmtpClient smtpClient, MimeMessage mime { case MessageOutput.None: break; - case MessageOutput.Directory: + case MessageOutput.Directory when config.MailOutputDirectory != null: await mimeMsg.WriteToAsync(System.IO.Path.Combine(config.MailOutputDirectory, Guid.NewGuid().ToString("N") + mailExt), _cancellationTokenSource.Token); break; #if NETFRAMEWORK @@ -493,10 +492,10 @@ public void SendCancel(int waitTime = 0) public void Send(MailMergeMessage mailMergeMessage, IEnumerable dataSource) { if (mailMergeMessage == null) - throw new ArgumentNullException($"{nameof(Send)}: {nameof(mailMergeMessage)} is null."); + throw new ArgumentNullException(nameof(mailMergeMessage),$"{nameof(Send)}: {nameof(mailMergeMessage)} is null."); if (dataSource == null) - throw new ArgumentNullException($"{nameof(dataSource)} is null."); + throw new ArgumentNullException(nameof(dataSource),$"{nameof(dataSource)} is null."); if (IsBusy) throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}."); @@ -521,7 +520,7 @@ public void Send(MailMergeMessage mailMergeMessage, IEnumerable dataSource OnMergeProgress?.Invoke(this, new MailSenderMergeProgressEventArgs(startTime, numOfRecords, sentMsgCount, 0)); - MimeMessage mimeMessage = null; + MimeMessage? mimeMessage = null; try { mimeMessage = mailMergeMessage.GetMimeMessage(dataItem); @@ -601,7 +600,7 @@ public void Send(MailMergeMessage mailMergeMessage, IEnumerable dataSource public void Send(MailMergeMessage mailMergeMessage, object dataItem) { if (mailMergeMessage == null) - throw new ArgumentNullException($"{nameof(Send)}: {nameof(mailMergeMessage)} is null."); + throw new ArgumentNullException(nameof(mailMergeMessage),$"{nameof(Send)}: {nameof(mailMergeMessage)} is null."); if (IsBusy) throw new InvalidOperationException($"{nameof(Send)}: A send operation is pending in this instance of {nameof(MailMergeSender)}."); @@ -612,7 +611,7 @@ public void Send(MailMergeMessage mailMergeMessage, object dataItem) { var smtpClientConfig = Config.SmtpClientConfig[0]; // use the standard configuration using var smtpClient = GetInitializedSmtpClientDelegate(smtpClientConfig); - MimeMessage mimeMessage = null; + MimeMessage? mimeMessage = null; try { mimeMessage = mailMergeMessage.GetMimeMessage(dataItem); @@ -666,7 +665,7 @@ public void Send(MailMergeMessage mailMergeMessage, object dataItem) internal void SendMimeMessage(SmtpClient smtpClient, MimeMessage mimeMsg, SmtpClientConfig config) { var startTime = DateTime.Now; - Exception sendException; + Exception? sendException; // the client can rely on the sequence of events: OnBeforeSend, OnSendFailure (if any), OnAfterSend OnBeforeSend?.Invoke(smtpClient, new MailSenderBeforeSendEventArgs(config, mimeMsg, startTime, null, _cancellationTokenSource.Token.IsCancellationRequested)); @@ -682,7 +681,7 @@ internal void SendMimeMessage(SmtpClient smtpClient, MimeMessage mimeMsg, SmtpCl { case MessageOutput.None: break; - case MessageOutput.Directory: + case MessageOutput.Directory when config.MailOutputDirectory != null: mimeMsg.WriteTo(System.IO.Path.Combine(config.MailOutputDirectory, Guid.NewGuid().ToString("N") + mailExt), _cancellationTokenSource.Token); break; #if NETFRAMEWORK @@ -838,52 +837,52 @@ internal void SendMimeMessageToSmtpServer(SmtpClient smtpClient, MimeMessage mes /// /// Event raising when getting the merged MimeMessage of the MailMergeMessage has failed. /// - public event EventHandler OnMessageFailure; + public event EventHandler? OnMessageFailure; /// /// Event raising before sending a mail message. /// - public event EventHandler OnBeforeSend; + public event EventHandler? OnBeforeSend; /// /// Event raising right after the 's connection to the server is up (but not yet authenticated). /// - public event EventHandler OnSmtpConnected; + public event EventHandler? OnSmtpConnected; /// /// Event raising after the has authenticated on the server. /// - public event EventHandler OnSmtpAuthenticated; + public event EventHandler? OnSmtpAuthenticated; /// /// Event raising after the has disconnected from the server. /// - public event EventHandler OnSmtpDisconnected; + public event EventHandler? OnSmtpDisconnected; /// /// Event raising after sending a mail message. /// - public event EventHandler OnAfterSend; + public event EventHandler? OnAfterSend; /// /// Event raising, if an error occurs when sending a mail message. /// - public event EventHandler OnSendFailure; + public event EventHandler? OnSendFailure; /// /// Event raising before starting with mail merge. /// - public event EventHandler OnMergeBegin; + public event EventHandler? OnMergeBegin; /// /// Event raising during mail merge progress, i.e. after each message sent. /// - public event EventHandler OnMergeProgress; + public event EventHandler? OnMergeProgress; /// /// Event raising after completing mail merge. /// - public event EventHandler OnMergeComplete; + public event EventHandler? OnMergeComplete; /// /// The settings for a MailMergeSender. @@ -953,4 +952,4 @@ private void Dispose(bool disposing) } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MailSender_EventArgs.cs b/Src/MailMergeLib/MailSender_EventArgs.cs index e43daf3..9f4969a 100644 --- a/Src/MailMergeLib/MailSender_EventArgs.cs +++ b/Src/MailMergeLib/MailSender_EventArgs.cs @@ -8,15 +8,33 @@ namespace MailMergeLib; /// public class MailSenderAfterSendEventArgs : EventArgs { + /// + /// Returns , if the operation has cancelled state. + /// public readonly bool Cancelled; - public readonly Exception Error; + /// + /// Contains any that was thrown. + /// + public readonly Exception? Error; + /// + /// Gets the current . + /// public readonly SmtpClientConfig SmtpClientConfig; + /// + /// Gets the current . + /// public readonly MimeMessage MimeMessage; + /// + /// Gets the when sending has started. + /// public readonly DateTime StartTime; + /// + /// Gets the when sending was completed. + /// public readonly DateTime EndTime; internal MailSenderAfterSendEventArgs(SmtpClientConfig smtpConfig, MimeMessage mailMergeMessage, DateTime startTime, DateTime endTime, - Exception error, bool cancelled) + Exception? error, bool cancelled) { Error = error; Cancelled = cancelled; @@ -32,13 +50,28 @@ internal MailSenderAfterSendEventArgs(SmtpClientConfig smtpConfig, MimeMessage m /// public class MailSenderBeforeSendEventArgs : EventArgs { + /// + /// Returns , if the operation has cancelled state. + /// public readonly bool Cancelled; - public readonly Exception Error; + /// + /// Contains any that was thrown. + /// + public readonly Exception? Error; + /// + /// Gets the current . + /// public readonly SmtpClientConfig SmtpClientConfig; + /// + /// Gets the current . + /// public readonly MimeMessage MimeMessage; + /// + /// Gets the when sending has started. + /// public readonly DateTime StartTime; - internal MailSenderBeforeSendEventArgs(SmtpClientConfig smtpConfig, MimeMessage mimeMessage, DateTime startTime, Exception error, + internal MailSenderBeforeSendEventArgs(SmtpClientConfig smtpConfig, MimeMessage mimeMessage, DateTime startTime, Exception? error, bool cancelled) { Error = error; @@ -54,6 +87,9 @@ internal MailSenderBeforeSendEventArgs(SmtpClientConfig smtpConfig, MimeMessage /// public class MailSenderSmtpClientEventArgs : EventArgs { + /// + /// Gets the current . + /// public readonly SmtpClientConfig SmtpClientConfig; internal MailSenderSmtpClientEventArgs(SmtpClientConfig smtpConfig) @@ -66,7 +102,13 @@ internal MailSenderSmtpClientEventArgs(SmtpClientConfig smtpConfig) /// public class MailSenderMergeBeginEventArgs : EventArgs { + /// + /// Gets the when sending was completed. + /// public readonly DateTime StartTime; + /// + /// Gets current number of total messages sent. + /// public readonly int TotalMsg; internal MailSenderMergeBeginEventArgs(DateTime startTime, int totalMsg) @@ -81,7 +123,13 @@ internal MailSenderMergeBeginEventArgs(DateTime startTime, int totalMsg) /// public class MailSenderMergeProgressEventArgs : MailSenderMergeBeginEventArgs { + /// + /// Gets the number of sent messages. + /// public readonly int SentMsg; + /// + /// Gets the number of messages that were not sent due to errors. + /// public readonly int ErrorMsg; internal MailSenderMergeProgressEventArgs(DateTime startTime, int totalMsg, int sentMsg, int errorMsg) : base(startTime, totalMsg) @@ -96,7 +144,13 @@ internal MailSenderMergeProgressEventArgs(DateTime startTime, int totalMsg, int /// public class MailSenderMergeCompleteEventArgs : MailSenderMergeProgressEventArgs { + /// + /// Gets the when sending was completed. + /// public readonly DateTime EndTime; + /// + /// Gets the number of s that were used for sending. + /// public readonly int NumOfSmtpClientsUsed; internal MailSenderMergeCompleteEventArgs(DateTime startTime, DateTime endTime, int totalMsg, int sentMsg, int errorMsg, int numOfSmtpClientsUsed) : base(startTime, totalMsg, sentMsg, errorMsg) @@ -111,9 +165,21 @@ internal MailSenderMergeCompleteEventArgs(DateTime startTime, DateTime endTime, /// public class MailSenderSendFailureEventArgs : EventArgs { + /// + /// Contains any that was thrown. + /// public readonly Exception Error; + /// + /// Gets the current number of failures that happened when trying to send the message. + /// public readonly int FailureCounter; + /// + /// Gets the current . + /// public readonly MimeMessage MimeMessage; + /// + /// Gets the current . + /// public readonly SmtpClientConfig SmtpClientConfig; internal MailSenderSendFailureEventArgs(Exception error, int failureCounter, SmtpClientConfig smtpClientConfig, @@ -145,12 +211,12 @@ public class MailMessageFailureEventArgs : EventArgs /// /// The data item which was used to build the . /// - public readonly object DataSource; + public readonly object? DataSource; /// /// The which could be built until any errors occurred. /// The delegate may modify the mime message by resolving any errors and return the new . /// - public MimeMessage MimeMessage; + public MimeMessage? MimeMessage; /// /// The value returned to the build process to determine /// whether a should be thrown. @@ -158,7 +224,7 @@ public class MailMessageFailureEventArgs : EventArgs /// public bool ThrowException; - internal MailMessageFailureEventArgs(Exception error, MailMergeMessage mailMergeMessage, object dataSource, MimeMessage mimeMessage = null, bool throwException = true) + internal MailMessageFailureEventArgs(Exception error, MailMergeMessage mailMergeMessage, object? dataSource, MimeMessage? mimeMessage = null, bool throwException = true) { Error = error; MailMergeMessage = mailMergeMessage; @@ -166,4 +232,4 @@ internal MailMessageFailureEventArgs(Exception error, MailMergeMessage mailMerge MimeMessage = mimeMessage; ThrowException = throwException; } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MailSmartFormatter.cs b/Src/MailMergeLib/MailSmartFormatter.cs index 47d05f7..c8cab9e 100644 --- a/Src/MailMergeLib/MailSmartFormatter.cs +++ b/Src/MailMergeLib/MailSmartFormatter.cs @@ -1,7 +1,6 @@ using SmartFormat; -using SmartFormat.Core.Extensions; +using SmartFormat.Core.Settings; using SmartFormat.Extensions; -using SmartFormatMail = SmartFormat; namespace MailMergeLib; @@ -15,37 +14,36 @@ namespace MailMergeLib; /// public class MailSmartFormatter : SmartFormatter { - internal MailSmartFormatter() : base() + private MailSmartFormatter(SmartSettings? currentSmartSettings) : base(currentSmartSettings) { - // Register all default extensions here: - // Add all extensions: - // Note, the order is important; the extensions - // will be executed in this order: - var listFormatter = new ListFormatter(this); - + Templates = new TemplateFormatter(); + + // Default sources from Smart.CreateDefaultSmartFormat() v3.2.1 AddExtensions( - (ISource)listFormatter, // ListFormatter MUST be first - new DictionarySource(this), - new ValueTupleSource(this), - new ValueTupleSource(this), - new JsonSource(this), - //new XmlSource(this), - new ReflectionSource(this), - // The DefaultSource reproduces the string.Format behavior: - new DefaultSource(this) - ); - AddExtensions( - (IFormatter)listFormatter, - new PluralLocalizationFormatter("en"), - new ConditionalFormatter(), - new TimeFormatter("en"), - //new XElementFormatter(), - new ChooseFormatter(), - new DefaultFormatter() - ); + new StringSource(), + new ListFormatter(), + new DictionarySource(), + new ValueTupleSource(), + new ReflectionSource(), + new DefaultSource(), + new KeyValuePairSource()) + + // Default formatters from Smart.CreateDefaultSmartFormat() v3.2.1 + .AddExtensions(new PluralLocalizationFormatter(), + new ConditionalFormatter(), + new IsMatchFormatter(), + new NullFormatter(), + new ChooseFormatter(), + new SubStringFormatter(), + // The DefaultSource reproduces the string.Format behavior: + new DefaultFormatter()) - Templates = new TemplateFormatter(this); - AddExtensions(Templates); + // Extensions to keep API compatibility with MailMergeLib v5.x + .AddExtensions( + new NewtonsoftJsonSource()) + .AddExtensions( + new TimeFormatter { CanAutoDetect = false }, + Templates); } /// @@ -54,25 +52,22 @@ internal MailSmartFormatter() : base() /// Error actions are SmartFormatters defaults. /// /// - internal MailSmartFormatter(SmartFormatterConfig config) : this() + /// + internal MailSmartFormatter(SmartFormatterConfig config, SmartSettings? currentSmartSettings) : this(currentSmartSettings) { - if (config == null) return; - SetConfig(config); } /// /// Gets or sets the where the templates can be registered later on. /// - internal TemplateFormatter Templates { get; set; } - + internal TemplateFormatter? Templates { get; set; } + internal void SetConfig(SmartFormatterConfig sfConfig) { - if (sfConfig == null) return; - - Settings.FormatErrorAction = sfConfig.FormatErrorAction; - Settings.ParseErrorAction = sfConfig.ParseErrorAction; - Settings.CaseSensitivity = sfConfig.CaseSensitivity; - Settings.ConvertCharacterStringLiterals = sfConfig.ConvertCharacterStringLiterals; + Settings.Formatter.ErrorAction = (FormatErrorAction) sfConfig.FormatErrorAction; + Settings.Parser.ErrorAction = (ParseErrorAction) sfConfig.ParseErrorAction; + Settings.CaseSensitivity = (SmartFormat.Core.Settings.CaseSensitivityType) sfConfig.CaseSensitivity; + Settings.Parser.ConvertCharacterStringLiterals = sfConfig.ConvertCharacterStringLiterals; } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MessageConfig.cs b/Src/MailMergeLib/MessageConfig.cs index be97b77..8e198ae 100644 --- a/Src/MailMergeLib/MessageConfig.cs +++ b/Src/MailMergeLib/MessageConfig.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using System.Text; using MimeKit; @@ -33,6 +32,7 @@ public MessageConfig() [YAXSerializableField] public ContentEncoding BinaryTransferEncoding { get; set; } = ContentEncoding.Base64; +#pragma warning disable IDE0051 /// /// Character encoding. /// Used for serialization. It is the string representation of . @@ -44,12 +44,14 @@ private string CharacterEncodingName get => CharacterEncoding.WebName; set => CharacterEncoding = Encoding.GetEncoding(value); } +#pragma warning restore IDE0051 /// /// Character encoding. /// public Encoding CharacterEncoding { get; set; } = Encoding.UTF8; +#pragma warning disable IDE0051 /// /// Culture information. /// Used for serialization. It is the string representation of . @@ -61,6 +63,7 @@ private string CultureInfoName get => CultureInfo.Name; set => CultureInfo = new CultureInfo(value); } +#pragma warning restore IDE0051 /// /// Culture information. @@ -115,34 +118,36 @@ public string FileBaseDirectory [YAXSerializableField] public MessagePriority Priority { get; set; } = MessagePriority.Normal; +#pragma warning disable IDE0051 /// /// The standard mailbox address which will be used as one of the "from" addresses. /// Used for serialization. It is the string representation of . /// [YAXSerializableField] [YAXSerializeAs("StandardFromAddress")] - private string StandardFromAddressText + private string? StandardFromAddressText { get => StandardFromAddress?.ToString(); set => StandardFromAddress = !string.IsNullOrEmpty(value) ? MailboxAddress.Parse(ParserOptions.Default, value) : null; } +#pragma warning restore IDE0051 /// /// The standard mailbox address which will be used as one of the "from" addresses. /// - public MailboxAddress StandardFromAddress { get; set; } + public MailboxAddress? StandardFromAddress { get; set; } /// /// The organization header of a mail message. /// [YAXSerializableField] - public string Organization { get; set; } + public string? Organization { get; set; } /// /// Gets or sets the "x-mailer" header value to be used. /// [YAXSerializableField] - public string Xmailer { get; set; } + public string? Xmailer { get; set; } /// /// SmartFormatter configuration for parsing and formatting errors. @@ -151,6 +156,10 @@ private string StandardFromAddressText public SmartFormatterConfig SmartFormatterConfig { get; internal set; } = new SmartFormatterConfig(); #region *** Equality *** + + /// + /// Compares for equality + /// protected bool Equals(MessageConfig other) { return TextTransferEncoding == other.TextTransferEncoding && @@ -168,22 +177,24 @@ protected bool Equals(MessageConfig other) SmartFormatterConfig.Equals(other.SmartFormatterConfig); } - private bool Equals(MailboxAddress addr, MailboxAddress otherAddr) + private static bool Equals(MailboxAddress? addr, MailboxAddress? otherAddr) { if (addr is null && otherAddr is null) return true; if (ReferenceEquals(addr, otherAddr)) return true; - if (otherAddr.GetType() != addr?.GetType()) return false; - return addr.ToString() == otherAddr.ToString(); + if (otherAddr?.GetType() != addr?.GetType()) return false; + return addr?.ToString() == otherAddr?.ToString(); } - public override bool Equals(object obj) + /// + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((MessageConfig)obj); } + /// public override int GetHashCode() { unchecked @@ -206,4 +217,4 @@ public override int GetHashCode() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MessageStore/FileMessageInfo.cs b/Src/MailMergeLib/MessageStore/FileMessageInfo.cs index 581de14..c3fc2c9 100644 --- a/Src/MailMergeLib/MessageStore/FileMessageInfo.cs +++ b/Src/MailMergeLib/MessageStore/FileMessageInfo.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text; using MailMergeLib.Serialization; @@ -19,7 +20,7 @@ internal FileMessageInfo() /// /// The location of the xml serialized file. /// - public FileInfo MessageFile { get; internal set; } + public FileInfo? MessageFile { get; internal set; } /// /// Gets or sets the to apply when loading s from the file system. @@ -30,8 +31,11 @@ internal FileMessageInfo() /// Deserializes the and returns a new message object. /// /// Returns the deserialized object from the . - public override MailMergeMessage LoadMessage() + /// In case is + public override MailMergeMessage? LoadMessage() { + if (MessageFile == null) throw new InvalidOperationException($"{nameof(MessageFile)} must not be null"); + return SerializationFactory.Deserialize(MessageFile.FullName, MessageEncoding); } } \ No newline at end of file diff --git a/Src/MailMergeLib/MessageStore/FileMessageStore.cs b/Src/MailMergeLib/MessageStore/FileMessageStore.cs index b457a80..5e194f6 100644 --- a/Src/MailMergeLib/MessageStore/FileMessageStore.cs +++ b/Src/MailMergeLib/MessageStore/FileMessageStore.cs @@ -142,7 +142,7 @@ private void Serialize(TextWriter writer, bool isStream) /// /// /// - public static FileMessageStore Deserialize(string xml) + public static FileMessageStore? Deserialize(string xml) { return SerializationFactory.Deserialize(xml); } @@ -152,7 +152,7 @@ public static FileMessageStore Deserialize(string xml) /// /// /// - public static FileMessageStore Deserialize(Stream stream, System.Text.Encoding encoding) + public static FileMessageStore? Deserialize(Stream stream, System.Text.Encoding encoding) { return SerializationFactory.Deserialize(stream, encoding); } @@ -162,7 +162,7 @@ public static FileMessageStore Deserialize(Stream stream, System.Text.Encoding e /// /// /// - public static FileMessageStore Deserialize(string filename, System.Text.Encoding encoding) + public static FileMessageStore? Deserialize(string filename, System.Text.Encoding encoding) { return SerializationFactory.Deserialize(filename, encoding); } @@ -171,7 +171,7 @@ public static FileMessageStore Deserialize(string filename, System.Text.Encoding #region *** Equality *** - private bool Equals(FileMessageStore other) + private bool Equals(FileMessageStore? other) { if (other == null) return false; return !SearchFolders.Except(other.SearchFolders).Union(other.SearchFolders.Except(SearchFolders)).Any() || @@ -183,7 +183,7 @@ private bool Equals(FileMessageStore other) /// /// /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; diff --git a/Src/MailMergeLib/MessageStore/IMessageInfo.cs b/Src/MailMergeLib/MessageStore/IMessageInfo.cs index 5af2d7b..49ae6ab 100644 --- a/Src/MailMergeLib/MessageStore/IMessageInfo.cs +++ b/Src/MailMergeLib/MessageStore/IMessageInfo.cs @@ -13,22 +13,25 @@ public interface IMessageInfo /// /// The category of the . A user-defined string without further relevance for . /// - string Category { get; set; } + string? Category { get; set; } /// /// Description for the . A user-defined string without further relevance for . /// - string Description { get; set; } + string? Description { get; set; } /// /// Comments for the . A user-defined string without further relevance for . /// - string Comments { get; set; } + string? Comments { get; set; } /// /// Data hint for the . A user-defined string without further relevance for . /// - string Data { get; set; } + string? Data { get; set; } + /// + /// Compares for equality + /// bool Equals(IMessageInfo obj); } \ No newline at end of file diff --git a/Src/MailMergeLib/MessageStore/MessageInfo.cs b/Src/MailMergeLib/MessageStore/MessageInfo.cs index a3053a5..acc63b4 100644 --- a/Src/MailMergeLib/MessageStore/MessageInfo.cs +++ b/Src/MailMergeLib/MessageStore/MessageInfo.cs @@ -1,5 +1,4 @@ -using MailMergeLib.Serialization; -using YAXLib.Attributes; +using YAXLib.Attributes; using YAXLib.Enums; namespace MailMergeLib.MessageStore; @@ -21,21 +20,21 @@ public class MessageInfo : IMessageInfo /// [YAXSerializableField] [YAXErrorIfMissed(YAXExceptionTypes.Ignore)] - public string Category { get; set; } + public string? Category { get; set; } /// /// Description for the . A user-defined string without further relevance for . /// [YAXSerializableField] [YAXErrorIfMissed(YAXExceptionTypes.Ignore)] - public string Description { get; set; } + public string? Description { get; set; } /// /// Comments for the . A user-defined string without further relevance for . /// [YAXSerializableField] [YAXErrorIfMissed(YAXExceptionTypes.Ignore)] - public string Comments { get; set; } + public string? Comments { get; set; } /// /// Data hint for the . A user-defined string without further relevance for . @@ -43,16 +42,16 @@ public class MessageInfo : IMessageInfo [YAXSerializableField] [YAXErrorIfMissed(YAXExceptionTypes.Ignore)] [YAXTextEmbedding(TextEmbedding.CData)] - public string Data { get; set; } + public string? Data { get; set; } #region *** Equality *** /// - /// Determines whether the specified is equal to the current. + /// Determines whether the specified is equal to the current. /// /// /// bool - public bool Equals(IMessageInfo other) + public bool Equals(IMessageInfo? other) { if (other == null) return false; return Id == other.Id && Category == other.Category && Description == other.Description && Comments == other.Comments && Data == other.Data; @@ -63,9 +62,9 @@ public bool Equals(IMessageInfo other) /// /// /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (!(obj is IMessageInfo)) return false; return Equals((IMessageInfo)obj); @@ -89,4 +88,4 @@ public override int GetHashCode() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/MessageStore/MessageInfoBase.cs b/Src/MailMergeLib/MessageStore/MessageInfoBase.cs index 7a8f2d1..771f289 100644 --- a/Src/MailMergeLib/MessageStore/MessageInfoBase.cs +++ b/Src/MailMergeLib/MessageStore/MessageInfoBase.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Text; using System.Xml; namespace MailMergeLib.MessageStore; @@ -19,29 +18,29 @@ public abstract class MessageInfoBase : IMessageInfo /// /// The category of the . A user-defined string without further relevance for . /// - public string Category { get; set; } + public string? Category { get; set; } /// /// Description for the . A user-defined string without further relevance for . /// - public string Description { get; set; } + public string? Description { get; set; } /// /// Comments for the . A user-defined string without further relevance for . /// - public string Comments { get; set; } + public string? Comments { get; set; } /// /// Data hint for the . A user-defined string without further relevance for . /// - public string Data { get; set; } + public string? Data { get; set; } /// /// Determines whether the specified is equal to the current. /// /// /// bool - public bool Equals(IMessageInfo other) + public bool Equals(IMessageInfo? other) { if (other == null) return false; return Id == other.Id && Category == other.Category && Description == other.Description && Comments == other.Comments && Data == other.Data; @@ -54,7 +53,7 @@ public bool Equals(IMessageInfo other) /// Method must be overridden in a derived class. /// /// Returns an instance of - public abstract MailMergeMessage LoadMessage(); + public abstract MailMergeMessage? LoadMessage(); #region *** Equality *** @@ -63,12 +62,12 @@ public bool Equals(IMessageInfo other) /// /// /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (!(obj is IMessageInfo)) return false; - return Equals((IMessageInfo)obj); + if (obj is not IMessageInfo info) return false; + return Equals(info); } /// @@ -102,10 +101,8 @@ public override int GetHashCode() /// Return the of a serialized public static IMessageInfo Read(FileSystemInfo fileSystemInfo) { - using (var xmlReader = XmlReader.Create(fileSystemInfo.FullName)) - { - return ReadInfo(xmlReader); - } + using var xmlReader = XmlReader.Create(fileSystemInfo.FullName); + return ReadInfo(xmlReader); } /// @@ -115,19 +112,15 @@ public static IMessageInfo Read(FileSystemInfo fileSystemInfo) /// Return the of a serialized public static IMessageInfo Read(string xmlString) { - using (var stringReader = new StringReader(xmlString)) - { - using (var xmlReader = XmlReader.Create(stringReader)) - { - return ReadInfo(xmlReader); - } - } + using var stringReader = new StringReader(xmlString); + using var xmlReader = XmlReader.Create(stringReader); + return ReadInfo(xmlReader); } private static IMessageInfo ReadInfo(XmlReader xmlReader) { // ReSharper disable once InconsistentNaming - MessageInfo Info = null; + MessageInfo? Info = null; while (xmlReader.ReadToFollowing(nameof(Info)) && xmlReader.Depth == 1) { if (Info != null) @@ -196,4 +189,4 @@ private static IMessageInfo ReadInfo(XmlReader xmlReader) } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/PlainBodyBuilder.cs b/Src/MailMergeLib/PlainBodyBuilder.cs index 6213b94..a6f9772 100644 --- a/Src/MailMergeLib/PlainBodyBuilder.cs +++ b/Src/MailMergeLib/PlainBodyBuilder.cs @@ -7,12 +7,11 @@ internal class PlainBodyBuilder : BodyBuilderBase { private readonly string _plainText; - public PlainBodyBuilder(string plainText) + public PlainBodyBuilder(string? plainText) { _plainText = plainText ?? string.Empty; } - /// /// Gets the ready made body part for a mail message as TextPart /// diff --git a/Src/MailMergeLib/SenderConfig.cs b/Src/MailMergeLib/SenderConfig.cs index 6507913..5880c1b 100644 --- a/Src/MailMergeLib/SenderConfig.cs +++ b/Src/MailMergeLib/SenderConfig.cs @@ -45,6 +45,9 @@ public int MaxNumOfSmtpClients #region *** Equality *** + /// + /// Checks for equality + /// protected bool Equals(SenderConfig other) { if (MaxNumOfSmtpClients != other.MaxNumOfSmtpClients || SmtpClientConfig.Length != other.SmtpClientConfig.Length) @@ -53,14 +56,16 @@ protected bool Equals(SenderConfig other) return !SmtpClientConfig.Where((t, i) => !t.Equals(other.SmtpClientConfig[i])).Any(); } - public override bool Equals(object obj) + /// + public override bool Equals(object? obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((SenderConfig) obj); } + /// public override int GetHashCode() { unchecked diff --git a/Src/MailMergeLib/Serialization/HeaderListSerializer.cs b/Src/MailMergeLib/Serialization/HeaderListSerializer.cs index 56c61e1..f3f132d 100644 --- a/Src/MailMergeLib/Serialization/HeaderListSerializer.cs +++ b/Src/MailMergeLib/Serialization/HeaderListSerializer.cs @@ -46,11 +46,10 @@ public HeaderList DeserializeFromElement(XElement element, ISerializationContext foreach (var header in element.Elements(HeaderElementName)) { - HeaderId id; var idAttr = header.Attributes(HeaderIdName).FirstOrDefault(); if (idAttr != null) { - if (Enum.TryParse(idAttr.Value, out id)) + if (Enum.TryParse(idAttr.Value, out HeaderId id)) { var valueAttr = header.Attributes(HeaderValueName).FirstOrDefault(); if (valueAttr != null) diff --git a/Src/MailMergeLib/Serialization/IPEndPointSerializer.cs b/Src/MailMergeLib/Serialization/IPEndPointSerializer.cs index de7ed00..413c768 100644 --- a/Src/MailMergeLib/Serialization/IPEndPointSerializer.cs +++ b/Src/MailMergeLib/Serialization/IPEndPointSerializer.cs @@ -9,14 +9,14 @@ namespace MailMergeLib.Serialization; /// /// Serializer for objects. /// -internal class IPEndPointSerializer : ICustomSerializer +internal class IPEndPointSerializer : ICustomSerializer { - public void SerializeToAttribute(IPEndPoint objectToSerialize, XAttribute attrToFill, ISerializationContext serializationContext) + public void SerializeToAttribute(IPEndPoint? objectToSerialize, XAttribute attrToFill, ISerializationContext serializationContext) { throw new NotImplementedException(); } - public void SerializeToElement(IPEndPoint objectToSerialize, XElement elemToFill, ISerializationContext serializationContext) + public void SerializeToElement(IPEndPoint? objectToSerialize, XElement elemToFill, ISerializationContext serializationContext) { if (objectToSerialize == null) return; @@ -24,7 +24,7 @@ public void SerializeToElement(IPEndPoint objectToSerialize, XElement elemToFill elemToFill.SetAttributeValue("Port", objectToSerialize.Port.ToString()); } - public string SerializeToValue(IPEndPoint objectToSerialize, ISerializationContext serializationContext) + public string SerializeToValue(IPEndPoint? objectToSerialize, ISerializationContext serializationContext) { throw new NotImplementedException(); } @@ -34,7 +34,7 @@ public IPEndPoint DeserializeFromAttribute(XAttribute attrib, ISerializationCont throw new NotImplementedException(); } - public IPEndPoint DeserializeFromElement(XElement element, ISerializationContext serializationContext) + public IPEndPoint? DeserializeFromElement(XElement element, ISerializationContext serializationContext) { if (!element.HasAttributes) return null; diff --git a/Src/MailMergeLib/Serialization/SerializationFactory.cs b/Src/MailMergeLib/Serialization/SerializationFactory.cs index 524a2af..e259a0e 100644 --- a/Src/MailMergeLib/Serialization/SerializationFactory.cs +++ b/Src/MailMergeLib/Serialization/SerializationFactory.cs @@ -68,13 +68,9 @@ internal static void Serialize(T obj, Stream stream, System.Text.Encoding enc /// internal static void Serialize(T obj, string filename, Encoding encoding) { - using (var fs = new FileStream(filename, FileMode.Create)) - { - using (var sr = new StreamWriter(fs, encoding)) - { - Serialize(obj, sr, false); - } - } + using var fs = new FileStream(filename, FileMode.Create); + using var sr = new StreamWriter(fs, encoding); + Serialize(obj, sr, false); } /// @@ -102,10 +98,10 @@ internal static void Serialize(T obj, TextWriter writer, bool isStream) /// /// /// Returns an instance of T. - internal static T Deserialize(string xml) + internal static T? Deserialize(string xml) { - var serializer = GetStandardSerializer(typeof(T)); - return (T)serializer.Deserialize(xml); + var serializer = GetStandardSerializer(typeof(T?)); + return (T?)serializer.Deserialize(xml); } /// @@ -113,7 +109,7 @@ internal static T Deserialize(string xml) /// /// /// - internal static T Deserialize(Stream stream, Encoding encoding) + internal static T? Deserialize(Stream stream, Encoding encoding) { return Deserialize(new StreamReader(stream, encoding), true); } @@ -123,15 +119,11 @@ internal static T Deserialize(Stream stream, Encoding encoding) /// /// /// - internal static T Deserialize(string filename, System.Text.Encoding encoding) + internal static T? Deserialize(string filename, System.Text.Encoding encoding) { - using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - using (var sr = new StreamReader(fs, encoding)) - { - return Deserialize(sr, false); - } - } + using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + using var sr = new StreamReader(fs, encoding); + return Deserialize(sr, false); } /// @@ -140,12 +132,12 @@ internal static T Deserialize(string filename, System.Text.Encoding encoding) /// /// Returns a T instance. /// If true, the writer will not be closed and disposed, so that the underlying stream can be used on return. - internal static T Deserialize(StreamReader reader, bool isStream) + internal static T? Deserialize(StreamReader reader, bool isStream) { var serializer = GetStandardSerializer(typeof(T)); reader.BaseStream.Position = 0; var str = reader.ReadToEnd(); - var s = (T)serializer.Deserialize(str); + var s = (T?)serializer.Deserialize(str); if (isStream) return s; #if NETFRAMEWORK diff --git a/Src/MailMergeLib/Settings.cs b/Src/MailMergeLib/Settings.cs index 8db03d5..7a8d351 100644 --- a/Src/MailMergeLib/Settings.cs +++ b/Src/MailMergeLib/Settings.cs @@ -67,7 +67,7 @@ public void Serialize(Stream stream, Encoding encoding) /// /// /// - public void Serialize(string filename, Encoding encoding = null) + public void Serialize(string filename, Encoding? encoding = null) { using var fs = new FileStream(filename, FileMode.Create); using var sr = new StreamWriter(fs, encoding ?? Encoding.UTF8); @@ -98,7 +98,7 @@ private void Serialize(TextWriter writer, bool isStream) /// /// /// - public static Settings Deserialize(Stream stream, Encoding encoding) + public static Settings? Deserialize(Stream stream, Encoding encoding) { using var sr = new StreamReader(stream, encoding); return SerializationFactory.Deserialize(sr, true); @@ -109,7 +109,7 @@ public static Settings Deserialize(Stream stream, Encoding encoding) /// /// /// - public static Settings Deserialize(string filename, Encoding encoding) + public static Settings? Deserialize(string filename, Encoding encoding) { return SerializationFactory.Deserialize(filename, encoding ?? Encoding.UTF8); } @@ -118,7 +118,7 @@ public static Settings Deserialize(string filename, Encoding encoding) /// Read the MailMergeLib settings from an xml string. /// /// Returns the MailMergeLib settings as an xml string. - public static Settings Deserialize(string xml) + public static Settings? Deserialize(string xml) { return SerializationFactory.Deserialize(xml); } diff --git a/Src/MailMergeLib/SmartFormatterConfig.cs b/Src/MailMergeLib/SmartFormatterConfig.cs index abca909..4bce48e 100644 --- a/Src/MailMergeLib/SmartFormatterConfig.cs +++ b/Src/MailMergeLib/SmartFormatterConfig.cs @@ -1,6 +1,5 @@ using System; using SmartFormat.Core.Parsing; -using SmartFormat.Core.Settings; namespace MailMergeLib; @@ -23,7 +22,7 @@ public ErrorAction ParseErrorAction set { _parseErrorAction = value; - OnConfigChanged?.Invoke(this); + OnConfigChanged?.Invoke(); } } @@ -36,7 +35,7 @@ public ErrorAction FormatErrorAction set { _formatErrorAction = value; - OnConfigChanged?.Invoke(this); + OnConfigChanged?.Invoke(); } } @@ -50,7 +49,7 @@ public CaseSensitivityType CaseSensitivity set { _caseSensitivity = value; - OnConfigChanged?.Invoke(this); + OnConfigChanged?.Invoke(); } } @@ -67,14 +66,14 @@ public bool ConvertCharacterStringLiterals set { _convertCharacterStringLiterals = value; - OnConfigChanged?.Invoke(this); + OnConfigChanged?.Invoke(); } } /// /// Event raising when the configuration has changed. /// - public event Action OnConfigChanged; + public event Action? OnConfigChanged; #region *** Equality *** @@ -89,11 +88,11 @@ private bool Equals(SmartFormatterConfig other) /// /// /// Returns true, if both SmartFormatterConfigs are equal, else false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((SmartFormatterConfig) obj); } @@ -113,4 +112,4 @@ public override int GetHashCode() } #endregion -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/SmtpClientConfig.cs b/Src/MailMergeLib/SmtpClientConfig.cs index 310d195..2b67ff6 100644 --- a/Src/MailMergeLib/SmtpClientConfig.cs +++ b/Src/MailMergeLib/SmtpClientConfig.cs @@ -13,25 +13,25 @@ using System.Net.Configuration; #endif -namespace MailMergeLib +namespace MailMergeLib; + +/// +/// Class which is used by MailMergeSender in order to build preconfigured SmtpClients. +/// +[YAXSerializableType(FieldsToSerialize = YAXSerializationFields.AttributedFieldsOnly)] +public class SmtpClientConfig { + private int _maxFailures = 2; + private int _retryDelayTime; + private string? _mailOutputDirectory; + /// - /// Class which is used by MailMergeSender in order to build preconfigured SmtpClients. + /// Creates a new instance of the configuration which is used by MailMergeSender in order to build a preconfigured SmtpClient. /// - [YAXSerializableType(FieldsToSerialize = YAXSerializationFields.AttributedFieldsOnly)] - public class SmtpClientConfig + public SmtpClientConfig() { - private int _maxFailures = 2; - private int _retryDelayTime; - private string _mailOutputDirectory = null; - - /// - /// Creates a new instance of the configuration which is used by MailMergeSender in order to build a preconfigured SmtpClient. - /// - public SmtpClientConfig() - { - MailOutputDirectory = System.IO.Path.GetTempPath(); - } + MailOutputDirectory = System.IO.Path.GetTempPath(); + } #if NETFRAMEWORK /// @@ -52,7 +52,7 @@ public void ReadSmtpConfigurationFromWebConfig() /// public void ReadSmtpConfigurationFromConfigFile() { - SmtpSection smtpSection; + SmtpSection? smtpSection; try { // runs in a standalone app @@ -78,7 +78,7 @@ public void ReadSmtpConfigurationFromConfigFile() break; case System.Net.Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory: MessageOutput = MessageOutput.Directory; - _mailOutputDirectory = smtpSection.SpecifiedPickupDirectory?.PickupDirectoryLocation; + _mailOutputDirectory = smtpSection.SpecifiedPickupDirectory.PickupDirectoryLocation; break; } @@ -93,174 +93,176 @@ public void ReadSmtpConfigurationFromConfigFile() ClientDomain = smtpSection.Network.ClientDomain; } #endif - /// - /// Get or sets the name of configuration. - /// It's recommended to choose different names for each configuration. - /// - [YAXSerializableField] - [YAXAttributeForClass] - public string Name { get; set; } + /// + /// Get or sets the name of configuration. + /// It's recommended to choose different names for each configuration. + /// + [YAXSerializableField] + [YAXAttributeForClass] + public string? Name { get; set; } - /// - /// Gets or sets the name or IP address of the SMTP host to be used for sending mails. - /// - /// Used during SmtpClient connect. - [YAXSerializableField] - public string SmtpHost { get; set; } = "localhost"; + /// + /// Gets or sets the name or IP address of the SMTP host to be used for sending mails. + /// + /// Used during SmtpClient connect. + [YAXSerializableField] + public string SmtpHost { get; set; } = "localhost"; - /// - /// Gets or set the port of the SMTP host to be used for sending mails. - /// - /// Used during SmtpClient connect. - [YAXSerializableField] - public int SmtpPort { get; set; } = 25; + /// + /// Gets or set the port of the SMTP host to be used for sending mails. + /// + /// Used during SmtpClient connect. + [YAXSerializableField] + public int SmtpPort { get; set; } = 25; - /// - /// Gets or sets the name of the local machine sent to the SMTP server with the hello command - /// of an SMTP transaction. Defaults to the windows machine name. - /// - [YAXSerializableField] - public string ClientDomain { get; set; } + /// + /// Gets or sets the name of the local machine sent to the SMTP server with the hello command + /// of an SMTP transaction. Defaults to the windows machine name. + /// + [YAXSerializableField] + public string? ClientDomain { get; set; } - /// - /// Gets or sets the local IP end point or null to use the default end point. - /// - [YAXSerializableField] - [YAXCustomSerializer(typeof(IPEndPointSerializer))] - public IPEndPoint LocalEndPoint { get; set; } + /// + /// Gets or sets the local IP end point or null to use the default end point. + /// + [YAXSerializableField] + [YAXCustomSerializer(typeof(IPEndPointSerializer))] + public IPEndPoint? LocalEndPoint { get; set; } - /// - /// Gets the collection of certificates the will use. - /// - /// - /// Add a certificate using a PFX file (i.e. a PKCS#12 archive bag): - /// ClientCertificates.Add(new X509Certificate2("path_to_cert_file.pfx", "optional password")); - /// Add a certificate using a certificate file in PEM format: - /// ClientCertificates.Add(X509Certificate.CreateFromCertFile("path_to_cert_file.pem")); - /// - [YAXDontSerialize] - public X509CertificateCollection ClientCertificates { get; } = new X509CertificateCollection(); + /// + /// Gets the collection of certificates the will use. + /// + /// + /// Add a certificate using a PFX file (i.e. a PKCS#12 archive bag): + /// ClientCertificates.Add(new X509Certificate2("path_to_cert_file.pfx", "optional password")); + /// Add a certificate using a certificate file in PEM format: + /// ClientCertificates.Add(X509Certificate.CreateFromCertFile("path_to_cert_file.pem")); + /// + [YAXDontSerialize] + public X509CertificateCollection ClientCertificates { get; } = new X509CertificateCollection(); - /// - /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. - /// - [YAXDontSerialize] - public System.Net.Security.RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; } + /// + /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. + /// + [YAXDontSerialize] + public System.Net.Security.RemoteCertificateValidationCallback? ServerCertificateValidationCallback { get; set; } - /// - /// Set authentication details for logging into an SMTP server. - /// Set NetworkCredential to null if no authentication is required. - /// - /// Used during SmtpClient connect. - /// Will be serialized with attribute yaxlib:realtype="MailMergeLib.Credential" if assigned to an instance of MailMergeLib.Credential. - [YAXSerializableField] - public ICredentials NetworkCredential { get; set; } + /// + /// Set authentication details for logging into an SMTP server. + /// Set NetworkCredential to null if no authentication is required. + /// + /// Used during SmtpClient connect. + /// Will be serialized with attribute yaxlib:realtype="MailMergeLib.Credential" if assigned to an instance of MailMergeLib.Credential. + [YAXSerializableField] + public ICredentials? NetworkCredential { get; set; } - /// - /// Gets or sets the name of the output directory of sent mail messages - /// (only used if messages are not sent to SMTP server) - /// - [YAXSerializableField] - public string MailOutputDirectory + /// + /// Gets or sets the name of the output directory of sent mail messages + /// (only used if messages are not sent to SMTP server) + /// + [YAXSerializableField] + public string? MailOutputDirectory + { + get { - get + return MessageOutput switch { - return MessageOutput switch - { - MessageOutput.None => null, - MessageOutput.SmtpServer => null, - MessageOutput.Directory => (_mailOutputDirectory ?? System.IO.Path.GetTempPath()), + MessageOutput.None => null, + MessageOutput.SmtpServer => null, + MessageOutput.Directory => (_mailOutputDirectory ?? System.IO.Path.GetTempPath()), #if NETFRAMEWORK MessageOutput.PickupDirectoryFromIis => GetPickDirectoryFromIis(), #endif - _ => throw new ArgumentOutOfRangeException() - }; - } - set => _mailOutputDirectory = value; + _ => throw new ArgumentOutOfRangeException() + }; } + set => _mailOutputDirectory = value; + } - /// - /// Gets or sets the location where to send mail messages. - /// - [YAXSerializableField] - public MessageOutput MessageOutput { get; set; } = MessageOutput.None; + /// + /// Gets or sets the location where to send mail messages. + /// + [YAXSerializableField] + public MessageOutput MessageOutput { get; set; } = MessageOutput.None; - /// - /// Gets or sets the SSL/TLS protocols the is allowed to use. - /// - [YAXSerializableField] - public SslProtocols SslProtocols { get; set; } = SslProtocols.None; + /// + /// Gets or sets the SSL/TLS protocols the is allowed to use. + /// + [YAXSerializableField] + public SslProtocols SslProtocols { get; set; } = SslProtocols.None; - /// - /// Gets or sets the SecureSocketOptions the will use (e.g. SSL or STARTLS - /// In case a secure socket is needed, setting options to SecureSocketOptions.Auto is recommended. - /// - /// Used during connect. - [YAXSerializableField] - public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.None; + /// + /// Gets or sets the SecureSocketOptions the will use (e.g. SSL or STARTLS + /// In case a secure socket is needed, setting options to SecureSocketOptions.Auto is recommended. + /// + /// Used during connect. + [YAXSerializableField] + public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.None; - /// - /// Gets or sets the timeout for sending a message, after which a time-out exception will raise. - /// Timeout value in milliseconds. The default value is 100,000 (100 seconds). - /// - [YAXSerializableField] - public int Timeout { get; set; } = 100000; + /// + /// Gets or sets the timeout for sending a message, after which a time-out exception will raise. + /// Timeout value in milliseconds. The default value is 100,000 (100 seconds). + /// + [YAXSerializableField] + public int Timeout { get; set; } = 100000; - /// - /// The delegate for an that will use to log the dialogue with the SMTP server. - /// This logger is dedicated to debugging, not for production use. - /// - /// - /// Have in mind that MailMergeLib may use several concurrently. - /// The delegate is called when creating a new instance of an is created. - /// - /// - /// ProtocolLoggerDelegate = () => new ProtocolLogger(System.IO.Path.Combine("targetDirectory", "Smtp-" + System.IO.Path.GetRandomFileName() + ".log")); - /// - [YAXDontSerialize] - public Func ProtocolLoggerDelegate; + /// + /// The delegate for an that will use to log the dialogue with the SMTP server. + /// This logger is dedicated to debugging, not for production use. + /// + /// + /// Have in mind that MailMergeLib may use several concurrently. + /// The delegate is called when creating a new instance of an is created. + /// + /// + /// ProtocolLoggerDelegate = () => new ProtocolLogger(System.IO.Path.Combine("targetDirectory", "Smtp-" + System.IO.Path.GetRandomFileName() + ".log")); + /// + [YAXDontSerialize] + public Func? ProtocolLoggerDelegate; - /// - /// Gets or sets the delay time in milliseconds (0-10000) between the messages. - /// In case more than one SmtpClient will be used concurrently, the delay will be used per thread. - /// Mainly used for debug purposes. - /// - [YAXSerializableField] - public int DelayBetweenMessages { get; set; } + /// + /// Gets or sets the delay time in milliseconds (0-10000) between the messages. + /// In case more than one SmtpClient will be used concurrently, the delay will be used per thread. + /// Mainly used for debug purposes. + /// + [YAXSerializableField] + public int DelayBetweenMessages { get; set; } - /// - /// Gets or sets the number of failures (1-10) for which a retry to send will be performed. - /// - [YAXSerializableField] - public int MaxFailures - { - get { return _maxFailures; } - set { _maxFailures = (value >= 1 && value <= 10) ? value : 1; } - } + /// + /// Gets or sets the number of failures (1-10) for which a retry to send will be performed. + /// + [YAXSerializableField] + public int MaxFailures + { + get { return _maxFailures; } + set { _maxFailures = (value >= 1 && value <= 10) ? value : 1; } + } - /// - /// Gets or sets the delay time in milliseconds (0-10000) to elaps between retries to send the message. - /// - [YAXSerializableField] - public int RetryDelayTime - { - get { return _retryDelayTime; } - set { _retryDelayTime = (value >= 0 && value <= 10000) ? value : 0; } - } + /// + /// Gets or sets the delay time in milliseconds (0-10000) to elaps between retries to send the message. + /// + [YAXSerializableField] + public int RetryDelayTime + { + get { return _retryDelayTime; } + set { _retryDelayTime = (value >= 0 && value <= 10000) ? value : 0; } + } #if NETFRAMEWORK - private static string GetPickDirectoryFromIis() + private static string? GetPickDirectoryFromIis() { try { - var internalType = typeof(System.Net.Mail.SmtpClient).Assembly.GetType("System.Net.Mail.IisPickupDirectory"); + var internalType = + typeof(System.Net.Mail.SmtpClient).Assembly.GetType("System.Net.Mail.IisPickupDirectory"); if (internalType == null) { throw new NotImplementedException("Assembly does not contain type System.Net.Mail.IisPickupDirectory"); } var smtpClient = new System.Net.Mail.SmtpClient(); - var methodInfo = internalType.GetMethod("GetPickupDirectory", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + var methodInfo = + internalType.GetMethod("GetPickupDirectory", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (methodInfo == null) { throw new NotImplementedException("System.Net.Mail.IisPickupDirectory does not contain a method GetPickupDirectory()"); @@ -277,75 +279,75 @@ private static string GetPickDirectoryFromIis() } #endif - #region *** Equality *** + #region *** Equality *** - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// - /// - /// Excluding those properties which are not serialized: - /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate - /// - public override bool Equals(object other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - if (other.GetType() != this.GetType()) return false; - return Equals((SmtpClientConfig)other); - } + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// + /// + /// Excluding those properties which are not serialized: + /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate + /// + public override bool Equals(object? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + if (other.GetType() != GetType()) return false; + return Equals((SmtpClientConfig)other); + } - /// - /// Determines whether the specified object is equal to the current object. - /// - /// - /// - /// - /// Excluding those properties which are not serialized: - /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate - /// - protected bool Equals(SmtpClientConfig other) - { - return MaxFailures == other.MaxFailures && RetryDelayTime == other.RetryDelayTime && - string.Equals(MailOutputDirectory, other.MailOutputDirectory) && - string.Equals(Name, other.Name) && - string.Equals(SmtpHost, other.SmtpHost) && SmtpPort == other.SmtpPort && - string.Equals(ClientDomain, other.ClientDomain) && Equals(LocalEndPoint, other.LocalEndPoint) && - MessageOutput == other.MessageOutput && - SslProtocols == other.SslProtocols && SecureSocketOptions == other.SecureSocketOptions && - Timeout == other.Timeout && DelayBetweenMessages == other.DelayBetweenMessages; - } + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// + /// + /// Excluding those properties which are not serialized: + /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate + /// + protected bool Equals(SmtpClientConfig other) + { + return MaxFailures == other.MaxFailures && RetryDelayTime == other.RetryDelayTime && + string.Equals(MailOutputDirectory, other.MailOutputDirectory) && + string.Equals(Name, other.Name) && + string.Equals(SmtpHost, other.SmtpHost) && SmtpPort == other.SmtpPort && + string.Equals(ClientDomain, other.ClientDomain) && Equals(LocalEndPoint, other.LocalEndPoint) && + MessageOutput == other.MessageOutput && + SslProtocols == other.SslProtocols && SecureSocketOptions == other.SecureSocketOptions && + Timeout == other.Timeout && DelayBetweenMessages == other.DelayBetweenMessages; + } - /// - /// Returns the hashcode for this object. - /// - /// - /// - /// Excluding those properties which are not serialized: - /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate - /// - public override int GetHashCode() + /// + /// Returns the hashcode for this object. + /// + /// + /// + /// Excluding those properties which are not serialized: + /// ClientCertificates, ServerCertificateValidationCallback, NetworkCredential, ProtocolLoggerDelegate + /// + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = MaxFailures; - hashCode = (hashCode * 397) ^ RetryDelayTime; - hashCode = (hashCode * 397) ^ (MailOutputDirectory != null ? MailOutputDirectory.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (SmtpHost != null ? SmtpHost.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ SmtpPort; - hashCode = (hashCode * 397) ^ (ClientDomain != null ? ClientDomain.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (LocalEndPoint != null ? LocalEndPoint.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (int) MessageOutput; - hashCode = (hashCode * 397) ^ (int) SslProtocols; - hashCode = (hashCode * 397) ^ (int) SecureSocketOptions; - hashCode = (hashCode * 397) ^ Timeout; - hashCode = (hashCode * 397) ^ DelayBetweenMessages; - return hashCode; - } + var hashCode = MaxFailures; + hashCode = (hashCode * 397) ^ RetryDelayTime; + hashCode = (hashCode * 397) ^ (MailOutputDirectory != null ? MailOutputDirectory.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (SmtpHost != null ? SmtpHost.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ SmtpPort; + hashCode = (hashCode * 397) ^ (ClientDomain != null ? ClientDomain.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (LocalEndPoint != null ? LocalEndPoint.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)MessageOutput; + hashCode = (hashCode * 397) ^ (int)SslProtocols; + hashCode = (hashCode * 397) ^ (int)SecureSocketOptions; + hashCode = (hashCode * 397) ^ Timeout; + hashCode = (hashCode * 397) ^ DelayBetweenMessages; + return hashCode; } - - #endregion } + + #endregion } + diff --git a/Src/MailMergeLib/StringAttachment.cs b/Src/MailMergeLib/StringAttachment.cs index 165525c..26175f0 100644 --- a/Src/MailMergeLib/StringAttachment.cs +++ b/Src/MailMergeLib/StringAttachment.cs @@ -9,7 +9,10 @@ public class StringAttachment /// Creates a new string attachment for a /// public StringAttachment() - {} + { + DisplayName = Content = string.Empty; + MimeType = MimeKit.MimeTypes.GetMimeType( "application/octet-stream"); + } /// /// Creates a new string attachment for a @@ -59,11 +62,11 @@ public StringAttachment(string content, string displayName) /// E.g. necessary for HashSet<FileAttachment>. /// /// Returns true, if both FileAttachments are equal, else false. - public override bool Equals(object sa) + public override bool Equals(object? sa) { if (sa is null) return false; if (ReferenceEquals(this, sa)) return true; - if (sa.GetType() != this.GetType()) return false; + if (sa.GetType() != GetType()) return false; return Equals((StringAttachment) sa); } @@ -89,4 +92,4 @@ public override int GetHashCode() } } -#endregion \ No newline at end of file +#endregion diff --git a/Src/MailMergeLib/Templates/Part.cs b/Src/MailMergeLib/Templates/Part.cs index 817b434..ccd985b 100644 --- a/Src/MailMergeLib/Templates/Part.cs +++ b/Src/MailMergeLib/Templates/Part.cs @@ -12,7 +12,7 @@ namespace MailMergeLib.Templates; [YAXCustomSerializer(typeof(PartSerializer))] public class Part { - private string _value; + private string _value = string.Empty; /// /// Initialize an instance of a part. @@ -30,13 +30,13 @@ public Part(PartType type, string key, string value) : this() { Type = type; Key = key; - _value = value ?? string.Empty; + _value = value; } /// /// Gets the key of a part. /// - public string Key { get; private set; } + public string Key { get; private set; } = string.Empty; /// /// Gets the of a part. @@ -51,7 +51,7 @@ public Part(PartType type, string key, string value) : this() public string Value { get => _value; - private set => _value = value ?? string.Empty; + private set => _value = value; } /// @@ -59,7 +59,7 @@ public string Value /// /// /// Returns true, if both instances are equal, else false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; diff --git a/Src/MailMergeLib/Templates/Parts.cs b/Src/MailMergeLib/Templates/Parts.cs index 7f29f79..c96b958 100644 --- a/Src/MailMergeLib/Templates/Parts.cs +++ b/Src/MailMergeLib/Templates/Parts.cs @@ -57,11 +57,11 @@ private bool Equals(Parts other) /// /// /// Returns true, if both instances are equal, else false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((Parts) obj); } @@ -76,4 +76,4 @@ public override int GetHashCode() return this.Aggregate(0, (current, item) => (current * 397) ^ (item != null ? item.GetHashCode() : 0)); } } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/Templates/Template.cs b/Src/MailMergeLib/Templates/Template.cs index bbe07bb..6716bd2 100644 --- a/Src/MailMergeLib/Templates/Template.cs +++ b/Src/MailMergeLib/Templates/Template.cs @@ -12,9 +12,9 @@ namespace MailMergeLib.Templates; [YAXSerializeAs("Template")] public class Template { - private string _key; - private string _defaultKey; - private Parts _text = new Parts(); + private string? _key; + private string? _defaultKey; + private Parts _text = new(); /// /// Creates an instance of a class. @@ -30,14 +30,14 @@ public Template() /// The name of the template. /// The of the template. /// The default key for the template, may be omitted. - public Template(string name, Parts parts, string defaultKey = null) : this() + public Template(string name, Parts parts, string? defaultKey = null) : this() { Name = name; Text.AddRange(parts); DefaultKey = defaultKey; } - private void TextOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + private void TextOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) { // call the setter to verify the current value can still be set after changing the collection DefaultKey = DefaultKey; @@ -49,7 +49,7 @@ private void TextOnCollectionChanged(object sender, NotifyCollectionChangedEvent [YAXSerializableField] [YAXAttributeForClass] [YAXErrorIfMissed(YAXExceptionTypes.Error)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// The property is a list of type . @@ -74,9 +74,9 @@ internal set /// /// The key to get the parts for. If null or omitted, the property of the will be used. /// If the key parameter is found, it returns an array of for the key parameter, else from the default key. - public Part[] GetParts(string key = null) + public Part[] GetParts(string? key = null) { - if (key == null) key = Key; + key ??= Key; // Gracious detection: // If Text only has entries with 1 key and nothing else is selected, return the parts for the only key @@ -91,7 +91,7 @@ public Part[] GetParts(string key = null) return this[DefaultKey]; } - return this[key]; + return this[key!]; } /// @@ -99,7 +99,7 @@ public Part[] GetParts(string key = null) /// /// [YAXDontSerialize] - public string Key + public string? Key { get => _key; set @@ -119,7 +119,7 @@ public string Key [YAXAttributeFor("Text")] [YAXErrorIfMissed(YAXExceptionTypes.Ignore)] [YAXDontSerializeIfNull] - public string DefaultKey + public string? DefaultKey { get => _defaultKey; set @@ -149,11 +149,11 @@ public Part[] this[string key] /// /// /// Returns true, if both instances are equal, else false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj.GetType() != GetType()) return false; return Equals((Template)obj); } @@ -182,4 +182,4 @@ public override int GetHashCode() return hashCode; } } -} \ No newline at end of file +} diff --git a/Src/MailMergeLib/Templates/TemplateException.cs b/Src/MailMergeLib/Templates/TemplateException.cs index 552fb25..0493b1e 100644 --- a/Src/MailMergeLib/Templates/TemplateException.cs +++ b/Src/MailMergeLib/Templates/TemplateException.cs @@ -15,18 +15,18 @@ public class TemplateException : Exception /// /// /// - public TemplateException(string message, Part part, Parts parts, Template template, Templates templates) : base (message) + public TemplateException(string message, Part? part, Parts? parts, Template? template, Templates? templates) : base (message) { Part = part; - Parts = parts; + Parts = parts ?? new (); Template = template; - Templates = templates; + Templates = templates ?? new(); } /// /// Gets the causing the exception, if not null. /// - public Part Part { get; private set; } + public Part? Part { get; private set; } /// /// Gets the causing the exception, if not null. @@ -36,7 +36,7 @@ public TemplateException(string message, Part part, Parts parts, Template templa /// /// Gets the causing the exception, if not null. /// - public Template Template { get; private set; } + public Template? Template { get; private set; } /// /// Gets the causing the exception, if not null. diff --git a/Src/MailMergeLib/Templates/Templates.cs b/Src/MailMergeLib/Templates/Templates.cs index 8df2fa8..faa5e40 100644 --- a/Src/MailMergeLib/Templates/Templates.cs +++ b/Src/MailMergeLib/Templates/Templates.cs @@ -56,14 +56,11 @@ public class Templates : List