Yazılım Mimarileri ve Tasarım Desenleri Üzerine

.NET Core – Refit Kütüphanesi Nedir? Nasıl Kullanılır?

.NET Core - Refit Kütüphanesi Nedir? Nasıl Kullanılır?

Merhaba,

Bu içeriğimizde, uygulamalarımızda HttpClient yerine basit interface tanımlamaları sayesinde Rest API’lar ile kolay ve hızlı bir şekilde işlemler yapmamızı sağlayan oldukça güçlü Refit kütüphanesi üzerine konuşuyor olacağız. Makalemizde içeriksel akış olarak Console Application üzerinde temellendirmeler yapılmasının ardından Asp.NET Core uygulaması üzerinde de entegrasyonun nasıl gerçekleştirildiğini inceleyeceğiz. O halde hiç vakit kaybetmeden Refit tanımıyla başlayalım…

İçindekiler;

Refit Kütüphanesi Nedir?

Temel anlamda Rest API’ları uygulamada bir arayüze dönüştürerek iletişim kurulacak bağımlılıkları daha kolay bir şekilde yönetmemizi sağlayan ve Android tarafında geliştirilmiş olan Retrofit isimli kütüphaneden esinlenerek oluşturulmuş olan .NET tabanlı bir kütüphanedir.

Refit kütüphanesinin işlevsel açıdan birçok avantajı mevcuttur;

Nasıl Kullanılır?

Temel kullanım örneklendirmesi Console Application üzerinden anlatılmaktadır.

Refit kütüphanesini kullanabilmek için öncelikle aşağıdaki kod ile projeye yüklenmesi gerekmektedir.
dotnet add package refit

Ardından hedef API’ların bir interface üzerinden modellenmesi gerekmektedir. Bizler burada örneklendirme için http://jsonplaceholder.typicode.com/todos adresini hedef API olarak kullanacak ve tüm sistemi buradaki datalara göre inşa edeceğiz.

Tabi öncelikle ilgili adresteki dataya uygun bir entity oluşturarak işe başlayalım.

Makalenin kaleme alındığı tarihte http://jsonplaceholder.typicode.com/todos adresindeki örnek data modeli…

   public class Todo
   {
      public int userId { get; set; }
      public int id { get; set; }
      public string title { get; set; }
      public bool completed { get; set; }
   }

Ardından ilgili API’a aşağıdaki arayüzü tasarlayalım.

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodos();
    }

Buradaki interface’e odaklanırsak eğer kendisi hedef API’ın base url’ine(http://jsonplaceholder.typicode.com) karşılık gelmektedir. İçerisinde tanımlanan imzalar ise ilgili base url’lerin tetikleneceği metotlarını temsil etmektedir. Örneğin; ‘Get’ attribute’u ile işaretlenmiş ‘GetTodos’ imzası base url’in devamındaki metodu/yolu(/todos) temsil etmektedir.

Şimdi bu arayüz üzerinden hedef API’a bir istek gönderelim;

        static async Task Main(string[] args)
        {
            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
            List<Todo> todos = await todoAPI.GetTodos();
            foreach (var todo in todos)
                Console.WriteLine(todo.title);
        }

Yukarıdaki kod bloğunu incelersek eğer ‘RestService.For’ komutu aracılığıyla base url’imizi belirlemekte ve dönülen nesne üzerinden interface’de tanımlanan imzalar vasıtasıyla ilgili metotlara istekler yapmaktayız.

Çıktı

API Attributes

Refit üzerinden attribute’lar ile birbirinden farklı birçok annotations tanımlaması yapılabilmektedir. Bu tanımlamaları incelersek eğer;

Dynamic Querystring Parameters

Bir nesne aşağıdaki gibi dinamik olarak sorgu parametresi şeklinde belirtilebilmektedir.

    public class TodoQueryParam
    {
        [AliasAs("order")]
        public string SortOrder { get; set; }
        public int Limit { get; set; }
    }
    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodos(TodoQueryParam todoQueryParam);
    }

