×
Back to book

Client Server Communication in C#Bot

This covers the basics of how to fetch data from a C#Bot server using GraphQL and React

In this article we are going to go over the basics of fetching data from the server and displaying it on the client-side.

Retrieving Data

There are 2 different ways to fetch data from the server-side. The first is by using REST endpoints created for each entity displayed on the entity model. Details on these endpoints can be viewed by navigating to /api/swagger in your application when it is running.

The second, and arguably more powerful, way of fetching data is by using a GraphQL API implemented by C#Bot. This API allows for fetching data based on a series of conditions, as well as fetching entities related to the entities you have fetched. This article is going to go more into depth on the GraphQL approach.

Setting Up Your Application

In this article we are going to use the following model for the entity diagram:

Diagram for the entity model

And the following model for the ui diagram:

Diagram for the ui model

Note that the custom tile on this ui model is named Main.

Introduction to GraphQL

GraphQL is a query language that is often used over (but not limited to) a REST transport medium. C#Bot has an interactive query tool called GraphiQL built into the application. This tool is located at /admin/graphiql in your application and requires you to be logged in.

GraphQL is effectively a series of functions that can take arguments and return values which you have requested. It is important to note that GraphQL is simply a specification of a protocol and the implementation of these functions is left to the application itself.

The first thing that we must do is determine what queries there are which can be run. GraphiQL has a built in documentation explorer that can be accessed from the top right hand corner of the interface.

The documentation interface

Opening this documentation explorer we can see there are two different root types of functions that can be run: queries and mutations. A query is used for the fetching of data and a mutation is used for creating, updating or deleting data. In the search box of the docs window we can search for 'employee' to find what operations we can perform on the employee entity.

The GraphQL documentation for the employee entity

Here we can see all of the different functions that can be run for entity. For this case, we are going to fetch a list of all employee entities. To do this we must use the employeeEntitys query. We can click into the docs for this query to see some more information about it as well.

The docs for the employee query

We can see from these docs, this function takes several arguments and returns an array of employee entities.

Using this we can write our first query in the query editor. This editor has tab completion based on the types that are specified in the docs and can be invoked using the Ctrl+Space hotkey.

# Anything after a hash symbol is a comment

query MyFirstQuery {   # The name of the query, this can be anything
  employeeEntitys {    # The GraphQl function to run
    id                 # The return fields you are requesting
    email
    givenName
    surname
  }
}

This query will fetch all employees from the API and return them as JSON like so.

{
  "data": {
    "employeeEntitys": [
      {
        "id": "614a25f9-2d18-424d-bc93-7dd0fdfd7be0",
        "email": "employee@example.com",
        "givenName": "Jane",
        "surname": "Doe"
      }
    ]
  }
}

Querying Relations in GraphQL

One of the most powerful features of GraphQL is the ability to query your API as if it was a graph. Before, when we were exploring the documentation for the query, we could see that for the function we were running it was returning a list of EmployeeEntityType. We can click into this to see what fields on that entity we can fetch.

The employee entity docs

On this we can see some of the fields we fetched before which return primitive types such as strings or ids however, we can also see there is a field called department that returns a DepartmentEntityType. This field is created because of the relation between employees and departments in the model. We can use this to fetch the department that the employees are in.

query MyFirstQuery {
  employeeEntitys {
    id
    email
    givenName
    surname
    department {         # The department sub query
      id                 # The fields of the department to fetch
      name
    }
  }
}

Which will return the following data:

{
  "data": {
    "employeeEntitys": [
      {
        "id": "614a25f9-2d18-424d-bc93-7dd0fdfd7be0",
        "email": "employee@example.com",
        "givenName": "Jane",
        "surname": "Doe",
        "department": {
          "id": "868c4afb-06f0-45bb-842b-b958a767f32e",
          "name": "Accounting"
        }
      }
    ]
  }
}

This means we can now fetch multiple levels of data without having to write any custom server-side code.

Query Arguments

If we could only fetch all the data from our API then this would not be very useful, so that's why we can put arguments into our queries to restrict the data that is returned. We can see the list of arguments that are accepted in the documentation for the query that we fetched before.

In this case we are going to write a query to return the first 10 employees in alphabetical order by their surname and given name. First up we have to modify our query a little bit.

query MyFirstQuery($take:Int, $orderBy: [OrderByGraph]) {   # Now the query takes arguments
  employeeEntitys(take: $take, orderBy: $orderBy) {         # Use the arguments in the function
    id
    email
    givenName
    surname
    department {
      id
      name
    }
  }
}

We have now made the query take 2 arguments that can be provided to the query, and those arguments are going to be used for the data function. To add the variables we must expand the 'Query Variables' tab below the editor and type the following JSON:

{
  "take": 10,
  "orderBy": [
    {
      "path": "surname",
      "descending": true
    },
    {
      "path": "givenName",
      "descending": true
    }
  ]
}

So now your query window will look like this:

The query window after adding variables

You can run this query and now the variables that were provided will be used in the query and fetch the data correctly.

Using the Data in React

Now we have an idea on how the GraphQL works, we can now take to implementing it in react. For the common tasks of fetching entities, we already have some functions that implement this for us already. In clientside/src/Views/Tiles/MainTile.tsx we can place the following code:

First we are going to add some imports to the top of the file:

