Visual Studio Application Insights と ライブストリームを試してみた
概要
こちらのde:codeセッションを見ていたところ、Application Insightsという機能が紹介されていました。
channel9.msdn.com
Visual Studio Application Insights は、実行中のアプリケーションを監視し、パフォーマンスの問題や例外の検出と診断、アプリの使用方法の把握に役立ちます。
https://azure.microsoft.com/ja-jp/documentation/articles/app-insights-asp-net/
まだプレビューなのですが、面白そうだったのでアプリケーションに組み込んでAzureのポータルでのライブストリームでの監視をするところまで試してみます!
環境
Visual Studio Community 2015 Update 2
ASP.NET MVC 5
Azure 開発者プログラム特典サブスクリプション
実装
新規プロジェクト作成時にも追加することができますが、今回は既存のプロジェクトに追加します。
Visual Studioでプロジェクトを右クリックし、コンテキストメニューから[Application Insights テレメトリの追加]を選択します。
アカウント情報とテレメトリの送信先を選択します。
新規でも作成できますが、既存のリソースに追加してみました。大きなアプリケーションの場合は他のコンポーネントと同じリソースグループに配置することがお勧めらしいです。
この状態でデバッグしても動くのですが、Application Insights Web SDKを最新の安定版に更新するようお勧めされるのでNuGetパッケージマネージャで[Microsoft.ApplicationInsights.Web]を最新プレビューに更新しました。
ローカル環境でデバッグ実行するとVisual Studioの診断ツールや、Application Insightsの検索ウィンドウでアプリケーションの状態を観測できます。先日作ったWeb APIの動作の詳細も確認できました!
また、プロジェクトを右クリックし、コンテキストメニューから[Application Insights]-[Open Application Insights Portal]を選択することでAzureのポータルが開きます。
ここでもアプリケーションの状態を監視することができ、さらにライブストリームではほぼリアルタイムで状態を観測できるようです。
試しに一人でリクエスト投げたり、エラー発生させてみたりしてみましたが、ほぼリアルタイムでした。
このライブストリーム機能はMicrosoft.ApplicationInsights.Webの2.1.0-beta2以降が必要です。上記の通り、デフォルトのバージョンが1.2.3だったため更新するまで使えませんでした。
あとこのWeb SDKは現段階では.NET Framework 4/4.5に依存するため、ASP.NET Coreプロジェクトではダメでした。
ふわーっと触って満足してGitHubにコミットしようと思ったのですが、.csprojファイルにサブスクリプションIDが記述されてますね。
サブスクリプション ID とは
Windows Azure をご利用頂く際に、お客様と日本マイクロソフト株式会社は、無償または有償のサブスクリプション契約 (無料評価版、従量課金プラン等) を結びます。お客様が締結したこのサブスクリプション契約を一意のものとして識別するために、マイクロソフトでは 32 桁の GUID を付与しています。この GUID がサブスクリプション ID です。
https://blogs.msdn.microsoft.com/dsazurejp/2013/12/06/id-id/
うーん、これは公開しないほうがいいかも?
ASP.NET MVC オートコンプリート
概要
あると便利!jQuery UIのAutocomplete(オートコンプリート)をASP.NET MVCで実装してみます。
今回のサンプルは入力した文字列に応じて入力候補が表示され、選択した値が各項目にセットされるというものです。
環境
Visual Studio Community 2015 Update 2
ASP.NET MVC5
jQuery UI
実装
コントローラー
必要なのはJSONデータなので、JsonメソッドによりJsonResultオブジェクトを返します。
既定ではHTTP GETリクエストではJSONデータを返せないようです。
Controllers\HomeController.cs
//ビューの表示 public ActionResult Create() { return View(); } public JsonResult AutoComplete(String name) { var data = _service.GetPeopleByName(name); return Json(data); }
明示的にGETリクエストを許可するには以下のようにする必要があります。
return Json(data, JsonRequestBehavior.AllowGet);
ワーカーサービス
多くの場合、リポジトリクラスからデータベースにアクセスし必要なデータを取得すると思いますが、今回はサンプルのため擬似コードで割愛。
Services\People\PeopleService.cs
public class PeopleService : IPeopleService { public IEnumerable<Person> GetPeopleByName(string name) { var data = GetData(); var result = data.Where(c => c.Name.StartsWith(name, ignoreCase: true, culture: ci)); return result; } }
モデル
Personクラスをベースにします。
Models\Person.cs
public class Person { public int Id { get; set; } public String Name { get; set; } public String Email { get; set; } public String Birthday { get; set; } }
ビュー
簡単な入力フォーム上でName項目に入力した値で動作するようにしています。
Views\Home\Create.cshtml
@using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Person</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Birthday, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Birthday, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Birthday, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> }
$(function () { $('#Name').autocomplete({ source: function (request, response) { var param = { name: $('#Name').val() }; $.ajax({ url: '@Url.Content("~/Home/AutoComplete")', data: JSON.stringify(param), datatype: "json", type: "POST", contentType: "application/json", dataFilter: function (data, type) { return data }, success: function (data, dataType) { response($.map(data, function (item) { return { label: "ID:" + item.Id + " Name:" + item.Name + " Email:" + item.Email + " Birthday:" + item.Birthday, value: item.Name, extend: item }; })); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert('error'); } }); }, select: function (event, ui) { $('#Email').val(ui.item.extend.Email); $('#Birthday').val(ui.item.extend.Birthday); } }); });
実行結果
Name項目に入力した文字列に対して入力候補が絞られていきます。
最終的に選択した値が各項目にセットされました!
ASP.NET Web APIという選択
ASP.NET MVCはRPCベースのスタイルであるのに対し、ASP.NET Web APIはRESTfullスタイルが既定(RPCもイケル)です。
ASP.NET MVCではJSONデータが返せるため、あまりメリットを感じないのですが、本来はWeb APIとして提供した方がいいケースもあります。
実装
APIコントローラー
MVC6では統合されたようですが、今回はMVC5なのでASP.NET Web APIコントローラーはまだSystem.Web.HttpアセンブリのApiControllerを継承しています。
Api\PeopleController.cs
public class PeopleController : ApiController { public IEnumerable<Person> GetByName(String name) { return _service.GetPeopleByName(name); } }
プロジェクト作成時にテンプレートでWeb APIを選択していない場合、APIコントローラーを追加しただけでは動かないため、以下を追加します。
既定のルートの設定
App_Start\WebApiConfig.cs
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
Web APIの既定ルートを登録
Global.asax
protected void Application_Start() { //追記 GlobalConfiguration.Configure(WebApiConfig.Register); ... }
ビュー
GETリクエストでhttp://{server}/api/{apicontroller}へアクセスしJSONデータを取得します。
JavaScript
$(function () { $('#Name').autocomplete({ source: function (request, response) { var param = { name: $('#Name').val() }; $.getJSON('@Url.Content("~/api/people")', param) .done(function (data) { response($.map(data, function (item) { return { label: "ID:" + item.Id + " Name:" + item.Name + " Email:" + item.Email + " Birthday:" + item.Birthday, value: item.Name, extend: item }; })); }) .fail(function (jqXHR, textStatus, err) { alert('error : ' + err); }); }, select: function (event, ui) { $('#Email').val(ui.item.extend.Email); $('#Birthday').val(ui.item.extend.Birthday); } }); });
まとめ
見た目は同じですが、実装をASP.NET Web APIに変更してみました。
どちらを選択するかは仕様によると思いますが、MVC6で統合された経緯を見る限りどちらも使いこなせたほうが良さそうですね。
ASP.NET Coreを視野に入れつつASP.NET Web APIも学習してみようっと。
参考
プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版
- 作者: ディノエスポシト
- 出版社/メーカー: 日経BP社
- 発売日: 2015/11/18
- メディア: Kindle版
- この商品を含むブログを見る
.NET開発テクノロジ入門 2014年版 VisualStudio2013対応版 (MSDNプログラミングシリーズ)
- 作者: 酒井達明,山田祥寛,小高太郎,中原幹雄,芝村達郎,和田健司,日本マイクロソフト株式会社エバンジェリストチーム
- 出版社/メーカー: 日経BP社
- 発売日: 2014/06/04
- メディア: 単行本
- この商品を含むブログ (6件) を見る
ASP.NET MVC ファイルアップロード
概要
久しぶりにASP.NET MVCです。以前いくつかMVC2についてポストしましたが、いい加減Razor使いたい!ということでASP.NET MVC5にしてみました。まったく仕事でMVC5を使う予定はありませんがね!
それはさておき、ファイルのアップロードです。最初に完成イメージ。
環境
Visual Studio Community 2015 Update 2
ASP.NET MVC5
実装
アップロードする画面と結果を表示する画面に対応したコントローラー、モデル、ビューで構成されています。
コントローラー
ファイルをアップロード後にファイル一覧を表示する画面にリダイレクトしています。
Controllers\HomeController.cs
[HttpGet] public ActionResult Upload() { return View(); } [HttpPost] public ActionResult Upload(UserData inputModel) { var destinationFolder = Server.MapPath("~/Users"); if (!Directory.Exists(destinationFolder)) { Directory.CreateDirectory(destinationFolder); } var postedFile = inputModel.Picture; if (postedFile.ContentLength > 0) { var fileName = Path.GetFileName(postedFile.FileName); var path = Path.Combine(destinationFolder, fileName); postedFile.SaveAs(path); } return RedirectToAction("FileIndex", "Home"); } [HttpGet] public ActionResult FileIndex() { var destinationFolder = Server.MapPath("~/Users"); string[] files = Directory.GetFiles(destinationFolder, "*", SearchOption.AllDirectories); ViewBag.files = files; return View(); }
モデル
アップロードファイルのモデルバインディングに対応するにはHttpPostedFileBase型として宣言する必要があります。
Models\UserData.cs
public class UserData { public string Name { get; set; } public string Email { get; set; } public HttpPostedFileBase Picture { get; set; } }
ビュー
(念願のRazorです!)
ファイルアップロードするにはformタグにenctype = "multipart/form-data"が必要です。
Views\Home\Upload.cshtml
@using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>UserData</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Picture, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> <input type="file" id="picture" name="picture" class="form-control" /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> }
結果はViewBagに入れて表示するだけ。
Views\Home\FileIndex.cshtml
@foreach (var file in ViewBag.files) { <div>@file</div> }
実行結果
Pictureを選択して[Create]ボタンを押下すると…
保存されました!これはAzure App ServiceのWebサイトにアップした結果です。こんなパスになるんですね。
Kuduでも確認してみました(初めて使ってみる)。ちゃんと保存されているようです。
実際にはセキュリティだとかエラーハンドリングだとかもっとしっかりやらないとダメですよ!(‘А`)
Server.MapPathについて
参考書には以下のように指定されてまして、この通りでもローカルで実行した場合はうまく動きました。
Server.MapPath("/Users")
ただ、IISにデプロイするときにアプリケーション名(このサンプルではvoyager02)を指定するとパスが通らなくなってしまいました。
これは/(スラッシュ)を指定するとWebサイト(ドメインのルート)の物理パスを取得するため一個上の階層になってるんですね。また、ドメインのルートとアプリケーションのルートは必ずしも階層関係にある必要はないため余計にややこしいですね。
Webサイト直下に配置するのであればスラッシュでもいいのかもしれませんが、ルート演算子(~)をつけてあげるとアプリケーションルートの物理パスが取れるので、アプリケーション名に依存しなくなります。
Server.MapPath("~/Users")
参考
プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版
- 作者: ディノエスポシト
- 出版社/メーカー: 日経BP社
- 発売日: 2015/11/18
- メディア: Kindle版
- この商品を含むブログを見る