Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net WebAPI URI convention for advanced searching /filtering

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

like image 703
gcoleman0828 Avatar asked Nov 29 '22 16:11

gcoleman0828


2 Answers

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

like image 82
Oppositional Avatar answered Dec 06 '22 05:12

Oppositional


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
        };
}
}
like image 28
Coding101 Avatar answered Dec 06 '22 04:12

Coding101