We have a GetDocuments request DTO which looks like this:
public class Constants
{
public const int DefaultPageSize = 100;
public const int DefaultPageNumber = 1;
}
[Route("/documents", "GET POST")]
public class GetDocuments : IReturn<ObservableCollection<Document>>
{
public Guid? ViewID { get; set; }
public int PageSize { get; set; } = Constants.DefaultPageSize;
public int PageNumber { get; set; } = Constants.DefaultPageNumber;
public bool AllVersions { get; set; } = true;
public string SearchCriteria { get; set; }
}
Note that PageSize, PageNumber and AllVersions have a default value.
The request of the client is for example:
GET GetDocuments?allversions=false&pagenumber=0&pagesize=50
The Gateway receives the correct values, but sends it without the correct values to the Service. It seems ServiceStack assumes that the default value AllVersions is false because it is a boolean value, but does not take in account that the DTO request object has a default value of True.
With or without the commented out lines does not do anything. Also decorating the DTO with [DataContract] and [DataMember(EmitDefaultValue = true)] doesn’t do anything.
How can we assure the properties with explicit default values are always sent from Gateway to Service?
You shouldn’t be using default values in DTOs which should be implementation-free data contracts. No implementation logic including default values are included in any of the generated Add ServiceStack Reference DTOs.
It also wont work as default values are applied at instance construction whilst deserialization is applied later and will override any default values.
To support default values in your API make your Value Type properties nullable so your Service implementation is able to detect them.
[Route("/documents", "GET POST")]
public class GetDocuments : IReturn<List<Document>>
{
public Guid? ViewID { get; set; }
public int? PageSize { get; set; }
public int? PageNumber { get; set; }
public bool? AllVersions { get; set; }
public string SearchCriteria { get; set; }
}
Then your Service implementation can detect when a value wasn’t provided and that it should use the default value instead, e.g:
public class DocumentsService : Service
{
public object Get(GetDocuments request)
{
var pageSize = request.PageSize ?? Constants.DefaultPageSize;
var pageNumber = request.PageNumber ?? Constants.DefaultPageNumber;
var allVersions = request.AllVersions ?? true;
}
}
You also shouldn’t return non standard collection types in your IReturn<T> marker which as your external API contract should specify the serialization type of the response Wire Format. For ICollection<T> types it’s recommended to instead use IReturn<List<Document>> or IReturn<Document[]>.
The defaults are deserialized perfectly to an object if the values are not specified in the JSON, but not when the object is serialized to a JSON string.
We had the defaults in the DTO so it is clear to the user of the DTO what the defaults are. If we remove it in the DTO, then we need to specify it somehow in the description, because it is programmed in the service handler.
We are using the ObservableCollection for approx. 9 years without any issues, but we will try to move that back to a List type.
It’s recommended to document APIs with [ApiMember], e.g:
[Route("/documents", "GET POST")]
public class GetDocuments : IReturn<List<Document>>
{
public Guid? ViewID { get; set; }
[ApiMember(Description = "The number of documents to return (Default 100)")]
public int? PageSize { get; set; }
//..
}
If the APIs are just being used internally and you’re not using Add ServiceStack Types to code generate for other languages it should be ok.