Dotnet api graphql linux

From bibbleWiki
Jump to navigation Jump to search

Creating a Project

Copy the project from RAID array

Sorry no help here

Add the packages

dotnet add package GraphQL

Create Database

Change the connection string

"CarvedRock": "Server=.;Database=CarvedRock;Trusted_Connection=False;User Id=test;Password=nottherealone"

Update the database

dotnet ef database update

Run the Site

To the site and look at https://localhost:5001/ui/playground

Creating a Schema

Create a Product Graph Type

Create Fields based on Meta Fields within ObjectGraphType

    public class ProductType: ObjectGraphType<Product>
    {
        public ProductType()
        {
            Field(t => t.Id);
            Field(t => t.Name).Description("The name of the product");
            Field(t => t.Description);
        }
    }

Create a Query

Create a Query which looks in the repository (DB) and gets all of the products.

    public class CarvedRockQuery: ObjectGraphType
    {
        public CarvedRockQuery(ProductRepository productRepository)
        {
            Field<ListGraphType<ProductType>>(
                "products", 
                resolve: context => productRepository.GetAll()
            );
        }
    }

Create a Schema for the Carved Rock Query

This holds the queries the schema supports. In our case the CarvedRockQuery created above

    public class CarvedRockSchema: Schema
    {
        public CarvedRockSchema(IDependencyResolver resolver): base(resolver)
        {           
            Query = resolver.Resolve<CarvedRockQuery>();
        }
    }

Configuring ASP .NET Core

In the Startup, Add the dependency resolver to the services, add the Schema and GraphQL configuring options.

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));

            services.AddScoped<CarvedRockSchema>();

            services.AddGraphQL(o => { o.ExposeExceptions = false; })
                .AddGraphTypes(ServiceLifetime.Scoped);

