Skip to content

Commit 974a5dd

Browse files
committed
Move OperationResults
1 parent 68e12c8 commit 974a5dd

File tree

44 files changed

+3489
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3489
-0
lines changed

Dependencies.Build.props

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project>
2+
<PropertyGroup>
3+
<!-- ForEvolve packages -->
4+
<ForEvolvePackagesVersion Condition="'$(FOREVOLVE_PACKAGES_VERSION)'==''">[2.0.0,3.0)</ForEvolvePackagesVersion>
5+
<ForEvolvePhantomJsDependenciesPackagesVersion Condition="'$(FOREVOLVE_PHANTOMJS_DEPENDENCIES_PACKAGES_VERSION)'==''">1.0.0</ForEvolvePhantomJsDependenciesPackagesVersion>
6+
7+
<!-- Versioning helpers -->
8+
<!-- <ForEvolveTargetFrameworks Condition="'$(FOREVOLVE_TARGET_FRAMEWORKS)'==''">netstandard2.0;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1</ForEvolveTargetFrameworks> -->
9+
<ForEvolveTargetFrameworks Condition="'$(FOREVOLVE_TARGET_FRAMEWORKS)'==''">net5.0</ForEvolveTargetFrameworks>
10+
<ForEvolveTestTargetFramework Condition="'$(FOREVOLVE_TARGET_FRAMEWORKS)'==''">net5.0</ForEvolveTestTargetFramework>
11+
</PropertyGroup>
12+
13+
<!-- Source packages -->
14+
<PropertyGroup>
15+
<ForEvolveNetCoreVersion Condition="'$(FOREVOLVE_ASPNETCORE_VERSION)'==''">5.0.0</ForEvolveNetCoreVersion>
16+
<ForEvolveIdentityVersion Condition="'$(FOREVOLVE_IDENTITY_VERSION)'==''">5.0.0</ForEvolveIdentityVersion>
17+
<ForEvolveEFCoreVersion Condition="'$(FOREVOLVE_EFCORE_VERSION)'==''">5.0.0-preview.3.20181.2</ForEvolveEFCoreVersion>
18+
<DefineConstants>SYSTEM_TEXT_JSON</DefineConstants>
19+
</PropertyGroup>
20+
21+
<!-- Tests packages -->
22+
<PropertyGroup>
23+
<ForEvolveTestingVersion Condition="'$(FOREVOLVE_TESTING_VERSION)'==''">1.0.6</ForEvolveTestingVersion>
24+
<ForEvolveXunitVersion Condition="'$(FOREVOLVE_XUNIT_VERSION)'==''">2.4.1</ForEvolveXunitVersion>
25+
<ForEvolveMoqVersion Condition="'$(FOREVOLVE_MOQ_VERSION)'==''">4.14.1</ForEvolveMoqVersion>
26+
<ForEvolveTestSdkVersion Condition="'$(FOREVOLVE_TEST_SDK_VERSION)'==''">16.6.1</ForEvolveTestSdkVersion>
27+
<ForEvolveAspNetCoreTestHostVersion Condition="'$(FOREVOLVE_ASPNETCORE_TESTHOST_VERSION)'==''">5.0.0-preview.3.20215.14</ForEvolveAspNetCoreTestHostVersion>
28+
</PropertyGroup>
29+
30+
</Project>

Directory.Build.props

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project>
2+
<Import Project="Dependencies.Build.props" />
3+
4+
<PropertyGroup>
5+
<Product>ForEvolve Framework</Product>
6+
<RepositoryUrl>https://github.com/ForEvolve/ForEvolve-Framework</RepositoryUrl>
7+
<RepositoryType>git</RepositoryType>
8+
<LangVersion>latest</LangVersion>
9+
</PropertyGroup>
10+
11+
<ItemGroup Condition="'$(TargetFramework)'!='net461'">
12+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
13+
</ItemGroup>
14+
15+
</Project>

