Skip to content
Categories:

Creating a Basic Inventory Tracker with ASP.NET MVC

Post date:
Author:
Tags:

Introduction

In this tutorial, we’ll walk through the process of creating a basic inventory tracker using ASP.NET MVC. This
project will serve as a simple yet functional inventory management system, allowing users to add, edit, and
delete items from their inventory. For naming purposes, I’ll call this app “YourGroceryStore” but you can name
it whatever you want.

Setup

1. Create the project

To get started, let’s create a new ASP.NET MVC project using the dotnet CLI:

dotnet new mvc -n YourGroceryStore

2. Install Nuget Packages

We’ll need the following NuGet packages for Entity Framework and SQLite:

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Coding Time

1. Create the Item Model

First, let’s define the Item model class to represent our inventory items. We’ll include properties for the
item’s name, quantity, price, and category. (File location – YourGroceryStore/Models/Item.cs)

using System.ComponentModel.DataAnnotations;
    
    public enum GroceryCategory
    {
        Produce,
        Dairy,
        Meat,
        Other
    }
    
    public class Item
    {
        [Key]
        public int ItemId { get; set; }
    
        [Required]
        public string Name { get; set; }
    
        [Range(0, int.MaxValue, ErrorMessage = "Quantity must be a non-negative number")]
        public int Quantity { get; set; }
    
        [Range(0, double.MaxValue, ErrorMessage = "Price must be a non-negative number")]
        public decimal Price { get; set; }
    
        [Required]
        public GroceryCategory Category { get; set; }
    }

2. Setup the Database Context

Now, let’s create a DataContext class in the Data folder to serve as the DbContext for our application (File
location: YourGroceryStore/Data/DataContext.cs).

using Microsoft.EntityFrameworkCore;
    using inventory_tracker.Models;
    
    namespace inventory_tracker.Data
    {
        public class DataContext : DbContext
        {
            public DataContext(DbContextOptions<DataContext> options) : base(options)
            { }
    
            public DbSet<Item> Items { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
            }
        }
    }

3. Add the Database Context to Your Program.cs File

To integrate the database context into your application, follow these steps:

Step 1: Open your appsettings.json file located in the root directory of your project
(YourGroceryStore/appsettings.json).

Step 2: Add the connection string to the JSON object as shown below:

  "ConnectionStrings": {
        "DefaulConnection": "Data Source=inventory_tracker.db"
      },

Step 3: Now, navigate to your Program.cs file (YourGroceryStore/Program.cs).

Step 4: Inside the Main method, use the AddDbContext method to register the DataContext with the dependency
injection container. Provide the connection string from the configuration.

using Microsoft.EntityFrameworkCore;
    using inventory_tracker.Data;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Other configurations...
    
    builder.Services.AddDbContext<DataContext>(options =>
    {
        options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"));
    });
    
    var app = builder.Build();
    
    // Further app configurations...
    
    app.Run();

Now you’ve successfully added the database context to your application. This will enable your application to
interact with the SQLite database using Entity Framework Core.

4. Create Repository Methods

We’ll need repository methods to interact with the database. If you don’t know what repository methods are, we’re
creating helper methods to interface with EF Core / the Database. Here’s my implementation:

