だるろぐ

明日できることは、今日しない。

Visual Studio 2017.3 が出たっぽいので、ASP.NET Core Razor Pages をチラ見してみる

blogs.msdn.microsoft.com

Visual Studio 2017.3 に合わせて .NET Core 2.0 などもリリースされたみたいなので、かねてからやってみたかった Razor Pages をチラ見してみました。

ASP.NET Core Razor Pages は、とっても単純にいうと、C# で PHP みたいにサクッと Web ページ(CSHTML、名前の通り HTML に C# を埋め込んだ感じ)を書くためのフレームワークです。MVC とかめんどくせえ、ペライチのサイト(でも、サーバーサイドでの処理はちょっと必要)を作りたいだけなんじゃー! というときに便利。むかしは ASP.NET Web Pages という技術があったのですが、ASP.NET Core ではこれを使うようですね、よく知らんけど。

プロジェクトの作成

f:id:daruyanagi:20170815024132p:plain

まず、[新しいプロジェクト]コマンドで .NET Core、ASP.NET Core Web アプリケーションと辿ってソリューションを作ります。上の方に“.NET Framework 4.7”とか書いてあるのは気にしない。

f:id:daruyanagi:20170815024315p:plain

ソリューションを作成するとアプリケーションのタイプを選択するダイアログが現れるので、今度は上部のプルダウンメニューから“.NET Core”、“ASP.NET Core 2.0”を選択し、“Web アプリケーション”を作成。これで ASP.NET Core Razor Page のサンプルページが出力されるはずです。今回は“チラ見”なのでそのまま進みますが、一度“空”のアプリケーションから Razor Pages を使うまでもやってみたいですね。

f:id:daruyanagi:20170815024643p:plain

とりあえず[F5]キーで実行。Twitter Bootstrap+jQuery ベースの割とごついサンプルページをブラウザーで表示することができました。

f:id:daruyanagi:20170815025010p:plain

ファーストインプレッション

f:id:daruyanagi:20170815025059p:plain

