DEV Community

Jasper Aurelio Villas
Jasper Aurelio Villas

Posted on

πŸ“š How to Build a ListView in ASP.NET Core MVC (Book Listing Example) Part 1

This guide walks through displaying a list of books using a layered architecture with DTOs, services, and a Razor view.

🧱 Step 1: Define the DTO
Create a DTO to shape the data you want to expose in the view.

csharp
namespace library_system_dotnet.Models.Dto.Book
{
    public class BookReadDto
    {
        public int Id { get; set; }
        public string? ISBN { get; set; }
        public string? Title { get; set; }
        public string? Description { get; set; }
        public string? Author { get; set; }
        public int? PublishedYear { get; set; }
        public string? Genre { get; set; }
        public int? CopiesAvailable { get; set; }
        public int? TotalCopies { get; set; }
        public string? Location { get; set; }
        public string? CoverImageUrl { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”„ Step 2: Create a Mapper
Use a manual mapper to convert your Book entity to BookReadDto.

csharp
public static class BookMapper
{
    public static BookReadDto ToReadDto(Models.Book book)
    {
        return new BookReadDto
        {
            Id = book.Id,
            ISBN = book.ISBN,
            Title = book.Title,
            Description = book.Description,
            Author = book.Author,
            PublishedYear = book.PublishedYear,
            Genre = book.Genre,
            CopiesAvailable = book.CopiesAvailable,
            TotalCopies = book.TotalCopies,
            Location = book.Location,
            CoverImageUrl = book.CoverImageUrl
        };
    }
}

Enter fullscreen mode Exit fullscreen mode

🧩 Step 3: Define the Service Contract

csharp
public interface IBookService
{
    Task<IEnumerable<BookReadDto>> GetAllAsync(CancellationToken ct = default);
}

Enter fullscreen mode Exit fullscreen mode

βš™οΈ Step 4: Implement the Service

csharp
public class BookService : IBookService
{
    private readonly AppDbContext _context;
    private readonly IMapper _mapper;

    public BookService(AppDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<IEnumerable<BookReadDto>> GetAllAsync(CancellationToken ct = default)
    {
        var books = await _context.Books.ToListAsync(ct);
        return _mapper.Map<IEnumerable<BookReadDto>>(books);
    }
}

Enter fullscreen mode Exit fullscreen mode

🧭 Step 5: Register Services in Program.cs

csharp
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllersWithViews();
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddScoped<IBookService, BookService>();
Enter fullscreen mode Exit fullscreen mode

🎯 Step 6: Create the Controller Action

csharp
[HttpGet("books")]
public async Task<ActionResult<IEnumerable<BookReadDto>>> Index()
{
    var books = await _context.Books.ToListAsync();
    var bookDtos = books.Select(BookMapper.ToReadDto);
    return View(bookDtos);
}
Enter fullscreen mode Exit fullscreen mode

Note: You can switch to _bookService.GetAllAsync() once your service is fully wired.

πŸ–ΌοΈ Step 7: Create the Razor View (Views/Books/Index.cshtml)

razor
@model IEnumerable<library_system_dotnet.Models.Dto.Book.BookReadDto>

@{
    ViewData["Title"] = "Books";
}

<h1>Books</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>

<table class="table">
    <thead>
        <tr>
            <th>@Html.DisplayNameFor(model => model.ISBN)</th>
            <th>@Html.DisplayNameFor(model => model.Title)</th>
            <th>@Html.DisplayNameFor(model => model.Description)</th>
            <th>@Html.DisplayNameFor(model => model.Author)</th>
            <th>@Html.DisplayNameFor(model => model.PublishedYear)</th>
            <th>@Html.DisplayNameFor(model => model.Genre)</th>
            <th>@Html.DisplayNameFor(model => model.CopiesAvailable)</th>
            <th>@Html.DisplayNameFor(model => model.TotalCopies)</th>
            <th>@Html.DisplayNameFor(model => model.Location)</th>
            <th>@Html.DisplayNameFor(model => model.CoverImageUrl)</th>
            <th>@Html.DisplayNameFor(model => model.CreatedAt)</th>
            <th>@Html.DisplayNameFor(model => model.UpdatedAt)</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
    @foreach (var item in Model)
    {
        <tr>
            <td>@Html.DisplayFor(modelItem => item.ISBN)</td>
            <td>@Html.DisplayFor(modelItem => item.Title)</td>
            <td>@Html.DisplayFor(modelItem => item.Description)</td>
            <td>@Html.DisplayFor(modelItem => item.Author)</td>
            <td>@Html.DisplayFor(modelItem => item.PublishedYear)</td>
            <td>@Html.DisplayFor(modelItem => item.Genre)</td>
            <td>@Html.DisplayFor(modelItem => item.CopiesAvailable)</td>
            <td>@Html.DisplayFor(modelItem => item.TotalCopies)</td>
            <td>@Html.DisplayFor(modelItem => item.Location)</td>
            <td>@Html.DisplayFor(modelItem => item.CoverImageUrl)</td>
            <td>@Html.DisplayFor(modelItem => item.CreatedAt)</td>
            <td>@Html.DisplayFor(modelItem => item.UpdatedAt)</td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
            </td>
        </tr>
    }
    </tbody>
</table>
Enter fullscreen mode Exit fullscreen mode

βœ… Final Notes

  • Make sure the view file is named Index.cshtml and placed in /Views/Books/.
  • Ensure your controller inherits from Controller, not ControllerBase, to support view rendering.
  • If you use AutoMapper, ensure the profile is registered and the mapping is defined.

Top comments (0)