MSDN マガジン > Home > 発行物 > 2008 > October >  Cutting Edge: WPF および Silverlight 2 でコードを再利用する
Cutting Edge
WPF および Silverlight 2 でコードを再利用する
Dino Esposito

コードのダウンロード : MSDN Code Gallery (604 KB)
オンラインでのコードの参照

このコラムは、Silverlight 2 のプレリリース バージョンに基づいています。ここに記載されているすべての情報は、変更される場合があります。

Silverlight 2 では、XAML (Extensible Application Markup Language) を使用してユーザー インターフェイスを設計およびレンダリングします。同時に、組み込みコア CLR を利用してブラウザ内のマネージ コードを処理します。その結果、Web ベースの Silverlight 2 アプリケーションとデスクトップの WPF (Windows Presentation Foundation) アプリケーションにはよく似たところがあります。類似のプログラミング モデルを作成する目的の 1 つは、2 つの間でコードの再利用を容易にすることです。このコラムでは、Silverlight 2 と WPF でできる限り簡単にコードと XAML マークアップを共有するいくつかのパターンについて説明します。

WPF と Silverlight 2 の互換性
Silverlight 2 の導入により、XAML は新世代の UI モジュールの API となっています。Silverlight 2 は、リッチなレイアウト管理、データ バインド、スタイル、メディア、アニメーション、グラフィックス、テンプレートの機能など、完全な WPF フレームワークのサブセットをサポートします。
ただし、Silverlight 2 での XAML のフル サポートは、ダウンロード可能なプラグインのサイズによって制限されます。ほんの数メガバイト (Beta 2 では 5 MB 未満) で、Silverlight 2 プラグインは、コア CLR、WPF および WCF (Windows Communication Foundation) クライアント プラットフォームのサブセットを含む Microsoft .NET Framework 3.5 のバージョン、XAML パーサー、およびいくつかの Silverlight 固有コントロールを提供する必要があります。
Silverlight 2 では、WPF 3D グラフィックス機能はサポートされず、削除または縮小されている属性や要素があります。それでも、最後には、互換性のあるサブセットができあがるので、そこそこ複雑な WPF ユーザー インターフェイスの Silverlight バージョンを生成できます。互換性の重要な部分については、後でもう一度説明します。
ただし、Silverlight では新しい API がいくつか追加されており、現時点では WPF のデスクトップ バージョンに対応するものがない場合があります。最も関連のある機能は、DataGrid などのコントロールと、簡単な GET スタイルのネットワーク呼び出しのための WebClient などのクラスです。これらの機能も WPF に追加されます。

Visual State Manager の概要
Silverlight 2 に付属し、WPF に追加されるもう 1 つの機能は、コントロール用の非常に便利な VSM (Visual State Manager) です。VSM は、Visual State と状態遷移を導入することで対話形式コントロール テンプレートを簡単に開発できるようにします。WPF が導入されたことで、開発者は、テンプレートとスタイルを使用して、WPF のルック アンド フィールと動作をカスタマイズできます。たとえば、コントロールの形状や外観を変更できるだけでなく、コントロールがクリックされたときやフォーカスを得たり失ったりしたときの新しいアクションやアニメーションを定義することもできます。
VSM では、Visual State と状態遷移が作業の一部を自動的に行います。一般的な VSM の Visual State は、通常、マウス ポイント、無効、フォーカスありです。これらの状態のそれぞれについて、スタイルまたは形状を定義できます。状態遷移は、コントロールが状態間を移動する方法を定義します。通常は、アニメーションを使用して遷移を定義します。実行時に、Silverlight は適切なアニメーションを再生し、指定されたスタイルを適用して、コントロールを洗練された滑らかな動きである状態から別の状態に移動します。
Visual State を定義するには、次に示すようなマークアップをコントロール テンプレートに挿入します。
<vsm:VisualStateManager.VisualStateGroups>
  <vsm:VisualStateGroup x:Name="CommonStates">
    <vsm:VisualState x:Name="MouseOver">
      <Storyboard>
        ...
      </Storyboard>
    </vsm:VisualState>
  </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
各コントロールは、CommonStates や FocusStates などの 1 つ以上の状態のグループを定義します。各グループでは、MouseOver、Pressed、Checked などの特定の Visual State が定義されています。各 Visual State および状態間の遷移に対し、Silverlight で必要に応じて自動的に再生されるストーリーボードを定義できます。
つまり、WPF の機能の中には Silverlight 2 がサポートしていないものがあり、Silverlight 2 の機能の中には WPF には存在しないものがあります。これらの違いは、主に XAML レベルでの互換性に影響を与えます。共通のサブセットを使用することで完全な互換性が得られ、さいわいこの共通部分はほとんど何でも行うことができる十分な大きさです。

コードの共有
WPF のデスクトップ バージョンで VSM が使用できるようになれば、Silverlight アプリケーションと Windows デスクトップ アプリケーションの両方で同じアプローチを使用し、WPF プロジェクトと Silverlight プロジェクトでコントロール テンプレートを共有できるようになります。それまでは、WPF デスクトップと Web プロジェクトでコードを再利用できる方法があります。
WPF アプリケーションは、XAML とマネージ コードで構成されます。マネージ コードの対象は、サポートされる .NET のバージョンのクラスです。ユーザーは、デスクトップ WPF と Silverlight 2 の両方で認識される XAML の共通サブセットを意識して使用する必要があります。同様に、バックエンド フレームワークでの違いが適切に処理されるように分離コード クラスを編成することも、ユーザーの役割です。
WPF コードの再利用には、基本的に 2 つのシナリオがあります。1 つは、既存の WPF デスクトップ アプリケーションがあり、Web でそれを提供して保守と展開を簡単にする場合です。もう 1 つは、既存システムのフロントエンドを開発し、それを Windows クライアントと Web クライアントの両方に提供する場合です。
ここでは、WPF から Silverlight へのコードの再利用という視点から説明します。コードとマークアップのリファクタリングのパターンに関しては、違いはほとんどありません。

WPF アプリケーションについての考え方
一般的な WPF アプリケーションは、オブジェクトのツリーから構築され、ツリーのルートは Window です。一方、Window 要素には、さまざまな方法でレイアウトまたはスタックされる子要素のメンバが含まれます。要素は、基本的な形状、レイアウト マネージャ、ストーリーボード、およびコントロール (カスタム サードパーティおよびユーザー コントロールを含みます) を示します。次に、基本的な例を示します。
<Window x:Class="Samples.MyTestWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test App" Height="300" Width="350">
    <StackPanel Margin="10">
        ...
    </StackPanel>
</Window>
このコードをそのまま Silverlight 2 アプリケーションに組み込むことはできません。まず、Window 要素は Silverlight ではサポートされません。Silverlight アセンブリでは、System.Windows 名前空間にこのようなクラスはありません。すべての Silverlight アプリケーションのルート タグは UserControl です。要素は、System.Windows.Controls 名前空間で定義されている UserControl クラスにマップされます。
Silverlight アプリケーションの変更した先頭部分を次に示します。
<UserControl x:Class="Samples.MyTestWindow"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test App" Height="300" Width="350">
    <StackPanel Margin="10">
        ...
    </StackPanel>
