欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

GraphQL ---02 GraphQL和C#结合的实战项目

程序员文章站 2022-07-11 09:37:44
本文章是介绍和记录如何创建GraphQL项目,以及如何使用GraphQL进行数据的相关操作。项目参照GraphQL .Net 的官方文档进行实践 一、项目结构: 为了更好的和原有的项目结合在一起,尽可能减少对原项目的修改。我对项目结构做了如下分层。 二、项目结构分层说明 Contracts层: 项目 ......

本文章是介绍和记录如何创建graphql项目,以及如何使用graphql进行数据的相关操作。项目参照graphql .net 的官方文档进行实践

一、项目结构:

  为了更好的和原有的项目结合在一起,尽可能减少对原项目的修改。我对项目结构做了如下分层。

GraphQL ---02 GraphQL和C#结合的实战项目

二、项目结构分层说明

  contracts层: 项目的接口层,重点存放项目的一些接口。和原项目的分层结构的contracts一致

  entities层: 实体模型层,存放实体模型。与原有项目的分层结构entites层一致

  graphqldemo: 是使用console控制台应用程序对graphql的调用实例

  graphqls: 使用graphql 的模型定义和查询、变更等操作的定义

  services: 提供服务的具体实现层,和原有项目分层中的services 层一致

  tests: 使用unit test 测试调用graphql

在这里重点关注 标红的部分的介绍

三、graphqls项目介绍:

  graphqls重点是存储项目的graphql操作相关的内容

  1.在项目解决方案中,新建程序集,命名为graphqls

  2. 安装graphql

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进行测试

GraphQL ---02 GraphQL和C#结合的实战项目

  1. 查询测试:
    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,也可以学习杨旭的文章。很好的博主