Friday 13 April 2012

ServiceStack: REST with ProtoBuf

Service Stack

Service Stack makes services easy! 

You can download their excellent software and examples from the website:  http://www.servicestack.net/

This blog post combines three of their examples into one project: 

  • Web Service
  • REST Services
  • ProtoBuf plugin

Web Services in 5 easy Steps

1. Create an empty ASP.NET Web Application

new-web

2. Add ServiceStack via Nuget

servicestack-via-nuget

3. Create your DTO, Response and Service

// DTO
public class Hello
{
public string Name { get; set; }
}

// Response
public class HelloResponse
{
public string Result { get; set; }
}

//Service
public class HelloService : IService<Hello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}

4. Add your Global.asax


Notice typeof(AppHost).Assembly on line 11.  This allows us to pass in multiple assemblies that contain our services.  In this case we’re just working within the one assembly.

using System;
using System.Web;
using Funq;
using ServiceStack.Demo.WebService;
using ServiceStack.WebHost.Endpoints;

namespace ServiceStack.Demo
{
public class AppHost : AppHostBase
{
public AppHost() : base("ServiceStack makes services easy!", typeof(AppHost).Assembly) { }

public override void Configure(Container container)
{
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}");
}
}

public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
new AppHost().Init();
}
}
}

5. Update your Web.Config


Notice line 6: <location path=”servicestack”>  This creates a virtual directory that will contain your services, in this example we’ve called it servicestack but it could be called anything we like.

<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<location path="servicestack">
<system.web>
<httpHandlers>
<add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*"/>
</httpHandlers>
</system.web>

<!-- Required for IIS 7.0 -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add path="*" name="ServiceStack.Factory" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
</handlers>
</system.webServer>
</location>

</configuration>


That’s it, ready to run!


image


Don’t panic, remember we set a virtual directory to hold all our services (see step 5)?

All we need to do is navigate to the servicestack folder.

Out of the box you get a nice metadata landing page, which lists all your hosted services:


image


If we run the default web service URL we see a nice HTML rendered presentation of our data.


image


Or we can append the format of our choice to the URL:


image


For Google Chrome to be able to render JSON I use the following plugin:


https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc 


image


?format=JSV would return us the following data:


image


JSV is a compact version of JSON, which by default Google Chrome cannot display in the browser.


NOTE TO CLIENTS: When using other clients to your services you can set the HTTP Accept header on the request to determine which response format you’ll receive.


image


I’m using RestClient-Tool to test my services: http://code.google.com/a/eclipselabs.org/p/restclient-tool/


Rest Services


Two simples steps:


1. Add your Services

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using ServiceStack.Common.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;

namespace ServiceStack.Demo.Rest
{
[Description("GET or DELETE a single movie by Id. Use POST to create a new Movie and PUT to update it")]
public class Movie
{
public Movie()
{
this.Genres = new List<string>();
}

public int Id { get; set; }
public string Title { get; set; }
public decimal Rating { get; set; }
public string Director { get; set; }
public DateTime ReleaseDate { get; set; }
public List<string> Genres { get; set; }
}

public class MovieResponse
{
public Movie Movie { get; set; }
}

public class MovieService : RestServiceBase<Movie>
{
/// GET /movies/{Id}
public override object OnGet(Movie movie)
{
// normally you would return a movie from the db
return new MovieResponse { Movie = movie };
}

/// POST /movies
///
/// returns HTTP Response =>
/// 201 Created
/// Location: http://localhost/ServiceStack.MovieRest/movies/{newMovieId}
///
/// {newMovie DTO in [xml|json|jsv|etc]}
public override object OnPost(Movie movie)
{
// insert a movie into your database... returns the new Id = 999
var newId = 999;
var newMovie = new MovieResponse { Movie = new Movie() { Id = newId } };

return new HttpResult(newMovie)
{
StatusCode = HttpStatusCode.Created,
Headers = {{ HttpHeaders.Location, this.RequestContext.AbsoluteUri.WithTrailingSlash() + newId }}
};
}

/// PUT /movies/{id}
public override object OnPut(Movie movie)
{
// save to db
return null;
}

/// DELETE /movies/{Id}
public override object OnDelete(Movie request)
{
// delete from db
return null;
}
}

[Description("Find movies by genre, or all movies if no genre is provided")]
public class Movies
{
public string Genre { get; set; }
}

public class MoviesResponse
{
public List<Movie> Movies { get; set; }
}

public class MoviesService : RestServiceBase<Movies>
{
/// GET /movies
/// GET /movies/genres/{Genre}
public override object OnGet(Movies request)
{
return new MoviesResponse { Movies = new List<Movie>() { new Movie() { Id=10 }} };
}
}
}

