Different binding behavior based on Content-Type

Using ServiceStack v4.0.48. I’ve been very happy with ServiceStack as an API framework and now I’m researching its Razor support as a full web application framework. I’m noticing some different binding behavior based on whether my service is called with JSON content or application/x-www-form-urlencoded content.

Here’s my request DTO:

[Route("/showsomething")]
public class ShowSomethingRequest : IReturn<ShowSomethingResponse>
{
	public string Name { get; set; }
	public int Age { get; set; }
}

Response DTO:

public class ShowSomethingResponse
{
	public string[] Kids { get; set; }
}

Service:

public class ShowSomething : Service
{
	public ShowSomethingResponse Get(ShowSomethingRequest request)
	{
		return new ShowSomethingResponse
		{
			Kids = new[] {"One", "Two", "Three"}
		};
	}

	public ShowSomethingResponse Post(ShowSomethingRequest request)
	{
		return new ShowSomethingResponse
		{
			Kids = new[] {"new Name: {0}, Age: {1}".Fmt(request.Name, request.Age == null ? "[null]" : request.Age.ToString())}
		};
	}
}

And the .cshtml file:

@inherits ViewPage<ShowSomethingResponse>

<h1>Kids</h1>
<ul>
	@foreach (var kid in Model.Kids)
	{
		<li>@kid</li>
	}
</ul>

<form method="post">
	Name: <input type="text" name="Name"/><br/>
	Age: <input type="text" name="Age"/><br/>
	<input type="submit" value="Save"/><br/>
</form>

If I post JSON data to this service with an Age value that can’t be converted to an integer (example: “fff”), then my Post method receives a DTO with an Age of zero. Likewise, if I make Age a nullable int, I’ll get a null.

However, when the browser posts application/x-www-form-urlencoded with an Age value that can’t be converted to an int, instead of getting a 0 (or null for int?), my POST method is never called and a null Model is sent to the .cshtml page (which then throws a null reference exception trying to access the Kids property).

Is this the expected behavior or am I doing something wrong? I could change Age to a string and then do my own parsing of its value to see if its an integer, but that seems to defeat the purpose of having simple, clean DTO’s that express the request data in a simple manner.

Thanks!

First check the Razor Page doesn’t have an Error, some API’s you can use to check for errors:

  • GetErrorStatus() - get populated Error ResponseStatus
  • GetErrorHtml() - get error info marked up in a structured html fragment

Or you can print the error and short-circuit the page by putting adding to the top:

@if (RenderErrorIfAny()) { return; }

If you do think there’s a difference in behavior, please post the Raw HTTP Request/Response for both requests, it’s possible they’re not equivalent requests.

Also if you’re just starting to use ServiceStack for web development, rather than posting to a Razor page directly I’d recommend following an API First Development approach where POST’s to ServiceStack are sent via ajax instead, i.e. using the same API that mobile/desktop clients would use. ServiceStack has good support for this with its built-in HTML Form binding which ajaxifies the HTML Form and sends the Form data via ajax and automatically binds any validation errors back to the page in the HTML placeholders.

Thanks Demis. I would definitely look to API/ajax for new development, but I was testing with old-style <form> POSTs because there’s a possibility of reducing project scope by being able to re-use some existing HTML forms while creating new, cleaner server-side code with ServiceStack services.

When I add the call to RenderErrorIfAny(), I get the following response in the browser:

RequestBindingException: Unable to bind request

   at ServiceStack.Host.RestHandler.CreateRequest(IRequest httpReq, IRestPath restPath)
   at ServiceStack.Host.RestHandler.ProcessRequestAsync(IRequest httpReq, IResponse httpRes, String operationName)

Here’s the working JSON Request:

POST /showsomething HTTP/1.1
Host: localhost:5771
Content-Type: application/json
Cache-Control: no-cache

{
    "Name" : "yyy",
    "Age" : "ss"
}

And here’s the <form> POST that fails to bind:

POST /showsomething HTTP/1.1
Host: localhost:5771
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache

Name=yyy&Age=ss

Thanks.

This is intentional, the binding exception is the behavior for FormData and QueryStrings where the requests are used for sending flat, purpose-specific requests.

Whereas the text serializers are resilient by default which is often used to handle much larger and deep complex object graphs which try to deserialize as much as possible so 1 field doesn’t break deserialization for the entire request. You can change the behavior of the text serializers to throw on deserialization error by setting:

JsConfig.ThrowOnDeserializationError = true;

Is the recommended approach to use client-side validation (in the browser in this case) to ensure the service receives values of the correct data type?

If I wanted to handle this on the server-side (like MVC) what would be the appropriate hook to use?

(MVC would produce a validation-like error of “Invalid value” if a string was provided where an int was expected.)

The Service doesn’t receive invalid data, if POST’ing a large JSON complex type invalid data is ignored and logged so the service only receives the default/null value - if this was a mandatory field your Service would throw.

If JsConfig.ThrowOnDeserializationError = true; is enabled, deserialization instead throws on invalid data.

Sorry, I was referring to FormData not JSON, like the OP. Though, I’m using utils.js. If text is submitted where an int is expected, a deserialization error is thrown. Above you said this is intentional. I’m asking, if this is intentional, would you recommend that client-size validation is in place to validate the data type? Or alternatively is there a way to hook into the deserialization so the FormData deserialization can be made resilient like JSON is?

The issue I’m trying to prevent can be seen in [Email Contacts][1], if you enter “aaa” as the age, a RequestBindingException is returned to the client, but without an indication as to which field has caused the problem. If possible I’d like to return a validation exception instead, something like “‘aaa’ is an invalid value for ‘Age’”.
[1]: http://emailcontacts.servicestack.net/

Yep makes sense, I’ve updated the Request Binding error messages to populate the error field as seen in this commit:

This change is available from v4.0.51 that’s now available from MyGet.

Thanks for implementing this :smile:

However, the field errors aren’t coming through for FormData, only QueryStrings. I’ve done some investigation and I think the problem is that in DeserializeHttpRequest the original SerializationException in being wrapped in another SerializationException, so once the OnExceptionTypeFilter is reached ex.Data["propertyName"] is actually in the inner exception, so the ResponseStatus isn’t built.

I’ve added a test to test FormData which seems to work:

Do you have a test showing it failing?

The difference seems to be that I was using the pre-defined route (json/reply). I added a custom route and this endpoint worked as expected, the errors collection was returned.

Cool, thx for the pointers - should now be resolved with this commit which is now available from v4.0.51 on MyGet.

I’ve attempted to extend the new behaviour to return all binding errors rather than just the first one. Please take a look at my pull request to see what you think. https://github.com/ServiceStack/ServiceStack/pull/1015

@georgehemmings sweet PR, merged, thx!

@georgehemmings FYI, your change is now available on MyGet.

1 Like