نسخه‌بندی و مستندسازی APIها با Swagger

نسخه‌بندی و مستندسازی APIها با Swagger

نسخه‌بندی و مستندسازی APIها با Swagger

منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti

اعتبار ترجمه: ترجمه با کمک هوش مصنوعی

تصویر منبع — صفحهٔ 188Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 188.

نسخه‌بندی و مستندسازی APIها

در بخش پیش دیدیم که چگونه CORS را پیکربندی کنیم. شکل ۴-۴۶ نمونه‌ای از شکست درخواست را نشان می‌دهد؛ زیرا Origin درخواست در فهرست مبدأهای مجاز قرار ندارد.

شکل ۴-۴۶ — شکست CORS به‌دلیل مجاز نبودن Origin درخواست

API Versioning (نسخه‌بندی API)

وقتی API در طول زمان تکامل پیدا می‌کند، ممکن است قراردادها، مدل‌ها یا رفتارهای آن تغییر کنند. نسخه‌بندی کمک می‌کند چند نسخه از یک API هم‌زمان در دسترس بمانند تا مصرف‌کنندگان قدیمی مجبور نباشند فوراً به قرارداد جدید مهاجرت کنند.

سه روش رایج برای اعلام نسخه عبارت‌اند از:

  • فرستادن نسخه در Header، برای مثال api-version: 2.
  • قرار دادن نسخه در مسیر، برای مثال /v2/countries.
  • قرار دادن نسخه در Query String، برای مثال /countries?api-version=2.

روش Query String از نظر نویسنده انتخاب مطلوبی نیست. نسخه‌بندی در مسیر بسیار آشکار و قابل‌فهم است و نسخه‌بندی در Header نیز URL را تمیز نگه می‌دارد. در ادامه هر دو رویکرد بررسی می‌شوند.

نسخه‌بندی با Header

برای این کار بستهٔ NuGet با نام Asp.Versioning.Http را به پروژه اضافه کنید. سپس سرویس نسخه‌بندی را مانند Listing 4-40 ثبت کنید.

builder.Services.AddApiVersioning(options =>
    {
        options.DefaultApiVersion = new ApiVersion(1, 0);
        options.ReportApiVersions = true;
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.ApiVersionReader = new HeaderApiVersionReader("api-version");
    });

    var app = builder.Build();
Listing 4-40 — پیکربندی API Versioning با Header

DefaultApiVersion نسخهٔ پیش‌فرض را تعیین می‌کند. ReportApiVersions باعث می‌شود نسخه‌های پشتیبانی‌شده و منسوخ‌شده در Headerهای پاسخ گزارش شوند. با AssumeDefaultVersionWhenUnspecified اگر مصرف‌کننده نسخه‌ای نفرستد، نسخهٔ پیش‌فرض در نظر گرفته می‌شود. در پایان، HeaderApiVersionReader مشخص می‌کند نسخه از Header با نام api-version خوانده شود.

اکنون یک مجموعه نسخه (Version Set) برای نسخه‌های ۱ و ۲ ایجاد می‌کنیم.

var versionSet = app.NewApiVersionSet()
                        .HasApiVersion(1.0)
                        .HasApiVersion(2.0)
                        .Build();
Listing 4-41 — ایجاد Version Set

سپس Endpointها را به نسخه‌های مشخص نگاشت می‌کنیم. یک Endpoint فقط نسخهٔ ۱، یکی نسخهٔ ۲، یک Endpoint فقط نسخهٔ ۲ و نمونهٔ آخر بدون وابستگی به نسخه است.

app.MapGet("/version", () => "Hello version 1")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(1.0);

    app.MapGet("/version", () => "Hello version 2")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(2.0);

    app.MapGet("/version2only", () => "Hello version 2 only")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(2.0);

    app.MapGet("/versionneutral", () => "Hello neutral version")
       .WithApiVersionSet(versionSet)
       .IsApiVersionNeutral();
Listing 4-42 — نگاشت Endpointها به نسخه‌ها

درخواست به /version با Header نسخهٔ ۱ پاسخ نسخهٔ ۱ را می‌گیرد و همان URL با نسخهٔ ۲ پاسخ نسخهٔ ۲ را برمی‌گرداند. اگر نسخه ارسال نشود، به‌دلیل تنظیمات قبلی نسخهٔ پیش‌فرض ۱ اعمال می‌شود. Endpoint خنثی نسبت به نسخه، مستقل از مقدار نسخه قابل فراخوانی است.

شکل ۴-۴۷ — فراخوانی نسخهٔ ۱
شکل ۴-۴۸ — پاسخ Endpoint نسخهٔ ۱
شکل ۴-۴۹ — فراخوانی نسخهٔ ۲
شکل ۴-۵۰ — پاسخ Endpoint نسخهٔ ۲
شکل ۴-۵۱ — Endpoint فقط مخصوص نسخهٔ ۲
شکل ۴-۵۲ — فراخوانی Endpoint خنثی از نسخه

اگر نسخه‌ای مانند ۳ درخواست شود که در مجموعه نسخه‌ها تعریف نشده است، چارچوب درخواست را رد می‌کند و پاسخ 400 Bad Request برمی‌گرداند.

شکل ۴-۵۳ — درخواست نسخهٔ پشتیبانی‌نشده
شکل ۴-۵۴ — پاسخ خطا برای نسخهٔ پشتیبانی‌نشده
تصویر منبع — صفحهٔ 198Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 198.

نسخه‌بندی با Route

راه دیگر آن است که نسخه جزئی از مسیر باشد. در این حالت می‌توان Route Groupهای جداگانه برای هر نسخه ساخت و Endpointهای هر نسخه را در گروه مربوط قرار داد.

public static class VersionGroup
    {
        public static RouteGroupBuilder GroupVersion1(this RouteGroupBuilder group)
        {
            group.MapGet("/version", () => "Hello version 1");
            return group;
        }

        public static RouteGroupBuilder GroupVersion2(this RouteGroupBuilder group)
        {
            group.MapGet("/version", () => "Hello version 2");
            group.MapGet("/version2only", () => "Hello version 2 only");
            return group;
        }
    }
Listing 4-43 — گروه‌بندی Endpointها بر اساس نسخه

در Program.cs کافی است هر گروه را زیر Prefix نسخهٔ خودش نگاشت کنید.

app.MapGroup("/v1").GroupVersion1();
    app.MapGroup("/v2").GroupVersion2();

    app.Run();
Listing 4-44 — نگاشت Route Groupهای نسخهٔ ۱ و ۲

اکنون URLهایی مانند /v1/version و /v2/version نسخه را به‌وضوح در خود مسیر نشان می‌دهند. نویسنده این روش را ترجیح می‌دهد؛ زیرا درخواست نسخه‌ای که Route آن وجود ندارد به‌طور طبیعی با 404 Not Found پاسخ داده می‌شود، نه خطای نسخه‌بندی ۴۰۰.

شکل ۴-۵۵ — فراخوانی نسخهٔ ۱ از مسیر
شکل ۴-۵۶ — فراخوانی نسخهٔ ۲ از مسیر
تصویر منبع — صفحهٔ 201Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 201.

Documentation (مستندسازی)

یک REST API خوب فقط باید درست کار کند؛ مصرف‌کننده باید بتواند قرارداد، ورودی‌ها، خروجی‌ها، نسخه‌ها و وضعیت‌های پاسخ آن را نیز بفهمد. در اکوسیستم ASP.NET Core، OpenAPI و Swagger ابزارهای اصلی این کار هستند.

برای نمونهٔ این بخش بسته‌های زیر استفاده می‌شوند:

  • Asp.Versioning.Http
  • Asp.Versioning.Mvc.ApiExplorer
  • Microsoft.AspNetCore.OpenApi
  • Swashbuckle.AspNetCore

پیکربندی Swagger برای چند نسخه

کلاس زیر تنظیمات Swagger را بر اساس نسخه‌هایی که ApiExplorer گزارش می‌کند ایجاد می‌کند.

using Asp.Versioning.ApiExplorer;
    using Microsoft.Extensions.Options;
    using Microsoft.OpenApi.Models;
    using Swashbuckle.AspNetCore.SwaggerGen;

    namespace AspNetCore8MinimalApis.Swagger;

    public class SwaggerConfigurationsOptions : IConfigureOptions<SwaggerGenOptions>
    {
        private readonly IApiVersionDescriptionProvider _provider;

        public SwaggerConfigurationsOptions(IApiVersionDescriptionProvider provider)
        {
            _provider = provider;
        }

        public void Configure(SwaggerGenOptions options)
        {
            foreach (var description in _provider.ApiVersionDescriptions)
            {
                options.SwaggerDoc(description.GroupName, new OpenApiInfo
                {
                    Title = "Minimal APIs",
                    Version = description.ApiVersion.ToString()
                });
            }
        }
    }
Listing 4-45 — تنظیم Swagger برای نسخه‌های API

سپس سرویس‌های ApiExplorer، نسخه‌بندی و Swagger را در Program.cs ثبت می‌کنیم و Swagger UI را برای دو سند نسخه پیکربندی می‌کنیم.

using Asp.Versioning;
    using Asp.Versioning.Conventions;
    using AspNetCore8MinimalApis.Swagger;
    using Swashbuckle.AspNetCore.SwaggerGen;
    using Microsoft.Extensions.Options;

    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddEndpointsApiExplorer();

    builder.Services.AddApiVersioning(options =>
    {
        options.DefaultApiVersion = new ApiVersion(1, 0);
        options.ReportApiVersions = true;
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.ApiVersionReader = new HeaderApiVersionReader("api-version");
    })
    .AddApiExplorer(options =>
    {
        options.GroupNameFormat = "'v'VV";
    });

    builder.Services.AddSwaggerGen();
    builder.Services.AddSingleton<IConfigureOptions<SwaggerGenOptions>,
        SwaggerConfigurationsOptions>();

    var app = builder.Build();

    app.UseSwagger().UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint($"/swagger/v1.0/swagger.json", "Version 1.0");
        c.SwaggerEndpoint($"/swagger/v2.0/swagger.json", "Version 2.0");
    });

    var versionSet = app.NewApiVersionSet()
                        .HasApiVersion(1.0)
                        .HasApiVersion(2.0)
                        .Build();

    app.MapGet("/version", () => "Hello version 1")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(1.0);

    app.MapGet("/version", () => "Hello version 2")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(2.0);

    app.MapGet("/version2only", () => "Hello version 2 only")
       .WithApiVersionSet(versionSet)
       .MapToApiVersion(2.0);

    app.MapGet("/versionneutral", () => "Hello neutral version")
       .WithApiVersionSet(versionSet)
       .IsApiVersionNeutral();

    app.Run();
Listing 4-46 — پیکربندی کامل API Versioning و Swagger

AddEndpointsApiExplorer اطلاعات Endpointهای Minimal API را در اختیار OpenAPI قرار می‌دهد. AddSwaggerGen مولد سند Swagger را ثبت می‌کند و AddApiExplorer نسخه‌ها را برای ApiExplorer قابل مشاهده می‌سازد. کلاس تنظیمات نیز یک سند جداگانه برای هر نسخه ایجاد می‌کند.

UseSwagger JSONهای OpenAPI را منتشر می‌کند و UseSwaggerUI رابط کاربری Swagger را فعال می‌کند. آدرس پیش‌فرض رابط کاربری /swagger/index.html است. در نسخهٔ Preview مورد استفاده هنگام نگارش کتاب، به‌دلیل یک مشکل موقت، Endpointهای نسخه در UI به‌صورت صریح تنظیم شده‌اند.

شکل ۴-۵۷ — صفحهٔ Swagger UI برای نسخهٔ ۱
شکل ۴-۵۸ — صفحهٔ Swagger UI برای نسخهٔ ۲
شکل ۴-۵۹ — جزئیات یک Endpoint در Swagger
شکل ۴-۶۰ — نمایش خودکار Header نسخه در Swagger
تصویر منبع — صفحهٔ 210Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 210.

