إنشاء BasketController لإدارة الـ Customer Basket

1. إنشاء الـ Controller

هنبدأ بإنشاء Controller جديد داخل مشروع الـ API، ونسميه BasketController. الهدف من الـ Controller هو توفير Endpoints للتعامل مع الـ BasketRepository اللي أنشأناه في الخطوات السابقة.

الكود:

public class BasketController : BaseApiController
{
    private readonly IBasketRepository _basketRepository;
 
    public BasketController(IBasketRepository basketRepository)
    {
        _basketRepository = basketRepository;
    }
 
    // Endpoints هنا هنضيف الـ
}

2. إعداد الـ Dependency Injection

هنقوم بتسجيل الـ BasketRepository في الـ DI Container في ملف Program.cs:

الكود:

// Register IBasketRepository with its implementation
builder.Services.AddScoped<IBasketRepository, BasketRepository>();

ملاحظات:

  • استخدمنا Scoped لأن الـ BasketRepository مش محتاج يكون موجود طول عمر التطبيق، بعكس الـ Redis Connection اللي لازم يكون Singleton.

3. إنشاء Endpoints

3.1. Get Basket

  • يسترجع بيانات سلة المشتريات بناءً على الـ id.
  • لو السلة غير موجودة (Expired أو مفيش بيانات)، يتم إنشاء سلة جديدة بنفس الـ id.
الكود:
[HttpGet("{id}")] // GET : /api/basket/id
public async Task<ActionResult<CustomerBasket>> GetBasket(string id)
{
    var basket = await _basketRepository.GetBasketAsync(id);
    return Ok(basket ?? new CustomerBasket(id)); // لو السلة مش موجودة، يتم إنشاء سلة جديدة فارغة
}
 
// Constructor for creating a new CustomerBasket
// Make Also Empty Ctor
public CustomerBasket(string id)
{
    Id = id;
    Items = new List<BasketItem>(); // قائمة فارغة
}
ملاحظة:
  • يمكن استخدام Query Parameter بدلًا من Segment:

    [HttpGet] // GET: /api/basket?id=1

3.2. Update Basket

  • يسمح بإنشاء أو تحديث سلة المشتريات.
  • لو حصل خطأ أثناء العملية، يتم إرجاع استجابة خطأ (400 Bad Request).
الكود:
[HttpPost] // POST: /api/basket
public async Task<ActionResult<CustomerBasket>> UpdateBasket(CustomerBasket basket)
{
    var createdOrUpdatedBasket = await _basketRepository.UpdateBasketAsync(basket);
 
    if (createdOrUpdatedBasket == null)
        return BadRequest(new ApiResponse(400)); // خطأ في العملية
 
    return Ok(createdOrUpdatedBasket);
}

3.3. Delete Basket

  • يقوم بحذف سلة المشتريات بناءً على الـ id.
الكود:
[HttpDelete] // DELETE: /api/basket?id=1
public async Task<ActionResult> DeleteBasket(string id)
{
    await _basketRepository.DeleteBasketAsync(id);
    return NoContent(); // 204 No Content
}

4. الكود النهائي لـ BasketController

public class BasketController : BaseApiController
{
    private readonly IBasketRepository _basketRepository;
 
    public BasketController(IBasketRepository basketRepository)
    {
        _basketRepository = basketRepository;
    }
 
    [HttpGet("{id}")] // GET : /api/basket/id
    public async Task<ActionResult<CustomerBasket>> GetBasket(string id)
    {
        var basket = await _basketRepository.GetBasketAsync(id);
        return Ok(basket ?? new CustomerBasket(id));
    }
 
    [HttpPost] // POST: /api/basket
    public async Task<ActionResult<CustomerBasket>> UpdateBasket(CustomerBasket basket)
    {
        var createdOrUpdatedBasket = await _basketRepository.UpdateBasketAsync(basket);
 
        if (createdOrUpdatedBasket == null)
            return BadRequest(new ApiResponse(400));
 
        return Ok(createdOrUpdatedBasket);
    }
 
    [HttpDelete] // DELETE: /api/basket?id=1
    public async Task<ActionResult> DeleteBasket(string id)
    {
        await _basketRepository.DeleteBasketAsync(id);
        return NoContent();
    }
}

5. النقاط المهمة في التصميم

  1. Singleton vs Scoped:
    • الـ Redis Connection تم تسجيله كـ Singleton لأنه يتم استخدامه في أكثر من مكان ويحتاج للبقاء مفتوحًا طوال عمر التطبيق.
    • أما الـ BasketRepository تم تسجيله كـ Scoped لأنه خاص بكل طلب HTTP على حدة.
  2. استجابة السلة الجديدة:
    • في حالة عدم وجود سلة، يتم إنشاء سلة جديدة وإرجاعها مباشرةً للعميل.
  3. التعامل مع الأخطاء:
    • تم إضافة استجابة 400 Bad Request عند حدوث أي مشكلة أثناء تحديث السلة.
  4. مرونة في التصميم:
    • يمكن استخدام Query Parameters بدلًا من Route Segments حسب الحاجة.