← Home
.NETWeb APIReported by candidates

What is the MVC pattern, and how does a request flow through ASP.NET Core MVC: routing, controller, action, view?

34 reports in the question index (8 linked below), the second most-asked web question: Capgemini (2), Cognizant, CGI, Wipro, EPAM, DXC Technology and others, mostly in the first technical round.

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

Invented to show the shape, in C#. Tap Next to walk one arrangement for contract UDC-50213, then a refresh.

UDC.sln · where this file sits
  • Controllers/
Diagram · step 1Staff press Save: the browser POSTs the form to .
ArrangementsController.cs · line 2 · 1 of 10: the MVC base class that gives this class View() and .

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
  • MVC splits a web page into three parts. The model is the data the page shows (the business rules live in services, not in the model), the view is the Razor template that turns the model into HTML, and the controller takes the request, asks the services for data, builds the model and picks the view. That way the page layout, the business rules and the request handling can change separately.
  • In ASP.NET Core a request first goes through the middleware pipeline. Routing matches the URL to a controller and an action, either with a route pattern in Program.cs or with attributes on the controller. Then MVC creates a new controller for that request, and dependency injection hands it the services it needs.
  • Before the action runs, model binding turns the URL, the query string and the form into the action's parameters, and validation checks them. Filters run around all of this: authorization filters first, then resource filters, then action filters just before and after the action.
  • The action returns a result. For a page that's a view result: the Razor view engine finds the view by the controller and action name, fills it with the model and writes HTML. In a Web API the same pipeline returns JSON instead, so the view is really a separate Angular app in the browser.
  • The trap I watch for is binding a form straight to a database entity. We once lost dealer credit limits that way at BMW FS, because fields that weren't on the form were saved as zero. Every form now gets its own view model with only the fields it edits.
Check your answer: tick what you said

You said 0 of 9

Then add your example: UDC Finance collections site (invented example)
  • Situation: UDC Finance's collections team uses an internal ASP.NET Core MVC site to set up payment arrangements: a customer who is behind agrees to pay a smaller amount each week. Staff pressed refresh after Save to check it worked, the browser sent the form again, and the customer got two arrangements.
  • What I did: after a successful save, the action no longer returns the confirmation view. It returns a redirect to a separate confirmation page, the Post/Redirect/Get pattern: post the form, redirect, then the browser gets the page with a normal GET. (GET reads a page, POST sends a form, 302 means "go to this other address". On refresh the browser repeats the last request it made; if the page on screen was the reply to a POST, that sends the form again.)
  • Result: refresh now repeats only the harmless GET, so no more duplicate arrangements. The confirmation page can be bookmarked and shared.
  • What I'd say: a POST that changes data ends in a redirect, never a view, so refresh and the back button can't repeat it.

See it

1/8A staff member opens dealer 42. Kestrel, the web server in the app, takes the request.

In plain words

The senior answer · about 2 minutes
The pattern, in my words
  • MVC separates three jobs. The controller handles the request: it reads what came in, asks a service to do the work, and decides what to send back. The model is the data that goes to the page, usually a view model shaped for that one page. The view is a Razor template that turns the model into HTML and contains no business rules.
  • What that buys you is that each part changes on its own. Our BMW FS dealer operations site was redesigned twice without touching a controller, and the controllers are unit-tested without rendering any HTML.
One request, start to finish
  • The request goes through the middleware pipeline first (): exception handling, HTTPS, static files, routing, authentication, authorization. Routing matches the URL to an endpoint, which for MVC is one action on one controller.
  • Then MVC takes over. Authorization filters run, then resource filters, and only then does MVC create the controller, a new object for every request, with its services injected by the DI container. Model binding fills the action's parameters from the route, the query string and the form, and validation records any errors in .
  • Action filters run just before and after the action. The action returns a result: a view result is executed by the Razor view engine, which finds the view by convention, renders HTML with the model, and result filters wrap that step. The response then goes back up through the middleware.
MVC and Web API today
  • In ASP.NET Core they are the same framework. is everything an API needs; adds views on top. An API controller marked [ApiController] returns JSON through the same lifecycle, and the "view" is the Angular portal or another system.
  • In old ASP.NET they were separate: MVC 5 in and Web API 2 in , with their own base classes and pipelines. That is why older interviewers ask for the difference.
What I watch for in production
  • Never bind a form to an entity. On the BMW FS dealer operations site, an edit page bound the entity directly, so the credit limit, which wasn't on the form, was saved as zero for 37 dealers. That is the incident in Deep. The fix and the rule: one view model per form, with only the fields the form edits.
  • A POST that changes data ends with a redirect, never a view, so refresh can't submit it twice. And business rules stay in services, not controllers, so the API and the MVC site share them.

One edit, every stage

A BMW FS staff member saves a dealer's new phone number. Every stage of the lifecycle, in the order it runs, then the redirect.

1/9Request. Save POSTs the form to /Dealers/Edit/42.

The story

  1. Before

    BMW FS's dealer operations team uses an internal website to look after its car dealers: their contacts, regions and credit lines. In its first version, one class did everything for each page: it read the URL, ran the SQL, applied the business rules and built the HTML by gluing strings together.

    1/4Before: one class per page reads the URL, runs SQL, checks rules and builds HTML.

  2. Why it had to change

    When one class mixes the request, the data, the rules and the HTML, every change touches everything, and nothing can be tested without a browser. Designers can't change the page without reading SQL.

  3. How it works now

    MVC gives each job its own part, and ASP.NET Core runs them in a fixed order for every request. Words you will meet: GET is the browser asking for a page; routing matches the address to code; the controller is the class that handles the request, and an action is the method in it that does so; the service holds the rules and the database work; Razor is HTML with small bits of C#. Here is who talks to whom for one page:

    1/4The browser asks for /Dealers/Details/42. Routing sends it to the controller's action.

  4. What we gained

    Each part changes on its own and can be tested on its own. The rest of the card follows one request through every stage of ASP.NET Core MVC, in order.

In the lending API

The controller: each marked line is one stage of the lifecycle; the diagram above shows it running.

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

Lending.sln · where this file sits
  • src/DealerOps/Controllers/
⋯ 5 lines in Deep⋯ 4 lines in Deep
DealersController.cs · line 5 · 1 of 3Controller created: MVC makes a new controller for every request, and the DI container passes in .

The code, file by file

The dealer details page on the BMW FS dealer operations site: start-up, controller, model and view. Tap Next to walk one request for dealer 42.

Lending.sln · where these 4 files sit
  • src/DealerOps/
    • Controllers/
    • Models/
    • Views/Dealers/
Diagram · step 1A staff member opens dealer 42. Kestrel, the web server in the app, takes the request.
Program.cs · line 3 · 1 of 9Registers MVC: controllers plus the Razor view engine. An API-only app calls instead.

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: Editing a dealer's phone number set its credit limit to zero

P2 · serious, has a workaroundBMW FScame with a deployDealer operations site (ASP.NET Core MVC on App Service)

Composed, not a real event at BMW FS. The mechanism is real ASP.NET Core and EF Core, checked by a run on .NET 10: binding a form to an entity and calling `Update` saves every column, so a field the form didn't send is saved as its default. Times, counts, names, ticket and PR numbers are composed.

The story in 5 lines
  1. On Thu 8 Oct, 37 BMW FS dealers couldn't fund new contracts: the funding check said their credit limit was $0. The limits are what BMW FS lets each dealer have financed at once.
  2. Every one of them had had a contact detail edited on the internal dealer operations site since Tuesday's release 7.2, which added an "Edit contact" page.
  3. The page's POST action bound the form straight to the entity and saved it with EF Core's (EF Core is the library that talks to the database; tells it to write every column). The form had name, phone and email; the credit limit wasn't on it, so model binding left it at 0, and saved every column.
  4. hid the page with a and restored the 37 limits from the audit table (a table that keeps the history of every change) by 13:10 ET. The fix: a view model with only the three contact fields, loading the dealer and changing only those.
  5. The rule since: no form binds an entity, and an alert fires when any dealer's credit limit drops to zero.
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 and runbooks, flips feature flags (switches that hide a feature without a deploy) with approval.
BMW FS dealer credit manager
Owns dealer credit limits. Confirms the restored numbers.
BMW FS IT change manager
Approves the expedited change.

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

1/8 Detect

Thursday: 37 dealers can't fund new contracts. Their credit limit reads $0.