افزودن توضیحات و Commentها

Swagger می‌تواند Commentهای XML را در مستندات نمایش دهد. برای Lambdaهای Endpoint، مستندسازی XML به همان شکلی که برای متدهای معمولی وجود دارد در دسترس نیست، اما می‌توان انواع پیچیدهٔ ورودی را مستند کرد. توضیحات سرویس‌هایی که از DI به Endpoint تزریق می‌شوند نیز نباید به‌عنوان پارامتر عمومی API نمایش داده شوند.

ابتدا Extension زیر مسیر فایل XML تولیدشده برای اسمبلی را پیدا کرده و آن را به Swagger معرفی می‌کند.

using Swashbuckle.AspNetCore.SwaggerGen;
    using System.Reflection;

    namespace AspNetCore8MinimalApis.Swagger;

    public static class SwaggerXmlComments
    {
        public static void AddXmlComments(this SwaggerGenOptions options)
        {
            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            options.IncludeXmlComments(xmlPath);
        }
    }
Listing 4-47 — افزودن XML Comments به Swagger

در فایل پروژه نیز تولید فایل Documentation XML را فعال و هشدار ۱۵۹۱ را غیرفعال کنید.

<PropertyGroup>
          <GenerateDocumentationFile>true</GenerateDocumentationFile>
          <NoWarn>$(NoWarn);1591</NoWarn>
    </PropertyGroup>
Listing 4-48 — فعال‌سازی فایل مستندات XML

سپس Extension را هنگام ثبت Swagger فراخوانی کنید.

builder.Services.AddSwaggerGen(options =>
    {
        options.AddXmlComments();
    });
Listing 4-49 — ثبت XML Comments در SwaggerGen

برای مثال، Endpoint ایجاد کشور یک مدل Country می‌پذیرد و نتیجه را از سرویس مربوط دریافت می‌کند. مدل را می‌توان با Commentهای XML مستند کرد تا توضیح Propertyها در OpenAPI ظاهر شود.

app.MapPost("/countries", async (Country country,
                                     ICountryService countryService) =>
    {
        var result = await countryService.AddCountry(country);
        return Results.Created($"/countries/{result.Id}", result);
    });
Listing 4-50 — Endpoint نمونه برای نمایش مستندسازی مدل ورودی
public class Country
    {
        /// <summary>
        /// Country identifier
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Country name
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Country description
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Country flag URI
        /// </summary>
        public string FlagUri { get; set; }
    }
Listing 4-51 — مستندسازی Propertyهای Country با XML Comment
شکل ۴-۶۱ — نمایش توضیحات مدل Country در Swagger UI

Swagger Annotations

روش دیگر استفاده از بستهٔ Swashbuckle.AspNetCore.Annotations است. پس از نصب بسته، Annotationها را فعال کنید.

builder.Services.AddSwaggerGen(options =>
    {
        options.EnableAnnotations();
        options.AddXmlComments();
    });
Listing 4-52 — فعال‌سازی Swagger Annotations

اکنون می‌توان با SwaggerOperation خلاصه و توضیح Endpoint را مشخص کرد.

app.MapGet("/versionneutral",
        [SwaggerOperation(Summary = "Neutral version",
                          Description = "This version is neutral")] ()
            => "Hello neutral version")
       .WithApiVersionSet(versionSet)
       .IsApiVersionNeutral();
Listing 4-53 — مستندسازی Endpoint با SwaggerOperation
شکل ۴-۶۲ — نتیجهٔ SwaggerOperation در Swagger UI

راه دیگر، استفاده مستقیم از WithOpenApi است.

app.MapGet("/versionneutral",() => "Hello neutral version")
       .WithApiVersionSet(versionSet)
       .IsApiVersionNeutral()
       .WithOpenApi(operation => new(operation)
       {
           Summary = "This is a summary",
           Description = "This is a description"
       });
Listing 4-54 — سفارشی‌سازی OpenAPI با WithOpenApi

گروه‌بندی Endpointها با Tag

