💻

Avanade Beef でカスタムのデータ ソースを使ってみる

2022/06/21に公開

はじめに

Avanade Beef (以下、Beef) は ASP.NET Core をベースとする Web API の自動生成ツールです。

https://github.com/Avanade/Beef

概要については以下のスライドもご覧ください。

Beef は既定で RDBMS (ストアドプロシージャまたは Entity Framework)、Cosmos DB、OData をデータソースとして扱うことができますが、それ以外のデータ ソースをカスタムとして組み込むこともできます。今回はサンプルとして、Microsoft Graph をデータ ソースとして Azure AD のすべてのユーザーを取得する API を作成する例を見てみたいと思います。

サンプル コード

https://github.com/karamem0/samples/tree/main/avanade-beef-with-microsoft-graph

実行手順

プロジェクトの作成

今回はデータ ソースを None としてプロジェクトを作成します。

dotnet new beef --company Karamem0 --appname SampleApplication --datasource None

Api および Business プロジェクトに Microsoft.Graph を追加します。

dotnet add package Microsoft.Graph

コードの修正

entity.beef.yaml

エンティティを定義します。ユーザーの一覧を返すので getAll: true とします。またデータ ソースはカスタムで実装するので autoImplement: None とするのがポイントです。

entityScope: Autonomous
appBasedAgentArgs: true
webApiAutoLocation: true
eventOutbox: None
entities:
  - name: User
    getAll: true
    collection: true
    collectionResult: true
    webApiRoutePrefix: users
    autoImplement: None
    properties:
      - name: Id
        type: Guid
        uniqueKey: true
      - name: UserPrincipalName
        type: string
      - name: DisplayName
        type: string

コードを自動生成します。

dotnet run all

Business/Data/UserData.cs

autoImplement: None としたので自動生成される UserData.cs の実装は以下のようになっています。

/// <summary>
/// Gets the <see cref="UserCollectionResult"/> that contains the items that match the selection criteria.
/// </summary>
/// <returns>The <see cref="UserCollectionResult"/>.</returns>
public Task<UserCollectionResult> GetAllAsync() => DataInvoker.Current.InvokeAsync(this, () => GetAllOnImplementationAsync());

ここで呼び出される GetAllOnImplementationAsync メソッドを自前で実装する必要があります。そこで UserData.cs をパーシャル クラスとして追加します (Generated フォルダーにある UserData.cs は直接修正しないでください)。コンストラクターを追加して GraphServiceClient を受け取るようにして、GetAllOnImplementationAsyncGraphServiceClient を使って取得したユーザーの一覧を返すようにします。

namespace Karamem0.SampleApplication.Business.Data
{
    /// <summary>
    /// Provides the <see cref="User"/> data access.
    /// </summary>
    public partial class UserData : IUserData
    {
        private readonly GraphServiceClient _graphServiceClient;

        /// <summary>
        /// Initializes a new instance of the <see cref="UserData"/> class.
        /// </summary>
        public UserData(GraphServiceClient graphServiceClient) : this()
        {
            _graphServiceClient = graphServiceClient;
        }

        /// <summary>
        /// Gets the <see cref="UserCollectionResult"/> that contains the items that match the selection criteria.
        /// </summary>
        /// <returns>The <see cref="UserCollectionResult"/>.</returns>
        public async Task<UserCollectionResult> GetAllOnImplementationAsync()
        {
            var __result = await _graphServiceClient.Users.Request().GetAsync();
            return new UserCollectionResult(__result.Select(_ => new Entities.User()
            {
                Id = new Guid(_.Id),
                UserPrincipalName = _.UserPrincipalName,
                DisplayName = _.DisplayName,
            }));
        }
    }
}

Api/Startup.cs

UserData.cs に DI で GraphServiceClient を渡せるように ConfigureServices メソッドを構成します。今回はサンプルなのでわかりやすさのために Client Credentials Grant を使っていますが 絶対に真似しないでください。通常は AddMicrosoftGraph メソッドで構成することをおすすめします。

services.AddSingleton<GraphServiceClient>(services =>
{
    var credential = new ClientSecretCredential(
    _config.GetValue<string>("MicrosoftGraph:TenantId"),
    _config.GetValue<string>("MicrosoftGraph:ClientId"),
    _config.GetValue<string>("MicrosoftGraph:ClientSecret"));
    var scopes = new[] { "https://graph.microsoft.com/.default" };
    var client = new GraphServiceClient(credential);
    return client;
});

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/scenario-web-api-call-api-app-configuration?WT.mc_id=M365-MVP-5002941

Api/webapisettings.json

別途 Azure AD アプリケーションを登録してその情報を webapisettings.json に設定します。

"MicrosoftGraph": {
  "TenantId": "{{tenent-id}}",
  "ClientId": "{{client-id}}",
  "ClientSecret": "{{tenent-secret}}"
},

実行結果

プロジェクトをデバッグ実行します。ブラウザーで http://localhost:5000/swagger を起動し /users を実行して結果が取得できることを確認します。

おわりに

カスタムのデータ ソースを使用した場合でも、データ アクセス層のみを実装すればいいので、簡単に実装できることがわかります。既定のデータ ソースを使用する場合でも、ロジックをカスタマイズしたいときは同様のテクニックを使用するので、ぜひ覚えておきたいです。

Discussion