モデル バインドと Web フォームを使用するプロジェクトへのビジネス ロジック レイヤーの追加

Tom FitzMacken

このチュートリアル シリーズでは、ASP.NET Web Forms プロジェクトでモデル バインドを使用する基本的な側面について説明します。 モデル バインドを使用すると、データ ソース オブジェクト (ObjectDataSource や SqlDataSource など) を処理するよりも、データの操作が簡単になります。 このシリーズは入門資料から始まり、後のチュートリアルでより高度な概念に移ります。

このチュートリアルでは、ビジネス ロジック レイヤーでモデル バインドを使用する方法について説明します。 OnCallingDataMethods メンバーを設定して、現在のページ以外のオブジェクトを使用してデータ メソッドを呼び出すように指定します。

このチュートリアルは、シリーズの の部分で作成されたプロジェクトに基づいています。

完全なプロジェクトは C# または VB で ダウンロード できます。 ダウンロード可能なコードは、Visual Studio 2012 または Visual Studio 2013で動作します。 Visual Studio 2012 テンプレートを使用します。これは、このチュートリアルで示すVisual Studio 2013 テンプレートとは若干異なります。

作成する内容

モデル バインドを使用すると、Web ページの分離コード ファイルまたは別のビジネス ロジック クラスにデータ操作コードを配置できます。 前のチュートリアルでは、データ操作コードに分離コード ファイルを使用する方法を示しました。 この方法は小規模なサイトに対して機能しますが、コードの繰り返しにつながる可能性があり、大規模なサイトを維持する際の難易度が高くなります。 また、抽象化レイヤーがないため、分離コード ファイルに存在するコードをプログラムでテストすることは非常に困難な場合があります。

データ対話コードを一元化するために、データを操作するためのすべてのロジックを含むビジネス ロジック レイヤーを作成できます。 次に、Web ページからビジネス ロジック レイヤーを呼び出します。 このチュートリアルでは、前のチュートリアルで記述したすべてのコードをビジネス ロジック レイヤーに移動し、そのコードをページから使用する方法について説明します。

このチュートリアルでは、次のことについて説明します。

  1. 分離コード ファイルからビジネス ロジック レイヤーにコードを移動する
  2. ビジネス ロジック レイヤー内のメソッドを呼び出すようにデータ バインド コントロールを変更する

ビジネス ロジック レイヤーを作成する

次に、Web ページから呼び出されるクラスを作成します。 このクラスのメソッドは、前のチュートリアルで使用したメソッドと似ていますが、値プロバイダー属性が含まれています。

まず、 BLL という名前の新しいフォルダーを追加します。

フォルダーの追加

BLL フォルダーに SchoolBL.cs という名前の新しいクラスを作成します。 これには、分離コード ファイルに最初に存在していたすべてのデータ操作が含まれます。 メソッドは分離コード ファイル内のメソッドとほぼ同じですが、いくつかの変更が含まれます。

注意すべき最も重要な変更は、 Page クラスのインスタンス内からコードを実行しなくなったということです。 Page クラスには、 TryUpdateModel メソッドと ModelState プロパティが含まれています。 このコードをビジネス ロジック レイヤーに移動すると、これらのメンバーを呼び出す Page クラスのインスタンスがなくなりました。 この問題を回避するには、TryUpdateModel または ModelState にアクセスする任意のメソッドに ModelMethodContext パラメーターを追加する必要があります。 この ModelMethodContext パラメーターを使用して、TryUpdateModel を呼び出すか、ModelState を取得します。 この新しいパラメーターを考慮するために、Web ページ内の何も変更する必要はありません。

SchoolBL.cs のコードを次のコードに置き換えます。

using System;
using System.Linq;
using ContosoUniversityModelBinding.Models;
using System.Web.ModelBinding;
using System.Web.UI.WebControls;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

namespace ContosoUniversityModelBinding.BLL
{
    public class SchoolBL : IDisposable
    {
        SchoolContext db = new SchoolContext();

        public IQueryable<Student> GetStudents([Control] AcademicYear? displayYear)
        {
            var query = db.Students.Include(s => s.Enrollments.Select(e => e.Course));

            if (displayYear != null)
            {
                query = query.Where(s => s.Year == displayYear);
            }

            return query;
        }

        public void InsertStudent(ModelMethodContext context)
        {
            var item = new Student();

            context.TryUpdateModel(item);
            if (context.ModelState.IsValid)
            {
                db.Students.Add(item);
                db.SaveChanges();
            }
        }

