Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
H
HIAST-Clinics
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
almohanad.hafez
HIAST-Clinics
Commits
9dc19d17
Unverified
Commit
9dc19d17
authored
Aug 18, 2024
by
Almouhannad Hafez
Committed by
GitHub
Aug 18, 2024
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8 from Almouhannad/B_Add-fluent-validation
B add fluent validation
parents
a7ea84d5
041d69cc
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
318 additions
and
9 deletions
+318
-9
API.csproj
Clinics.Backend/API/API.csproj
+1
-0
Program.cs
Clinics.Backend/API/Program.cs
+9
-0
Application.csproj
Clinics.Backend/Application/Application.csproj
+1
-0
ValidationPipelineBehavior.cs
...ckend/Application/Behaviors/ValidationPipelineBehavior.cs
+90
-0
CreateEmployeeCommandValidator.cs
...ployees/Commands/Create/CreateEmployeeCommandValidator.cs
+73
-0
IValidationResult.cs
...ics.Backend/Domain/Shared/Validation/IValidationResult.cs
+11
-0
ValidationResult.cs
Clinics.Backend/Domain/Shared/Validation/ValidationResult.cs
+18
-0
ValidationResultT.cs
...ics.Backend/Domain/Shared/Validation/ValidationResultT.cs
+17
-0
ValidationErrorMessages.cs
...idationConstants/ErrorMessages/ValidationErrorMessages.cs
+19
-0
ValidationRegularExpressions.cs
...stants/RegularExpressions/ValidationRegularExpressions.cs
+18
-0
ApiController.cs
...cs.Backend/Presentation/Controllers/Base/ApiController.cs
+53
-0
EmployeesController.cs
...s.Backend/Presentation/Controllers/EmployeesController.cs
+8
-9
No files found.
Clinics.Backend/API/API.csproj
View file @
9dc19d17
...
...
@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
...
...
Clinics.Backend/API/Program.cs
View file @
9dc19d17
using
API.Options.Database
;
using
API.SeedDatabaseHelper
;
using
Application.Behaviors
;
using
FluentValidation
;
using
MediatR
;
using
Microsoft.EntityFrameworkCore
;
using
Microsoft.Extensions.Options
;
using
Persistence.Context
;
...
...
@@ -49,6 +52,12 @@ builder
#region Add MadiatR
builder
.
Services
.
AddMediatR
(
configuration
=>
configuration
.
RegisterServicesFromAssembly
(
Application
.
AssemblyReference
.
Assembly
));
#region Add validation pipeline
builder
.
Services
.
AddScoped
(
typeof
(
IPipelineBehavior
<,>),
typeof
(
ValidationPipelineBehavior
<,>));
builder
.
Services
.
AddValidatorsFromAssembly
(
Application
.
AssemblyReference
.
Assembly
);
#endregion
#endregion
#region Link controllers with presentation layer
...
...
Clinics.Backend/Application/Application.csproj
View file @
9dc19d17
...
...
@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.2" />
<PackageReference Include="MediatR" Version="12.4.0" />
</ItemGroup>
...
...
Clinics.Backend/Application/Behaviors/ValidationPipelineBehavior.cs
0 → 100644
View file @
9dc19d17
using
Domain.Shared.Validation
;
using
Domain.Shared
;
using
MediatR
;
using
FluentValidation
;
namespace
Application.Behaviors
;
// Using MediatR pipeline to perform validation
// Using Fluent validation (IValidator) to make validators
public
class
ValidationPipelineBehavior
<
TRequest
,
TResponse
>
:
IPipelineBehavior
<
TRequest
,
TResponse
>
// From MediatR
where
TRequest
:
IRequest
<
TResponse
>
// Request sent by the pipeline (enforced by imp. of CQRS)
where
TResponse
:
Result
// Type of response (enforced by imp. of CQRS)
{
#
region
CTOR
DI
for
validators
private
readonly
IEnumerable
<
IValidator
<
TRequest
>>
_validators
;
public
ValidationPipelineBehavior
(
IEnumerable
<
IValidator
<
TRequest
>>
validators
)
{
_validators
=
validators
;
}
#
endregion
#
region
Create
validation
result
private
static
TResult
CreateValidationResult
<
TResult
>(
Error
[]
errors
)
where
TResult
:
Result
{
// Simple case, Result only
if
(
typeof
(
TResult
)
==
typeof
(
Result
))
{
return
(
ValidationResult
.
WithErrors
(
errors
)
as
TResult
)!;
// This won't fail ever
}
// Other complicated case, we have a Result<Type>
// Using reflection
object
validationResult
=
typeof
(
ValidationResult
<>)
.
GetGenericTypeDefinition
()
.
MakeGenericType
(
typeof
(
TResult
).
GenericTypeArguments
[
0
])
.
GetMethod
(
nameof
(
ValidationResult
.
WithErrors
))!
.
Invoke
(
null
,
[
errors
])!;
return
(
TResult
)
validationResult
;
}
#
endregion
#
region
Handle
method
(
pipeline
behavior
)
public
async
Task
<
TResponse
>
Handle
(
TRequest
request
,
RequestHandlerDelegate
<
TResponse
>
next
,
CancellationToken
cancellationToken
)
{
// Validate request
// If any errors, return validation request
// Otherwise,return next() [Pipline ~ Middleware]
// Case of no validators
if
(!
_validators
.
Any
())
{
return
await
next
();
}
// Generate errors array
Error
[]
errors
=
_validators
.
Select
(
validator
=>
validator
.
Validate
(
request
))
// Select the result of validate method
.
SelectMany
(
validationResult
=>
validationResult
.
Errors
)
.
Where
(
validationFailure
=>
validationFailure
is
not
null
)
.
Select
(
failure
=>
new
Error
(
// Project into Error object
failure
.
PropertyName
,
failure
.
ErrorMessage
))
.
Distinct
()
.
ToArray
();
if
(
errors
.
Length
!=
0
)
{
return
CreateValidationResult
<
TResponse
>(
errors
);
// Static method in this class
}
return
await
next
();
}
#
endregion
}
Clinics.Backend/Application/Employees/Commands/Create/CreateEmployeeCommandValidator.cs
0 → 100644
View file @
9dc19d17
using
Domain.ValidationConstants.ErrorMessages
;
using
Domain.ValidationConstants.RegularExpressions
;
using
FluentValidation
;
namespace
Application.Employees.Commands.Create
;
public
class
CreateEmployeeCommandValidator
:
AbstractValidator
<
CreateEmployeeCommand
>
{
public
CreateEmployeeCommandValidator
()
{
#
region
First
name
RuleFor
(
c
=>
c
.
FirstName
)
.
NotEmpty
()
.
WithMessage
(
ValidationErrorMessages
.
Required
);
RuleFor
(
c
=>
c
.
FirstName
)
.
MaximumLength
(
50
)
.
WithMessage
(
ValidationErrorMessages
.
FixedLength
);
RuleFor
(
c
=>
c
.
FirstName
)
.
Matches
(
ValidationRegularExpressions
.
ArabicLettersOnly
)
.
WithMessage
(
ValidationErrorMessages
.
ArabicLettersOnly
);
#
endregion
#
region
Middle
name
RuleFor
(
c
=>
c
.
MiddleName
)
.
NotEmpty
()
.
WithMessage
(
ValidationErrorMessages
.
Required
);
RuleFor
(
c
=>
c
.
MiddleName
)
.
MaximumLength
(
50
)
.
WithMessage
(
ValidationErrorMessages
.
FixedLength
);
RuleFor
(
c
=>
c
.
MiddleName
)
.
Matches
(
ValidationRegularExpressions
.
ArabicLettersOnly
)
.
WithMessage
(
ValidationErrorMessages
.
ArabicLettersOnly
);
#
endregion
#
region
Last
name
RuleFor
(
c
=>
c
.
LastName
)
.
NotEmpty
()
.
WithMessage
(
ValidationErrorMessages
.
Required
);
RuleFor
(
c
=>
c
.
LastName
)
.
MaximumLength
(
50
)
.
WithMessage
(
ValidationErrorMessages
.
FixedLength
);
RuleFor
(
c
=>
c
.
LastName
)
.
Matches
(
ValidationRegularExpressions
.
ArabicLettersOnly
)
.
WithMessage
(
ValidationErrorMessages
.
ArabicLettersOnly
);
#
endregion
#
region
Serial
number
RuleFor
(
c
=>
c
.
SerialNumber
)
.
NotEmpty
()
.
WithMessage
(
ValidationErrorMessages
.
Required
);
RuleFor
(
c
=>
c
.
SerialNumber
)
.
Matches
(
ValidationRegularExpressions
.
NumbersOnly
)
.
WithMessage
(
ValidationErrorMessages
.
NumbersOnly
);
RuleFor
(
c
=>
c
.
SerialNumber
)
.
MaximumLength
(
20
)
.
WithMessage
(
ValidationErrorMessages
.
FixedLength
);
#
endregion
#
region
Center
status
RuleFor
(
c
=>
c
.
CenterStatus
)
.
NotEmpty
()
.
WithMessage
(
ValidationErrorMessages
.
Required
);
RuleFor
(
c
=>
c
.
CenterStatus
)
.
Matches
(
ValidationRegularExpressions
.
ArabicLettersOnly
)
.
WithMessage
(
ValidationErrorMessages
.
ArabicLettersOnly
);
RuleFor
(
c
=>
c
.
CenterStatus
)
.
MaximumLength
(
50
)
.
WithMessage
(
ValidationErrorMessages
.
FixedLength
);
#
endregion
// TODO: add validation rules for additional fields
}
}
Clinics.Backend/Domain/Shared/Validation/IValidationResult.cs
0 → 100644
View file @
9dc19d17
namespace
Domain.Shared.Validation
;
public
interface
IValidationResult
{
public
static
readonly
Error
ValidationError
=
new
(
"ValidationError"
,
"القيم المدخلة غير صالحة"
);
#
region
Properties
Error
[]
Errors
{
get
;
}
#
endregion
}
Clinics.Backend/Domain/Shared/Validation/ValidationResult.cs
0 → 100644
View file @
9dc19d17
namespace
Domain.Shared.Validation
;
public
class
ValidationResult
:
Result
,
IValidationResult
{
private
ValidationResult
(
Error
[]
errors
)
:
base
(
false
,
IValidationResult
.
ValidationError
)
=>
Errors
=
errors
;
#
region
Properties
public
Error
[]
Errors
{
get
;
}
#
endregion
#
region
Static
factory
public
static
ValidationResult
WithErrors
(
Error
[]
errors
)
=>
new
(
errors
);
#
endregion
}
Clinics.Backend/Domain/Shared/Validation/ValidationResultT.cs
0 → 100644
View file @
9dc19d17
namespace
Domain.Shared.Validation
;
public
class
ValidationResult
<
TValue
>
:
Result
<
TValue
>,
IValidationResult
{
private
ValidationResult
(
Error
[]
errors
)
:
base
(
default
,
false
,
IValidationResult
.
ValidationError
)
=>
Errors
=
errors
;
#
region
Properties
public
Error
[]
Errors
{
get
;
}
#
endregion
#
region
Static
factory
public
static
ValidationResult
<
TValue
>
WithErrors
(
Error
[]
errors
)
=>
new
(
errors
);
#
endregion
}
Clinics.Backend/Domain/ValidationConstants/ErrorMessages/ValidationErrorMessages.cs
0 → 100644
View file @
9dc19d17
namespace
Domain.ValidationConstants.ErrorMessages
;
public
static
class
ValidationErrorMessages
{
public
static
string
Required
=>
"هذا الحقل مطلوب"
;
public
static
string
ArabicOrEnglishLettersOnly
=>
"يجب أن يحوي هذا الحقل على أحرف عربية فقط أو أحرف انكليزية فقط ولا يحوي أي رموز أو أرقام"
;
public
static
string
ArabicLettersOnly
=>
"يجب أن يحوي هذا الحقل على أحرف عربية فقط ولا يحوي أي رموز أو أرقام"
;
public
static
string
FixedLength
=>
"هذا الحقل طويل للغاية"
;
public
static
string
NumbersOnly
=>
"يجب أن يحوي هذا الحقل على أرقام فقط"
;
}
Clinics.Backend/Domain/ValidationConstants/RegularExpressions/ValidationRegularExpressions.cs
0 → 100644
View file @
9dc19d17
namespace
Domain.ValidationConstants.RegularExpressions
;
public
static
class
ValidationRegularExpressions
{
public
static
string
ArabicOrEnglishLettersOnly
=>
@"^[\u0600-\u06ff\s]+$|^[a-zA-Z\s]+$"
;
public
static
string
ArabicLettersOnly
=>
@"^[א-ء-ي]+$"
;
public
static
string
EmailAddress
=>
@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
;
public
static
string
NumbersOnly
=>
@"^\d+$"
;
}
Clinics.Backend/Presentation/Controllers/Base/ApiController.cs
0 → 100644
View file @
9dc19d17
using
Domain.Shared.Validation
;
using
Domain.Shared
;
using
MediatR
;
using
Microsoft.AspNetCore.Http
;
using
Microsoft.AspNetCore.Mvc
;
namespace
Presentation.Controllers.Base
;
[ApiController]
public
abstract
class
ApiController
:
ControllerBase
{
#
region
CTOR
DI
for
MediatR
sendse
protected
readonly
ISender
_sender
;
protected
ApiController
(
ISender
sender
)
{
_sender
=
sender
;
}
#
endregion
protected
IActionResult
HandleFailure
(
Result
result
)
=>
result
switch
{
{
IsSuccess
:
true
}
=>
throw
new
InvalidOperationException
(),
IValidationResult
validationResult
=>
BadRequest
(
CreateProblemDetails
(
"Validation Error"
,
StatusCodes
.
Status400BadRequest
,
result
.
Error
,
validationResult
.
Errors
)),
_
=>
BadRequest
(
CreateProblemDetails
(
"Bad Request"
,
StatusCodes
.
Status400BadRequest
,
result
.
Error
))
};
private
static
ProblemDetails
CreateProblemDetails
(
string
title
,
int
status
,
Error
error
,
Error
[]?
errors
=
null
)
=>
new
()
{
Title
=
title
,
Type
=
error
.
Code
,
Detail
=
error
.
Message
,
Status
=
status
,
Extensions
=
{
{
nameof
(
errors
),
errors
}
}
};
}
\ No newline at end of file
Clinics.Backend/Presentation/Controllers/EmployeesController.cs
View file @
9dc19d17
using
Application.Employees.Commands.Create
;
using
MediatR
;
using
Microsoft.AspNetCore.Mvc
;
using
Presentation.Controllers.Base
;
namespace
Presentation.Controllers
;
[Route("api/[controller]
")]
[ApiController]
public
class
EmployeesController
:
ControllerBase
[Route("api/Employees")]
public
class
EmployeesController
:
ApiController
{
#
region
DI
for
MeditR
sender
private
readonly
ISender
_sender
;
public
EmployeesController
(
ISender
sender
)
public
EmployeesController
(
ISender
sender
)
:
base
(
sender
)
{
_sender
=
sender
;
}
#
endregion
...
...
@@ -21,8 +20,8 @@ public class EmployeesController : ControllerBase
public
async
Task
<
IActionResult
>
Create
(
CreateEmployeeCommand
command
)
{
var
result
=
await
_sender
.
Send
(
command
);
if
(
result
.
Is
Success
)
return
Created
(
);
else
return
BadRequest
(
result
.
Error
.
Message
);
if
(
result
.
Is
Failure
)
return
HandleFailure
(
result
);
return
Created
(
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment