Bu yazımda “Minimal API nedir?” sorusunu cevaplamaya çalışacağım. Geleneksel Web API’a göre Minimal API’ın ne gibi avantajları olduğunu göstermeye çalışacağım. Paylaştığım kodlar ile sıfırdan bir Minimal API oluşturmayı göstereceğim. Yazının içerik kısmı aşağıdaki gibidir.
- Minimal API nedir?
- Minimal API avantajları nelerdir?
- Minimal API örnekleri
- Minimal API için Model oluşturulması
- Minimal API için CRUD Endpoint’lerin oluşturulması
- AutoMapper kullanımı
- Validator kullanımı
Bu yazının devamı olarak daha sonra paylaşacağım yazılarımın içeriği ise aşağıdaki konu başlıklarını içerecek.
- Database ve Repository kullanımı (CRUD)
- Minimal API’yi nasıl organize edebilirim?
Minimal API nedir?
Minimal API’lar geleneksel Web API oluşturmak için gerekli kod miktarını en aza indirmek ve basitleştirmek için ortaya çıkan bir teknolojidir. Anlaşılabilir ve temiz sözdizimi ile daha hızlı Web API geliştirmeyi ve kolay müdahale edilmesini sağlayan özelliklerdir.
Minimal API avantajları nelerdir?
- Kolay ve anlaşılır söz dizimi → En dikkat çeken avantajlardan birisidir. Geleneksel Web API’a göre çok daha kolay ve anlaşılır bir sözdizimine sahiptir.
- Geliştirilmesi ve müdahale edilmesi kolay → Yukarıda bahsedilen kolay ve anlaşılır sözdizimi sayesinde geliştirme yapması ve müdahale edilmesi geleneksel Web API’a göre daha kolay bir hal alıyor.
- Daha az proje dosyası → Daha az proje dosyası ile proje yapısının karmaşık olmasını engelleyerek basit bir proje yapısı sunuyor.
- Daha iyi performans → Geleneksel API’lara göre daha az ek yüke sahip olması, daha yüksek performans sunmasını ve daha az kaynak tüketmesini sağlar.
- Yeni başlayanlar için kolay → Yukarıda belirtilen avantajlar yeni geliştiricilerin Web API’a hızlı adapte olmasını ve kavramayı kolaylaştıracaktır.
Minimal API örnekleri
Proje oluşturulmasını ve proje yapısını VS 2022 üzerinden göstermeye çalışacağım.
- İlk olarak ASP.NET Core Web API seçimini yaparak yeni proje oluşturmaya başlıyoruz.
Web API projesi oluşturmak için seçilmesi gereken şablon
- Proje ismini ve projenin bulunacağı konumu belirliyoruz.
- Sonraki adımda seçenekler kısmında Minimal API kullanmak için “Use controllers” seçeneğinin tikini kaldırıyoruz.
Minimal API tercihi: “Use controllers” seçeneğinin tiki kaldırılır
- Program.cs dosyasının içerisine bakıldığında ne kadar yalın ve basit bir söz dizimi olduğu ilk bakışta anlaşılmaktadır.
Klasik Hello World API
VS 2022’nin otomatik oluşturduğu örnekleri temizleyerek aşağıdaki hale getirerek klasik Hello World API’ını inceleyelim.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapGet("/", () => "Hello World from GET!");
app.MapPost("/", () => "Hello World from POST!");
app.Run();
MapGetmetodu HTTP GET isteklerini tutar, Arrow Function çalışır, gerekli işlemler yapılır ve yanıt geri döndürülür.MapPostmetodu da benzer şekilde HTTP POST isteklerini tutar; Arrow Function çalışır, gerekli işlemler yapılır ve yanıt döner.
Router Parameters
Router parameters kullanımı aşağıdaki şekildedir.
//...
app.MapGet("/users/{id:int}/films/{filmId:int}", (int id, int filmId) =>
{
return Results.Ok($"User Id: {id} and Film Id: {filmId}");
});
//...
- Örnekte görüldüğü gibi
idvefilmIdfonksiyonda bu şekilde kullanılır. - Parametrelerin tipi,
MapGetmetodunun ilk parametresindeki string pattern kısmında{id:int}şeklinde belirtilebilir. - Return type aşağıdaki gibi değiştirilebilir. Aşağıda user id
0olarak gönderilirseBadRequesttipinde yanıt gönderilebilir.
//...
app.MapGet("/users/{id:int}/films/{filmId:int}", (int id, int filmId) =>
{
if(id == 0)
{
return Results.BadRequest("Wrong user id");
}
return Results.Ok($"User Id: {id} and Film Id: {filmId}");
});
//...
Minimal API için Model oluşturulması
Projeye “Models” adında bir klasör eklenerek, bu klasör altına Film.cs dosyası eklenir ve bir Film class’ı oluşturulur.
namespace MinimalAPI_Examples.Models
{
public class Film
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public double Imdb { get; set; }
public DateTime? Created { get; set; }
public DateTime? LastUpdated { get; set; }
}
}
Models klasörü içerisine eklenen Film.cs
Yukarıdaki gibi Film modeli oluşturulur ve veri tabanını temsilen şimdilik bir FilmStore oluşturalım. “Data” adında bir klasör oluşturalım ve bu klasör altına FilmStore.cs dosyası eklenerek bir FilmStore class’ı oluşturulur.
using MinimalAPI_Examples.Models;
namespace MinimalAPI_Examples.Data
{
public class FilmStore
{
public static List<Film> filmList = new List<Film>
{
new Film{Id=1, Name="The Shawshank Redemption", Imdb=9.3},
new Film{Id=2, Name="The Godfather", Imdb=9.2},
new Film{Id=3, Name="The Dark Knight", Imdb=9.0}
};
}
}
Data klasörü içerisine eklenen FilmStore.cs
Minimal API için CRUD Endpoint’lerin oluşturulması
Tekrar Program.cs dosyasının içerisine dönüyoruz ve örnekleri yazdığımız kısmı temizleyerek aşağıdaki metodları ekliyoruz. WithName metodu kullanımını da aşağıda görebiliriz.
Get All
// Get All Films
app.MapGet("/api/films", () =>
{
return Results.Ok(FilmStore.filmList);
}).WithName("GetFilms");
Get Film By Id
// Get Film By Id
app.MapGet("/api/films/{id}", (int id) =>
{
return Results.Ok(FilmStore.filmList.FirstOrDefault(x => x.Id == id));
}).WithName("GetFilm");
Create Film
CreatedAtRoute metodu kullanılarak oluşturulan film tekrar sorgulanarak yanıt olarak dönülür.
// Create Film
app.MapPost("/api/films", ([FromBody] Film film) =>
{
// Yeni eklenen filmin uygun olup olmadigi kontrol edilir.
if (film.Id != 0 || string.IsNullOrEmpty(film.Name))
{
return Results.BadRequest("Invalid id or Film name");
}
// Yeni eklenen film listede var mi diye kontrol edilir.
if (FilmStore.filmList.FirstOrDefault(x => x.Name == film.Name) != null)
{
return Results.BadRequest("Film name already exists");
}
// Yeni eklenen filmin Id'si hesaplanir.
film.Id = FilmStore.filmList.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1;
FilmStore.filmList.Add(film);
return Results.CreatedAtRoute("GetFilm", new { id = film.Id }, film);
}).WithName("CreateFilm");
DTO’ların oluşturulması
DTO’ları oluşturmak için Models klasörü altına “DTO” adında bir klasör oluşturalım ve içine FilmCreateDTO.cs dosyasını oluşturalım. Bu model, filmi yaratmak için gerekli bilgilerin istekle gönderilmesini sağlayacak.
namespace MinimalAPI_Examples.Models.DTO
{
public class FilmCreateDTO
{
public string Name { get; set; } = null!;
public double Imdb { get; set; }
}
}
Models altında oluşturulan DTO klasörü ve FilmCreateDTO.cs
Aynı şekilde Film modelini temsil eden FilmDTO class’ını oluşturalım.
namespace MinimalAPI_Examples.Models.DTO
{
public class FilmDTO
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public double Imdb { get; set; }
public DateTime? Created { get; set; }
}
}
DTO klasörü içerisine eklenen FilmDTO.cs
Oluşturduğumuz DTO’ları, yazdığımız endpoint’leri güncelleyerek aşağıdaki gibi kullanabiliriz.
// Create Film
app.MapPost("/api/films", ([FromBody] FilmCreateDTO filmCreateDTO) =>
{
if (string.IsNullOrEmpty(filmCreateDTO.Name))
{
return Results.BadRequest("Invalid id or Film name");
}
if (FilmStore.filmList.FirstOrDefault(x => x.Name == filmCreateDTO.Name) != null)
{
return Results.BadRequest("Film name already exists");
}
Film film = new()
{
Name = filmCreateDTO.Name,
Imdb = filmCreateDTO.Imdb,
};
film.Id = FilmStore.filmList.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1;
FilmStore.filmList.Add(film);
FilmDTO filmDTO = new()
{
Id = film.Id,
Name = film.Name,
Imdb = film.Imdb,
Created = film.Created,
};
return Results.CreatedAtRoute("GetFilm", new { id = film.Id }, filmDTO);
}).WithName("CreateFilm");
AutoMapper’ın eklenmesi
Yukarıdaki örneklerde yeni Film modeli ya da FilmDTO oluşturduk. Bunu elle yazmak yerine AutoMapper kullanabiliriz. İlk olarak AutoMapper ve AutoMapper.Extensions.Microsoft.DependencyInjection paketleri NuGet Package Manager ile projeye eklenir.
AutoMapper paketlerinin NuGet Package Manager’da görünümü
Projeye MappingConfig.cs dosyası eklenir ve aşağıdaki gibi MappingConfig class’ı oluşturulur.
using AutoMapper;
using MinimalAPI_Examples.Models;
using MinimalAPI_Examples.Models.DTO;
namespace MinimalAPI_Examples
{
public class MappingConfig : Profile
{
public MappingConfig()
{
CreateMap<Film, FilmDTO>().ReverseMap();
CreateMap<Film, FilmCreateDTO>().ReverseMap();
}
}
}
Projeye eklenen MappingConfig.cs
Program.cs içerisinde servislerin eklendiği kısımda AutoMapper servisi eklenir.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAutoMapper(typeof(MappingConfig)); // Eklenen satır.
Aşağıdaki gibi create endpoint fonksiyonuna IMapper eklenerek Film, FilmDTO ve FilmCreateDTO birbirlerine maplenir ve daha kısa bir biçimde yeni model oluşturma işlemleri yapılır.
// Create Film
app.MapPost("/api/films", (IMapper _mapper, [FromBody] FilmCreateDTO filmCreateDTO) =>
{
if (string.IsNullOrEmpty(filmCreateDTO.Name))
{
return Results.BadRequest("Invalid id or Film name");
}
if (FilmStore.filmList.FirstOrDefault(x => x.Name == filmCreateDTO.Name) != null)
{
return Results.BadRequest("Film name already exists");
}
Film film = _mapper.Map<Film>(filmCreateDTO);
film.Id = FilmStore.filmList.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1;
FilmStore.filmList.Add(film);
FilmDTO filmDTO = _mapper.Map<FilmDTO>(film);
return Results.CreatedAtRoute("GetFilm", new { id = film.Id }, filmDTO);
}).WithName("CreateFilm");
Validator eklenmesi
İlk olarak NuGet Package Manager ile FluentValidation ve FluentValidation.DependencyInjectionExtensions paketlerini projeye ekliyoruz.
FluentValidation paketlerinin NuGet Package Manager’da görünümü
Projeye Validations adında bir klasör oluşturarak FilmCreateValidation.cs dosyasını ekliyoruz. FilmCreateValidation class’ını oluşturarak validasyon kurallarımızı aşağıdaki gibi içine yazıyoruz.
using FluentValidation;
using MinimalAPI_Examples.Models.DTO;
namespace MinimalAPI_Examples.Validations
{
public class FilmCreateValidation : AbstractValidator<FilmCreateDTO>
{
public FilmCreateValidation()
{
RuleFor(model => model.Name).NotEmpty();
RuleFor(model => model.Imdb).InclusiveBetween(0, 10);
}
}
}
Projeye eklenen Validations klasörü ve FilmCreateValidation.cs
Yazdığımız kurallar ile film create isteğinde Name kısmının boş olması veya Imdb değerinin [0, 10] arasında olmaması durumunda validasyon hatası oluşacaktır. Program.cs içerisine validation servisini ekliyoruz.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAutoMapper(typeof(MappingConfig));
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
Aşağıdaki gibi fonksiyona eklemeler yapılarak create endpoint’in validasyon özelliğini kullanması ve hataların yanıt olarak dönülmesi sağlanabilir.
// Create Film
app.MapPost("/api/films", async (IValidator<FilmCreateDTO> _validator, IMapper _mapper, [FromBody] FilmCreateDTO filmCreateDTO) =>
{
var validationResult = await _validator.ValidateAsync(filmCreateDTO);
if (!validationResult.IsValid)
{
return Results.BadRequest(validationResult.Errors.FirstOrDefault().ToString());
}
if (FilmStore.filmList.FirstOrDefault(x => x.Name == filmCreateDTO.Name) != null)
{
return Results.BadRequest("Film name already exists");
}
Film film = _mapper.Map<Film>(filmCreateDTO);
film.Id = FilmStore.filmList.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1;
FilmStore.filmList.Add(film);
FilmDTO filmDTO = _mapper.Map<FilmDTO>(film);
return Results.CreatedAtRoute("GetFilm", new { id = film.Id }, filmDTO);
}).WithName("CreateFilm");
Put (Update) ve Delete endpoint’lerinin eklenmesi
İlk olarak daha önce yaptığımız gibi FilmUpdateDTO’yu ve FilmUpdateValidator’ı projeye ekliyoruz.
namespace MinimalAPI_Examples.Models.DTO
{
public class FilmUpdateDTO
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public double Imdb { get; set; }
}
}
using FluentValidation;
using MinimalAPI_Examples.Models.DTO;
namespace MinimalAPI_Examples.Validations
{
public class FilmUpdateValidator : AbstractValidator<FilmUpdateDTO>
{
public FilmUpdateValidator()
{
RuleFor(model => model.Id).NotEmpty().GreaterThan(0);
RuleFor(model => model.Name).NotEmpty();
RuleFor(model => model.Imdb).InclusiveBetween(0, 10);
}
}
}
Tüm DTO ve Validation dosyaları eklendikten sonra projenin son dosya yapısı
Daha sonra Update ve Delete endpoint fonksiyonları oluşturulur.
// Update Film
app.MapPut("/api/films", async (IMapper _mapper, IValidator<FilmUpdateDTO> _validator, [FromBody] FilmUpdateDTO filmUpdateDTO) =>
{
var validationResult = await _validator.ValidateAsync(filmUpdateDTO);
if (!validationResult.IsValid)
{
return Results.BadRequest(validationResult.Errors.FirstOrDefault().ToString());
}
Film filmFromStore = FilmStore.filmList.FirstOrDefault(u => u.Id == filmUpdateDTO.Id);
filmFromStore.Name = filmUpdateDTO.Name;
filmFromStore.Imdb = filmUpdateDTO.Imdb;
filmFromStore.LastUpdated = DateTime.Now;
FilmDTO filmDTO = _mapper.Map<FilmDTO>(filmFromStore);
return Results.Ok(filmDTO);
}).WithName("UpdateFilm");
// Delete Film
app.MapDelete("/api/films/{id}", (int id) =>
{
Film filmFromStore = FilmStore.filmList.FirstOrDefault(u => u.Id == id);
if (filmFromStore != null)
{
FilmStore.filmList.Remove(filmFromStore);
return Results.Ok();
}
else
{
return Results.BadRequest("Invalid Id");
}
}).WithName("DeleteFilm");
Son Söz
Bu yazıda nasıl Minimal API oluşturacağımızı, AutoMapper ve Validator kullanımını, yanıt türlerini sahte veriler kullanarak göstermeye çalıştım. Bir sonraki yazıda Database ve Repository eklemeyi, Repository kullanarak nasıl CRUD işlemlerinin gerçekleştirilebileceğini ve Minimal API’yi nasıl organize edebileceğimizi göstermeye çalışacağım.
Vaktinizi ayırıp okuduğunuz için teşekkür ederim. Bir sonraki yazımda görüşmek dileğiyle.