PC から iPhone 専用サイトを覗く方法(User-Agentの偽装)

端的に言えば「User-Agent」を偽装します、ということです。
User-Agent というのは、HTTP プロトコルのヘッダ部に設定してある「今、私はこのブラウザを使っています」という印ですね。サーバーのほうで、携帯電話からなのか、PC からなのか、iPhone/iPad からなのか、Android からなのか、ということが分かります。

と、云いますか、当然これは「印(ルール)」でしかなくって、WebClinet クラスなり、Firefox のプラグインを利用するすれば、簡単に自分が何者かを偽装するこができます(「詐称」という言葉を使っているところもあるけど、技術的にすり替えるだけなので「偽装」のほうがよいかなと)。

さて、なぜ iphone 専用のサイトを pc から覗く必要があるかというと、hon.jp の api を利用して amazon 風のブログパーツを作りたいからなのです。amazon ブログパーツは、書籍の画像を返してくれるのですが、hon.jp api は、画像を返してくれません。hon.jp の場合、タイトル検索などのテキスト検索が主なので、電子書籍の画像は各社のサイトの中にしかありません。pc 用の電子書籍がある場合は、pc からキャッシュ的に取得すればよいのですが、iphone 用しかない場合には、iphone 用のページにアクセスする必要があります。なので、画像を取得するためには iphone であると偽装して画像用の url を取得しないと駄目なのです。

と前置きはそれくらいにして、ざっとこんな感じ。

private void button2_Click(object sender, EventArgs e)
{
	// url を取得
	string url = textBox1.Text;
	WebClientEx client = new WebClientEx();
	// cookie を設定
	client.Cookie = new CookieContainer();
	client.Headers.Add("User-Agent", "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_4 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8K2 Safari/6533.18.5");
	// sjis で全て読み込み
	StreamReader sr = new StreamReader(client.OpenRead(url), Encoding.GetEncoding("shift_jis"));
	string content = sr.ReadToEnd();
	sr.Close();
	// ファイルに出力
	StreamWriter sw = new StreamWriter("temp.html", false, Encoding.GetEncoding("shift_jis"));
	sw.Write(content);
	sw.Close();

	// ブラウザコントロールで表示
	webBrowser1.Navigate("file://" + @"D:\work\blog\src\SampleUserAgent\SampleUserAgent\bin\Debug\"+ "temp.html");
	// テキストボックスでも表示
	textBox2.Text = content;

}

文字コードが sjis になっているのは、電子文庫パブが sjis で返すからです。本来は charset を見てコードを判別しないと駄目ですね。また、電子文庫パブの場合は、cookie が必要になるのでこれも設定してます。
Cookie 自体は、内部の HttpWebRequest を弄る必要があるので、こんな風に継承したクラスを作ります(という例があった)。

class WebClientEx : WebClient
{
	public CookieContainer Cookie { get; set; }
	protected override WebRequest GetWebRequest(Uri address)
	{
		HttpWebRequest wreq = base.GetWebRequest(address) as HttpWebRequest;
		wreq.CookieContainer = this.Cookie;
		return wreq;
	}
}

実行するとこんな感じで取得できます。

画像が表示されないのは、img タグの src 相対パスになっているからです。
保存した html ファイルを開いて を追加すると、こんな感じで表示できます。

これで画像の url が分かるので、ブログパーツを表示する時に画像が表示できますね。
画像 url が変わる可能性もあるのでキャッシュしてもいいのですが、まぁ、amazon と同じで画像ファイル名はほとんど変わらないのではないかなと。これは、いくつかの電子書籍の会社を探ってから考えましょう。

■参考
userAgent一覧/ユーザーエージェント一覧
http://www.openspc2.org/userAgent/

カテゴリー: C# | PC から iPhone 専用サイトを覗く方法(User-Agentの偽装) はコメントを受け付けていません

UILabelやUIImageViewのタッチイベントを取得する

iPhone プログラミングの中で、ボタンのクリックイベントは簡単に取れるのですが、ラベルや画像のクリックイベントが手軽に取れません。いくつか調べると、UITapGestureRecognizer を使うか、touchesEnded メソッドをオーバーライドするか、という方法があるのですが手軽でもないので。

iphone – How can I determine if a UILabel was touched? – Stack Overflow
http://stackoverflow.com/questions/2539380/how-can-i-determine-if-a-uilabel-was-touched
UILabelのタッチイベントを検出する方法 ? 拡張現実ライフ
http://akio0911.net/archives/3419

どうやら、tag を使うと一番手軽そうなので紹介しておきます。

最初に、viewDidLoad の中で tag を設定しておきます。userInteractionEnabled プロパティの値を YES にしておかないとイベントが発生しなくなるので注意してください(何故イベントが発生しないのかは不明)

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    labelCommand.userInteractionEnabled = YES;
    labelCommand.tag = 100;
    imageLogo.userInteractionEnabled = YES;
    imageLogo.tag = 101;
}

「100」とか「101」とかは、適当な値で十分です(0は初期値なので駄目)。
#define しても良いのですが、使い捨てなのでそのまま。

touchesBegan イベントをオーバーライドします。ViewController 上にあるイベントをタッチイベントを全てフックするので、これから目的のオブジェクトを探し出します。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    if ( touch.view.tag == labelCommand.tag )
        [self clickCommand:labelCommand];
    else if ( touch.view.tag == imageLogo.tag )
        [self clickLogo:imageLogo];
}

ここで、touch.view.tag と、ラベルの tag の値とを比較します。本来は switch で比較するのが良いのでしょうが、面倒なので(苦笑)そのまま tag の値と比較しています。実は、こうすると #define が必要なくなるのです。

イベントは、ボタンのクリックイベントのように書きたいため、ViewController 上で定義した clickCommand を呼び出します。一応、sender はラベルや画像オブジェクトそのもの渡します。

-(IBAction)clickCommand:(id)sender 
{
    NSLog(@"in clickCommand");

}
-(IBAction)clickLogo:(id)sender 
{
    NSLog(@"in clickLogo");
}

こんな風に、あたかもボタンと同じようにしておきます。こうすると、クリックイベントみたいで分かり易いですよね。

実行すると、普通のボタンと同じようにログが出力されます。

カテゴリー: Objective-C | 6件のコメント

LinqToTwitter を使う

ツイートキャッチ★★★を LINQ to Twitter を使うように書き換え中…と言いますか、なんか、LINQ to Twitter って、内部で Invoke をしているらしく、サーバーエラー時の挙動がおかしいんですよね。なので、Twitter が不安定な時の対処が難しい、というのが前置きなのですが。

とは云え、使い方は結構簡単です。LINQ を使って検索するので、検索系はOKかと。更新系はどうなのかいまいち確認してないのでツイート専用のほうは、TwiLib を使ったままなんですが。

例えば、ログインしている人のツイート数を取得するのは、こんなに簡単に書けます。twitter が返してくる xml の要素名と、linq to twitter で定義しているプロパティ名との「ずれ」が、いまいち分かりづらいのですが、慣れればなんとか。

// ツイート数を取得
var ctx = new TwitterContext(this.auth);
var user = (from t in ctx.User 
			where t.Type == UserType.Show &&
			t.ScreenName == username
			select t).First();
this.profile_image_url = user.ProfileImageUrl;
this.tweet_count = user.StatusesCount;

で、具体的に oauth 認証をどうするかというと、ツイートキャッチでは次のようにしています。

if (accessToken == "")
{
	auth = new PinAuthorizer();
	auth.Credentials = new InMemoryCredentials {
		ConsumerKey = this.consumerKey,
		ConsumerSecret = this.consumerSecret 
	};
	auth.UseCompression = false;
	auth.GoToTwitterAuthorization = link => {
		Process.Start(link);
	};
	auth.GetPin = () =>
	{
		FormAuth frm = new FormAuth();
		frm.ShowDialog();
		return frm.PinCode;
	};
	auth.Authorize();
	this.SavaAuth();
	return;
}
auth = new PinAuthorizer();
auth.Credentials = new InMemoryCredentials
{
	ConsumerKey = this.consumerKey,
	ConsumerSecret = this.consumerSecret
};
auth.OAuthTwitter.OAuthToken = this.accessToken;
auth.OAuthTwitter.OAuthTokenSecret = this.accessSecret;

oauth 認証を使う時のキーワードとして、

  • アプリケーションが使う consumerKey
  • アプリケーションが使う consumerSecret

のセットと

  • 認証後に使う accessToken
  • 認証後に使う accessSecret

の4つのキーワードがあります。

手順としては、

  1. アプリケーションを作るときに consumerKey と consumerSecret を取得(アプリに書き込む、app.configとか)
  2. ユーザーが初回実行時に consumerKey と consumerSecret を使って pincode を得る。
  3. pincode を使って、accessToken と accessSecret のセットを得る。
  4. web ならば、ユーザー名と紐づけて保持、winodws アプリならばレジストリとかに保存

  5. 次回以降は、consumerKey, consumerSecret, accessToken, accessSecret の 4つがあれば ok

という具合です。

なので、ワンクッションだけユーザーに pincode を入力して貰う必要があるのです。まぁ、これが認証ということです。勿論、内部ブラウザで表示させたりして、pincode を自動入力させることも可能ですが、手入力でも対して手間じゃないのでそのまま。

で、linq to twitter はこの pincode を入力するとこがややこしくて、下記のように GetPin にコールバックを登録させます。

	auth.GetPin = () =>
	{
		FormAuth frm = new FormAuth();
		frm.ShowDialog();
		return frm.PinCode;
	};

どうしようかと悩んだのですが、ツイートキャッチ★★★では、別のダイアログを出して対処をするようにしました。

linq は検索のほうを得意とするので、直近のツイートを取得なんてのは、こんな風に書けます。

/// <summary>
/// ツイートを取得
/// </summary>
/// <param name="sname"></param>
/// <param name="max_id"></param>
/// <returns></returns>
private List<Status> GetTweets(string sname, string max_id = "")
{
	var ctx = new TwitterContext(this.auth);
	var items = from t in ctx.Status
				where t.Type == StatusType.User
				&& t.ScreenName == username
				&& t.Count == MAX_TWEET
				&& t.MaxID == max_id
				select t;
	var lst = items.ToList<Status>();
	return lst;
}

