Projecting DTO fields into Route?

We are wanting to add: result limits, pagination, sorting and filtering to certain API’s in our services. And looking for a design pattern that can work for each API that wants to support these options.

Lets say I have a request DTO like this:

public class ListBananas
{
    public SearchOptions Options { get; set; }
}

where SearchOptions is a DTO like this:

    public class SearchOptions
    {
        public int Limit { get; set; }

        public int Offset { get; set; }

        public Sorting Sort { get; set; }

        public Filtering Filter { get; set; }
    }

    public class Sorting
    {
        public string By { get; set; }

        public SortDirection Direction { get; set; }
    }

    public enum SortDirection
    {
        Ascending = 0,
        Descending = 1
    }

    public class Filtering
    {
        public List<string> Fields { get; set; }
    }

As you can see, we have decided to make SearchOptions a hierarchical POCO, instead of adding a bunch of top level properties to our request DTO. Just a design decision.

We did that because we (a) wanted to keep this data together to pass through to our other layers within the service and i.e. a data store layer, (b) because we wanted to avoid inheritance in our DTO’s.

Now arguably however, we could define a IHasSearchOptions interface and have ‘ListBananas’ inherit from that, that puts the properties directly in the request DTO, and have some component derive the hierarchical POCO for us to pass through the layers of our service.

public interface IHasSearchOptions
{
    public int Limit { get; set;}

    public int Offset { get; set;}

    public string Sort { get; set;}

    public string Filters { get; set;}
}

Now, it would be very nice to have fields (i.e. limit, offset, sort, filter) directly in the QueryString of the API call for convenience of the caller, but primarily for caching the responses as they will vary.

For example: GET /bananas?limit=50&sort=peeled

If we didn’t do IHasSearchOptions approach, and stuck with SearchOptions in the request DTO, is there a clever way, perhaps with RequestFilters or another mechanism, that for request DTO’s that have ‘SearchOptions’ we can effectively ‘extract’ fields like: limit, offset, sort and filter form the request QueryString? and internally construct the SearchOptions and pass it to the Service Operation in a generic way?

With AutoQuery DTO’s we’ve gone for a IQuery interfaces and a QueryBase base class primarily so we can keep a minimal dev experience to create where AutoQuery Services can be created with a single body-less Request DTO. For other Services I’d normally go for a common interface and implemented properties as I view Request DTO properties as an explicit Service Contract definition instead of something to DRY and hide.

If you do go for a base class make it abstract so ServiceStack metadata services know it’s used in inheritance. You could go for a property approach, e.g:

public class ListBananas : IHasSearchOptions
{
    public SearchOptions Options { get; set; }
}

Which would mean you would need to call it with either a complex type url, e.g:

/bananas?options={limit:100,offset:200,Sort:Ascending}

Or you could register a Typed Request Filter to generically handle all IHasSearchOptions and pluck the values from the queryString an populate the Request DTO, e.g:

RegisterTypedRequestFilter<IHasSearchOptions>((req, res, dto) =>
{
    dto.Options = new SearchOptions {
        Limit = int.Parse(req.GetParam("limit")),
        Offset = int.Parse(req.GetParam("offset")),
        Sort = (req.GetParam("sort") ?? "").StartsWith("desc") 
            ? SortDirection.Descending 
            : SortDirection.Ascending,
        Filters = req.GetParam("filters").split(',').ToList(),
    };
});

Also since these fields are similar to IQuery you may want to consider reusing the IQuery or QueryBase conventions which seem to already enable the same API.

Thanks Mythz,

Thinking design goals, you are so right about contract definition over DRY. Good reminder!
Which then leads me back to the IHasSearchOptions approach.

In considering AutoQuery to avoid reinventing the wheel, we really want the properties to be named: limit, offset, sort and filter. So not sure how to do both and incorporate AutoQuery there.
Not even sure we can use AutoQuery, as I think it assumes an Ormlite backend and some other pieces, which we don’t have and are not so familiar with. I could be wrong about that, but I have to say, the docs about what AutoQuery IS at a high level seem to make a bunch of assumptions that are not stated, about how it would/could fit in one’s existing architecture. So it looks like it does not for us - which is a shame.

The AutoQuery has its own querying conventions where the base properties have fixed names and behavior so in that case you may not want to reuse the existing types. I was only suggesting considering using the AutoQuery Types and conventions (not AQ itself), but if you prefer your own naming conventions then you’ll wont want to reuse the existing types.

BTW we’ve got 2 implementations of AutoQuery, the newer AutoQuery Data is an open data provider model which allows implementing AutoQuery functionality over a different data sources. We’ve got the following data sources available:

1 Like

OK, thanks. Good tip, I’ll take another look at AutoQuery then.