Condividi tramite


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

di Scott Mitchell

Scaricare il PDF

In questo tutorial vedremo come centralizzare le regole aziendali in un Business Logic Layer (BLL) che funge da intermediario per lo scambio di dati tra il livello di presentazione e il DAL.

Introduzione

Il livello di accesso ai dati (DAL) creato nella prima esercitazione separa in modo netto la logica di accesso ai dati dalla logica di presentazione. Tuttavia, sebbene il DAL separi in modo netto i dettagli di accesso ai dati dal livello di presentazione, non applica alcuna regola aziendale che potrebbe essere applicata. Ad esempio, per la nostra applicazione potremmo voler impedire la CategoryID modifica dei SupplierID campi o Products della tabella quando il Discontinued campo è impostato su 1, oppure potremmo voler applicare le regole di anzianità, vietando le situazioni in cui un dipendente è gestito da qualcuno che è stato assunto dopo di lui. Un altro scenario comune è l'autorizzazione, forse solo gli utenti con un particolare ruolo possono eliminare i prodotti o possono modificare il UnitPrice valore.

In questo tutorial vedremo come centralizzare queste regole di business in un Business Logic Layer (BLL) 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 questi tutorial implementeremo il BLL come una serie di classi nella nostra App_Code cartella per semplificare la struttura del progetto. Nella Figura 1 vengono illustrate le relazioni architetturali tra il livello di presentazione, BLL e DAL.

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

Figura 1: Il BLL separa il livello di presentazione dal livello di accesso ai dati e impone regole di 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 nostro BLL sarà composto da quattro classi, una per ogni TableAdapter nel DAL; ognuna di queste classi BLL disporrà di metodi per il recupero, l'inserimento, l'aggiornamento e l'eliminazione dal rispettivo TableAdapter nel DAL, applicando le regole di business appropriate.

Per separare in modo più netto le classi correlate a DAL e BLL, creiamo due sottocartelle nella App_Code cartella DAL e BLL. È sufficiente fare clic con il pulsante destro del App_Code mouse sulla cartella in Esplora soluzioni e scegliere Nuova cartella. Dopo aver creato queste due cartelle, spostare il DataSet tipizzato creato nella prima esercitazione nella DAL sottocartella.

Quindi, crea i quattro file di BLL classe BLL nella sottocartella. Per fare ciò, fai clic con il pulsante destro del BLL mouse sulla sottocartella, scegli Aggiungi un nuovo elemento e scegli il modello di classe. Assegna un nome alle quattro classi ProductsBLL, CategoriesBLL, SuppliersBLL, e EmployeesBLL.

Aggiungere quattro nuove classi alla cartella App_Code

Figura 2: Aggiunta di quattro nuove classi alla App_Code cartella

Successivamente, aggiungiamo metodi a ciascuna delle classi per eseguire semplicemente il wrapping dei metodi definiti per i TableAdapter dalla prima esercitazione. Per ora, questi metodi chiameranno direttamente il DAL; Torneremo più avanti per aggiungere la logica di business necessaria.

Annotazioni

Se si utilizza Visual Studio Standard Edition o versione successiva, ovvero non si utilizza Visual Web Developer, è possibile progettare le classi in modo visivo utilizzando Progettazione classi. Per altre informazioni su questa nuova funzionalità di Visual Studio, vedere il blog di Progettazione classi .

Per la ProductsBLL classe dobbiamo 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 della 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 utilizzando 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 utilizzando i valori passati; restituisce True se è stata aggiornata esattamente una riga, False altrimenti
  • 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, GetProductsByCategoryID, e GetProductBySuppliersID sono abbastanza semplici in quanto chiamano semplicemente il DAL. Anche se in alcuni scenari potrebbero essere presenti regole di business che devono essere implementate a questo livello (ad esempio regole di autorizzazione basate sull'utente attualmente connesso o sul ruolo a cui l'utente appartiene), lasceremo semplicemente questi metodi as-is. Per questi metodi, quindi, il BLL funge semplicemente da proxy attraverso il quale il livello di presentazione accede ai dati sottostanti dal livello di accesso ai dati.

I AddProduct metodi and UpdateProduct prendono entrambi come parametri i valori per i vari campi del prodotto e aggiungono un nuovo prodotto o ne aggiornano uno esistente, rispettivamente. Poiché molte delle colonne della Product tabella possono accettare NULL valori (CategoryID, SupplierID, e UnitPrice, per citarne alcuni), i parametri di input per AddProduct e UpdateProduct che eseguono il mapping a tali colonne utilizzano tipi nullable. I tipi nullable sono una novità di .NET 2.0 e forniscono una tecnica per indicare se un tipo di valore deve essere Nothing. Per ulteriori informazioni, fare riferimento al post del blog di Paul VickThe Truth About Nullable Types and VB e alla 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, poiché l'operazione potrebbe non generare una riga interessata. Ad esempio, se lo sviluppatore della pagina chiama DeleteProduct il passaggio di a ProductID per un prodotto inesistente, l'istruzione DELETE emessa nel database non avrà alcun effetto e pertanto il DeleteProduct metodo restituirà False.

