Presentation, Business and Data Layer

Jon H picture Jon H · Apr 29, 2009 · Viewed 15.9k times · Source

I just started programming in C# and was reading about dividing your application / website into the three different layers was the best practice but I am having a hard time understanding exactly how. Im working on a pet project to lean more about C# but I dont want to start on any bad habits. Can you look at what I have and see if I am doing this right? Offer some hints suggestions as to how to break everything down to the different layers?

Presentation Layer

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Project: Ruth</title>
  <link href="CSS/StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <form id="form1" runat="server">
    <div class="Body">
      <div class="Header">
        <div class="Nav">
          <img src="images/Header_Main.gif" alt="" width="217" height="101" />
          <div class="Menu">
            <a href="Default.aspx">
              <img src="images/Header_Home-Off.gif" alt="" /></a>
            <a href="Default.aspx">
              <img src="images/Header_About-Off.gif" alt="" /></a>
            <a href="Register.aspx">
              <img src="images/Header_Register-Off.gif" alt="" /></a>
            <a href="Default.aspx">
              <img src="images/Header_Credits-Off.gif" alt="" /></a>
          </div>
        </div>
      </div>
      <div class="Content">
        <div class="CurrentlyListening">
          <asp:Label ID="lblCurrentListen" runat="server" Text="(Nothing Now)" CssClass="Txt"></asp:Label>
        </div>
        <asp:GridView ID="gvLibrary" runat="server" AutoGenerateColumns="False" DataKeyNames="lib_id" DataSourceID="sdsLibrary" EmptyDataText="There are no data records to display." Width="760" GridLines="None">
          <RowStyle CssClass="RowStyle" />
          <AlternatingRowStyle CssClass="AltRowStyle" />
          <HeaderStyle CssClass="HeaderStyle" />
          <Columns>
            <asp:BoundField DataField="artist_name" HeaderText="Artist" SortExpression="artist_name" HeaderStyle-Width="200" />
            <asp:BoundField DataField="album_title" HeaderText="Album" SortExpression="album_title" HeaderStyle-Width="200" />
            <asp:BoundField DataField="song_title" HeaderText="Track" SortExpression="song_title" HeaderStyle-Width="200" />
            <asp:TemplateField HeaderText="DL">
              <ItemTemplate>
                <a href="http://####/Proj_Ruth/Data/<%# Eval("file_path") %>" class="lnk">Link</a>
              </ItemTemplate>
            </asp:TemplateField>
          </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="sdsLibrary" runat="server" ConnectionString="<%$ ConnectionStrings:MusicLibraryConnectionString %>" DeleteCommand="DELETE FROM [Library] WHERE [lib_id] = @lib_id" InsertCommand="INSERT INTO [Library] ([artist_name], [album_title], [song_title], [file_path]) VALUES (@artist_name, @album_title, @song_title, @file_path)" ProviderName="<%$ ConnectionStrings:MusicLibraryConnectionString.ProviderName %>" SelectCommand="SELECT [lib_id], [artist_name], [album_title], [song_title], [file_path] FROM [Library] ORDER BY [artist_name], [album_title]" UpdateCommand="UPDATE [Library] SET [artist_name] = @artist_name, [album_title] = @album_title, [song_title] = @song_title, [file_path] = @file_path WHERE [lib_id] = @lib_id">
          <DeleteParameters>
            <asp:Parameter Name="lib_id" Type="Int32" />
          </DeleteParameters>
          <InsertParameters>
            <asp:Parameter Name="artist_name" Type="String" />
            <asp:Parameter Name="album_title" Type="String" />
            <asp:Parameter Name="song_title" Type="String" />
            <asp:Parameter Name="file_path" Type="String" />
          </InsertParameters>
          <UpdateParameters>
            <asp:Parameter Name="artist_name" Type="String" />
            <asp:Parameter Name="album_title" Type="String" />
            <asp:Parameter Name="song_title" Type="String" />
            <asp:Parameter Name="file_path" Type="String" />
            <asp:Parameter Name="lib_id" Type="Int32" />
          </UpdateParameters>
        </asp:SqlDataSource>
      </div>
    </div>
  </form>
