Getting Started with Grapevine
Keeping It Simple
This guide will be focused on the simplest implementation of Grapevine - a console application. Create a new console application project and name it "GrapevineConsole". Add Grapevine in your project, then add the following code to your Main()
method.
using (var server = new RestServer())
{
server.Start();
Console.ReadLine();
server.Stop();
}
When you run it, a console window appears with a blinking cursor, and stays there until you either hit Enter or close the program. If you open a web browser while the application is running to http://localhost:1234, you will see a very unhelpful page with the words Not Found.
Logging To The Console
We can make this example a little more helpful by having Grapevine log out to the console what it's doing. Where reasonable, methods on objects in Grapevine are chainable, making it easy to tell the RestServer to write diagnostic information out to the console.
using (var server = new RestServer())
{
server.LogToConsole().Start();
Console.ReadLine();
server.Stop();
}
Run the application again, and the same console will appear, but this time with information on what's going on. Your console should display the following:
1/1/2016 12:30:15 PM Trace Scanning resources for routes...
1/1/2016 12:30:15 PM Trace Generating routes for assembly GrapevineConsole
1/1/2016 12:30:17 PM Trace Listening: http://localhost:1234/
This gives us a much better idea about what's going on. The RestServer is scanning for routes and not finding anything, so requests are returned having not found a route that responds to it.
Creating Routes
So far, going to http://localhost:1234 still shows the Not Found page. This is because we haven't defined any routes in our assembly. Routes are added by creating classes and methods in our project that meet specific criteria.
For a given Type in an assembly to be automatically scanned for routes, the criteria it must meet are:
- It must be a public, reference-type class; not an abstract class, struct, interface or value-type.
- It must have the
[RestResource]
attribute on it. - It must have a parameterless constructor.
For methods in a class to be automatically registered as routes, the criteria the must meet are:
- It must have the
[RestRoute]
attribute on it. - It must take a single argument of type
IHttpContext
. - It must have a return value of type
IHttpContext
.
The Router
on Grapevine's RestServer
maintains an internal list of routes. While you can add routes to it manually, if the list is empty when the server is started, the Router
will scan assemblies in your current AppDomain
for routes. Assemblies are scanned in alphabetical order (skipping Grapevine and assemblies in the Global Assembly Cache), and each type in an assembly is likewise scanned in alphabetical order. In contrast, methods inside each type are discovered in the order the appear in the class itself.
This is what is meant by optional route registration. As long as your routes follow these conventions, they will be automatically registered with the router and incoming request will be routed to them, and you don't have to register them manually.
Armed with this information, let's create our first routes.
[RestResource]
public class TestResource
{
[RestRoute]
public IHttpContext HelloWorld(IHttpContext context)
{
context.Response.SendResponse("Hello, world.");
return context;
}
}
When we start our server this time, and then go to http://localhost:1234, our message "Hello, world." shows up in the browser. Notice that we also see some different output in the console:
1/1/2016 12:30:15 PM Trace Scanning resources for routes...
1/1/2016 12:30:15 PM Trace Generating routes for assembly GrapevineConsole
1/1/2016 12:30:15 PM Trace Generating routes from type TestResource
1/1/2016 12:30:15 PM Trace Generated route ALL > GrapevineConsole.TestResource.HelloWorld
1/1/2016 12:30:17 PM Trace Listening: http://localhost:1234/
1/1/2016 12:31:00 PM Info Routing Request : 27ebe73f0fe5 - GET / has 1 routes
1/1/2016 12:31:00 PM Trace Route Invoked : 27ebe73f0fe5 - 1/1 GrapevineConsole.TestResource.HelloWorld
1/1/2016 12:31:00 PM Trace Routing Complete : 27ebe73f0fe5 - 1 of 1 routes invoked
And now we can see that a route was registered, and a request sent to the server was routed to the route, which then responded to it.
Let's define another route now, specifying the HttpMethod
and PathInfo
on the route, and see how they interact together.
[RestResource]
public class TestResource
{
[RestRoute(HttpMethod = HttpMethod.GET, PathInfo = "/repeat")]
public IHttpContext RepeatMe(IHttpContext context)
{
var word = context.Request.QueryString["word"] ?? "what?";
context.Response.SendResponse(word);
return context;
}
[RestRoute]
public IHttpContext HelloWorld(IHttpContext context)
{
context.Response.SendResponse("Hello, world.");
return context;
}
}
Okay, lets run this and take a look at our console output. The first thing you should notice is that both routes were registered, but the new route was registered first. That's because it shows up first in the class itself. You should also get a better idea of what the pattern looks like when a route gets registered; namely [HttpMethod] [PathInfo] > [MethodToExecute]
.
The word ALL
in the registration for our HelloWorld
method means that - since we didn't specify which http method this route should handle - it will respond to routes with all http methods. The extra space where the PathInfo
should be indicates that it will respond to requests from any path info. Compare that to the registration of the RepeatMe
method, which will only respond to GET
requests sent to /repeat
.
1/1/2016 12:30:15 PM Trace Scanning resources for routes...
1/1/2016 12:30:15 PM Trace Generating routes for assembly GrapevineConsole
1/1/2016 12:30:15 PM Trace Generating routes from type TestResource
1/1/2016 12:30:15 PM Trace Generated route GET /repeat > GrapevineConsole.TestResource.RepeatMe
1/1/2016 12:30:15 PM Trace Generated route ALL > GrapevineConsole.TestResource.HelloWorld
1/1/2016 12:30:17 PM Trace Listening: http://localhost:1234/
Open your browser again, and first go to http://localhost:1234/repeat?word=parrot and see the word "parrot" show up in the browser. Next go to http://localhost:1234 and see "Hello, world." again. Then go back to the console window and see what has shown up there as a result of those requests.
1/1/2016 12:30:20 PM Info Routing Request : 0b310261437c - GET /repeat has 2 routes
1/1/2016 12:30:20 PM Trace Route Invoked : 0b310261437c - 1/2 GrapevineConsole.TestResource.RepeatMe
1/1/2016 12:30:20 PM Trace Routing Complete : 0b310261437c - 1 of 2 routes invoked
1/1/2016 12:30:25 PM Info Routing Request : 095038b0deed - GET / has 1 routes
1/1/2016 12:30:25 PM Trace Route Invoked : 095038b0deed - 1/1 GrapevineConsole.TestResource.HelloWorld
1/1/2016 12:30:25 PM Trace Routing Complete : 095038b0deed - 1 of 1 routes invoked
So, for each request we have a set of outputs. The Routing Request message is generated when the request is received by the router. In addition to the http method and path info, it tells us how many matching routes were found. The Route Invoked message is generated when a specific route has been invoked, and the Routing Complete message indicates that the router has finished invoking routes for a given request.
Let me first draw your attention to the string of numbers and letters between the :
and the -
on each message. Because requests are handled in parallel on different threads, this unique identifier is used to keep track of which messages belong to the same request.
Next, did you notice how it tells us that two routes were discovered for GET /repeat
, but only one was executed? What's up with that?
Responding To Requests
Routes are invoked on an incoming request until one of the routes responds to the request. By default, routing stops and subsequent routes are not invoked. Let's take advantage of that behaviour in this next example to execute several methods in a specific order.
Let's replace the RepeatMe
method with three new methods, MeFirst
, MeSecond
and MeThird
, in that order, as shown below.
[RestResource]
public class TestResource
{
[RestRoute(HttpMethod = HttpMethod.GET, PathInfo = "/inorder")]
public IHttpContext MeFirst(IHttpContext context)
{
return context;
}
[RestRoute(HttpMethod = HttpMethod.GET, PathInfo = "/inorder")]
public IHttpContext MeSecond(IHttpContext context)
{
return context;
}
[RestRoute(HttpMethod = HttpMethod.GET, PathInfo = "/inorder")]
public IHttpContext MeThird(IHttpContext context)
{
return context;
}
[RestRoute]
public IHttpContext HelloWorld(IHttpContext context)
{
context.Response.SendResponse("Hello, world.");
return context;
}
}
After starting our server and going to http://localhost:1234/inorder, we can take a look at the messages generated in the console.
1/1/2016 12:30:15 PM Info Routing Request : b194febe36f5 - GET /inorder has 4 routes
1/1/2016 12:30:15 PM Trace Route Invoked : b194febe36f5 - 1/4 GrapevineConsole.TestResource.MeFirst
1/1/2016 12:30:15 PM Trace Route Invoked : b194febe36f5 - 2/4 GrapevineConsole.TestResource.MeSecond
1/1/2016 12:30:15 PM Trace Route Invoked : b194febe36f5 - 3/4 GrapevineConsole.TestResource.MeThird
1/1/2016 12:30:15 PM Trace Route Invoked : b194febe36f5 - 4/4 GrapevineConsole.TestResource.HelloWorld
1/1/2016 12:30:15 PM Trace Routing Complete : b194febe36f5 - 4 of 4 routes invoked
Notice that all four routes were invoked until one of them - HelloWorld - responded to the request.