Skip to content
Open
93 changes: 93 additions & 0 deletions src/AspNetCoreMulti/Example/GraphQL/Operations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Example.Repositories;
using GraphQL.Resolvers;
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Security.AccessControl;

namespace Example.GraphQL
{
public interface IOperation
{
IEnumerable<IFieldType> GetFields();
}

public interface IDogOperation : IOperation { }

public interface ICatOperation : IOperation { }

public class DogOperation : IDogOperation
{
private readonly IServiceProvider _serviceProvider;

public DogOperation(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public IEnumerable<IFieldType> GetFields()
{
var fields = new List<IFieldType>();

var sayField = new FieldType
{
Name = "say",
Type = typeof(StringGraphType),
Resolver = new FuncFieldResolver<object, object>(context => "woof woof woof")
};

fields.Add(sayField);

var breedListField = new FieldType
{
Name = "dogBreeds",
Type = typeof(NonNullGraphType<ListGraphType<NonNullGraphType<DogType>>>),
Resolver = new FuncFieldResolver<object, object>(context =>
{
using var scope = _serviceProvider.CreateScope();
var dogRepository = scope.ServiceProvider.GetRequiredService<DogRepository>();
var dogs = dogRepository.GetDogs();
return dogs;
})
};

fields.Add(breedListField);

var imageDetailsField = new FieldType
{
Name = "dogImageDetails",
Type = typeof(NonNullGraphType<ImageDetailsType>),
Resolver = new FuncFieldResolver<object, object>(async context =>
{
using var scope = _serviceProvider.CreateScope();
var imageDetailsRepository = scope.ServiceProvider.GetRequiredService<DogImageDetailsRepository>();
var imageDetails = await imageDetailsRepository.GetDogImageDetails();
return imageDetails;
})
};

fields.Add(imageDetailsField);

return fields;
}
}

public class CatSayOperation : ICatOperation
{
public IEnumerable<IFieldType> GetFields()
{
var fields = new List<IFieldType>();
var sayField = new FieldType
{
Name = "say",
Type = typeof(StringGraphType),
Resolver = new FuncFieldResolver<object, object>(context => "meow meow meow")
};

fields.Add(sayField);

return fields;
}
}
}
38 changes: 38 additions & 0 deletions src/AspNetCoreMulti/Example/GraphQL/Queries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using GraphQL.Types;
using System.Collections.Generic;

namespace Example.GraphQL
{
public class Queries
{
public class DogQuery : ObjectGraphType<object>
{
public DogQuery(IEnumerable<IDogOperation> dogOperations)
{
foreach (var dogOperation in dogOperations)
{
var fields = dogOperation.GetFields();
foreach (var field in fields)
{
AddField((FieldType)field);
}
}
}
}

public class CatQuery : ObjectGraphType<object>
{
public CatQuery(IEnumerable<ICatOperation> catOperations)
{
foreach (var catOperation in catOperations)
{
var fields = catOperation.GetFields();
foreach (var field in fields)
{
AddField((FieldType)field);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to use AutoSchema here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give me some more details here? Do you have any doc handy for AutoSchema? I'm thinking it is some way to set things up automatically.

One reason I am doing the way I am right now, is to be non-opinionated on the setup. I give control to the implementor of IQuery to give me the fields as they see fit. They can subsequently use reflection or any other controlled mechanism to set things up and finally return the fields which I would then attach to the root query. This is especially useful when the application is large, and consumers need several ways to opt in/out of things.

Again, I might be talking about something different but will wait for information on AutoSchema.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure how the use of AutoSchema fits into your example. AutoSchema is useful when creating a type-first schema, where the schema is automatically constructed from CLR types. Your sample does not use those features. Moreover, you are attempting to use methods from multiple classes to assemble a large root graph type. An auto-generated root query type would be contrary to that goal.

Copy link
Member

@Shane32 Shane32 Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can see a demonstration of a type-first schema here:

https://github.com/graphql-dotnet/graphql-dotnet/tree/master/src/GraphQL.StarWars.TypeFirst

The entire schema can be constructed via:

services.AddGraphQL(b => b
    .AddSystemTextJson()
    .AddAutoSchema<StarWarsQuery>(c => c.WithMutation<StarWarsMutation>()));

You will notice there are no 'graph types' but rather simple CLR classes decorated with attributes where necessary

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my own graphs, I use a slight variation of this to allow for DI injection of scoped services, which is not possible with the sample seen there. However, the code exists in the tests here:

https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.Tests/Bugs/Issue2932_DemoDIGraphType.cs

I also published it as a NuGet package here:

https://www.nuget.org/packages/Shane32.GraphQL.DI

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EmmanuelPonnudurai I'm fine with example as is without AutoSchema.

}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using GraphQL.Types;
using System;
using static Example.GraphQL.Queries;

namespace Example
namespace Example.GraphQL
{
public class DogSchema : Schema
{
Expand All @@ -12,14 +13,6 @@ public DogSchema(IServiceProvider provider, DogQuery query)
}
}

public class DogQuery : ObjectGraphType<object>
{
public DogQuery()
{
Field<StringGraphType>("say", resolve: context => "woof woof woof");
}
}

public class CatSchema : Schema
{
public CatSchema(IServiceProvider provider, CatQuery query)
Expand All @@ -28,12 +21,4 @@ public CatSchema(IServiceProvider provider, CatQuery query)
Query = query;
}
}

public class CatQuery : ObjectGraphType<object>
{
public CatQuery()
{
Field<StringGraphType>("say", resolve: context => "meow meow meow");
}
}
}
28 changes: 28 additions & 0 deletions src/AspNetCoreMulti/Example/GraphQL/Types.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using GraphQL.Types;

namespace Example.GraphQL
{
public class DogType : ObjectGraphType<Dog>
{
public DogType()
{
Field(x => x.Breed);
}
}

public class CatType : ObjectGraphType<Dog>
{
public CatType()
{
Field(x => x.Breed);
}
}

public class ImageDetailsType : ObjectGraphType<ImageDetails>
{
public ImageDetailsType()
{
Field(x => x.Url);
}
}
}
22 changes: 22 additions & 0 deletions src/AspNetCoreMulti/Example/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Example
{
public interface IBreed
{
public string Breed { get; set; }
}

public class Dog : IBreed
{
public string Breed { get; set; }
}

public class Cat : IBreed
{
public string Breed { get; set; }
}

public class ImageDetails
{
public string Url { get; set; }
}
}
16 changes: 16 additions & 0 deletions src/AspNetCoreMulti/Example/Repositories/CatRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Example.Repositories;

using System.Collections.Generic;
using System.Linq;

public class CatRepository
{
private static readonly List<Cat> Cats = new()
{
new Cat { Breed = "Abyssinian" },
new Cat { Breed = "American Bobtail" },
new Cat { Breed = "Burmese" }
};

public IEnumerable<Cat> GetCats() => Cats.AsEnumerable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Example.Repositories;

using System;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

public class DogImageDetailsRepository
{
private readonly IHttpClientFactory _httpClientFactory;

public DogImageDetailsRepository(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public async Task<ImageDetails> GetDogImageDetails()
{
try
{
var client = _httpClientFactory.CreateClient("DogsApi");
var result = await client.GetStringAsync("api/breeds/image/random");
var apiResponse = JsonSerializer.Deserialize<DogsImageApiResponse>(result);

return new ImageDetails { Url = apiResponse.Message };
}
catch (Exception ex)
{

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

return new ImageDetails { Url = ex.Message };
}
}

private class DogsImageApiResponse
{
[JsonPropertyName("status")]
public string Status { get; set; }

[JsonPropertyName("message")]
public string Message { get; set; }
}
}
16 changes: 16 additions & 0 deletions src/AspNetCoreMulti/Example/Repositories/DogRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Example.Repositories;

using System.Collections.Generic;
using System.Linq;

public class DogRepository
{
private static readonly List<Dog> Dogs = new()
{
new Dog { Breed = "Doberman" },
new Dog { Breed = "Pit Bull" },
new Dog { Breed = "German Shepard" }
};

public IEnumerable<Dog> GetDogs() => Dogs.AsEnumerable();
}
14 changes: 13 additions & 1 deletion src/AspNetCoreMulti/Example/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Example.GraphQL;
using Example.Repositories;
using GraphQL;
using GraphQL.MicrosoftDI;
using GraphQL.Server;
using GraphQL.Server.Ui.Playground;
using GraphQL.SystemTextJson;
using GraphQL.Types;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -16,6 +17,13 @@ public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DogRepository>();
services.AddScoped<DogImageDetailsRepository>();
services.AddScoped<CatRepository>();

services.AddSingleton<IDogOperation, DogOperation>();
services.AddSingleton<ICatOperation, CatSayOperation>();

services.AddGraphQL(b => b
.AddHttpMiddleware<DogSchema>()
.AddHttpMiddleware<CatSchema>()
Expand All @@ -28,6 +36,10 @@ public void ConfigureServices(IServiceCollection services)

services.AddLogging(builder => builder.AddConsole());
services.AddHttpContextAccessor();
services.AddHttpClient("DogsApi", x =>
{
x.BaseAddress = new System.Uri("https://dog.ceo/");
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down