带有GraphQL数据访问和JWT身份验证的.NET 5服务

目录

介绍

服务如何运作?

GraphQL的用法和优化

组成和结构

如何运行?

前提条件(对于Windows)

行动顺序

使用Playground的查询和变异

测验

结论


本文和代码说明了.NET 5GraphQL的用法。GraphQL数据访问已通过数据缓存进行了优化。开发了一些库来支持GraphQLJWT身份验证、TLS、可配置日志记录。

介绍

本文介绍了两个.NET 5 Web服务。第一个GraphQlService支持使用GraphQL技术通过数据库(SQL Server)创建、检索、更新和删除(CRUD)操作。传输层安全性(TLS)保护消息在跨网络传输时不被读取,并且使用JSON Web令牌(JWT)进行用户身份验证和授权。第二种LoginService提供用户登录机制,并根据用户的凭证生成JWT

维基

GraphQL是一种用于API的开源数据查询和操作语言,并且是用于使用现有数据执行查询的运行时。GraphQLFacebook2012年内部开发,并于2015年公开发布。目前,GraphQL项目由GraphQL Foundation运行。它提供了一种开发Web API的方法,并且已与REST和其他Web服务体系结构进行了比较和对比。它允许客户端定义所需数据的结构,并从服务器返回相同的数据结构,因此可以防止返回过多的数据。

本文的代码演示了以下主要功能:

  • 使用GraphQL技术的交易数据存储库的CRUD操作
  • 方便的PlaygroundGraphiQL现成的Web UI应用程序,用于GraphQL查询和变异,不需要前端代码
  • JWT认证
  • OpenApi(又名Swagger)与GraphQL结合使用
  • 灵活的可配置日志记录(当前已配置为仅向控制台提供一些最小输出)
  • 使用内存服务进行集成测试

使用了几个使用NuGet进行GraphQL开发的开源软件包。

服务如何运作?

服务的工作如下图所示:

1.服务工作

要开始她/他的工作,用户需要向LoginService (1)提供凭据(用户名和密码)。后者生成JWT并将其返回给用户。然后,用户向GraphQlService (2) 发送查询/更新,并从服务获得响应。

这些服务具有单独的数据库。LoginService访问的用户数据库包UsersDb含一个Users表,该表由用户名、密码(在现实世界中加密)和每个用户的角色组成。GraphQlService访问的人员数据库PersonsDb包含几个与人员、组织及其关系和隶属关系有关的表。

GraphQL的用法和优化

GraphQL定义了客户端和服务器之间的约定,用于数据检索(查询)和更新(变异)。查询和变异都构成类似JSON的结构。检索到的数据被格式化为与请求几乎相同的结构,然后返回给客户端。由于GraphQL查询的分层形式,数据检索的过程是对嵌套字段的处理程序的一系列调用。

GraphQL暗示对每个数据字段使用解析函数(解析器)。通常,包括此处使用的GraphQL的实现可确保在Web响应层次结构形成期间调用适当的解析器。如果每个解析器向数据库发出SELECT查询,则这些查询的总数等于层次结构每个级别上的返回行之和。考虑一个获取所有人的查询。在最高级别,所有n个人都被提取。然后在第二级,SELECT查询被执行Ñ是时候获取每个人的隶属关系。在以下每个级别上观察到相似的图片。显然,大量的返回行导致对数据库的大量查询,从而导致严重的性能损失。在这种情况下,对数据库的查询数为:

此问题通常称为N + 1查询问题

高效的GraphQL实现必须为该问题提供合理的解决方案。在这项工作中实现的解决方案可以表述如下。在天真的实现中,每个字段的处理程序都调用数据库来检索数据。在优化的解决方案中,每个级别上的字段处理程序的第一次调用都会从数据库数据中检索该级别上所有字段的信息,并将它们存储在附加到GraphQL上下文对象的缓存中。GraphQL上下文对象可用于所有字段处理程序。给定级别字段处理程序的后续调用从缓存而不是从数据库获取数据。在优化的情况下,对数据库的查询数为:

database_queries = levels

如您所见,数据库调用的数量(SELECT -s)与GraphQL查询的内部级别相对应,并且与每个级别上提取的记录的数量无关。

差异在下面的图2和图3中说明:

2.未优化的数据提取

3.优化的数据获取

解析程序的返回值将自动插入到GraphQL查询的响应对象中。我们仅需要提供该级别的解析器中已经从数据库中获取的数据的返回值。实现此目的的最简单方法是将获取的数据放入内存中的缓存对象,并将其附加到所有解析程序可用的上下文对象中。根据解析器,将缓存组织为具有关键字的字典。每个解析器都使用适当的密钥返回从缓存中提取的一条数据。

