Interview Handbook by Sayed Elmahdy English

المحور 07 · الويب والـ APIs

ASP.NET Core و MVC

MVC، و Kestrel، و Middleware، و Routing، و Model binding، و Filters، و DI، و Configuration، و CORS.

24 سؤالJunior · 8Mid · 10Senior · 612 اختيارات

المفاهيم

هنفهم إيه؟ هنمشي مع طلب داخل من لحظة وصوله لحد response، ونشوف مكان routing و و وconfiguration. الهدف إنك تعرف تختار شكل التطبيق وتحط كل مسؤولية في المكان المناسب، مع رؤية أثر ترتيب الخطوات على الطلب الحقيقي.

قبل ما تبدأ: هو طريقة client يبعث بها request والسيرفر يرجع response. endpoint هو العنوان والمعالج اللي يرد عليه. مش لازم تعرف framework قبلاً، لكن راجع method وclass في C# (محور 02) عشان الأمثلة تبقى أسهل.

المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.

#MVC واختيار شكل الواجهةMVC and choosing an application model

لو Controller بيعمل ويحسب discounts ويبني ، تغييره واختباره هيبقوا صعبين. يفصل التنسيق عن business behavior وعن العرض.

في متجر: Controller زي موظف بياخد الطلب وينسّق تنفيذه؛ Model هو بيانات وسلوك البيع؛ View طريقة عرض النتيجة. Model مش مرادف لجدول database، و Controller مش مكان كل business rules.

الأسلوبالنتيجة والاستخدام
MVCControllers و Razor views لصفحات HTML
Web controllersControllerBase لبيانات ؛ غالباً
Razor PagesPage مع handlers؛ مناسب لتدفقات مرتبطة بالصفحة
Minimal APIsRoute handlers و endpoint groups من غير controllers؛ مناسب لـ APIs منظمة حسب feature
Blazor components بـ C#؛ render modes بتحدد مكان التنفيذ والتفاعل
// IProductService and ProductViewModel are application types.
public sealed class ProductsController(IProductService products) : Controller
{
    public async Task<IActionResult> Details(int id, CancellationToken ct)
    {
        var product = await products.GetAsync(id, ct);       // (1)
        if (product is null) return NotFound();
        return View(product);                              // (2)
    }
}
🧾 شرح الكود
  1. (1) Action بتنسّق service call وتمرير cancellation؛ service مسؤولة عن تفاصيل الاسترجاع.
  2. (2) MVC يختار Views/Products/Details.cshtml حسب convention. في API ترجع بدل View.

اختار حسب الفريق والـ UI والـ conventions المطلوبة؛ Minimal API مش معناها كل التطبيق في Program.cs، و MVC مش معناها architecture كاملة.

#Kestrel و IIS و reverse proxyKestrel, IIS and reverse proxies

التطبيق محتاج server، والتشغيل قدام الإنترنت محتاج قرارات و routing و limits. Kestrel server cross-platform؛ ممكن يبقى edge server مباشرةً أو ورا proxy زي Nginx أو .

Client -> optional CDN/WAF -> reverse proxy -> Kestrel -> pipeline

IIS in-process:     IIS -> ASP.NET Core Module -> IIS HTTP Server -> app
IIS out-of-process: IIS -> ASP.NET Core Module -> Kestrel -> app
using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

// Before Build; replace this address with the trusted proxy's address.
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
        | ForwardedHeaders.XForwardedProto;                // (1)
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.10")); // (2)
});
// Early in the pipeline, before HTTPS redirection:
app.UseForwardedHeaders();                                 // (3)
🧾 شرح الكود
  1. (1) يقرأ الـ original client والـ scheme من forwarded headers الموثوقة.
  2. (2) يحدد proxy مسموح؛ العنوان مجرد مثال. إعداد proxy chains لازم يطابق الشبكة.
  3. (3) يصحح معلومات request قبل redirects والـ auth. متثقش في headers من أي client.

in-process مش بيستخدم Kestrel لاستقبال requests. يفيد في TLS termination والـ load balancing، لكنه مش شرط لتشغيل Kestrel بأمان لو متظبط صح. راعي body size و timeouts في كل طبقة.

#Request pipeline و custom middleware و Use و Run و MapPipeline order, custom middleware, Use, Run and Map

مثال خطوة بخطوة
  1. تخيّل الطلب يمر على ثلاث خطوات: تسجيل البداية، فحص الهوية، ثم تنفيذ endpoint الطلبات. كل يقدر ينفذ قبل الخطوة التالية وبعد رجوعها.
  2. لو فحص الهوية رفض الطلب وأنهى الرد، endpoint لا يتنفذ. ولو logger قاس قبل next وبعده، يقيس زمن الخطوات التالية معًا.
  3. لهذا الترتيب جزء من السلوك: خطوة تحتاج هوية مؤكدة لازم تأتي بعد الخطوة التي تثبتها. ارسم رحلة الطلب وراجع أي خطوة قد تنهيها مبكرًا.

كل middleware ممكن تعمل شغل قبل next وبعده، أو توقف السلسلة. الترتيب هو nesting: الـ response بيرجع عكس اتجاه الدخول، والـ exception handler يقدر يمسك أخطاء اللي بعده بس.

Forwarded headers -> exception handler -> HSTS/HTTPS -> static files
  -> routing -> CORS -> authentication -> rate limiter -> authorization
  -> endpoint -> response unwinds through executed middleware

