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.

results matching ""

    No results matching ""