Add the GraphQL to the middleware

        public void Configure(
            IApplicationBuilder app, 
            CarvedRockDbContext dbContext)
        {
           app.UseGraphQL<CarvedRockSchema>();
           app.UseGraphQLPlayground(new GraphQLPlaygroundOptions());

Creating a GraphQL API

Adding Scalar Type

  • Create the enumerator
  • Derive a Graph QL Type to hold the Enum
  • Add to Graph QL Type
  • Add the new Graph QL Type to the existing Type

Create the enumerator

    public enum ProductType
    {
        Boots,
        ClimbingGear,
        Kayaks
    }

Derive a Graph QL Type to hold the Enum

    public class ProductTypeEnumType: EnumerationGraphType<Data.ProductType>
    {
        public ProductTypeEnumType()
        {
            Name = "Type";
            Description = "The type of product";
        }
    }

Add the new Graph QL Type to the existing Type

    public class ProductType: ObjectGraphType<Product>
    {
        public ProductType()
        {
            Field(t => t.Id);
...
            Field<ProductTypeEnumType>("Type", "The type of product");
...

Adding Complex Type

Steps Required to Add a Complex Type

  • Create a class
  • Add a Accessor to the DbContext
  • Add the new Repository Class to Startup
  • Add a GraphQL Type
  • Create a Repository Class
  • Add the Graph QL Type to the Product
  • Update the Database

Create the class

    public class ProductReview
    {
        public int Id {get; set;}
        public int ProductId {get; set;}
        public Product Product {get; set;}
        [StringLength(200), Required]
        public string Title {get; set;}
        public string Review {get; set;}
    }

Add a Accessor to the DbContext

    public class CarvedRockDbContext: DbContext
    {
...
        public DbSet<ProductReview> ProductReviews { get; set; }
    }

Add a GraphQL Type

    public class ProductReviewType : ObjectGraphType<ProductReview>
    {
        public ProductReviewType()
        {
            Field(t => t.Id);
            Field(t => t.Title);
            Field(t => t.Review);
        }
    }

Create a Repository Class

    public class ProductReviewRepository
    {
        private readonly CarvedRockDbContext _dbContext;

        public ProductReviewRepository(CarvedRockDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IEnumerable<ProductReview>> GetForProduct(int productId)
        {
            return await _dbContext.ProductReviews.Where(pr => pr.ProductId == productId).ToListAsync();
        }

        public async Task<ILookup<int, ProductReview>> GetForProducts(IEnumerable<int> productIds)
        {
            var reviews = await _dbContext.ProductReviews.Where(pr => productIds.Contains(pr.ProductId)).ToListAsync();
            return reviews.ToLookup(r => r.ProductId);
        }
    }

Add the new Repository Class to Startup

        public void ConfigureServices(IServiceCollection services)
        {
...
            services.AddScoped<ProductRepository>();
            services.AddScoped<ProductReviewRepository>();

Add the Graph QL Type to the Product

    public class ProductType : ObjectGraphType<Product>
    {
        public ProductType(ProductReviewRepository reviewRepository)
        {
            Field(t => t.Id);
            Field(t => t.Name).Description("The name of the product");
...
            Field<ListGraphType<ProductReviewType>> (
                "Reviews",
                resolve : context =>  reviewRepository.GetForProduct(context.Source.Id)
            );

Update the Database

This did not work for me the first time so this might be wrong. Whatever you need to make sure the table exists on the database.

dotnet ef migrations add AddReviews
dotnet ef database update

DataLoader

Introduction

Might be being unfair but this looks the same a creating a view of tables to cache parent /child relationships

  • Add to Starup Services
  • Define a Lookup Function in Repository
  • Inject Loader Accessor into Graph QL Type=
  • For the Graph QL field, define the loader

Add to Starup Services

        public void ConfigureServices(IServiceCollection services)
        {
...
            services.AddGraphQL(o => { o.ExposeExceptions = false; })
                .AddGraphTypes(ServiceLifetime.Scoped)
                .AddDataLoader();
        }

Define a Lookup Function in Repository

    public class ProductReviewRepository
    {
...
        public async Task<ILookup<int, ProductReview>> GetForProducts(IEnumerable<int> productIds)
        {
            var reviews = await _dbContext.ProductReviews.Where(pr => productIds.Contains(pr.ProductId)).ToListAsync();
            return reviews.ToLookup(r => r.ProductId);
        }

Inject Loader Accessor into Graph QL Type

    public class ProductType : ObjectGraphType<Product>
    {
        //        public ProductType(ProductReviewRepository reviewRepository)
        public ProductType(
            ProductReviewRepository reviewRepository,
            IDataLoaderContextAccessor dataLoaderAccessor)
        {
...

For the Graph QL field, define the loader

    public class ProductType : ObjectGraphType<Product>
    {
...
            Field<ListGraphType<ProductReviewType>>(
                "Reviews",
                resolve: context =>
                {
                    // Create the loader passing
                    // Name of the loader Key
                    // The Function to call on the repository
                    //     this returns a look up by product id, ProductReview
                    var loader =
                        dataLoaderAccessor.Context.GetOrAddCollectionBatchLoader<int, ProductReview>(
                                "GetReviewsByProductId",
                                reviewRepository.GetForProducts);

                    return loader.LoadAsync(context.Source.Id);
                });

Add Arguments To Query

Introduction

  • Add Support to Repository to Get One Item
  • Create A Query For Graph QL Type

Add Support to Repository to Get One Item

        public async Task<Product> GetOne(int id)
        {
            return await _dbContext.Products.SingleOrDefaultAsync(p => p.Id == id);
        }

Create A Query For Graph QL Type

Add the arguments you want with the name for the argument along with a context to suppport it.

    public class CarvedRockQuery : ObjectGraphType
    {
        public CarvedRockQuery(ProductRepository productRepository)
        {
...
            Field<ProductType>(
                "product",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "id" }),
                resolve: context =>
                {
                    var id = context.GetArgument<int>("id");
                    return productRepository.GetOne(id);
                });

Creating A Mutation

  • Create Repository function to support Mutation
  • Create an Input Type
  • Create a Mutation
  • Add To Schema
  • Test in Playground

Create Repository function to support Mutation

    public class ProductReviewRepository
    {
...
        public async Task<ProductReview> AddReview(ProductReview review)
        {
            _dbContext.ProductReviews.Add(review);
            await _dbContext.SaveChangesAsync();
            return review;
        }

Create an Input Type

Create an Input Type. This defines the fields associated with the type.

    public class ProductReviewInputType: InputObjectGraphType
    {
        public ProductReviewInputType()
        {
            Name = "reviewInput";
            Field<NonNullGraphType<StringGraphType>>("title");
            Field<StringGraphType>("review");
            Field<NonNullGraphType<IntGraphType>>("productId");
        }
    }

Create a Mutation

Create a Mutation which specifies the arguments and the type to be used. This defines the Input Type to use and the repository function which will support it.

   public class CarvedRockMutation : ObjectGraphType
    {
        public CarvedRockMutation(ProductReviewRepository reviewRepository)
        {
            FieldAsync<ProductReviewType>(
                "createReview",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<ProductReviewInputType>> {Name = "review"}),
                resolve: async context =>
                {
                    var review = context.GetArgument<ProductReview>("review");
                    return await context.TryAsyncResolve(
                        async c => await reviewRepository.AddReview(review));
                });
        }
    }

Add To Schema

    public class CarvedRockSchema : Schema
    {
        public CarvedRockSchema(IDependencyResolver resolver) : base(resolver)
        {
            Query = resolver.Resolve<CarvedRockQuery>();
            Mutation = resolver.Resolve<CarvedRockMutation>();
        }

Test in Playground

Create the mutation

mutation ($review: reviewInput!) {
    createReview(review: $review) { 
      id
      title
    }
}

Create the arguments

{
   "review": {
      "title": "Test is fab",
      "productId": 1
   }
}

Create Subscriptions

Create Subscriptions

Subscriptions allow the consumer to be notified when an event occurs. E.g. someone adds a review.

  • Create a Message
  • Create a Service to send notifications
  • Create a Graph QL Message Type
  • Create a Subscription
  • Add Subscription to Schema
  • Configure ASP .NET Core

Create a Message

    public class ReviewAddedMessage
    {
        public int ProductId { get; set; }
        public string Title { get; set; }
    }

Create a Service to send notifications

    public class ReviewMessageService
    {
        private readonly ISubject<ReviewAddedMessage> _messageStream = new ReplaySubject<ReviewAddedMessage>(1);

        public ReviewAddedMessage AddReviewAddedMessage(ProductReview review)
        {
            var message = new ReviewAddedMessage
            {
                ProductId = review.ProductId,
                Title = review.Title
            };
            _messageStream.OnNext(message);
            return message;
        }

        public IObservable<ReviewAddedMessage> GetMessages()
        {
            return _messageStream.AsObservable();
        }
    }

Change Mutation to notify Message Service

    public class CarvedRockMutation : ObjectGraphType
    {
        public CarvedRockMutation(ProductReviewRepository reviewRepository,
            ReviewMessageService reviewMessageService)
        {
            FieldAsync<ProductReviewType>(
                "createReview",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<ProductReviewInputType>> { Name = "review" }),
                resolve: async context =>
                {
                    var review = context.GetArgument<ProductReview>("review");
                    await reviewRepository.AddReview(review);
                    reviewMessageService.AddReviewAddedMessage(review);
                    return review;
                });
...

Create a Graph QL Message Type

    public class ReviewAddedMessageType : ObjectGraphType<ReviewAddedMessage>
    {
        public ReviewAddedMessageType()
        {
            Field(t => t.ProductId);
            Field(t => t.Title);
        }
    }

Create a Subscription

This is a type for the schema and defines type and subscriber.

    public class CarvedRockSubscription: ObjectGraphType
    {
        public CarvedRockSubscription(ReviewMessageService messageService)
        {
            Name = "Subscription";
            AddField(new EventStreamFieldType
            {
                Name = "reviewAdded",
                Type = typeof(ReviewAddedMessageType),
                Resolver = new FuncFieldResolver<ReviewAddedMessage>(c => c.Source as ReviewAddedMessage),
                Subscriber = new EventStreamResolver<ReviewAddedMessage>(c => messageService.GetMessages())
            });
        }
    }

Add Subscription to the Schema

    public class CarvedRockSchema: Schema
    {
        public CarvedRockSchema(IDependencyResolver resolver): base(resolver)
        {
...
            Subscription = resolver.Resolve<CarvedRockSubscription>();
        }

Configure ASP .NET Core

Add Packages

Add the GraphQL Transports Package

dotnet add package GraphQL.Server.Transports.WebSockets

Configure Startup

Configure the Service

        public void ConfigureServices(IServiceCollection services)
        {
...
            services.AddGraphQL(o => { o.ExposeExceptions = false; })
                .AddGraphTypes(ServiceLifetime.Scoped)
                .AddDataLoader()
                .AddWebSockets();
        }

Configure the middleware for Web Sockets and GraphQL Websockets

       public void Configure(IApplicationBuilder app, CarvedRockDbContext dbContext)
        {
            app.UseWebSockets();
            app.UseGraphQLWebSockets<CarvedRockSchema>("/graphql");

Client Example

The GraphQL Client is provided for .Net. This implements an interface very similar to the server. An example of reading and writing it shown below.

    public class ProductGraphClient
    {
        private readonly GraphQLClient _client;

        public ProductGraphClient(GraphQLClient client)
        {
            _client = client;
        }


        public async Task<ProductModel> GetProduct(int id)
        {
            var query = new GraphQLRequest
            {
                Query = @" 
                query productQuery($productId: ID!)
                { product(id: $productId) 
                    { id name price rating photoFileName description stock introducedAt 
                      reviews { title review }
                    }
                }",
                Variables = new {productId = id}
            };
            var response = await _client.PostAsync(query);
            return response.GetDataFieldAs<ProductModel>("product");
        }

        public async Task<ProductReviewModel> AddReview(ProductReviewInputModel review)
        {
            var query = new GraphQLRequest
            {
                Query = @" 
                mutation($review: reviewInput!)
                {
                    createReview(review: $review)
                    {
                        id
                    }
                }",
                Variables = new { review }
            };
            var response = await _client.PostAsync(query);
            return response.GetDataFieldAs<ProductReviewModel>("createReview");
        }