Creazione di un livello per la logica di business (VB)

di Scott Mitchell

Scarica il PDF

In questa esercitazione verrà illustrato come centralizzare le regole aziendali in un livello BLL (Business Logic Layer) che funge da intermediario per lo scambio di dati tra il livello di presentazione e il DAL.

Introduzione

Il livello di accesso ai dati creato nella prima esercitazione separa in modo pulito la logica di accesso ai dati dalla logica di presentazione. Tuttavia, mentre dal dal punto di vista pulito separa i dettagli di accesso ai dati dal livello di presentazione, non applica regole aziendali che possono essere applicate. Ad esempio, per l'applicazione potrebbe essere consigliabile impedire la CategoryID modifica dei campi o SupplierID dei campi della Products tabella quando il Discontinued campo è impostato su 1 oppure potrebbe essere necessario applicare regole di seniorità, impedendo situazioni in cui un dipendente viene gestito da un dipendente che è stato assunto dopo di essi. Un altro scenario comune è l'autorizzazione forse solo gli utenti in un determinato ruolo possono eliminare i prodotti o modificare il UnitPrice valore.

In questa esercitazione verrà illustrato come centralizzare queste regole business in un livello BLL (Business Logic Layer) che funge da intermediario per lo scambio di dati tra il livello di presentazione e il DAL. In un'applicazione reale, il BLL deve essere implementato come progetto di libreria di classi separato; Tuttavia, per queste esercitazioni si implementerà il BLL come serie di classi nella App_Code cartella per semplificare la struttura del progetto. La figura 1 illustra le relazioni architetturali tra il livello di presentazione, BLL e DAL.

BLL separa il livello di presentazione dal livello di accesso ai dati e impone regole business

Figura 1: BLL separa il livello di presentazione dal livello di accesso ai dati e impone regole business

Anziché creare classi separate per implementare la logica di business, è possibile inserire in alternativa questa logica direttamente nel DataSet tipizzato con classi parziali. Per un esempio di creazione ed estensione di un DataSet tipizzato, fare riferimento alla prima esercitazione.

Passaggio 1: Creazione delle classi BLL

Il BLL sarà composto da quattro classi, una per ogni TableAdapter nel DAL; ognuna di queste classi BLL avrà metodi per il recupero, l'inserimento, l'aggiornamento e l'eliminazione dai rispettivi TableAdapter nel DAL, applicando le regole business appropriate.

Per separare in modo più pulito le classi correlate a DAL e BLL, creare due sottocartelle nella App_Code cartella DAL e BLL. Fare semplicemente clic con il pulsante destro del mouse sulla App_Code cartella nella Esplora soluzioni e scegliere Nuova cartella. Dopo aver creato queste due cartelle, spostare l'oggetto DataSet tipizzato creato nella prima esercitazione nella DAL sottocartella.

Creare quindi i quattro file di classe BLL nella BLL sottocartella. A questo scopo, fare clic con il pulsante destro del mouse sulla BLL sottocartella, scegliere Aggiungi un nuovo elemento e scegliere il modello di classe. Assegnare un nome alle quattro classi ProductsBLL, CategoriesBLL, SuppliersBLLe EmployeesBLL.

Aggiungere quattro nuove classi alla cartella App_Code

Figura 2: Aggiungere quattro nuove classi alla App_Code cartella

Aggiungere quindi metodi a ognuna delle classi per eseguire semplicemente il wrapping dei metodi definiti per TableAdapters dalla prima esercitazione. Per il momento, questi metodi chiameranno direttamente nel DAL; verrà restituito in un secondo momento per aggiungere qualsiasi logica di business necessaria.

Nota

Se si usa Visual Studio Standard Edition o versione successiva(ovvero, non si usa Visual Web Developer), è possibile progettare le classi in modo visivo usando la classe Designer. Per altre informazioni su questa nuova funzionalità in Visual Studio, vedere il blog classe Designer.

