سناریوی ساختن یک کیک را در نظر بگیرید:

  • اصافه کردن مواد کیک
  • اضافه کردن شیر
  • اضافه کردن تخم مرغ
  • مخلوط کردن مواد کیک
  • اماده کردن فر
  • پختن کیک
  • کیک اماده است

در این مطلب میخواهیم یک سناریوی شبیه به پختن کیک را به صورت Short polling  پیاده سازی کنیم. روش معمول روش Long polling است که در آن کاربر به سایت یک درخواست ارسال میکند و منتظر میماند تا تمامی مراحل انجام شوند و سپس کیک را دریافت کند. در طول این درخواست کاربر از وضعیت کیک خبر ندارد و فقط میتواند صبر کند تا کیک آماده شود. اما در روش Short polling کاربر هر چند ثانیه یکبار به سرور درخواست ارسال میکند تا  بفهمد وضعیت کیکش در چه حالتی است. ساختار روش Short polling به این صورت است که کاربر ابتدا یک درخواست به سایت ارسال میکند برای ایجاد کیک و فورا یک توکن دریافت میکند سپس در دفعات بعدی کاربر باید توکن را به سایت ارسال کند تا وضعیت کیک را بررسی کند.

مزایا:

  1. کاربر از وضعیت درخواست خود با خبر است.

معایب:

  1. تعداد درخواست های ارسالی به سرور افزایش می یابد.
  2. پیچیدگی این روش مقداری بیشتر است

برای پیاده سازی این سناریو یک پروژه از نوع Asp.Net Core 5 ایجاد کنید. سپس یک کلاس به نام CakeState ایجاد کنید برای وضعیت کیک به صورت زیر:

public class CakeState
{
    public string Token { get; set; }
    public string State { get; set; }
    public bool IsFinish { get; set; }
}

سپس یک کلاس برای ذخیره سازی دیتاها (برای تست یک کلاس static در نظر گرفته شده است) :

public static class CakeStateDate
{
    private static readonly List<CakeState> CakeStates = new List<CakeState>();
    public static List<CakeState> GetCakeStatus(string token)
    {
        return CakeStates.Where(a => a.Token == token).ToList();
    }
    public static void AddCakeState(CakeState cakeState)
    {
        Console.WriteLine(cakeState.State);
        CakeStates.Add(cakeState);
    }
}

سرویس ساخت کیک:

public interface IMakeCakeService
{
    Task<CakeState> MakeCakeLongPolling();
    Task MakeCakeShortPolling(string token);
    List<CakeState> GetCakeState(string token);
}
public class MakeCakeService : IMakeCakeService
{
    public List<CakeState> GetCakeState(string token)
    {
        return CakeStateDate.GetCakeStatus(token);
    }

    public async Task<CakeState> MakeCakeLongPolling()
    {
        await Task.Delay(2000);
        Console.WriteLine("Added cake mix");
        await Task.Delay(3000);
        Console.WriteLine("Added milk");
        await Task.Delay(1000);
        Console.WriteLine("Added eggs");
        await Task.Delay(1000);
        Console.WriteLine("Cake ingredients mixed");
        await Task.Delay(6000);
        Console.WriteLine("Oven is ready");
        Console.WriteLine("Baking cake...");
        await Task.Delay(2000);
        Console.WriteLine("Cake is ready");
        return new CakeState { IsFinish = true, State = "Cake is ready" };
    }
    public async Task MakeCakeShortPolling(string token)
    {
        await Task.Delay(2000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Added cake mix",
            Token = token
        });
        await Task.Delay(3000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Added milk",
            Token = token
        });
        await Task.Delay(3000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Added eggs",
            Token = token
        });
        await Task.Delay(1000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Cake ingredients mixed",
            Token = token
        });
        await Task.Delay(1000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Oven is ready",
            Token = token
        });
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = false,
            State = "Baking cake...",
            Token = token
        });
        await Task.Delay(6000);
        CakeStateDate.AddCakeState(new CakeState
        {
            IsFinish = true,
            State = "Cake is ready",
            Token = token
        });
    }
}

