İnternete bağlı diğer uygulamaların konuşması için geliştirilmiş bir ara yüzdür. Bu bölümde ASP.NET Core ile bir Web API yapmayı göreceğiz. Kodlarımızı yine Visual Studio Kod üzerinde yazacağız.
Aslında internetteki uygulamaların konuşması çok eski bir konudur. Örneğin, Web Servisleri benzer mantıkla çalışıyordu. Ama Web API’ler biraz daha geniş bir kavram ve genelde çıktı olarak genelde JSON üretiyor. Bir çıktı ürettiğine göre bunu üretmesi için bir GET isteği aklımıza gelmiştir. Öncelikle istek ve cevap türlerine bir bakalım.
API | Tanımı | Request kısmı | Response kısmı |
---|---|---|---|
GET /api/todo | Tüm verileri alır. | Yok | Bir Array Döndürür. (JSON olabilir.) |
GET /api/todo/{id} | Bir ID bağlı veri, veriler üretir. | Yok | Veri, Array |
POST /api/todo | Yeni bir veri ekleme | Veri | Veri |
PUT /api/todo/{id} | Var olan veriyi günceller | Veri | Yok |
DELETE /api/todo/{id} | Bir veriyi siler | Yok | Yok |
Burada veri kelimesi genel bir sınıf nesnesi için kullanılmıştır. Örneğin bunun bir öğrenci verisi olduğunu düşünürsek içinde numarası, adı, soyadı ve bölümü gibi metinleri içerir. Sistemin çalışması kısaca aşağıdaki gibidir.
Bir API’ye istek geldiğinde Controller üzerinde karşılanır. Controller veritabanından kayıtları elde eder. Ardından modele göre kayıtlar serialize eder ve istemciye gönderir. Burada Serialize etmek demek JSON formatına (veya XML, hatta size özel bir formata) çevirmektir. İstemci tarafında bir programlama dili ile (örneğin Javascript) veri deserialize edilebilir. Tabii gelen istek sadece veri istemeyebilir, veri eklemek, düzenlemek veya silmekte isteyebilir. Kısacası, veritabanından sadece read yapmaz, ayrıca write işlemi yapabilir.
04.01. Merhaba web API
Standart işlemleri hızlı bir şekilde yapmak için bir şablon üzerinden basit bir Web API’si uygulaması üreteceğiz. Burada örneği açıklayarak, Web API konusunu anlatacağız. Öncelikle boş bir klasör açın ve VS Code ile Open Folder diyerek bu boş klasörü açın. Yeni bir terminal yaratın ve aşağıdaki kodları çalıştırın.
1 2 |
dotnet new webapi -o TodoApi code -r TodoApi |
Projemizin klasörleri ve dosyaları yaratıldı, debug işlemini başlatıp, bir sorun olup olmadığını test edelim. Debug sırasında tarayıcı açılınca adres çubuğuna:
https://localhost:5001/api/values
yazalım. Eğer [“value1″,”value2”] metnini tarayıcımızda gördüysek doğru yoldayız.
Yukarıdaki istek Controllers klasöründeki ValuesController.cs controller’ı tarafından cevaplandı. Bu sınıfı inceleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } } |
Her istek türü için ayrı bir controller yazılmıştır. ActionResult<IEnumerable<string>> türünden (bir string dizisi olduğunu belirtiyor) veri döndüren Get() metodu kullanılmıştır. [Route(“api/[controller]”)] URL kısmını yolu belirler. (https://localhost:5001/api/values) values controller’ın ismidir. Herhangi bir parametre olmadığı için direkt get() metodu çalışır. [ApiController] niteliği, Web API isteklerine cevap vereceğini gösterir.
Buradaki veriler statiktir. Şimdi basit bir Veritabanı uygulaması yapalım. Daha önceden SQL Server ve SQLite örnekleri yapılmıştı. Şimdi tüm veriyi memory’de tutacağız. Tabii bilgisayarı veya uygulamayı kapattığımızda verilerin uçacağını unutmayalım. Bu arada hızlı işlemler için memory kullanmak iyi bir çözümdür.
04.02. Models ve Context
Veritabanı için Models klasörünü ve TodoItem.cs dosyasını oluşturalım. Models klasörü yerine herhangi bir ismi kullanabilirdik, ancak MVC’den gelen alışkanlıkla genelde veri işlemleri için Models kullanıldığı için seçilmiştir.
1 2 3 4 5 6 7 8 9 |
namespace TodoApi.Models { public class TodoItem { public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; } } } |
Şimdi Veritabanı ile köprü görevi görecek Context’i oluşturalım. TodoContext.cs dosyasına aşağıdaki kodları oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using Microsoft.EntityFrameworkCore; namespace TodoApi.Models { public class TodoContext : DbContext { public TodoContext(DbContextOptions<TodoContext> options) : base(options) { } public DbSet<TodoItem> TodoItems { get; set; } } } |
Startup.cs dosyasına aşağıdaki kodları ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
… using Microsoft.EntityFrameworkCore; using TodoApi.Models; namespace TodoApi { public class Startup { … public void ConfigureServices(IServiceCollection services) { services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList")); … } … } } |
Artık bu memory Veritabanı ile konuşacak controller’a sıra geldi.
04.02. Controller
Controllers klasörüne TodoController.cs oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TodoApi.Models; namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class TodoController : ControllerBase { private readonly TodoContext _context; public TodoController(TodoContext context) { _context = context; if (_context.TodoItems.Count() == 0) { // Create a new TodoItem if collection is empty, // which means you can't delete all TodoItems. _context.TodoItems.Add(new TodoItem { Name = "Item1" }); _context.TodoItems.Add(new TodoItem { Name = "Item2" }); _context.TodoItems.Add(new TodoItem { Name = "Item3" }); _context.SaveChanges(); } } } } |
TodoController yapıcısı her HTTP isteğinde çalışır. İlk çalıştığında memory tablosu boş olduğu için 3 veri ekleyecektir ve memory’ye kaydedilmiştir. Henüz kodunuzu debug etmeyin, çünkü hiç metodumuz yok. Biri parametresiz ve biri parametreli iki GET metodu ile başlayalım. Bir önceki sınıf içine aşağıdaki kodları ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// GET: api/Todo [HttpGet] public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems() { return await _context.TodoItems.ToListAsync(); } // GET: api/Todo/5 [HttpGet("{id}")] public async Task<ActionResult<TodoItem>> GetTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } return todoItem; } |
Şimdi debug zamanı. Tarayıcı açılınca GET isteklerimizi gönderelim.
- https://localhost:5001/api/todo
- https://localhost:5001/api/todo/2
Çıktılar:
- [{“id”:1,”name”:”Item1″,”isComplete”:false},{“id”:2,”name”:”Item2″,”isComplete”:false},{“id”:3,”name”:”Item3″,”isComplete”:false}]
- {“id”:2,”name”:”Item2″,”isComplete”:false}
Dönen değerler ASP.NET Core otomatik olarak nesneyi JSON şekline serialize eder ve JSON formatında istemciye gönderilir. Burada cevap kodu 200 değerindedir. Eğer URL üzerinden yapılan istek bulunmuyorsa cevap kodu 404 gönderir. Belirlenemeyen hatalar 5xx cevap kodunu döndürür.
Bir tarayıcı GET isteklerine cevap verebilir ancak POST, PUT ve DELETE isteklerine ne yazık ki cevap veremez. Bu işlemleri yapmak için API geliştirme için sıklıkla tercih edilen Postman uygulamasını kuracağız. Bu uygulamanın ekran çıktıları için tıklayın. Web API kişisel bilgisayarınızda ise File->Settings bölümden SSL certificate verification bölümünü kapatın. Yukarıdaki GET linklerini kopyalanın ve ilk testlerinizi yapın. Body bölümde sonuçlarınızı gördüyseniz diğer metotlara geçebilirsiniz.
Şimdi yeni bir veri eklemeye geldik. Öncelikle çalıştığımız Controller sınıfı içine aşağıdaki kodu ekleyelim.
1 2 3 4 5 6 7 8 9 |
// POST: api/Todo [HttpPost] public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem) { _context.TodoItems.Add(todoItem); await _context.SaveChangesAsync(); return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem); } |
Eklemeyi context üzerinden yapıp, sonucu tekrar döndüren bir koddur. Bu kodu yazdıktan sonra debug yapalım. Şimdi Postman üzerinden testimizi yapalım. Burada iki bölüm var. Params, Authorization, Headers, Body, Pre-request Script ve Test sekmelerini içeren bölüm istemci olarak düşünebiliriz. Yani verileri göndereceğimiz bölümdür. Body, Cookies, Headers ve Test Results sekmeleri içeren bölüm ise dönen sonuçları gösteren bölümdür. Birinci bölümün body sekmesini tıklayıp JSON (application/json) özelliğini seçin aşağıdaki JSON kodunu yapıştırın.
1 2 3 4 |
{ "name":"Merhaba API Post", "isComplete":true } |
POST işlemini seçin “https://localhost:5001/api/todo” yazdıktan sonra SEND için her şey hazır, 201 created gördüyseniz işlem tamamdır. Tebrikler. POST işlemi de çalıştı. Şimdi tekrar GET yapıp kaydın eklenip eklenmediğini kontrol edebilirsiniz.
Şimdi bir PUT yani güncelleme işlemi yapalım. Aşağıdaki kodu sınıfa ekleyip debug edin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// PUT: api/Todo/5 [HttpPut("{id}")] public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem) { if (id != todoItem.Id) { return BadRequest(); } _context.Entry(todoItem).State = EntityState.Modified; await _context.SaveChangesAsync(); return NoContent(); } |
Değiştirmek istediğimiz kaydın id değerini URL’ye ekleyelim. Örneğin,
https://localhost:5001/api/todo/1
Birinci bölümdeki Body sekmesinde JSON seçili iken aşağıdaki kodu yapıştırın.
1 2 3 4 5 |
{ "ID":1, "name":"Update", "isComplete":true } |
Eğer 204 değerini gördü iseniz güncelleme işlemi de tamamdır. Silme için aşağıdaki kodu sınıfa ekleyin, debug edin ve silmek istediğiniz kaydın id’si yukarıdaki gibi URL’ye ekleyin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// DELETE: api/Todo/5 [HttpDelete("{id}")] public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id) { var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { return NotFound(); } _context.TodoItems.Remove(todoItem); await _context.SaveChangesAsync(); return todoItem; } |
Postman üzerinde çalıştırdığınızda 200 cevabı alırsanız, silme de başarılı ile yapılmıştır. Postman testlerinden başarılı ile geçtiysek şimdi bir web uygulamasından bu API’yi kullanmaya çalışalım. Aynı proje içinde bir çözüm üretmeyi amaçlıyorum.
04.03. API’yi JQuery üzerinden çağırma
Static dosya için wwwroot klasörünün kullanıldığını söylemiştik. Bu uygulama içinde olmadığı için sıfırdan kurmamız gerekir. wwwroot klasörünü aktive etmek için Startup.cs dosyasına Confiure metodu içine aşağıdaki satırları ekleyin.
1 2 |
app.UseDefaultFiles(); app.UseStaticFiles(); |
wwwroot klasörü içine index.html dosyasını yaratalım ve aşağıdaki dosyaları kopyalayalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>To-do CRUD</title> <style> input[type='submit'], button, [aria-label] { cursor: pointer; } #spoiler { display: none; } table { font-family: Arial, sans-serif; border: 1px solid; border-collapse: collapse; } th { background-color: #0066CC; color: white; } td { border: 1px solid; padding: 5px; } </style> </head> <body> <h1>To-do CRUD</h1> <h3>Add</h3> <form action="javascript:void(0);" method="POST" onsubmit="addItem()"> <input type="text" id="add-name" placeholder="New to-do"> <input type="submit" value="Add"> </form> <div id="spoiler"> <h3>Edit</h3> <form class="my-form"> <input type="hidden" id="edit-id"> <input type="checkbox" id="edit-isComplete"> <input type="text" id="edit-name"> <input type="submit" value="Save"> <a onclick="closeInput()" aria-label="Close">✖</a> </form> </div> <p id="counter"></p> <table> <tr> <th>Is Complete</th> <th>Name</th> <th></th> <th></th> </tr> <tbody id="todos"></tbody> </table> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script src="site.js"></script> </body> </html> |
Şimdi bir debug edin ve index.html dosyasının çalıştığına emin olun. JQuery’den API’ye erişme kodlarımız site.js dosyasına yazacağız. site.js dosyasını da wwwroot klasörü içine yaratın.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
const uri = "api/todo";//URL adresi let todos = null; function getCount(data) { //öğe sayısını bulur const el = $("#counter"); let name = "to-do"; if (data) { if (data > 1) { name = "to-dos"; } el.text(data + " " + name); } else { el.text("No " + name); } } //doküman yüklendiğinde getdata() metodu çağrılır $(document).ready(function() { getData(); }); //veriler GET ile ister //tablo üzerine satır satır eklemesini yapar function getData() { $.ajax({ type: "GET", url: uri, cache: false, success: function(data) { const tBody = $("#todos"); $(tBody).empty(); getCount(data.length); $.each(data, function(key, item) { const tr = $("<tr></tr>") .append( $("<td></td>").append( $("<input/>", { type: "checkbox", disabled: true, checked: item.isComplete }) ) ) .append($("<td></td>").text(item.name)) .append( $("<td></td>").append( $("<button>Edit</button>").on("click", function() { editItem(item.id); }) ) ) .append( $("<td></td>").append( $("<button>Delete</button>").on("click", function() { deleteItem(item.id); }) ) ); tr.appendTo(tBody); }); todos = data; } }); } //Bir veri, id="aa-name" input'undan veriyi alır function addItem() { const item = { name: $("#add-name").val(), isComplete: false }; // JSON.stringify veriyi JSON formatına çevirir // POST, ekleme işlemi çalıştır $.ajax({ type: "POST", accepts: "application/json", url: uri, contentType: "application/json", data: JSON.stringify(item), error: function(jqXHR, textStatus, errorThrown) { alert("Something went wrong!"); }, success: function(result) { getData(); $("#add-name").val(""); } }); } //id si verilen kaydı silme API'sini çalıştırır function deleteItem(id) { $.ajax({ url: uri + "/" + id, type: "DELETE", success: function(result) { getData(); } }); } //id'si belli olan bir kaydı API üzerinden değiştirir function editItem(id) { $.each(todos, function(key, item) { if (item.id === id) { $("#edit-name").val(item.name); $("#edit-id").val(item.id); $("#edit-isComplete")[0].checked = item.isComplete; } }); $("#spoiler").css({ display: "block" }); } //Form submit işlemi yapılınca çalışacak metotlar $(".my-form").on("submit", function() { const item = { name: $("#edit-name").val(), isComplete: $("#edit-isComplete").is(":checked"), id: $("#edit-id").val() }; $.ajax({ url: uri + "/" + $("#edit-id").val(), type: "PUT", accepts: "application/json", contentType: "application/json", data: JSON.stringify(item), success: function(result) { getData(); } }); closeInput(); return false; }); function closeInput() { $("#spoiler").css({ display: "none" }); } |
Kod satırları açıklamalar yapıldı. Postman’le test ettiğimiz metotları AJAX-based bir şekilde sayfa yenilemeden Web API’leri bir web uygulaması üzerinden çağıralım. Postman üzerinden işlem sonuçlarını kontrol edebilirsiniz.
Bu bölüme kadar SQL Server, SQLite ve Memory tabanlı bir Veritabanı yarattık. Günümüzde NoSQL veritabanlarının popülerliği de son derece artmıştır. MongoDB JSON veri tutan en popüler veritabanlarından biridir. Bu güne kadar hiç kullanmadıysanız linkteki örneği incelemenizi öneririm.
Eğer bir Web API uygulaması geliştiriyorsanız, farklı metotları üretmek bir geliştirici için zordur. Swagger (OpenAPI) problemi çözmek için iyi dokümanlara sahip ve yardım sayfaları olan iyi bir çözümdür. Bu uygulama bir .Net Core çözümü değildir. Ancak, Swashbuckle.AspNetCore ve NSwag projeleri ASP.NET Core Web API’leri için dokümantasyon üretmek için açık kaynak kodlu olarak Swagger uygulamasının kullanıma olanak sağlar. Daha fazla bilgi için tıklayın.