Per la ProductsBLL classe è necessario aggiungere un totale di sette metodi:

  • GetProducts() restituisce tutti i prodotti
  • GetProductByProductID(productID) restituisce il prodotto con l'ID prodotto specificato
  • GetProductsByCategoryID(categoryID) restituisce tutti i prodotti dalla categoria specificata
  • GetProductsBySupplier(supplierID) restituisce tutti i prodotti dal fornitore specificato
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) inserisce un nuovo prodotto nel database usando i valori passati; restituisce il ProductID valore del record appena inserito
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)aggiorna un prodotto esistente nel database usando i valori passati; restituisce True se è stata aggiornata esattamente una riga, in caso contrario, False
  • DeleteProduct(productID) elimina il prodotto specificato dal database

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

I metodi che restituiscono semplicemente i dati GetProducts, GetProductByProductID, GetProductsByCategoryIDe GetProductBySuppliersID sono abbastanza semplici come semplicemente chiamano nel DAL. Anche se in alcuni scenari potrebbero essere implementate regole aziendali che devono essere implementate a questo livello (ad esempio le regole di autorizzazione basate sull'utente attualmente connesso o sul ruolo a cui appartiene l'utente), si lascerà semplicemente questi metodi come è. Per questi metodi, il BLL funge semplicemente da proxy tramite il quale il livello di presentazione accede ai dati sottostanti dal livello di accesso ai dati.

I AddProduct metodi e UpdateProduct accettano entrambi come parametri i valori per i vari campi del prodotto e aggiungono rispettivamente un nuovo prodotto o aggiornano uno esistente. Poiché molte delle Product colonne della tabella possono accettare valori (CategoryID, SupplierID, e UnitPrice, per denominare NULL alcuni), tali parametri di input per AddProduct e UpdateProduct che vengono mappati a tali colonne usano tipi nullable. I tipi nullable sono nuovi a .NET 2.0 e forniscono una tecnica per indicare se un tipo di valore deve invece essere Nothing. Per altre informazioni, vedere la voce di blog di Paul VickThe Truth About Nullable Types e VB e la documentazione tecnica per la struttura Nullable .

Tutti e tre i metodi restituiscono un valore booleano che indica se una riga è stata inserita, aggiornata o eliminata perché l'operazione potrebbe non comportare una riga interessata. Ad esempio, se lo sviluppatore di pagine chiama DeleteProduct un oggetto per un ProductID prodotto non esistente, l'istruzione DELETE rilasciata al database non avrà alcun effetto e pertanto il DeleteProduct metodo restituirà False.

Si noti che quando si aggiunge un nuovo prodotto o si aggiorna un oggetto esistente, si accettano i valori di campo del prodotto nuovo o modificato come elenco di scalari anziché accettare un'istanza ProductsRow . Questo approccio è stato scelto perché la ProductsRow classe deriva dalla classe ADO.NET DataRow , che non ha un costruttore senza parametri predefinito. Per creare una nuova ProductsRow istanza, è prima necessario creare un'istanza ProductsDataTable e quindi richiamare il NewProductRow() relativo metodo (che viene eseguito in AddProduct). Questo problema si rivolge all'inserimento e all'aggiornamento dei prodotti usando ObjectDataSource. In breve, ObjectDataSource tenterà di creare un'istanza dei parametri di input. Se il metodo BLL prevede un'istanza ProductsRow , ObjectDataSource tenterà di crearne uno, ma avrà esito negativo a causa della mancanza di un costruttore senza parametri predefinito. Per altre informazioni su questo problema, vedere i due post seguenti ASP.NET Forum: Aggiornamento di ObjectDataSources con Strongly-Typed DataSet e Problema con ObjectDataSource e Strongly-Typed DataSet.

