Tworzenie warstwy logiki biznesowej (VB)

Autor: Scott Mitchell

Pobierz plik PDF

W tym samouczku zobaczymy, jak scentralizować reguły biznesowe w warstwie logiki biznesowej (BLL), która służy jako pośrednik w wymianie danych między warstwą prezentacji a usługą DAL.

Wprowadzenie

Warstwa dostępu do danych (DAL) utworzona w pierwszym samouczku w sposób czysty oddziela logikę dostępu do danych od logiki prezentacji. Jednak mimo że funkcja DAL w sposób czysty oddziela szczegóły dostępu do danych od warstwy prezentacji, nie wymusza żadnych reguł biznesowych, które mogą być stosowane. Na przykład w przypadku naszej aplikacji możemy nie zezwalać CategoryID na modyfikowanie pól Products lub SupplierID tabeli, gdy Discontinued pole jest ustawione na 1, lub możemy chcieć wymusić reguły seniorstwa, zakazując sytuacji, w których pracownik jest zarządzany przez kogoś, kto został zatrudniony po nich. Innym typowym scenariuszem jest autoryzacja może być tylko użytkownicy w określonej roli mogą usuwać produkty lub zmieniać UnitPrice wartość.

W tym samouczku zobaczymy, jak scentralizować te reguły biznesowe w warstwie logiki biznesowej (BLL), która służy jako pośrednik w wymianie danych między warstwą prezentacji a usługą DAL. W rzeczywistej aplikacji BLL należy zaimplementować jako oddzielny projekt biblioteki klas; Jednak w przypadku tych samouczków wdrożymy usługę BLL jako serię klas w naszym App_Code folderze, aby uprościć strukturę projektu. Rysunek 1 ilustruje relacje architektury między warstwą prezentacji, BLL i DAL.

Usługa BLL oddziela warstwę prezentacji od warstwy dostępu do danych i nakłada reguły biznesowe

Rysunek 1. Usługa BLL oddziela warstwę prezentacji od warstwy dostępu do danych i nakłada reguły biznesowe

Zamiast tworzyć oddzielne klasy w celu zaimplementowania logiki biznesowej, możemy alternatywnie umieścić tę logikę bezpośrednio w typowym zestawie danych z klasami częściowymi. Aby zapoznać się z przykładem tworzenia i rozszerzania typu zestawu danych, zapoznaj się z pierwszym samouczkiem.

Krok 1. Tworzenie klas BLL

Nasza BLL będzie składać się z czterech klas, jeden dla każdego TableAdapter w DAL; każda z tych klas BLL będzie miała metody pobierania, wstawiania, aktualizowania i usuwania z odpowiedniej klasy TableAdapter w dal, stosując odpowiednie reguły biznesowe.

Aby dokładniej oddzielić klasy związane z dal i BLL, utwórzmy dwa podfoldery w folderze App_CodeDAL i BLL. Po prostu kliknij prawym przyciskiem myszy App_Code folder w Eksplorator rozwiązań i wybierz pozycję Nowy folder. Po utworzeniu tych dwóch folderów przenieś typowy zestaw danych utworzony w pierwszym samouczku do podfolderu DAL .

Następnie utwórz cztery pliki klas BLL w BLL podfolderze. Aby to osiągnąć, kliknij prawym przyciskiem myszy BLL podfolder, wybierz pozycję Dodaj nowy element, a następnie wybierz szablon Klasa. Nazwij cztery klasy ProductsBLL, CategoriesBLL, SuppliersBLLi EmployeesBLL.

Dodawanie czterech nowych klas do folderu App_Code

Rysunek 2. Dodawanie czterech nowych klas do App_Code folderu

Następnie dodajmy metody do każdej klasy, aby po prostu opakować metody zdefiniowane dla klasy TableAdapters z pierwszego samouczka. Na razie te metody będą po prostu wywoływać bezpośrednio do dal; Wrócimy później, aby dodać dowolną potrzebną logikę biznesową.

