Visual Studio 2005でのリモーティングに役立つ13のヒントはじめに本稿は「The Baker’s Dozen」シリーズの一記事です。本稿では、リモーティングとリモーティングインターフェイスの概要を解説します。 .NETリモーティングを使用すると、マシンの物理的な境界の外側で実行されているコード(例えば、アプリケーションサーバー上のクラスにアクセスするWebサービスや、クライアント/サーバー環境の中間層にアクセスするクライアントアプリケーションなど)を呼び出して実行することができます。こうしたコードは、コードが置かれているドメインで実行されます。 リモーティングは、別のドメインにあるコードを実行するための理想的なアプローチです。リモーティングインターフェイスを使うことで、現在のプロジェクトの外側にある厳密に型指定されたモデルのコードにアクセスするプログラムを作成することができます。リモーティングソリューションの構築には工夫が必要なので、初めはWebサービスより複雑な感じを受けるかもしれません。そこで、本稿では、簡単なリモーティングプロジェクトを少しずつ説明しながらリモーティングを分かりやすく紹介します。まず、リモーティングアーキテクチャの概要とリモーティングのさまざまな使い方を説明してから、リモーティングの基本的な性質を詳しく説明します。また、Visual Studio 2005におけるリモーティングの新しい機能もいくつか紹介します。最後に、高度なリモーティングを解説しているWebサイトを紹介します。 目的ここ数ヶ月間、私はVisual Studio 2005でのリモーティングに関する記事を書く予定でいたのですが、数週間前からリモーティングに直接関係するさまざまな質問をインターネットで目にしてきました。例えば、インターフェイスの実践的な使用に関する質問や、ジェネリックの価値に関する質問、リモーティングがWebサービスより好ましい理由についての質問などがありました。こうした質問はこの記事を書く刺激になりました。 私は1年半に渡り、「Baker’s Dozen」シリーズで特定の技術や経験のある人を対象とした記事を書いてきました。今回紹介するヒントは次のとおりです。リモートアプリケーションの基礎知識と、インターフェイスの使い方を解説します。
Visual Studio 2005と特に明記されていない限り、これらのヒントの多くはVisual Studio 2003にも当てはまります。本稿の最後に、リモーティングに役立つ参考サイトを紹介します。では、始めましょう。 ヒント1:リモーティングアーキテクチャの概要初めに非常に簡単な例を紹介しましょう。中間層(外部メソッド)にある別のクラスを呼び出す必要があるローカルクラス(クライアントクラス)があるとします。外部メソッドは顧客キーに基づいて顧客データを取得します。クライアントクラスとリモートメソッドは別々のアプリケーション境界にあるので、クライアントクラスは、外部メソッドが取得するデータがプロジェクトファイルなのか参照なのか分かりません。クライアントクラスが外部メソッドを実行する場合、外部メソッド内のコードは外部メソッドが置かれているドメインで実行しなければなりません。 最初は、リフレクションに使って外部メソッドを動的に検索して読み込もうと思うかもしれません。それも可能ですが、このアプローチを使うときは、モジュール、名前、場所などを指定できることが前提となります。 この問題を解決するには、呼び出し側のプロジェクトと外部メソッドの両方が使用できる厳密に型指定されたリモーティングインターフェイスを定義します。TCPリモーティングを使って、アプリケーション境界を越えて外部メソッドにアクセスします。厳密な型指定によるアプローチは効率性に優れ、ランタイムエラーが発生することもほとんどありません。 これを実行するステップをまとめると、次のようになります。
以上5つのステップは、リモーティングで扱う「もの」を表します。リモーティングの「方法」と「理由」については、ヒント3〜7で説明します。 ヒント2:リモーティングの実装オプションリモーティングにはTCPとHTTPの2つの種類があります。これで、.NET Webサービスを追加するときの分散コンピューティングの選択肢は合計で3つになります。これらの実装は組み合わせることも可能で、例えば、クライアント層とアプリケーション層の間でWebサービスを使い、アプリケーション層の内側でリモーティングを使うことができます。 3つの実装オプションを使う一般的な理由は次のとおりです。
Visual Studio 2005ではリモーティングがさらに強化され、アプリケーション境界を越えてバイナリ形式でデータセットをシリアライズできるようになりました(ヒント7を参照)。これにより、データセットとXML文字列の間の変換をプログラミングする必要がなくなり、物理的な境界を越えて渡すデータセットのサイズが縮小されます。 ヒント3:リモーティングインターフェイスを作成するヒント1で説明したように、最初のステップはインターフェイスの作成です。アクセス先となる外部クラスがこのインターフェイスを実装します。クライアントはこのインターフェイスを使って外部クラスにアクセスします。 using System; using System.Text; using System.Data; namespace SimpleInterfaces { public interface ICustomer { DataSet GetCustomer(int AccountID); // we could have more, like a // SaveData method //void SaveData(DataSet CustomerData); } } このインターフェイスの ヒント4:バックエンドリモーティングオブジェクトを作成する次のステップは外部ビジネスオブジェクトの作成です。簡単に言えば、外部クラスは次の3つのことを実行しなければなりません。
ここで問題があります。1つのクラスは1つのクラスと1つのインターフェイスを継承することはできますが、複数のクラスを継承することはできません。この問題を解決するには、 これにより、 using System; using System.Collections.Generic; using System.Text; namespace SimpleBaseBzObject { public class BaseBzObject : System.MarshalByRefObject { // Base business object methods go here } } using System; using System.Collections.Generic; using System.Text; using System.Data; using SimpleBaseBzObject; using SimpleInterfaces; namespace SimpleCustomerBzObject { public class CustomerBzObject : BaseBzObject, ICustomer { public DataSet GetCustomer(int AccountID) { // do something, return a dataset } } } ヒント5:クライアントを作成するインターフェイスとサーバーサイドのビジネスオブジェクトを作成したので、次にこのバックエンドオブジェクトにアクセスするコードを作成する必要があります。 既に説明したように、クライアントには顧客ビジネスオブジェクト( 次のコードは外部クラスにアクセスする方法を示しています。 using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using SimpleInterfaces; ICustomer oRemoteCustomer; Type tCustomer = typeof(ICustomer); ChannelServices.RegisterChannel( new TcpClientChannel()); oRemoteCustomer = (ICustomer)Activator.GetObject(tCustomer, "tcp://localhost:8228/CustomerBzObject"); DataSet DsTemp = oRemoteCustomer.GetCustomer(123); このコードが実行することは次のとおりです。
最も重要なのは、リフレクションを使わずにこれらを実現できたということです。厳密な型指定によるソリューションを構築することができました。図1に、 ヒント6:リモーティングサーバーリスナを作成するもう1つ必要なパーツがあります。ビジネスオブジェクトに関するメソッドコードが置かれているドメインで、リスナを作成する必要があります。このリスナはクライアントコードで指定されたポートをチェックし、リモートアクセス用のビジネスオブジェクトを登録します。 using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; TcpServerChannel Tcps; int nTCPPort = 8228; Tcps = new TcpServerChannel(nTCPPort); ChannelServices.RegisterChannel(Tcps); RemotingConfiguration.RegisterWellKnownServiceType( typeof(SimpleCustomerBzObject.CustomerBzObject), "CustomerBzObject", WellKnownObjectMode.Singleton); このコードは少し難解に見えますが、メソッドの名前は一目瞭然です。ビジネスオブジェクトが置かれているドメインはTCPポートチャネルと ヒント7:Visual Studio 2005でデータセットリモーティングのパフォーマンスを向上させる一般的なデータドリブンアプリケーションでは、ある物理的な境界から別の境界へとデータセットを渡します。データセットはオーバーヘッドが大きいため、よく目にするのがデータセットとXML文字列の間で変換を行って転送のオーバーヘッドを削減する手法です。 うれしいことに、Visual Studio 2005では、データセットをバイナリファイルとしてシリアライズすることでパフォーマンスを向上させることができます。 DsCustomer.RemotingFormat = SerializationFormat.Binary; ヒント8:Windowsサービスを作成するヒント7では、リモートコールを待機する基本的なWindowsフォームのアプリケーションのコードを示しました。実際、ほとんどのインストールシステムではリスナがWindowsサービスとして実行されています。 Windowsサービスは、Visual Studio 2003とVisual Studio 2005のどちらでも作成できます。開発者の中には、このプロセスを「簡単すぎて怖いくらいだ」と表現する人もいます。サービスのエンティティ(プロセスインストーラなど)のセットアップについて知る必要はほとんどありませんが、幸い、.NETでWindowsサービスを作成する方法についてはインターネット上で情報がたくさん公開されています。 本稿のサンプルファイルには、Windowsサービス用のプロジェクトが収録されています。 ヒント9:Visual Studio 2005におけるTCPリモーティングの新しいセキュリティ強化Visual Studio 2003では、IIS下にHTTPリモーティングオブジェクトを置いてセキュリティ手段を確保しなければなりませんでした。Visual Studio 2005では、TCPチャネルが強化され、セキュアなTCP通信を構成することができます。特に、TCPリモーティングは、SSPI(Security Support Provider Interface)を使って暗号化と認証をサポートしています。 クライアントサイドで、必要なセキュリティ設定がすべて含まれた簡単なコレクションを定義し、そのコレクションをTCPチャネルのコンストラクタに渡すことができます。 Dictionary<string, string> oDict = new Dictionary<string, string>(); oDict.Add("secure", "true"); TcpChannel oChannel = new TcpChannel(oDict, null, null); ChannelServices.RegisterChannel(oChannel); サーバーサイドでは、2つ目のパラメータ(
Tcps = new TcpServerChannel(nTCPPort);
ChannelServices.RegisterChannel(Tcps,true);
ヒント10:ジェネリックの概要ジェネリックはVisual Studio 2005で最も話題になっている新しい言語機能の1つです。ジェネリックの説明でよく目にするのが、以前のバージョンの.NETではサポートされていなかったものであるということです。代表的な例がコレクションです。従来は、コレクションにあらゆる型のオブジェクトへの参照を格納しており、コレクション内の特定インデックスの値を取得するときにはキャストを実行していました。そのため、コンパイル時のタイプセーフが保証されず、キャスト時にボックス化とボックス化解除が生じるせいでパフォーマンスが低いという欠点がありました。 新しいVisual Studio 2005の名前空間Systems.Generic.Collectionsを使えば、コレクション作成時に受け付けるメンバの型を指定することができます。これにより、コンパイラは事前に宣言されている型のオブジェクトの追加のみを認めるようになります。その結果、コレクションから値を取得するときにキャストを実行する必要はなくなりました。 昨年秋にニュージャージー州で開かれたMSDN CodeCampで、Carl Franklinが行ったジェネリックの説明は、私がこれまでに聞いた中で最も短いものでした(www.DotNetRocks.comを参照)。Franklinによると、「ジェネリックは、内部型が異なる複数のクラスを簡易化する素晴らしい方法である」ということです。これを踏まえて、次のヒントに移ります。 ヒント11:本稿のスポットライト:ジェネリックを使ってリモーティングインターフェイスを簡易化する 先ほどのごく簡単な例には、1つのインターフェイスと1つのメソッドしか含まれていませんでした。もっと現実的なアプリケーションを考えてみましょう。モジュールが12あり、それぞれのモジュールに基本キーのデータを取得するメソッド( Visual Studio 2005では、必ずしもそうなるとは限りません。Carl Franklinの「ジェネリックは内部型が異なる複数のクラスを簡易化する」という考えを応用すれば、1つの基本キーの整数(例えば、アカウントキーまたは製品キー)を受け取ってデータセット(例えば、アカウント結果または製品結果)を返すジェネリックインターフェイスを定義することができます。 using System; using System.Collections.Generic; using System.Text; using System.Data; namespace GenericInterfaces { public interface IRemoteGenericResultSet<T> { DataSet GetResults(int nID); } } 構文に注意してください。 string cServer = "tcp://localhost:8228"; object oRemoteObject = Activator.GetObject( typeof(IRemoteGenericResultSet<DataSet>), cServer + "/GetCustomers"); IRemoteGenericResultSet<DataSet> oCustomer = oRemoteObject as IRemoteGenericResultSet<DataSet>; oCustomer.GetResults(111); 呼び出し側のプロシージャには特定のモジュールへの型参照は含まれていません。バックエンドオブジェクトの名前に関するリテラルがあるので、そのリテラルを文字列またはプロパティに簡単に挿入できます。 これは、ジェネリックを使ってプログラミングを簡易化する方法の一例です。ジェネリックの詳細は、「Baker’s Dozen」シリーズの今後の記事で取り上げる予定です。 ヒント12:リモーティングオブジェクトファクトリを作成するリモーティングを簡易化するもう1つの手段として、リモーティングの詳細を独立したクラスに分離する方法があります。リスト1に、リモーティングオブジェクトファクトリのコードを示します。アプリケーションから数多くのリモートオブジェクトにアクセスする場合は、このファクトリをインスタンス化し、ポート、サーバー、インターフェイス、および型参照に関するプロパティを目的のバックエンドオブジェクトのものに設定して、戻り値を適切なインターフェイスに渡します。 リスト1 クライアントサイドのリモーティングファクトリのコード
using System; using System.Collections.Generic; using System.Text; using System.Runtime ; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; namespace SimpleClientRemotingFactory { Public class RemotingFactory { private static int _nTCPPort; public int nTCPPort { get {return _nTCPPort ;} set {_nTCPPort = value;} } private static string _cTcpServer; public string cTcpServer { get {return _cTcpServer ;} set {_cTcpServer = value;} } private Type _tInterface; public Type tInterface { get {return _tInterface ;} set {_tInterface = value;} } private string _cRemoteObjectName; public string cRemoteObjectName { get { return _cRemoteObjectName; } set { _cRemoteObjectName = value; } } public RemotingFactory() { } public object GetRemoteInterfaceObject() { object oAccessObject = new object(); IChannel[] myIChannelArray = ChannelServices.RegisteredChannels; if(myIChannelArray.Length ==0) ChannelServices.RegisterChannel( new TcpClientChannel()); // activate back-end object oAccessObject = Activator.GetObject( this.tInterface, this.cTcpServer.ToString().Trim() + ":" + this.nTCPPort.ToString().Trim() + "/" + this.cRemoteObjectName ) ; return oAccessObject; } } } ヒント13:リモーティングオブジェクトファクトリを使う次に、ファクトリの簡単な使用例を示します。 using SimpleInterfaces; RemotingFactory oFactory = new RemotingFactory(); oFactory.nTCPPort = 8228; oFactory.cTcpServer = "tcp://localhost"; oFactory.cRemoteObjectName = "CustomerBzObject"; oFactory.tInterface = typeof(ICustomer); ICustomer oRemoteCustomer; oRemoteCustomer = (ICustomer)oFactory.GetRemoteInterfaceObject(); DataSet DsTemp= oRemoteCustomer.GetCustomer(111); このコード例は、アドレスプロパティの設定方法を示しているだけで、実際のリモーティングコードは含まれていないことに注意してください。ここで重要なのは、リモーティングオブジェクトファクトリによってリモーティングの複雑さがすべて隠されていることです。 コメントここ1年間で、リモーティングに対する開発者の反応についていろいろなことを知りました。開発者の多くは、リモーティングは複雑だと思って敬遠しています。そこで私は、少しの手間をかければ、リモーティングに必要な処理をアプリケーションの他の部分から分離できるということを実証しようと思い立ちました。最初にわかったことは、リモーティングは少し複雑だが少し勉強すれば慣れるということでした。今では、私は可能なときにはいつでもリモーティングを使っています。 また、リモーティングを使っている開発者が多いという嬉しい驚きもありました。Indigoなどの新しいテクノロジーを待っている開発者もいますが、目下のアプリケーション問題を解決する方法を今すぐ知りたがっている開発者もたくさんいます。ちなみに、現在あるテクノロジの使い方を具体的に紹介することが、この「Baker’s Dozen」シリーズの目標の1つです。 まとめリモーティングと、厳密な型指定による開発のメリットをお分かりいただけたでしょうか。今後、新しいMicrosoft開発テクノロジー(Indigo)が現れても、少なくとも当面の間はリモーティングが使われ続けるでしょう。 参考資料リモーティングについて勉強したことのある人ならば、Ingo Rammerという名前に聞き覚えがあるでしょう。Ingo Rammerは、リモーティングの分野でトップクラスの専門家の1人です。Ingo Rammerの教えを受けたい人は、www.thinktecture.com/を参照してください。 リモーティングのソースコードの例は、MSDNオンラインのほかに、www.c-sharpcorner.comとwww.codeproject.comでも公開されています。www.TheServerSide.netでは、有益な記事が数多く紹介されています。www.Developer.comや、Microsoftのパブリックニュースサーバーのニュースグループにも、有益な情報が数多く掲載されています。 著者紹介Kevin S. Goff(Kevin S. Goff)
.NET、Visual FoxPro、SQL Server、Crystal Reportsによる独自のWebソリューション/デスクトップソリューションを提供するコンサルティンググループ「Common Ground Solutions」の創業者兼主任コンサルタント。ソフトウェアアプリケーションの開発経験は17年に及ぶ。米農務省からシステムオートメーション関連の賞をいくつか受賞。また、6桁の投資見返りを実現するソリューションを開発したことによりFortune 500企業から特別表彰を受ける。保険、会計、環境衛生、不動産、出版、広告、製造、金融、日用品、貿易振興など多様な業界に携わった経験を持ち、さまざまな形態で独自のトレーニングも行っている。
|