ASP.NET CORE GraphQL Api Servisi oluşturmak ve kullanımı

Bu yazımızda ASP.NET CORE ile basit bir GraphQL oluşturacağız. Daha sonra request ve response incleyeceğiz.

Merhaba arkadaşlar,

 Öncelikle GraphQL nedir? Biraz bundan bahsetmek istiyorum. Günümüzde teknolojinin her yerde olduğunu görmekteyiz. Ve bu teknolojilerinin(Akıllı telefonlar, tabletler, akıllı Tv’ler, web yada desktop uygulamalar vsvs..) birbiriyle konuştuğu, veri transferinin yapıldığı bir çok noktada web servisler vadır.

Günümüz de ise en çok kullanılan web servisler ayırmak gerekirse WCF ve Rest Api’lerdir. Şimdi konumuz olan GraphQL gelecek olursak;

Bir proje yazmaya başladınız ve bu projenin web servisleri olacaktır. Sizde bu her endpoint için DTO’lar açmak zorundasınız ve ilgili tablolarınıza teker teker gidip işlem yapmanız gerekmektedir. İşte sıkıldığınız ve tek tek uğraştınız noktada günümüz teknolojilerinden GraphQL yardımınıza koşmaktadır.

Peki nasıl yani?

Başka bir bakış açısıyla düşünelim.  Bir masaüstü bilgisayar toplayacaksınız ve her bir parçasını başka bir yerden alacaksınız. Örnek veriyorum; Anakartı Kadıköy’den, Ram’leri Şişli’den, işlemciyi de Üsküdar’dan alacaksınız. REST mimarisiyle bu işi yaptığınız da her birine teker gitmeniz gerekmektedir ve çok fazla yolculuk yaptınız yoruldunuz. Dahası da fazladan bir tane daha Ram almışsınız. Boşuna bir tane daha fazladan Ram almış oldunuz.

GraphQL ise, sizin yerinize bu işleri yapan bir asistanınız olduğunu düşünün. Yine Kadıköy’e, Şişli’ye ve Üsküdar’a gidilecek fakat bu sefer siz gitmiyorsunuz. Siz söylüyorsunuz asistanınız sizin yerinize gidiyor ve istediğiniz listeyi o alıyor. Hatta Ram almaktan vazgeçtiyseniz o almadan geliyor. Size sadece istediğiniz malzemeler geliyor.

Kısacası GraphQL ile tek bir request’te istediğim veriyi istediğim şekilde alabilirim.

En çok yaşadığımız sıkıntılardan bir tanesinden örnek vermek istiyorum.

Bir ürün listesini çekeceğim Request var. Fakat bu requestin içerisinde benim kullanmayacağım bir çok alan mevcut. Benim bunların hiç birine ihtiyacım yok. Bana sadece ürün adı, açıklaması, fiyatı, stok adedi lazım iken bana gelen request’te bu ürünün oluşturma tarihi, düzenlenme tarihi, Kategori bilgisi, ürün tedarikçisi vsvs.. gibi bilgiler mevcuttu. Halbuki bana 4 adet alan lazımdı. GraphQL ile yazılmış olsaydı beni daha az uğraştıracak ve bunca DTO’lar alanlar açmak zorunda kalmayacaktım.

Yani aynı modelin A sayfasında 5 propertysi lazımsa 5 tanesini alabilirim. B sayfasında 3 tanesi lazımsa 3 tanesini ya da C sayfasında hem x modelini hem de y modelinin istediğim propertylerini aynı requestte alabilirim.

 

Evet bu kadar açıklamadan sonra artık bir proje açalım ve kodlamaya başlayalım.

Bu arada ASP.NET CORE 3.0 kullanıyorum.

Visual Studio açıp >>Create New Project>>Blank Solution>>Solution sağ tık Add>>New Project>>ASP.NET Core Web Application>>Empty diyerek projemizi açıyoruz.

Şimdi ürün listemizin modelini hazırlıyoruz. Bunun için Models klasörü oluşturalım. İçerisine Product ve Brand adında iki class açıyoruz.

 public class Brand
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
        public Brand Brand { get; set; }
    }

Daha sonra Context diye bir klasör oluşturuyoruz ve ismine YvzContext dedim. Context’timizi oluşturuyoruz. İçerisinde dummy datalarımız da oluşturacağız. Ben bunu bu şekilde yaptım fakat siz burayı veritabanı işlemleri yaptığınız repository olarak düşenebilirsiniz

