GraphQL和C#
GraphQL ---02 GraphQL和C#结合的实战项目
本文章是介绍和记录如何创建GraphQL项目,以及如何使用GraphQL进行数据的相关操作。项目参照GraphQL .Net 的官方文档进行实践
一、项目结构:
为了更好的和原有的项目结合在一起,尽可能减少对原项目的修改。我对项目结构做了如下分层。
二、项目结构分层说明
Contracts层: 项目的接口层,重点存放项目的一些接口。和原项目的分层结构的Contracts一致
Entities层: 实体模型层,存放实体模型。与原有项目的分层结构Entites层一致
GraphQLDemo: 是使用Console控制台应用程序对GraphQL的调用实例
GraphQLs: 使用GraphQL 的模型定义和查询、变更等操作的定义
Services: 提供服务的具体实现层,和原有项目分层中的Services 层一致
Tests: 使用Unit Test 测试调用GraphQL
在这里重点关注 标红的部分的介绍
三、GraphQLs项目介绍:
GraphQLs重点是存储项目的GraphQL操作相关的内容
1.在项目解决方案中,新建程序集,命名为GraphQLs
2. 安装Graphql
1
|
NuGet 搜索 GraphQL |
3.创建GraphQL 的相关概念
GraphQL有两种方式创建Schema,
- 一种是使用Schema First,也就是使用GraphQL Schema Language创建Schema. 可以对比EntityFramework的DB First
- 一种是使用Graph Type定义Schema,可以对比EntityFramework 的Code First
在这里适用Code First定义数据模型,可以与原有的数据服务应用一起使用。可分为以下步骤:
1)定义数据模型:
假设原有的数据模型Book的结构是这样的:

- public class User
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public int Age { get; set; }
- public string Gender { get; set; }
- }

那么定义对应的GraphQL的数据模型可以是这样的:

- public class UserType:ObjectGraphType<User>// 继承自ObjectGraphType,并传递范型User
- {
- public UserType()// 在构造函数中,对属性作影射
- {
- Name = "User";
- Field(x => x.Id);
- Field(x => x.Name);
- Field(x => x.Age);
- Field(x => x.Gender);
- }
- }

2)定义操作模型:
GraphQL的操作分为: Query(Select), Mutation(Create,Update,Delete),Subscription(订阅)
- 定义Query操作

- public class Query : ObjectGraphType// 定义Query
- {
- private IWrapper wrapper = new Wrapper();
- IEnumerable<User> users = null;
- public Query()
- {
- Field<ListGraphType<UserType>>(//在构造函数中定义查询操作
- name: "users", //注意这个名字,后边查询的时候需要对应
- arguments: new QueryArguments //定义查询参数
- {
- new QueryArgument<StringGraphType>
- {
- Name = "name",
- Description = "The name for the user"
- },
- new QueryArgument<IntGraphType>
- {
- Name = "age",
- Description = "The age for the user"
- },
- new QueryArgument<StringGraphType>
- {
- Name = "gender",
- Description = "The gender for user"
- }
- },
- resolve: context =>// 定义查询操作的执行
- {
- var usercontext = context.UserContext;// 获取上下文,可在此作用户验证操作
- users = wrapper.User.Find(u => true);
- var name = context.GetArgument<string>("name");
- users = users.Where(u => name == null || u.Name == name);
- var age = context.GetArgument<int?>("age");
- users = users.Where(u => age == null || u.Age == age);
- var gender = context.GetArgument<string>("gender");
- users = users.Where(u => gender == null || u.Gender == gender);
- return users;
- });
}
}

- 定义Mutation操作

- public class Mutation:ObjectGraphType
- {
- private IWrapper wrapper = new Wrapper();
- IEnumerable<User> users = null;
- public Mutation()
- {
- Field<UserType>(
- name: "createUser",
- arguments: new QueryArguments(
- new QueryArgument<NonNullGraphType<UserInputType>>
- {
- Name = "user"
- }
- ),
- resolve: context =>
- {
- var user = context.GetArgument<User>("user");
- return wrapper.User.Add(user);
- }
- );
- }
- }

3. 定义GraphSchema
定义GraphSchema就是定义Schema的Query、Mutation、Subscription操作