برای خواناتر شدن رابط Swagger، می‌توان Endpointهای هر Route Group را با یک Tag مشترک گروه‌بندی کرد.

app.MapGroup("/v1").GroupVersion1().WithTags("V1");
    app.MapGroup("/v2").GroupVersion2().WithTags("V2");
Listing 4-55 — گروه‌بندی نسخه‌ها با Tag
شکل ۴-۶۳ — گروه‌های V1 و V2 در Swagger UI

سفارشی‌سازی‌های دیگر Swagger

می‌توان Endpointی را از مستندات پنهان کرد، منسوخ بودن آن را نشان داد و پاسخ‌های ممکن آن را صریحاً توصیف کرد.

برای حذف یک Endpoint یا گروه از OpenAPI از ExcludeFromDescription استفاده کنید.

app.MapGroup("/v1")
       .GroupVersion1()
       .WithTags("V1")
       .ExcludeFromDescription();
Listing 4-56 — حذف Endpointها از توضیحات OpenAPI
شکل ۴-۶۴ — Swagger UI پس از حذف گروه از مستندات

برای اعلام منسوخ‌شدن یک عملیات، Property مربوط به Deprecated را در OpenAPI تنظیم کنید.

.WithOpenApi(operation => new(operation)
    {
        Deprecated = true
    });
Listing 4-57 — علامت‌گذاری Endpoint به‌عنوان Deprecated
شکل ۴-۶۵ — نمایش Endpoint منسوخ در Swagger UI
تصویر منبع — صفحهٔ 221Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 221.

در نهایت، وضعیت‌های پاسخ مهم را با Produces مستند کنید. نمونهٔ زیر Endpoint دانلود فایل را با پاسخ موفق، پیدا نشدن، خطای داخلی و Timeout توصیف می‌کند.

app.MapGet("/countries/download", (ICountryService countryService) =>
    {
        (byte[] fileContent, string mimeType, string fileName) =
            countryService.GetFile();

        if (fileContent is null || mimeType is null)
            return Results.NotFound();

        return Results.File(fileContent, mimeType, fileName);
    })
    .Produces<Stream>(StatusCodes.Status200OK, "video/mp4")
    .Produces(StatusCodes.Status404NotFound)
    .Produces(StatusCodes.Status500InternalServerError)
    .Produces(StatusCodes.Status408RequestTimeout);
Listing 4-58 — توصیف پاسخ‌های Endpoint با Produces
شکل ۴-۶۶ — پاسخ‌های مستندشده در Swagger UI
تصویر منبع — صفحهٔ 223Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 223.

جمع‌بندی فصل

در این فصل حداقل دانش لازم برای ساخت REST APIهای تمیز با Minimal APIs در ASP.NET Core مرور شد: مسیریابی، اتصال پارامتر، اعتبارسنجی، نگاشت شیء، عملیات CRUD، وضعیت‌های HTTP، کار با فایل و Streaming، CORS، نسخه‌بندی و مستندسازی با OpenAPI و Swagger. فصل بعد به قابلیت‌های پیشرفته‌تر Minimal API می‌پردازد.

تصاویر منبع مرتبط با این بخش

تصویر منبع — صفحهٔ 192Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 192.
تصویر منبع — صفحهٔ 193Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 193.
تصویر منبع — صفحهٔ 194Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 194.
تصویر منبع — صفحهٔ 195Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 195.
تصویر منبع — صفحهٔ 196Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 196.
تصویر منبع — صفحهٔ 197Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 197.
تصویر منبع — صفحهٔ 197Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 197.
تصویر منبع — صفحهٔ 200Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 200.
تصویر منبع — صفحهٔ 208Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 208.
تصویر منبع — صفحهٔ 208Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 208.
تصویر منبع — صفحهٔ 209Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 209.
تصویر منبع — صفحهٔ 214Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 214.
تصویر منبع — صفحهٔ 216Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 216.
تصویر منبع — صفحهٔ 218Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 218.
تصویر منبع — صفحهٔ 220Visual واقعی استخراج‌شده از PDF، مربوط به صفحهٔ 220.

منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti.

فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620