Search code examples
c#asp.net-core-mvcasp.net-core-6.0

Why do I get a HTTP ERROR 404 when listing products by category using ASP.NET Core 6 MVC with pagination?


Why do I have HTTP ERROR 404?

I am trying to list products by category using ASP.NET Core 6 MVC, and at the same time including a pagination to a view. I am using a ProductsController.cs which has an Index method to display all products, and ProductsByCategory to display only the products with the same category. When I go to: https://localhost:7179/products I got: This localhost page can’t be found, HTTP ERROR 404. atm, I have two categories cacti and bouquet, and can display them when I go to: https://localhost:7179/products/cacti I have not yest finished pagination as I have problem displaying all products view. Category Model:

    public class Category
    {
        public int Id { get; set; }
        [Required, MinLength(2, ErrorMessage ="Minimum Length is 2")]
        [RegularExpression(@"^[a-zA-Z]+$", ErrorMessage = "Only Letters are allowed")]
        public string? Name { get; set; }
        public string? Slug { get; set; }
        public int Sorting { get; set; }
    }

Products Controller:

    public class ProductsController : Controller
    {

        private readonly FlowerShopContext context;
        public ProductsController(FlowerShopContext context)
        {
            this.context = context;
        }


        //GET /Products/Index
        public async Task<IActionResult> Index(int p = 1)
        {
            int pageSize = 12;
            var products = context.Products.OrderByDescending(x => x.Id).Include(x => x.Category).Skip((p - 1) * pageSize).Take(pageSize);

            ViewBag.PageNumber = p;
            ViewBag.PageRange = pageSize;
            ViewBag.TotalPages = (int)Math.Ceiling((decimal)context.Products.Count() / pageSize);

            return View(await products.ToListAsync());
        }

        // GET /products/category
        public async Task<IActionResult> ProductsByCategory(string categorySlug, int p = 1)
        {
            Category category = await context.Categories.Where(x => x.Slug == categorySlug).FirstOrDefaultAsync();
            if (category == null) return RedirectToAction("Index");

            int pageSize = 6;
            var products = context.Products.OrderByDescending(x => x.Id)
                                            .Where(x => x.CategoryId == category.Id)
                                            .Skip((p - 1) * pageSize)
                                            .Take(pageSize);

            ViewBag.PageNumber = p;
            ViewBag.PageRange = pageSize;
            ViewBag.TotalPages = (int)Math.Ceiling((decimal)context.Products.Where(x => x.CategoryId == category.Id).Count() / pageSize);
            ViewBag.CategoryName = category.Name;
            ViewBag.CategorySlug = categorySlug;

            return View(await products.ToListAsync());
        }
    }

Index.cshtml View:

@model IEnumerable<Product>

    @{
        ViewData["Title"] = "All Products";
    }


<h1>All Products</h1>


<div class="row">
    @foreach (var item in Model)
    {
        <div class="col-4">
            <img src="~/media/products/@item.Image" class="image-fluid" width="250" alt=""/>
            <h4>@item.Name</h4>
            <div>
                @Html.Raw(item.Description)
            </div>
            <p>@item.Price.ToString("c2")</p>
            <p>
                <a href="#" class="btn btn-outline-primary">Add to cart</a>
            </p>
        </div>
    }
</div>

ProductsByCategory.cshtml View:

@model IEnumerable<Product>

    @{
        ViewData["Title"] = "Products by category";
    }


<h1>Products by category</h1>


<div class="row">
    @foreach (var item in Model)
    {
        <div class="col-4">
            <img src="~/media/products/@item.Image" class="image-fluid" width="250" alt=""/>
            <h4>@item.Name</h4>
            <div>
                @Html.Raw(item.Description)
            </div>
            <p>@item.Price.ToString("c2")</p>
            <p>
                <a href="#" class="btn btn-outline-primary">Add to cart</a>
            </p>
        </div>
    }
</div>

Program.cs endpoints:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
      "pages",
      "{slug?}",
      defaults: new { controller = "Pages", action = "Page" }
    );

    endpoints.MapControllerRoute(
        "products-category",
        "products/{categorySlug}",
        defaults: new { controller = "Products", action = "ProductsByCategory" }
    );

    endpoints.MapControllerRoute(
        name: "areas",
        pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
    );

    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}"); 
});

FlowerShopContext.cs

   public class FlowerShopContext : DbContext
    {
        public FlowerShopContext(DbContextOptions<FlowerShopContext> options) : base(options)
        {
        }

        public DbSet<Page> Pages { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
    }

Solution

  • I would suggest you concentrate on the Index and perhaps the List ? it returns. Get that working then figure out the filter by Category part. - Show the Product class; I assume Product has a Category object or a List Categories if they can have multiple? Try to make things obvious to your 5 years from now self who maintains it and use good names like Index(int page = 1)

    Study routing https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-7.0

    [Route("[controller]/[action]")]
    public class ProductsController : Controller
    {
        [Route("~/")]
        [Route("/Home")]
        [Route("~/Home/Index")]
        public IActionResult Index()
        {
            return ControllerContext.MyDisplayRouteInfo();
        }
    
        [Route("/products/{page}")]
        public IActionResult ListAProducts(int page)
        {
            return ControllerContext.MyDisplayRouteInfo(page);
        }
    
        public IActionResult About()
        {
            return ControllerContext.MyDisplayRouteInfo();
        }
    }
    

    Notice I don't put anything in the ViewBag? Create a pagination object you can put in your "Page" object which contains your Pagination and List<Products> perhaps