Introduction
In the Model-View-Controller (MVC) architectural pattern, the Model represents the data and the business logic of your application. In ASP.NET Core MVC, models are typically plain C# classes that encapsulate the data you're working with, such as user information, product details, or database records. They are the backbone of your application's data management and processing.
This tutorial will guide you through the fundamental concepts of using models in ASP.NET Core MVC, including creating data models, handling data validation, and the role of ViewModels.
What are Models?
Models in ASP.NET Core MVC serve several key purposes:
- Data Representation: They define the structure of your application's data.
- Business Logic: They can contain methods that perform operations on the data.
- Data Access: While controllers often interact with data access layers, models can sometimes encapsulate simple data retrieval logic.
- Validation: They are central to implementing data validation rules to ensure data integrity.
A typical model is a simple POCO (Plain Old CLR Object) class with properties that represent fields of data.
Creating Models
Creating a model in ASP.NET Core MVC is as simple as defining a C# class. Let's create a simple `Product` model.
Example: A Simple Product Model
Create a new class file named Product.cs
in a Models
folder in your project.
Models/Product.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace YourAppName.Models
{
public class Product
{
public int Id { get; set; }
[Required(ErrorMessage = "Product name is required.")]
[StringLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")]
public string Name { get; set; }
[Required(ErrorMessage = "Price is required.")]
[Range(0.01, 1000.00, ErrorMessage = "Price must be between 0.01 and 1000.00.")]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(500, ErrorMessage = "Description cannot exceed 500 characters.")]
public string Description { get; set; }
}
}
In this example:
- The
Id
property is an integer, often used as a primary key. - The
Name
property is marked with[Required]
and[StringLength]
attributes for validation. - The
Price
property uses[Required]
,[Range]
, and[DataType(DataType.Currency)]
for validation and formatting. - The
Description
property uses[StringLength]
for validation.
Model Validation
ASP.NET Core MVC provides a powerful data validation framework using Data Annotations. These attributes can be applied directly to model properties to define validation rules.
When a form is submitted, the framework can automatically validate the model based on these attributes. This validation can be performed on the client-side (using JavaScript) and server-side (in your controller).
Example: Enabling Validation in a Controller
In your controller action, you can check the validity of the model using ModelState.IsValid
.
Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
using YourAppName.Models; // Assuming Product.cs is here
namespace YourAppName.Controllers
{
public class ProductsController : Controller
{
public IActionResult Create()
{
return View(); // Show the form
}
[HttpPost]
public IActionResult Create(Product product)
{
// Model binding and validation happen automatically here.
// If any validation attributes fail, ModelState.IsValid will be false.
if (ModelState.IsValid)
{
// Save the product to the database or perform other actions
// For example: _dbContext.Products.Add(product); _dbContext.SaveChanges();
return RedirectToAction(nameof(Index)); // Redirect to a success page
}
// If validation failed, redisplay the form with validation errors
return View(product);
}
public IActionResult Index()
{
// Normally you'd fetch data from a database here
var products = new List
{
new Product { Id = 1, Name = "Laptop", Price = 999.99m, Description = "Powerful computing." },
new Product { Id = 2, Name = "Keyboard", Price = 75.50m, Description = "Mechanical typing." }
};
return View(products);
}
}
}
In your corresponding view (e.g., Views/Products/Create.cshtml
), you'll typically render validation messages:
Views/Products/Create.cshtml (Snippet)
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Create</button>
ViewModels
While models represent your core data entities, ViewModels are specialized classes designed to shape data specifically for a particular view. They are often used to:
- Combine data from multiple models.
- Add properties not present in the original data models (e.g., dropdown list options, computed values).
- Exclude properties from models that shouldn't be exposed to the view.
- Simplify data for display or input.
ViewModels help decouple your views from your underlying data structures, leading to cleaner and more maintainable code.
Example: A Product Creation ViewModel
Imagine you want to provide a dropdown of categories for a product creation form.
ViewModels/ProductCreateViewModel.cs
using Microsoft.AspNetCore.Mvc.Rendering;
using YourAppName.Models;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace YourAppName.ViewModels
{
public class ProductCreateViewModel
{
public Product Product { get; set; } // The actual product data
public SelectList Categories { get; set; } // For the category dropdown
[Display(Name = "Select Category")]
public int SelectedCategoryId { get; set; } // To hold the selected category ID
}
}
Your controller would then populate and pass this ViewModel:
Controllers/ProductsController.cs (Modified)
// ... other using statements
public class ProductsController : Controller
{
// Assume _categoryService is injected and provides category data
private readonly ICategoryService _categoryService;
public ProductsController(ICategoryService categoryService)
{
_categoryService = categoryService;
}
public IActionResult Create()
{
var categories = _categoryService.GetCategories(); // Fetch categories
var viewModel = new ProductCreateViewModel
{
Product = new Product(), // Initialize an empty product
Categories = new SelectList(categories, "Id", "Name") // Populate dropdown
};
return View(viewModel);
}
[HttpPost]
public IActionResult Create(ProductCreateViewModel viewModel)
{
if (ModelState.IsValid)
{
// Assign the selected category ID to the product if needed
// viewModel.Product.CategoryId = viewModel.SelectedCategoryId;
// Save viewModel.Product to database
return RedirectToAction(nameof(Index));
}
// If validation failed, re-fetch categories to repopulate dropdown
viewModel.Categories = new SelectList(_categoryService.GetCategories(), "Id", "Name");
return View(viewModel);
}
// ... other actions
}
And the view would bind to the ViewModel:
Views/Products/Create.cshtml (Using ViewModel)
@model YourAppName.ViewModels.ProductCreateViewModel
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.Name"></label>
<input asp-for="Product.Name" class="form-control" />
<span asp-validation-for="Product.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.Price"></label>
<input asp-for="Product.Price" class="form-control" />
<span asp-validation-for="Product.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SelectedCategoryId"></label>
<select asp-for="SelectedCategoryId" asp-items="Model.Categories" class="form-control"></select>
<span asp-validation-for="SelectedCategoryId" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Create Product</button>
Best Practices
- Keep Models Focused: Models should primarily represent data. Complex business logic can be moved to services.
- Use ViewModels for Views: Avoid passing entity models directly to views if they contain sensitive information or require data shaping. ViewModels provide better separation.
- Leverage Data Annotations: Use built-in validation attributes extensively for robust validation.
- Separate Concerns: Use services or repositories to handle data access, keeping controllers and models clean.
- Naming Conventions: Follow standard C# naming conventions for properties and classes.