C#、ASP.NET、TypeScript、AngularJS を中心にプログラミングに関した話題を諸々。
by @jsakamoto
検索
リンク
北海道のITコミュニティ - CLR/H 無聊を託つ
タグ
カテゴリ
最新の記事
はじめての C# からの S..
at 2018-12-09 00:00
npm パッケージのバージョ..
at 2018-11-09 22:28
.NET Framework..
at 2018-10-28 00:59
ASP.NET Core の..
at 2018-09-28 23:29
[解決] スリープからの復帰..
at 2018-08-28 18:37
最新のコメント
すみません、記事書きかけ..
by developer-adjust at 22:36
続きはないんですか?
by hanamo at 13:03
助かりました。ありがとう..
by あああ at 21:32
いえす、F#! F#! :)
by developer-adjust at 21:46
F#はAzure Not..
by 幻のK泉さん at 18:37
> 今、Setcronj..
by developer-adjust at 08:25
今、Setcronjob..
by Kibe at 03:23
Jichym 改め yi..
by yi at 23:00
バッチファイル(.bat..
by Jichym at 01:26
なるほど、そこにテコ入れ..
by developer-adjust at 21:25
記事ランキング
最新のトラックバック
Web API における..
from 松崎 剛 Blog
[Other]Code2..
from KatsuYuzuの日記
Developer @ ..
from .NET Clips
asp.netでrail..
from 4丁目より
F#でASP.NET M..
from ナオキにASP.NET(仮)
[B!] これはいい h..
from Twitter Mirror
[報告] Microso..
from .NET Clips
Developer @ ..
from .NET Clips
[F#]F# でブログア..
from 予定は未定Blog版
ASP.NET MVC ..
from ナオキにASP.NET(仮)
以前の記事
2018年 12月
2018年 11月
2018年 10月
2018年 09月
2018年 08月
2018年 07月
2018年 06月
2018年 05月
2018年 04月
2018年 03月
2018年 02月
2018年 01月
2017年 12月
2017年 11月
2017年 10月
2017年 09月
2017年 08月
2017年 07月
2017年 06月
2017年 05月
2017年 04月
2017年 02月
2017年 01月
2016年 12月
2016年 11月
2016年 10月
2016年 09月
2016年 08月
2016年 07月
2016年 06月
2016年 05月
2016年 04月
2016年 03月
2016年 02月
2016年 01月
2015年 12月
2015年 11月
2015年 10月
2015年 09月
2015年 08月
2015年 07月
2015年 05月
2015年 04月
2015年 03月
2015年 02月
2015年 01月
2014年 12月
2014年 11月
2014年 10月
2014年 09月
2014年 08月
2014年 06月
2014年 04月
2014年 03月
2014年 02月
2014年 01月
2013年 12月
2013年 10月
2013年 09月
2013年 08月
2013年 07月
2013年 06月
2013年 05月
2013年 04月
2013年 03月
2013年 02月
2013年 01月
2012年 12月
2012年 11月
2012年 10月
2012年 09月
2012年 08月
2012年 07月
2012年 06月
2012年 05月
2012年 04月
2012年 03月
2012年 02月
2012年 01月
2011年 12月
2011年 11月
2011年 10月
2011年 09月
2011年 08月
2011年 07月
2011年 06月
2011年 05月
2011年 04月
2011年 03月
2011年 02月
2011年 01月
2010年 12月
2010年 11月
2010年 10月
2010年 09月
2010年 08月
2010年 07月
2010年 06月
2010年 05月
2010年 04月
2010年 03月
2010年 02月
2010年 01月
2009年 12月
2009年 10月
2009年 09月
2009年 07月
2009年 06月
2009年 05月
2009年 04月
2009年 03月
2009年 02月
2009年 01月
2008年 12月
2008年 11月
2008年 10月
2008年 09月
2008年 08月
2008年 07月
2008年 06月
2008年 05月
2008年 04月
2008年 03月
2008年 02月
2008年 01月
2007年 12月
2007年 11月
2007年 04月
2007年 03月
2007年 02月
2007年 01月
2006年 11月
2006年 10月
2006年 09月
2006年 08月
2006年 07月
ファン
ブログジャンル
画像一覧
2017年 10月 30日

ASP.NET Core SignalR v.1.0 Preview2 Final で Web API のアクションメソッド内など任意の箇所からクライアントにプッシュ送信する方法

ASP.NET Core SignalR とは

C# などの .NET 言語で実装する Web アプリのフレームワークである ASP.NET。
その ASP.NET フレームワーク上で、ブラウザ~サーバー間のリアルタイム双方向通信を実現するライブラリが SignalR である。

Node.js でいうところの Socket.IO に相当する、といえば話のとおりが良いだろうか。
(※もっとも、両者はそれぞれの思想や特性があるのであまり似てはいないとのこと。サーバー側からブラウザ側へ "プッシュ" する機能をカバーするという点で類似のライブラリとして話題にされるようだ。)

その SignalR だが、ASP.NET の新世代である ASP.NET Core 版もちゃんと存在する。

ただ、この記事を書いている時点では、ver.1.0.0 Alpha2 リリース最終版というバージョンが、プレリリース版として公開されているのみ。
まだ正式の初版リリースには至っていない。

Alpha1 から Alpha2 に更新されたときも、若干の破壊的変更があったようで、いかにもまだ Alpha 段階という感じ。

そんな αバージョン時点での、ASP.NET Core SignalR のお話。

ASP.NET Web API から SignalR への連携

SignalR を使った Web アプリを実装しているときに、サーバー側とクライアント側との間の通信技術が SignalR 一択である場合は、とくに困ることなく教科書どおりに実装すればよい。

しかし時折、その同一 Web アプリ上で、任意の HTTP クライアントに対する公開 Web API を搭載したい、なんてことがある。

で、えてして、その Web API で何やらが POST されたら、SignalR 経由で他のクライアントにプッシュ通知したい、なんて話になったりする。

さて、ASP.NET (ASP.NET Core じゃないほう) 時代、Web API の実装にあたっては、ASP.NET WebAPI が提供する ApiController クラスの派生クラスで API コントローラクラスを実装する。
Web API の要求は、この API コントローラクラスのメソッドに紐づけられる寸法だ。

ということで、Web API への POST を、SingalR によるプッシュ通信につなげるには、(POSTを受け付ける) API コントローラクラスのメソッド内で、SignalR における Hub クラスのコンテキストを手に入れる必要がある。


これを実現するには、下記のように書けば OK だ。
using Microsoft.AspNet.SignalR;
// FooHub という SignalR Hub クラスが定義済みだとして:
...
public IActionResult Post(...) {
...
var connectionManager = GlobalHost.ConnectionManager;
var hubContext = connectionManager.GetHubContext<FooHub>();
// ここで hubContext.Clients.All.hoge(...) とかできる
..
すなわち、ASP.NET SignalR が提供する GlobalHost クラスの ConnectionManager という静的プロパティを経由して、Hub コンテキストを入手可能という仕組みだ。

静的プロパティは、少なくともスコープの観点では事実上のグローバル変数のようなものだろう。
それでどんなシチュエーションからでも参照・入手可能ということになる。

ちなみに他にも、"HubController<T> を使う" という技法もあるらしい (2013年の記事だが、下記参照)。

ASP.NET Core SignalR からは GlobalHost クラスは廃止

...と、まぁ、ここまでは ASP.NET Core じゃない、元祖(?) ASP.NET における話。

実は ASP.NET Core 上の SignalR では、GlobalHost クラスは廃止された模様だ。

では ASP.NET Core の SignalR において、"API コントローラクラスのメソッド内から SignalR でのサーバー側からのプッシュ送信" を行うにはどうしたらよいか?


ASP.NET Core では、単純に、コントローラクラスのコンストラクタ引数に必要とするサービスインスタンスを渡してくれる、"DI (Dependency Injection, 依存性注入)" の仕組みで、任意の Hub のコンテキストが手に入る。

すなわち、コントローラクラスのコンストラクタの引数に、使用したい Hub コンテキスト型の引数を設けておけば、それだけで、ASP.NET Core MVC がそのコントローラクラスをインスタンス化するときに、その Hub コンテキストをコンストラクタ引数に入れてくれるのだ。

あとはコンストラクタに引数として渡された Hub コンテキストを、コントローラ自身のプロパティにキャッシュしておいて、アクションメソッド内から利用すれば OK だ。

具体的には下記コードとなる。
public class BarController : Controller {

private IHubContext<FooHub> FooHubContext { get; }

public BarController(IHubContext<FooHub> fooHub) {
this.FooHubContext = fooHub;
}

public IActionResult Post(...){
// ここで this.FooHubContext.Clients.All.hoge(...) とかできる
}
}
SignalR の Hub コンテキストだけ特別扱いということなく、他の各種サービスインスタンスと同じように、DI の仕組みで参照が手に入るのは、すっきりしていて覚えやすい。

また、かつての ASP.NET SignalR における GlobalHost.ConnectionManager のような static プロパティ = 実質のグローバル変数を参照することがなくなったので、単体テストもより書きやすくなる。

Web API コントローラクラス"以外"からの SignalR 連携

さてところで、Web API のコントローラクラスではなく、もっとほかのトリガーをもとに SignalR のサーバー側からクライアント側へのプッシュ送信を行うにはどうしたらよいだろう?

例えば ASP.NET Core をセルフホスティングしたプロセス上で、GPIO ピンに対する入力を監視し、入力に変化があったら SignalR でクライアントに通知する、などの実装である。

先述のとおり、(ASP.NET Core じゃない) ASP.NET SignalR では、事実上のグローバル変数 = GlobalHost.ConnectionManager をどこからでも参照できてた。
それで上記例のような案件でも GlobalHost.ConnectionManager 経由でクライアントへのプッシュ送信が記述できた。

しかし ASP.NET Core の SignalR では、既に説明したとおり、GlobalHost.ConnectionManager のような実質のグローバル変数は、もう存在しない。

ではどうするか?


私が思いついたのは、ASP.NET Core の DI の仕組みで任意の Hub クラスのコンテキストが手に入るのだから、きっと ASP.NET Core の DI の仕組みに乗っかればいいはず、というアイディアだ。


ただ残念なことに、現時点の私の知識・技量では ASP.NET Core における DI の仕組みに疎い。
ASP.NET Core MVC がコントローラクラスのインスタンス生成をどうやってるか、同じことを自分のコードで真似するにはどうしたらよいか (先の例でいうと、GPIO の監視をつかさどるクラスを new するときに、どうやって依存性の注入を行うのか) がわかってないのだ。

それでも自分がわかっている範囲で、そこそこの解決策はある。

ASP.NET Core におけるエントリポイント、Startup クラスにおいて、アプリ起動時に呼び出される Configure() メソッドの引数に渡される IApplicationBuilder オブジェクトを参照すれば、DI の仕組みで注入可能なサービスインスタンスを入手可能である。

ということで、Startup クラスの Configure() メソッド内で下記のとおり実装すれば、任意の Hub コンテキストの参照を入手可能だ。
using Microsoft.AspNetCore.SignalR;
// FooHub という SignalR Hub クラスが定義済みだとして:
...

public void Configure(IApplicationBuilder app) {
...
var hubContext = app
.ApplicationServices
.GetService<IHubContext<FooHub>>();

こうして入手した Hub コンテキストを、目的のオブジェクトに何らかの手段で引き渡せば OK だ。
(先の例でいえば、この Configure メソッド内で GPIO 監視クラスを new し、そのコンストラクタに Hub コンテキストを引き渡す設計にする、などの実装が考えられる)

まとめ

ASP.NET Core 時代の SignalR では、"DI (Dependency Injection, 依存性注入)" の仕組みで、Hub コンテキストを入手可能だ。

特に ASP.NET Core MVC/Web API コントローラクラスであれば、そのコンストラクタに IHubContext<T> 型の引数を用意すれば、そのコントローラクラスがインスタンス化 (new) されるときに、このコンストラクタ引数に T 型の Hub のコンテキストが渡される。

ASP.NET Core の DI の仕組みに乗っかれない場合でも、最悪、Startup の Configure() メソッドのタイミングで、IApplicationBuilder オブジェクト経由で、任意の Hub コンテキストを入手可能だ。
 

by developer-adjust | 2017-10-30 22:29 | .NET | Comments(0)
2017年 09月 22日

ASP.NET Core 2.0 の Razor Pages を markdown 構文で書く

ASP.NET Core 2.0 から使えるようになった "Razor Pages"

ASP.NET Core 2.0 には、"Razor Pages" と呼ばれる、サーバーサイドレンダリングの Web アプリ実装方法が追加された。

詳しくは下記動画の 0:56:00 あたりからを視聴するとよいかも。

この Razor Pages、いろいろと面白いのだけど、自分的には「ちょこっとだけ動的要素がある Web ページ」を作りたいときに、ただそれだけのために ASP.NET MVC のコントローラーとビューとを作るまでもなく、"Razor Pages" として ~/Pages フォルダに "なんちゃら.cshtml" を置けば済んでしまうところが気に入った。

とくに ASP.NET Core MVC では、Web API 用とMVC用とのコントローラーの区別がなくなっており、Swagger UI で Web API のドキュメントページを自動生成させると、API 目的ではなく動的 Web ページのために設けたコントローラーも含まれてドキュメント自動生成されてしまう面倒があった。
(これを回避するために、Swagger ジェネレータでフィルタリングするコードを自前で書き足していた。)

その点、その動的 Web ページが Razor Pages でシンプルに書いて済ませられるようであれば、この "混信問題" は解消するのが良いと感じた。
(Razor Pages は Swagger ジェネレータの対象にならないので。)

シンプルに .cshtml を書ける? > じゃぁ markdown で!

さてそんなシンプルな使い方もできる Razor Pages なのだが、さらにもっとシンプルに、.cshtml の内容を記述したくなった。

具体的には markdown 記法で書けないか、と考えた。

ということで用意してみたのが、AspNetCore.MarkdownPages だ。

この拙作 NuGet パッケージは、ASP.NET Core の HTTP 要求処理パイプラインに挟んでおくことで、応答ヘッダのコンテンツタイプが "text/markdown" だと、その応答コンテンツを markdown だと見なして HTML に変換してしまう、という "ミドルウェア" だ。

拡張子 .md な静的ファイルを配置しておいて、その .md ファイルを指す URL を開けば、AspNetCore.MarkdownPages が HTML に変換してブラウザに返信してくれる、みたいな使い方をする (下図)。
d0079457_18330322.png
さてこのミドルウェア、繰り返しになるが、静的ファイルに限らずコンテンツタイプが "text/markdwon" である応答ならなんでも HTML に変換する。

のであれば、Razor Pages 上でも markdown 記法で記述して、それを HTML 化してブラウザに返すようにできるはず、と考えた。

実際にやってみた

AspNetCore.MarkdownPages のインストール・導入方法は、GitHub 上のドキュメントに譲るとして、
.cshtml 側の書き方を補足する。

最低限必要なのは、応答のコンテンツタイプを "text/markdown" に変更することだ。
一例としては .cshtml 中の冒頭のコードブロックで下記のように記述する。
@page
@{
Response.ContentType = "text/markdown";
}
もしも _Layout.cshtml などを併用してる場合は、
@page
@{
Layout = null;
Response.ContentType = "text/markdown";
}
というように、共通レイアウトビューも外しておく。

以上で、あとはこの .cshtml 中に
# Header Level 1

```
Current Time is @DateTime.Now
```
とか書くと、ブラウザで表示したときに HTML 化して表示される。
d0079457_18400540.png
"@DateTime.Now" の部分が、ちゃんと C# コードとして評価・実行されてレンダリングされていることもわかる。

オブジェクトの集合を foreach で列挙しての箇条書きはちょっと苦しくて、
(※下記例は、Razor Pages の機能での "ページモデル" に "Items" という List<string> なプロパティが生えている、というシナリオ)
## Properties

@foreach (var item in Model.Items)
{
<text>- @item</text>
}
というようにきれいに書いてはダメで、
## Properties

@foreach (var item in Model.Items)
{<text>- @item
</text>}
というように、
  • foreach ブロック内で "<text>" ブロックを形成しつつ、
  • しかし、余分な空白が行頭に入り込まないようにして、
  • 且つ、1アイテムごとに改行はするように、
という、インデントを潰した、どん詰まりな記述で書く必要がある。
d0079457_18444579.png

しかし利用にはリスクも...

以上、こんな感じで、Razor Pages を markdown 記法で記述することができた。

ただし、IDE/エディタは、.cshtml 内に markdown 記法で記述するとは想定していないはずで、コーディング支援やソース自動整形で難があるかもしれない。

そもそもの Razor ビューエンジンが、まさか markdown 記法でかかれたソースを読み込まされるとはつゆ知らずなわけで、Razor 構文と markdown 構文のはざまでレンダリング時の何か問題があるかもしれない。

また、拡張子 .md のファイルであれば、多くのエディタ/IDE では、HTML に変換後のプレビューを見ながら markdown をコーディングできることだろう。
しかし、おそらく拡張子 .cshtml では markdown プレビューしながらのコーディングは無理だろう。

さらには、現状の AspNetCore.MarkdownPages の機能上の限界であるのだが、実は、ページのタイトルや meta タグを制御することも難しい。
(markdown 記法はそういったメタ情報のことまでは範疇ではない...はず。)

...というように、Razor Pages を markdown 記法で書くのは若干微妙な感じも残る。

なお、ごくごく普通の Razor 構文で記述した .cshtml 中に、markdown 記法での記述を部分的に埋め込める Tag ヘルパーも見かけたような記憶もある。
そういったタグヘルパーライブラリを使ったほうが良いかもしれない。

それでもなお、この AspNetCore.MarkdownPages ミドルウェアと Razor Pages とを組み合わせることで、すっきりしたコーディングができる範疇のプロダクトであれば、これはこれで面白いかも、と思う次第。
 


by developer-adjust | 2017-09-22 21:54 | .NET | Comments(0)
2016年 05月 31日

ASP.NET Core 1.0 のアプリケーション構成 (オプション) を動的に読み取る

ASP.NET Core 1.0 ではアプリケーション構成は型付けされる

ASP.NET Core 1.0 からは、アプリケーション構成(オプション)を読み取る方法が大幅に改築された。

まずはオプション値を表すクラスを記述し、そのオプション値の型にアプリケーション構成を逆シリアライズして、そうして生成されたオプション値を DI 機構を経由して受け取る、という "型付け" された形態になった。

詳しくは下記が大変参考になる。

ASP.NET Core 1.0 でオプションを柔軟に扱えるようになった話
http://blog.shibayan.jp/entry/20160529/1464456800

型がついてまわるので、文字列でアプリケーション構成のエントリ名を記述するのとは異なり、
  • インテリセンスが効くのはもちろん、
  • オプション値を参照している個所の特定や
    (同僚が書いたコード又は自分が3ヵ月前に書いたコードを保守するときは、これがとても心強い)、
  • エントリ名の変更といった仕様変更も楽勝
である。

※余談になるが、自分の場合、旧来の web.config 中の appSettings 構成セクションへのアクセスを静的型付けするために、SwissKnife.T4.AppSettings NuGet パッケージを多用してきた。


また、ASP.NET MVC コントローラクラスなどでは、DI 機構を通じてオプション値を入手するため、コントローラの単体テストの記述のしやすさに大きく貢献することだろう。

しかし実行時にエントリ名が決まることもある

このように、ASP.NET Core 1.0 から型付けを前提にアクセスすることになったアプリケーション構成(オプション)。
しかし、そうやたらとあるわけではないにせよ、まれに、実行時にエントリ名が決定されるような要件、言い換えると、文字列でエントリ名を指定してオプション値を入手しなければならないケースがある。

さきのしばやん先生のブログ記事でも触れられているが、"裏技的な感じ" はするものの、 IConfiguration を DI に追加すればこの要件に対応できると予想された。

ということで、試してみた。

IConfiguration を DI に登録してみる

DI 機構にて注入される "サービス" オブジェクトを DI 機構に登録するには、Startup クラスの ConfigureServices メソッド内でその登録処理を行う。

そこで、Startup クラスのコンストラクタにて別途 Configuration プロパティに初期化みの、IConfiguration インターフェースを備える "構成オブジェクト" を、ConfigureServices メソッド内で DI 機構に登録する。

具体的なコードは下記のとおり。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IConfiguration>(this.Configuration);
}
以上で、ASP.NET MVC コントローラクラスなどで、コンストラクタ引数に IConfiguration 型の引数を記述しておくと、フレームワーク側にてこのコントローラクラスをインスタンス化するときに、先に DI 機構に登録されていた IConfiguration 型に対するオブジェクトが、この引数に渡される次第である。

具体的なコード例としては下記のとおり。
public class HomeController : Controller
{
private IConfiguration Config { get; }

public HomeController(IConfiguration config)
{
this.Config = config;
}

public IActionResult Index()
{
return View(this.Config["foo"]);
}
...
このコードを実行してみたところ、appsettings.json に「{"foo":"bar"}」が記述されていると、「this.Config["foo"]」は文字列 "bar" を返すことを確認できた。

このように、裏技感は否めないものの、実行時にエントリ名を示す文字列を組み立てて "動的" にアプリケーション構成を読み取ることが、DI 機構に IConfiguration オブジェクトを直接登録してしまうことで実現できた。

階層化されたオプションは?

ところで、appsettings.json が以下のように記述されていた場合、
{
"layer1":{
"layer2":{
"key1":"foo",
"key2":"bar"
}
}
}
いちばん末端の "key2" エントリの値 ( この例では "bar" ) を IConfiguration インターフェース経由で読み取るにはどうすればよいか?

先の HomeController の例でいうと、
this.Config.GetSection("layer1").GetSection("layer2")["key2"]
というコードで取得可能ではある。

しかしこれにはもっと簡易に書ける短縮構文があって、先の JSON の子要素をたどるのにコロン「:」でメンバ名を区切って記述すればそれでアクセス可能だ。

具体的には
this.Config["layer1:layer2:key2"]
で OK である。

GetValue 拡張メソッドも便利

ここまでは IConfiguration のインデクサ構文でアプリケーション構成を参照していた。

しかし参照方法としてはほかにもあり、「GetValue」という拡張メソッドも用意されている。

ここまでの例はすべて文字列としてアプリケーション構成を読み取っていたが、GetValue 拡張メソッドを使えば、
  • int や DateTime としてアプリケーション構成を読み取ってそれらの型で返してくれたり、
  • 指定した名前のエントリが実在しなかった場合の既定値を併せて指定できたり
など、便利なことも多い。

Microsoft.Extensions.Configuration 名前空間をインポート(using)すれば GetValue 拡張メソッドが使えるようになるので、いちどインテリセンスでどんなオーバーロードバージョンがあるのか眺めてみるのもよいかもしれない。

英字の大文字小文字は区別されない模様

あと、これらアプリケーション構成(オプション)機構は、そのエントリ名に関して、大文字小文字を区別しないようだ。

先の例でいえば、
this.Config["layer1:layer2:key2"]
this.Config["LAYER1:Layer2:kEY2"]
も、同じ結果を返す。

これは型付けしたオプション用クラスについても同じで、オプション用クラスのプロパティ名と、appsettings.json に記載されているプロパティ名とが、英大文字小文字が違っていてもバインドされる。

まとめ

IConfiguration オブジェクトを DI 機構に登録することで、実行時にエントリ名文字列を構築してアプリケーション構成を読み取るような要件にも対応できることが確かめられた。

一般的な用途では、基本的には型付けされたオプション機構を用いたほうが利が大きい。
とはいえ、場合・要件によってはここで紹介した技法も役に立つだろう。

以上、ご参考まで。
 
by developer-adjust | 2016-05-31 22:47 | .NET | Comments(0)