Successivamente, sia in AddProduct e UpdateProduct, il codice crea un'istanza ProductsRow e lo popola con i valori appena passati. Quando si assegnano valori a DataColumns di un oggetto DataRow possono verificarsi vari controlli di convalida a livello di campo. Pertanto, l'inserimento manuale dei valori passati in un DataRow consente di garantire la validità dei dati passati al metodo BLL. Purtroppo le classi DataRow fortemente tipizzate generate da Visual Studio non usano tipi nullable. Invece, per indicare che un determinato DataColumn in un DataRow deve corrispondere a un NULL valore del database che è necessario usare il SetColumnNameNull() metodo .

Nel UpdateProduct primo caricamento nel prodotto per aggiornare l'uso di GetProductByProductID(productID). Anche se questo potrebbe sembrare un viaggio non necessario nel database, questo viaggio aggiuntivo sarà utile nelle esercitazioni future che esplorano la concorrenza ottimistica. La concorrenza ottimistica è una tecnica per garantire che due utenti che lavorano simultaneamente sugli stessi dati non sovrascrivono accidentalmente le modifiche di un altro. Il recupero dell'intero record semplifica anche la creazione di metodi di aggiornamento nel BLL che modificano solo un subset delle colonne di DataRow. Quando si esplora la SuppliersBLL classe verrà visualizzato un esempio simile.

Si noti infine che la ProductsBLL classe ha l'attributo DataObject applicato a esso (la [System.ComponentModel.DataObject] sintassi prima dell'istruzione di classe nella parte superiore del file) e i metodi hanno attributi DataObjectMethodAttribute. L'attributo DataObject contrassegna la classe come oggetto adatto per l'associazione a un controllo ObjectDataSource, mentre indica DataObjectMethodAttribute lo scopo del metodo. Come vedremo nelle esercitazioni future, ASP.NET 2.0 ObjectDataSource semplifica l'accesso dichiarativo ai dati da una classe. Per filtrare l'elenco delle classi possibili da associare nella procedura guidata di ObjectDataSource, per impostazione predefinita solo le classi contrassegnate come DataObjects vengono visualizzate nell'elenco a discesa della procedura guidata. La ProductsBLL classe funzionerà anche senza questi attributi, ma aggiungendoli semplifica l'uso nella procedura guidata di ObjectDataSource.

Aggiunta delle altre classi

Con il completamento della ProductsBLL classe, è comunque necessario aggiungere le classi per lavorare con categorie, fornitori e dipendenti. Per creare le classi e i metodi seguenti, usare i concetti dell'esempio precedente:

  • CategoriesBLL.cs

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

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

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

Il metodo che vale la pena notare è il SuppliersBLL metodo della UpdateSupplierAddress classe. Questo metodo fornisce un'interfaccia per l'aggiornamento solo delle informazioni sull'indirizzo del fornitore. Internamente, questo metodo legge nell'oggetto per l'oggetto SupplierDataRow specificato supplierID (usando GetSupplierBySupplierID), imposta le relative proprietà correlate all'indirizzo e quindi chiama il SupplierDataTablemetodo nel metodo 's Update . Il UpdateSupplierAddress metodo segue:

<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

Vedere il download di questo articolo per l'implementazione completa delle classi BLL.

Passaggio 2: Accesso ai set di dati tipiti tramite le classi BLL

Nella prima esercitazione sono stati illustrati esempi di utilizzo diretto con Il set di dati tipizzato a livello di codice, ma con l'aggiunta delle classi BLL, il livello di presentazione deve funzionare rispetto al BLL. Nell'esempio AllProducts.aspx della prima esercitazione, l'oggetto ProductsTableAdapter è stato usato per associare l'elenco di prodotti a un oggetto GridView, come illustrato nel codice seguente:

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

Per usare le nuove classi BLL, tutto ciò che deve essere modificato è la prima riga di codice semplicemente sostituire l'oggetto ProductsTableAdapter con un ProductBLL oggetto:

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

Le classi BLL possono anche essere accessibili in modo dichiarativo (come può il DataSet tipizzato) usando ObjectDataSource. Verranno descritti in dettaglio ObjectDataSource nelle esercitazioni seguenti.