Si noti che quando si aggiunge un nuovo prodotto o si aggiorna uno esistente, i valori dei campi del prodotto nuovo o modificato vengono presi in considerazione come un elenco di scalari, anziché accettare un'istanza ProductsRow . Questo approccio è stato scelto perché la ProductsRow classe deriva dalla classe ADO.NET DataRow , che non dispone di un costruttore senza parametri predefinito. Per creare una nuova ProductsRow istanza, dobbiamo prima creare un'istanza ProductsDataTable e poi invocare il suo NewProductRow() metodo (cosa che facciamo in AddProduct). Questa lacuna si manifesta quando si passa all'inserimento e all'aggiornamento dei prodotti utilizzando 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 una, ma non riuscirà a causa della mancanza di un costruttore senza parametri predefinito. Per ulteriori informazioni su questo problema, fare riferimento ai seguenti due post del forum ASP.NET: Aggiornamento di ObjectDataSources con Strongly-Typed DataSet e Problema con ObjectDataSource e Strongly-Typed DataSet.

Successivamente, in both e AddProductUpdateProduct, il codice crea un'istanza ProductsRow e la popola con i valori appena passati. Quando si assegnano valori a DataColumns di un 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. Sfortunatamente, le classi DataRow fortemente tipizzate generate da Visual Studio non utilizzano tipi nullable. Piuttosto, per indicare che un particolare DataColumn in un DataRow deve corrispondere a un NULL valore di database, è necessario utilizzare il SetColumnNameNull() metodo.

In UpdateProduct prima cosa carichiamo il prodotto per l'aggiornamento utilizzando GetProductByProductID(productID). Anche se questo può sembrare un viaggio non necessario nel database, questo viaggio extra si rivelerà utile nelle esercitazioni future che esplorano la concorrenza ottimistica. La concorrenza ottimistica è una tecnica per garantire che due utenti che lavorano contemporaneamente sugli stessi dati non sovrascrivano accidentalmente le modifiche dell'altro. L'acquisizione dell'intero record semplifica anche la creazione di metodi di aggiornamento nel BLL che modificano solo un sottoinsieme delle colonne di DataRow. Quando esploreremo la SuppliersBLL classe, vedremo un esempio del genere.

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

Aggiunta delle altre classi

Una volta completata la ProductsBLL classe, dobbiamo ancora aggiungere le classi per lavorare con categorie, fornitori e dipendenti. Prenditi un momento per creare le classi e i metodi seguenti utilizzando 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)

L'unico metodo degno di SuppliersBLL nota è il metodo della UpdateSupplierAddress classe. Questo metodo fornisce un'interfaccia per aggiornare solo le informazioni sull'indirizzo del fornitore. Internamente, questo metodo legge l'oggetto per l'oggetto SupplierDataRow specificato supplierID (using GetSupplierBySupplierID), imposta le proprietà correlate all'indirizzo e quindi chiama il SupplierDataTablemetodo 's Update . Il UpdateSupplierAddress metodo è il seguente:

<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

Fare riferimento al download di questo articolo per l'implementazione completa delle classi BLL.

Passaggio 2: Accesso ai DataSet tipizzati tramite le classi BLL

Nella prima esercitazione sono stati illustrati esempi di utilizzo diretto del DataSet tipizzato a livello di codice, ma con l'aggiunta delle classi BLL, il livello di presentazione dovrebbe invece funzionare contro il BLL. Nell'esempio AllProducts.aspx della prima esercitazione, è ProductsTableAdapter stato usato per associare l'elenco di prodotti a un controllo GridView, come illustrato nel codice seguente:

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

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

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

È inoltre possibile accedere alle classi BLL in modo dichiarativo (come il DataSet tipizzato) utilizzando ObjectDataSource. Verrà illustrato in modo più dettagliato ObjectDataSource nelle esercitazioni seguenti.

L'elenco dei prodotti viene visualizzato in un GridView

Figura 3: L'elenco dei prodotti viene visualizzato in un GridView (fare clic per visualizzare l'immagine a dimensione intera)

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

La convalida a livello di campo è un controllo relativo ai 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 avere una lunghezza massima di 40 caratteri
  • Il QuantityPerUnit campo deve avere una lunghezza massima di 20 caratteri
  • I ProductIDcampi , ProductName, e Discontinued sono obbligatori, ma tutti gli altri campi sono facoltativi
  • I UnitPricecampi , UnitsInStock, UnitsOnOrder, e ReorderLevel devono essere maggiori o uguali a zero

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

Oltre a far rispettare queste regole nel database, dovrebbero 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 DataColumns di ogni DataTable. Per visualizzare la convalida a livello di campo esistente fornita automaticamente, accedere a DataSet Designer, selezionare un campo da una delle DataTable e quindi passare alla finestra Proprietà. Come illustrato nella Figura 4, l'oggetto QuantityPerUnit DataColumn ha ProductsDataTable una lunghezza massima di 20 caratteri e consente NULL valori. Se tentiamo di impostare la ProductsDataRowproprietà 's QuantityPerUnit su un valore stringa più lungo di 20 caratteri, verrà lanciato un an ArgumentException .

