diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d339575
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,237 @@
+#
+# Severity: suggestion, warning, error
+#
+#top-most EditorConfig file
+root = true
+
+[*]
+
+#Formatting - indentation
+#use soft tabs (spaces) for indentation
+indent_style = space
+
+[*.cs]
+
+#Formatting - indentation
+
+#size of soft tabs (spaces)
+indent_size = 4
+#remove any whitespace characters preceding newline characters
+trim_trailing_whitespace = true
+
+#Formatting - indentation options
+
+#indent switch case contents.
+csharp_indent_case_contents = true
+#indent switch labels
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+
+#place catch statements on a new line
+csharp_new_line_before_catch = true
+#place else statements on a new line
+csharp_new_line_before_else = true
+#require finally statements to be on a new line after the closing brace
+csharp_new_line_before_finally = true
+#require members of object initializers to be on separate lines
+csharp_new_line_before_members_in_object_initializers = true
+#require members of anonymous types to be on separate lines
+csharp_new_line_before_members_in_anonymous_types = true
+#require elements of query expression clauses to be on separate lines
+csharp_new_line_between_query_expression_clauses = true
+#require braces to be on a new line for all expressions ("Allman" style)
+csharp_new_line_before_open_brace = all
+
+#Formatting - organize using options
+
+#do not place System.* using directives before other using directives
+dotnet_sort_system_directives_first = false
+
+#Formatting - spacing options
+
+#require a space between a cast and the value
+csharp_space_after_cast = false
+#require a space before the colon for bases or interfaces in a type declaration
+csharp_space_after_colon_in_inheritance_clause = true
+#require a space after a keyword in a control flow statement such as a for loop
+csharp_space_after_keywords_in_control_flow_statements = true
+#require a space before the colon for bases or interfaces in a type declaration
+csharp_space_before_colon_in_inheritance_clause = true
+#remove space within empty argument list parentheses
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+#remove space between method call name and opening parenthesis
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
+csharp_space_between_method_call_parameter_list_parentheses = false
+#remove space within empty parameter list parentheses for a method declaration
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+
+#leave code block on single line
+csharp_preserve_single_line_blocks = true
+#leave statements and member declarations on the same line
+csharp_preserve_single_line_statements = true
+
+#Style - code block preferences
+
+#prefer curly braces even for one line of code
+csharp_prefer_braces = true:suggestion
+
+#Style - expression bodied member options
+
+#prefer block bodies for constructors
+csharp_style_expression_bodied_constructors = false:suggestion
+#prefer block bodies for methods
+csharp_style_expression_bodied_methods = false:suggestion
+#prefer expression-bodied members for properties
+csharp_style_expression_bodied_properties = true:suggestion
+
+#Style - expression level options
+
+#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_member_access = true:error
+
+#Style - expression-level preferences
+
+#prefer objects to be initialized using object initializers when possible
+dotnet_style_object_initializer = true:suggestion
+#prefer collections to be initialized using collection initializers when possible
+dotnet_style_collection_initializer = true:suggestion
+#prefer tuple names to ItemX properties
+dotnet_style_explicit_tuple_names = true:error
+#prefer inferred tuple element names
+dotnet_style_prefer_inferred_tuple_names = true:warning
+#prefer inferred anonymous type member names
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+#prefer autoproperties over properties with private backing fields
+dotnet_style_prefer_auto_properties = true:warning
+#prefer assignments with a ternary conditional over an if-else statement
+dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
+#prefer return statements to use a ternary conditional over an if-else statement
+dotnet_style_prefer_conditional_expression_over_return = false
+#prefer using a null check with pattern-matching over object.ReferenceEquals
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
+
+#Style - implicit and explicit types
+
+#prefer var is used to declare variables with built-in system types such as int
+csharp_style_var_for_built_in_types = true:warning
+#prefer var when the type is already mentioned on the right-hand side of a declaration expression
+csharp_style_var_when_type_is_apparent = true:warning
+#prefer var is used to declare variables over explicit type in all cases, unless overridden by another code style rule
+csharp_style_var_elsewhere = true:warning
+
+#Style - language keyword and framework type options
+
+#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_locals_parameters_members = true:error
+
+#Style - qualification options
+
+#prefer fields not to be prefaced with This
+dotnet_style_qualification_for_field = false:error
+##prefer methods not to be prefaced with This
+dotnet_style_qualification_for_method = false:error
+##prefer properties not to be prefaced with This
+dotnet_style_qualification_for_property = false:error
+##prefer events not to be prefaced with This
+dotnet_style_qualification_for_event = false:error
+
+#Style - modifier preferences
+
+#prefer accessibility modifiers to be specified
+dotnet_style_require_accessibility_modifiers = always:error
+#when this rule is set to a list of modifiers, prefer the specified ordering
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+
+#Style - parentheses preferences
+
+#prefer parentheses to clarify arithmetic operator(*, /, %, +, -, <<, >>, &, ^, |) precedence
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion
+#prefer parentheses to clarify relational operator (>, <, <=, >=, is, as, ==, !=) precedence
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+#prefer parentheses to clarify other binary operator (&&, ||, ??) precedence
+dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion
+#prefer parentheses to clarify operator precedence
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+
+#Naming rules
+
+#async methods are PascalCase and end with Async
+dotnet_naming_rule.async_methods_should_end_in_async.severity = error
+dotnet_naming_rule.async_methods_should_end_in_async.symbols = async_methods
+dotnet_naming_rule.async_methods_should_end_in_async.style = async_methods_style
+
+dotnet_naming_symbols.async_methods.applicable_kinds = method
+dotnet_naming_symbols.async_methods.applicable_accessibilities = *
+dotnet_naming_symbols.async_methods.required_modifiers = async
+
+dotnet_naming_style.async_methods_style.capitalization = pascal_case
+dotnet_naming_style.async_methods_style.required_suffix = Async
+
+
+#private fields are camelCase and start with _
+dotnet_naming_rule.private_fields_should_be_camel_case.severity = error
+dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be_camel_case.style = private_field_style
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+
+dotnet_naming_style.private_field_style.required_prefix = _
+dotnet_naming_style.private_field_style.capitalization = camel_case
+
+
+#methods are PascalCase
+dotnet_naming_rule.methods_should_be_pascal_case.severity = error
+dotnet_naming_rule.methods_should_be_pascal_case.symbols = methods
+dotnet_naming_rule.methods_should_be_pascal_case.style = methods_style
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = *
+
+dotnet_naming_style.methods_style.capitalization = pascal_case
+
+
+#classes are PacalCase
+dotnet_naming_rule.classes_should_be_pascal_case.severity = error
+dotnet_naming_rule.classes_should_be_pascal_case.symbols = classes
+dotnet_naming_rule.classes_should_be_pascal_case.style = classes_style
+
+dotnet_naming_symbols.classes.applicable_kinds = class
+dotnet_naming_symbols.classes.applicable_accessibilities = *
+
+dotnet_naming_style.classes_style.capitalization = pascal_case
+
+
+#parameters are camelCase
+dotnet_naming_rule.parameters_should_be_camel_case.severity = error
+dotnet_naming_rule.parameters_should_be_camel_case.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camel_case.style = parameters_style
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+
+dotnet_naming_style.parameters_style.capitalization = camel_case
+
+
+#interfaces are PacalCase and start with I
+dotnet_naming_rule.interfaces_should_be_pascal_case.severity = error
+dotnet_naming_rule.interfaces_should_be_pascal_case.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_pascal_case.style = interfaces_style
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = *
+
+dotnet_naming_style.interfaces_style.capitalization = pascal_case
+dotnet_naming_style.interfaces_style.required_prefix = I
+
+
+# private fields and Async methods naming are optional for tests.
+# this allows to declare `private sut` and name async test without the `Async` suffix.
+[**/*.{Tests,IntegrationTests,FunctionalTests}/**.cs]
+dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.async_methods_should_end_in_async.severity = suggestion
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
new file mode 100644
index 0000000..7aaeaf1
--- /dev/null
+++ b/.github/workflows/master.yml
@@ -0,0 +1,98 @@
+name: Build, Test, and Deploy
+
+on:
+ push:
+ branches:
+ - master
+ - main
+ paths-ignore:
+ - 'samples/**'
+
+ pull_request:
+ branches:
+ - master
+ - main
+
+ repository_dispatch:
+ types:
+ - deploy
+
+ workflow_dispatch:
+ inputs:
+ deployToFeedz:
+ description: 'Set to `true` to deploy to Feedz.io'
+ required: false
+ default: ''
+ deployToNuget:
+ description: 'Set to `true` to deploy to NuGet.org'
+ required: false
+ default: ''
+
+env:
+ DOTNET_2_VERSION: '2.1.x'
+ DOTNET_3_VERSION: '3.1.x'
+ DOTNET_5_VERSION: '5.0.x'
+ BUILD_CONFIGURATION: Release
+ FEEDZ_URI: https://f.feedz.io/forevolve/operationresults/nuget/index.json
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ dotnet: ['5.0.x']
+ env:
+ IS_NOT_DISPATCH: ${{ github.event_name != 'repository_dispatch' && github.event_name != 'workflow_dispatch' }}
+
+ steps:
+ - uses: actions/checkout@v1
+ if: env.IS_NOT_DISPATCH
+
+ - name: Setup .NET Core
+ if: env.IS_NOT_DISPATCH
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ matrix.dotnet }}
+
+ - name: Build
+ if: env.IS_NOT_DISPATCH
+ run: dotnet build --configuration ${{ env.BUILD_CONFIGURATION }}
+
+ - name: Unit Test
+ if: env.IS_NOT_DISPATCH
+ run: dotnet test --configuration ${{ env.BUILD_CONFIGURATION }}
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build-and-test
+ strategy:
+ fail-fast: true
+ matrix:
+ dotnet: ['5.0.x']
+
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ ref: ${{ github.event.client_payload.ref }}
+
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ matrix.dotnet }}
+
+ - uses: dotnet/nbgv@master
+ with:
+ setAllVars: true
+
+ - name: Pack
+ run: dotnet pack --configuration ${{ env.BUILD_CONFIGURATION }}
+
+ - name: Push to feedz.io
+ run: dotnet nuget push **/*.nupkg -k ${{ secrets.FEEDZ_API_KEY }} -s ${{ env.FEEDZ_URI }}
+ if: github.event_name == 'pull_request' || (github.event_name == 'repository_dispatch' && github.event.client_payload.feedz == true) || (github.event_name == 'workflow_dispatch' && github.event.inputs.deployToFeedz == 'true')
+
+ - name: Push to NuGet.org
+ run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
+ if: github.event_name == 'push' || (github.event_name == 'repository_dispatch' && github.event.client_payload.nuget == true) || (github.event_name == 'workflow_dispatch' && github.event.inputs.deployToNuget == 'true')
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..62e1b92
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,265 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# Code coverage
+coverage/
+.nyc_output/
+
+# Local build scripts
+local-build.ps1
+local-signed.ps1
+local-test.ps1
+local-test-output/
+
+# VS Code
+.vscode/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..7eb69ac
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json.schemastore.org/prettierrc",
+ "tabWidth": 4,
+ "printWidth": 140,
+ "singleQuote": true,
+ "overrides": [
+ {
+ "files": ["*.yaml", "*.yml"],
+ "options": {
+ "tabWidth": 2
+ }
+ }
+ ]
+}
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..fcb2be8
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+ latest
+
+
\ No newline at end of file
diff --git a/ForEvolve.OperationResults.sln b/ForEvolve.OperationResults.sln
new file mode 100644
index 0000000..92d002e
--- /dev/null
+++ b/ForEvolve.OperationResults.sln
@@ -0,0 +1,111 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30021.99
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5E6BF7CA-D8BA-40DC-A57F-DC42AE9BAF6E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C2A89744-3ADE-4349-B861-243D394CE14E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForEvolve.OperationResults", "src\ForEvolve.OperationResults\ForEvolve.OperationResults.csproj", "{4263DE5B-45F9-43A2-9916-0CDC7910DD4C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForEvolve.OperationResults.AspNetCore", "src\ForEvolve.OperationResults.AspNetCore\ForEvolve.OperationResults.AspNetCore.csproj", "{0DEA78FB-065C-4B01-9B66-57E70A3B647F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForEvolve.OperationResults.Tests", "test\ForEvolve.OperationResults.Tests\ForEvolve.OperationResults.Tests.csproj", "{2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ForEvolve.OperationResults.AspNetCore.Tests", "test\ForEvolve.OperationResults.AspNetCore.Tests\ForEvolve.OperationResults.AspNetCore.Tests.csproj", "{89FD91BC-13E4-4A7B-9F37-7AB075C2700A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{CAE60B3D-061E-4B12-BFD3-E966AA7B10E8}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{206A433D-0EEB-4BB5-9C9A-7636AB172D4F}"
+ ProjectSection(SolutionItems) = preProject
+ src\Directory.Build.props = src\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C4D44813-C5C9-45B7-B918-7B078BEBCE9C}"
+ ProjectSection(SolutionItems) = preProject
+ test\Directory.Build.props = test\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{243DF7F2-15EF-45E9-81E9-C223F04E2015}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|x64.Build.0 = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Debug|x86.Build.0 = Debug|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|x64.ActiveCfg = Release|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|x64.Build.0 = Release|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|x86.ActiveCfg = Release|Any CPU
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C}.Release|x86.Build.0 = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|x64.Build.0 = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Debug|x86.Build.0 = Debug|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|x64.ActiveCfg = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|x64.Build.0 = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|x86.ActiveCfg = Release|Any CPU
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F}.Release|x86.Build.0 = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|x64.Build.0 = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Debug|x86.Build.0 = Debug|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|x64.ActiveCfg = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|x64.Build.0 = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|x86.ActiveCfg = Release|Any CPU
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C}.Release|x86.Build.0 = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|x64.Build.0 = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Debug|x86.Build.0 = Debug|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|x64.ActiveCfg = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|x64.Build.0 = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|x86.ActiveCfg = Release|Any CPU
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {4263DE5B-45F9-43A2-9916-0CDC7910DD4C} = {5E6BF7CA-D8BA-40DC-A57F-DC42AE9BAF6E}
+ {0DEA78FB-065C-4B01-9B66-57E70A3B647F} = {5E6BF7CA-D8BA-40DC-A57F-DC42AE9BAF6E}
+ {2F188C34-E6F5-4C8F-851A-AF09D8AE2E5C} = {C2A89744-3ADE-4349-B861-243D394CE14E}
+ {89FD91BC-13E4-4A7B-9F37-7AB075C2700A} = {C2A89744-3ADE-4349-B861-243D394CE14E}
+ {206A433D-0EEB-4BB5-9C9A-7636AB172D4F} = {CAE60B3D-061E-4B12-BFD3-E966AA7B10E8}
+ {C4D44813-C5C9-45B7-B918-7B078BEBCE9C} = {CAE60B3D-061E-4B12-BFD3-E966AA7B10E8}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {18F52EC3-580A-4474-8BBE-C7DBF2F0855A}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 0000000..45adfe3
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,32 @@
+
+
+
+ True
+ False
+ Carl-Hugo Marcotte
+ ForEvolve
+ https://github.com/ForEvolve/ForEvolve.OperationResults
+ MIT
+ Carl-Hugo Marcotte
+ true
+ true
+ True
+ snupkg
+
+
+
+ preview
+
+
+ latest
+
+
+
+
+
+ 3.3.37
+ all
+
+
+
+
\ No newline at end of file
diff --git a/src/ForEvolve.OperationResults.AspNetCore/ExceptionExtensions.cs b/src/ForEvolve.OperationResults.AspNetCore/ExceptionExtensions.cs
new file mode 100644
index 0000000..29b1e7c
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/ExceptionExtensions.cs
@@ -0,0 +1,26 @@
+using ForEvolve.OperationResults;
+using ForEvolve.OperationResults.AspNetCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System
+{
+ public static class ExceptionExtensions
+ {
+ public static IOperationResult ToOperationResult(this Exception exception)
+ {
+ var result = new OperationResult();
+ result.Messages.Add(new ExceptionMessage(exception));
+ return result;
+ }
+
+ public static IOperationResult ToOperationResult(this Exception exception)
+ {
+ var result = new OperationResult();
+ result.Messages.Add(new ExceptionMessage(exception));
+ return result;
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/ExceptionMessage.cs b/src/ForEvolve.OperationResults.AspNetCore/ExceptionMessage.cs
new file mode 100644
index 0000000..8923dda
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/ExceptionMessage.cs
@@ -0,0 +1,41 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Text.Json.Serialization;
+
+namespace ForEvolve.OperationResults.AspNetCore
+{
+ ///
+ /// Represents a wrapper message around an .
+ ///
+ public class ExceptionMessage : ProblemDetailsMessage
+ {
+ ///
+ /// Get the exception represented by this message.
+ ///
+ [JsonIgnore]
+ public Exception Exception { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that represents the message.
+ public ExceptionMessage(Exception exception)
+ : base(OperationMessageLevel.Error)
+ {
+ Exception = exception ?? throw new ArgumentNullException(nameof(exception));
+ LoadProblemDetails(new ProblemDetails
+ {
+ Title = exception.GetType().Name,
+ Detail = exception.Message
+ });
+ }
+
+ ///
+ [JsonIgnore]
+ public override Type Type => Exception.GetType();
+
+ ///
+ [JsonIgnore]
+ public override object OriginalObject => Exception;
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/ForEvolve.OperationResults.AspNetCore.csproj b/src/ForEvolve.OperationResults.AspNetCore/ForEvolve.OperationResults.AspNetCore.csproj
new file mode 100644
index 0000000..50da2dc
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/ForEvolve.OperationResults.AspNetCore.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net5.0;netcoreapp3.1
+ Extensions of ForEvolve.OperationResults ASP.NET Core. Adds support for ProblemDetails and includes a ModelBinderErrorActionFilter.
+ forevolve,aspnetcore,mvc,filters,asp.net,core,aspnet,asp,operation,result,results,message,exception,net5
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ForEvolve.OperationResults.AspNetCore/MessageCollectionExtensions.cs b/src/ForEvolve.OperationResults.AspNetCore/MessageCollectionExtensions.cs
new file mode 100644
index 0000000..1649c5c
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/MessageCollectionExtensions.cs
@@ -0,0 +1,26 @@
+using ForEvolve.OperationResults.AspNetCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ForEvolve.OperationResults
+{
+ public static class MessageCollectionExtensions
+ {
+ ///
+ /// Filters exception messages and returns their that are of the specified type.
+ ///
+ /// The type of to search for.
+ ///
+ /// The filtered messages .
+ public static IEnumerable GetExceptionsOfType(this MessageCollection messages)
+ where TException : Exception
+ {
+ return messages
+ .GetAll()
+ .HavingDetailsOfTypeAs();
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/Mvc/ModelBinderErrorActionFilter.cs b/src/ForEvolve.OperationResults.AspNetCore/Mvc/ModelBinderErrorActionFilter.cs
new file mode 100644
index 0000000..4fdb645
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/Mvc/ModelBinderErrorActionFilter.cs
@@ -0,0 +1,43 @@
+using ForEvolve.OperationResults.AspNetCore;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ForEvolve.OperationResults.AspNetCore.Mvc
+{
+ public class ModelBinderErrorActionFilter : IAsyncActionFilter
+ {
+ public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+ {
+ if (!context.ModelState.IsValid)
+ {
+ var errorMessages = context.ModelState.Values
+ .SelectMany(x =>
+ {
+ return x.Errors;
+ })
+ .Where(x => x.Exception == default)
+ .Select(x => new Message(OperationMessageLevel.Error, new
+ {
+ ErrorCode = "ModelBindingError",
+ x.ErrorMessage
+ }));
+ var exceptionMessages = context.ModelState.Values
+ .SelectMany(x => x.Errors)
+ .Where(x => x.Exception != default)
+ .Select(x => new ExceptionMessage(x.Exception));
+
+ var messages = errorMessages.Concat(exceptionMessages);
+ var failure = new OperationResult();
+ failure.Messages.AddRange(messages);
+ context.Result = new BadRequestObjectResult(failure);
+ return Task.CompletedTask;
+ }
+ return next();
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/Mvc/OperationResultStartupExtensions.cs b/src/ForEvolve.OperationResults.AspNetCore/Mvc/OperationResultStartupExtensions.cs
new file mode 100644
index 0000000..063e69f
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/Mvc/OperationResultStartupExtensions.cs
@@ -0,0 +1,30 @@
+using ForEvolve.OperationResults.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class OperationResultAspNetCoreStartupExtensions
+ {
+ ///
+ /// Adds operation results Asp.Net Core filters.
+ /// This includes an interceptor that returns a non-successful operation result
+ /// when the ModelBinder is not able to create the model; see for more info.
+ ///
+ /// The services.
+ /// IServiceCollection.
+ public static IServiceCollection AddForEvolveOperationResultModelBinderErrorActionFilter(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.Configure(options =>
+ {
+ options.Filters.AddService();
+ });
+ return services;
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsExtensions.cs b/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsExtensions.cs
new file mode 100644
index 0000000..cac14c0
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsExtensions.cs
@@ -0,0 +1,41 @@
+using ForEvolve.OperationResults;
+using ForEvolve.OperationResults.AspNetCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+ public static class ProblemDetailsExtensions
+ {
+ public static IOperationResult ToOperationResult(this ProblemDetails problemDetails)
+ {
+ if (problemDetails == null) { throw new ArgumentNullException(nameof(problemDetails)); }
+ return ToOperationResult(problemDetails, OperationMessageLevel.Error);
+ }
+
+ public static IOperationResult ToOperationResult(this ProblemDetails problemDetails, OperationMessageLevel severity)
+ {
+ if (problemDetails == null) { throw new ArgumentNullException(nameof(problemDetails)); }
+ var result = new OperationResult();
+ result.Messages.Add(new ProblemDetailsMessage(problemDetails, severity));
+ return result;
+ }
+
+ public static IOperationResult ToOperationResult(ProblemDetails problemDetails)
+ {
+ if (problemDetails == null) { throw new ArgumentNullException(nameof(problemDetails)); }
+ return ToOperationResult(problemDetails, OperationMessageLevel.Error);
+ }
+
+ public static IOperationResult ToOperationResult(ProblemDetails problemDetails, OperationMessageLevel severity)
+ {
+ if (problemDetails == null) { throw new ArgumentNullException(nameof(problemDetails)); }
+ var result = new OperationResult();
+ result.Messages.Add(new ProblemDetailsMessage(problemDetails, severity));
+ return result;
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsMessage.cs b/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsMessage.cs
new file mode 100644
index 0000000..5f3c3ba
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/ProblemDetailsMessage.cs
@@ -0,0 +1,80 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace ForEvolve.OperationResults.AspNetCore
+{
+ ///
+ /// Represents an operation result message build around [RFC3986] .
+ /// Inherits from
+ ///
+ ///
+ public class ProblemDetailsMessage : Message
+ {
+ ///
+ /// Gets the problem details.
+ ///
+ /// The problem details.
+ [JsonIgnore]
+ public ProblemDetails ProblemDetails { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The problem details.
+ /// The severity.
+ /// problemDetails
+ public ProblemDetailsMessage(ProblemDetails problemDetails, OperationMessageLevel severity)
+ : base(severity, problemDetails)
+ {
+ ProblemDetails = problemDetails ?? throw new ArgumentNullException(nameof(problemDetails));
+ LoadProblemDetails(problemDetails);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Sub-classes must manually call the method.
+ ///
+ /// The severity.
+ protected ProblemDetailsMessage(OperationMessageLevel severity)
+ : base(severity)
+ {
+ }
+
+ ///
+ /// Loads the specified problem details into the dictionary.
+ ///
+ /// The problem details to load.
+ protected void LoadProblemDetails(ProblemDetails problemDetails)
+ {
+ if (problemDetails.Type != null)
+ {
+ Details.Add(nameof(problemDetails.Type).ToLowerInvariant(), problemDetails.Type);
+ }
+ if (problemDetails.Title != null)
+ {
+ Details.Add(nameof(problemDetails.Title).ToLowerInvariant(), problemDetails.Title);
+ }
+ if (problemDetails.Status != null)
+ {
+ Details.Add(nameof(problemDetails.Status).ToLowerInvariant(), problemDetails.Status);
+ }
+ if (problemDetails.Detail != null)
+ {
+ Details.Add(nameof(problemDetails.Detail).ToLowerInvariant(), problemDetails.Detail);
+ }
+ if (problemDetails.Instance != null)
+ {
+ Details.Add(nameof(problemDetails.Instance).ToLowerInvariant(), problemDetails.Instance);
+ }
+ foreach (var item in problemDetails.Extensions)
+ {
+ Details.Add(item);
+ }
+ }
+ }
+}
diff --git a/src/ForEvolve.OperationResults.AspNetCore/Standardizer/DefaultOperationResultStandardizer.cs b/src/ForEvolve.OperationResults.AspNetCore/Standardizer/DefaultOperationResultStandardizer.cs
new file mode 100644
index 0000000..d9aeb22
--- /dev/null
+++ b/src/ForEvolve.OperationResults.AspNetCore/Standardizer/DefaultOperationResultStandardizer.cs
@@ -0,0 +1,82 @@
+using Microsoft.CSharp.RuntimeBinder;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ForEvolve.OperationResults.Standardizer
+{
+ ///
+ /// Represents the default standardizer.
+ /// Implements the
+ ///
+ ///
+ public class DefaultOperationResultStandardizer : IOperationResultStandardizer
+ {
+ private readonly IPropertyNameFormatter _propertyNameFormatter;
+ private readonly IPropertyValueFormatter _propertyValueFormatter;
+ private readonly DefaultOperationResultStandardizerOptions _options;
+ private readonly ILogger _logger;
+
+ public DefaultOperationResultStandardizer(
+ IPropertyNameFormatter propertyNameFormatter,
+ IPropertyValueFormatter propertyValueFormatter,
+ IOptionsMonitor options,
+ ILogger logger)
+ {
+ _propertyNameFormatter = propertyNameFormatter ?? throw new ArgumentNullException(nameof(propertyNameFormatter));
+ _propertyValueFormatter = propertyValueFormatter ?? throw new ArgumentNullException(nameof(propertyValueFormatter));
+ _options = options.CurrentValue ?? throw new ArgumentNullException(nameof(options));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public object Standardize(IOperationResult operationResult)
+ {
+ if (operationResult == null) { throw new ArgumentNullException(nameof(operationResult)); }
+
+ var dictionary = new Dictionary();
+ if (!string.IsNullOrEmpty(_options.OperationName))
+ {
+ dictionary.Add(_options.OperationName, new
+ {
+ operationResult.Messages,
+ operationResult.Succeeded
+ });
+ }
+ try
+ {
+ AddValueToDictionary(operationResult, dictionary);
+ }
+ catch (RuntimeBinderException ex)
+ {
+ _logger.LogError(ex, ex.Message);
+ }
+ return dictionary;
+ }
+
+ private void AddValueToDictionary(IOperationResult operationResult, Dictionary dictionary)
+ {
+ var value = FindValueProperty(operationResult);
+ if (value != null)
+ {
+ foreach (var property in value.GetType().GetProperties())
+ {
+ var formattedName = _propertyNameFormatter.Format(property.Name);
+ var formattedValue = _propertyValueFormatter.Format(property.GetValue(value));
+ dictionary.Add(formattedName, formattedValue);
+ }
+ }
+ }
+
+ private static object FindValueProperty(IOperationResult operationResult)
+ {
+ const string valuePropertyName = nameof(IOperationResult