Use: may call next; Run(RequestDelegate): terminal handler
Map("/path", branch): branch pipeline
MapGet / MapControllers: register endpoints, not the same branching API
public sealed class RequestLogMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context,
        ILogger<RequestLogMiddleware> log)
    {
        var started = Stopwatch.GetTimestamp();             // (1)
        try { await next(context); }                        // (2)
        finally
        {
            double ms = Stopwatch.GetElapsedTime(started).TotalMilliseconds;
            log.LogInformation("Request {TraceId} ended after {ElapsedMs} ms",
                context.TraceIdentifier, ms);               // (3)
        }
    }
}

// Required CORS, authentication, authorization and limiter services are registered.
app.UseExceptionHandler();
app.UseMiddleware<RequestLogMiddleware>();
if (!app.Environment.IsDevelopment()) app.UseHsts();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("storefront");
app.UseAuthentication();
app.UseRateLimiter();                                       // (4)
app.UseAuthorization();
app.MapControllers();
🧾 شرح الكود
  1. (1) Stopwatch من System.Diagnostics يقيس elapsed time، مش wall clock.
  2. (2) next ينقل التنفيذ، و finally يضمن تسجيل الزمن حتى مع exception.
  3. (3) Structured log من غير request body أو secrets. لو exception اتحرك للـ outer handler، status هنا ممكن لسه مش final؛ ده log وقت مش حكم نجاح.
  4. (4) Limiter بعد routing للـ endpoint metadata، وبعد authentication لو التقسيم حسب user. Forwarded headers تتحط بدري حسب hosting setup.

Conventional middleware constructor عمره طويل؛ خد scoped dependencies في InvokeAsync، أو استخدم IMiddleware بتسجيل مناسب. app.Run() اللي من غير بيبدأ host؛ غير Run(handler) اللي بيوقف pipeline. Static files لو قبل auth بتبقى public؛ احمِ المحتوى الخاص بمسار مناسب.

#Routing و model binding: الطلب يوصل لمين والداتا منين؟Routing and binding: destination and input sources

Routing يختار endpoint؛ model binding يحوّل request values لـ parameters. Constraint زي int بيحدد route match، مش رسالة validation لمدخلات المستخدم.

// Conventional routing for controllers with views:
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

// Attribute routing; IOrderService and UpdateOrder are application types.
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(IOrderService orders) : ControllerBase
{
    [HttpPut("{id:int}")]
    public async Task<IActionResult> Update(
        [FromRoute] int id,                              // (1)
        [FromQuery] bool notify,
        [FromHeader(Name = "Idempotency-Key")] string? key,
        [FromBody] UpdateOrder request,                  // (2)
        CancellationToken ct)
    {
        await orders.UpdateAsync(id, request, notify, key, ct);
        return NoContent();
    }
}

// Minimal API grouping:
var group = app.MapGroup("/api/products").RequireAuthorization();
group.MapGet("/{id:int}", (int id) => TypedResults.Ok(new { Id = id }));
🧾 شرح الكود
  1. (1) Route للهوية، و query للخيارات، و header للـ metadata. [FromServices] للـ ، و [FromForm] للـ form data والـ uploads.
  2. (2) body واحد يُقرأ كـ . استخدم request type منفصل لتجنب overposting للـ entity.

[ApiController] يعمل inference حسب النوع والـ route وتسجيل DI؛ explicit attributes مفيدة لما المصدر مهم. Minimal ليها binding rules خاصة زي TryParse و BindAsync، مش model binders. /orders/abc مش هيطابق {id:int} فيرجع غالباً 404، بينما conversion failure بعد match ممكن يبقى 400. Constraints مش بديل عن range checks.

#Validation و filters و Minimal APIs في .NET 10Validation, filters and .NET 10 Minimal APIs

Binding يجاوب «الداتا اتقرت؟»، validation يجاوب «شكلها مقبول؟»، والـ domain يجاوب «العملية مسموحة دلوقتي؟». Stock availability لازم يتراجع مع update آمن، مش annotation على request.

// .NET 10 Web project; package: Microsoft.Extensions.Validation.
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidation();                         // (1)
var app = builder.Build();
app.MapPost("/products", (CreateProduct request) =>
    TypedResults.Created("/products/1", request));         // (2) illustrative id
app.Run();

public sealed class CreateProduct
{
    [Required, StringLength(200)]
    public string Name { get; set; } = "";
    [Range(0.01, 1_000_000)]
    public decimal Price { get; set; }
}
🧾 شرح الكود
  1. (1) AddValidation في 10 يفعّل automatic validation للـ supported Minimal parameters باستخدام metadata generated. المثال في نفس assembly.
  2. (2) لو annotations فشلت، handler ما يشتغلش ويرجع 400. ده مثال للـ validation؛ endpoint الحقيقي ينشئ resource ويرجع Location بالـ Id الفعلي.
الاختيارمكانه
Data annotationsقواعد بسيطة على input؛ مع ApiController بيرجع 400 تلقائي على invalid ModelState
FluentValidationقواعد مركبة وقابلة للاختبار؛ استخدم ValidateAsync صراحةً للـ async rules وادمج النتيجة مع response
MVC filtersAuthorization، و resource، و action، و exception، و result stages؛ مش كل filter بيلف كل المراحل
Endpoint filtersMinimal API handler arguments والـ result؛ مش نفس MVC action filters

Action filters بتشوف arguments بعد binding؛ resource filters بتشتغل قبل binding. Exception filters مش بتغطي كل أو response-writing failures. لو concern عام زي logging أو global errors، middleware غالباً أنسب. مرجع validation؛ راجع metadata discovery للأنواع في assemblies تانية.

#Results و status codes و response contractsResults, status codes and response contracts

response مش بس؛ status و headers جزء من contract. خلي نوع return يوضح احتمالات النجاح والفشل بدل 200 لكل حاجة.

using Microsoft.AspNetCore.Http.HttpResults;

// IOrderReader and OrderDto are application types registered with DI.
app.MapGet("/orders/{id:int}",
    async Task<Results<Ok<OrderDto>, NotFound>>
    (int id, IOrderReader reader, CancellationToken ct) =>
    {
        var order = await reader.GetAsync(id, ct);          // (1)
        return order is null
            ? TypedResults.NotFound()
            : TypedResults.Ok(order);                      // (2)
    });
🧾 شرح الكود
  1. (1) Null بيعبر عن absence متوقع، مش exception مصطنع.
  2. (2) Results union يقيّد return types ويضيف metadata للـ OpenAPI. ده Minimal مش IActionResult بتاع .

IActionResult مرن لنتائج MVC مختلفة؛ ActionResult<T> يضيف success type. وثّق failure responses كمان، وماترجعش entities مباشرةً.

Statusالمعنى
200 / 201 / 202 / 204نجاح ببيانات؛ إنشاء مع Location؛ قبول شغل لسه ما خلصش؛ نجاح من غير body
400 / 401 / 403 / 404Input غلط؛ credentials ناقصة أو غير صالحة؛ هوية معروفة بس مش مسموح؛ resource مش موجود
409 / 422 / 429 / 500تعارض state؛ محتوى مفهوم بس غير قابل للتنفيذ حسب contract؛ rate limit؛ خطأ غير متوقع

فرّق 202 عن اكتمال العملية. 401 عادةً challenge ومعاه WWW-Authenticate حسب scheme؛ 403 forbid. اختار سياسة ثابتة للـ business errors بدل حفظ status لكل exception.

#DI lifetimes و captive dependencies و keyed servicesDI lifetimes, captive dependencies and keyed services

مثال خطوة بخطوة
  1. افترض خدمة تقارير تستقبل . الـ scoped lifetime معناها نفس النسخة داخل scope واحد؛ في طلب ويب معتاد، نطاق الطلب هو scope.
  2. لو خليت خدمة التقارير singleton وهي تحتفظ بـ DbContext، فهي تعيش أطول من عمر الاعتماد المتوقع وتشاركه بين طلبات. دي captive dependency، وقد يظهر خطأ تحقق أو مشاكل تزامن وحالة قديمة.
  3. اختار عمر الخدمة بناءً على الحالة التي تحتفظ بها، وراجع أعمار احتياجاتها. BackgroundService الذي يحتاج scoped services ينشئ scope مناسبًا لكل وحدة عمل بدل الاحتفاظ بها طوال عمره.

مش بس بديل لـ new؛ هو كمان إدارة ownership و lifetime. Service طويلة العمر لو احتفظت بحاجة قصيرة العمر، ممكن تشارك state بين requests بالغلط.

Lifetimeالمعنىمثال
TransientInstance لكل resolutionخدمة خفيفة بلا state مشترك
ScopedInstance لكل scope؛ غالباً requestDbContext
SingletonInstance لكل root service providerCache أو خدمة مشتركة لازم thread-safe
// Application types: IPaymentProvider, CardProvider and WalletProvider.
builder.Services.AddKeyedScoped<IPaymentProvider, CardProvider>("card");
builder.Services.AddKeyedScoped<IPaymentProvider, WalletProvider>("wallet"); // (1)

app.MapPost("/payments/card",
    ([FromKeyedServices("card")] IPaymentProvider provider) =>
        TypedResults.Ok(provider.Name));                   // (2)

// A singleton's unit of work, not a singleton-held DbContext:
await using var scope = scopeFactory.CreateAsyncScope();   // (3)
var work = scope.ServiceProvider.GetRequiredService<IOrderWork>();
await work.ExecuteAsync(ct);
🧾 شرح الكود
  1. (1) Keyed DI من 8 يميّز implementations بنفس interface.
  2. (2) اختيار معلن للمفتاح، من غير نشر IServiceProvider في كل business class.
  3. (3) Scope جديد لكل unit of work، ويتقفل بعد await. متخليش dependency تهرب خارج scope.

Captive dependency زي singleton ماسك DbContext scoped. ValidateScopes يكشف مخالفات كتير لما scope validation مفعّل؛ شغّل validation في اختبارات startup كمان. Transient داخل singleton ممكن يعيش معاه فعلياً. الـ container يتصرف في disposables اللي يملكها؛ متقفلش injected service عشوائياً ولا تعمل BuildServiceProvider جوه التسجيلات.

#Configuration و options و environments و secretsConfiguration, options, environments and secrets

قيم deployment مختلفة مش سبب لنسخ config متناثرة أو passwords في Git. اجمع الإعدادات في options typed، وافشل بدري لو ناقصة.

Default application configuration, low -> high priority:
appsettings.json -> appsettings.{Environment}.json
  -> User Secrets in Development -> environment variables -> command-line

Later added providers can override earlier values.
Payments__ApiKey maps to Payments:ApiKey.
public sealed class PaymentOptions
{
    [Required] public string ApiKey { get; set; } = "";
    [Range(1, 60)] public int TimeoutSeconds { get; set; } = 10;
}

// Before Build; DataAnnotations attributes need their namespace.
builder.Services.AddOptions<PaymentOptions>()
    .BindConfiguration("Payments")                          // (1)
    .ValidateDataAnnotations()
    .ValidateOnStart();                                     // (2)
🧾 شرح الكود
  1. (1) Bind للقسم بدل قراءة strings متكررة في كل class.
  2. (2) التحقق وقت بدء host، بدل أول payment request. ماتسجّلش ApiKey في exception أو logs.
Interfaceالعمر والتغيير
IOptions<T>Singleton wrapper؛ القيمة cached، مش متابعة reload
IOptionsSnapshot<T>Scoped؛ يحسب القيمة لكل scope عند الطلب، مش injectable في singleton
IOptionsMonitor<T>Singleton؛ CurrentValue و OnChange عند provider بيدعم reload

Environment زي Development أو Staging أو Production بيختار ملف settings وسلوك debug؛ مش security boundary. في WebApplicationBuilder لو DOTNET_ENVIRONMENT و ASPNETCORE_ENVIRONMENT الاتنين متحددين، DOTNET_ENVIRONMENT ليه الأولوية؛ تجنب التعارض. User Secrets للتطوير خارج Git، مش encrypted vault. في production استخدم secret store أو injection مناسب؛ environment variables مش تشفير.

#IExceptionHandler و ProblemDetails و structured loggingIExceptionHandler, ProblemDetails and structured logging

تكرار try/catch في كل action بيطلع errors غير متسقة، وممكن يكشف stack . خلي expected outcomes results، والأخطاء غير المتوقعة ليها handler عام و trace id يربط الرد بالـ logs.

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

// Before Build:
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalErrors>();       // (1)
// After Build, early: app.UseExceptionHandler();

public sealed class GlobalErrors(ILogger<GlobalErrors> log) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext context,
        Exception exception, CancellationToken ct)
    {
        log.LogError(exception, "Unhandled request {TraceId}",
            context.TraceIdentifier);                     // (2)
        context.Response.StatusCode = 500;
        await context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = 500,
            Title = "An unexpected error occurred.",
            Extensions = { ["traceId"] = context.TraceIdentifier }
        }, contentType: "application/problem+json", cancellationToken: ct); // (3)
        return true;                                      // (4)
    }
}
🧾 شرح الكود
  1. (1) Register handler من 8 مع ProblemDetails services؛ ممكن handlers متعددة بترتيب التسجيل.
  2. (2) Exception كاملة في sink داخلي بصلاحيات مناسبة. الـ placeholder يخلي TraceId field قابل للبحث.
  3. (3) العميل يشوف رسالة ثابتة و trace id، مش تفاصيل database أو secrets.
  4. (4) true يعني تمت المعالجة؛ false يسيب الفرصة للي بعده. Handler طويل العمر، فخد بالك من lifetimes.

لو response بدأ، مش دايماً تقدر تغيّر status أو body. تعامل مع request cancellation كحالة متوقعة حسب السياق. في .NET 10 diagnostics الافتراضية للـ exceptions اللي handler رجّع لها true بتتسكت افتراضياً؛ لو محتاج logging اعمله بقرار واضح زي المثال ومن غير تكرار. Logging levels: Trace، و Debug، و Information، و Warning، و Error، و Critical؛ ماتسجّلش أو card data. مرجع errors.

#CORS و preflight و rate limiting و health checksCORS, preflight, rate limits and health checks

Browser محتاج إذن يقرا response من origin تاني؛ محتاجة كمان تحد الاستخدام وتعلن readiness. دول تلات concerns مختلفة ومش بدائل عن authentication.

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

builder.Services.AddCors(o => o.AddPolicy("storefront", p => p
    .WithOrigins("https://shop.example.com")
    .WithMethods("GET", "POST")
    .WithHeaders("Content-Type", "Authorization")));       // (1)
builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddFixedWindowLimiter("browse", p =>
    {
        p.PermitLimit = 100;
        p.Window = TimeSpan.FromMinutes(1);
        p.QueueLimit = 0;
    });                                                    // (2)
});
builder.Services.AddHealthChecks();                        // (3)

// After Build; install CORS and rate-limiting middleware as shown earlier.
app.MapGet("/catalog", () => TypedResults.Ok("Catalog"))
    .RequireRateLimiting("browse");
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false                                 // (4)
});
app.MapHealthChecks("/health/ready");
🧾 شرح الكود
  1. (1) Origins صريحة. أو Authorization header غالباً يسبب preflight OPTIONS قبل الطلب؛ UseCors يتعامل معاه قبل authorization.
  2. (2) Policy مشتركة للمسار في instance واحدة، مش 100 لكل user تلقائياً. استخدم partitioning لو المطلوب لكل user أو client.
  3. (3) ده registration بس؛ أضف readiness checks فعلية للـ dependencies الضرورية، وإلا ready هيقول Healthy من غير فحصها.
  4. (4) Liveness هنا نجاح host بس من غير dependency checks؛ readiness يشغّل المسجّل.

browser policy، و curl مش ملتزم بيها؛ مش حماية . لو cookie credentials مطلوبة، استخدم AllowCredentials مع origins محددة، مش wildcard. Limiter جوه process مش quota موزّعة ولا DDoS defense كامل؛ gateway ينظم المستوى المشترك. ماتخليش dependency outage يعمل restart لكل instances بسبب liveness، وماتكشفش health details للعامة.

#HttpClient و resilience: حدود الوقت وإعادة المحاولةHttpClient and resilience: time budgets and retries

إنشاء client و handler جديد لكل call ممكن يضيّع connection reuse ويضغط sockets. Factory بيفصل عمر client عن handlers، ويسهّل config و resilience.

// Package: Microsoft.Extensions.Http.Resilience.
using Microsoft.Extensions.Http.Resilience;

builder.Services.AddHttpClient<CatalogClient>(http =>
{
    http.BaseAddress = new Uri("https://catalog.example.com/"); // (1)
}).AddStandardResilienceHandler(options =>
{
    options.Retry.DisableForUnsafeHttpMethods();             // (2)
});

public sealed class CatalogClient(HttpClient http)
{
    public Task<ProductDto?> GetAsync(int id, CancellationToken ct) =>
        http.GetFromJsonAsync<ProductDto>($"products/{id}", ct); // (3)
}
🧾 شرح الكود
  1. (1) BaseAddress وإعدادات client في مكان واحد؛ factory يدير pooled handlers.
  2. (2) Standard handler يضيف timeouts و retry و circuit breaker وغيرهم؛ بنمنع retries للـ unsafe methods لحد ما يبقى فيه contract.
  3. (3) Cancellation ماشية مع call؛ 404 أو response غير ناجح محتاج mapping مقصود لو النوع nullable مش كفاية. GetFromJsonAsync بيرمي على non-success.

Client طويل العمر مع SocketsHttpHandler و PooledConnectionLifetime اختيار صحيح كمان. متحتفظش typed client قصير العمر جوه singleton من غير دراسة و lifetime. ظبّط total budget و per-attempt timeout و retries حسب dependency؛ retry ممكن يكرر payment اتنفذت حتى لو الرد ضاع.

#BackgroundService و durable jobs و deploymentBackgroundService, durable jobs and deployment

شغل زي cleanup أو reconciliation مينفعش يبقى fire-and-forget متعلق بـ request scope. اختار in-process loop للشغل اللي تقدر تعيده، و durable scheduler أو queue لما فقدان الشغل بعد restart غير مقبول.

// IOrderMaintenance is a scoped application service.
public sealed class OrderCleaner(IServiceScopeFactory scopes,
    ILogger<OrderCleaner> log) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(10)); // (1)
        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                try
                {
                    await using var scope = scopes.CreateAsyncScope(); // (2)
                    var work = scope.ServiceProvider
                        .GetRequiredService<IOrderMaintenance>();
                    await work.CleanAsync(stoppingToken);
                }
                catch (Exception ex) when (ex is not OperationCanceledException)
                {
                    log.LogError(ex, "Order cleanup failed");        // (3)
                }
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            // Normal shutdown.
        }
    }
}
// Register before Build:
// builder.Services.AddHostedService<OrderCleaner>();
🧾 شرح الكود
  1. (1) Loop واحدة بتنتظر tick؛ مفيش overlap من نفس loop. كل replica لسه هيشغّل نسخته.
  2. (2) Hosted service طويلة العمر، فتعمل scope لكل iteration بدل captive .
  3. (3) فشل iteration يتسجّل؛ tick الجاية فرصة تانية، مش durable retry policy.

Unhandled ExecuteAsync exception توقف host افتراضياً من 6. في .NET 10 ExecuteAsync كلها بتشتغل في الخلفية؛ ماتعتمدش على الجزء قبل أول await كـ startup barrier. Hangfire يوفر persisted jobs و retries و dashboard؛ Quartz مناسب scheduling ومع persistent store و clustering عند تهيئتهم. مفيش ضمان exactly-once للـ side effects؛ خلّي jobs .

  1. Publish Release كـ framework-dependent أو self-contained، وابنِ artifact ثابت للاختبار والترقية.
  2. في container استخدم للبناء و ASP.NET runtime للتشغيل؛ runtime و محتاجين patching.
  3. مرّر config و secrets لكل environment، واضبط trusted proxies و .
  4. شغّل migrations بخطوة متحكم فيها، واستخدم تغييرات backward-compatible مع rolling أو blue-green deployment.
  5. Readiness قبل traffic، و graceful shutdown و request draining؛ deployment strategy لوحدها مش ضمان zero downtime.

أسئلة الانترفيو

راجع فهمك

ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟

7.1Explain MVC and the responsibility of a controller.Junior

Model داتا وسلوك، View عرض، Controller تنسيق . مش كل rules في controller، ومش Model لازم entity.

الإجابة النموذجية (English)

I describe as separating the model's data and behavior, the view's presentation and the controller's request coordination. Routing selects an action, which binds input, calls application behavior and chooses a response. I keep business rules out of controllers so they can be tested and reused independently of . The model is not limited to an entity; a view model can contain exactly what a page needs without exposing persistence details.

7.2When would you choose controllers, Razor Pages or Minimal APIs?Junior

اختار حسب output و workflow. Controllers conventions و filters، Razor Pages للصفحات، Minimal handlers وتنظيم features؛ Blazor للـ component .

الإجابة النموذجية (English)

I use controllers when the application benefits from conventions, action filters or an existing controller organization. Razor Pages fits page-oriented workflows. Minimal suit endpoints organized around features with explicit route handlers and endpoint groups. Blazor addresses component-based rather than merely replacing an API style. I base the choice on team conventions, rendering needs and supported capabilities, and keep application logic separate so the hosting style does not dominate the design.

7.3What is Kestrel, and is it always behind IIS?Junior

Kestrel server cross-platform، مش محتاج . IIS in-process ليه server integration مختلف، و out-of-process بيعمل proxy لـ Kestrel.

الإجابة النموذجية (English)

Kestrel is 's cross-platform server. It can run directly or behind a such as Nginx, or . A proxy may centralize , routing and load balancing, but IIS is not required. On Windows I distinguish IIS in-process hosting, which uses the IIS server integration, from out-of-process hosting that forwards to Kestrel. I also configure forwarding trust so application code sees the correct client address and scheme.

7.4What is middleware and how does next work?Junior

سلسلة بترتيب التسجيل؛ قبل next في الدخول وبعده في الرجوع. ممكن short-circuit، فاللي بعدها مش هيتنفذ.

الإجابة النموذجية (English)

forms the processing pipeline. A component can inspect or change the request, call next to run downstream components, and do more work as the response unwinds. It can also short-circuit, for example by rejecting a request without calling next. Ordering therefore determines both what runs and what context is available. I place broad exception handling early enough to wrap the relevant components and authentication before authorization that needs the user identity.

7.5What is the difference between Use, Run and Map?Junior

Use ممكن يكمل، Run(handler) terminal، Map(path) branch. MapGet endpoint registration؛ app.Run() يبدأ host، مش .

الإجابة النموذجية (English)

I use Use for that may call the next component. Run with a request adds terminal middleware. Map with a path branches the pipeline for matching requests. I distinguish that branching from endpoint registration such as MapGet and MapControllers, which uses endpoint routing. There is also a separate parameterless app.Run call that starts the host. Mixing these meanings is a common reason an endpoint becomes unreachable behind an earlier terminal handler.

7.6How do conventional and attribute routing differ?Junior

Conventional pattern مركزي للـ ؛ attribute routes قريبة من actions. Constraints تختار match، مش validation response.

الإجابة النموذجية (English)

Conventional routing defines patterns centrally, such as controller, action and optional identifier, which works well for views. Attribute routing places route templates and methods on controllers and actions, making contracts explicit. Constraints such as int or guid narrow route matching. I do not use constraints as input validation: a failed constraint commonly means no matching endpoint and a 404, whereas an invalid bound value can produce a 400 response.

7.7Where can action parameters be bound from?Junior

Route و query و header و body و form و . خلي request واضح، وماتعملش bind للـ entity بكل properties.

الإجابة النموذجية (English)

Parameters can come from route values, the query string, headers, a body, form data or dependency injection. I use explicit source attributes when they clarify the contract and separate request from persistence entities to avoid overposting. ApiController can infer many sources, but I do not assume every framework style has identical rules. Minimal have their own binding conventions, including TryParse and BindAsync, and one request body should represent one coherent input model.

7.8How do IActionResult, ActionResult<T> and TypedResults differ?Junior

أول اتنين للـ ؛ generic يوضح success type. TypedResults للـ Minimal ، و Results union يقيّد الاختيارات ويساعد metadata.

الإجابة النموذجية (English)

IActionResult gives actions flexibility to return different result kinds. ActionResult of T combines a declared success payload with other action results. In Minimal , TypedResults returns concrete result types, and a Results union can declare the permitted alternatives, such as Ok of OrderDto and NotFound. That helps compile-time checking and OpenAPI metadata. I still document errors and headers deliberately, because a success type alone does not define the entire contract.

7.9Why does middleware order matter for routing, authentication and limiting?Mid

Routing يوفّر endpoint metadata، authentication يوفّر User. Limiter حسب user لازم بعد auth، و authorization بعد معرفة الهوية، و error handler يلف اللي محتاج يحميه.

الإجابة النموذجية (English)

Each depends on information established earlier. Routing selects endpoint metadata used by , authorization and endpoint-specific rate limits. Authentication establishes the user, so I place a user-partitioned limiter after it and authorization after identity is available. Exception handling needs to wrap the components whose failures it should handle. I make the order explicit when these dependencies matter, even though minimal hosting can insert some middleware automatically in simpler configurations.

7.10When should you use a filter instead of middleware?Mid

لو محتاج arguments و ModelState أو result بتاع action، filter مناسب. للـ pipeline؛ بعد routing ممكن تعرف endpoint metadata، مش bound arguments.

الإجابة النموذجية (English)

I use for -wide behavior such as timing, correlation and global error handling. I use an filter when the concern depends on action context, bound arguments, ModelState or action results. Middleware after routing can inspect endpoint metadata, so it is inaccurate to say it knows nothing about endpoints, but it does not automatically get bound action arguments. For Minimal , endpoint filters provide a separate handler-level mechanism rather than MVC action filters.

