Skip to main content

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:

  1. Properties that represent data fields
  2. Data annotations for validation and metadata
  3. Methods for data manipulation (optional)
  4. Navigation properties for relationships with other models

Creating Your First Model

Let's create a simple Product model for an e-commerce application:

csharp
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:

  1. We've defined properties like Id, Name, Price, etc.
  2. We've added data annotations like [Required] and [StringLength] for validation
  3. 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:

  1. Validation - Ensuring data meets specific rules
  2. Display - Controlling how properties are labeled and formatted
  3. Database mapping - Informing how properties map to database fields

Here are common data annotations:

csharp
// 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:

  1. Form data is submitted
  2. Query strings are provided
  3. Route data is passed

For example, with this model:

csharp
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:

csharp
[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:

  1. Create a new LoginModel instance
  2. Map form fields to model properties by name
  3. Validate the model based on annotations
  4. 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:

  1. Shape data specifically for a view's needs
  2. Combine data from multiple models
  3. Remove sensitive or unnecessary properties
  4. Add display-specific properties or methods

Here's an example:

csharp
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:

  1. Generate database schemas (Code First approach)
  2. Query databases with LINQ
  3. Track changes to entities
  4. Handle relationships between entities

Here's how to set up a DbContext with our product model:

csharp
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:

  1. One-to-One
  2. One-to-Many
  3. Many-to-Many

Let's add a Category model related to our Product:

csharp
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:

csharp
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:

csharp
// 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:

  1. Store user information and addresses
  2. Track orders and their statuses
  3. Manage order items with product references
  4. Calculate order totals

Working with Models in Controllers

Here's how you might use these models in a controller:

csharp
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:

  1. Keep models focused and single-purpose
  2. Use data annotations for validation
  3. Create view models for specialized view needs
  4. Structure model relationships thoughtfully
  5. Consider using model builders or fluent API for complex configurations

Exercises

  1. Create a Blog application model structure with BlogPost, Comment, and User models with appropriate relationships
  2. Implement a simple contact form model with validation for email, name, and message
  3. Design view models for a product catalog that groups products by categories
  4. Create models for a task management application with tasks, projects, and users
  5. 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! :)