# MaxID はクローリングのために使っているのですが、元のソースは int 型になっているのでエラーになります。ソースを string 型に適当に直しています。現在、twitter id は intの範囲を超えてしまっているので、string で保持するのがよいでしょう。

実は、最大の難関がサーバーエラーの対処なのですが、そのままではうまく動かないので諦めました。
なので、試しに oauth 認証の部分だけ linq to twitter のコードを使って、戻されてきた xml データは自前で解析するというのを試してみました。この話しは別の記事で。

カテゴリー: C# | LinqToTwitter を使う はコメントを受け付けていません

inline php を使う

WordPress ? Inline PHP ≪ WordPress Plugins
http://wordpress.org/extend/plugins/inline-php/

先日 wordpres 上で javascript が動作するプラグインを入れたのですが、php のコードを直接動かすことも可能なのですね。
公開する場合には、うかつな php コードを入れるとセキュリティ上危ないのでお勧めできませんが、まぁ、試しにいれるのも良いかなと。

 [ exec]
 echo "テスト表示";
 [ /exec]

のように簡単に書けますということで。

で、プログインのソースコードを見ていたのですが、実は大したことはやってないのですね。数行だけなのです。なるほど、こういう風に拡張するのかという。

[exec]
function curl_get_contents( $url, $timeout = 60 ){
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_HEADER, false );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
$result = curl_exec( $ch );
curl_close( $ch );
return $result;
}
// アクセス解析
$result = curl_get_contents( “http://ameblo.jp/s-mitsumori/entry-11142403355.html”, 60 );
[/exec]

カテゴリー: 開発, Wordpress | inline php を使う はコメントを受け付けていません

Excel を LINQ で検索する方法

よく業務の帳票を作る時は、Excel の隠しシートを使って「印刷画面」と「データ画面」を分けて作ります。
直接帳票をデータベースから書き込んでもいいのですが、

  • セルの名前付けの不整合などがややこしい。
  • 行列を指定するときなんか、かなり大変。

ということがあって、別にデータ用のシートを用意しておいて、セル参照させるんですよね。ただ、このパターンって、行数が増えるようなレポートの場合はうまくいかなくて、結局のところコードのほう(C#/VB)で、がりがりと行列を作り込んだりします。

さて、本来はデータの書き込みを紹介したいところなのですが、OleDb プロバイダって entity data model に対応していないじゃん、ということでちょっとげんなり…どうしたものかと思っていたところ、結構簡単に linq 実装が出来そうなソースを見つけました。

Using Linq with Excel sheets
http://geekswithblogs.net/CodeSpeaker/archive/2009/10/04/using-linq-with-excel-sheets.aspx

肝は、LinqToExcelProvider クラスのところで、難ということはない、従来の OLEDB 接続をしてから、LINQ で使えるようなリスト(EnumerableRowCollection<>)を返しているだけなんですね。なるほど、これで十分です。

DataTableExtensions.AsEnumerable メソッド (System.Data)
http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=JA-JP&k=k(SYSTEM.DATA.DATATABLEEXTENSIONS.ASENUMERABLE);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-CSHARP)&rd=true

DataSet/DataTable の AsEnumerable メソッドは、拡張メソッドという訳で、ver3.5 の時に追加されたものです。

練習がてら、ちょっとだけソースコードを書き換えたのが以下のコードです。

public partial class Form1 : Form
{
	public Form1()
	{
		InitializeComponent();
	}

	/// <summary>
	/// 内部クラス
	/// </summary>
	public class Book
	{
		public string ISBN { get; set; }
		public string Title { get; set; }
		public int Price { get; set; }
	}

	private void button1_Click(object sender, EventArgs e)
	{
		LinqToExcelProvider provider = new LinqToExcelProvider(@"app_data\sampleData.xlsx");
		var items = from t in provider.GetWorkSheet("book")
					where t["title"].ToString().IndexOf("ひと目") >= 0
					select new Book
					{
						ISBN = t["isbn"].ToString(),
						Title = t["title"].ToString(),
						Price = int.Parse(t["price"].ToString())
					};
		foreach (var it in items)
		{
			Debug.Print("{0} {1}", it.ISBN, it.Title);
		}
		// バインドできるように List に変換
		dataGridView1.DataSource = items.ToList();

	}
}

/// <summary>
/// Provides linq querying functionality towards Excel (xls) files
/// </summary>
public class LinqToExcelProvider
{
	/// <summary>
	/// Gets or sets the Excel filename
	/// </summary>
	private string FileName { get; set; }

	/// <summary>
	/// Template connectionstring for Excel connections
	/// </summary>
	// private const string ConnectionStringTemplate = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=Excel 8.0;";
	/// Excel 2007 Connection String Samples - ConnectionStrings.com
	/// http://www.connectionstrings.com/excel-2007
	private const string ConnectionStringTemplate = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0 Xml;HDR=YES\";";

	/// <summary>
	/// Default constructor
	/// </summary>
	/// <param name="fileName">The Excel file to process</param>
	public LinqToExcelProvider(string fileName)
	{
		FileName = fileName;
	}

	/// <summary>
	/// Returns a worksheet as a linq-queryable enumeration
	/// </summary>
	/// <param name="sheetName">The name of the worksheet</param>
	/// <returns>An enumerable collection of the worksheet</returns>
	public IQueryable<DataRow> GetWorkSheet(string sheetName)
	{
		// Build the connectionstring
		string connectionString = string.Format(ConnectionStringTemplate, FileName);

		// Query the specified worksheet
		OleDbDataAdapter da = new OleDbDataAdapter(string.Format("SELECT * FROM [{0}$]", sheetName), connectionString);

		// Fill the dataset from the data adapter
		DataTable dt = new DataTable();
		da.Fill(dt);

		// Return the data table contents as a queryable enumeration

		return dt.AsEnumerable().AsQueryable();
	}
}

のような Excel を用意しておいて、シート名は「book」にします。

実行すると、こんな感じ

ちなみに「Microsoft.ACE.OLEDB」の「ACE」の部分は「Microsoft Access データベース エンジン」の略とのこと、以下のところでコンポーネントがダウンロードできます(って、visual studio 2010 が入っていないと入らない?)

ダウンロード詳細 Microsoft Access データベース エンジン 2010 再頒布可能コンポーネント
http://www.microsoft.com/downloads/ja-jp/details.aspx?FamilyID=c06b8369-60dd-4b64-a44b-84b371ede16d

カテゴリー: C#, データベース | Excel を LINQ で検索する方法 はコメントを受け付けていません

WebMatrix はどうやって、データベースに接続しているのか

SQL Server Compact が単体で動くのかを調べるのと同時に、ちょっと不思議だったのが WebMatrix のデータベース接続です。自動生成される Raizor のソースを見る限り LINQ で接続しているわけでもないし、どういう風に表示しているのかな、と思っていたのですが。具体的には、WebMatrix.Data.dll という WebMatrix 付属のデータアクセスコンポーネントを使っています。

webmatrix を起動して、ベーカリーのテンプレートで自動生成して、Default.cshtml を開くと、

@{
    Page.Title = "Home";

    var db = Database.Open("bakery");
    var products = db.Query("SELECT * FROM PRODUCTS").ToList();
    var featured = products[new Random().Next(products.Count)];
}

<h1>Welcome to Fourth Coffee!</h1>

のようなコードがあります。
この「Database.Open」のところでデータベースへの接続、「db.Query」でクエリの実行、ってことが想像できます。
MSDN 上のヘルプはこちら、Database.Open Method (WebMatrix.Data)

■windows アプリで webmarix.data.dll を使う

ならば、web 上の webmatrix じゃなくて、windows form でも使えるのでは?と思って試しに接続してみました。

1.先のテンプレートサイトの bin フォルダにある webmatrix.data.dll を参照設定
2.app_data/bakery.sdf を windows プロジェクトにコピー

3.「using WebMatrix.Data;」を追加
4. コードを記述

string CNSTR = "app_data\\bakery";
private void button1_Click(object sender, EventArgs e)
{
	var db = Database.Open(CNSTR);
	var products = db.Query("SELECT * FROM PRODUCTS").ToList();
	dataGridView1.DataSource = products;
}

接続文字列…というか、Openメソッドに渡す string は comapct のデータファイルそのものです。実行ファイルのカレントディレクトリから参照されるようなので、”app_data\\bakery” を渡しておきます。同じフォルダにある場合は “bakery” だけでも ok です。

こうすると、さっくりと実行できます。

まあ、windows フォームなんだから LINQ to Entities を使えばよいし、SQL 文を直接記述したい場合には SqlCommand や SqlDataAdapter を使えばよいので、あまりメリットはないのですが、さっくりと接続したときには便利…かもしれませんね。

■ちなみに、webmatrix.data.dll と *.exe だけで動くのか?

と、肝心なことを。

この webmatrix.data.dll と *.exe 、*.sdf(データベースファイル)を windows 7 のコンピュータにコピーするだけで動作するのかどうか?、と試してみたのですが駄目でした。別途、「sql server compact edition」のインストールが必要になります。SQL Server の express edition を入れるよりは軽いとはいえ、インストールしないと使えないのはなぁ、という懸念が。

こうなると、データファイルをコピーするだけで動作可能(他のセッティングが不要)なのは、

  • SQLite(未検証)
  • Access

ってところですかね。XML ファイルを用意して、linq to xml で検索することも可能なのですが、ガッツリ読み込んでしまうので、でかいファイル(100MBのファイルとか)はちょっとなぁという感じがするのです。もっとも、それだけ大きくなると、access では駄目なような気もしますが。これは要検証ということで。

カテゴリー: C#, データベース | 1件のコメント

東京メトロ UI

やっぱり、「メトロ」といえば、これでしょう?

HTML5 の canvas を利用して、東京メトロUI を実現している方なんて、誰もいませんからッ!!!

HTMLでメトロUIっぽい画面をつくってみた ? テスターですが何か?

http://david9142.wordpress.com/2012/01/18/html%e3%81%a7%e3%83%a1%e3%83%88%e3%83%adui%e3%81%a3%e3%81%bd%e3%81%84%e7%94%bb%e9%9d%a2%e3%82%92%e3%81%a4%e3%81%8f%e3%81%a3%e3%81%a6%e3%81%bf%e3%81%9f/

[inline]

図形を表示するには、canvasタグをサポートしたブラウザが必要です。

[script type=”text/javascript”]

[/script]
[/inline]

ソースコードはこちら

<canvas id="sample1" style="background-color:white;" width="1000" height="600">
図形を表示するには、canvasタグをサポートしたブラウザが必要です。
</canvas>

<script type="text/javascript">
<!--
function sample() {
var canvas = document.getElementById('sample1');
if (!canvas.getContext) return ;
var ctx = canvas.getContext('2d');

metro( ctx, 60+(100+20)*0,75, 'rgb(243,173,0)',"G" );
metro( ctx, 60+(100+20)*1,75, 'rgb(227,27,11)',"M" );
metro( ctx, 60+(100+20)*2,75, 'rgb(208,200,184)',"H" );
metro( ctx, 60+(100+20)*3,75, 'rgb(11,157,227)',"T" );
metro( ctx, 60+(100+20)*4,75, 'rgb(3,168,90)',"C" );

metro( ctx, 60+(100+20)/2*1,75+150, 'rgb(217,175,82)',"Y" );
metro( ctx, 60+(100+20)/2*3,75+150, 'rgb(147,127,185)',"Z" );
metro( ctx, 60+(100+20)/2*5,75+150, 'rgb(0,175,149)',"N" );
metro( ctx, 60+(100+20)/2*7,75+150, 'rgb(171,91,25)',"F" );

}
function metro(ctx,x,y,col,m)
{
ctx.beginPath();
ctx.fillStyle = col;
ctx.arc(x,y,50,0,Math.PI*2,true);
ctx.fill();

ctx.beginPath();
ctx.arc(x,y,25,0,Math.PI*2,true);
ctx.fillStyle = 'rgb(255,255,255)';
ctx.fill();

ctx.beginPath();
ctx.font = "bold 42px 'Arial'";
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.fillText(m, x, y+15);
}
// 実行
sample();
//-->
</script>
カテゴリー: 雑談 | 2件のコメント

C# で XML ファイルを作成するときの比較(XmlDocument, XDocument, ExDoc)

「ツイートキャッチ★★★」で xml 形式でファイルに出力するために、ExDoc を拡張している途中です。ExDoc の主旨として「ユーザーインターフェースを優先する」という項目があるので(勝手に自分で付けたw)、ユーザーから使いやすいコーディングの仕方に合わせて拡張していきます。ユーザーってのは、「コーディングをするプログラマ」ってことですね、新しい UIDD(User Interface Developement Driven)の一環です(というのも、勝手に自分で付けたw)

と前置きはそれくらいにして、コードを晒しておきます。
TwiCatchStar の内部データを直接 xml に書き出します。

■XmlDocument を使う場合

通常の XmlDocument を使う場合です。まあ、旧来の DOM インターフェースを知っていれば作れるので、java などの他言語から入った人にはいいのでしょうが、結構手間です。

// XmlDocument を使う場合
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("TwiCatchStar");
root.SetAttribute("version", APPVERSION);
root.SetAttribute("created_at", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
doc.AppendChild(root);
XmlElement user = doc.CreateElement("user");
root.AppendChild(user);
user.AppendChild(doc.CreateElement("screen_name")).InnerText= username;
user.AppendChild(doc.CreateElement("profile_image_url")).InnerText= profile_image_url;
XmlElement statuses = doc.CreateElement("statuses");
statuses.SetAttribute("type", "array");
root.AppendChild(statuses);
foreach (Tweet twi in items)
{
	XmlElement status = doc.CreateElement("status");
	statuses.AppendChild(status);
	status.AppendChild(doc.CreateElement("id")).InnerText= twi.ID;
	status.AppendChild(doc.CreateElement("text")).InnerText= twi.text;
	status.AppendChild(doc.CreateElement("created_at")).InnerText= twi.CreatedAt.ToString("yyyy/MM/dd HH:mm:ss");
}

■LINQ to XML を使う場合

今後、.NET 言語でデータストレージ絡み(XML形式も含む)のは、一括して LINQ が推奨、ってことになるんでしょうが、LINQ to XML っていまいち使いづらいんですよね。それでも、XmlDocument よりは直感的かも。

// LINQ to XML を使う場合
XDocument doc = new XDocument();
XElement root = new XElement("TwiCatchStar");
doc.Add(root);
root.SetAttributeValue("version", APPVERSION);
root.SetAttributeValue("created_at", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
XElement user = new XElement("user");
root.Add(user);
user.Add(new XElement("screen_name", username));
user.Add(new XElement("profile_image_url", profile_image_url));
XElement statuses = new XElement("statuses");
statuses.SetAttributeValue("type", "array");
foreach (Tweet twi in items)
{
	XElement status = new XElement("status");
	statuses.Add(status);
	status.Add(new XElement("id", twi.ID));
	status.Add(new XElement("text", twi.text));
	status.Add(new XElement("created_at", twi.CreatedAt.ToString("yyyy/MM/dd HH:mm:ss")));
}

■ExDoc を使う場合

ExDoc は「もう LINQ to XML はいらない」を主旨にして作っているので、+= 演算子や []演算子を適当にオーバーライドして作成できるようにします。
本来ならば、CreateElement とか AppendElement という「名前」を使わずにやりたいのですが、ちょっと模索中。

EXDocument doc = new EXDocument("TwiCatchStar");
doc.Root["version"] = APPVERSION;
doc.Root["created_at"] = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
EXElement user = doc.Root.AppendElement("user");
user += doc.CreateElement("screen_name", username);
user += doc.CreateElement("profile_image_url", profile_image_url);
EXElement statuses = doc.Root.AppendElement("statuses");
statuses["type"] = "array";
foreach (Tweet twi in items)
{
	EXElement status = statuses.AppendElement("status");
	status += doc.CreateElement("id", twi.ID);
	status += doc.CreateElement("text", twi.text);
	status += doc.CreateElement("created_at", twi.CreatedAt.ToString("yyyy/MM/dd HH:mm:ss"));
}

LINQ to XML では、Add メソッド内で要素を作成していますが、実は適当な拡張メソッドを作れば AppendElement のような形でアクセスが可能なので、ExDoc の優位性というのはこの場合はあまりありません。

■VB で作る場合

実は、VB で作るのが一番直感的なんですよね。XML 用の拡張文法が VB に備わっているので、これを使うと…

'''
''' コレクション
'''
'''
Public _tweets As List(Of Tweet)
'''
''' エンティティクラス
'''
'''
Public Class Tweet
	Public Property ID As String
	Public Property Text As String
	Public Property Create_at As Date
End Class

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
	Dim APPVERSION = "0.3"
	Dim username = "moonmile"
	Dim profile_image_url = ""

	Dim root As XElement =
	 <TwiCatchStar version=<%= APPVERSION %> created_at=<%= Date.Now.ToString("yyyy/MM/dd HH:mm:ss") %>>
	 </TwiCatchStar>

	Dim user As XElement =
	 <user>
		 <screen_name><%= username %></screen_name>
		 <profile_image_url><%= profile_image_url %></profile_image_url>
	 </user>

	Dim statuses =
	 <statues>
		 <%= From t In _tweets Select
			 <status>
				 <id><%= t.ID %></id>
				 <text><%= t.Text %></text>
				 <create_at><%= t.Create_at.ToString("yyyy/MM/dd HH:mm:ss") %></create_at>
			 </status> %>
	 </statues>

	root.Add(user)
	root.Add(statuses)

	Me.TextBox1.Text = root.ToString()

End Sub

のように、なんて直感的に書けるんでしょうッ!!! まるで、ASP.NET の埋め込みか、PHP のように記述ができます。これには、ExDoc も脱帽 orz
一見、VB のほうが行数が長いので非効率に見えるのですが、保守性は抜群ですよね。一瞥しただけで変更が可能です。このために VB を使うのもいいかも(違

2012/07/08 追記

Egtra さんより教えて貰った方法で、LINQ to XML を書き直すとこんな感じ。
この記事を書いたときに、XElement に子のコレクションをどう入れれば分からなかったのですが、items.Select な感じでコレクションを渡すと、自動的に子に配列として付け加わるのですね。なるほど。

XDocument doc = new XDocument(
	new XElement("TwiCatchStar",
		new XAttribute("version", APPVERSION),
		new XAttribute("created_at", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")),
		new XElement("user",
			new XElement("screen_name", username),
			new XElement("profile_image_url", profile_image_url)),
		new XElement("statuses",
			new XAttribute("type", "array"),
			items.Select(twi =>
				new XElement("status",
					new XElement("id", twi.ID),
					new XElement("text", twi.Text),
					new XElement("created_at", twi.CreatedAt.ToString("yyyy/MM/dd HH:mm:ss")))))));

実は以前、XElement を扱った時「これってDOMに準拠していない」(DocumentからNodeを作成しない)という点でちょっと嫌っていたのですが、こういう書き方ができると、VB のように XML の構造通りに書けるからいいですよね。
ExDoc も doc.CreateElement のようにせずに、やっぱり doc += new EXElement(…) とか、doc.Append( new EXElement(…), new EXElemnet(…)) のように羅列できるように改造しようかな。

カテゴリー: UIDD, C#, VB | 2件のコメント

XmlWriter で StringWriter を使うと Encoding が「UTF-16」になってしまうの対処方法

ExDoc 絡みで、今更ながら XmlWriter クラスを使って xml 形式のデータを出力しています。
そこで、XmlWriter クラスのエンコードなのですが、デフォルトでは「UTF-8」なんだけど、何故か StringWriter を使って文字列に吐き出させると「<?xml version=”1.0″ encoding=”utf-16″?>
」として吐き出されてしまう、問題があります。

ファイル自身は「utf-8」なのに、xml ヘッダが「utf-16」では、困るわけですね。

ややこしいのでコードの抜粋を示すと、

/// <summary>
/// ドキュメントから文字列に変換
/// </summary>
/// <returns></returns>
public string SaveXML()
{
	StringWriter sw = new StringWriter();
	XmlWriterSettings setting = new XmlWriterSettings();
	setting.Indent = true;
	XmlWriter writer = XmlWriter.Create(sw, setting);

	writer.WriteStartDocument();
	SaveXML(writer, this.DocumentElement);
	writer.WriteEndDocument();
	writer.Close();
	string xml = sw.ToString();
	sw.Close();
	return xml;
}

こんな風に、XmlWriter を使って出力する先を、StringWriter にしています。MSDN のドキュメントを見ると、

XmlWriterSettings.Encoding プロパティ (System.Xml)
http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=JA-JP&k=k(SYSTEM.XML.XMLWRITERSETTINGS.ENCODING);k(SOLUTIONITEMSPROJECT);k(TargetFrameworkMoniker-%22.NETFRAMEWORK%2cVERSION%3dV4.0%22);k(DevLang-CSHARP)&rd=true

なところで、XmlWriter は渡されたクラスのエンコードを使うので、XmlWriter 自身のエンコードは無視される、という書き方がされています。実際、StringWriter は内部コードなので、Unicode を使っているので「UTF-16」となるのは動きとしては正しいのですが、この StringWriter オブジェクトを、ファイルに書き込もうとするとちょっと困ったことになるのです。

/// <summary>
/// ドキュメントからファイルを作成する
/// </summary>
/// <param name="path"></param>
public void Save(string path)
{
	StreamWriter sr = new StreamWriter(new FileStream(path, FileMode.Create));
	string xml = SaveXML();
	sr.Write(xml);
	sr.Close();
}

こんな風にエンコードを指定しないと、ファイルは「UTF-8」で書かれてしまうのです。なので、

  • ファイルのコードは「UTF-8」なのだが、
  • XML のヘッダには「UTF-16」と書いてある。

という不整合のファイルができてしまいます。仕方がないので、どちらかのエンコードを替えます。ファイルのエンコードを「UTF-16」に揃えてもよいのですが、どちらかというと XML ファイル自体を「UTF-8」に揃えておきたい訳です。
そうなると、先の StringWriter の Encoding プロパティの値が邪魔で、なんともできないなぁ、と思っていたのですが。

下記のように、StringWriter クラスを継承して、Encoding プロパティでは、常に「UTF-8」を返すようにすれば良いのでした。なるほど、確かにこれで XML のヘッダ部分が「UTF-8」になります。

private class StringWriterUTF8 : StringWriter
{
	public override System.Text.Encoding Encoding
	{
		get { return System.Text.Encoding.UTF8; }
	}
}

/// <summary>
/// ドキュメントから文字列に変換
/// </summary>
/// <returns></returns>
public string SaveXML()
{
	StringWriter sw = new StringWriterUTF8();
	XmlWriterSettings setting = new XmlWriterSettings();
	setting.Indent = true;
	XmlWriter writer = XmlWriter.Create(sw, setting);

	writer.WriteStartDocument();
	SaveXML(writer, this.DocumentElement);
	writer.WriteEndDocument();
	writer.Close();
	string xml = sw.ToString();
	sw.Close();
	return xml;
}

こんな風に、内部クラスとして StringWriter を継承したクラスを作ると、変更が局所化されて便利です。

カテゴリー: C#, EXDoc | 1件のコメント

クラウドの課金は高いのか?

結論から言うと「無償で提供するには、限りなく高いッ!!!」です。まぁ、大人の道楽として月の呑み代を2,3回分突っ込む気持ちでいればいいんでしょうけど。単純にショバ代を払っているだけだと、アレかなぁと。

敢えて「某ひと目 Azure」(伏字になってないw)では課金の話はハズしてあります。将来的にどうなるか分からないし、開発者としては課金よりも「どう使うのか」、「どこから手をつけたらいいのか」のところから入るほうがいいでしょうから、お金の話はオミットしたのですね。

で、原稿の執筆のために「MSDN サブスクリプション」で windows azure を利用していたのですが、いやぁ、これが結構高くつきます。

執筆の開始は、実は去年の4月頃なので「クラウドアプリケーションって cpu 時間による課金だから、あまり使われないサービスであれば、セッション料金などを含めても、ほぼ無料で使えるよね」と軽く思っていた次第です。それがですね、去年の9月の google app engine の料金改定、windows azure の課金開始にあたって、実際に請求がきてみると、cpu 時間ではなくて「コンピューティング時間」というものに変わっていました。
この「コンピューティング時間」というのは、何者かというと、いわゆる「アプリがデプロイ(配置)されている時間」を示します。前後、デプロイしている時間も含まれるのですが、細かい話は別として、「アプリケーションが置いてある時間」なのです。ということは、アプリをアップロードして、1ヶ月ほど放置すると、「1ヶ月」まるごとの課金が掛かってしまう訳ですね。時間単位だと、24時間x30日=680時間ほどかかります。なので、普通のレンタルホスティングサーバー(月額1,000円やら500円やらの格安サーバー)感覚で使うと、ひどく痛い目に遭います。

azure の場合、MSDN サブスクリプションを使うと「S プラン」というものが適用されるので、単位時間あたり約10円かかります。

デプロイ1つに対して x 24時間 x 30日 x 約10円 = 7,200円/月

程度かかるわけです(この他に転送などが多ければ、それも加算される)。

# ちょっと、補記 2012/01/30
# 実は、サブスクリプション版は、1500時間/月の無料分が付いている(月でリセットされる)ので
# 総計が、2プロセス x 24時間 x 30 日 = 1,440時間 の範囲であれば、無料になります。
# この 1500 時間を超えた分に 10円/時間 単位で課金されます。
運用時には XS プランという最低限のプランがあるのですが、これが単位時間あたり 3.5円です。

デプロイ1つに対して x 24時間 x 30日 x 約10円 = 2,520円/月

まぁ、払えない金額ではないのですが「デプロイ1つにつき」というのが曲者で、azure の場合、ステージング環境という試験環境と、運用環境の2つを使うことが通例ですから、開発時にはこの倍ば掛かるんですね。ちょっと、個人では出しにくい金額です。

デプロイしている時間を減らそうと思って、追加削除を繰り返せばよいのかというとそうでもなくて、azure の場合、1時間単位等で課金がされるので、ちょっとテストのつもりで5分間デプロイしただけでも、あっという間に課金されてしまいます…といいますか、開発中にいちいちデプロイを削除するのは面倒なので、1か月間放置ということが多いですよね。そうなると、まるごと1か月ほど課金されてしまうわけです。

windows azure が「意外と高い」ことが分かりましたが、じゃあ、google app engine が安いのかというとそうではなくて、google の場合は、0.08$/h という課金がかかります。これが1分単位で計算されるわけですが、上記の通り開発時にはデプロイしたままが普通なので、同じように1ヶ月まるごと課金がかかるわけです。(ただし、放置していてほとんどアクセスがない場合は、課金はされない模様なんだけど、その差がよくわからず)

デプロイ1つに対して x 24時間 x 30日 x 0.08ドル = 4,600円/月 (1$ = 80円換算)

これにアプリ料金(だっけ)が900円ほどかかるので、azure と比べても決して安いわけではありません。

▼参照先

6 か月プラン (Windows Azure) – Windows Azure
https://www.windowsazure.com/ja-jp/offers/ms-azr-0019p

App Engine の料金体系変更に関する FAQ – Google Japan Developer Relations Blog
http://googledevjp.blogspot.com/2011/07/app-engine-faq.html

■じゃあ、何に使えばよいのか?

なので個人で web サービスを開始するのであれば、レンタルサーバーで十分です。レンタルサーバーの場合、言語が限られている(php, python, ruby)のですが、mysql がそのまま使えたりします。
ただし、ある程度スケールアップをしたい場合には、クラウド環境に移行したいですよね。自作サーバーを家で運用するのもいいのでしょうが、終日動いているのは電気代もかかるし、家族持ちには辛いものです。独身だと、まぁ、暖房替わりになるけど。

あと、月額4,000円ぐらい出せば、仮想専用サーバーが借りられので、クラウドのセッション数の課金などを気にせずに使えます。まぁ、環境が linux が多いとは思いますが、確か windows server もあったはずです。

なので、最低のランニングコストが、1万円/月を超えるような場合は、クラウド環境を利用したほうがお得という感じですかね。実は、このランニングコストには、人件費である SE 費用も入るんですよね。「某ひと目」に書きましたが、クラウドを使う利点として、

  • サーバー機の故障などのメンテナンスがフリーである。
  • サーバー機の電気代が料金に含まれている。

ということになります。レンタルサーバーの場合にも電気代、メンテナンス代が入っているので、小さい場合にはいいのですが、社内サーバーと比較すると月額1万円というの人件費からすれば結構格安でしょう。社内サーバーのメンテをしていて、保守料金数万円を支払っているならば、クラウドに移行するのもアリだと思います。

ただし、安く上がるための条件がいくつかあって、

  • デプロイするアプリケーション数が、開発時に減らせること。
  • アプリケーションのセッション数、転送量が減らせること。
  • 大きすぎる静的なストレージを持たないこと。

などがあります。課金をまじめに考えて(請求書を見て、えッ???と思ったので)みると、結構クラウド開発をするときには、設計時のノウハウが必要な気がします。漠然と、インスタンスを沢山作ってしまうと、cpu 課金ではないので、ランニングコストが莫大になってしまうので、注意しないといけません。

ええ、クラウドアプリを提供する側としては、そういう姿勢で良いかと。下手な設計をすると、課金が莫大になるという点で、ちょっと経済性に踏み込む必要があります。

カテゴリー: Azure | 1件のコメント