using inventory_tracker.Data;
    using inventory_tracker.Interfaces;
    using inventory_tracker.Models;
    using Microsoft.EntityFrameworkCore;
    
    namespace inventory_tracker.Repository
    {
        public class ItemRepository : IItemRepository
        {
            private readonly DataContext _context;
    
            public ItemRepository(DataContext context)
            {
                _context = context;
            }
    
            private async Task<bool> Save()
            {
                return await _context.SaveChangesAsync() > 0;
            }
    
            public async Task<bool> CreateItem(Item item)
            {
                await _context.Items.AddAsync(item);
                return await Save();
            }
    
            public async Task<bool> EditItem(Item updatedItem)
            {
                Item? existingItem = await _context.Items.FindAsync(updatedItem.ItemId);
    
                if (existingItem == null) return false;
    
                existingItem.Name = updatedItem.Name;
                existingItem.Quantity = updatedItem.Quantity;
                existingItem.Price = updatedItem.Price;
                existingItem.Category = updatedItem.Category;
    
                return await Save();
            }
    
            public async Task<bool> DeleteItem(int itemId)
            {
                Item? item = await _context.Items.SingleOrDefaultAsync(i => i.ItemId == itemId);
    
                if (item == null) return false;
                _context.Items.Remove(item);
                return await Save();
            }
    
            public async Task<Item?> GetItem(int itemId)
            {
                return await _context.Items.SingleOrDefaultAsync(i => i.ItemId == itemId);
            }
    
            public async Task<List<Item>> GetItems()
            {
                return await _context.Items.ToListAsync();
            }
        }
    }

5. Write Controller Logic