public class YvzContext
    {
        private readonly List<Brand> _brands = new List<Brand>();
        private readonly List<Product> _products = new List<Product>();

        public YvzContext()
        {
            for (int i = 0; i < 10; i++)
            {
                _brands.Add(new Brand
                {
                    Id = i,
                    Name = $"Brand-{i}"
                });
            }

            for (int i = 0; i < 53; i++)
            {
                Random random = new Random();
                _products.Add(new Product
                {
                    Id = i,
                    Name = $"Product-{i}",
                    Price = random.Next(10),
                    Brand = _brands.FirstOrDefault(x => x.Id == random.Next(1, 10))
                });
            }
        }

        public List<Brand> GetBrands()
        {
            return _brands;
        }

        public List<Product> GetProducts()
        {
            return _products;
        }

        public List<Product> GetProductsByBrandId(int brandId)
        {
            return _products.Where(x => x.Brand?.Id == brandId).ToList();
        }
    }

Buradan sonra graphQL query ile konuşabilmesi için bazı nesnelere ihtiyacı vardır.

Bunlar;

  • Schema: Hangi queryi kullanacağımızı tanımladığımız yer.
  • Query: GraphQL endpointlerimizin belirlendiğin yerdir. Route olarak düşüebiliriz.
  • Mutation: Genelde GraphQL’ de crud işlemlerini yaptımak için kullanılıyor.
  • Mevcut modellerimizin GraphqlType hali: Queryimizden dönen modellerimizin hangi propertyleri alacağını belirlediğimiz alan. Kısaca mapping diyebiliriz.
  • GraphqlParameter: Client tarafından gelen ilk istediğin karşılandığı modeldir.

Şimdi nugetten GraphQL yükleyelim ve devam edelim.

Gerekli olan paketi yükledikten sonra GraphqlType’larımızı hazırlayacağız. Bunun için Graphql diye bir klasör açalım içerisine de GraphqlParameter, YvzQuery, YvzSchema diye class’larımızı açalım.

Not: Yvz diye isimlendirme doğru değildir. Siz projenize göre isim veriniz.

///Bu classı client tarafından gelen istekleri almak için kullanacağız.
    public class GraphqlQueryParameter
    {
        public string OperationName { get; set; }
        public string NamedQuery { get; set; }
        public string Query { get; set; }
        public JObject Variables { get; set; }
    }

//Graphql tarafı için gerekli olan şemamız.
    public class YvzSchema:Schema
    {
        public YvzSchema(IDependencyResolver resolver) : base(resolver)
        {
            Query = resolver.Resolve<YvzQuery>();
        }
    }
    
//Graphql tarafındaki endpointlerimizin belirlendiği classtır. IGraphType interface’inden türeyen bir tip olmalıdır.(Kısaca bir GraphType olmalı.)
    public class YvzQuery : ObjectGraphType<object>
    {
        public YvzQuery(YvzContext _yvzContext)
        {
            Name = "Marketing_Query";

            //Graphql endpointlerini belirliyoruz.
            Field<ListGraphType<ProductType>>("Products",
                resolve: ctx => _yvzContext.GetProducts());

            Field<ListGraphType<ProductType>>("MeterialByBrandId",
                arguments: new QueryArguments
                {
                    new QueryArgument<IntGraphType>{
                        Name="Id",
                        Description="Brand Id"
                    }
                },
                resolve: ctx => _yvzContext.GetProductsByBrandId(ctx.GetArgument<int>("Id")));
                Field<ListGraphType<BrandType>>("Brands", resolve: ctx => _yvzContext.GetBrands());
        }
    }
  • Field: GraphType’mızın hangi property sahip olacağı belirliyoruz. Mapping kısmı yani.
  • Arguments: Graphql isteğinden alınacak parametreler varsa bu kısımda tanılanır. Yani Product request içerisinde atılabilecek parametre tanımlamaları burada yapılır.
  • Resolve: Graphql’in döneceği data bu kısımdadır.