- public class GraphSchema:Schema
- {
- public GraphSchema()
- {
- Query = new Query();
- Mutation = new Mutation();
- }
- }

4. 附.
为了检验查询、修改操作,这里定义一个GraphQLQuery来定义操作,并定义一个查询操作类

- public class GraphQLQuery
- {
- public string OperationName { get; set; }
- public string NamedQuery { get; set; }
- public string Query { get; set; }
- public object UserContext { get; set; }
- public JObject Variables { get; set; }
- }


- public class ActionExecute
- {
- private IDocumentExecuter executer;
- private IDocumentWriter writer;
- private ISchema schema;
- public ActionExecute()
- {
- executer = new DocumentExecuter();
- writer = new DocumentWriter();
- schema = new GraphSchema();
- }
- public async Task<ExecutionResult> ExecuteAction(GraphQLQuery query)
- {
- var result = await executer.ExecuteAsync(_ =>
- {
- _.Schema = schema;
- _.Query = query.Query;
- _.Inputs = query.Variables.ToInputs();// 查询变量的输入
- _.OperationName = query.OperationName;// 操作名称
- _.UserContext = query.UserContext;// 添加用户上下文对象
- _.ValidationRules = DocumentValidator.CoreRules(); // 添加自定义查询验证 逻辑
- _.ExposeExceptions = true;// 是否追踪错误
- _.FieldMiddleware.Use<ErrorHandlerMiddleware>(); // 使用中间件
- _.EnableMetrics = true;// 是否使用查询度量
- _.ComplexityConfiguration = new ComplexityConfiguration // 防止恶意查询
- {
- MaxComplexity = 12,
- MaxDepth = 15 // 允许查询总最大嵌套数
- };
- });
- return result;
- }
- public async Task<string> Execute(GraphQLQuery query)
- {
- var result = await ExecuteAction(query).ConfigureAwait(false);
- var json = await writer.WriteToStringAsync(result);
- return json;
- }
- }

四、 测试和检验
一切准备就绪,下边对创建的GraphQL进行测试
- 查询测试:

- public class QueryTest
- {
- private ActionExecute execute = new ActionExecute();
- [Fact]
- public void TestMethod1()
- {
- Assert.True(1 == 1);
- }
- [Theory]
- [InlineData(16, "Male")]
- [InlineData(18, "FeMale")]
- public async void QueryUsers(int age, string gender)
- {
- var queryStr = @"{users(age:" + age + ",gender:" + "\"" + gender + "\"" + "){id name gender age}}";
- var result = await execute.ExecuteAction(new GraphQLQuery { Query = queryStr,UserContext= "Add Role" });
- var data = result.Data;
- Assert.Null(result.Errors?.Count);
- }
- }

为了检验GraphQL的查询优越性,你可以修改一下queryStr=@"{users{id name gender age}}"; 或queryStr=@"{users{gender age}}";queryStr=@"{users{ name age}}";注意这里的@和{}只是C# 对字符串操作的一种方式。
发现了什么?
如果我们在前端(Web、微信小程序、手机APP),在web端,作为后台管理系统,我可能需要获取用户的所有信息,那么我可能需要使用queryStr=@"{users{id name gender age}}"。在微信小程序端,我只要根据用户的id查询用户名字就可以了,那么我只用变动查询语句:queryStr=@"{users(id){ name}}";
意味着什么?
意味着我们只需要提供一个API接口,该端口接受传递的查询字符串就可以了。所有的实体都可以只用这一个接口了。想查询什么,由前端决定了,再也不需要追着后端接口开发工程师要数据了。我想这样以来,前端和后端只需要一个接口沟通,会比REST API来的更方便了。
2.变更测试:

- public class MutationTest
- {
- private ActionExecute execute = new ActionExecute();
- [Theory]
- [InlineData(16, "Test1")]
- [InlineData(18, "Test2")]
- public async void CreateUser(int age, string name)
- {
- var queryStr = @"{query: mutation ($user: UserInput!){createUser(user:$user){id name age}},variables:{user:{name: " + name + @",age:" + age + @"}}}";
- var query = new GraphQLQuery
- {
- Query = "mutation ($user: UserInput!){createUser(user:$user){id name age}}",
- Variables = JObject.Parse("{user:{\"name\": \"" + name + "\",\"age\":" + age + "}}")
- };
- var result = await execute.ExecuteAction(query);
- Assert.Null(result.Errors.Count);
- }
- }

