Skip to content

Instantly share code, notes, and snippets.

@ianbattersby
Created July 5, 2012 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianbattersby/3056291 to your computer and use it in GitHub Desktop.
Save ianbattersby/3056291 to your computer and use it in GitHub Desktop.
ServiceStackReSTExample: Trying to figure out 300 Multiple Choices return
namespace ServiceStackReSTExample
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Threading;
using ServiceStack.Common.Web;
using ServiceStack.Logging;
using ServiceStack.Logging.Support.Logging;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.ServiceModel;
using ServiceStack.WebHost.Endpoints;
// Warning: Contrived example ahead
public class Movie
{
public string Title { get; set; }
public List<string> Actors { get; set; }
}
public class MovieResponse
{
public string Id { get; set; }
public ResponseStatus ResponseStatus { get; set; }
public List<Movie> Movies { get; set; }
}
[Description("Movie web service")]
[RestService("/movies", "GET,OPTIONS")]
[DataContract]
public class MovieLookup
{
}
public class MovieLookupService : RestServiceBase<MovieLookup>
{
public override object OnGet(MovieLookup request)
{
// So what I really want to do here is return a 300 Multiple Chocies with links to the other two model-based services.
// It seems sensible to use HttpResult opposed to hijacking IHasOptions directly, however I get caught in a loop. Possible
// HEAD issue? Most likely.
return new HttpResult(new object(), base.RequestContext.ContentType, HttpStatusCode.MultipleChoices);
// Obviously I would be returned an empty object here in practice, but some links to the services.
// As we don't have a preferred link we won't be using the LOCATION header (as per RFC2616).
}
}
[Description("Movie by title web service")]
[RestService("/movies/title/{title}", "GET,OPTIONS")]
[DataContract]
public class MovieLookupTitle
{
[DataMember]
public string Title { get; set; }
}
public class MovieLookupTitleService : RestServiceBase<MovieLookupTitle>
{
public override object OnGet(MovieLookupTitle request)
{
return new MovieResponse()
{
// Lookup in DB for this title..
Id = Guid.NewGuid().ToString(),
Movies = new List<Movie> { new Movie() { Title = "Nelly packs her trunk", Actors = new List<string> { "Demis Bellot", "Ian Battersby" } } },
};
}
}
[Description("Movie by actor web service")]
[RestService("/movies/actor/{actor}", "GET,OPTIONS")]
[DataContract]
public class MovieLookupActor
{
[DataMember]
public string Actor { get; set; }
}
public class MovieLookupActorService : RestServiceBase<MovieLookupActor>
{
public override object OnGet(MovieLookupActor request)
{
return new MovieResponse()
{
// Lookup in DB for this actor
Id = Guid.NewGuid().ToString(),
Movies = new List<Movie> {
new Movie() { Title = "Nelly packs her trunk", Actors = new List<string> { "Demis Bellot", "Ian Battersby" }},
new Movie() { Title = "Planes, Trains, and Strawberries", Actors = new List<string> { "Ian Battersby", "Rob Ashton" }}
},
};
}
}
public class AppListenerHost
: AppHostHttpListenerBase
{
private static ILog log;
public AppListenerHost(string appName)
: base(appName, typeof(Movie).Assembly)
{
LogManager.LogFactory = new DebugLogFactory();
log = LogManager.GetLogger(typeof(AppListenerHost));
}
public override void Configure(Funq.Container container)
{
SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders =
{
{ "Access-Control-Allow-Origin", "*" },
{ "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" },
},
WsdlServiceNamespace = "http://www.servicestack.net/types",
});
}
}
class Program
{
public const string AppName = @"Ian's Movie App";
public const string ListeningOn = @"http://localhost:83/";
static void Main(string[] args)
{
using (var appHost = new AppListenerHost(AppName))
{
appHost.Init();
appHost.Start(ListeningOn);
Console.WriteLine("Started listening on: " + ListeningOn);
Console.WriteLine("AppHost Created at {0}, listening on {1}", DateTime.Now, ListeningOn);
Thread.Sleep(System.Threading.Timeout.Infinite);
}
System.Console.WriteLine("ReadLine()");
System.Console.ReadLine();
}
}
}
@mythz
Copy link

mythz commented Jul 5, 2012

What is an example of the HTTP wire response you wish to achieve with the HEAD request?

@ianbattersby
Copy link
Author

It's hard to find examples but my understanding is that you would include LINK rels as below. I believe the convention is to use the "rel" to provide a URI where documentation can be found, this would effectively be the same as the metdata pages.

<link rel="/rels/movie/title" href="/movie/title" />
<link rel="/rels/movie/actor" href="/movie/actor" />

See RFC5988: http://tools.ietf.org/html/rfc5988

Incidentally I'm starting to think I have the 300 wrong, it might be returned as 200 :=/

@ianbattersby
Copy link
Author

OK, think I did get the 300 thing wrong - but would be good to know how to do.

http://martinfowler.com/articles/richardsonMaturityModel.html#level3

@ianbattersby
Copy link
Author

Realise the above example is XML based, HEADER-version most likely something like;

Link: </movie/title>; rel="http://localhost:83/rels/movie/title"
Link: </movie/actor>; rel="http://localhost:83/rels/movie/actor"

@mythz
Copy link

mythz commented Jul 5, 2012

In which case you can just return a custom contentType, headers and body, e.g:

new HttpResult { 
    ContentType = "application/xml",
    Headers = {
        {"Link","</movie/title>; rel=\"http://localhost:83/rels/movie/title\""},
        {"Link","</movie/actor>; rel=\"http://localhost:83/rels/movie/actor\""},
    },
    ResponseText = "<link rel=...",      //If you also want to return it in the body.
};

@ianbattersby
Copy link
Author

Thanks Demis, been tied up but will follow this up tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment