Filter lifetime: ServiceFilter vs TypeFilter
“How do filters get services from DI, and what is the difference between ServiceFilter and TypeFilter?”
How you register a filter decides whether each request gets its own filter object or every request shares one.
See it
1/6 keeps the approver in a field, _approver. By type: one object per request.
In plain words
A filter is a class with code that MVC (ASP.NET Core's controller-based style of building web APIs) runs before or after a controller action (). A global filter applies to every action in the app. A filter is a class, so somebody must create its object. Who creates it, and how often, depends on how you register it. Four ways, three results. New object for each request: options.Filters.Add<AuditFilter>() (global, by type: MVC creates it, and DI fills its constructor) and [TypeFilter(typeof(AuditFilter))] (MVC creates a new one each time, no registration needed, DI only fills its constructor). Whatever you registered: [ServiceFilter(typeof(AuditFilter))] on a controller or action makes MVC ask the DI container for the filter, so you must register it (AddScoped<AuditFilter>() and so on). Scoped means one per request; singleton means one for the whole app. One object for every request: options.Filters.Add(new AuditFilter(...)) (you created one object yourself), or [ServiceFilter] with a singleton. Why it matters: a field in a shared filter is shared by every request running at that moment. If one request writes it and another reads it, data mixes. The code below shows that bug and the fix.
- By type,
Filters.Add<T>()and[TypeFilter]create a new filter per request, with constructor parameters resolved from DI; needs no registration. [ServiceFilter(typeof(T))]resolves the filter itself from the container, so it must be registered, and its lifetime is the registered one: scoped is per request, singleton is shared.Filters.Add(new T())adds one instance that every request shares, so it must hold no per-request state in fields.- My rule: filters keep per-request data in locals or , never in fields, so they're safe however they're registered.
In the lending API
The four ways side by side, then the bug a shared filter causes. The filter stamps the approver on a response header, standing in for an audit row. Three files, each visited in turn (Program.cs registers, the controller picks, the filter file shows the bug), so the walk goes back to the first case in each. Tap Next line.
_approver. By type: one object per request.What they ask next
Interviewers check that you can explain every line. Tap a question to open it.
Why would you ever add a filter as an instance?
When it has no state and needs values you have at start-up, like a setting. It saves one object per request. That is fine only while the filter keeps no per-request data in fields, and the next person to touch it must know that. The incident is what happens when they don't.
What does mean on a filter factory?
and are filter factories: they build the real filter.
IsReusable = truetells MVC it may keep the built filter and use it again for later requests, so it can act like a shared instance. It's a hint, not a promise of one object. Leave it false (the default) unless the filter keeps no per-request state.
Traps
- Don't say
SayIt uses the lifetime you registered. Register it as a singleton and it's shared.[ServiceFilter]makes the filter scoped. - Don't say
Fields in a filter are fine; each request has its own.SayOnly when it's created per request. Locals are safe every time.