Eğer ki, API üzerinde bir önek tanımlanması gerekiyorsa Query attribute’unun kullanılması yeterli olacaktır.

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodosQuery([Query("search")]TodoQueryParam todoQueryParam);
    }

Böylece ilgili arayüz üzerinden imzaları kullanırken aşağıdaki gibi API’lara istekler gönderilmiş olacaktır.

            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
            await todoAPI.GetTodos(new TodoQueryParam { Limit = 5, SortOrder = "desc" });
            await todoAPI.GetTodosQuery(new TodoQueryParam { Limit = 5, SortOrder = "desc" });

“/todos/?order=desc&Limit=5”
“/todos/?search.order=desc&search.Limit=10”

Collections as Querystring parameters

Aşağıdaki gibi interface içerisindeki imzada tanımlanan koleksiyonel değerler direkt olarak query string değerleri olarak kullanılacaktır.

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodosMulti([Query(CollectionFormat.Multi)] int[] ids);
        [Get("/todos")]
        Task<List<Todo>> GetTodosCsv([Query(CollectionFormat.Csv)] int[] ids);
    }

Her iki imza arasındaki kullanım farkı;

            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
            await todoAPI.GetTodosMulti(new[] { 3, 5, 7 });
            await todoAPI.GetTodosCsv(new[] { 3, 5, 7 });

/todos?ids=3&ids=5&ids=7
/todos?ids=3%2C5%2C7

şeklinde olacaktır.

Ayrıca aşağıdaki gibi koleksiyonel parametrelerin ‘Query’ attribute’u ile işaretlenmeksizin ‘CollectionFormat’ tanımlaması yapılmadığı durumlar olabilir.

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodosMulti(int[] ids);
        [Get("/todos")]
        Task<List<Todo>> GetTodosCsv(int[] ids);
    }

İşte böyle bir durumda ‘RestService’ üzerinden bir ‘RefitSettings’ nesnesiyle genel konfigürasyon sağlanabilir.

            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com", new RefitSettings { CollectionFormat = CollectionFormat.Csv });
            await todoAPI.GetTodosMulti(new[] { 3, 5, 7 });
            await todoAPI.GetTodosCsv(new[] { 3, 5, 7 });

Unescape Querystring Parameters

İmza parametrelerine verilen değerler URL yapısına uygun olmayan karakterleri barındırıyorsa eğer tarayıcılar tarafından otomatik escape karakterlerine dönüştürülürler. Örneğin;

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodos(string q);
    }

bu arayüze uygun şöyle bir istek gönderilirse eğer

            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
            await todoAPI.GetTodos("sebepsiz+boş+yere.ayrılacaksan!");

/todos?q=sebepsiz%2Bbo%C5%9F%2Byere.ayr%C4%B1lacaksan%21
şeklinde query string değeri oluşturulacaktır.

Lakin bu şekilde özel karakterlerin query string değerine olduğu gibi eklenmesini istiyorsak eğer ilgili imzayı aşağıdaki gibi ‘QueryUriFormat’ attribute’u ile işaretleyerek ‘UriFormat.Unescaped’ değerinin verilmesi yeterli olacaktır.

    public interface ITodoAPI
    {
        [Get("/todos")]
        [QueryUriFormat(UriFormat.Unescaped)]
        Task<List<Todo>> GetTodos(string q);
    }

/todos?q=sebepsiz+bo%C5%9F+yere.ayr%C4%B1lacaksan!

Headers Ekleme

Yapılacak isteklere metot seviyesinde ve interface seviyesinde olmak üzere iki türlü header eklenebilmektedir.

Metot seviyesinde header ekleme;

    public interface ITodoAPI
    {
        [Headers("name:gencay yildiz")]
        [Get("/todos")]
        Task<List<Todo>> GetTodos();
    }

Interface seviyesinde header ekleme;

    [Headers("name:gencay yildiz")]
    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodos();
    }

