Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override http status code from validator

I've got the following DTO:

public class SomethingRequest {
     public string Code { get; set; }
}

Code must be unique, so I've created a validator that checks if there is already a record with provided code, like the following

public class SomethingValidator: AbstractValidator<SomethingRequest> 
{
    public SomethingValidator(ISomethingRepository repo) {
         RuleFor(something => something.Code).Must(BeUnique);
    }

    private bool BeUnique(string code) { ... uniqueness check... }
}

As I'm using validation feature, the validator is automatically wired for all methods with SomethingRequest, which is really great.

When condition fails I would like to return 409 Conflict HTTP status code, but 400 Bad Request is always returned.

So, the questions are:

  1. Am I misusing vaidation feature? (i.e. autowired validators were not designed to be used for application logic checks)
  2. If I'm not, are there any ways to override 400 BadRequest status code from validator?
like image 509
J0HN Avatar asked Mar 31 '14 12:03

J0HN


Video Answer


2 Answers

Am I misusing validation feature? (i.e. autowired validators were not designed to be used for application logic checks)

I would say this is best done in the business logic away from the validation, because checking for a uniqueness is actually a verification check rather than validation, because it requires checking against a data source. My answer on this question addresses a similar concern.

While you can override the response status code of the validation error using the ErrorResponseFilter, I would recommend creating your own request filter for this business logic, as overriding the response there will be messy as your application grows, and again, it's not really validation.

Using a filter attribute is straightforward in ServiceStack:

public class VerifySomethingCodeAttribute : Attribute, IHasRequestFilter
{
    IHasRequestFilter IHasRequestFilter.Copy()
    {
        return this;
    }

    public int Priority { get { return int.MinValue; } }

    public void RequestFilter(IRequest req, IResponse res, object requestDto)
    {
        SomethingRequest somethingRequestDto = requestDto as SomethingRequest;
        if(somethingRequestDto == null)
            return;

        // Verify the code
        // Replace with suitable logic
        // If you need the database your wire it up from the IoC
        // i.e. HostContext.TryResolve<IDbConnectionFactory>();
        bool isUnique = ...

        if(!isUnique)
            throw HttpError.Conflict("This record already exists");
    }
}

Then simply annotate the DTO:

[VerifySomethingCode]
public class SomethingRequest {
    public string Code { get; set; }
}

Then you can be sure that the Code in the DTO will have been verified as unique and you can return any status and response you want. The filter gives you total control.

Hope this helps.

like image 148
Scott Avatar answered Oct 07 '22 16:10

Scott


1) Although it allows dependency injection and wiring up of repositories, the fluent validation code isn't the place you are supposed to put this kind of code as it is more along the lines of verification code. This answer has a good explanation of the differences between the two. I'll just add that it also makes sense for splitting the verification up from validation if only for more easily returning the appropriate status code.

2)If you would like to override the 400 BadRequest status code, you can use the validation feature's ErrorResponseFilter like so:

Plugins.Add(new ValidationFeature
{
    ErrorResponseFilter = CustomValidationError
});

...

private object CustomValidationError(ValidationResult validationResult, object errorDto)
{
    var firstError = validationResult.Errors.First();
    return new HttpError(HttpStatusCode.Conflict, firstError.ErrorCode, firstError.ErrorMessage);
}

This filter looks to be intended for a global solution as it doesn't appear to give you any easy way to determine the dto/service the error came from. I would suggest looking at making the change in 1 instead.

like image 30
bpruitt-goddard Avatar answered Oct 07 '22 15:10

bpruitt-goddard