Hot Chocolate 构建 GraphQL .Net Core 服务

news2024/9/24 6:03:03

Hot Chocolate 是 .NET 平台下的一个开源组件库, 您可以使用它创建 GraphQL 服务, 它消除了构建成熟的 GraphQL 服务的复杂性, Hot Chocolate 可以连接任何服务或数据源,并创建一个有凝聚力的服务,为您的消费者提供统一的 API。

我会在 .NET 应用中使用 Hot Chocolate 组件来构建 GraphQL 服务, 让我们开始吧!

创建服务

  •  创建一个GraphQL服务

        安装nuget包:        

        HotChocolate.AspNetCore // GraphQL -  HotChocolate实现包
        HotChocolate.Data.EntityFramework //HotChocolate-IQueryable 实现包
        HotChocolate.Subscriptions.Redis //redis订阅
        Microsoft.EntityFrameworkCore.SqlServer //orm ef sqlServer
        Microsoft.EntityFrameworkCore.Tools //ef 工具

        builder.Services.AddGraphQLServer()

使用Hot Chocolate

  • 新增一个Query

        builder.Services.AddQueryType<MyQuery>()//一个Query,所有Query写在一起

public class MyQuery
    {
        [UseOffsetPaging]
        [UseProjection]
        [UseFiltering]
        [UseSorting]
        public IQueryable<Superhero> GetSuperheroes([Service] ApplicationDbContext context) =>
            context.Superheroes;

        [UseOffsetPaging]
        [UseProjection]
        [UseFiltering]
        [UseSorting]
        public IQueryable<Movie> GetMovies([Service] ApplicationDbContext context) =>
            context.Movies;

        [UseOffsetPaging]
        [UseProjection]
        [UseFiltering]
        [UseSorting]
        public IQueryable<Superpower> GetSuperpowers([Service] ApplicationDbContext context) =>
            context.Superpowers;
    }
  • 使用特性  搜索、排序、投影

         builder.Services.AddProjections()
         .AddFiltering()
        .AddSorting()
        .SetPagingOptions(new PagingOptions
        {
                    MaxPageSize = 10000,
                    DefaultPageSize = 10,
                    IncludeTotalCount = true
        });

  • 创建多个Query,通过ExtendObjectType("Query")关联
.AddQueryType(q => q.Name("Query"))
                .AddTypeExtension<SuperheroQuery>()
                .AddTypeExtension<MovieQuery>()
                .AddTypeExtension<SuperpowerQuery>()

[ExtendObjectType("Query")]
    public class SuperheroQuery
    {
        [UseOffsetPaging]
        [UseProjection]//始终显示的数据字段,无论是否查询该字段
        [UseFiltering]
        [UseSorting]
        [GraphQLDescription("获取超级英雄集合")]
        public IQueryable<Superhero> GetSuperheroes([Service] ApplicationDbContext context) =>
            context.Superheroes;
    }
  • 创建Mutation&自定义错误信息

                .AddMutationType(m => m.Name("Mutation"))
                .AddTypeExtension<SuperheroMutation>()


    [ExtendObjectType("Mutation")]
    public class SuperheroMutation
    {
        public async Task<Boolean> AddSuperheroAsync(SuperheroDto superhero, [Service] ISuperheroRepository _repository, [Service] ITopicEventSender sender)
        {
            var (Success, KeyId) = await _repository.AddSuperheroAsync(superhero);
            await sender.SendAsync("SuperheroModified", superhero);
            return Success;
        }

        [Error(typeof(NameTakenException))]
        public async Task<Boolean> UpdateSuperheroAsync([ID] Guid Id, string name, [Service] ISuperheroRepository _repository, [Service] ITopicEventSender sender)
        {
            var Success = await _repository.UpdateSuperheroAsync(Id, name);
            await sender.SendAsync("SuperheroModified", new SuperheroDto
            {
                Id = Id,
                Name = name,
                Description = "",
                Height = 0,
                Movies = null,
                Superpowers = null
            });
            return Success;
        }
    }

    public class NameTakenException : Exception
    {
        public NameTakenException(string username)
            : base($"The name {username} is already taken.")
        {
        }
    }
  • 创建指令

                .AddDirectiveType<ToUpperDirectiveType>()
                .AddType<toLowerDirective>()


    /// <summary>
    /// 转大写指令
    /// </summary>
    public class ToUpperDirectiveType : DirectiveType
    {
        protected override void Configure(
            IDirectiveTypeDescriptor descriptor)
        {
            descriptor.Name("toupper");
            //descriptor.Argument("name").Type<NonNullType<StringType>>();
            descriptor.Location(DirectiveLocation.Field);
            //https://chillicream.com/docs/hotchocolate/v13/execution-engine/field-middleware/#field-middleware-as-a-class
            //中间件 
            descriptor.Use((next, directive) =>
            {
                return async context =>
                {
                    await next(context);
                    if (context.Result is string str)
                    {
                        context.Result = str.ToUpper();
                    }
                    else
                    {
                        context.ReportError("Bad Request.");
                        context.OperationResult.SetResultState(WellKnownContextData.HttpStatusCode, 500);
                    }
                };
            });
        }
    }

    /// <summary>
    /// 转小写指令
    /// 属性模式
    /// </summary>
    [DirectiveType(DirectiveLocation.Field)]
    [toLowerDirectiveMiddleware]
    public class toLowerDirective
    {
          
    }

    /// <summary>
    /// 指令中间件
    /// </summary>
    public class toLowerDirectiveMiddlewareAttribute : DirectiveTypeDescriptorAttribute
    {
        protected override async void OnConfigure(IDescriptorContext context, IDirectiveTypeDescriptor descriptor, Type type)
        {
            descriptor.Use((next, directive) =>
            {
                return async context =>
                {
                    await next(context);
                    if (context.Result is string str)
                    {
                        context.Result = str.ToLower();
                    }
                    else
                    {
                        context.ReportError("Bad Request.");
                        context.OperationResult.SetResultState(WellKnownContextData.HttpStatusCode, 500);
                    }
                };
            });
        }
    }
  •  创建订阅(通过webSocket方式)

                .AddInMemorySubscriptions()
                //.AddRedisSubscriptions((sp) => ConnectionMultiplexer.Connect("127.0.0.1:6379,password=Michael,defaultDatabase=2"))
                .AddSubscriptionType(q => q.Name("Subscription"))
                .AddTypeExtension<SuperheroSubscribe>()
                //.AddSubscriptionType<SuperheroSubscribe>()//指定一个订阅类,所有订阅写在一起

[ExtendObjectType("Subscription")]
    public class SuperheroSubscribe
    {
        [Subscribe]
        [Topic("SuperheroUpdated")]
        public async Task<SuperheroDto> SuperheroUpdated([EventMessage] SuperheroDto superherodto, [Service] ISuperheroRepository _repository)
        {
            var ret = await _repository.UpdateSuperheroAsync(superherodto.Id, $"{superherodto.Name}_{DateTime.Now.ToString("yyMMdd")}");
            superherodto.Description = "Subscribe-SuperheroModified";
            return superherodto;
        }

        #region 混合模式(订阅逻辑和解析器分离)

        /// <summary>
        /// 数据逻辑处理
        /// </summary>
        /// <param name="receiver"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<SuperheroDto> SubscribeToSuperheroDto(
        [Service] ITopicEventReceiver receiver, [Service] ISuperheroRepository _repository)
        {
            yield return new SuperheroDto { Id = Guid.NewGuid(), Name = $"Name-{DateTime.Now.ToString("HHmmss")}" };
            //return ISourceStream<SuperheroDto>
            var source = await receiver.SubscribeAsync<SuperheroDto>("SuperheroModified");
            Task.Delay(3000);
            await foreach (SuperheroDto superherodto in source.ReadEventsAsync())
            {
                superherodto.Name = $"{superherodto.Name}_{DateTime.Now.ToString("HHmmss")}";
                var ret = await _repository.UpdateSuperheroAsync(superherodto.Id, superherodto.Name);
                yield return superherodto;
            }
        }

        /// <summary>
        /// 订阅
        /// 服务端必须开启websocket
        /// 订阅人监听websocket
        /// </summary>
        /// <param name="superherodto"></param>
        /// <param name="_repository"></param>
        /// <returns></returns>
        [Topic("SuperheroModified")]
        [Subscribe(With = nameof(SubscribeToSuperheroDto))]
        public async Task<SuperheroDto> SuperheroModified([EventMessage] SuperheroDto superherodto)
        {
            return superherodto;
        }

        #endregion
    }

必须先链接WebSocket,Mutation事件才会推送 ,GraphQL语法

subscription{
   superheroUpdated {
    id
     name
     description
   }
}

 Promgram 整体配置

 var builder = WebApplication.CreateBuilder(args);
            Microsoft.Extensions.Configuration.ConfigurationManager configuration = builder.Configuration;

            // Add services to the container.
            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            builder.Services.AddGraphQLServer()
                .AddInMemorySubscriptions()
                //.AddRedisSubscriptions((sp) => ConnectionMultiplexer.Connect("127.0.0.1:6379,password=Michael,defaultDatabase=2"))
                .AddSubscriptionType(q => q.Name("Subscription"))
                .AddTypeExtension<SuperheroSubscribe>()
                //.AddSubscriptionType<SuperheroSubscribe>()//指定一个订阅类,所有订阅写在一起
                //.AddTypeExtensionsFromFile("./Stitching.graphql")
                //.ModifyOptions(options =>
                //{
                //    /*
                //     * code-first模式-Explicit:显示绑定(手动绑定字段,或者使用ObjectType<T>-override Configure手动设置)
                //     * 显示绑定 必须所有数据都要声明包括 Tsortinput,Tinput,TOperationFilterInput等。。。
                //     * Annotation-based模式-Implicit:隐式绑定(默认展示所有字段,或者使用ObjectType<T>-override Configure手动设置)
                //     * GraphQLIgnoreAttribute 可过滤不需要的字段
                //     */
                //    options.DefaultBindingBehavior = BindingBehavior.Explicit;
                //})
                .AddType<SuperheroType>()
                .AddType<MovieType>()
                .AddType<SuperpowerType>()
                //.AddQueryType<MyQuery>()//一个Query,所有Query写在一起
                .AddQueryType(q => q.Name("Query"))
                .AddTypeExtension<SuperheroQuery>()
                .AddTypeExtension<MovieQuery>()
                .AddTypeExtension<SuperpowerQuery>()
                .AddMutationType(m => m.Name("Mutation"))
                .AddTypeExtension<SuperheroMutation>()
                .AddProjections()
                .AddFiltering()
                .AddSorting()
                .SetPagingOptions(new PagingOptions
                {
                    MaxPageSize = 10000,
                    DefaultPageSize = 10,
                    IncludeTotalCount = true
                })
                .AddDirectiveType<MyDirectiveType>()
                .AddDirectiveType<ToUpperDirectiveType>()
                .AddType<toLowerDirective>()
                ;

            string appRoot = builder.Environment.ContentRootPath;
            Environment.SetEnvironmentVariable("AppDataDirectory", System.IO.Path.Combine(appRoot, "App_Data"));
            var SqlServerConnStr = Environment.ExpandEnvironmentVariables(configuration.GetConnectionString("SqlServer"));

            // Add Application Db Context options
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(SqlServerConnStr));

            // Register custom services for the superheroes
            builder.Services.AddScoped<ISuperheroRepository, SuperheroRepository>();
            builder.Services.AddScoped<ISuperpowerRepository, SuperpowerRepository>();
            builder.Services.AddScoped<IMovieRepository, MovieRepository>();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();
            //使用GraphQL-Subscription 必须开启websocket
            app.UseWebSockets();
            //https://blog.christian-schou.dk/how-to-implement-graphql-in-asp-net-core/
            app.MapGraphQL();

            app.Run();
  •  https://localhost:7199/graphql/

 