Schema ve Query hazırladıktan sonra Product ve Brand class’larımızın GraphqlType’larını oluşturacağız. Graphql klasörü içerisine Type adında bir klasör oluşturup içerisine BrandType ve ProductType adında iki class açalım.

   public class BrandType : ObjectGraphType<Brand>
    {
        public BrandType()
        {
            Name = "Brand";
            Field(p => p.Id);
            Field(p => p.Name);
        }
    }
  public class ProductType:ObjectGraphType<Product>
    {
        public ProductType()
        {
            Name = "Product";
            Field(x => x.Id);
            Field(x => x.Name);
            Field(x => x.Price);
            Field<BrandType>("Brand", resolve: _ => _.Source.Brand);
        }
    }

Fieldlara bütün alanları mapledim. Böylece tiplerimin graphql tarafına propertylerini aktarmış oldum.

Şimdi ise Controller kısmına geçelim. Projemize Controllers diye bir klasör oluşturalım ve Add>>Controller>>Empty Controller diyerek, ismine YvzGraphController verelim.

[Route("api/[controller]")]
    [ApiController]
    public class YvzGraphController : ControllerBase
    {
        private readonly IDocumentExecuter _documentExecuter;
        private readonly ISchema _schema;

        public YvzGraphController(IDocumentExecuter documentExecuter, ISchema schema)
        {
            _documentExecuter = documentExecuter;
            _schema = schema;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] GraphqlQueryParameter query)
        {
            if (query == null) { throw new ArgumentNullException(nameof(query)); }

            var executionOptions = new ExecutionOptions
            {
                Schema = _schema,
                Query = query.Query,
                Inputs = query.Variables?.ToInputs(),
                FieldNameConverter = new PascalCaseFieldNameConverter()
            };

            try
            {
                var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);

                if (result.Errors?.Count > 0)
                {
                    return BadRequest(result);
                }

                return Ok(result.Data);
            }
            catch (Exception ex)
            {
                return BadRequest(ex);
            }
        }
    }
  • IDocumentExecuter: Graphql tarafında gelen queryi çalıştırmamız için gerekli
  • ISchema: Gelen query için hangi şemayı kullanacağımızı belirlemek için gerekli
  • ExecutionOptions: Graphql tarafında query çalışırken gerekli olan ayarların yapıldığı kısımdır.
  • FieldNameConverter: Bu kısım graphql query standartta hangi yazılması gerektiğini belirttiğimiz kısımdır.
  • Inputs: Querylerimizi hazırlarken, client tarafından istek atılırken, variablesların tanımlanması için kullanılır.
  • Query: Çalışacak query

Geriye sadece Dependecy tanımlamaları kaldı. Startup dosyasına gidip aşağıdaki tanımlamaları yapıyoruz.

 public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<YvzContext>();
            services.AddSingleton<IDependencyResolver>(_ => new FuncDependencyResolver(_.GetRequiredService));
            services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
            services.AddSingleton<ProductType>();
            services.AddSingleton<BrandType>();

            services.AddSingleton<YvzQuery>();
            services.AddSingleton<ISchema, YvzSchema>();
            services.AddMvcCore().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            services.AddMvc().AddNewtonsoftJson();
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

Artık test edebiliriz. Ben Postman kullanıyorum.

İlk Query’imizi çalıştırıyorum.

{"query":"query{Products{Brand{Id,Name}Name}}"}

Şimdi Product’Ların Id’lerini getirmek istersek sadece Id alanını json datanın içerisine eklemek yeterli olacaktır. Aynı şekilde Brand’ları istemiyorsak json içerisinden silebiliriz. Ve SONUÇ:

Tamam, güzel oldu J O zaman birde variables nasıl kullabilirim ona bakalım.

Variables kullanmadan da grapql sorgumuzda parametre vermek mümkün. Sadece ProductByBrandId(Id:8) olarak düzenledikten sonra variables kısmını silmek.

DipNot: Variables kısmını kullanmak için YvzGraphController içerisinde ExecutionOptions kısmından Inputs özelliğin tanımlı olması gerekir.

Evet bir yazımızın daha sonuna geldik. Son olarak şunu eklemek istiyorum. Daha önceden Graphql hiçbir projemde kullanmadım ama yeni küçük projeler de kullanmayı düşünüyorum. Şimdilik GraphQL hakkında aktarabileceklerim bu kadar yeni özelliklerini keşfettiğimde ekleyeceğim.

Kaynak Kodu : https://github.com/Myavuz34/GraphQLExample

Herkese iyi çalışmalar dilerim.