Sync/async requests cause manual validation to return different responses

A service can have sync or async request methods. However manual validation exception returns different responses depending upon sync/async. The sync request returns the correct response information in order to use ss-utils to applyErrors(). The async request seems to return more generic information and the errors[] array is empty.

With a sync request both .Validate(...) and .ValidateAsync(...) work correctly.
With an async request both do not work (in that they return a generic validation error).

// sync (1) .Validate(...) Works Correctly
public class RegisterService : Service
{
    public object Post(Register request)
    {
        // biz code

        // manual validate
        var validator = new RegisterValidator() { Request = Request };
        var validationResult = validator.Validate(Request, request);
        if (!validationResult.IsValid)
        {
            // Note: .ToException() returns custom FormValidationException code below
            throw validationResult.ToException();
        }
        // more biz code
        return response;
    }
}
// sync (2) .ValidateAsync(...) Works Correctly
public class RegisterService : Service
{
    public object Post(Register request)
    {
        // biz code

        // manual validate
        var validator = new RegisterValidator() { Request = Request };
        var validationResult = validator.ValidateAsync(Request, request).Result;
        if (!validationResult.IsValid)
        {
            // Note: .ToException() returns custom FormValidationException code below
            throw validationResult.ToException();
        }
        // more biz code
        return response;
    }
}
// async (1) .ValidateAsync(...) Doesn't work - Returns generic `ValidationError` response.
public class RegisterService : Service
{
    public async Task<object> Post(Register request)
    {
        // biz code

        // manual validate
        var validator = new RegisterValidator() { Request = Request };
        var validationResult = await validator.ValidateAsync(Request, request);
        if (!validationResult.IsValid)
        {
            // Note: .ToException() returns custom FormValidationException code below
            throw validationResult.ToException();
        }
        // more biz code
        return response;
    }
}
// async (2) .Validate(...)  Doesn't work - Returns generic `ValidationError` response.
public class RegisterService : Service
{
    public async Task<object> Post(Register request)
    {
        // biz code

        // manual validate
        var validator = new RegisterValidator() { Request = Request };
        var validationResult = validator.Validate(Request, request);
        if (!validationResult.IsValid)
        {
            // Note: .ToException() returns custom FormValidationException code below
            throw validationResult.ToException();
        }
        // more biz code
        return response;
    }
}

Validator code:

public class RegisterValidator : AbstractValidator<Register>
    {
        public RegisterValidator()
        {
            RuleSet(
                ApplyTo.Put | ApplyTo.Post,
                () =>
                {
                    RuleFor(x => x.Email).EmailAddress();
                });
        }
    }

Note: FormValidationError is custom, but isn’t necessary. Was trying to return additional info in the ValidationError, but it isn’t relevant to the issue that’s happening.

public class FormValidationError : ValidationError
    {
        public FormValidationError(string errorCode) : base(errorCode) { }
        public FormValidationError(ValidationErrorResult validationResult) : base(validationResult) { }
        public FormValidationError(ValidationErrorField validationError) : base(validationError) { }
        public FormValidationError(string errorCode, string errorMessage) : base(errorCode, errorMessage) { }

        // Note: This doesn't get included in error response. Not sure why. Not the issue at the moment.
        public string MyProp { get; set; }
    }

sync requests return correctly with:

image

{
    "JSON": {
        "responseStatus": {
            "errorCode": "Email",
            "message": "'Email' is not a valid email address.",
            \"errors\":[{\"errorCode\":\"Email\",\"fieldName\":\"Email\",\"message\":\"'Email' is not a valid email address.\",\"meta\":{\"PropertyName\":\"Email\",\"PropertyValue\":\"test@\"}}]}}",
            "mode": "application/json"
        }
    }
}

stacktrace was removed from above json, but it was:

