I have a service stack service we'll call Orders
that has the standard GET routes
/orders
- Gets All Customers/orders/{Ids}
- Gets Specific customersThis works all fine and dandy, but I thought I'd add another route
/orders/customers/{CustomerId}
-Gets orders with a specific customer idThis works find when hitting the routes in the browser, but when I use the ServiceStack Client I get ambiguous routes exception, and it lists the three routes.
I'm not quite sure what the best way around this is..is what I'm doing not the correct RESTish way to it?
I know I can simply manual enter the routes into the JsonServiceClient
like
client.Get<List<Orders>>("/orders/customers/7")
and that will work, but I would prefer to do it the typed way...i.e
client.Get(new OrdersRequest { CustomerId = 7 });
Here's an example RequestDTO i'm using
public class OrdersRequest : IReturn<List<Orders>>
{
public int[] Ids {get; set;}
public CustomerId {get; set;}
public OrdersRequest(params int[] ids)
{
this.Ids = ids;
}
}
Do I have to use different Dtos for this or...?
any help or pointers to any of the samples that have a way around this, or a better way to create the services would be appreciated.
Thanks
My advise is you're not using REST properly. There is a good answer about how to best structure a ServiceStack REST service. It's a common issue when starting out, I had issues like this too.
In your specific case if we look at /orders/customers/7
this would work better is you think of it this way:
/customers/7/orders
The reason you do it this way:
Think of routing like this:
/orders Everybody's Orders
/orders/1 Order 1 (without being aware of the customer it belongs to)
/customers All Customers
/customers/7 Customer 7's details
/customers/7/orders All Customer 7's orders
/customers/7/orders/3 Customer 7's order number 3
The beauty of doing things like this is operations on data are done consistently. So you want to find all cancelled orders:
/orders/cancelled
You want to cancel a specific order by it's orderId
/orders/4/cancel
You want to list a specific customer's open orders
/customers/6/orders/open
You want to list customer 6's cancelled orders
/customers/6/orders/cancelled
You want to cancel an order for customer 6 that you are viewing
/customers/6/orders/2/cancel
Obviously these are just scenarios, your routes will differ.
You will probably want to define your action handlers so they can cope with coming from multiple routes. What I mean is, one action handler would be responsible for Listing Orders
/orders
/customers/6/orders
What I do is this:
[Route("/orders","GET")]
[Route("/customers/{CustomerId}/orders","GET")]
public class ListOrdersRequest : IReturn<List<Order>>
{
public int? CustomerId { get; set; }
}
So you can see the two order listing routes come in. If they use /orders
then our customerId
won't have a value but the other route will. So in our action handler we can simply test for this:
public List<Order> Get(ListOrdersRequest request)
{
// Use `CustomerId` to narrow down your search scope
if(request.CustomerId.HasValue){
// Find customer specific orders
} else {
// Find all orders
}
return orders;
}
So to address your using 'typed' client concerns. Creating the routes and action handlers using the above method will allow you to do:
client.Get(new ListOrdersRequest { CustomerId = 7 }); // To get customer 7's orders
client.Get(new ListOrdersRequest()); // All orders
Thus giving you 1 clear method for Listing Orders.
Obviously it means you will have to rework what you have which will probably be a pain, but it's the best approach, well worth the effort.
I hope this helps you understand the best structure.
@stefan2410 requested that I address the issue of Kyle's use of an int[]
in a route for a GET request:
If you want to use an int[]
in a GET request you have to consider changing it during transport in the URL so it can be used RESTfully.
So you could do this:
class OrdersRequest
{
public string Ids { get; set; }
public OrdersRequest(int[] ids)
{
Ids = string.Join(",", Array.ConvertAll(ints, item => item.ToString()));
}
}
client.Get(new OrdersRequest(new [] {1,2,3}));
Creates the route /orders/1,2,3
and matches /orders/{Ids}
. The problem with doing this is in order to use the Ids at the server side you have to convert the string "1,2,3"
back to an int[]
.
The better way to deal with an int[]
in a request is to use POST. So you can do:
class OrdersRequest
{
public int[] Ids { get; set; }
public OrdersRequest(int[] ids)
{
Ids = ids;
}
}
client.Post(new OrdersRequest(new[] {1,2,3}));
Creates the route /orders
and matches the route /orders
.
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