这些返回值形成响应对象,以完成GraphQL查询后发送回客户端。由于缓存对象是上下文对象的属性,因此在GraphQL查询处理结束时,它将与上下文一起销毁。因此,为每个客户端请求创建了缓存对象,并且缓存对象的生存期受到此请求处理的限制。

基于内存缓存的数据采集优化可提高性能。但是,它的限制在于可用操作内存(RAM)的大小。如果缓存对象太大而无法容纳在单个进程内存中,则可以使用分布式缓存解决方案,例如RedisMemcached或类似的缓存。在本文中,我们假设简单的内存高速缓存可以满足绝大多数实际情况。

组成和结构

成分

项目类型

地点

描述

GraphQlService

服务(控制台应用程序)

.\

该服务使用GraphQL技术执行CRUD操作。它提供了两个控制器。GqlController处理所有的GraphQL请求,而PersonController处理无参数的GET请求和一些预定义的文本,以及另一个以Person id作为参数的GET请求。该请求在内部作为具有硬编码查询的普通GraphQL请求进行处理。它是常用GraphQL查询的捷径。在此,PersonController主要用于说明目的。

LoginService

服务(控制台应用程序)

.\

该服务支持用户登录过程。它具有一个LoginController创建JWT以响应用户的凭据。

ServicesTest

测试项目(控制台应用程序)

.\Tests

Project为这两种服务都提供集成测试。这些测试基于内存服务的概念。这种方法使开发人员可以轻松测试实际的服务代码。

ConsoleClient

控制台应用

.\

用于服务的简单客户端控制台应用程序。

PersonModelLib

动态链接库

.\Model

该项目提供了针对给定领域问题的特定代码(在我们的案例中是Persons)。

AsyncLockLib

动态链接库

.\Libs

提供异步/等待方法的锁定机制,特别适用于GraphQL缓存的实现。

AuthRolesLib

动态链接库

.\Libs

提供enum UserAuthRole

GraphQlHelperLib

动态链接库

.\Libs

包含与GraphQL相关的常规代码,其中包括一个用于数据缓存的代码,用于解决N + 1个查询问题。

HttpClientLib

动态链接库

.\Libs

用于创建HTTP客户端,实现HttpClientWrapper类。

JwtAuthLib

动态链接库

.\Libs

通过用户的凭证生成JWT

JwtLoginLib

动态链接库

.\Libs

提供用户登录处理,使用JwtAuthLib

RepoInterfaceLib

动态链接库

.\Libs

定义IRepo<T>用于处理事务数据存储库的接口。

RepoLib

动态链接库

.\Libs

EntityFrameworkCore实现RepoInterfacesLib中的IRepo<T>接口。它为数据保存程序配备了事务处理。

如何运行?

前提条件(对于Windows

  • 本地SQL Server(请参阅服务的文件appsettings.json中的连接字符串)
  • 带有.NET 5支持的Visual Studio 2019VS2019
  • Postman应用程序通过身份验证测试用例

行动顺序

1、使用支持.NET 5VS2019打开解决方案GraphQL_DotNet.sln并构建解决方案。

2、使用SQL Server。为了简单起见,采用代码优先范式。在运行适当的服务或其集成测试时,会自动创建数据库UsersDbPersonsDb。请调整appsettings.json服务配置文件中的连接字符串(如果需要)。首先,数据库中填充了代码中的多个初始记录。为了确保身份机制正常运行,所有这些记录均分配了负Id-s,但UsersDb.Users表不会在本工作中以编程方式更改。

3、GraphQlService的配置文件appsetting.json包含对象FeatureToggles

 

"FeatureToggles": {
	"IsAuthJwt": true,
	"IsOpenApiSwagger": true,
	"IsGraphIql": true,
	"IsGraphQLPlayground": true,
	"IsGraphQLSchema": true
}

 

默认情况下,所有选项均设置为true。让我们先从没有身份验证的状态开始,然后将其"IsAuthJwt"设置为false

4、开始GraphQlService。它可以从VS2019作为服务或在IIS Express下进行。具有GraphQL的带有Playground Web UI应用程序的浏览器将自动启动。

Playground网页中,您可能会看到GraphQL模式,并使用不同的查询和变异。可以从文件querys-mutations-examples.txt复制一些预定义的查询和变异。

4. Playground Web应用程序

您可以使用类似的GraphiQL Web应用程序代替Playground:在https://localhost:5001/graphiql上浏览

5. GraphiQL Web应用程序

5、Playground应用程序使用中间件来绕过响应GqlController(通常在开发过程中使用,但是在该项目中,所有版本都可用)。它不会调用客户在生产中使用的那个GqlController。要使用GqlController,您可以使用Postman应用程序。

Postman,使POSThttps://localhost:5001/gqlBody-> GraphQLQUERY文本框中提供您实际的GraphQL查询/变异的。

6.使用Postman进行GraphQL查询

6、您也可以使用OpenApiaka Swagger):浏览至https://localhost:5001/swagger

7. OpenApiSwagger

Swagger网页中激活POST/Gql

然后在Postman,按右上角的Code链接:

8. PostmanHTTP请求。

将查询复制到SwaggerRequest body文本框并执行方法。

9. POST/Gql请求。

10. POST/Gql响应。

7、在所有情况下,您都可以使用不安全的调用方式进行http://localhost:5000 的说明和调试。

8、现在让我们使用JWT身份验证。停止运行GraphQlService(如果是),在GraphQlService的配置文件appsetting.json中的对象FeatureToggle设置"IsAuthJwt"true,在VS2019,定义LoginServiceGraphQlService多启动项目并运行它们。

或者,可以通过从相应的DebugRelease目录中激活文件LoginService.exeGraphQlService.exe来启动服务。在这种情况下,应在服务已经运行时手动启动浏览器在https://localhost:5001/playground上导航。

首先,您需要从Postmanhttps://localhost:5011/login发生一个POST,提供用户凭据username = "Super", password = "SuperPassword"。请注意端口5011:如您所见,LoginService监听该端口。

11:登录

然后在Postman打开一个新选项卡,以POSThttps://localhost:5001/gql,打开Authorization-> Bearer Token,将登录时收到的令牌复制到Token文本框中,并通过单击Send按钮post。您可以使用OpenApi身份验证。为此,在OpenApi Web页面中, 按一下按钮Authorize(请参见图7),在Value文本框中插入单词Bearer,然后插入JWT令牌,然后按Authorize按钮。

9、集成测试可以在目录\\Test中的ServicesServiceTest项目中找到。

使用Playground查询和变异

Playground是一个Web应用程序,可以由GraphQL库中间件立即激活(在这个例子中,使用NuGetGraphQL.Server.Ui.Playground)。它提供了便捷、直观的方式来定义,记录和执行GraphQL查询和变异。Playground提供智能感知、错误处理和单词提示。它还显示了GraphQL模式以及可用于给定任务的所有查询和变异。Playground的屏幕截图如上图4所示。

这些是我们解决方案的查询和变异示例。您可能会在Playground DOCS窗格中看到其描述。

以下是Persons查询返回所有人。

query Persons {
  personQuery {
    persons {
      id
      givenName
      surname
      affiliations {
        organization {
          name
          parent {
            name
          }
        }
        role {
          name
        }
      }
      relations {
        p2 {
          givenName
          surname
        }
        kind
        notes
      }
    }
  }
}

 

查询PersonById通过其唯一id参数返回一个人。在以下示例中,id将设置为1

query PersonById {
  personByIdQuery {
    personById(id: 1) {
	  id
	  givenName
      surname
      relations {
        p2 {
          id
	      givenName
          surname
        }
        kind
      }
      affiliations {
        organization {
          name
        }
        role {
          name
        }
      }
    }
  }
}

 

变异PersonMutation允许用户创建新人员或更新现有人员。

mutation PersonMutation {
  personMutation {
    createPersons(
      personsInput: [
        {
          givenName: "Vasya"
          surname: "Pupkin"
          born: 1990
          phone: "111-222-333"
          email: "vpupkin@ua.com"
          address: "21, Torn Street"
          affiliations: [{ since: 2000, organizationId: -4, roleId: -1 }]
          relations: [{ since: 2017, kind: "friend", notes: "*!", p2Id: -1 }]
        }
        {
          givenName: "Antony"
          surname: "Fields"
          born: 1995
          phone: "123-122-331"
          email: "afields@ua.com"
          address: "30, Torn Street"
          affiliations: [{ since: 2015, organizationId: -3, roleId: -1 }]
          relations: [
            { since: 2017, kind: "friend", notes: "*!", p2Id: -2 }
            { since: 2017, kind: "friend", notes: "*!", p2Id: 1 }
          ]
        }
      ]
    ) {
      status
      message
    }
  }
}

 

测验

集成测试位于项目ServicesTest(目录.\Tests)中。内存服务用于集成测试。这种方法大大减少了开发集成测试的工作。由于测试可以创建并最初填充数据库,因此它们可以开箱即用地运行。

结论

这项工作讨论了GraphQL技术在具有事务性数据存储库的CRUD操作中的用法,并介绍了在.NET 5 C#中开发的适当服务。它还使用内存服务实现了一些有用的功能,例如JWT身份验证、OpenApi、可配置的日志和集成测试。

https://www.codeproject.com/Articles/5295966/NET-5-Services-with-GraphQL-Data-Access-and-JWT

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值