├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── StatusGeneric.sln ├── StatusGeneric ├── ErrorGeneric.cs ├── IStatusGeneric.Generic.cs ├── IStatusGeneric.cs ├── IStatusGenericHandler.cs ├── StatusGeneric.csproj ├── StatusGenericHandler.Generic.cs └── StatusGenericHandler.cs └── Test ├── ExampleUsages.cs ├── Test.csproj ├── TestExampleUsages.cs └── TestStatusGeneric.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jon P Smith - Selective Analytics Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GenericServices.StatusGeneric 2 | 3 | This [NuGet library](https://www.nuget.org/packages/GenericServices.StatusGeneric/) provides a way to return the status of a method/class that you run. It contains two main things 4 | 5 | 1. A IReadOnlyList of `Errors`, which may be empty. If the list is empty, then the `IsValid` property of the status will be true. 6 | 2. A `Message` which can be set by you (default value is "Success"), but if it has any errors the `Message` returns "Failed with nn errors". 7 | 3. The `IStatusGeneric` version will return a `Result` which you can set, but returns `default(T)` if there are errors. 8 | 9 | There are various methods to add errors to the `Errors` list, and a way to combine statuses which I show next. 10 | 11 | *NOTE: There is a companion library called [EfCore.GenericServices.AspNetCore](https://github.com/JonPSmith/EfCore.GenericServices.AspNetCore) which can convert a GenericService into Model errors for ASP.NET Core MVC controllers or Razor pages, AND ASP.NET Core Web API - well worth looking at.* 12 | 13 | 14 | ## Simple usage examples 15 | 16 | 1. Returns just a status telling you if the method was successful. You can also set the `Message` in the status - default is "Success". 17 | 18 | ```c# 19 | public IStatusGeneric NonStatusGenericString(string s) 20 | { 21 | var status = new StatusGenericHandler(); 22 | 23 | //add error and return immediately 24 | if (s == null) 25 | //You can return just an error message, but adding the property name 26 | //will improve the error feedback in ASP.NET Core etc. 27 | return status.AddError("input must not be null", nameof(s)); 28 | 29 | status.Message = "All went well"; 30 | 31 | //If no errors were added then its returns a IsValid status with the message 32 | //If there are errors then the Message says something like "Failed with 1 error" 33 | //and the HasErrors will be true, IsValid will be false 34 | return status; 35 | } 36 | 37 | ``` 38 | 39 | 2. Returns a status with a value 40 | 41 | ```c# 42 | public IStatusGeneric StatusGenericNumReturnString(int i) 43 | { 44 | var status = new StatusGenericHandler(); 45 | 46 | //add error and return immediately 47 | if (i <= 0) 48 | return status.AddError("input must be positive", nameof(i)); 49 | 50 | //This sets the Result property in the generic status 51 | status.SetResult(i.ToString()); 52 | 53 | //If there are errors then the Result is set to the default value for generic type 54 | return status; 55 | } 56 | ``` 57 | 58 | ## Combining multiple checks 59 | 60 | This approach makes sure all the errors are returned, which is best for the user 61 | 62 | ```c# 63 | public IStatusGeneric CheckPassword(string password) 64 | { 65 | var status = new StatusGenericHandler(); 66 | 67 | //series of tests and then return all the errors together 68 | //Good because the user gets all the errors at once 69 | if (password.Length < 10) 70 | status.AddError("A password must be 10 or more in length", 71 | nameof(password)); 72 | if (!password.Any(char.IsUpper)) 73 | status.AddError("A password must contain an upper case character", 74 | nameof(password)); 75 | if (!password.Any(char.IsLower)) 76 | status.AddError("A password must contain a lower case character", 77 | nameof(password)); 78 | if (!password.Any(char.IsDigit)) 79 | status.AddError("A password must contain an number", 80 | nameof(password)); 81 | 82 | return status; 83 | } 84 | ``` 85 | 86 | ## Combining errors from multiple statuses 87 | 88 | Sometimes the testing for errors is best coded by called to other methods that return the IStatusGeneric interface. The code below shows a fictitious example of logging in with tests on the email, password and the actual login part. 89 | 90 | ```c# 91 | public IStatusGeneric Login 92 | (string email, string password) 93 | { 94 | var status = new StatusGenericHandler(); 95 | 96 | status.CombineStatuses( 97 | CheckValidEmail(email)); 98 | 99 | if (status.HasErrors) 100 | return status; 101 | 102 | if (status.CombineStatuses( 103 | CheckPassword(password)).HasErrors) 104 | return status; 105 | 106 | var loginStatus = LoginUser(email, password); 107 | status.CombineStatuses(loginStatus); 108 | 109 | status.SetResult(loginStatus.Result); 110 | 111 | return status; 112 | } 113 | ``` 114 | 115 | 116 | ## When unit testing... 117 | 118 | If you are testing a service its always good to check that the service returns the status you expect. If you expect it to pass then you can use this following xUnit fluent test which will 119 | 1. Test that it was a successful result 120 | 2. Show you the errors in the unit test window 121 | 122 | ```c# 123 | status.IsValid.ShouldBeTrue(status.GetAllErrors()); 124 | ``` -------------------------------------------------------------------------------- /StatusGeneric.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusGeneric", "StatusGeneric\StatusGeneric.csproj", "{CAF5E644-C1F6-4275-AFB7-F741E5A2FE5A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FAF1C016-1CE4-4392-9730-00E6204B6C60}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C9A7F661-C2B6-4F4C-A320-EF33B5F83D2B}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {CAF5E644-C1F6-4275-AFB7-F741E5A2FE5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {CAF5E644-C1F6-4275-AFB7-F741E5A2FE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {CAF5E644-C1F6-4275-AFB7-F741E5A2FE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {CAF5E644-C1F6-4275-AFB7-F741E5A2FE5A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {FAF1C016-1CE4-4392-9730-00E6204B6C60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {FAF1C016-1CE4-4392-9730-00E6204B6C60}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {FAF1C016-1CE4-4392-9730-00E6204B6C60}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {FAF1C016-1CE4-4392-9730-00E6204B6C60}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {CE8535A3-E367-4C1D-BE3E-1541AB71126D} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /StatusGeneric/ErrorGeneric.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.Text; 8 | 9 | namespace StatusGeneric 10 | { 11 | /// 12 | /// This holds an error registered in the Errors collection 13 | /// 14 | public struct ErrorGeneric 15 | { 16 | /// 17 | /// If there are multiple headers this separator is placed between them, e.g. Update>Author 18 | /// 19 | public const string HeaderSeparator = ">"; 20 | 21 | /// 22 | /// This ctor will create an ErrorGeneric 23 | /// 24 | /// 25 | /// 26 | public ErrorGeneric(string header, ValidationResult error) : this() 27 | { 28 | Header = header ?? throw new ArgumentNullException(nameof(header)); 29 | ErrorResult = error ?? throw new ArgumentNullException(nameof(error)); 30 | } 31 | 32 | internal ErrorGeneric(string prefix, ErrorGeneric existingError) 33 | { 34 | Header = string.IsNullOrEmpty(prefix) 35 | ? existingError.Header 36 | : string.IsNullOrEmpty(existingError.Header) 37 | ? prefix 38 | : prefix + HeaderSeparator + existingError.Header; 39 | ErrorResult = existingError.ErrorResult; 40 | DebugData = existingError.DebugData; 41 | } 42 | 43 | /// 44 | /// A Header. Can be null 45 | /// 46 | public string Header { get; private set; } 47 | 48 | /// 49 | /// This is the error provided 50 | /// 51 | public ValidationResult ErrorResult { get; private set; } 52 | 53 | /// 54 | /// This can be used to contain extra data to help the developer debug the error 55 | /// For instance, the content of an exception. 56 | /// 57 | public string DebugData { get; private set; } 58 | 59 | /// 60 | /// This copies the exception Message, StackTrace and any entries in the Data dictionary into the DebugData string 61 | /// 62 | /// 63 | internal void CopyExceptionToDebugData(Exception ex) 64 | { 65 | var sb = new StringBuilder(); 66 | sb.AppendLine(ex.Message); 67 | sb.Append("StackTrace:"); 68 | sb.AppendLine(ex.StackTrace); 69 | foreach (DictionaryEntry entry in ex.Data) 70 | { 71 | sb.AppendLine($"Data: {entry.Key}\t{entry.Value}"); 72 | } 73 | 74 | DebugData = sb.ToString(); 75 | } 76 | 77 | /// 78 | /// A human-readable error display 79 | /// 80 | /// 81 | public override string ToString() 82 | { 83 | var start = string.IsNullOrEmpty(Header) ? "" : Header + ": "; 84 | return start + ErrorResult.ToString(); 85 | } 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /StatusGeneric/IStatusGeneric.Generic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace StatusGeneric 5 | { 6 | /// 7 | /// This is a version of that contains a result. 8 | /// Useful if you want to return something with the status 9 | /// 10 | /// 11 | public interface IStatusGeneric : IStatusGeneric 12 | { 13 | /// 14 | /// This contains the return result, or if there are errors it will return default(T) 15 | /// 16 | T Result { get; } 17 | } 18 | } -------------------------------------------------------------------------------- /StatusGeneric/IStatusGeneric.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace StatusGeneric 7 | { 8 | /// 9 | /// This is the interface for creating and returning 10 | /// 11 | public interface IStatusGeneric 12 | { 13 | /// 14 | /// This holds the list of errors. If the collection is empty, then there were no errors 15 | /// 16 | IReadOnlyList Errors { get; } 17 | 18 | /// 19 | /// This is true if there are no errors registered 20 | /// 21 | bool IsValid { get; } 22 | 23 | /// 24 | /// This is true if any errors have been added 25 | /// 26 | bool HasErrors { get; } 27 | 28 | /// 29 | /// On success this returns any message set by GenericServices, or any method that returns a status 30 | /// If there are errors it contains the message "Failed with NN errors" 31 | /// 32 | string Message { get; set; } 33 | 34 | /// 35 | /// This allows statuses to be combined 36 | /// 37 | /// 38 | IStatusGeneric CombineStatuses(IStatusGeneric status); 39 | 40 | /// 41 | /// This is a simple method to output all the errors as a single string - null if no errors 42 | /// Useful for feeding back all the errors in a single exception (also nice in unit testing) 43 | /// 44 | /// if null then each errors is separated by Environment.NewLine, otherwise uses the separator you provide 45 | /// a single string with all errors separated by the 'separator' string 46 | string GetAllErrors(string separator = null); 47 | } 48 | } -------------------------------------------------------------------------------- /StatusGeneric/IStatusGenericHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License file in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel.DataAnnotations; 7 | 8 | namespace StatusGeneric 9 | { 10 | /// 11 | /// This is the interface for the full StatusGenericHandler 12 | /// 13 | public interface IStatusGenericHandler : IStatusGeneric 14 | { 15 | /// 16 | /// This adds one error to the Errors collection 17 | /// NOTE: This is virtual so that the StatusGenericHandler.Generic can override it. That allows both to return a IStatusGeneric result 18 | /// 19 | /// The text of the error message 20 | /// optional. A list of property names that this error applies to 21 | IStatusGeneric AddError(string errorMessage, params string[] propertyNames); 22 | 23 | /// 24 | /// This adds one error to the Errors collection and saves the exception's data to the DebugData property 25 | /// 26 | /// The exception that you want to turn into a IStatusGeneric error. 27 | /// The user-friendly text for the error message 28 | /// optional. A list of property names that this error applies to 29 | IStatusGeneric AddError(Exception ex, string errorMessage, params string[] propertyNames); 30 | 31 | /// 32 | /// This adds one ValidationResult to the Errors collection 33 | /// 34 | /// 35 | void AddValidationResult(ValidationResult validationResult); 36 | 37 | /// 38 | /// This appends a collection of ValidationResults to the Errors collection 39 | /// 40 | /// 41 | void AddValidationResults(IEnumerable validationResults); 42 | } 43 | } -------------------------------------------------------------------------------- /StatusGeneric/StatusGeneric.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | netstandard2.0 14 | true 15 | 1.2.0 16 | 1.1.0 17 | 1.1.0.0 18 | 1.1.0.0 19 | GenericServices.StatusGeneric 20 | Jon P Smith 21 | Selective Analytics 22 | GenericServices.StatusGeneric 23 | A support library to provide a common status handling for my projects. 24 | Copyright (c) 2019 Jon P Smith 25 | MIT 26 | https://github.com/JonPSmith/GenericServices.StatusGeneric 27 | https://github.com/JonPSmith/GenericServices.StatusGeneric 28 | GitHub 29 | EfCore.GenericServices, EfCore.GenericEventRunner 30 | 31 | GetAllErrors returns "No Errors" if no errors (previous version returned null) 32 | 33 | true 34 | true 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /StatusGeneric/StatusGenericHandler.Generic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace StatusGeneric 8 | { 9 | /// 10 | /// This contains the error handling part of the GenericBizRunner 11 | /// 12 | public class StatusGenericHandler : StatusGenericHandler, IStatusGeneric 13 | { 14 | private T _result; 15 | 16 | /// 17 | /// This is the returned result 18 | /// 19 | public T Result => IsValid ? _result : default(T); 20 | 21 | /// 22 | /// This sets the result to be returned 23 | /// 24 | /// 25 | /// 26 | public StatusGenericHandler SetResult(T result) 27 | { 28 | _result = result; 29 | return this; 30 | } 31 | 32 | /// 33 | /// This adds one error to the Errors collection 34 | /// 35 | /// The text of the error message 36 | /// optional. A list of property names that this error applies to 37 | public new IStatusGeneric AddError(string errorMessage, params string[] propertyNames) 38 | { 39 | if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage)); 40 | _errors.Add(new ErrorGeneric(Header, new ValidationResult(errorMessage, propertyNames))); 41 | return this; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /StatusGeneric/StatusGenericHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.Linq; 8 | 9 | namespace StatusGeneric 10 | { 11 | /// 12 | /// This contains the error handling part of the GenericBizRunner 13 | /// 14 | public class StatusGenericHandler : IStatusGenericHandler 15 | { 16 | /// 17 | /// This is the default success message. 18 | /// 19 | public const string DefaultSuccessMessage = "Success"; 20 | protected readonly List _errors = new List(); 21 | private string _successMessage = DefaultSuccessMessage; 22 | 23 | /// 24 | /// This creates a StatusGenericHandler, with optional header (see Header property, and CombineStatuses) 25 | /// 26 | /// 27 | public StatusGenericHandler(string header = "") 28 | { 29 | Header = header; 30 | } 31 | 32 | /// 33 | /// The header provides a prefix to any errors you add. Useful if you want to have a general prefix to all your errors 34 | /// e.g. a header if "MyClass" would produce error messages such as "MyClass: This is my error message." 35 | /// NOTE: Headers aren't supported by the https://github.com/JonPSmith/Net.LocalizeMessagesAndErrors library 36 | /// 37 | public string Header { get; set; } 38 | 39 | /// 40 | /// This holds the list of ValidationResult errors. If the collection is empty, then there were no errors 41 | /// 42 | public IReadOnlyList Errors => _errors.AsReadOnly(); 43 | 44 | /// 45 | /// This is true if there are no errors 46 | /// 47 | public bool IsValid => !_errors.Any(); 48 | 49 | /// 50 | /// This is true if any errors have been added 51 | /// 52 | public bool HasErrors => _errors.Any(); 53 | 54 | /// 55 | /// On success this returns the message as set by the business logic, or the default messages set by the BizRunner 56 | /// If there are errors it contains the message "Failed with NN errors" 57 | /// 58 | public string Message 59 | { 60 | get => IsValid 61 | ? _successMessage 62 | : $"Failed with {_errors.Count} error" + (_errors.Count == 1 ? "" : "s"); 63 | set => _successMessage = value; 64 | } 65 | 66 | /// 67 | /// This allows statuses to be combined. This does the following 68 | /// 1. The status parameter's Errors are added to the current status 69 | /// 2. It updates the current Message if the status parameter's Message has the DefaultSuccessMessage 70 | /// NOTE: If you are using Headers, then it will combine the headers in any errors in combines 71 | /// e.g. If current Header is "MyClass" and the status parameter's Header is "MyProp", then 72 | /// each error in the status parameter's would start with "MyClass>MyProp:", e.g. "MyClass>MyProp: This is my error message." 73 | /// NOTE: Headers aren't supported by the https://github.com/JonPSmith/Net.LocalizeMessagesAndErrors library. 74 | /// 75 | /// 76 | public IStatusGeneric CombineStatuses(IStatusGeneric status) 77 | { 78 | if (!status.IsValid) 79 | { 80 | _errors.AddRange(string.IsNullOrEmpty(Header) 81 | ? status.Errors 82 | : status.Errors.Select(x => new ErrorGeneric(Header, x))); 83 | } 84 | if (IsValid && status.Message != DefaultSuccessMessage) 85 | Message = status.Message; 86 | 87 | return this; 88 | } 89 | 90 | /// 91 | /// This is a simple method to output all the errors as a single string - returns "No errors" if no errors. 92 | /// Useful for feeding back all the errors in a single exception (also nice in unit testing) 93 | /// 94 | /// if null then each errors is separated by Environment.NewLine, otherwise uses the separator you provide 95 | /// a single string with all errors separated by the 'separator' string, or "No errors" if no errors. 96 | public string GetAllErrors(string separator = null) 97 | { 98 | separator = separator ?? Environment.NewLine; 99 | return _errors.Any() 100 | ? string.Join(separator, Errors) 101 | : "No errors"; 102 | } 103 | 104 | /// 105 | /// This adds one error to the Errors collection 106 | /// NOTE: This is virtual so that the StatusGenericHandler.Generic can override it. That allows both to return a IStatusGeneric result 107 | /// 108 | /// The text of the error message 109 | /// optional. A list of property names that this error applies to 110 | public virtual IStatusGeneric AddError(string errorMessage, params string[] propertyNames) 111 | { 112 | if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage)); 113 | _errors.Add(new ErrorGeneric(Header, new ValidationResult(errorMessage, propertyNames))); 114 | return this; 115 | } 116 | 117 | /// 118 | /// This adds one error to the Errors collection and saves the exception's data to the DebugData property 119 | /// 120 | /// The exception that you want to turn into a IStatusGeneric error. 121 | /// The user-friendly text for the error message 122 | /// optional. A list of property names that this error applies to 123 | public IStatusGeneric AddError(Exception ex, string errorMessage, params string[] propertyNames) 124 | { 125 | if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage)); 126 | var errorGeneric = new ErrorGeneric(Header, new ValidationResult(errorMessage, propertyNames)); 127 | errorGeneric.CopyExceptionToDebugData(ex); 128 | _errors.Add(errorGeneric); 129 | return this; 130 | } 131 | 132 | /// 133 | /// This adds one ValidationResult to the Errors collection 134 | /// 135 | /// 136 | public void AddValidationResult(ValidationResult validationResult) 137 | { 138 | _errors.Add(new ErrorGeneric(Header, validationResult)); 139 | } 140 | 141 | /// 142 | /// This appends a collection of ValidationResults to the Errors collection 143 | /// 144 | /// 145 | public void AddValidationResults(IEnumerable validationResults) 146 | { 147 | _errors.AddRange(validationResults.Select(x => new ErrorGeneric(Header, x))); 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /Test/ExampleUsages.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using StatusGeneric; 6 | 7 | namespace Test 8 | { 9 | public class ExampleUsages 10 | { 11 | public IStatusGeneric NonStatusGenericString(string s) 12 | { 13 | var status = new StatusGenericHandler(); 14 | 15 | //add error and return immediately 16 | if (s == null) 17 | //You can return just an error message, but adding the property name 18 | //will improve the error feedback in ASP.NET Core etc. 19 | return status.AddError("input must not be null", nameof(s)); 20 | 21 | status.Message = "All went well"; 22 | 23 | //If no errors were added then its returns a IsValid status with the message 24 | //If there are errors then the Message says something like "Failed with 1 error" and the HasErrors will be true, IsValid will be false 25 | return status; 26 | } 27 | 28 | public IStatusGeneric StatusGenericNumReturnString(int i) 29 | { 30 | var status = new StatusGenericHandler(); 31 | 32 | //add error and return immediately 33 | if (i <= 0) 34 | return status.AddError("input must be positive", nameof(i)); 35 | 36 | //This sets the Result property in the generic status 37 | status.SetResult(i.ToString()); 38 | 39 | //If there are errors then the Result is set to the default value for generic type 40 | return status; 41 | } 42 | 43 | public IStatusGeneric NonStatusGenericNum(int i) 44 | { 45 | var status = new StatusGenericHandler(); 46 | 47 | //add error and return immediately 48 | if (i <= 0) 49 | return status.AddError("input must be positive", nameof(i)); 50 | 51 | //combine error from another non-StatusGeneric method and return if has errors 52 | status.CombineStatuses(NonStatusGenericString(i == 10 ? null : "good")); 53 | if (status.HasErrors) 54 | return status; 55 | 56 | //combine errors from another generic status, keeping the status to extract later 57 | var stringStatus = StatusGenericNumReturnString(i); 58 | if (status.CombineStatuses(stringStatus).HasErrors) 59 | return status; 60 | 61 | var stringResult = stringStatus.Result; 62 | //Other methods here 63 | 64 | return status; 65 | } 66 | 67 | public IStatusGeneric CheckPassword(string password) 68 | { 69 | var status = new StatusGenericHandler(); 70 | 71 | //series of tests and then return all the errors together 72 | //Good because the user gets all the errors at once 73 | if (password.Length < 10) 74 | status.AddError("A password must be 10 or more in length", 75 | nameof(password)); 76 | if (!password.Any(char.IsUpper)) 77 | status.AddError("A password must contain an upper case character", 78 | nameof(password)); 79 | if (!password.Any(char.IsLower)) 80 | status.AddError("A password must contain a lower case character", 81 | nameof(password)); 82 | if (!password.Any(char.IsDigit)) 83 | status.AddError("A password must contain an number", 84 | nameof(password)); 85 | 86 | return status; 87 | } 88 | 89 | public IStatusGeneric Login 90 | (string email, string password) 91 | { 92 | var status = new StatusGenericHandler(); 93 | 94 | status.CombineStatuses( 95 | CheckValidEmail(email)); 96 | 97 | if (status.HasErrors) 98 | return status; 99 | 100 | if (status.CombineStatuses( 101 | CheckPassword(password)).HasErrors) 102 | return status; 103 | 104 | var loginStatus = LoginUser(email, password); 105 | status.CombineStatuses(loginStatus); 106 | 107 | status.SetResult(loginStatus.Result); 108 | 109 | return status; 110 | } 111 | 112 | //DUMMY 113 | public IStatusGeneric LoginUser(string email, string password) 114 | { 115 | var status = new StatusGenericHandler(); 116 | 117 | return status; 118 | } 119 | 120 | //DUMMY 121 | public IStatusGeneric CheckValidEmail(string email) 122 | { 123 | var status = new StatusGenericHandler(); 124 | if (email == null) 125 | status.AddError("The email must not be null", nameof(email)); 126 | return status; 127 | } 128 | 129 | 130 | public IStatusGeneric StatusGenericNum(int i) 131 | { 132 | var status = new StatusGenericHandler(); 133 | 134 | //series of tests and then return all the errors together 135 | //Good because the user gets all the errors at once 136 | if (i == 20) 137 | status.AddError("input must not be 20", nameof(i)); 138 | if (i == 30) 139 | status.AddError("input must not be 30", nameof(i)); 140 | if (i == 40) 141 | status.AddError("input must not be 40", nameof(i)); 142 | if (i == 50) 143 | status.AddError("input must not be 50", nameof(i)); 144 | if (!status.IsValid) 145 | return status; 146 | 147 | //add error and return immediately 148 | if (i <= 0) 149 | return status.AddError("input must be positive", nameof(i)); 150 | 151 | //combine error from another non-StatusGeneric method and return if has errors 152 | status.CombineStatuses(NonStatusGenericString(i == 10 ? null : "good")); 153 | if (status.HasErrors) 154 | return status; 155 | 156 | //combine errors from another generic status, keeping the status to extract later 157 | var stringStatus = StatusGenericNumReturnString(i); 158 | if (status.CombineStatuses(stringStatus).HasErrors) 159 | return status; 160 | 161 | //Do something with the Result from the StatusGenericNumReturnString method 162 | var combinedNums = int.Parse(stringStatus.Result) + i; 163 | //Set the result to be returned from this method if there are no errors 164 | status.SetResult(combinedNums); 165 | 166 | //You can put tests just before a result would be returned - any error will set the result to default value 167 | if (combinedNums <= 0) 168 | status.AddError("input must be positive", nameof(i)); 169 | 170 | //If you return a IStatusGeneric and there are errors then the result will be set to default 171 | return status; 172 | } 173 | } 174 | 175 | 176 | } -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Test/TestExampleUsages.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test 11 | { 12 | public class TestExampleUsages 13 | { 14 | private readonly ITestOutputHelper _output; 15 | 16 | public TestExampleUsages(ITestOutputHelper output) 17 | { 18 | _output = output; 19 | } 20 | 21 | [Theory] 22 | [InlineData("xxx", true)] 23 | [InlineData(null, false)] 24 | public void TestNonStatusGenericString(string s, bool valid) 25 | { 26 | //SETUP 27 | var service = new ExampleUsages(); 28 | 29 | //ATTEMPT 30 | var status = service.NonStatusGenericString(s); 31 | 32 | //VERIFY 33 | status.IsValid.ShouldEqual(valid); 34 | if (valid) 35 | { 36 | status.Message.ShouldEqual("All went well"); 37 | } 38 | else 39 | { 40 | status.Errors.Single().ToString().ShouldEqual("input must not be null"); 41 | status.Message.ShouldEqual("Failed with 1 error"); 42 | } 43 | } 44 | 45 | [Theory] 46 | [InlineData(1, true)] 47 | [InlineData(-1, false)] 48 | public void TestStatusGenericNumReturnString(int i, bool valid) 49 | { 50 | //SETUP 51 | var service = new ExampleUsages(); 52 | 53 | //ATTEMPT 54 | var status = service.StatusGenericNumReturnString(i); 55 | 56 | //VERIFY 57 | status.IsValid.ShouldEqual(valid); 58 | if (valid) 59 | { 60 | status.Result.ShouldEqual("1"); 61 | } 62 | else 63 | { 64 | status.Errors.Single().ToString().ShouldEqual("input must be positive"); 65 | status.Message.ShouldEqual("Failed with 1 error"); 66 | status.Result.ShouldEqual(null); 67 | } 68 | } 69 | 70 | [Theory] 71 | [InlineData(1, true, null)] 72 | [InlineData(-1, false, "input must be positive")] 73 | [InlineData(10, false, "input must not be null")] 74 | public void TestNonStatusGenericNum(int i, bool valid, string error) 75 | { 76 | //SETUP 77 | var service = new ExampleUsages(); 78 | 79 | //ATTEMPT 80 | var status = service.NonStatusGenericNum(i); 81 | 82 | //VERIFY 83 | status.IsValid.ShouldEqual(valid); 84 | if (valid) 85 | { 86 | status.Message.ShouldEqual("All went well"); 87 | } 88 | else 89 | { 90 | status.Errors.Single().ToString().ShouldEqual(error); 91 | status.Message.ShouldEqual("Failed with 1 error"); 92 | } 93 | } 94 | 95 | [Theory] 96 | [InlineData("Ab1aaaaaaaaa", 0)] 97 | [InlineData("aaaaaaaaaa1", 1)] 98 | [InlineData("", 4)] 99 | public void TestCheckPassword(string s, int numErrors) 100 | { 101 | //SETUP 102 | var service = new ExampleUsages(); 103 | 104 | //ATTEMPT 105 | var status = service.CheckPassword(s); 106 | 107 | //VERIFY 108 | foreach (var errorGeneric in status.Errors) 109 | { 110 | _output.WriteLine($"Error: '{errorGeneric.ToString()}', members: {string.Join(", ",errorGeneric.ErrorResult.MemberNames)}"); 111 | } 112 | status.Errors.Count.ShouldEqual(numErrors); 113 | } 114 | 115 | [Theory] 116 | [InlineData("me@gmail.com", "Ab1aaaaaaaaa", null)] 117 | [InlineData("me@gmail.com", "aaaaaaaaaa1", "A password must contain an upper case character")] 118 | [InlineData(null, "Ab1aaaaaaaaa", "The email must not be null")] 119 | public void TestLogin(string email, string password, string error) 120 | { 121 | //SETUP 122 | var service = new ExampleUsages(); 123 | 124 | //ATTEMPT 125 | var status = service.Login(email, password); 126 | 127 | //VERIFY 128 | status.IsValid.ShouldEqual(error == null); 129 | if (error != null) 130 | { 131 | status.Errors.Single().ToString().ShouldEqual(error); 132 | } 133 | } 134 | 135 | } 136 | } -------------------------------------------------------------------------------- /Test/TestStatusGeneric.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using StatusGeneric; 7 | using Xunit; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test 11 | { 12 | public class TestStatusGeneric 13 | { 14 | [Fact] 15 | public void TestGenericStatusOk() 16 | { 17 | //SETUP 18 | 19 | //ATTEMPT 20 | var status = new StatusGenericHandler(); 21 | 22 | //VERIFY 23 | status.IsValid.ShouldBeTrue(); 24 | status.Errors.Any().ShouldBeFalse(); 25 | status.Message.ShouldEqual("Success"); 26 | } 27 | 28 | [Fact] 29 | public void TestGenericStatusSetMessageOk() 30 | { 31 | //SETUP 32 | var status = new StatusGenericHandler(); 33 | 34 | //ATTEMPT 35 | status.Message = "New message"; 36 | 37 | //VERIFY 38 | status.IsValid.ShouldBeTrue(); 39 | status.HasErrors.ShouldBeFalse(); 40 | status.Errors.Any().ShouldBeFalse(); 41 | status.Message.ShouldEqual("New message"); 42 | } 43 | 44 | [Fact] 45 | public void TestGenericStatusSetMessageViaInterfaceOk() 46 | { 47 | //SETUP 48 | IStatusGeneric status = new StatusGenericHandler(); 49 | 50 | //ATTEMPT 51 | status.Message = "New message"; 52 | 53 | //VERIFY 54 | status.IsValid.ShouldBeTrue(); 55 | status.HasErrors.ShouldBeFalse(); 56 | status.Errors.Any().ShouldBeFalse(); 57 | status.Message.ShouldEqual("New message"); 58 | } 59 | 60 | [Fact] 61 | public void TestGenericStatusWithTypeSetMessageViaInterfaceOk() 62 | { 63 | //SETUP 64 | IStatusGeneric status = new StatusGenericHandler(); 65 | 66 | //ATTEMPT 67 | status.Message = "New message"; 68 | 69 | //VERIFY 70 | status.IsValid.ShouldBeTrue(); 71 | status.HasErrors.ShouldBeFalse(); 72 | status.Errors.Any().ShouldBeFalse(); 73 | status.Message.ShouldEqual("New message"); 74 | } 75 | 76 | [Fact] 77 | public void TestGenericStatusWithErrorOk() 78 | { 79 | //SETUP 80 | var status = new StatusGenericHandler(); 81 | 82 | //ATTEMPT 83 | status.AddError("This is an error."); 84 | 85 | //VERIFY 86 | status.IsValid.ShouldBeFalse(); 87 | status.HasErrors.ShouldBeTrue(); 88 | status.Errors.Single().ToString().ShouldEqual("This is an error."); 89 | status.Errors.Single().DebugData.ShouldBeNull(); 90 | status.Message.ShouldEqual("Failed with 1 error"); 91 | } 92 | 93 | [Fact] 94 | public void TestGenericStatusCombineStatusesWithErrorsOk() 95 | { 96 | //SETUP 97 | var status1 = new StatusGenericHandler(); 98 | var status2 = new StatusGenericHandler(); 99 | 100 | //ATTEMPT 101 | status1.AddError("This is an error."); 102 | status2.CombineStatuses(status1); 103 | 104 | //VERIFY 105 | status2.IsValid.ShouldBeFalse(); 106 | status2.Errors.Single().ToString().ShouldEqual("This is an error."); 107 | status2.Message.ShouldEqual("Failed with 1 error"); 108 | } 109 | 110 | [Fact] 111 | public void TestGenericStatusCombineStatusesIsValidWithMessageOk() 112 | { 113 | //SETUP 114 | var status1 = new StatusGenericHandler(); 115 | var status2 = new StatusGenericHandler(); 116 | 117 | //ATTEMPT 118 | status1.Message = "My message"; 119 | status2.CombineStatuses(status1); 120 | 121 | //VERIFY 122 | status2.IsValid.ShouldBeTrue(); 123 | status2.Message.ShouldEqual("My message"); 124 | } 125 | 126 | [Fact] 127 | public void TestGenericStatusHeaderAndErrorOk() 128 | { 129 | //SETUP 130 | var status = new StatusGenericHandler("MyClass"); 131 | 132 | //ATTEMPT 133 | status.AddError("This is an error."); 134 | 135 | //VERIFY 136 | status.IsValid.ShouldBeFalse(); 137 | status.Errors.Single().ToString().ShouldEqual("MyClass: This is an error."); 138 | } 139 | 140 | [Fact] 141 | public void TestGenericStatusHeaderCombineStatusesOk() 142 | { 143 | //SETUP 144 | var status1 = new StatusGenericHandler("MyClass"); 145 | var status2 = new StatusGenericHandler("MyProp"); 146 | 147 | //ATTEMPT 148 | status2.AddError("This is an error."); 149 | status1.CombineStatuses(status2); 150 | 151 | //VERIFY 152 | status1.IsValid.ShouldBeFalse(); 153 | status1.Errors.Single().ToString().ShouldEqual("MyClass>MyProp: This is an error."); 154 | status1.Message.ShouldEqual("Failed with 1 error"); 155 | } 156 | 157 | [Fact] 158 | public void TestCaptureException() 159 | { 160 | //SETUP 161 | var status = new StatusGenericHandler(); 162 | 163 | //ATTEMPT 164 | try 165 | { 166 | MethodToThrowException(); 167 | } 168 | catch (Exception ex) 169 | { 170 | status.AddError(ex, "This is user-friendly error message"); 171 | } 172 | 173 | //VERIFY 174 | var lines = status.Errors.Single().DebugData.Split(Environment.NewLine); 175 | lines.Length.ShouldEqual(6); 176 | lines[0].ShouldEqual("This is a test"); 177 | lines[1].ShouldStartWith("StackTrace: at Test.TestStatusGeneric.MethodToThrowException()"); 178 | lines[3].ShouldEqual("Data: data1\t1"); 179 | lines[4].ShouldEqual("Data: data2\t2"); 180 | } 181 | 182 | private void MethodToThrowException() 183 | { 184 | var ex = new Exception("This is a test"); 185 | ex.Data.Add("data1", 1); 186 | ex.Data.Add("data2", "2"); 187 | throw ex; 188 | } 189 | 190 | //------------------------------------ 191 | 192 | [Fact] 193 | public void TestGenericStatusGenericOk() 194 | { 195 | //SETUP 196 | 197 | //ATTEMPT 198 | var status = new StatusGenericHandler(); 199 | 200 | //VERIFY 201 | status.IsValid.ShouldBeTrue(); 202 | status.Result.ShouldEqual(null); 203 | } 204 | 205 | [Fact] 206 | public void TestGenericStatusGenericSetResultOk() 207 | { 208 | //SETUP 209 | 210 | //ATTEMPT 211 | var status = new StatusGenericHandler(); 212 | status.SetResult("Hello world"); 213 | 214 | //VERIFY 215 | status.IsValid.ShouldBeTrue(); 216 | status.Result.ShouldEqual("Hello world"); 217 | } 218 | 219 | [Fact] 220 | public void TestGenericStatusGenericSetResultThenErrorOk() 221 | { 222 | //SETUP 223 | 224 | //ATTEMPT 225 | var status = new StatusGenericHandler(); 226 | status.SetResult("Hello world"); 227 | var statusCopy = status.AddError("This is an error."); 228 | 229 | //VERIFY 230 | status.IsValid.ShouldBeFalse(); 231 | status.Result.ShouldEqual(null); 232 | statusCopy.ShouldEqual(status); 233 | } 234 | } 235 | } --------------------------------------------------------------------------------