DataColumn fornisce la convalida di base Field-Level

Figura 4: DataColumn fornisce la convalida di base dell'Field-Level (fare clic per visualizzare l'immagine a dimensione intera)

Sfortunatamente, non possiamo specificare i controlli dei limiti, ad esempio il UnitPrice valore deve essere maggiore o uguale a zero, tramite la 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, gli oggetti DataSet, DataTable e DataRow creati dal DataSet tipizzato possono essere estesi tramite l'utilizzo di classi parziali. Utilizzando questa tecnica possiamo creare un ColumnChanging gestore di eventi per la ProductsDataTable classe. Inizia creando una classe nella App_Code cartella denominata ProductsDataTable.ColumnChanging.vb.

Aggiungere una nuova classe alla cartella App_Code

Figura 5: Aggiunta di una nuova classe alla cartella (App_Code a dimensione intera)

Creare quindi un gestore eventi per l'evento ColumnChanging che garantisca che i valori , UnitPrice, UnitsInStocke UnitsOnOrder di ReorderLevelcolonna (se non NULL) siano maggiori o uguali a zero. Se una colonna di questo tipo non è compresa nell'intervallo, lanciare un ArgumentExceptionfile .

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 aziendali personalizzate alle classi del BLL

Oltre alla convalida a livello di campo, possono essere presenti regole aziendali personalizzate di alto livello che coinvolgono diverse entità o concetti non esprimibili a livello di singola colonna, ad esempio:

  • Se un prodotto è fuori produzione, non può UnitPrice essere aggiornato
  • Il paese di residenza di un dipendente deve essere lo stesso del paese di residenza del suo manager
  • Un prodotto non può essere interrotto se è l'unico prodotto fornito dal fornitore

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

Immaginate che le nostre regole aziendali impongano che un prodotto non possa essere contrassegnato come fuori produzione se fosse l'unico prodotto di un determinato fornitore. Ovvero, se il prodotto X fosse l'unico prodotto che abbiamo acquistato dal fornitore Y, non potremmo contrassegnare X come fuori produzione; se, tuttavia, il fornitore Y ci forniva tre prodotti, A, B e C, potevamo contrassegnare tutti questi prodotti come fuori produzione. Una strana regola aziendale, ma le regole aziendali e il buon senso non sono sempre allineati!

Per applicare questa regola aziendale nel UpdateProducts metodo, iniziamo controllando se Discontinued è stato impostato su True e, in caso affermativo, chiamiamo GetProductsBySupplierID per determinare quanti prodotti abbiamo acquistato dal fornitore di questo prodotto. Se viene acquistato un solo prodotto da questo fornitore, lanciamo un file .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

Risposta agli errori di convalida nel livello di presentazione

Quando si chiama il BLL dal livello di presentazione, è possibile decidere se tentare di gestire eventuali eccezioni che potrebbero essere generate o lasciarle propagarsi fino a ASP.NET (che genererà l'evento HttpApplication's Error ). Per gestire un'eccezione quando si lavora con il BLL a livello di codice, è possibile utilizzare un metodo Try... 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 vedremo nelle esercitazioni future, la gestione delle eccezioni che si propagano dal BLL quando si utilizza un controllo Web dati per l'inserimento, l'aggiornamento o l'eliminazione di dati può essere gestita direttamente in un gestore eventi anziché dover eseguire il wrapping del codice in Try...Catch blocchi.

Riassunto

Un'applicazione ben progettata è realizzata in livelli distinti, ognuno dei quali racchiude un ruolo particolare. Nel primo tutorial di questa serie di articoli abbiamo creato un livello di accesso ai dati utilizzando DataSet tipizzati; in questo tutorial abbiamo costruito un livello di logica di business come una serie di classi nella cartella dell'applicazione App_Code che chiamano il nostro DAL. Il BLL implementa la logica a livello di campo e a livello di business per la nostra applicazione. Oltre a creare un BLL separato, come abbiamo fatto in questa esercitazione, un'altra opzione consiste nell'estendere i metodi di TableAdapter tramite l'uso di classi parziali. Tuttavia, l'utilizzo di questa tecnica non ci consente di sovrascrivere i metodi esistenti né separa il nostro DAL e il nostro BLL in modo così netto come l'approccio che abbiamo adottato in questo articolo.

Con il DAL e il BLL completi, siamo pronti per iniziare il nostro livello di presentazione. Nell'esercitazione successiva verrà fatta una breve deviazione dagli argomenti relativi all'accesso ai dati e verrà definito un layout di pagina coerente da utilizzare in tutte le esercitazioni.

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto a mitchell@4GuysFromRolla.com.

Grazie speciale a

Questa serie di esercitazioni è stata esaminata da molti revisori competenti. I revisori principali di questo tutorial sono stati Liz Shulok, Dennis Patterson, Carlos Santos e Hilton Giesenow. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, mandami un messaggio a mitchell@4GuysFromRolla.com.