在Blazor应用程序中整合GraphQL

333 阅读11分钟

在上一次的JS状态调查中,开发人员的满意度达到了94%,GraphQL正迅速成为开发人员在网络应用中消费数据时的默认API选择。

GraphQL承诺的性能提升是其在不同规模的许多应用程序中被越来越多地采用的一个主要原因。

在本教程中,我们将学习如何在Blazor WebAssembly应用程序中消费GraphQL API的数据。虽然我们不会考虑使用GraphQL的性能影响,但我们将重点关注如何将GraphQL API集成到应用程序中。

开始学习

在开始本教程之前,请确保你对C#有基本的了解,并在你的本地计算机上安装了.NET SDK

什么是GraphQL?

Graph Query Language,俗称GraphQL,是一种API技术,它允许客户端通过查询请求所需的确切数据,以声明的方式从服务器获取数据。

在使用GraphQL API时,你可以对GraphQL服务器执行以下三种操作。

  • 查询,这类似于REST API中的GET HTTP动词,从服务器中获取数据。
  • 突变,类似于REST API中的POST,UPDATEDELETE HTTP动词。
  • 订阅,连接到GraphQL服务器,以重新获取数据,而无需重新启动连接。

在本文中,我们将从Blazor应用程序中对一个预建的Node.js应用程序执行查询和突变操作。这在启动时暴露了一个GraphQL端点。

首先,使用终端的Git CLI从GitHub仓库克隆GraphQL应用程序

git clone https://github.com/vickywane/blazor-graphql-api.git

接下来,执行下面的命令,将终端的当前目录改为克隆的blazor-graphql-api 项目,并在项目中安装package.json 文件中列出的两个依赖项。

# change directory
cd blazor-graphql-api

# install dependencies
npm install

在安装了项目的依赖性之后,执行下面的命令来启动应用程序。

yarn start

上面的命令执行项目中的index.js 文件,该文件使用ApolloServer在你的本地主机上运行GraphQL服务器,使其可以在端口4000

保持GraphQL应用程序的运行,直到本教程结束,因为我们将在本教程的后面部分使用GraphQL API。

将GraphQL引入Blazor

Blazor是一个开源框架,它让开发者通过利用WebAssembly,使用C#构建交互式用户界面。这使得非JavaScript的代码可以在浏览器中执行。

一些可用于在.NET应用程序中消费GraphQL API的客户端库是。

在本文中,我们将使用graphql-dotnet库,因为它的设置更快,可以快速上手。

演示Blazor应用程序

我们将首先用默认的模板页面启动一个新的Blazor应用程序,并重写FetchData.razor 页面,以使用从GraphQL API获取的数据。

从你的本地终端执行以下命令,通过dotnet CLI创建一个新的Blazor应用程序。

dotnet new blazorserver -o BlazorApp --no-https

上面的命令创建了一个新的目录,其中包含一个名为Graphql-blazor 的.NET 5.0应用程序,并使用Razor语法渲染FetchDataCounterIndex 页面。

接下来,使用dotnet CLI将下面的软件包引用添加到新项目中。

# Graphql package
dotnet add package Graphql 

# NewtonsoftJsonSerializer package
dotnet add package GraphQL.Client.Serializer.Newtonsoft 

# GraphQLHttpClient package
dotnet add package GraphQL.Client.Http

上面安装的软件包将Blazor应用程序连接到GraphQL API。

通过打开的终端,将默认的终端目录改为Graphql-blazor ,并通过执行下面的命令以观察模式启动该应用程序。

dotnet watch run

执行上述命令后,Blazor服务器在你的本地主机上运行,端口为5000

此时,我们可以通过我们的网络浏览器http://localhost:5000,查看正在运行的应用程序。然而,我们的重点将放在fetchData ,该页面位于http://localhost:5000/fetchdata,显示天气预报的表格列表。

这个表格元素详细介绍了从fetchData.razor 文件中呈现的五天的预测。

GraphQL Blazor Fetch Data Page

通过渲染上述页面的fetchData.razor 文件,OnInitializedAsync 生命周期方法从data/WeatherForecastService 文件中WeatherForecastService 类中的GetForecastAsync 方法获取模拟预报数据。

接下来,我们将把天气预报数据改为国家数据,以展示如何使用外部GraphQL API。

要做到这一点,我们必须创建一个新的GraphqlService 类,其中包含一个GraphQL客户端,以及一个执行GraphQL查询的方法,通过GraphQL客户端获取数据。

定义GraphQL数据

与在C#应用程序中存储数据类似,我们必须创建具有字段和属性的类,以存储和结构化从每个GraphQL操作返回的数据。

如果不翻阅API的代码库,了解GraphQL API的模式定义的一种方法是通过反省GraphQL API模式。

大多数GraphQL API都有一个Playground,允许用户针对API编写和执行GraphQL操作。游乐场内的文档部分也允许用户使用网络浏览器来反省API。

在你的网络浏览器中打开克隆的GraphQL API的GraphiQL Playground,网址是http://localhost:4000

我们将执行下面的查询,以检索GraphQL应用程序中的所有现有国家。

 query {
    name
    id
    dateInserted
    states {
      id
      name
    }
  }

点击灰色的Play按钮,执行上面的查询。这在请求完成后返回一个数据对象。

点击GraphiQL Playground中的绿色Docs按钮,可以打开模式定义,在这里我们可以反观API中的GraphQL操作,包括其返回类型。

下面的图片显示了克隆的GraphQL API中单个countries 查询的模式定义,该查询返回一个国家数组,包含一个id,name,code,dateInserted, 和一个嵌套的states 数组。

Showing Schema Definition For GraphiQL Playground Query

切换到突变,从GraphiQL操场执行下面代码块中的字句,对GraphQL API执行突变操作,创建一个国家。

在克隆的GraphQL API中,单个CreateCountries 突变的模式定义接受一个整数ID,以及一个字符串类型的名称和代码。这将返回一个包含id,name,code,dateInserted, 和一个嵌套的states 数组的对象。

Showing The Schema Definition For CreateCountries mutation

现在我们知道了从突变和查询操作中返回的数据类型,我们可以继续创建类来在Blazor应用程序中构造它们。

data 目录中创建一个GraphqlData.cs 文件。这将存储返回的查询数据定义,以获取可用的国家。

下面的文件包含四个类,它们定义了从查询和变异操作中返回的数据结构。

// ./data/GraphqlData.cs
using System.Collections.Generic;

namespace graphql_blazor.Data
{
    public class CreateCountry
    {
        public int id { get; set; }
        public string name { get; set; }
        public string dateInserted { get; set; }
        public string code { get; set; }
    } 

    public class GraphqlData
    {
        public List<Countries> countries { get; set; }
    }

    public class Countries
    { 
        public string name { get; set; }
        public int id { get; set; }
        public string dateInserted { get; set; }
        public int code { get; set; }

        public List<State> states { get; set; }
    }

    public class State
    {
        public int id { get; set; }

        public string name { get; set; }
    }
}

从最上面的类开始,GraphqlData 使用通用命名空间List集合描述整个数据响应,并带有公共的Countries 类。

Countries 类包含name,id,code,dateInserted 属性,而State 类包含一个name 和一个id 属性。

所有这些类都使用较短的自动属性语法来定义每个类中的GET/SET 访问器,并且它们都被标记为public

创建一个Singleton服务

接下来,我们必须创建一个自定义的Singleton服务,包含一个创建GraphQL客户端的类。这通过其URL端点将应用程序连接到克隆的GraphQL应用程序。方法也通过连接的API发送GraphQL操作。

首先,创建一个GraphqlService.cs 文件,并添加下面代码块的内容。

# ./data/GraphqlService.cs

using GraphQL.Client.Serializer.Newtonsoft;
using GraphQL.Client.Http;

namespace graphql_blazor.Data
{
    public class GraphqlService
    {
       private readonly GraphQL.Client.Http.GraphQLHttpClient _graphqlClient =
        new GraphQLHttpClient("http://localhost:4000", new NewtonsoftJsonSerializer());
 }
}

注意,在部署这个应用程序时,考虑使用环境变量来存储和检索GraphQL API端点URL,而不是在类中硬编码。

创建一个GraphQL查询

随着GraphQL客户端实例在GraphqlService 类中可用,下一步是使用该实例来发送GraphQL查询操作。

要做到这一点,将下面的代码块添加到GraphqlService.cs 文件中。

// ./data/GraphqlService.cs

using GraphQL.Client.Serializer.Newtonsoft;
using GraphQL;
using GraphQL.Client.Http;
using System.Threading.Tasks;

namespace graphql_blazor.Data
{
    public class GraphqlService
    {
       private readonly GraphQL.Client.Http.GraphQLHttpClient _graphqlClient =
       new GraphQLHttpClient("http://localhost:4000", new NewtonsoftJsonSerializer());

        private readonly GraphQLRequest _fetchCountriesQuery = new GraphQLRequest
        {
            Query = @"
            query FetchCountries {
                countries {
                    name
                    id
                    dateInserted
                    code
                    states {
                      id
                      name
                    }
                  }
            }
        ",
            OperationName = "FetchCountries"
        };

        public async Task<GraphQL.GraphQLResponse<graphql_blazor.Data.GraphqlData>> FetchCountries()
        {
            var fetchQuery = await _graphqlClient.SendQueryAsync<GraphqlData>(_fetchCountriesQuery);

            return fetchQuery;
        }
    }
}

这个代码块包含一个新的私有字段,持有GraphQL查询字符串和一个异步方法,FetchCountries ,它执行来自GraphQL客户端类的SendQueryAsync 方法。

这从连接的GraphQL API中获取数据并返回结果。

创建一个GraphQL突变

除了GraphQL查询外,我们还想在GraphQL应用程序中插入更多的国家数据。这可以通过在GraphqlService 类中实现一个突变来完成。

将下面代码块中的新方法添加到/data/GraphqlService.cs 文件中,通过连接的GraphQL API向克隆的GraphQL应用程序发送一个GraphQL突变。

这在GraphqlService 类中添加了一个新的参数化方法,InsertCountry

# ./data/GraphqlService.cs

using GraphQL.Client.Serializer.Newtonsoft;
using GraphQL;
using GraphQL.Client.Http;
using System.Threading.Tasks;

        public async Task InsertCountry(string countryName, string code)
        {
            var createCountryMutationString = new GraphQLRequest
            {
                Query = @"            
                 mutation insertCountry($code : String, $countryName : String) {
                   CreateCountry(input: {
                       name: $countryName
                       code: $code
                     }) 
                    {
                      id
                      dateInserted
                      code
                      name
                     }
                 }
             ",
                OperationName = "insertCountry",
                Variables = new
                {
                    countryName,
                    code
                }
            };

            await _graphqlClient.SendMutationAsync<CreateCountry>(createCountryMutation);
        }

InsertCountry 方法签名包含两个字符串参数,当执行时,createCountryMutation变量形成,包含GraphQL字符串的突变与countryNamecode 的参数值。

最后,来自GraphQL客户端实例的SendMutationAsync 方法通过连接的GraphQL API发送包含在createCountryMutation 变量中的突变。

我们将在下一节中测试添加到GraphqlService 类中的两个新方法,届时我们将重建模板fetchData.razor 文件以使用GraphqlService 类。

重建FetchData 页面

到目前为止,我们创建了一个包含两个方法的GraphqlService 类,分别用来发送突变和查询操作。现在我们只剩下从fetchData 页面执行这些方法了。

然而,我们必须首先将自定义的GraphqlService 服务添加到应用程序内配置的服务中,然后才能将其注入到fetchData 组件中。

使用你的代码编辑器,打开Startup.cs 文件,将新的GraphqlService 类添加到Startup 类中ConfigureServices 方法内的现有默认服务列表中。

# ./Startup.cs

    services.AddSingleton<GraphqlService>();

接下来,用下面代码块的内容替换fetchData.razor 文件中的全部代码,重新设计FetchData 页面。这样我们就可以使用GraphqlService 服务内的方法。

//./pages/fetchData.cs
@page "/fetchdata";

@using graphql_blazor.Data;
@using System.ComponentModel.DataAnnotations
@inject GraphqlService GraphqlOps;

<section>
    <h4> Available Countries </h4>

    <div>
        @if (countriesData == null)
        {
            <p>
                <em>Loading...</em>
            </p>
        }
        else
        {
            <table class="table">
                <thead>
                <tr>
                    <th>Country Name</th>
                    <th>Country Code </th>
                    <th> Date Inserted </th>
                    <th>Country States </th>
                </tr>
                </thead>
                <tbody>
                @foreach (var item in countriesData.Data.countries)
                {
                    <tr>
                        <td>@item.name</td>
                        <td>@item.code</td>
                        <td format-value="yyyy-MM-dd" >@item.dateInserted</td>

                        @if (item.states != null)
                        {
                            @foreach (var state in item.states)
                            {
                                <td> @state.name </td>
                            }
                        }

                    </tr>
                }
                </tbody>
            </table>
        }
    </div>