L'elenco dei prodotti viene visualizzato in un controllo GridView

Figura 3: l'elenco dei prodotti viene visualizzato in Un controllo GridView (Fare clic per visualizzare l'immagine a dimensioni complete)

Passaggio 3: Aggiunta della convalida Field-Level alle classi DataRow

La convalida a livello di campo è verifica che riguarda i valori delle proprietà degli oggetti business durante l'inserimento o l'aggiornamento. Alcune regole di convalida a livello di campo per i prodotti includono:

  • Il ProductName campo deve essere di 40 caratteri o minore in lunghezza
  • Il QuantityPerUnit campo deve essere di 20 caratteri o minore in lunghezza
  • I ProductIDcampi , ProductNamee Discontinued sono obbligatori, ma tutti gli altri campi sono facoltativi
  • I UnitPricecampi , , UnitsInStockUnitsOnOrdere ReorderLevel devono essere maggiori o uguali a zero

Queste regole possono e devono essere espresse a livello di database. Il limite di caratteri nei ProductName campi e QuantityPerUnit viene acquisito rispettivamente dai tipi di dati di tali colonne nella Products tabella (nvarchar(40) e nvarchar(20), ). Se i campi sono obbligatori e facoltativi sono espressi da se la colonna della tabella di database consente NULL s. Esistono quattro vincoli di controllo che garantiscono che solo i valori maggiori o uguali a zero possano renderli nelle UnitPricecolonne , , UnitsInStockUnitsOnOrdero ReorderLevel .

Oltre a applicare queste regole al database, devono essere applicate anche a livello di DataSet. In effetti, la lunghezza del campo e se un valore è obbligatorio o facoltativo sono già acquisiti per il set di DataTable di DataColumns. Per visualizzare automaticamente la convalida a livello di campo esistente, passare all'Designer DataSet, selezionare un campo da una delle tabelle dati e quindi passare alla Finestra Proprietà. Come illustrato nella figura 4, DataColumn QuantityPerUnit in ProductsDataTable ha una lunghezza massima di 20 caratteri e consente NULL valori. Se si tenta di impostare la ProductsDataRowproprietà 's QuantityPerUnit su un valore di stringa più di 20 caratteri, verrà generato un ArgumentException valore di stringa.

DataColumn fornisce la convalida di base Field-Level

