Hep böyle uzun aralar oluyor, hiç sevmiyorum… Ayıp bana. Neyse, uzun bir aradan sonra yeni bir yazı ile ASP.NET Core tarafındaki sıcak konulardan birine, gRPC servislerine farklı bir açıdan yine bakalım. Yine bakalım diyorum çünkü daha önce “gRPC, .NET Core ve “streaming”” başlıklı yazıda az biraz .NET Core ve gRPC’den bahsetmeye çalışmıştım.

gRPC, Google tarafından geliştirilen bir “Remote Procedure Call” framework’ü… Aktarım protokolü olarak HTTP/2 üzerinde yüksek performanslı bir yapısının olması, platform ve dil bağımsız olması da birçok kompleks çözüm için tercih edilmesinin en önemli sebeblerinden. “Protocol Buffers” ile oluşturalan kontratlar ile servis tanımlarını net belirtme ve bu tanımların “binary serialization” ile hızlı çözümlenmesi de “microservices” dünyası için oldukça değerli.

Google’ın back-end tarafındaki birçok servisinin bu framework üzerinde kurulmuş olması ve bir çok büyük organizasyonun gRPC yaklaşımını tercih etmesi ile de gRPC‘nin, kendini zaten ispatlamış bir teknoloji olduğunun ispatı.

Şu an gRPC‘nin en büyük handikapı tarayıcılar(browser) tarafında kullanılabilen varsayılan API’lar olmaması. Yani tarayıcılar üzerinden gRPC servis çağrılarının yapılabilmesi için ekstra bir yapıya gereksinim var. gRPC-Web‘de bu ihtiyaç için ortaya çıkmış bir protokol. Basitçe özetlemek gerekirse, ara bir JS katmanı ile tarayıcıların gRPC çağrıları yapılması sağlanıyor.

.NET Core 3.0 ile .NET tarafında gRPC ile tanışmıştık ama gRPC-Web tarafının daha karışılığı tam olarak yoktu. Geçtiğimiz hafta .NET için de gRPC-Web, resmi olarak yayınlandı. Yani ASP.NET Core gRPC servislerini de artık tarayıcılardan da çağırabiliyoruz. Bir servisin, bir tarayıcıdan çağrılabiliyor olması çok büyük bir olay değil belki… “Bu mu yani, öhhhhh???” gibi ifadeler ile yaklaşabiliriz ama WebAssembly, Blazor, SPA(Single-Page Application) uygulama modelleri geliştikçe ve gRPC servislerinin kullanımı arttıkça, dönüp baktığımızda önemli bir gelişme diyeceğiz.

Geçtiğimiz haftalarda hatırlarsanız ASP.NET Core Blazor WebAssembly uygulama modeli de yayınlanmıştı. C# ile WebAssembly alt yapısı ile, direkt tarayıcılarda çalışan .NET uygulamaları geliştirmek mümkün hale gelmişti. gRPC-Web de bu açıdan daha bir anlamlı duruma gelmiş oluyor.

Önceki gRPC yazımda çok basit bir örnek yapmıştım. O örneği biraz genişleterek, yeni bir Blazor WebAssembly uygulamasından geliştirdiğimiz gRPC servisini çağırmaya bakalım. Dolayısıyla önceki yazıyı okumadıysanız önce ona bir bakmakta fayda olacaktır.

ASP.NET Core gRPC servis ayarı

Öncelikle geliştirdiğimiz bir ASP.NET Core gRPC servisinin, tarayıcılardan çağrılabilmesi için servis tarafında küçük birkaç düzenleme yapmamız gerekiyor.

1- gRPC servis projemize Grpc.AspNetCore.Web kütüphanesini eklememiz gerekiyor.

dotnet add package Grpc.AspNetCore.Web –version 2.29.0

2- Startup.cs içersinde, uygulamamızı oluştururken .UseGrpcWeb(); ve .EnableGrpcWeb(); methodları ile servisin tarayıcılar tarafından çağrılabileceğiniz belirtiyoruz.

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();

            }
            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseGrpcWeb();
            
            app.UseCors("AllowAll");

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<QuizService>().EnableGrpcWeb();

                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
                });
            });
        }

3- CORS eklentisi ile tarayıcıdan bu kaynağa erişim politikasını belirliyoruz. Artık olmazsa olmaz bir yaklaşım zaten. gRPC’ye özel bir durum değil yani…

            services.AddCors(o => o.AddPolicy("AllowAll", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader()
                       .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
            }));

Bu üç ekleme ile mevcut geliştirdiğimiz gRPC servisinin tarayıcılardan çağrılabilir olmasını sağlıyoruz.

Daha önce gRPC servisi geliştirmediyseniz,

dotnet new grpc -n HelloGrpc

şeklinde örnek bir proje oluşturup üzerinden gidebilirsiniz.

Blazor WebAssembly projesi…

Şimdi basit bir Blazor WebAssembly projesi oluşturalım.

dotnet new blazorwasm -n HelloBlazor

Yukarıdaki dotnet komutu ile bir Blazor WebAssembly projesi oluşturabiliriz. Şablon olarak gelen proje üzerinden ASP.NET Core gRPC servisimizi nasıl çağırabiliriz önce buna bakalım.

