OData Routing Kuralları ve Özelleştirme

asp-net-core-api-odata

Open Data‘ya henüz giriş yapmadıysan ya da mevcut bilgilerini tazelemek istersen bir önceki yazıma GraphQL’i beğendin mi? OData’yı dene! göz atabilirsin, ayrıca OData serisinde kullanmış olduğum projeye buradan erişebilirsin. Bu yazıda OData ile ilgili routing (yönlendirme) kurallarına göz atıp, bunları nasıl özelleştirebileceğimize (customization) bakacağız.

OData Routing Kuralları (Conventions)

Mevcut projemizde controller action methodlarında veri çekmek için Get() isimli methodları kullandık, bu method isimlerinin rastgele verilmediği, bir standarda bağlı olduğunu belirtmek isterim.

Konuyu daha da detaylandırmadan önce bir kolleksiyonu değil de; göndereceğimiz Id bilgisine göre eşleşen tek bir kaydı çekeceğimiz methodumuzu yazalım.

[EnableQuery]
public IActionResult Get([FromODataUri] int key)
{
	return Ok(_dbContext.Vehicles.Where(w => w.Id == key));
}

Yaptıklarımızı biraz detaylandıralım; methodu OData‘nın kullanımına sunmak adına yine [EnableQuery] ile işaretliyoruz. Ardından key adında int tipinde bir parametre alıyor ve [FromODataUri] şeklinde işaretliyoruz. Bu, OData kurallarına göre özelleştirilmiş bir ModelBinder implementasyonundan başka bir şey değil. Burada yine parametremiz kesinlikle kurallar dahilinde key adında olmalı. Ayrıca direk kaydın kendisini değil, yine IQueryable döndüğümüze de dikkat edin.

https://localhost:44357/odata/vehicles(1) adresine istek attığımızda ilgili Vehicle verisinin geldiğini görüyoruz. Ayrıca yine burada query options kullanabiliriz. Vehicle bilgisi yanında Manufacturer bilgisini de isteyelim.

odata-get-by-key

Yazdığımız ikinci action methodumuza da Get(int key) ismini verdik, gelin şimdi bu kurallara göz atalım.

Aşağıda EntitySet’lerimizi sorgulamak için kullanılan kurallar (conventions) yer almaktadır.

MethodUriActionController
GET/VehiclesGet()VehiclesController
GetVehicles()
GET/Vehicles(1)Get(key)
GetVehicle(key)

Tabloda gördüğümüz gibi action method isimlerimiz ya Get() veya controller ismini sonuna almış haliyle GetVehicles() olmalı, tek bir veri çekeceğimiz durumda ise controller isminin tekil (plural) halini kullanıyor olacağız.

Henüz bahsetmedik, ancak bir sonraki yazıda bu konuyu ele alacağım. OData Crud işlemleri için kullanılan routing kuralları aşağıda sunulmuştur.

MethodUriActionController
POST/VehiclesPost(Vehicle)VehiclesController
PostVehicle(Vehicle)
PUT/Vehicles(1)Put(key, Vehicle)
PutVehicle(key, Vehicle)
DELETE/Vehicles(1)Delete(key)
DeleteVehicle(key)

OData Routing Özelleştirme (Customizations)

Action method ve parametre isimlerimizi gerekli şartları yerine getirerek özelleştirebiliriz. Bunun için [ODataRoute] öz niteliğinden (attribute) yararlanacağız. Örneğin ManufacturerController içerisindeki action methodlarımızı özelleştirelim.

public class ManufacturersController : ODataController
{
	[ODataRoute("manufacturers")]
	[EnableQuery(PageSize = 2)]
	public IActionResult Get()
	{
		return Ok(_dbContext.Manufacturers);
	}

	[ODataRoute("manufacturers({manufacturerId})")]
	[EnableQuery]
	public IActionResult GetSingleManufacturer([FromODataUri] int manufacturerId)
        {
		return Ok(_dbContext.Manufacturers.Where(w => w.Id == manufacturerId));
	}
}

Controller sınıfımızda aynı route bilgilerini tekrar açıkca yazmış olmamıza rağmen, burada action method ismi ve parametre adını değiştirdik. Artık GetSingleManufacturer() methodunda parametremizi key olarak değil manufacturerId olarak alacağız. Kodu biraz daha iyileştirecek olursak tüm action methodlara /manufacturer ön ekini (prefix) tanımalamak yerine, controller düzeyine çekerek [ODataRoutePrefix] öz niteliğiyle (attribute) bir defaya mahsus tanımlayabilir ve yönetimini tek bir yerden sağlayabiliriz.

