次の方法で共有


Coding4Fun - ASP.NET 2.0 を使用して音楽ライブラリにナビゲートする Web サイトを構築する

ASP.NET 2.0 を使用して音楽ライブラリにナビゲートする Web サイトを構築する

Jeff Key
InRule Technology

はじめに

コンピュータを持っている人のほとんどがメディア ライブラリを持っていることは簡単に想定できます。コンピュータが安価になり、ネットワーク接続や使用が簡単になるにつれて、ホーム ネットワークを作成する人が増えてきます。自宅環境でサーバーを使用することは実際的でないため、情報はネットワーク内の複数のコンピュータに分散されます。これにより、構成の管理と理解が難しくなります。このことは、おそらく音楽ライブラリで最も明白になります。

ほとんどのメディア プレーヤーでは、ユーザーが認識しているすべての曲を含むプライベート データベースが保守されます。これらのデータベースを最新に保つことは 1 台のコンピュータでも面倒な作業であり、複数のコンピュータでデータベースを最新に保つことは不可能に思えることがあります。

一般的な解決策は、すべての音楽ファイルを1台のコンピュータにまとめ、他のコンピュータのメディアライブラリは、すべてこのコンピュータのファイルを参照するように設定し、ファイルの更新はファイルが置かれたコンピュータ上でのみ行うようにする方法です。これを実際に試してみると、十分な解決策ではあるが理想的な解決策ではないことがわかります。筆者個人としては、音楽は管理するものではなく聴くものだと考えています。

筆者の自宅の構成には、テレビを駆動し、すべての音楽が格納されている Windows Media Center コンピュータ、ワークステーション、そしてときにはラップトップが含まれます。Media Center の音楽でワークステーションを最新に保つことは常に厄介な問題であり、これをラップトップで行うことは実際的ではありません。筆者は、ライブラリの正確さを気にすることなく、アパート内のどこででも、どのコンピュータからでも音楽を聴ける単純明快な方法がないものかと考えました。そして、聴きたい音楽をできるだけ簡単に取得することのみを目的とした小さな Web サイトを構築することにしました。Microsoft Visual Web Developer 2005 Express Edition Beta 2 を使用して、これを行いました。

COM: 昔の友人

その昔、COM と呼ばれるテクノロジがありました。このテクノロジでは、現在の .NET と同様に、使用している言語に関係なく (ただし、その言語が COM をサポートしている必要があります)、任意の言語で記述されたライブラリをコードから使用できます。COM はいくつかの問題の解決に大きな功績がありましたが、時間がたつにつれて明らかになった少数の問題ももたらしました。.NET は、これらの問題の多くに対するソリューションです。

何年も前に、Microsoft は COM を介したオペレーティング システムおよびアプリケーション サービスの公開を開始しました。これにより、人々は Microsoft Word、Windows Scripting Host、Windows Media Player、その他多数のものに対してコードを作成できます。COM をサポートする言語やフレームワークなどは多いため、COM は現在も Microsoft の世界の主力であり、Microsoft アプリケーションで .NET サポートよりも前に COM サポートを探すことは一般的です。幸い、.NET は (たいていの場合) COM とうまく対話できます。

サイトでは、Windows Media Player COM コンポーネントを使用してメディア ライブラリにアクセスするため、この歴史的教訓が役に立ちます。COM コンポーネントへの参照の追加は、.NET アセンブリへの参照の追加と同じぐらい簡単です。[Web サイト] メニューをクリックして [参照の追加] を選択し、[参照の追加] ダイアログの [COM] タブをクリックします。

図 1: Windows Media Player COM コンポーネントへの参照を追加する

データベース

WMP ライブラリの情報のサブセットにすばやくアクセスする必要があるため、Artist、Album、Database、Track の各クラスから構成されるメモリ内データベースが必要です。Database クラスには、データベースに Refresh メソッドの呼び出しを挿入する静的 (または Visual Basic の共有) コンストラクタがあります。

    Private Shared Sub Refresh()
        Dim wmp As WindowsMediaPlayer = New WindowsMediaPlayer
        Dim playlist As IWMPPlaylist = wmp.mediaCollection.getAll()
        Dim artistDictionary As Dictionary(Of String, Artist) = _