syntax = "proto3";

option csharp_namespace = "gRPC.Server";

package Quiz;

service Maths {
  rpc SolveOperation (QuestionRequest) returns (AnswerReply);
}


message QuestionRequest {
  repeated string texts = 1;
}

message AnswerReply {
  string question = 1;
  double answer=2;
}

İlk iş servis tanımını yani Protocol Buffer yapısı ile *.proto dosyamızı eklememiz lazım. Bu *.proto dosyası ile, gRPC servis tanımını uygulamamıza ekliyor ve böyle bir servisi çağıracağımızı tanımlıyoruz. ASP.NET Core’un bu tanıma göre arka tarafta “proxy” yapısını oluşturabilmesi için *.csproj dosyasına da protobuf eklentisini de yapıyoruz.

  <ItemGroup>
    ..............
    .........
    .....
    ...
    ..
    <Protobuf Include="Protos\maths.proto" GrpcServices="Client" Link="Protos\maths.proto" Access="Internal" />
  </ItemGroup>

Bu sayede, projemizi build ettiğimizde bu protobuf’a göre servisi çağırabilmek için proxy yapısı oluşacak.

Şimdi servisimizi çağıracağımız Razor ön yüz bileşenini oluşturalım. Öncelikle MathematicalOperation.razor diye bir Razor bileşenini projemize ekleyelim ve içeriğini de aşağıdaki gibi yapalım.

@inject GrpcChannel Channel

<div class="d-flex flex-row">
    <div class="p-2 flex-fill">@Question = @(Answer.HasValue? Answer.ToString():"???")</div>
    <div class="p-2"><button class="btn btn-sm btn-primary" @onclick="SolveOperation">Solve</button></div>
</div>
@code {
    [Parameter]
    public string Question { get; set; }

    private double? Answer { get; set; }

    private async Task SolveOperation()
    {
        if (!string.IsNullOrEmpty(Question))
        {
            try
            {
                var request = new Server.QuestionRequest();
                request.Texts.Add(Question);

                var client = new gRPC.Server.Maths.MathsClient(Channel);
                var result = await client.SolveOperationAsync(request);

                Answer = result.Answer;
            }
            catch (Exception ex)
            {

                throw;
            }


        }
    }
}

Biraz dan dun hızlı gittim belki ama önceki yazıyı okuduğunuzu farz ediyorum. Bu örnekte de önceki yazıdaki gibi, belli matematik işlemleri gRPC servisimize sorup, cevaplarını alıyoruz. Bu oluşturduğumuz Razor bileşeni de servis çağrılarını yapacak olan bileşen. Burada önemli olan kısım SolveOperation() metodunun içerisi. Burada projemizi build ettiğimizde otomatik olarak oluşan proxy objesini kullanarak servisimizi çağırıyoruz.

Burada dikkat etmek gereken önemli kısım bileşenimize @inject ettiğimiz Channel özelliği. Bunu da Program.cs’e aşağıdaki gibi bir eklenti ile ASP.NET Core’un “Dependency Injection” özelliğini kullanarak yapıyoruz. Yaptığımız olay temelinde, gRPC servisini çağıracak bir kanal oluşturmak ve özelliklerini belirtmek. Burada fark edeceğiniz üzere, servis adresini belirtiyoruz.

    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");
            builder.Services.AddSingleton(services =>
            {
                var config = services.GetRequiredService<IConfiguration>();
                
                var httpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler());

                return GrpcChannel.ForAddress(config["ServerEndpoint"], new GrpcChannelOptions { HttpHandler = httpHandler });
            });
            await builder.Build().RunAsync();

        }
    }

Projemizdeki Index.razor içerisine, bu yeni eklediğimiz bileşen ile örnek sorularımızı oluşturup, projemizi çalıştıralım. Hem gRPC servisimizi(a.k.a gRPC.Server), hem de Blazor WebAssembly(a.k.a gRPC.WWW) ayağa kaldırdığımızda aşağıdaki gibi sayfa bizi karşılıyor olacak.

Her sorunun karşısındaki Solve tuşuna basınca, gRPC servisi çağırıları ile soruların sonuçlarını görebileceğimizi fark edeceksiniz.

Basitçe özetlemek gerekirse, bu yaptığımız geliştirmeler ile tarayıcıda çalışan bir C# uygulaması üzerinden(Blazor WebAssembly), gRPC servis çağırıları yaparak servis çağrılarının sonuçlarını alabildik.

Şu an çok basit gelse de Blazor WebAssembly ile yapılabilecekleri de düşündüğümüze, kontrat bazlı servis çağrılarını çok performanslı bir şekilde gRPC ile yapabiliyor olmak ilerisi için değerli çözümler üretmemizi sağlayacak.

Biraz hızlı oldu ama açıkçası sadece temel noktalardan bahsederek, çok uzatmadan, anahtar kelimeler ile biraz kapı açmak istedim. Komple projeler ve kodlar her zamanki gibi burada. Mutlaka indirip, kurcalayın derim. Takılırsanız, sorunuz olursa da, yazmaktan çekinmeyin. Bir sonraki yazıya kadar, mutlu kodlamalar…