Azure AD(v2.0)の認証をWPF(.NET Core)とASP.NET Core WebAPIで使用しようとしてハマった話
はじめに
下記の構成で構築しています。
一部Previewを使用していることもあり
参照されるタイミングによっては掲載するソースコードで動作しない可能性がありますので
ご注意くださいませ。
やりたいこと
基本そこまで難しいことはないというか、下記のドキュメントままなのですが
一部ハマったポイントがあるので備忘録的にメモを落としておきます。
1. AzureAD Application(v2.0)について
検証時点は個人アカウント(outlook)を使用するので👆画像を選択
この場合、ADアプリケーションのバージョンは強制的に2.0となります。
ここを設定しない場合は、Manifestの「accessTokenAcceptedVersion」を指定する必要があります。
2. トークン取得時に失敗する事象について
AuthenticationResult result = await _app.AcquireTokenInteractive(Scopes).ExecuteAsync();
アクセストークンの取得は上記の処理で行います。
しかし、このコードの実行の際に👇のエラーが発生しハマってしまいました。
AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'
暗黙フローを使用してトークンを取得するつもりだったので、シークレットを使用する余地もなく「?」な感じだったのですが
GitHubのIssueに同一のエラーが報告されていました。
ドキュメント曰く
Azure ADアプリケーションの「allowPublicClient」を「true」に変更すれば良いようです。
上記の設定を行うことで、認証とトークンの取得、トークンを使用したWebAPIへのアクセスが行えるようになりました。
最後に
今回作ってみたコードは下記となります。
AngularのライブラリをAzure Artifactsで使用する
はじめに
2020年1月時点の記事となります。
記事を見ていただくタイミングによっては
記事中で使用しているAzureのキャプチャやコマンドが変更されている可能性がありますのでご留意ください。
また、Angluarは下記バージョンで構成しています。
- @angular/cli: 8.3.21
- Angular: 8.2.14
同様に、コマンドなどが変更されている可能性がありますので、こちらもご留意ください。
やりたいこと
- Angularでライブラリを作る。
- そのライブラリをnpm installできるようにする。
プロジェクトが巨大かつ複数にまたがってくると
Angularに限らずライブラリとして分離したくなってくるのが開発者の性かもしれません。
そして作成したライブラリは既存のライブラリ同様npmやNuGetといったパッケージマネージャで管理したいところです。
ただ、社内だけでしか使わないようなものではありますしPublishに公開したくはない。
おまけに、npmの有料契約やってないし、verdaccio
みたいなプライベートレジストリもってない。という状況です。
そこでAzure Artifactsを利用し、サクッとプライベートなレジストリを作りたいと思います。
Angularでライブラリを作る
ここはドキュメントにある通りです。
Sandboxの切り方など参考になったので、StackOverflowの回答も参考になるかとは思います。
Azure Artifats
新しいFeedの作成
早速ライブラリを公開するレジストリ(Feedと呼ばれているようです)を作っていきます。
Azure DevOpsの「Artifacts」を選択します。
一番最初は空の状態です。ここからFeedを作成していきます。
上図、Create Feedから新しいFeedを作成します。
そこまで複雑な設定画面ではありませんね。
- FeedのURLで使用される名称
- Visibity: Feedを使用できるユーザーの制御
- UpstreamSourceの使用有無:
npmjs.org
やnuget.org
のパッケージも使用するか?設定
ちなみに、Artifactsを作成するProject自体のVisibilityがPublicになっている場合はAritefactsもPublic一択となります。
Feedへの接続
Connect to feedでFeedへの接続方法を確認できます。
今回はnpmを使用するのでnpmの接続方法を確認します。
他のパッケージレジストリを使用するときと同様に、プロジェクトに.npmrc
ファイルを作成し
レジストリとして作成したAzure ArtifactsのFeedを指定していることがわかります。
vsts-npm-auth
の使用
PrivateなFeedのため、npm publishするためにはAzureDevOpsへの認証が必要となります。
WIndowsを使用している場合、認証に使用するトークンの取得が比較的かんたんに行えます。
npm install -g vsts-npm-auth
しvsts-npm-auth -config .npmrc
を行うだけですね。
ちょいと面倒なPersonalAccessTokenの作成から、なにからなにまでやってくれます。楽。
Publish
あとはnpmコマンドでPublishするだけです。
トークンの発行ができている場合は npm publish .\dist\[パッケージディレクトリ名]
だけでFeedに発行されます。
※Angularのビルドコマンドで生成物がdist配下にできるので。
発行されたら、DevOpsのFeedの画面から下図のように確認できます。
赤枠内にパッケージのインストールコマンドもあるので、これを使用して作成したライブラリのインストールを行えます。
Feedを使う
Feedを使用する場合、前述の「Feedへの接続」と同じことをしてあげる必要があります。
レジストリのURLを指定したnpmrcファイルの作成と(場合によっては)トークンの取得です。
その設定だけで、npm installで作成したライブラリのインストールが行えるようになります。
他のライブラリ使用時にエラーとなる場合
例えば、npm install @angular/animation
などnpmjs.orgで管理されているパッケージを使用しようとした場合、404エラーとなることがあります。
これはUpstreamが正しく設定されておらず、npmjs.orgのパッケージを見に行けていないことが原因であると考えられます。
VisiblityをPublicからPrivateに手動で変えたときなどにそのような状況になるようです。
FeedSettingsからUpstreamの設定を行います。
上図の通りnpmjs.orgが存在すれば404は発生しないはずです。
Azure Pipelinesでライブラリをデリバリする
一々ローカルでnpm publish
するのは面倒なのでCIでPublishされるようにしたいところです。
そこで、使用しているAzure Artifactsと同じプロジェクトでCIパイプランを構築します。
といってもそこまで複雑なことはなく、npmAuthenticate タスクを実行の後
npm publish
するだけです。同一のプロジェクトだからかな?非常にかんたんです。
- task: npmAuthenticate@0 inputs: workingFile: '.npmrc' - script: | npm install -g @angular/cli npm install npm run build:lib npm publish .\\dist\\test-lib displayName: 'npm install ,build and publish'
これで、ライブラリのCIも構築できました。
おわりに
「Angularの」とありましたがほぼAzure Artfactsの使い方ですね。。。
プライベートなパッケージ管理をしたい場合でAzureDevOpsを使用している場合
別途の契約や、自前のサーバーを用意する必要がないので、Azure Aritfactsは非常に効果的な手なのかもしれません。
どんどん活用していきたいです。
👇今回検証で使用したリポジトリです。
参考文献
フロントエンドのMicrosoft Graphことはじめ
はじめに
使用するライブラリは下記バージョンとなります。
- typescript: 3.5.3
- msal.js : 1.1.3
- @microsoft/microsoft-graph-client : 1.7.0
- @microsoft/microsoft-graph-types: 1.10.0
また、Graph使用部分はAngularになるべく依存しない形で構成していますが
手を抜きたい箇所がちょこちょことあるのでベースは下記Ver.のAngularで作成しています。
- @angular/cli: 8.3.0
- Angular: 8.2.3
それぞれの内容について記事をご覧になるタイミングによっては、画面、仕様が変更されている可能性があるのでご留意くださいませ。
なにがしたいか
フロントエンドだけでMicrosoft Graphから情報取得したい。
以上。
実装
1.Azure AD Applicationの準備
例のごとく、Graphへのアクセストークン取得用のAzure AD Applicationを作成します。
今回はOutlookで管理しているメールの情報などを取得するため
Microsoft個人アカウントにアクセスできるアプリケーションを作成します。
ADアプリを作成する際に「個人アカウント」が使用できるタイプを選択します。
2.ライブラリのインストール
Httpの通信ベースでもGraphからは情報は取得できますが
.net同様公式からClientライブラリが提供されていますので、それを使用します。
インストールするライブラリは下記のとおりです。
msal.js
今回、個人アカウントの情報にアクセスするためAzureAdv2アプリケーションを使用します。
そのためADALではなくMSALを使用します。
Graphにアクセスする際に使用するアクセストークンの取得に使用します。
npm install msal --save
@microsoft/microsoft-graph-client
Microsoft Graphとやり取りすためのライブラリです。
npm install @microsoft/microsoft-graph-client --save
@microsoft/microsoft-graph-types
Clientには型情報がついてこないようです。
TypeScriptを使用している方は@microsoft/microsoft-graph-types
もインストールしておくと
Graphから取得したデータのマッピングが幾分か楽になると思います。
後述するクエリオプションの$select
を使用するのであればいらないかなとは思います。
npm install @microsoft/microsoft-graph-types --save-dev
3. msal.jsのイニシャライズ
Graphからデータを取得するためには、何はともあれアクセストークンを取得する必要があります。
リダイレクト時に読み込まれるページ、あるいはリダイレクト時に読み込まれるJS等でmsalのイニシャライズを行います。
JSでClassを使用している場合はconstructorでイニシャライズを行います。
とにかくリダイレクト時にUserAgentApplication
が生成されるようにしておきます。
import * as msal from 'msal'; export class AuthService { private authClient: msal.UserAgentApplication; constructor() { this.authClient = new msal.UserAgentApplication({ auth: { clientId: '<ADアプリのClientID>', authority: 'https://login.microsoftonline.com/common' } }); } }
UserAgentApplicationのコンストラクタの引数で、使用するAzureADアプリケーションの情報を与えます。
今回Microsoft個人アカウントの情報を取り扱いたいので、authority
はhttps://login.microsoftonline.com/common
を指定します。
テナントに紐づく情報のみに抑えたい場合はhttps://login.microsoftonline.com/<tenant-id>
でOKです。
4. GraphClientのイニシャライズ
先に記述したとおり、Graphからデータを取得するためには
AzureADアプリケーションから提供されるアクセストークンが必要となります。
GraphClientはイニシャライズ時に、Graphアクセス用のトークンを取得するauthProvider
を設定することができます。
Providerを指定せずにGraphアクセス時にヘッダにアクセストークンを設定する事も可能ですが
アクセス都度ヘッダを設定する処理を書くのも邪魔くさいと思うのでProviderを設定するほうがいいかなと思います。
import * as graph from '@microsoft/microsoft-graph-client'; export class GraphService { private client: graph.Client; constructor() { this.client = graph.Client.init({ authProvider: async (done) => { const token = await this.authService.acquireToken({ scopes: [ 'mail.read' ] }); if (token) { done(null, token.accessToken); } else { done('Can not Get Token', null); } } }); } }
authProvider
として、通信を行う際のトークン取得処理を記述します。
3.で設定したmsal.jsのファンクションacquireToken
を使用しアクセストークンを取得します。
acquireTokenの引数で指定するスコープ情報は、使用したいリソースの権限情報をドキュメントで参照してください。
例えば、Messageの権限情報はこちらから参照できます。
acquireToken
はPromiseで値を返却してくれるので、async-awaitでアクセストークンを取得し
authProvider
のCallbackdone
の引数にトークンを指定し実行します。
done
は(error: any, accessToken: string | null)
を引数で実行されるCallbackです。
トークンが取得できなかったりエラーが発生した場合はaccessToken
をnullにし、error
にエラー情報を格納し
逆にトークンが取得できた場合はaccessToken
に取得したトークンを設定し、error
にnullを格納し実行します。
5. Graphの実行
下準備は整いました。GraphClientを使用し、自分に送信されたメールの情報を取得してみようと思います。
export class GraphService { private client: graph.Client; .... getMessage() { this.client.api('/me/messages').get() } }
処理を組み立てるときはMicrosoftのドキュメントにお世話になります。
例えばメッセージを一覧表示する - Microsoft Graph v1.0 | Microsoft Docs
.api()
内のURLの指定は、この「HTTP要求」にあるアドレスを指定すればOKです。
と、いいますか、ページ下部にある例ほぼそのまんまですね。
6. 複数ページにまたがるデータの取得
メールや予定表など、データ数がべらぼうに多そうなデータは一回のリクエストで全件データ取得できません。
あるいは自分で1回あたりの取得データの件数を絞ったりもできます。
アプリで Microsoft Graph データをページングする - Microsoft Graph | Microsoft Docs
その場合、@odata.nextLink
というプロパティに次ページのリンクURLが格納された状態で、Graphからデータが帰ってきます。
なので、全件データを取得したい場合は@odata.nextLink
に、次ページへのリンクが格納されなくなるまでループを回し続けなければならないというわけですね。
private messageList: Message[] = []; /* Message:@microsoft/microsoft-graph-types で提供されている型情報 */ // 初回は「/me/messages」で以降はnextlinkのURLが引数に getMessage(url: string) { this.client.api(url).get().then(x => { (x.value as Message[]).forEach(element => { this.messageList.push(element); }); if (x['@odata.nextLink'] !== '') { const nestNextLink = x['@odata.nextLink']; this.getMessage(nestNextLink); } }); }
7. クエリオプション
特定条件でのデータの絞り込み。取得データを特定の項目のみにする絞り込み等が行なえます。
何が行えるかは下記のドキュメントを参照してください。
クエリ パラメーターを使用して応答をカスタマイズする - Microsoft Graph | Microsoft Docs
その中からいくつかのクエリオプションを見てみます。
$select
取得する項目を指定する事ができます。
通信でどのような項目のやり取りされるかはAPIドキュメントを参照すればOKです。
今回使用してみた/message
のプロパティは下記のドキュメントで確認できます。
メッセージ リソースの種類 - Microsoft Graph v1.0 | Microsoft Docs
このプロパティから取得したい項目を選択することになります。
GraphClientでは.select
がそれにあたります。
下記のように.select
で取得したい項目名を指定します。
export class GraphService { private client: graph.Client; .... getMessage() { this.client.api('/me/messages') .select('subject, receivedDateTime') .get() } }
messageのメール表題のsubject
と受信日時のreceivedDateTime
を取得しています。
$filter
取得する項目の絞り込みを行うことができます。
$select
同様、絞り込む項目はAPIドキュメントを参照します。
GraphClientでは.filter
を下記のように使用します。
filterの論理演算子については下記のドキュメントを参照してください。
クエリ パラメーターを使用して応答をカスタマイズする - Microsoft Graph | Microsoft Docs
export class GraphService { private client: graph.Client; .... getMessage() { this.client.api('/me/messages') .filter('isRead+eq+false') .get() } }
messageの内未読のもののみを取得しています。
まとめ
フロントエンドのみでGraphのデータにアクセスする方法を確認しました。
- Graphへのアクセスは
@microsoft/microsoft-graph-client
を便利に使用しましょう - Graphの使用の仕方は公式ドキュメントが一番参考になります
- クエリオプションを使用してGraphを使いこなしましょう
今回検証に使用したサンプルは下記のリポジトリとなります。
MSAL.js+Azure AD v2を触ってみる
はじめに
使用ライブラリは下記のバージョンでお送ります。
msal.js: 1.0.1
SPAっぽいなにかを作りたかったのでAngularも使用しています。
@angular/cli: 8.0.0
ただ、記述するソースコードの殆どはmsal.jsのヴァニラなものを使用しているので
他のフレームワークやバニラJSでも問題なく動作すると思います。
Azureは2019/6時点の画面をキャプチャしています。
それぞれの内容について記事をご覧になるタイミングによっては、画面、仕様が変更されている可能性があるのでご留意くださいませ。
あらまし
マイクロソフトのアカウント認証に便利なJavascriptのライブラリ、msal.js
長らく0.x.xとβ?みたいな扱いだったのですが、いつの間にか(5/4)に1.0.0とメジャーバージョンアップしていたので改めて使い方を復習してみようと思います。
この記事ではmsal.jsの使い方にのみフォーカスを当てます。
Azure AD アプリケーションの作り方とかADのスコープの設定とか
その周りの話については割愛していきます。
Azure AD v2については流れ上触る機会があったので簡単に。。。
単純にサインイン
msal.jsのREADMEにある通り、まずはClientIdのみを指定してログイン処理を実行してみます。
import * as msal from 'msal'; export class SampleAuth { private msalClient: msal.UserAgentApplication; login() { const con: msal.Configuration = { auth: { clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b' } }; this.msalClient = new msal.UserAgentApplication(con); this.msalClient.loginPopup({scopes: ['openid']}).then( res => { console.log(res); } ); } }
単純に書くとこんなところですね。
何はともあれ実行してみましょう。
いつものログイン画面が表示されました。
ログイン後、バッチリアカウント情報も取得できているようです。
注意点-LoginPopup
LoginPopupを行う場合、ポップアップされた画面で認証画面からのリダイレクトが発生します。
しかし、リダイレクト先でUserAgentApplication
インスタンスが生成されていないと
ポップアップ画面が閉じられず残りっぱなしになってしまうようです。
ログイン用のボタン押下でインスタンス生成する処理になっている場合はこの部分で嵌りそうです(ハマった)。
リダイレクトページのコンストラクタなどでインスタンスを生成するようにしておく必要があります。
トークンを取得する
何はともあれトークンを取得しないことには始まりません。
ログインができたのであれば、トークンを取得してみましょう。
acquireTokenSilent
を使用し、openid
のスコープでトークンを取得してみます。
login() { const con: msal.Configuration = { auth: { clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b' } }; this.msalClient = new msal.UserAgentApplication(con); this.msalClient.loginPopup({scopes: ['openid']}).then( res => { console.log(res); this.msalClient.acquireTokenSilent({ scopes: ['openid' ] }).then(token => { console.log('get token'); console.log(token); }); } ); }
トークンが取得できました。
The provided value for the input parameter 'response_type' is not allowed for this client....
上記のようなエラーが出る場合は認証に使用しているADアプリケーションの暗黙的フローの許可がされていない場合があるので確認してみてください。
生成されたトークンがどのようなものか
JSON Web Tokens - jwt.io で確認してみます。
iss
でADのテナントID、aud
で使用したADアプリケーションIDが設定されていることが確認できます。
取得したトークンで、ASP.net Coreで構築した認証付きのWebAPIにアクセスしてみます。
しかし、APIにアクセスはできませんでした。
それは当然で、生成されたJWTのaud
は認証用に使用しているAzure ADアプリケーションのClientIdで
APIの認証で使用されているAzure ADアプリケーションのClientIdとは異なるからです。
WebAPIのClientIdは "ClientId": "792dd3ef-a306-4288-b12d-5aff71f16193"
が指定されています。
WebAPI側のClientIdを変更してもいいのですが、それも芸がないので、別の方法を使用してみます。
AzureADアプリケーションの設定変更
認証で使用しているAzureADアプリケーションで、作成したWebAPIのPermissionを通しておきます。
こんな状態ですね。
スコープの変更
追加されたAPI認証にしているアプリケーションのスコープは下図で取得できます。
早速、スコープを指定し、トークンを取得してみます。
this.msalClient.acquireTokenSilent({ scopes: [ 'user.read' ] })
となっていたものを
this.msalClient.acquireTokenSilent({ scopes: [ '<APIのスコープ>' ] })
のように変更してみます。
これでトークンが取得できました。
注意点-Authorityの指定
Authorityを未指定のままでトークン取得処理を実行すると下記のようなエラーが発生しました。
AADSTS501941: Resource '792dd3ef-a306-4288-b12d-5aff71f16193'(WebApplication1) is not configured as a multi-tenant application. Usage of the /common endpoint is not supported for such applications created after '10/15/2018'. Use a tenant-specific endpoint or configure the application to be multi-tenant.
標準で使用されている認証のURL https://login.microsoft.com/common
は使用できず、テナントの指定を促されました。
その場合は、msal.jsのインスタンス生成時にauthority
を指定することで回避できます。
const con: msal.Configuration = { auth: { clientId: 'd476053b-7f9a-4c9a-9241-cbd54714266b', authority: 'https://login.microsoftonline.com/<テナントのGUID>' } }; this.msalClient = new msal.UserAgentApplication(con);
注意点 - トークンの指定
ADのトークンは仕様として、複数のリソースのトークンを一挙に取得できないようです。
複数のリソースの承認を取得する (Microsoft Authentication Library for .NET) | Microsoft Docs
なので、スコープ指定時に、 scopes: [ 'user.read', '<APIのスコープURI>' ]
のようにリソースをまたがる設定にすると
AADSTS28000: Provided value for the input parameter scope is not valid because it contains more than one resource.
上記のようなエラーとなります。トークンの取得は単一リソースごとに取得が基本ということですね。
Azure AD アプリケーションのエンドポイントを v2の設定に
さて、取得できたトークンを解析してみると、aud
は下図のような状態でした。
ほしいのはClientIdであって、スコープではありません。
Azure AD v2であればClientIDを取得できそうなので(情報元は失念。。。)Azure AD アプリケーションの設定を変更します。
accessTokenAcceptedVersion
の null
を 2
に変更しました。
これで再度トークンを取得してみます。
お望みのトークンを取得できました。
ASP.net Core WebAPIの実装
さて、トークンは帰ってきましたが、Azure AD v2を使用することでissのエンドポイントが変わってしまいました。
(URLの末尾に/v2.0
がついた)
GitHubにサンプルが転がっていたのでそれと同じように実装してみましょう。
と、いってもやっていることは非常に単純で 上記GitHubで管理されているMicrosoft.Identity.Web
をAPIプロジェクトで参照して
startup.cs
の認証部分を少し書き換えるだけですね。appsettings.json
も書き換え必要ありません。
(Microsoft.Identity.Web
はNuGetで公開されていないのですね…)
startup.cs
- services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
- .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
+ services.AddProtectWebApiWithMicrosoftIdentityPlatformV2(Configuration);
msal.jsで取得したトークンで、APIを使用できるようになりました!
まとめ
msal.jsの基本的な使用方法と派生してAzure AD v2の認証方法を抑えることができました。
ただ、Azure ADv2は各所みるにまだまだ開発中の匂いがプンプンしますし
AzureADアプリケーションを使用して認証を行う場合は
v1エンドポイントを使用して、adal.jsを使用するほうがまだまだ無難なようです。
力尽きたので、Azure AD B2Cとmsal.jsの認証は別の機会に記事にしようと思います。。。
Azure Front Doorを使ってみる
はじめに
2019/6/2時点のAzureを使用しています。
記事をご覧頂いているタイミングによっては
記事内に出てくる画像、設定内容などが変更されている可能性があることをご留意くださいませ。
あらまし
先日のde:code19でデプロイ王子のアンプラグドイベントに参加しました。
API Managementが死んだ際の対応についてお話を聞いたところ
AzureFrontDoorもイイよ。と教えていただきました。
が、触ったことないので触ってみよう!というのが今回のあらましです。
この記事では、基本的な設定内容と使い方、キャッシュ機能の検証について記載しています。
なにはともあれ価格帯
個人で触るか会社で触るかの分水嶺ですね。Azure死するのも嫌ですし。
価格 - Front Door | Microsoft Azure
油断してたら結構持ってかれそうですが、時間単位で見ればまぁ普通にお安め。
作ってすぐ潰す検証環境であれば1,000円以内で済みそうです。
作ってみる
Azure クイック スタート - アプリケーションの高可用性を実現するフロント ドア プロファイルを Azure portal を使って作成する | Microsoft Docs
新しいサービスを使うときは基本ドキュメントに則るのが一番かなと思うので
上記のドキュメント通りに設定していきます。
結果非常に簡単に作成&使用できました。
FrontDoor作成
Configurationタブで下図のような設定画面が表示されるのでそれぞれ「+」で設定していきます。
FrontEndHost設定
SESSION AFFINITYはバックエンドなんかでCookieを使用した処理や認証を使用している際に使うものでしょうか。
ひとまず現状はセッション情報は使用しないので Disabled
に設定しておきます。
BackEndPool設定
Backendの登録は悩まずに設定することができました。
HEALTH PROBES
Azure Front Door Service - バックエンドの正常性監視 | Microsoft Docs
上記ドキュメントによると正常性の特定は200コードで判定されるようです。
なので、バックエンドで認証を設けている場合等は疎通試験用の無認証のAPIを用意したほうが良さそうです。
今回自分が作ったWebAPIは認証機構を設けておりませんが、上図のPathの/
は404になるので
使用できるAPIエンドポイントを指定するのが良さそうです。
今回はテストで使用する /api/values
を指定しました。
ヘルスチェックを行うインターバルも指定できるようです。
頻繁にアクセスされるようなリソースはインターバルを短めに設定しておいたほうが良さそうです。
後述しますが、停止~ヘルスチェックのインターバル間にアクセスした場合、停止したエンドポイントにアクセスすることになりそうです。
LOAD BALANCING
Azure Front Door Service のバックエンドとバックエンド プール | Microsoft Docs
負荷分散の設定については今回特に留意することはなさそうです。
Ruleの設定
PATTERNS TO MATCH
パターンについては今回特に指定していないですが 特定のAPIについてのみのアクセス。とかBackendPool/Ruleが複数設定される場合に重宝しそうです。
RouteType
後の検証の関係でRouteTypeはForward
を指定しています。
キャッシュがある場合はキャッシュを使用する。という設定のようですね。Redirect
はキャッシュ関係なしにAPIに直接アクセスする設定のようです。
頻繁に変更があるAPI/めったに変更がないAPIという感じにRule分離させているといいのかもしれません。
Forwardung Protocol/URL Rewriteはがっつり使う段階からになりそうなので割愛
Cachingは後の検証で使用しますが、ひとまず今はDisabled
にして終わらせます。
できあがり
大凡1分くらいでリソースが作成されました。
検証
みんなだいすきPostmanを使って検証してみます。
WebAPIは下記のようにASP.net Coreで雑に文字列が返却されるものをデプロイしています。
[HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "This is", "API One" }; // バックエンド1/2で返却される内容は変えている }
まずは普通に疎通試験してみます。
問題なく使用できてそうです。
優先的にアクセスされるAPIエンドポイントを停止させました。
これで接続されるエンドポイントが変更されるはずです。
設定通り同一のエンドポイントURLで別リソースを参照していることが確認できました!
注意!!
先で設定した通りエンドポイントのヘルスチェックは30秒のインターバルで行われます。
なので、停止後即アクセスすると403が返却されます(されました。焦った…)。
停止直後に403等が返却された場合は時間をおいて再度アクセスしてみると良いかもしれません。
キャッシュ機能の検証
de:code19で王子とお話した際に「キャッシュ機能もあるよ」とお聞きしたので、キャッシュ機能も試してみます。
「まずはキャッシュデータが参照される」というのがデータ取得時の動きなのだと思われます。
以下設定と検証。
まずは、FrontDoorの設定を変更します。
Cachingを有効に
Query string caching behavior
「クエリ文字列を無視」「一意なURLをすべてキャッシュ」の2種があります。
クエリ文字列を使用するURLが指定された場合のキャッシュ動作のようですね。
クエリ文字列を無視してキャッシュするか、クエリ文字列含めたURLでキャッシュするか。といった設定のようです。
ここはひとまずCatche every Unique URL
でクエリ文字列含めたURLでキャッシュするようにしておきます。
検証
クエリ文字列の検証も含めるたいので、Controllerの内容もちょっと変更しました。
[HttpGet(Name = "Get Single Data")] public ActionResult<string> GetSingleData([FromQuery]int id) { return $"This is API One And QuerId={id}!"; }
クエリ文字列で指定された文字列含め返却するよう変更しています。
まずは普通に実行した結果です。
queryで指定された文字列が返却されていることが確認できます。
次にバックエンドのAPIに使用しているWebAppsを停止したうえで実行してみます。
もう一方のバックエンドは参照せずに、キャッシュされたデータが返却されているようです。
query文字列を変更した状態で実行してみます。
こちらはデータがキャッシュされていないので停止されていないAPIの方を参照していることが確認できます。
Ignore Query Strings
の設定にした場合
queryでid=300
を指定して、実行してみました。結果は下図のとおりです。
では、idを変更して実行してみます。
idを変更しても返却される値は変更されず、queryの文字列を無視してキャッシュを行っていることが確認できました。
おわりに
Azure Front Doorを触ってみましたが、簡単に設定/使用することができることがわかりました。
キャッシュ機能については「はじめにキャッシュ」という動き方であるが故に
頻繁に更新が発生するデータの場合などには、すこし気をつけて使う必要がありそうです。
キャッシュが有効な期間とか、リソース使用の優先順位とか、キャッシュについて色々設定できると嬉しいかもですね。
ReactiveFormsで使用できるカスタムコンポーネントを作成する
はじめに
本記事ではAngularは下記Versionを使用しています。
- AngularCLI: 7.3.6
- Angular: 7.2.10
ゴール
Inputコントロールなんかと同様に formControlName
を指定できるような
ReactiveFormsで使用できるComponentを作成します。
今回は「ダイアログ表示ボタン+入力ダイアログ」を一つのコンポーネントとし
そのコンポーネントをReactiveFormsで使用します。
操作イメージは下のGifのような動作イメージです。
「Result」内にあるように、ダイアログの入力内容が反映されます。
ControlValueAccessor
Angularの公式リファレンスによると、ControlValueAccessorを使用して、Valueとコントロールのリンクを行っていると記載されています。
ControlValueAccessorは、下記4つの機能のインタフェースを持つようです。
- writeValue(obj: any) →ModelからViewへの値の書き込み
- registerOnChange(fn: any) →View変更時のCallback
- registerOnTouched(fn: any) → ViewTouch時のCallback
- setDisabledState(isDisabled: boolean) → disabled/enabledをViewに反映する
全容は下記のソースです。
@Component({ selector: 'app-address-input-dialog', templateUrl: './address-input-dialog.component.html', styleUrls: ['./address-input-dialog.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AddressInputDialogComponent), multi: true } ] }) export class AddressInputDialogComponent implements ControlValueAccessor { private addressData: AddressModel; private isDisabled = false; private onChange: any = () => {}; private onTouched: any = () => {}; constructor( private dialog: MatDialog ) { } openDiaog() { const dialog = this.dialog.open(DialogComponent, { data: this.addressData }); dialog.afterClosed().subscribe(data => { this.addressData = data; this.onChange(data); this.onTouched(); }); } // ↓ControlValueAccessorのInterface群 writeValue(obj: any): void { this.addressData = obj; } registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState?(isDisabled: boolean): void { this.isDisabled = isDisabled; } }
コントロールの動きとして、ボタン押下時にダイアログを表示
ダイアログが確定されたらデータを反映。という流れです。
そのため、dialog.afterClosed()
でダイアログが閉じられた際に、formcontrolの値変更時のイベントを実行しています。
ダイアログコンポーネント側はオーソドックスなReactiveFormなので割愛します。
何が嬉しい?
下記は作成したカスタムコンポーネントを使用しているコンポーネントのソースです。
export class MyFormGroupFieldComponent implements OnInit { public fg: FormGroup; constructor( private fb: FormBuilder ) { } get formResult() { if (typeof(this.fg) !== 'undefined') { return JSON.stringify(this.fg.value); } else { return ''; } } ngOnInit() { const initAddressData: AddressModel = { city: '', prefecture: '', zipCode: ''}; this.fg = this.fb.group({ userId: ['', Validators.required], familyName: ['', Validators.required], lastName: ['', Validators.required], address: [initAddressData] }); } patchData() { const initAddressData: AddressModel = { city: '港区', prefecture: '東京都', zipCode: '000-0000'}; const data: MyFormGroupModel = { userId: 'devtakas', familyName: 'familyname', lastName: 'lastname', address: initAddressData }; this.fg.patchValue(data); } }
色々と書かれているように見えますが
初期値を設定したり、固定データを反映したり、画面上に表示する文字列を作成しているだけです。
なので、「ダイアログ開いたときは値を反映して~」とか「閉じたときは変数に値を格納~」みたいな処理は記述しておりません。
@ViewChild
で子コンポーネントのプロパティにアクセスする。なんてこともしていないです。
コンポーネントの処理記述が非常に簡潔になります。
またformControlNameをカスタムコンポーネントに指定することができるため
Htmlテンプレートも下記のようにシンプルに記述できるようになります。
<form [formGroup]="fg"> ... <div> <app-address-input-dialog formControlName="address"></app-address-input-dialog> </div> ... </form>
formに対して構造体の値そのままでやり取りできるのも良いポイントですね。楽。
参考ページ
Angularでカスタムフォーム(CustomFormControls)を作る(1/2) - Qiita
最後に
今回作成したデモは下記に格納しています。
デモソースのごった煮ですがご容赦ください ><
NLogを.Net Standard/Coreで使用する
はじめに
.Net Core等は下記バージョンでお送りします。
- netstandard: 2.0
- netcoreapp: 2.2
- AspNetCore: 2.2.0
- NLog: 4.6.3
あらまし
NLogを使うとかは今更ではあるのですが
.Net CoreのExeアプリケーションからASP.net WebAPIアプリケーションで
NLogを使用する機会があったので、学習ついでの備忘録な感じのトピックです。
NLog.configの設定を外に出す
基本的な使い方は本家のGitHubやググったら山程でてくるので割愛…
NLogのログ出力の設定は、nlog.config
のXML形式ファイルに記述していく感じです。
が、これはいまいち好きじゃない。
と、いうのもASPにしてもCoreの普通のアプリにしてもjsonファイルにアプリケーション設定を記述していますし
あちこちのファイルに設定内容が散らばっているのも少し邪魔くさい。
ASP.netに関してはWebAppsがもっているアプリケーション設定で出力先をいろいろ設定できるようにすれば
CI/CD側の負担も軽くなるんでは?と思ったわけです。
(nlog.configのようなXMLファイルの設定もWebAppsの設定上でできるのであればいいのですが…ない?ですよね?)
NLog.configの構成
まずはXMLで設定されるNLogの構成を見てみます。
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <target name="console" xsi:type="Console" /> </targets> <rules> <logger name="*" minlevel="Info" writeTo="console" /> </rules> </nlog>
これはLogの内容をConsoleに吐き出す設定ですが、target
で「何に対して」ログ出力するか指定し
rule
で 'target' に対して出力するログの内容を指定しています。
この場合、「Info
レベルからのログを Console
に出力する」という設定になっているわけですね。
NLogのTarget/Ruleの関係は、下記サイトが参考になりました。
NLogの設定を動的に指定する
こちらのサイトを参考にしました。
NLogをプログラマブルに初期化し動的に構成変更する - M12i.
NLog.Target
名前空間に ConsoleTarget
というClassが存在します。
NLog/ConsoleTarget.cs at dev · NLog/NLog · GitHub
他にも FileTarget
や DatabaseTarget
が存在します。
Classにあるプロパティを見てみると、Layout
やConnectionString
などXMLで設定するプロパティが存在するのが確認できます。
XMLの設定を確認しながら、TargetClassの同名プロパティに値を設定していく…という方法でNLogの設定ができそうです。
で、出来上がったのが下記です。
public static void ConsoleLogInit() { var conf = LogManager.Configuration; var console = new ConsoleTarget("console"); // consoleターゲットを生成 console.Layout = LogLayout; // アウトプットフォーマットレイアウトを設定 conf.AddTarget(console); // NLogの設定に生成したターゲット情報を追加 conf.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, console)); // consoleターゲットを使用するルールを追加 LogManager.Configuration = conf; // NLogの設定に反映 }
あとは同じように設定するだけ
TargetのClassにたいしてどのようなAPIが存在してどのような引数が求められているのかは
上記のGitHubのソースコードやドキュメントから抑えることができるので
ファイルに出力する場合やDBに出力する場合も同じように設定していくだけです。
ファイルに出力する場合はファイル名称やエンコードを指定するAPIが追加されていたりします。
public static void WriteLogToFileInit(string filePath, LogLevel targetLogLevel) { var conf = LogManager.Configuration; var file = new FileTarget("file"); file.Encoding = Encoding.UTF8; file.FileName = filePath; file.Layout = LogLayout; conf.AddTarget(file); conf.LoggingRules.Add(new LoggingRule("*", targetLogLevel, file)); LogManager.Configuration = conf; }
Databaseはちょっと変わり種でDatabaseParameterInfoのインスタンスにLayoutを設定しないといけません。
public static void WriteLogToSqlDatabaseInit(string connectionString, LogLevel targetLogLevel) { var conf = LogManager.Configuration; var dbtarget = new DatabaseTarget(); dbtarget.ConnectionString = connectionString; dbtarget.Name = "dbtarget"; dbtarget.DBProvider = "System.Data.SqlClient"; dbtarget.CommandText = "Insert Into LoggingTable(" + "Logged," + ") values (" + "@logged," + ")"; var loggedParam = new DatabaseParameterInfo(); loggedParam.Name = "@logged"; loggedParam.Layout = "${date}"; dbtarget.Parameters.Add(loggedParam); conf.AddTarget(dbtarget); conf.LoggingRules.Add(new LoggingRule("*", targetLogLevel, dbtarget)); LogManager.Configuration = conf; }
外部からNLogの設定を行う!
と、いうわけでソースコード上でNLogの設定が十二分に行えることがわかりました。
あとは、ASP.net WebAPIなどのappsettings.jsonなどに適当な設定を作ってあげればいいだけです。
適当に↓な感じでaapsettings.jsonを作って
{ "Logging": { "LogLevel": { "Default": "Warning" }, "OutputToLogFile": { "FilePath": "logfile.txt", "LogLevel": "Error" }, "OutputToDatabase": { "ConnectionString": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Logging;Integrated Security=True;", "LogLevel": "Info" } }, "AllowedHosts": "*" }
Startup.cs
あたりで設定ファイルを読み込んでNlogを設定すればいい感じかなと思います。
private void NLogSettings() { Logging.LoggingSettings.ConsoleLogInit(); var fileName = this.Configuration["Logging:OutputToLogFile:FilePath"]; var logLevel = this.Configuration["Logging:OutputToLogFile:LogLevel"]; // NLogの設定を行う~~ }
これでWebAppsのアプリケーション設定への設定のみでログ出力先の挿げ替えなどが簡単に行えるようになりました。
終わりに
今回作成したモロモロのデモは下記リポジトリになります。
ASP.net WebAPIの ActionFilterAttribute
や ExceptionFilterAttribute
使ったり
EntityFrameworkの実行SQLをログ出力したりする実験コードも含んでたりします。