New Dictionary(Of String, Artist) For i As Integer = 0 To playlist.count - 1 Dim media As IWMPMedia = playlist.Item(i) Dim albumArtistName As String = media.getItemInfo("AlbumArtist") Dim albumName As String = media.getItemInfo("Album") Dim trackName As String = media.getItemInfo("Title") Dim trackLocation As String = media.getItemInfo("SourceUrl") Dim trackNumberString As String = media.getItemInfo("OriginalIndex") Dim theArtist As Artist Dim artistSortName As String = Artist.GetSortName(albumArtistName) If Not artistDictionary.TryGetValue(artistSortName, theArtist) Then theArtist = New Artist(albumArtistName) artistDictionary.Add(artistSortName, theArtist) End If Dim theAlbum As Album If Not theArtist.Albums.TryGetValue(albumName, theAlbum) Then theAlbum = New Album(albumName, theArtist) theArtist.Albums.Add(albumName, theAlbum) End If Dim theTrack As Track If Not theAlbum.Tracks.TryGetValue(trackName, theTrack) Then Dim trackNumber As Integer If Integer.TryParse(trackNumberString, trackNumber) Then theTrack = New Track(trackNumber, trackName, trackLocation) Else theTrack = New Track(trackName, trackLocation) End If theTrack.Album = theAlbum theAlbum.Tracks.Add(trackName, theTrack) End If Next ArtistList.AddRange(artistDictionary.Values) ArtistList.Sort() End Sub

WMP ライブラリはメディア項目のフラット構造体であるため、データベースへのデータ挿入時に Artist -> Albums -> Tracks 階層を手動で作成する必要があります。WMP ライブラリへのアクセスは簡単です。getAll メソッドを WindowsMediaPlayer オブジェクトの mediaCollection プロパティから呼び出すと、IWMPPlaylist インターフェイスで表されるライブラリ内のすべての項目の再生リストが返されます。(IWMPMedia インターフェイスで表される) 各メディア項目からの必要な情報の収集は簡単であり、IWMPMedia の getItemInfo メソッドを呼び出すことで行います。メディア項目に関するすべての関連情報が収集された後で、アーティスト、アルバム、およびトラック オブジェクトを取得または作成する必要があります。

再生リストの反復中に汎用の Dictionary オブジェクトを使用してアーティストを格納した点に注意してください。数万の項目から構成されるメディア コレクションもあるため、ArtistList ですべてのメディア項目を総当たり検索する必要があると、既に低速な動作がさらに低速になります (Dictionary オブジェクトの方が高速な理由については、この記事では説明しません。データ構造に関する Scott Mitchell 氏によるすばらしい一連の記事 (英語) で、Dictionary と同等の非汎用的な Hashtable クラスの説明を参照してください)。

Web サイトをデザインする

できるだけ単純にするために、アーティスト一覧、アーティストのアルバム一覧、アルバムという 3 つのページを作成します。

図 2: アーティスト一覧ページ

アーティスト一覧ページ (図 2) には、一度に 10 グループのアーティストが表示されます。グループをクリックすると、そのグループがページに表示されます。ユーザーは目的のアーティストが見つかるまでこれを繰り返します。

図 3: アルバム一覧ページ

アルバム ページ (図 3) には、1 アーティストのすべてのアルバムがアートを含めて表示されます。アルバム名をクリックするとアルバム ページに移動し、再生ボタンをクリックすると、アルバム内のトラックを含む再生リストが Windows Media Player で起動します。

図 4: アルバム ページ

アルバム ページには、大きなアルバム アートとトラックが表示されます。ユーザーは、アルバム全体または個々のトラックを再生できます。

各ページは、オブジェクト データ バインディングを使用し、共通マスタ ページを共有します。ASP.NET には、マスタ ページという新しい機能があります。この機能により、Web サイトに対して一貫性のあるレイアウトを作成し、すべてのページに対して複製せずに共通機能を共有できます。この Web サイトでマスタ ページを使用して、カスケード スタイル シート (CSS) 情報を共有し、"階層リンク バー" (ASP.NET では SiteMapPath と呼ばれています) をホスティングします。SiteMapPath コントロールについては説明しませんが、その実装は、静的な定義ではなくプログラムによって駆動する必要のある人にとっては興味深いものです。マスタ ページの詳細については、Fritz Onion 氏の「Master Your Site Design with Visual Inheritance and Page Templates」 (英語) を参照してください。

アルバム一覧ページとアルバムページには、画像と、Windows Media Player 再生リスト ファイルを起動する再生ボタンもあります。アルバム画像は Web アクセス可能なディレクトリにキャッシュされ、WMP 再生リスト ファイルは HTTP ハンドラでその場で生成されます。この両方のトピックについては後述します。