و درنهایت کنترلر مربوط به کیک :

[Route("api/[controller]")]
[ApiController]
public class CakeController : ControllerBase
{
    private readonly IMakeCakeService _makeCakeService;

    public CakeController(IMakeCakeService makeCakeService)
    {
        _makeCakeService = makeCakeService;
    }
    [HttpPost("MakeCake")]
    public IActionResult MakeCake()
    {
        var token = TokenGenerator.GenerateString(50);
        BackgroundJob.Enqueue(() => _makeCakeService.MakeCakeShortPolling(token).GetAwaiter().GetResult());
        return Ok(token);
    }
    [HttpGet("CheckState/{token}")]
    public IActionResult CakeState(string token)
    {
        return Ok(_makeCakeService.GetCakeState(token));
    }
    [HttpPost("MakeCakeLongPolling")]
    public async Task<IActionResult> MakeCakeLongPolling()
    {
        return Ok(await _makeCakeService.MakeCakeLongPolling());
    }
}

برای ساخت توکن ها یک کلاس به نام TokenGenerator ایجاد کرده ایم:

public class TokenGenerator
{
    private const string AllowableCharacters = "abcdefghijklmnopqrstuvwxyz0123456789";
    public static string GenerateString(int length)
    {
        var bytes = new byte[length];

        using (var random = RandomNumberGenerator.Create())
        {
            random.GetBytes(bytes);
        }
        return new string(bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length]).ToArray());
    }
}

زمانی که کاربر یک درخواست برای کیک از نوع Short polling ارسال میکند متد MakeCake فراخوانی میشود در کنترلر و یک توکن ایجاد میشود برای ریکوست و سپس از طریق Hangfire کار ساخت کیک به صورت پس زمینه اجرا میشود با استفاده از BackgroundJob.Enqueue و بلافاصله توکن به کاربر ارسال میشود. سپس متد MakeCakeShortPolling شروع به ساختن کیک میکند به صورت پس زمینه و هر زمان که کاری را تمام میکند یک نمونه از کلاس CakeState را درون کلاس CakeStateData ذخیره میکند با این کار هر بار که کاربر API وضعیت کیک را فراخوانی میکند تمامی مراحل اجرا شده کیک را دریافت میکند. سپس کاربر باید هر چند ثانیه یکبار API مربوط به دریافت وضعیت کیک (CakeState) را فراخوانی کند تا آخرین وضعیت کیک را دریافت کند و تا زمانی که مقدار فیلد IsFinish برابر true نباشد باید این کار را تکرار کند. متد GetCakeState یک توکن دریافت میکند و رکوردهای ذخیره شده درون کلاس CakeStateData را که با توکن ارسال شده کاربر برابر هستند را دریافت میکند و به کاربر نمایش میدهد.
نمونه رکوست ساخت کیک:

POST
/api/Cake/MakeCake

ریسپانس :

zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb

نمونه اولین درخواست دریافت وضعیت کیک:

GET
/api/Cake/CheckState/zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb

نمونه ریسپانس اولین درخواست :

[
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added cake mix",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added milk",
    "isFinish": false
  }
]

نمونه ریسپانس درخواست دوم :

[
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added cake mix",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added milk",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added eggs",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Cake ingredients mixed",
    "isFinish": false
  }
]

آخرین ریسپانس :

[
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added cake mix",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added milk",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Added eggs",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Cake ingredients mixed",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Oven is ready",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Baking cake...",
    "isFinish": false
  },
  {
    "token": "zlgmghaikpuq230zogmkh6ipz21c1j1c24hdvd0dfk067ltzwb",
    "state": "Cake is ready",
    "isFinish": true
  }
]

کدهای این مقاله را میتوانید از گیت هاب دانلود کنید ;)

Powered by Froala Editor