詳しいことは以下のサイトで学べるので、今回は省略。自分もまだ斜め読みしかしていません(汗

docs.microsoft.com

でも、パッと見た感じ、ASP.NET Web Pages とあまり変わらないですね。目につく違いは、

  • ページの先頭に @Page ディレクティブが必要
  • ルートではなく /Page 以下に *.cshtml を配置する(変更可能)
  • ビューモデル(MVVM のアレではない)を *.cs ファイルに分離して紐づけられる(一枚の *.cshtml に @functions{ } でガリガリ書いていた処理を分離できる)
  • OnGet などでリクエストを処理。非同期版もあるみたい public async Task OnPostAsync()

ぐらいでしょうか。

f:id:daruyanagi:20170815025107p:plain

ひな型には message メンバーを OnGet(GET リクエスト)で吐く感じのコードが書かれています。新しいビューを追加すると、CSHTML/CS の両方が吐き出されるんだなー。

一方、ASP.NET Web Pages 時代に慣れ親しんでたけど見当たらない、

  • /App_Code や /App_Data の代わりになるものはどれ?
  • _ViewStarat.cshtml(それぞれのビューをレンダリングする前に呼び出される)などはあるけど、_AppStart.cshtml はどこにいったんだろう(アプリケーション名など、App レベルで変数をもちたい場合はどこに?)

などの点は、今後の個人的課題ですね。ASP.NET と ASP.NET Core の違いもあるだろうし、ちょっとまた頑張らんと。

あと、モデルバインディングタグヘルパーといった技術がより身近になるみたいです。必須というわけではないようですが、それを前提としているところも多いので(ひな型でもバシバシ使われてるしな……)、避けて通るのは難しそう。使いこなせれば便利なようなので、おいおい勉強していきたいと思います。

Azure Web App+Job+Table Storage :Twitter の位置情報を拾ってマッピングする

f:id:daruyanagi:20170415163622p:plain

以前 WebMatrix+SQL CE で作っていたものを Visual Studio 2017 で編集したら、ランタイムだかライブラリのバージョンの食い違いで起動不能になった&いろいろ試行錯誤したけど Visual Studio 2017 から SQL CE(WebMatrix.Data)がうまく扱えなかったので、データを Azure Table Storage へ保存するように書き換えた。

ぶっちゃけよくわかっていないのだけど、ちゃんと動いているみたいなのでよしとする(ぉ

Twitter の位置情報

f:id:daruyanagi:20170415163522p:plain

Twitter の位置情報は

  • Place:大まかな位置を共有(矩形)
  • Coordinates:経度・緯度を正確に共有(点)

の2つがあるみたい。

面倒な話だが Coordinates はアプリで明示的に・共有する都度[正確な位置情報を共有]オプションを有効にしなければ共有されない(Foursquare/Swarm などのチェックインアプリでは共有されることもあるみたいだが、それはアプリの意図した動作だと思うので問題ない)。今回ほしいのは Coordinates なので、これを定期的に収集することにした。

public static void CollectCheckIns()
{
    // CONNECTION_STRING は Azure Table Storage の接続文字列
    //
    // Web Jobs プロジェクトを作成
    // ・単なるコンソールアプリみたい
    //
    // NuGet で
    // ・CoreTweet:Twitter のライブラリ
    // ・WindowsAzure.Storage
    // なんかをインストールしておく

    try
    {
        var account = CloudStorageAccount.Parse(CONNECTION_STRING);
        var client = account.CreateCloudTableClient();
        var table = client.GetTableReference("checkins");
        table.CreateIfNotExists();

        // めいいっぱいツイートをかき集めてくる
        var tweets = tokens.Statuses
            .UserTimeline(count: 200)
            .Where(_ => _.Coordinates != null);

        foreach (var tweet in tweets)
        {
            var entity = new CheckinEntity(tweet);

            var operation = TableOperation.InsertOrReplace(entity);

            table.Execute(operation);

            Console.WriteLine($"{entity.Url} is collected");
        }
    }
    catch (Exception exception)
    {
        System.Diagnostics.Debug.WriteLine(exception.Message);
    }
}

基本的な流れは簡単だと思った。Table Storage に保存するデータ(モデル?)を TableEntity の派生クラスとして定義し、InsertOrReplace 操作にそのインスタンスをわたし、実行するだけ。

モデルの定義には、引数のないコンストラクター(必須)と、使いやすいように引数を設定したコンストラクターを作り、後者で PartitionKey と RowKey(いずれも string 型、必須)を設定する。この二つのキーでデータを特定・範囲指定・並び替えするみたいだけど、よくわからんかったのでツイートの Id と CreatedAt をキーにしておいた。

public class CheckinEntity : TableEntity
{
    public CheckinEntity() { }

    public CheckinEntity(Status status)
    {
        this.PartitionKey = status.Id.ToString();
        // RowKey にスラッシュは含められない
        this.RowKey = status.CreatedAt.UtcDateTime.ToString("u");

        Id = status.Id;
        Text = status.Text;
        ScreenName = status.User.ScreenName;
        Latitude = status.Coordinates.Latitude;
        Longitude = status.Coordinates.Longitude;
        CreatedAt = status.CreatedAt.UtcDateTime;
    }

    public long Id { get; set; }

    public string Text { get; set; }

    public string ScreenName { get; set; }

    public double Latitude { get; set; }

    public double Longitude { get; set; }

    public DateTime CreatedAt { get; set; }

    public string Url { get { return $"https://twitter.com/{ScreenName}/status/{Id}"; } }
}

ちょっとハマったのは、RowKey にスラッシュを含められないこと。table.Execute() が 400 という HTTP ステータスコードを返して失敗するときは、このあたりを疑ってみるといいのかも。今回はスラッシュを含まず、並び替えにも使える書式 "u" で ToString() しておいた。

成功すると、こんな感じで Table Storage にデータが格納される。

f:id:daruyanagi:20170415165656p:plain

データを読み出す場合は、こんな感じ。

public static IEnumerable<CheckinEntity> Get()
{
    var account = CloudStorageAccount.Parse(CONNECTION_STRING);
        
    var client = account.CreateCloudTableClient();

    var table = client.GetTableReference("checkins");

    var rangeQuery = new TableQuery<CheckinEntity>();
        
    return table.ExecuteQuery(rangeQuery).OrderByDescending(_ => _.CreatedAt);
}

データが多くなってきたら、ちゃんと範囲を絞ってクエリしたほうがいいのかもしれない。

f:id:daruyanagi:20170415170111p:plain

あとはこれを JSON で吐くようにして――

var map_element = document.getElementById("daru_map");
var map;
    
var markerData;

var xhr = new XMLHttpRequest();

xhr.open("get", "/map/checkins", false);
xhr.onload = function(){
    markerData = JSON.parse(this.responseText);
}
xhr.send(null);

function initMap() {
    map = new google.maps.Map(map_element, { center: markerData[0], zoom: 15, });

    for (var i = 0; i < markerData.length; i++) {
        (function () {
            //マーカーの設定と作成
            var marker = new google.maps.Marker({
                position: {
                    lat: markerData[i].lat,
                    lng: markerData[i].lng
                },
                // title: markerData[i].title,
                icon: "/Assets/Marker.png",
                map: map
            });

            //情報ウインドウの設定と作成
            var infoWindow = new google.maps.InfoWindow({
                content: markerData[i].content
            });
                        
            //情報ウインドウのオープンをマーカーのクリックイベントに登録
            marker.addListener('click', function () {
                infoWindow.open(map, marker);
                setTimeout(function () { infoWindow.close(); }, 1000 * 3)
            });
        }());
    }
}

Google マップにプロットしてみた(情報ウィンドウは一つ開いたら一つ閉じるようにしたかったけど、なんかうまくいかんのでタイマーで閉じた)。

f:id:daruyanagi:20170415170311p:plain

テキトーだけど、今回はこのぐらいで。

追伸

Web Job の話をするのを忘れたけど、単に作った Job をアップロードしてスケジューリング実行するだけなので割愛。今は 3 時間おきにデータを収集するようにしている。

はてなブログのドメインを daruyanagi.jp から blog.daruyanagi.jp へ引越しした

f:id:daruyanagi:20170325190700p:plain

あまり気付かれてない気がしますが(こっそりやったので!)、はてなブログのドメインをお引越ししました。はてなブログを独自ドメインにする話はよく見かけますが、独自ドメインで運用していたはてなブログを別のドメインへ移動させた例はあまりないかもしれないので、メモがてら残しておきます。

とはいえ、心配したようなトラブルもとくに起こりませんでした。1時間ちょっと はてなブログ がダウンしたのと、AdSense がちょっと減ったような気がする程度です。

以前の状態と解決すべき課題

以前は

  • daruyanagi.net:メインのブログ(WordPress)
  • daruyanagi.jp:はてなブログ

という運用をしていたのですが(こういうちょっと謎な構造になっていたのは、もっぱら歴史的な事情によります)、WordPress よりも はてなブログの方が管理が楽だったこともあり、いつしか daruyanagi.jp がメインに。持て余していた daruyanagi.net をポートフォリオサイト(というか自己紹介サイト)っぽい感じにして使っていたのですが、とある日、ドメインの延長手続きを失念してしまい、daruyanagi.net を失ってしまいました。

f:id:daruyanagi:20170326230159p:plain

現在は水素水を布教するサイトになっています。

そんなわけで、

  • daruyanagi.jp:はてなブログ
  • about.daruyanagi.jp:自己紹介サイト

というよくわからない構造になってしまい、ちょっと困っていました。できればこれを

  • blog.daruyanagi.jp:はてなブログ
  • daruyanagi.jp:自己紹介サイト

という感じに整理したかった。あと、トップドメインで はてなブログ を運用するのはあまりよくないので直したかった、というのもあります。

※はてなブログでは、サブドメインを使用しないexample.comでブログを運用すると、ドメイン名の仕組み上、トラブルの原因になることがあります。取得したドメインの前に、必ずサブドメイン(wwwやblogなど)を付加してください。

はてなブログを独自ドメインで利用する - はてなブログ ヘルプ

Twitter で煽られるのも悲しいですしね!


WebMatrix × ASP.NET Web Pages で自己紹介サイトを構築し、Azure へ配置する

というわけで、サイトのお引越し処理です。やらなければいけないのは以下の2つになります。

  1. はてなブログを daruyanagi.jp から blog.daruyanagi.jp へ移動
    1. 独自ドメインの割り当てを解除し、daruyanagi.hatenablog.com に戻す
    2. DNS設定を行う
    3. はてなブログに新しい独自ドメインを割り当てる
  2. 自己紹介サイトを daruyanagi.jp にする
    1. DNS設定を行う
    2. Azure のカスタムドメイン機能で daruyanagi.jp を割り当てる

うちは20年近くバリュードメインですので、そこで DNS の設定を書き換えます。

f:id:daruyanagi:20170326233254p:plain

これが反映されるのを待って、はてなブログ と Azure 側でもドメインの設定をしました。

はてなブログのドメイン移動は、反映までに少し時間がかかりましたが(1時間ちょい)、問題なく行えました。あまり期待していなかったんですが、はてなブックマークなどもちゃんと受け継がれたようです。さすが!*1

しかし、以前に外部からいただいた はてなブログ へのリンクが、移転に伴い 404 になってしまうのはあまりいただけません。というわけで、自己紹介サイトを拡張し、blog.daruyanagi.jp へリダイレクトを行うことにしました。幸い、自己紹介サイトは WebMatrix × ASP.NET Web Pages で作ってあったので、そんなに難しくはなさそう。

しかも幸運なことに、はてなブログ は記事を配信するディレクトリを一段深く掘ってあります(ドメイン直下ではない)。

※ディレクトリとして、はてなブログで使用しているaboutやarchiveなどを設定することはできません。

記事を配信するディレクトリを変更する - はてなブログ ヘルプ

というわけで、daruyanagi.jp/entrydaruyanagi.jp/archivedaruyanagi.jp/about へのリクエストをそのまま blog.daruyanagi.jp の各ディレクトリへリダイレクトすればよさそう。

f:id:daruyanagi:20170326232734p:plain

# ~/Entry.cs、 ~/Archive.cs、 ~/About.cs

@{
    var url = App.Blog + Request.Url.PathAndQuery;

    Response.RedirectPermanent(url);
}

App.Blog には、blog.daruyanagi.jp が格納されています*2HttpResponse.RedirectPermanent メソッド (System.Web) は 301 リダイレクトを行ってくれるらしいです。楽ちんですね! WebMatrix/ASP.NET Web Pages を使ってる人は少ないと思いますが、他の言語やフレームワーク、サーバーでも似たような感じの運用で対処できそうな気がします。

フィードもリダイレクトしようかなと思いましたが、それはなんとなくやめました。ごめんなさい。購読してくれてる方は、URL の登録をやり直してください。*3

Azure のカスタムドメイン割り当ては、はてなブログの独自ドメイン設定よりも早く、30分弱で反映されました。うまくいってるみたいでよかったです。Google はすぐに古い URL を参照しなくなりましたが(RedirectPermanent のおかげでしょうか?)、まだ古い URL からくるトラフィックもあるみたいで、残念な思いをさせなくてよかったと思います(ブログを読んでがっかりするのは防げませんが!)。

f:id:daruyanagi:20170326233705p:plain

丸一日様子を見てから、daruyanagi.jp -> Azure も cname ではなく a レコードで引くようにしてみました。これで今回のミッションは完了! お疲れさまでした。WebMatrix は今年いっぱいでサポート終了ですけど、最後まで役に立ってくれました。僕のようなアマチュアにはピッタリなツールだったのですが、やっぱり Visual Studio Code などと比べてエディターが弱いので仕方がないかな。ともあれ、ありがとうと言いたいです。

追記

*1:スターはダメなんだっけ? よくわかんないけどいいや

*2:開発中は daruyanagi.jp にしていろいろテストしていました

*3:daruyanagi.jp/feed を登録している場合は、はてなブログと僕のもう一つのブログ(コラムのみ)の新着情報の両方を受け取れます

WebMatrix とおさらばして、Visual Studio 2015 で ASP.NET Web Pages をはじめる

f:id:daruyanagi:20170212020533p:plain

長年愛用してきた「WebMatrix」ですが、昨年10月に2017年11月1日 でのサポート終了がアナウンスされました。

Hi everyone

After a long and successful run, Microsoft has decided to end formal support of WebMatrix. Formal support will end on November 1st, 2017. Community support will continue on the WebMatrix forums

Please take a look at Visual Studio Code, our new, free, open source, multi-platform editor! VS Code support git integration, extensions and a whole bunch of other great features!

Thanks!

The WebMatrix team

WebMatrix formal support ends November 1st, 2017 : The Official Microsoft IIS Forums

f:id:daruyanagi:20170212020959p:plain

すでにテンプレートをダウンロードする際に 404 が発生することが多くなったほか、拡張機能のダウンロード提供が終了しており、第一線で使うにはつらい状況になっています。そろそろ乗り換えを検討すべきでしょう。

Microsoft は後継ソフトとして「Visual Studio Code」を推奨していますが、これは統合ターミナルでバリバリとコマンドを打つ感じなので、GUI に甘やかされた僕にはしんどい感じ(最近「Express」を少し触っているのですが、そっちの文化にあわせるなら割と使いやすいですけどねー)。いずれ慣れないといけないなーとは思うんですが、APS.NET Web Pages を使うならば、当面の間は「Visual Studio 2015」が一番楽かなーって感じです。

1. 「Visual Studio 2015」で ASP.NET プロジェクトを作成

f:id:daruyanagi:20170212021454p:plain

まず「Visual Studio 2015」で ASP.NET プロジェクトを作成します。

f:id:daruyanagi:20170212021543p:plain

プロジェクトタイプは“Empty”でよいです。というか、「WebMatrix」でいうところのスターターテンプレートの類は「Visual Studio」に用意されていないので、“Empty”が無難な気がします。

2. プロジェクトに Razor ページを追加

f:id:daruyanagi:20170212021735p:plain

このテンプレートは本当にほぼ空なので、プロジェクトのコンテキストメニューなどから Web ページ(Razor)を追加する必要があります。

f:id:daruyanagi:20170212021905p:plain

“Razor”などのキーワードで探すと簡単。いろいろあるけど、わかんなかった“Web ページ(Razor v3)”でいいと思います。

f:id:daruyanagi:20170212022148p:plain

最初のページを追加すると、ASP.NET Web Pages に必要な参照が勝手に追加されます。すごーい!

3. [F5]キーを押して実行

とりあえずなんか書いて実行してみましょう。

<!DOCTYPE html>
@{ 
    var message = "Hello! World";
}
<html>
    <head>
        <title></title>
    </head>
    <body>
        <p>@message</p>
    </body>
</html>

[F5]キーを押すとサーバー&ブラウザーが立ち上がります。

f:id:daruyanagi:20170212022322p:plain

message を変えてブラウザーをリロードすると、即座に反映されまっする。なんとなく気軽で「WebMatrix」を使い続けてきましたが、機能面では「Visual Studio」のほうがずっと強力(ブレークポイントとかもしかけられるやで。入力補完はちょっと緩いところあるけど)です。……で、「Visual Studio Code」ではこの手順をどうやるんだ? 

最後に「WebMatrix」さん、長いことありがとうございました。

WebMatrix:フィードを読み込むときに System.Xml.XmlException が発生する

ASP.NET Web Pages 製の BOT が、ある日を境に突然、System.Xml.XmlException エラーを吐くようになった。

The element with name 'RDF' and namespace 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' is not an allowed feed format.

某サイトのリニューアルで、RSS 2.0 が RSS 1.0 になったせいでフィードが読めなくなったらしい/(^o^)\

Classes That Model Syndication Feeds and Items

  • SyndicationFeed - represents a syndication feed. Has properties like Title, Description, Links, and Items. The Items property represents the collection of content items expressed in the feed.
  • SyndicationItem - represents a specific syndication feed item and includes properties like Title, Summary, PublishDate, Authors, and so on.

Classes That Transform Syndication Feeds To/From XML

  • Rss20FeedFormatter - can take a SyndicationFeed object and turn it into XML that conforms to the RSS 2.0 specification. Also, can be used to consume a properly-formatted RSS 2.0 feed, turning the XML into a SyndicationFeed object with its properties set based on the data in the consumed XML.
  • Atom10FeedFormatter - same as the Rss20FeedFormatter, but uses the Atom 1.0 standard.
Syndicating and Consuming RSS 1.0 (RDF) Feeds in ASP.NET 3.5 - 4GuysFromRolla.com

Rss10FeedFormatter は標準で用意されていないみたいなので、自分で実装する必要がある。

幸い、コードが GitHub に転がっていたので拝借(見覚えのあるアイコンの人だなぁ……)。おおきにさんきゅーやで!

f:id:daruyanagi:20160603190721p:plain

これを以下のように呼び出してやれば、解決。めでたしめでたし。

try
{
    feed = SyndicationFeed.Load(reader);
}
catch
{
    // エラーがでたら RSS 1.0 ってことにしておく
    var formatter = new Rss10FeedFormatter();
    formatter.ReadFrom(reader);
    feed = formatter.Feed;
}

WebMatrix:改修工廠の早見表がほしかったので作ってみた

f:id:daruyanagi:20150919201617p:plain

今週は超忙しい&体調がよろしくなかった“ので”、合間を見つけて前々からほしかった改修工廠の早見表を作ってみた。接続詞が間違っているというツッコミが入りそうだけど、こういうのって、そういうときにこそやりたくならない?

最初に作ったヤツ

f:id:daruyanagi:20150919201737p:plain

ASP.NET Web Pages + SQL Server Compact で作ってみたけど、だいぶ遅かった。遅いのは多分、自分が SQL わかってないせいだと思う(SELECT でごっそりとってきたデータを LINQ で加工するとかいうわけのわからないことをやっていた)。SQL が分かんないのは一朝一夕に改善できないので、とりあえずデータベースへの問い合わせ結果をキャッシュしまくってみたけれど、スタートアップの遅さは改善できない。Web サイトがスタンバイ(スリープ? なに?)したら、キャッシュを一から再構築しなきゃいけないのも困った。

――というわけで、廃棄する予定。

二番目に作ったヤツ

f:id:daruyanagi:20150919202146p:plain

で、ちょっと考えてみたんだけど、こういうデータっていうのは稀にあるサーバーのメンテナンスのときにしか更新されないわけだ。だったら、なにもデータベースに入れる必要はなかった(CURD のうち R しかやらへんやん?)。JSON か何かでデータを用意しておいて、サーバーのスタートアップ時に読み込めばいい。

俺ってアホだなーと思いながら、ぐちゃぐちゃっと今日半日で作ったら(ロジック組むより、データ打ち込む方がよっぽど時間かかった)、最初に作ったヤツよりだいぶ早くて満足。コードを整理しながらロジック見直せばもうちょっと早くなりそう。なんせ今のは cshtml に foreach が腐るほど埋まってるという正真正銘のクソコードだし……ちょうどいいから今度 GitHub にあげて、GitHub Flow ってのを勉強するネタにしようと思う。

ほんとうはこれを Windows Phone に移植しておでコンに出したかったけれど、いつの間にか締め切り過ぎてたみたい。残念。艦これ Android 版が出るらしいし、Android アプリにするのもアリかなぁ? 

WebMatrix: URL にドットを含めたい

f:id:daruyanagi:20150913032211p:plain

Wiki エンジンなんかを作るときなど、「ドット(.)」を URL に含めたい場合は、Web.config に一行、以下のように加えるといいみたい。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>
</configuration>

静的リソースまでアプリケーションで処理するようになる(?)ので、効率が悪そうだけど。もっとちゃんと対策したければ、以下の URL が参考になりそう。

「Visual Studio Community」で ASP.NET Web Pages をはじめる

f:id:daruyanagi:20150705212532p:plain

みんなも大好きな「WebMatrix」ですが、そろそろ本格的に“なかったこと”になっているような気がしてならない今日この頃。代わりとなるのはおそらく「Visual Studio Code」なのでしょうが、まだちょっとベータ版なので、今回は定評のありまくる「Visual Studio Community」で ASP.NET Web Pages をはじめる方法を調べてみました。「WebMatrix」で“空のサイト”に相当するものを作るのが今回の目標です。

とりあえずプロジェクトを作成する

f:id:daruyanagi:20150705213348p:plain

まずはプロジェクトを作成する必要があります。プロジェクトの新規作成ダイアログで[Templates]-[Visual C#]-[Web]-[Visual Studio 2012]とツリーを辿り、「ASP.NET Empty Web Application」を選択します。Visual Basic じゃないと死んでしまう病に冒されている人は、[Visual C#]の代わりに[Visual Basic]を選択するとよいでしょう。

f:id:daruyanagi:20150705213553p:plain

ちなみに、[Templates]-[Visual C#]-[Web]にある「ASP.NET Web Application」テンプレートを使っても構いません。

f:id:daruyanagi:20150705213747p:plain

このテンプレートを選択すると、プロジェクトの種類なんかを選べるダイアログが現れるので、ここで「Empty」を選択すればさっきと同じ結果になると思います(厳密にくらべたわけじゃないから知らんけど)。このダイアログからプロジェクトを作ると Azure Web Sites も同時に作れるので、必要な場合はこっちを選ぶと手間が省けるかも。

ページの追加

f:id:daruyanagi:20150705214139p:plain

プロジェクトを作成したら、ページの追加。コンテストメニューを開いて[Add]-[MVC 5 View Page (Razor)]を選択します。

f:id:daruyanagi:20150705214317p:plain

あとは名前を付けると、cshtml ファイルが生成されます。ASP.NET(Web Pages)の流儀にしたがって、最初のファイルの名前は Default.cshtml にしておきましょうか。中身はこんな感じです。

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <div>
    
    </div>
</body>
</html>

body が空っぽなので、ついでにこんな感じでサンプルコードを加えておきます。

@{
    Layout = null;
    var message = "Hello! World"; // 追加
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <div>
        @message <!-- 追加 -->
    </div>
</body>
</html>

これで[F5]キーを押せば、"Hello! World" と書かれたサイトが立ち上がるはず――

f:id:daruyanagi:20150705214739p:plain

――だったのですが、ダメでした。http://go.microsoft.com/fwlink/?LinkId=254126 を読めと言われるのでそれに目を通してみますと、Web.config で利用する ASP.NET Web Pages のバージョンを指定しろと書いてあります。仕方ないので、言われたとおりにキーを追加。

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

  <appSettings> <!-- このセクションを追加 -->
    <add key="webPages:Version" value="2.0" />
  </appSettings>
</configuration>

f:id:daruyanagi:20150705215117p:plain

すると、サイトが立ち上がりました! めでたしめでたし! あとは SQL CE データベースを読み書きできるようにしたり、その中身を見るためのよさげなツールを探したり(クッソめんどくせー!)、追加の NuGet パッケージを入れたりするだけですね。これで WebMatrix を窓からポイできます。スターターサイトなんかは、Yeoman かなにかで準備する感じになるのかなぁ……ちょっとずつ新しい流儀に慣れていかないとね。

おまけ

ASP.NET Web Pages は .NET 言語で PHP っぽく Web ページのロジックを記述できるフレームワークです。PHP よりも安全に作られているので、お仕事で使わなければならないかわいそうな人でもなければ、こっちの方がお勧めデス。

書き方は @shibayan のサイトでも参考にしてください。PHP の記法と比べながら簡単に解説してくれています。

「(1)」って書いてあるのに続編がないのは気にしないでおきましょう。あとは

なんかが詳しいかもしれません。

WebMatrix:フィードを読み込んでデータベースへつっこむときに System.Data.SqlServerCe.SqlCeException が発生する。

だいぶ悩んで、Visual Studio まで立ち上げていろいろデバッグしていたのだけど、原因は簡単だった。

datetime

300 分の 1 秒、つまり 3.33 ミリ秒の精度で、1753 年 1 月 1 日から 9999 年 12 月 31 日までの日付と時刻のデータを格納するデータ型です。値は .000、.003、または .007 ミリ秒の単位になるように丸められます。

2 つの 4 バイト整数として格納されます。最初の 4 バイトは、base date である 1900 年 1 月 1 日からの日数、またはこの日までの日数を格納します。基準日とは、システムが参照する日付です。datetime 型の値には、1753 年 1 月 1 日より前の日付を使用できません。もう一方の 4 バイトは、午前 0 時から数えた時間をミリ秒単位で格納します。秒の有効範囲は 0 から 59 までです。

データ型

f:id:daruyanagi:20150625045042p:plain

一方、C# の default(DateTime)0001/01/01 0:00:00。つまり、SyndicationFeed オブジェクトの LastUpdateTime プロパティあたりにちゃんと値がセットされてなくて(そういうフィードを配信しているサイトは割とある)、default(DateTime) を返してくるとき、それをそのまま SQL CE データベースに格納しようとするとエラーになる。

f:id:daruyanagi:20150625050249p:plain

SQL CE の datetime 型を扱う場合は、事前に値の範囲に収まるかチェックして、ダメな場合はハネておかないといけないんだね。

var min_date = new DateTime(1753, 1, 1); // ほんとは readonly でどこかに
var updated_at = item.LastUpdatedTime.UtcDateTime < min_date 
    ? new DateTimeOffset(DateTime.Now).UtcDateTime 
    : item.LastUpdatedTime.UtcDateTime;

// Database.Execute();

教訓

ちゃんとマニュアルは読もう。

WebMatrix 3:oEmbed ヘルパーを作ってみた(2)

WebMatrix 3:oEmbed ヘルパーを作ってみた - だるろぐ の続き。今回は Flickr の埋め込みをやってみようかと思う。

~/App_Code/OEmbed.cshtml

@helper Flickr(string url) {
    const string API_ENDPOINT = "http://www.flickr.com/services/oembed/";

    using (var downloader = new WebClient())
    {
        try
        {
            // URL を組み立てて JSON の oEmbed データを取得
            var request = string.Format("{0}?url={1}&format={2}", API_ENDPOINT, url, "json");
            var oembed_data = downloader.DownloadString(request);
            var oembed_json = Json.Decode(oembed_data);

            @ObjectInfo.Print(oembed_json) // デバッグのため

            var embed_type = oembed_json.type as string;

            switch (embed_type) // photo と video の二種類がある
            {
                case "photo":
<figure>
    <img src='@oembed_json.url' alt='@oembed_json.title'>
    <figcaption>
        <img src='http://favicon.qfor.info/f/@oembed_json.provider_url' />
        <a href='@oembed_json.web_page'>@oembed_json.title</a>
        by <a href='@oembed_json.author_url'>@oembed_json.author_name</a>
    </figcaption>
</figure>
                    break;
                case "video":
<figure>
    @Html.Raw(oembed_json.html)
</figure>
                    break;
                default:
                    break;

            }
        }
        catch (Exception exception)
        {
            <p class='error'>@url: @exception.Message</p>
        }
    }
}

~/Default.cshtml

<!DOCTYPE html>

<html lang="ja">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @OEmbed.Flickr("https://www.flickr.com/photos/daruyanagi/6219334657/")
        @OEmbed.Flickr("https://www.flickr.com/photos/dmartinie/5760711397/")
    </body>
</html>

結果

たとえば Photo の場合。Twitter のときみたいに html を返してくれないので、自分で組み立てる。

f:id:daruyanagi:20140812194342p:plain

<figure>
    <img src='https://farm7.staticflickr.com/6162/6219334657_ba91a4498d_z.jpg' alt='SMSはやく使えるようになりたい'>
    <figcaption>
        <img src='http://favicon.qfor.info/f/https://www.flickr.com/' />
        <a href='https://www.flickr.com/photos/daruyanagi/6219334657/'>SMSはやく使えるようになりたい</a>
        by <a href='https://www.flickr.com/photos/daruyanagi/'>daruyanagi</a>
    </figcaption>
</figure>

Video の場合。これは html があるのでそれを使う。

f:id:daruyanagi:20140812194506p:plain

<figure>
    <object type="application/x-shockwave-flash" width="500" height="281" data="https://www.flickr.com/apps/video/stewart.swf?v=145061" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"> <param name="flashvars" value="intl_lang=en-us&photo_secret=5f7c3bff83&photo_id=5760711397&flickr_show_info_box=true"></param> <param name="movie" value="https://www.flickr.com/apps/video/stewart.swf?v=145061"></param> <param name="bgcolor" value="#000000"></param> <param name="allowFullScreen" value="true"></param><embed type="application/x-shockwave-flash" src="https://www.flickr.com/apps/video/stewart.swf?v=145061" bgcolor="#000000" allowfullscreen="true" flashvars="intl_lang=en-us&photo_secret=5f7c3bff83&photo_id=5760711397&flickr_show_info_box=true" height="281" width="500"></embed></object>
</figure>

f:id:daruyanagi:20140812194912p:plain

写真の方は CSS でいい感じにデコってみた。なかなかいいかも。