מימוש Dependency Injection

נראה את מימוש Dependency Injection על הפרוייקט לדוגמא.

בפונקציית ה-get ב-controller יש לנו coupling.

קובץ ProductsController.cs

[HttpGet]
public ActionResult<List<Product>> Get() {
    ProductsRepository repo = new ProductsRepository();
    var result = repo.GetAllProducts();
    return Ok(result);
}

יצירת ה-Interface

כדי להשתמש ב-GetAllProducts צריך להכיר את ProductsRepository וזה כרגע מייצר את הבעיה. כדי לפתור את זה ניצור תיקייה שנקרא לה Contracts. בתוכה נוסיף item שהוא interface שנקרא לו IProductsRepository.

בתוך IProductsRepository נשים את חתימת הפונקציה שאנחנו צריכים.

קובץ IProductsRepository.cs

public interface IProductsRepository
{
    public List<Product> GetAllProducts();
}

כהערה, אנחנו נרצה אחר כך פונקציה שמחזירה Task.

מימוש ה-Interface

השלב הבא יהיה לחזור ל-ProductsRepository ולהגיד שהוא מימוש של IProductsRepository.

public class ProductsRepository : IProductsRepository
{
...
}

שימוש ב-dependency injection

עכשיו נחזור ל-controller, ובמקום להשתמש ב-new, נזריק את ה-interface ב-constructor.

קובץ ProductsController.cs

private readonly IProductsRepository _productRepository;
public ProductsController(
    IProductsRepository productRepository,
    IOptionsSnapshot<HeadersRemoveConfig> options) {
    _productRepository = productRepository;
    this._headerConfig = options.Value;
}

מה שנשאר זה להשתמש בזה בפונקציות ב-class.

public ActionResult<List<Product>> Get() {
    var result = _productRepository.GetAllProducts();
    return Ok(result);
}

עכשיו יש בעיה, שלא אמרנו באיזה מימוש להשתמש במקרה ויש כמה מימושים ל-class. כרגע רק קישרנו ל-interface. נקבל שגיאת 500 בהרצה.

את ההגדרה הזאת אנחנו נעשה ב-program. יש את החלק עד ל-build שזה התפקיד שלו. שם יש תפקיד של resolve DI. צריך להגיד מה ה-implementation והאם זה singleton.

קובץ Program.cs

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Resolve DI
builder.Services.AddSingleton<IProductsRepository, ProductsRepository>();

var app = builder.Build();

מכניסים ל-AddSingleton את ה-interface ואת המימוש. הוא מייצר אותו פעם אחת.

בצורה כזאת אם רוצים לשנות את המימוש כותבים ProductsRepository חדש ומפנים אליו.

הגדרות DI

יש 3 אפשרויות לרישום DI בקובץ ה-program. האפשרויות: AddSinglton, AddTransient, AddScoped.

AddSinglton – כאשר מישהו מבקש להוסיף interface מדובר על אותו instance שכולם יקבלו אותו. האובייקט נוצר בפעם הראשונה שמישהו מבקש אותו. אם יש כמה שרתים, יכול להיווצר לי Instance בכל אחד מהם ואז אם יש לי data שאמור להיות עבור כל המשתמשים, לא באמת לכולם תהיה גישה אליו.

במקרה כזה עובדים עם שרת מידעי ששומר מידע עבור כל השרתים, למשל Redis. כמעט ולא משתמשים עם AddSinglton. משתמשים בזה רק כשלוקח המון זמן כשעולה instance של class ואז אנחנו לא רוצים לבזבז כל כך הרבה זמן על היצירה של ה-class כל פעם.

AddTransient – כל פעם כשמישהו מבקש class יוצרים לו instance חדש.

AddScoped – יהיה instance אחד פר בקשה. בפעם הראשונה שמישהו מבקש את ה-class הוא יווצר, ואחר כך בכל בקשה יהיה שימוש באותו ה-instance. ההמלצה למרבית ה-services היא להיות מוגדרים scoped.