拡張ライブラリ
プリザンターには、起動時に外部の DLL を読み込む「拡張ライブラリ」機能があり、任意の Web API や Web ページを追加できます。このページでは、クラスライブラリで API コントローラを作り、プリザンターに組み込む手順と、プリザンターの認証を利用する方法を説明します。
読み込みの仕組み
- 起動時の処理は
Startup.csにあり、GetExtendedLibraryPathsで対象パスを取得しています(1.5.8.1 のソースで確認)。 - 対象は
Assembly.GetEntryAssembly().Locationのフォルダ(Implem.Pleasanter.dllがあるフォルダ)直下のExtendedLibrariesと、その1 階層下のサブフォルダです(該当コード)。2 階層以上深いフォルダの DLL は読み込まれません。 - 各フォルダの
*.dllをまとめて読み込み、MvcBuilder にAddApplicationPartで登録します。 - DLL の中に
Implem.Pleasanter.NetCore.ExtendedLibrary.ExtendedLibraryクラスがあれば、そのInitializeメソッド(引数なしの static メソッド)も呼び出します。
図を読み込み中…
実装上の決まりごと
インターフェースの実装などは求められず、次の規約だけで動きます。
| 要素 | 必須 | 内容 |
|---|---|---|
ExtendedLibrary クラス | 任意 | 無くても読み込みは成功する |
Initialize() | 任意 | ExtendedLibrary クラスがあるときだけ呼ばれる |
| コントローラ | 任意 | ControllerBase を継承したクラスがあれば MVC に登録される |
読み込みで注意すること
- 読み込みに try-catch がありません。
Assembly.LoadFromやInitializeで例外が出ると、プリザンターの起動そのものが止まります。本番に置く前にテスト環境で起動を確かめてください。 - DLL を読むのは起動時だけです。差し替えたら必ずプリザンターを再起動します。
- 独自の
AssemblyLoadContextは使わず、本体と同じ既定のコンテキストに読み込みます。サブフォルダに分けても、アセンブリが分離されるわけではありません。 - 本体が既に読み込んでいるのと同じ名前の DLL(
Newtonsoft.Json.dllなど)を一緒に置いても、先に読み込まれた本体側が使われます。バージョンが違う DLL を置いて新しいバージョンにしかない API を使うと、実行時にMissingMethodExceptionやTypeLoadExceptionになり得ます。ExtendedLibrariesには自作の DLL だけを置き、本体が使っているライブラリは含めないでください。
URL(ルーティング)
AddApplicationPart で登録されたコントローラは、本体と同じルーティングに乗ります。
[Route]属性を付けたコントローラ(下の手順のapi/[controller]など)は属性ルーティングで、その URL になります。- 属性を付けない MVC コントローラは、本体の
Startup.Configureの規約ルート({controller}/{action}など)で呼ばれます(Startup.cs)。CustomPageControllerのIndexなら/CustomPage/Indexです。
手順
1. プロジェクトを作成する
プリザンターは 1.5.0.0 から .NET 10 になっているため、.NET 10 のクラスライブラリを作ります。
dotnet new classlib -n MyExtendedLibrary -f net10.02. ASP.NET Core への参照を追加する
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- ASP.NET Core MVC参照 -->
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>3. API コントローラを作成する
Hello と Echo を返すだけの例です。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MyExtendedLibrary.Controllers
{
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class CustomApiController : ControllerBase
{
[HttpGet("hello")]
public IActionResult Hello()
{
return Ok(new { message = "Hello from Extended Library!" });
}
[HttpPost("echo")]
public IActionResult Echo([FromBody] object data)
{
return Ok(new { echo = data });
}
}
}4. DLL を配置する
ビルドした DLL を次のどちらかに置きます。ライブラリごとに置き場所を分けられるので、サブディレクトリに置く方法がおすすめです。
| 配置場所 | パス |
|---|---|
| 基本 | {プリザンターのフォルダ}/ExtendedLibraries/ |
| サブディレクトリ | {プリザンターのフォルダ}/ExtendedLibraries/MyPlugin/ |
{プリザンターのフォルダ} は Implem.Pleasanter.dll があるフォルダです(静的ファイルを置く wwwroot フォルダではありません)。
5. 動作を確認する
/api/CustomApi/hello に GET でアクセスすると、Hello from Extended Library! が返ります。
初期化処理(Initialize)
起動時に初期化処理を行いたい場合は、次の名前空間・クラス名で Initialize を用意します。
namespace Implem.Pleasanter.NetCore.ExtendedLibrary
{
public static class ExtendedLibrary
{
public static void Initialize()
{
// 拡張DLL起動時の初期化処理
// バックグラウンドワーカーの起動など
Console.WriteLine("ExtendedLibrary initialized!");
}
}
}ただし名前空間が固定されているのはかなり面倒な制約になるため、実際に使う場面は多くありません。Initialize を使わないなら、名前空間は自由です。
プリザンターの認証を使う
プリザンターの Context を使うと、呼び出し元がプリザンターに登録済みのユーザかどうかを確認できます。Implem.Pleasanter をプロジェクト参照に追加して使います。認証の仕組み全体は 認証基盤(Cookie・API キー・Bearer) にまとめています。
本体をプロジェクト参照するときは、Private="false" と ExcludeAssets="runtime" を付けて、本体とその依存 DLL をビルド出力にコピーしないようにします。出力フォルダから ExtendedLibraries に置くのは自作の DLL(と、デバッグするなら pdb)だけです。
<ItemGroup>
<ProjectReference
Include="..\Implem.Pleasanter\Implem.Pleasanter.csproj"
Private="false"
ExcludeAssets="runtime" />
</ItemGroup>ビルド後の自動コピーやデバッガの付け方は 拡張ライブラリの開発・デバッグ にまとめています。
拡張ライブラリのコントローラに掛かるフィルタ
拡張ライブラリのコントローラも本体と同じ MVC に組み込まれる(AddApplicationPart)ため、本体のグローバルフィルタがそのまま掛かります(Startup.cs)。
| フィルタ | 掛かり方 | 内容 |
|---|---|---|
AuthorizeFilter(RequireAuthenticatedUser) | 全コントローラ | ログイン(Cookie 認証)していないと拒否。[AllowAnonymous] を付けると外れる |
CheckContextAttributes | 全コントローラ | IP 制限(/api/ 以外)、テナントの IP 制限・契約期限、CSRF トークン(TokenCheck 有効時のフォーム送信)、Users に居ないユーザーのサインアウトなど |
CheckApiContextAttributes | 属性を付けたコントローラだけ | 本文の読み取り、IP 制限、JSON 形式の確認、Cookie 認証時の CSRF トークン |
API キーだけで呼ばれる API(Cookie が無い)を作るときは、上の手順の例のように [AllowAnonymous] を付けないとグローバルの AuthorizeFilter で拒否されます。本体の API コントローラと同じく [AllowAnonymous] と [CheckApiContextAttributes](名前空間 Implem.PleasanterFilters)を付け、アクションの中で context.Authenticated を確かめる形にすると、IP 制限なども本体の API と揃います。
画面(Cookie 認証)から使う
ログイン済みのブラウザから呼ばれる MVC コントローラでは、引数なしで new Context() を生成するだけです。Cookie のログイン ID で Users テーブルが検索され、context.Authenticated・context.UserId・context.TenantId・context.DeptId・context.HasPrivilege などが設定されます。
public class CustomPageController : Controller
{
[HttpGet]
public ActionResult Index()
{
var context = new Context();
if (!context.Authenticated)
{
return StatusCode(403);
}
return Content($"Hello, {context.User.Name}", "text/plain");
}
}context.IsAuthenticated(Cookie が認証済みか)と context.Authenticated(Users に有効なユーザーが居るか)は別のプロパティです。判定には Authenticated を使います。
API キーで使う
API キーで認証するときは、次のように Context を生成します。
//リクエストでも埋込でも
var apiKey = "外部から与えられたAPIキー";
//Implem.Pleasanter.Libraries.Requests.Apiにデシリアライズ可能であること
var req = JsonSerializer.Serialize(new { ApiKey = apiKey });
var context = new Context(
sessionStatus: false,
sessionData: false,
apiRequestBody: req,
contentType: "application/json",
api: true);context.Authenticated が true なら認証済みで、context.UserId や context.TenantId などのユーザ情報も取得できます。詳細は Context.cs を参照してください。
context.LoginIdは Cookie(HttpContext.User)から取り出す値で、API キーで認証したときは設定されません。API キーのユーザーのログイン ID はcontext.User.LoginIdで取れます(SetUser)。- 本文に
ApiKeyがあればそれを優先し、無ければ Cookie のログイン ID でユーザーを決めます。 - 無効化(
Disabled)・ロックアウト中のユーザーは、API キーが正しくてもAuthenticated = falseです。 - ユーザーの「API の利用を許可」などの API 利用可否は、
Contextの生成時には確かめません。本体の API では各バリデータが確かめているので、必要なら拡張ライブラリ側でもcontext.UserSettings?.AllowApi(context: context)などで確認してください。 - multipart/form-data で受ける場合は、本体のファイルアップロード API と同じく
Authorization: Bearer <API キー>ヘッダーから API キーを取り出し、new { ApiKey = ... }の JSON にしてapiRequestBodyに渡す方法があります(BinariesController.cs)。
ライセンス
プリザンター Community Edition のライセンスは AGPL です。作成したライブラリも同じ条件で配布する必要があります。
活用例
カレンダーに iCal のインターフェースを追加したり、Webhook を受けるインターフェースを追加したりして、外部システムとの接続性を広げられます。ログインなしで見られるカレンダーを拡張ライブラリで作る設計は 拡張ライブラリで外部公開カレンダーを作る にまとめています。