[ODataRoutePrefix("manufacturers")]
public class ManufacturersController : ODataController
{
	[HttpGet]
	[ODataRoute]
	[EnableQuery(PageSize = 2)]
	public IActionResult Get()
	{
		return Ok(_dbContext.Manufacturers);
	}

	[ODataRoute("({manufacturerId})")]
	[EnableQuery]
	public IActionResult GetSingleManufacturer([FromODataUri] int key)
        {
		return Ok(_dbContext.Manufacturers.Where(w => w.Id == key));
	}
}

Kolleksiyon dönecek action method varsayılan olarak tetikleneceği için açıkca [ODataRoute] niteliğine route tanımlamamıza gerek yok, bunun yerine GET isteğine cevap verebilmesi için [HttpGet] ile dekore edildi.

Peki, ya [ODataRoutePrefix] niteliğine verdiğimiz ön eki değiştirmek istersek? Verilerimi /manufacturers üzerinden değil de /vehicle-manufacturers üzerinden erişmek istediğimde yapmam gereken şey çok basit. Öncelikle bir önceki yazıda yazmış olduğumuz GetEdmModel() isimli methodu hatırlayalım, burada kullanıma açtığımız EntitySet’lere birer isim vermiştik; o isimle eşleşen kısım tam olarak burası.

private IEdmModel GetEdmModel()
{
	var builder = new ODataConventionModelBuilder();
	builder.EntitySet<Category>("Categories");
	builder.EntitySet<Manufacturer>("Producers");
	builder.EntitySet<Vehicle>("Vehicles");

	return builder.GetEdmModel();
}

Şimdi controller sınıfımızı dekore ettiğimiz [ODataRoutePrefix] değerini yukarıda verdiğimiz aynı değerle değiştirebiliriz.

[ODataRoutePrefix("producers")]
public class ManufacturersController : ODataController
{
	// ...
}

Bu değişikliklerden sonra Manufacturer verisine https://localhost:44357/odata/vehicle-manufacturers adresinden eriştiğimizi göreceksiniz.

OData Custom Routing konusunu biraz daha detaylandıralım. Örneğin: belirli bir üretici ve kategoriye ilişkin araçları listelemek istediğimizi düşünelim.

[ODataRoutePrefix("producers")]
public class ManufacturersController : ODataController
{
	[EnableQuery]
	[ODataRoute("({producerId})/vehicles({categoryId})")]
	public IActionResult GetVehicles([FromODataUri]int producerId, [FromODataUri]int categoryId)
	{
		return Ok(_dbContext.Vehicles.Where(w => w.ManufacturerId == producerId && w.CategoryId == categoryId));
	}
}

Hemen https://localhost:44357/odata/producers(1)/vehicles(1) adresine istek gönderdiğimizde sonucun geldiğini göreceğiz.

odata-route-customization

Ancak; route yapısını neden /producers(1)/categories(1) şeklinde yap(a)madığımızı merak ediyor olabilirsiniz. ODataRoute kısıtlamalarından dolayı; belirleyeceğimiz url yapısını kullanan varlık (entity) böyle bir property’e -bizim durumumuzda bu Manufacturer oluyor- sahip olmalı; aksi takdirde aşağıdaki hatayı alırsınız.

InvalidOperationException: The path template '...' on the action '...' in controller '...' is not a valid OData path template. Found an unresolved path segment '...' in the OData path template '...'.

İkinci bir örnek olarak; bir kategoriye bağlı araçları getirecek yapımızı kuralım.

public class CategoriesController : ODataController
{	
	[EnableQuery]
	[ODataRoute("categories({categoryId})/vehicles")]
	public IActionResult GetProductsByCategoryId([FromODataUri] int categoryId)
	{
		return Ok(_dbContext.Vehicles.Where(w => w.CategoryId == categoryId));
	}
}

Ardından isteğimizi https://localhost:44357/odata/categories(3)/vehicles adresine gönderelim ve gelen veriye göz atalım.

odata-route-conventions

Bir sonraki yazımıza OData CRUD (Create, Read, Update, Delete) İşlemleri adresinden göz atabilirsin.

You may also like...

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir