読者です 読者をやめる 読者になる 読者になる

書籍をレビュー数でランキング表示するWebサービスを作ってみた

本をレビュー数でランキング表示するWebサービスぶくれび」を日曜プログラミングで作ってみました。

http://laboone.net/book

1月から作り始めて、約2ヶ月かかりました。
初めてWebサービスを作ってみましたが、Web力がなさすぎて苦労しました。

Webサービスを作った背景

色々なWebサービスを個人が作っているのを見て、何か作って公開したいなと常々思っていたけど、作るもののネタがなかった。
ふと、Amazonの本やDVDをレビュー数でソートして表示できればよいのにと思い、AmazonAPIを見てみるとレビュー数でのソート表示に対応していないことを知る。
楽天ブックスAPIであればレビュー数での表示ができることがわかったので、じゃ、ちょっと作ってみようと思った。

使った技術

言語はC#
WebフレームワークはASP.NET MVC 3。
ORMにはDapper
ASP.NET MVCもDapperも今回初めて使ってみた。
最初はPerl+Mojoliciousで作ってたけど、最近C#を使っていたこともあり、VisualStudioのもろもろの楽さに惹かれてC#で書き直した。

CSSフレームワークは、Bootstrap
BootstrapはWebのデザインとか全くできない僕でもちゃんとしたレイアウトができるので素敵です。
それでも、ちょっとした変更をしたい場合などにCSS力なさすぎて苦労しましたが。

公開にあたって

ExpressWebレンタルサーバを借りて、お名前.comドメインを取りました。
ExpressWebはWindowsレンタルサーバで、シェルログインがなく、すべてWebのコントロールパネルからの設定なので、最初は何がどこにあるのかわからなくてちょっとつまる。
このあたりは、シェルログインが使えるLinuxレンタルサーバがよいなと思いました。
LinuxレンタルサーバにMono(Xamarin)を載せて動かしてみようかとも少し思ったけど、色々と調べたりしないといけなさそうなので今回は断念。
さくっと使えるもんなんでしょうか。

これから

せっかくレンタルサーバ借りて、ドメインも取ってみたので、他にも何か作っていきたいところ。
とりあえずDVDのレビュー数ランキングは、今のを少し変えればできるので近日中に作る予定。
モチベーションが続くかどうかが怪しいけど。
というか、これぐらいの簡単なWebサービスを最初に作るものとして選んで良かった。*1
これが今回一番大きいかも。

ってことで、書籍レビュー数ランキング ぶくれび使ってみてください!

*1:楽天ブックスAPIでレビュー数ソートで書籍データを取ってきた段階で、もはやモチベーションを失いかけた。

hudsonでMSTestのテスト結果とカバレッジをレポートする設定をしてみた。

こんな感じでhudsonに表示される。


手順は以下。自分用のメモなので雑です!
バッチファイルを作って、hudsonから叩く。

過去の結果があれば削除
del MsTestSample\TestResults\TestResults.trx rmdir /S /Q MsTestSample\TestResults\TestResult
テスト実行
"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\MSTest.exe" /runconfig:MsTestSample\LocalTestRun.testrunconfig /testcontainer:MsTestSample\TestMsTestSample\bin\Debug\TestMsTestSample.dll /resultsfile:MsTestSample\TestResults\TestResults.trx
カバレッジを出力(カバレッジxmlデータに変換)
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe MsTestSample\GenerateCoverage.build
カバレッジを出力(xmlデータをemma用に変換)
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe MsTestSample\ConvertToEmma.build

カバレッジデータはバイナリなので、xmlに変換して、それをさらにEmmaデータに変換してる。
hudsonからはMSTest PluginとEmma Pluginを入れておけばレポートが見える。
テスト結果は、デフォルトではテスト実行の日時が入るので、.testrunconfigにテスト結果のファイル名を固定にするように別途設定が必要。
カバレッジをとる設定(インストルメント化する項目の設定)も.testrunconfigに設定しておく。
カバレッジを取る設定は、プロジェクトを増やすたびに手動で設定しないといけない。(追加するスクリプト書いてもよいけど。)
あと、テストは、ひとつのテストプロジェクトに突っ込んでおいたほうが自動テストするときにやりやすいかなと思った。
テストをプロジェクト毎にわけると、MSTest.exeの実行時にtestcontainerオプションで、そのプロジェクト(dll)を指定しないといけないので。
テストプロジェクトが膨れ上がって、テストプロジェクトのビルドが遅くなるという欠点はあるかも。


GenerateCoverage.buildの内容は、以下のとおり。



    

    
        
        
    

使っているdllは、以下からダウンロードできる。

http://code.msdn.microsoft.com/vscoveragetoxmltask


ConvertToEmma.buildの内容は、以下のとおり。



    
     "C:\Program Files\msxsl\msxsl.exe"
    

    
      
    


msxslとmsxmlのダウンロードが必要。
MSTestCoverageToEmma.xslは、以下からダウンロードできる。

http://wiki.hudson-ci.org/pages/viewpageattachments.action?pageId=41878013&metadataLink=true



参考サイト:
http://wiki.hudson-ci.org/display/HUDSON/MSTest+Coverage+Reports

ADO.NET Data ServiceをSilverlightから使ってみた

簡単にサーバからデータを取得できるので、とっても便利。
サーバ側の実装工数をかなり削減できそうな雰囲気。

以下は、ADO.NET Data Servicesを触ってみた感じの疑問点と回答。
回答は適当に書いているので間違ってる可能性あり。

Q:
特定のユーザからのデータ操作を許可したりしなかったりはできるか
A:
インターセプターという概念があり、これを使ったらできそう
(http://msdn.microsoft.com/ja-jp/library/dd552872.aspx)

Q:
クライアントのデータクラスのカスタマイズはできるか。
例えば、画面にバインドさせていたあるオブジェクトのメンバの値が変更されれば、別のメンバの値も変更するとか。
A:
サービスの参照で自動生成されるデータクラスは、メンバ名_Changign()とメンバ名_Changed()というPartialメソッドを持ってる。
これを実装すればできそう。

ちなみにADO.NET Data Servicesは、WCF Data Serviceに名前が変わるようだ。
WCF RIA Serviceとか.NET RIA Servicesとか似たような名前がいっぱいあって混乱中・・・

testlinkと複数tracサイトとの連携

Testlinkでテストプロジェクト毎に別のTracサイトを指定できるようにTestlinkのコードを修正してみたので、メモ代わりに修正箇所を記録しておきます。(TestlinkのVersionは、1.7.4です。)

install_dir/lib/bugtracking/int_trac.phpのcheckConnectionViaXmarpc()に次のような箇所があります。

$this->m_dbHost = BUG_TRACK_DB_HOST . $tracProjectName;

これを

$this->m_dbHost = $tracProjectName;

に修正して、install_dir/cfg/trac.cfg.phpの$g_interface_bugs_project_name_mappingの設定をTracプロジェクトの名前ではなく、URLで指定します。
これまでは以下のようにテストプロジェクト毎にTracのプロジェクト名を指定していました。

/** Mapping TL test project name vs trac project url */
$g_interface_bugs_project_name_mapping = array(
    'testlinkProject' => 'tracProject',
    '<YourTLTestProjectName2>' => '<YourTracProject2>',
);

これを次のようにTracのURLを指定するようにします。

/** Mapping TL test project name vs trac project url */
$g_interface_bugs_project_name_mapping = array(
    'testlinkProject1' => 'http://localhost/tracProject1',
    'testlinkProject2' => 'http://localhost:8080/tracProject2',
    '<YourTLTestProjectName2>' => '<YourTracProject2>',
);


これで、Testlinkのプロジェクト毎に別のTracサイトを指定することができます。

Testlinkでテストケースをユーザに一括アサインするスクリプトを書いてみた

Testlinkでテストケースをユーザにアサインするのがとても面倒だったので、一括アサインするGreasemonkeyスクリプトを書いてみた。
インストールすると、一番上の実行ボタンの横にAll Asignボタンが表示されてクリックすると、横のプルダウンメニューで選ばれたユーザが下の全てのテストケースにアサインされます。

今使っているTestlinkはver1.7.4だから、最新のTestlinkでは、このあたりのUIは改善されているかもしれないけど。
1.9のβもリリースされてるっぽいし、ちょっと今度さわってみなきゃ。

ってことで、書いたスクリプトは↓
javascripterの人はきっとxpathとかで華麗に書くんだろうけど、うまくいかなかったので、地道にやりました(汗
相変わらず汚くてすみません。

// ==UserScript==
// @name           testlink_assign_user
// @namespace      http://d.hatena.ne.jp/iox
// @description    Assign selected user to all testcase.
// @include        http://*/testlink/*
// ==/UserScript==

h1element = document.getElementsByTagName('h1');
if(h1element.length == 0)
{
	return;
}

if(h1element[0].innerHTML.match(/テストケースに実行タスクをアサインするテスト計画/))
{
	create_assign_button();
}

function create_assign_button()
{
	// 一番上の実行ボタンを検索
	var all_input_tags = document.getElementsByTagName('input');
	var exec_button;
	for(var i in all_input_tags)
	{
		if(all_input_tags[i].value == "実行")
		{
			exec_button = all_input_tags[i];
			break;
		}
	}

	// ボタン作成
	var assign_button = document.createElement('input');
	assign_button.setAttribute('type', 'button');
	assign_button.setAttribute('value', 'All Asign');

	assign_button.addEventListener('click', function() {

		// 選択されているユーザ情報を取得
		var select_user = exec_button.previousSibling.previousSibling;
		var index = select_user.selectedIndex;
		var selected_value = select_user.options[index].value;
		var selected_user = select_user.options[index].label;

		// 全てのプルダウンメニューに一番上のプルダウンメニューで選択
		// したユーザを設定
		var all_selects = document.getElementsByTagName('select');
		for(var j in all_selects)
		{
			var options = all_selects[j].options;

			for(var k in options)
			{
				if(options[k].value == select_user.value)
				{
					options[k].selected = true;
					break;
				}
			}
		}
    }, false);

	// ボタン配置
	exec_button.parentNode.insertBefore(assign_button, exec_button.nextSibling);
}

MSBuildでプロジェクト発行(昨日の続き)

subversionで更新→AssemblyInfoの更新→Configの更新→ビルド→発行をするMSBuildスクリプトができた。ちょっときたないスクリプトだけど、MSBuildが大体わかってきた。

設定ファイルの更新は、XmlUpdateを使いましたが、XmlMassUpdate*1も便利そうです。

<?xml version="1.0" encoding="utf-8"?>

<Project DefaultTargets="Run" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
  <!--<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets"/>-->
  <!--<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />-->

  <PropertyGroup>
    <SvnPath>E:/TracLight/subversion/bin</SvnPath>
    <Major>2</Major>
    <Minor>0</Minor>
    <Build>0</Build>
    <Revision>0</Revision>
    <OutputFolder>E:/temp/msbuildTest</OutputFolder>
  </PropertyGroup>

  <Target Name="Run">
    <CallTarget Targets="SvnUpdate"/>
    <CallTarget Targets="AssemblyInfo"/>
    <CallTarget Targets="Config"/>
    <CallTarget Targets="Build"/>
    <CallTarget Targets="Publish"/>
  </Target>

  <Target Name="SvnUpdate">
    <SvnUpdate LocalPath="$(MSBuildProjectDirectory)">
      <Output TaskParameter="Revision" PropertyName="Revision" />
    </SvnUpdate>
  </Target>

  <Target Name="AssemblyInfo">
    <Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)"/>
    <AssemblyInfo CodeLanguage="CS"
      OutputFile="./MSBuildTest/Properties/AssemblyInfo.cs"
      AssemblyTitle="MSBuild Community Tasks"
      AssemblyDescription="Collection MSBuild Tasks"
      AssemblyCompany="http://example.org"
      AssemblyProduct="MSBuild.Community.Tasks"
      AssemblyCopyright="Copyright"
      ComVisible="false"
      CLSCompliant="true"
      Guid="Guid"
      AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
      AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"
      Condition="$(Revision) != '0' "/>
  </Target>

  <Target Name="Config">
    <XmlUpdate
       XmlFileName="./MSBuildTest/ServiceReferences.ClientConfig"
       Xpath="//configuration/system.serviceModel/client/endpoint/@address"
       Value="localhost"/>
    <XmlUpdate
       XmlFileName="./MSBuildTest/ServiceReferences.ClientConfig"
       Xpath="//configuration/appSettings/add[@key='HogeHoge']/@value"
       Value="aaabbbccc"/>
  </Target>

  <Target Name="Build">
    <MSBuild Projects="./MSBuildTest.sln"
             Properties="Configuration=Release;"/>
  </Target>

  <Target Name="Publish">
    <RemoveDir Directories="$(OutputFolder)"
               ContinueOnError="true" />
    <MSBuild Projects="./MSBuildTest.Web/MSBuildTest.Web.csproj"
             Targets="ResolveReferences;_CopyWebApplication"
             Properties="Configuration=Release;
                         WebProjectOutputDir=$(OutputFolder);
                         OutDir=$(OutputFolder)/"/>
  </Target>
</Project>