Dynamic Headers Ekleme

Aşağıdaki gibi dinamik bir şekilde header eklenebilmektedir.

    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<Todo> GetTodos([Header("authorization")] string authorization);
    }

Authorization (Dynamic Headers Redux)

Genellikle header authorization işlemlerinde token taşımak amacıyla kullanılan bir yapıdır. Dolayısıyla bu yapıya dinamik bir süreç neticesinde aşağıdaki gibi token değeri set edilebilir ve talep öylece gönderilebilir.

Tabi bu dinamik süreci inşa edebilmek için aşağıdaki adımların sırasıyla uygulanması gerekmektedir;

Header’ların Yeniden Tanımlanması

Aşağıdaki öncelik sırasına göre header’lar yeniden tanımlanırlar.

Örneğin;

    [Headers("position:interface")]
    public interface ITodoAPI
    {
        [Get("/todos")]
        Task<List<Todo>> GetTodos();
        [Get("/todos")]
        [Headers("position:metot")]
        Task<List<Todo>> GetTodos(int id);
        [Get("/todos")]
        [Headers("position:metot")]
        Task<List<Todo>> GetTodos(int id, int userId, [Header("position")] string headerparam);
    }

arayüzünü ele alırsak eğer aşağıdaki gibi yapılan isteklerde sırasıyla

            ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
            await todoAPI.GetTodos();
            await todoAPI.GetTodos(5);
            await todoAPI.GetTodos(5, 2, "parametre");

{[position, {interface}]}
{[position, {metot}]}
{[position, {parametre}]}
headerlarını alacaktır.

Header’ların yeniden tanımlanma davranışı sadece aynı isimdeki headerlar için geçerlidir.

Header’ların Silinmesi

Tanımlanmış herhangi bir header, boş değere sahip olacak şekilde tekrardan tanımlanarak ezilebilir ve böylece temizlenebilir.

    [Headers("position:interface")]
    public interface ITodoAPI
    {
        [Get("/todos")]
        [Headers("position")]
        Task<List<Todo>> GetTodos();
    }

Ayrıca aşağıdaki gibi bir tanımlama yapılarak da header’a silinmeksizin empty değer atılır.

    [Headers("position:interface")]
    public interface ITodoAPI
    {
        [Get("/todos")]
        [Headers("position:")]
        Task<List<Todo>> GetTodos();
    }

Asp.NET Core Uygulamalarında Refit Kullanımı

Bir Asp.NET Core uygulamasında Refit’i kullanabilmek için öncelikle projeye Refit kütüphanesiyle birlikte Refit.HttpClientFactory kütüphanesinin yüklenmesi gerekmektedir.

Ardından ‘Startup.cs’ dosyasındaki ‘ConfigureServices’ metodu içerisinde aşağıdaki gibi ‘AddRefitClient’ servisinin eklenmesi gerekmektedir.

    public interface ITodoAPI
    {
        [Get("/todos")]
        public Task<List<Todo>> GetTodos();
    }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRefitClient<ITodoAPI>()
                .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://jsonplaceholder.typicode.com"));
            services.AddControllers();
        }

Tabi istek doğrultusunda bir ‘RefitSettings’ objeside eklenebilmektedir.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRefitClient<ITodoAPI>(new RefitSettings { CollectionFormat = CollectionFormat.Csv })
                .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://jsonplaceholder.typicode.com"));
            services.AddControllers();
        }

Tüm bu düzenlemeler neticesinde tasarlanan Refit arayüzü aşağıdaki gibi dependency injection’dan talep edilerek, kullanılabilir.

    [Route("api/[controller]")]
    [ApiController]
    public class TodosController : ControllerBase
    {
        readonly ITodoAPI _todoAPI;
        public TodosController(ITodoAPI todoAPI)
        {
            _todoAPI = todoAPI;
        }

        public async Task<IActionResult> GetTodos()
        {
            return Ok(await _todoAPI.GetTodos());
        }
    }

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Exit mobile version