GraphQL For .NET
Tags: showdev, dotnet, graphql
Published: 2022-07-03 00:41:00

---
title: GraphQL For .NET
published: true
description: A .NET GraphQL examples project
tags: showdev, dotnet, graphql
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgkb2knxvk15kqu0wubs.png
---

I have recently created [a project on Github](https://github.com/drminnaar/graphql-dotnet-series) that demonstrates how to build a GraphQL API through the use of a number of examples. I created the project in order to help serve as a guide for .NET developers that are new to GraphQL and would like to learn how to get started. As a final step to this project, I also demonstrate how to create the required infrastructure in Microsoft Azure to host the GraphQL API.

&nbsp;

---

&nbsp;

## Key Takeaways

&nbsp;

- Project Overview
- Learn how to build a .NET 6 GraphQL API from scratch
- Learn how to define GraphQL queries, and mutations
- Learn how to provide validation and error handling
- Add EntityFramework support both in-memory and SQLite database
- Add support for query projection, filtering, sorting, and paging
- Provision Azure App Service using Azure CLI, Azure, Powershell, and Azure Bicep

&nbsp;

---

&nbsp;

## Project Overview

&nbsp;

- This project explores how to build and deploy a _[GraphQL]_ API in a number of easy to follow examples.
- Each example introduces a new concept that is clear, small, and easy to understand
- All examples are built using _[.NET 6 (LTS)]_ and _[ChilliCream Hot Chocolate (Open-Source GraphQL Server for Microsoft .NET platform)]_
- The GraphQL API is deployed to _Azure App Service_
- The project demonstrates how to provision an _Azure App Service_ using _[Azure CLI]_, _[Azure Powershell]_, and _[Azure Bicep]_

&nbsp;

All examples are based on a fictitious company called _MyView_, that provides a service to view games and game ratings. It also allows reviewers to post and delete game reviews. Therefore, in the examples that follow, I demonstrate how to build a GraphQL API that provides the following capabilities:

&nbsp;

- List games
- List games with associated reviews
- Find a game by id
- Post a game review
- Delete a game review

&nbsp;

The details of the provided examples are as follows:

&nbsp;

- 📘 [Example 1]
  &nbsp;
  - Create empty API project
  - Add GraphQL Server support through the use of the [ChilliCream Hot Chocolate] nuget package
  - Create a games query
  - Create a games mutation
  - Add metadata to support schema documentation

&nbsp;
- 📘 [Example 2]
  &nbsp;
  - Add global error handling
  - Add input validation

&nbsp;
- 📘 [Example 3]
  &nbsp;
  - Add EntityFramework support with an in-memory database
  - Add support for query filtering, sorting, and paging
  - Add projection support
  - Add query type extensions to allow splitting queries into multiple classes

&nbsp;
- 📘 [Example 4]
  &nbsp;
  - Change project to use SQlite instead of an in-memory database

&nbsp;
Lastly, I provide the instructions on how to provision an Azure App Service and deploy the GraphQL API to it. The details are as follows:

&nbsp;

- Provision Azure App Service using Azure CLI
- Provision Azure App Service using Azure Powershell
- Provision Azure App Service using Azure Bicep
- Deploy GraphQL API

&nbsp;

📘 [Find all the infrastructure as code here](https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac)

&nbsp;

---

&nbsp;

## Required Setup and Tools

&nbsp;

It is recommended that the following tools and frameworks are installed before trying to run the examples:

&nbsp;

- [.NET 6 SDK]

  All code in this example is developed with C# 10 using the latest cross-platform .NET 6 framework.

  See the [.NET 6 SDK] official website for details on how to download and setup for your development environment.
  &nbsp;

- [Visual Studio Code]

  Find more information on [Visual Studio Code] with [relevant C# and .NET extensions](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-pack).
  &nbsp;

- [Bicep Installation and Docs](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/install)

  Everything you need to install and configure your windows, linux, and macos environment
  &nbsp;

- [Official Bicep Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)

  Provides Bicep language support
  &nbsp;

- [JQ](https://stedolan.github.io/jq/)

  jq is a lightweight and flexible command-line JSON processor
  &nbsp;

- [Azure Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack)

  A package of all the Visual Studio Code extensions that you will need to work with Azure
  &nbsp;

- [Azure CLI Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.azurecli)

  Tools for developing and running commands of the Azure CLI


&nbsp;

---

&nbsp;

## Example 1

&nbsp;

[Example 1] demonstrates how to get started in terms of creating and running a GraphQL Server.

&nbsp;

**📘 [The full example can be found on Github.](https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-1)**

&nbsp;

### Step 1 - Create project

&nbsp;

```bash

# create empty solution
dotnet new sln --name MyView

# create basic webapi project using minimal api's
dotnet new webapi --no-https --no-openapi --framework net6.0 --use-minimal-apis --name MyView.Api

# add webapi project to solution
dotnet sln ./MyView.sln add ./MyView.Api

```

&nbsp;

### Step 2 - Add GraphQL Capability to Web API

In this section, we turn the web application into a GraphQL API by installing the [Chillicream Hot Chocolate] GraphQL nuget package called `HotChocolate.AspNetCore`. This package contains the Hot Chocolate GraphQL query execution engine and query validation.

```bash

# From the /MyView.Api project folder, type the following commands:

# Add HotChocolate packages
dotnet add package HotChocolate.AspNetCore --version 12.11.1

```

&nbsp;

### Step 3 - Add Types

&nbsp;

We are building a GraphQL API to allow querying and managing game reviews. Therefore, to get started we need to create a _Game_ and _GameReview_ type. A few things to note about the types we create:

&nbsp;
- I append the suffix "Dto" to indicate that the type is a [Data Transfer Object](https://en.wikipedia.org/wiki/Data_transfer_object). I use this to make it explicitly clear as to the intent of the type.
- The `GraphQLName` attribute is used to rename the type for public consumption. The types will be exposed as `Game` and `GameReview` as opposed to `GameDto` and `GameReviewDto`
- The `GraphQLDescription` attribute is used to provide a description of the type that is used by the GraphQL server to provide more detailed schema information
- The types are defined as a _record_ type, but can be declared as classes. I have chosen to use the _record_ type as it allows me to define data contracts that are immutable and support value based equality comparison (should I require it).

&nbsp;

```csharp

[GraphQLDescription("Basic game information")]
[GraphQLName("Game")]
public sealed record GameDto
{
   public GameDto()
   {
      Reviews = new List<GameReviewDto>();
   }

   [GraphQLDescription("A unique game identifier")]
   public Guid GameId { get; set; }

   [GraphQLDescription("A brief description of game")]
   public string Summary { get; set; } = string.Empty;

   [GraphQLDescription("The name of the game")]
   public string Title { get; set; } = string.Empty;

   [GraphQLDescription("The date that game was released")]
   public DateTime ReleasedOn { get; set; }
   
   [GraphQLDescription("A list of game reviews")]
   public ICollection<GameReviewDto> Reviews { get; set; } = new List<GameReviewDto>();
}

// GameReviewDto.cs

[GraphQLDescription("Game review information")]
[GraphQLName("GameReview")]
public sealed record GameReviewDto
{
   [GraphQLDescription("Game identifier")]
   public Guid GameId { get; set; }

   [GraphQLDescription("Reviewer identifier")]
   public Guid ReviewerId { get; set; }

   [GraphQLDescription("Game rating")]
   public int Rating { get; set; }

   [GraphQLDescription("A brief description of game experience")]
   public string Summary { get; set; } = string.Empty;
}

```

&nbsp;

### Step 4 - Create First Query

One or more queries must be defined in order to support querying data.

&nbsp;

For our first example we will create a query to support retrieving of _game related (games, reviews)_ data. A few things to note are as follows:

&nbsp;

- At this stage we do not not have a database configured and will instead use in-memory data to demonstrate fetching of data.
- I use the `GraphQLDescription` attribute to provide GraphQL schema documentation 

&nbsp;

```csharp

// In this example, we use GameData (in-memory list) to provide sample dummy game related data

[GraphQLDescription("Query games")]
public sealed class GamesQuery
{
   [GraphQLDescription("Get list of games")]
   public IEnumerable<GameDto> GetGames() => GameData.Games;

   [GraphQLDescription("Find game by id")]
   public GameDto? FindGameById(Guid gameId) =>
      GameData.Games.FirstOrDefault(game => game.GameId == gameId);
}

```

&nbsp;

### Step 5 - Create First Mutation

&nbsp;

We will add 2 operations. One to create, and one to remove a game review. A few things to note are as follows:

&nbsp;

- At this stage we do not not have a database configured and will instead use in-memory data to demonstrate saving of data.
- The `GraphQLDescription` attribute is used to provide GraphQL schema documentation

&nbsp;

```csharp

[GraphQLDescription("Manage games")]
public sealed class GamesMutation
{
   [GraphQLDescription("Submit a game review")]
   public GameReviewDto SubmitGameReview(GameReviewDto gameReview)
   {
      var game = GameData
         .Games
         .FirstOrDefault(game => game.GameId == gameReview.GameId)
         ?? throw new Exception("Game not found");

      var gameReviewFromDb = game.Reviews.FirstOrDefault(review =>
         review.GameId == gameReview.GameId && review.ReviewerId == gameReview.ReviewerId);

      if (gameReviewFromDb is null)
      {
         game.Reviews.Add(gameReview);
      }
      else
      {
         gameReviewFromDb.Rating = gameReview.Rating;
         gameReviewFromDb.Summary = gameReview.Summary;
      }

      return gameReview;
   }

   [GraphQLDescription("Remove a game review")]
   public GameReviewDto DeleteGameReview(Guid gameId, Guid reviewerId)
   {
      var game = GameData
         .Games
         .FirstOrDefault(game => game.GameId == gameId)
         ?? throw new Exception("Game not found");

      var gameReviewFromDb = game
         .Reviews
         .FirstOrDefault(review => review.GameId == gameId && review.ReviewerId == reviewerId)
         ?? throw new Exception("Game review not found");

      game.Reviews.Remove(gameReviewFromDb);

      return gameReviewFromDb;
   }
}

```

&nbsp;

### Step 6 - Configure API with GraphQL services

&nbsp;

We need to configure the API to use the ChilliCream Hotchocolate GraphQL services and middleware.

&nbsp;

```csharp

builder
   .Services
   .AddGraphQLServer() // Adds a GraphQL server configuration to the DI
   .AddMutationType<GamesMutation>() // Add GraphQL root mutation type
   .AddQueryType<GamesQuery>() // Add GraphQL root query type
   .ModifyRequestOptions(options =>
   {
      // allow exceptions to be included in response when in development
      options.IncludeExceptionDetails = builder.Environment.IsDevelopment();
   });

```

&nbsp;

### Step 7 - Map GraphQL Endpoint

&nbsp;

```csharp

app.MapGraphQL("/"); // Adds a GraphQL endpoint to the endpoint configurations

```

&nbsp;

### Step 8 - Run GraphQL API

&nbsp;

Run the Web API project by typing the following command:

&nbsp;

```pwsh

# From the /MyView.Api project folder, type the following commands:

dotnet run

```

&nbsp;

![Screenshot](https://user-images.githubusercontent.com/33935506/175770259-3c9be357-0e1e-49b7-ab81-b69c5bb1d077.png)

&nbsp;

#### Schema Information

&nbsp;

Selecting the *_Browse Schema_* options allows one to view the schema information for queries, mutations, and objects as can be seen by the following screen shots.

&nbsp;

##### Objects Schema

&nbsp;

![object-schema](https://user-images.githubusercontent.com/33935506/176976392-190135c5-c9a8-4f45-8cea-c0592d358dfa.png)

&nbsp;

##### Queries Schema

&nbsp;

![query-schema](https://user-images.githubusercontent.com/33935506/176976394-3afcfcd2-bd49-4efa-abc7-c26652efeff8.png)

&nbsp;

##### Mutations Schema

&nbsp;

![mutation-schema](https://user-images.githubusercontent.com/33935506/176976391-9477ebe8-1113-4a74-a6dc-2cbf6ec3b214.png)

&nbsp;

### Write GraphQL Queries

&nbsp;

```yaml

# List all games and associated reviews:

query listGames {
  games {
    gameId
    title
    releasedOn
    summary
    reviews {
      reviewerId
      rating
      summary
    }
  }
}

```

&nbsp;

```yaml

# Find a game (with reviews) by game id

# Add the following JSON snippet to the variables section:

{
  "gameId": "8f7e254f-a6ce-4f13-a44c-8f102a17f2f5"
}

# Write query to find a game (with reviews)

query findGameById ($gameId: UUID!) {
  findGameById (gameId: $gameId) {
    gameId
    title
    reviews {
      reviewerId
      rating
      summary
    }
  }
}

```

&nbsp;

![queries](https://user-images.githubusercontent.com/33935506/176976393-ebe9027f-11c8-402b-b988-a4a566ed0074.png)

&nbsp;

### Write Mutations

&nbsp;

```yaml

# Submit a game review

# Define the following JSON in the 'Variables' section:

{
  "gameReview": {
    "gameId": "8f7e254f-a6ce-4f13-a44c-8f102a17f2f5",
    "reviewerId": "025fefe4-e2de-4765-9af3-f5dcd6d47458",
    "rating": 75,
    "summary": "Enim quidem enim. Eius aut velit voluptas."
  },
}

# Write a mutation to submit a game review

mutation submitGameReview($gameReview: GameReviewInput!) {
  submitGameReview(gameReview: $gameReview) {
    gameId
    reviewerId
    rating
    summary
  }
}

# Write a mutation to DELETE a game review

# Define the following JSON in the 'variables' section:

{
    "gameId": "8f7e254f-a6ce-4f13-a44c-8f102a17f2f5",
    "reviewerId": "025fefe4-e2de-4765-9af3-f5dcd6d47458"
}

# Write mutation to delete game review

mutation deleteGameReview($gameId: UUID!, $reviewerId: UUID!) {
  deleteGameReview(gameId: $gameId, reviewerId: $reviewerId) {
    gameId
    reviewerId
    rating
    summary
  }
}

```

&nbsp;

![mutations](https://user-images.githubusercontent.com/33935506/176976389-6bc4da01-32eb-455a-aca6-f12e9f3b116a.png)

&nbsp;

---

&nbsp;

## Example 2

[Example 2] demonstrates the following concepts:

&nbsp;
- Implement input validations (like validating a review that is submitted)
- Implement global error handling

&nbsp;

**📘 [The full example can be found on Github.](https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-2)**

&nbsp;

### Implement Input Validation

&nbsp;

Currently, we have no validation in place when submitting a `GameReview`. In this section, we are going to provide input validation through the use of the [Fluent Validation] nuget package.

&nbsp;

#### Step 1 - Add Fluent Validation packages

&nbsp;

```bash

dotnet add package FluentValidation.AspNetCore --version 11.1.1
dotnet add package FluentValidation.DependencyInjectionExtensions --version 11.1.0

```

#### Step 2 - Create Validator

&nbsp;

We need to define a class that will handle validation for the `GameReview` type. To do this, we create a `GameReviewValidator` as follows:

&nbsp;

```csharp

public sealed class GameReviewValidator : AbstractValidator<GameReviewDto>
{
   public GameReviewValidator()
   {
      RuleFor(e => e.GameId)
        .Must(gameId => GameData.Games.Any(game => game.GameId == gameId))
        .WithMessage(e => $"A game having id '{e.GameId}' does not exist");

      RuleFor(e => e.Rating)
        .LessThanOrEqualTo(100)
        .GreaterThanOrEqualTo(0);

      RuleFor(e => e.Summary)
        .NotNull()
        .NotEmpty()
        .MinimumLength(20)
        .MaximumLength(500);
   }
}

```

&nbsp;

#### Step 3 -  Use GameReviewValidator

&nbsp;

- The `GameReviewValidator` is used in the `GamesMutation` class to validate the `GameReviewDto`.

- We use constructor dependency injection to provide the `GameReviewValidator`

- The code `_validator.ValidateAndThrow(gameReview);` will execute the validation rules defined for `GameReviewDto` and throw a validation exception if there are any validation failures.

&nbsp;

```csharp

[GraphQLDescription("Manage games")]
public sealed class GamesMutation
{
   private readonly AbstractValidator<GameReviewDto> _validator;

   public GamesMutation(AbstractValidator<GameReviewDto> validator)
   {
      _validator = validator ?? throw new ArgumentNullException(nameof(validator));
   }

   [GraphQLDescription("Submit a game review")]
   public GameReviewDto SubmitGameReview(GameReviewDto gameReview)
   {
      // use fluent validator
      _validator.ValidateAndThrow(gameReview);

      .
      .
      .
   }
}

```

&nbsp;

#### Step 4 - Add Validation Service

&nbsp;

```csharp

// configure fluent validation for GameReviewDto
builder.Services.AddTransient<AbstractValidator<GameReviewDto>, GameReviewValidator>();

```

&nbsp;

### Configure Global Error Handling

&nbsp;

We are throwing exceptions in a number of areas. Those exceptions will be allowed to bubble all the way to the client if allowed to. The following list highlights the areas where we are throwing exceptions along with the GraphQL response resultiing from the exception:

&nbsp;

- Validation  
  &nbsp;
  ```csharp

  public GameReviewDto SubmitGameReview(GameReviewDto gameReview)
  {
      // use fluent validator
      _validator.ValidateAndThrow(gameReview);
      .
      .
      .
  }

  ```
  &nbsp;
  ```json
  
  {
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "locations": [
                {
                "line": 2,
                "column": 3
                }
            ],
            "path": [
                "submitGameReview"
            ],
            "extensions": {
                "message": "Validation failed: \r\n -- Rating: 'Rating' must be greater than or equal to '0'. Severity: Error\r\n -- Summary: 'Summary' must not be empty. Severity: Error\r\n -- Summary: The length of 'Summary' must be at least 20 characters. You entered 0 characters. Severity: Error",
                "stackTrace": "   at FluentValidation.AbstractValidator`..."
                }
        }
    ]
  }

  ```

&nbsp;

- Delete Game  
  &nbsp;  
  ```csharp

  public GameReviewDto DeleteGameReview(Guid gameId, Guid reviewerId)
  {
      var game = GameData
         .Games
         .FirstOrDefault(game => game.GameId == gameId)
         ?? throw GameNotFoundException.WithGameId(gameId);
         .
         .
         .
  }

  ```
  &nbsp;
  ```json

  {
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "locations": [
                {
                "line": 11,
                "column": 3
                }
            ],
            "path": [
                "deleteGameReview"
            ],
            "extensions": {
                "message": "Exception of type 'MyView.Api.Games.GameNotFoundException' was thrown.",
                "stackTrace": "   at MyView.Api.Games.GamesMutation.DeleteGameReview ..."
            }
        }    
    ]
  }

  ```

&nbsp;

#### Step 1 - Create Custom Error Filter

&nbsp;

We create a new class called `ServerErrorFilter` that inherits from the `IErrorFilter` interface as follows:

&nbsp;

```csharp

public sealed class ServerErrorFilter : IErrorFilter
{
   private readonly ILogger _logger;
   private readonly IWebHostEnvironment _environment;

   public ServerErrorFilter(ILogger logger, IWebHostEnvironment environment)
   {
      _logger = logger ?? throw new ArgumentNullException(nameof(logger));
      _environment = environment ?? throw new ArgumentNullException(nameof(environment));
   }

   public IError OnError(IError error)
   {
      _logger.LogError(error.Exception, error.Message);

      if (_environment.IsDevelopment())
         return error;

      return ErrorBuilder
         .New()
         .SetMessage("An unexpected server fault occurred")
         .SetCode(ServerErrorCode.ServerFault)
         .SetPath(error.Path)
         .Build();
   }
}

```

&nbsp;

#### Step 2 - Add ValidationException Handler

&nbsp;

```csharp

public sealed class ServerErrorFilter : IErrorFilter
{
    .
    .
    .

    public IError OnError(IError error)
    {
        if (error.Exception is ValidationException validationException)
        {
            _logger.LogError(validationException, "There is a validation error");

            var errorBuilder = ErrorBuilder
                .New()
                .SetMessage("There is a validation error")
                .SetCode(ServerErrorCode.BadUserInput)
                .SetPath(error.Path);

            foreach (var validationFailure in validationException.Errors)
            {
                errorBuilder.SetExtension(
                    $"{ServerErrorCode.BadUserInput}_{validationFailure.PropertyName.ToUpper()}",
                    validationFailure.ErrorMessage);
            }

            return errorBuilder.Build();
        }

        .
        .
        .
    }
}

```

&nbsp;

#### Step 3 - Add GameNotFoundException Handler

&nbsp;

```csharp

public sealed class ServerErrorFilter : IErrorFilter
{
    .
    .
    .

    public IError OnError(IError error)
    {
        .
        .
        .

        if (error.Exception is GameNotFoundException gameNotFoundException)
        {
            _logger.LogError(gameNotFoundException, "Game not found");

            return ErrorBuilder
                .New()
                .SetMessage($"A game having id '{gameNotFoundException.GameId} could not found")
                .SetCode(ServerErrorCode.ResourceNotFound)
                .SetPath(error.Path)
                .SetExtension($"{ServerErrorCode.ResourceNotFound}_GAME_ID", gameNotFoundException.GameId)
                .Build();
        }

        .
        .
        .
    }
}

```

&nbsp;

#### Step 4 - Add GameReviewNotFoundException Handler

&nbsp;

```csharp

public sealed class ServerErrorFilter : IErrorFilter
{
    .
    .
    .

    public IError OnError(IError error)
    {
        .
        .
        .
        
        if (error.Exception is GameReviewNotFoundException gameReviewNotFoundException)
        {
            _logger.LogError(gameReviewNotFoundException, "Game review not found");

            return ErrorBuilder
                .New()
                .SetMessage($"A game review having game id '{gameReviewNotFoundException.GameId}' and reviewer id '{gameReviewNotFoundException.ReviewerId}' could not found")
                .SetCode(ServerErrorCode.ResourceNotFound)
                .SetPath(error.Path)
                .SetExtension($"{ServerErrorCode.ResourceNotFound}_GAME_ID", gameReviewNotFoundException.GameId)
                .SetExtension($"{ServerErrorCode.ResourceNotFound}_REVIEWER_ID", gameReviewNotFoundException.ReviewerId)
                .Build();
        }

        .
        .
        .
    }
}

```

&nbsp;

#### Step 5 - Configure GraphQL Service to support Error Filter

&nbsp;

```csharp

builder
   .Services
   .AddGraphQLServer()
   // Add global error handling
   .AddErrorFilter(provider =>
      {
         return new ServerErrorFilter(
            provider.GetRequiredService<ILogger<ServerErrorFilter>>(),
            builder.Environment);
      })
      .
      .
      .
   .ModifyRequestOptions(options =>
   {
      options.IncludeExceptionDetails = builder.Environment.IsDevelopment();
   });

```

&nbsp;

#### Step 6 - Test Error Handling

&nbsp;

- Validation  
  &nbsp;
  ```json

  {
    "errors": [
        {
            "message": "There is a validation error",
            "path": [
                "submitGameReview"
            ],
            "extensions": {
                "code": "BAD_USER_INPUT",
                "BAD_USER_INPUT_RATING": "'Rating' must be greater than or equal to '0'.",
                "BAD_USER_INPUT_SUMMARY": "The length of 'Summary' must be at least 20 characters. You entered 0 characters."
            }
        }
    ]  
  }

  ```

&nbsp;

- GameNotFoundException  
  &nbsp;  
  ```json

  {
    "errors": [
        {
            "message": "A game having id '8f7e254f-a6ce-4f13-a44c-8f102a17f2f4 could not found",
            "path": [
                "deleteGameReview"
            ],
            "extensions": {
                "code": "RESOURCE_NOT_FOUND",
                "RESOURCE_NOT_FOUND_GAME_ID": "8f7e254f-a6ce-4f13-a44c-8f102a17f2f4"
            }
        }
    ]
  }

  ```

&nbsp;

- GameReviewNotFoundException  
  &nbsp;  
  ```json

  {
      "errors": [
          {
            "message": "A game review having game id '8f7e254f-a6ce-4f13-a44c-8f102a17f2f5' and reviewer id '8b019864-4af2-4606-88b5-13e5eb62ff4e' could not found",
            "path": [
                "deleteGameReview"
            ],
            "extensions": {
                "code": "RESOURCE_NOT_FOUND",
                "RESOURCE_NOT_FOUND_GAME_ID": "8f7e254f-a6ce-4f13-a44c-8f102a17f2f5",
                "RESOURCE_NOT_FOUND_REVIEWER_ID": "8b019864-4af2-4606-88b5-13e5eb62ff4e"
            }
          }
      ]
  }
  
  ```

&nbsp;

---

&nbsp;

## Example 3

&nbsp;

For _[Example 3]_, we extend _[Example 2]_ to cover the following topics:

&nbsp;

- Add EntityFramework support with in-memory database
- Add support for query projection, filtering, sorting, and paging
- Add query type extensions to allow splitting queries into multiple classes
- Add database seeding (seed with fake data)

&nbsp;

### Add EntityFramework Support

&nbsp;

- Add required nuget packages
- Create the following entities that will be used to help represent the data stored in our database
  - `Game`
  - `GameReview`
  - `Reviewer`
- Create a context that will serve as our Repository/UnitOfWork called `AppDbContext`
- Configure data services

&nbsp;

#### Add EntityFramework Packages

&nbsp;

```bash

# add required EntityFramework packages
dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.6
dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 6.0.6

# add HotChocolate package providing EntityFramework support
dotnet add package HotChocolate.Data.EntityFramework --version 12.11.1

```

&nbsp;

#### Define Entities

&nbsp;

```csharp

public class Game
{
   public Guid Id { get; set; }
   public string Title { get; set; } = string.Empty;
   public string Summary { get; set; } = string.Empty;
   public DateTime ReleasedOn { get; set; }
   public ICollection<GameReview> Reviews { get; set; } = new HashSet<GameReview>();
}

public sealed class GameReview
{
   public Guid GameId { get; set; }
   public Game? Game { get; set; }
   public Guid ReviewerId { get; set; }
   public Reviewer? Reviewer { get; set; }
   public int Rating { get; set; }
   public string Summary { get; set; } = string.Empty;
   public DateTime ReviewedOn { get; set; }
}

public class Reviewer
{
   public Guid Id { get; set; }
   public string Name { get; set; } = string.Empty;
   public string Email { get; set; } = string.Empty;
   public string Username { get; set; } = string.Empty;
   public string Picture { get; set; } = string.Empty;
   public ICollection<GameReview> GameReviews { get; set; } = new HashSet<GameReview>();
}

```

&nbsp;

#### Create AppDbContext

&nbsp;

```csharp

public sealed class AppDbContext : DbContext
{
   public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
   {
   }

   public DbSet<Game> Games { get; set; } = null!;
   public DbSet<GameReview> Reviews { get; set; } = null!;
   public DbSet<Reviewer> Reviewers { get; set; } = null!;

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      modelBuilder.Entity<Game>(game =>
      {
         ...
      });

      modelBuilder.Entity<Reviewer>(reviewer =>
      {
         ...
      });

      modelBuilder.Entity<GameReview>(gameReview =>
      {
         ...
      });

      base.OnModelCreating(modelBuilder);
   }
}

```

&nbsp;

#### Configure Data Services

&nbsp;

```csharp

var builder = WebApplication.CreateBuilder(args);

// configure in-memory database
builder
   .Services
   .AddDbContextFactory<AppDbContext>(options =>
   {
      options.UseInMemoryDatabase("myview");
      options.EnableDetailedErrors(builder.Environment.IsDevelopment());
      options.EnableSensitiveDataLogging(builder.Environment.IsDevelopment());
   });

builder
   .Services
   .AddScoped<AppDbContext>(provider => provider
      .GetRequiredService<IDbContextFactory<AppDbContext>>()
      .CreateDbContext());

```

&nbsp;

#### Update GamesQuery

&nbsp;

```csharp

   [GraphQLDescription("Get list of games")]
   public IQueryable<GameDto> GetGames([Service] AppDbContext context)
   {
      return context
         .Games
         .AsNoTracking()
         .TagWith($"{nameof(GamesQuery)}::{nameof(GetGames)}")
         .OrderByDescending(game => game.ReleasedOn)
         .Include(game => game.Reviews)
         .Select(game => new GameDto
         {
            GameId = game.Id,
            Reviews = game.Reviews.Select(review => new GameReviewDto
            {
               GameId = review.GameId,
               Rating = review.Rating,
               ReviewerId = review.ReviewerId,
               Summary = review.Summary
            }),
            ReleasedOn = game.ReleasedOn,
            Summary = game.Summary,
            Title = game.Title
         });
   }

   [GraphQLDescription("Find game by id")]
   public async Task<GameDto?> FindGameById([Service] AppDbContext context, Guid gameId)
   {
      var game = await context
         .Games
         .AsNoTracking()
         .TagWith($"{nameof(GamesQuery)}::{nameof(FindGameById)}")
         .Include(game => game.Reviews)
         .FirstOrDefaultAsync(game => game.Id == gameId);

      if (game is null) return null;

      return new GameDto
      {
         GameId = game.Id,
         Reviews = game.Reviews.Select(review => new GameReviewDto
         {
            GameId = review.GameId,
            Rating = review.Rating,
            ReviewerId = review.ReviewerId,
            Summary = review.Summary
         }),
         ReleasedOn = game.ReleasedOn,
         Summary = game.Summary,
         Title = game.Title
      };
   }

```

&nbsp;

#### Update GamesMutation

&nbsp;

```csharp

   [GraphQLDescription("Submit a game review")]
   public async Task<GameReviewDto> SubmitGameReview(
        [Service] AppDbContext context,
        GameReviewDto gameReview)
   {
      // use fluent validator
      await _validator.ValidateAndThrowAsync(gameReview);

      var game = await context
         .Games
         .FirstOrDefaultAsync(game => game.Id == gameReview.GameId)
         ?? throw GameNotFoundException.WithGameId(gameReview.GameId);
      
      var reviewer = await context
         .Reviewers
         .FirstOrDefaultAsync(reviewer => reviewer.Id == gameReview.ReviewerId)
         ?? throw ReviewerNotFoundException.WithReviewerId(gameReview.ReviewerId);

      var gameReviewFromDb = await context
         .Reviews
         .FirstOrDefaultAsync(review => 
            review.GameId == gameReview.GameId 
            && review.ReviewerId == gameReview.ReviewerId);

      if (gameReviewFromDb is null)
      {
         context.Reviews.Add(new GameReview
         {
            GameId = gameReview.GameId,
            Rating = gameReview.Rating,
            ReviewedOn = DateTime.UtcNow,
            ReviewerId = gameReview.ReviewerId,
            Summary = gameReview.Summary
         });
      }
      else
      {
         gameReviewFromDb.Rating = gameReview.Rating;
         gameReviewFromDb.Summary = gameReview.Summary;
      }

      await context.SaveChangesAsync();

      return gameReview;
   }


   [GraphQLDescription("Remove a game review")]
   public async Task<GameReviewDto> DeleteGameReview(
      [Service] AppDbContext context,
      Guid gameId,
      Guid reviewerId)
   {
      var gameReviewFromDb = await context
         .Reviews
         .FirstOrDefaultAsync(review => review.GameId == gameId && review.ReviewerId == reviewerId)
         ?? throw GameReviewNotFoundException.WithGameReviewId(gameId, reviewerId);

      context.Reviews.Remove(gameReviewFromDb);

      return new GameReviewDto
      {
         GameId = gameReviewFromDb.GameId,
         Rating = gameReviewFromDb.Rating,
         ReviewerId = gameReviewFromDb.ReviewerId,
         Summary = gameReviewFromDb.Summary
      };
   }

```

&nbsp;

### Add Query Projection, Paging, Filtering and Sorting Support

&nbsp;

There are 2 steps required to enable projections, paging, filtering and sorting.

&nbsp;

#### Step 1 - Add Attributes To Queries

&nbsp;

Add the following attributes to the method performing queries. The ordering of the attributes is important and should be defined in the following order

&nbsp;

- `UsePaging`
- `UseProjection`
- `UseFiltering`
- `UseSorting`

&nbsp;

```csharp

[GraphQLDescription("Get list of games")]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<GameDto> GetGames([Service] AppDbContext context)
{
    ...
}

[GraphQLDescription("Find game by id")]
[UseProjection]
public async Task<GameDto?> FindGameById([Service] AppDbContext context, Guid gameId)
{
    ...
}

```

&nbsp;

#### Step 2 - Configure GraphQL Service

&nbsp;

```csharp

builder
   .Services
   .AddGraphQLServer()
   .
   .
   .   
   // The .AddTypeExtension allows having queries defined in multiple files
   // whilst still having a single root query
   .AddQueryType() // Add GraphQL root query type
      .AddTypeExtension<GamesQuery>()
      .AddTypeExtension<ReviewerQuery>()
    // Add Projection, Filtering and Sorting support. The ordering matters.    
   .AddProjections()
   .AddFiltering()
   .AddSorting()
   ...
   );

```

&nbsp;

### Add Database Seeding

&nbsp;

#### Define Seeder class to generate fake data

&nbsp;

```csharp

internal static class Seeder
{
   public static async Task SeedDatabase(WebApplication app)
   {
      await using (var serviceScope = app.Services.CreateAsyncScope())
      {
         var context = serviceScope
            .ServiceProvider
            .GetRequiredService<AppDbContext>();

         await context.Database.EnsureCreatedAsync();

         if (!await context.Reviewers.AnyAsync())
         {
            var reviewers = JsonSerializer.Deserialize<IEnumerable<Reviewer>>(
               _reviewersText,
               new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;

            context.AddRange(reviewers);
         }

         if (!await context.Games.AnyAsync())
         {
            var games = JsonSerializer.Deserialize<IEnumerable<Game>>(
               _gamesText,
               new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;

            context.Games.AddRange(games);
         }

         await context.SaveChangesAsync();
      }
   }

   private static readonly string _gamesText = @"
      [
         .
         .
         .
      ]";

   private static readonly string _reviewersText = @"
      [
         .
         .
         .
      ]";
}


```

&nbsp;

#### Configure when to Seed database

&nbsp;

```csharp

// Program.cs

app.MapGraphQL("/");

// seed database after all middleware is setup
await Seeder.SeedDatabase(app);

```

&nbsp;

---

&nbsp;

## Example 4

&nbsp;

For _[Example 4]_, we extend _[Example 3]_ to use a SQLite database.

&nbsp;

### Configure SQLite

&nbsp;

#### Add Packages

&nbsp;

```bash

# add required EntityFramework packages
dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.6
dotnet add package Microsoft.EntityFrameworkCore.SQLite --version 6.0.6

# add HotChocolate package providing EntityFramework support
dotnet add package HotChocolate.Data.EntityFramework --version 12.11.1

```

&nbsp;

#### Configure AppDbContext

&nbsp;

Update the `OnModelCreating` method to provide all the required entity-to-table mappings and relationships.

&nbsp;

```csharp

public sealed class AppDbContext : DbContext
{
   .
   .
   .

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      // configure Game
      modelBuilder.Entity<Game>(game =>
      {
         // configure table
         game.ToTable(nameof(Game).ToLower());

         // configure properties
         game.Property(e => e.Id).HasColumnName("id");
         game.Property(e => e.ReleasedOn).HasColumnName("released_on").IsRequired();
         game.Property(e => e.Summary).HasColumnName("summary").IsRequired();
         game.Property(e => e.Title).HasColumnName("title").IsRequired();

         // configure primary key
         game.HasKey(e => e.Id).HasName("pk_game_id");

         // configure game relationship
         game.HasMany(e => e.Reviews).WithOne(e => e.Game);
      });

      
      // configure Reviewer
      modelBuilder.Entity<Reviewer>(reviewer =>
      {
         // configure table
         reviewer.ToTable(nameof(Reviewer).ToLower());

         // configure properties
         reviewer.Property(e => e.Id).HasColumnName("id");
         reviewer.Property(e => e.Email).HasColumnName("email").IsRequired();
         reviewer.Property(e => e.Name).HasColumnName("name").IsRequired();
         reviewer.Property(e => e.Picture).HasColumnName("picture").IsRequired();
         reviewer.Property(e => e.Username).HasColumnName("username").IsRequired();

         // configure primary key
         reviewer.HasKey(e => e.Id).HasName("pk_reviewer_id");

         // configure reviewer relationship
         reviewer.HasMany(e => e.GameReviews).WithOne(e => e.Reviewer);
      });

      
      // configure GameReview
      modelBuilder.Entity<GameReview>(gameReview =>
      {
         // configure table
         gameReview.ToTable("game_review");

         // configure properties
         gameReview.Property(e => e.GameId).HasColumnName("game_id").IsRequired();
         gameReview.Property(e => e.ReviewerId).HasColumnName("reviewer_id").IsRequired();
         gameReview.Property(e => e.Rating).HasColumnName("rating").IsRequired();
         gameReview.Property(e => e.ReviewedOn).HasColumnName("reviewed_on").IsRequired();
         gameReview.Property(e => e.Summary).HasColumnName("summary").HasDefaultValue("");

         // configure primary key
         gameReview
            .HasKey(e => new { e.GameId, e.ReviewerId })
            .HasName("pk_gamereview_gameidreviewerid");

         // configure game relationship
         gameReview
            .HasOne(e => e.Game)
            .WithMany(e => e.Reviews)
            .HasConstraintName("fk_gamereview_gameid");

         gameReview
            .HasIndex(e => e.GameId)
            .HasDatabaseName("ix_gamereview_gameid");

         // configure reviewer relationship
         gameReview
            .HasOne(e => e.Reviewer)
            .WithMany(e => e.GameReviews)
            .HasConstraintName("fk_gamereview_reviewerid");

         gameReview
            .HasIndex(e => e.ReviewerId)
            .HasDatabaseName("ix_gamereview_reviewerid");
      });

      base.OnModelCreating(modelBuilder);
   }
}

```

&nbsp;

#### Configure Data Services

&nbsp;

```csharp

builder
   .Services
   .AddDbContextFactory<AppDbContext>(options =>
   {
      options.UseSqlite("Data Source=myview.db");
      options.EnableDetailedErrors(builder.Environment.IsDevelopment());
      options.EnableSensitiveDataLogging(builder.Environment.IsDevelopment());
   });

builder
   .Services
   .AddScoped<AppDbContext>(provider => provider
      .GetRequiredService<IDbContextFactory<AppDbContext>>()
      .CreateDbContext());

```

&nbsp;

#### Create Database Migrations

&nbsp;

Before running the application, we need to create the database migrations in order to create/update the SQLite database. After the migrations and database update are complete, a SQLite database called `myview.db` will be created in the root of the project folder.

&nbsp;

```bash

# navigate to API project folder
cd ./MyView.Api

# create the database migrations
dotnet ef migrations add "CreateInitialDatabaseSchema"

```

&nbsp;

### Create and Update SQLite Database

&nbsp;

```bash

# run the migrations to create database
dotnet ef database update

```

&nbsp;

---

&nbsp;

## Deploy GraphQL API

&nbsp;

In this section, we will cover the following primary topics:

&nbsp;

- Provision Azure App Service
- Deploy GraphQL API to Azure App Service

&nbsp;

### Provision Azure App Service

&nbsp;

We will be deploying the _*'MyView API'*_ into [Azure App Service]. However, before we can deploy our API, we first need to provision our _Azure App Service_. This section demonstrates how to provision an Azure App Service using 3 different techniques and are listed as follows:

&nbsp;

- Azure CLI
- Azure Powershell
- Azure Bicep

&nbsp;

Regardless of the chosen technique, there are 3 general steps that need to be completed in order to provision the _Azure App Service_.

&nbsp;

- Create a resource group
- Create an App Service Plan
- Create an App Service

&nbsp;

Once all the required resources are created, we will be ready to deploy the _*'MyView API'*_ to _Azure_.

&nbsp;

#### Create Azure App Service Using Azure CLI

&nbsp;

All the commands that are required to create the _Azure App Service_ using the _Azure CLI_ can be found in the `iac/azcli/deploy.azcli` file that is part of the [example github repository](https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac)

&nbsp;

```bash

$location = "australiaeast"

# STEP 1 - create resource group
$rgName = "myview-rg"
az group create `
    --name $rgName `
    --location $location

# STEP 2 - create appservice plan
$aspName = "myview-asp"
$appSku = "F1"
az appservice plan create `
    --name $aspName `
    --resource-group $rgName `
    --sku $appSku `
    --is-linux

# STEP 3 - create webapp
$appName = "myview-webapp-api"
$webapp = az webapp create `
    --name $appName `
    --resource-group $rgName `
    --plan $aspName `
    --runtime '"DOTNETCORE|6.0"'

# STEP 4 - cleanup
az group delete --resource-group $rgName -y

```

&nbsp;

#### Create Azure App Service Using Azure Powershell

&nbsp;

All the commands that are required to create the _Azure App Service_ using the _Azure Powershell_ can be found in the `/azpwsh/deploy.azcli` file that is part of the [example github repository](https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac).

&nbsp;

```bash

$location = "australiaeast"

# STEP 1 - create resource group
$rgName = "myview-rg"
New-AzResourceGroup -Name $rgName -Location $location

# STEP 2 - create appservice plan
$aspName = "myview-asp"
$appSku = "F1"
New-AzAppServicePlan `
    -Name $aspName `
    -Location $location `
    -ResourceGroupName $rgName `
    -Tier $appSku `
    -Linux

# STEP 3 - create webapp
$appName = "myview-webapp-api"
New-AzWebApp `
    -Name $appName `
    -Location $location `
    -AppServicePlan $aspName `
    -ResourceGroupName $rgName

# STEP 4 - configure webapp

## At this point, the webapp is not using .NET v6 as is required.
## This can be verified by the following commands
az webapp config show `
    --resource-group $rgName `
    --name $appName `
    --query netFrameworkVersion

Get-AzWebApp `
    -Name $appName `
    -ResourceGroupName $rgName `
    | Select-Object -ExpandProperty SiteConfig `
    | Select-Object -Property NetFrameworkVersion

Get-AzWebApp `
    -Name $appName `
    -ResourceGroupName $rgName `
    | ConvertTo-Json `
    | jq ".SiteConfig.NetFrameworkVersion"

## lets configure the webapp with the correct version of .NET
Set-AzWebApp `
    -Name $appName `
    -ResourceGroupName $rgName `
    -AppServicePlan $aspName `
    -NetFrameworkVersion 'v6.0'

$apiVersion = "2020-06-01"
$config = Get-AzResource `
    -ResourceGroupName $rgName `
    -ResourceType Microsoft.Web/sites/config `
    -ResourceName $appName/web `
    -ApiVersion $apiVersion
$config.Properties.linuxFxVersion = "DOTNETCORE|6.0"
$config | Set-AzResource -ApiVersion $apiVersion -Force

# cleanup
Remove-AzResourceGroup -Name $rgName -Force

```

&nbsp;

#### Create Azure App Service Using Azure Bicep

&nbsp;

In this example, I demonstrate both a basic and more advanced option (uses modules) to deploy bicep templates.

&nbsp;

##### Azure Bicep - Basic

&nbsp;

All the commands and templates that are required to create the _Azure App Service_ using _Azure Bicep_ can be found in the `iac/bicep/basic` folder that is part of the [example github repository](https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac)

&nbsp;

```bash

# file: iac/bicep/basic/deploy.azcli

$location = "australiaeast"

# STEP 1 - create resource group
$rgName = "myview-rg"
az group create --name $rgName --location $location

# STEP 2 - deploy template
$aspName = "myview-asp"
$appName = "myview-webapp-api"
$deploymentName = "myview-deployment"

az deployment group create `
    --name $deploymentName `
    --resource-group $rgName `
    --template-file ./main.bicep `
    --parameters appName=$appName `
    --parameters aspName=$aspName

# cleanup
az group delete --resource-group $rgName -y

```

&nbsp;

```javascript

// file: iac/bicep/basic/main.bicep

// parameters
@description('The name of app service')
param appName string

@description('The name of app service plan')
param aspName string

@description('The location of all resources')
param location string = resourceGroup().location

@description('The Runtime stack of current web app')
param linuxFxVersion string = 'DOTNETCORE|6.0'

@allowed([
  'F1'
  'B1'
])
param skuName string = 'F1'

// variables

resource asp 'Microsoft.Web/serverfarms@2021-02-01' = {
  name: aspName
  location: location
  kind: 'linux'
  sku: {
    name: skuName
  }
  properties: {
    reserved: true
  }
}

resource app 'Microsoft.Web/sites@2021-02-01' = {
  name: appName
  location: location
  properties: {
    serverFarmId: asp.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: linuxFxVersion
    }
  }
}

```

&nbsp;

##### Azure Bicep - Advanced (with modules)

&nbsp;

All the commands and templates that are required to create the _Azure App Service_ using _Azure Bicep_ can be found in the `iac/bicep/advanced` folder that is part of the [example github repository](https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac)

&nbsp;

```bash

# file: iac/bicep/advanced/deploy.azcli

$location = "australiaeast"


# STEP 1 - deploy template
$deploymentName = "myview-deployment"

az deployment sub create `
    --name $deploymentName `
    --location $location `
    --template-file ./main.bicep `
    --parameters location=$location


# STEP 2 - get outputs from deployment
az deployment sub show --name $deploymentName --query "properties.outputs"
$hostName = $(az deployment sub show --name $deploymentName --query "properties.outputs.defaultHostName.value")
$rgName = $(az deployment sub show --name $deploymentName --query "properties.outputs.resourceGroupName.value")
echo $hostName, $rgName


# STEP 3 - cleanup
az group delete --resource-group $rgName -y

```

&nbsp;

```javascript

// file: iac/bicep/advanced/modules/app-service.bicep

// parameters
@description('The name of app service')
param appName string

@description('The name of app service plan')
param aspName string

@description('The location of all resources')
param location string = resourceGroup().location

@description('The Runtime stack of current web app')
param linuxFxVersion string = 'DOTNETCORE|6.0'

@allowed([
  'F1'
  'B1'
])
param skuName string = 'F1'


// define resources
resource asp 'Microsoft.Web/serverfarms@2021-02-01' = {
  name: aspName
  location: location
  kind: 'linux'
  sku: {
    name: skuName
  }
  properties: {
    reserved: true
  }
}

resource app 'Microsoft.Web/sites@2021-02-01' = {
  name: appName
  location: location
  properties: {
    serverFarmId: asp.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: linuxFxVersion
    }
  }
}


// define outputs
output defaultHostName string = app.properties.defaultHostName

```

&nbsp;

```javascript

// file: iac-src/bicep/advanced/main.bicep

// define scope
targetScope = 'subscription'


// define parameters
@description('The location of all resources')
param location string = deployment().location


// define variables
@description('The name of resource group')
var rgName = 'myview-rg'

@description('The name of resource group')
var aspName = 'myview-asp'

@description('The name of app')
var appName = 'myview-webapp-api'


// define resources
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: rgName
  location: location
}


// define modules
module appServiceModule 'modules/app-service.bicep' = {
  scope: resourceGroup
  name: 'myview-module'
  params: {
    appName: appName
    aspName: aspName
    location: location
  }
}


// define outputs
output defaultHostName string = appServiceModule.outputs.defaultHostName
output resourceGroupName string = rgName

```

&nbsp;

### Deploy GraphQL API to Azure App Service

&nbsp;

#### Publish GraphQL API Locally

&nbsp;

```bash

dotnet publish `
  --configuration Release `
  --framework net6.0 `
  --output ./release `
  ./MyView.Api

```

&nbsp;

#### Deploy GraphQL API

&nbsp;

```bash

cd ./release

az webapp up `
  --plan myview-asp `
  --name myview-webapp-api `
  --resource-group myview-rg `
  --os-type linux `
  --runtime "DOTNETCORE:6.0"

```

&nbsp;

---

&nbsp;

## Where To Next?

&nbsp;

I have provided a number of [examples](https://github.com/drminnaar/graphql-dotnet-series) that show how to build a GraphQL Server using [ChilliCream Hot Chocolate] GraphQL Server. If you would like to learn more, please view the following learning resources:

&nbsp;

- [Official Getting Started Documentation](https://chillicream.com/docs/hotchocolate/get-started)
- [Official ChilliCream Hot Chocolate Examples](https://github.com/ChilliCream/hotchocolate-examples)
- [On.NET Show - Getting Started with HotChocolate](https://docs.microsoft.com/en-us/shows/on-net/getting-started-with-hotchocolate)
- [Modern data APIs with EF Core and GraphQL](https://youtu.be/GBvTRcV4PVA)
- [Entity Framework Community Standup - Hot Chocolate 12 and GraphQL 2021](https://youtu.be/3_4nt2QQSeE)
- [GraphQL API with .NET 5 and Hot Chocolate](https://youtu.be/HuN94qNwQmM) (Still applicable to .NET 6)
- [GraphQL in .NET with HotChocolate (Playlist)](https://www.youtube.com/playlist?list=PLA8ZIAm2I03g9z705U3KWJjTv0Nccw9pj)
- [Azure Tips & Tricks - How to use GraphQL on Azure](https://microsoft.github.io/AzureTipsAndTricks/blog/tip287.html)
- [Hot Chocolate: GraphQL Schema Stitching with ASP.Net Core - Michael Staib - NDC London 2021](https://youtu.be/_ncO6kUP-zs)

&nbsp;

### HotChocolate Templates

&nbsp;

There are also a number of Hot Chocolate templates that can be installed using the `dotnet CLI` tool.

&nbsp;

- Install HotChocolate Templates:
  &nbsp;
  ```bash
  
  # install Hot Chocolate GraphQL server templates (includes Azure function template)
  dotnet new -i HotChocolate.Templates
  
  # install template that allows you to create a GraphQL Star Wars Demo
  dotnet new -i HotChocolate.Templates.StarWars
  
  ```
&nbsp;

- List HotChocolate Templates
  &nbsp;
  ```bash

  # list HotChocolate templates
  dotnet new --list HotChocolate

  ```

  ```text

  Template Name                        Short Name   Language  Tags
  -----------------------------------  -----------  --------  ------------------------------
  HotChocolate GraphQL Function        graphql-azf  [C#]      Web/GraphQL/Azure
  HotChocolate GraphQL Server          graphql      [C#]      Web/GraphQL
  HotChocolate GraphQL Star Wars Demo  starwars     [C#]      ChilliCream/HotChocolate/Demos

  ```
&nbsp;
- Create HotChocolate project using templates
  &nbsp;
  ```bash

  # create ASP.NET GraphQL Server
  dotnet new graphql --name MyGraphQLDemo

  # create graphql server using Azure Function
  dotnet new graphql-azf --name MyGraphQLAzfDemo
  
  # create starwars GraphQL demo
  mkdir StarWars
  cd StarWars
  dotnet new starwars

  ```

&nbsp;

---

[Visual Studio Code]: https://code.visualstudio.com/
[.NET Extension Pack]: https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-pack

[GraphQL]: https://graphql.org
[C# .NET GraphQL Libraries]: https://graphql.org/code/#c-net

[.NET 6 SDK]: https://dotnet.microsoft.com/en-us/download/dotnet/6.0
[.NET 6]: https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-6
[.NET 6 (LTS)]: https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-6
[Entity Framework Core]: https://docs.microsoft.com/en-us/ef/core/

[ChilliCream Hot Chocolate]: https://chillicream.com/docs/hotchocolate
[ChilliCream Hot Chocolate (Open-Source GraphQL Server for Microsoft .NET platform)]: https://chillicream.com/docs/hotchocolate

[Azure CLI]: https://docs.microsoft.com/en-us/cli/azure/
[Azure Powershell]: https://docs.microsoft.com/en-us/powershell/azure/?view=azps-8.0.0
[Azure Bicep]: https://docs.microsoft.com/EN-US/azure/azure-resource-manager/bicep/

[example github repository]: https://github.com/drminnaar/graphql-dotnet-series
[example-1]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-1
[Example 1]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-1
[example-2]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-2
[Example 2]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-2
[example-3]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-3
[Example 3]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-3
[example-4]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-4
[Example 4]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/example-4
[example-iac]: https://github.com/drminnaar/graphql-dotnet-series/tree/main/iac