[Register: 10/17/2019 1:05:59 AM]:[REQUEST: {email:test@,continue:"https://localhost:44333/home/"}]<snip>.FormValidationError: 'Email' is not a valid email address.   at  <snip>\RegisterService.cs:line 172   at <snip>.RegisterService.Post(Register request) in <snip>\RegisterService.cs:line 84   at ServiceStack.Host.ServiceRunner`1.ExecuteAsync(IRequest req, Object instance, TRequest requestDto) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack\Host\ServiceRunner.cs:line 133

async requests return the following json

image

{
    "JSON": {
        "responseStatus": {
            "errorCode": "FormValidationError",
            "message": "'Email' is not a valid email address.",
            "stackTrace": "   at <snip>.RegisterService.cs:line 172\r\n   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.HandleResponse(IRequest httpReq, IResponse httpRes, Object response) in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack\\Host\\Handlers\\ServiceStackHandlerBase.cs:line 81\r\n   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest req, IResponse httpRes, String operationName) in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack\\Host\\RestHandler.cs:line 103"
        }
    },
    "Response payload": {
        "EDITOR_CONFIG": {
            "text": "{\"responseStatus\":{\"errorCode\":\"FormValidationError\",\"message\":\"'Email' is not a valid email address.\",\"stackTrace\":\"   at <snip>\\\\RegisterService.cs:line 172\\r\\n   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.HandleResponse(IRequest httpReq, IResponse httpRes, Object response) in C:\\\\BuildAgent\\\\work\\\\3481147c480f4a2f\\\\src\\\\ServiceStack\\\\Host\\\\Handlers\\\\ServiceStackHandlerBase.cs:line 81\\r\\n   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest req, IResponse httpRes, String operationName) in C:\\\\BuildAgent\\\\work\\\\3481147c480f4a2f\\\\src\\\\ServiceStack\\\\Host\\\\RestHandler.cs:line 103\"}}",
            "mode": "application/json"
        }
    }
}

Stack Trace

at <snip>\\\\RegisterService.cs:line 172\\r\\n   at ServiceStack.Host.Handlers.ServiceStackHandlerBase.HandleResponse(IRequest httpReq, IResponse httpRes, Object response) in C:\\\\BuildAgent\\\\work\\\\3481147c480f4a2f\\\\src\\\\ServiceStack\\\\Host\\\\Handlers\\\\ServiceStackHandlerBase.cs:line 81\\r\\n   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest req, IResponse httpRes, String operationName) in C:\\\\BuildAgent\\\\work\\\\3481147c480f4a2f\\\\src\\\\ServiceStack\\\\Host\\\\RestHandler.cs:line 103

Notice that the async requests have ValidationError responses with an empty errors[] array and errorCode: "FormValidationError

Sync requests have a populated/filled in errors[] array and have an error code of errorCode: "Email".

It doesn’t matter if the sync request uses .ValidateAsync(...).Result or the sync .Validate(...), it still works.

The difference or issue seems to be with whether the request is sync or async.

Is it just manual validation with the issue? i.e. are there any issues with normal auto request validation?

There are no issues with regular registered validations. That I’ve seen so far.

I’m not able to repro this, the bottom 2 call styles you say doesn’t work returns the same response as your first sync response. Can you modify this stand-alone integration test with a failing test that repros the issue: ManualValidationTests.cs

Where is AppSelfHostBase coming from? I don’t see it. Is it in .NET Core?

It’s in the ServiceStack.Kestrel NuGet package.

Everything in your tests works.

It looks to be programmer error. The async Post() was calling into another async method (that was originally sync) that throws the validation error however that async method did not have await on it. I don’t think there is anything you could do about that or give a warning.

Looks to be a gotcha when moving from code that was sync to async.

If you want to see what was going on below is the updated MyRegisterService in your ManualValidationTests.cs

.Get(...) Works b/c it uses await
.Post(...) Doesn’t work b/c it forgets await
.Put(...) Doesn’t work b/c it forgets await

public class MyRegisterService : Service
    {

        public async Task<object> Get(MyRegister request)
        {
            object response = null;

            // b/c of "await" this works
            response = await DoValidationAndSomeOtherBizLogic(request);

            // return new EmptyResponse();
            return response;
        }

        public async Task<object> Post(MyRegister request)
        {

            object response = null;

            // missing "await" this doesn't work
            response = DoValidationAndSomeOtherBizLogic(request);

            return response;
        }

        public async Task<object> Put(MyRegister request)
        {
            // missing "await" this doesn't work
            return DoValidationAndSomeOtherBizLogic(request);
        }

        private async Task<object> DoValidationAndSomeOtherBizLogic(MyRegister request)
        {
            object response = null;
            var validator = new MyRegisterValidator { Request = Request };
            var validationResult = validator.Validate(Request, request);
            if (!validationResult.IsValid)
                throw validationResult.ToException();

            return response;
        }
    }
1 Like