REST Routing in ServiceStack - servicestack

I just start to learn REST and ServiceStack and there's something about Route that I just can't quite understand. For example if we take the very basic HelloWorld example from GitHub tutorial and re-write it to return collection of User objects. Here is example:
public User
{
public string Name;
public string Address;
public int Age;
}
// Hello - request object without [Route] attribute
public class Hello
{
public string Name { get; set; }
}
public class HelloResponse
{
public IEnumerable<User> Result {get;set;}
}
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { // Collection of User object };
}
}
now everything working right and no problems here. But now I want to add another routing url like: /Hello/{name}/Address
Actually this call (GET) to this url will return a single User selected by Age parameter. How I can do this ? Should I add another Service ? And if the url will be:
/Hello/{name}/{age}/Address
It seems I don't understand something.....

See this earlier answer for details about Routing in ServiceStack. The Smart Routing section in ServiceStack's New API explains further options and different precedence.
There are a few problems with your example. First ServiceStack text serializers only support public properties so you need to change your User Model to use public properties instead of fields, e.g:
public User
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
Next, Interfaces on DTOs are a bad idea as there's no good reason for it. They're still supported but you can end up with undesirable results. Use a concrete collection like a List<T> which provides more utility, e.g:
public class HelloResponse
{
public List<User> Results { get; set; }
}
Also the routes should match the property names on your DTO exactly, they are case-insensitive when matching against the Request Path, but they need to map to an exact property name, e.g:
/Hello/{Name}/{Age}/Address

Related

AutoQuery insight needed

So, I'm working with ServiceStack and love what it offers. We've come to a point where I'm needing to implement a queryable data API... prior to my coming to this project, a half backed OData implementation was done. I'd rather not try and weed through that to make it work.
Which brings me to AutoQuery. I'd like to try it with our SQL Server database. I'm looking at the examples at http://docs.servicestack.net/autoquery-rdbms - but I cannot for the life of me get this to work. Is there something I'm missing here?
I'm using ORMLite to query SQL, and my integration tests I've written show it to be working as I would expect. I have registered the OrmLiteConnectionFactory in the container, as well as my repository which uses it by way of dependency injection.
Specific to code so far, I have a type, and a message that is based on QueryDb:
public class Detail
{
public string Div { get; set; }
public string Reg { get; set; }
}
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
The message, DetailQuery, is used by my service:
public class ReportService : Service
{
public object Get(DetailQuery dq)
{
// not sure what to put here?
}
}
With all of that, I am able to see the AutoQuery service instance in the admin interface. When I play with the query interface, I hit my service endpoint, and I see the data I expect - filter values in the 'Div' and 'Reg' collections. What am I missing for this to 'just work' here? I have done plenty in ServiceStack accessing my repositories from the Service itself, but I'm trying to gain some insight into what AutoQuery brings to the table here. I have yet to see a 'straight forward' example of how this works... or am I looking for a pot of gold that just isn't there?
AutoQuery works with just the Request DTO i.e. it doesn't need any Service implementation, so your query:
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
When called from /report/detail will query the Detail RDBMS Table. But your properties here either need to match a column on the Detail table (e.g. Div or Reg) in order to have an exact match (default), however exact matches aren't normally done with arrays they're done with scalar values like a string, e.g:
public string Div { get; set; }
public string Reg { get; set; }
If you're querying a collection you'd be instead making an IN Query where the values would contain list of values, in which case they're normally pluralized:
public string[] Divs { get; set; }
public string[] Regs { get; set; }
and can be called with:
/report/detail?Divs=A,B&Regs=C,D
Which will perform a query similar to:
SELECT * FROM Detail WHERE Div IN ('A','B') AND Rev IN ('C','D')
If that's not the behavior you want it needs to match an implicit convention, e.g:
public string[] DivBetween { get; set; }
Which will then query:
SELECT * FROM Detail WHERE Div BETWEEN 'A' AND 'B'
If you wanted to you could override the AutoQuery service with a custom implementation, e.g:
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
//Override with custom implementation
public object Any(DetailQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
return AutoQuery.Execute(request, q);
}
}
But you'd only need to do that when you want to customize the default behavior, e.g. add an extra filter to the populated SqlExpression.

ServiceStack - Dynamic/Object in DTO