// % protected region % [Add any extra imports here] on begin
import { EmployeeEntity } from 'Models/Entities';
import { action, observable } from 'mobx';
// % protected region % [Add any extra imports here] end

Secondly we can add some class methods for the fetching of the data:

// % protected region % [Add class properties here] on begin
@observable
private status: 'loading' | 'error' | 'done' = 'loading';

@observable
private employees: EmployeeEntity[] = [];

public componentDidMount(): void {
    EmployeeEntity.fetch<EmployeeEntity>()
        .then(this.onDone)
        .catch(this.onError);
}

@action
public onDone = (employees: EmployeeEntity[]) => {
    this.employees = [...employees];
    this.status = 'done';
}

@action
public onError = (error: any) => {
    console.error(error);
    this.status = 'error';
}
// % protected region % [Add class properties here] end

Going through these fields one by one:

  • status - The status of the http request that we are sending.
  • employees - The list to store the fetched employees in after we have received the data.
  • componentDidMount - This function is a react lifecycle method that occurs when the component is first mounted to the page. We can use this to fetch the data when the tile is first displayed.
  • onDone - The callback function for when we have fetched the data.
  • onError - The callback function in the case that fetching the data failed.

Finally we can do the implementation of the render function:

public render() {
    let contents = null;

    // % protected region % [Override contents here] on begin
    // We deal with the possible states of the request in a switch statement
    switch (this.status) {
        case 'loading':
            contents = 'Loading....';
            break;
        case 'error':
            contents = 'Something went wrong with the request';
            break;
        case 'done':
            contents = (
                <>
                    <h3>Employees</h3>
                    <div>
                        {this.employees.map(e => (
                            <div>{e.givenName} {e.surname}</div>
                        ))}
                    </div>
                </>
            );
            break;
    }
    // % protected region % [Override contents here] end

    return contents;
}

We can see that we use the status field in a switch statement to change the contents of the tile based on what the network request is doing.

Fetching data with conditions

Now that we have a basic fetch done for the application we can perform some of the more complex operations we did in GraphQL earlier. Under the hood this fetch function is actually using the same employeeEntitys GraphQL function we were using before to fetch data, so it can be passed the same arguments. This can be done by passing a JSON object of variables to the first argument of the fetch function.

So to perform the same query as before where we fetch the first 10 employees in the order of their surname then given name we can can alter the componentDidMount function.

public componentDidMount(): void {
    EmployeeEntity.fetch<EmployeeEntity>({
        "take": 10,
        "orderBy": [
            {
                "path": "surname",
                "descending": true
            },
            {
                "path": "givenName",
                "descending": true
            }
        ]
    })
        .then(this.onDone)
        .catch(this.onError);
}

These variables are then passed through to the underlying graphql function for fetching data. These arguments are typed in the typescript to validate that only the correct arguments are being passed though.

Fetching Relations

There are also times where we want to fetch relationships as well. This can be done as a part of the second argument of the fetch function. This argument is a string that is appended to the body of the GraphQL query. If we wanted to fetch the department the employee is in then we could copy the department section from the GraphQL query we made before and use it for the second argument.

public componentDidMount(): void {
    EmployeeEntity.fetch<EmployeeEntity>(undefined, `
        department {
            id
            name
        }
    `)
        .then(this.onDone)
        .catch(this.onError);
}

Note that both parameters can be used at the same time to use all the functionality.

Default Configuration

The query run by fetch will have several default fields. These are all of the attributes present on the model as well as some of the relationships. To see what fields are already provided, you can take a look at the defaultExpands in the client-side model. For example in clientside/src/Models/Entities/EmployeeEntity.tsx you can see the department is already fetched by default.

// % protected region % [Customize Default Expands here] off begin
public defaultExpands = `
    department {
        ${Models.DepartmentEntity.getAttributes().join('\n')}
    }
`;
// % protected region % [Customize Default Expands here] end

The data in this string can be changed by enabling the protected region.

Running Raw GraphQL

In some cases we do not want some more control over the query we are running. This can be done by invoking the query directly. To call the GraphQL API we use a library called Apollo. We can replace our implementation of the data fetch with this if we need to.

First we will need to update our imports:

// % protected region % [Add any extra imports here] on begin
import { EmployeeEntity, IEmployeeEntityAttributes } from 'Models/Entities';
import { action, observable } from 'mobx';
import { store } from 'Models/Store'
import { gql } from 'apollo-boost';
// % protected region % [Add any extra imports here] end

Now we can call use the apollo client to call the GraphQL API directly in componentDidMount. The rest of the code for the tile can remain unchanged:

public componentDidMount(): void {
    store.apolloClient.query({
        query: gql`
            query MyFirstQuery($take:Int, $orderBy: [OrderByGraph]) {
                employeeEntitys(take: $take, orderBy: $orderBy) {
                    id
                    email
                    givenName
                    surname
                    department {
                        id
                        name
                    }
                }
            }
            `,
        variables: {
            "take": 10,
            "orderBy": [
                {
                    "path": "surname",
                    "descending": true
                },
                {
                    "path": "givenName",
                    "descending": true
                }
            ]
        }
    })
        .then(result => result.data.employeeEntitys.map((e: IEmployeeEntityAttributes) => new EmployeeEntity(e)))
        .then(this.onDone)
        .catch(this.onError);
}

This will now run the exact same GraphQL we composed by hand before.