GraphQL
What is GraphQL?
Section titled “What is GraphQL?”- Yet another communication standard
- Mainly for client ⇔ server
- GraphQL is typically served over HTTP
- Contrary to REST we use a single endpoint
- GraphQL services typically respond using JSON (yet not required by the spec)
Data model
Section titled “Data model”With GraphQL you think of data as a graph of nodes.
Query language
Section titled “Query language”GraphQL has its own language to query data
{ rewards { title }}
{ rewards(id: 1) { title, savings { amount } }}
- GraphQL gives power to the client.
- The client can specify what they want exactly for a specific use-case.
- The server needs to be implemented in a way that supports the queries.
- This leads to very targeted queries fetching only the information that is needed.
Add GraphQL to an ASP.NET Core Web API
Section titled “Add GraphQL to an ASP.NET Core Web API”Using HotChocolate1
NuGet:
Install-Package HotChocolate.AspNetCoreInstall-Package HotChocolate.DataInstall-Package GraphQL.Server.Transports.AspNetCore
Add a query
Section titled “Add a query”public class Query{ public IQueryable<Reward> GetRewards => new List<Reward>().AsQueryable();}
The methods can return any data. We just return an empty list here to get started.
Register with dependency injection
Section titled “Register with dependency injection”// Type Query points to Query class (previous slide)builder.Services.AddGraphQLServer().AddQueryType<Query>()// ...app.MapGraphQL(path: "/graphql");
Run and verify
Section titled “Run and verify”Run application and go to route /graphql in the browser.
Create a new document and try out the following query.
query MyQuery { getRewards { title }}
Should yield result:
{ "data": { "getRewards": [] }}
Add connection to database
Section titled “Add connection to database”public class Query{ [UseProjection] [UseFiltering] [UseSorting] public IQueryable<Reward> GetRewards( [Service] RewardDbContext context) => context.Rewards;}
builder.Services.AddGraphQLServer().AddQueryType<Query>() .AddProjections().AddFiltering().AddSorting();
Verify again
Section titled “Verify again”query GetRewards { rewards { title }}
returns
{ "data": { "rewards": [ { "title": "X-Jam" } ] }}
Get element by ID
Section titled “Get element by ID”public class Query{ [UseFirstOrDefault] [UseProjection] public IQueryable<Reward?> GetReward( [Service] RewardDbContext context, int id) => context.Rewards.Where(r => r.Id == id);}
query GetReward { reward(id: 1) { title }}
{ "data": { "reward": { "title": "X-Jam" } }}
Add GraphQL to a Blazor WASM Client
Section titled “Add GraphQL to a Blazor WASM Client”Using StrawberryShake2
# in root folder of solutiondotnet new tool-manifestdotnet tool install StrawberryShake.Tools
# in Blazor Client projectInstall-Package StrawberryShake.Blazor
Generate client
Section titled “Generate client”# server needs to run for this command# run command in root folder of solution# use whatever url your server is serving fromdotnet graphql init https://localhost:7232/graphql/ `-n RewardClient ` # give your generated client a name-p SavingsPlanner/Client # point to the client project
Customize namespace
Section titled “Customize namespace”{ "schema": "schema.graphql", "documents": "**/*.graphql", "extensions": { "strawberryShake": { "name": "RewardClient", "namespace": "SavingsPlanner.Client.GraphQL", "url": "https://localhost:7232/graphql/", //... } }}
Write query definition
Section titled “Write query definition”query GetRewards { rewards { title, description, targetAmount, id }}
Build project
Section titled “Build project”dotnet build
Register client with dependency injection
Section titled “Register client with dependency injection”builder.Services .AddRewardClient() .ConfigureHttpClient(client => client.BaseAddress = new Uri( builder.HostEnvironment.BaseAddress + "graphql"));
Query data
Section titled “Query data”@inject RewardClient RewardClient// ...await RewardClient.GetRewards.ExecuteAsync();
Mutations
Section titled “Mutations”GraphQL Mutations let you modify data.
mutation MyMutation { addReward(reward: { title: "Apple", description: "fruit", targetAmount: 1337, savingStart: "2023-03-01T00:00:00.000+01:00", savingEnd: "2023-04-01T00:00:00.000+01:00", }) { title, id }}
You send the data and have control of what you get back.
Server Implementation
Section titled “Server Implementation”public class Mutation{ public async Task<RewardListItemDto> AddReward( [Service] IRepository<Reward> rewardRepository, AddRewardDto reward) { return await rewardRepository .AddAsync<AddRewardDto, RewardListItemDto>(reward); }}
It is recommended to create specific types for adding items (e.g. without ID property).
Registration
Section titled “Registration”builder.Services .AddGraphQLServer() // ... your queries .AddMutationType<Mutation>();
Update Client
Section titled “Update Client”# run from Client directory# server needs to run# correct server address needs to be specified in .graphqlrcdotnet graphql update
Add Definition for Mutation
Section titled “Add Definition for Mutation”mutation AddReward($rewardInput: AddRewardDtoInput!) { addReward(reward: $rewardInput) { id }}
Build again
Section titled “Build again”dotnet build
Use client to modify data
Section titled “Use client to modify data”@inject RewardClient RewardClient// ...await RewardClient.AddReward.ExecuteAsync( new AddRewardDtoInput() { Title = _title, TargetAmount = _targetAmount, SavingStart = _startDate.Value, SavingEnd = _endDate.Value, Description = _description });
Subscriptions
Section titled “Subscriptions”GraphQL offers an easy way to subscribe to events.
The Server can notify the client of changes via a websocket connection.
Server Implementation
Section titled “Server Implementation”public class Subscription{ [Subscribe] public RewardListItemDto RewardAdded( [EventMessage] RewardListItemDto reward) => reward;}
Send subscription message in Mutation
Section titled “Send subscription message in Mutation”public async Task<RewardListItemDto> AddReward( [Service] IRepository<Reward> rewardRepository, [Service] ITopicEventSender sender, AddRewardDto reward){ var addedReward = await rewardRepository .AddAsync<AddRewardDto, RewardListItemDto>(reward); await sender.SendAsync( nameof(Subscription.RewardAdded), addedReward); return addedReward;}
Registration
Section titled “Registration”builder.Services .AddGraphQLServer() // ... queries and mutations .AddInMemorySubscriptions() .AddSubscriptionType<Subscription>();
// ...
app.UseRouting();app.UseWebSockets(); // add this line below UseRouting
Test subscriptions
Section titled “Test subscriptions”subscription MySubscription { rewardAdded { id, title }}
Client implementation
Section titled “Client implementation”# run from Client directory# server needs to run# correct server address needs to be specified in .graphqlrcdotnet graphql update
# Add NuGet PackageInstall-Package StrawberryShake.Transport.WebSockets
Add subscription definition
Section titled “Add subscription definition”subscription SubscribeRewardAdded { rewardAdded { id, title, description, targetAmount, }}
Build project to regenerate client afterwards.
Configuration
Section titled “Configuration”var graphQlUri = new Uri( builder.HostEnvironment.BaseAddress + "graphql");var graphQlWebSocketUri = new UriBuilder(graphQlUri){ Scheme = Uri.UriSchemeWss}.Uri;
builder.Services.AddRewardClient() .ConfigureHttpClient(client => client.BaseAddress = graphQlUri) .ConfigureWebSocketClient(client => client.Uri = graphQlWebSocketUri);
Use subscription in client
Section titled “Use subscription in client”@implements IDisposable// ...private IDisposable? rewardAddedSubscription;
protected override async Task OnInitializedAsync(){ await LoadDataAsync(); rewardAddedSubscription = RewardClient .SubscribeRewardAdded .Watch() .Subscribe(message => { var dto = new RewardListItemDto( message.Data.RewardAdded.Id, message.Data.RewardAdded.Title, message.Data.RewardAdded.TargetAmount, message.Data.RewardAdded.Description); rewards.Add(dto); StateHasChanged(); });}
public void Dispose(){ bookAddedSubscription?.Dispose();}