I am running into an issue while looking at SS.
I am writing a custom Stripe implementation and got stuck on web hooks, this in particular:
https://stripe.com/docs/api#event_object
data->object - this can be anything.
Here is my DTO for it:
public class StripeEvent
{
public string id { get; set; }
public StripeEventData data { get; set; }
public string type { get; set; }
}
[DataContract]
public class StripeEventData
{
[DataMember(Name = "object")]
public object _object { get; set; }
}
My hope is to basically just get that object as a string, and then parse it:
var invoice = (StripeInvoice)JsonSerializer.DeserializeFromString<StripeInvoice>(request.data._object.ToString());
Unfortunately the data that is returned from ToString does not have quotes surrounding each json property's name:
Capture
So, the DeserializeFromString returns an object that has everything nulled out.
Why does SS internally strip the quotes out? Is this the proper way to handle a json member that can be one of many different types? I did try the dynamic stuff, but did not have any luck with that either - basically the same result with missing quotes.
I searched very thoroughly for the use of objects and dynamic within DTOs, but there really was nothing that helped with this question.
Thank you!
The issue is that you should never have an object type in DTOs as the serializer has no idea what concrete type to deserialize back into.
The Stripe documentation says object is a hash which you should be able to use a Dictionary to capture, e.g:
public class StripeEventData
{
public Dictionary<string,string> #object { get; set; }
}
Or as an alternative you could use JsonObject which provides a flexible API to access dynamic data.
This will work for flat object structures, but for complex nested object structures you'll need to create Custom Typed DTOs, e.g:
public class StripeEventInvoice
{
public string id { get; set; }
public StripeEventDataInvoice data { get; set; }
public string type { get; set; }
}
public class StripeEventData
{
public StripeInvoice #object { get; set; }
}

ServiceStack: Pass an array to a Service

