If you're using standard Web Forms ASP.Net (i.e. not MVC), I would use a ListView control for what you're describing. The idea is that you specify a LayoutTemplate, and an ItemTemplate, and databind to your list.
I would steer WELL clear of building HTML as massive long strings and injecting it into your page, debugging that kind of thing is no fun when it inevitably breaks, and it's likely to be a performance dog. ASP.Net Controls are there to do that exact purpose, and have the benefit of thousands/millions of man-hours of development and testing.
My rule of thumb is that HTML is generated by controls designed for that purpose, and the codebehind is just for setting properties on these controls.
Generally speaking for what you described, I would use LinqToSql or more likely Entity Framework to wrap my database access. Depending on the complexity of the project, I might abstract that away behind a repository, but only if it merits doing so. The Linq/EF context represents what you describe as (1), i.e. a single class with responsibility only for database access.
I would then use a ListView control in the page, and bind it on page load.
For example:
<!-- rest of page -->
<asp:ListView ID="ListView_Books" runat="server" ItemPlaceholderID="itemPlaceholder">
<LayoutTemplate>
<ul>
<asp:Placeholder ID="itemPlaceholder" runat="server" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li>
<%# DataItem.Title %>, by <%# DataItem.Author %>
</li>
</ItemTemplate>
</asp:ListView>
<!-- rest of page -->
My code behind would then have something like (VB):
Protected Sub Page_Load()
ListView_Books_Bind()
End Sub
Protected Sub ListView_Books_Bind()
Dim dataContext As New MyDataContextClass
ListView_Books.DataSource = dataContext.Books
ListView_Books.DataBind()
End Sub
Protected Readonly Property DataItem As Book
Get
Return DirectCast(Page.GetDataItem(), Book)
End Get
End Property
Edit - incorporated Pauli's comment - thanks!