Hot Chocolate 构建 GraphQL .Net Core 服务

13 篇文章 0 订阅

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值