Figura 4: DataColumn fornisce la convalida di base Field-Level (fare clic per visualizzare l'immagine full-size)

Sfortunatamente, non è possibile specificare i controlli dei limiti, ad esempio il valore deve essere maggiore o uguale a zero, attraverso la UnitPrice Finestra Proprietà. Per fornire questo tipo di convalida a livello di campo, è necessario creare un gestore eventi per l'evento ColumnChanging di DataTable. Come accennato nell'esercitazione precedente, è possibile estendere gli oggetti DataSet, DataTables e DataRow creati da DataSet tipizzati tramite l'uso di classi parziali. Usando questa tecnica è possibile creare un ColumnChanging gestore eventi per la ProductsDataTable classe. Iniziare creando una classe nella App_Code cartella denominata ProductsDataTable.ColumnChanging.vb.

Aggiungere una nuova classe alla cartella App_Code

Figura 5: Aggiungere una nuova classe alla cartella (fare clic per visualizzare l'immagineApp_Code full-size)

Creare quindi un gestore eventi per l'evento ColumnChanging che garantisce che i UnitPricevalori di colonna , , UnitsInStockUnitsOnOrdere ReorderLevel (se non NULL) siano maggiori o uguali a zero. Se una colonna di questo tipo non è compreso nell'intervallo, generare un ArgumentExceptionoggetto .

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

Passaggio 4: Aggiunta di regole business personalizzate alle classi BLL

Oltre alla convalida a livello di campo, è possibile che siano presenti regole business personalizzate di alto livello che coinvolgono entità o concetti diversi non esprimebili a livello di colonna singola, ad esempio:

  • Se un prodotto viene interrotto, non è possibile aggiornarlo UnitPrice
  • Il paese di residenza di un dipendente deve essere lo stesso del paese di residenza del responsabile
  • Un prodotto non può essere interrotto se è l'unico prodotto fornito dal fornitore

Le classi BLL devono contenere controlli per garantire l'conformità alle regole aziendali dell'applicazione. Questi controlli possono essere aggiunti direttamente ai metodi a cui si applicano.

Si supponga che le nostre regole aziendali determinino che un prodotto non poteva essere contrassegnato come interrotto se fosse l'unico prodotto di un determinato fornitore. Vale a dire, se il prodotto X era l'unico prodotto acquistato dal fornitore Y, non potevamo contrassegnare X come interrotto; se, tuttavia, il fornitore Y ci ha fornito tre prodotti, A, B e C, allora potremmo contrassegnare qualsiasi e tutti questi come interrotti. Una regola di business strana, ma le regole aziendali e il senso comune non sono sempre allineati!

Per applicare questa regola aziendale nel UpdateProducts metodo che si inizierà controllando se Discontinued è stato impostato su True e, in caso affermativo, si chiamerà GetProductsBySupplierID per determinare il numero di prodotti acquistati dal fornitore di questo prodotto. Se viene acquistato un solo prodotto da questo fornitore, viene generato un ApplicationExceptionoggetto .

<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

Risposta agli errori di convalida nel livello presentazione

Quando si chiama il BLL dal livello di presentazione, è possibile decidere se tentare di gestire eventuali eccezioni che potrebbero essere generate o lasciarle bollere fino a ASP.NET (che genererà l'evento HttpApplication's).Error Per gestire un'eccezione quando si usa BLL a livello di codice, è possibile usare un oggetto Try... Blocco Catch , come illustrato nell'esempio seguente:

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

Come si vedrà nelle esercitazioni future, la gestione delle eccezioni che si spostano dall'BLL quando si usa un controllo Web dati per l'inserimento, l'aggiornamento o l'eliminazione dei dati possono essere gestiti direttamente in un gestore eventi anziché dover eseguire il wrapping del codice in Try...Catch blocchi.

Riepilogo

Un'applicazione ben progettata viene creata in livelli distinti, ognuno dei quali incapsula un ruolo specifico. Nella prima esercitazione di questa serie di articoli è stato creato un livello di accesso ai dati usando Tipid DataSets; in questa esercitazione è stato creato un livello di logica di business come una serie di classi nella cartella dell'applicazione App_Code che chiamano il servizio nel servizio di distribuzione. BLL implementa la logica a livello di campo e di business per l'applicazione. Oltre a creare un BLL separato, come illustrato in questa esercitazione, un'altra opzione consiste nell'estendere i metodi di TableAdapters tramite l'uso di classi parziali. Tuttavia, l'uso di questa tecnica non consente di eseguire l'override dei metodi esistenti né di separare il servizio DAL e il BLL in modo pulito come l'approccio adottato in questo articolo.

Con il completamento di DAL e BLL, è possibile iniziare a usare il livello di presentazione. Nell'esercitazione successiva si esaminerà una breve deviazione dagli argomenti di accesso ai dati e si definirà un layout di pagina coerente da usare in tutte le esercitazioni.

Programmazione felice!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato con le tecnologie Microsoft Web dal 1998. Scott lavora come consulente indipendente, allenatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2,0 in 24 Ore. Può essere raggiunto a mitchell@4GuysFromRolla.com. o tramite il suo blog, che può essere trovato in http://ScottOnWriting.NET.

Grazie speciali

Questa serie di esercitazioni è stata esaminata da molti revisori utili. I revisori principali per questa esercitazione sono stati Liz Shulok, Dennis Patterson, Carlos Santos e Hilton Giesenow. Interessati a esaminare i prossimi articoli MSDN? In tal caso, lasciami una riga in mitchell@4GuysFromRolla.com.