C# Models
Introduction
In C# web development, models are a fundamental component that represent the data structure of your application. They are essentially C# classes that define the shape of the data you're working with and often map directly to database tables. Models are a core part of the MVC (Model-View-Controller) and other web architectural patterns, serving as the "M" in MVC.
Models help you:
- Organize your application's data structure
- Validate data through attributes and rules
- Facilitate data binding with forms and UI elements
- Create a strong foundation for database operations through ORM tools like Entity Framework
In this tutorial, we'll dive deep into C# models, how to create them, and how they fit into web development workflows.
What Are Models in C#?
At their simplest, models are C# classes that represent data entities in your application. They typically contain:
- Properties that represent data fields
- Data annotations for validation and metadata
- Methods for data manipulation (optional)
- Navigation properties for relationships with other models
Creating Your First Model
Let's create a simple Product
model for an e-commerce application:
using System;
using System.ComponentModel.DataAnnotations;
namespace EcommerceApp.Models
{
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Required]
[Range(0.01, 10000.00)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(500)]
public string Description { get; set; }
public bool IsAvailable { get; set; }
[DataType(DataType.Date)]
public DateTime CreatedDate { get; set; }
}
}
This model represents a product with various properties and validation rules. Let's break down what's happening:
- We've defined properties like
Id
,Name
,Price
, etc. - We've added data annotations like
[Required]
and[StringLength]
for validation - We've specified data types using
[DataType]
for proper rendering and handling
Data Annotations and Validation
Data annotations are attributes that add metadata to your model properties. They serve several purposes:
- Validation - Ensuring data meets specific rules
- Display - Controlling how properties are labeled and formatted
- Database mapping - Informing how properties map to database fields
Here are common data annotations:
// Validation annotations
[Required(ErrorMessage = "This field is required")]
[StringLength(50, ErrorMessage = "Name cannot be longer than 50 characters")]
[Range(18, 100, ErrorMessage = "Age must be between 18 and 100")]
[RegularExpression(@"^[A-Z][a-zA-Z]*$", ErrorMessage = "Name must start with a capital letter")]
[Compare("Password", ErrorMessage = "Passwords do not match")]
// Display annotations
[Display(Name = "Full Name")]
[DisplayFormat(DataFormatString = "{0:C}")]
[DataType(DataType.Password)]
[ScaffoldColumn(false)] // Hides in scaffolded views
// Database mapping annotations
[Key]
[Column("product_name")]
[ForeignKey("CategoryId")]
[Table("Products")]
Model Binding
Model binding is the process where ASP.NET Core maps data from HTTP requests to model objects. This happens automatically when:
- Form data is submitted
- Query strings are provided
- Route data is passed
For example, with this model:
public class LoginModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
And this controller action:
[HttpPost]
public IActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
// Process valid login
return RedirectToAction("Dashboard");
}
// Return to form with validation messages
return View(model);
}
When a form is submitted, ASP.NET Core will:
- Create a new
LoginModel
instance - Map form fields to model properties by name
- Validate the model based on annotations
- Provide the populated model to your action method
View Models
View models are special models designed specifically for use in views, rather than representing database entities directly. They help:
- Shape data specifically for a view's needs
- Combine data from multiple models
- Remove sensitive or unnecessary properties
- Add display-specific properties or methods
Here's an example:
public class ProductDetailsViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string CategoryName { get; set; }
public List<string> Reviews { get; set; }
public string FormattedPrice => $"${Price:F2}"; // Computed property
public bool IsOnSale { get; set; }
}
This view model combines product info with category and review data, plus adds a formatted price property for the view.
Working with Entity Framework
Models often serve as the foundation for Entity Framework Core, Microsoft's ORM (Object-Relational Mapper). With EF Core, your models can be used to:
- Generate database schemas (Code First approach)
- Query databases with LINQ
- Track changes to entities
- Handle relationships between entities
Here's how to set up a DbContext
with our product model:
using Microsoft.EntityFrameworkCore;
namespace EcommerceApp.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
// Configure model relationships and constraints
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.Price)
.HasColumnType("decimal(18,2)");
modelBuilder.Entity<Product>()
.HasIndex(p => p.Name)
.IsUnique();
}
}
}
Relationships Between Models
In real applications, models often relate to each other. Common relationships include:
- One-to-One
- One-to-Many
- Many-to-Many
Let's add a Category model related to our Product:
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
// Navigation property - One Category has many Products
public virtual ICollection<Product> Products { get; set; }
}
And update our Product model to reference the Category:
public class Product
{
// Previous properties...
[Required]
public int CategoryId { get; set; }
// Navigation property - Many Products belong to one Category
public virtual Category Category { get; set; }
}
Real-World Example: E-commerce Application
Let's build a more comprehensive example showing models for an e-commerce application:
// User model
public class User
{
public int Id { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(50)]
public string FirstName { get; set; }
[Required]
[StringLength(50)]
public string LastName { get; set; }
// Navigation properties
public virtual ICollection<Order> Orders { get; set; }
public virtual Address ShippingAddress { get; set; }
public virtual Address BillingAddress { get; set; }
}
// Address model
public class Address
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string StreetLine1 { get; set; }
public string StreetLine2 { get; set; }
[Required]
[StringLength(50)]
public string City { get; set; }
[Required]
[StringLength(50)]
public string State { get; set; }
[Required]
[StringLength(20)]
[RegularExpression(@"^\d{5}(-\d{4})?$", ErrorMessage = "Invalid Zip Code format")]
public string ZipCode { get; set; }
[Required]
[StringLength(50)]
public string Country { get; set; }
}
// Order model
public class Order
{
public int Id { get; set; }
[Required]
public int UserId { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime OrderDate { get; set; }
[Required]
public OrderStatus Status { get; set; }
public decimal TotalAmount { get; set; }
// Navigation properties
public virtual User User { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
// Order item model (represents product in an order)
public class OrderItem
{
public int Id { get; set; }
[Required]
public int OrderId { get; set; }
[Required]
public int ProductId { get; set; }
[Required]
[Range(1, int.MaxValue)]
public int Quantity { get; set; }
[Required]
[Range(0.01, double.MaxValue)]
public decimal UnitPrice { get; set; }
// Navigation properties
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
}
// Order status enum
public enum OrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Canceled
}
This model structure allows us to:
- Store user information and addresses
- Track orders and their statuses
- Manage order items with product references
- Calculate order totals
Working with Models in Controllers
Here's how you might use these models in a controller:
public class OrdersController : Controller
{
private readonly ApplicationDbContext _context;
public OrdersController(ApplicationDbContext context)
{
_context = context;
}
// GET: Orders
public async Task<IActionResult> Index()
{
// Get all orders for the current user
var userId = GetCurrentUserId(); // Implement this method
var orders = await _context.Orders
.Where(o => o.UserId == userId)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.OrderByDescending(o => o.OrderDate)
.ToListAsync();
return View(orders);
}
// GET: Orders/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var order = await _context.Orders
.Include(o => o.User)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.FirstOrDefaultAsync(o => o.Id == id);
if (order == null)
{
return NotFound();
}
// You might use a view model here to shape data for the view
var orderViewModel = new OrderDetailsViewModel
{
OrderId = order.Id,
OrderDate = order.OrderDate,
Status = order.Status.ToString(),
TotalAmount = order.TotalAmount,
CustomerName = $"{order.User.FirstName} {order.User.LastName}",
Items = order.OrderItems.Select(oi => new OrderItemViewModel
{
ProductName = oi.Product.Name,
Quantity = oi.Quantity,
UnitPrice = oi.UnitPrice,
Subtotal = oi.Quantity * oi.UnitPrice
}).ToList()
};
return View(orderViewModel);
}
}
Summary
Models are a cornerstone of C# web development, providing structure for your application's data and enabling:
- Clear organization of your application's domain data
- Automatic validation through data annotations
- Simplified database operations when used with ORMs like Entity Framework
- Enhanced type safety throughout your codebase
- Better separation of concerns in your architecture
Best practices for working with models include:
- Keep models focused and single-purpose
- Use data annotations for validation
- Create view models for specialized view needs
- Structure model relationships thoughtfully
- Consider using model builders or fluent API for complex configurations
Exercises
- Create a Blog application model structure with
BlogPost
,Comment
, andUser
models with appropriate relationships - Implement a simple contact form model with validation for email, name, and message
- Design view models for a product catalog that groups products by categories
- Create models for a task management application with tasks, projects, and users
- Implement custom validation attributes for a user registration model
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)