Implementing paging, partial responses, ordering, etc. across resources

3,142 views
Skip to first unread message

Mark Swiatek

unread,
Jun 7, 2012, 11:47:55 AM6/7/12
to servic...@googlegroups.com
I need to implement a set of operations across all GETs of resources. For example,
  • partial response - only return fields of a resource specified in a query parameter
  • search - filter the scoped resource by keywords
  • pagination - return a "page" - offset=x&limit=y - of a resource set
More than likely, some of these operations (I'm thinking of partial response in particular), could be implemented during serialization, without the service having any knowledge of it. Others, such as pagination, will more than likely need to be visible to the service for performance reasons.

I'm not sure where to best integrate this into ServiceStack. Any suggestions?

Demis Bellot

unread,
Jun 7, 2012, 12:37:20 PM6/7/12
to servic...@googlegroups.com
You could have an interface with the common properties on DTOs so you can treat these options generically.

Otherwise if they don't need to be on DTO's, you can just extract them from the QueryString in your filters or your service with:

var httpReq = base.RequestContext.Get<IHttpRequest>();
var page = httpReq.QueryString["page"]; //etc

There are a few hooks where you can run Generic logic: 

 - In a sub class by overriding the OnBeforeExecute/OnAfterExecute methods.
 - In a Request or Response Filter, i.e. you could mark the services where this logic should apply using a custom RequestFilter/ResponseFilter attribute, see:
 - Or just by using global Request/Response Filters:   

For completeness, you can also provide custom deserialization using Custom Request Binders or IRequiresRequestStream: 

Cheers,

Mark Swiatek

unread,
Jun 8, 2012, 7:22:26 AM6/8/12
to servic...@googlegroups.com
Thanks as usual for the prompt reply, helpful insights - and a great framework.

Per Stenmar

unread,
Nov 2, 2012, 1:38:38 PM11/2/12
to servic...@googlegroups.com
Hi Mark,

I'm about to build an API and look for the same implementation options. Have you come up with some good code/patterns you can share?

Best regards 
Per

Michael West

unread,
Jan 18, 2013, 2:39:41 PM1/18/13
to servic...@googlegroups.com
Demis,
Based on your recommendations, I implemented the following (rough implementation):

public class BaseRequest<T> where T : class
{
public string Query { get; set; }
public int Limit { get; set; }
public int Offset { get; set; }
}

[Route("/v1/users")]
public class User : IReturn<List<User>>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}

public class RequestFilterAttribute : Attribute, IHasRequestFilter
{
#region IHasRequestFilter Members

public IHasRequestFilter Copy()
{
return this;
}

public int Priority
{
get { return -100; }
}

public void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
var query = req.QueryString["q"] ?? req.QueryString["query"];
var limit = req.QueryString["limit"];
var offset = req.QueryString["offset"];

var user = requestDto as BaseRequest<User>;
if (user == null) { return; }
user.Query = query;
user.Limit = limit.IsEmpty() ? int.MaxValue : int.Parse(limit);
user.Offset = offset.IsEmpty() ? 0 : int.Parse(offset);
}

#endregion
}

[Route("/v1/users/search", "GET")]
[RequestFilter]
public class SearchUser : BaseRequest<User>, IReturn<BaseResponse<User>> { }

