Grapevine 5.0 Beta 1

Scott Offen

Scott Offen

Grapevine Creator

The second beta version of Grapevine 5.0 was uploaded to NuGet.org today, December 29, 2020. Here are some updated code samples to start playing around with.

For more code samples, see https://github.com/scottoffen/grapevine/tree/main/src/Samples.

As promised, this beta drops the RestServer.DeveloperConfiguration that we used in the first beta, and introduces the RestServerBuilder. Until the documentation is complete, you can dig into the details of the class in the source code if you like. Here I'm going to show you the two static methods it exposes that I expect will be most used going forward, RestServerBuilder.UseDefaults and RestServerBuilder.From<T>.

tip

The builder instantiates and configures the RestServer but does not start it. You will still need to call server.Start() before it will respond to requests.

Using The Default Configuration#

var server = RestServerBuilder.UseDefaults().Build();

The default configuration is good for early development. It uses the ServiceCollection implementation from Microsoft, sets the logging level to Trace (so you can see all the output) and adds the default listener prefix of https://localhost:1234. You can now start that server, it will autoscan for routes, and you'll be up and running!

Using A Startup Class#

The startup class approach allows you to specify your prefered IServiceCollection implementation, easily register implementations with the container, and configure your IRestServer, all inside of a simple class that you specify as a generic parameter.

var server = RestServerBuilder.From<Startup>().Build();

The class you specify must have a parameterless constructor (implict or explicit doesn't matter). The naming of the methods in the class doesn't matter, only their visibility (public) and method signature. Using reflection, only the first matching method will be used. If no matching methods are found, what you get will be similar to if you had used the default configuration, with the expection that no prefixes are added to the listener.

The example below provides the same outcome as if you had used the UseDefaults() approach.

Startup.cs
public class Startup
{
/*
* Include a method with this signature (method name does not matter) if
* you want to use an IServiceCollection implementation other than the one
* provided by Microsoft. You can choose to configure some services here
* as well, if you'd like.
*/
public IServiceCollection GetServices()
{
return new ServiceCollection();
}
/*
* Include a method with this signature (method name does not matter) to
* configure your services. Prior to the method being called, implementations
* for IRestServer, IRouter and IRouteScanner have already been registered.
*/
public void ConfigureServices(IServiceCollection services)
{
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Trace);
}
/*
* Include a method with this signature (method name does not matter) to
* configure your IRestServer. Add event handlers for stopping and starting
* the server, request recieved, and before and after routing. If you want
* to do manual route registration (more complex) this is this place to do it.
*/
public void ConfigureServer(IRestServer server)
{
server.Prefixes.Add("http://localhost:1234/");
}
}

Specifying A Service Collection#

You can use any dependency injection library you want, as long as it exposes the IServiceCollection interface. If the library you want to use doesn't expose this interface, you'd have wrap it in a class that did. See the documentation for your prefered library on how to do this.

If you do specify a different implementation, you can configure your services here as well. However, between calling this method and calling the configure services method, concrete implementations for IRestServer, IRouter and IRouteScanner will be added. If you want to add your own implementations for that, do that in the configure services method, not here.

Configuring Services#

This method is used to add concrete implementations for dependencies you want injected into your routes. If you want to specify your own implementations for IRestServer, IRouter and IRouteScanner, this would be the place to do it. Configure logging here, too. By default, a console logger has already been added, so you might want to clear all other loggers before adding your prefered implementation (e.g. NLog or Serilog).

Configuring The Server#

By the time this method is called, a concrete implementation of IRestServer has been created from the service collection. The service collection created has been added to IRestServer.Router.ServiceCollection to be used during the route scanning and routing process. Here, you want to configure your rest server.

Pipeline Event Handlers#

You can add event handlers to be executed in the following places of the pipeline:

  • IRestServer.BeforeStarting
  • IRestServer.AfterStarting
  • IRestServer.BeforeStopping
  • IRestServer.AfterStopping
  • IRestServer.OnRequestAsync
  • IRouter.BeforeRoutingAsync
  • IRouter.AfterRoutingAsync

The first four are good for logging information during server startup and shutdown, but can be used for any other purpose as well.

Use BeforeRoutingAsync and AfterRoutingAsync for things you want done before and after the request is routed, respectively.

Because any added middleware (described below) might respond to requests before the request is routed (and hence not calling BeforeRoutingAsync and AfterRoutingAsync), use OnRequestAsync for things you want done regardless of whether the request gets routed. Because this is the same event that middleware should connect into, you might want to add your own event handlers before adding any middleware, lest the request be responded to before the handler has a chance to execute.

Global Response Headers#

You can also configure global response headers here. These are key/value pairs that you want to send back unmodified in every request.

server.GlobalResponseHeaders.Add("key", "value");

Custom IHttpContext#

If you want to use your own implementation of IHttpContext, but not your own implementation of IRestServer (which is where the IHttpContext is created before being passed to the router), you can replace the default factory with your own.

server.HttpContextFactory = (context, server, token) => { return new MyHttpContext(context, server, token); };

Configure Middleware#

Finally, this is also where you want to configure middleware. There are three built-in middleware components you can turn on. I'll list them here, but for now you'll have to check out the source code for their implementation until the documentation is up.

// Turns on using content folders to server static content
server.UseContentFolders();

In Grapevine 5, IPublicFolder has been replaced with IContentFolder. The implementation is still being worked on, but the functionality will be similar. You can add content folders to IRestServer.ContentFolders. The primary difference is that you will need to turn on the use of them. This can be easily done using server.UseContentFolders(). This change allowed for better seperation of concerns between the server and the router, and cleaned up a number of code smells from previous versions.

// Turns on using correlation ids
server.UseCorrelationId();

Correlation ids are ids that are used to trace user requests between multiple microservices. You can turn this on to ensure that correlation ids from incoming requests get added to the outgoing response, and if no correlation id was on the request a new one is created. There are several overloads that allow you to specify the field the correlation id should be in and the method used to generate new correlation ids.

// Turns on CORS
server.UseCorsPolicy();

CORS was a popular request in earlier versions of Grapevine. This middleware turns on CORS based on the technique described in this issue. I'd like to get some feedback on how well this works, but if the defaults provided don't work you can always create your own CORS policy and use it here via the Grapevine.Middleware.ICorsPolicy interface. (The location of that interface is subject to change.)

Running The Server#

As always, you can start the server by calling the Start() method. This starts the server by starting the listener and the thread that listens for incoming request, and it doesn't block the execution thread. So, for those rare occasions that you do want to block the thread, an extension method has been added that does just that.

server.Run();

This first calls Start(), and then waits until the listener is no longer running before releasing the execution thread.