        public void DeleteStudent(int studentID, ModelMethodContext context)
        {
            var item = new Student { StudentID = studentID };
            db.Entry(item).State = EntityState.Deleted;
            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                context.ModelState.AddModelError("",
                    String.Format("Item with id {0} no longer exists in the database.", studentID));
            }
        }

        public void UpdateStudent(int studentID, ModelMethodContext context)
        {
            Student item = null;
            item = db.Students.Find(studentID);
            if (item == null)
            {
                context.ModelState.AddModelError("", String.Format("Item with id {0} was not found", studentID));
                return;
            }

            context.TryUpdateModel(item);
            if (context.ModelState.IsValid)
            {
                db.SaveChanges();
            }
        }

        public IQueryable<Enrollment> GetCourses([QueryString] int? studentID)
        {
            var query = db.Enrollments.Include(e => e.Course)
                .Where(e => e.StudentID == studentID);
            return query;
        }

        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    db.Dispose();
                }
            }
            this.disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

ビジネス ロジック レイヤーからデータを取得するように既存のページを変更する

最後に、Students.aspx、AddStudent.aspx、Courses.aspx の各ページを分離コード ファイル内のクエリの使用からビジネス ロジック レイヤーの使用に変換します。

Students、AddStudent、Courses の分離コード ファイルで、次のクエリ メソッドを削除またはコメントアウトします。

  • studentsGrid_GetData
  • studentsGrid_UpdateItem
  • studentsGrid_DeleteItem
  • addStudentForm_InsertItem
  • coursesGrid_GetData

これで、データ操作に関連する分離コード ファイルにコードが含まれないはずです。

OnCallingDataMethods イベント ハンドラーを使用すると、データ メソッドに使用するオブジェクトを指定できます。 Students.aspx で、そのイベント ハンドラーの値を追加し、データ メソッドの名前をビジネス ロジック クラスのメソッドの名前に変更します。

<asp:GridView runat="server" ID="studentsGrid"
    ItemType="ContosoUniversityModelBinding.Models.Student" DataKeyNames="StudentID"
    SelectMethod="GetStudents"
    UpdateMethod="UpdateStudent" DeleteMethod="DeleteStudent"
    AllowSorting="true" AllowPaging="true" PageSize="4"
    AutoGenerateEditButton="true" AutoGenerateDeleteButton="true"
    AutoGenerateColumns="false" 
    OnCallingDataMethods="studentsGrid_CallingDataMethods">

Students.aspx の分離コード ファイルで、CallingDataMethods イベントのイベント ハンドラーを定義します。 このイベント ハンドラーでは、データ操作のビジネス ロジック クラスを指定します。

protected void studentsGrid_CallingDataMethods(object sender, CallingDataMethodsEventArgs e)
{
    e.DataMethodsObject = new ContosoUniversityModelBinding.BLL.SchoolBL();
}

AddStudent.aspx で、同様の変更を行います。

<asp:FormView runat="server" ID="addStudentForm"
    ItemType="ContosoUniversityModelBinding.Models.Student"
    InsertMethod="InsertStudent" DefaultMode="Insert"
    OnCallingDataMethods="addStudentForm_CallingDataMethods"
    RenderOuterTable="false" OnItemInserted="addStudentForm_ItemInserted">
protected void addStudentForm_CallingDataMethods(object sender, CallingDataMethodsEventArgs e)
{
    e.DataMethodsObject = new ContosoUniversityModelBinding.BLL.SchoolBL();
}

Courses.aspx で、同様の変更を行います。

<asp:GridView runat="server" ID="coursesGrid"
    ItemType="ContosoUniversityModelBinding.Models.Enrollment"
    SelectMethod="GetCourses" AutoGenerateColumns="false"
    OnCallingDataMethods="coursesGrid_CallingDataMethods">
protected void coursesGrid_CallingDataMethods(object sender, CallingDataMethodsEventArgs e)
{
    e.DataMethodsObject = new ContosoUniversityModelBinding.BLL.SchoolBL();
}

アプリケーションを実行し、すべてのページが以前と同じように機能していることに注意してください。 検証ロジックも正しく機能します。

まとめ

このチュートリアルでは、データ アクセス層とビジネス ロジック層を使用するようにアプリケーションを再構築しました。 データ コントロールで、データ操作に現在のページではないオブジェクトを使用することを指定しました。