public class UsersService : Service
{
public static List<User> UserRepository = new List<User>
{
new User{ Id="1", FirstName = "Michael", LastName = "A", Email = "michaelEmail" },
new User{ Id="2", FirstName = "Robert", LastName = "B", Email = "RobertEmail" },
new User{ Id="3", FirstName = "Khris", LastName = "C", Email = "KhrisEmail" },
new User{ Id="4", FirstName = "John", LastName = "D", Email = "JohnEmail" },
new User{ Id="4", FirstName = "Lisa", LastName = "E", Email = "LisaEmail" }
};

public BaseResponse<User> Get(SearchUser request)
{
var query = request.Query;
var users = request.Query.IsNullOrEmpty()
           ? UserRepository.ToList()
: UserRepository.Where(x => x.FirstName.Contains(query) || x.LastName.Contains(query) || x.Email.Contains(query)).ToList();

var totalItems = users.Count;
var totalPages = (int)Math.Ceiling((decimal)totalItems / (decimal)request.Limit);
var currentPage = request.Offset;
users = users.Skip(request.Offset * request.Limit).Take(request.Limit).ToList();
var itemCount = users.Count;

return new BaseResponse<User>
{
TotalItems = totalItems,
TotalPages = totalPages,
ItemCount = itemCount,
Items = users,
CurrentPage = currentPage
};
}
}


Then you can make a request to the url:

I hope this helps someone.

Demis Bellot

unread,
Jan 19, 2013, 1:41:22 AM1/19/13
to servic...@googlegroups.com
Hey Michael,

Hey this is really cool, any chance you can put this in a blog post somewhere? 
As things like this tends to get lost here.

If you don't have a blog you can also ask and answer your own question in StackOverflow, otherwise we could just put this in a wiki page on https://github.com/ServiceStack/ServiceStack/wiki

Some of the things I would change would be to rename BaseRequest<T> to QueryBase, it can be non-generic since you're not using the generic argument. And instead of BaseResponse<T> I would rename it to PagedResult<T> since it's not a base class and more specific to what it does. 

Michael West

unread,
Aug 2, 2013, 11:48:18 AM8/2/13
to servic...@googlegroups.com
What's funny is that I was looking for this solution again and realized I made the post :) 

Thank you again for the feedback. Working on a project with Sitecore so if I get that going then I'll try the StackOverflow post you mentioned.

Drew Hawken

unread,
Aug 14, 2013, 11:38:35 PM8/14/13
to servic...@googlegroups.com
Hi Michael,

Looking at pushing pagination and search down from our SS API to an MVC front end using jquery Datatables as the grid. 
This will be very helpful!
Did you make any further progress with this?  Looking at your stuff and some things in Ivan Fioravanti's blog: http://ivanfioravanti.wordpress.com/2013/03/24/servicestack-a-basic-paging-and-sorting-implementation/



Regards,

Drew
Message has been deleted

Drew Hawken

unread,
Aug 15, 2013, 5:24:53 AM8/15/13
to servic...@googlegroups.com
This is what I ended up with ...

public class RequestFilterAttribute : Attribute, IHasRequestFilter
    {
        #region IHasRequestFilter Members

        public IHasRequestFilter Copy()
        {
            return this;
        }

        public int Priority
        {
            get { return -100; }
        }

        public void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
        {
            var request = requestDto as QueryBase;
            if (request == null)
            {
                return;
            }

            request.Sort = req.QueryString["sort"].IsNullOrEmpty() ? "Id" : req.QueryString["sort"];
            request.Limit = req.QueryString["limit"].IsEmpty() ? int.MaxValue : int.Parse(req.QueryString["limit"]);
            request.Offset = req.QueryString["offset"].IsEmpty() ? 0 : int.Parse(req.QueryString["offset"]);
        }

        #endregion
    }

    public class QueryBase
    {
        public string Query { get; set; }
        public string Sort { get; set; }
        public int Limit { get; set; }
        public int Offset { get; set; }
    }

    public class PagedResult<T> where T : class
    {
        public int TotalItems { get; set; }
        public int TotalPages { get; set; }
        public List<T> Items { get; set; }
        public int CurrentPage { get; set; }

        public PagedResult(int limit, int offset, IReadOnlyCollection<T> items)
        {
            TotalItems = items.Count;
            TotalPages = (int) Math.Ceiling(TotalItems/(decimal) limit);
            Items = items.Skip(offset*limit).Take(limit).ToList();
            CurrentPage = offset;
Reply all
Reply to author
Forward
0 new messages