アーカイブ

Archive for 2010年12月

ASPXファイルやjavascriptファイル、スタイルシートファイルもリンク参照したい。

2010年12月31日 1件のコメント

Visual Studioを使ったアプリケーション開発では、他のプロジェクトのクラスやリソースを利用したい場合は、アセンブリの参照を行ってクラスやリソースを共有します。しかしアセンブリの参照では、WPFとSilverlightのようにアセンブリの互換性がなかったり、テキストファイルなどを直接参照することはできません。

リンク参照とコンテンツファイル

複数のプロジェクトでファイルそのものを共有したい場合に利用するのが、リンク参照です。例えばWPFとSilverlightではアセンブリレベルの互換性は一部のアセンブリだけですが、ソースをリンク参照することで同じソースコードを利用してコンパイルすることができます。

複数のプロジェクトでファイルを共有するには?

この機能を使うと、ソースコードであれば問題なくビルドすることはできのですが、ファイルそのものを実行時に利用するようなHTMLや画像ファイルの場合は困ります。あくまでリンクがVisual Studioのビルド時に解決されるだけなので、実際のコンテンツファイルは参照先にしか存在しないので、ブラウザーなどでそのURLを見てもファイルが存在しないのです。

image image

プロジェクト上はCommonPageプロジェクトのASPXがリンク参照されているが、ブラウザーでそのASPXを参照すると、ファイルが存在しないエラーになってしまう。

リンクの解決とファイルのコピー

じゃぁビルド時にプロジェクトファイルの内容を見て、リンク先のファイルを自分のフォルダーにコピーしてあげればいいじゃん!!って事で、まずはプロジェクトファイルを確認してみましょう。プロジェクトファイルをテキストエディターなどでひらくと、リンク参照先は/Project/ItemGroup/Content/Linkに定義されいることが確認できます。

  <ItemGroup>
    <Content Include="..\CommonPages\pages\共通ページ.aspx">
      <Link>pages\共通ページ.aspx</Link>
    </Content>
    <Content Include="Default.aspx" />
    <Content Include="pages\個別ページ.aspx" />
    <Content Include="Web.config" />
    <Content Include="Web.Debug.config">
      <DependentUpon>Web.config</DependentUpon>
    </Content>
    <Content Include="Web.Release.config">
      <DependentUpon>Web.config</DependentUpon>
    </Content>
  </ItemGroup>

ここまでわかればLINQ to XMLでざっくりと取れるわけですが、どうせなら、VB.NETでXML Schemaをつくって、インテリセンス経由でリンク参照の一覧を取り出してみましょう。まずは、アイテムの新規追加から、Xml To Schemaを選択します。

新しい項目の追加 - ConsoleApplication1

次に表示さる、XMLドキュメントからのXMLスキーマセットの生成では、ファイルから追加ボタンをクリックして、リンク参照を含んだVBやC#のプロジェクトファイルを追加します。

XML ドキュメントからの XML スキーマ セットの生成

あとは、こんなふうにコードを記述すると、リンクとして追加したファイルの参照先の一覧を取得することができます。

Imports <xmlns:ns="http://schemas.microsoft.com/developer/msbuild/2003">

    ''' <summary>
    ''' リンクされているプロジェクトアイテムを取得します。
    ''' </summary>
    Private Shared Function GetLinkItems(ByVal projectFilePath As String) As Dictionary(Of String, String)
        Dim xml = XElement.Load(projectFilePath)
        Dim query = From element In xml...<ns:ItemGroup>.<ns:Content> _
                   Where element.<ns:Link> IsNot Nothing AndAlso _
                         Not String.IsNullOrEmpty(element.<ns:Link>.Value) _
                   Order By element.@Include

        Dim linkItems As New Dictionary(Of String, String)
        For Each item In query
            Console.WriteLine(String.Format("追加 {0}", item.@Include))
            linkItems.Add(item.@Include, item.<ns:Link>.Value)
        Next
        Return linkItems
    End Function

あとは、取得したファイルの一覧をループして、プロジェクトディレクトリーの相対パスにファイルをコピーしていくだけです。

        Dim linkItems = GetLinkItems(targetProjectPath)
        Dim projectDirectoryPath = Path.GetDirectoryName(targetProjectPath)
        Console.WriteLine("ファイルのコピーを開始します。")
        For Each linkItem In linkItems
            Dim copyFrom = Path.Combine(projectDirectoryPath, linkItem.Key)
            Dim copyTo = Path.Combine(projectDirectoryPath, linkItem.Value)

            Copy(copyFrom, copyTo)
        Next

ここで気をつけたいこととして、ソース管理ツールを使っている場合、ディレクトリーの中がからの場合はディレクトリーそのものが作られないことがあります。Visual Studioのリンクとして参照を利用すると物理ファイルが存在しないので、コピー先のフォルダーが存在しないことがあるので、コピーするときは必ずフォルダーの存在有無を確認して、フォルダーが存在しなかったら作成してあげる必要があります。

    ''' <summary>
    ''' ファイルのコピー(上書き)
    ''' </summary>
    Private Shared Sub Copy(ByVal CopyFrom As String, ByVal CopyTo As String)
        Dim dir = Path.GetDirectoryName(CopyTo)
        If Not Directory.Exists(dir) Then
            Directory.CreateDirectory(dir)
        End If

        Console.WriteLine(String.Format("[Copy]:{0}→{1}", CopyFrom, CopyTo))
        File.Copy(CopyFrom, CopyTo, True)
    End Sub

あとはこれを正常時は0を、異常時には1を返すコンソールアプリケーションにしてにしてあげれば、Visual Studioのビルド後動作に組み込むことができます。

ビルド後動作の追加

ビルド後の動作を設定したい場合、VBのプロジェクトであれば、プロジェクトのプロパティー⇒[コンパイル]⇒[ビルドイベント]から、C#であれば、プロジェクトのプロパティー⇒[ビルドイベント]から設定できます。

今回はWebプロジェクトのプロジェクトがビルドされた後に共通Webプロジェクトからファイルをコピーしたいので、ビルド後に実行するコマンドラインに次のようなコマンドを記述します。

 

“$(ProjectDir)..\Tools\ConsoleApplication1.exe” “$(ProjectPath)”

 

$()で記述されているのはVisual StudioというかMsBuildのマクロの一種です。ここではプロジェクトフォルダーの一つ上のToolsフォルダに作ったコピー用のコンソールアプリケーションを呼び出し、引数にプロジェクトファイルのパスを与えています。

ビルドを実行すると、出力ウインドウには次のようにログが出力されます。実際にエクスプローラーなどで確認してみると、リンクとして参照したファイルがコピーされているのを確認できます。

—— ビルド開始: プロジェクト: Page2, 構成: Debug Any CPU ——

Page2 -> C:\Source\LinkSample\Page2\bin\Page2.dll

追加 ..\CommonPages\pages\共通ページ.aspx

ファイルのコピーを開始します。

[Copy]:c:\Source\linksample\page2\..\CommonPages\pages\共通ページ.aspx→c:\Source\linksample\page2\pages\共通ページ.aspx

ファイルのコピーが終了しました。

image

動作確認

実際にWebブラウザーで確認すると、ちゃんと表示されるのを確認できます。

image

注意)ソリューションエクスプローラーなどでブラウザーで確認を行った場合、~/参照先の「Webサイト/参照したファイルのパス」が表示されてしまいうまく参照できませんが、Webブラウザーでちゃんとしたパスを指定すれば確認できます。

注意)あっそうだ、リンク参照とは別にCommonPageプロジェクトに対する参照も追加するのを忘れないようにしておいてくださいね。今回はCSファイルやVBファイルはコピーしていないので、コードファイルは参照先のものを使っていますから。CSファイルやVBファイルをコピーしたいときはコンソールアプリケーション側に手を入れてちょっと細工をしてあげてください。ダウンロードようのプロジェクトにはコメントとしてコードが入っているので参考にしてください。

今回のプロジェクトはここからダウンロードできます。

http://cid-b17f5fcd8095628b.office.live.com/self.aspx/%E5%85%AC%E9%96%8B/LinkSample.zip

Silverlightでamazonの書籍検索を使ってみよう

2010年12月24日 4件のコメント

書籍管理用のSilverlightアプリがほしいなーと思っています。画像や書籍の情報なんかを手軽に利用したいとなるとやっぱりAmazonかな、ということで、とりあえずAmazon Web Service API(Amazon AWS)をSilverlightで使ってみようと思います。

1.Amazon AWSのアクセスポリシー

Silverlightではそのアプリケーションがホストされたドメイン以外のサイトに対して通信を行う場合、クロスドメインの制限がかかるため、通信先のホストのルートディレクトリにあるclientaccesspolicy.xmlか、crossdomain.xml でそのサイトに対するアクセスが許可されている必要があります。Silverlightで他のドメインに通信をする場合は、まずはじめにこの部分を確認する必要があります。

くわしくは、CodeZineのこの記事を参照してくださいw。
Silverlightとサーバーサービスの連携

