3

I have been experimenting with the ServiceStack framework to develop a simple REST API. I am having troubles trying to define the Routes properly to handle the different URLs I desire.

I have a simple DomainModel class:

public class Student
{
    public int Id { get; set; } // unique
    public string Email { get; set; } // not unique

    // some other properties and business logic
}

I would like to create a REST service which responds to HTTP requests to the following URLs:

  • /students
    • GET returns all students
  • /students/123
    • GET returns student with Id = 123
  • /students?id=123
    • GET returns student with Id = 123
  • /students?email=foo@example.com
    • GET returns all students with matching Email

I have tried using the following Request DTOs to accomplish this:

[Route("/students", "GET")]
public class GetAllStudents : IReturn<StudentList>
{
}

[Route("/students", "GET")] // I want this to be for /students?Id=
[Route("/students/{Id}", "GET")]
public class GetStudentById : IReturn<Student>
{
    public int Id { get; set; }
}

[Route("/students", "GET")] // I want this to be for /students?Email=
public class GetStudentsByEmail : IReturn<StudentList>
{
    public string Email { get; set; }
}

As you can see, I am only trying to use two different Response DTOs:

public class StudentList : List<Student>
{
}

public class Student
{
    public int Id { get; set; }
    public String Email { get; set; }
}

Note: This Reponse DTO Student class is separate than my DomainModel class; they are in different namespaces.

Unfortunately, when I try to use these routes, every request to /students?<anything> ends up returning all students. The only Route that does work is if I make a GET request to /students/123, then I get back Student with Id 123. I assume this isn't working because I am re-using the "/students" Route definition for multiple Request DTOs.

I have been reading through the ServiceStack wiki and examples but I can not figure out how to accomplish what I am attempting. The most detailed documentation I can find about advanced routing is the "Smart Routing" section of the "New API" page but it doesn't really address this situation either.

How can I re-use the same Route definition for mulitple Request DTOs?

Jesse Webb
  • 43,135
  • 27
  • 106
  • 143
  • I'm not sure you can do that the way you want. Might I suggest the route `/student/{Id}` as an alternative? – rossipedia May 10 '13 at 19:58
  • @rossipedia Even if I get rid of the `/students?id=123` route, I still have the problem of duplicate route definitions on `GetAllStudents` and `GetStudentsByEmail`. Honestly, I don't need that `?id=` route but getting rid of it won't get me any further. Maybe I am misunderstanding your suggestion? Can you provide more detail in an answer? – Jesse Webb May 10 '13 at 20:04
  • See @mythz's answer below. He explains it much better than I can :) – rossipedia May 10 '13 at 20:11

1 Answers1

5

Having ByXXX suffixes on your Request DTOs is a code-smell in ServiceStack who wants to encourage the design of message-based coarse-grained interfaces. Please see this earlier answer on How to design a Message-Based API with ServiceStack and why you should group services by response types and call semantics

In this case you have a service doing a Get on a Unique Id returning a single Student and you also wish to have a Search on Email which returns multiple Students. So in this case I would have the following services:

[Route("/students/{Id}")]
public GetStudent : IReturn<Student>
{
    public int Id { get; set; }
}

Which would handle just /students/123.

[Route("/students")]
public FindStudents : IReturn<List<Student>>
{
    public int? Id { get; set; }
    public string Email { get; set; }
}

Which can be used to handle:

  • /students
  • /students?id=123 //but returns List<Student> instead
  • /students?email=foo@example.com
  • /students?id=123&email=foo@example.com
  • etc

Although I personally wouldn't include an Id here since it's a Get instead of a Filter.

Community
  • 1
  • 1
mythz
  • 141,670
  • 29
  • 246
  • 390
  • I was doing this approach at first, based on your answer in that link. I didn't like how I needed a bunch of `if` logic in my Service method to determine what the user was searching with. For example, if I also wanted to add `Birthday` to my `FindStudents` RequestDTO, then my Service method needs to do this logic: *"is Id set? is Email set? is Birthday set? what if Birthday doesn't match Id? etc."* I was trying to keep smaller, more focused service methods like `Get(StudentsByEmail request)` but I may be trying to 'fight the framework' here, which I don't want to do. I will experiment more... – Jesse Webb May 10 '13 at 20:22
  • 1
    @JesseWebb Note the if logic should be what you're using to build-up an internal query or filter so the same service can be called with any combination of filters. – mythz May 10 '13 at 20:29