Routing using data transfer object attributes
ServiceStack routes incoming HTTP requests to services by a number of mechanisms. Perhaps the most convenient is the Route
attribute— simply annotate the class to indicate where ServiceStack should expect requests.
Getting ready
You'll need a request object. For instance, Reidson-Industries is interested in developing a group messaging application for use on the Web and on mobile devices. We could model an incoming request like this:
public class Message { public string Body { get; set; } public string Sender { get; set; } public string GroupName { get; set; } }
We might handle requests for this Message
object with a MessengerService
class, as follows:
public class MessengerService : Service { static List<Message> _messages = new List<Message>(); public MessageResponse Post(Message request) { _messages.Add(request); return new MessageResponse { Response = "OK" }; } }
In previous examples, our service methods have simply returned the object, with the service passing a string back. To add a bit more structure to our contract, a response might be modeled like this:
public class MessageResponse { public string Response { get; set; } }
How to do It...
To tell ServiceStack where to expect requests that contain Message
objects, simply annotate the object with a Route
annotation, as follows:
[Route("/message")] public class Message { public string Body { get; set; } public string Sender { get; set; } public string GroupName { get; set; } }
How it works...
Now ServiceStack will accept POST
requests containing either JSON data or form data at http://yourserver/message
containing strings named Body
, Sender
, and GroupName
. These requests will be deserialized in to an instance of the Message
DTO. You can provide more information in the annotation as well— for instance, if you wanted to be able to provide an alternate URL format, you could do the following:
[Route("/message")] [Route("/message/{GroupName}")] public class Message { public string Body { get; set; } public string Sender { get; set; } public string GroupName { get; set; } }
Now, if ServiceStack sees a request for http://yourserver/message/BestFriends
, it will assume that Message
is destined for the BestFriends
group. You can add multiple routes for the same request object by placing multiple Route
annotations on the same object.
We'll need to retrieve messages too. We can do that by querying the /group
endpoint. To do this, simply create a request
object, as follows:
[Route("/group/{GroupName}")] public class Group { public string GroupName { get; set; } }
We'll expand MessengerService
to be able to handle this request:
public class MessengerService : Service { static List<Message> _messages = new List<Message>(); public MessageResponse Post(Message request) { _messages.Add(request); return new MessageResponse { Response = "OK" }; } public GroupResponse Get(Group request) { return new GroupResponse { Messages = _messages.Where(message => message.GroupName.Equals(request.GroupName)) .ToList() }; } }
The GroupResponse
class is a simple DTO to model the expected response. In this example, the GroupResponse
class has a single property Messages
and simple List<Message>
containing the messages that the user has requested:
public class GroupResponse { public List<Message> Messages { get; set; } }
Note
Note that the code in the previous example will work to get us started building a simple application, but storing all of our incoming messages in static List<Message>
with no backing store isn't likely to work out well in practice. We'll refactor this into something more production-ready in a later recipe.
Once you start the app, you can send a form post to the BestFriends
group with curl
. A command-line HTTP utility, curl makes it easy to craft full-featured HTTP requests. It can specify headers and HTTP methods, send form data, and return any results on the command line.
We want to send an HTTP POST
request with enough form data to provide a message to our service, and we'd like to see the response in JSON. We'll use curl
with -H
to specify the header, -X
, to specify the HTTP method, and --data
to specify the data to send. Put together, it looks like this:
curl -H "Accept: application/json" -X POST --data \ "Body=first post&Sender=Kyle&GroupName=BestFriends" \ http://myserver/message
Then, you can read the messages that have been sent to the BestFriends
group, as follows:
curl -H "Accept: application/json" \ http://myserver/group/BestFriends
The result would be the JSON response, as follows:
{"Messages": [{"Body":"first post", "Sender":"Kyle","GroupName":"BestFriends"}]}
There's more...
Let's imagine that we wanted people to be able to search for a specific search term or possibly search for a term within a specified group. We could use the Route
annotation to do this easily with one single request type. Implement it like this:
[Route("/message/search")] [Route("/message/search/{Group}")] public class Search { public string Group { get; set; } public string Query { get; set; } }
We'll easily expand our MessengerService
class to be able to handle this request by adding a new method, as follows:
public GroupResponse Get(Search request) { return new GroupResponse { Messages = _messages.Where( message => message.GroupName.Equals(request.Group) && message.Body.Contains(request.Query)) .ToList() }; }
We could make use of this new endpoint with another curl
command. First, we'll post a few messages so that we have something to search, as follows:
curl -H "Accept: application/json" -X POST \ --data "Body=first post&Sender=Kyle" \ http://myserver/message/BestFriends {"Response":"OK"} curl -H "Accept: application/json" -X POST \ --data "Body=second post&Sender=Kyle" \ http://myserver/message/BestFriends {"Response":"OK"}
Then, we can easily search by sending a simple GET
call:
curl -H "Accept: application/json" \ http://myserver/message/search/BestFriends?query=second {"Messages": [ {"Body":"second post", "Sender":"Kyle", "GroupName":"BestFriends"} ] }