Back to: ASP.NET Core Tutorials For Beginners and Professionals
How to Export Data to an Excel File in ASP.NET Core MVC
In this article, I will discuss How to Export Data to an Excel File in an ASP.NET Core MVC Application with Examples. Please read our previous article discussing How to Upload Multiple Files in ASP.NET Core MVC Application with Examples.
What is an Excel File?
An Excel file is a spreadsheet document created using Microsoft Excel, which is part of the Microsoft Office Suite. Excel files typically have the extensions .xls (older format) or .xlsx (newer format introduced with Excel 2007 and later) and are widely used for organizing, analyzing, and storing data in tabular format. It consists of one or more worksheets (tabs), each containing a grid of cells arranged in rows and columns. These files allow you to:
- Store data in a tabular format (rows/columns).
- Perform calculations and data manipulations using formulas, pivot tables, charts, and conditional formatting.
Why Do We Need to Export Data to Excel in a Web Application?
Almost every data-driven web application involves reporting, analysis, or the ability to share data in a widely-used format. Excel is one of the most common formats for sharing structured data. The following are a few reasons and real-life examples:
- Reporting and Analytics: Companies often generate sales reports, employee performance reports, or financial reports that managers want in Excel format for further analysis.
- Data Portability: Excel files can be shared easily with non-technical stakeholders or clients.
- Offline Processing: Finance or HR personnel might need data in Excel to perform additional manual calculations or reformat data offline.
- Archiving and Backups: Sometimes, businesses need to keep historical snapshots of data in Excel for reference or auditing purposes.
- Interoperability: Many third-party systems (accounting software, CRM tools, etc.) accept Excel files as input.
Real-Time Application to Understand File Export in ASP.NET Core MVC:
We will build an internal HR web application where HR managers log into an admin portal to view an “Employee List” page. On this page, HR can:
- Filter employee data by Departments, Employee Types, and Active Status.
- Navigate through pages of employee data using pagination controls (First, Previous, Current Page, Next, Last).
- Export the filtered employee data (including details and attendance summaries) to an Excel file on the fly.
- The finance team then uses this Excel file for payroll verification and record updates.
For a better understanding, please have a look at the following image. This is the page that we will develop. When you click the Export to Excel button, it will create an Excel file with the required filtered data and download it.
Implementing the Internal HR Application:
Let us implement this application step by step using ASP.NET Core MVC. In this example, we will use:
- ASP.NET Core MVC is used to build the web application.
- Entity Framework Core (Code First) to interact with the SQL Server database.
- EPPlus for generating Excel files.
- Bootstrap and Font Awesome for a professional, responsive user interface.
Create a New ASP.NET Core MVC Project and Install EF Core Packages
Open Visual Studio, create a new ASP.NET Core Web App (Model-View-Controller) project and give it the name HRInternalApp. Once you create the Project, please execute the following commands in the Visual Studio Package Manager Console to install the necessary packages:
- Install-Package Microsoft.EntityFrameworkCore.SqlServer
- Install-Package Microsoft.EntityFrameworkCore.Tools
- Install-Package EPPlus
Create the Model Classes
Model classes (often called Entity classes) represent the core data in your application, usually mapping directly to database tables. Each class corresponds to a table, and each property corresponds to a column. For example, Department, Employee, EmployeeType, and Attendance are Model classes that define your domain data.
Department.cs
Create a class file named Department.cs within the Models folder and then copy and paste the following code. This entity represents a department in the organization (e.g., HR, IT, Finance). It holds the department name and the list of employees that belong to it. It is used to manage and retrieve department-related data.
namespace HRInternalApp.Models { // Represents a Department (e.g., HR, IT, Finance) public class Department { public int DepartmentId { get; set; } // Primary Key public string Name { get; set; } // Department Name // Navigation property to related Employees public ICollection<Employee> Employees { get; set; } } }
EmployeeType.cs
Create a class file named EmployeeType.cs within the Models folder, and then copy and paste the following code. This entity represents different types of employment (e.g., Full Time, Part Time, Contractor). It holds the type name and the list of employees categorized under that type. It is used to classify employees and apply filtering based on employment type.
namespace HRInternalApp.Models { // Represents the type of employee (e.g., Full Time, Part Time, Contractor) public class EmployeeType { public int EmployeeTypeId { get; set; } // Primary Key public string TypeName { get; set; } // Name of the type // Navigation property to related Employees public ICollection<Employee> Employees { get; set; } } }
Employee.cs
Create a class file named Employee.cs within the Models folder, and then copy and paste the following code. This entity represents an individual employee in the system. It includes properties like first and last name, email, department ID, employee type ID, salary, and active status. It is the core data entity for employee-related operations and associations with departments, employee types, and attendance records.
using System.ComponentModel.DataAnnotations.Schema; namespace HRInternalApp.Models { // Represents an employee with details and related navigation properties public class Employee { public int EmployeeId { get; set; } // Primary Key public string FirstName { get; set; } // First Name public string LastName { get; set; } // Last Name public string Email { get; set; } // Email address // Foreign Key and Navigation for Department public int DepartmentId { get; set; } public Department Department { get; set; } // Foreign Key and Navigation for EmployeeType public int EmployeeTypeId { get; set; } public EmployeeType EmployeeType { get; set; } public DateTime JoinDate { get; set; } // Date the employee joined [Column(TypeName ="decimal(18,2)")] public decimal Salary { get; set; } // Salary public bool IsActive { get; set; } // Active status // Navigation property for Attendance records public ICollection<Attendance> Attendances { get; set; } } }
Attendance.cs
Create a class file named Attendance.cs within the Models folder, and then copy and paste the following code. This entity represents attendance records for each employee. It tracks attendance dates and whether the employee was present or absent. It helps calculate attendance summaries and track employee attendance history.
namespace HRInternalApp.Models { // Represents an attendance record for an employee public class Attendance { // Primary key public int AttendanceId { get; set; } // Foreign key to the Employee public int EmployeeId { get; set; } // Navigation property for the related Employee public Employee Employee { get; set; } // The date of the attendance record public DateTime Date { get; set; } // True if the employee was present; false if absent. public bool IsPresent { get; set; } } }
Create the EF Core DbContext with Seed Data
A DbContext is the bridge between our domain/entity classes and the underlying database. It manages CRUD (Create, Read, Update, Delete) operations, tracks changes, and handles queries via Entity Framework Core. The following are the Key Features:
- Configures the EF Core models (mapping classes to database tables).
- Provides DbSet<T> properties for each model (allowing CRUD operations).
- Seeds initial data for Departments, Employee Types, Employees, and Attendance records for testing purposes.
So, first, create a folder called Data in the project root directory. Then, create a class file named ApplicationDbContext.cs within the Data folder and copy and paste the following code. The OnModelCreating method uses Fluent API calls to seed data into the database when migrations are applied.
using HRInternalApp.Models; using Microsoft.EntityFrameworkCore; namespace HRInternalApp.Data { public class ApplicationDbContext : DbContext { // Inject options via Dependency Injection public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } // Configure model and seed data protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Seed Departments modelBuilder.Entity<Department>().HasData( new Department { DepartmentId = 1, Name = "HR" }, new Department { DepartmentId = 2, Name = "Finance" }, new Department { DepartmentId = 3, Name = "IT" } ); // Seed Employee Types modelBuilder.Entity<EmployeeType>().HasData( new EmployeeType { EmployeeTypeId = 1, TypeName = "Full Time" }, new EmployeeType { EmployeeTypeId = 2, TypeName = "Part Time" }, new EmployeeType { EmployeeTypeId = 3, TypeName = "Contractor" } ); // Seed Employees (10 employees, each on a single row) modelBuilder.Entity<Employee>().HasData( new Employee { EmployeeId = 1, FirstName = "John", LastName = "Doe", Email = "john.doe@example.com", DepartmentId = 1, EmployeeTypeId = 1, JoinDate = new DateTime(2020, 1, 15), Salary = 60000, IsActive = true }, new Employee { EmployeeId = 2, FirstName = "Jane", LastName = "Smith", Email = "jane.smith@example.com", DepartmentId = 2, EmployeeTypeId = 2, JoinDate = new DateTime(2021, 3, 1), Salary = 40000, IsActive = true }, new Employee { EmployeeId = 3, FirstName = "Bob", LastName = "Johnson", Email = "bob.johnson@example.com", DepartmentId = 3, EmployeeTypeId = 3, JoinDate = new DateTime(2022, 7, 10), Salary = 50000, IsActive = false }, new Employee { EmployeeId = 4, FirstName = "Alice", LastName = "Williams", Email = "alice.williams@example.com", DepartmentId = 1, EmployeeTypeId = 1, JoinDate = new DateTime(2019, 5, 20), Salary = 65000, IsActive = true }, new Employee { EmployeeId = 5, FirstName = "Michael", LastName = "Brown", Email = "michael.brown@example.com", DepartmentId = 2, EmployeeTypeId = 2, JoinDate = new DateTime(2018, 8, 15), Salary = 55000, IsActive = true }, new Employee { EmployeeId = 6, FirstName = "Linda", LastName = "Davis", Email = "linda.davis@example.com", DepartmentId = 3, EmployeeTypeId = 3, JoinDate = new DateTime(2023, 2, 1), Salary = 48000, IsActive = false }, new Employee { EmployeeId = 7, FirstName = "David", LastName = "Miller", Email = "david.miller@example.com", DepartmentId = 1, EmployeeTypeId = 1, JoinDate = new DateTime(2020, 9, 10), Salary = 62000, IsActive = true }, new Employee { EmployeeId = 8, FirstName = "Susan", LastName = "Wilson", Email = "susan.wilson@example.com", DepartmentId = 2, EmployeeTypeId = 2, JoinDate = new DateTime(2021, 1, 5), Salary = 43000, IsActive = true }, new Employee { EmployeeId = 9, FirstName = "Robert", LastName = "Moore", Email = "robert.moore@example.com", DepartmentId = 3, EmployeeTypeId = 3, JoinDate = new DateTime(2022, 11, 1), Salary = 51000, IsActive = false }, new Employee { EmployeeId = 10, FirstName = "Karen", LastName = "Taylor", Email = "karen.taylor@example.com", DepartmentId = 1, EmployeeTypeId = 1, JoinDate = new DateTime(2019, 12, 12), Salary = 70000, IsActive = true } ); // Seed Attendance records (2 per employee, each on a single row) modelBuilder.Entity<Attendance>().HasData( new Attendance { AttendanceId = 1, EmployeeId = 1, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 2, EmployeeId = 1, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 3, EmployeeId = 2, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 4, EmployeeId = 2, Date = DateTime.Today, IsPresent = false }, new Attendance { AttendanceId = 5, EmployeeId = 3, Date = DateTime.Today.AddDays(-1), IsPresent = false }, new Attendance { AttendanceId = 6, EmployeeId = 3, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 7, EmployeeId = 4, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 8, EmployeeId = 4, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 9, EmployeeId = 5, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 10, EmployeeId = 5, Date = DateTime.Today, IsPresent = false }, new Attendance { AttendanceId = 11, EmployeeId = 6, Date = DateTime.Today.AddDays(-1), IsPresent = false }, new Attendance { AttendanceId = 12, EmployeeId = 6, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 13, EmployeeId = 7, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 14, EmployeeId = 7, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 15, EmployeeId = 8, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 16, EmployeeId = 8, Date = DateTime.Today, IsPresent = false }, new Attendance { AttendanceId = 17, EmployeeId = 9, Date = DateTime.Today.AddDays(-1), IsPresent = false }, new Attendance { AttendanceId = 18, EmployeeId = 9, Date = DateTime.Today, IsPresent = true }, new Attendance { AttendanceId = 19, EmployeeId = 10, Date = DateTime.Today.AddDays(-1), IsPresent = true }, new Attendance { AttendanceId = 20, EmployeeId = 10, Date = DateTime.Today, IsPresent = true } ); } // DbSets for each entity/table public DbSet<Department> Departments { get; set; } public DbSet<EmployeeType> EmployeeTypes { get; set; } public DbSet<Employee> Employees { get; set; } public DbSet<Attendance> Attendances { get; set; } } }
Create the View Models
View Models are specialized data-transfer classes that shape what your Views (UI) need to display or capture. They decouple your Views from your domain (or entity) models, preventing over-posting and controlling how much data is sent to/from the UI. Add a new folder named ViewModels in the Project root directory, where we will create all our View Model classes:
EmployeeViewModel
Create a class file named EmployeeViewModel.cs within the ViewModels folder, and then copy and paste the following code. This view model is a simplified data structure that displays employee details on the UI. It combines employee information with the associated department and employee type names. It includes attendance summaries to provide a quick overview of an employee’s attendance.
namespace HRInternalApp.ViewModels { // A simple view model for displaying employee details public class EmployeeViewModel { public int EmployeeId { get; set; } public string FullName { get; set; } public string Email { get; set; } public string DepartmentName { get; set; } public DateTime JoinDate { get; set; } public decimal Salary { get; set; } public bool IsActive { get; set; } public string EmployeeTypeName { get; set; } public int TotalAttendance { get; set; } public int PresentDays { get; set; } public int AbsentDays { get; set; } } }
EmployeeListViewModel
Create a class file named EmployeeListViewModel.cs within the ViewModels folder, and then copy and paste the following code. This View Model is used for the employee listing page. It combines a list of employees with filtering and pagination data. It provides dropdown options for departments and employee types, selected filter values, and pagination details to maintain state across page views.
using HRInternalApp.Models; namespace HRInternalApp.ViewModels { // View model for the Employee List page including filters and pagination public class EmployeeListViewModel { // List of employees for the current page public IEnumerable<EmployeeViewModel> Employees { get; set; } // Pagination information public int CurrentPage { get; set; } public int TotalPages { get; set; } // Selected filter values public int? SelectedDepartmentId { get; set; } public int? SelectedEmployeeTypeId { get; set; } public bool? SelectedStatus { get; set; } // Dropdown lists for filter options public IEnumerable<Department> Departments { get; set; } public IEnumerable<EmployeeType> EmployeeTypes { get; set; } } }
Create the Controller
Controllers handle incoming requests, gather necessary data from your DbContext or other services, prepare a ViewModel, and return a View (HTML) or other result (like an Excel file). Essentially, a controller organizes the logic for an HTTP request and decides how to respond.
Create a controller named EmployeesController.cs in the Controllers folder. This controller will include actions for listing employees (with filtering and pagination) and provide an action to export the filtered data to an Excel file. So, once you create the controller, copy and paste the following code:
using HRInternalApp.Data; using HRInternalApp.ViewModels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using OfficeOpenXml; namespace HRInternalApp.Controllers { public class EmployeesController : Controller { private readonly ApplicationDbContext _context; // Define page size for pagination private const int PageSize = 3; public EmployeesController(ApplicationDbContext context) { _context = context; } // GET: Employees // This action displays the list of employees with filtering and pagination. public async Task<IActionResult> Index(int? departmentId, int? employeeTypeId, bool? isActive, int page = 1) { // Retrieve filter dropdown data var departments = await _context.Departments.AsNoTracking().ToListAsync(); var employeeTypes = await _context.EmployeeTypes.AsNoTracking().ToListAsync(); // Build the query for employees including related data var query = _context.Employees .AsNoTracking() .Include(e => e.Department) .Include(e => e.EmployeeType) .Include(e => e.Attendances) .AsQueryable(); // Apply filters if parameters are provided if (departmentId.HasValue) { query = query.Where(e => e.DepartmentId == departmentId.Value); } if (employeeTypeId.HasValue) { query = query.Where(e => e.EmployeeTypeId == employeeTypeId.Value); } if (isActive.HasValue) { query = query.Where(e => e.IsActive == isActive.Value); } // Get total record count for pagination int totalRecords = await query.CountAsync(); int totalPages = (int)Math.Ceiling(totalRecords / (double)PageSize); // Retrieve the paginated data var employees = await query .OrderBy(e => e.EmployeeId) .Skip((page - 1) * PageSize) .Take(PageSize) .ToListAsync(); // Map the data to the view model var employeeViewModels = employees.Select(e => new EmployeeViewModel { EmployeeId = e.EmployeeId, FullName = $"{e.FirstName} {e.LastName}", Email = e.Email, DepartmentName = e.Department.Name, JoinDate = e.JoinDate, Salary = e.Salary, IsActive = e.IsActive, EmployeeTypeName = e.EmployeeType.TypeName, TotalAttendance = e.Attendances.Count, PresentDays = e.Attendances.Count(a => a.IsPresent), AbsentDays = e.Attendances.Count(a => !a.IsPresent) }).ToList(); // Build the complete view model var model = new EmployeeListViewModel { Employees = employeeViewModels, CurrentPage = page, TotalPages = totalPages, SelectedDepartmentId = departmentId, SelectedEmployeeTypeId = employeeTypeId, SelectedStatus = isActive, Departments = departments, EmployeeTypes = employeeTypes }; return View(model); } // GET: Employees/ExportToExcel // This action exports the filtered employee data to an Excel file. public async Task<IActionResult> ExportToExcel(int? departmentId, int? employeeTypeId, bool? isActive) { // Build the same query as in Index (without pagination) var query = _context.Employees.AsNoTracking() .Include(e => e.Department) .Include(e => e.EmployeeType) .Include(e => e.Attendances) .AsQueryable(); if (departmentId.HasValue) { query = query.Where(e => e.DepartmentId == departmentId.Value); } if (employeeTypeId.HasValue) { query = query.Where(e => e.EmployeeTypeId == employeeTypeId.Value); } if (isActive.HasValue) { query = query.Where(e => e.IsActive == isActive.Value); } var employees = await query.OrderBy(e => e.EmployeeId).ToListAsync(); // Use EPPlus to generate an Excel file. // IMPORTANT: Set the EPPlus license context to non-commercial. ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // Create the Excel package and worksheet using (var package = new ExcelPackage()) { // Create a worksheet. var worksheet = package.Workbook.Worksheets.Add("Employees"); // Set up header row. worksheet.Cells[1, 1].Value = "Employee ID"; worksheet.Cells[1, 2].Value = "Full Name"; worksheet.Cells[1, 3].Value = "Email"; worksheet.Cells[1, 4].Value = "Department"; worksheet.Cells[1, 5].Value = "Join Date"; worksheet.Cells[1, 6].Value = "Salary"; worksheet.Cells[1, 7].Value = "Is Active"; worksheet.Cells[1, 8].Value = "Employee Type"; worksheet.Cells[1, 9].Value = "Total Attendance"; worksheet.Cells[1, 10].Value = "Present Days"; worksheet.Cells[1, 11].Value = "Absent Days"; // Fill in the employee data rows int row = 2; foreach (var e in employees) { worksheet.Cells[row, 1].Value = e.EmployeeId; worksheet.Cells[row, 2].Value = $"{e.FirstName} {e.LastName}"; worksheet.Cells[row, 3].Value = e.Email; worksheet.Cells[row, 4].Value = e.Department.Name; worksheet.Cells[row, 5].Value = e.JoinDate.ToString("yyyy-MM-dd"); worksheet.Cells[row, 6].Value = e.Salary; worksheet.Cells[row, 7].Value = e.IsActive ? "Yes" : "No"; worksheet.Cells[row, 8].Value = e.EmployeeType.TypeName; worksheet.Cells[row, 9].Value = e.Attendances.Count; // Calculate attendance summary: count the number of days the employee was present and absent. worksheet.Cells[row, 10].Value = e.Attendances.Count(a => a.IsPresent); worksheet.Cells[row, 11].Value = e.Attendances.Count(a => !a.IsPresent); row++; } // Auto-fit columns for better readability worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns(); // Convert the package to a byte array var fileBytes = package.GetAsByteArray(); // Return the Excel file for download // 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' is the MIME type for Excel files return File( fileBytes, //Excel File data in Byte Array "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //Excel Sheet Mime Type "Employees.xlsx" //File Name ); } } } }
Code Explanation:
Index Action:
Displays the employee list with support for filtering and pagination. Fetches data from the database applies filters and prepares the view model to be displayed.
- Retrieves the list of employees from the database, optionally filtered by department, employee type, or active status.
- Applies pagination to limit how many employees appear on a single page.
- Maps the entities to the EmployeeViewModel and prepares EmployeeListViewModel for the view.
- Returns the Index.cshtml view.
ExportToExcel Action:
Generates an Excel file based on filtered employee data and returns it as a downloadable file. It ensures that the HR team can export data to share with finance.
- It uses the same filtering logic but no pagination so that we can export all matching employees.
- Generates an Excel file using the EPPlus library.
- Returns the file as a downloadable .xlsx file.
- This file can then be sent to finance or used for record-keeping.
Create the View
Views are Razor files (.cshtml) that define your pages’ HTML layout and UI elements. They use View Models (or Models) data to present dynamic content. In ASP.NET Core MVC, views are typically grouped by controller name.
Create a view named Index.cshtml within the Views/Employee folder and then copy and paste the following code. The view binds to the EmployeeListViewModel provided by the controller. It iterates over the list of EmployeeViewModel objects to create rows in the table, while the form elements use the view model properties to maintain the filter state.
@model HRInternalApp.ViewModels.EmployeeListViewModel @{ ViewData["Title"] = "Employee List"; } <div class="container mt-5"> <!-- Page Header --> <div class="row"> <div class="col"> <h2 class="text-center mb-4">Employee List</h2> </div> </div> <!-- Filter Section --> <div class="card mb-4 shadow-sm"> <div class="card-header bg-primary text-white"> Filter Options </div> <div class="card-body"> <form method="get" asp-controller="Employees" asp-action="Index"> <div class="form-row align-items-end"> <!-- Department Filter --> <div class="form-group col-md-3"> <label for="departmentId">Department:</label> <select id="departmentId" name="departmentId" class="custom-select"> <option value="">All</option> @foreach (var dept in Model.Departments) { <option value="@dept.DepartmentId" selected="@(dept.DepartmentId == Model.SelectedDepartmentId ? "selected" : null)"> @dept.Name </option> } </select> </div> <!-- Employee Type Filter --> <div class="form-group col-md-3"> <label for="employeeTypeId">Employee Type:</label> <select id="employeeTypeId" name="employeeTypeId" class="custom-select"> <option value="">All</option> @foreach (var type in Model.EmployeeTypes) { <option value="@type.EmployeeTypeId" selected="@(type.EmployeeTypeId == Model.SelectedEmployeeTypeId ? "selected" : null)"> @type.TypeName </option> } </select> </div> <!-- Status Filter --> <div class="form-group col-md-3"> <label for="isActive">Status:</label> <select id="isActive" name="isActive" class="custom-select"> <option value="">All</option> <option value="true" selected="@(Model.SelectedStatus.HasValue && Model.SelectedStatus.Value ? "selected" : null)">Active</option> <option value="false" selected="@(Model.SelectedStatus.HasValue && !Model.SelectedStatus.Value ? "selected" : null)">Inactive</option> </select> </div> <!-- Filter Button --> <div class="form-group col-md-3"> <button type="submit" class="btn btn-primary btn-block"> <i class="fas fa-filter"></i> Apply Filters </button> </div> </div> </form> </div> </div> <!-- Employee Data Table with Export Button in Header --> <div class="card shadow-sm"> <div class="card-header bg-secondary text-white"> <div class="d-flex justify-content-between align-items-center"> <span>Employee Data</span> <!-- Export to Excel Button placed in header --> <form method="get" asp-controller="Employees" asp-action="ExportToExcel" class="mb-0"> <input type="hidden" name="departmentId" value="@Model.SelectedDepartmentId" /> <input type="hidden" name="employeeTypeId" value="@Model.SelectedEmployeeTypeId" /> <input type="hidden" name="isActive" value="@Model.SelectedStatus" /> <button type="submit" class="btn btn-success btn-sm"> <i class="fas fa-file-excel"></i> Export to Excel </button> </form> </div> </div> <div class="card-body p-0"> <!-- Table wrapped with table-responsive to include scroll bar if needed --> <div class="table-responsive"> <table class="table table-hover mb-0"> <thead class="thead-light"> <tr> <th>Employee ID</th> <th>Full Name</th> <th>Email</th> <th>Department</th> <th>Join Date</th> <th>Salary</th> <th>Is Active</th> <th>Employee Type</th> <th>Total Attendance</th> <th>Present Days</th> <th>Absent Days</th> </tr> </thead> <tbody> @foreach (var employee in Model.Employees) { <tr> <td>@employee.EmployeeId</td> <td>@employee.FullName</td> <td>@employee.Email</td> <td>@employee.DepartmentName</td> <td>@employee.JoinDate.ToString("yyyy-MM-dd")</td> <td>@employee.Salary.ToString("C")</td> <td>@(employee.IsActive ? "Yes" : "No")</td> <td>@employee.EmployeeTypeName</td> <td>@employee.TotalAttendance</td> <td>@employee.PresentDays</td> <td>@employee.AbsentDays</td> </tr> } </tbody> </table> </div> </div> </div> <!-- Pagination Controls --> @if (Model.TotalPages >= 1) { <nav aria-label="Page navigation" class="mt-4"> <ul class="pagination justify-content-center"> <!-- First Page Button --> <li class="page-item @(Model.CurrentPage == 1 ? "disabled" : "")"> <a class="page-link" asp-action="Index" asp-route-page="1" asp-route-departmentId="@Model.SelectedDepartmentId" asp-route-employeeTypeId="@Model.SelectedEmployeeTypeId" asp-route-isActive="@Model.SelectedStatus"> First </a> </li> <!-- Previous Page Button --> <li class="page-item @(Model.CurrentPage == 1 ? "disabled" : "")"> <a class="page-link" asp-action="Index" asp-route-page="@(Model.CurrentPage - 1)" asp-route-departmentId="@Model.SelectedDepartmentId" asp-route-employeeTypeId="@Model.SelectedEmployeeTypeId" asp-route-isActive="@Model.SelectedStatus"> Previous </a> </li> <!-- Current Page Indicator --> <li class="page-item active"> <span class="page-link"> Page @Model.CurrentPage of @Model.TotalPages </span> </li> <!-- Next Page Button --> <li class="page-item @(Model.CurrentPage == Model.TotalPages ? "disabled" : "")"> <a class="page-link" asp-action="Index" asp-route-page="@(Model.CurrentPage + 1)" asp-route-departmentId="@Model.SelectedDepartmentId" asp-route-employeeTypeId="@Model.SelectedEmployeeTypeId" asp-route-isActive="@Model.SelectedStatus"> Next </a> </li> <!-- Last Page Button --> <li class="page-item @(Model.CurrentPage == Model.TotalPages ? "disabled" : "")"> <a class="page-link" asp-action="Index" asp-route-page="@(Model.TotalPages)" asp-route-departmentId="@Model.SelectedDepartmentId" asp-route-employeeTypeId="@Model.SelectedEmployeeTypeId" asp-route-isActive="@Model.SelectedStatus"> Last </a> </li> </ul> </nav> } </div>
Key Features of Index View:
- The primary page where HR managers can view the employee list.
- Displays a filter form with options for departments, employee types, and status.
- Includes a table to show employee data with columns like full name, email, department, join date, salary, etc.
- Provides pagination controls to navigate through pages.
- Includes an “Export to Excel” button to export the current data view into an Excel file.
Modifying Layout View:
The Layout View provides the common layout (header, footer, scripts, and styles) for all pages. It ensures consistency across the application. So, please Modify the Layout View as follows:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - HRInternalApp</title> <!-- Local Bootstrap and Site CSS --> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/HRInternalApp.styles.css" asp-append-version="true" /> <!-- CDN Bootstrap and Font Awesome --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" /> <!-- Inline CSS: add bottom margin to the main content container --> <style> /* The content-container class will ensure there's enough space at the bottom so that the pagination and other content do not overlap with the footer. */ .content-container { margin-bottom: 100px; /* Adjust the value as needed */ } </style> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container-fluid"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">HRInternalApp</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> </ul> </div> </div> </nav> </header> <!-- Wrap the main content in a container with the "content-container" class --> <div class="container content-container"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="border-top footer text-muted"> <div class="container"> © 2025 - HRInternalApp - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>
appsettings.json
It stores application configuration settings. It contains the connection string to the SQL Server database, making it easier to modify database configurations without changing code. So, please modify the appsettings.json file as follows:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=HRDB;Trusted_Connection=True;TrustServerCertificate=True;" } }
Configure the Application and Database Connection
The Program class sets up the application and registers services like controllers and EF Core DbContext. It also configures routing, middleware, and the database connection. It acts as the application’s entry point, orchestrating how the components interact and ensuring the app runs smoothly. So, please modify the Program.cs class file as follows:
using HRInternalApp.Data; using Microsoft.EntityFrameworkCore; namespace HRInternalApp { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); // Configure EF Core to use SQL Server. // Ensure you have added the "Microsoft.EntityFrameworkCore.SqlServer" NuGet package. builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")) ); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Employees}/{action=Index}/{id?}"); app.Run(); } } }
Migrate and Create the Database
Open Visual Studio Package Manager Console and execute the Add-Migration Mig1 and Update-Database commands as shown in the below image:
This will create our HRDB database with the required database tables, as shown in the image below:
Now, run the application and test its functionality; it should work as expected.
Real-time Use Cases of Flie Export in Web Application:
Examples of scenarios where exporting data to Excel is helpful:
- A company’s internal HR portal might export a list of employees and their leave balances for a quarterly review.
- A sales dashboard could allow managers to export daily or monthly sales performance reports for team analysis and presentations.
- An e-commerce admin panel might enable downloading order histories for reconciliation and inventory planning.
- An education portal might export student enrolment lists, exam scores, and attendance sheets.
- A financial application exports transaction history for audits or compliance.
So, exporting data to an Excel file is a common requirement in web applications because Excel is widely used for data analysis, reporting, and business decision-making.
In the next article, I will discuss How to Import Excel Data to a Database in an ASP.NET Core MVC Application with Examples. In this article, I try to explain How to Export Data to an Excel File in an ASP.NET Core MVC Application with Examples. I hope you enjoy this article on how to export data to an Excel file in the ASP.NET Core MVC.