Uwaga

Jeśli używasz programu Visual Studio Standard Edition lub nowszego (czyli nie używasz programu Visual Web Developer), możesz opcjonalnie wizualnie zaprojektować klasy przy użyciu Projektant klasy. Aby uzyskać więcej informacji na temat tej nowej funkcji w programie Visual Studio, zapoznaj się z blogami klasy Projektant.

ProductsBLL Dla klasy musimy dodać łącznie siedem metod:

  • GetProducts() zwraca wszystkie produkty
  • GetProductByProductID(productID) zwraca produkt o określonym identyfikatorze produktu
  • GetProductsByCategoryID(categoryID) zwraca wszystkie produkty z określonej kategorii
  • GetProductsBySupplier(supplierID) zwraca wszystkie produkty od określonego dostawcy
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) wstawia nowy produkt do bazy danych przy użyciu przekazanych wartości; ProductID zwraca wartość nowo wstawionego rekordu
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) aktualizuje istniejący produkt w bazie danych przy użyciu przekazanych wartości; zwraca wartość True , jeśli dokładnie jeden wiersz został zaktualizowany, False w przeciwnym razie
  • DeleteProduct(productID) usuwa określony produkt z bazy danych

ProductsBLL.vb

Imports NorthwindTableAdapters

<System.ComponentModel.DataObject()> _
Public Class ProductsBLL

    Private _productsAdapter As ProductsTableAdapter = Nothing
    Protected ReadOnly Property Adapter() As ProductsTableAdapter
        Get
            If _productsAdapter Is Nothing Then
                _productsAdapter = New ProductsTableAdapter()
            End If

            Return _productsAdapter
        End Get
    End Property

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As Northwind.ProductsDataTable
        Return Adapter.GetProducts()
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductByProductID(ByVal productID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductByProductID(productID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsByCategoryID(categoryID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsBySupplierID(ByVal supplierID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsBySupplierID(supplierID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Insert, True)> _
    Public Function AddProduct( _
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean) _
        As Boolean

        Dim products As New Northwind.ProductsDataTable()
        Dim product As Northwind.ProductsRow = products.NewProductsRow()

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        products.AddProductsRow(product)
        Dim rowsAffected As Integer = Adapter.Update(products)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(_
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean, productID As Integer) _
        As Boolean

        Dim products As Northwind.ProductsDataTable = _
            Adapter.GetProductByProductID(productID)

        If products.Count = 0 Then
            Return False
        End If

        Dim product as Northwind.ProductsRow = products(0)

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        Dim rowsAffected As Integer = Adapter.Update(product)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, True)> _
    Public Function DeleteProduct(ByVal productID As Integer) As Boolean
        Dim rowsAffected As Integer = Adapter.Delete(productID)

        Return rowsAffected = 1
    End Function
End Class

Metody, które po prostu zwracają dane GetProducts, , GetProductByProductIDGetProductsByCategoryIDi GetProductBySuppliersID są dość proste, ponieważ po prostu są wywoływane do dal. W niektórych scenariuszach mogą istnieć reguły biznesowe, które należy zaimplementować na tym poziomie (takie jak reguły autoryzacji oparte na aktualnie zalogowanym użytkowniku lub roli, do której należy użytkownik), po prostu pozostawimy te metody zgodnie z oczekiwaniami. W przypadku tych metod usługa BLL służy jedynie jako serwer proxy, za pośrednictwem którego warstwa prezentacji uzyskuje dostęp do danych bazowych z warstwy dostępu do danych.

Metody AddProduct i UpdateProduct przyjmują zarówno jako parametry wartości różnych pól produktu, jak i dodają nowy produkt lub zaktualizują istniejącą odpowiednio. Ponieważ wiele Product kolumn tabeli może akceptować NULL wartości (CategoryID, SupplierIDi UnitPrice, w celu nadenia kilku), te parametry wejściowe dla AddProduct i UpdateProduct mapowania na takie kolumny używają typów dopuszczających wartość null. Typy dopuszczające wartość null są nowe dla platformy .NET 2.0 i zapewniają technikę wskazującą, czy zamiast tego powinien być Nothingtypem wartości . Aby uzyskać więcej informacji, zapoznaj się z wpisem w blogu Paul VickThe Truth About Nullable Types and VB (Prawda o typach dopuszczanych do wartości null) oraz dokumentacją techniczną dotyczącą struktury dopuszczalnej do wartości null .

Wszystkie trzy metody zwracają wartość logiczną wskazującą, czy wiersz został wstawiony, zaktualizowany lub usunięty, ponieważ operacja może nie spowodować wystąpienia wiersza, którego dotyczy problem. Jeśli na przykład deweloper strony wywołuje DeleteProduct przekazywanie elementu ProductID dla nieistniejącego produktu, DELETE instrukcja wystawiona dla bazy danych nie będzie miała wpływu, a zatem DeleteProduct metoda zwróci Falsewartość .

Należy pamiętać, że podczas dodawania nowego produktu lub aktualizowania istniejącego elementu przyjmujemy wartości pól nowego lub zmodyfikowanego produktu jako listę skalarnych, a nie akceptowanie ProductsRow wystąpienia. Ta metoda została wybrana, ponieważ ProductsRow klasa pochodzi z klasy ADO.NETDataRow, która nie ma domyślnego konstruktora bez parametrów. Aby utworzyć nowe ProductsRow wystąpienie, musimy najpierw utworzyć wystąpienie, a następnie wywołać jego NewProductRow() metodę ProductsDataTable (co robimy w AddProductprogramie ). To krótkie tyły głowy, gdy zwrócimy się do wstawiania i aktualizowania produktów przy użyciu obiektu ObjectDataSource. Krótko mówiąc, obiekt ObjectDataSource spróbuje utworzyć wystąpienie parametrów wejściowych. Jeśli metoda BLL oczekuje wystąpienia, obiekt ObjectDataSource spróbuje ProductsRow go utworzyć, ale nie powiedzie się z powodu braku domyślnego konstruktora bez parametrów. Aby uzyskać więcej informacji na temat tego problemu, zapoznaj się z następującymi dwoma wpisami na forach ASP.NET: Aktualizowanie obiektówDataSources przy użyciu zestawów danych Strongly-Typedoraz Problem z obiektami ObjectDataSource i Strongly-Typed DataSet.

Następnie w obu przypadkach AddProductUpdateProductkod tworzy ProductsRow wystąpienie i wypełnia je wartościami, które właśnie zostały przekazane. Podczas przypisywania wartości do kolumny DataColumns obiektu DataRow mogą wystąpić różne kontrole poprawności na poziomie pola. W związku z tym ręczne umieszczenie przekazanych wartości z powrotem do elementu DataRow pomaga zapewnić ważność danych przekazywanych do metody BLL. Niestety silnie typizowane klasy DataRow wygenerowane przez program Visual Studio nie używają typów dopuszczalnych wartości null. Aby wskazać, że określona kolumna DataColumn w elemecie DataRow powinna odpowiadać NULL wartości bazy danych, należy użyć SetColumnNameNull() metody .

W UpdateProduct pliku najpierw załadujemy produkt do aktualizacji przy użyciu polecenia GetProductByProductID(productID). Chociaż może to wydawać się niepotrzebną podróżą do bazy danych, ta dodatkowa podróż okaże się warta w przyszłych samouczkach, które eksplorują optymistyczną współbieżność. Optymistyczna współbieżność to technika zapewniająca, że dwóch użytkowników, którzy pracują jednocześnie nad tymi samymi danymi, nie zastępuje przypadkowo zmian. Pobranie całego rekordu ułatwia również tworzenie metod aktualizacji w usłudze BLL, które modyfikują tylko podzestaw kolumn DataRow. Podczas eksplorowania SuppliersBLL klasy zobaczymy taki przykład.

Na koniec należy pamiętać, że ProductsBLL klasa ma zastosowany atrybut DataObject ( [System.ComponentModel.DataObject] składnia bezpośrednio przed instrukcją klasy w górnej części pliku) i metody mają atrybuty DataObjectMethodAttribute. Atrybut DataObject oznacza klasę jako obiekt odpowiedni do powiązania z kontrolką ObjectDataSource, natomiast DataObjectMethodAttribute element wskazuje przeznaczenie metody. Jak zobaczymy w przyszłych samouczkach, ASP.NET 2.0 ObjectDataSource ułatwia deklaratywne uzyskiwanie dostępu do danych z klasy. Aby ułatwić filtrowanie listy możliwych klas do powiązania z kreatorem objectDataSource, domyślnie tylko te klasy oznaczone jako DataObjects są wyświetlane na liście rozwijanej kreatora. Klasa ProductsBLL będzie działać równie dobrze bez tych atrybutów, ale dodanie ich ułatwia pracę z kreatorem ObjectDataSource.

Dodawanie innych klas

ProductsBLL Po ukończeniu klasy nadal musimy dodać klasy do pracy z kategoriami, dostawcami i pracownikami. Poświęć chwilę, aby utworzyć następujące klasy i metody przy użyciu pojęć przedstawionych w powyższym przykładzie:

  • CategoriesBLL.cs

    • GetCategories()
    • GetCategoryByCategoryID(categoryID)
  • SuppliersBLL.cs

    • GetSuppliers()
    • GetSupplierBySupplierID(supplierID)
    • GetSuppliersByCountry(country)
    • UpdateSupplierAddress(supplierID, address, city, country)
  • EmployeesBLL.cs

    • GetEmployees()
    • GetEmployeeByEmployeeID(employeeID)
    • GetEmployeesByManager(managerID)

Jedną z metod, których warto zauważyć, jest SuppliersBLL metoda klasy UpdateSupplierAddress . Ta metoda udostępnia interfejs umożliwiający aktualizowanie tylko informacji o adresach dostawcy. Wewnętrznie ta metoda odczytuje obiekt dla określonego SupplierDataRowsupplierID (przy użyciu GetSupplierBySupplierID), ustawia jego właściwości związane z adresami, a następnie wywołuje w dół do SupplierDataTablemetody "s Update ". Metoda jest następująca UpdateSupplierAddress :

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateSupplierAddress(ByVal supplierID As Integer, _
    ByVal address As String, ByVal city As String, ByVal country As String) _
    As Boolean

    Dim suppliers As Northwind.SuppliersDataTable = _
        Adapter.GetSupplierBySupplierID(supplierID)

    If suppliers.Count = 0 Then
        Return False
    Else
        Dim supplier As Northwind.SuppliersRow = suppliers(0)

        If address Is Nothing Then
            supplier.SetAddressNull()
        Else
            supplier.Address = address
        End If

        If city Is Nothing Then
            supplier.SetCityNull()
        Else
            supplier.City = city
        End If

        If country Is Nothing Then
            supplier.SetCountryNull()
        Else
            supplier.Country = country
        End If

        Dim rowsAffected As Integer = Adapter.Update(supplier)

        Return rowsAffected = 1
    End If
End Function

Zapoznaj się z pobieraniem tego artykułu, aby uzyskać pełną implementację klas BLL.

Krok 2. Uzyskiwanie dostępu do typowych zestawów danych za pośrednictwem klas BLL

W pierwszym samouczku widzieliśmy przykłady pracy bezpośrednio z typed DataSet programowo, ale z dodatkiem naszych klas BLL warstwa prezentacji powinna działać w stosunku do BLL. W przykładzie AllProducts.aspx z pierwszego samouczka ProductsTableAdapter użyto narzędzia do powiązania listy produktów z elementem GridView, jak pokazano w poniższym kodzie:

Dim productsAdapter As New ProductsTableAdapter()
GridView1.DataSource = productsAdapter.GetProducts()
GridView1.DataBind()

Aby użyć nowych klas BLL, wszystko, co należy zmienić, to pierwszy wiersz kodu po prostu zastąpić ProductsTableAdapter obiekt obiektem ProductBLL :

Dim productLogic As New ProductsBLL()
GridView1.DataSource = productLogic.GetProducts()
GridView1.DataBind()

Do klas BLL można również uzyskać dostęp deklaratywnie (co może być typed DataSet) przy użyciu obiektu ObjectDataSource. Bardziej szczegółowo omówimy obiekt ObjectDataSource w poniższych samouczkach.

Lista produktów jest wyświetlana w siatceView

Rysunek 3. Lista produktów jest wyświetlana w widoku GridView (kliknij, aby wyświetlić obraz pełnowymiarowy)

Krok 3. Dodawanie weryfikacji Field-Level do klas DataRow

Sprawdzanie poprawności na poziomie pola dotyczy wartości właściwości obiektów biznesowych podczas wstawiania lub aktualizowania. Niektóre reguły sprawdzania poprawności na poziomie pola dla produktów obejmują:

  • Pole ProductName musi mieć długość 40 znaków lub mniej
  • Pole QuantityPerUnit musi mieć długość 20 znaków lub mniej
  • Pola ProductID, ProductNamei Discontinued są wymagane, ale wszystkie inne pola są opcjonalne
  • Pola UnitPrice, UnitsInStock, UnitsOnOrderi ReorderLevel muszą być większe niż lub równe zero

Te reguły mogą i powinny być wyrażone na poziomie bazy danych. Limit znaków w ProductName polach i QuantityPerUnit są przechwytywane przez typy danych tych kolumn w Products tabeli (nvarchar(40) i nvarchar(20), odpowiednio). Określa, czy pola są wymagane, a opcjonalne są wyrażane przez kolumnę tabeli NULL bazy danych. Istnieją cztery ograniczenia sprawdzania, które zapewniają, że tylko wartości większe niż lub równe zero mogą przekształcić je w UnitPricekolumny , , UnitsInStocklub UnitsOnOrderReorderLevel .

Oprócz wymuszania tych reguł w bazie danych powinny być również wymuszane na poziomie zestawu danych. W rzeczywistości długość pola i to, czy wartość jest wymagana, czy opcjonalna, są już przechwytywane dla zestawu kolumn DataColumns każdej tabeli danych. Aby automatycznie wyświetlić istniejącą weryfikację na poziomie pola, przejdź do Projektant Zestawu danych, wybierz pole z jednej z tabel danych, a następnie przejdź do okno Właściwości. Jak pokazano na rysunku 4, kolumna QuantityPerUnit DataColumn w obiekcie ProductsDataTable ma maksymalną długość 20 znaków i zezwala na NULL wartości. Jeśli spróbujemy ustawić ProductsDataRowwłaściwość "s QuantityPerUnit " na wartość ciągu dłuższą niż 20 znaków, ArgumentException zostanie zgłoszony.

Funkcja DataColumn zapewnia podstawową weryfikację Field-Level

Rysunek 4. Kolumna danych zapewnia podstawową weryfikację Field-Level (kliknij, aby wyświetlić obraz pełnowymiarowy)

Niestety, nie można określić kontroli granic, takich jak UnitPrice wartość musi być większa lub równa zero, za pośrednictwem okno Właściwości. Aby zapewnić ten typ weryfikacji na poziomie pola, musimy utworzyć procedurę obsługi zdarzeń dla zdarzenia ColumnChanging tabeli Danych. Jak wspomniano w poprzednim samouczku, obiekty DataSet, DataTables i DataRow utworzone przez typowy zestaw danych można rozszerzyć za pomocą klas częściowych. Korzystając z tej techniki, można utworzyć procedurę ColumnChanging obsługi zdarzeń dla ProductsDataTable klasy. Zacznij od utworzenia klasy w folderze App_Code o nazwie ProductsDataTable.ColumnChanging.vb.

Dodawanie nowej klasy do folderu App_Code

Rysunek 5. Dodawanie nowej klasy do App_Code folderu (kliknij, aby wyświetlić obraz pełnowymiarowy)

Następnie utwórz procedurę obsługi zdarzeń dla ColumnChanging zdarzenia, która gwarantuje, że UnitPricewartości kolumn , , UnitsInStockUnitsOnOrderi ReorderLevel (jeśli nie NULL) są większe lub równe zero. Jeśli jakakolwiek taka kolumna jest poza zakresem, wyrzuć wartość ArgumentException.

ProductsDataTable.ColumnChanging.vb

Imports System.data

Partial Public Class Northwind
    Partial Public Class ProductsDataTable
        Public Overrides Sub BeginInit()
            AddHandler Me.ColumnChanging, AddressOf ValidateColumn
        End Sub

        Sub ValidateColumn(sender As Object, e As DataColumnChangeEventArgs)
            If e.Column.Equals(Me.UnitPriceColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Decimal) < 0 Then
                    Throw New ArgumentException( _
                        "UnitPrice cannot be less than zero", "UnitPrice")
                End If
            ElseIf e.Column.Equals(Me.UnitsInStockColumn) OrElse _
                e.Column.Equals(Me.UnitsOnOrderColumn) OrElse _
                e.Column.Equals(Me.ReorderLevelColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Short) < 0 Then
                    Throw New ArgumentException(String.Format( _
                        "{0} cannot be less than zero", e.Column.ColumnName), _
                        e.Column.ColumnName)
                End If
            End If
        End Sub
    End Class
End Class

Krok 4. Dodawanie niestandardowych reguł biznesowych do klas BLL

Oprócz sprawdzania poprawności na poziomie pola mogą istnieć niestandardowe reguły biznesowe wysokiego poziomu, które obejmują różne jednostki lub pojęcia, które nie są wyrażalne na poziomie jednej kolumny, takie jak:

  • Jeśli produkt zostanie przerwany, nie można go UnitPrice zaktualizować
  • Kraj zamieszkania pracownika musi być taki sam jak kraj zamieszkania swojego menedżera
  • Nie można wycofać produktu, jeśli jest to jedyny produkt dostarczony przez dostawcę

Klasy BLL powinny zawierać kontrole w celu zapewnienia zgodności z regułami biznesowymi aplikacji. Te kontrole można dodać bezpośrednio do metod, do których mają zastosowanie.

Załóżmy, że nasze zasady biznesowe określają, że produkt nie może być oznaczony jako przerwany, jeśli był to jedyny produkt od danego dostawcy. Oznacza to, że jeśli produkt X był jedynym produktem zakupionym od dostawcy Y, nie mogliśmy oznaczyć X jako przerwanego; jeśli jednak dostawca Y dostarczył nam trzy produkty, A, B i C, możemy oznaczyć dowolne i wszystkie z nich jako przerwane. Dziwna reguła biznesowa, ale reguły biznesowe i zdrowy rozsądek nie zawsze są zgodne!

Aby wymusić tę regułę biznesową UpdateProducts w metodzie, zaczniemy od sprawdzenia, czy Discontinued została ustawiona wartość True i, jeśli tak, wywołamy metodę GetProductsBySupplierID określania liczby produktów zakupionych od dostawcy tego produktu. W przypadku zakupu tylko jednego produktu od tego dostawcy zgłaszamy wyjątek ApplicationException.

<System.ComponentModel.DataObjectMethodAttribute_
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct( _
    productName As String, supplierID As Nullable(Of Integer), _
    categoryID As Nullable(Of Integer), quantityPerUnit As String, _
    unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
    unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
    discontinued As Boolean, productID As Integer) _
    As Boolean

    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)

    If products.Count = 0 Then
        Return False
    End If

    Dim product As Northwind.ProductsRow = products(0)

    If discontinued Then
        Dim productsBySupplier As Northwind.ProductsDataTable = _
            Adapter.GetProductsBySupplierID(product.SupplierID)

        If productsBySupplier.Count = 1 Then
            Throw New ApplicationException( _
                "You cannot mark a product as discontinued if it is " & _
                "the only product purchased from a supplier")
        End If
    End If

    product.ProductName = productName

    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If

    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If

    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If

    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If

    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If

    If Not unitsOnOrder.HasValue Then
        product.SetUnitsOnOrderNull()
    Else
        product.UnitsOnOrder = unitsOnOrder.Value
    End If

    If Not reorderLevel.HasValue Then
        product.SetReorderLevelNull()
    Else
        product.ReorderLevel = reorderLevel.Value
    End If

    product.Discontinued = discontinued

    Dim rowsAffected As Integer = Adapter.Update(product)

    Return rowsAffected = 1
End Function

Odpowiadanie na błędy walidacji w warstwie prezentacji

Podczas wywoływania biblioteki BLL z warstwy prezentacji możemy zdecydować, czy należy podjąć próbę obsługi wszelkich wyjątków, które mogą zostać podniesione, czy pozwolić im utworzyć bąbelek do ASP.NET (co spowoduje podniesienie HttpApplicationError zdarzenia). Aby obsłużyć wyjątek podczas pracy z BLL programowo, możemy użyć try... Blok catch , jak pokazano w poniższym przykładzie:

Dim productLogic As New ProductsBLL()

Try
    productLogic.UpdateProduct("Scotts Tea", 1, 1, Nothing, _
      -14, 10, Nothing, Nothing, False, 1)
Catch ae As ArgumentException
    Response.Write("There was a problem: " & ae.Message)
End Try

Jak zobaczymy w przyszłych samouczkach, obsługa wyjątków, które są bąbelkowe z poziomu BLL podczas korzystania z kontrolki internetowej danych do wstawiania, aktualizowania lub usuwania danych, można obsłużyć bezpośrednio w procedurze obsługi zdarzeń, w przeciwieństwie do konieczności zawijania kodu w Try...Catch blokach.

Podsumowanie

Dobrze zaprojektowana aplikacja jest spreparowana na różne warstwy, z których każda hermetyzuje określoną rolę. W pierwszym samouczku tej serii artykułów utworzyliśmy warstwę dostępu do danych przy użyciu typowych zestawów danych; W tym samouczku utworzyliśmy warstwę logiki biznesowej jako serię klas w folderze naszej aplikacji App_Code , które wywołają usługę DAL. Usługa BLL implementuje logikę na poziomie pola i na poziomie biznesowym dla naszej aplikacji. Oprócz tworzenia oddzielnej BLL, podobnie jak w tym samouczku, inną opcją jest rozszerzenie metod TableAdapters przy użyciu klas częściowych. Jednak użycie tej techniki nie pozwala nam zastąpić istniejących metod ani oddzielić naszego dal i naszego BLL tak czysto, jak podejście podjęte w tym artykule.

Po zakończeniu dal i BLL możemy rozpocząć pracę nad naszą warstwą prezentacji. W następnym samouczku omówimy krótki przewodnik od tematów dostępu do danych i zdefiniujemy spójny układ strony do użycia w ramach samouczków.

Szczęśliwe programowanie!

Informacje o autorze

Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w ciągu 24 godzin. Można do niego dotrzeć pod adresem mitchell@4GuysFromRolla.com. Lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.

Specjalne podziękowania

Ta seria samouczków została sprawdzona przez wielu pomocnych recenzentów. Recenzenci w tym samouczku byli Liz Shulok, Dennis Patterson, Carlos Santos i Hilton Giesenow. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, upuść mi wiersz pod adresem mitchell@4GuysFromRolla.com.