Condividi tramite


Creazione di un livello per la logica di business (C#)

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

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.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

[System.ComponentModel.DataObject]
public class ProductsBLL
{
    private ProductsTableAdapter _productsAdapter = null;
    protected ProductsTableAdapter Adapter
    {
        get {
            if (_productsAdapter == null)
                _productsAdapter = new ProductsTableAdapter();

            return _productsAdapter;
        }
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
    public Northwind.ProductsDataTable GetProducts()
    {
        return Adapter.GetProducts();
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductByProductID(int productID)
    {
        return Adapter.GetProductByProductID(productID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
    {
        return Adapter.GetProductsByCategoryID(categoryID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsBySupplierID(int supplierID)
    {
        return Adapter.GetProductsBySupplierID(supplierID);
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Insert, true)]
    public bool AddProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice,  short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued)
    {
        // Create a new ProductRow instance
        Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
        Northwind.ProductsRow product = products.NewProductsRow();

        product.ProductName = productName;
        if (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Add the new product
        products.AddProductsRow(product);
        int rowsAffected = Adapter.Update(products);

        // Return true if precisely one row was inserted,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Update, true)]
    public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
    {
        Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
        if (products.Count == 0)
            // no matching record found, return false
            return false;

        Northwind.ProductsRow product = products[0];

        product.ProductName = productName;
        if (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Update the product record
        int rowsAffected = Adapter.Update(product);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteProduct(int productID)
    {
        int rowsAffected = Adapter.Delete(productID);

        // Return true if precisely one row was deleted,
        // otherwise false
        return rowsAffected == 1;
    }
}

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 null. In C# è possibile contrassegnare un tipo di valore come tipo nullable aggiungendo ? dopo il tipo (ad esempio int? x;). Per altre informazioni, vedere la sezione Tipi nullable nella Guida per programmatori C# .

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 bool UpdateSupplierAddress
    (int supplierID, string address, string city, string country)
{
    Northwind.SuppliersDataTable suppliers =
        Adapter.GetSupplierBySupplierID(supplierID);
    if (suppliers.Count == 0)
        // no matching record found, return false
        return false;
    else
    {
        Northwind.SuppliersRow supplier = suppliers[0];

        if (address == null) supplier.SetAddressNull();
          else supplier.Address = address;
        if (city == null) supplier.SetCityNull();
          else supplier.City = city;
        if (country == null) supplier.SetCountryNull();
          else supplier.Country = country;

        // Update the supplier Address-related information
        int rowsAffected = Adapter.Update(supplier);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }
}

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:

ProductsTableAdapter productsAdapter = 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:

ProductsBLL productLogic = 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)). Indica se i campi sono obbligatori e facoltativi sono espressi da se la colonna della tabella di database è consentita NULL . Esistono quattro vincoli CHECK che assicurano che solo i valori maggiori o uguali a zero possano renderli nelle UnitPricecolonne , UnitsInStock, UnitsOnOrdero ReorderLevel .

Oltre a applicare queste regole al database, è necessario applicare anche a livello di DataSet. Infatti, 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 alla Designer DataSet, selezionare un campo da una delle tabelle DataTable 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à di su QuantityPerUnit un valore stringa più lungo di 20 caratteri, verrà generata un'eccezione ArgumentException .

DataColumn fornisce la convalida Field-Level di base

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

Sfortunatamente, non è possibile specificare controlli dei limiti, ad esempio il UnitPrice valore deve essere maggiore o uguale a zero, tramite il 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 . Per iniziare, creare una classe nella App_Code cartella denominata ProductsDataTable.ColumnChanging.cs.

Aggiungere una nuova classe alla cartella App_Code

Figura 5: Aggiungere una nuova classe alla App_Code cartella (fare clic per visualizzare l'immagine a dimensione intera)

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

ProductsDataTable.ColumnChanging.cs

public partial class Northwind
{
    public partial class ProductsDataTable
    {
        public override void BeginInit()
         {
            this.ColumnChanging += ValidateColumn;
         }

         void ValidateColumn(object sender,
           DataColumnChangeEventArgs e)
         {
            if(e.Column.Equals(this.UnitPriceColumn))
            {
               if(!Convert.IsDBNull(e.ProposedValue) &&
                  (decimal)e.ProposedValue < 0)
               {
                  throw new ArgumentException(
                      "UnitPrice cannot be less than zero", "UnitPrice");
               }
            }
            else if (e.Column.Equals(this.UnitsInStockColumn) ||
                     e.Column.Equals(this.UnitsOnOrderColumn) ||
                     e.Column.Equals(this.ReorderLevelColumn))
            {
                if (!Convert.IsDBNull(e.ProposedValue) &&
                    (short)e.ProposedValue < 0)
                {
                    throw new ArgumentException(string.Format(
                        "{0} cannot be less than zero", e.Column.ColumnName),
                        e.Column.ColumnName);
                }
            }
         }
    }
}

Passaggio 4: Aggiunta di regole business personalizzate alle classi BLL

Oltre alla convalida a livello di campo, possono essere 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 non è più disponibile, UnitPrice non può essere aggiornato
  • Il paese di residenza di un dipendente deve essere uguale al 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 la conformità alle regole business dell'applicazione. Questi controlli possono essere aggiunti direttamente ai metodi a cui si applicano.

Si supponga che le nostre regole di business impongono che un prodotto non possa essere contrassegnato come interrotto se fosse l'unico prodotto di un determinato fornitore. Cioè, 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 sospesi. Una regola di business strana, ma le regole di business e il buon senso non sono sempre allineate!

Per applicare questa regola di business nel UpdateProducts metodo si inizierà controllando se Discontinued è stato impostato su true e, in tal caso, 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 generata un'eccezione ApplicationException.

public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
    string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
    short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
{
    Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;

    Northwind.ProductsRow product = products[0];

    // Business rule check - cannot discontinue
    // a product that is supplied by only
    // one supplier
    if (discontinued)
    {
        // Get the products we buy from this supplier
        Northwind.ProductsDataTable productsBySupplier =
            Adapter.GetProductsBySupplierID(product.SupplierID);

        if (productsBySupplier.Count == 1)
            // this is the only product we buy from this supplier
            throw new ApplicationException(
                "You cannot mark a product as discontinued if it is the only
                  product purchased from a supplier");
    }

    product.ProductName = productName;
    if (supplierID == null) product.SetSupplierIDNull();
      else product.SupplierID = supplierID.Value;
    if (categoryID == null) product.SetCategoryIDNull();
      else product.CategoryID = categoryID.Value;
    if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
      else product.QuantityPerUnit = quantityPerUnit;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null) product.SetUnitsInStockNull();
      else product.UnitsInStock = unitsInStock.Value;
    if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
      else product.UnitsOnOrder = unitsOnOrder.Value;
    if (reorderLevel == null) product.SetReorderLevelNull();
      else product.ReorderLevel = reorderLevel.Value;
    product.Discontinued = discontinued;

    // Update the product record
    int rowsAffected = Adapter.Update(product);

    // Return true if precisely one row was updated,
    // otherwise false
    return rowsAffected == 1;
}

Risposta agli errori di convalida nel livello presentazione

Quando si chiama il BLL dal livello presentazione, è possibile decidere se tentare di gestire eventuali eccezioni che potrebbero essere generate o lasciare che vengano visualizzate fino a ASP.NET (che genererà l'evento HttpApplication).Error Per gestire un'eccezione quando si usa BLL a livello di codice, è possibile usare un tentativo... blocco catch , come illustrato nell'esempio seguente:

ProductsBLL productLogic = new ProductsBLL();

// Update information for ProductID 1
try
{
    // This will fail since we are attempting to use a
    // UnitPrice value less than 0.
    productLogic.UpdateProduct(
        "Scott s Tea", 1, 1, null, -14m, 10, null, null, false, 1);
}
catch (ArgumentException ae)
{
    Response.Write("There was a problem: " + ae.Message);
}

Come si vedrà nelle esercitazioni future, la gestione delle eccezioni che si eseguono dal BLL quando si usa un controllo Web dati per l'inserimento, l'aggiornamento o l'eliminazione dei dati può essere gestita 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 particolare ruolo. Nella prima esercitazione di questa serie di articoli è stato creato un livello di accesso ai dati usando dataset tipizzato; In questa esercitazione è stato creato un livello di logica di business come una serie di classi nella cartella dell'applicazione App_Code che chiamano dal dal. Il BLL implementa la logica a livello di campo e a livello di business per l'applicazione. Oltre a creare un BLL separato, come è stato fatto 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 dal e BLL come l'approccio adottato in questo articolo.

Dopo aver completato DAL e BLL, è possibile iniziare a usare il livello di presentazione. Nell'esercitazione successiva verrà eseguita una breve deviazione dagli argomenti di accesso ai dati e verrà definito un layout di pagina coerente da usare in tutte le esercitazioni.

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora 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 all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, disponibile all'indirizzo http://ScottOnWriting.NET.

Grazie speciale a

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