The letter 'D' in SOLID stands for Dependency inversion principle. The principle states,

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions. Wikipedia

Newing up instances cause strict coupling between code modules. To keep them decoupled from each other, we follow the 'Dependency Inversion Principle'. In this way the modules are not dependent on each other's concrete implementation rather they are dependent upon abstractions e.g. interfaces.

An abstraction can have many many implementations. So, whenever we encounter an abstraction, there should be some way of passing a specific implementation to that. A class is held responsible for this kind of servings and it should be configured in such a way so. We call it a dependency injection container.

ASP.Net Core has a built-in dependency injection container. It's simple and can serve our purpose very well. Not only it can be configured to serve implementations to abstractions but it also can control the lifetime of the created instances.

Instead of using the concrete DocumentWriter and DocumentExecutor, we can use their abstractions i.e. IDocumentWriter and DocumentExecutor. And for this purpose we have to configure the built-in dependency container as follows,

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDocumentWriter, DocumentWriter>();
    services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
}
Startup.cs

They both have Singleton lifetimes since we want to create instances of them only once and use it for the rest of the application's lifecycle.

For the GameStoreQuery, we don't have any abstraction. We will just use the raw implementation but with a Transient lifetime so that instance on the type is created per request,

services.AddTransient<GameStoreQuery>();
Startup.cs

The schema contains the query and later in the series it will also have mutation and subscription. We better make a separate class for it. The class is extended from the Schema type from GraphQL.Types. GameStoreSchema is made constructor injectable so that we can inject and pass down IServiceProvider to the base type i.e. Schema. IServiceProvider.GetRequiredService<T>() is an utility function available in GraphQL.Utilities that uses Activator.CreateInstance() to create an instance of the generic type.

public class GameStoreSchema : Schema
{
    public GameStoreSchema(IServiceProvider serviceProvider) : base(serviceProvider)
    {
        Query = serviceProvider.GetRequiredService<GameStoreQuery>();
    }
}
Startup.cs

Finally, it's time to configure the GameStoreSchema in the ConfigureServices method as following,

services.AddSingleton<ISchema, GameStoreSchema>();
Startup.cs

The ISchema is coming from the GraphQL.Types as well.

Now, we can shift the middleware code to its middleware class (special type having a `Invoke/InvokeAsync(HttpContext httpContext)` method. Following is the middleware class named GraphQLMiddleware,

public class GraphQLMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IDocumentWriter _writer;
    private readonly IDocumentExecuter _executor;
    
    public GraphQLMiddleware(RequestDelegate next, IDocumentWriter writer, IDocumentExecuter executor)
    {
        _next = next;
        _writer = writer;
        _executor = executor;
    }

    public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
    {
        if (httpContext.Request.Path.StartsWithSegments("/graphql") && string.Equals(httpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
        {
            var request = await JsonSerializer
                                    .DeserializeAsync<GraphQLRequest>(
                                        httpContext.Request.Body,
                                        new JsonSerializerOptions
                                        {
                                            PropertyNameCaseInsensitive = true
                                        });

            var result = await _executor
                            .ExecuteAsync(doc =>
                            {
                                doc.Schema = schema;
                                doc.Query = request.Query;
                            }).ConfigureAwait(false);

            httpContext.Response.ContentType = "application/json";
            httpContext.Response.StatusCode = 200;

            await _writer.WriteAsync(httpContext.Response.Body, result);
        }
        else
        {
            await _next(httpContext);
        }
    }
}
GraphQLMiddleware.cs

Notice, how we replace all the concrete type initializations with abstractions and make our code loosely coupled. Every dependency injectable service is injected via the constructor (constructor injection) at this moment. A middleware sits in the request pipeline and have a Singleton lifetime. But it doesn't mean that we can't work with services with Transient or Scoped lifetime. That's where Invoke/InvokeAsync method comes into play where we can inject services of Transient and Scoped lifetime. To know more about DI in ASP.NET Core middleware, refer to this link.

The last but not least, we must attach the middleware in the application startup pipeline. IApplicationBuilder has an extension method called UseMiddleware which is used to attach middleware classes. So, the final look of the Configure method is as follows,

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMiddleware<GraphQLMiddleware>();
}
Startup.cs

This next section is all about refactoring and making the GraphQLMiddleware more configurable.

public static class GraphQLMiddlewareExtensions
{
    public static IApplicationBuilder UseGraphQL(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<GraphQLMiddleware>();
    }

    public static IServiceCollection AddGraphQL(this IServiceCollection services, Action<GraphQLOptions> action)
    {
        return services.Configure(action);
    }
}
GraphQLMiddleware.cs

AddGraphQL is an extension method that takes various options like a configurable service endpoint. The type of the object we are using to pass the options is named GraphQLOptions. We can pass options from the ConfigureServices() as follows,

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDocumentWriter, DocumentWriter>();
    services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
    services.AddTransient<ISchema, GameStoreSchema>();
    services.AddTransient<GameStoreQuery>();

    services.AddGraphQL(options =>
    {
        options.EndPoint = "/graphql";
    });
}
Startup.cs

For the time being, GraphQLOptions only have a single property called EndPoint.

public class GraphQLOptions
{
    public string EndPoint { get; set; }
}
GraphQLOptions.cs

To use the configuration options in the GraphQLMiddleware, we inject the constructor with IOptions<GraphQLOptions>

public GraphQLMiddleware(RequestDelegate next, IDocumentWriter writer, IDocumentExecuter executor, IOptions<GraphQLOptions> options)
{
	_next = next;
  	_writer = writer;
   	_executor = executor;
  	_options = options.Value;
}
GraphQLMiddleware.cs

UseGraphQL is another one that is just a wrapper around app.UseMiddleware() to make it look more meaningful like app.UseGraphQL().

Part - III

fiyazbinhasan/GraphQLCoreFromScratch
https://fiyazhasan.me/tag/graphql/. Contribute to fiyazbinhasan/GraphQLCoreFromScratch development by creating an account on GitHub.

Dependency injection in ASP.NET Core

Dependency injection to the core