How to implement GraphQL in ASP.Net Core Web API (.NET 6) using HotChocolate

Miguel Ponce G
GlobalLogic LatAm
Published in
10 min readApr 13, 2023

In this article you will learn how to implement GraphQL using Hot Chocolate in an ASP.NET Core Web API built on the .NET 6 framework.
I had explain a little about GraphQL and its advantages in What is GraphQL, you can check it out.
We are also going to take a look at the different GraphQL elements used to integrate GraphQL in our ASP.NET Core Web API.

Introduction

HotChocolate is a .NET GraphQL platform that can help you build a GraphQL layer over your existing and new application. HotChocolate is very easy to set up and takes the clutter away from writing GraphQL schemas.

We will create a simple application that will have a CRUD of products and get the information of the order detail of a Technology Store online

Repository

The code for this sample can be found on the MPonceG/GraphQLNetCoreHotChocolate repository on GitHub.

Prerequisites

Visual Studio 2022 (version 17.0.0 upwards)

SQL Server 2019

The Database

The database schema and data script can be found in the folder MPonceG/GraphQLNetCoreHotChocolate/Scripts. Ensure to run the script in SQL Server Management Studio or in Visual Studio SQL Server Object Explore to set up the Database and tables.

  1. Create ASP.NET Core Web API from Template
    Open Visual Studio (I use version 2022) as developing with the .NET 6 framework. Create a new project, select ASP.NET Core Web API as the template, and give it a name you like.
  2. Install Dependencies
    We need to install the HotChocolate.AspNetCore(v12.15.2) package. The package contains the GraphQL API’s for ASP.NET. To install the package, right click the solution in the solution explorer and select Manage NuGet Packages for Solution. Under the Browse section, search for HotChocolate.AspNetCore and click on it, then in the preview panel click the Install button:

Also, install the Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer packages respectively.

3. Add Models/Entities
We will make use of EntityFrameworkCore as the ORM. Models/Entities (classes) are used to define the database objects (tables) and those models are mapped to the database table in the DbContext.

We will define four Models, one for the Accounts, another for Products, another for the Order and the other for the Order Details tables.

We will keep everything simple by using folders to hold the data access code, the GraphQL code and the Database Entities. In a real application, a Class library would be better for the different layers.

Create a folder called Models in the GraphQLHotChocolate project.

Account Model
Add a C# Class file called Account.cs to the Models folder. This class will be used to define the Account model. The code for this file is:

using System.ComponentModel.DataAnnotations;
namespace GraphQLHotChocolate.Models
{
#nullable disable warnings
public class Account
{
[Key]
public int AccountId { get; set; }
public string AccountEmail { get; set; }
public string? AccountPassword { get; set; }
public string? AccountFirstName { get; set; }
public string? AccountLastName { get; set; }
public string? AccountPhone { get; set; }
public DateTime AccountDate { get; set; }
public bool AccountStatus { get; set; }
}
}

Order Model
Add a C# Class file called Order.cs to the Models folder. This class will be used to define the Order model. The code for this file is:

using System.ComponentModel.DataAnnotations;
namespace GraphQLHotChocolate.Models
{
#nullable disable warnings
public class Order
{
[Key]
public int OrderId { get; set; }
public decimal OrderAmount { get; set; }
public string? OrderShipName { get; set; }
public string? OrderShipAddress { get; set; }
public string? OrderEmail { get; set; }
public DateTime OrderDate { get; set; }
public bool OrderStatus { get; set; }
public int AccountId { get; set; }
public Account Account { get; set; }
public List<OrderDetail> orderDetails { get; set; }
}
}

Order Detail Model
Add a C# Class file called OrderDetail.cs to the Models folder. This class will be used to define the Order Detail model. The code for this file is:

