ObjectDataSourceの使い道

先日のSilverlightを囲む会#4id:kiyokuraさんとASP.NETのObjectDataSourceの話をしていて、きよくらさんがObjectDataSourceを使わないと言っていたに対して自分がObjectDataSourceはSpring.NETと絡めると効果的ですよなんて話をしたので、そのへんの事を書いてみる。

そもそもObjectDataSourceはどんな物かというと、GridViewなんかのデータバインドに対応したサーバーコントロールに対して、ビジネスオブジェクトのCRUD操作を実行して、その結果をデータソースとして自動的にバインドしてくれるという代物。

イメージ的にはこんな感じ↓
http://sites.google.com/site/coma2n/Home/image-gallary/002.png

まぁ要はコントロールビジネスロジックが直接結び付くのを間に一枚かますことで防いでくれるという事。しかもノンコーディングで。

これだけでもメチャクチャ使えそうな気がするけども、実際にはそうでも無かったりする。ObjectDataSourceのよろしくないところは、関連付けるビジネスオブジェクトを型で指定するため、リフレクションを使って(たぶんポストバックの度)オブジェクトを生成すること。

ビジネスオブジェクトがそれ単体で機能することなど、ほとんどなくて実際にはいろんなオブジェクトの複合体になっていると思う。なのでインスタンス化したビジネスオブジェクトに対して、色々細工をする(プロパティの設定とか)必要があるんだけど、それをするのがObjectDataSourceの場合ちょっと面倒くさい*1

他にも色々ありそうだけど、なんにせよ自由度があまり高くないということ。

で、今回の話はObjectDataSourceがビジネスオブジェクトをインスタンス化する時にちょっと細工をして、Spring.NETのDIコンテナからオブジェクトを取ってきて、それをビジネスオブジェクトとして設定してしまうのだ。

実装

じゃあ、さっそく作ってみる。

今回使用するSpring.NETのバージョンは「1.1」とする。モジュールのダウンロードは以下から

「Webアプリケーションプロジェクト」でプロジェクトを作成して、「Spring.Core」と「Spring.Web」アセンブリを参照設定しておく。

プロジェクト構成

http://sites.google.com/site/coma2n/Home/image-gallary/003.png

まずはエンティティクラスから。以下のようなログインユーザの情報を格納する「LoginUser」というクラスがあるとする。

LoginUser.cs

using System;
using System.Diagnostics;

namespace SpringObjectDataSourceApp {
    /// <summary>
    /// ログインユーザの情報を格納するクラス
    /// </summary>
    [Serializable]
    [DebuggerStepThrough]
    public sealed class LoginUser {
        /// <summary>
        /// ユーザIDを取得、設定します。
        /// </summary>
        public string UserId {
            get;
            set;
        }
        /// <summary>
        /// ユーザ名を取得、設定します。
        /// </summary>
        public string UserName {
            get;
            set;
        }
        /// <summary>
        /// パスワードを取得、設定します。
        /// </summary>
        public string Password {
            get;
            set;
        }
    }
}

まぁ、説明の必要はないね。

で、次はこのログインユーザの情報を管理するクラスとして「LoginUserManager」というクラスがあるとする。

LoginUserManager.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;

namespace SpringObjectDataSourceApp {
    /// <summary>
    /// ログインユーザの情報を管理するクラス
    /// </summary>
    [DataObject]
    public sealed class LoginUserManager {
        /// <summary>
        /// ログインユーザの一覧を検索します。
        /// </summary>
        /// <returns>ログインユーザ情報</returns>
        [DataObjectMethod(DataObjectMethodType.Select)]
        public IEnumerable<LoginUser> FindLoginUsers() {
            return new[]{
                new LoginUser {
                    UserId="john", UserName="ジョン太ゆう", Password="john00"
                },
                new LoginUser {
                    UserId="bob", UserName="ボブサップ", Password="bobobo"
                }
            };
        }
    }
}

ログインユーザの一覧を列挙する「FindLoginUsers」メソッドが定義されているだけ。

あと「DataObjectAttribute」と「DataObjectMethodAttribute」という属性は別にいらないけど、後でちょっと楽になるので付けとく。

とりあえず、このオブジェクトをSpring.NETのDIコンテナに登録するために「App_Data」フォルダ配下に「spring.net」というフォルダを作成して、その中に「objects.config」というオブジェクト定義ファイルを追加する。

