Alberto Cruz
Alberto Cruz

Reputation: 157

How can I write a HotChocolate middleware that acts after UseFiltering?

I am building a GraphQL interface in an existing dotnet webapi using HotChocolate v13.

I want to write a middleware in a field that has UseFiltering(), which as far as I know is a middleware as well.

But when I put breakpoints in my middleware before and after calling await next(context); the filters don't seem to be applied yet.

Consider the following code

public class QueryType : ObjectType<Query>
{
    protected override void Configure(IObjectTypeDescriptor<GraphQuery> descriptor)
    {
        descriptor.Authorize();

        descriptor
            .Field(x => x.GetProducts(default!, default!))
            .UseFiltering()
            .Use(next => async context =>
            {
                //Breakpoing here: context.Result is null
                await next(context);
                //Breakpoing here: context.Result has AppDbContext.Products (without filters)
            })
            .Type<ListType<ProductType>>();

        base.Configure(descriptor);
    }
}

public class Query
{
    public IQueryable<Product> GetProducts([Service] AppDbContext dbContext, [ScopedService] ICurrentUser currentUser)
    {
        return dbContext.Products;
    }
}

If I send a query with filters to this field, none of the breakpoints show me a filtered IQueryable<Product> in context.Result

Is there a way in which I can write a middleware that at some point has the IQueryable<Product> with the filter sent by the client already applied?

Upvotes: 1

Views: 400

Answers (1)

Jalpa Panchal
Jalpa Panchal

Reputation: 12749

To see filtered data you need to first understand the order of the middleware. HotChocolate's filtering middleware works by modifying the IQueryable and adding expressions to it. However, the actual query execution (which translates the IQueryable to a database query and executes it) happens later in the pipeline.

Below is the code with the logging you can check:

AppDbContext.cs:

using GraphQLMiddlewareExample.Models;
using Microsoft.EntityFrameworkCore;

namespace GraphQLMiddlewareExample.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

        public DbSet<Product> Products { get; set; }


    }
}

GraphQL\Query.cs:

using GraphQLMiddlewareExample.Data;
using GraphQLMiddlewareExample.Models;

namespace GraphQLMiddlewareExample.GraphQL
{
    public class Query
    {
         [UseFiltering]
        public IQueryable<Product> GetProducts([Service] AppDbContext dbContext)
        {
            return dbContext.Products;
        }


    }
}

GraphQL\QueryType.cs:

using GraphQLMiddlewareExample.Models;

namespace GraphQLMiddlewareExample.GraphQL
{
    public class QueryType : ObjectType<Query>
    {
        protected override void Configure(IObjectTypeDescriptor<Query> descriptor)
        {
            descriptor.Field(x => x.GetProducts(default!))
                .Use(next => async context =>
                {
                    var logger = context.Services.GetRequiredService<ILogger<QueryType>>();
                    logger.LogInformation("Before next middleware is invoked");

                    await next(context);

                    logger.LogInformation("After next middleware is invoked");

                    if (context.Result is IQueryable<Product> queryable)
                    {
                        logger.LogInformation("context.Result is IQueryable<Product>");

                        var filteredProducts = queryable.ToList(); // Execute the query to get filtered results

                        logger.LogInformation("Filtered products count: {0}", filteredProducts.Count);

                        foreach (var product in filteredProducts)
                        {
                            logger.LogInformation("Product - Id: {0}, Name: {1}, Price: {2}", product.Id, product.Name, product.Price);
                        }

                        context.Result = filteredProducts; // You can inspect 'filteredProducts' here
                    }
                    else
                    {
                        logger.LogInformation("context.Result is not IQueryable<Product>");
                    }
                })
                .Type<ListType<ObjectType<Product>>>();

        }


    }
}

Program.cs:

using Microsoft.EntityFrameworkCore;
using GraphQLMiddlewareExample.Data;
using GraphQLMiddlewareExample.GraphQL;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using GraphQLMiddlewareExample;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("ProductsDb"));

builder.Services
    .AddGraphQLServer()
    .AddQueryType<QueryType>()
    .AddFiltering()
    .UseField<LoggingMiddleware>();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyHeader()
               .AllowAnyMethod();
    });
});

builder.Services.AddLogging();

var app = builder.Build();

// Seed the database
SeedDatabase(app);

app.UseRouting();
app.UseCors();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGraphQL();
});

app.Run();

static void SeedDatabase(WebApplication app)
{
    using var serviceScope = app.Services.CreateScope();
    var context = serviceScope.ServiceProvider.GetService<AppDbContext>();

    context.Products.AddRange(
        new GraphQLMiddlewareExample.Models.Product { Name = "Product1", Price = 10 },
        new GraphQLMiddlewareExample.Models.Product { Name = "Product2", Price = 20 },
        new GraphQLMiddlewareExample.Models.Product { Name = "Product3", Price = 30 }
    );

    context.SaveChanges();
}

enter image description here

Upvotes: 1

Related Questions