A graphql library that integrates nice with .net
Graphql is the strong typed protocol to pass data, that is the way I see it so my goal with this project is simple, use the strong typed system of .Net to create your Graphql schema in a way that you feel it natural.
dotnet add package GraphqlController --version 2.0.0
dotnet add package GraphqlController.AspNetCore --version 2.0.0
Configure your services
publicvoidConfigureServices(IServiceCollectionservices){services.AddControllers();// Call this two methodsservices// Add GraphQlController to your project.AddGraphQlController()// Use the current assembly to locate the graphql types .AddCurrentAssembly();// Call this to add http enpoint support services.AddGraphQlEndpoint();}Create your graphql types using normal C#, make sure to create them in the same assembly
publicclassStudent{publicstringName{get;set;}publicstringLastName{get;set;}publicintGrades{get;set;}}publicclassTeacher{publicstringName{get;set;}publicstringLastName{get;set;}}Create your root type. The root type is the top of the graph where you can access the data and it also represent a graphql api
// Must have the attribute [RootType] // and must derive from GraphNodeType[RootType]publicclassRoot:GraphNodeType{publicStudentStudent=>newStudent(){ Name =>"Jhon",
LastName =>"Robinson",
Grades =>80}publicTeacherTeacher=newTeacher(){
Name =>"Alejandro",
LastName =>"Guardiola"}}As last step we have to declare what the path for this api should be, in our Startup.cs class:
// use GraphqlControllerapp.UseGraphQLController();app.UseEndpoints(endpoints =>{// Include this line to declare that the root with type Root// will be serving at /graphql endpoints.MapGraphQLEnpoint<Root>("/graphql");endpoints.MapControllers();});If you run the project and call the api at /graphql using any tool like graphiql you can query this schema like:
{
student
{
namelastNamegrades
}
teacher
{
namelastName }
}
and get as result
{
"student": {
"name": "Jhon",
"lastName": "Robinson",
"grades": 80
},
"teacher":
{
"name": "Alejandro",
"lastName": "Guardiola",
}
}
To include attributes instead of using a property use a function
Root.cs
publicStudentStudent(stringstudentId){returnnewStudent(){
Name =>"Jhon",
LastName =>"Robinson",
Grades =>80}}graphql:
{
student(studentId: "somerandomid")
{
name
}
}
{
"student": {
"name": "Jhon"
}
} To create custom attribute types, just create the .net class for it
publicStudentStudent(SearchCriteriasearch){returnnewStudent(){
Name =>"Jhon",
LastName =>"Robinson",
Grades =>80}}// All properties must have both setterspublicclassSearchCriteria{publicstringName{get;set;}publicstringLastName{get;se;}} {
student(search: { name: "some name", lastName: "some last name" })
{
name
}
}
{
"student": {
"name": "Jhon"
}
} Every type that derive from IEnumerable will be considered as a list, for example we can add a new method in our Root to get all the students
Root.cs:
// this method is inside the Root classpublicIEnumerable<Student>AllStudents(intskip,inttake){returnnewList<Student>(){newStudent(){
Name =>"Jhon",
LastName =>"Robinson",
Grades =>80},newStudent(){
Name =>"Jhonatan",
LastName =>"Cruz",
Grades =>60},newStudent(){
Name =>"Rubio",
LastName =>"Acosta",
Grades =>30},}.Skip(skip).Take(take);} {
allStudent(skip: 1, take: 2)
{
name
}
}
{
"allStudent": [
{
"name": "Jhonatan"
},
{
"name": "Rubio"
}
] } To mark a type as non null you have to use the [NonNull] attribute, we can put the attribute in anything we want to be non null for example:
// this method is inside the Root class// the return type has be marked as non null[NonNull]// the attributes are marked as non null as wellpublicIEnumerable<Student>AllStudents([NonNull]intskip,[NonNull]inttake){returnnewList<Student>(){newStudent(){
Name =>"Jhon",
LastName =>"Robinson",
Grades =>80},newStudent(){
Name =>"Jhonatan",
LastName =>"Cruz",
Grades =>60},newStudent(){
Name =>"Rubio",
LastName =>"Acosta",
Grades =>30},}.Skip(skip).Take(take);}To use interfaces you just have to do it as you normally do in .net, for example lets create an interface for Student and Teacher sharing name and lastName:
publicinterfaceIPerson{publicstringName{get;set;}publicstringLastName{get;set;}}publicclassStudent:IPerson{publicstringName{get;set;}publicstringLastName{get;set;}publicintGrades{get;set;}}publicclassTeacher:IPerson{publicstringName{get;set;}publicstringLastName{get;set;}}now we can add a field in the root type to return the interface
Root.cs
// this method is inside the Root classpublicIPersonPerson(stringpersonId){returnTeacher=newTeacher(){
Name =>"Alejandro",
LastName =>"Guardiola"}}graphql:
{
person(personId: "somerandomId")
{
namelastName...onStudent
{
grades
}
}
}
{
"person": {
"name": "Alejandro",
"lastName": "Guardiola"
} } For union types use the Union<> class or derive from it, for example if we want to create an union of string and int we return Union<string, int> or create a class that derive from it.
Root.cs
// this property is inside the Root classpublicUnion<string,int>UnionTest=>new Union<string,int>("Myvalue");// this property is inside the Root classpublicUnion<string,int>UnionTest2=>new Union<string,int>(10);graphql:
{
unionTestunionTest2
}
{
"unionTest": "My value",
"unionTest2": 10 } To create mutations just create a class or multiple classes with the mutations but it should derive from GraphNodeType and have the mutation attribute with the Root type that they are mutating
// must have this attribute with the type of the root that they mutate[Mutation(tyepof(Root))]publicclassStudentMutations:GraphNodeType{// inside the class we create the functions for the mutationspublicStudentAddStudent([NonNull]StudentInputstudent){// logic to add student to the databasereturnnewStudent(){Name=student.Name,LastName=student.LastName,Grades=student.Grades}}}publicclassStudentInput{[NonNull]publicstringName{get;set;}[NonNull]publicstringLastName{get;set;}[NonNull]publicintGrades{get;set;}}graphql:
mutation {
addStudent(student:{
name: "Alejandro",
lastName: "Guardiola",
grades: 0
})
{
name
}
}
{
"addStudent": {
"name": "Alejandro"
}
} Similar to mutations to create subscriptions a class or multiple classes containing the subscriptions have to have GraphNodeType as base class and they have to use the SubscriptionAttribute with the type of the root that the subscriptions belog to. Every subscription have to retirn an IObservable.
// must have this attribute with the type of the root that // the subscriptions belog to.[Subscription(tyepof(Root))]publicclassStudentSubscriptions:GraphNodeType{// return the observable to the subscription// in this example we are just using an array of students// and streamming a value every 2 seconds.publicIObservable<Teacher>TeacherAdded()=>newIPerson[]{newTeacher(){Name="Alejo",LastName="Guardiola",},newTeacher(){Name="AlejoA",LastName="GuardiolaA",},newTeacher(){Name="AlejoB",LastName="GuardiolaB",},newTeacher(){Name="AlejoC",LastName="GuardiolaC",},newTeacher(){Name="AlejoD",LastName="GuardiolaD",}}.ToObservable().Zip(Observable.Interval(TimeSpan.FromSeconds(2)),(x,y)=>x);}graphql:
subscription {
teacherAdded
{
name
}
}
To use it in asp.net core in the Configure method in your Startup.cs add:
app.UseWebSockets();
...// after UseGraphQLController()app.UseGraphqlWebSocketProtocol<Root>("/graphql");This implementation uses apollo websocket subscription protocol, you can use apollo client learn more here: https://www.apollographql.com/docs/react/data/subscriptions/
SignalR support is comming!!
Thre is two ways to create a description for a type
Putting the description attribute where we want the description
[Description("Represent a person")]publicinterfaceIPerson{[Description("Person name.")]publicstringName{get;set;}[Description("Person last name.")]publicstringLastName{get;set;}}[Description("Represent an student")]publicclassStudent:IPerson{publicstringName{get;set;}publicstringLastName{get;set;}publicintGrades{get;set;}}the attribute can be used for the fields attributes as well
Just creating a xml documentation for the property or method where we want the description
[Mutation(typeof(Root))]publicclassStudentMutations:GraphNodeType{/// <summary>/// Add a new student to the database/// </summary>/// <param name="student">The student to add</param>/// <returns></returns>publicStudentAddStudent([NonNull]StudentInputstudent){returnnewStudent(){Name=student.Name,LastName=student.LastName,Grades=student.Grades};}}publicclassStudentInput{/// <summary>/// Name of the student/// </summary>[NonNull]publicstringName{get;set;}/// <summary>/// Last name of the student/// </summary>[NonNull]publicstringLastName{get;set;}/// <summary>/// Grades/// </summary>[NonNull]publicintGrades{get;set;}}The only downside of this method is that for this to work you have to include the xml documentation file in the build
To asignate a custom name to the type, the type must have the Name attribute
[Name("MyCustomStudentTypeName")]publicclassStudent:IPerson{publicstringName{get;set;}publicstringLastName{get;set;}publicintGrades{get;set;}}this can be used for the attributes as well
When you return a Task<> from a field GraphQLController automatically take of that.
You can get the CancellationToken by adding it in the method parameters
publicasyncTask<Student>AddStudent(StudentInputstudent,CancellationTokencancellationToken){returnawait// some result }GraphqlController support Dependency Injection out of the box, it integrates with the built-in asp.net core dependency injection. To inject services to a type it must derive from GraphNodeType, because the root and the mutations types already has to derive from it, you can inject services in the root and mutations already.
Root.cs:
[RootType]publicclassRoot:GraphNodeType{// private methods and properties are ignoredprivateStudentRepository_studentRepo;publicRoot(StudentRepositorystudentRepo){_studentRepo=studentRepo;}// exposed by the graphql apipublicList<Student>AllStudents=>_studentRepo.GetAll();}In order to use dependency injection in nested types, again they have to derive from GraphNodeType and be created using IGraphqlResolver service. Classes that derive from GraphNodeType can override the OnCreateAsync function to initialize
Lets modify the Teacher class a little:
// TeacherDomain in the genric parameter// is a custom parameter that we can pass to the instance// to initializedpublicclassTeacher:GraphNodeType<TeacherDomain>{StudentRepo_studentRepo;// Inject servicespublicTeacher(StudentRepostudentRepo){_studentRepo=studentRepo;}publicoverrideTaskOnCreateAsync(TeacherDomaindomain,CancellationTokencancellationToken){// Initialize the instance hereName=domain.Name;LastName=domain.LastName;returnTask.CompletedTask;}publicstringName{get;set;}publicstringLastName{get;set;}publicIEnumerable<Student>Students(){return_studentRepo.GetTeacherStudents(Name);}}Now to create the teacher from another type we use the IGraphqlResolver service:
Root.cs
// services injected in RootIGraphqlResolver_graphqlResolver;TeacherRepo_teacherRepo;// this method is inside Root.cspublicasyncTask<Teacher>Teacher(stringname,CancellationTokencancellationToken){varteacherDomain=_techerRepo.GetTeacherByName(name);// create teacher using IGraphqlResolverreturnawait_graphqlResolver.CreateGraphqlEnityAsync<Teacher,TeacherDomain>(teacherDomain,cancellationToken);}A persisted query is an ID or hash that can be sent to the server instead of the entire GraphQL query string. This smaller signature reduces bandwidth utilization and speeds up client loading times. Persisted queries are especially nice paired with GET requests, enabling the browser cache and integration with a CDN. (Copied from the Apollo documentation)
Persisted queries are implmented using the Apollo protocol https://github.com/apollographql/apollo-link-persisted-queries#protocol
Persisted queries can be cached using memory cache or a distributed cache.
Using memory cache the queries are stored on the app memory. This aproach works well for the majority of many cases:
Pros:
- It is fast because works with the app memory
- Dont need an external service or store
- Dont need any configuration
Cons:
- Cannot be share between multiple app instances
- The cache is reseted every time the app restart
- Many queries cached can make your memory go higher
To enable Persisted queries with memory cache call, you have to make sure you have Asp.net core memory cache added to your services you can learn more here: https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1
In your Startup.cs in ConfigureServices add:
// add asp.net core memory cacheservices.AddMemoryCache();services.AddGraphQlEndpoint()// add in memory persisted query support.AddPersistedQuery().AddInMemoryPersistedQuery();In your Startup.cs in Configure add:
app.UseGraphQLController()// specify for what root you want the middleware.UseGraphQlExecutionFor<Root>()// add persisted query execution middleware.UsePersistedQuery();(From Microsoft docs) A distributed cache is a cache shared by multiple app servers, typically maintained as an external service to the app servers that access it. A distributed cache can improve the performance and scalability of an ASP.NET Core app, especially when the app is hosted by a cloud service or a server farm.
A distributed cache has several advantages over other caching scenarios where cached data is stored on individual app servers.
Pros:
- Is coherent (consistent) across requests to multiple servers.
- Survives server restarts and app deployments.
- Doesn't use local memory.
Cons:
- Require external cache service (Redis, NCache, etc...)
- Require more configuration
- Slighty slower than Memory Cache
To use it first add the distributed cache: https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1
and then call AddDistributedPersistedQuery in your services.
In your Startup.cs in ConfigureServices add:
// add asp.net core memory cacheservices.AddMemoryCache();services.AddGraphQlEndpoint()// add distributed persisted query support.AddDistributedPersistedQuery();and dont forget to add the middleware as explained in In Memory Persisted queries
You can cache resources that does not change frequently, this is very useful because the server does not need to recompute the values or get it from the database every time a client request it.
GraphqlController.AspNetCore has support for caching with the CacheControlAttribute. You can use CacheControlAttribute on a Type or a field to indicate the time in seconds that the specific resource will be cached.
// Student will be cached for 60 seconds[CacheControl(60)]publicclassStudent:IPerson{publicstringName{get;set;}publicstringLastName{get;set;}publicintGrades{get;set;}}// The field allStudents will be cached for 10 seconds.// When you use it on a field it will override any Cache that the// type has, this means that the 60 seconds on the type Student// will be overrided to 10[CacheControl(10)]publicIEnumerable<Student>AllStudents([NonNull]intskip,[NonNull]inttake){returnnewList<Student>(){newStudent(){
Name =>"Jhon",
LastName =>"Robinson",
Grades =>80},newStudent(){
Name =>"Jhonatan",
LastName =>"Cruz",
Grades =>60},newStudent(){
Name =>"Rubio",
LastName =>"Acosta",
Grades =>30},}.Skip(skip).Take(take);}After the query is executed the cache time in second will be computed for that execution. The current cache policy takes the lowest value.
For the cache to work we have to added in our services
In your Startup.cs in ConfigureServices add:
services.AddGraphQlEndpoint().AddGraphqlCache(newCacheConfiguration(){// Default max age in seconds if no cache control// can be found the default value is 0DefaultMaxAge=5,// Set this option if you want to cache the responses// using the server cache, the options are Distributed,// Memory or None by default. If you want to use other different// than none make sure you have the correspondent cache service// as explained in persisted queries.ResponseCache=ResponseCacheType.Distributed,// When this property is set to true the cache control// headers will be included if the request is served// via HTTP GET. This is very useful because browsers// can cache the responses and not even have to ask // the server. If you use the Apollo Client this is a // good combination with persisted queries forced to GEET// only. The default value is false.UseHttpCaching=true,// When this property is set to true the eEtag header// will be computed in the response, this is ver useful// if you want to save bandwidth. The browser will include// the hash and if the hash is equal to the response hash// the server will send a status code of 304(Not Modified).// Only for GET requestsIncludeETag=true});In your Startup.cs in Configure add:
app.UseGraphQLController()// specify for what root you want the middleware.UseGraphQlExecutionFor<Root>()// Important!! This middleware must be the last one registerd// That means if you register another middleware like// .UsePersistedQuery() this one must go after..UseCache();- Support for the graphql net server project
- Posibly more features to add and bugs to fix
This library is not possible without https://github.com/graphql-dotnet/graphql-dotnet
And also to make the xml documentation descriptions https://github.com/loxsmoke/DocXml

