ASP.NET Core 6/Web Servisleri/Web Servislerini Belgeleme ve Araştırma

Bu bölümde yazdığımız web servislerinin üçüncü parti yazılımlar tarafından nasıl kullanılacağını belirteceğiz. Web servislerimiz için yazdığımız bu dokümantasyonlara gelişticiler programatik olarak erişebilecek. Servislerimizi belgelemek için OpenAPI'yı kullanacağız.

Action metot çakışmasını giderme

değiştir

OpenAPI'ın düzgün çalışabilmesi için action metotlarımızın karşılık verdiği path ve HTTP metodu bakımından çakışma olmaması gerekmektedir. Bir önceki dersteki ContentController sınıfındaki SaveProductJson() ve SaveProductXml() action'larının ikisi de POST request'i üzerinden tetiklenmekte ve ikisi de "api/content" path'ına karşılık vermektedir. ASP.NET Core yönünden bir çakışma yoktur. Çünkü birisi JSON taleplerine, diğeri XML taleplerine cevap vermektedir ve bu ayrım Consumes attribute'u üzerinden yapılmaktadır. Ancak OpenAPI'ın attribute seviyesinde ayrım yapma imkanı yoktur. OpenAPI'ın tek baktığı path ve HTTP metodudur. Bu bakımdan bir önceki dersteki ContentController sınıfındaki SaveProductXml() action'ını silelim.

Swashbuckle paletinin yüklenmesi ve yapılandırılması

değiştir

Swashbuckle paketi OpenAPI standardının bir ASP.NET Core implementasyonudur. Şimdi NuGet Paket Yöneticisini kullanarak Swashbuckle.AspNetCore paketinin 6.2.3 versiyonunu kuralım. Daha sonra Program.cs dosyasını aşağıdaki gibi değiştirelim:

using Microsoft.EntityFrameworkCore;
using WebApp.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DataContext>(opts => {
    opts.UseSqlServer(builder.Configuration["ConnectionStrings:ProductConnection"]);
    opts.EnableSensitiveDataLogging(true);
});
builder.Services.AddControllers().AddNewtonsoftJson().AddXmlDataContractSerializerFormatters();
builder.Services.Configure<MvcNewtonsoftJsonOptions>(opts => {
    opts.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
builder.Services.Configure<MvcOptions>(opts => {
    opts.RespectBrowserAcceptHeader = true;
    opts.ReturnHttpNotAcceptable = true;
});
builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp", Version = "v1" }));
var app = builder.Build();
app.MapControllers();
app.MapGet("/", () => "Hello World!");
app.UseSwagger();
app.UseSwaggerUI(options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApp"));
var context = app.Services.CreateScope().ServiceProvider.GetRequiredService<DataContext>();
SeedData.SeedDatabase(context);
app.Run();

Kodumuza eklediğimiz bu servis ve middleware çağrıları ile programımıza bazı yetenekler kazandırdık. İlk olarak programı çalıştırdıktan sonra http://localhost:5000/swagger/v1/swagger.json adresine talepte bulunduğunuzda uygulamanızdaki bütün action'lar, bu action'ların hangi path'a karşılık verdiği, parametreleri, vs. hakkında detaylı bilgi alabilirsiniz. Aldığınız bu bilgiler JSON formatındadır ve pek de human-readable değildir.

İkinci olarak da http://localhost:5000/swagger adresine talepte bulunabilirsiniz. Bu adreste de yine bir önceki linkteki aynı bilgiler vardır, ama daha okunaklıdır.

API açıklamasına ince ayar yapma

değiştir

Swagger, otomatik olarak uygulamamızın desteklediği path'ları, HTTP metotlarını, her bir action'ın hangi HTTP durum kodlarını döndürebileceğini, vs. belirtmektedir. Ancak sağladığı verilerde bazen hata yapabilmektedir. Örneğin ProductsController sınıfındaki GetProduct() action'ının kaynak koduna bakalım:

[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(long id)
{
    Product p = await context.Products.FindAsync(id);
    if (p == null)
    {
        return NotFound();
    }
    return Ok(p);
}

Kodumuzda gördüğümüz gibi GetProduct() action'ı hem HTTP 404, hem de HTTP 200 kodunu döndürebilmektedir. Ancak Swagger üzerinden bakarsanız bu action'ın sadece 200 kodunu döndürebileceğini söylediğini görürsünüz. Elbette ki Swagger kodumuzu satır satır incelemez, bazen kolaycılığa kaçar, veya tahminlerde bulunur. Bir üçüncü parti yazılım geliştiricisi için bu kötü bir şeydir. Yazdığı programı HTTP 404 hatalarıyla baş edebilecek şekilde yazması gerekirken Swagger'ın verdiği bilgilere güvenerek bu ihtimalin önlemini almayabilir.

API analizcisini çalıştırma

değiştir

API analizcisi web servisimizi inceler ve servisimizdeki olası sorunları -biraz önce bahsettiğimiz sorunu dahil- ortaya koyar. Şimdi proje dosyanızı şöyle değiştirin:

<Project Sdk="Microsoft.NET.Sdk.Web">
	<PropertyGroup>
		<TargetFramework>net6.0</TargetFramework>
		<Nullable>enable</Nullable>
		<ImplicitUsings>enable</ImplicitUsings>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
		<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
			<IncludeAssets>
				runtime; build; native; contentfiles; analyzers; buildtransitive
			</IncludeAssets>
			<PrivateAssets>all</PrivateAssets>
		</PackageReference>
		<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
		<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
	</ItemGroup>
	<PropertyGroup>
		<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
	</PropertyGroup>
</Project>

Bu XML tabanlı dosyadaki biri hariç bütün içerik ASP.NET Core tarafından otomatik oluşturulmuştur. Bizim tek eklediğimiz <PropertyGroup> etiketi ve içindeki <IncludeOpenAPIAnalyzers> etiketidir. Proje dosyanızı böyle değiştirdikten sonra artık ASP.NET Core GetProduct() action'ındaki return NotFound(); satırının altını yeşil ile çizecek ve action'ın döndürebileceği response kodlarının bariz olmadığını söyleyecek. Bu sorunun üstesinden gelmek için GetProduct() action'ını şöyle değiştirebilirsiniz:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetProduct(long id)
{
    Product p = await context.Products.FindAsync(id);
    if (p == null)
    {
        return NotFound();
    }
    return Ok(p);
}

Örnekte de anlaşıldığı gibi ProducesResponseType attribute'u bir action'ın geri dönrüebileceği HTTP kodlarını belirtmek için kullanılabilir. Artık Swagger doğru bir şekilde ilgili action metodumuzun hangi HTTP kodlarını döndürebileceğini servisimizi kullanacak geliştiriciye söyleyebilecek.