I'm having an issue when I pass an array to my service, it only recognizes the first value in the array:
Here is my request object:
[Route("/dashboard", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
Here is the request which is made:
http://localhost:9090/json/reply/DashboardRequest?EquipmentIds=1&EquipmentIds=2
But when I observe the array in my service, it only contains one value, which is 1.
public object Get(DashboardRequest request)
{
// request.EquipmentIds.Length == 1;
// request.EquipmentIds[0] == 1;
}
One Solution I've done is the following, which seems a bit hacky? I thought the point of specifying it in my Request Object is that I get a strongly typed Request Object?
var equipmentIds = Request
.QueryString["EquipmentIds"]
.Split(',')
.Select(int.Parse)
.ToList();
This works when you use the custom route, e.g:
[Route("/dashboard", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
and call it via the User Defined route, e.g:
http://localhost:9090/dashboard?EquipmentIds=1&EquipmentIds=2
Support for this has also been added on Predefined Routes in this commit which will be available from v4.0.24+ that's now available on MyGet.
So your existing request now works as well, e.g:
http://localhost:9090/json/reply/DashboardRequest?EquipmentIds=1&EquipmentIds=2
Bind the request object to the int array like
[Route("/dashboard/{EquipmentIds}", "GET")]
public class DashboardRequest : IReturn<Dashboard>
{
public int[] EquipmentIds { get; set; }
}
http://localhost:9090/dashboard/1,2

ServiceStack "Handler for request not found" when it is working for dozens of similar DTOs

I have been using ServiceStack for months now. It has been working great for awhile and I've used many advanced approaches and Redis integration. I have a license, so my issue is not regarding a license issue, but I wonder if it is related. It almost looks like I have hit a maximum of DTO or paths, but I do not get any such error, simply the "Handler for request not found". So here is my question: how can you debug and isolate this error? I have read all the posts I can find on proper formats for DTO and DTO filters and I have been doing this long enough that I can see nothing wrong in this regard. Identically styled DTO's and paths work, but new ones fail, or so it seems. Even if I find there is something I am doing wrong in my DTO setup, the question remains, is there a way to debug this? Of course, finding what I'm doing wrong, if that is the case, is the first question.
Here is my code, AppHost first:
.Add<UsersCredentials>("/userscredentials", "GET")
.Add<UserCredential>("/userscredentials", "DELETE")
.Add<UserCredential>("/userscredentials/{UserName}", "POST PUT DELETE")
.Add<UserCredential("/userscredentials/{UserName}/(Permissions}/{System}/{ParamSet}/{Instrument}/{Interval}", "POST PUT DELETE")
DTO:
[Route("/userscredentials", "GET")]
public class UsersCredentials : IReturn<UsersCredentials>
{
public string UserName { get; set; }
public string Permissions { get; set; }
public string System { get; set; }
public uint ParamSet { get; set; }
public string Instrument { get; set; }
public uint Interval { get; set; }
} //Request DTO
[Route("/userscredentials", "DELETE")]
[Route("/userscredentials/{UserName}", "POST PUT DELETE")]
[Route("/userscredentials/{UserName}/(Permissions}/{System}/{ParamSet}/{Instrument}/{Interval}", "POST PUT DELETE")]
public class UserCredential : IReturn<UserCredential>
{
public string UserName { get; set; }
public string Permissions { get; set; }
public string System { get; set; }
public uint ParamSet { get; set; }
public string Instrument { get; set; }
public uint Interval { get; set; }
} //Request DTO
And Service:
// UsersCredentials
public class UsersCredentialsResponse
{
public string Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
public class UsersCredentialsService : Service
{
private bool init = false;
public object Get(UsersCredentials request)
{
return (request);
}
public object Post(UserCredential request)
{
return request;
}
public object Put(UserCredential request)
{
return request;
}
public void Delete(UserCredential request)
{
}
}
I use "POSTMAN" for debug and send this as a POST:
http://sun:1300/userscredentials/a?format=json
It works. Then I send as POST:
http://sun:1300/userscredentials/a/b/c/1/d/2?format=json
and get, "Handler for Request not found: Request.HttpMethod: POST Request.PathInfo: /userscredentials/a/b/c/1/d/2 Request.QueryString: format=json Request.RawUrl: /userscredentials/a/b/c/1/d/2?format=json"
Routing:
You shouldn't be defining the routes in the AppHost using the .Add<T> method as well as using [Route("/route", "METHOD")] on the DTO.
You only need to use one method. So this may cause conflict, and certainly extra maintenance. I recommend using just the latter, of the Route attribute. So remove the Add rules from your AppHost as they are covered by the DTO routes.
You should also read the routing documentation here, and this post about routing also.
Typo:
You have a typo in your route code. You have an incorrect bracket ( instead of {:
(Permissions}
Should be:
{Permissions}
Metadata
An excellent place to check the service is defined properly is by checking the applications Metadata feature. This is enabled by default, so you can do this by adding /metadata to your server url. i.e.
http://localhost:{port}/metadata
You can see an example metadata page here
Hope that helps.

Option for Include to only return foreign keys

Does Entity Framework provide an option to retrieve child objects that are only populated with fields that are foreign keys to the parent object?
Sample code might illustrate this better.
Assuming you have the following POCO classes...
public abstract class Base
{
public Guid Id { get; set; }
}
public class User : Base
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Photo : Base
{
public string Description { get; set; }
public User UploadedBy { get; set; }
}
... and assuming you've configured a DbContext correctly, how do you query for a list of all Photos including the UploadedBy object, but where that UploadedBy object only contains the Id property?
I know I can do this...
return await _dbContext.Photos.Include(p => p.UploadedBy).ToListAsync();
... but that returns the entire User object.
I'd like to do something like this...
return await _dbContext.Photos.Include(p => p.UploadedBy.Id).ToListAsync();
... to indicate that I only want the Id property back.
If we could chain those includes we would be able to pick each property on the child object that we want returned.
Or even better, I'd love to be able to configure a setting at a more global level that would make it so that anytime I ask for Photos, give me all members of photos, even child objects, but only populate their foreign keys and nothing more.
The last request is less important though because I could just create the following extension method for each POCO object...
public static IQueryable<Photo> IncludeForigenKeys(this PhotoAlbumDbContext context){
return context.Photos
.Include(photo => photo.UploadedBy.Id);
}
As far as I understand there is no way to partially load a Navigation Property.
However for foreign keys the standard way of accessing these without loading the Nav property is to include the actual key in your model. Eg:
public class Photo : Base
{
public string Description { get; set; }
public int UploadedById { get; set; }
public User UploadedBy { get; set; }
}
This id will be populated even if you don't actually load the whole navigation property.
In the case where you load both you can update either the value on the local or remote end of the nav property and that update will be persisted to the database on save. In my experience EF is very clever around this. The only scenario where it becomes a little more tricky is in unit tests where EF is not maintaining this state.

Resources