ViewModels in ASP.NET MVC are crucial for connecting data models to views. Here's a quick rundown of 10 key patterns:
- Basic ViewModel: Simple class holding only data a view needs
- Composite ViewModel: Bundles multiple models into one package
- Flattened ViewModel: Simplifies complex, nested structures
- Presentation ViewModel: Focuses on displaying data without business logic
- Form ViewModel: Handles form submissions and data validation
- List ViewModel: Manages large data sets with pagination and sorting
- Tree-like ViewModel: Shows nested data like file systems or org charts
- Read-only ViewModel: Displays data without allowing changes
- Edit ViewModel: Manages data updates in CRUD operations
- Custom ViewModel: Tailored combination of data for specific needs
Choose the right pattern based on your view's requirements. Mix and match as needed for different parts of your app.
Pattern | Best For | Key Feature |
---|---|---|
Basic | Simple views | Holds essential data |
Composite | Complex data from multiple sources | Combines models |
Form | User input | Includes validation |
List | Large datasets | Supports pagination |
Custom | Specific view needs | Flexible combination |
ViewModels are evolving, with trends in automation, performance optimization, and better testing. Keep learning to improve your ASP.NET MVC apps.
Related video from YouTube
Basic ViewModel
A basic ViewModel in ASP.NET MVC is a simple class that holds only the data a specific view needs. It's like a custom container for your view's data.
Here's what you need to know:
- It's a plain C# class
- It contains properties matching your view's needs
- It can include data from one or more models
Let's look at a login page example. You only need the username and password, not all the user's info. Here's a basic ViewModel for that:
public class UserLoginViewModel {
[Required(ErrorMessage = "Please enter your username")]
[Display(Name = "User Name")]
[MaxLength(50)]
public string UserName { get; set; }
[Required(ErrorMessage = "Please enter your password")]
[Display(Name = "Password")]
[MaxLength(50)]
public string Password { get; set; }
}
This ViewModel focuses on what the login view needs. Nothing more, nothing less.
Key Elements
A basic ViewModel typically has:
-
Properties: Match the data fields in your view (like
UserName
andPassword
). -
Data Annotations: Add rules or metadata to your properties (like
[Required]
,[Display]
, and[MaxLength]
). -
Computed Properties: Show data calculated from other properties.
Here's a quick breakdown:
Element | Purpose | Example |
---|---|---|
Properties | Hold view data | public string UserName { get; set; } |
Data Annotations | Add rules and metadata | [Required(ErrorMessage = "Please enter your username")] |
Computed Properties | Calculate display values | public string FullName => $"{FirstName} {LastName}"; |
2. Composite ViewModel
Composite ViewModels are your go-to for complex views in ASP.NET MVC. They let you bundle multiple models into one neat package.
Creating a Composite ViewModel
It's simple:
- Make a new class
- Add properties from the models you need
Here's what it looks like:
public class OrderViewModel
{
public Customer CustomerInfo { get; set; }
public Order OrderDetails { get; set; }
public List<Product> OrderedProducts { get; set; }
}
This ViewModel mixes Customer
, Order
, and Product
data.
Using it in your controller? Easy:
public ActionResult OrderDetails(int orderId)
{
var viewModel = new OrderViewModel
{
CustomerInfo = _customerRepository.GetCustomerByOrderId(orderId),
OrderDetails = _orderRepository.GetOrderById(orderId),
OrderedProducts = _productRepository.GetProductsByOrderId(orderId)
};
return View(viewModel);
}
When to Use Composite ViewModels
They're perfect when:
- You need data from multiple sources
- You're dealing with complex forms
- You want to show related info from different models
Think of an e-commerce checkout page. You need customer details, cart items, AND shipping options. That's where Composite ViewModels shine.
Scenario | What You Can Do |
---|---|
Product page | Mix product details, reviews, and related items |
User dashboard | Show user info, recent activity, and account stats |
Order summary | Combine order details, customer info, and shipping status |
Just remember: Only include what you NEED. Don't stuff your ViewModel with extra fluff.
3. Flattened ViewModel
Flattened ViewModels simplify form bindings in ASP.NET MVC. They turn complex, nested structures into a single, manageable layer.
How to flatten
Think of flattening a ViewModel like unpacking nested boxes. You bring all properties to the top level.
Here's a quick before and after:
Before (Nested):
public class OrderViewModel
{
public Customer Customer { get; set; }
public Order Order { get; set; }
}
After (Flattened):
public class FlattenedOrderViewModel
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
}
Flattening helps you avoid complex object graphs in views and controllers.
Tips for use
1. Keep it lean
Only include properties you'll use in the view. Don't copy everything from domain models.
2. Use AutoMapper
AutoMapper can flatten complex structures automatically. It's a time-saver.
3. Think about validation
Flattened ViewModels make it easier to apply validation attributes where needed.
4. Handle lists smartly
For collections, use extension methods to create SelectList
items:
public IEnumerable<SelectListItem> OrderSelectList(string defaultId = "")
{
return Orders.ToSelectList(
e => $"{e.OrderDate.ToShortDateString()} ({e.TotalItems} items)",
e => e.OrderId.ToString(),
defaultId);
}
This keeps views clean and logic in the ViewModel.
5. Don't overdo it
If your ViewModel has 50+ properties, it might be time to rethink your approach.
4. Presentation ViewModel
Presentation ViewModels in ASP.NET MVC are all about showing data without the business logic baggage. Think of them as a store's display window - they're there to make things look good, not manage inventory.
Keep It Clean
Here's how to keep your Presentation ViewModels simple:
- No business logic
- Only display properties
- Use data annotations for formatting
Here's a quick example:
public class ProductViewModel
{
[Display(Name = "Product Name")]
public string Name { get; set; }
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal Price { get; set; }
[Display(Name = "In Stock")]
public bool IsAvailable { get; set; }
}
Don't forget to map from your domain models. Tools like AutoMapper can help keep things separate.
Smart Design
When building Presentation ViewModels:
- One ViewModel per view
- Flatten nested objects
- Use read-only properties where needed
- Include display-specific logic (but not business logic)
For example:
public string StockStatus => IsAvailable ? "In Stock" : "Out of Stock";
Group related data with nested classes:
public class OrderViewModel
{
public CustomerInfo Customer { get; set; }
public OrderDetails Order { get; set; }
public class CustomerInfo
{
public string Name { get; set; }
public string Email { get; set; }
}
public class OrderDetails
{
public string OrderNumber { get; set; }
public DateTime OrderDate { get; set; }
}
}
5. Form ViewModel
Form ViewModels in ASP.NET MVC handle form submissions and validate data. Here's how they work:
Checking data
Form ViewModels use Data Annotations for validation. Here's an example:
public class UserFormModel
{
[Required(ErrorMessage = "Username is required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
public string Username { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; }
[Required(ErrorMessage = "Password is required")]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters long")]
public string Password { get; set; }
public bool SendEmail { get; set; }
}
ASP.NET MVC checks these rules automatically when the form is submitted.
For complex validations, use the IValidatableObject
interface:
public class UserFormModel : IValidatableObject
{
// ... other properties ...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Username.ToLower() == "admin")
{
yield return new ValidationResult("Username cannot be 'admin'", new[] { nameof(Username) });
}
}
}
Moving data back
After validation, you'll need to move data from the Form ViewModel to your main models. Here's how:
- Use AutoMapper for simple mappings:
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<UserFormModel, User>();
}
}
- In your controller action:
[HttpPost]
public ActionResult Edit(UserFormModel userFormModel)
{
if (ModelState.IsValid)
{
var user = _mapper.Map<User>(userFormModel);
_userService.SaveUser(user);
return RedirectToAction("Index");
}
return View(userFormModel);
}
Or manually map properties:
var user = new User
{
Username = userFormModel.Username,
Email = userFormModel.Email
};
Don't forget to handle extra form data:
if (userFormModel.SendEmail)
{
_emailService.SendWelcomeEmail(user.Email);
}
sbb-itb-29cd4f6
6. List ViewModel
List ViewModels in ASP.NET MVC are your go-to for handling big data sets. They're crucial for creating user-friendly interfaces when you're dealing with tons of info.
Splitting into pages
Got a huge list? Pagination's your friend:
1. Create a PaginatedList
class:
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
2. Update your controller:
public async Task<IActionResult> Index(int? pageNumber)
{
int pageSize = 10;
return View(await PaginatedList<Student>.CreateAsync(
_context.Students.AsNoTracking(), pageNumber ?? 1, pageSize));
}
3. Add page navigation to your view:
@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}
<a asp-action="Index"
asp-route-pageNumber="@(Model.PageIndex - 1)"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-pageNumber="@(Model.PageIndex + 1)"
class="btn btn-default @nextDisabled">
Next
</a>
Sorting and filtering
Want to level up? Add sorting and filtering:
1. Beef up your controller:
public async Task<IActionResult> Index(string sortOrder, string searchString, int? pageNumber)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
}
2. Spice up your view:
<form asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" />
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
</tr>
}
</tbody>
</table>
Now you've got a setup where users can sort by clicking column headers and filter with the search box. The controller handles it all, serving up sorted and filtered results like a pro.
7. Tree-like ViewModel
Tree-like ViewModels are perfect for showing nested data in ASP.NET MVC apps. Think file systems, org charts, or product categories.
Building the Tree
Here's how to set up a tree structure in your ViewModel:
1. Create a self-referencing class:
public class Tab {
public int ID { get; set; }
public string Name { get; set; }
public string Alias { get; set; }
public List<Tab> Tabs { get; set; }
}
2. Set up your controller:
public class TabController : Controller {
public void Index() {
ViewData["tabs"] = TabService.GetTabs();
RenderView("Index");
}
}
3. Make a partial view for recursive rendering:
<ul>
<% foreach (Tab tab in ViewData["tabs"] as List<Tab>) { %>
<li><%= tab.Name %>
<% if (tab.Tabs != null && tab.Tabs.Count > 0) { %>
<% this.RenderPartial("TabPartial", new Dictionary<string, object>() { { "tabs", tab.Tabs } }); %>
<% } %>
</li>
<% } %>
</ul>
This setup lets you show nested tab lists, each potentially holding sub-tabs.
Making Navigation Better
Want to improve user experience? Try these:
- Add breadcrumbs
- Use expand/collapse icons
- Add search functionality
You could also use a third-party control like aciTree, a free jQuery plugin for tree views.
Here's a sample structure for tree nodes using aciTree:
public class TreeNode
{
public int id { get; set; }
public int? parent { get; set; }
public string label { get; set; }
public bool inode { get; set; }
public bool open { get; set; }
public bool checkbox { get; set; }
public bool radio { get; set; }
public string myurl { get; set; }
public List<TreeNode> branch { get; set; }
public TreeNode()
{
branch = new List<TreeNode>();
}
}
This structure lets you create flexible trees with checkboxes, radio buttons, and custom URLs for each node.
8. Read-only ViewModel
Read-only ViewModels are great for showing data without letting users change it. They can speed up your app and make it easier to use.
Speed boost
Read-only ViewModels can make your ASP.NET MVC app faster by:
- Cutting data binding overhead
- Skipping validation
- Using less memory
For a product catalog with 10,000 items, a read-only ViewModel could slash page load times by 40%.
Here's a basic read-only ViewModel:
public class ProductReadOnlyViewModel
{
public int Id { get; }
public string Name { get; }
public decimal Price { get; }
public ProductReadOnlyViewModel(int id, string name, decimal price)
{
Id = id;
Name = name;
Price = price;
}
}
See those get-only properties? They stop accidental changes.
Best practices
To make the most of read-only ViewModels:
- Keep it simple: Only include what you need to show.
- Use DTOs: They help map complex models to simple ViewModels.
- Cache smart: If data doesn't change much, cache ViewModels to avoid extra database hits.
- Use projection: Only grab the fields you need when fetching data.
Here's a LINQ projection example:
var products = dbContext.Products
.Select(p => new ProductReadOnlyViewModel(p.Id, p.Name, p.Price))
.ToList();
This query only grabs what you need, making it faster and more efficient.
9. Edit ViewModel
Edit ViewModels are crucial for CRUD operations in ASP.NET MVC. They make data updates smooth and safe.
Managing edits
Here's a basic Edit ViewModel:
public class CustomerEditViewModel : BaseEntity
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[HiddenInput]
public byte[] Timestamp { get; set; }
}
In your controller:
[HttpGet]
public ActionResult Edit(int id)
{
var customer = db.Customers.Find(id);
var viewModel = new CustomerEditViewModel
{
Id = customer.Id,
FirstName = customer.FirstName,
LastName = customer.LastName,
Timestamp = customer.Timestamp
};
return View(viewModel);
}
Dealing with conflicts
Conflicts happen when multiple users edit the same data. Here's how to handle them:
[HttpPost]
public ActionResult Edit(CustomerEditViewModel viewModel)
{
if (ModelState.IsValid)
{
try
{
var customer = db.Customers.Find(viewModel.Id);
customer.FirstName = viewModel.FirstName;
customer.LastName = viewModel.LastName;
customer.Timestamp = viewModel.Timestamp;
db.Entry(customer).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbValues = (Customer)entry.GetDatabaseValues().ToObject();
ModelState.AddModelError("", "Someone else updated this customer. Your changes weren't saved.");
viewModel.Timestamp = dbValues.Timestamp;
}
}
return View(viewModel);
}
This approach catches and handles edit conflicts, keeping your data safe and users happy.
10. Custom ViewModel
Custom ViewModels are your secret weapon for handling data in ASP.NET MVC apps. They let you mix and match exactly what you need.
Here's a real-world example:
public class EmployeeDetailsViewModel
{
public Employee Employee { get; set; }
public Address Address { get; set; }
public string PageTitle { get; set; }
public string PageHeader { get; set; }
}
This ViewModel is like a Swiss Army knife. It combines:
- Employee basics
- Address details
- Page display info
Pro tip: Stick all your ViewModels in a "ViewModels" folder. Your future self will thank you.
Want to create your own? Here's how:
- Cherry-pick the data you need
- Make a new class with those goodies
- Throw in any extra bits you want
Need to spice up a dropdown? Try this:
public IEnumerable<SelectListItem> OrderSelectList(string defaultId = "") {
return Orders.ToSelectList(
e => string.Format("{0} ({1} items)", e.OrderDate.ToShortDateString(), e.TotalItems.ToString()),
e => e.OrderId.ToString(),
defaultId);
}
This method turns boring order data into a tasty SelectList. Perfect for your view to gobble up.
Remember: Keep your ViewModel lean and mean. Only pack what you'll use. Your code will be cleaner, and you'll be happier.
Conclusion
Choosing the right pattern
Picking a ViewModel pattern for your ASP.NET MVC app depends on your needs:
- Basic ViewModel: For simple views
- Composite ViewModel: For complex data from multiple sources
- Form ViewModel: For user input and validation
- List ViewModel: For pagination, sorting, or filtering
Mix and match as needed. Different parts of your app might use different patterns.
What's next
ViewModel trends to watch:
1. Automation
Smarter tools are coming to auto-generate ViewModels for complex scenarios.
2. Performance boosts
New techniques like lazy-loading properties in ViewModels are speeding up page loads.
3. Better testing
New frameworks make unit testing ViewModels easier, leading to more reliable code.
4. Integration with other tech
ViewModels are working with other technologies. Some developers use them with GraphQL for flexible APIs.
ViewModels are evolving. They're getting smarter and more powerful. Keep learning and experimenting to improve your ASP.NET MVC apps.
FAQs
When to use ViewModel in MVC?
Use ViewModels in ASP.NET MVC when you need to:
- Manage dropdown lists
- Build Master-Details views
- Show shopping carts
ViewModels are great for combining data from multiple models or adding view-specific properties. Think of an OrderViewModel
that includes customer details, order items, shipping options, and order total.
This keeps your views clean and your models focused on business logic.
What is the view model in ASP.NET Core?
In ASP.NET Core, a ViewModel is a class that holds only the data a specific view needs. It's like a custom data package from the controller to the view.
Here's what you need to know:
- It's tailored for view requirements
- It can include properties from multiple domain models
- You can add view-specific properties or methods
Check out this example:
public class ProductViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsInStock { get; set; }
public string FormattedPrice => $"${Price:F2}";
}
This ViewModel has basic product info plus a formatted price property just for the view.
Using ViewModels helps keep your code clean and your views focused. It's all about separation of concerns.