Thu 8 Oct, 09:40 ET (19:10 IST)BMW FS dealer support, then the L2 pod

  • What: dealer support gets calls from three dealers in an hour: "funding refused, credit limit exceeded" on contracts that should be well inside their limits.
  • How we saw it: checks the funding service's logs in . 37 dealers have failed every funding check since Tuesday evening, all with the same reason, and the database shows their limit as 0.
  • Why it hid: a refused funding looks normal to the system; dealers go over their limits every day. No error was logged, so no alert fired.
App Insights: refused funding by dealer
traces
| where timestamp > ago(3d)
| where message has "Funding refused"
| extend dealer = tostring(
    customDimensions.DealerId)
| summarize refused = count()
    by dealer, bin(timestamp, 1d)
| order by refused desc
In theory

An alert on data that should never happen: a live dealer with a credit limit of zero.

In practice

Only errors raised alerts, and a refused funding isn't an error. Dealers found it first.

Try it yourself · 15 minutes, on your Mac
  1. dotnet new mvc -n BindLab, add EF Core with SQLite, and a entity with and . Seed one dealer with a limit of 2,500,000.
  2. Write a POST action Edit(Dealer dealer) that calls and . Post only and with curl.
  3. Read the dealer back: the limit is 0.
  4. Change the action to take a view model with only , load the dealer, copy the phone, save. Post CreditLimit=9999999 as well: the limit doesn't change.
You should see

Binding a form to an entity saves what the form didn't send as defaults, and saves extra fields an attacker adds. A view model per form closes both.

The senior move

"Every form gets a view model with only the fields it edits. The action loads the entity and copies those fields, so nothing missing becomes zero and nothing extra gets saved."

Sources Sourced: model binding sets only the properties found in the request; marks every property modified; binding entities directly allows over-posting (ASP.NET Core docs, "Model binding" and the EF Core MVC tutorial on over-posting). Checked with a real run on .NET 10 with EF Core and SQLite. Composed: BMW FS's systems, dates, counts, names, ticket and numbers, the and the SQL.

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

Reportedeasy · 20 minutes

Write a with two actions: lists dealers, Details(int id) shows one or returns 404. Use a view model per page, and write the view.

Grafically: CRUD operations in MVC
Stuck? Hints
  1. Where does the data come from: the controller, or a service it is given?
  2. What should return when the dealer doesn't exist?
  3. What does the view need to know about the type of its model?
Show a solution
Lending.sln · where these 2 files sit
  • Controllers/
  • Views/Dealers/
DealersController.cs · line 14 · 1 of 3 turns each dealer into a small row for the list page. d => ... means: for each dealer d, make this.
What the interviewer checks
  • A service injected through the constructor; no database code in the controller.
  • for a missing dealer, so the answer is a 404, not a crash.
  • A view model per page, not the entity.
  • @model at the top of the view.
  • Can name the lifecycle step each line belongs to.

Task 2

Question index: Mustmedium · 20 minutes

Write an action filter that logs how long each action takes, register it for every controller, and say where in the lifecycle it runs and what it can't measure.

LTM Limited: the request lifecycle in ASP.NET MVC
Stuck? Hints
  1. Which filter type runs just before and just after the action?
  2. Where do you keep the start time between the two methods?
  3. Does view rendering happen inside or outside that window?
Show a solution
Lending.sln · where these 2 files sit
  • src/DealerOps/
    • Filters/
Diagram · step 1Model binding is done. Just before the action, MVC calls the filter's before method.
Program.cs · line 2 · 1 of 1Filters.Add<TimingFilter>() runs the filter for every action of every controller.
What the interviewer checks
  • (or ) with before and after methods.
  • Registered globally in AddControllersWithViews(o => o.Filters.Add<...>()).
  • A , not subtraction.
  • Knows it misses model binding and view rendering; a result filter or middleware measures more.
With AI tools

Interviewers now ask how AI changes this work. Assistants scaffold MVC controllers and views quickly, and the scaffolds often bind the form straight to the EF entity and call . That is exactly the over-posting and lost-field trap in the incident, so I check every generated POST action for a view model, a redirect after saving, and . I also ask for the request flow in my own words in reviews, because a scaffold that works can still put business rules in the controller.

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