Finally, let’s implement the controller logic to handle CRUD operations:

    using System.Diagnostics;
    using Microsoft.AspNetCore.Mvc;
    using inventory_tracker.Models;
    using inventory_tracker.ViewModels;
    using inventory_tracker.Mappers;
    using inventory_tracker.Interfaces;
    
    namespace inventory_tracker.Controllers;
    
    public class InventoryController : Controller
    {
        private readonly ILogger<InventoryController> _logger;
        private readonly IItemRepository _itemRepository;
    
        public InventoryController(ILogger<InventoryController> logger, IItemRepository itemRepository)
        {
            _logger = logger;
            _itemRepository = itemRepository;
        }
    
        [HttpGet]
        public async Task<IActionResult> Index()
        {
            List<Item> items = await _itemRepository.GetItems();
    
            return View(items);
        }
    
        [HttpGet]
        public IActionResult CreateItem()
        {
            return View();
        }
    
        [HttpPost]
        [ProducesResponseType(302)]
        [ProducesResponseType(400)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> CreateItem(ItemVM itemVM)
        {
            if (itemVM == null || !ModelState.IsValid) return BadRequest();
    
            Item item = itemVM.ToItemFromViewModel();
    
            if (!await _itemRepository.CreateItem(item))
            {
                ModelState.AddModelError("", "Error creating Item");
                return StatusCode(500, ModelState);
            }
    
            return RedirectToAction("Index");
        }
    
        [HttpGet]
        public async Task<IActionResult> EditItem(int itemId)
        {
            if (!ModelState.IsValid) return View("Error");
    
            Item? item = await _itemRepository.GetItem(itemId);
            if (item == null) return View("Error");
    
            return View(item);
        }
    
        [HttpPost]
        [ProducesResponseType(200)]
        [ProducesResponseType(400)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> EditItem(Item item)
        {
            if (!ModelState.IsValid) return View("Error");
    
            if (!await _itemRepository.EditItem(item)) 
            {
                ModelState.AddModelError("", "Error updating Item");
                return StatusCode(500, ModelState);
            }
    
            return RedirectToAction("Index");
        }
    
        [HttpPost]
        [ProducesResponseType(200)]
        [ProducesResponseType(400)]
        [ProducesResponseType(500)]
        public async Task<IActionResult> DeleteItem(int itemId)
        {
            if (!ModelState.IsValid) return BadRequest();
    
            if (!await _itemRepository.DeleteItem(itemId))
            {
                ModelState.AddModelError("", "Error deleting category");
                return StatusCode(500, ModelState);
            }
    
            return RedirectToAction("Index");
        }
    
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }

6. Create the Views

Views in MVC work by matching folder names to controller names. For example, “InventoryController” maps to the
“Views/Inventory” folder. Then, requests that return a View will look for a corresponding .cshtml file that
matches the action name.

Here are the views we’re using:

CreateItem.cshtml

Location: YourGroceryStore/Views/Inventory/CreateItem.cshtml

@using inventory_tracker.ViewModels;
    @using inventory_tracker.Models;
    @model ItemVM
    @{
        ViewData["Title"] = "Create Item";
    }
    
    <div class="text-center">
        <h1 class="display-4">Create Item</h1>
    
        <form method="post" asp-action="CreateItem">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" required aria-required="true"/>
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="Quantity" class="control-label"></label>
                <input asp-for="Quantity" class="form-control" required aria-required="true" />
                <span asp-validation-for="Quantity" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" required aria-required="true" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="GroceryCategory" class="control-label"></label>
                <select asp-for="GroceryCategory" class="form-control" required aria-required="true">
                    <option value="">Select Category</option>
                    @foreach (var category in Enum.GetValues(typeof(GroceryCategory)))
                    {
                        <option value="@category">@category</option>
                    }
                </select>
                <span asp-validation-for="GroceryCategory" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Create</button>
        </form>
    </div>
EditItem.cshtml

Location: YourGroceryStore/Views/Inventory/EditItem.cshtml

@using inventory_tracker.Models;
    @model Item
    @{
        ViewData["Title"] = "Edit Item";
    }
    
    <div class="text-center">
        <h1 class="display-4">Edit Item</h1>
        
        <form method="post" asp-action="EditItem">
            <input type="hidden" asp-for="ItemId" value="@Model.ItemId" />
    
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" required aria-required="true" value="@Model.Name" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="Quantity" class="control-label"></label>
                <input asp-for="Quantity" class="form-control" required aria-required="true" value="@Model.Quantity" />
                <span asp-validation-for="Quantity" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" required aria-required="true" value="@Model.Price.ToString("C2")"/>
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <br>
            <div class="form-group">
                <label asp-for="Category" class="control-label"></label>
                <select asp-for="Category" class="form-control" required aria-required="true">
                    <option value="">Select Category</option>
                    @foreach (var category in Enum.GetValues(typeof(GroceryCategory)))
                    {
                        <option value="@category" selected="@((GroceryCategory)category == Model.Category)">@category</option>
                    }
                </select>
                <span asp-validation-for="Category" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Update</button>
        </form>
    </div>
Index.cshtml

Location: YourGroceryStore/Views/Inventory/Index.cshtml

@using inventory_tracker.Models
    @model List<Item>
    @{
        ViewData["Title"] = "Inventory Page";
    }
    
    <div class="text-center">
        <h1 class="display-4">Current Inventory</h1>
    
        <ul class="list-group">
            @foreach (var item in Model)
            {
                <li class="list-group-item d-flex justify-content-around">
                    <div class="d-inline">
                        @item.Name (@item.Category) - Quantity: @item.Quantity - Price: @item.Price.ToString("C2")
                    </div>
                    <div class="d-inline">
                        <a class="btn btn-secondary d-inline" asp-area="" asp-controller="Inventory" asp-action="EditItem"
                            asp-route-itemId="@item.ItemId">Edit</a>
                        <form class="d-inline" asp-area="" asp-controller="Inventory" asp-action="DeleteItem" method="post"
                            onsubmit="return confirm('Are you sure you want to delete this category?');">
                            <input aria-hidden="true" type="hidden" name="itemId" value="@item.ItemId">
                            <button type="submit" class="btn btn-danger">Delete</button>
                        </form>
                    </div>
                </li>
            }
        </ul>
    
        <br>
    
        <a class="btn btn-primary" asp-area="" asp-controller="Inventory" asp-action="CreateItem">
            Add New Item
        </a>
    </div>

Conclusion

In this tutorial, we’ve explored ASP.NET MVC by creating an inventory tracker app. We started by setting up the
project, defining the data model, configuring the database context, and implementing repository methods for
database interaction. Finally, we wrote controller logic to handle CRUD operations for managing inventory items.
This was a very simplified example so feel free to expand upon this project by adding features such as
authentication, validation, and more advanced CRUD operations.