嬉しいことに、日本のamazon のWebサービスがホストされているhttp://ecs.amazonaws.jp/のcrossdomain.xmlを見ると、嬉しいことに次のように全サイトからのアクセスを許可してくれています。

Amazon Web Service の crossdomain.xml (http://ecs.amazonaws.jp/crossdomain.xml

<?xml version="1.0"?> 
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> 
<cross-domain-policy> 
   <allow-access-from domain="*" /> 
</cross-domain-policy> 

2.Amazon AWS Access Keyの取得と確認

Amazon AWSを利用するためには、Amazon AWSのサイトで、自分のAccess key IDとSecret Access keyを確認する必要があります。Amazon AWSのAccess KeyはこのAmason AWSのポータルサイトで確認できるので、自分のAmazonアカウントでログインして確認して下さい。Access key Idが公開鍵で、Secret Access keyが秘密鍵になります。この2つのキーをもとにAWSを使うための署名を作成します。Secret Access keyは外部に漏れないように厳重に管理しましょう。

Amazon AWSのポータルサイト⇒http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key

Amazon AWSに登録していない人や、Amazonのアカウントを持っていない人やはこのあたりのサイトを参考にAmazon AWSのアカウントを取得してください。

Access Key IDの取得

3.書籍検索クエリの作成

Amazon AWSでは書籍の検索に限らず、Amazonで取り扱っている商品の検索や購入をProduct Advertising APIを使うことで行うことができます。今回はとりあえずSilverlightアプリケーションで書籍の検索を行ってみましょう。

書籍の検索は、次のクエリを利用します。(実際は1行です。)

http://ecs.amazonaws.jp/onca/xml?

AWSAccessKeyId=xxx&

Keywords=Silverlight&

Operation=ItemSearch&

ResponseGroup=Small&

SearchIndex=Books&

Service=AWSECommerceService&

Timestamp=2010-12-23T15%3A54%3A24Z&

Version=2009-03-31&

Signature=xxxxx"

リクエストの詳細はこのあたりで確認できます。クエリの詳細はこのあたりを確認して下さい。

⇒  Ajax Tower.jp 商品検索(ItemSearch)

また、Amazon AWSへのリクエスト2009年に改正され、リクエスト中にタイムスタンプや利用者のハッシュした署名を含める必要があります。署名の作成方法は下記のサイトで確認できます。

⇒ Ajax Tower.jp 認証(Timestamp及びSignature)

署名を作成するのは大変ですが、.NETで署名を作成するライブラリが、Amazonのデベロッパーフォーラムで公開されていますので、通常のパッケージに含まれるSignedRequestHelperクラスを使うのがいいでしょう。

Product Advertising API Signed Requests Sample Code – C# REST/QUERY

利用方法はこんな感じです。

var helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION);
var param = new Dictionary<string, String>();
param["Service"] = "AWSECommerceService";
param["Version"] = "2009-03-31";
param["Operation"] = "ItemSearch";
param["Keywords"] = bookName.Text;
param["SearchIndex"] = "Books";
param["ResponseGroup"] = "Small";

var requestUrl = helper.Sign(param);
var request = new WebClient();
request.DownloadStringCompleted += (_s, _e) =>
{
    if (_e.Error != null)
    {
        MessageBox.Show(_e.Error.ToString());
        return;
    }
    MessageBox.Show(e.Result);

};
request.DownloadStringAsync(new Uri(requestUrl));
}

MY_AWS_ACCESS_KEY_IDとMY_AWS_SECRET_KEYには2で確認したキーを指定します。DESTINATIONには日本のAWSを利用するのでJPを指定します。

4.SignedRequestHelperの変更

困ったことに、SignedRequestHelperは.NET Frameworkを対象に書かれているため、Silverlightでは利用できないクラスを使っています。具体的には、SortedDictionaryクラスがそれです。特に、AWSのクエリはハッシュの作成のために、クエリの順番が特に重要なためです。しかし落胆することはありません、問題はキーの順番が保証されてさえいればいいのです。嬉しいことにSilverlightにはLINQがあります。とりあえず順序保証がされないDictonaryにパラメータを放りこんで、ハッシュを作成する時に並び替えてあげればいいのです。

/*
 * Consttuct the canonical query string from the sorted parameter map.
 */
private string ConstructCanonicalQueryString(IDictionary<string, string> sortedParamMap)
{
    StringBuilder builder = new StringBuilder();

    if (sortedParamMap.Count == 0)
    {
        builder.Append("");
        return builder.ToString();
    }

    // Dictionaryは順序保証されていないので、OrderByしてあげる
	// foreach (KeyValuePair<string, string> kvp in sortedParamMap)
    foreach (KeyValuePair<string, string> kvp in sortedParamMap.OrderBy(item => item.Key))
    {
        builder.Append(this.PercentEncodeRfc3986(kvp.Key));
        builder.Append("=");
        builder.Append(this.PercentEncodeRfc3986(kvp.Value));
        builder.Append("&");
    }
    string canonicalString = builder.ToString();
    canonicalString = canonicalString.Substring(0, canonicalString.Length - 1);
    return canonicalString;
}

