היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 14 Mar 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173440)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). Generic Host ב-.NET — התשתית ל-DI, Configuration ולוגים. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173440 https://comcomponent.com/he/blog/dotnet-generic-host-what-is/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173440
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173441
כשמתחילים לכתוב אפליקציית console או worker ב-.NET, בהתחלה מסתדרים עם קצת קוד ב-Main. ברגע שהאפליקציה גדלה קצת, בדרך כלל מצטברים כמה דברים:
- רוצים לקרוא את
appsettings.json - רוצים לדרוס ערכים עם environment variables
- רוצים לכתוב לוגים עם
ILogger - לא רוצים ש-construction של services יהיה מלא ב-
new - רוצים להריץ loop ברקע
- רוצים לצאת נקי עם
Ctrl+Cאו stop של service
כאן נכנס Generic Host. גם השם הזה נוטה להתבלבל.
- מה ההבדל בין
Host.CreateApplicationBuilderל-Host.CreateDefaultBuilder - האם
IHostזהה ל-DI container - מה הקשר ל-
BackgroundService - האם זה משהו נפרד מ-
WebApplicationBuilderשל ASP.NET Core - האם יש ערך להשתמש בו גם באפליקציית console
כשהדברים האלה מתערבבים, Generic Host נראה לפעמים כ”משהו שמיועד לאפליקציות Web”, ולפעמים כ”משהו שצריך לעטוף בו הכול”. שתי התפיסות קצת רשלניות.
במאמר הזה, בהנחה של הפרקטיקה הנוכחית שמתמקדת בעיקר ב-.NET 6 ואילך, נסגור קודם ארבעה דברים:
- מה Generic Host באמת
- מה הוא מטפל בו יחד
- הקשר בין
Host.CreateApplicationBuilder,Host.CreateDefaultBuilderו-WebApplication.CreateBuilder - מאיפה נוח להתחיל
תוכן עניינים
- קודם המסקנה (במשפט אחד)
- 1.1. קודם סוגרים מינוח
- הטבלה שכדאי לראות ראשונה
- 2.1. מה Generic Host מחזיק
- 2.2. ההבדל בין ה-builders
- 2.3. למה יש כמה נקודות כניסה
- התמונה הכוללת של Generic Host (תרשים)
- מה מרוויחים מ-Generic Host
- 4.1. אפשר לרכז את קוד ה-startup במקום אחד
- 4.2. DI / Configuration / לוגים מחוברים מההתחלה
- 4.3. קל לטפל ב-graceful shutdown ובתהליך long-running
- הרכב מינימלי
- 5.1. דוגמה מינימלית לאפליקציית console
- 5.2.
appsettings.json - 5.3. הוספת
BackgroundService
- תבניות טיפוסיות
- 6.1. כלי console קצר-חיים
- 6.2. worker / background service
- 6.3. גם מתחת ל-ASP.NET Core
- מקרים שמתאימים
- מקרים שלא מתאימים / overhead
- מלכודות נפוצות
- סיכום
- מקורות
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 16, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
1. קודם המסקנה (במשפט אחד)
- Generic Host הוא תשתית שמטפלת יחד בstartup וב-lifetime של אפליקציית .NET.
- בפנים נכנסים DI, Configuration, לוגים,
IHostedService/BackgroundService, וטיפול ב-stop של האפליקציה. - באפליקציה חדשה שאינה Web, טבעי להתחיל מ-
Host.CreateApplicationBuilder(args). - גם
WebApplicationBuilderשל ASP.NET Core אינו עולם נפרד — זה entry point שמרחיב את אותה תפיסת host לצרכי Web. - כלומר Generic Host אינו סיפור של DI container לבדו, אלא מנגנון שמאחד את נקודת ההרכבה של האפליקציה עם ניהול ה-lifetime.
בקיצור, מרגע שהאפליקציה עוברת קצת מעבר ל”קורא arguments, מדפיס פעם אחת ומסיים”, Generic Host מתחיל להשפיע משמעותית. מהצד השני, לא חייבים להכניס אותו בכל פעם גם לכלי קטן שעוד לא הגיע לשלב הזה.
flowchart TB
accTitle: הטווח שבו Generic Host מתחיל להשפיע
accDescr: תרשים שמראה שכלי קטן שרק מדפיס פעם אחת ומסיים לא צריך להכניס בכל פעם, ואילו אפליקציה שעברה מעבר לכך מרוויחה הרבה מ-Generic Host.
small1["כלי שמדפיס פעם אחת ומסיים"] -.->|"לא מכניסים בכל פעם"| ghost0["Generic Host"]
grown1["אפליקציה שעברה מעבר לכך"] -->|"משפיע משמעותית"| ghost0
איור 1: מרגע שהאפליקציה עוברת קצת מעבר ל”מדפיס פעם אחת ומסיים”, Generic Host מתחיל להשפיע.
1.1. קודם סוגרים מינוח
המאמר הזה ישתמש בהמשך לא מעט במטפורות, אז קודם נניח כאן ניסוח מדויק.
| מונח | הניסוח המדויק | המטפורה במאמר |
|---|---|---|
| DI (dependency injection) | דרך בנייה שבה מחלקה לא עושה new בעצמה לצדדים שהיא צריכה, אלא מקבלת אותם מבחוץ. המקום שבו רושמים מראש את מה שיימסר הוא DI container (IServiceProvider), ול-Generic Host יש אותו מההתחלה |
wiring |
Builder (HostApplicationBuilder) |
אובייקט להרכבת ה-host. יש לו properties כמו Services, Configuration ו-Logging, ואליהם רושמים דברים. עד שקוראים ל-Build() האפליקציה לא רצה |
שולחן הרכבה |
Host (IHost) |
גוף האפליקציה שכבר הורכב, שמתקבל כתוצאה מ-Build(). הוא מחזיק את ה-DI container, את ה-Configuration, את הלוגים ואת ה-hosted services, ומטפל בהם מ-Run() / RunAsync() עד ל-stop |
תשתית |
Hosted service (IHostedService / BackgroundService) |
מיכל לעבודה שרצה לפי ה-start וה-stop של ה-host. כשה-host עולה נקרא StartAsync, וב-BackgroundService רץ ExecuteAsync |
עבודה long-running |
| Lifetime | ניהול מה-start עד ה-stop של האפליקציה. בתגובה ל-signals כמו Ctrl+C, SIGTERM או stop של service, מיישרים את אופן הסיום |
lifetime |
הבלבול הנפוץ ביותר הוא בין Builder ל-Host. ה-Builder הוא הצד שמרכיב, וה-Host הוא התוצאה שכבר הורכבה, ו-Build() הוא הגבול ביניהם. ברגע שזה ברור, גם הניסוחים “תשתית”, “קופסה”, “entry point” ו”נקודת כניסה” שיופיעו בהמשך קלים לקריאה בלי לערבב מה הם מצביעים עליו.
flowchart TB
accTitle: הגבול בין Builder ל-Host
accDescr: תרשים שמראה ש-Builder הוא הצד שמרכיב ו-IHost הוא התוצאה שכבר הורכבה, וקריאת Build היא הגבול ביניהם.
bld1["Builder (הצד שמרכיב)"] -->|"Build()"| hst1["IHost (התוצאה שכבר הורכבה)"]
hst1 --> life1["Run / RunAsync מה-start עד ה-stop"]
איור 2: Builder הוא הצד שמרכיב ו-IHost הוא התוצאה שכבר הורכבה, ו-Build() הוא הגבול ביניהם.
אם DI חדש לכם, כדאי לחשוב כך: במקום לכתוב בעצמכם שרשרת של new, רושמים ב-startup “כשיהיה צורך בסוג הזה, תמסרו את ה-implementation הזה”, והצד המקבל רק מקבל אותו כ-constructor argument. מקום הרישום הזה הוא builder.Services.
2. הטבלה שכדאי לראות ראשונה
2.1. מה Generic Host מחזיק
קודם כל, כדאי לפרק את תוכן הקופסה הזו.
| רכיב | מה Generic Host מטפל בו | מה יוצא מזה |
|---|---|---|
| DI | מרכיב services מתוך IServiceCollection |
קל יותר לצמצם שרשרת של new |
| Configuration | מאחד appsettings.json, environment variables ו-command-line arguments |
קל יותר להתמודד עם הבדלים בין סביבות |
| Logging | בונה תשתית לשימוש ב-ILogger<T> |
קל יותר להחליף אחר כך את יעד פלט הלוגים |
| Hosted service | מטפל ב-start וב-stop של IHostedService / BackgroundService |
קל יותר להפריד עבודה ברקע מגוף האפליקציה |
| Lifetime | מטפל ב-start וב-stop דרך IHostApplicationLifetime, IHostEnvironment וכדומה |
קל יותר ליישר את אופן הסיום עם Ctrl+C, SIGTERM או stop של service |
חשוב כאן להבין ש-Generic Host אינו “עטיפת DI נוחה אחת בלבד”. בפועל, ההשקפה שהכי לא מטעה היא: קופסה שעושה wiring יחד לכל סביבת נקודת הכניסה של האפליקציה.
2.2. ההבדל בין ה-builders
גם כאן מהיר יותר לראות הכול על טבלה אחת מראש.
| נקודת כניסה | שימוש עיקרי | סגנון כתיבה | הבחירה הראשונה |
|---|---|---|---|
Host.CreateApplicationBuilder(args) |
אפליקציה חדשה שאינה Web — console / worker וכדומה | כותבים ישירות אל builder.Services / builder.Configuration / builder.Logging |
לאפליקציה חדשה — זה |
Host.CreateDefaultBuilder(args) |
קוד קיים או הרכבה שמבוססת בעיקר על extension methods ישנים | משרשרים קריאות כמו ConfigureServices |
אם יש נכסים קיימים — זה |
WebApplication.CreateBuilder(args) |
אפליקציית Web / API של ASP.NET Core | נקודת כניסה שמוסיפה ל-Generic Host נוחות ייעודית ל-Web | לאפליקציית Web — זה |
CreateApplicationBuilder ו-CreateDefaultBuilder אינם מקרה שבו אחד הוא feature חדש והשני משהו נפרד.
לשניהם אותה פונקציונליות ליבה ואותה התנהגות default. ההבדל העיקרי הוא סגנון הכתיבה.
באפליקציה חדשה שאינה Web, כיום טבעי להתחיל מ-Host.CreateApplicationBuilder(args). כדאי לחשוב על WebApplication.CreateBuilder(args) כעל אותו זרם, מורחב לצרכי Web.
flowchart TB
accTitle: הקשר בין שלוש נקודות הכניסה
accDescr: תרשים שמראה ש-CreateApplicationBuilder ו-CreateDefaultBuilder חולקים אותה פונקציונליות ליבה ואותה התנהגות default אך נבדלים בסגנון הכתיבה, ו-WebApplication.CreateBuilder הוא נקודת כניסה שמרחיבה את אותו זרם לצרכי Web.
appb1["CreateApplicationBuilder"] --> core1["אותה פונקציונליות ליבה והתנהגות default"]
defb1["CreateDefaultBuilder"] --> core1
appb1 -.-> sty1["סגנון כתיבה ישיר"]
defb1 -.-> sty2["סגנון שרשור"]
core1 -.->|"נקודת כניסה מורחבת ל-Web"| webb1["WebApplication.CreateBuilder"]
איור 3: שני ה-builders חולקים אותה פונקציונליות ליבה ואותה התנהגות default ונבדלים רק בסגנון הכתיבה, ולצורכי Web יש נקודת כניסה שמרחיבה את אותו זרם.
2.3. למה יש כמה נקודות כניסה
יש כמה נקודות כניסה כי צד ה-Web וצד שאינו Web גדלו בנפרד ואז התמזגו.
- במקור, ל-ASP.NET Core היה Web Host ייעודי ל-Web (
IWebHostBuilder), ו-Generic Host (IHostBuilder) לאפליקציות שאינן Web הוכן בנפרד. - לאחר מכן ASP.NET Core עבר להתבסס על Generic Host, כך שגם Web וגם לא-Web יושבים על אותה תפיסת host.
- בנוסף, לצד סגנון הכתיבה שמחבר callbacks בשרשרת (
ConfigureServicesוכדומה), התווספה נקודת כניסה של כתיבה ישירה אל properties (builder.Servicesוכדומה).Host.CreateApplicationBuilderו-WebApplication.CreateBuilderשייכים לצד הזה.
בתיעוד הרשמי הנוכחי, קבוצת Host.CreateApplicationBuilder (IHostApplicationBuilder) מוגדרת כמיועדת לפרויקטים חדשים, וברירת המחדל של התבניות הנוכחיות, וקבוצת Host.CreateDefaultBuilder (IHostBuilder) מוגדרת כהדרך המסורתית שנשארת לצורך תאימות עם קוד קיים. גם ההסבר ששניהם חולקים אותה פונקציונליות ליבה ואותה התנהגות default כתוב במפורש.
אם מגיעים מקוד מתקופת .NET Framework או .NET Core 3.1, יש תחושה של “למה יש שתי דרכי כתיבה”. אבל קל יותר להשלים עם זה כשמבינים שזה לא שני דברים נפרדים חדש-ישן, אלא נקודות כניסה שהתרבו בתהליך ההתמזגות. אם אין סיבה להתאים לנכסים קיימים, אפשר בהחלט להשתמש ב-Host.CreateApplicationBuilder באפליקציה חדשה.
flowchart TB
accTitle: הרקע לריבוי נקודות הכניסה
accDescr: תרשים שמראה ש-Web Host הייעודי ל-Web ו-Generic Host לאפליקציות שאינן Web היו נפרדים, ש-ASP.NET Core התמזג לתוך Generic Host, ושבנוסף התווספה נקודת כניסה של כתיבה ישירה אל properties.
wh1["Web Host (IWebHostBuilder)"] --> mg1["ASP.NET Core מתמזג עם Generic Host"]
gh1["Generic Host (IHostBuilder)"] --> mg1
mg1 --> ad1["נוספת נקודת כניסה של כתיבה ישירה"]
ad1 -.-> ex1["CreateApplicationBuilder ו-WebApplication.CreateBuilder"]
איור 4: Web Host ו-Generic Host שגדלו בנפרד התמזגו, ובתהליך הזה התרבו נקודות כניסה בסגנון הכתיבה הישיר.
3. התמונה הכוללת של Generic Host (תרשים)
בקווים כלליים, התמונה הכוללת נראית כך:
flowchart LR
accTitle: התמונה הכוללת של Generic Host
accDescr: תרשים שמראה כיצד args, environment variables ו-appsettings.json זורמים אל ה-builder, איך רושמים Configuration, services ולוגים, ואיך Build מוביל ל-IHost שמופעל דרך Run או RunAsync ומחובר ל-lifetime ול-hosted service.
args1["args / environment variables / appsettings.json"] --> builder1["Host.CreateApplicationBuilder(args)"]
builder1 --> config1["builder.Configuration"]
builder1 --> services1["builder.Services"]
builder1 --> logging1["builder.Logging"]
services1 --> hosted1["IHostedService / BackgroundService"]
builder1 --> build1["builder.Build()"]
build1 --> host1["IHost"]
host1 --> run1["Run / RunAsync"]
run1 --> lifetime1["start · stop · Ctrl+C · SIGTERM"]
lifetime1 --> hosted1
איור 5: רושמים Configuration, services ולוגים אל ה-builder, מריצים את ה-IHost שמתקבל מ-Build() דרך Run/RunAsync, וה-lifetime מתחבר עד ל-start ול-stop של ה-hosted service.
בדרך כלל יוצרים את ה-builder ב-Program.cs, מוסיפים services אל builder.Services, מכווננים לפי הצורך את builder.Configuration ואת builder.Logging, ולבסוף קוראים ל-Build() כדי לקבל IHost ומריצים אותו עם Run() / RunAsync().
מה שקצת חבוי אבל משמעותי הוא שכבר בשלב Host.CreateApplicationBuilder(args) הרבה דברים כבר טעונים. כברירת מחדל נכללים למשל אלה:
- content root הוא הספרייה הנוכחית
- Configuration של ה-host היא environment variables עם הקידומת
DOTNET_ו-command-line arguments - Configuration של האפליקציה היא
appsettings.json,appsettings.{Environment}.json, user secrets בסביבת Development, environment variables ו-command-line arguments - הלוגים הם Console / Debug / EventSource / EventLog (Windows בלבד)
- בסביבת
Developmentיש scope validation ו-dependency validation
כלומר לא עושים wiring מאפס בלי לחשוב, אלא מונחת מההתחלה “תשתית שמספיקה יפה לשימוש רגיל”.
flowchart TB
accTitle: ברירות המחדל שכבר טעונות מההתחלה
accDescr: תרשים שמראה שכבר בשלב CreateApplicationBuilder טעונות host configuration, app configuration ולוגים בברירת מחדל, ושתשתית שמספיקה לשימוש רגיל מונחת מההתחלה.
ent1["CreateApplicationBuilder(args)"] --> dz1["host configuration (משתני DOTNET_ ו-arguments)"]
ent1 --> dz2["app configuration (appsettings.json ועוד)"]
ent1 --> dz3["לוגים בברירת מחדל (Console ועוד)"]
dz1 --> rdy1["תשתית שמספיקה לשימוש רגיל"]
dz2 --> rdy1
dz3 --> rdy1
איור 6: כבר בשלב יצירת ה-builder נטענות ברירות מחדל של Configuration ולוגים, ולא עושים wiring להכול מאפס.
4. מה מרוויחים מ-Generic Host
4.1. אפשר לרכז את קוד ה-startup במקום אחד
ההשפעה הכי שקטה והכי גדולה של Generic Host היא שקוד ה-startup של האפליקציה פחות נוטה להתפזר.
כשאפליקציה גדלה קצת, אלה הדברים שמצטברים סביב Main:
- טעינת קבצי Configuration
- החלפה בין סביבות
- אתחול ה-logger
- הרכבת
HttpClient, repository או service - הפעלת עבודה ברקע
- cleanup ב-termination signal
אם מחברים את כל זה ידנית בלי host, בהתחלה זה קל, אבל בהמשך נקודת הכניסה נהיית דביקה יותר ויותר.
כשמשתמשים ב-Generic Host, Program.cs נהיה בבירור “המקום שבו מרכיבים יחד את התלויות”. רק הסידור הזה משנה משמעותית עד כמה קל לעשות code review.
flowchart TB
accTitle: קוד ה-startup מתרכז במקום אחד
accDescr: תרשים שמראה ש-wiring ידני בלי host גורם לנקודת הכניסה להיות דביקה בהמשך, ואילו שימוש ב-Generic Host הופך את Program.cs למקום ברור שבו מרכיבים את התלויות.
no1["wiring ידני בלי host"] --> st1["נקודת הכניסה נהיית דביקה בהמשך"]
yes1["שימוש ב-Generic Host"] --> pg1["Program.cs נהיה מקום הרכבה"]
pg1 -.-> rv1["קל יותר לעשות code review"]
איור 7: ב-wiring ידני נקודת הכניסה נעשית דביקה בהמשך, אבל כשמרכזים אל ה-host, Program.cs נהיה בבירור מקום ההרכבה.
4.2. DI / Configuration / לוגים מחוברים מההתחלה
כשמשתמשים ב-Generic Host, DI, Configuration ולוגים יושבים מההתחלה על אותה תשתית.
לדוגמה, בצד המחלקה אפשר לקבל בפשטות דברים כמו אלה:
ILogger<T>IConfigurationIHostEnvironmentIOptions<T>
מה שמשמעותי כאן הוא שדרך קריאת ה-Configuration ודרך בניית ה-services לא נוטות להיות שני סגנונות נפרדים.
אם יש הגדרה אחת או שתיים, אפשר להסתדר גם עם קריאה ישירה של IConfiguration["Section:Key"]. אבל כשההגדרות גדלות בעבודה מעשית, בטוח יותר לאגד אותן לפי section למחלקה עם IOptions<T>. קו הגבול הוא בערך כשמספר מחרוזות המפתח עובר 5. בהיקף הזה, טעויות הקלדה מתחילות להופיע ככשלים שלא מתגלים עד runtime, וגם קשה יותר לעקוב היכן נקרא כל מפתח.
באופן דומה, גם בלוגים, במקום לבנות ידנית ILoggerFactory בכל מקום, עדיף להזריק ILogger<T> למחלקה הרלוונטית — כך קל יותר לעקוב.
מה ש-Generic Host נוח בו הוא שהוא לא הופך את הדברים האלה לנושאים נפרדים, אלא מאפשר לטפל בהם יחד כתשתית אחת של כל האפליקציה.
flowchart TB
accTitle: איך מתפתחת דרך קריאת ה-Configuration
accDescr: תרשים שמראה שאם יש הגדרה אחת או שתיים אפשר להסתדר עם קריאה ישירה של IConfiguration, אך משמספר מחרוזות המפתח עובר 5 בטוח יותר לאגד אותן לפי section עם IOptions.
few1["הגדרה אחת או שתיים"] --> dr1["קריאה ישירה של IConfiguration"]
many1["יותר מ-5 מחרוזות מפתח"] --> op1["איגוד למחלקה עם IOptions"]
many1 -.-> rk1["טעויות הקלדה לא מתגלות עד runtime"]
איור 8: כשההגדרות מעטות מספיקה קריאה ישירה, אבל משמספר המפתחות עובר 5 בטוח יותר לאגד אותן עם IOptions.
4.3. קל לטפל ב-graceful shutdown ובתהליך long-running
Generic Host מטפל לא רק ב”איך מפעילים” אלא גם ב”איך עוצרים”.
כשה-host עולה, נקרא StartAsync של כל IHostedService שנרשם. ב-worker service, רץ ExecuteAsync של ה-hosted service, כולל BackgroundService.
“graceful shutdown” כאן פירושו לא לחתוך את התהליך בפתאומיות, אלא לסיים לפי הסדר הבא:
- לשדר את אות ה-stop
- לצאת מה-loop או מההמתנה
- לעשות cleanup לחיבורים ולמשאבים
באפליקציה שרצה זמן רב, זה קריטי. אירועים כמו Ctrl+C, SIGTERM או stop של service מאפשרים ליישר את דרך ה-stop של כל האפליקציה.
בנוסף, כשרוצים לבקש סיום מצד האפליקציה עצמה, אפשר להשתמש ב-IHostApplicationLifetime.StopApplication(). אפשר לשדר בהקשר ה-host את האות “העבודה הסתיימה, אפשר לרדת בצורה מסודרת”.
flowchart TB
accTitle: סדר ה-graceful shutdown
accDescr: תרשים שמראה שבתגובה לאירוע Ctrl+C, SIGTERM או stop של service, משדרים אות stop, יוצאים מה-loop או מההמתנה, ואז עושים cleanup לחיבורים ולמשאבים.
sg1["Ctrl+C / SIGTERM / stop של service"] --> p1["שידור אות stop"]
p1 --> p2["יציאה מ-loop או המתנה"]
p2 --> p3["cleanup לחיבורים ולמשאבים"]
ap1["בקשת סיום מצד האפליקציה"] -.->|"StopApplication()"| p1
איור 9: graceful shutdown עובר לפי הסדר של אות stop, יציאה מ-loop ו-cleanup, וגם מצד האפליקציה אפשר לשדר את אותו אות דרך StopApplication().
5. הרכב מינימלי
5.1. דוגמה מינימלית לאפליקציית console
חשוב קודם: השימוש ב-Generic Host לא מחייב ליצור דווקא BackgroundService.
גם בכלי console שרץ פעם אחת, אם רוצים DI, Configuration ולוגים, Generic Host שימושי לגמרי.
אם מוסיפים אותו בדיעבד לפרויקט console רגיל, קודם מוסיפים הפניה ל-Microsoft.Extensions.Hosting.
dotnet add package Microsoft.Extensions.Hosting
הדוגמה המינימלית ל-Program.cs יכולה להיראות כך:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();
using IHost host = builder.Build();
try
{
JobRunner runner = host.Services.GetRequiredService<JobRunner>();
await runner.RunAsync();
return 0;
}
catch (Exception ex)
{
ILogger logger = host.Services
.GetRequiredService<ILoggerFactory>()
.CreateLogger("Program");
logger.LogError(ex, "Unhandled exception occurred during job execution.");
return 1;
}
internal sealed class JobRunner(
ILogger<JobRunner> logger,
IConfiguration configuration,
IHostEnvironment hostEnvironment)
{
public Task RunAsync()
{
string message = configuration["Sample:Message"] ?? "(no message)";
logger.LogInformation("Environment: {EnvironmentName}", hostEnvironment.EnvironmentName);
logger.LogInformation("Message: {Message}", message);
return Task.CompletedTask;
}
}
בהרצת dotnet run, ה-console מציג כך (הערך של Message מגיע מ-appsettings.json שנניח בסעיף 5.2 הבא):
info: JobRunner[0]
Environment: Production
info: JobRunner[0]
Message: hello from Generic Host
מה שמופיע מימין ל-info: הוא קטגוריית הלוג (כאן שם הסוג, כי מדובר ב-ILogger<JobRunner>) ומזהה האירוע. ה-logger בברירת המחדל של ה-console מציג בצורה של “שורה ראשונה — קטגוריה, שורה שנייה — גוף ההודעה”. הסיבה ש-Environment הוא Production היא שזו ברירת המחדל כשלא הוגדרו לא DOTNET_ENVIRONMENT ולא ASPNETCORE_ENVIRONMENT. כדי להחליף בזמן פיתוח, מריצים עם DOTNET_ENVIRONMENT=Development מוגדר.
אם לא צריך תהליך long-running, לא חייבים להגיע עד RunAsync(). אפשר לעשות Build(), לפתור את ה-service הנדרש, ולסיים בתום העבודה. גם כך מקבלים במלואו את היתרון של Generic Host.
זו נקודה חשובה יותר ממה שנראה. לא חייבים להביא בכל פעם את תבנית ה-Worker ל-jobs קצרי-חיים.
flowchart TB
accTitle: איך משתמשים ב-job קצר-חיים
accDescr: תרשים שמראה שאם לא צריך תהליך long-running, לא חייבים להגיע עד RunAsync, אלא אפשר לעשות Build, לפתור את ה-service הנדרש, לבצע את העבודה ולסיים ישירות — וגם כך מקבלים את היתרון של Generic Host.
st2["ביצוע Build()"] --> rs1["פתרון ה-service הנדרש"]
rs1 --> jb1["ביצוע העבודה"]
jb1 --> fin1["סיום ישיר"]
fin1 -.-> nt1["לא חייבים להגיע עד RunAsync()"]
איור 10: ב-job קצר-חיים, גם עם Build(), פתרון service, ביצוע וסיום — כבר מקבלים את היתרון של ה-host.
5.2. appsettings.json
בדוגמה שלמעלה, מספיקה צורה מינימלית כזו לקובץ ה-Configuration:
{
"Sample": {
"Message": "hello from Generic Host"
}
}
יש מלכודת קלאסית אחת. בפרויקט console, רק הוספת appsettings.json אינה גורמת להעתקה לתיקיית הפלט. או שמכוונים במאפייני הפרויקט את “העתק לתיקיית הפלט” ל”העתק אם חדש יותר”, או שכותבים ב-csproj את הבא:
<ItemGroup>
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
כדאי גם להכיר את התסמין כשזה נשכח. Generic Host קורא את appsettings.json כקובץ אופציונלי, כך שגם אם הוא לא קיים, לא נזרק exception. פשוט לא מתקבל ערך. בדוגמה המינימלית שלמעלה, יוצג Message: (no message). כשיש “אין שגיאה אבל ה-Configuration לא עובד”, קודם בדקו האם appsettings.json נמצא בתיקיית הפלט.
flowchart TB
accTitle: התסמין כש-appsettings.json חסר
accDescr: תרשים שמראה שאם appsettings.json לא הועתק לתיקיית הפלט, הוא עדיין נקרא כקובץ אופציונלי, ולכן לא נזרק exception, ופשוט לא מתקבל ערך.
ms1["הקובץ חסר בתיקיית הפלט"] --> rd1["נקרא כקובץ אופציונלי"]
rd1 --> ne1["לא נזרק exception"]
ne1 --> nv1["פשוט לא מתקבל ערך"]
nv1 -.-> ck1["קודם בודקים את תיקיית הפלט"]
איור 11: גם כש-appsettings.json חסר, לא נזרק exception — פשוט “לא מתקבל ערך” — אז קודם בודקים את תיקיית הפלט.
בדוגמה הזו, קוראים ישירות configuration["Sample:Message"]. אם צריך לבדוק ערך אחד או שניים, זה מספיק.
אבל כשההגדרות גדלות בעבודה מעשית, קל יותר להימנע מפיזור מחרוזות מפתח אם עוברים לצורה הבאה:
- מפרידים למחלקה לפי section
- מזריקים עם
IOptions<T> - מאמתים בזמן ה-startup
בנוסף, בברירת המחדל של Generic Host מתחברים לא רק appsettings.json אלא גם appsettings.{Environment}.json, environment variables ו-command-line arguments. לכן “להחליף רק בזמן פיתוח” או “לדרוס בפרודקשן עם environment variables” מתאפשרים בטבעיות רבה.
5.3. הוספת BackgroundService
בתהליך שרץ זמן רב, שימוש ב-BackgroundService הוא הבחירה הטבעית.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddScoped<PollingJob>();
builder.Services.AddHostedService<PollingWorker>();
using IHost host = builder.Build();
await host.RunAsync();
internal sealed class PollingWorker(
IServiceScopeFactory scopeFactory,
ILogger<PollingWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
using IServiceScope scope = scopeFactory.CreateScope();
PollingJob job = scope.ServiceProvider.GetRequiredService<PollingJob>();
await job.RunAsync(stoppingToken);
logger.LogInformation("Polling completed.");
}
}
}
internal sealed class PollingJob(ILogger<PollingJob> logger)
{
public Task RunAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Do work here.");
return Task.CompletedTask;
}
}
בדוגמה הזו כדאי לשים לב לשתי נקודות:
- הגוף של
BackgroundServiceהואExecuteAsync - אם רוצים תלות scoped, יוצרים scope עם
IServiceScopeFactory
ל-BackgroundService עצמו אין scope כברירת מחדל. אם רוצים להשתמש ב-scoped service כמו DbContext, בטוח יותר לפתור את צד ה-job בתוך scope, כפי שמופיע למעלה.
flowchart TB
accTitle: איך משתמשים ב-scoped בתוך BackgroundService
accDescr: תרשים שמראה של-BackgroundService עצמו אין scope כברירת מחדל, ולכן מזריקים IServiceScopeFactory, יוצרים scope בתוך ExecuteAsync, ופותרים שם את ה-service של ה-job.
bg1["BackgroundService (בלי scope כברירת מחדל)"] --> fc1["הזרקת IServiceScopeFactory"]
fc1 --> mk1["יצירת scope בתוך ExecuteAsync"]
mk1 --> rv2["פתרון ה-job בתוך ה-scope"]
איור 12: ל-BackgroundService שאין לו scope כברירת מחדל, יוצרים scope עם IServiceScopeFactory ופותרים בתוכו את ה-job.
בחירת כלי ההרצה התקופתי עצמו היא נושא נפרד, אבל אם כותבים בסגנון async, PeriodicTimer נוח למדי. זה מתחבר גם למאמר הקשור על timers.
6. תבניות טיפוסיות
6.1. כלי console קצר-חיים
גם באפליקציה שעושה עבודה פעם אחת ומסיימת — כמו batch, כלי המרה או פקודת תחזוקה — Generic Host שימושי לגמרי.
הוא מתאים למצבים כאלה:
- רוצים לקרוא קובץ Configuration
- רוצים לכתוב לוגים
- רוצים להזריק
HttpClientאו repository - רוצים להחזיר exit code
בסוג הזה של אפליקציה, אם מביאים ישירות BackgroundService ו-RunAsync(), זה קצת כבד מדי ומשתמש ביתר על ניהול ה-lifetime של ה-host.
ב-job קצר-חיים, מספיק כמו בדוגמה המינימלית הקודמת — לפתור את JobRunner ולהריץ אותו.
flowchart TB
accTitle: הבחירה לכלי console קצר-חיים
accDescr: תרשים שמראה שבאפליקציה שעושה עבודה פעם אחת ומסיימת, הבאת BackgroundService ו-RunAsync היא שימוש עודף בניהול lifetime, ומספיק לפתור את ה-service ולהריץ אותו.
on1["אפליקציה שעושה עבודה פעם אחת ומסיימת"] -->|"מספיק"| lg1["פתרון JobRunner והרצתו"]
on1 -.->|"נוטה להיות overhead"| hv1["BackgroundService ו-RunAsync()"]
איור 13: להביא BackgroundService לאפליקציה חד-פעמית זה overhead, ומספיק לפתור את ה-service ולהריץ אותו.
6.2. worker / background service
בתהליכים כמו worker long-running, polling, צריכת queue, ניטור או הרצה תקופתית, השילוב של Generic Host עם BackgroundService טבעי מאוד.
מה שנוח במיוחד:
- זרימת ה-start וה-stop מסודרת בצד ה-host
- לוגים, Configuration ו-DI זמינים מההתחלה
- קל להזרים cancellation עם
Ctrl+Cאו אות stop - קל להפריד את גוף התהליך ה-long-running מ-
Program.cs
בנוסף, קל לחבר גם להקשר של Windows Service או container. אם מפתחים את האפליקציה כתהליך long-running, Generic Host הוא תשתית טבעית למדי.
במעבר ל-Windows Service, פחות תקלות אם מחפשים קבצים מנקודת מוצא IHostEnvironment.ContentRootPath, ולא מהנחת הספרייה הנוכחית. הסיבה היא ש”נתיב הבסיס של האפליקציה” נקבע בהקשר ה-host.
flowchart TB
accTitle: התשתית ל-worker long-running
accDescr: תרשים שמראה שב-worker long-running או בהרצה תקופתית השילוב של Generic Host עם BackgroundService טבעי, וקל לחבר גם להקשר של Windows Service ו-container.
wk1["worker long-running / הרצה תקופתית"] --> cb1["Generic Host עם BackgroundService"]
cb1 --> ws1["Windows Service"]
cb1 --> ct1["תהליך long-running ב-container"]
ws1 -.-> cr1["חיפוש מנקודת מוצא ContentRootPath"]
איור 14: תהליך long-running נוח יותר עם השילוב של host ו-BackgroundService, וקל לפתח אותו גם אל Windows Service או container.
6.3. גם מתחת ל-ASP.NET Core
באפליקציית Web / API משתמשים ב-WebApplication.CreateBuilder(args), ולכן במבט ראשון זה יכול להיראות כעולם נפרד מ-Generic Host.
אבל התחושה בפועל מחוברת מאוד.
builder.Servicesbuilder.Configurationbuilder.Logging
הדמיון בסגנון הכתיבה נובע מכך.
ב-ASP.NET Core, גם הפעלת שרת ה-HTTP נכללת בתוך ה-lifetime של ה-host. כלומר, כשקוראים את Program.cs בצד Web, ההבנה של Generic Host עוזרת גם להבין “למה כאן נוגעים ב-DI, ב-Configuration ובלוגים”.
flowchart TB
accTitle: גם Web על אותו host
accDescr: תרשים שמראה שגם אפליקציית ASP.NET Core נמצאת דרך WebApplication.CreateBuilder על אותה תפיסת host, ושהפעלת שרת ה-HTTP נכללת בתוך ה-lifetime של ה-host.
wa1["אפליקציית ASP.NET Core"] --> wb2["WebApplication.CreateBuilder"]
wb2 --> gh2["אותה תפיסת host"]
gh2 -.-> ht1["גם הפעלת שרת ה-HTTP בתוך ה-lifetime"]
איור 15: גם ה-builder בצד Web נמצא על אותה תפיסת host, וגם הפעלת שרת ה-HTTP נכללת ב-lifetime.
7. מקרים שמתאימים
הנה כמה מצבים שבהם Generic Host נכנס בנוחות רבה.
- אפליקציית console שמשתמשת ב-Configuration, לוגים ו-DI
- worker בסגנון queue consumer, poller, watchdog או scheduler
- אפליקציה שרצה זמן רב ורוצה לעשות cleanup ב-
Ctrl+Cאו SIGTERM - אפליקציה שעשויה לגדול בעתיד לכיוון Windows Service או תהליך long-running ב-container
- אפליקציה שרוצה להתאים לסגנון אותה קבוצת הרחבות כמו ASP.NET Core
המשותף לכולם הוא “לא רוצים לזלזל בנקודת הכניסה של האפליקציה ובניהול ה-lifetime שלה”.
עם זאת, קשה לצייר קו רק על סמך זה, אז נוסיף כאן גם קווים מנחים. אם מתקיימים שניים או יותר מהבאים, לרוב כדאי מההתחלה להעמיס את האפליקציה על Generic Host — זה נוח יותר בהמשך.
flowchart TB
accTitle: קו מנחה להחלטת אימוץ
accDescr: תרשים שמראה שאם מתקיימים שניים או יותר מהקווים המנחים, מעמיסים מההתחלה על Generic Host, ואם אף אחד לא מתקיים אפשר להחליט ש-Generic Host מיותר.
qn1["סופרים כמה קווים מנחים מתקיימים"] -->|"שניים או יותר"| ok1["מעמיסים מההתחלה על Generic Host"]
qn1 -->|"אף אחד"| ng1["אפשר להחליט ש-Generic Host מיותר"]
איור 16: אם מתקיימים שניים או יותר מהקווים המנחים, מעמיסים מההתחלה, ואם אף אחד לא מתקיים לא חייבים להכניס אותו.
| קו מנחה | קו הגבול המעשי |
|---|---|
| מספר הגדרות | לפחות 3 הגדרות שמשתנות בין סביבות (יעד חיבור, סף, יעד פלט וכדומה) |
| לוגים | צריך לשמור לקובץ או ל-Event Log. לא מספיק לכתוב ל-stdout בלבד |
| צורת ההרצה | רץ באופן long-running, או פועל לפחות פעם ביום במרווח קבוע |
| תלויות | לפחות 3 צדדים שרוצים לקבל דרך ה-constructor, או שיש צד שרוצים להחליף בבדיקות |
| lifetime | נדרש cleanup באמצע בעת Ctrl+C או stop של service |
| עתיד | קיים סיכוי להריץ כ-Windows Service או ב-container |
8. מקרים שלא מתאימים / overhead
מהצד השני, יש גם מצבים שבהם לא צריך להפוך את Generic Host לשחקן הראשי מההתחלה.
- כלי קטן שרק קורא argument פעם אחת, מדפיס פלט פעם אחת ומסיים
- קוד בדיקה גס שמשתמשים בו רק כמה עשרות דקות
- פרויקט library
- מקרה שבו קוראים הגדרה אחת בלבד, ולא צריך DI, לוגים או ניהול lifetime
כאן, כתיבה ישירה ב-Main במקום הקמת host חוסכת גם בכמות הקריאה וגם במספר הקבצים. כקו מנחה, אם אף אחד מהתנאים בטבלה שבפרק 7 לא מתקיים, אפשר להחליט ללא חשש ש-Generic Host מיותר.
חשוב להבין: חוזק של Generic Host לא הופך אותו לחובה בכל executable.
9. מלכודות נפוצות
לבסוף, נסכם נקודות שקל למעוד בהן בפעם הראשונה עם Generic Host.
- לראות ב-Generic Host רק DI container
- בפועל, זו תשתית שכוללת start, stop, Configuration, לוגים ו-hosted service.
- להתחיל מ-
Host.CreateDefaultBuilderמתוך הרגל, למרות שזו אפליקציה חדשה- אם אין סיבה להתאים לקוד קיים,
Host.CreateApplicationBuilderהוא הבחירה הטבעית יותר.
- אם אין סיבה להתאים לקוד קיים,
- להכניס scoped service ישירות אל
BackgroundService- ל-hosted service אין scope כברירת מחדל. בטוח יותר ליצור scope עם
IServiceScopeFactory.
- ל-hosted service אין scope כברירת מחדל. בטוח יותר ליצור scope עם
- לא להודיע ל-host על stop ב-worker שרץ פעם אחת בלבד
- אם עושים “run once” בתבנית Worker, בלי לקרוא ל-
IHostApplicationLifetime.StopApplication()בסיום העבודה, ה-host ימשיך לרוץ כרגיל.
- אם עושים “run once” בתבנית Worker, בלי לקרוא ל-
- לחתוך עם
Environment.Exitכשרוצים graceful shutdown- אם משתמשים ב-host, כשרוצים לעצור בצורה מסודרת,
StopApplication()היא הדרך הנכונה יותר.
- אם משתמשים ב-host, כשרוצים לעצור בצורה מסודרת,
- להניח את הספרייה הנוכחית כמובנת מאליה ב-Windows Service
- יציב יותר לחפש קבצים מנקודת מוצא
IHostEnvironment.ContentRootPath.
- יציב יותר לחפש קבצים מנקודת מוצא
- לעטוף ב-
BackgroundServiceמההתחלה כלי CLI קצר-חיים- בעבודה חד-פעמית, מספיק לפתור מחלקת service רגילה ולהריץ אותה.
- להכניס בגסות timer מבוסס callback להרצה תקופתית של
BackgroundService- אם כותבים בזרימה
async,PeriodicTimerלרוב קריא יותר ופחות נוטה להתפרע.
- אם כותבים בזרימה
ב-Generic Host, חלוקה מראש בין “job קצר-חיים” ל”job long-running” לבדה מפחיתה משמעותית את הבלבול.
flowchart TB
accTitle: השאלה שמחלקים ראשונה
accDescr: תרשים שמראה שקודם מחלקים בין job קצר-חיים ל-job long-running — בקצר-חיים מספיק לפתור service ולהריץ אותו, וב-long-running משתמשים ב-BackgroundService ובניהול ה-lifetime של ה-host.
qq1["job קצר-חיים או long-running?"] -->|"קצר-חיים"| sj1["רק פתרון service והרצה"]
qq1 -->|"long-running"| lj1["BackgroundService וניהול lifetime"]
איור 17: החלוקה הראשונית בין קצר-חיים ל-long-running מפחיתה בלבול לגבי כמה מכלי ה-host להשתמש.
10. סיכום
במשפט אחד, Generic Host הוא תשתית שמאחדת את נקודת הכניסה ואת ניהול ה-lifetime של אפליקציית .NET.
נסכם את הנקודות שכדאי לזכור.
- Generic Host כולל לא רק DI, אלא גם Configuration, לוגים, טיפול ב-stop ו-hosted service
- באפליקציה חדשה שאינה Web, טבעי להתחיל מ-
Host.CreateApplicationBuilder(args) - ב-job קצר-חיים, אפשר בלי
BackgroundService— רק build והרצה - בתהליך long-running,
BackgroundServiceוניהול ה-lifetime של ה-host משמעותיים מאוד - ל-
BackgroundServiceאין scope כברירת מחדל, אז scoped services דורשים יצירת scope במפורש - גם
WebApplicationBuilderשל ASP.NET Core נמצא, מבחינה רעיונית, על אותו זרם
Generic Host אינו כלי לטקס כבד. הוא כלי לרכז בפתח את ה-Configuration, הלוגים, התלויות, ה-startup וה-shutdown — ברגע שהם גדלים ולו במעט — במקום לתת להם לברוח לתוך הקירות.
מהצד השני, באפליקציה קטנה שעוד לא צריך את זה, אפשר גם לא להכניס אותו. כשיודעים להבחין בין המקרים, Generic Host מפסיק להיות “משהו שמכניסים סתם”, והופך לתשתית מעשית עם ייעוד ברור.
11. מקורות
- .NET Generic Host - .NET
- Worker Services in .NET
- Use scoped services within a BackgroundService - .NET
- Configuration in .NET
- Options pattern in .NET
- .NET Generic Host in ASP.NET Core
- Create Windows Service using BackgroundService - .NET
- מאמר קשור: שלושת סוגי ה-timers ב-.NET - מתי להשתמש ב-PeriodicTimer / Timer / DispatcherTimer
- מאמר קשור: טבלת החלטה מעשית ל-async/await ב-C# - Task.Run ו-ConfigureAwait
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
למה להכניס Generic Host ו-BackgroundService לאפליקציית desktop ב-.NET
בכלי Windows ובאפליקציות long-running, איך להשתמש ב-Generic Host וב-BackgroundService כדי לרכז הפעלה, עיבוד תקופתי, shutdown, לוג, הגדרות...
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
שיטות עבודה מומלצות ל-multithreading בפועל: מהדורת .NET — מה להחליט לפני שמוסיפים threads
כללי תכנון שמונעים מקוד multithreaded ב-.NET/C# לקרוס או להיתקע מדי פעם: להשתמש ב-Task במקום ליצור threads בעצמכם, לצמצם shared mutable s...
WMI/CIM מ-C# ומ-PowerShell — מדריך מעשי למידע חומרה, ניטור process ושאילתות remote
WMI/CIM הוא הדרך הסטנדרטית לשלוף serial number של מחשב, לנטר דיסק פנוי ולזהות process שהתחיל. המאמר מכסה CIM cmdlets כמו Get-CimInstance,...
מעמקי ה-I/O ב-Windows (פרק 4) — Cache Manager: מתי WriteFile באמת מגיע לדיסק
פרק 4 בסדרה שמסבירה בתרשימים את Cache Manager של Windows. המאמר עובר על מטמון שממומש כמיפוי קובץ, read-ahead ו-lazy writer, הבחירה בין Fl...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
Generic Host וארכיטקטורת יישומים
Generic Host, BackgroundService, DI, תצורה, רישום ומחזור חיי היישום.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
זה נושא של בניית אפליקציית Windows עם process ברקע, טיפול ב-shutdown, לוגים ו-Configuration, ולכן הוא מתאים לפרויקט של Windows Custom Software Development.
ייעוץ טכני וסקירת תכנון
אם רוצים לסגור DI, lifetime וחלוקת אחריות לפני שכותבים קוד, אפשר להתחיל מייעוץ טכני ו-design review.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה זה Generic Host?
- זו התשתית שמטפלת ב-startup וב-lifetime של אפליקציית .NET במקום אחד. בפנים יושבים DI, Configuration, לוגים, IHostedService / BackgroundService, וטיפול ב-stop של האפליקציה. זו לא רק עטיפה סביב DI container; הזווית הנכונה היא מנגנון שמאחד את נקודת ההרכבה של האפליקציה עם ניהול ה-lifetime. הוא משתלם ברגע ש-Configuration, לוגים, תלויות, startup ו-shutdown מתחילים לגדול.
- מה עדיף — Host.CreateApplicationBuilder או Host.CreateDefaultBuilder?
- לאפליקציה חדשה שאינה Web, נקודת הכניסה הטבעית היא Host.CreateApplicationBuilder(args). לשניהם אותה פונקציונליות ליבה ואותה התנהגות default; אחד אינו feature חדש והשני אינו משהו אחר. ההבדל הוא בעיקר בסגנון הכתיבה: CreateApplicationBuilder כותבים ישירות אל builder.Services וכדומה, ו-CreateDefaultBuilder משרשרים קריאות כמו ConfigureServices. אם צריך להתאים לקוד קיים או להרכבה שמבוססת על extension methods ישנים, בוחרים CreateDefaultBuilder.
- שווה להשתמש ב-Generic Host גם באפליקציית console?
- אם רוצים DI, Configuration ולוגים — כן, גם בכלי console שרץ פעם אחת. אין חובה ליצור BackgroundService: אפשר לעשות Build(), לפתור את ה-services שצריך, ולצאת כשהעבודה נגמרה, ועדיין לקבל את היתרון של Generic Host. בכלי קטן שרק קורא argument אחד, מדפיס שורה אחת ומסיים, או בקוד בדיקה גס, זה overhead — לא חייבים להכניס אותו בכל פעם.
- איך משתמשים ב-scoped service מתוך BackgroundService?
- ל-BackgroundService אין scope כברירת מחדל, ולכן לא בטוח להזריק scoped service ישירות ב-constructor. הצורה הבטוחה היא להזריק IServiceScopeFactory, ליצור scope במפורש בתוך ExecuteAsync, ולפתור שם את ה-services של ה-job. אם רוצים DbContext או scoped service אחר, חשוב במיוחד לשמור על הצורה הזו.