</body>
</html>

Business Layer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

public class User
{
  DA da = new DA();

  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string EmailAddress { get; set; }
  public string Password { get; set; }
  public string AccessCode { get; set; }

  public User(string firstName, string lastName, string emailAddress, string password, string accessCode)
  {
    FirstName = firstName;
    LastName = lastName;
    EmailAddress = emailAddress;
    Password = password;
    AccessCode = accessCode;
  }

  public void CreateUser(User newUser)
  {
    if (da.IsValidAccessCode(newUser.AccessCode))
    {
      da.CreateUser(newUser);
    }
  }
}

Data Access Layer (DAL)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.Configuration;

public class DA
{
  public DA()
  {
  }

  public bool IsValidAccessCode(string accessCode)
  {
    bool isValid = false;
    int count = 0;

    using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
    {
      sqlCnn.Open();
      using (SqlCommand sqlCmd = new SqlCommand(String.Format("SELECT COUNT(*) FROM [AccessCodes] WHERE [accessCode_accessCode] = '{0}';", accessCode), sqlCnn))
      {
        count = (int)sqlCmd.ExecuteScalar();
        if (count == 1)
        {
          isValid = true;
        }
      }
    }
    return isValid;
  }

  public void CreateUser(User newUser)
  {
    using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
    {
      sqlCnn.Open();
      using (SqlCommand sqlCmd = new SqlCommand(String.Format("INSERT INTO [Users] (user_firstName, user_lastName, user_emailAddress, user_password, user_accessCode) VALUES ('{0}', '{1}', '{2}', '{3}', '{4}');", newUser.FirstName, newUser.LastName, newUser.EmailAddress, newUser.Password, newUser.AccessCode), sqlCnn))
      {
        sqlCmd.ExecuteNonQuery();
      }
    }
    DeleteAccessCode(newUser.AccessCode);
  }

  public void DeleteAccessCode(string accessCode)
  {
    using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
    {
      sqlCnn.Open();
      using (SqlCommand sqlCmd = new SqlCommand(String.Format("DELETE FROM [AccessCodes] WHERE [accessCode_accessCode] = '{0}';", accessCode), sqlCnn))
      {
        sqlCmd.ExecuteNonQuery();
      }
    }
  }
}

Answer

Cerebrus picture Cerebrus · Apr 29, 2009

Jon,

One of the first things to understand is that if you intend to build layer-based applications, then you should not be storing SQL statements directly within ASPX pages (as the SqlDataSource requires). The SqlDataSource control was built to demonstrate how easy it is to bind and update an application with database data and is not intended to be used in real world applications, because it kinda defeats the purpose of having a BL layer and Datalayer if you are going to store Select/Update/Delete/Insert statements in the ASPX page.

The whole purpose of layer-based application design is to encapsulate each layer so that there is no intersection. Each layer interacts with the public interface of the other layers and knows nothing about their internal implementation.

The viable alternative, therefore, is to use the ObjectDataSource control. This control allows you to bind directly to a DataLayer or to a Biz logic layer which in turn can call the Datalayer. Binding to a Datalayer directly has the drawback that you will be returning data structures which expose the schema of the database tables (for e.g., DataTables or DataViews).

So, the recommended flow of logic is as follows:

The ASPX page uses a DataSource control to bind to a BL class. This BL class provides appropriate functions such as GetData, UpdateData, DeleteData and InsertData (with any required overloads) and these functions return strongly typed objects or collections that the ObjectDataSource can work with and display. Each public function in the BL class internally calls into the DataLayer to select/update/delete/insert data to/from the database.

An excellent introduction to this layer based design in ASP.NET is provided in the Quickstarts

P.S: @Andy mentioned generic datalayers that work with all scenarios. See this question for an example of what it would look like.