</section>

@code {
   // countries state data
    private GraphQL.GraphQLResponse<graphql_blazor.Data.GraphqlData> countriesData;

    private async Task FetchCountryData()
    {
        countriesData = await GraphqlOps.FetchCountries();
    } 

    // Executed immediately component is created
    protected override async Task OnInitializedAsync()
    {
        await FetchCountryData();
    }
}

上面的组件被重新设计,通过@inject 自定义指令注入GraphqlService 服务。

随即,FetchData 组件被创建,OnInitializedAsync 生命周期方法执行FetchCountryData 方法,该方法在 HTTP 请求中向克隆的 GraphQL 应用程序发送 GraphQL 查询。

FetchCountryData 返回的查询结果中的数据被存储在countriesData 本地状态中。

OnInitializedAsync 生命周期事件执行后,组件的界面渲染时,查询结果的数据,存储在countriesData 组件状态中,渲染出从GraphQL API返回的国家的表格列表。

在本地浏览器中打开fetchData 页面,http://localhost:5000/fetchdata,查看新的变化。

GraphQL And Blazor Fetch Page With Country Data

现在,我们只剩下通过fetchData 页面的突变来插入一个新的国家。将下面的代码块添加到fetchData.cs 文件中。

// ./pages/fetchData.cs

<section id="create-country" >
    <div>
        <h4> Insert A New Country </h4>
        <hr/>

        <form>
            <div class="input-container" style="display: flex; flex-direction: column;" >
                <label> Country Name </label>
                <input @bind="countryName" placeholder="Country Name"/>
            </div>

            <br />
            <div style="display: flex; flex-direction: column;"  class="input-container">
                <label> Country Code </label>
                <input @bind="countryCode" placeholder="Country Name"/>
            </div>
            <br />

            <button
                @onclick="HandleCreateCountry"
            @onclick:preventDefault="true"
                > Submit country details </button>
        </form>
    </div>
</section>

<br/>
<br/>



@code {
    bool _isLoading;

    private string countryName { get; set; }
    private string countryCode { get; set; }

    async void HandleCreateCountry()
    {
        _isLoading = true;

        await GraphqlOps.InsertCountry(countryName, countryCode);
        await FetchCountryData();

        _isLoading = false;
        this.StateHasChanged();
    }
}

fetchData 组件中的create-country 部分包含两个输入字段,分别与countryNamecountryCode 属性绑定。

在填写完这两个输入字段后,HandleCreateCountry 方法执行GraphqlService 自定义服务中的InsertCountry 方法,将字符串countryNamecountryCode 输入值作为参数。

为了测试突变,在国家名称文本字段中输入一个国家的名称,在国家代码文本字段中输入相应的国家代码。在本教程中,我们将使用 "阿尔巴尼亚 "作为国家名称,并使用 "+355 "作为国家代码。

FetchData Page Showing Inserting Country Information

点击提交 国家 详细信息按钮,发送由fetchData 页面中两个输入字段的输入值组成的 GraphQL 变异。

突变操作成功执行后,FetchCountryData 方法重新获取新国家的数据。

最后,在FetchCountryData 中的两个异步操作解决后,StateHasChanged方法被调用以手动通知组件的状态变化。这时,组件就会重新显示新的数据。

FetchData Page Mutation Result

结论

在这一点上,整个应用程序已经被完全重建,以使用GraphQL API。你可以重复使用输入字段创建新国家的过程,以添加更多的数据。

为了进一步清理应用程序,你可以删除未使用的WeatherForcast.csWeatherForcastService.cs 文件,并从Startup.cs 文件中的ConfigureServices 方法内的注册服务中删除该服务。

本教程中构建的整个Blazor应用程序已被推送到这个GitHub仓库。欢迎下载或克隆该应用程序供你个人使用。

在Blazor应用程序中集成GraphQL》一文首次出现在LogRocket博客上。