7.11How does validation differ between API controllers and .NET 10 Minimal APIs?Mid

ApiController عنده automatic 400 للـ invalid ModelState. Minimal في 10 فيها AddValidation مع generated metadata؛ من غير التسجيل مش نفترض annotations هتشتغل.

الإجابة النموذجية (English)

With ApiController, binding and model validation populate ModelState, and invalid input normally produces an automatic 400 response. 10 Minimal can enable built-in validation through AddValidation and the validation package, using generated metadata for supported parameter types. I verify discovery when models live in other assemblies. For complex asynchronous rules I explicitly integrate a validator such as FluentValidation. In both cases, request validation does not replace transactional enforcement of business .

7.12What are the three DI lifetimes, and what is a captive dependency?Mid

Transient لكل resolve، scoped لكل scope، singleton لكل root provider. Captive لما طويل العمر يحتفظ بقصير العمر، زي singleton و .

الإجابة النموذجية (English)

Transient creates an instance per resolution, scoped shares one within a scope, and singleton shares one within the root service provider. A request normally supplies a scope. A captive dependency occurs when a longer-lived service retains something intended to be shorter-lived, such as a singleton capturing a scoped . That can share unsafe state across requests. I enable scope validation and create explicit scopes for singleton background units of work, keeping scoped objects inside those scopes.

7.13What do keyed services solve?Mid

اختيار implementation بنفس interface بمفتاح من 8؛ ينفع injection معلن أو factory في boundary. ماتحوّلش كل business logic لـ service locator.

الإجابة النموذجية (English)

Keyed services let the container register multiple implementations under one interface with distinct keys, available from 8. A payment endpoint can request the card provider explicitly without ambiguous registration. When the key comes from runtime input, I usually put selection behind a small factory or strategy boundary instead of injecting IServiceProvider everywhere. I still validate the allowed key and respect the registered lifetime; keys do not change ownership or make a provider thread-safe.

7.14How do configuration precedence and environments work?Mid

Default providers بترتيب، والأخير يكسب لنفس key؛ environment يختار appsettings file وسلوك dev. User Secrets مش vault مشفر، و custom providers مكانهم في الترتيب مهم.

الإجابة النموذجية (English)

Default application configuration layers the base settings file, environment-specific settings, development User Secrets, environment variables and command-line arguments, with later values overriding earlier ones. Added providers participate according to registration order. The environment name selects files and development behavior, but is not a security boundary. I avoid conflicting environment variables and keep secrets outside source control. User Secrets are a development convenience rather than an encrypted production vault, so production needs an appropriate secret-management mechanism.

7.15When would you use IOptions, IOptionsSnapshot or IOptionsMonitor?Mid

Options قيمة cached، Snapshot scoped، Monitor singleton مع updates لو provider بيدعم reload. ValidateOnStart يكشف config الغلط بدري.

الإجابة النموذجية (English)

I use IOptions for stable cached configuration, IOptionsSnapshot for a value computed on demand within each scope, and IOptionsMonitor for a singleton-friendly current value with change notifications. Reload depends on the underlying provider; it is not guaranteed for every source. Snapshot is scoped and should not be captured by a singleton. I bind a typed options class and use validation with ValidateOnStart so missing keys or invalid timeouts fail during startup rather than during a payment.

7.16How do you handle unexpected errors without leaking internals?Mid

IExceptionHandler و UseExceptionHandler مع ProblemDetails؛ log داخلي و traceId. Expected not found ممكن result، وماتحاولش تعيد كتابة response بدأ.

الإجابة النموذجية (English)

I centralize unexpected failures with IExceptionHandler and exception-handler , returning a ProblemDetails response with a stable message and identifier. Detailed exceptions stay in internal logs. Expected outcomes such as a missing resource usually return explicit results instead of exceptions. I handle cancellation according to its context and remember that an already-started response cannot always be rewritten. In 10, handled exceptions suppress default diagnostics, so I choose application logging deliberately to avoid missing or duplicate records.

7.17What is CORS preflight, and does CORS secure an API?Mid

Browser يبعت OPTIONS يتأكد من origin و method و headers. يحدد قراءة response من JS، مش authorization ولا منع curl ولا protection.

الإجابة النموذجية (English)

A browser sends a preflight OPTIONS request for cross-origin requests that need permission for their method or headers, such as requests with Authorization. answers according to the allowed origins, methods and headers. It is a browser-enforced sharing policy, not server authentication, authorization or protection, and tools such as curl do not enforce it. I allow explicit origins and never combine wildcard origins with credentialed access.

7.18Why use IHttpClientFactory, and what is a valid alternative?Mid

Factory يدير pooled handlers ويجمع config. البديل client طويل العمر مع PooledConnectionLifetime؛ new handler لكل call هو اللي يضيّع reuse.

الإجابة النموذجية (English)

IHttpClientFactory centralizes configuration and pools handlers so short-lived clients can reuse connections without rebuilding a connection pool for every call. Typed clients also give a focused application boundary. A long-lived HttpClient using SocketsHttpHandler with a suitable PooledConnectionLifetime is a valid alternative. I avoid claiming that every static client is wrong. I check refresh, cancellation, timeouts and lifetime capture, especially when a singleton holds a typed client intended to be short-lived.

7.19How do you safely configure retries for outbound HTTP calls?Senior

