Created
July 4, 2018 10:57
-
-
Save rasoulian/157807a4637df94d5fdba67c7db1e22a to your computer and use it in GitHub Desktop.
webapi-fluentvalidation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CreateTableDto | |
{ | |
[Required] | |
[StringLength(50)] | |
public string Name { get; set; } | |
[StringLength(200)] | |
public string Description { get; set; } | |
[Required] | |
[Range(0, 100)] | |
public int? MaxPartySize { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CreateTableDto | |
{ | |
public string Name { get; set; } | |
public string Description { get; set; } | |
public int? MaxPartySize { get; set; } | |
public int? AreaId { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public TableResource CreateTable(int restaurantId, [ValidateParameter] CreateTableDto request) | |
{ | |
var area = _areaRepository.GetById(request.AreaId, restaurantId); | |
if (area == null) | |
{ | |
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest) | |
{ | |
Content = new StringContent($"Area Id '{request.AreaId}' does not exist in restaurant Id '{restaurantId}'.") | |
}); | |
} | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CreateTableDtoValidator : AbstractValidator<CreateTableDto>, INeedRequestContext | |
{ | |
private int _restaurantId; | |
public CreateTableDtoValidator(IAreaRepository areaRepository) | |
{ | |
// ... | |
RuleFor(t => t.AreaId).NotNull(); | |
RuleFor(t => t.AreaId) | |
.Must(areaId => IsAreaInRestaurant(areaId, areaRepository)) | |
.WithMessage("Area Id '{PropertyValue}' does not exist in restaurant Id '{0}'.", t => _restaurantId); | |
} | |
private bool IsAreaInRestaurant(int? areaId, IAreaRepository areaRepository) | |
{ | |
return areaId == null || areaRepository.GetById(areaId.Value, _restaurantId); | |
} | |
public void ProvideContext(ActionContext actionContext) | |
{ | |
_restaurantId = int.Parse(actionContext.ActionArguments["restaurantId"]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (validator != null) | |
{ | |
var contextProvider = validator as INeedRequestContext; | |
if (contextProvider != null) | |
{ | |
contextProvider.ProvideContext(actionContext); | |
} | |
var result = validator.Validate(parameterValue); | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CreateTableDtoValidator : AbstractValidator<CreateTableDto> | |
{ | |
public CreateTableDtoValidator() | |
{ | |
RuleFor(t => t.Name).NotEmpty().Length(1, 50); | |
RuleFor(t => t.Description).Length(0, 200); | |
RuleFor(t => t.MaxLength).NotNull().GreaterThan(0).LessThanOrEqual(100); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var validator = new CreateTableDtoValidator(); | |
var table = new CreateTableDto | |
{ | |
Name = "Table 1", | |
Description = new string('a', 201), | |
MaxPartySize = null | |
}; | |
var validationResult = validator.Validate(table); | |
Assert.IsTrue(validationResult.IsValid); // Will fail |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Test] | |
public void Name_HasErrorWhenNull() | |
{ | |
new CreateTableDtoValidator().ShouldHaveValidationErrorFor(t => t.Name, (string)null); | |
} | |
[Test] | |
public void Name_DoesNotHaveErrorWhenValid() | |
{ | |
new CreateTableDtoValidator().ShouldNotHaveValidationErrorFor(t => t.Name, "Table 1"); | |
} | |
[Test] | |
public void Name_HasErrorWhenTooLong() | |
{ | |
new CreateTableDtoValidator().ShouldHaveValidationErrorFor(t => t.Name, new string('a', 51)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class TableController : ApiController | |
{ | |
private readonly IValidator<CreateTableDto> _createTableValidator; | |
public TableController(IValidator<CreateTableDto> createTableValidator) | |
{ | |
_createTableValidator = createTableValidator; | |
} | |
[Route("api/Table")] | |
[HttpPost] | |
public TableResource CreateTable(int restaurantId, CreateTableDto request) | |
{ | |
if (request == null) | |
{ | |
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest) | |
{ | |
Content = new StringContent("You must specify the table details in the request body") | |
}); | |
} | |
var validationResult = _createTableValidator.Validate(request); | |
if (!validationResult.IsValid) | |
{ | |
throw new ApiValidationException(validationResult); | |
} | |
// Do the real work | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"ValidationErrors": [ | |
{ | |
"PropertyName": "Name", | |
"ErrorMessage": "'Name' must not be empty." | |
}, | |
{ | |
"PropertyName": "MaxPartySize", | |
"ErrorMessage": "'MaxPartySize' must not be null." | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ValidationExceptionFilterAttribute : ExceptionFilterAttribute | |
{ | |
private readonly IMapper _mapper; | |
public ValidationExceptionFilterAttribute(IMapper mapper) | |
{ | |
_mapper = mapper; | |
} | |
public override void OnException(HttpActionExecutedContext actionExecutedContext) | |
{ | |
var exception = actionExecutedContext.Exception as ApiValidationException; | |
if (exception != null) | |
{ | |
var apiErrorDto = _mapper.Map<ApiErrorDto>(exception); | |
actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) | |
{ | |
Content = | |
new ObjectContent(typeof (ApiErrorDto), | |
apiErrorDto, | |
new JsonMediaTypeFormatter()), | |
ReasonPhrase = exception.Message | |
}; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Route("api/Table")] | |
[HttpPost] | |
public TableResource CreateTable(int restaurantId, CreateTableDto request) | |
{ | |
if (!ModelState.IsValid) | |
{ | |
throw new HttpResponseException(...); | |
} | |
// Do work | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public TableResource CreateTable(int restaurantId, [Required] CreateTableDto request) | |
{ | |
// ... | |
} | |
// or | |
public TableResource CreateTable(int restaurantId, [ValidateParameter] CreateTableDto request) | |
{ | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ValidateActionAttribute : ActionFilterAttribute | |
{ | |
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) | |
{ | |
foreach (var parameter in actionContext.ActionDescriptor.GetParameters()) | |
{ | |
object parameterValue; | |
if (actionContext.ActionArguments.TryGetValue(parameter.ParameterName, out parameterValue)) | |
{ | |
if (parameter.GetCustomAttributes<RequiredAttribute>().Any() || | |
parameter.GetCustomAttributes<ValidateParameterAttribute>().Any()) | |
{ | |
if (parameterValue == null) | |
{ | |
var validateParameterAttribute = | |
parameter.GetCustomAttributes<ValidateParameterAttribute>().FirstOrDefault(); | |
if (validateParameterAttribute == null || !validateParameterAttribute.AllowNull) | |
{ | |
actionContext.Response = actionContext.Request.CreateErrorResponse( | |
HttpStatusCode.BadRequest, | |
string.Format("The '{0}' parameter must be specified with the request.", | |
parameter.ParameterName)); | |
} | |
} | |
else | |
{ | |
ValidateParameter(actionContext, parameter, parameterValue); | |
} | |
} | |
} | |
} | |
return base.OnActionExecutingAsync(actionContext, cancellationToken); | |
} | |
private void ValidateParameter(HttpActionContext actionContext, HttpParameterDescriptor parameter, object parameterValue) | |
{ | |
if (parameter.GetCustomAttributes<ValidateParameterAttribute>().Any()) | |
{ | |
var validatorBaseType = typeof (IValidator<>); | |
var validatorType = validatorBaseType.MakeGenericType(parameter.ParameterType); | |
var validator = (IValidator) ServiceLocator.Current.GetInstance(validatorType); | |
if (validator != null) | |
{ | |
var result = validator.Validate(parameterValue); | |
if (!result.IsValid) | |
{ | |
var errorResponse = new ApiErrorDto | |
{ | |
Message = string.Format("Parameter '{0}' has failed validation.", parameter.ParameterName), | |
ValidationErrors = | |
result.Errors.Select( | |
e => | |
new ValidationErrorDto | |
{ | |
ErrorMessage = e.ErrorMessage, | |
PropertyName = e.PropertyName | |
}) | |
}; | |
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest) | |
{ | |
Content = | |
new ObjectContent(typeof (ApiErrorDto), | |
errorResponse, | |
new JsonMediaTypeFormatter()), | |
ReasonPhrase = errorResponse.Message | |
}; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment