← Home
.NETWeb APIReported by candidates

What are filters in ASP.NET Core (authorization, resource, action, exception, result), in which order do they run, and how are they different from middleware?

17 reports in the question index (8 linked below): Hexaware (4), Publicis Sapient, Capgemini, TraviYo, Excellon Software, Colan Infotech, Infosys and others, in screening and first technical rounds.

Say this first · about 30 seconds
Make it yours · UDC Finance payments API (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next: a payment at 12:30 UTC (01:30 in New Zealand's summer), inside the window.

UDC.sln · where these 3 files sit
  • Controllers/
  • Filters/
Diagram · step 112:30 UTC: the portal posts a payment. MVC reaches the action filter first.
PaymentsController.cs · line 3 · 1 of 2The attribute puts the filter on this action only. You write [BlockDuringFisBatch]: C# lets you leave off the ending of the class name.

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • Filters are code that MVC runs before and after particular steps of handling a request, so things like auditing, validation or turning exceptions into error responses are written once instead of in every action.
  • There are five kinds, in this order. Authorization filters first, a check that the request may run. Then resource filters, which can answer from a cache. Then the parameters get filled in, and action filters run just before and after the action. Exception filters handle a throw, and result filters wrap writing the response.
  • The difference from middleware is scope and knowledge. Middleware runs for every request and sees the HTTP request; after routing it can see which endpoint was chosen, but not the arguments or the result. Filters run only for controller actions and know the action, its arguments and its result. So cross-cutting HTTP things like HTTPS, CORS and authentication go in middleware, and action-aware things go in filters.
  • You register a filter globally in the controller options, or as an attribute on a controller or an action. One trap I've seen: a filter registered as a single instance is shared by every request, so a field in it got overwritten by parallel requests and the audit log recorded the wrong approver.
Check your answer: tick what you said

You said 0 of 8

Then add your example: UDC Finance payments API (invented example)
  • Situation: every night between 01:00 and 03:00 New Zealand time, the FIS core runs its , which posts the day's payments to the contracts. Payments that our API posted during that window sometimes landed half-processed, and fixed them by hand the next morning.
  • What I did: an action filter attribute, [BlockDuringFisBatch], on the four actions that post money. During the window it sets context.Result to 503 Service Unavailable ("try again later"), so the action never runs. Reads stay open all night. UTC is the world clock time with no time zone; the filter converts it to New Zealand time, so daylight saving is handled.
  • Result: no more half-posted payments, and no manual fixes. The UDC portal shows "payments open again at 3 am" when it gets the 503.
  • What I'd say: a filter fits because only some actions need it and it must stop the action before it runs: it sets the result, and MVC skips the action.

See it

1/9A dealer app asks for contract 7. Middleware runs first, for every request.

In plain words

The senior answer · about 2 minutes
What filters are for
  • Filters are MVC's hooks around an action, for code that every action needs but that isn't its own job: an audit record on every change, timing, a check that a contract belongs to the dealer, or mapping a domain exception to a status code. You write it once and register it globally, per controller or per action.
The order, and what each one sees
  • Authorization filters run first; in practice [Authorize] is checked even earlier, by the authorization middleware, so I almost never write one. Resource filters wrap everything after them, including model binding, which is why a response cache lives there. Then model binding fills the parameters, and action filters run just before and after the action, with the arguments and the result in hand.
  • If the action or an action filter throws, exception filters run instead of the normal path. Result filters wrap writing the response, and they're skipped when an exception filter produced it. Any filter can short-circuit by setting context.Result: we block payment posts during the FIS core's nightly batch that way, and the action never runs.
Middleware or filter?
  • Middleware runs for every request, health checks and static files included, and sees the HTTP context. After routing it can see which endpoint was chosen and its attributes, but not the bound arguments, model state or the action's result. So HTTPS, CORS, authentication, request logging and the global exception handler are middleware.
  • A filter only runs for controller actions but knows which action it is, its arguments and its result. So auditing a contract change, or turning into a 404, is a filter. One trap: an exception filter doesn't see errors from middleware, resource or result filters or JSON serialization (turning the C# object into JSON text), so the real safety net is with ProblemDetails, and exception filters only map specific domain errors.
What I watch for
  • How a filter is registered decides its lifetime. Added by type, MVC creates one per request and DI fills its constructor. Added as an instance with , one object serves every request (like a singleton: one object shared by the whole app), so a field in it is shared. That bit us at BMW FS: the approval audit recorded the wrong approver under load, which is the incident in Deep. Our rule now: filters keep no per-request state in fields, and a test fires parallel approvals.

Every filter, in order

The real output of a test app with one filter of each kind logging a line, for a normal call and then for an action that throws.

1/6In. Middleware (HTTP), authorization filter (1), then resource filter before (2).

The story

  1. Before

    BMW FS's contracts API has about forty actions that change a contract: approve, fund, change the payment date. (An action is one method of a controller class that answers one kind of request. A controller is a class that groups the actions for one area, like contracts. MVC is the part of ASP.NET Core that sends each request to the right action: .) Every change must leave an audit record, a line saying who did what and when, which auditors check. In the first version every action started with the same few lines that wrote that record, copied by hand.

    1/4Before: each action writes its own audit record, with the same lines copied in.

  2. Why it had to change

    Code that every action needs, but that isn't the action's own job, is called cross-cutting: auditing, timing, turning errors into a standard response. Copied into each action, it gets forgotten or changed in one place and not the others.

  3. How it works now

    A filter is a class that MVC calls at a fixed point while it handles a request for a controller action (a public method that answers a request). You write it once and register it for every action, for one controller, or for one action. There are five kinds, and each runs at its own point. Middleware is the similar idea one level out: code that every HTTP request passes through before MVC even starts (, ). The main diagram follows one request through all of them.

  4. What we gained

    The audit lines live in one class, every action is covered, and the actions only do their own work. The rest of the card shows where each kind of filter runs and when to pick a filter over middleware.

In the lending API

The audit filter: written once, run around every action; the diagram above shows where.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/BmwFs.Contracts.Api/Filters/
⋯ 8 lines in Deep⋯ 3 lines in Deep
AuditFilter.cs · line 1 · 1 of 3An action filter: is an with two methods, one MVC calls just before the action, one just after. (The other method, run after the action, is in the full view.)

The code, file by file

The BMW FS contracts API: start-up, a controller, and the audit filter. Tap Next to walk one request.

Lending.sln · where these 3 files sit
  • src/BmwFs.Contracts.Api/
    • Controllers/
    • Filters/
Diagram · step 1A dealer app asks for contract 7. Middleware runs first, for every request.
Program.cs · line 3 · 1 of 6Registers MVC. options => { ... }: a small function .NET calls once to fill in MVC's settings.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: The approval audit trail recorded the wrong approver

P2 · serious, has a workaroundBMW FScame with a deployContracts API (ASP.NET Core on App Service)

Composed, not a real event at BMW FS. The mechanism is real ASP.NET Core, checked by a run on .NET 10: a filter added with Filters.Add(new ...) is one object shared by every request, so a field set in its before-method can be overwritten by a parallel request before its after-method reads it. Times, counts, names, ticket and PR numbers are composed.

The story in 5 lines
  1. On Mon 12 Oct BMW FS's monthly report flagged 14 contract approvals where the approver was the same person who created the contract, which the rules forbid.
  2. The credit officers named in the audit trail said they never approved those contracts. request logs showed someone else had made each call.
  3. Release 5.1 had changed how was registered: from by type (one per request) to one shared instance. The filter kept the approver in a field, and parallel approvals overwrote it.
  4. An the same evening registered it by type again. The fix: no fields at all, an async filter with a local variable, a parallel-approvals test, and 45 audit rows rebuilt from request logs.
  5. The rule since: filters keep no per-request state in fields, and the audit trail is compared with request logs every night.
Who's who (5 people)
You
.NET developer on the vendor team (L3). Owns the fix.
Tech lead
Vendor senior. Runs the bridge call, reviews every PR.
L2 pod
Vendor support. Runs queries, deploys with approval.
BMW FS compliance analyst
Owns the maker-checker report and the audit trail.
BMW FS IT change manager
Approves the emergency change.

Tap a step. The map lights the place where it happens.

1/8 Detect

The report: 14 contracts approved by their own maker.

Mon 12 Oct, 10:00 ET, US Eastern (19:30 IST)BMW FS compliance analyst, then the L2 pod

  • What: the monthly maker-checker report (the person who creates a contract change can't also approve it) flags 14 contracts where the audit trail shows the same person as maker and approver.
  • How we saw it: the analyst asks two of those credit officers. Both say they never approved those contracts. opens a ticket with the vendor team.
  • Why it hid: every audit row looked valid: a real officer, a real contract, the right time. Only a rule that compares people noticed anything.
In theory

The audit trail is checked against an independent record of who called the API.

In practice

Nobody compared them. A monthly report found 14 of the 45 wrong rows by luck: the rest named a different, innocent officer.

Try it yourself · 10 minutes, on your Mac
  1. dotnet new webapi -n FilterLab. Write an action filter that stores a request header in a field in and prints it in .
  2. Register it with options.Filters.Add(new AuditFilter()), and add an action that waits 200 ms for one id and 20 ms for another.
  3. Send two requests 50 ms apart with different headers: the slow one prints the fast one's header.
  4. Change to with a local variable and repeat: each prints its own.
You should see

A filter added as an instance is shared by all requests, so a field in it is shared too. Local variables, or a filter created per request, keep each request's data its own.

The senior move

"Filters, like singletons, must not keep per-request data in fields. I use an async filter with locals, and I test audit code with parallel requests."

Sources Sourced: filter instances added with Filters.Add(instance) are reused across requests, while Filters.Add<T>() creates them per request (ASP.NET Core docs, "Filters": filter scopes and dependency injection, ); is a standard control in lending operations. Checked with a real run on .NET 10: two parallel requests through a shared filter instance logged the wrong user; an with a local logged both correctly. Composed: BMW FS's systems, dates, counts, names, ticket and numbers and the .

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 10 minutes

Write an action filter that logs which user called which action, and register it globally so every controller gets it.

Prudent Corporate Advisory: action filters and a custom filter
Stuck? Hints
  1. Which interface has a before-method and an after-method around the action?
  2. Where do you find the user and the action's name?
  3. Where do you register a filter so it runs for every controller?
Show a solution
Lending.sln · where these 2 files sit
  • Filters/
Diagram · step 1A dealer app asks for contract 7. Middleware runs first, for every request.
Program.cs · line 1 · 1 of 2Registers MVC.
What the interviewer checks
  • (or ), not middleware.
  • User from context.HttpContext.User, action from context.ActionDescriptor.
  • Registered with options.Filters.Add<AuditFilter>() in , so DI fills the constructor.
  • Structured log message (named placeholders), no per-request state in fields.
  • Can say where it runs: after model binding, around the action.

Task 2

Question index: Mustmedium · 15 minutes

Write an exception filter that turns into a 404 with a ProblemDetails body, register it globally, and say which errors it will not catch.

Hexaware: filters in MVC and their types
Stuck? Hints
  1. Which interface is called only when the action throws?
  2. How do you check the exception's type and use it in one step?
  3. What must you set so MVC knows the error is dealt with?
Show a solution
Lending.sln · where these 2 files sit
  • Filters/
Diagram · step 2MVC calls the exception filter with the exception.
Program.cs · line 3 · 1 of 1Global: any action that throws this exception gets a 404.
What the interviewer checks
  • that handles only and leaves other exceptions alone.
  • with status 404, a title and the detail.
  • Sets context.Result and context.ExceptionHandled = true.
  • Knows it misses errors from middleware, result filters and JSON writing; is the safety net.
With AI tools

Interviewers now ask how AI changes this. Assistants write a filter in seconds, and two mistakes show up again and again in what they generate: a field that holds per-request state in a filter added with Filters.Add(new ...), and an exception filter offered as "global error handling". I check every generated filter for how it's registered and what it stores, and keep the global error handling in .

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked