I am relatively new to REST and WebAPI by Microsoft. We are implementing a hub REST service that will house several types of object gets and sets. Being the lead on the project, I am being tasked with coming up with the proper Uri design we are going with. I was wondering what thoughts were on war is better. Yes I specifically phased that without using the word "standard".
Here are the options my team and I are currently entertaining:
Http://servername/API/REST/Ldap/AD/employees?username=jsmith
Http://servername/API/REST/Ldap/AD/employee/UserName?searchTerm=jsmith (this seems RPC to me)
Http://servername/API/REST/Ldap/AD/employees/getusername?searchterm?jsmith
We are also creating a Soap version hence the rest in the Uri.
Thanks for the input
I recommend you take a look at Web API Design from Brian Mulloy. He has several recommendations when it comes to searching and filtering.
Simplify associations - sweep complexity under the ‘?’
Most APIs have intricacies beyond the base level of a resource. Complexities can include many states that can be updated, changed, queried, as well as the attributes associated with a resource. Make it simple for developers to use the base URL by putting optional states and attributes behind the HTTP question mark. Keep your API intuitive by simplifying the associations between resources, and sweeping parameters and other complexities under the rug of the HTTP question mark.
Tips for search
While a simple search could be modeled as a resourceful API (for example, dogs/?q=red), a more complex search across multiple resources requires a different design. If you want to do a global search across resources, we suggest you follow the Google model:
Global search
/search?q=fluffy+fur
Here, search is the verb; ?q represents the query.
Scoped search
To add scope to your search, you can prepend with the scope of the search. For example, search in dogs owned by resource ID 5678
/owners/5678/dogs?q=fluffy+fur
Notice that the explicit search has been dropped from the URL and instead it relies on the parameter 'q' to indicate the scoped query.
Pagination and partial response
Support partial response by adding optional fields in a comma delimited list.
/dogs?fields=name,color,location
Use limit and offset to make it easy for developers to paginate objects.
/dogs?limit=25&offset=50
To Oppositional's comment, this is what I put together some time ago.
https://groups.google.com/d/msg/servicestack/uoMzASmvxho/CtqpZdju7NcJ
public class QueryBase
{
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 QueryBase;
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 : QueryBase, IReturn<PagedResult<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 PagedResult<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 PagedResult<User>
{
TotalItems = totalItems,
TotalPages = totalPages,
ItemCount = itemCount,
Items = users,
CurrentPage = currentPage
};
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With