2. Add your routes to the Global.asax

public override void Configure(Container container)
{
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}");

Routes
.Add<Movie>("/movies", "POST,PUT,DELETE")
.Add<Movie>("/movies/{Id}")
.Add<Movies>("/movies")
.Add<Movies>("/movies/genres/{Genre}");
}

Run the app!


Our services have been detected.


image


image


Adding ProtoBuf for Raw Performance


1. Add the plugin via Nuget


protobuf


Which adds WebActivator framework which runs code before the start of application in the ASP.NET pipeline.


ProtoBuf_AppStart is added and will run to handle the configuration.


2. Add Attributes to your DTOs


ProtoBuf requires attributes to explain the de/serialization order for the properties.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Runtime.Serialization;
using ServiceStack.Common.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;

namespace ServiceStack.Demo.Rest
{
[DataContract]
[Description("GET or DELETE a single movie by Id. Use POST to create a new Movie and PUT to update it")]
public class Movie
{
public Movie()
{
this.Genres = new List<string>();
}

[DataMember(Order = 1)] public int Id { get; set; }
[DataMember(Order = 2)] public string Title { get; set; }
[DataMember(Order = 3)] public decimal Rating { get; set; }
[DataMember(Order = 4)] public string Director { get; set; }
[DataMember(Order = 5)] public DateTime ReleaseDate { get; set; }
[DataMember(Order = 6)] public List<string> Genres { get; set; }
}

[DataContract]
public class MovieResponse
{
[DataMember(Order = 0)]
public Movie Movie { get; set; }
}

public class MovieService : RestServiceBase<Movie>
{
/// GET /movies/{Id}
public override object OnGet(Movie movie)
{
// normally you would return a movie from the db
return new MovieResponse { Movie = movie };
}

/// POST /movies
///
/// returns HTTP Response =>
/// 201 Created
/// Location: http://localhost/ServiceStack.MovieRest/movies/{newMovieId}
///
/// {newMovie DTO in [xml|json|jsv|etc]}
public override object OnPost(Movie movie)
{
// insert a movie into your database... returns the new Id = 999
var newId = 999;
var newMovie = new MovieResponse { Movie = new Movie() { Id = newId } };

return new HttpResult(newMovie)
{
StatusCode = HttpStatusCode.Created,
Headers = {{ HttpHeaders.Location, this.RequestContext.AbsoluteUri.WithTrailingSlash() + newId }}
};
}

/// PUT /movies/{id}
public override object OnPut(Movie movie)
{
// save to db
return null;
}

/// DELETE /movies/{Id}
public override object OnDelete(Movie request)
{
// delete from db
return null;
}
}

[DataContract]
[Description("Find movies by genre, or all movies if no genre is provided")]
public class Movies
{
[DataMember(Order = 1)]
public string Genre { get; set; }
}

[DataContract]
public class MoviesResponse
{
[DataMember(Order=1)]
public List<Movie> Movies { get; set; }
}


public class MoviesService : RestServiceBase<Movies>
{
/// GET /movies
/// GET /movies/genres/{Genre}
public override object OnGet(Movies request)
{
return new MoviesResponse { Movies = new List<Movie>() { new Movie() { Id=10 }} };
}
}
}

Ready to run!


Notice how the X-PROTOBUF column has been added.


image


image


image


image


Framework tests for ServiceStack,Plugins.ProtoBuf can be found here:



Blog Source

http://stevenhollidge.com/blog-source-code/ServiceStack.Demo.zip

No comments:

Post a Comment