Skip to content

Instantly share code, notes, and snippets.

@steff-mueller
Created December 23, 2011 14:06
Show Gist options
  • Save steff-mueller/1514298 to your computer and use it in GitHub Desktop.
Save steff-mueller/1514298 to your computer and use it in GitHub Desktop.
Servicestack - Validation of request dtos

This feature is now included in the latest release of ServiceStack and the up-to-date documentation can be found here: https://github.com/ServiceStack/ServiceStack/wiki/Validation

FluentValidation for request dtos

First in the app host the validation mechanism must be initialized:

ValidationHandler.Init(this);

This request dto should be validated:

[RestService("/users")]
public class User
{
    public string Name { get; set; }
    public string Company { get; set; }
    public int Age { get; set; }
    public int Count { get; set; }
}

The validation rules for this request dto are made with FluentValidation (http://fluentvalidation.codeplex.com/documentation). ServiceStack makes heavy use of rule sets (http://fluentvalidation.codeplex.com/wikipage?title=CreatingAValidator&referringTitle=Documentation&ANCHOR#RuleSets) to provide different validation rules for each HTTP method (GET, POST, PUT...).

public class ValidatedValidator : AbstractValidator<Validated>
{
    public ValidatedValidator()
    {
        //Validation rules for all requests
        RuleFor(r => r.Name).NotEmpty();
        RuleFor(r => r.Age).GreaterThan(0);

        //Validation rules for GET request
        RuleSet(HttpMethods.Get, () =>
            {
                RuleFor(r => r.Count).GreaterThan(10);
            });

        //Validation rules for POST request
        RuleSet(HttpMethods.Post, () =>
            {
                RuleFor(r => r.Company).NotEmpty();
            });
    }
}

Warning: If a validator for a request dto is created, all rules which aren't in any rule set are executed + the rules in the matching rule set. Normally FluentValidation only executes the matching rule set and ignores all other rules (whether they're in a rule set or not) and the rules which don't belong to any rule set are normally only executed, if no rule set-name was given to the validate method of the validator.

This validator has to be registered in the IoC container as IValidator<User>. This can be made in the app host:

container.Register<IValidator<Validated>>(new ValidatedValidator());

Now the service etc can be created and the validation rules are checked every time a request comes in.

If you try now for example to send this request:

POST localhost:50386/validated
{
    "Name": "Max"
} 

You'll get this JSON response:

{
    "ErrorCode": "'Age' must be greater than '0'.",
    "Message": "' age' must be greater than ' 0'.",
    "Errors": [
        {
            "ErrorCode": "'Age' must be greater than '0'.",
            "FieldName": "Age",
            "Message": "' age' must be greater than ' 0'."
        },
        {
            "ErrorCode": "'Company' should not be empty.",
            "FieldName": "Company",
            "Message": "' company' should not be empty."
        }
    ]
}

Use FluentValidation everywhere!

Of course FluentValidation can be used for any other classes, too:

public class TestClass
{
    public string Text { get; set; }
    public int Length { get; set; }
}

Now the validator:

public class TestClassValidator : AbstractValidator<TestClass>
{
    public TestClassValidator()
    {
        RuleFor(x => x.Text).NotEmpty();
        RuleFor(x => x.Length).GreaterThan(0);
    }
}

Inside some service code you can validate an instance of this class:

public class SomeService : RestServiceBase<Validated>
{
    //You should have registered your validator in the IoC container to inject the validator into this property
    public IValidator<TestClass> Validator { get; set; }

    public override object OnGet(Validated request)
    {
        TestClass instance = new TestClass();

        ValidationResult result = this.Validator.Validate(instance);

        if (!result.IsValid)
        {
            //The result will be serialized into a SerializableValidationException and throw this one
            //The erros will be serialized in a clean way (as the above JSON example)
            result.Throw();
        }

    }
}
@mythz
Copy link

mythz commented Dec 23, 2011

Hey, if possible I think the ErrorCode field should be something that can be programatically useful, e.g. GreaterThan or NotEmpty for the examples above (i.e. the name of the classes that were invalid).
I'm not sure how easy that's stuff is to get to, but it would make it more programatically useful on the client which can switch on these values to provide their own custom error message / localization or behaviour, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment