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

    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,也可以学习杨旭的文章。很好的博主https://www.cnblogs.com/cgzl/p/9691323.html

  • 相关阅读:
    js 数组相减
    js 对象数组去重
    vue 首次不触发watch的解决方法
    "神药"推荐--紫花地丁
    openstack 平台添加 nvidia vGPU
    string易错点整理总结
    CentOS安装图形界面
    CentOs安装ssh和scp
    数组把0移到末尾
    Windows下Celery安装与下使用
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/10554242.html
Copyright © 2020-2023  润新知