4.XMLをパースする。

リクエストに対するレスポンスはこんなXMLです。

image

XMLのパースといえば、やっぱりここでも LINQ です。 DataGridに表示するためには、匿名型ではダメなので、検索結果というクラスを作って、パース結果をそのクラスに流し込んでいます。

    // Grid表示
    var xml = XElement.Parse(_e.Result);
    XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2009-03-31";
    var result = from item in xml.Descendants(ns + "Item")
                 select new 検索結果
                 {
                     Asin = item.Element(ns + "ASIN").Value,
                     Url = item.Element(ns + "DetailPageURL").Value,
                     Author = item.Element(ns + "ItemAttributes").Element(ns + "Author").Value,
                     Title = item.Element(ns + "ItemAttributes").Element(ns + "Title").Value,
                 };
    bookList.ItemsSource = result.ToList();

5.動かしてみる。

image

うごいた。後は受け取ったURLに含まれる情報からAmazonのサイトをWebブラウザーコントロールに表示市たいんですが、それはCodeZineの連載を見てください。多分2月ぐらいの回を乞うご期待です。

6.おまけ

とりあえずサンプルプロジェクトはここにおいておきます。

http://cid-b17f5fcd8095628b.office.live.com/self.aspx/%E5%85%AC%E9%96%8B/WebApplication2.zip

 

#追記

通りすがりさんのコメントを参考に、ConstructCanonicalQueryStringメソッドでのソート時に、大文字小文字を区別してソートするようにしました。(2010/12/30)

カテゴリー:Silverlight タグ:

ASP.NETでOracleのSessionStateサーバーを利用する

2010年12月2日 5件のコメント

さてさて、今の会社ではデータベースは基本的にOracleを利用しているということで、ASP.NETのSateServerもたしかOracleが利用できたはずと思ってちょっと調べてみました。

OTNのこのページに載っていますが、ちょとはまったのでメモを。

ORACLE_HOMEはODE.NETをインストールした場所です。各自で読み替えてください。

    SessionStateサーバー用のテーブルの作成

    ASP.NETでSessionStateサーバーを利用するには、兎にも角にもセッション情報を保存するデータストアーが必要です。OracleにSessionState用のテーブルを作るには、OracleHome以下にある次のSQLを実行します。

パス:ORACLE_HOME\ASP.NET\SQL

実行するSQL:

  • InstallOracleASPNETCommon.sql
  • InstallOracleSessionState.sql
    両方共内部で別のSQLを実行するので、sqlplusなんかでカレントディレクトリーをパスのフォルダーに移動させて各SQLを実行しないとSQLがないよっていうエラーがでるので注意しましょう。

ASP.NET側のサービスの有効化

    ASP.NETで上記のテーブルを使ってSessionを保存する場合は、Oracleの機能を有効化擦る必要があります。
    SessionState用のテーブルを作成した時と同様に、コマンドプロンプトから次の設定用のプログラムを実行します。
    パス:ORACLE_HOME\ASP.NET\bin\4\OraProvCfg.exe
    実行するコマンドライン:実際は一行です。

OraProvCfg.exe /action:config /product:aspnet /component:all
/frameworkversion:v4.0.30319
/providerpath:"ORACLE_HOME\ASP.NET\bin\4\Oracle.Web.dll"

    web.configの修正

    さて、準備ができたので、各Webアプリケーションで使えるようにweb.configを修正しましょう。

    修正ポイントはデータベース接続用のconnectionStringとsessionStateの部分です。

    <configuration>
      <connectionStrings>
        <add name="OracleAspNetConString" connectionString="DATA SOURCE=接続文字列" />
      </connectionStrings>
    
      <system.web>
        <sessionState mode="Custom" customProvider="MyOracleSessionStateStore">
            <providers>
              <add name="MyOracleSessionStateStore"
                   type="Oracle.Web.SessionState.OracleSessionStateStore, 
    Oracle.Web, Version=4.112.1.2, Culture=neutral, PublicKeyToken=89b483f429c47342"
                   connectionStringName="OracleAspNetConString"/>
            </providers>
          </sessionState>
      </system.web>
    </configuration>
    
    カテゴリー:ASP.NET タグ: ,