オブジェクトをグリッドにバインドする

新しい ObjectDataSource は、グリッドとオブジェクトの仲介を行います。これは、クラスと連携してグリッドから要求されたデータを返すように構成する必要があります。アルバム一覧ページのデータ ソースの作成について、詳細に説明します。

図 5: ObjectDataSource ウィザード、最初のページ

"Binder" クラスがビジネス オブジェクトとして選択されています。アーティストなどを表すために使用されるオブジェクトは単純で、主にデータを表現するため、永続メソッドに関して多くの情報は持っていません。Binder クラスは、オブジェクト自体によっても処理できる機能を提供します。

図 6: ObjectDataSource ウィザード、第 2 ページ

次のページでは、データを取得するために使用するメソッドを選択するため、GetAlbums を選択します。Update、Insert、Delete の各メソッドを指定することもできますが、必須ではありません。

図 7: ObjectDataSource ウィザード、第 3 ページ

最後のページでは、パラメータ値の取得元を指定します。値は、Cookie、Control、Form、Profile、QueryString、および Session から取得できます。

データ ソースの構成後に行う必要があるのは、これをグリッドの DataSource として設定することだけです。

カスタム グリッド値を生成する

すべてのグリッドは、テンプレート列およびカスタム コードを使用してハイパーリンクを生成します。たとえば、アーティスト一覧ページ (図 2) は、RangeListItems の汎用リストにバインドされる単一の GridView から構成されます。RangeListItem は、範囲内の最初と最後のアーティスト ID と、グリッドに表示されるテキストを表すカスタム クラスです。ハイパーリンクは、グリッド内のテンプレート フィールドを使用し、ページのサーバー側スクリプト セクション内のメソッドを呼び出すことで作成されます。

図 8: アーティスト一覧ページのテンプレート フィールド列

NavigateUrl 属性は、CreateNavigateUrl メソッドを呼び出すことで設定されます。

    Private Function CreateNavigateUrl(ByVal o As Object) As String
        Dim listItem As RangeListItem = CType(o, RangeListItem)
        
        If (listItem.StartIndex = listItem.EndIndex) Then
            Return "albums.aspx?artist=" & listItem.StartIndex
        Else
            Return String.Format("default.aspx?start={0}&end={1}", _
listItem.StartIndex, listItem.EndIndex) End If End Function
ASP.NET 2.0 のテンプレートの詳細については、Dino Esposito 氏の「Move Over DataGrid, There's a New Grid in Town!」 (英語) を参照してください。

アルバム アートを表示する

Windows Media Player では、可能であれば大きなバージョンと小さなバージョンの両方のアルバム アートがダウンロードされます。これらの画像ファイルは、アルバム トラックと同じフォルダに格納されますが、既定では非表示になります。これらの画像は、ブラウザでアクセスできるように Web サイトの子フォルダにコピーする必要があります。この処理は、最初に適切な画像を検索してから、Web サイトのサブフォルダにそのファイルをコピーし、次に必要になったときに簡単に取得できるように一意の名前を付けることで行います。アルバム アートが見つからない場合は、小さな透明の画像が代わりに使用されます。

    Public Function GetAlbumArtUrl(ByVal size As AlbumArtSize) As String
        Dim albumArtFileName As String = GetCustomAlbumArtFileName(size)
        Dim albumArtFullFilename As String = _
Path.Combine(_albumArtDirectory, albumArtFileName) Dim fileExists As Boolean = File.Exists(albumArtFullFilename) If Not fileExists Then Dim dir As String = Path.GetDirectoryName(Tracks(0).Location) Dim filename As String If Directory.Exists(dir) Then filename = GetRealAlbumArtFileName(dir, size) If Not filename Is Nothing Then File.Copy(filename, albumArtFullFilename) File.SetAttributes(albumArtFullFilename, FileAttributes.Normal) fileExists = True End If End If End If Dim url As String If fileExists Then url = "/coding4fun/images/coolapps/musiclib/AlbumArt/" & _
albumArtFileName Else url = "/coding4fun/images/coolapps/musiclib/dot.gif" End If Return url End Function

