Вопрос

Я только начал программировать на 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();
      }
    }
  }
}
Это было полезно?

Решение

Джон,

Первое, что нужно понять, это то, что если вы собираетесь создавать многоуровневые приложения, вам не следует хранить операторы SQL непосредственно на страницах ASPX (поскольку SqlDataSource требует).А SqlDataSource элемент управления был создан, чтобы продемонстрировать, насколько легко связать и обновить приложение с данными базы данных, и он не предназначен для использования в реальных приложениях, поскольку он как бы противоречит цели использования уровня BL и уровня данных, если вы собираетесь хранить Select Операторы /Update/Delete/Insert на странице ASPX.

Вся цель проектирования приложений на основе слоев — инкапсулировать каждый уровень так, чтобы не было пересечений.Каждый уровень взаимодействует с общедоступным интерфейсом других уровней и ничего не знает об их внутренней реализации.

Поэтому жизнеспособной альтернативой является использование ObjectDataSource контроль.Этот элемент управления позволяет напрямую привязываться к уровню данных или к уровню логики Biz, который, в свою очередь, может вызывать уровень данных.Непосредственная привязка к уровню данных имеет тот недостаток, что вы будете возвращать структуры данных, которые раскрывают схему таблиц базы данных (например, DataTables или DataViews).

Итак, рекомендуемая логика выглядит следующим образом:

Страница ASPX использует элемент управления DataSource для привязки к классу BL.Этот класс BL предоставляет соответствующие функции, такие как GetData, UpdateData, DeleteData and InsertData (со всеми необходимыми перегрузками), и эти функции возвращают строго типизированные объекты или коллекции, которые ObjectDataSource можно работать и отображать.Каждая общедоступная функция в классе BL внутренне вызывает DataLayer для выбора/обновления/удаления/вставки данных в/из базы данных.

Отличное введение в этот многоуровневый дизайн в ASP.NET представлено в Краткое руководство

P.S.:@Энди упомянул общие уровни данных, которые работают со всеми сценариями.Видеть этот вопрос для примера, как это будет выглядеть.

Другие советы

Лучшее объяснение логических уровней в приложениях ASP.NET можно получить из двух источников.Первый — это собственный веб-сайт Microsoft ASP.NET, написанный Скоттом Митчеллом. Он представляет собой хорошее введение в разделение логики.Учебники довольно многословны, но я нашел их очень полезными.URL-адрес http://www.asp.net/learn/data-access/.

Второй ресурс, который я нашел очень полезным, последовал за ним, он был написан Имаром Спааньяарсом и доступен. здесь.Это гораздо более техническая статья, но она предоставляет отличный способ добавить структуру в ваше приложение.

Надеюсь, это поможет.

Ян.

Если вы напишете свой код максимально переносимым, вы обнаружите, что в вашем приложении будет 3 (или более!) слоя.

Например, вместо того, чтобы заставлять свой уровень доступа к данным работать специально для этого одного приложения, напишите его так, чтобы вам никогда не приходилось писать его снова.Убедитесь, что всем вашим функциям можно передавать переменные, и вы не полагаетесь на глобальные переменные (или как можно меньше).Когда придет время для вашего следующего проекта — скопируйте и вставьте свой DAL, и вы снова сможете работать.

И на этом все не заканчивается: возможно, вам захочется написать подуровень для вашего DAL, который будет интерпретировать данные между MySQL и MSSQL (просто в качестве примера).Или у вас может быть библиотека общих функций, которые вы выполняете, например очистка текста, генерация CSS или что-то в этом роде.

Если вы пишете свой код так, что однажды сядете писать приложение — а это в основном включает в себя вырезание и вставку предыдущего кода — вы достигли нирваны программиста.:)

Вся идея многоуровневого приложения заключается в том, что каждый уровень не зависит от деталей реализации нижележащих слоев.Например, в вашем коде внутри уровня представления есть оператор T-SQL.Это означает, что у вас есть прямая зависимость вашего уровня представления от вашей базы данных (нижний уровень).Если вы вносите изменения в свою базу данных, вы также должны внести изменения в уровень представления.В идеале это не то, что вам нужно.Уровень представления должен заботиться только о представлении данных, а не о том, как их получить.Предположим, вы переместили всю свою базу данных в файлы CSV (знаю, сумасшедшая идея), тогда ваш уровень представления вообще не должен знать об этом.

Поэтому в идеале у вас есть метод бизнес-уровня, который возвращает только те данные, которые вы хотите показать пользователю.Вам следует взглянуть на ObjectDataSource вместо SqlDataSource. SqlDataSource хорош для небольших проектов прототипирования, но вам не следует использовать его для более серьезных проектов.

Между бизнес-уровнем и уровнем данных должно быть аналогичное разделение.Уровень данных отвечает за получение нужных данных из какого-либо места хранения (база данных, CSV-файл, веб-служба и т. д.).Опять же, в идеале бизнес-уровень не должен зависеть от деталей реализации уровня данных.Например, если вы разговариваете с SQL Server, вам не следует возвращать SqlDataReader экземпляр на ваш бизнес-уровень.Делая это, вы создаете зависимость вашего бизнес-уровня от деталей реализации вашего уровня данных:фактическая база данных, из которой он извлекает данные.

На практике вы видите, что бизнес-уровень так или иначе зависит от деталей реализации уровня данных, и обычно это неплохо.Когда вы в последний раз решали сменить базу данных?Но устранение зависимостей и максимально возможная изоляция деталей реализации почти всегда приводит к тому, что приложение становится легче поддерживать и понимать.

Вы можете найти подобное объяснение здесь.

Помимо основной сути его вопроса, я бы порекомендовал вам взглянуть на ASPNET_REGSQL, чтобы настроить вашу базу данных SQL для обработки встроенных возможностей членства/профиля/роли .Net.Это устранило бы много хлопот и хлопот по созданию/обновлению пользователей и т. д.Я не очень часто использовал профиль, но он позволяет вам «прикрепить» дополнительные атрибуты к вашему пользователю, например.Код доступа.

Если вы имеете дело с существующей структурой БД, которая уже выполняет аутентификацию пользователей и т. д., вы можете создать собственный поставщик членства, который будет использовать существующие таблицы БД и хранимые процедуры.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top