ServiceStack Clients and Ambiguous Routes

I have a service stack service, which we will call Orders , which has standard GET routes

  • /orders - Get all customers
  • /orders/{Ids} - Get specific customers

This works fine and dandy, but I thought I would add another route

  • /orders/customers/{CustomerId} - Fulfills orders with a specific customer ID

This works when you click the routes in the browser, but when I use the ServiceStack Client, I get the ambiguous routes exception and lists three routes.

I'm not quite sure if this is the best way ... is this what I am doing in the wrong RESTish way?

I know that I can just manually enter routes in JsonServiceClient as

client.Get<List<Orders>>("/orders/customers/7")

and this will work, but I would rather do it in a typed way ... i.e.

client.Get(new OrdersRequest { CustomerId = 7 });

Here is an example RequestDTO that I am using

 public class OrdersRequest : IReturn<List<Orders>> { public int[] Ids {get; set;} public CustomerId {get; set;} public OrdersRequest(params int[] ids) { this.Ids = ids; } } 

Should I use different Dtos for this or ...?

any help or pointers to any of the samples that are relevant to this, or the best way to create services, will be appreciated.

thanks

+7
rest web-services servicestack routes
source share
1 answer

I can advise that you are not using REST correctly. There is a good answer on how to best structure your ServiceStack REST service . This is a common startup problem, I also had such problems.

Understanding your use case:

In your specific case, if we look at /orders/customers/7 , this will work better, you think of it as follows:

/ customers / 7 / Orders

The reason you do this:

  • It fixes route ambiguity problems.
  • The context is clear. We see that we are working with Customer 7 without navigating a long URL
  • We can see "with customer 7", we want their orders

Think of routing as follows:

 /orders Everybody Orders /orders/1 Order 1 (without being aware of the customer it belongs to) /customers All Customers /customers/7 Customer 7 details /customers/7/orders All Customer 7 orders /customers/7/orders/3 Customer 7 order number 3 

The beauty of doing such things is data operations that are performed sequentially. So you want to find all canceled orders:

 /orders/cancelled 

You want to cancel its specific orderId order

 /orders/4/cancel 

Do you want to specify specific customer orders

 /customers/6/orders/open 

You want to display sales order 6 canceled orders

 /customers/6/orders/cancelled 

You want to cancel the order for customer 6 you are viewing

 /customers/6/orders/2/cancel 

Obviously these are just scenarios, your routes will be different.

Simplification of action handlers:

You probably want to define your action handlers so that they can handle multiple routes. I mean that one handler will be responsible for listing orders

 /orders /customers/6/orders 

What am I doing:

 [Route("/orders","GET")] [Route("/customers/{CustomerId}/orders","GET")] public class ListOrdersRequest : IReturn<List<Order>> { public int? CustomerId { get; set; } } 

So, you can see how the two routes of the order list go. If they use /orders , then our customerId will not matter, and there will be another route. Therefore, in our action handler, we can simply verify 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; } 

Customer side questions:

So, to address your "typed" customer needs. Creating routes and action handlers using the above method will allow you to:

 client.Get(new ListOrdersRequest { CustomerId = 7 }); // To get customer 7 orders client.Get(new ListOrdersRequest()); // All orders 

Thus, you will get 1 clear method for listing orders.

Final thoughts:

Obviously, this means that you have to rework what you have, which is likely to be a pain, but this is the best approach, well worth the effort.

Hope this helps you understand a better structure.


Update:

@ stefan2410 asked me to address the issue of using Kyle int[] in the route for a GET request:

If you want to use int[] in a GET request, you need to consider changing it during transport in the URL so that it can be used RESTfully.

So you can 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 this is to use server-side identifiers, you need to convert the string "1,2,3" back to int[] .

The best way to handle 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 a route /orders and matches the route /orders .

+6
source share

All Articles