ASP.NET CORE MVC - Controllers, Actions and Views
Controllers, Actions and Views
Create a new ProductsController, add action methods, understand
IActionResult, use conventional routing, return Razor views, and pass
simple data from a controller to a view using ViewData,
ViewBag and a strongly typed model.
By the end of this tutorial, you will be able to create MVC controllers, define action methods, connect URLs to actions through conventional routing, and return Razor views containing data.
This tutorial assumes a Xubuntu virtual machine with Linux username
xubuntu. The project is located under
/home/xubuntu/aspnet-mvc-tutorial/ProductManagement.
- Review the MVC request flow
- Create
ProductsController - Create the
Indexaction - Create the
Detailsaction - Understand conventional routing
- Return Razor views
- Understand view discovery and diagnose a missing-view error
- Pass data with
ViewData - Pass data with
ViewBag - Create a simple strongly typed view
- Test the routes in the browser
- Complete a hands-on exercise
1. Review the MVC Request Flow
In Part 1, the default application used HomeController.
We will now create a separate controller for product-related requests.
The controller acts as the coordinator between the incoming HTTP request and the response.
2. Open the Existing Project
Open the Xubuntu Terminal. The project created in Part 1 is located under ~/aspnet-mvc-tutorial/ProductManagement. Go to the project directory:
cd ~/aspnet-mvc-tutorial/ProductManagement
Confirm the current location:
pwd
For the xubuntu user, the expected path is:
/home/xubuntu/aspnet-mvc-tutorial/ProductManagement
Open the project in Visual Studio Code:
code .
/Products yet
We will first create both the controller and its matching Razor view.
If ProductsController.Index() returns View() before
Views/Products/Index.cshtml exists, ASP.NET Core will report that
the Index view was not found.
3. Create ProductsController
In the VS Code Explorer, open the Controllers folder.
Create a new file named:
ProductsController.cs
Add the following code:
using Microsoft.AspNetCore.Mvc;
namespace ProductManagement.Controllers;
public class ProductsController : Controller
{
public IActionResult Index()
{
return View();
}
}
What does this code mean?
| Code | Purpose |
|---|---|
using Microsoft.AspNetCore.Mvc; |
Imports MVC types such as Controller and IActionResult. |
ProductsController |
The controller class for product-related requests. |
: Controller |
Inherits MVC controller functionality. |
Index() |
An action method. |
return View(); |
Returns a Razor view to the browser. |
MVC controller class names normally end with Controller.
The URL uses the shorter name. Therefore,
ProductsController is addressed as /Products.
4. What Is an Action Method?
A public method on a controller can act as an MVC action when it is selected by routing.
Our first action is:
public IActionResult Index()
{
return View();
}
When the browser requests /Products, conventional routing can select this
Index() action.
5. What Is IActionResult?
IActionResult is an interface representing the result of an MVC action.
An action can return different kinds of results.
For now, our actions will mainly return View().
6. Conventional Routing
Recall the default route from Program.cs:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
The URL convention is therefore:
Examples:
| URL | Controller | Action | ID |
|---|---|---|---|
/Products |
Products | Index | — |
/Products/Index |
Products | Index | — |
/Products/Details/1 |
Products | Details | 1 |
The route is called conventional routing because the URL follows a predefined controller/action/id pattern.
7. Create the Products View Folder
Before testing the controller, create the Razor view that
Index() expects.
From the project directory, create the Products view folder:
mkdir -p Views/Products
Linux file and folder names are case-sensitive. Use
Products with a capital P.
The structure should now contain:
Views/
├── Home/
├── Products/
├── Shared/
├── _ViewImports.cshtml
└── _ViewStart.cshtml8. Create the Index View
Create the view file:
touch Views/Products/Index.cshtml
Open it in Visual Studio Code:
code Views/Products/Index.cshtml
Add:
@{
ViewData["Title"] = "Products";
}
<h1>Products</h1>
<p>This is the product list page.</p>
Save the file.
Verify the file before running the application
From the project directory, run:
ls -l Views/Products
You should see:
Index.cshtml
The required MVC path is exactly:
Views/Products/Index.cshtml
These paths are not equivalent:
Views/Products/Index.cshtml
Views/products/Index.cshtml
Views/Products/index.cshtml
Run the application
Now start the ASP.NET Core application:
dotnet run
Use the exact localhost address shown after Now listening on:,
then add /Products.
For example:
http://localhost:5223/Products
Your port may be different. Use the port displayed on your VM.
You should now see:
Products
This is the product list page.
The URL /Products now reaches
ProductsController.Index(), which successfully finds and renders
Views/Products/Index.cshtml.
9. How Does View() Find the Correct View?
When ProductsController.Index() executes:
return View();
MVC uses naming conventions to search for a matching view.
The controller name determines the folder, and the action name normally determines the view file.
10. Troubleshooting: “The view 'Index' was not found”
If the browser displays an error similar to:
InvalidOperationException: The view 'Index' was not found.
The following locations were searched:
/Views/Products/Index.cshtml
/Views/Shared/Index.cshtml
this means the controller and action were found, but MVC could not find the
Razor view requested by return View();.
Check the file directly from the terminal:
cd ~/aspnet-mvc-tutorial/ProductManagement
ls -l Views/Products/Index.cshtml
If the file does not exist, create it:
mkdir -p Views/Products
touch Views/Products/Index.cshtml
Then add the Index view content from Section 8, save the file, and refresh
/Products.
An MVC route can successfully find the controller and action while still failing later because the requested Razor view does not exist. Controller discovery and view discovery are separate steps.
11. Create the Details Action
Return to ProductsController.cs and add:
public IActionResult Details(int id)
{
return View();
}
The complete controller now looks like:
using Microsoft.AspNetCore.Mvc;
namespace ProductManagement.Controllers;
public class ProductsController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Details(int id)
{
return View();
}
}
12. Create the Details View
Create the Details view file:
touch Views/Products/Details.cshtml
Open it in Visual Studio Code:
code Views/Products/Details.cshtml
Add:
@{
ViewData["Title"] = "Product Details";
}
<h1>Product Details</h1>
<p>This page will display one product.</p>
Now browse to:
/Products/Details/1
Routing selects ProductsController.Details() and supplies 1
to the id parameter.
13. Display the Route ID Using ViewData
Modify the action:
public IActionResult Details(int id)
{
ViewData["ProductId"] = id;
return View();
}
Then modify Details.cshtml:
@{
ViewData["Title"] = "Product Details";
}
<h1>Product Details</h1>
<p>Product ID: @ViewData["ProductId"]</p>
Test:
/Products/Details/1
/Products/Details/25
/Products/Details/100
The displayed product ID should change according to the URL.
14. What Is ViewData?
ViewData is a dictionary that lets a controller pass small pieces of data
to a Razor view.
Controller:
ViewData["ProductId"] = id;
View:
@ViewData["ProductId"]
It is useful for small values such as page titles or short messages.
Later in this tutorial we will use strongly typed models and ViewModels for structured application data.
15. Pass Data Using ViewBag
ASP.NET Core MVC also provides ViewBag, a dynamic way of passing small values to a view.
Modify Index():
public IActionResult Index()
{
ViewBag.Message = "Welcome to the Product Management System";
return View();
}
Then add this to Views/Products/Index.cshtml:
<p>@ViewBag.Message</p>
Refresh /Products.
16. ViewData versus ViewBag
| ViewData | ViewBag |
|---|---|
| Dictionary-based. | Dynamic property syntax. |
ViewData["Message"] |
ViewBag.Message |
| String key required. | Property-style syntax. |
| Useful for small supplementary values. | Useful for small supplementary values. |
For important structured data, a strongly typed model is usually clearer and safer.
17. Create a Simple Product Model for the View
We will formally study models in Part 3. For now, create a very small class so that we can see how strongly typed views work.
Create:
Models/Product.cs
Add:
namespace ProductManagement.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
18. Return a Product Object from the Controller
Add the model namespace to ProductsController.cs:
using ProductManagement.Models;
Then change Details():
public IActionResult Details(int id)
{
var product = new Product
{
Id = id,
Name = "Sample Product"
};
return View(product);
}
Notice the difference:
return View();
becomes:
return View(product);
The product object is now supplied to the Razor view.
19. Make the Details View Strongly Typed
Replace Views/Products/Details.cshtml with:
@model ProductManagement.Models.Product
@{
ViewData["Title"] = "Product Details";
}
<h1>Product Details</h1>
<p><strong>ID:</strong> @Model.Id</p>
<p><strong>Name:</strong> @Model.Name</p>
The first line:
@model ProductManagement.Models.Product
declares the type of object expected by the view.
The view can then access:
@Model.Id
@Model.Name
Strong typing makes the expected data structure explicit and provides better compile-time checking than passing important application data through dynamic values. Microsoft also recommends strongly typed approaches for model-driven MVC views.
20. Test the Strongly Typed Details Page
Run the application if necessary:
dotnet run
Open:
/Products/Details/7
You should see:
Product Details
ID: 7
Name: Sample Product
Try:
/Products/Details/42
The ID changes because it comes from the route.
21. Compare the Three Ways of Passing Data
| Method | Example | Best use |
|---|---|---|
ViewData |
ViewData["Title"] |
Small supplementary values. |
ViewBag |
ViewBag.Message |
Small supplementary values using dynamic syntax. |
| Strongly typed model | @Model.Name |
Structured application data. |
22. Current Project Structure
ProductManagement/
├── Controllers/
│ ├── HomeController.cs
│ └── ProductsController.cs
├── Models/
│ ├── ErrorViewModel.cs
│ └── Product.cs
├── Views/
│ ├── Home/
│ ├── Products/
│ │ ├── Index.cshtml
│ │ └── Details.cshtml
│ └── Shared/
├── wwwroot/
├── Program.cs
└── ProductManagement.csproj23. Full ProductsController at the End of Part 2
using Microsoft.AspNetCore.Mvc;
using ProductManagement.Models;
namespace ProductManagement.Controllers;
public class ProductsController : Controller
{
public IActionResult Index()
{
ViewBag.Message = "Welcome to the Product Management System";
return View();
}
public IActionResult Details(int id)
{
var product = new Product
{
Id = id,
Name = "Sample Product"
};
return View(product);
}
}
24. Request Flow for /Products
25. Request Flow for /Products/Details/7
26. Hands-On Exercise
Add a new action:
public IActionResult About()
{
ViewBag.Message = "Product Management System tutorial";
return View();
}
Create:
Views/Products/About.cshtml
Use this content:
@{
ViewData["Title"] = "About Products";
}
<h1>About Products</h1>
<p>@ViewBag.Message</p>
Test:
/Products/About
27. Challenge Exercise
Modify Details() so that the product name includes the requested ID.
For example:
/Products/Details/5
should display:
ID: 5
Name: Product 5
Show solution
public IActionResult Details(int id)
{
var product = new Product
{
Id = id,
Name = $"Product {id}"
};
return View(product);
}
28. Knowledge Check
- What naming convention is normally used for MVC controller classes?
- What URL maps to
ProductsController.Index()under the default route? - What is an action method?
- What does
IActionResultrepresent? - What does
return View();do? - Where does MVC normally search for the
ProductsController.Index()view? - What does the optional
{id?}segment represent? - How can a route value be received by an action?
- What is
ViewData? - What is
ViewBag? - What does the Razor
@modeldirective do? - Why is a strongly typed model preferable for structured application data?
Show suggested answers
- The class name normally ends with
Controller. /Productsor/Products/Index.- A controller method selected to handle an HTTP request.
- The result produced by an MVC action.
- It returns a Razor view.
Views/Products/Index.cshtml.- An optional route value named
id. - By declaring a matching action parameter such as
int id. - A dictionary used to pass small values from controller to view.
- A dynamic property-based mechanism for passing small values to a view.
- It declares the type of model expected by the Razor view.
- It makes the expected structure explicit and provides stronger compile-time checking.
29. Part 2 Summary
In this tutorial, you:
- created
ProductsController; - created
IndexandDetailsactions; - used
IActionResult; - returned views with
View(); - used conventional routing;
- received an
idroute value; - created Razor views under
Views/Products; - used
ViewDataandViewBag; - created a simple
Productobject; and - created your first strongly typed Razor view.
In Part 3, we will expand the Product model with
Id, Name, Description,
Price and Quantity, then explore Razor syntax,
@model, HTML helpers and Tag Helpers in more detail.