通过Strawberry Shake,自动链接GraphQL服务,创建客户端

 Introduction - Strawberry Shake - ChilliCream GraphQL Platform

 NSwagStudio,通过Swagger.json 文档创建 TypeScript Client、CSharp Client、CSharp Controller

NSwagStudio · RicoSuter/NSwag Wiki · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/376807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql一两种索引方式hash和btree

1. Hash索引&#xff1a; Hash 索引结构的特殊性&#xff0c;其检索效率非常高&#xff0c;索引的检索可以一次定位&#xff0c;不像B-Tree 索引需要从根节点到枝节点&#xff0c;最后才能访问到页节点这样多次的IO访问&#xff0c;所以 Hash 索引的查询效率要远高于 B-Tree 索…

FFmpeg 编译和集成

背景FFmpeg 是一款知名的开源音视频处理软件&#xff0c;它提供了丰富而友好的接口支持开发者进行二次开发。FFmpeg 读作 “ef ef em peg” &#xff0c;其中的 “FF” 指的是 “Fast Forward”&#xff0c;“mpeg” 则是 “Moving Picture Experts Group” &#xff08;动态图…

隧道代理的工作原理是什么,为何爬虫使用起来更高效?

在网络爬虫领域&#xff0c;使用HTTP代理是非常普遍的一种技术手段。而隧道代理则是HTTP代理中的一种&#xff0c;它是指将请求通过隧道传输到代理服务器上&#xff0c;并由代理服务器向目标服务器发送请求&#xff0c;从而达到隐藏真实IP的目的。那么&#xff0c;隧道HTTP代理…

告别空指针让代码变优雅,Optional使用图文例子源码解读

一、前言 我们在开发中最常见的异常就是NullPointerException&#xff0c;防不胜防啊&#xff0c;相信大家肯定被坑过&#xff01; 这种基本出现在获取数据库信息中、三方接口&#xff0c;获取的对象为空&#xff0c;再去get出现&#xff01; 解决方案当然简单&#xff0c;只…

