Scenario-based .NET interviews describe an ASP.NET Core situation instead of asking for a definition, such as a body that binds as null, an endpoint anyone can call, memory that climbs until the pod restarts, or a webhook that charges twice. Every question here is a concrete scenario. The answers walk through the order of checks, say what to look at first, and name what would change your mind. This page is written for anyone facing an ASP.NET Core round built on scenarios like these. Practice by reading only the question, talking through your own plan out loud, then comparing it with the sample. For core concepts like middleware, DI lifetimes and change tracking, start with the main .NET page.
Search all questions by round, difficulty and level, or save the ones you want to practice.
Read it: the container is building OrderService and has nothing registered for IEmailSender.
Look for the registration: is it missing, registered as the concrete class only, or inside an extension method nobody calls?
Guard it: keep scope and build validation on so a missing registration fails at startup, not on a live request.
“The message tells me exactly what's wrong: to build OrderService the container needs an IEmailSender, and nothing is registered for that interface. So I'd search Program.cs and any AddSomething extension methods for IEmailSender. Common causes are that it was never registered, that someone registered the concrete SmtpEmailSender but the constructor asks for the interface, or that the registration lives in an extension method in another project that Program.cs never calls. I'd also check it's the same IEmailSender, because two interfaces with the same name in different namespaces catch people out. The fix is one line, like AddScoped<IEmailSender, SmtpEmailSender>(). What would change my mind is if it's registered and still fails, because then I'd look at the namespace or at a second constructor the container is picking.”
Fixing it by creating the dependency with new inside OrderService instead of registering it.
First suspect: the template only maps Swagger inside if (app.Environment.IsDevelopment()).
Confirm the environment: if ASPNETCORE_ENVIRONMENT is not set, the app runs as Production.
Decide on purpose: turn it on for the test server if wanted, but keep it off or protected in real production.
“If only Swagger is missing, I'd bet on the environment check. The project template wraps the Swagger calls in an IsDevelopment check, and when ASPNETCORE_ENVIRONMENT isn't set on the server, ASP.NET Core assumes Production. So the code that maps Swagger simply never runs. I'd confirm by logging app.Environment.EnvironmentName at startup. Then it's a decision, not just a fix. For a test server, I'd set the environment to Staging and allow Swagger there, or read a config flag for it. For real production, I'd usually keep it off or put it behind authentication, because it hands anyone a map of the API. If the environment turned out to be Development already, I'd check whether a reverse proxy is sending the path somewhere else.”
Setting the server to Development to make Swagger appear, which also turns on detailed error pages for everyone.
Name first: on Linux a colon in a variable name often isn't allowed; use a double underscore, Payment__ApiKey.
Is it there: confirm the variable exists in the running container, and the app was restarted after setting it.
Who wins: list the configuration sources and check nothing later, like command-line arguments, overrides it.
“Environment variables normally beat appsettings.json in the default setup, so either the variable isn't reaching the app or its name doesn't map to the key. My first suspect is the colon. Many Linux shells and container tools can't use a colon in a variable name, so .NET accepts a double underscore instead: Payment__ApiKey becomes Payment:ApiKey. Next I'd exec into the container and print the environment to see the variable really exists, and check the app was restarted, because it reads variables at startup. Then I'd check the code reads the same section name, spelled the same way. Locally, with the same variables set, I'd print the configuration debug view, which shows which provider supplied each value. What would change my mind is if the value is right in configuration but wrong in the class, because then something is caching it at startup.”
Putting the real key into appsettings.json and committing it to make the container work.
Binding source: without [ApiController], a complex parameter is not read from the body unless you add [FromBody].
Request: check the Content-Type header and the raw body the client really sends.
Shape: compare JSON names with the class, and check for public fields or non-public setters the serializer skips.
“I'd start with where the parameter is bound from. If the controller has [ApiController], a complex type comes from the body automatically. Without it, ASP.NET Core looks in the form and query string, so I'd need [FromBody]. Next I'd capture the actual request in the browser's network tab: is Content-Type application/json, and what's really in the body? If the object arrives but every property is empty, the names probably don't match. The default serializer ignores case, but customer_id still won't match CustomerId. It also skips public fields and properties with private setters unless you tell it otherwise. What would change my mind is getting a 400 or 415 instead of an empty object, because then the framework is rejecting the request, and the response body tells me why.”
Switching the parameter to a string and parsing JSON by hand instead of finding why binding fails.
[ApiController] return 400 for bad JSON when a plain controller just gives you null?Who answered: [ApiController] returns a 400 problem response when model state is invalid, before the action runs.
Why required: with nullable reference types on, a non-nullable string property is treated as required.
Fix on purpose: make optional fields string?, and keep required the ones that truly are.
“The breakpoint never hitting tells me the framework answered, not my code. With [ApiController], if model validation fails, ASP.NET Core sends a 400 with a problem details body automatically and the action never runs. So the question is why MiddleName counts as required. My guess is nullable reference types are on, and the property is declared as string rather than string?. MVC treats a non-nullable reference type as if it had a Required attribute. The client left the field out, so validation failed. The fix is to decide what the contract should be: middle name is optional, so I'd make it string?. I'd read the errors object in the response, because it names each failing field. If the error was about converting a value instead, I'd look at the JSON types, like an enum sent as text.”
Turning off nullable reference types for the whole project to make one error go away.
Why only the browser: CORS is a browser rule; Postman and curl ignore it.
The preflight: look at the OPTIONS request and its response headers in the network tab.
Server setup: exact origin match, UseCors in the right place, and no AllowAnyOrigin with credentials.
“First, Postman working doesn't prove much, because CORS is enforced by the browser, not the server. So I'd open the browser's network tab. For a POST with JSON, the browser sends a preflight OPTIONS request first, and I'd check whether its response has the Access-Control-Allow-Origin header. If it doesn't, I'd check the policy: the origin must match exactly, scheme and port included, with no trailing slash. Then the order in Program.cs: UseCors has to come before authorization, or a rejected request, sometimes even the preflight, comes back without CORS headers. If the app sends cookies, AllowAnyOrigin won't work with AllowCredentials; I'd list the real origins. What would change my mind is if the failing request is actually a 500. An unhandled error can come back without the CORS headers, so the browser reports CORS when the real bug is in my code.”
builder.Services.AddCors(o => o.AddPolicy("web", p => p
.WithOrigins("https://app.example.com")
.AllowAnyHeader()
.AllowAnyMethod()));
var app = builder.Build();
app.UseCors("web");
app.UseAuthentication();
app.UseAuthorization();
Allowing every origin with credentials in production just to make the error disappear.
Why duplicates: providers retry when they don't get a quick success, and a slow handler invites retries.
Idempotency: store the event ID with a unique constraint; if the insert fails, it's a repeat, so return success.
Respond fast: verify the signature, record the event, answer 200, and process it in the background.
Guard the state: only apply a credit if the order isn't already paid.
“Providers deliver webhooks at least once, not exactly once, and ours makes it worse by doing slow work before replying, so the provider times out and retries while the first call is still running. I'd fix both sides. First, idempotency: when a webhook arrives, I verify the signature, then insert the event ID into a table with a unique constraint. If that insert fails on the constraint, I've already seen this event, so I return 200 and do nothing. Second, speed: after recording the event I respond straight away and hand the work to a background processor, ideally through an outbox so nothing is lost. Third, the business logic itself checks state, so it only credits an order that isn't already credited, inside a transaction. I'd also find and reverse the double credits already made.”
Adding a lock or a delay to the handler and assuming the provider will stop sending duplicates.
Now: roll back, or send the old field name again, so old apps stop crashing.
Why it happened: the wire name was tied to the C# name, so a rename was a breaking change.
Future: pin names with [JsonPropertyName], make changes additive, and version the API for true breaks.
Catch it: compare the OpenAPI document or run contract tests in CI.
“First, stop the crashes. Old app versions stay installed for months, so I'd roll back or quickly ship a fix that sends the old field name again, possibly alongside the new one. Then I'd tell the mobile team what happened and when it's fixed. The root cause is that our JSON names came straight from C# property names, so a harmless-looking rename was a breaking change. For the future I'd pin wire names with JsonPropertyName on public contracts, so refactoring can't change them. I'd agree a rule that changes are additive: add new fields, keep old ones until usage drops, and use API versioning for real breaks. And I'd add a CI step that compares the generated OpenAPI document with the last release and fails on removed or renamed fields. What would change my mind is an internal-only API, where coordinated releases are simpler.”
Telling mobile users to update the app and leaving the API as it is.
This endpoint: is [Authorize] or RequireAuthorization there at all, and is [AllowAnonymous] on it, its controller or a base class?
Contain: add authorization now and check logs for calls that already happened.
Default deny: set a fallback policy so every endpoint needs a signed-in user unless it opts out.
“First I'd look at how the endpoint got its protection, or didn't. In ASP.NET Core authorization is per endpoint, so a new controller without [Authorize], or a minimal API without RequireAuthorization, is open even if everything around it is locked. I'd also check for [AllowAnonymous] on the controller or a base class, because it overrides [Authorize] wherever it appears. I'd fix it straight away and search the access logs for calls to that route. The real lesson is that security shouldn't depend on remembering an attribute. I'd set a fallback authorization policy that requires an authenticated user, so any endpoint without its own rules is locked by default, and only login and health checks opt out. I'd also add a test that lists every endpoint and fails if one has no authorization rule.”
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
app.MapGet("/health", () => Results.Ok()).AllowAnonymous();
Adding the attribute to that one endpoint and calling it done, with nothing to stop the next one.
Read the code: 403 means authentication worked and authorization failed, so the token itself is fine.
See the claims: log the claim types and values the API actually built from the token.
Match them: point RoleClaimType at the claim that holds roles, and check spelling and case.
“A 403 rather than a 401 tells me the token was accepted and the problem is the role check. So I'd stop looking at signing keys and look at claims. In a development build I'd log every claim type and value on User for that request. Often the role is there but under a claim name the framework doesn't treat as a role, like roles, groups or a custom user_role, or claim mapping was switched off so the name stays short while the framework expects the long role claim type. The fix is to set RoleClaimType in the token validation parameters to the claim that really holds roles. I'd also check the exact value, since role names are compared exactly, so admin isn't Admin. What would change my mind is if the roles arrive as one comma-separated string, because then it needs splitting into separate claims.”
Regenerating tokens or changing signing keys when the status code already says authentication succeeded.
Escalate: tell your lead or security contact now; it's an incident, not a cleanup task.
Stop the leak: find the source, like HTTP logging with request bodies, custom body logging, or EF sensitive data logging.
Clean up: restrict and purge the affected logs, and reset passwords if many people could read them.
Prevent: redact sensitive fields, log safe summaries instead of whole objects, and add a review rule.
“I'd stop the bug work and tell my lead or the security contact straight away, because passwords in logs is an incident, and other people may need to decide on resets. Then I'd find the source. Usually it's HTTP logging configured to include request bodies, a custom middleware that logs every body, logging the whole login object with structured logging, or EF Core's sensitive data logging left on, which logs parameter values. I'd switch that off with a config change and deploy. Then, with the security team, restrict access to the affected logs and purge them where we can, including copies in any log platform. If a lot of people could read those logs, forcing affected users to reset passwords is the safe call. Finally I'd add redaction for sensitive fields and a test that checks the login body never appears in logs.”
Quietly deleting the log lines and fixing the config without telling anyone.
Treat it as an incident: it's a data leak, so escalate, and turn off any suspect cache first.
Hunt shared state: singletons and static fields holding the current user, or an HttpContext kept past its request.
Check caches: any cache key built from the URL alone for a response that depends on the user.
Prove it: a load test with two users that checks each response belongs to its caller.
“Only under load and only briefly sounds like shared state being overwritten between requests, so I'd treat it as a data leak and tell my lead straight away. Then I'd look for three things. First, anything registered as a singleton, or any static field, that stores the current user, like a CurrentUser service where middleware sets the user ID on each request. Two requests at once overwrite each other. Second, any code that keeps the HttpContext itself in a field instead of reading it per request. Third, caches: a response or data cache keyed only by URL will happily serve one customer's orders to the next. To prove it, I'd write a load test with two accounts that asserts every response belongs to the caller. The fix is making per-user state scoped, and putting the user ID in every cache key.”
Dismissing it because it can't be reproduced locally, or adding a lock around the controller.
Is it a leak: watch GC heap size and gen 2 over time with dotnet-counters; a real leak keeps rising after collections.
Compare snapshots: take two heap dumps hours apart and see which types grew and what roots them.
Usual suspects: unbounded static or singleton collections, caches without limits, event subscriptions, disposables resolved from the root provider.
“First I'd make sure it's a leak and not the server garbage collector being generous with memory. I'd run dotnet-counters against the container and watch the GC heap size and gen 2 size. If gen 2 keeps rising after collections, something is holding references. Then I'd take a gcdump early in the day and another a few hours later and compare which types grew, and follow the paths back to their roots. In APIs the answer is usually boring: a static dictionary used as a cache with no removal, IMemoryCache entries with no expiry or size limit, a singleton subscribed to events that never unsubscribes, or disposable transient services resolved from the root provider, which the container holds until the app shuts down. What would change my mind is if managed heap stays flat while process memory grows, because then I'd look at native memory or unreleased streams.”
Raising the memory limit or scheduling a nightly restart and calling the problem solved.
Ask first: how stale may a price be, and who must see a change immediately?
Options: IMemoryCache per instance, a shared distributed cache like Redis, or output caching of whole responses.
Invalidation: with four instances, clearing one memory cache leaves three stale copies.
Stampede: stop many requests rebuilding the same entry at once when it expires.
“My first question is how stale a price is allowed to be, because that decides the design. If a minute or two is fine, IMemoryCache on each instance with a short expiry is the simplest win. The catch is four instances: when an admin changes a price, clearing the cache on one instance leaves three with the old value until they expire. If prices must update at once, I'd use a shared cache like Redis through IDistributedCache, or output caching with a Redis store, and evict by tag when the admin saves. I'd make sure the cache key includes anything that changes the response, like currency or language. I'd also protect against a stampede, where the entry expires and hundreds of requests hit the database together. What would change my mind is if checkout reads these prices, because checkout should use the database price, not a cached one.”
Adding an in-memory cache with no expiry on four instances and assuming admin edits will show up.
Meaning: every pooled connection is in use and none came back in time.
Leaks: raw connections or DbContexts created without using, or long transactions left open.
Holding too long: slow queries, blocking calls, or outside HTTP calls made while a transaction is open.
Measure: count open sessions on the database side and match them to code paths.
“That error means all the connections in the pool are checked out and none were returned within the wait time. The default pool for SQL Server allows 100, so the question is why so many are busy. I'd first look for leaks: Dapper or ADO.NET code that opens a connection without a using, or a DbContext created with new somewhere and never disposed. Then for connections held too long, like slow queries, a transaction open while the code calls an outside API, or sync-over-async blocking that makes each request slower. On the database side I'd list the open sessions and what they're running, which usually points straight at the code path. What would change my mind is if every connection is busy with fast, healthy queries, because then it really is traffic, and I'd look at scaling or raising the pool size carefully.”
Setting Max Pool Size to a huge number and moving on without finding who holds the connections.
See the SQL: log it or call ToQueryString() to check filters and limits reach the database.
Memory filtering: a ToList() or AsEnumerable() before Where pulls the whole table into the app.
Shape: page the results, select only needed columns, and index the columns you filter and sort on.
“Development has a few hundred rows, so anything works there. I'd start by looking at the SQL EF Core actually generates, from the logs or with ToQueryString(). The classic cause is a ToList() or AsEnumerable() too early, or a repository method returning IEnumerable, so the Where runs in memory after the whole table has been loaded. Next I'd check there's paging at all, because returning every order to one screen will never scale. Then I'd run the generated SQL against a production-sized copy and look at the plan: filtering by customer and sorting by date without an index on those columns means a scan. I'd also project to just the fields the list shows. What would change my mind is if the SQL is tight and indexed but still slow, because then I'd look at locking or at the database server itself.”
var rows = await db.Orders
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(o => new OrderRow(o.Id, o.CreatedAt, o.Total))
.ToListAsync(ct);
Raising the command timeout so the query has more time to load the whole table.
Cause: one SaveChanges per row is one round trip per row, and the change tracker keeps growing.
Batch: add rows in chunks, save once per chunk, and clear the tracker between chunks.
Move it: for big files, run the import as a background job and report progress.
Bigger still: use the database's bulk load path when batching isn't enough.
“Calling SaveChanges fifty thousand times means fifty thousand round trips to the database, and every tracked entity stays in the change tracker, so each save gets a bit slower. I'd batch it: add a thousand rows at a time, call SaveChanges once per batch, which EF Core sends in a few batched commands, then clear the change tracker so it doesn't keep growing. I'd also decide what failure means. Should one bad row roll back everything, or just be reported? I'd validate all rows before writing anything. And an import this size shouldn't run inside an HTTP request, so I'd accept the file, queue a background job and let the admin see progress. What would change my mind is much larger files, where I'd use the database's bulk copy feature instead of EF Core.”
foreach (var chunk in rows.Chunk(1000))
{
db.Products.AddRange(chunk.Select(ToEntity));
await db.SaveChangesAsync(ct);
db.ChangeTracker.Clear();
}
Only raising the request timeout so the twenty-minute request can finish.
Ask the goal: what problem is this solving: testing, swapping databases, or hiding EF Core?
What EF gives: DbContext is already a unit of work, and DbSet already works much like a repository.
Costs: a generic layer tends to hide Include, projections and async, or leaks IQueryable anyway.
Middle ground: focused repositories or query services with named methods, and integration tests on a real database.
“I'd start by asking what problem we're solving, because the two reasons given don't hold up well. DbContext already is a unit of work, since SaveChanges commits everything together, and a DbSet already behaves a lot like a repository. A generic IRepository<T> with Get, Add and Delete usually ends up either hiding the things that make EF Core fast, like projections, Include and split queries, or returning IQueryable, which means EF Core leaks through anyway. Swapping databases is rare, and EF Core's providers handle much of that. For testing, mocks of a repository don't test the SQL, so I'd rather run integration tests against a real database in a container. What I'd support is focused repositories or query classes with meaningful names, like GetOverdueInvoices, where they make the domain code clearer. I'd suggest trying that on one feature first.”
Either accepting the pattern because it's common or dismissing it without asking what problem it solves.
What happened: since .NET 6, an exception escaping ExecuteAsync stops the whole host by default.
Per item: catch and log failures inside the loop so one bad message doesn't end the worker.
Bad messages: retry with a delay, then move repeat failures aside instead of retrying for ever.
Don't hide it: setting the behaviour to Ignore leaves a dead worker inside a live API.
“Since .NET 6, if an exception escapes a BackgroundService's ExecuteAsync, the host logs it and shuts down the whole app by default, API included. So one bad message took down everything. I'd first find which message caused it and why. Then I'd change the worker so each message is handled inside its own try and catch: log the error with the message ID, wait a moment, and carry on. A message that keeps failing should go to a dead-letter queue after a few tries, not loop for ever. Shutdown should still work, so I'd let cancellation from the stopping token end the loop cleanly. I wouldn't just switch the host to ignore worker exceptions, because then the worker dies silently while the API looks healthy. I'd also think about running the consumer as its own service.”
while (!stoppingToken.IsCancellationRequested)
{
try
{
await ProcessNextMessageAsync(stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
logger.LogError(ex, "Message processing failed");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
Wrapping everything in a catch that swallows errors without logging, or setting the host to ignore them.
Traffic: make the pod stop getting new requests before it stops serving; readiness fails first, a short pre-stop delay helps.
Host: on SIGTERM Kestrel finishes in-flight requests up to the shutdown timeout; keep it shorter than the pod's grace period.
Workers: honour the stopping token and acknowledge a message only after it's fully processed.
Assume duplicates: make message handlers idempotent anyway.
“Two different problems share one cause: the app stops before its work is done. For failed requests, I'd check the order of events. When a pod is terminated, the load balancer may still route to it for a few seconds, so I'd add a short pre-stop delay and make readiness fail first. Then ASP.NET Core gets SIGTERM, stops accepting connections and waits for in-flight requests up to the host's shutdown timeout. That timeout must fit inside the pod's grace period, or Kubernetes kills it mid-request. For duplicate messages, I'd look at when the worker acknowledges. If it's processing when the stopping token fires and gets killed before acking, the broker redelivers. So workers should stop taking new messages on cancellation and finish the current one. And since redelivery can always happen, I'd make handlers idempotent.”
Blaming the deploy tool and accepting a few errors per release as normal.
Why it cascades: a liveness probe that checks the database restarts healthy pods for someone else's problem.
Split them: liveness only says the process can answer; readiness includes the database.
Tag checks: use health check tags and a predicate per endpoint.
“The pods aren't broken; the database is slow. But because the liveness probe calls the database, Kubernetes thinks every pod is dead and restarts them all, so we get cold starts and a wave of reconnects on top of a struggling database. I'd split the health checks. Liveness should only prove the process can answer a request, with no outside dependencies. Readiness can include the database, so a pod that can't reach it is taken out of the load balancer but not killed. In ASP.NET Core I'd tag the database check as ready and map two endpoints with different predicates. I'd also check the probe timeouts, because a very short timeout under load looks like a failure. What would change my mind is if the pods really do hang, because then liveness failing is doing its job.”
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database", tags: new[] { "ready" });
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
Just making the liveness timeout longer while it still depends on the database.
Timeouts: HttpClient waits 100 seconds by default; set one that matches what checkout can afford.
Cancellation: pass the request's cancellation token down so abandoned requests stop work.
Fail fast: a circuit breaker stops calling a partner that keeps failing.
Degrade: decide the fallback, like a cached price or a clear error, and retry only safe calls.
“First I'd check the timeout, because HttpClient waits 100 seconds by default, far longer than any customer will. I'd set a timeout based on what checkout can afford, a few seconds at most. I'd pass the request's cancellation token into the call, so if the customer gives up, we stop waiting too. Then I'd add resilience through HttpClientFactory: a per-attempt timeout, a circuit breaker so that after repeated failures we stop calling the partner for a short time and fail fast, and retries only for calls that are safe to repeat. Getting a price is safe to retry. Anything that charges money needs an idempotency key first. Finally I'd agree the fallback with the product owner, like using a recently cached price or showing a clear message. What would change my mind is a partner with a strict contract, where we'd follow their retry rules.”
Adding retries with no timeout or circuit breaker, which multiplies the load on a partner that is already struggling.
Who said 413: Kestrel, IIS and a proxy like Nginx each have their own request size limit.
Raise it narrowly: lift the limit only on the upload endpoint, not for the whole app.
Don't buffer: stream the body to storage, or let the client upload straight to object storage with a short-lived link.
“First I'd find out which layer returned the 413. Kestrel's default body limit is about 30 MB, IIS has its own, and Nginx in front has a small default, so the response headers or body usually tell me who answered. Then I'd raise the limit only on the upload endpoint with the RequestSizeLimit attribute, plus the form's multipart limit if needed, and match it on the proxy. For memory, IFormFile buffers the whole upload before my code runs, and copying it into a byte array or MemoryStream holds the whole video in memory. I'd stream the request body straight to storage instead. For files this big, my preferred design is letting the browser upload directly to object storage using a short-lived signed URL, so the API never carries the bytes. What would change my mind is if files must be scanned or processed first, which changes where that work happens.”
Removing the request size limit for the whole app and reading every upload into a byte array.
Why: the app sees a plain HTTP connection from the proxy, so scheme and IP are the proxy's.
Fix: use the forwarded headers middleware for X-Forwarded-For and X-Forwarded-Proto, early in the pipeline.
Trust: only accept those headers from your proxy's addresses, or clients can fake their IP.
“The load balancer ends HTTPS and talks plain HTTP to the app, so the app thinks every request is HTTP and comes from the load balancer. That's why redirect URLs are built with http and the logged IP is wrong. The proxy sends the original details in X-Forwarded-Proto and X-Forwarded-For headers, and the forwarded headers middleware applies them. It has to run early, before authentication, HTTPS redirection and logging, so they see the corrected values. By default it only trusts proxies on the local machine, so I'd add the load balancer's address range as a known network. I wouldn't trust the headers from anywhere, because then any client could claim any IP and dodge IP-based limits. What would change my mind is if the headers never arrive, which I'd check by logging raw request headers once in a test environment.”
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// also add the load balancer's address range as a known network
});
var app = builder.Build();
app.UseForwardedHeaders();
Hard-coding https into redirect URLs instead of fixing how the app reads the original request.
ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.