[{"data":1,"prerenderedAt":260},["ShallowReactive",2],{"topic-dotnet-performance-deployment":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Fdotnet.yml",".NET Core interview questions on the CLR, C# language features, ASP.NET Core, dependency injection, Entity Framework, and deployment — the backbone of modern Microsoft-stack backend development.","yml","dotnet",{},".NET Core",7,"frameworks\u002Fdotnet",1,"_nP5kVwZOMUX9nd4gS5Ozj5Q6zQOEmGtLONE3ue01Gw",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Fdotnet-performance-deployment.yml","Production .NET — caching, structured logging, observability, and deploying ASP.NET Core to Docker and Kubernetes. The operational topics senior roles increasingly test.",{},"Performance & Deployment",8,"performance-deployment","topics\u002Fdotnet-performance-deployment","-plFvM7McZ3X26AbMSfdSSX0w1iRI9rj4PlH6dOeMaI",[25,110,185],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":37,"navigation":38,"order":13,"path":39,"questions":40,"questionsCount":103,"related":104,"seo":105,"seoDescription":106,"stem":107,"subtopic":27,"topic":19,"topicSlug":21,"updated":108,"__hash__":109},"qa\u002Fdotnet\u002Fperformance-deployment\u002Fcaching.md","Caching",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"medium","md",{},true,"\u002Fdotnet\u002Fperformance-deployment\u002Fcaching",[41,46,50,54,58,62,66,70,74,79,83,87,91,95,99],{"id":42,"difficulty":43,"q":44,"a":45},"why-cache","easy","Why is caching important in ASP.NET Core applications?","Caching stores the result of an expensive operation so subsequent requests can\nbe served from fast memory instead of re-computing or re-querying. It reduces\nlatency, cuts database load, and improves throughput under high concurrency.\n\n```csharp\n\u002F\u002F Without caching — every request hits the database:\npublic async Task\u003CIEnumerable\u003CProduct>> GetProductsAsync()\n    => await _db.Products.ToListAsync(); \u002F\u002F ~50 ms per call\n\n\u002F\u002F With IMemoryCache — first call populates, subsequent calls \u003C 1 ms:\npublic async Task\u003CIEnumerable\u003CProduct>> GetProductsAsync()\n{\n    if (_cache.TryGetValue(\"products:all\", out IEnumerable\u003CProduct>? cached))\n        return cached!;\n\n    var products = await _db.Products.ToListAsync();\n\n    _cache.Set(\"products:all\", products, TimeSpan.FromMinutes(5));\n    return products;\n}\n```\n\nThe three canonical reasons to cache:\n1. **Expensive queries** — database joins, aggregations, full-text search\n2. **External API calls** — rate-limited or slow third-party services\n3. **Computed results** — serialization, report generation, complex calculations\n\n**Rule of thumb:** Cache data that is read far more often than it changes and\nwhere a slightly stale value is acceptable. Never cache security-sensitive data\n(session tokens, permissions) without careful thought about invalidation.\n",{"id":47,"difficulty":43,"q":48,"a":49},"imemorycache","How do you use IMemoryCache in ASP.NET Core?","`IMemoryCache` is an in-process, per-server cache backed by `ConcurrentDictionary`.\nIt is registered by default in ASP.NET Core and injected like any service.\n\n```csharp\n\u002F\u002F Registration (already included in most default templates):\nbuilder.Services.AddMemoryCache();\n\n\u002F\u002F Injection and usage:\npublic class ProductService\n{\n    private readonly IMemoryCache   _cache;\n    private readonly AppDbContext   _db;\n\n    public ProductService(IMemoryCache cache, AppDbContext db)\n    {\n        _cache = cache;\n        _db    = db;\n    }\n\n    public async Task\u003CProduct?> GetByIdAsync(int id)\n    {\n        var key = $\"product:{id}\";\n\n        \u002F\u002F GetOrCreateAsync — atomic \"get or populate\" in one call:\n        return await _cache.GetOrCreateAsync(key, async entry =>\n        {\n            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);\n            entry.SlidingExpiration               = TimeSpan.FromMinutes(2);\n            entry.Priority                        = CacheItemPriority.Normal;\n\n            return await _db.Products.FindAsync(id);\n        });\n    }\n\n    public void Invalidate(int id) => _cache.Remove($\"product:{id}\");\n}\n```\n\nKey options:\n- `AbsoluteExpirationRelativeToNow` — evict after a fixed duration from insertion\n- `SlidingExpiration` — evict if not accessed for this duration (reset on each access)\n- `Priority` — controls eviction order under memory pressure (`Low` evicted first)\n\n**Rule of thumb:** `IMemoryCache` is ideal for single-server deployments and data\nthat fits in process memory. For multi-server deployments use `IDistributedCache`\n(Redis) so all instances share the same cache.\n",{"id":51,"difficulty":35,"q":52,"a":53},"idistributedcache","What is IDistributedCache and how does it differ from IMemoryCache?","`IDistributedCache` is an abstraction for a shared, out-of-process cache.\nMultiple application instances read and write the same cache store, so there\nis no inconsistency between servers. Redis and SQL Server are the most common\nimplementations.\n\n```csharp\n\u002F\u002F Registration — Redis:\nbuilder.Services.AddStackExchangeRedisCache(opts =>\n{\n    opts.Configuration = builder.Configuration.GetConnectionString(\"Redis\");\n    opts.InstanceName  = \"myapp:\";\n});\n\n\u002F\u002F Or in-memory (for dev\u002Ftest, single process only):\nbuilder.Services.AddDistributedMemoryCache();\n\n\u002F\u002F Usage — IDistributedCache works with byte[] or string:\npublic class ProductService\n{\n    private readonly IDistributedCache _cache;\n\n    public async Task\u003CProduct?> GetByIdAsync(int id)\n    {\n        var key  = $\"product:{id}\";\n        var json = await _cache.GetStringAsync(key);\n\n        if (json is not null)\n            return JsonSerializer.Deserialize\u003CProduct>(json);\n\n        var product = await _db.Products.FindAsync(id);\n        if (product is null) return null;\n\n        await _cache.SetStringAsync(key,\n            JsonSerializer.Serialize(product),\n            new DistributedCacheEntryOptions\n            {\n                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),\n                SlidingExpiration               = TimeSpan.FromMinutes(2),\n            });\n\n        return product;\n    }\n\n    public async Task InvalidateAsync(int id)\n        => await _cache.RemoveAsync($\"product:{id}\");\n}\n```\n\n| | IMemoryCache | IDistributedCache |\n|-|--------------|-------------------|\n| Scope | Single process | All instances |\n| Speed | \u003C 1 ms | ~1–5 ms (network) |\n| Capacity | Limited by RAM | Redis cluster capacity |\n| Consistency | Per-server | Shared |\n\n**Rule of thumb:** Use `IMemoryCache` on single-server deployments or for\ntransient per-request data. Use `IDistributedCache` (Redis) whenever you run\nmore than one application instance.\n",{"id":55,"difficulty":35,"q":56,"a":57},"cache-aside-pattern","What is the cache-aside pattern and how is it implemented in .NET?","**Cache-aside** (lazy loading) means the application manages the cache\nexplicitly: check the cache first, fetch from the source on a miss, write\nback to the cache, then return. The cache is a side structure that the\napplication populates on demand.\n\n```csharp\n\u002F\u002F Generic cache-aside helper — eliminates repeated boilerplate:\npublic static class CacheExtensions\n{\n    public static async Task\u003CT?> GetOrSetAsync\u003CT>(\n        this IDistributedCache cache,\n        string key,\n        Func\u003CTask\u003CT?>> factory,\n        TimeSpan expiry)\n    {\n        var json = await cache.GetStringAsync(key);\n        if (json is not null)\n            return JsonSerializer.Deserialize\u003CT>(json);\n\n        var value = await factory();     \u002F\u002F 1. MISS — call the source\n        if (value is not null)\n            await cache.SetStringAsync(  \u002F\u002F 2. POPULATE the cache\n                key,\n                JsonSerializer.Serialize(value),\n                new DistributedCacheEntryOptions\n                    { AbsoluteExpirationRelativeToNow = expiry });\n\n        return value;                    \u002F\u002F 3. RETURN to caller\n    }\n}\n\n\u002F\u002F Call site is now one line:\nvar product = await _cache.GetOrSetAsync(\n    $\"product:{id}\",\n    () => _db.Products.FindAsync(id).AsTask(),\n    TimeSpan.FromMinutes(10));\n\n\u002F\u002F On write — invalidate the cache entry so the next read is fresh:\nawait _db.SaveChangesAsync();\nawait _cache.RemoveAsync($\"product:{id}\");\n```\n\nThe alternative is **write-through**: update the cache at the same time as\nthe source. Write-through keeps the cache warm but adds write latency and\ncomplexity when the write fails.\n\n**Rule of thumb:** Use cache-aside when reads are much more frequent than\nwrites and a brief window of stale data is acceptable. Use write-through\nfor write-heavy workloads where staleness is not tolerable.\n",{"id":59,"difficulty":35,"q":60,"a":61},"response-caching","What is response caching in ASP.NET Core and how do you enable it?","**Response caching** caches the full HTTP response at the middleware level,\nkeyed by URL and `Vary` headers. It avoids running the controller and service\nlayer entirely on cache hits.\n\n```csharp\n\u002F\u002F Program.cs:\nbuilder.Services.AddResponseCaching();\n\u002F\u002F ...\napp.UseResponseCaching(); \u002F\u002F must come before routing \u002F endpoints\n\n\u002F\u002F Controller or action — [ResponseCache] sets the Cache-Control header:\n[HttpGet(\"products\")]\n[ResponseCache(Duration = 60, VaryByQueryKeys = new[] { \"page\", \"size\" })]\npublic async Task\u003CIActionResult> GetProducts(int page = 1, int size = 20)\n{\n    var products = await _productService.GetPageAsync(page, size);\n    return Ok(products);\n}\n\n\u002F\u002F Equivalent header set by the above:\n\u002F\u002F Cache-Control: public, max-age=60\n\u002F\u002F Vary: page, size\n\n\u002F\u002F Profile — reuse settings across multiple actions:\nbuilder.Services.AddControllersWithViews(opts =>\n{\n    opts.CacheProfiles.Add(\"5MinutePublic\", new CacheProfile\n    {\n        Duration = 300,\n        Location = ResponseCacheLocation.Any,\n    });\n});\n\n[ResponseCache(CacheProfileName = \"5MinutePublic\")]\npublic IActionResult StaticData() => Ok(_staticData);\n```\n\nResponse caching only works for GET\u002FHEAD requests, non-authenticated responses,\nand when no `Authorization` header is present. It relies on the client or a\nproxy honouring `Cache-Control`.\n\n**Rule of thumb:** Use response caching for public, read-only endpoints that\nreturn the same data for all users. For per-user or authenticated responses,\nuse `IMemoryCache` or `IDistributedCache` at the service layer instead.\n",{"id":63,"difficulty":35,"q":64,"a":65},"output-caching","What is output caching in .NET 7+ and how does it differ from response caching?","**Output caching** (introduced in .NET 7) caches the rendered endpoint output\non the server. Unlike response caching, it is server-controlled and works\nregardless of client `Cache-Control` headers or `Authorization` headers.\n\n```csharp\n\u002F\u002F Program.cs:\nbuilder.Services.AddOutputCache(opts =>\n{\n    \u002F\u002F Named policy — reuse across multiple endpoints:\n    opts.AddPolicy(\"5min\", policy => policy.Expire(TimeSpan.FromMinutes(5)));\n\n    \u002F\u002F Base policy — applies to all endpoints unless overridden:\n    opts.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(60)));\n});\n\napp.UseOutputCache(); \u002F\u002F after UseRouting, before endpoints\n\n\u002F\u002F Apply to a minimal API endpoint:\napp.MapGet(\"\u002Fproducts\", async (AppDbContext db) => await db.Products.ToListAsync())\n   .CacheOutput(\"5min\");\n\n\u002F\u002F Apply to a controller action:\n[HttpGet]\n[OutputCache(PolicyName = \"5min\")]\npublic async Task\u003CIActionResult> GetProducts()\n    => Ok(await _service.GetAllAsync());\n\n\u002F\u002F Vary by query string parameter:\nopts.AddPolicy(\"ByPage\", policy =>\n    policy.SetVaryByQuery(\"page\", \"size\").Expire(TimeSpan.FromMinutes(5)));\n\n\u002F\u002F Tag-based invalidation — evict cache entries by tag:\n[HttpPost]\npublic async Task\u003CIActionResult> UpdateProduct(Product p)\n{\n    await _service.UpdateAsync(p);\n    await _outputCacheStore.EvictByTagAsync(\"products\", ct); \u002F\u002F invalidate\n    return NoContent();\n}\n```\n\nKey difference from response caching: output caching caches on the server\nand can cache authenticated responses and vary on any request property (headers,\nroute values, custom values). Response caching is HTTP cache semantics delegated\nto the client or proxy.\n\n**Rule of thumb:** Prefer output caching in .NET 7+ — it gives you server-side\ncontrol, tag-based invalidation, and works with authenticated endpoints. Use\nresponse caching only when you need standard HTTP cache semantics for CDN or\nproxy caching.\n",{"id":67,"difficulty":35,"q":68,"a":69},"cache-invalidation","What are the main strategies for cache invalidation in .NET?","Cache invalidation is famously hard because stale data causes correctness bugs\nwhile over-invalidation erases all performance benefit.\n\n```csharp\n\u002F\u002F Strategy 1: TTL (Time-To-Live) expiry — simplest, always stale briefly\n_cache.Set(\"products:all\", products,\n    new MemoryCacheEntryOptions\n    {\n        AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),\n    });\n\n\u002F\u002F Strategy 2: Explicit removal on write — fresh immediately after mutation\npublic async Task UpdateProductAsync(Product product)\n{\n    await _db.SaveChangesAsync();\n    _cache.Remove($\"product:{product.Id}\");   \u002F\u002F evict specific entry\n    _cache.Remove(\"products:all\");              \u002F\u002F evict collection\n}\n\n\u002F\u002F Strategy 3: Cache tags (output cache) — evict by semantic group\napp.MapPut(\"\u002Fproducts\u002F{id}\", async (int id, Product p, IOutputCacheStore store, CancellationToken ct) =>\n{\n    await _db.SaveChangesAsync();\n    await store.EvictByTagAsync(\"products\", ct); \u002F\u002F evicts all \"products\"-tagged entries\n});\napp.MapGet(\"\u002Fproducts\", GetAll).CacheOutput(p => p.Tag(\"products\"));\napp.MapGet(\"\u002Fproducts\u002F{id}\", GetOne).CacheOutput(p => p.Tag(\"products\"));\n\n\u002F\u002F Strategy 4: CancellationTokenSource — invalidate a group via token\nprivate CancellationTokenSource _productsCts = new();\n\n_cache.Set(\"products:all\", data, new MemoryCacheEntryOptions()\n    .AddExpirationToken(new CancellationChangeToken(_productsCts.Token)));\n\npublic void InvalidateAll()\n{\n    _productsCts.Cancel();\n    _productsCts = new CancellationTokenSource(); \u002F\u002F reset for next group\n}\n```\n\n**Rule of thumb:** Use TTL for data that can tolerate brief staleness (catalogs,\nreference data). Use explicit removal for data where staleness causes bugs\n(user profiles, inventory counts). Use tag-based eviction with output cache\nfor groups of related entries.\n",{"id":71,"difficulty":35,"q":72,"a":73},"redis-in-dotnet","How do you connect to Redis in .NET and what is it used for beyond caching?","**StackExchange.Redis** is the standard Redis client for .NET. Beyond\n`IDistributedCache`, it supports pub\u002Fsub, sorted sets, Lua scripting, and\ndistributed locks — patterns that go well beyond simple key\u002Fvalue caching.\n\n```csharp\n\u002F\u002F Install:\n\u002F\u002F dotnet add package StackExchange.Redis\n\u002F\u002F dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis\n\n\u002F\u002F Program.cs — register both the cache and the raw connection:\nbuilder.Services.AddStackExchangeRedisCache(opts =>\n    opts.Configuration = \"localhost:6379,password=secret,abortConnect=false\");\n\n\u002F\u002F Direct access via IConnectionMultiplexer (for pub\u002Fsub, sets, etc.):\nbuilder.Services.AddSingleton\u003CIConnectionMultiplexer>(\n    ConnectionMultiplexer.Connect(\"localhost:6379\"));\n\n\u002F\u002F Distributed lock (prevents cache stampede):\npublic class InventoryService\n{\n    private readonly IConnectionMultiplexer _redis;\n\n    public async Task\u003Cint> GetStockAsync(string sku)\n    {\n        var db  = _redis.GetDatabase();\n        var key = $\"stock:{sku}\";\n\n        \u002F\u002F Try to acquire a lock so only one thread recomputes on a miss:\n        var lockKey   = $\"lock:{key}\";\n        var lockValue = Guid.NewGuid().ToString();\n        var acquired  = await db.StringSetAsync(lockKey, lockValue,\n            TimeSpan.FromSeconds(5), When.NotExists);\n\n        if (!acquired)\n        {\n            await Task.Delay(50);        \u002F\u002F wait and retry\n            return await GetStockAsync(sku);\n        }\n\n        try\n        {\n            var cached = await db.StringGetAsync(key);\n            if (cached.HasValue) return (int)cached;\n\n            var stock = await _db.Inventory.Where(i => i.Sku == sku).SumAsync(i => i.Qty);\n            await db.StringSetAsync(key, stock, TimeSpan.FromMinutes(1));\n            return stock;\n        }\n        finally { await db.KeyDeleteAsync(lockKey); }\n    }\n}\n```\n\nRedis use cases beyond caching: rate limiting (sliding window counters),\nsession storage, real-time leaderboards (sorted sets), pub\u002Fsub message fan-out,\nand distributed work queues.\n\n**Rule of thumb:** Use `IDistributedCache` for standard caching and configuration-\nswappable behavior. Use `IConnectionMultiplexer` directly only when you need\nRedis-specific data structures or pub\u002Fsub.\n",{"id":75,"difficulty":76,"q":77,"a":78},"cache-stampede","hard","What is a cache stampede and how do you prevent it in .NET?","A **cache stampede** (also called \"thundering herd\") occurs when a popular cache\nentry expires and many concurrent requests all miss at the same time, all racing\nto rebuild it from the slow data source simultaneously.\n\n```csharp\n\u002F\u002F Problem — naive cache-aside under load:\n\u002F\u002F 1000 concurrent requests all miss → 1000 DB queries fire simultaneously\nif (!_cache.TryGetValue(key, out var value))\n{\n    value = await _db.Products.ToListAsync(); \u002F\u002F all 1000 threads hit here!\n    _cache.Set(key, value, TimeSpan.FromMinutes(5));\n}\n\n\u002F\u002F Solution 1: SemaphoreSlim — only one thread rebuilds, others wait:\nprivate static readonly SemaphoreSlim _lock = new(1, 1);\n\npublic async Task\u003CIEnumerable\u003CProduct>> GetProductsAsync()\n{\n    if (_cache.TryGetValue(\"products\", out IEnumerable\u003CProduct>? cached))\n        return cached!;\n\n    await _lock.WaitAsync();\n    try\n    {\n        \u002F\u002F Double-check after acquiring — another thread may have populated it:\n        if (_cache.TryGetValue(\"products\", out cached))\n            return cached!;\n\n        cached = await _db.Products.ToListAsync();\n        _cache.Set(\"products\", cached, TimeSpan.FromMinutes(5));\n        return cached;\n    }\n    finally { _lock.Release(); }\n}\n\n\u002F\u002F Solution 2: Lazy\u003CTask\u003CT>> per key — collapses concurrent misses into one op:\nprivate readonly ConcurrentDictionary\u003Cstring, Lazy\u003CTask\u003CIEnumerable\u003CProduct>>>> _locks = new();\n\npublic Task\u003CIEnumerable\u003CProduct>> GetProductsAsync()\n    => _locks.GetOrAdd(\"products\", _ => new Lazy\u003CTask\u003CIEnumerable\u003CProduct>>>(\n        () => _db.Products.ToListAsync())).Value;\n\n\u002F\u002F Solution 3: Probabilistic early refresh (background repopulation before expiry):\nentry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration\n{\n    EvictionCallback = (k, v, r, s) =>\n    {\n        if (r == EvictionReason.Expired)\n            _ = Task.Run(() => RefreshCacheAsync(k.ToString()!));\n    }\n});\n```\n\n**Rule of thumb:** Use `SemaphoreSlim` with a double-check pattern for\nhigh-traffic endpoints. Background refresh is the most sophisticated approach\nbut adds complexity — reach for it only when measured latency spikes justify it.\n",{"id":80,"difficulty":35,"q":81,"a":82},"icacheentry-size","How do you limit memory usage in IMemoryCache?","By default, `IMemoryCache` has no size limit and can grow unbounded. You can\nset a size limit and assign a size cost to each entry to cap memory usage.\n\n```csharp\n\u002F\u002F Registration with size limit (units are arbitrary — just needs to be consistent):\nbuilder.Services.AddMemoryCache(opts =>\n{\n    opts.SizeLimit = 1024; \u002F\u002F max 1024 \"units\"\n});\n\n\u002F\u002F Each entry declares its size cost:\n_cache.Set(\"small-lookup\", value, new MemoryCacheEntryOptions\n{\n    Size     = 1,                                          \u002F\u002F costs 1 unit\n    Priority = CacheItemPriority.High,                     \u002F\u002F last to be evicted\n    AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),\n});\n\n_cache.Set(\"large-dataset\", bigList, new MemoryCacheEntryOptions\n{\n    Size     = 50,                                         \u002F\u002F costs 50 units\n    Priority = CacheItemPriority.Low,                      \u002F\u002F evicted first under pressure\n    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),\n});\n\n\u002F\u002F Compact — manually free a percentage of cache:\nif (_cache is MemoryCache mc)\n    mc.Compact(0.25); \u002F\u002F evict 25% of items by priority\u002Fsize\u002Fexpiry\n\n\u002F\u002F Monitor cache stats:\nvar stats = mc.GetCurrentStatistics();\nConsole.WriteLine($\"Entries: {stats?.CurrentEntryCount}, \" +\n                  $\"Size: {stats?.CurrentEstimatedSize}\");\n```\n\nWithout `SizeLimit`, setting `Size` on entries has no effect. Both must be\nconfigured together.\n\n**Rule of thumb:** Set `SizeLimit` whenever cached entries vary significantly\nin size (e.g., large JSON blobs vs small integers). Without it, a burst of\nlarge entries can exhaust process memory silently.\n",{"id":84,"difficulty":35,"q":85,"a":86},"session-caching","How does ASP.NET Core session storage relate to caching, and how do you configure it?","ASP.NET Core **session** stores per-user state across requests using a session\ncookie. The session data is backed by `IDistributedCache`, so Redis or SQL\nServer can be used as the session store.\n\n```csharp\n\u002F\u002F Program.cs:\nbuilder.Services.AddDistributedMemoryCache(); \u002F\u002F or AddStackExchangeRedisCache\nbuilder.Services.AddSession(opts =>\n{\n    opts.IdleTimeout        = TimeSpan.FromMinutes(20); \u002F\u002F inactivity expiry\n    opts.Cookie.HttpOnly    = true;\n    opts.Cookie.IsEssential = true;   \u002F\u002F GDPR: always set the cookie\n    opts.Cookie.SecurePolicy = CookieSecurePolicy.Always;\n});\n\napp.UseSession(); \u002F\u002F after UseRouting, before endpoints\n\n\u002F\u002F Reading and writing session values:\napp.MapGet(\"\u002Fcart\u002Fcount\", (HttpContext ctx) =>\n{\n    var count = ctx.Session.GetInt32(\"CartCount\") ?? 0;\n    return Results.Ok(count);\n});\n\napp.MapPost(\"\u002Fcart\u002Fadd\", (HttpContext ctx) =>\n{\n    var count = (ctx.Session.GetInt32(\"CartCount\") ?? 0) + 1;\n    ctx.Session.SetInt32(\"CartCount\", count);\n    return Results.Ok(count);\n});\n\n\u002F\u002F Complex objects — serialize to JSON manually:\nctx.Session.SetString(\"Cart\", JsonSerializer.Serialize(cartItems));\nvar cart = JsonSerializer.Deserialize\u003CList\u003CCartItem>>(\n    ctx.Session.GetString(\"Cart\") ?? \"[]\");\n```\n\nSession is backed by `IDistributedCache` — switching from in-memory to Redis\nchanges only the `AddDistributedMemoryCache()` call, not the session code.\n\n**Rule of thumb:** Use session for lightweight per-user transient state (cart\nitem count, wizard step). Store larger or durable user data in a database\nkeyed by user ID, not in session.\n",{"id":88,"difficulty":76,"q":89,"a":90},"hybrid-cache","What is HybridCache in .NET 9 and why was it introduced?","**HybridCache** (introduced in .NET 9, available as a NuGet package for .NET 8)\ncombines a fast in-process L1 cache (`IMemoryCache`) with a shared L2 cache\n(`IDistributedCache` \u002F Redis). A cache hit serves from L1 in \u003C 1 ms; on L1\nmiss it checks L2 before falling back to the data source.\n\n```csharp\n\u002F\u002F Installation:\n\u002F\u002F dotnet add package Microsoft.Extensions.Caching.Hybrid\n\n\u002F\u002F Registration:\nbuilder.Services.AddHybridCache(opts =>\n{\n    opts.MaximumPayloadBytes         = 1024 * 1024; \u002F\u002F 1 MB max per entry\n    opts.DefaultEntryOptions = new HybridCacheEntryOptions\n    {\n        Expiration         = TimeSpan.FromMinutes(5),\n        LocalCacheExpiration = TimeSpan.FromMinutes(1), \u002F\u002F L1 evicts sooner\n    };\n});\n\n\u002F\u002F Usage — GetOrCreateAsync handles L1 hit, L2 hit, and source fetch:\npublic class ProductService\n{\n    private readonly HybridCache _cache;\n\n    public async ValueTask\u003CProduct?> GetByIdAsync(int id, CancellationToken ct)\n        => await _cache.GetOrCreateAsync(\n            $\"product:{id}\",\n            async cancel => await _db.Products.FindAsync(new object[] { id }, cancel),\n            cancellationToken: ct);\n\n    public async Task InvalidateAsync(int id)\n        => await _cache.RemoveAsync($\"product:{id}\");\n}\n```\n\nKey benefits over manual L1+L2 wiring:\n- **Stampede protection built in** — only one call to the factory per missing key\n- **Serialization handled** — no manual `JsonSerializer.Serialize` per call\n- **Tag-based invalidation** — `RemoveByTagAsync(\"products\")` evicts from both levels\n\n**Rule of thumb:** In .NET 9+, prefer `HybridCache` over manual `IMemoryCache` +\n`IDistributedCache` wiring. It solves stampede, serialization, and L1\u002FL2 sync\nin one abstraction.\n",{"id":92,"difficulty":76,"q":93,"a":94},"benchmark-cache","How do you measure the impact of caching on ASP.NET Core application performance?","Measuring cache effectiveness requires both micro-benchmarks (to measure the\ncache hit path) and load tests (to measure real-world throughput improvements).\n\n```csharp\n\u002F\u002F BenchmarkDotNet micro-benchmark — compare cached vs uncached:\n\u002F\u002F dotnet add package BenchmarkDotNet\n[MemoryDiagnoser]\npublic class ProductBenchmarks\n{\n    private readonly IMemoryCache  _cache = new MemoryCache(new MemoryCacheOptions());\n    private readonly FakeDbContext _db    = new();\n\n    [GlobalSetup]\n    public void Setup() => _cache.Set(\"products\", _db.Products.ToList());\n\n    [Benchmark(Baseline = true)]\n    public List\u003CProduct> NoCache()     => _db.Products.ToList();\n\n    [Benchmark]\n    public List\u003CProduct>? WithCache()  =>\n        _cache.TryGetValue(\"products\", out List\u003CProduct>? p) ? p : null;\n}\n\n\u002F\u002F Key cache metrics to track in production:\n\u002F\u002F - Hit rate (hits \u002F total requests): target > 90% for high-value entries\n\u002F\u002F - Miss latency: how long does a cache miss take vs a cache hit?\n\u002F\u002F - Eviction rate: high evictions → size limit too small or TTL too short\n\n\u002F\u002F Expose hit\u002Fmiss counters via metrics (System.Diagnostics.Metrics):\nprivate static readonly Counter\u003Clong> _hits   = Metrics.Meter.CreateCounter\u003Clong>(\"cache.hits\");\nprivate static readonly Counter\u003Clong> _misses = Metrics.Meter.CreateCounter\u003Clong>(\"cache.misses\");\n\npublic async Task\u003CProduct?> GetByIdAsync(int id)\n{\n    if (_cache.TryGetValue($\"product:{id}\", out Product? p))\n    {\n        _hits.Add(1, new TagList { { \"cache\", \"product\" } });\n        return p;\n    }\n    _misses.Add(1, new TagList { { \"cache\", \"product\" } });\n    \u002F\u002F ... fetch and populate\n    return p;\n}\n```\n\n**Rule of thumb:** Measure before and after. A cache that is never hit (low hit\nrate) wastes memory. A cache that is invalidated too aggressively offers no\nbenefit. Track hit rate per cache key in production dashboards.\n",{"id":96,"difficulty":35,"q":97,"a":98},"cache-warming","What is cache warming and how do you implement it in ASP.NET Core?","**Cache warming** (also called pre-population) loads frequently accessed data\ninto the cache at application startup, before the first real request arrives.\nThis avoids a burst of slow cache misses immediately after a deployment.\n\n```csharp\n\u002F\u002F IHostedService that runs once at startup to populate the cache:\npublic class CacheWarmupService : IHostedService\n{\n    private readonly IMemoryCache  _cache;\n    private readonly AppDbContext  _db;\n    private readonly ILogger\u003CCacheWarmupService> _logger;\n\n    public CacheWarmupService(\n        IMemoryCache cache,\n        AppDbContext db,\n        ILogger\u003CCacheWarmupService> logger)\n    {\n        _cache  = cache;\n        _db     = db;\n        _logger = logger;\n    }\n\n    public async Task StartAsync(CancellationToken ct)\n    {\n        _logger.LogInformation(\"Warming caches...\");\n\n        \u002F\u002F Load reference data that every request needs:\n        var categories = await _db.Categories\n            .AsNoTracking()\n            .ToListAsync(ct);\n\n        _cache.Set(\"categories:all\", categories, new MemoryCacheEntryOptions\n        {\n            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),\n            Priority = CacheItemPriority.NeverRemove, \u002F\u002F do not evict under pressure\n        });\n\n        var topProducts = await _db.Products\n            .OrderByDescending(p => p.Views)\n            .Take(100)\n            .AsNoTracking()\n            .ToListAsync(ct);\n\n        foreach (var product in topProducts)\n            _cache.Set($\"product:{product.Id}\", product, TimeSpan.FromMinutes(30));\n\n        _logger.LogInformation(\"Cache warm-up complete: {Count} products loaded\",\n            topProducts.Count);\n    }\n\n    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;\n}\n\n\u002F\u002F Registration — runs before the app starts accepting traffic:\nbuilder.Services.AddHostedService\u003CCacheWarmupService>();\n```\n\nIn Kubernetes, keep the readiness probe returning `503` until warm-up completes\nby setting a flag that the readiness health check reads:\n\n```csharp\n\u002F\u002F Simple flag — readiness check returns Unhealthy until flag is set:\nbuilder.Services.AddSingleton\u003CCacheWarmupFlag>();\nbuilder.Services.AddHealthChecks()\n    .AddCheck\u003CCacheWarmupHealthCheck>(\"cache-warmup\", tags: [\"readiness\"]);\n```\n\n**Rule of thumb:** Warm only the data that is both expensive to compute and\naccessed on virtually every request. Warming too much delays startup and wastes\nmemory on data that may never be needed.\n",{"id":100,"difficulty":35,"q":101,"a":102},"etag-conditional-caching","What are ETags and how do you implement HTTP conditional caching in ASP.NET Core?","An **ETag** is a hash or version token attached to an HTTP response. On\nsubsequent requests the client sends the ETag back in `If-None-Match`; if\nthe resource has not changed the server returns `304 Not Modified` with no\nbody, saving bandwidth and processing.\n\n```csharp\n\u002F\u002F Manual ETag — compute a hash of the response data:\n[HttpGet(\"products\u002F{id}\")]\npublic async Task\u003CIActionResult> GetProduct(int id)\n{\n    var product = await _cache.GetOrCreateAsync($\"product:{id}\",\n        entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);\n                   return _db.Products.FindAsync(id).AsTask(); });\n\n    if (product is null) return NotFound();\n\n    \u002F\u002F Compute a deterministic ETag from the entity's updated timestamp:\n    var etag = new EntityTagHeaderValue(\n        $\"\\\"{product.UpdatedAt.Ticks}\\\"\");\n\n    \u002F\u002F Check the request's If-None-Match header:\n    if (Request.Headers.IfNoneMatch.ToString() == etag.ToString())\n        return StatusCode(StatusCodes.Status304NotModified); \u002F\u002F no body sent\n\n    Response.Headers.ETag = etag.ToString();\n    Response.Headers.CacheControl = \"private, max-age=0, must-revalidate\";\n    return Ok(product);\n}\n\n\u002F\u002F Middleware approach — ResponseCaching honours ETags automatically\n\u002F\u002F when the response has Cache-Control: public, max-age=N\n\u002F\u002F and the client sends If-None-Match or If-Modified-Since.\n\n\u002F\u002F For collections — hash the whole result set:\nvar hash = Convert.ToHexString(\n    SHA256.HashData(\n        Encoding.UTF8.GetBytes(JsonSerializer.Serialize(products))));\nvar etag = new EntityTagHeaderValue($\"\\\"{hash}\\\"\");\n```\n\nETag benefits:\n- **Bandwidth savings** — 304 response has no body\n- **Client freshness** — client always gets fresh data when it changes\n- **Reduced server load** — skip serialization on 304 path\n\n**Rule of thumb:** Use ETags for large resources (product listings, reports)\nwhere the response body is kilobytes or more. For tiny payloads the overhead\nof hashing and the extra round-trip makes ETags counterproductive — a short\n`max-age` is simpler and faster.\n",15,null,{"description":32},"Caching interview questions — IMemoryCache, IDistributedCache, Redis, cache-aside pattern, output caching, cache invalidation, and cache stampede.","dotnet\u002Fperformance-deployment\u002Fcaching","2026-06-23","jRccySZ3LoYKXy7il4ZmR6dZmQ79CTBvLctoRRHMJq0",{"id":111,"title":112,"body":113,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":117,"navigation":38,"order":33,"path":118,"questions":119,"questionsCount":103,"related":104,"seo":180,"seoDescription":181,"stem":182,"subtopic":183,"topic":19,"topicSlug":21,"updated":108,"__hash__":184},"qa\u002Fdotnet\u002Fperformance-deployment\u002Flogging-monitoring.md","Logging Monitoring",{"type":29,"value":114,"toc":115},[],{"title":32,"searchDepth":33,"depth":33,"links":116},[],{},"\u002Fdotnet\u002Fperformance-deployment\u002Flogging-monitoring",[120,124,128,132,136,140,144,148,152,156,160,164,168,172,176],{"id":121,"difficulty":43,"q":122,"a":123},"ilogger-basics","How does the built-in ILogger work in ASP.NET Core?","`ILogger\u003CT>` is the standard logging abstraction in ASP.NET Core. It is\nregistered automatically and injected like any other service. The generic\nparameter `T` becomes the **category name** that appears in every log entry,\nletting you filter by class or namespace.\n\n```csharp\npublic class OrderService\n{\n    private readonly ILogger\u003COrderService> _logger;\n\n    public OrderService(ILogger\u003COrderService> logger) => _logger = logger;\n\n    public async Task\u003COrder> PlaceOrderAsync(Order order)\n    {\n        _logger.LogInformation(\"Placing order {OrderId} for customer {CustomerId}\",\n            order.Id, order.CustomerId);\n\n        try\n        {\n            var result = await _repository.SaveAsync(order);\n            _logger.LogInformation(\"Order {OrderId} saved successfully\", result.Id);\n            return result;\n        }\n        catch (Exception ex)\n        {\n            \u002F\u002F Exception is the first parameter when logging errors:\n            _logger.LogError(ex, \"Failed to place order {OrderId}\", order.Id);\n            throw;\n        }\n    }\n}\n\n\u002F\u002F Configuration in appsettings.json — control minimum levels per namespace:\n\u002F\u002F {\n\u002F\u002F \"Logging\": {\n\u002F\u002F \"LogLevel\": {\n\u002F\u002F \"Default\":             \"Information\",\n\u002F\u002F \"Microsoft.AspNetCore\": \"Warning\",\n\u002F\u002F \"MyApp.Data\":          \"Debug\"\n\u002F\u002F }\n\u002F\u002F }\n\u002F\u002F }\n```\n\nUse **message templates** with named placeholders (`{OrderId}`) rather than\nstring interpolation (`$\"Order {order.Id}\"`). Structured logging providers\ncapture placeholders as separate fields, enabling rich querying in log aggregators.\n\n**Rule of thumb:** Always use message templates, never string interpolation.\n`LogInformation($\"Order {id}\")` stores one string; `LogInformation(\"Order {Id}\", id)`\nstores a structured document with a searchable `Id` field.\n",{"id":125,"difficulty":43,"q":126,"a":127},"log-levels","What are the log levels in .NET and when should each be used?",".NET defines six log levels in ascending severity. The configured minimum level\nfilters out everything below it, so lower-severity messages cost nothing at runtime\nwhen they are below the threshold.\n\n```csharp\n\u002F\u002F Levels in order — Trace (0) through Critical (5):\n_logger.LogTrace(\"Entering GetProductAsync(id={Id})\", id);       \u002F\u002F 0 — per-step traces\n_logger.LogDebug(\"Cache miss for product {Id}\", id);             \u002F\u002F 1 — developer diagnostics\n_logger.LogInformation(\"Order {Id} placed successfully\", id);    \u002F\u002F 2 — normal business events\n_logger.LogWarning(\"Retry {Attempt} for order {Id}\", attempt, id); \u002F\u002F 3 — unexpected but handled\n_logger.LogError(ex, \"Failed to save order {Id}\", id);           \u002F\u002F 4 — failure, action needed\n_logger.LogCritical(ex, \"Database connection lost\");             \u002F\u002F 5 — system down\n\n\u002F\u002F Guard against expensive computation when level is disabled:\nif (_logger.IsEnabled(LogLevel.Debug))\n    _logger.LogDebug(\"Full order payload: {Payload}\", JsonSerializer.Serialize(order));\n\n\u002F\u002F Common level strategy by environment:\n\u002F\u002F Development: Debug (verbose, includes framework internals)\n\u002F\u002F Staging:     Information (business events, warnings)\n\u002F\u002F Production:  Warning (only unexpected or error events)\n```\n\n| Level | Use case |\n|-------|----------|\n| Trace | Step-by-step tracing for deep debugging |\n| Debug | Developer diagnostics (loop values, cache state) |\n| Information | Normal business events (order placed, user logged in) |\n| Warning | Handled unexpected conditions (retry, degraded mode) |\n| Error | Unhandled failures that need investigation |\n| Critical | System-wide failures requiring immediate action |\n\n**Rule of thumb:** Log `Information` for every significant business event —\nthese are the facts you want in an audit trail. Log `Warning` when you handle\nan error gracefully but something unexpected happened. Log `Error` only for\nfailures that actually matter.\n",{"id":129,"difficulty":35,"q":130,"a":131},"structured-logging","What is structured logging and why is it preferred over plain text logging?","**Structured logging** captures log entries as key-value documents rather than\nflat strings. This allows log aggregators (Seq, Elasticsearch, Splunk) to query,\nfilter, and aggregate on individual fields — something impossible with plain text.\n\n```csharp\n\u002F\u002F Plain text — one unsearchable string:\n_logger.LogInformation($\"Order {order.Id} for customer {order.CustomerId} total {order.Total}\");\n\u002F\u002F Log: \"Order 42 for customer 99 total 150.00\"\n\n\u002F\u002F Structured — document with searchable fields:\n_logger.LogInformation(\n    \"Order {OrderId} placed for {CustomerId}, total {Total:C}\",\n    order.Id, order.CustomerId, order.Total);\n\u002F\u002F Log document: { \"OrderId\": 42, \"CustomerId\": 99, \"Total\": 150.00,\n\u002F\u002F \"@t\": \"...\", \"@l\": \"Information\", ... }\n\n\u002F\u002F Querying structured logs in Seq or Kibana:\n\u002F\u002F OrderId = 42\n\u002F\u002F Total > 100 AND @l = 'Error'\n\u002F\u002F CustomerId = 99 AND @t > '2026-06-01'\n\n\u002F\u002F Destructure objects — capture all properties with @:\n_logger.LogInformation(\"Processing {@Order}\", order);\n\u002F\u002F Captures: Order.Id, Order.Sku, Order.Total, Order.CustomerId as separate fields\n\n\u002F\u002F Avoid destructuring large objects — it serializes the whole graph:\n\u002F\u002F _logger.LogInformation(\"Context {@DbContext}\", dbContext); \u002F\u002F thousands of fields!\n\u002F\u002F _logger.LogInformation(\"DB query completed for {EntityType}\", typeof(Order).Name);\n```\n\nStructured logging transforms log files from write-only archives into\nqueryable datasets. You can answer \"how many orders over $100 failed last\nThursday?\" without parsing strings.\n\n**Rule of thumb:** Every significant log entry should capture the entity ID\nand the relevant business context as named fields. Never log by concatenating\nstrings — you will want to query those fields later.\n",{"id":133,"difficulty":35,"q":134,"a":135},"serilog","How do you configure Serilog in an ASP.NET Core application?","**Serilog** is the most popular third-party logging library for .NET. It\nintegrates with `ILogger\u003CT>` so existing code needs no changes, but adds\nrich sink support (files, Seq, Elasticsearch, Application Insights) and\noutput templates.\n\n```csharp\n\u002F\u002F dotnet add package Serilog.AspNetCore\n\u002F\u002F dotnet add package Serilog.Sinks.Console\n\u002F\u002F dotnet add package Serilog.Sinks.File\n\n\u002F\u002F Program.cs — configure Serilog before building the host:\nLog.Logger = new LoggerConfiguration()\n    .MinimumLevel.Information()\n    .MinimumLevel.Override(\"Microsoft.AspNetCore\", LogEventLevel.Warning)\n    .Enrich.FromLogContext()                \u002F\u002F adds scope properties to every event\n    .Enrich.WithMachineName()\n    .WriteTo.Console(new JsonFormatter())   \u002F\u002F structured JSON to stdout\n    .WriteTo.File(\n        path: \"logs\u002Fapp-.log\",\n        rollingInterval: RollingInterval.Day,\n        outputTemplate: \"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}\")\n    .WriteTo.Seq(\"http:\u002F\u002Fseq:5341\")         \u002F\u002F central log server\n    .CreateLogger();\n\nbuilder.Host.UseSerilog();                  \u002F\u002F replaces the built-in providers\n\n\u002F\u002F appsettings.json-driven config (recommended for production flexibility):\nLog.Logger = new LoggerConfiguration()\n    .ReadFrom.Configuration(builder.Configuration)\n    .CreateLogger();\n\n\u002F\u002F Request logging middleware — one log line per HTTP request with timing:\napp.UseSerilogRequestLogging(opts =>\n{\n    opts.MessageTemplate =\n        \"{RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms\";\n    opts.EnrichDiagnosticContext = (diagCtx, httpCtx) =>\n        diagCtx.Set(\"UserId\", httpCtx.User.FindFirst(\"sub\")?.Value ?? \"anon\");\n});\n```\n\n**Rule of thumb:** Use `UseSerilogRequestLogging()` to replace ASP.NET Core's\nverbose default request log (which emits two events per request) with one\nstructured event per request that includes timing, status code, and custom fields.\n",{"id":137,"difficulty":35,"q":138,"a":139},"log-scopes","What are log scopes in .NET and when do you use them?","**Log scopes** add ambient context properties to every log entry emitted within\na `using` block. This is essential for correlating all logs from one request or\noperation without passing extra parameters through every method call.\n\n```csharp\n\u002F\u002F ILogger.BeginScope — add fields to all logs within the using block:\npublic async Task\u003COrder> PlaceOrderAsync(Order order)\n{\n    using (_logger.BeginScope(new Dictionary\u003Cstring, object>\n    {\n        [\"OrderId\"]    = order.Id,\n        [\"CustomerId\"] = order.CustomerId,\n        [\"CorrelationId\"] = Activity.Current?.Id ?? Guid.NewGuid().ToString(),\n    }))\n    {\n        \u002F\u002F Every log inside this using block carries OrderId + CustomerId:\n        _logger.LogInformation(\"Validating order\");         \u002F\u002F has OrderId\n        await ValidateAsync(order);\n        _logger.LogInformation(\"Saving order to database\"); \u002F\u002F has OrderId\n        await _repository.SaveAsync(order);\n        _logger.LogInformation(\"Sending confirmation email\"); \u002F\u002F has OrderId\n        await _emailService.SendAsync(order);\n    }\n    \u002F\u002F Outside the using — scope properties are gone\n    _logger.LogInformation(\"Done\"); \u002F\u002F no OrderId here\n}\n\n\u002F\u002F Scopes nest — inner scopes add to outer:\nusing (_logger.BeginScope(\"RequestId: {RequestId}\", requestId))\nusing (_logger.BeginScope(\"TenantId: {TenantId}\", tenantId))\n{\n    _logger.LogInformation(\"Processing\"); \u002F\u002F has both RequestId and TenantId\n}\n\n\u002F\u002F In Serilog, Enrich.FromLogContext() is required to pick up scope properties.\n\u002F\u002F In the built-in provider, IncludeScopes must be true (default):\n\u002F\u002F \"Logging\": { \"Console\": { \"IncludeScopes\": true } }\n```\n\n**Rule of thumb:** Use scopes to attach a correlation ID, request ID, or\ntransaction ID at the top of an operation. This makes it trivial to filter\nall logs for one request in any log aggregator.\n",{"id":141,"difficulty":35,"q":142,"a":143},"health-checks","How do you implement health checks in ASP.NET Core?","ASP.NET Core's **health checks** expose a `\u002Fhealth` endpoint that orchestrators\n(Kubernetes, load balancers) probe to decide whether to route traffic to an\ninstance. Checks can verify databases, message brokers, and external services.\n\n```csharp\n\u002F\u002F Program.cs:\nbuilder.Services.AddHealthChecks()\n    .AddDbContextCheck\u003CAppDbContext>()       \u002F\u002F checks EF Core can query\n    .AddRedis(\"localhost:6379\")              \u002F\u002F checks Redis connectivity\n    .AddUrlGroup(new Uri(\"https:\u002F\u002Fapi.partner.com\u002Fhealth\"), \"partner-api\")\n    .AddCheck(\"custom\", () =>               \u002F\u002F arbitrary custom check\n    {\n        var queueDepth = _queue.GetDepth();\n        return queueDepth \u003C 1000\n            ? HealthCheckResult.Healthy($\"Queue depth: {queueDepth}\")\n            : HealthCheckResult.Degraded($\"Queue depth high: {queueDepth}\");\n    }, tags: [\"readiness\"]);\n\n\u002F\u002F Two endpoints — liveness (is it running?) and readiness (can it take traffic?):\napp.MapHealthChecks(\"\u002Fhealth\u002Flive\",  new HealthCheckOptions\n{\n    Predicate = _ => false,  \u002F\u002F no checks — just returns 200 if the process is up\n});\n\napp.MapHealthChecks(\"\u002Fhealth\u002Fready\", new HealthCheckOptions\n{\n    Predicate = check => check.Tags.Contains(\"readiness\"),\n    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, \u002F\u002F rich JSON\n});\n\n\u002F\u002F Response shape (healthy):\n\u002F\u002F { \"status\": \"Healthy\", \"checks\": [{ \"name\": \"...\", \"status\": \"Healthy\" }] }\n```\n\nHealth check packages:\n- `AspNetCore.HealthChecks.SqlServer` \u002F `Npgsql`\n- `AspNetCore.HealthChecks.Redis`\n- `AspNetCore.HealthChecks.UI` for a dashboard\n\n**Rule of thumb:** Always expose separate liveness and readiness probes.\nLiveness (is the process alive?) should never check external dependencies —\na failing database should not kill the process, just take it out of rotation.\n",{"id":145,"difficulty":76,"q":146,"a":147},"metrics-opentelemetry","How do you emit custom metrics in .NET using System.Diagnostics.Metrics?",".NET 8+ has a built-in metrics API in `System.Diagnostics.Metrics`. Metrics\nare counters, histograms, and gauges that can be exported to Prometheus,\nOpenTelemetry, or Application Insights without changing application code.\n\n```csharp\n\u002F\u002F Define a Meter and instruments once (usually a static field):\npublic static class AppMetrics\n{\n    private static readonly Meter _meter = new(\"MyApp.Orders\", \"1.0.0\");\n\n    public static readonly Counter\u003Clong>   OrdersPlaced =\n        _meter.CreateCounter\u003Clong>(\"orders.placed.total\",\n            unit: \"{orders}\", description: \"Total orders placed\");\n\n    public static readonly Histogram\u003Cdouble> OrderTotal =\n        _meter.CreateHistogram\u003Cdouble>(\"orders.total.amount\",\n            unit: \"USD\", description: \"Distribution of order totals\");\n\n    public static readonly ObservableGauge\u003Cint> QueueDepth =\n        _meter.CreateObservableGauge(\"orders.queue.depth\",\n            () => OrderQueue.CurrentDepth, unit: \"{orders}\");\n}\n\n\u002F\u002F Emit metrics in application code:\npublic async Task\u003COrder> PlaceOrderAsync(Order order)\n{\n    var result = await _repo.SaveAsync(order);\n\n    AppMetrics.OrdersPlaced.Add(1, new TagList\n    {\n        { \"region\",  order.Region },\n        { \"channel\", order.Channel },\n    });\n    AppMetrics.OrderTotal.Record(order.Total, new TagList\n    {\n        { \"currency\", order.Currency },\n    });\n\n    return result;\n}\n\n\u002F\u002F Export to Prometheus (dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore):\nbuilder.Services.AddOpenTelemetry()\n    .WithMetrics(metrics => metrics\n        .AddMeter(\"MyApp.Orders\")\n        .AddPrometheusExporter());\n\napp.MapPrometheusScrapingEndpoint(\"\u002Fmetrics\");\n```\n\n**Rule of thumb:** Define meters and instruments as static fields — creating\nthem per-request is expensive and causes duplicate registration errors.\nUse tags (dimensions) to slice metrics by region, tenant, or error type\nrather than creating separate instruments per value.\n",{"id":149,"difficulty":76,"q":150,"a":151},"distributed-tracing","What is distributed tracing in .NET and how does OpenTelemetry enable it?","**Distributed tracing** tracks a request as it flows through multiple services.\nEach service adds a span to a shared trace, creating a full timeline that shows\nwhere time was spent and where errors occurred.\n\n```csharp\n\u002F\u002F dotnet add package OpenTelemetry.Extensions.Hosting\n\u002F\u002F dotnet add package OpenTelemetry.Instrumentation.AspNetCore\n\u002F\u002F dotnet add package OpenTelemetry.Instrumentation.Http\n\u002F\u002F dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore\n\u002F\u002F dotnet add package OpenTelemetry.Exporter.Jaeger\n\nbuilder.Services.AddOpenTelemetry()\n    .WithTracing(tracing => tracing\n        .AddSource(\"MyApp.Orders\")            \u002F\u002F custom spans from this source\n        .AddAspNetCoreInstrumentation()        \u002F\u002F HTTP requests auto-instrumented\n        .AddHttpClientInstrumentation()        \u002F\u002F outbound HTTP calls\n        .AddEntityFrameworkCoreInstrumentation() \u002F\u002F EF Core queries\n        .AddJaegerExporter(j =>\n            j.AgentHost = \"jaeger\"));          \u002F\u002F export to Jaeger\n\n\u002F\u002F Create custom spans for significant operations:\nprivate static readonly ActivitySource _source = new(\"MyApp.Orders\");\n\npublic async Task\u003COrder> PlaceOrderAsync(Order order)\n{\n    using var activity = _source.StartActivity(\"PlaceOrder\");\n    activity?.SetTag(\"order.id\",       order.Id);\n    activity?.SetTag(\"customer.id\",    order.CustomerId);\n    activity?.SetTag(\"order.total\",    order.Total);\n\n    try\n    {\n        var result = await _repo.SaveAsync(order);\n        activity?.SetStatus(ActivityStatusCode.Ok);\n        return result;\n    }\n    catch (Exception ex)\n    {\n        activity?.SetStatus(ActivityStatusCode.Error, ex.Message);\n        activity?.RecordException(ex);\n        throw;\n    }\n}\n```\n\nThe W3C `traceparent` header propagates the trace ID across service boundaries.\nASP.NET Core reads and forwards this header automatically when\n`AddAspNetCoreInstrumentation()` is configured.\n\n**Rule of thumb:** Add custom spans (`ActivitySource.StartActivity`) for your\nbusiness operations, not just for infrastructure. A Jaeger or Zipkin trace\nthat shows \"HTTP → EF Core → HTTP\" is less useful than one that shows\n\"HTTP → PlaceOrder → ValidateInventory → ChargePayment.\"\n",{"id":153,"difficulty":35,"q":154,"a":155},"exception-middleware-logging","How do you log unhandled exceptions globally in ASP.NET Core?","ASP.NET Core provides two standard mechanisms for global exception handling:\n`UseExceptionHandler` (for non-API responses) and `IExceptionHandler`\n(introduced in .NET 8, interface-based, testable).\n\n```csharp\n\u002F\u002F .NET 8+ — IExceptionHandler (preferred):\npublic class GlobalExceptionHandler : IExceptionHandler\n{\n    private readonly ILogger\u003CGlobalExceptionHandler> _logger;\n\n    public GlobalExceptionHandler(ILogger\u003CGlobalExceptionHandler> logger)\n        => _logger = logger;\n\n    public async ValueTask\u003Cbool> TryHandleAsync(\n        HttpContext httpContext,\n        Exception   exception,\n        CancellationToken ct)\n    {\n        _logger.LogError(exception,\n            \"Unhandled exception on {Method} {Path}: {Message}\",\n            httpContext.Request.Method,\n            httpContext.Request.Path,\n            exception.Message);\n\n        httpContext.Response.StatusCode  = StatusCodes.Status500InternalServerError;\n        httpContext.Response.ContentType = \"application\u002Fproblem+json\";\n\n        await httpContext.Response.WriteAsJsonAsync(new ProblemDetails\n        {\n            Status   = 500,\n            Title    = \"An unexpected error occurred\",\n            Instance = httpContext.Request.Path,\n        }, ct);\n\n        return true; \u002F\u002F handled — stop propagation\n    }\n}\n\n\u002F\u002F Registration:\nbuilder.Services.AddExceptionHandler\u003CGlobalExceptionHandler>();\nbuilder.Services.AddProblemDetails();\napp.UseExceptionHandler();\n\n\u002F\u002F Older approach — UseExceptionHandler with a lambda:\napp.UseExceptionHandler(appError => appError.Run(async context =>\n{\n    var feature = context.Features.Get\u003CIExceptionHandlerFeature>();\n    var ex      = feature?.Error;\n    if (ex is not null)\n        logger.LogError(ex, \"Unhandled exception\");\n    context.Response.StatusCode = 500;\n    await context.Response.WriteAsync(\"An error occurred.\");\n}));\n```\n\n**Rule of thumb:** Use `IExceptionHandler` in .NET 8+ for global exception\nlogging — it's DI-friendly, testable, and can be chained (multiple handlers\ntried in registration order). Always return a `ProblemDetails` JSON response\nrather than a plain string so API clients can parse the error.\n",{"id":157,"difficulty":76,"q":158,"a":159},"logging-performance","How do you avoid performance overhead from logging in hot paths?","Logging is never truly free. Even when a message is below the minimum level,\nthe arguments may be evaluated before the logger can discard them. High-volume\npaths need additional guards.\n\n```csharp\n\u002F\u002F Problem — string interpolation always allocates:\n_logger.LogDebug($\"Processing item {item.Id} with payload {JsonSerializer.Serialize(item)}\");\n\u002F\u002F JsonSerializer.Serialize runs on every call, even if Debug is disabled!\n\n\u002F\u002F Fix 1 — IsEnabled guard:\nif (_logger.IsEnabled(LogLevel.Debug))\n    _logger.LogDebug(\"Processing item {Id} with payload {Payload}\",\n        item.Id, JsonSerializer.Serialize(item));\n\n\u002F\u002F Fix 2 — LoggerMessage.Define (zero-allocation for hot paths):\nprivate static readonly Action\u003CILogger, int, string, Exception?> _logProcessing =\n    LoggerMessage.Define\u003Cint, string>(\n        LogLevel.Debug,\n        new EventId(1001, \"ProcessingItem\"),\n        \"Processing item {Id} with payload {Payload}\");\n\n\u002F\u002F Call site — no allocation if Debug is disabled:\n_logProcessing(_logger, item.Id, item.Payload, null);\n\n\u002F\u002F Fix 3 — [LoggerMessage] source generator (.NET 6+, preferred):\npublic partial class ItemProcessor\n{\n    private readonly ILogger\u003CItemProcessor> _logger;\n\n    [LoggerMessage(Level = LogLevel.Debug,\n                   Message = \"Processing item {Id} with payload {Payload}\")]\n    partial void LogProcessing(int id, string payload);\n\n    public void Process(Item item)\n    {\n        LogProcessing(item.Id, item.Payload); \u002F\u002F zero-alloc if Debug is off\n        \u002F\u002F ...\n    }\n}\n```\n\n`[LoggerMessage]` source generators produce the same zero-allocation code as\n`LoggerMessage.Define` but with a clean, readable call site and compile-time\nvalidation of the message template.\n\n**Rule of thumb:** Use `[LoggerMessage]` source generators in any method called\nmore than ~1000 times per second. For normal business logic paths, the built-in\n`ILogger.LogXxx` with message templates is fast enough.\n",{"id":161,"difficulty":35,"q":162,"a":163},"application-insights","How do you integrate Application Insights with an ASP.NET Core application?","**Application Insights** is Azure's application performance monitoring (APM)\nservice. It collects logs, metrics, traces, and exceptions with minimal\nconfiguration and provides a rich analytics dashboard.\n\n```csharp\n\u002F\u002F dotnet add package Microsoft.ApplicationInsights.AspNetCore\n\n\u002F\u002F Program.cs:\nbuilder.Services.AddApplicationInsightsTelemetry(opts =>\n    opts.ConnectionString = builder.Configuration[\"ApplicationInsights:ConnectionString\"]);\n\n\u002F\u002F appsettings.json:\n\u002F\u002F {\n\u002F\u002F \"ApplicationInsights\": {\n\u002F\u002F \"ConnectionString\": \"InstrumentationKey=...;IngestionEndpoint=...\"\n\u002F\u002F }\n\u002F\u002F }\n\n\u002F\u002F Custom telemetry — track business events:\npublic class OrderService\n{\n    private readonly TelemetryClient _telemetry;\n\n    public async Task\u003COrder> PlaceOrderAsync(Order order)\n    {\n        var result = await _repo.SaveAsync(order);\n\n        \u002F\u002F Track a custom event with properties:\n        _telemetry.TrackEvent(\"OrderPlaced\", new Dictionary\u003Cstring, string>\n        {\n            [\"OrderId\"]    = result.Id.ToString(),\n            [\"CustomerId\"] = order.CustomerId.ToString(),\n            [\"Channel\"]    = order.Channel,\n        }, new Dictionary\u003Cstring, double>\n        {\n            [\"Total\"]    = (double)order.Total,\n            [\"ItemCount\"] = order.Items.Count,\n        });\n\n        return result;\n    }\n}\n\n\u002F\u002F Custom dependency tracking (for non-HTTP dependencies):\nusing var operation = _telemetry.StartOperation\u003CDependencyTelemetry>(\"PaymentGateway\");\noperation.Telemetry.Type   = \"HTTP\";\noperation.Telemetry.Target = \"payment.example.com\";\ntry { await _gateway.ChargeAsync(order); operation.Telemetry.Success = true; }\ncatch { operation.Telemetry.Success = false; throw; }\n```\n\n**Rule of thumb:** Let Application Insights auto-collect HTTP, SQL, and exception\ntelemetry. Add `TrackEvent` only for domain-significant events (order placed,\nsubscription cancelled) that are not captured automatically.\n",{"id":165,"difficulty":43,"q":166,"a":167},"log-filtering-overrides","How do you filter log output by namespace or provider in ASP.NET Core?","The logging framework applies a **minimum level** filter per category (namespace\nor class name) and per provider (Console, File, Application Insights). Filters\nare configured in `appsettings.json` or in code without touching log call sites.\n\n```json\n\u002F\u002F appsettings.json — granular filtering by category and provider:\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\":                        \"Information\",\n      \"Microsoft\":                      \"Warning\",\n      \"Microsoft.AspNetCore\":           \"Warning\",\n      \"Microsoft.EntityFrameworkCore\":  \"Warning\",\n      \"Microsoft.EntityFrameworkCore.Database.Command\": \"Information\",\n      \"MyApp.Services.OrderService\":    \"Debug\"\n    },\n    \"Console\": {\n      \"LogLevel\": {\n        \"Default\": \"Warning\"\n      }\n    },\n    \"ApplicationInsights\": {\n      \"LogLevel\": {\n        \"Default\": \"Information\"\n      }\n    }\n  }\n}\n```\n\n```csharp\n\u002F\u002F Equivalent in code — useful for dynamic configuration:\nbuilder.Logging.AddFilter(\"Microsoft.EntityFrameworkCore\", LogLevel.Warning);\nbuilder.Logging.AddFilter\u003CConsoleLoggerProvider>(\"Default\", LogLevel.Warning);\n\n\u002F\u002F Filter by predicate — suppress specific event IDs:\nbuilder.Logging.AddFilter((provider, category, level) =>\n{\n    \u002F\u002F Suppress EF Core command logging in Application Insights to reduce cost:\n    if (provider.Contains(\"ApplicationInsights\") &&\n        category.StartsWith(\"Microsoft.EntityFrameworkCore\"))\n        return false;\n    return level >= LogLevel.Information;\n});\n```\n\nCategory rules are matched by prefix: `Microsoft.AspNetCore` matches\n`Microsoft.AspNetCore.Routing`, `Microsoft.AspNetCore.Mvc`, etc. The most\nspecific matching rule wins.\n\n**Rule of thumb:** In production, set `Microsoft` and `System` namespaces to\n`Warning` and your own application namespaces to `Information`. This cuts log\nvolume by 80% and eliminates noise from framework internals while keeping\nall business events.\n",{"id":169,"difficulty":35,"q":170,"a":171},"enrichers-log-context","How do you enrich log entries with ambient context (user, tenant, request ID) in .NET?","**Log enrichers** add properties to every log entry automatically, without\npassing extra parameters through every method call. ASP.NET Core's `ILogger`\nscopes and Serilog's `LogContext` are the two main mechanisms.\n\n```csharp\n\u002F\u002F Middleware — push ambient context into the log scope for every request:\npublic class LogEnrichmentMiddleware\n{\n    private readonly RequestDelegate _next;\n\n    public LogEnrichmentMiddleware(RequestDelegate next) => _next = next;\n\n    public async Task InvokeAsync(HttpContext ctx, ILogger\u003CLogEnrichmentMiddleware> logger)\n    {\n        var userId   = ctx.User.FindFirst(\"sub\")?.Value   ?? \"anon\";\n        var tenantId = ctx.User.FindFirst(\"tenant\")?.Value ?? \"none\";\n        var traceId  = Activity.Current?.TraceId.ToString()\n                       ?? ctx.TraceIdentifier;\n\n        \u002F\u002F ILogger scope — carried by all loggers in this request:\n        using (logger.BeginScope(new Dictionary\u003Cstring, object>\n        {\n            [\"UserId\"]    = userId,\n            [\"TenantId\"]  = tenantId,\n            [\"TraceId\"]   = traceId,\n        }))\n        {\n            await _next(ctx);\n        }\n    }\n}\n\n\u002F\u002F Register the middleware early in the pipeline:\napp.UseMiddleware\u003CLogEnrichmentMiddleware>();\n\n\u002F\u002F Serilog LogContext — same concept, uses Serilog's enrichment mechanism:\n\u002F\u002F (requires Enrich.FromLogContext() in LoggerConfiguration)\nusing (LogContext.PushProperty(\"UserId\",   userId))\nusing (LogContext.PushProperty(\"TenantId\", tenantId))\n{\n    await _next(ctx);\n}\n\n\u002F\u002F Result — every log entry inside the request automatically includes:\n\u002F\u002F { \"UserId\": \"u-123\", \"TenantId\": \"acme\", \"TraceId\": \"4bf92f3577b34da6...\" }\n```\n\n**Rule of thumb:** Push user ID, tenant ID, and correlation\u002Ftrace ID into the\nlog scope in a single middleware. This makes filtering logs by user or tenant in\nSeq or Kibana a one-field query instead of a grep across unstructured strings.\n",{"id":173,"difficulty":35,"q":174,"a":175},"correlation-id-middleware","How do you propagate a correlation ID across service boundaries in .NET?","A **correlation ID** links all log entries for one logical operation across\nmultiple services. The originating service generates the ID and passes it in\nan HTTP header; downstream services read it, log it, and forward it to their\nown dependencies.\n\n```csharp\n\u002F\u002F Middleware — read or generate a correlation ID for every request:\npublic class CorrelationIdMiddleware\n{\n    private const string HeaderName = \"X-Correlation-Id\";\n    private readonly RequestDelegate _next;\n\n    public CorrelationIdMiddleware(RequestDelegate next) => _next = next;\n\n    public async Task InvokeAsync(HttpContext ctx, ILogger\u003CCorrelationIdMiddleware> logger)\n    {\n        \u002F\u002F Accept an inbound ID (from upstream caller) or generate a new one:\n        var correlationId = ctx.Request.Headers[HeaderName].FirstOrDefault()\n                            ?? Activity.Current?.TraceId.ToString()\n                            ?? Guid.NewGuid().ToString(\"N\");\n\n        \u002F\u002F Echo the ID back in the response header:\n        ctx.Response.Headers[HeaderName] = correlationId;\n\n        \u002F\u002F Push into log scope so every log in this request carries it:\n        using (logger.BeginScope(new Dictionary\u003Cstring, object>\n                   { [\"CorrelationId\"] = correlationId }))\n        {\n            await _next(ctx);\n        }\n    }\n}\n\n\u002F\u002F Forward the correlation ID on outbound HTTP calls (HttpClientFactory):\nbuilder.Services.AddHttpClient(\"downstream\")\n    .AddHttpMessageHandler\u003CCorrelationIdDelegatingHandler>();\n\npublic class CorrelationIdDelegatingHandler : DelegatingHandler\n{\n    private const string HeaderName = \"X-Correlation-Id\";\n\n    protected override Task\u003CHttpResponseMessage> SendAsync(\n        HttpRequestMessage request, CancellationToken ct)\n    {\n        \u002F\u002F Propagate the current trace ID as the correlation header:\n        var traceId = Activity.Current?.TraceId.ToString();\n        if (traceId is not null)\n            request.Headers.TryAddWithoutValidation(HeaderName, traceId);\n\n        return base.SendAsync(request, ct);\n    }\n}\n```\n\nNote: if OpenTelemetry is configured, the W3C `traceparent` header propagates\nthe trace ID automatically via `AddHttpClientInstrumentation()`. A manual\n`X-Correlation-Id` header is still useful for non-OpenTelemetry consumers\n(front-end apps, third-party APIs) that do not understand `traceparent`.\n\n**Rule of thumb:** Use the W3C `traceparent` header as your correlation ID\nwhen all services use OpenTelemetry. Add `X-Correlation-Id` as an alias for\nclients and logs that need a human-readable identifier in dashboards.\n",{"id":177,"difficulty":76,"q":178,"a":179},"dotnet-monitor","What is dotnet-monitor and how does it help diagnose production issues?","**dotnet-monitor** is a sidecar tool that exposes diagnostic APIs (traces,\ndumps, metrics, logs) over HTTP without modifying the application or attaching\na debugger. It is the recommended way to capture diagnostics from containerized\n.NET workloads in production.\n\n```bash\n# Run dotnet-monitor as a sidecar (Docker Compose example):\n# dotnet-monitor listens on localhost:52323 (diagnostic) and 52325 (metrics)\n# and connects to the application via the .NET diagnostic pipe.\n\n# docker-compose.yml snippet:\n# services:\n#   app:\n#     image: myapp:latest\n#   monitor:\n#     image: mcr.microsoft.com\u002Fdotnet\u002Fmonitor:8\n#     environment:\n#       - DOTNETMONITOR_DiagnosticPort__ConnectionMode=Listen\n#       - DOTNETMONITOR_Storage__DumpTempFolder=\u002Ftmp\n#     volumes:\n#       - \u002Ftmp:\u002Ftmp\n#     command: [\"collect\", \"--no-auth\"]  # Note: add auth in production\n\n# Capture a CPU trace via the REST API:\ncurl -X POST http:\u002F\u002Flocalhost:52323\u002Ftrace \\\n    -H \"Content-Type: application\u002Fjson\" \\\n    -d '{\"profile\": \"CpuSampling\", \"durationSeconds\": 30}' \\\n    --output trace.nettrace\n\n# Capture a memory dump:\ncurl -X POST http:\u002F\u002Flocalhost:52323\u002Fdump?type=Full \\\n    --output app.dmp\n\n# Live metrics stream (Prometheus-compatible scrape):\n# GET http:\u002F\u002Flocalhost:52325\u002Fmetrics\n```\n\n```json\n\u002F\u002F Trigger-based collection — automatically capture a dump when CPU exceeds 80%:\n\u002F\u002F (collectionrules.json mounted into the monitor container)\n{\n  \"CollectionRules\": {\n    \"HighCpuDump\": {\n      \"Trigger\": { \"Type\": \"EventCounter\",\n        \"Settings\": { \"ProviderName\": \"System.Runtime\",\n          \"CounterName\": \"cpu-usage\", \"GreaterThan\": 80 } },\n      \"Actions\": [\n        { \"Type\": \"CollectDump\", \"Settings\": { \"Type\": \"Heap\",\n          \"Egress\": \"AzureBlobStorage\" } }\n      ],\n      \"Limits\": { \"ActionCount\": 2, \"ActionCountSlidingWindowDuration\": \"01:00:00\" }\n    }\n  }\n}\n```\n\n**Rule of thumb:** Deploy dotnet-monitor as a sidecar in every Kubernetes pod\nrunning a .NET service. It gives you on-demand traces and dumps without\nredeploying the application or breaching the container boundary.\n",{"description":32},"Logging and monitoring interview questions — ILogger, structured logging, Serilog, log scopes, health checks, OpenTelemetry metrics, and distributed tracing.","dotnet\u002Fperformance-deployment\u002Flogging-monitoring","Logging & Monitoring","EB_8n88YuCvlmWZviVj9Wokjt8SlSzSqOUpv8KJ73YQ",{"id":186,"title":187,"body":188,"description":32,"difficulty":76,"extension":36,"framework":10,"frameworkSlug":8,"meta":192,"navigation":38,"order":193,"path":194,"questions":195,"questionsCount":103,"related":104,"seo":256,"seoDescription":257,"stem":258,"subtopic":187,"topic":19,"topicSlug":21,"updated":108,"__hash__":259},"qa\u002Fdotnet\u002Fperformance-deployment\u002Fdeployment.md","Deployment",{"type":29,"value":189,"toc":190},[],{"title":32,"searchDepth":33,"depth":33,"links":191},[],{},3,"\u002Fdotnet\u002Fperformance-deployment\u002Fdeployment",[196,200,204,208,212,216,220,224,228,232,236,240,244,248,252],{"id":197,"difficulty":43,"q":198,"a":199},"dotnet-publish-modes","What are the different publish modes for a .NET application and when do you use each?","`dotnet publish` supports three deployment models: **framework-dependent**,\n**self-contained**, and **single-file**. Each trades off binary size, portability,\nand startup time differently.\n\n```bash\n# Framework-dependent (default) — requires .NET runtime installed on host:\ndotnet publish -c Release\n# Output: ~500 KB of app DLLs + app.exe shim\n# Advantage: small, shares runtime with other apps on the host\n\n# Self-contained — includes the .NET runtime in the output:\ndotnet publish -c Release --self-contained true -r linux-x64\n# Output: ~70 MB including runtime\n# Advantage: no runtime required on host; portable to fresh machines\n\n# Single-file — bundles everything into one executable:\ndotnet publish -c Release --self-contained true -r linux-x64 \\\n    -p:PublishSingleFile=true\n# Output: one ~70 MB file\n# Advantage: simple deployment; one file to copy\u002Fmove\n\n# ReadyToRun (R2R) — ahead-of-time compile for faster startup:\ndotnet publish -c Release -r linux-x64 \\\n    -p:PublishReadyToRun=true\n\n# Trimming — remove unused framework code (careful: reflection-heavy code may break):\ndotnet publish -c Release --self-contained true -r linux-x64 \\\n    -p:PublishTrimmed=true\n# Output: ~20-40 MB depending on what's used\n```\n\n| Mode | Host requirement | Binary size | Startup |\n|------|-----------------|-------------|---------|\n| Framework-dependent | .NET runtime | Small | Normal |\n| Self-contained | Nothing | ~70 MB | Normal |\n| Single-file | Nothing | ~70 MB (one file) | Slightly slower |\n| R2R | Nothing | Larger | Faster |\n\n**Rule of thumb:** Use framework-dependent for servers where you control the\nruntime version. Use self-contained for containers (Docker handles the runtime)\nor edge deployments where installing the runtime is not possible.\n",{"id":201,"difficulty":35,"q":202,"a":203},"kestrel-reverse-proxy","What is Kestrel and why is it typically used behind a reverse proxy in production?","**Kestrel** is ASP.NET Core's built-in cross-platform HTTP server. It handles\nraw TCP\u002FTLS connections and is fast enough for production traffic, but in\nmost deployments it runs behind a reverse proxy (Nginx, Apache, or IIS) that\nhandles TLS termination, static files, rate limiting, and connection management.\n\n```csharp\n\u002F\u002F Program.cs — Kestrel is the default; configure limits explicitly:\nbuilder.WebHost.ConfigureKestrel(opts =>\n{\n    opts.Limits.MaxConcurrentConnections         = 1000;\n    opts.Limits.MaxRequestBodySize               = 10 * 1024 * 1024; \u002F\u002F 10 MB\n    opts.Limits.MinRequestBodyDataRate           = null;  \u002F\u002F disable for uploads\n    opts.Limits.RequestHeadersTimeout            = TimeSpan.FromSeconds(30);\n\n    \u002F\u002F HTTPS directly on Kestrel (without a reverse proxy):\n    opts.ListenAnyIP(443, listenOpts =>\n        listenOpts.UseHttps(\"\u002Fetc\u002Fssl\u002Fcerts\u002Fapp.pfx\", \"cert-password\"));\n\n    \u002F\u002F HTTP\u002F2 and HTTP\u002F3:\n    opts.ListenAnyIP(5000, listenOpts =>\n        listenOpts.Protocols = HttpProtocols.Http1AndHttp2);\n});\n\n\u002F\u002F Behind a reverse proxy — forward headers so HTTPS links are correct:\napp.UseForwardedHeaders(new ForwardedHeadersOptions\n{\n    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,\n});\n\u002F\u002F Must come BEFORE UseAuthentication \u002F UseRouting\n\n\u002F\u002F Nginx config snippet (reverse proxy to Kestrel on port 5000):\n\u002F\u002F server {\n\u002F\u002F listen 443 ssl;\n\u002F\u002F location \u002F {\n\u002F\u002F proxy_pass         http:\u002F\u002Flocalhost:5000;\n\u002F\u002F proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;\n\u002F\u002F proxy_set_header   X-Forwarded-Proto $scheme;\n\u002F\u002F }\n\u002F\u002F }\n```\n\n**Rule of thumb:** Always call `UseForwardedHeaders` when running behind a\nreverse proxy. Without it, `Request.Scheme` returns `http` even for HTTPS\nrequests, breaking redirect URLs and cookie `Secure` flags.\n",{"id":205,"difficulty":43,"q":206,"a":207},"environment-config","How does ASP.NET Core handle configuration across different environments?","ASP.NET Core layers configuration from multiple sources in priority order.\nHigher-priority sources override lower ones. The `ASPNETCORE_ENVIRONMENT`\nenvironment variable selects environment-specific files.\n\n```csharp\n\u002F\u002F Default configuration loading order (last wins):\n\u002F\u002F 1. appsettings.json              — base config, committed to source control\n\u002F\u002F 2. appsettings.{Environment}.json — environment overrides (Development, Production)\n\u002F\u002F 3. Environment variables          — injected by host OS \u002F orchestrator \u002F Docker\n\u002F\u002F 4. Command-line arguments         — highest priority, useful for one-off overrides\n\n\u002F\u002F appsettings.json:\n\u002F\u002F { \"ConnectionStrings\": { \"Default\": \"Server=localhost;Database=dev\" } }\n\n\u002F\u002F appsettings.Production.json:\n\u002F\u002F { \"ConnectionStrings\": { \"Default\": \"Server=prod-db;Database=app\" } }\n\n\u002F\u002F Environment variables override any file:\n\u002F\u002F ConnectionStrings__Default=Server=prod-db;Database=app\n\u002F\u002F (double underscore = colon separator for nested keys)\n\n\u002F\u002F Access in code:\nvar connStr = builder.Configuration.GetConnectionString(\"Default\");\n\n\u002F\u002F Secrets in development — user secrets (never committed):\n\u002F\u002F dotnet user-secrets set \"ConnectionStrings:Default\" \"Server=localhost;...\"\n\n\u002F\u002F Secrets in production — environment variables or Azure Key Vault:\nbuilder.Configuration.AddAzureKeyVault(\n    new Uri($\"https:\u002F\u002F{vaultName}.vault.azure.net\u002F\"),\n    new DefaultAzureCredential());\n\n\u002F\u002F ASPNETCORE_ENVIRONMENT controls which appsettings file is loaded:\n\u002F\u002F Development → appsettings.Development.json\n\u002F\u002F Staging      → appsettings.Staging.json\n\u002F\u002F Production   → appsettings.Production.json (default if not set)\n```\n\n**Rule of thumb:** Commit `appsettings.json` and environment-specific files\nwith non-sensitive defaults. Never commit secrets — use environment variables\nin production and `dotnet user-secrets` in development.\n",{"id":209,"difficulty":35,"q":210,"a":211},"docker-multistage","How do you write a production-ready multi-stage Dockerfile for an ASP.NET Core application?","A **multi-stage Dockerfile** uses the SDK image to build and the smaller\nruntime image to run, keeping the final image lean and free of build tools.\n\n```dockerfile\n# Stage 1: restore and build (uses full SDK)\nFROM mcr.microsoft.com\u002Fdotnet\u002Fsdk:8.0 AS build\nWORKDIR \u002Fsrc\n\n# Copy project files first to leverage layer caching:\nCOPY [\"MyApp\u002FMyApp.csproj\", \"MyApp\u002F\"]\nRUN dotnet restore \"MyApp\u002FMyApp.csproj\"\n\n# Copy source and publish:\nCOPY . .\nWORKDIR \u002Fsrc\u002FMyApp\nRUN dotnet publish \"MyApp.csproj\" -c Release -o \u002Fapp\u002Fpublish \\\n    --no-restore\n\n# Stage 2: final runtime image (no SDK, no build tools)\nFROM mcr.microsoft.com\u002Fdotnet\u002Faspnet:8.0 AS final\nWORKDIR \u002Fapp\n\n# Run as non-root (security hardening):\nRUN useradd --uid 1001 --no-create-home appuser\nUSER appuser\n\n# Copy only the publish output:\nCOPY --from=build \u002Fapp\u002Fpublish .\n\nENV ASPNETCORE_ENVIRONMENT=Production\nENV ASPNETCORE_URLS=http:\u002F\u002F+:8080\n\nEXPOSE 8080\nENTRYPOINT [\"dotnet\", \"MyApp.dll\"]\n```\n\n```bash\n# Build and run:\ndocker build -t myapp:latest .\ndocker run -p 8080:8080 \\\n    -e ConnectionStrings__Default=\"Server=db;Database=app\" \\\n    myapp:latest\n```\n\nMulti-stage builds reduce the final image from ~1 GB (SDK) to ~250 MB (runtime).\nRunning as a non-root user prevents the container process from escalating\nprivileges if compromised.\n\n**Rule of thumb:** Always copy the project file and run `dotnet restore` as a\nseparate `COPY`\u002F`RUN` step before copying source code. Docker caches the restore\nlayer until the `.csproj` changes, making subsequent builds much faster.\n",{"id":213,"difficulty":35,"q":214,"a":215},"health-probe-kubernetes","How do you configure Kubernetes liveness and readiness probes for an ASP.NET Core app?","Kubernetes uses **liveness** probes to restart an unhealthy container and\n**readiness** probes to stop routing traffic to a container that is not ready.\nASP.NET Core's health checks map directly to these two probe types.\n\n```csharp\n\u002F\u002F Program.cs — separate endpoints for liveness and readiness:\nbuilder.Services.AddHealthChecks()\n    .AddDbContextCheck\u003CAppDbContext>(tags: [\"readiness\"])\n    .AddRedis(\"localhost:6379\",      tags: [\"readiness\"]);\n\napp.MapHealthChecks(\"\u002Fhealth\u002Flive\", new HealthCheckOptions\n{\n    Predicate = _ => false,  \u002F\u002F always 200 if process is running\n});\n\napp.MapHealthChecks(\"\u002Fhealth\u002Fready\", new HealthCheckOptions\n{\n    Predicate = check => check.Tags.Contains(\"readiness\"),\n    ResultStatusCodes =\n    {\n        [HealthStatus.Healthy]   = StatusCodes.Status200OK,\n        [HealthStatus.Degraded]  = StatusCodes.Status200OK,   \u002F\u002F still route traffic\n        [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,\n    },\n});\n```\n\n```yaml\n# Kubernetes deployment — liveness and readiness probes:\nspec:\n  containers:\n  - name: myapp\n    image: myapp:latest\n    ports:\n    - containerPort: 8080\n    livenessProbe:\n      httpGet:\n        path: \u002Fhealth\u002Flive\n        port: 8080\n      initialDelaySeconds: 5   # wait for startup\n      periodSeconds: 10\n      failureThreshold: 3      # restart after 3 consecutive failures\n    readinessProbe:\n      httpGet:\n        path: \u002Fhealth\u002Fready\n        port: 8080\n      initialDelaySeconds: 10\n      periodSeconds: 5\n      failureThreshold: 2      # pull from load balancer after 2 failures\n```\n\n**Rule of thumb:** Liveness checks should only verify the process is not deadlocked\n(no external dependency checks). Readiness checks should verify all dependencies\nthe app needs to serve traffic are available. A liveness failure restarts the pod;\na readiness failure only removes it from the load balancer.\n",{"id":217,"difficulty":35,"q":218,"a":219},"graceful-shutdown","How do you implement graceful shutdown in ASP.NET Core?","**Graceful shutdown** lets the app finish in-flight requests before stopping.\nASP.NET Core handles SIGTERM automatically but the default shutdown timeout\n(5 s) may be too short for long-running requests.\n\n```csharp\n\u002F\u002F Increase the shutdown timeout:\nbuilder.Host.ConfigureHostOptions(opts =>\n    opts.ShutdownTimeout = TimeSpan.FromSeconds(30));\n\n\u002F\u002F Or per web host:\nbuilder.WebHost.UseShutdownTimeout(TimeSpan.FromSeconds(30));\n\n\u002F\u002F IHostedService — implement long-running background work with cancellation:\npublic class OrderProcessorService : BackgroundService\n{\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            var order = await _queue.DequeueAsync(stoppingToken);\n            if (order is null) continue;\n\n            try   { await ProcessOrderAsync(order, stoppingToken); }\n            catch (OperationCanceledException) { break; } \u002F\u002F shutting down — stop cleanly\n            catch (Exception ex)               { _logger.LogError(ex, \"Processing failed\"); }\n        }\n        _logger.LogInformation(\"OrderProcessorService stopped gracefully\");\n    }\n}\n\n\u002F\u002F IHostApplicationLifetime — hook into specific lifecycle events:\npublic class StartupService : IHostedService\n{\n    private readonly IHostApplicationLifetime _lifetime;\n\n    public Task StartAsync(CancellationToken ct)\n    {\n        _lifetime.ApplicationStopping.Register(() =>\n            _logger.LogInformation(\"Shutdown signal received\"));\n        return Task.CompletedTask;\n    }\n    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;\n}\n```\n\nIn Kubernetes, a `preStop` hook can add a sleep before SIGTERM to let the\nload balancer drain connections before the process starts shutting down:\n\n```yaml\nlifecycle:\n  preStop:\n    exec:\n      command: [\"\u002Fbin\u002Fsh\", \"-c\", \"sleep 5\"]\n```\n\n**Rule of thumb:** Always pass `CancellationToken` through all async operations\nin background services. If you don't, a shutdown signal is ignored and the\nprocess may be forcibly killed, dropping in-flight work.\n",{"id":221,"difficulty":35,"q":222,"a":223},"azure-app-service","How do you deploy an ASP.NET Core application to Azure App Service?","Azure App Service hosts ASP.NET Core applications without managing virtual\nmachines. Deployment can be done via GitHub Actions, Azure DevOps, or the\nAzure CLI.\n\n```bash\n# Create an App Service Plan and Web App via Azure CLI:\naz group create --name myapp-rg --location eastus\naz appservice plan create --name myapp-plan --resource-group myapp-rg \\\n    --sku B1 --is-linux\naz webapp create --resource-group myapp-rg --plan myapp-plan \\\n    --name myapp-prod --runtime \"DOTNETCORE|8.0\"\n\n# Deploy from local publish output:\ndotnet publish -c Release -o .\u002Fpublish\naz webapp deployment source config-zip \\\n    --resource-group myapp-rg --name myapp-prod \\\n    --src .\u002Fpublish.zip\n\n# Configure app settings (environment variables):\naz webapp config appsettings set --resource-group myapp-rg --name myapp-prod \\\n    --settings \"ConnectionStrings__Default=Server=...\" \\\n               \"ASPNETCORE_ENVIRONMENT=Production\"\n```\n\n```yaml\n# GitHub Actions workflow:\n- name: Deploy to Azure App Service\n  uses: azure\u002Fwebapps-deploy@v3\n  with:\n    app-name: myapp-prod\n    publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}\n    package: .\u002Fpublish\n```\n\nKey App Service features for .NET:\n- **Deployment slots** — blue\u002Fgreen deployments with warm-up\n- **Auto-scaling** — scale out based on CPU or HTTP queue depth\n- **Managed Identity** — access Key Vault without storing credentials\n- **Health check integration** — automatically restart unhealthy instances\n\n**Rule of thumb:** Use App Service deployment slots for zero-downtime releases —\ndeploy to a staging slot, warm it up, then swap to production in a single\natomic operation.\n",{"id":225,"difficulty":35,"q":226,"a":227},"ci-cd-github-actions","How do you set up a CI\u002FCD pipeline for a .NET application using GitHub Actions?","GitHub Actions provides first-class .NET support via `actions\u002Fsetup-dotnet`.\nA typical pipeline builds, tests, and deploys the application on every push\nto main.\n\n```yaml\n# .github\u002Fworkflows\u002Fdeploy.yml\nname: Build and Deploy\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build-and-test:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions\u002Fcheckout@v4\n\n    - name: Setup .NET\n      uses: actions\u002Fsetup-dotnet@v4\n      with:\n        dotnet-version: '8.0.x'\n\n    - name: Restore dependencies\n      run: dotnet restore\n\n    - name: Build\n      run: dotnet build --no-restore -c Release\n\n    - name: Test\n      run: dotnet test --no-build -c Release \\\n           --collect:\"XPlat Code Coverage\" \\\n           --results-directory .\u002Fcoverage\n\n    - name: Publish coverage\n      uses: codecov\u002Fcodecov-action@v4\n      with:\n        directory: .\u002Fcoverage\n\n    - name: Publish\n      run: dotnet publish src\u002FMyApp\u002FMyApp.csproj \\\n           -c Release -o .\u002Fpublish\n\n    - name: Upload artifact\n      uses: actions\u002Fupload-artifact@v4\n      with:\n        name: publish\n        path: .\u002Fpublish\n\n  deploy:\n    needs: build-and-test\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs\u002Fheads\u002Fmain'  # only deploy on main push\n    environment: production               # requires manual approval if configured\n\n    steps:\n    - uses: actions\u002Fdownload-artifact@v4\n      with:\n        name: publish\n        path: .\u002Fpublish\n\n    - name: Deploy to Azure\n      uses: azure\u002Fwebapps-deploy@v3\n      with:\n        app-name: myapp-prod\n        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}\n        package: .\u002Fpublish\n```\n\n**Rule of thumb:** Separate `build-and-test` and `deploy` into two jobs with\n`needs`. This ensures that tests pass before deployment runs, and the deploy\njob can require manual approval for production via GitHub Environments.\n",{"id":229,"difficulty":35,"q":230,"a":231},"configuration-secrets","How do you manage secrets securely in a .NET production deployment?","Never store secrets in source code or committed configuration files. .NET has\nseveral mechanisms for injecting secrets at runtime.\n\n```csharp\n\u002F\u002F Development: dotnet user-secrets (per-developer, never committed)\n\u002F\u002F dotnet user-secrets init\n\u002F\u002F dotnet user-secrets set \"ConnectionStrings:Default\" \"Server=localhost...\"\n\u002F\u002F Stored in ~\u002F.microsoft\u002Fusersecrets\u002F\u003Cproject-guid>\u002Fsecrets.json\n\n\u002F\u002F Production option 1: environment variables (simplest)\n\u002F\u002F Set in the OS, Docker, or Kubernetes:\n\u002F\u002F CONNECTIONSTRINGS__DEFAULT=Server=prod-db;...\n\u002F\u002F (double underscore maps to colon in .NET config)\n\n\u002F\u002F Production option 2: Azure Key Vault (recommended for Azure deployments)\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets\nbuilder.Configuration.AddAzureKeyVault(\n    new Uri($\"https:\u002F\u002F{vaultName}.vault.azure.net\u002F\"),\n    new DefaultAzureCredential()); \u002F\u002F uses Managed Identity in Azure, dev credentials locally\n\n\u002F\u002F Production option 3: Kubernetes secrets\n\u002F\u002F kubectl create secret generic myapp-secrets \\\n\u002F\u002F --from-literal=ConnectionStrings__Default=\"Server=prod-db;...\"\n\u002F\u002F Mounted as environment variables in the pod spec:\n\u002F\u002F env:\n\u002F\u002F - name: ConnectionStrings__Default\n\u002F\u002F valueFrom:\n\u002F\u002F secretKeyRef:\n\u002F\u002F name: myapp-secrets\n\u002F\u002F key: ConnectionStrings__Default\n\n\u002F\u002F Validate required configuration at startup — fail fast:\nbuilder.Services.AddOptions\u003CDatabaseOptions>()\n    .BindConfiguration(\"Database\")\n    .ValidateDataAnnotations()\n    .ValidateOnStart(); \u002F\u002F throw on startup if required fields are missing\n```\n\n**Rule of thumb:** Always use `ValidateOnStart()` on critical configuration.\nA startup crash with a clear message (\"ConnectionString is required\") is far\nbetter than a runtime NullReferenceException discovered under load.\n",{"id":233,"difficulty":76,"q":234,"a":235},"performance-profiling","What tools do you use to profile and diagnose performance issues in a .NET application?",".NET ships several built-in diagnostic tools that work on Linux and Windows\nwithout attaching a GUI profiler to production.\n\n```bash\n# dotnet-trace — capture a CPU and allocation trace:\ndotnet tool install --global dotnet-trace\ndotnet-trace collect --process-id \u003Cpid> --duration 00:00:30 \\\n    --profile cpu-sampling\n\n# Analyze with PerfView or SpeedScope (speedscope.app):\ndotnet-trace convert trace.nettrace --format Speedscope\n\n# dotnet-dump — capture and analyze a memory dump:\ndotnet tool install --global dotnet-dump\ndotnet-dump collect --process-id \u003Cpid>\ndotnet-dump analyze core_20260623.dump\n> dumpheap -stat   # top types by allocation count\n> gcroot \u003Caddress> # find what's keeping an object alive\n\n# dotnet-counters — live metrics in the terminal:\ndotnet tool install --global dotnet-counters\ndotnet-counters monitor --process-id \u003Cpid> \\\n    System.Runtime Microsoft.AspNetCore.Hosting\n\n# Outputs real-time:\n# [System.Runtime]\n#     GC Heap Size (MB)          87\n#     Gen 0 GC Count             42\n#     ThreadPool Queue Length     0\n#     CPU Usage (%)              12\n```\n\n```csharp\n\u002F\u002F In code — BenchmarkDotNet for micro-benchmarks:\n[MemoryDiagnoser]\npublic class StringBenchmarks\n{\n    [Benchmark(Baseline = true)]\n    public string Interpolation() => $\"Hello {name}!\";\n\n    [Benchmark]\n    public string SpanBased()     =>\n        string.Create(6 + name.Length, name, (buf, n) =>\n        {\n            \"Hello \".AsSpan().CopyTo(buf);\n            n.AsSpan().CopyTo(buf[6..]);\n            buf[^1] = '!';\n        });\n}\n```\n\n**Rule of thumb:** Profile first, optimize second. Use `dotnet-counters` to\nspot elevated GC pressure or thread pool starvation before reaching for a\nfull trace. Most .NET performance problems are GC allocation issues, not\nalgorithmic complexity — `[MemoryDiagnoser]` in BenchmarkDotNet reveals\nallocations per operation.\n",{"id":237,"difficulty":35,"q":238,"a":239},"deployment-checklist","What is the production deployment checklist for an ASP.NET Core application?","Shipping to production involves configuration, observability, security hardening,\nand infrastructure concerns beyond just running `dotnet publish`.\n\n```csharp\n\u002F\u002F 1. Environment is set to Production:\n\u002F\u002F ASPNETCORE_ENVIRONMENT=Production\n\u002F\u002F (disables developer exception pages, enables compressed responses)\n\n\u002F\u002F 2. Secrets are in environment variables or Key Vault — NOT in committed files\n\n\u002F\u002F 3. HTTPS enforced:\napp.UseHttpsRedirection();\napp.UseHsts(); \u002F\u002F sends Strict-Transport-Security header\n\n\u002F\u002F 4. Security headers:\napp.Use(async (ctx, next) =>\n{\n    ctx.Response.Headers.Append(\"X-Content-Type-Options\", \"nosniff\");\n    ctx.Response.Headers.Append(\"X-Frame-Options\",        \"DENY\");\n    ctx.Response.Headers.Append(\"Referrer-Policy\",        \"strict-origin-when-cross-origin\");\n    await next();\n});\n\n\u002F\u002F 5. Response compression (saves bandwidth):\nbuilder.Services.AddResponseCompression(opts =>\n    opts.EnableForHttps = true);\napp.UseResponseCompression();\n\n\u002F\u002F 6. Health check endpoints registered and tested (see health-probe-kubernetes)\n\n\u002F\u002F 7. Structured logging to a central sink (Seq, Application Insights, ELK)\n\n\u002F\u002F 8. Database migrations run before startup (not during startup in production):\n\u002F\u002F Run as a separate job\u002Finit container:\n\u002F\u002F dotnet ef database update --project MyApp.Data\n\n\u002F\u002F 9. Connection pool tuning:\n\u002F\u002F \"MaxPoolSize=200;Min Pool Size=10;Connection Timeout=30\"\n\n\u002F\u002F 10. Data protection key ring persisted (ASP.NET Core cookie\u002FJWT signing keys):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToAzureBlobStorage(blobClient)\n    .ProtectKeysWithAzureKeyVault(keyIdentifier, credential);\n```\n\n**Rule of thumb:** The most common production bugs are: wrong environment name\n(still `Development`), secrets not injected, data protection keys not shared\nbetween instances (cookie auth breaks), and migrations not run. Verify all\nfour before every first-time production deployment.\n",{"id":241,"difficulty":35,"q":242,"a":243},"blue-green-rolling","What are blue-green and rolling deployment strategies, and how do you implement them for .NET apps?","**Blue-green deployment** runs two identical production environments (blue and\ngreen). Traffic is switched from the current (blue) to the new (green) in a\nsingle atomic step. **Rolling deployment** gradually replaces old instances\nwith new ones, keeping some capacity available throughout.\n\n```yaml\n# Kubernetes rolling deployment (default strategy):\nspec:\n  replicas: 4\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxUnavailable: 1   # at most 1 pod down at a time\n      maxSurge: 1         # at most 1 extra pod during rollout\n  template:\n    spec:\n      containers:\n      - name: myapp\n        image: myapp:v2   # update this field to trigger a rollout\n```\n\n```bash\n# Trigger a rolling update by updating the image:\nkubectl set image deployment\u002Fmyapp myapp=myapp:v2\n\n# Monitor rollout status:\nkubectl rollout status deployment\u002Fmyapp\n\n# Roll back if the new version is unhealthy:\nkubectl rollout undo deployment\u002Fmyapp\n```\n\n```csharp\n\u002F\u002F Blue-green via Azure App Service deployment slots:\n\u002F\u002F 1. Deploy new version to the \"staging\" slot (pre-warmed, no traffic):\n\u002F\u002F    az webapp deployment source config-zip --slot staging ...\n\n\u002F\u002F 2. Swap slots atomically — staging becomes production:\n\u002F\u002F    az webapp deployment slot swap --slot staging --target-slot production\n\n\u002F\u002F 3. If issues arise, swap back in seconds:\n\u002F\u002F    az webapp deployment slot swap --slot production --target-slot staging\n\n\u002F\u002F Slot swap warm-up — ASP.NET Core startup hooks:\nbuilder.Services.Configure\u003CIISServerOptions>(opts =>\n    opts.AutomaticAuthentication = false);\n\n\u002F\u002F The slot swap waits for the app to return 200 on \u002Fhealth\u002Fready\n\u002F\u002F before completing the swap:\napp.MapHealthChecks(\"\u002Fhealth\u002Fready\");\n```\n\n**Rule of thumb:** Use rolling updates in Kubernetes for normal deploys —\nthey are zero-downtime by default. Use blue-green (deployment slots or a\nsecond Kubernetes deployment) when you need an instant rollback path with\nno traffic impact.\n",{"id":245,"difficulty":35,"q":246,"a":247},"ef-migrations-cicd","How should you run Entity Framework Core migrations in a CI\u002FCD pipeline?","Running `database update` inside the application startup (`DbContext.Migrate()`)\nis risky in multi-instance deployments because all instances race to apply the\nsame migration simultaneously. The safer pattern is to apply migrations as a\ndedicated step before the application starts.\n\n```csharp\n\u002F\u002F Avoid this in production — causes races with multiple instances:\n\u002F\u002F app.Services.GetRequiredService\u003CAppDbContext>().Database.Migrate();\n\n\u002F\u002F Safe pattern 1: migration as a separate CLI step (GitHub Actions \u002F Azure Pipelines):\n\u002F\u002F dotnet ef database update --project MyApp.Data --startup-project MyApp\n\u002F\u002F Run this AFTER the new binaries are deployed but BEFORE traffic is switched.\n\n\u002F\u002F Safe pattern 2: dedicated migrator project \u002F job:\n\u002F\u002F Create a console app that only runs migrations, then exits:\nvar app = Host.CreateDefaultBuilder(args)\n    .ConfigureServices((ctx, services) =>\n        services.AddDbContext\u003CAppDbContext>(opts =>\n            opts.UseNpgsql(ctx.Configuration.GetConnectionString(\"Default\"))))\n    .Build();\n\nawait using var scope = app.Services.CreateAsyncScope();\nvar db = scope.ServiceProvider.GetRequiredService\u003CAppDbContext>();\nawait db.Database.MigrateAsync();\n\u002F\u002F Process exits here — used as a Kubernetes init container\n```\n\n```yaml\n# Kubernetes init container — runs migrations before the app pod starts:\nspec:\n  initContainers:\n  - name: db-migrate\n    image: myapp:v2\n    command: [\"dotnet\", \"MyApp.Migrator.dll\"]\n    env:\n    - name: ConnectionStrings__Default\n      valueFrom:\n        secretKeyRef:\n          name: myapp-secrets\n          key: db-connection\n  containers:\n  - name: myapp\n    image: myapp:v2\n    # App container starts only after init container succeeds\n```\n\nWrite **backward-compatible migrations**: add nullable columns or columns with\ndefaults first, deploy the app, then tighten constraints in a follow-up migration.\nThis keeps old and new code running against the same schema simultaneously.\n\n**Rule of thumb:** Never call `Migrate()` in `Program.cs` in production. Run\nmigrations as an init container or a pipeline step. If the migration fails, the\napp pods should not start — this prevents serving traffic against a broken schema.\n",{"id":249,"difficulty":76,"q":250,"a":251},"data-protection-multiinstance","Why does ASP.NET Core Data Protection need special configuration in multi-instance deployments?","**Data Protection** generates the keys used to encrypt cookies, anti-forgery\ntokens, and TempData. By default keys are stored in memory and are unique per\nprocess. In a multi-instance deployment, instance A cannot decrypt a cookie\nencrypted by instance B, causing authentication failures and anti-forgery errors.\n\n```csharp\n\u002F\u002F Problem — default in-memory keys: each pod has different keys\n\u002F\u002F User authenticates on pod A, next request goes to pod B → 401 or CSRF failure\n\n\u002F\u002F Solution: share keys across all instances via a common store + key ring protection\n\n\u002F\u002F Option 1: Azure Blob Storage + Azure Key Vault (recommended for Azure):\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.DataProtection.Blobs\n\u002F\u002F dotnet add package Azure.Extensions.AspNetCore.DataProtection.Keys\nbuilder.Services.AddDataProtection()\n    .PersistKeysToAzureBlobStorage(\n        new Uri(\"https:\u002F\u002Fmyaccount.blob.core.windows.net\u002Fkeys\u002Fdata-protection.xml\"),\n        new DefaultAzureCredential())\n    .ProtectKeysWithAzureKeyVault(\n        new Uri(\"https:\u002F\u002Fmyvault.vault.azure.net\u002Fkeys\u002Fdata-protection-key\"),\n        new DefaultAzureCredential())\n    .SetApplicationName(\"myapp\"); \u002F\u002F isolates keys from other apps in the same store\n\n\u002F\u002F Option 2: File system share (shared NFS \u002F Azure Files):\nbuilder.Services.AddDataProtection()\n    .PersistKeysToFileSystem(new DirectoryInfo(\"\u002Fmnt\u002Fshared\u002Fkeys\"))\n    .ProtectKeysWithCertificate(certificate);\n\n\u002F\u002F Option 3: Redis (StackExchange.Redis):\n\u002F\u002F dotnet add package Microsoft.AspNetCore.DataProtection.StackExchangeRedis\nbuilder.Services.AddDataProtection()\n    .PersistKeysToStackExchangeRedis(redisConnection, \"DataProtection-Keys\")\n    .SetApplicationName(\"myapp\");\n\n\u002F\u002F Option 4: SQL Server \u002F EF Core:\n\u002F\u002F dotnet add package Microsoft.AspNetCore.DataProtection.EntityFrameworkCore\nbuilder.Services.AddDataProtection()\n    .PersistKeysToDbContext\u003CAppDbContext>();\n```\n\nKey ring settings to review:\n- `SetApplicationName` — required when multiple apps share the same key store\n- `SetDefaultKeyLifetime` — keys rotate every 90 days by default\n- `DisableAutomaticKeyGeneration` — use only in secondary read-only instances\n\n**Rule of thumb:** Always configure a shared key store when running more than\none instance of an ASP.NET Core app. The most common symptom of missing key\nsharing is intermittent 400 errors on form submissions (anti-forgery) or users\nbeing logged out randomly (cookie decryption fails).\n",{"id":253,"difficulty":35,"q":254,"a":255},"container-resource-limits","How do resource limits in Docker and Kubernetes affect .NET runtime behaviour?","The .NET runtime reads CPU and memory limits from cgroup information set by\nDocker or Kubernetes. Misconfigured limits cause GC thrashing, thread pool\nstarvation, or OOMKilled pods.\n\n```yaml\n# Kubernetes resource requests and limits:\nresources:\n  requests:\n    memory: \"256Mi\"   # minimum guaranteed — used for scheduling\n    cpu: \"250m\"       # 0.25 cores guaranteed\n  limits:\n    memory: \"512Mi\"   # hard cap — OOMKill if exceeded\n    cpu: \"1000m\"      # 1 core max (throttled, not killed)\n```\n\n```csharp\n\u002F\u002F .NET automatically sizes GC and thread pool based on cgroup limits\n\u002F\u002F (requires .NET 3.0+ with cgroup v1, .NET 6+ for cgroup v2):\n\n\u002F\u002F Verify the runtime sees correct limits at startup:\nvar cpuCount    = Environment.ProcessorCount;  \u002F\u002F reflects cgroup CPU quota\nvar gcHeapBytes = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; \u002F\u002F reflects limit\n\n_logger.LogInformation(\"CPU count: {Cpu}, GC heap limit: {Heap} MB\",\n    cpuCount, gcHeapBytes \u002F 1024 \u002F 1024);\n\n\u002F\u002F Server GC (default) — one heap per CPU core; bad with low limits:\n\u002F\u002F With 0.25 cores, Server GC still allocates heaps based on physical cores.\n\u002F\u002F Switch to Workstation GC for low-CPU containers:\n\u002F\u002F In .csproj:\n\u002F\u002F \u003CServerGarbageCollection>false\u003C\u002FServerGarbageCollection>\n\u002F\u002F Or via runtimeconfig.json:\n\u002F\u002F { \"configProperties\": { \"System.GC.Server\": false } }\n\n\u002F\u002F Thread pool: defaults to min threads = CPU count.\n\u002F\u002F With 0.25 vCPU, min threads = 1 by default. Tune if needed:\nThreadPool.SetMinThreads(workerThreads: 4, completionPortThreads: 4);\n```\n\nCommon pitfalls:\n- **No memory limit** — GC uses host total RAM, OOMKilled when the pod exceeds\n  the node's available memory\n- **CPU throttling** — `cpu: limits: 250m` means the container is throttled when\n  it exceeds 250 ms of CPU per 1000 ms; causes latency spikes, not crashes\n- **Server GC + low CPU** — Server GC creates one thread per logical core;\n  with many small-CPU containers this is wasteful\n\n**Rule of thumb:** Set `memory limits` to 1.5–2x the normal heap size observed\nin `dotnet-counters`. Set CPU limits to at least 1 core for latency-sensitive\nservices — CPU throttling introduces tail-latency spikes that are hard to\ndiagnose.\n",{"description":32},"Deployment interview questions — dotnet publish modes, Kestrel, Docker multi-stage builds, Kubernetes health probes, graceful shutdown, and CI\u002FCD.","dotnet\u002Fperformance-deployment\u002Fdeployment","ongyx4Vy8GfP4eZ8R4DkatFLI6BAXD0mU6E0RuP9sE4",1782244097589]