~/App_Data/spring.net/objects.config
<?xml version="1.0" encoding="utf-8" ?> 
<objects xmlns="http://www.springframework.net">
    
    <object name="loginUserManager" singleton="true" lazy-init="true"
            type="SpringObjectDataSourceApp.LoginUserManager, SpringObjectDataSourceApp">
        
    </object>
    
</objects>

そして、それを読み込むための設定を「Web.config」に追加しておく。

Web.config

<?xml version="1.0"?>
<configuration>
	<configSections>
		<sectionGroup name="spring">
			<section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/>
		</sectionGroup>
	</configSections>
	<spring>
		<context>
			<resource uri="~/App_Data/spring.net/objects.config"/>
		</context>
	</spring>
	<system.web>
		<httpHandlers>
			<remove verb="*" path="*.asmx"/>
			<add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>
		</httpHandlers>
		<httpModules>
			<add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
		</httpModules>
		<compilation debug="true"/></system.web>
	<system.codedom>
		<compilers>
			<compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
				<providerOption name="CompilerVersion" value="v3.5"/>
				<providerOption name="WarnAsError" value="false"/>
			</compiler>
		</compilers>
	</system.codedom>
</configuration>

「httpHandlers」と「httpModules」にSpring.NETの設定を追加するのを忘れずに。

後は「ObjectDataSource」を継承してSpring.NETからオブジェクトを取り出す「SpringObjectDataSource」というクラスを定義する。

SpringObjectDataSource.cs

using System;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Diagnostics;

using Spring.Context;
using Spring.Context.Support;

namespace SpringObjectDataSourceApp {
    /// <summary>
    /// Spring.NETから取り出したオブジェクトをDataSourceと設定するObjectDataSourceクラス
    /// </summary>
    public sealed class SpringObjectDataSource : ObjectDataSource {

        private IApplicationContext appContext;

        /// <summary>
        /// Springオブジェクト名を取得、設定します。
        /// </summary>
        [Category("Data"), Description("Spring.NET DIコンテナから取り出すオブジェクト名です。")]
        public string ObjectName {
            get;
            set;
        }
        
        /// <summary>
        /// 標準的なコンストラクタ
        /// </summary>
        public SpringObjectDataSource() {
            this.ObjectCreating += SpringObjectDataSource_ObjectCreating;
        }

        /// <summary>
        /// オブジェクトが生成される時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SpringObjectDataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e) {
            if(appContext == null) {
                appContext = WebApplicationContext.Current;
            }
            if(!string.IsNullOrEmpty(this.ObjectName)) {
                e.ObjectInstance = appContext.GetObject(this.ObjectName);
            }
        }
    }
}

「ObjectCreating」イベントをフックして、イベント引数の「ObjectInstance」プロパティにSpring.NETのオブジェクトを突っ込んでいる。

これの使い方は以下のように「GridView」と「SpringObjectDataSource」をフォームに配置して、データソースに関連付ける。

http://sites.google.com/site/coma2n/Home/image-gallary/005.png

データソースの構成で「LoginUserManager」クラスを指定してSELECT操作に「FindLoginUsers」メソッドを関連付けておく。
http://sites.google.com/site/coma2n/Home/image-gallary/006.png

そして、「SpringObjectDataSource」の「ObjectName」プロパティにSpring.NETのオブジェクト名を設定する。
http://sites.google.com/site/coma2n/Home/image-gallary/004.png

これでこのページが表示された時に「SpringObjectDataSource」がDIコンテナにオブジェクトを取りだしにいって、そのオブジェクトに対して各種メソッドを呼び出してくれるようになる。

こうすることで「ObjectDataSource」が生成するビジネスオブジェクトをSpring.NETのDIコンテナから取り出せるようになるので、インスタンス生成に関して自由が効くようになる。

このページのASPXファイル。

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SpringObjectDataSourceApp._Default" %>

<%@ Register Assembly="SpringObjectDataSourceApp" Namespace="SpringObjectDataSourceApp"
    TagPrefix="spring" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">

        <div>
            <asp:GridView runat="server" DataSourceID="springDataSource">
            </asp:GridView>
            <spring:SpringObjectDataSource runat="server" ID="springDataSource" OldValuesParameterFormatString="original_{0}"
                SelectMethod="FindLoginUsers" TypeName="SpringObjectDataSourceApp.LoginUserManager"
                ObjectName="loginUserManager">
            </spring:SpringObjectDataSource>
        </div>

    </form>
</body>
</html>

*1:コードを書かんといけん