using System.ComponentModel.DataAnnotations;
namespace GraphQLHotChocolate.Models
{
#nullable disable warnings
public class OrderDetail
{
[Key]
public int OrderDetailId { get; set; }
public decimal OrderDetailPrice { get; set; }
public string? OrderDetailName { get; set; }
public int OrderDetailQuantity { get; set; }
public DateTime OrderDetailDate { get; set; }
public bool OrderDetailStatus { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
}

Product Model
Add a C# Class file called Product.cs to the Models folder. This class will be used to define the Product model. The code for this file is:

using System.ComponentModel.DataAnnotations;namespace GraphQLHotChocolate.Models
{
public class Product
{ [Key]
public int? ProductId { get; set; }
public string? ProductName { get; set; }
public string? ProductDescription { get; set; }
public string? ProductSku { get; set; }
public decimal? ProductPrice { get; set; }
public DateTime? ProductUpdateDate { get; set; }
public bool? ProductStatus { get; set; } }
}

4. Add the DbContext
The DbContext will be simple and straightforward. In a real application, most of the database configurations are done in this file. Add a new C# Class file to the Data folder, name it StoreDbContext.cs. The code for this file is:

using GraphQLHotChocolate.Models;
using Microsoft.EntityFrameworkCore;

namespace GraphQLHotChocolate.Data
{
#nullable disable warnings
public class StoreDbContext: DbContext
{
public StoreDbContext(DbContextOptions<StoreDbContext> options) : base(options)
{

}

public DbSet<Product> Product { get; set; }
public DbSet<Account> Account { get; set; }
public DbSet<Order> Order { get; set; }
public DbSet<OrderDetail> OrderDetail { get; set; }
}
}

5. Add repositories
Next, we will write the layer that will send and receive data to and from the database. In the Data folder, add another folder called Repositories.

Order Repository
Add a C# class file called OrderRepository.cs to the Repositories folder:

using GraphQLHotChocolate.Models;
using Microsoft.EntityFrameworkCore;

namespace GraphQLHotChocolate.Data.Repositories
{
public class OrderRepository
{
private readonly StoreDbContext _storeDbContext;

public OrderRepository(StoreDbContext dbContext)
{
_storeDbContext = dbContext;
}

public List<Order> GetAllOrderwithDetails()
{
return _storeDbContext.Order
.Include(a => a.Account)
.Include(o => o.orderDetails).ThenInclude(p => p.Product)
.ToList();
}
}
}

Order Detail Repository
Add a C# class file called OrderDetailRepository.cs to the Repositories folder:

using GraphQLHotChocolate.Models;
using Microsoft.EntityFrameworkCore;

namespace GraphQLHotChocolate.Data.Repositories
{
public class OrderDetailRepository
{
private readonly StoreDbContext _storeDbContext;

public OrderDetailRepository(StoreDbContext dbContext)
{
_storeDbContext = dbContext;
}

public List<OrderDetail> GetAllOrderDetails()
{
return _storeDbContext.OrderDetail
.Include(o => o.Order).ThenInclude(a => a.Account)
.Include(p => p.Product)
.ToList();
}
}
}

Product Repository
Add a C# class file called ProductRepository.cs to the Repositories folder:

using GraphQLHotChocolate.Models;
using Microsoft.EntityFrameworkCore;

namespace GraphQLHotChocolate.Data.Repositories
{
public class ProductRepository
{
private readonly StoreDbContext _storeDbContext;
public ProductRepository(StoreDbContext dbContext)
{
_storeDbContext = dbContext;
}

public List<Product> GetAllProducts()
{
return _storeDbContext.Product.ToList();
}

public Product GetProductbyId(int id)
{
return _storeDbContext.Product.Where(p => p.ProductId == id).FirstOrDefault();
}

public async Task<Product> CreateProduct(Product product)
{
product.ProductUpdateDate = DateTime.Now;
await _storeDbContext.Product.AddAsync(product);
await _storeDbContext.SaveChangesAsync();
return product;
}

public async Task<Product> UpdateProduct(Product product)
{
var updateProduct = _storeDbContext.Product.Find(product.ProductId);
if (updateProduct != null)
{
updateProduct.ProductName = product.ProductName ?? updateProduct.ProductName;
updateProduct.ProductDescription = product.ProductDescription ?? updateProduct.ProductDescription;
updateProduct.ProductSku = product.ProductSku ?? updateProduct.ProductSku;
updateProduct.ProductPrice = product.ProductPrice ?? updateProduct.ProductPrice;
updateProduct.ProductUpdateDate = DateTime.Now;
updateProduct.ProductStatus = product.ProductStatus ?? updateProduct.ProductStatus;
_storeDbContext.Product.Update(updateProduct);
await _storeDbContext.SaveChangesAsync();
}

return updateProduct;
}


}
}

6. Adding GraphQL
If you are familiar with the REST API, we use GET to fetch data from the data store, we use POST, PUT DELETE to modify the state of data. GET in REST API is the same as Query in GraphQL. POST, PUT, DELETE, is the same as Mutation. In GraphQL, there is also Subscription which is used to set up event listeners.

7. Adding Query
Add a new C# class file called Query.cs to the GraphQL folder inside the Data folder. This class will contain all the Queries we need to perform:

using GraphQLHotChocolate.Data.Repositories;
using GraphQLHotChocolate.Models;
using HotChocolate.Subscriptions;

namespace GraphQLHotChocolate.Data.GraphQL
{
public class Query
{
public List<Product> GetAllProducts([Service] ProductRepository productRepository)
{
return productRepository.GetAllProducts();
}

public async Task<Product> GetProductbyId([Service] ProductRepository productRepository,
[Service] ITopicEventSender eventSender, int id)
{
Product product = productRepository.GetProductbyId(id);
await eventSender.SendAsync("ReturnedProduct", product);
return product;
}
public List<OrderDetail> GetAllOrderDetails([Service] OrderDetailRepository orderDeatilRepository)
{
return orderDeatilRepository.GetAllOrderDetails();
}

public List<Order> GetAllOrderwithDetails([Service] OrderRepository orderRepository)
{
return orderRepository.GetAllOrderwithDetails();
}
}
}

8. Adding Mutation
Add a new C# class file called Mutation.cs to the GraphQL folder inside the Data folder. This class will contain all the Mutations we need to perform:

using GraphQLHotChocolate.Data.Repositories;
using GraphQLHotChocolate.Models;
using HotChocolate.Subscriptions;

namespace GraphQLHotChocolate.Data.GraphQL
{
public class Mutation
{
public async Task<Product> CreateProduct([Service] ProductRepository productRepository,
[Service] ITopicEventSender eventSender, Product product)
{
var newProduct = await productRepository.CreateProduct(product);
await eventSender.SendAsync("AddProduct", newProduct);
return newProduct;
}

public async Task<Product> UpdateProduct([Service] ProductRepository productRepository, Product product)
{
var updateProduct = await productRepository.UpdateProduct(product);
return updateProduct;
}
}
}

9. Adding Subscription
Subscriptions are used to set up event listeners e.g. we can set up an event listener that responds when we create a new Product or Query the database for a Product etc. Add a new C# class file called Subscription.cs to the GraphQL folder inside the Data folder:

using GraphQLHotChocolate.Models;
using HotChocolate.Execution;
using HotChocolate.Subscriptions;

namespace GraphQLHotChocolate.Data.GraphQL
{
public class Subscription
{
[SubscribeAndResolve]
public async ValueTask<ISourceStream<Product>> OnProductGet([Service] ITopicEventReceiver eventReceiver,
CancellationToken cancellationToken)
{
return await eventReceiver.SubscribeAsync<string, Product>("ReturnedProduct", cancellationToken);
}

[SubscribeAndResolve]
public async ValueTask<ISourceStream<Product>> OnProductAdd([Service] ITopicEventReceiver eventReceiver,
CancellationToken cancellationToken)
{
return await eventReceiver.SubscribeAsync<string, Product>("AddProduct", cancellationToken);
}
}
}

10. Configuring GraphQL
We need to configure the application to make use of GraphQL. Replace the code in your Program.cs with:

using GraphQLHotChocolate.Data;
using GraphQLHotChocolate.Data.GraphQL;
using GraphQLHotChocolate.Data.Repositories;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<OrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderDetailRepository, OrderDetailRepository>();
builder.Services.AddScoped<ProductRepository, ProductRepository>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDbContext<StoreDbContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("TheStoreDB")));

