GraphQL

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. It is a single API face for different clients like mobile, desktop apps, tablets. ASP.NET Zero includes an implementation of GraphQL API. In the solution the *.GraphQL project exposes some built-in queries like Organization Units, Roles and Users . There is also a unit test project *.GraphQL.Tests in the test folder. You can add your own GraphQL unit tests to this project.

GraphQL Structure

The *.GraphQL project consists of a MainSchema. In the MainSchema there is QueryContainer that hosts all queries. Out of the box, there are queries for Roles, Organization Units and Users.

The .GraphQL project depends on AbpZeroTemplateCoreModule. It is used in the *.Web.Host and *.Web.Mvc projects. So both ASP.NET Core Angular and ASP.NET Core MVC templates contain GraphQL project.

The GraphQL is being added in the Configure method of *.Web.Host.Startup for Angular project and *.Web.Mvc.Startup for MVC project.

There is also GraphQL Playground included in the project. GraphQL Playground is an IDE like Swagger, helps you to write your queries directly from your website. The default endpoint of the playground is http://your-domain.com/ui/playground

The GraphQL middleware is being added in ConfigureService method of *.Web.Host.Startup for Angular project and *.Web.Mvc.Startup for MVC project.

services.AddAndConfigureGraphQL();

AddAndConfigureGraphQL() is an extension method to add the GraphQL middleware and makes necessary configurations. In debug mode, all GraphQL exceptions are being sent to the client but in release mode exceptions are not being sent to the client.

Enabling GraphQL

By default, GraphQL is disabled. To enable the GraphQL, open WebConsts.cs in the *.Web.Core project and set GraphQL.Enabled = true.

GraphQL Endpoint

Changing The Default GraphQL Endpoint

You can change the default endpoint of the GraphQL. To do this, open the Startup.cs in the *.Web.Host project for Angular or in the *.Web.Mvc project for MVC. In the Configure method, find the line app.UseGraphQL<MainSchema>(). And set the path parameter like below;

app.UseGraphQL<MainSchema>(path: "/mygraphql");

This will change your endpoint to yourdomain.com/mygraphql.

GraphQL Playground

GraphQL Playground is an extension tool that helps you write your GraphQL queries easily. It supports autocompletion & error highlighting. It is included AspNet Zero but disabled by default.
The default GraphQL Playground endpoints are;

Changing GraphQL Playground Default Endpoint

You can change the default endpoint of the GraphQL Playground. To do this, open Startup.cs of the *.Web.Host for Angular or the *.Web.Mvc for MVC. In the Configure method, find the lineapp.UseGraphQLPlayground() and set the Path property of GraphQLPlaygroundOptions ;

This will change your playground endpoint to yourdomain.com/myplayground.

GraphQL Playground Settings

To be able to run the queries that requires authentication, you need to open Playground on your browser, open settings tab and change the request.credentials from omit to include . This is needed for cookie authentication.

On the other hand, Playground uses polling to update the schema and refreshes the schema every 2 seconds. You can also safely disable this option to not see flooding XHR requests on your browser's network tab.

The necessary changes are;

{
"request.credentials": "include",
"schema.polling.enable": false
}

After these settings, you need to save it by pressing SAVE SETTINGS button.

Running Queries on GraphQL Playground

Playground gives you the advantage of writing the queries with intellisense and see the results on your browser. There are two types of queries; Queries require authentication and Queries don't require authentication. For the queries that don't require authentication, you can directly write the query on the Playground and see the results. But for the authentication required queries you have to authenticate first. To authenticate, read the next section of the project that is relevant to you..

Authenticating Playground Requests in MVC Project

Authenticating With Cookie

MVC project uses cookie authentication. Before running any queries that need authentication, you need to login to your website to retrieve the authentication cookie.
Start your *.Web.Mvc project, open your browser and navigate to http://localhost:62114/Account/Login.
After successful login, you will have a valid authentication cookie in your browser's storage.
Now you can go to http://localhost:62114/ui/playground and run your query as authenticated.

Authenticating Playground Requests in Angular Project

Angular project uses both token (Bearer) authentication and cookie authentication. The simplest way is authenticating with cookie.

Be aware that if your authentication is not valid or if you didn't set "request.credentials": "include" in your Playground settings, you will see the below error response for the authentication required queries.

GraphQL Query Execution Flow

