├── StatusGeneric
├── IStatusGeneric.Generic.cs
├── StatusGeneric.csproj
├── StatusGenericHandler.Generic.cs
├── IStatusGeneric.cs
├── IStatusGenericHandler.cs
├── ErrorGeneric.cs
└── StatusGenericHandler.cs
├── Test
├── Test.csproj
├── TestExampleUsages.cs
├── ExampleUsages.cs
└── TestStatusGeneric.cs
├── LICENSE
├── StatusGeneric.sln
├── .gitattributes
├── README.md
└── .gitignore
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.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/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 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | ```
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------