Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ServiceStack Routing does not work with querystring

I have a simple REST service built with ServiceStack.

If I configure the routes like this:

        //register user-defined REST-ful urls
        Routes
            .Add<Contact>("/Contacts")
            .Add<Contact>("/Contacts/{ContactId}")

This request succeeds.

http://<server>:59557/Contacts?ContactId=9999 //Works

If I configure the routes like this (Business Analyst prefers the generated metadata)

        //register user-defined REST-ful urls
        Routes
            .Add<UpdateContact>("/UpdateContact", "PUT")
            .Add<CreateContact>("/CreateContact", "POST")
            .Add<GetContact>("/Contacts/{ContactId}", "GET")

http://<server>:59557/Contacts/9999           //Works
http://<server>:59557/Contacts?ContactId=9999 //Fails, Handler for request not found

How can I configure the routes in the second sample so that a request to /Contacts?ContactId=9999 will succeed?

Thanks.

like image 711
Alper Avatar asked Aug 28 '12 17:08

Alper


2 Answers

Some of the Routing in ServiceStack is explained on the Your first website explained wiki page:

[Route("/hello/{Name}")]

only matches:

/hello/name

where as:

[Route("/hello")]

matches:

/hello?Name=XXX

Note: The QueryString, FormData and HTTP Request Body isn't a part of the Route (i.e. only the /path/info is) but they can all be used in addition to every web service call to further populate the Request DTO.

and using a route with a wild card path like:

[Route("/hello/{Name*}")]

matches:

/hello
/hello/name
/hello/my/name/is/ServiceStack

Another good use-case for when to use wildcard routes.

So to match /Customers?Key=Value and /Customers/{Id} you need to register matching routes for both these routes, e.g:

Routes
    .Add<GetContact>("/Contacts", "GET")
    .Add<GetContact>("/Contacts/{ContactId}", "GET")

How to Auto Register Convention-based Routes for all services

Also related to this is registering Auto routes via the AddFromAssembly extension method, where this single call:

Routes.AddFromAssembly(typeof(MyService).Assembly)

Goes through and scands all your services (in the Assemblies specified) and registers convention-based routes based on all the HTTP methods you have implemented. E.g. if your GetContact and UpdateContact services had Id properties it would automatically register the following routes:

Routes
    .Add<GetContact>("/Contacts", "GET")
    .Add<GetContact>("/Contacts/{Id}", "GET")
    .Add<UpdateContact>("/Contacts", "POST PUT")
    .Add<UpdateContact>("/Contacts/{Id}", "POST PUT")

And if you just had a single Contacts REST Service with implementations for all the HTTP Verbs it would register these routes:

Routes
    .Add<Contacts>("/Contacts", "GET POST PUT DELETE PATCH")
    .Add<Contacts>("/Contacts/{Id}", "GET POST PUT DELETE PATCH")

Routing Resolution Order

This is described in more detail on the New API Design wiki but the weighting used to select a route is based on:

  1. Any exact Literal Matches are used first
  2. Exact Verb match is preferred over All Verbs
  3. The more variables in your route the less weighting it has
  4. When Routes have the same weight, the order is determined by the position of the Action in the service or Order of Registration (FIFO)

See the Smart Routing section on the wiki for examples.

Great Performance

Since Routing in MVC can be slow when you have a large number of Routes, I think it's worthwhile pointing out ServiceStack's Routing implementation is implemented with hash lookups and so doesn't suffer the linear performance regression issues you might have had with MVC.

like image 140
mythz Avatar answered Oct 11 '22 05:10

mythz


Following the answer in the other StackOverflow post you could try this route:

/Contacts/{Ids*}

and use this request class:

public class ContactRequest { public string Ids { get; set; } }

Then given the URL <service>/Contacts?Id=1&Id=2&Id=3 ServiceStack will populate the Ids property of the request object with the following string:

Id=1&Id=2&Id=3

that you can then parse into a list of ID's, e.g.

public object Get(ContactRequest request)
{
    var idPairs = request.Ids.Split("&");
    var ids = new List<string>();
    foreach (var idPair in idPairs)
    {
        ids.Add(idPair.Split("=")[1];
    }
    // ... do something with your ids now ...
    // ... return results ...
}

Of course you might also want to wrap the whole ID parsing logic in a try...catch block to handle malformed query strings.

like image 28
Caspian Canuck Avatar answered Oct 11 '22 06:10

Caspian Canuck