</UserControl>
<Window> 要素と <UserControl> 要素の境界内のすべてのマークアップ コードは、一致している必要があります。元のデスクトップ XAML を変更して Silverlight XAML パーサーの要件に一致させるのはユーザーの責任です。この作業は、2 つの構文の間に高レベルの互換性があるおかげで、かなり楽になります。XAML の調整のほとんどは、若干の要素と属性の修正に関するものです。例を 2 つ示します。
デスクトップ WPF の <label> 要素は、Silverlight では認識されません。このマークアップを Silverlight に移植するときは、ラベルの回避策が必要です。内部にテキスト ブロックを含む四角形は、有効な解決策になる場合があります。
WPF では、次に示すように、ToolTip 属性を使用してツールヒントとコントロールを関連付けることができます。
<Button Tooltip="Click me" ... />
Silverlight 2 では、ToolTip 属性はサポートされていません。次のコードで示すように、ToolTipService を利用する必要があります。
<Button ToolTipService.ToolTip="Click me" ... />
デスクトップ WPF ではどちらの方法も動作することに注意してください。さらに、ToolTipService では、配置、オフセット、初期遅延、期間などのデスクトップ WPF の追加のツールヒント プロパティも提供されています。ただし、これらの追加プロパティはいずれも Silverlight ではサポートされていません。
WPF と Silverlight の間の互換性の問題はこれだけでしょうか。それは WPF の使用方法によって決まります。一般に、重要な WPF アプリケーションを Silverlight に移植することは難しく、実際には主要な目標でさえありません。まず、Silverlight 2 では、必要とされる場所でトリガを使用できません。たとえば、UI 要素には Triggers コレクション (FrameworkElement の子孫) がありますが、スタイル、データ、およびコントロール テンプレートにはありません。
同様に、Silverlight はデータ バインドをサポートしていますが、WPF とは方法が異なります。たとえば、Binding 要素や、データ コンテキスト、データ テンプレート、識別可能なコレクションなどがあります。既に説明したように、トリガや簡略化された XAML マークアップは使用できず、WPF よりも頻繁にコードを作成する必要があります。さらに、内部の実装が大幅に異なります。Silverlight の Binding オブジェクトには、WPF よりはるかに少ないプロパティしかありません。
グローバリゼーションはもう 1 つの頭痛の種です。パフォーマンスのため、コア CLR にはサポートされるすべてのカルチャについて独自のグローバリゼーション データが含まれません。代わりに、Silverlight の CultureInfo クラスは、基になっているオペレーティング システムで提供されるグローバリゼーション機能に依存します。つまり、異なるオペレーティング システムで同じグローバリゼーション設定をアプリケーションに提供する方法はありません。
最後に、WPF のリッチなコントロールのセットの中には、Silverlight では使用できないものがあります。その良い例が RichTextBox コントロールです。
まとめると、Silverlight アプリケーションを WPF に移植することは比較的簡単です。ただし、よりリッチなオブジェクト モデルによって発生する可能性のあるパフォーマンスの問題には注意する必要があります。留意する必要のある重要なことは、Silverlight はデスクトップ WPF によって提供される可能性のサブセットをサポートしているということです。適切な機能を選択してクロスプラットフォームのソリューションを考案する作業はユーザーに委ねられます。

クロス プラットフォーム WPF コードの作成
WPF プロジェクトから Silverlight プロジェクトにコードを移植する最も簡単な方法は、ユーザー コントロールを使用することです。異なる XML 名前空間とマークアップの違いを別にすれば、ユーザー コントロールはデスクトップと Web ベースの WPF でマークアップとコードを共有する唯一の手段です。
WPF アプリケーションが、ユーザー コントロールを広範に使用するように、もともと編成されている場合、またはリファクタリングできる場合は、Silverlight へのコードの移植は、カット アンド ペーストというあまり魅力的でない他のオプションよりはるかに簡単です。
それでは、WPF プロジェクトと Silverlight プロジェクトでアセンブリを共有できるのでしょうか。Silverlight 2 では、アセンブリにパックするカスタム クラス ライブラリを作成できます。ただし、このようなライブラリは Silverlight バージョンの .NET Framework を対象としたものであり、異なるセキュリティ モデルを使用することを理解しておく必要があります (詳細については、今月号の「CLR 徹底解剖」を参照してください)。
デスクトップ WPF アプリケーションで使用しているすべてのアセンブリを Silverlight のクラス ライブラリとして再コンパイルして、正しいアセンブリを参照し、正しくクラスを呼び出すようにする必要があります。言うまでもありませんが、再コンパイルを行うことで、サポートされていないクラスの呼び出しを修正する追加作業が発生する可能性があります。
つまり、若干の作業により、WPF アプリケーションを取り出し、Silverlight を使用して Web を介して公開できます。また、これにより、クライアントに .NET Framework の完全なインストールを強いることなく、Windows 以外のさまざまなプラットフォームでコードを実際に公開することになります。図 1 は、簡単な WPF スタンドアロン デスクトップ アプリケーションを示しています。図 2 は、同じアプリケーションを、Silverlight を使用して Internet Explorer でホストした場合です。
図 1 サンプル WPF アプリケーション
図 2 Silverlight 用に適合された WPF アプリケーション (クリックすると拡大画像が表示されます)

マネージ コードの分析
何らかの理由で、WPF と Silverlight の間のコードを再利用すると、XAML の問題が発生します。説明したように、コードを適用するときは XAML の問題がいくつかありますが、最大の課題は分離コード クラスです。
Windows および Silverlight のどちらも、XAML ファイルは、C# または別のマネージ言語で記述された分離コード クラスとペアになっています。残念ながら、これらの分離コード クラスは異なるバージョンの .NET Framework が対象です。デスクトップ バージョンの WPF は .NET Framework 3.5 の完全な基本クラス ライブラリ (BCL) に依存しますが、Silverlight 2 は軽量バージョンの BCL を使用します。
Silverlight バージョンの BCL は非常に小型ですが、それでも、コレクション、リフレクション、正規表現、文字列操作、スレッド、タイマなどの基本機能をサポートします。また、XML Web サービス、WCF サービス、ADO.NET Data サービスなどのさまざまなサービスを呼び出すツールを備えています。さらに、Plain Old XML (POX) サービスおよび Representational State Transfer (REST) サービスを使用して HTTP 経由で通信するためのリッチなネットワーク サポートを備え、通常は、任意のパブリック HTTP エンドポイントに到達できます。ネットワーク サポートには、(クロスドメイン) ソケットおよび二重通信も含まれます。
最後に、Silverlight BCL には、アドホック バージョンの XmlReader および XmlWriter クラスなど、XML データを操作するための便利なサポートもあります。これらのクラスは、デスクトップ バージョンの .NET Framework の類似クラスと非常によく似ています。
Silverlight 2 でこれらのコア機能を基にすると、LINQ to Objects、LINQ to XML、および式ツリーが完全にサポートされます。Beta 2 からは、JavaScript Object Notation (JSON) データに対して LINQ クエリを直接実行する新しい LINQ to JSON プロバイダも追加されました。
もう 1 つ指摘しておく必要があるのは、Silverlight でのネットワークは非同期でのみ実行できるということです。同期呼び出しを行うには、可能な場合は常に、ブラウザの相互運用層を呼び出し、ブラウザの XMLHttpRequest の実装にアクセスする方法を使用する必要があります (詳細については、go.microsoft.com/fwlink/?LinkId=124048 を参照してください)。
重要なのは、WPF と Silverlight の分離コード クラスが大きく異なるクラス ライブラリを利用していることです。この事実は、XAML の違いよりも、はるかに大きく再利用性を妨げます。次に、この問題に取り組みます。

重要なコードに対する Strategy の取得
Silverlight と Windows のランタイムが共有するコードを作成するときは、各プラットフォームがサポートするものをよく理解しておく必要があります。できれば、各プラットフォームでの特別な処理を必要とする要求されたタスクの一覧を作成します。次に、オブジェクト内のこれらのコードを分離し、インターフェイスを抽出します。
結果として、重要な機能について異なるコンポーネントを呼び出す完全に再利用可能なコードのブロックができあがります。このようなアプリケーションを Silverlight (または WPF) に移植することは、重要なコンポーネントをプラットフォーム固有の他のコンポーネントに置き換えるのと同じくらい簡単です。これらのコンポーネントはすべて共通のインターフェイスを公開するので、その実装はコードのメイン ブロックに対して透過的です。
この方法の背後にあるのは "Strategy" パターンです。その正式な定義については、go.microsoft.com/fwlink/?LinkId=124047 を参照してください。つまり、あるアプリケーションで特定のタスクを実行するために使用するアルゴリズムを動的に変更する必要がある場合は常に、Strategy パターンが役に立ちます。
実行時の条件によって変化する可能性のあるコードの部分を識別した後、コードの抽象的な動作を指定するインターフェイスを定義します。次に、そのインターフェイスを実装する戦略クラスを 1 つ以上作成し、それによって抽象的な動作を実現する異なる方法を表します。戦略クラスを変更することで、インターフェイスによって定義されているアルゴリズムを解決する方法を簡単に変更できます。このようにすると、動作の実際の実装 (戦略) が、それを使用するコードから分離されます。
たとえば、ASP.NET では、メンバシップ、ロール、ユーザー プロファイルなどのプロバイダ モデルの実装において、Strategy パターンが使用されています。ASP.NET ランタイムは、メンバシップやユーザーなどを処理するための特定のインターフェイスがあることを認識しています。また、これらのインターフェイスの種類に対する具象クラスを探してインスタンス化する方法についても認識しています。ただし、ランタイム コンポーネントはインターフェイスにのみ依存するので、具象クラスの詳細は ASP.NET とは関係なくなります。