builder.Services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddSubscriptionType<Subscription>()
.AddInMemorySubscriptions();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.UseWebSockets();
app.MapGraphQL();

app.MapControllers();

app.Run();

Line 21 is used to add the Service for GraphQL, Query, Mutation and Subscription. The AddQueryType(), AddMutationType() and AddSubscriptionType() takes in the Query, Mutation and Subscription classes respectively.

Line 39 is used to map the endpoint to GraphQL.

Line 18 adds the DbContext service; we configure this service to make use of SQL Server as the database. The connection string is defined in the appsettings.json file as:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"TheStoreDB": "Data Source=localhost;Initial Catalog=TheStoreDB;Integrated Security=True;Trusted_Connection=True;Encrypt=False"
},
"AllowedHosts": "*"
}

11. Running the Application
Right-click the solution and click Build Solution. After Build is successful, click CTRL + f5 or f5 to run the application. In your browser, navigate to https://localhost:7130/graphql/, you should get

Operations available on the project: Query, Mutation and Subscription

HotChocolate gives you a GraphQL IDE (Banana Cake Pop) right in your browser out of the box, you don’t need to install any GraphQL IDE.

The methods defined in Query.cs, Mutation.cs and Subscription.cs have been categorized under Query, Mutation and Subscription respectively.

Test Query

In Banana cake pop, you can add a new tab by clicking the + in the top right corner of the IDE. You can run your schema by clicking the Execute/Play button in the top right corner. Add and run:

Structure of the query allOrderwithDetails

You should get the response in the image below:

Response of the query allOrderwithDetails

Try out other Query types, defining the Schema of the data you need.

Test Subscription and Query

Add and run the schema below:

Structure of the subscription onProductGet
Logs of the subscription onProductGet

This Subscribes to the event “ReturnedProduct” in line 13 of Subscription.cs. You won’t get any output yet as shown in the image above, but you can see from the console that we have Subscribed to the event.

Launch https://localhost:7130/graphql/ in another browser tab. Next, we will run a Query that will trigger the event. Add and run the below in a new Banana Cake Pop tab in the new browser tab:

Structure of query productbyId
Response of the query productbyId

If you go back to the first browser tab where we subscribed to the event, you will get the response

Response of the subscription onProductGet

If you change the value of the id and run the Query, you will see the changes in the Subscription.

Test Mutation

Add and run the schema below:

Structure to build a mutation and the body of product

You would get:

Response of mutation createProduct

GraphQL is a very nice way to expose an API to the public. It makes it easier for the clients to get just the data they need. We now have a full ASP.NET Core Web API with GraphQL using HotChocolate with Query, Mutation and Subscription. It’s easy and offers a nice and easy way to interact with your application through the API.

--

--