7

我刚开始用 C# 编程,并且正在阅读关于将您的应用程序/网站分为三个不同层的最佳实践,但我很难准确理解如何。我正在从事一个宠物项目,以更多地了解 C#,但我不想从任何坏习惯开始。你能看看我有什么,看看我做得对吗?提供一些关于如何将所有内容分解为不同层的提示建议?

表示层

<%@ 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>

业务层

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

数据访问层 (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();
      }
    }
  }
}
4

5 回答 5

9

乔恩,

首先要理解的是,如果您打算构建基于层的应用程序,那么您不应该将 SQL 语句直接存储在 ASPX 页面中(根据SqlDataSource需要)。该SqlDataSource控件旨在演示使用数据库数据绑定和更新应用程序是多么容易,并且不打算在现实世界的应用程序中使用,因为如果您要存储,它有点违背了拥有 BL 层和 Datalayer 的目的在 ASPX 页面中选择/更新/删除/插入语句。

基于层的应用程序设计的全部目的是封装每一层,使之没有交叉。每一层都与其他层的公共接口交互,对它们的内部实现一无所知。

因此,可行的替代方法是使用ObjectDataSource控件。此控件允许您直接绑定到 DataLayer 或 Biz 逻辑层,后者又可以调用 Datalayer。直接绑定到 Datalayer 的缺点是您将返回暴露数据库表模式的数据结构(例如,DataTables 或 DataViews)。

因此,推荐的逻辑流程如下:

ASPX 页面使用 DataSource 控件绑定到 BL 类。这个 BL 类提供了适当的函数,例如(带有任何必需的重载),这些函数返回可以使用和显示GetData, UpdateData, DeleteData and InsertData的强类型对象或集合。ObjectDataSourceBL 类中的每个公共函数在内部调用 DataLayer 以从数据库中选择/更新/删除/插入数据。

快速入门中提供了对 ASP.NET 中基于层的设计的精彩介绍

PS:@Andy 提到了适用于所有场景的通用数据层。有关它的外观示例,请参阅此问题。

于 2009-04-29T06:18:16.023 回答
3

对 ASP.NET 应用程序中逻辑层的最佳解释来自两个来源。第一个是由 Scott Mitchell 编写的微软自己的 ASP.NET 网站,它很好地介绍了逻辑分离。教程很冗长,但我发现它们非常有用。URL 是http://www.asp.net/learn/data-access/

我发现第二个资源非常有用,它由 Imar Spaanjaars 编写,可在此处获得。这是一篇技术性更强的文章,但提供了一种将结构添加到应用程序的好方法。

我希望这会有所帮助。

伊恩。

于 2009-04-29T08:11:16.627 回答
0

如果您编写的代码最终是可移植的,您会发现您的应用程序中有 3 个(或更多!)层。

例如 - 不要让您的数据访问层专门为这个应用程序工作,而是编写它,这样您就不必再编写它了。确保您的所有函数都可以传递变量,并且您不依赖全局变量(或尽可能少)。当你的下一个项目到了 - 复制并粘贴你的 DAL 之后,你突然又重新启动并运行了。

它并没有就此结束——您可能想为您的 DAL 编写一个子层,在 MySQL 和 MSSQL 之间进行解释(仅作为示例)。或者你可能有一个你执行的常用函数库,比如文本清理或 CSS 生成等等。

如果你写你的代码,有一天,你坐下来写一个应用程序——它主要涉及剪切和粘贴以前的代码——你已经达到了程序员的必杀技。:)

于 2009-04-29T05:07:39.843 回答
0

分层应用程序背后的整个想法是,每一层都不依赖于下面各层的实现细节。例如,在您的代码中,您的表示层中有一条 T-SQL 语句。这意味着您的表示层直接依赖于数据库(底层)。如果您在数据库中进行更改,您还必须在表示层中进行更改。理想情况下,这不是您想要的。表示层应该只关心数据的呈现,而不是如何检索它。假设您将整个数据库移动到 CSV 文件中(我知道,疯狂的想法),那么您的表示层根本不应该意识到这一点。

所以理想情况下,你有一个业务层方法,它只返回你想向用户显示的数据。你应该看看ObjectDataSource而不是SqlDataSource. SqlDataSource非常适合小型原型项目,但您不应该将它用于任何更严肃的项目。

在业务层和数据层之间,您应该有类似的分离。数据层负责从某个存储位置(数据库、CSV 文件、Web 服务……)获取您想要的数据。同样,理想情况下,业务层不应依赖于数据层的实现细节。例如,如果您正在与 SQL Server 交谈,则不应将SqlDataReader实例返回到您的业务层。通过这样做,您创建了业务层对数据层实现细节的依赖关系:它从中检索数据的实际数据库。

在实践中,您会看到业务层确实以某种方式依赖于数据层的实现细节,这通常不是一件坏事。您最后一次决定切换数据库是什么时候?但是尽可能地消除依赖关系和隔离实现细节几乎总是会导致应用程序更易于维护和理解。

您可以在此处找到类似的解释。

于 2009-04-29T06:14:31.803 回答
0

除了他问题的主要内容之外,我建议您查看 ASPNET_REGSQL 来配置您的 SQL 数据库以处理 .Net 的内置成员/配置文件/角色功能。它将消除创建/更新用户等的大量麻烦和麻烦。我没有使用过很多配置文件,但它允许您为您的用户“附加”额外的属性,例如 AccessCode。

如果您正在处理已经进行用户身份验证等的现有数据库结构,您可以创建一个自定义成员资格提供程序,该提供程序将利用现有的数据库表和存储过程。

于 2009-10-01T14:52:19.483 回答