例の説明
1 および 2 に示すサンプル アプリケーションについて考えます。Silverlight および Windows のどちらのアプリケーションも、ユーザーが銘柄記号を入力して現在の価格を取得できるフォームを表しています。図 3 に、ユーザーがボタンをクリックしたときのアプリケーションのダイアグラムを示します。ユーザー インターフェイスはモデル ビュー プレゼンタ (MVP) パターンを使用し、UI の背後にあるすべてのロジックが単一のプレゼンタ クラスで伝達されます。プレゼンタは内部の QuoteService クラスを呼び出し、このクラスは最終的に、ユーザー インターフェイスに組み込まれるすべての情報を含む StockInfo オブジェクトを提供することで応答します (図 4 を参照)。
図 3 アプリケーションの動作のダイアグラム (クリックすると拡大画像が表示されます)
namespace Samples
{
    class SymbolFinderPresenter
    {
        // Internal reference to the view to update
        private ISymbolFinderView _view;

        public SymbolFinderPresenter(ISymbolFinderView view) {
            this._view = view;
        }

        public void Initialize() { }

        // Triggered by the user's clicking on button Find
        public void FindSymbol() {
            // Clear the view before operations start
            ClearView();

            // Get the symbol to retrieve 
            string symbol = this._view.SymbolName;
            if (String.IsNullOrEmpty(symbol)) {
                _view.QuickInfoErrorMessage = "Symbol not found.";
                return;
            }

            QuoteService service = new QuoteService();
            StockInfo stock = service.GetQuote(symbol);

            // Update the view
            UpdateView(stock);
        }

         private void ClearView() {
            _view.SymbolDisplayName = String.Empty;
            _view.SymbolValue = String.Empty;
            _view.SymbolChange = String.Empty;
            _view.ServiceProviderName = String.Empty;
        }

        private void UpdateView(StockInfo stock) {
             // Update the view
            _view.QuickInfoErrorMessage = String.Empty;
            _view.SymbolDisplayName = stock.Symbol;
            _view.SymbolValue = stock.Quote;
            _view.SymbolChange = stock.Change;
            _view.ServiceProviderName = stock.ProviderName;
        }
     }
}
QuoteService クラス自身は、価格の取得を試みません。最初にプロバイダ コンポーネントを作成してから、それに制御を渡します。QuoteService クラスが実装しているアルゴリズムは非常に単純です。インターネット接続がある場合は、パブリック Web サービスを使用して金融データを取得するプロバイダ クラスを使用します。インターネット接続がない場合は、ランダムな数値を返すだけの偽のプロバイダに切り替えます。したがって、図 5 に示すように、QuoteService クラスはいずれかの時点でインターネット接続を検査する必要があります。
public StockInfo GetQuote(string symbol)
{
    // Get the provider of the service
    IQuoteServiceProvider provider = ResolveProvider();
    return provider.GetQuote(symbol);
}
private IQuoteServiceProvider ResolveProvider()
{
    bool isOnline = IsConnectedToInternet();
    if (isOnline)
        return new FinanceInfoProvider();

    return new RandomProvider();
}
ここまでは、Silverlight と WPF に違いはなく、すべてのコードは完全に再利用できます。.NET でインターネット接続を検査するには、NetworkInterface オブジェクトの静的メソッドを使用できます。このオブジェクトは、System.Net.NetworkInformation 名前空間で定義されています。特に、GetIsNetworkAvailable メソッドはネットワーク接続が使用可能であるかどうかを示すブール値を返します。残念ながら、インターネット接続の詳細については示されません。インターネットへのアクセスが可能であることを確認する唯一の確実な方法は、ホストに ping を試みることです (図 6 を参照)。
private bool IsConnectedToInternet()
{
    string host = "...";
    bool result = false;
    Ping p = new Ping();
    try
    {
        PingReply reply = p.Send(host, 3000);
        if (reply.Status == IPStatus.Success)
            return true;
    }
    catch { }

    return result;
}
図 6 の唯一の問題は、このコードが Silverlight 2 ではサポートされないことです (同様に、NetworkInterface オブジェクトでもサポートされません)。このコード (および考えられる互換性の問題に対処する他のコード) を、置き換え可能なクラスに分離する必要があります (この制限の詳細については、このコラムに付属の「Silverlight での CoreCLR」リンクを参照してください)。付属のソース コードでは、この種の問題があると考えられるメソッドに対するユーティリティ インターフェイスを作成し、各プラットフォーム用のインターフェイスを実装する戦略クラスを作成しています。図 7 に、これを示します。
public partial class SilverCompatLayer : ICompatLib
{
    public bool IsConnectedToInternet()
    {
        string host = "...";
        bool result = false;
        Ping p = new Ping();
        try
        {
            PingReply reply = p.Send(host, 3000);
            if (reply.Status == IPStatus.Success)
                return true;
        }
        catch { }

        return result;
    }

