views:

1473

answers:

5

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"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<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/&lt;%# 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();
      }
    }
  }
}
A: 

If you write your code to be ultimately portable, you will find you will have 3 (or more!) layers in your application.

For example - instead of making your Data Access Layer work specifically for this one application, write it so that you will never have to write it again. Make sure all your functions can be passed variables and you don't rely on global variables (or as little as possible). When it comes time for your next project - copy and paste your DAL and suddenly you're up and running again.

And it doesn't end there - you might want to write a sub-layer for your DAL that interprets between MySQL and MSSQL (just as an example). Or you might have a library of common functions that you perform, like text sanitation or CSS generation or something.

If you write your code so that one day, you sit down to write an app - and it mostly involves cutting and pasting previous code - you've reached programmer nirvana. :)

Andy Moore
I dont quite get how that would work out, sorry I am still learning all of this. If one program is about invoices and another is a media player how would one use the same DAL for both? Wouldn't both programs use different classes and objects?
Jon H
@Jon- even though they have different objects and classes there can still be different project files for each
TStamper
@Andy: Both paradigms are appropriate for different scenarios. Sometimes it is preferred to have a specific datalayer for each project. While I use generic datalayers for most of my needs, I understand the need to build specific datalayers and do not believe that they should be discouraged.
Cerebrus
Thanks! That was helpful and I think I understand what you are talking about, pretty much a universal DAL that mimics whatever your DB is.
Jon H
A: 

The whole idea behind layering an application is that each layer does not depend on implementation details of the layer(s) below. For example, in your code you have a T-SQL statement inside your presentation layer. This means you have a direct dependency of your presentation layer on your database (the bottom layer). If you make a change in your database, you must also make a change in your presentation layer. Ideally this is not what you want. The presentation layer should only be concerned about presenting data, not about how to retrieve it. Suppose you move your whole database into CSV files (I know, crazy idea), then your presentation layer should not be aware of this at all.

So ideally, you have a business layer method that returns just the data you want to show to the user. You should take a look at ObjectDataSource instead of SqlDataSource. SqlDataSource is nice for small prototyping projects, but you should not use it for any more serious projects.

Between business layer and data layer you should have a similar separation. The data layer is responsible for getting the data you want from some storage location (database, CSV file, web service, ...). Again, ideally, the business layer should not depend on the implementation details of the data layer. If you're talking to SQL Server for example, you should not return a SqlDataReader instance to your business layer. By doing this you create a dependency of your business layer on an implementation detail of your data layer: the actual database it is retrieving it's data from.

In practice you see that the business layer does depend on implementation details of the data layer in some way or another and usually that's not a bad thing. When was the last time you decided to switch databases? But eliminating dependencies and isolating implementation details as much as possible almost always results in an application that's easier to maintain and understand.

You can find a similar explanation here.

Ronald Wildenberg
Thank you I did not know about the ObjectDataSource. This has helped a lot!
Jon H
+3  A: 

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.

Cerebrus
Off topic perhaps, but do I need to close the connection in my example in the DAL?
Jon H
How would you rate my current code above?
Jon H
@Jon: You don't *need* to explicitly close it because you are using a "using construct", which will dispose off the connection. That said, you should probably still close it for clarity.
Cerebrus
Also, it would be presumptuous of me to rate your code above. Feel free to experiment when you're learning. You're on the right track. And hey, thanks for accepting my answer! ;-)
Cerebrus
+2  A: 

The greatest explanation of logic layers in ASP.NET applications come from two sources. The first is Microsoft's own ASP.NET website written by Scott Mitchell it provides a good introduction to the separation of logic. The tutorials are quite wordy but I found them very useful. The URL is http://www.asp.net/learn/data-access/.

The second resource I found very useful followed on from that and it was written by Imar Spaanjaars and is available here. It is a much more technical article but provides a great way of adding the structure to your application.

I hope that helps.

Ian.

Ian Roke
The link by Imar Spaanjaars was very useful thanks for that!
Jon H
I'm glad you like it I found it a great intermediate resource to learn from.
Ian Roke
A: 

Hi Jon H, as an aside to the main thrust of his question, I would recommend you looking at ASPNET_REGSQL to configure your SQL database to handle .Net's built-in membership / profile / role abilities. It would remove a lot of shag and hassle for creating / updating users, etc. I've not used profile an awful lot, but it allows you to "tack on" extra attributes to your user, e.g. AccessCode.

If you're dealing with an existing DB structure which already does user authentication etc., you could create a custom Membership provider which would leverage the existing db tables and stored procedures.

Mike Kingscott