Retry لخطأ transient بس وبحدود budget، ومع . Timeout مش دليل إن الدفع ماحصلش، و retries على replicas كتير تزود overload.

الإجابة النموذجية (English)

I define a total time budget, per-attempt limits, bounded retries and which failures are transient. Backoff and jitter reduce synchronized retry bursts, while a circuit breaker limits repeated calls to an unhealthy dependency. I disable retries for unsafe operations unless an contract makes repetition safe. A timeout does not prove a payment failed; the response may simply have been lost. I also prevent stacked retry layers from multiplying attempts and verify behavior under dependency failure.

7.20What are the limits of in-process rate limiting?Senior

Quota حسب instance و partition key، مش global تلقائي. User partitioning بعد auth؛ forwarded لازم موثوق؛ gateway للـ distributed limits.

الإجابة النموذجية (English)

An in-process limiter enforces quotas within that application instance. With several replicas, a client can receive separate allowances unless a shared gateway or distributed mechanism coordinates them. I choose partition keys deliberately, authenticate before user-based partitioning and trust forwarded addresses only from configured proxies. I return an appropriate rejection response and consider queueing and retry guidance. Rate limiting protects capacity, but it does not replace authentication or upstream protection against large-scale traffic attacks.

7.21How do liveness and readiness checks differ?Senior

Liveness هل process حي؛ readiness هل يستقبل traffic. Dependency outage يخلي instance unready غالباً، مش نعيد تشغيل كل instances في loop.

الإجابة النموذجية (English)

Liveness answers whether the host is responsive enough to keep running; readiness answers whether it should receive traffic. I avoid making every dependency outage fail liveness because that can restart healthy processes continuously. Readiness checks include only dependencies needed for serving the intended traffic, with bounded checks and limited public detail. I also remember that AddHealthChecks alone does not inspect a database: real checks must be registered and selected for the appropriate endpoint.

7.22When would you choose BackgroundService, Hangfire or Quartz?Senior

BackgroundService loop داخل process؛ Hangfire persisted jobs و retries؛ Quartz scheduling مع storage و clustering حسب config. Jobs لازم مهما كانت الأداة.

الإجابة النموذجية (English)

I use BackgroundService for simple in-process loops whose work can be repeated or recovered elsewhere. I create a scope per unit of work and honor shutdown cancellation. For durable queued jobs with retries and operational visibility, Hangfire can fit. Quartz suits scheduling requirements, with persistence and clustering when configured. I do not equate either tool with exactly-once side effects. Jobs need and recovery because a crash can occur after an external effect but before completion is recorded.

7.23What changed for BackgroundService execution in .NET 10?Senior

ExecuteAsync كلها بقت background؛ قبل كده الجزء قبل أول await كان ممكن يحجز startup. Startup prerequisite لازم mechanism صريح.

الإجابة النموذجية (English)

In 10, the entire ExecuteAsync method runs in the background. I cannot rely on code before its first await to block startup as it could previously. If initialization must finish before requests are accepted, I use an explicit startup mechanism and readiness policy instead of an incidental scheduling behavior. I still handle operational failures deliberately, create scopes for scoped dependencies and honor cancellation; background execution does not make jobs durable or exceptions harmless.

7.24What makes an ASP.NET Core deployment safe for rolling upgrades?Senior

Artifact ثابت، config منفصل، readiness و draining، و compatible مع نسختين في نفس الوقت. Migrations خطوة متحكم فيها مش race بين replicas.

الإجابة النموذجية (English)

I promote the same tested artifact, inject environment-specific configuration and secrets, and verify runtime and patching responsibilities. Readiness gates traffic and graceful shutdown drains active work. The important constraint is compatibility while old and new versions overlap, including changes, messages and contracts. I run migrations as a controlled deployment step and use expand-and-contract changes when needed. Rolling or blue-green deployment helps availability, but without compatibility and recovery planning it cannot guarantee zero downtime.

اختبر نفسك

إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.

النتيجة: 0 / 0

Which server receives requests in IIS in-process hosting?

out-of-process هو اللي بيعمل proxy لـ Kestrel.

Where should a user-partitioned endpoint rate limiter run?

محتاج endpoint metadata والـ User.

A request does not match {id:int} because its segment is abc. Likely result?

Route constraint بيحدد matching، مش input error formatting.

Which .NET 10 registration enables built-in Minimal API validation?

Annotations محتاجة pipeline و metadata مناسبين.

A singleton directly retains a scoped DbContext. What is the problem?

Scope لكل unit of work يمنع الاحتفاظ بالـ context طول عمر الخدمة.

Which options abstraction is scoped?

Snapshot مش مناسبة للـ singleton constructor.

What does CORS prevent by itself?

Authentication و authorization و defenses ليهم أدوار منفصلة.

What does returning true from IExceptionHandler mean?

في 10 handled exceptions diagnostics بتتسكت افتراضياً.

Which claim about HTTP retry is correct?

الرد ممكن يضيع بعد نجاح العملية.

What should a readiness endpoint with no registered dependency checks imply?

AddHealthChecks لوحدها مش بتضيف database checks.

What is required for scoped services inside BackgroundService?

Hosted service طويلة العمر، لكن dependencies ممكن تبقى scoped.

Which statement about durable schedulers is correct?

Crash بين side effect وتسجيل النجاح ممكن يعمل retry لنفس الشغل.