2009-04-29 17 views
7

Ich habe gerade angefangen in C# zu programmieren und habe gelesen, dass das Teilen Ihrer Anwendung/Website in die drei verschiedenen Schichten die beste Praxis ist, aber es fällt mir schwer zu verstehen, wie. Ich arbeite an einem Haustier-Projekt, um mehr über C# zu lehnen, aber ich möchte keine schlechten Angewohnheiten anfangen. Kannst du schauen, was ich habe und sehen, ob ich das richtig mache? Bieten Sie einige Tipps an, wie Sie alles auf die verschiedenen Ebenen aufteilen können?Präsentation, Business und Data Layer

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(); 
     } 
    } 
    } 
} 

Antwort

9

Jon,

Eines der ersten Dinge zu verstehen, dass, wenn Sie beabsichtigen, Schicht-basierten Anwendungen zu bauen, dann sollten Sie nicht SQL-Anweisungen direkt in ASPX-Seiten zu speichern (wie die SqlDataSource erfordern). Das Steuerelement SqlDataSource wurde erstellt, um zu demonstrieren, wie einfach es ist, eine Anwendung mit Datenbankdaten zu binden und zu aktualisieren. Es ist nicht für den Einsatz in realen Anwendungen gedacht, da es den Zweck einer BL-Schicht und eines DataLayers vereitelt Speichern Wählen Sie/Aktualisieren/Löschen/Einfügen von Anweisungen in der ASPX-Seite.

Der gesamte Zweck des schichtbasierten Anwendungsdesigns besteht darin, jede Ebene so einzukapseln, dass keine Überschneidung entsteht. Jede Ebene interagiert mit der öffentlichen Schnittstelle der anderen Ebenen und weiß nichts über ihre interne Implementierung. Die praktikable Alternative ist daher, die ObjectDataSource Steuerung zu verwenden. Mit diesem Steuerelement können Sie direkt an einen DataLayer oder an eine Biz-Logikschicht binden, die wiederum den DataLayer aufrufen kann. Die Bindung an einen DataLayer hat direkt den Nachteil, dass Sie Datenstrukturen zurückgeben, die das Schema der Datenbanktabellen (z. B. DataTables oder DataViews) offen legen.

So ist der empfohlene Fluss der Logik wie folgt:

Die ASPX-Seite verwendet ein Datasource-Steuerelement zu einer BL-Klasse zu binden. Diese BL-Klasse stellt geeignete Funktionen wie GetData, UpdateData, DeleteData and InsertData (mit allen erforderlichen Überladungen) zur Verfügung, und diese Funktionen geben stark typisierte Objekte oder Auflistungen zurück, die ObjectDataSource verwenden und anzeigen kann. Jede öffentliche Funktion in der BL-Klasse ruft intern den DataLayer auf, um Daten aus der Datenbank auszuwählen/zu aktualisieren/zu löschen/einzufügen.

Eine ausgezeichnete Einführung in diese Schicht basierten Design in ASP.NET ist in dem

P. S

Quickstarts bereitgestellt: @Andy generic datalayers erwähnt, die mit allen Szenarien arbeiten. Ein Beispiel dafür, wie es aussehen würde, siehe this question.

+0

Off Thema vielleicht, aber muss ich die Verbindung in meinem Beispiel in der DAL schließen? –

+0

Wie bewerten Sie meinen aktuellen Code oben? –

+1

@Jon: Sie müssen * es * nicht explizit schließen, da Sie ein "using construct" verwenden, das die Verbindung abschafft. Das heißt, Sie sollten es wahrscheinlich noch für Klarheit schließen. – Cerebrus

0

Wenn Sie Ihren Code schreiben, um letztendlich tragbar zu sein, werden Sie feststellen, dass Sie in Ihrer Anwendung 3 (oder mehr!) Layer haben.

Zum Beispiel - anstatt Ihre Datenzugriffsschicht speziell für diese eine Anwendung zu verwenden, schreiben Sie sie so, dass Sie sie nie wieder schreiben müssen. Stellen Sie sicher, dass alle Ihre Funktionen an Variablen übergeben werden können und Sie sich nicht auf globale Variablen verlassen (oder so wenig wie möglich). Wenn es Zeit für dein nächstes Projekt ist - kopiere und füge deine DAL ein und plötzlich bist du wieder einsatzbereit.

Und es endet nicht dort - Sie könnten eine Unterschicht für Ihre DAL schreiben, die zwischen MySQL und MSSQL interpretiert (nur als ein Beispiel). Oder Sie haben vielleicht eine Bibliothek von allgemeinen Funktionen, die Sie ausführen, wie Texthygiene oder CSS-Generierung oder so etwas.

Wenn Sie Ihren Code so schreiben, dass Sie eines Tages eine App schreiben - und dabei meist vorherigen Code ausschneiden und einfügen -, haben Sie Programmierer Nirvana erreicht. :)

+0

Ich verstehe nicht ganz, wie das funktionieren würde, sorry, ich lerne immer noch alles davon. Wenn ein Programm über Rechnungen und ein anderer über einen Mediaplayer ist, wie würde man für beide die gleiche DAL verwenden? Würden nicht beide Programme verschiedene Klassen und Objekte verwenden? –

+0

@ Jon- obwohl sie verschiedene Objekte und Klassen haben, kann es immer noch verschiedene Projektdateien für jede – TStamper

+0

@Andy: Beide Paradigmen sind für verschiedene Szenarien geeignet. Manchmal ist es bevorzugt, für jedes Projekt eine bestimmte Datenschicht zu verwenden. Während ich für die meisten meiner Anforderungen generische Datenschichten verwende, verstehe ich die Notwendigkeit, spezifische Datenschichten zu erstellen, und glaube nicht, dass sie davon abgehalten werden sollten. – Cerebrus

0

Die ganze Idee hinter dem Schichten einer Anwendung ist, dass jede Schicht nicht von Implementierungsdetails der darunter liegenden Schicht (en) abhängt. Zum Beispiel haben Sie in Ihrem Code eine T-SQL-Anweisung in Ihrer Präsentationsebene. Dies bedeutet, dass Sie eine direkte Abhängigkeit Ihrer Präsentationsebene von Ihrer Datenbank (der untersten Ebene) haben. Wenn Sie Ihre Datenbank ändern, müssen Sie auch Ihre Präsentationsebene ändern. Im Idealfall ist dies nicht das, was Sie wollen. Die Präsentationsschicht sollte sich nur mit der Präsentation von Daten und nicht mit dem Abrufen von Daten beschäftigen. Angenommen, Sie verschieben Ihre gesamte Datenbank in CSV-Dateien (ich weiß, verrückte Idee), dann sollte Ihre Präsentationsschicht dies überhaupt nicht bemerken.

Im Idealfall verfügen Sie über eine Business-Layer-Methode, die nur die Daten zurückgibt, die Sie dem Benutzer anzeigen möchten. Sie sollten einen Blick auf ObjectDataSource statt SqlDataSource werfen. SqlDataSource ist gut für kleine Prototyping-Projekte, aber Sie sollten es nicht für ernstere Projekte verwenden.

Zwischen Business-Schicht und Datenschicht sollten Sie eine ähnliche Trennung haben. Die Datenschicht ist dafür verantwortlich, die gewünschten Daten von einem Speicherort (Datenbank, CSV-Datei, Webdienst usw.) abzurufen. Im Idealfall sollte die Business-Schicht nicht von den Implementierungsdetails der Datenschicht abhängen. Wenn Sie beispielsweise mit SQL Server sprechen, sollten Sie keine Instanz SqlDataReader an Ihre Business-Schicht zurückgeben. Auf diese Weise erstellen Sie eine Abhängigkeit Ihrer Business-Schicht von einem Implementierungsdetail Ihrer Datenschicht: der tatsächlichen Datenbank, aus der die Daten abgerufen werden.

In der Praxis sehen Sie, dass die Business-Schicht auf Implementierungsdetails der Datenschicht auf die eine oder andere Weise abhängt und das ist normalerweise keine schlechte Sache. Wann haben Sie sich zum letzten Mal für einen Wechsel der Datenbanken entschieden? Das Eliminieren von Abhängigkeiten und das Isolieren von Implementierungsdetails führt jedoch fast immer zu einer Anwendung, die einfacher zu verwalten und zu verstehen ist.

Sie können eine ähnliche Erklärung here finden.

+0

Vielen Dank, ich wusste nichts über die ObjectDataSource. Das hat sehr geholfen! –

3

Die größte Erklärung von Logikschichten in ASP.NET-Anwendungen stammt aus zwei Quellen. Die erste ist Microsofts eigene ASP.NET-Website, die von Scott Mitchell geschrieben wurde und eine gute Einführung in die Trennung der Logik bietet. Die Tutorials sind ziemlich wortreich, aber ich fand sie sehr nützlich. Die URL lautet http://www.asp.net/learn/data-access/.

Die zweite Ressource, die ich sehr nützlich fand, folgte darauf, und es wurde von Imar Spaanjaars geschrieben und ist verfügbar here. Es ist ein viel technischer Artikel, bietet aber eine großartige Möglichkeit, die Struktur zu Ihrer Anwendung hinzuzufügen.

Ich hoffe, dass hilft.

Ian.

+0

Der Link von Imar Spaanjaars war sehr hilfreich, danke dafür! –

+1

Ich bin froh, dass du es magst. Ich fand es eine großartige Zwischenressource, von der ich lernen kann. –

+0

Ich habe gerade Imars Artikel angeschaut. Nur eine einfache Frage - ist es nicht die Einführung des berüchtigten "Anemic Domain Model"? Ich meine, warum sollte ich meine Geschäftslogik von meinen Geschäftsobjekten trennen? Und wenn ich das tue, warum würde ich das Geschäftsobjekt sogar "Geschäftsobjekte" nennen? – atiyar

0

Als eine Nebenbemerkung zum Hauptteil seiner Frage, würde ich Ihnen empfehlen, ASPNET_REGSQL zu betrachten, um Ihre SQL-Datenbank für die integrierten .Mitgliedschafts-/Profil-/Rollenfähigkeiten von .Net zu konfigurieren. Es würde eine Menge Ärger und Hektik für das Erstellen/Aktualisieren von Benutzern usw. beseitigen. Ich habe das Profil nicht sehr oft verwendet, aber es erlaubt Ihnen, zusätzliche Attribute an Ihren Benutzer "anzuheften", z. Zugangscode.

Wenn Sie mit einer vorhandenen Datenbankstruktur arbeiten, die bereits eine Benutzerauthentifizierung usw. durchführt, können Sie einen benutzerdefinierten Mitgliedschaftsanbieter erstellen, der die vorhandenen db-Tabellen und gespeicherten Prozeduren nutzt.