华为OD机试模拟题 用 C++ 实现 - 最优资源分配(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明最优资源分配题目输入输出描述备注示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注

2023 Java 分布式 面试 大纲

前段时间 &#xff0c;公司部门的HR找到我&#xff0c;说来了几份简历 &#xff0c;都是三年所有的开发 让我面一下&#xff0c; HR那边 一面核对了基本的信息 二面技术&#xff0c;是由我来接手&#xff0c;然后问了 一些分布式的问题 &#xff0c;大部分都是在围绕着SpringCl…

这回稳了!电力巡检低功耗摄像头全新来袭

最近的狂飙成为大家的话题&#xff0c;互联网的发展让很多信息都很透明&#xff0c;这个也是我比较喜欢和各位技术大咖一起分享一些当下比较前沿的解决方案 春回大地&#xff0c;疫情远去&#xff0c;我们也没有理由逃避不去努力&#xff0c;在互相网的各种平台去获取各种自己需…

安全配置检查的必要性?以及检查流程

随着行业信息化建设的不断深入&#xff0c;生产、业务支撑系统的网络结构越来越复杂&#xff0c;由此带来的各种应用和服务器的数量及种类也日益增多&#xff0c;一旦发生维护人员错误操作&#xff0c;或者采用一成不变的初始系统设置&#xff0c;就可能会带来安全隐患&#xf…

〖大前端 - 基础入门三大核心篇②〗- 前端开发工具和环境准备

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

2020蓝桥杯真题跑步锻炼(填空题) C语言/C++

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小蓝每天都锻炼身体。 正常情况下&#xff0c;小蓝每天跑 1 千米。如果某天是周一或者月初&#xff08;1 日&#xff09;&#xff0c;为了激励自己&#xff0c;小蓝…

TCP协议原理三

文章目录七、延时应答八、捎带应答九、面向字节流粘包问题十、TCP异常情况总结七、延时应答 如果说滑动窗口的关键是让窗口大一些&#xff0c;传输速度就快一些。那么延时应答就是在接收方能够处理的前提下&#xff0c;尽可能把ack返回的窗口大小尽可能大一些。 如果在接受数据…

关于事务的理解

事务的概念 事务处理几乎是每一个信息系统中都会涉及到的问题&#xff0c;它存在的意义就是保证系统中的数据是正确的&#xff0c;不同数据间不会产生矛盾&#xff0c;也就是保证数据状态的一致性&#xff08;Consistency&#xff09;。 关于一致性&#xff0c;我们重点关注的…

MySQL —— 基本查询

文章目录1. 向表中插入数据2. 查询操作2.1 全列查询2.2 指定列查询2.3 查询字段带表达式2.4 为查询结果指定别名2.5 去重操作3. where 条件3.1 比较运算符和逻辑预算符的运用3.2 like的细节3. 3 null查询4. 对查询的结果进行排序4.1 对单一字段进行排序4.2 对多个字段排序4.3 对…

密码学基础概念

把一段原始数据通过某种算法处理成另外一种数据&#xff08;原始数据为明文&#xff0c;处理后的数据为密文&#xff09;。明文->密文&#xff1a;称之为加密。密文->明文&#xff1a;称之为解密。 在加密过程中我们需要知道下面的这些概念&#xff1a; 1&#xff09;明文…

操作系统——10.进程通信

这篇文章我们来讲一下进程通信的相关内容 目录 1.概述 2.什么是进程通信 2.1进程通信——共享存储 2.2进程通信——管道通信 2.3进程通信——消息传递 3.小结 1.概述 首先&#xff0c;我们来看一下这节内容的大体框架 2.什么是进程通信 顾名思义&#xff0c;进程通信就是…

QML 鼠标事件

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 QML 中有一些元素本身是不具备交互能力的(例如:Rectangle、Text、Image 等),那么如何通过鼠标来控制它们的行为呢?这里就需要用到 MouseArea 元素了,它继承于 Item 且不可见,通常需要与可见元素结合使…

【vue2小知识】路由守卫的使用与解决RangeError: Maximum call stack size exceeded问题的报错。

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;当我们在路由跳转前与后我们可实现触发的操作 【前言】当我们在做类似于登录页面的时候&…

jmeter-如何在多线程一起执行时,控制请求的执行顺序【临界部分控制器】

前言&#xff1a;一个线程多个脚本的时候&#xff0c;发现只要100个用户同时执行&#xff0c;请求就会乱。期望2个线程执行结果&#xff1a;获取验证码-注册-登录这个流程获取验证码-注册-登录这个流程实际2个线程执行结果&#xff1a;a. 登录-获取验证码-注册b. 注册-获取验证…

运动无线蓝牙耳机哪款好、运动无线蓝牙耳机推荐

作为 运动爱好者&#xff0c;每天早晨醒来后的第一件事就去家门口的湿地公园跑上一圈。各种运动装备都齐了&#xff0c;不过在耳机选择上还真的犯难&#xff0c;打着“运动耳机”旗号的产品也是种类繁多&#xff0c;那么到底什么样的无线耳机更适合运动呢&#xff1f;于是我花时…

零基础入门网络安全,看这一篇就够了!

前景 很多零基础朋友开始将网络安全作为发展的大方向&#xff0c;的确&#xff0c;现如今网络安全已经成为了一个新的就业风口&#xff0c;不仅大学里开设相关学科&#xff0c;连市场上也开始大量招人。 那么网络安全到底前景如何&#xff1f;大致从市场规模、政策扶持、就业…