发现了什么?
同样的。我们只需要传递查询的参数,传递对应的参数Variables 就能完成修改动作。同时,该变更和查询的操作字符串语句很像,只是多了一个mutation。
五、后续
这篇文章只是介绍了使用控制台和UnitTest测试使用了GraphQL,后续会更新在Asp.Net Core MVC 中使用GraphQL,也可以学习杨旭的文章。很好的博主https://www.cnblogs.com/cgzl/p/9691323.html
GraphQL和C#的更多相关文章
- Facebook的Web开发三板斧:React.js、Relay和GraphQL
2015-02-26 孙镜涛 InfoQ Eric Florenzano最近在自己的博客上发表了一篇题为<Facebook教我们如何构建网站>的文章,他认为软件开发有些时候需要比较大的跨 ...
- facebook graphql
思想先进,前端直接从后台调用所需要的数据. 最简单的理解: 从"select * from 学生表" 进化为"select name, sex from 学生表" ...
- Graphql介绍(Introduction to GraphQL)
Introduction to GraphQL GraphQL介绍 Learn about GraphQL, how it works, and how to use it in this seri ...
- graphql 新API 开发方式
我们知道 GraphQL 使用 Schema 来描述数据,并通过制定和实现 GraphQL 规范 定义了支持 Schema 查询的 DSQL (Domain Specific Query Langua ...
- [GraphQL] Use GraphQLNonNull for Required Fields
While certain fields in a GraphQL Schema can be optional, there are some fields or arguments that ar ...
- [GraphQL] Use Arguments in a GraphQL Query
In GraphQL, every field and nested object is able to take in arguments of varying types in order to ...
- [GraphQL] Write a GraphQL Schema in JavaScript
Writing out a GraphQL Schema in the common GraphQL Language can work for simple GraphQL Schemas, but ...
- [GraphQL] Serve a GraphQL Schema as Middleware in Express
If we have a GraphQL Schema expressed in terms of JavaScript, then we have a convenient package avai ...
- [GraphQL] Use GraphQL's List Type for Collections
In order to handle collections of items in a GraphQL Schema, GraphQL has a List Type. In this video, ...
- [GraphQL] Use GraphQL's Object Type for Basic Types
We can create the most basic components of our GraphQL Schema using GraphQL's Object Types. These ty ...
随机推荐
- java倒计时使用ScheduledExecutor实现,使用两个线程,以秒为单位
public class Countdown2 { private volatile int lin; private int curSec; public Countdown2(int lin) t ...
- 4.3 if-else语句使用
Q:对输入的成绩进行登记划分. #include<iostream> #include<cstdio> using namespace std; int main() { in ...
- UVa 10859 - Placing Lampposts 树形DP 难度: 2
题目 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&a ...
- 获取音、视频时长(NAudio,Shell32,FFmpeg)
参考网址:https://blog.csdn.net/u013810234/article/details/57471780 以下为本次测试用到的音.视频格式: audio :”.wav;.mp3;. ...
- Java的Properties类使用
一.Java Properties类 Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支持的配置文件,配置 ...
- 异常处理机制中的return关键字
Java中,执行try-catch-finally语句需要注意: 第一:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂 ...
- hustoj 管理员和后台设置
之前用过hustoj 的livecd版本,觉得有一些小问题,所以从头到尾搭建.主要包含的过程包括: 安装ubuntu系统 搭建hustoj 管理员和后台资源建设 本文介绍如何在搭建好hustoj的基础 ...
- בוא--来吧--IPA--希伯来语
灰常好听的希伯来语歌曲, Rita唱得真好.
- 4.4 C++虚析构函数
参考:http://www.weixueyuan.net/view/6373.html 总结: 构造函数是不能声明为虚函数的,析构函数可以被声明为虚函数. 将基类的析构函数声明为虚函数之后,派生类的析 ...
- FFT理解
*连续时间-周期性信号频谱 clc;clear;close all N = input('N= '); T = 0.05; n = 1:N; %原始数据输入 D = 2*pi/(N*T); %计算分 ...