ASP.NET Core MVC - グローバルフィルタで認証
グローバルフィルタを使って認証を必要とする方法です。認証を必要とするというかアクセス制御するというか。
ウェブアプリのうちログインを必要とする部分が多くてログイン不要な部分が少ない場合、グローバルフィルタで全体を制御しつつ、ログインなしで利用したいコントローラ・アクションにAllowAnonymous属性をつけると手堅いかなと思います。
Core MVCでは、ConfigureServicesメソッド内でAddMvcメソッドを使ってサービスを追加するときに、AuthorizeFilterを作成してMvcOptions.Filtersに追加すればいいみたいです。
public class Startup { public void ConfigureServices(IServiceCollection services) { // クッキー認証を行うためにサービスを登録 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); services.AddMvc(options => { // ウェブアプリ全体で認証を必要にするため、グローバルフィルタに追加 var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); } }
CoreではないMVCとかなり違ったので悩みました。たぶんこれでいいはず。
参考
T-SQL - lag関数/lead関数でn個前/n個後の行を取得する
lag関数とlead関数を知ったのでメモしておきます。
それぞれ一言で説明すると、
- lag関数 ... n個前の行を取得する
- lead関数 ... n個後の行を取得する
といった感じです。この2つもウィンドウ関数と言っていいのかな。
構文の簡単なパターンだと、関数の1つ目の引数には「取得するカラム名」を、2つ目の引数には「n個前/n個後」を指定します。
lag(カラム名, n個前) over(...) lead(カラム名, n個後) over(...)
年ごとや四半期ごとの比較といった実用的なサンプルは上記ドキュメントを参照してもらうとして、 ここでは超簡単なサンプルクエリを書いて実行結果を残しておきます。
with Seq(Value) -- CTE(共通テーブル式) as( -- 1から10まで連番を作る select 1 union all select Seq.Value + 1 from Seq where Seq.Value + 1 <= 10 ) select Value, -- Valueを昇順に並べ替えて3つ前の行を取得 lag(Value, 3) over(order by Value) as Lag, -- Valueを昇順に並べ替えて3つ後の行を取得 lead(Value, 3) over(order by Value) as Lead from Seq order by Value; /* Value Lag Lead ----------- ----------- ----------- 1 NULL 4 2 NULL 5 3 NULL 6 4 1 7 5 2 8 6 3 9 7 4 10 8 5 NULL 9 6 NULL 10 7 NULL */
結果セットを見ると例えばValueが4の場合に、lag関数は1、lead関数は7を取得できていることがわかります。
また、lag関数/lead関数を使わないで上記と同じ結果を取得するには、 次のような自己結合を使ったクエリになると思いますが、 lag/lead関数を使ったほうがわかりやすいかなと。
-- lag関数/lead関数の代わりに自己結合を使って、3つ前、3つ後の行を取得する with Seq(Value) as( select 1 union all select Seq.Value + 1 from Seq where Seq.Value + 1 <= 10 ) select Src.Value, Dst1.Value as Lag, Dst2.Value as Lead from Seq as Src left outer join Seq as Dst1 on Src.Value = Dst1.Value + 3 left outer join Seq as Dst2 on Src.Value = Dst2.Value - 3 order by Src.Value;
T-SQL - count(*)とcount(カラム名)の違い
知らなかったのでメモ。
count(*)
は全行数を取得するのに対して、count(カラム名)
はnullを除く行数を取得します。
サンプルクエリを書いて確認しておきましょう。
select -- nullに関係なく全行数を取得 count(*) as [count(*)], -- nullを除く行数を取得 count(Value) as [count(Value)] from (values (1, 100), (2, 200), (3, 200), (4, null), (5, 300), (6, 200)) as Test(Id, Value); -- 行は全部で6、nullを除くと5 /* count(*) count(Value) ----------- ------------ 6 5 */
おまけ
countだけでなくsumやavgなどの関数もnullを無視して集計します。
select count(Value) as [count(Value)], sum(Value) as [sum(Value)], avg(Value) as [avg(Value)] from (values (1, 100), (2, 200), (3, 200), (4, null), (5, 300), (6, 200)) as Test(Id, Value); /* count(Value) sum(Value) avg(Value) ------------ ----------- ----------- 5 1000 200 */
おまけ2
nullの行しか存在しない場合はどうなるんだろうと思って、countやsumなどの関数を試しました。次のような結果になります。
select count(Value) as [count(Value)], sum(Value) as [sum(Value)], avg(Value) as [avg(Value)], min(Value) as [min(Value)], max(Value) as [max(Value)] from (values -- nullの行が1つ (cast(null as int))) as Test(Value); /* count(Value) sum(Value) avg(Value) min(Value) max(Value) ------------ ----------- ----------- ----------- ----------- 0 NULL NULL NULL NULL 警告: NULL 値は集計またはその他の SET 演算で削除されました。 */
ん?警告?
参考
ASP.NET Core - ミドルウェアパイプラインを作ってみる
ミドルウェアとかミドルウェアパイプラインとかリクエストデリゲートなどを理解するために、次のドキュメントを読みながら記事内にあるミドルウェアパイプラインの画像の動きを作ってみました。
ASP.NET Core のミドルウェア | Microsoft Docs
この画像のことです。
Use/Run拡張メソッドを使ってパイプラインを作る
Startup.Configureメソッド内でミドルウェアパイプラインを構築します。Use拡張メソッドやRun拡張メソッドを使うと、ミドルウェアをインライン(ラムダ式)で記述できます。Use拡張メソッドは次のミドルウェア(コンポーネントとも言ったりしてる)を呼び出しますが、Run拡張メソッドは次のミドルウェアを呼び出さずパイプラインの終端になります。
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // 最初に呼び出されるミドルウェア app.Use(async (context, next) => { context.Response.ContentType = "text/plain"; // 次を呼び出す前の処理 await context.Response.WriteAsync("Middleware 1 before\n"); // 次のミドルウェアを呼び出す await next.Invoke(); // 次を呼び出した後の処理 await context.Response.WriteAsync("Middleware 1 after\n"); }); // 2つ目のミドルウェア app.Use(async (context, next) => { await context.Response.WriteAsync("\tMiddleware 2 before\n"); await next.Invoke(); await context.Response.WriteAsync("\tMiddleware 2 after\n"); }); // 最後に呼び出されるミドルウェア app.Run(async context => { await context.Response.WriteAsync("\t\tMiddleware 3\n"); }); } }
実行すると次のレスポンスが返ります。
Middleware 1 before Middleware 2 before Middleware 3 Middleware 2 after Middleware 1 after
ミドルウェアクラスとUseMiddleware拡張メソッドを使ってパイプラインを作る
上記と同じことをクラスを使って再現したいと思います。インラインでの処理をミドルウェアクラスにカプセル化します。
ミドルウェアクラスはHttpContextを引数に持つInvokeAsyncメソッドを持っていればいいみたいです。
// 最初に呼び出されるミドルウェア public class Middleware1 { private readonly RequestDelegate _next; public Middleware1(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { context.Response.ContentType = "text/plain"; // 次を呼び出す前の処理 await context.Response.WriteAsync("Middleware 1 before\n"); // 次のミドルウェアを呼び出す await _next(context); // 次を呼び出した後の処理 await context.Response.WriteAsync("Middleware 1 after\n"); } } // 2つ目のミドルウェア public class Middleware2 { private readonly RequestDelegate _next; public Middleware2(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync("\tMiddleware 2 before\n"); await _next(context); await context.Response.WriteAsync("\tMiddleware 2 after\n"); } } // 最後に呼び出されるミドルウェア public class Middleware3 { // 使わないけど private readonly RequestDelegate _next; public Middleware3(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync("\t\tMiddleware 3\n"); } } public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // 最初に呼び出されるミドルウェア app.UseMiddleware<Middleware1>(); // 2つ目のミドルウェア app.UseMiddleware<Middleware2>(); // 最後に呼び出されるミドルウェア app.UseMiddleware<Middleware3>(); } }
実行すると上記と同じレスポンスが返るようになります。
普段UseStaticFilesメソッドやUseMvcメソッドを使っていると思いますが、その中で実際にはミドルウェアはクラスとして用意されていて、UseMiddleware拡張メソッドを使っているようです。
勉強になりました。
AzureのComputer Vision APIのOCR機能を試してみた
Azure Cognitive ServicesにあるComputer Vision APIを使ってみました。
Computer Vision — 画像処理および画像分析 | Microsoft Azure
Computer Vision APIといってもいくつか機能があって、今回はそのうちOCR機能をJavaScript(というかTypeScript)で試してみました。
OCRを試す
APIの仕様はここにあります。
使い方は簡単でAPIのエンドポイントURLに、画像のURLをJSON形式でPOSTするか、画像のバイナリデータをPOSTするだけです。
TypeScriptでバイナリデータをPOSTする場合は次のような感じになるのかなと思います。
// 画像をBlobとして取得するとして const blob: Blob = await getBlob(); // APIのURL(今回は東アジア) const apiUrl = "https://eastasia.api.cognitive.microsoft.com/vision/v1.0/ocr"; // APIのキー const apiKey = "内緒のキー"; // APIを呼び出す const response = await fetch(apiUrl, { method: "POST", // リクエストのヘッダにメディアタイプとAPIのキーを指定する headers: { "Content-Type": "application/octet-stream", // ボディのメディアタイプ "Ocp-Apim-Subscription-Key": apiKey // APIのキー }, // リクエストのボディにBlobを設定する body: blob }); // レスポンスはJSON // OCRの結果を取得できる const result: IResult = await response.json(); /* // 適当に行(文字列)を抽出してみるならこんな感じ result.regions .map(region => region.lines) .reduce((previous, current) => previous.concat(current)) .map(line => line.words.map(word => word.text).join(" ")) .forEach(line => console.log(line)); */
レスポンスにはOCRの結果が入ります。フォーマットはJSONで、次のようなインターフェイスを持ったオブジェクトです。単にテキストだけでなく、その座標も取得できるようです。
// OCRの結果 interface IResult { language: string; textAngle: number; orientation: string, // 複数のリージョン regions: IRegion[]; } // リージョン interface IRegion { boundingBox: string; // 複数行を持つ lines: ILine[]; } // 行 interface ILine { boundingBox: string; words: IWord[]; } // 単語 interface IWord { boundingBox: string; text: string; }
参考
JavaScriptのチュートリアルとかはこのあたり。