GetCustomAlbumArtFileName メソッドでは、画像のコピーを保存し、それ以後の要求で簡単に取得できるようにするために使用する一貫性のある名前が作成されます。

    Private Function GetCustomAlbumArtFileName(ByVal size As AlbumArtSize) As String
        Return String.Format("{0}-{1}-{2}.jpg", _
GetAlphanumericString(_artist.Name), _
GetAlphanumericString(Name), size.ToString) End Function Private Function GetAlphanumericString(ByVal s As String) As String Dim sb As StringBuilder = New StringBuilder For Each c As Char In s If Char.IsLetterOrDigit(c) Then sb.Append(c) End If Next Return sb.ToString() End Function
さらに興味深いものにするために、アルバム アートのファイル名を次のいずれかのパターンと一致させることができます。

AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Large.jpg
AlbumArt__Large.jpg
Folder.jpg
AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Small.jpg
AlbumArt__Small.jpg
AlbumArtSmall.jpg

GetRealAlbumArtFileName は、アルバム アートが存在する場合に、前述のパターンを検索することでそのファイル名を取得します。

    Private Function GetRealAlbumArtFileName(ByVal dir As String, _
ByVal size As AlbumArtSize) As String Dim filename As String = Nothing Dim filenames() As String = Directory.GetFiles(dir, "AlbumArt*" & _
size.ToString() & ".jpg")
    If (filenames.Length > 0) Then
        filename = filenames(0)
    ElseIf (size = AlbumArtSize.Large) Then
        Dim file As FileInfo = New FileInfo(Path.Combine(dir, "Folder.jpg"))
        If file.Exists Then
            filename = file.FullName
        End If
    End If

    Return filename
End Function

再生リストを作成する

再生リストは、PlaylistCreator クラスによって作成されます。このクラスは、IHttpHandler インターフェイス (英語) を実装する軽量の Web 要求ハンドラです。ハンドラはかなり単純です。ヘッダーを書き出し、返されるコンテンツのタイプが "video/x-ms-asf" であることをブラウザに通知し、XML 再生リストを作成して返します。コンテンツ タイプ ヘッダーは、コンテンツに対して何を行うかをブラウザが判断するのに役立ちます。ここではブラウザに表示せずに再生リストを再生するため、これは非常に重要な手順です。

PlaylistCreator は、XmlTextWriter で応答ストリームに直接書き込みます。

    Public Sub ProcessRequest(ByVal context As HttpContext) _
Implements IHttpHandler.ProcessRequest _trackIndex = QueryStringHelper.TrackIndex _artistIndex = QueryStringHelper.ArtistIndex _albumIndex = QueryStringHelper.AlbumIndex
    context.Response.ContentType = "video/x-ms-asf"

    Dim streamWriter As StreamWriter = _<br />          New StreamWriter(context.Response.OutputStream)
    _writer = New XmlTextWriter(streamWriter)
    _writer.WriteProcessingInstruction("wpl", "version=\""1.0\""")

    _writer.WriteStartElement("smil")

    CreateHead()

    _writer.WriteStartElement("body")
    _writer.WriteStartElement("seq")

    If _trackIndex.HasValue Then
        CreateTrackEntry()
    ElseIf _albumIndex.HasValue Then
        CreateAlbumEntries()
    End If

    _writer.WriteEndElement()
    _writer.WriteEndElement()
    _writer.WriteEndElement()

    _writer.Close()
End Sub

再生リスト作成機能の登録

ASP.NET は、ASPX ページへの要求の処理方法を認識しています。その方法とは、同じ名前のページを検索し、実行することです。HTTP ハンドラは、少々異なる方法で処理されます。エントリを web.config ファイルに作成して、ファイル名またはパターンを要求の処理に使用するオブジェクトの種類に関連付ける必要があります。次に示す XML 断片は、web.config ファイルの configuration/system.web セクションから抽出したものです。この断片は、"wpl" 拡張子を持つ要求を "PlaylistCreator" タイプのオブジェクトに送るよう ASP.NET に指示します。

     <httpHandlers>
          <add path="*.wpl" type="PlaylistCreator" verb="*" validate="false" />
     </httpHandlers>
Jeff Key は、.NET ビジネス ルール エンジン テクノロジのリーダーである InRule Technology (英語) の設計者です。Jeff は、ファット クライアントから CGI、MTS/COM+、および ASP、またベータ時代からのあらゆる種類の .NET まで、Microsoft プラットフォームの進化と共に専門家としての道を歩んできました。Jeff には、彼の Web サイト (英語) から連絡することができます。