Custom Frameworks
The framework layer of GenHTTP allows application frameworks (such as webservices or controllers) to host a set of operations (put simply a route with a piece of C# code to be executed). This way the application framework itself is only a very thin layer - it analyzes the given scope (such as a controller class) and derives operations from it which are then handled by the generic framework part. This way, all application frameworks share the same feature level and can support things like parameter binding, serialization, async execution or code generation. To give an example, the webservice handler which provides the whole webservice framework consists of 70 lines of code only - everything else is done by the underlying framework layer.
In essence, the framework layer allows you to bind a dynamic bunch of C# methods to HTTP endpoints with some granularity to configure how they are exposed. Examples could be a framework mapping database access to REST or a framework to emulate file-based endpoints as we have with PHP.
Implementing a Custom Framework
To give an example, we will create a simple handler that creates some operations and delegates their execution
to a MethodCollection handler.
public class CustomFrameworkHandler : IHandler, IServiceMethodProvider
{
private MethodCollection? _methods;
public MethodCollection Methods => _methods ?? throw new InvalidOperationException("Handler is not prepared yet");
public async ValueTask PrepareAsync(IServer server)
{
// this snippet exposes a given method as an operation.
// your framework will typically analyze a scope given by the
// user to dynamically collect a bunch of operations
var list = new List<MethodHandler>();
// specify the supported methods of the operation we are exposing
var supportedMethods = new MethodConfiguration([RequestMethod.Get]);
// the actual piece of code to be executed - either a method info or a delegate
var methodInfo = GetType().GetMethod("ExposedMethod")!;
// auto enables code generation, otherwise reflection only
var executionSettings = new ExecutionSettings(ExecutionMode.Auto);
// configures the behavior for serialization, injection and formatting
var registry = new MethodRegistry(
Serialization.Default().Build(),
Injection.Default().Build(),
Formatting.Default().Build()
);
// build the operation we would like to provide
var operation = OperationBuilder.Create(server, "/path/:id", methodInfo, null, executionSettings, supportedMethods, registry);
// create a method handler from the operation which will serve it as an HTTP endpoint
// this handler requires an instance provider which tells the framework on which
// object to invoke the given method info or delegate
list.Add(new MethodHandler(operation, (_) => new(this), registry));
// build a method collection handler from all collected operations
// this handler is responsible for routing
_methods = new MethodCollection(list);
await _methods.PrepareAsync(server);
}
public ValueTask<IResponse?> HandleAsync(IRequest request) => Methods.HandleAsync(request);
public string ExposedMethod(int id) => id.ToString();
}Hosting this handler will expose /path/:id on our server, where :id is automatically mapped as a path
argument and converted into an integer. The given operation will be compiled using code generation
in the preparation phase of the MethodCollection.
By implementing IServiceMethodProvider, our custom framework seamlessly integrates into
functionality that discovers available operations, such as the Open API handlers.