    public string GetRawQuoteInfo(string symbol)
    {
        string output = String.Empty;
        StreamReader reader = null;

        // Set the URL to invoke
        string url = String.Format(UrlBase, symbol);

        // Connect and get response
        WebRequest request = WebRequest.Create(url);
        WebResponse response = request.GetResponse();

        // Read the response
        using (reader = new StreamReader(response.GetResponseStream()))
        {
           output = reader.ReadToEnd();
           reader.Close();
        }

        return output;
    }

    // A few other methods that require a different implementation 
    // (See source code)
    ...
}
このインターフェイスは、プラットフォーム固有の戦略クラスを、ホストするアプリケーションから分離します。その後、具象的な戦略クラスをインスタンス化するコードをファクトリ メソッドの背後に隠すことで、WPF と Silverlight の両方で次のコードを安全に使用できます。
private bool IsConnectedToInternet()
{
    ICompatLib layer = ServiceResolver.ResolveCompatLayer();
    return layer.IsConnectedToInternet();
}
SilverCompatLayer クラスは別のアセンブリにあります。WPF コードを Silverlight に移植する場合、またはその逆の移植を行う場合は、このアセンブリを変更するだけで済みます。
プラットフォーム固有の必要な戦略クラスを作成したら、後はアプリケーションの Silverlight バージョンを作成するだけです。元の WPF アプリケーションから導出される Silverlight プロジェクトには、互換性アセンブリを除くすべてのファイルの正確なコピーが含まれています。
この記事に付属のコード サンプルを見ると、ファクトリ メソッドで明示的に具象クラスをインスタンス化することで、使用する戦略の実装を判別していることがわかります。このようにクラスを直接インスタンス化することも、構成ファイルからクラス名を読み込み、リフレクションを使用してインスタンスを取得することもできます。これらは主に、構築しているシステムの種類に依存する実装の詳細です。WPF と Silverlight の互換性に関する限り、理解すべき重要な概念は、戦略と階層化です。

最後の考慮事項
サンプル アプリケーションでは、ネットワーク呼び出しを行っています。元の WPF アプリケーションでは、呼び出しは同期的です。一方、Silverlight 2 では、同期ネットワーク呼び出しはまったくサポートされていません。同期呼び出しを可能にするには、ブラウザの XMLHttpRequest の実装の呼び出しに基づいた前述の方法を使用します (詳細についてはソース コードを参照してください)。
サンプル コードでは、元の WPF アプリケーションを適切に Web に移植できました。コードを移植するときは、Silverlight 環境のネイティブ機能を考慮し、必要であればアプリケーションの構造を変更する場合があります。この単純な例では、Silverlight のプログラミング モデルを使用してアプリケーションを書き直す方が、WPF アプリケーションからの適用よりも少量のコードと作業で済むことに注意してください。
WPF と Silverlight には、XAML などの純粋に視覚的な部分について多くの共通項があります。しかし、基になるプログラミング モデルは若干異なり、WPF で動作するソリューションは必ずしも Silverlight にそのまま適用できるわけではなく、その逆もまた同様です。そうは言っても、WPF および Silverlight アプリケーションの間で XAML とコードを共有できることには間違いありません。

ご意見やご感想は、cutting@microsoft.com まで英語でお送りください。

Dino Esposito は、IDesign 社のアーキテクトであり、『Programming ASP.NET 3.5 Core Reference』の著者です。Dino はイタリアに在住し、世界各国で開催される業界のイベントで頻繁に講演しています。ブログは weblogs.asp.net/despos で読むことができます。

Page view tracker