OData Function & Action Kullanımı

asp-net-core-api-odata

Bu yazımıza kadar Open Data ile seçme, filtreleme, ekleme, güncelleme, silme gibi işlemleri gerçekleştirdik. Bazı durumlarda direk olarak tanımladığımız EntitySet ile ilgili olmayan işlemler de gerçekleştirmek isteyebiliriz. OData bu gibi durumları gerçekleştirmek için bize Action ve Function isimli iki seçenek sunuyor. OData yazı serisi boyunca kullanmış olduğum projeye buradan erişebilirsin.

Microsoft resmi dökümantasyonu Action ve Function arasındaki farkı; bir Action yan etkiye (side effect) sahip olabilirken, Function’da böyle bir durum olmadığını belirleterek açıklıyor.

OData Actions

OData Action‘larını HTTP üzerinden POST metoduyla tetikliyoruz. Bir parametresi olabilir veya bir değer döndürebilirler. Bu bilgileri EdmModel içerisinde tanımlıyoruz. Action‘ları; karmaşık işlemleri (transactions), birden fazla varlığı (entity) aynı anda manipüle etmek, sadece istediğimiz alanları güncellemek istediğimizde, bir varlıkla (entity) ilişkisi olmayan bir bilgiyi almak için kullanabiliriz.

Örneğin: bir üreticiye bağlı toplam araç sayısını çekeceğimiz bir action yazmak istediğimizi varsayalım, EdmModel içeriğine göre action bildirimini yaparak başlayalım.

private IEdmModel GetEdmModel()
{
	var builder = new ODataConventionModelBuilder();
	builder.EntitySet<Category>("Categories");
	builder.EntitySet<Manufacturer>("Producers");
	builder.EntitySet<Vehicle>("Vehicles");
	
	builder.EntityType<Category>().Action("TotalVehiclesByCategory").Returns<int>();
	
	return builder.GetEdmModel();
}

Burada Category varlığımıza (entity) TotalVehiclesByCategory isminde method tanımladığımızı ve yapacağımız işlemler sonunda int tipinde değer döneceğimizi belirtiyoruz. Şimdi controller sınıfımızda denk düşen action methodumuzu yazalım.

public class CategoriesController : ODataController
{
	[HttpPost]
	public IActionResult TotalVehiclesByCategory([FromODataUri] int key)
	{
	var total = _dbContext.Vehicles.Where(w => w.CategoryId == key).Count();

		return Ok(total);
	}
}

https://localhost:44357/odata/categories(2)/totalvehiclesbycategory adresine POST isteği attığımızda sonucun geldiğini göreceğiz.

odata-action-method

Şimdi de bir kayıtla ilgili değil, direk kolleksiyon (collection) üzerinden bir örnek yapalım. Atacağımız POST isteği sonucu kategori sayısını bize dönsün. Yine EdmModel üzerinde methodumuzu tanımlayalım.

builder.EntityType<Category>().Collection.Action("TotalCategoryCount").Returns<int>();

Controller sınıfımızda action methodumuzu yazalım.

[HttpPost]
public IActionResult TotalCategoryCount()
{
	var count = _dbContext.Categories.Count();

	return Ok(count);
}

https://localhost:44357/odata/categories/totalcategorycount adresine istek attığımızda kategori sayımızın geldiğini göreceğiz.

Şimdi de belirli bir kategori ve üreticiye göre araç sayılarını almak istediğimizi düşünelim. EdmModel içeriğine göre tanımlamamızı yapıyoruz.

builder.EntityType<Category>().Action("VehicleCountByManufacturer").Returns<int>().Parameter<int>("ManufacturerId");

Controller sınıfımızda ilgili action methoduzu yazalım.

[HttpPost]
public IActionResult VehicleCountByManufacturer([FromODataUri] int key, ODataActionParameters parameters)
{
	var manufacturerId = (int)parameters["ManufacturerId"];
	var vehicles = _dbContext.Vehicles.Where(w => w.CategoryId == key && w.ManufacturerId == manufacturerId).Count();

	return Ok(vehicles);
}

Dikkat ederseniz POST metodu gövdesindeki verileri ODataActionParameters üzerinden aldığımızı göreceksin, gelen veriler Dictionary<string, string> tipinde olacaktır.

{
    "ManufacturerId": 1
}

İsteğimizi https://localhost:44357/odata/categories(1)/vehiclesbymanufacturer adresine yukarıdaki gövdeyle gönderdiğimizde sonucun geldiğini göreceğiz.

Şimdi ise birden fazla primitive tip alan bir örnek yapalım. Yukarıdaki örneğimize yıl parametresini de ekleyelim. Buna göre bir kategori ve üreticinin gönderdiğimiz üretim yılındaki araç sayılarının sayısını elde edelim. EdmModel içeriğine göre tanımlamaları yapıyoruz.

var actionTotal = builder.EntityType<Category>().Action("VehicleCountByYearManufacturer").Returns<int>();
actionTotal.Parameter<int>("ManufacturerId");
actionTotal.Parameter<int>("Year");

Controller sınıfımızda action methodumuzu yazıp POST isteğimizi aşağıdaki bilgiyle gönderelim.

[HttpPost]
public IActionResult VehicleCountByYearManufacturer([FromODataUri] int key, ODataActionParameters parameters)
{
	var manufacturerId = (int)parameters["ManufacturerId"];
	var year = (int)parameters["Year"];

	var vehicles = _dbContext.Vehicles.Where(w => w.CategoryId == key && w.ManufacturerId == manufacturerId && w.Year
             == year).Count();

	return Ok(vehicles);
}
{
    "ManufacturerId": 1,
    "Year": 2005
}

https://localhost:44357/odata/categories(1)/vehiclecountbyyearmanufacturer adresine istek attığımızda verimizin geldiğini göreceğiz.

Function başlığına geçmeden önce primitive tip yerine reference tipinde bir parametre alan son bir örnek yapalım. Belirtilen kategori ve belli bir üreticiye ait araçlardan göndereceğimiz motor gücünden yüksek olan araç sayısını isteyelim. Referans (reference) tipimizi tanımlıyor ve EdmModel içeriğine action bildirimini yapıyoruz.

public class ActionForCategory
{
	public int ManufacturerId { get; set; }
	public int Engine { get; set; }
}
builder.EntityType<Category>().Action("VehicleHpByManufacturer").Returns<int>().Parameter<ActionForCategory>("ActionForCategory");

Controller sınıfımızda da action methodumuzu yazalım.

[HttpPost]
public IActionResult VehicleHpByManufacturer([FromODataUri] int key, ODataActionParameters parameters)
{
	var actionForCategory = parameters["ActionForCategory"] as ActionForCategory;

	var vehicles = _dbContext.Vehicles.Where(w => w.CategoryId == key && w.ManufacturerId == actionForCategory.ManufacturerId && w.Engine >= actionForCategory.Engine).Count();

	return Ok(vehicles);
}

Aşağıdaki veriyi https://localhost:44357/odata/categories(1)/vehiclehpbymanufacturer adresine POST ettiğimizde istediğimiz verinin geldiğini göreceğiz.

{
    "ActionForCategory": {
        "ManufacturerId": 1,
        "Engine": 100
    }
}

OData Functions

OData iki tür fonksiyonu destekler. Function‘ları da neredeyse Action‘lar ile aynı şekilde tanımlıyoruz. Function’ların, Action’un aksine HTTP üzerinde GET ile çalıştığını unutmamak gerekir.

OData Bound Functions

Bound Function‘lar bir varlık (entity) ya da kolleksiyonla (collection) doğrudan ilişkisi olmayan bilgileri döndürmek için tercih edilir.

Kategori bazında kaç adet aracımız olduğunu döndüren function‘ımızı yazalım. Yine EdmModel üzerinde function methodumuzu ve dönüş tipimizi tanımlıyoruz.

public class VehicleCountForCategory
{
	public string Category { get; set; }
	public int VehicleCount { get; set; }
}
builder.EntityType<Category>().Collection.Function("VehicleCounts").Returns<IQueryable<VehicleCountForCategory>>();

Controller sınıfımızda karşılık gelen action methodumuzu yazalım.

[HttpGet]
public IActionResult VehicleCounts()
{
	var vehiclesCount = _dbContext.Vehicles.GroupBy(g => g.Category.Name).Select(s => new VehicleCountForCategory { Category = s.Key, VehicleCount = s.Count() });

	return Ok(vehiclesCount);
}

https://localhost:44357/odata/categories/vehiclecounts adresine GET isteği yaptığımızda sonucun geldiğini göreceğiz.

odata-functions

Örneğin: belirli kategori ve üreticiye ilişkin belirli bir üretim yılı üstündeki araçların kaç yaşında olduğu bilgisini dönen başka bir function yazalım. Dönüş tip tanımını ve EdmModel bildirimizi yapıyoruz.

public class YearForVehicle
{
	public int Id { get; set; }
	public string Model { get; set; }
	public int Age { get; set; }
}
var functionYear = builder.EntityType<Category>().Function("CalculateVehicleAge").Returns<IQueryable<YearForVehicle>>();
functionYear.Parameter<int>("ManufacturerId");
functionYear.Parameter<int>("Year");

Controller sınıfımızda karşılık gelen action methodumuzu yazalım.

[HttpGet]
public IActionResult CalculateVehicleAge([FromODataUri] int key, [FromODataUri] int ManufacturerId, [FromODataUri] int Year)
{
	var vehiclesCount = _dbContext.Vehicles.Where(w => w.CategoryId == key && w.ManufacturerId == ManufacturerId && w.Year >= Year).Select(s => new YearForVehicle
	{
		Id = s.Id,
		Model = s.Model,
		Age = DateTime.Today.Year - s.Year
	});

	return Ok(vehiclesCount);
}

https://localhost:44357/odata/categories(1)/calculatevehicleage(ManufacturerId=1,Year=2000) adresine istek attığımızda verinin geldiğini göreceğiz. CalculateVehicleAge fonksiyonunu url üzerinden nasıl çağırdığımıza dikkat edin, OData Function‘larını bu şekilde çağırıyoruz.

odata-bound-function

OData Unbound Functions

Unbound Function’lar normalde bir servise bağlı olmadan servisteki static operasyonlar olarak görev alırlar. Bir parametre alabilir veya almayabilirler.

Örneğin: ülkeye göre vergi oranlarını dönecek bir unbound function yazalım. Edm tanımlamamızı yaparak işleme başlayalım ve ardından controller sınıfımızda action methodumuzu yazalım.

builder.Function("GetTaxRateByCountry").Returns<double>().Parameter<string>("Country");
[HttpGet]
[ODataRoute("GetTaxRateByCountry(Country={country})")]
public IActionResult GetTaxRateByCountry([FromODataUri] string country)
{
	var taxRate = 0.18; //Örnek için veri
	return Ok(taxRate);
}

https://localhost:44357/odata/GetTaxRateByCountry(Country=’Turkey’) adresine istek attığımızda sonucun geldiğini göreceğiz.

odata-unbound-function

Bir sonraki yazı OData Connected Services ile görüşmek dileğiyle.

You may also like...

Bir yanıt yazın

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