Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rasoulian/157807a4637df94d5fdba67c7db1e22a to your computer and use it in GitHub Desktop.
Save rasoulian/157807a4637df94d5fdba67c7db1e22a to your computer and use it in GitHub Desktop.
webapi-fluentvalidation
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; }
}
public class CreateTableDto
{
public string Name { get; set; }
public string Description { get; set; }
public int? MaxPartySize { get; set; }
public int? AreaId { get; set; }
}
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}'.")
});
}
// ...
}
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"]);
}
}
if (validator != null)
{
var contextProvider = validator as INeedRequestContext;
if (contextProvider != null)
{
contextProvider.ProvideContext(actionContext);
}
var result = validator.Validate(parameterValue);
// ...
}
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);
}
}
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
[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));
}
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
}
}
{
"ValidationErrors": [
{
"PropertyName": "Name",
"ErrorMessage": "'Name' must not be empty."
},
{
"PropertyName": "MaxPartySize",
"ErrorMessage": "'MaxPartySize' must not be null."
}
]
}
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
};
}
}
}
[Route("api/Table")]
[HttpPost]
public TableResource CreateTable(int restaurantId, CreateTableDto request)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(...);
}
// Do work
}
public TableResource CreateTable(int restaurantId, [Required] CreateTableDto request)
{
// ...
}
// or
public TableResource CreateTable(int restaurantId, [ValidateParameter] CreateTableDto request)
{
// ...
}
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