.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;

  • GET, POST, PUT ve DELETE gibi her türlü HTTP fonksiyonuna karşılık gelen metotlar oluşturulabilmektedir,
  • QueryString, Dynamic URL Parameter vs. gibi birçok farklı şekilde parametreler barındıran istekler oluşturulabilmektedir,
  • Datalar birden fazla serialization yöntemi ile harmanlanarak POST edilebilmektedir,
  • Multipart Upload’lar ile byte dizisi, string, Stream yahut FileInfo türlerinde dosya transferleri gerçekleştirilebilmektedir,
  • Yapılacak isteklere static yahut dynamic olmak üzere kolayca headerlar eklenebilmektedir.

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.

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

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.

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

Çıktı

API Attributes

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

  • [Get(“/todos”)]
    Şuana kadar yaptığımız örneklendirmelerde kullandığımız türdür.
  • [Get(“/todos/{id}”)]
    Url’de parametreler belirtilebilmektedir.
    Tanımlanma;

        public interface ITodoAPI
        {
            [Get("/todos/{id}")]
            Task<Todo> GetTodo(int id);
        }
    

    Kullanım;

                ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
                Todo todo = await todoAPI.GetTodo(5);
                Console.WriteLine(todo.title);
    

    Ayrıca parametre adının url’dekiyle eşleşmediği durumlarda aşağıdaki gibi ‘AliasAs’ attribute’u kullanılabilir.

        public interface ITodoAPI
        {
            [Get("/todos/{id}")]
            Task<Todo> GetTodo([AliasAs("id")]int todoid);
        }
    
  • [Get(“/todos?id={id}”)]
    Bu şekilde QueryString olacak şekilde sorgu parametreleri de verilebilmektedir.
    Tanımlanma;

        public interface ITodoAPI
        {
            [Get("/todos?id={id}")]
            Task<List<Todo>> GetTodosId(int id);
        }
    

    Kullanım;

                ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
                List<Todo> todos = await todoAPI.GetTodosId(5);
                foreach (var todo in todos)
                    Console.WriteLine(todo.title);
    
  • [Get(“/todos?id={request.Id}&UserId={request.UserId}”)]
    URL parametreleri özel bir nesneye bağlanarak bu şekilde kullanılabilmektedirler.
    Custom Object;

        public class TodoRequest
        {
            public int Id { get; set; }
            public int UserId { get; set; }
        }
    

    Tanımlama;

        public interface ITodoAPI
        {
            [Get("/todos?id={request.Id}&UserId={request.UserId}")]
            Task<List<Todo>> GetByIdAndUserId(TodoRequest request);
        }
    

    Kullanım;

                ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
                List<Todo> todos = await todoAPI.GetByIdAndUserId(new TodoRequest { Id = 1, UserId = 1 });
                foreach (var todo in todos)
                    Console.WriteLine(todo.title);
    
  • Tanımlanmayan Parametreler
    Aşağıdaki gibi tanımlanmayan parametreler QueryString parametresi olarak kullanılmaktadır.

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

    /todos?id=…

  • {**name} Parametresi
    {**name} parametresi ile tanımlanan ifadeler bütünsel olarak kendisinden sonraki tüm tanımlamaları kapsayabilmektedirler.
    Tanımlama;

        public interface ITodoAPI
        {
            [Get("/{**page}")]
            Task<Todo> Get(string page);
        }
    

    Kullanım;

                ITodoAPI todoAPI = RestService.For<ITodoAPI>("http://jsonplaceholder.typicode.com");
                Todo todo = await todoAPI.Get("todos/5");
                Console.WriteLine(todo.title);
    

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;

  • Adım 1
    Öncelikle ‘HttpClientHandler’ sınıfından türeyen ve ‘SendAsync’ metodunu override eden aşağıdaki nesne oluşturulur.

        class AuthenticatedHttpClientHandler : HttpClientHandler
        {
            private readonly Func<Task<string>> getToken;
            public AuthenticatedHttpClientHandler(Func<Task<string>> getToken)
            {
                if (getToken == null) throw new ArgumentNullException(nameof(getToken));
                this.getToken = getToken;
            }
    
            async protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                var auth = request.Headers.Authorization;
                if (auth != null)
                {
                    var token = await getToken().ConfigureAwait(false);
                    request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token);
                }
                return await base.SendAsync(request, cancellationToken);
            }
        }
    

    Yukarıdaki ‘AuthenticatedHttpClientHandler‘ sınıfına göz atarsak eğer biraz önce bahsedildiği gibi ‘HttpClientHandler’ sınıfından türemekte ve ‘SendAsync’ metodunu override etmektedir. Constructor’ına bakarsanız eğer ‘Func<Task<string>>’ türünden ‘getToken’ isimli bir delegate almaktadır. Bu Func yapılanması, ilgili sınıftan nesne üretilirken kendisine verilen metot içerisinde server’dan token’ı talep edecek ve geriye döndürecektir. 15. satırda ilgili Func referansına verilen metot çağrılarak token elde edilmekte ve 16. satırda request’in headerlarından Authorization’a yerleştirilmekte ve böylece request yenilenmiş olmaktadır. Ardından ilgili request base class’ta ki ‘SendAsync’ metoduna gönderilmektedir.

  • Adım 2
    Refit’in istekleri görselleştirebilmesi için arayüz tasarlanmalıdır.

        public interface ITodoAPI
        {
            [Get("/todos/login")]
            [Headers("Authorization: Bearer")]
            Task<string> Login();
        }
    
  • Adım 3
    Ardından istek aşağıdaki gibi tasarlanıp, gönderilmelidir.

                var api = RestService.For<ITodoAPI>(new HttpClient(new AuthenticatedHttpClientHandler(() =>
                {
                    //Sunucudan token talep edilebilir...
                    return Task.FromResult("elde edilen token degeri");
                }))
                {
                    BaseAddress = new Uri("https://localhost:5001")
                });
                await api.Login();
    

    Görüldüğü üzere ‘RestService.For’ komutuyla isteğin gönderilebilmesi için bir ‘HttpClient’ nesnesi kullanılmakta ve içerisine oluşturduğumuz ‘AuthenticatedHttpClientHandler’ nesnesi verilmektedir. Bu nesnenin tasarımı icabı constructor’ından istediği Func değerine karşılık tanımlanan lambda fonksiyonu içerisinde token talep edilmekte ve elde edildiği taktirde geriye döndürülmektedir. Ayrıca ‘HttpClient’ nesnesinin initializer’ında ‘BaseAddress’ propertysine base url verilmektedir.

Header’ların Yeniden Tanımlanması

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

  • Interfaceler’de ki header tanımlaması(en düşük öncelik),
  • Metotlarda ki header tanımlaması,
  • Parametrelerde ki header tanımlaması(en yüksek öncelik)

Ö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…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*