src/Directory.Build.props

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project>
2+
<!-- <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /> -->
3+
<Import Project="..\Directory.Build.props" />
4+
<PropertyGroup>
5+
<TargetFrameworks>$(ForEvolveTargetFrameworks)</TargetFrameworks>
6+
<IsPackable>True</IsPackable>
7+
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
8+
<Authors>Carl-Hugo Marcotte</Authors>
9+
<Company>ForEvolve</Company>
10+
<PackageProjectUrl>https://github.com/ForEvolve/ForEvolve-Framework</PackageProjectUrl>
11+
<PackageLicenseUrl>https://github.com/ForEvolve/ForEvolve-Framework/blob/master/LICENSE</PackageLicenseUrl>
12+
<AssemblyVersion>1.0.0.0</AssemblyVersion>
13+
<FileVersion>1.0.0.0</FileVersion>
14+
<Version>1.0.0</Version>
15+
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
16+
<Copyright>Carl-Hugo Marcotte</Copyright>
17+
<IncludeSymbols>True</IncludeSymbols>
18+
<IncludeSource>True</IncludeSource>
19+
<DebugType>Full</DebugType>
20+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
21+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
22+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
23+
<!--<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>-->
24+
<!--
25+
Removed until documentation is up to date.
26+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
27+
-->
28+
<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
29+
</PropertyGroup>
30+
<ItemGroup>
31+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
32+
</ItemGroup>
33+
</Project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.Filters;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace ForEvolve.OperationResults.AspNetCore
10+
{
11+
public class ModelBinderErrorActionFilter : IAsyncActionFilter
12+
{
13+
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
14+
{
15+
if (!context.ModelState.IsValid)
16+
{
17+
var errorMessages = context.ModelState.Values
18+
.SelectMany(x =>
19+
{
20+
return x.Errors;
21+
})
22+
.Where(x => x.Exception == default)
23+
.Select(x => new Message(OperationMessageLevel.Error, new
24+
{
25+
ErrorCode = "ModelBindingError",
26+
x.ErrorMessage
27+
}));
28+
var exceptionMessages = context.ModelState.Values
29+
.SelectMany(x => x.Errors)
30+
.Where(x => x.Exception != default)
31+
.Select(x => new ExceptionMessage(x.Exception));
32+
33+
var messages = errorMessages.Concat(exceptionMessages);
34+
var failure = new OperationResult();
35+
failure.Messages.AddRange(messages);
36+
context.Result = new BadRequestObjectResult(failure);
37+
return Task.CompletedTask;
38+
}
39+
return next();
40+
}
41+
}
42+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>$(ForEvolveTargetFrameworks)</TargetFrameworks>
5+
<Description>This package contains an operation result helpers for Asp.Net Core MVC and MediatR; for example: automatic validation.</Description>
6+
<PackageTags>forevolve,aspnetcore,mvc,filters,asp.net,core,aspnet,asp,operation,result,results,message,exception</PackageTags>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="FluentValidation" Version="8.6.2" />
11+
<PackageReference Include="MediatR" Version="8.0.1" />
12+
<PackageReference Include="Scrutor" Version="3.2.1" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\ForEvolve.OperationResults\ForEvolve.OperationResults.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using FluentValidation;
7+
using MediatR;
8+
using Scrutor;
9+
10+
namespace ForEvolve.OperationResults.MediatR
11+
{
12+
/// <summary>
13+
/// Contains extension methods to help wire up MediatR.
14+
/// </summary>
15+
public static class ForEvolveMediatRExtensions
16+
{
17+
/// <summary>
18+
/// Registers all <see cref="ValidationBehavior{TRequest, TResponse}"/>
19+
/// that uses <see cref="IValidator{TRequest}"/>
20+
/// as MediatR <see cref="IPipelineBehavior{TRequest, TResponse}"/>.
21+
///
22+
/// Automatic command and query validation should work as long there is a <see cref="IValidator{TRequest}"/> for the handler.
23+
/// </summary>
24+
/// <param name="selector">The selector used to scan for classes.</param>
25+
/// <returns>The selector.</returns>
26+
public static IImplementationTypeSelector AddValidationBehaviors(this IImplementationTypeSelector selector)
27+
{
28+
return selector.AddClasses(classes => classes.AssignableTo(typeof(ValidationBehavior<,>)))
29+
.As(typeof(IPipelineBehavior<,>))
30+
.WithTransientLifetime();
31+
}
32+
33+
/// <summary>
34+
/// Scans and registers all <see cref="IValidator{T}"/>.
35+
/// </summary>
36+
/// <param name="selector">The selector used to scan for classes.</param>
37+
/// <returns>The selector.</returns>
38+
public static IImplementationTypeSelector AddValidators(this IImplementationTypeSelector selector)
39+
{
40+
return selector.AddClasses(classes => classes.AssignableTo(typeof(IValidator<>)))
41+
.AsImplementedInterfaces()
42+
.WithScopedLifetime();
43+
}
44+
45+
/// <summary>
46+
/// Automatic validators discovery and automatic validation of commands/queries into the MediatR pipeline
47+
///
48+
/// Shortcut that calls <see cref="AddValidators"/> and <see cref="AddValidationBehaviors"/>.
49+
/// </summary>
50+
/// <param name="selector">The selector used to scan for classes.</param>
51+
/// <returns>The selector.</returns>
52+
public static IImplementationTypeSelector AddValidatorsAndBehaviors(this IImplementationTypeSelector selector)
53+
{
54+
return selector
55+
.AddValidators()
56+
.AddValidationBehaviors();
57+
}
58+
}
59+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using FluentValidation;
2+
using FluentValidation.Results;
3+
using MediatR;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace ForEvolve.OperationResults.MediatR
12+
{
13+
/// <summary>
14+
/// This behavior runs all validators and returns the error as an
15+
/// <see cref="IOperationResult"/> if validation fails.
16+
/// </summary>
17+
/// <typeparam name="TRequest"></typeparam>
18+
/// <typeparam name="TResponse"></typeparam>
19+
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
20+
where TRequest : IRequest<IOperationResult>
21+
where TResponse : IOperationResult
22+
{
23+
private readonly IEnumerable<IValidator<TRequest>> _validators;
24+
25+
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
26+
{
27+
_validators = validators ?? throw new ArgumentNullException(nameof(validators));
28+
}
29+
30+
/// <inheritdoc />
31+
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
32+
{
33+
// Validate
34+
var failures = _validators
35+
.Select(v => v.Validate(request))
36+
.SelectMany(r => r.Errors);
37+
var validationResult = new ValidationResult(failures);
38+
if (!validationResult.IsValid)
39+
{
40+
// Map errors to output
41+
var messages = validationResult.Errors
42+
.Select(validationFailure => Map(validationFailure))
43+
.ToArray();
44+
45+
// Try to return the result
46+
var castedResult = OperationResult
47+
.Failure(messages)
48+
.ConvertTo<TResponse>();
49+
if (castedResult != null)
50+
{
51+
return castedResult;
52+
}
53+
}
54+
return await next();
55+
}
56+
57+
private static IMessage Map(ValidationFailure validationFailure)
58+
{
59+
var severity = Map(validationFailure.Severity);
60+
return new Message(severity, new
61+
{
62+
validationFailure.ErrorCode,
63+
validationFailure.ErrorMessage
64+
});
65+
}
66+
67+
private static OperationMessageLevel Map(Severity severity)
68+
{
69+
switch (severity)
70+
{
71+
case Severity.Warning:
72+
return OperationMessageLevel.Warning;
73+
case Severity.Info:
74+
return OperationMessageLevel.Information;
75+
default:
76+
return OperationMessageLevel.Error;
77+
}
78+
}
79+
}
80+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using ForEvolve.OperationResults;
2+
using ForEvolve.OperationResults.AspNetCore;
3+
using ForEvolve.OperationResults.Standardizer;
4+
using Microsoft.AspNetCore.Mvc;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Microsoft.Extensions.DependencyInjection
12+
{
13+
public static class OperationResultStartupExtensions
14+
{
15+
/// <summary>
16+
/// Adds operation results Asp.Net Core filters.
17+
/// This includes an interceptor that returns a non-successful operation result
18+
/// when the ModelBinder is not able to create the model; see <see cref="ModelBinderErrorActionFilter"/> for more info.
19+
/// </summary>
20+
/// <param name="services">The services.</param>
21+
/// <returns>IServiceCollection.</returns>
22+
public static IServiceCollection AddForEvolveOperationResultFilters(this IServiceCollection services)
23+
{
24+
services.AddSingleton<ModelBinderErrorActionFilter>();
25+
services.Configure<MvcOptions>(options =>
26+
{
27+
options.Filters.AddService<ModelBinderErrorActionFilter>();
28+
});
29+
return services;
30+
}
31+
32+
/// <summary>
33+
/// Adds the default ForEvolve operation result standardizer filters.
34+
/// </summary>
35+
/// <param name="services">The services.</param>
36+
/// <returns>IServiceCollection.</returns>
37+
public static IServiceCollection AddForEvolveOperationResultStandardizer(this IServiceCollection services)
38+
{
39+
services
40+
.AddLogging()
41+
.AddSingleton<IPropertyNameFormatter, DefaultPropertyNameFormatter>()
42+
.AddSingleton<IPropertyValueFormatter, DefaultPropertyValueFormatter>()
43+
.AddSingleton<IOperationResultStandardizer, DefaultOperationResultStandardizer>()
44+
.AddOptions<DefaultOperationResultStandardizerOptions>()
45+
;
46+
services
47+
.Configure<MvcOptions>(options =>
48+
{
49+
options.Filters.Add<OperationResultStandardizerActionFilter<CreatedAtActionResult>>();
50+
options.Filters.Add<OperationResultStandardizerActionFilter<CreatedAtRouteResult>>();
51+
options.Filters.Add<OperationResultStandardizerActionFilter<CreatedResult>>();
52+
53+
options.Filters.Add<OperationResultStandardizerActionFilter<OkObjectResult>>();
54+
55+
options.Filters.Add<OperationResultStandardizerActionFilter<BadRequestObjectResult>>();
56+
options.Filters.Add<OperationResultStandardizerActionFilter<ConflictObjectResult>>();
57+
options.Filters.Add<OperationResultStandardizerActionFilter<NotFoundObjectResult>>();
58+
options.Filters.Add<OperationResultStandardizerActionFilter<UnprocessableEntityObjectResult>>();
59+
});
60+
return services;
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)