When a new query is initiated, the GraphQL middleware parses the query then routes it to the corresponding query handler which is registered in QueryContainer class of the *.GraphQL project. Out of the box there are 3 query handlers registered in QueryContainer: RoleQuery,UserQuery and OrganizationUnitQuery. The next step is the Resolve method of the query with the query context. The query context has all the information about a query like UserContext, Arguments, Selections and Variables. If there is an [AbpAuthorize] attribute on the Resolve method, AspNet Boilerplate framework checks the related permission of the logged on user and throws ABP_AUTHORIZATION exception when the related permission is not granted. If there is no [AbpAuthorize] attribute, then it can be run by anonymous users. In the Resolve method, the necessary query runs and returns a DTO object. Then the DTO object is being converted to the relevant IGraphType. This conversation is made by the GraphQL middleware and the middleware knows which IGraphType to be converted, as it is registered it in the QueryContainer.

Writing a New GraphQL Query

Let's create a new GraphQL query. For this example we will use AbpAuditLogs that stores all logs in the database. Below is a simplified version of the AuditLog entity.

Create a new DTO class. Name it AuditLogDto.cs, locate it in the DTO folder of the *.GraphQL project. Add the [AutoMapFrom(typeof(AuditLog))] attribute to register the AuditLogDto for mapping. This will be used to map from the AuditLog entity to AuditLogDto.

Create a new class in the Types folder of the *.GraphQL project and name it AuditLogType.cs. Derive this class from ObjectGraphType<AuditLogDto>. AuditLogType will be used to map from the AuditLogDto class.

Be careful with the nullable types. You must explicitly define the nullable field.

Create a new class and name it AuditLogQuery.cs in the Queries folder of the *.GraphQL project. This is the query resolver for AuditLogs. It should be derived from the base AbpZeroTemplateQueryBase<TField,TResult>. This abstract base class has two generic types. TField which is the type to return to the client. In this example, it is a list of AuditLogType. Other field is TResult, this is the return type of the Resolve() method. Basically TResult will be mapped to TField by the GraphQL middleware.

To get the list of audit logs, IRepository<AuditLog, long> is injected into the query. Note that the base class AbpZeroTemplateQueryBase is registered to the dependency injection container with ITransient lifecycle. So all the GraphQL queries derived from AbpZeroTemplateQueryBase are meant to be ITransientDependency.

The base constructor of the query requires a field name. In our sample, the field name is auditLogs. This name will be used in the query text. The second parameter is the list of Arguments. It is a Dictionary<string, Type> to allow clients to filter the query with these arguments. In this example, userId and serviceName are arguments and can be sent by clients to filter the records.

Let's write the content of Resolve() method. Basically we need to return a list of AuditLogDto filtered with the given arguments. To keep it simple, we will return only the 10 records. In the below code, ContainsArgument is an extension method that helps us to add a condition to the query if there is an argument. If the client sends UserId or ServiceName arguments, they will be filtered in the query. On the last row, ProjectToListAsync is a base method and maps the queryable list to the list of AuditLogDto.

The new query is ready but accessible by anonymous users. If you would like to add a permission check for your query, it is the same as we use in Application Services. Add the [AbpAuthorize] attribute with the required permission to the Resolve() method.

To run the final query with [AbpAuthorize] attribute, you need to get an authentication cookie. Go to your login page:

http://localhost:62114/Account/Login MVC.

http://localhost:22742/ui/login Angular

After successful login, you will have your authentication cookie and you can run the query.

Writing a New Unit Test For Your Query

When you write a new GraphQL query, it is suggested to write a new unit test for the query. ASP.NET Zero provides a unit test project for your GraphQL queries. There is a test folder in your ASP.NET Zero solution. In the test folder, open the *.GraphQL.Tests project. Create a new folder for your new query.

In our previous sample, we have written a query for AuditLog entity. Let's write a unit test for the AuditLogQuery.

Before writing a new unit test for a new entity. You need to add sample data to the in-memory test database. For the AuditLog tests, let's add some test data;

Open *.Test.Base project and create a new class, name it TestAuditLogsBuilder.cs.
In the below class two test audit log entries are being added.

Create a new folder in the root directory of *.GraphQL.Tests project, name it AuditLogs.

Create a new class in the AuditLogs folder and name it AuditLogQuery_Tests.cs.

Derive the AuditLogQuery_Tests class from GraphQLTestBase<MainSchema>. This will help us to use some handy base functionality.

Note that MainSchema is the schema for all our queries and it was registered in the Startup of the *.Web.Mvc or *.Web.Host project.

Below is the full content of the AuditLogQuery_Tests class. AssertQuerySuccessAsync() method comes from the base class. It gets a GraphQL query and an expected value. It performs a string comparison with the actual value and the expected value when it's not equal, it fails the test.

Conclusion

GraphQL helps us providing our clients a very flexible Web API. In this document, we have seen the structure of the ASP.NET Zero GraphQL implementation. We understood how we can enable GraphQL and GraphQL Playground. We added a new sample GraphQL query to understand it better. We also added a new unit test for the query.