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; }
}
}
π 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
};
}
}
π§© Step 3: Define the Service Contract
csharp
public interface IBookService
{
Task<IEnumerable<BookReadDto>> GetAllAsync(CancellationToken ct = default);
}
βοΈ 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);
}
}
π§ 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>();
π― 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);
}
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>
β 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)