ASP.NET Core バックエンド サーバー SDK の使用方法
この記事では、データ同期サーバーを生成するために ASP.NET Core バックエンド サーバー SDK を構成して使用する必要があることを示します。
サポートされているプラットフォーム
ASP.NET Core バックエンド サーバーは、ASP.NET 6.0 以降をサポートしています。
データベース サーバーは、ミリ秒精度で保存される DateTime
型か Timestamp
型のフィールドを持つ次の条件を満たす必要があります。 リポジトリの実装は、Entity Framework Core および LiteDb 用に提供されます。
特定のデータベースのサポートについては、次のセクションを参照してください。
新しいデータ同期サーバーを作成する
データ同期サーバーは、通常の ASP.NET Core メカニズムを使用してサーバーを作成します。 これは、次の 3 つのステップで構成されています。
- ASP.NET 6.0 (以降) のサーバー プロジェクトを作成します。
- Entity Framework Core の追加
- データ同期サービスの追加
Entity Framework Core を使用した ASP.NET Core サービスの作成の詳細については、「チュートリアル」を参照してください。
データ同期サービスを有効にするには、次の NuGet ライブラリを追加する必要があります。
- Microsoft.AspNetCore.Datasync
- Entity Framework Core ベースのテーブルの場合は、 Microsoft.AspNetCore.Datasync.EFCore。
- インメモリ テーブルの場合は、Microsoft.AspNetCore.Datasync.InMemory。
Program.cs
ファイルを変更します。 他のすべてのサービス定義の下に次の行を追加します。
builder.Services.AddDatasyncControllers();
[ASP.NET Core] datasync-server
テンプレートを使用することもできます。
# This only needs to be done once
dotnet new -i Microsoft.AspNetCore.Datasync.Template.CSharp
mkdir My.Datasync.Server
cd My.Datasync.Server
dotnet new datasync-server
このテンプレートには、サンプル モデルとコントローラーが含まれています。
SQL テーブルのテーブル コントローラーを作成する
既定のリポジトリでは、Entity Framework Core が使用されます。 テーブル コントローラーの作成は、3 つの手順からなるプロセスです。
- データ モデルのモデル クラスを作成します。
- アプリケーションの
DbContext
にモデル クラスを追加します。 - 新しい
TableController<T>
クラスを作成し、モデルを公開します。
モデル クラスを作成する
すべてのモデル クラスは ITableData
を実装する必要があります。 各リポジトリ型には、ITableData
を実装する抽象クラスがあります。 Entity Framework Core リポジトリでは次のように EntityTableData
が使用されます。
public class TodoItem : EntityTableData
{
/// <summary>
/// Text of the Todo Item
/// </summary>
public string Text { get; set; }
/// <summary>
/// Is the item complete?
/// </summary>
public bool Complete { get; set; }
}
ITableData
インターフェイスは、レコードの ID と、データ同期サービスを処理するための追加のプロパティを提供します。
UpdatedAt
(DateTimeOffset?
) は、レコードが最後に更新された日付を示します。Version
(byte[]
) は、書き込みのたびに変更される不透明な値を提供します。Deleted
(bool
) は、レコードが削除対象としてマークされているが、まだ消去されていない場合は true です。
これらのプロパティはデータ同期ライブラリによって管理されます。 独自のコードでこれらのプロパティを変更しないでください。
DbContext
を更新します
データベース内の各モデルは、DbContext
に登録される必要があります。 次に例を示します。
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
テーブル コントローラーを作成する
テーブル コントローラーは特殊化された ApiController
です。 最小のテーブル コントローラーは次のようになります。
[Route("tables/[controller]")]
public class TodoItemController : TableController<TodoItem>
{
public TodoItemController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<TodoItem>(context);
}
}
Note
- コントローラーはルートを持っている必要があります。 慣例により、テーブルは
/tables
のサブパスに公開されますが、任意の場所に配置できます。 v5.0.0 より前のクライアント ライブラリを使用している場合、テーブルは/tables
のサブパスである必要があります。 - コントローラーは
TableController<T>
を継承する必要があります。<T>
はリポジトリ タイプに対応したITableData
実装の実装です。 - モデルと同じ型に基づいてリポジトリを割り当てます。
インメモリ リポジトリの実装
また、永続ストレージを使用せずにインメモリ リポジトリを使用することもできます。 Program.cs
にリポジトリのシングルトン サービスを追加します。
IEnumerable<Model> seedData = GenerateSeedData();
builder.Services.AddSingleton<IRepository<Model>>(new InMemoryRepository<Model>(seedData));
テーブル コントローラーを次のように設定します。
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public MovieController(IRepository<Model> repository) : base(repository)
{
}
}
テーブル コントローラー オプションの構成
次のように TableControllerOptions
を使用して、コントローラーの特定の側面を設定できます。
[Route("tables/[controller]")]
public class MoodelController : TableController<Model>
{
public ModelController(IRepository<Model> repository) : base(repository)
{
Options = new TableControllerOptions { PageSize = 25 };
}
}
設定できるオプションには次が含まれます。
PageSize
(int
、既定値: 100) は、クエリ操作から 1 ページに返される項目の最大数です。MaxTop
(int
、既定値: 512000) は、ページングを行わずにクエリ操作で返される項目の最大数です。EnableSoftDelete
(bool
、既定値: false) は、論理的な削除を有効にします。これは、項目をデータベースから削除するのではなく、削除済みとしてマークします。 論理的な削除では、クライアントはオフライン キャッシュを更新できますが、削除された項目はデータベースから個別に削除する必要があります。UnauthorizedStatusCode
(int
、既定値: 401 未承認) は、ユーザーがアクションの実行を許可されていない場合に返される状態コードです。
アクセス許可のコンフィギュレーション
既定では、ユーザーはテーブル内のエンティティに対して必要なあらゆる操作を実行できます。つまり、レコードの作成、読み取り、更新、削除を行うことができます。 承認をより細かく制御するには、IAccessControlProvider
を実装するクラスを作成します。 IAccessControlProvider
では、次の 3 つの方法で承認を実装します。
GetDataView()
は、接続されているユーザーに表示される内容を制限するラムダを返します。IsAuthorizedAsync()
は、接続されているユーザーが、要求されている特定のエンティティに対してアクションを実行できるかどうかを判断します。PreCommitHookAsync()
は、リポジトリに書き込まれる直前に、エンティティを調整します。
この 3 つの方法の間では、ほとんどのアクセス制御ケースを効果的に処理できます。 HttpContext
へのアクセスが必要な場合は、HttpContextAccessor を構成します。
たとえば、以下では、ユーザーが自分のレコードのみを表示できる個人用テーブルを実装します。
public class PrivateAccessControlProvider<T>: IAccessControlProvider<T>
where T : ITableData
where T : IUserId
{
private readonly IHttpContextAccessor _accessor;
public PrivateAccessControlProvider(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
private string UserId { get => _accessor.HttpContext.User?.Identity?.Name; }
public Expression<Func<T,bool>> GetDataView()
{
return (UserId == null)
? _ => false
: model => model.UserId == UserId;
}
public Task<bool> IsAuthorizedAsync(TableOperation op, T entity, CancellationToken token = default)
{
if (op == TableOperation.Create || op == TableOperation.Query)
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(entity?.UserId != null && entity?.UserId == UserId);
}
}
public virtual Task PreCommitHookAsync(TableOperation operation, T entity, CancellationToken token = default)
{
entity.UserId == UserId;
return Task.CompletedTask;
}
}
適切な回答を得るために追加のデータベースの検索を行う必要がある場合に備えて、メソッドは非同期になっています。 コントローラーに IAccessControlProvider<T>
インターフェイスを実装できますが、スレッド セーフな方法で HttpContext
にアクセスするためには、IHttpContextAccessor
を渡す必要が依然としてあります。
このアクセス制御プロバイダーを使用するには、次のように TableController
を更新します。
[Authorize]
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public ModelsController(AppDbContext context, IHttpContextAccessor accessor) : base()
{
AccessControlProvider = new PrivateAccessControlProvider<Model>(accessor);
Repository = new EntityTableRepository<Model>(context);
}
}
テーブルに対して非認証のアクセスと認証済みのアクセスの両方を許可する場合は、[Authorize]
ではなく、[AllowAnonymous]
を使用して修飾します。
ログの構成
ログは、ASP.NET Core の通常のログ メカニズムで処理されます。 ILogger
オブジェクトを Logger
プロパティに割り当てます。
[Authorize]
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public ModelController(AppDbContext context, Ilogger<ModelController> logger) : base()
{
Repository = new EntityTableRepository<Model>(context);
Logger = logger;
}
}
リポジトリの変更を監視する
リポジトリが変更されたときに、ワークフローをトリガーしたり、クライアントへの応答をログに記録したり、次の 2 つの方法のいずれかで他の作業を実行したりできます。
オプション 1: PostCommitHookAsync を実装する
IAccessControlProvider<T>
インターフェイスは PostCommitHookAsync()
メソッドを提供します。 この PostCommitHookAsync()
メソッドは、データがリポジトリに書き込まれた後、クライアントにデータを返す前に呼び出されます。 クライアントに返されるデータがこのメソッドで変更されないように注意する必要があります。
public class MyAccessControlProvider<T> : AccessControlProvider<T> where T : ITableData
{
public override async Task PostCommitHookAsync(TableOperation op, T entity, CancellationToken cancellationToken = default)
{
// Do any work you need to here.
// Make sure you await any asynchronous operations.
}
}
フックの一部として非同期タスクを実行している場合は、この方法を使用します。
オプション 2: RepositoryUpdated イベント ハンドラーを使用する
TableController<T>
基底クラスには、PostCommitHookAsync()
メソッドと同時に呼び出されるイベント ハンドラーが含まれています。
[Authorize]
[Route(tables/[controller])]
public class ModelController : TableController<Model>
{
public ModelController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<Model>(context);
RepositoryUpdated += OnRepositoryUpdated;
}
internal void OnRepositoryUpdated(object sender, RepositoryUpdatedEventArgs e)
{
// The RepositoryUpdatedEventArgs contains Operation, Entity, EntityName
}
}
Azure App Service ID の有効化
ASP.NET Core データ同期サーバーは、ASP.NET Core ID、またはサポートするその他の認証および承認スキームをサポートします。 以前のバージョンの Azure Mobile Apps からのアップグレードを支援するために、Azure App Service ID を実装する ID プロバイダーも提供します。 アプリケーションで Azure App Service ID を構成するには、次のように Program.cs
を編集します。
builder.Services.AddAuthentication(AzureAppServiceAuthentication.AuthenticationScheme)
.AddAzureAppServiceAuthentication(options => options.ForceEnable = true);
// Then later, after you have created the app
app.UseAuthentication();
app.UseAuthorization();
データベースのサポート
Entity Framework Core では、日付/時刻列の値の生成は設定されません。 (「 日付/時刻値の生成)。 Entity Framework Core 用の Azure Mobile Apps リポジトリによって、フィールドが自動的に UpdatedAt
更新されます。 ただし、データベースがリポジトリの外部で更新される場合は、更新するフィールドとVersion
フィールドをUpdatedAt
配置する必要があります。
Azure SQL
各エンティティのトリガーを作成します。
CREATE OR ALTER TRIGGER [dbo].[TodoItems_UpdatedAt] ON [dbo].[TodoItems]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE
[dbo].[TodoItems]
SET
[UpdatedAt] = GETUTCDATE()
WHERE
[Id] IN (SELECT [Id] FROM INSERTED);
END
このトリガーは、移行を使用するか、データベースを作成した直後 EnsureCreated()
にインストールできます。
Azure Cosmos DB
Azure Cosmos DB は、あらゆるサイズまたはスケールの高パフォーマンス アプリケーション向けのフル マネージド NoSQL データベースです。 Entity Framework Core で Azure Cosmos DB を使用する方法については、Azure Cosmos DB プロバイダーに関する記事を参照してください。 Azure Mobile Apps で Azure Cosmos DB を使用する場合:
UpdatedAt
フィールドとId
フィールドを指定する複合インデックスを使用して Cosmos コンテナーを設定します。 複合インデックスは、Azure portal、ARM、Bicep、Terraform を通じて、またはコード内でコンテナーに追加できます。 bicep リソース定義の例を次に示します。resource cosmosContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = { name: 'TodoItems' parent: cosmosDatabase properties: { resource: { id: 'TodoItems' partitionKey: { paths: [ '/Id' ] kind: 'Hash' } indexingPolicy: { indexingMode: 'consistent' automatic: true includedPaths: [ { path: '/*' } ] excludedPaths: [ { path: '/"_etag"/?' } ] compositeIndexes: [ [ { path: '/UpdatedAt' order: 'ascending' } { path: '/Id' order: 'ascending' } ] ] } } } }
テーブル内の項目のサブセットをプルする場合は、クエリに関連するすべてのプロパティを指定してください。
ETagEntityTableData
クラスからモデルを派生させます。public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }
OnModelCreating(ModelBuilder)
メソッドをDbContext
に追加します。 Entity Framework 用 Cosmos DB ドライバーでは、既定ですべてのエンティティが同じコンテナーに配置されます。 少なくとも、適切なパーティション キーを選択し、EntityTag
プロパティがコンカレンシー タグとしてマークされていることを確認する必要があります。 たとえば、次のスニペットは、Azure Mobile Apps の適切な設定を使用してTodoItem
エンティティを独自のコンテナーに格納します。protected override void OnModelCreating(ModelBuilder builder) { builder.Entity<TodoItem>(builder => { // Store this model in a specific container. builder.ToContainer("TodoItems"); // Do not include a discriminator for the model in the partition key. builder.HasNoDiscriminator(); // Set the partition key to the Id of the record. builder.HasPartitionKey(model => model.Id); // Set the concurrency tag to the EntityTag property. builder.Property(model => model.EntityTag).IsETagConcurrency(); }); base.OnModelCreating(builder); }
Azure Cosmos DB は、v5.0.11 以降、Microsoft.AspNetCore.Datasync.EFCore
NuGet パッケージでサポートされています。 詳しくは、次のリンクをご覧ください。
- Cosmos DB サンプル。
- EF Core Azure Cosmos DB プロバイダーに関するドキュメント。
- Cosmos DB インデックス ポリシーに関するドキュメント。
PostgreSQL
各エンティティのトリガーを作成します。
CREATE OR REPLACE FUNCTION todoitems_datasync() RETURNS trigger AS $$
BEGIN
NEW."UpdatedAt" = NOW() AT TIME ZONE 'UTC';
NEW."Version" = convert_to(gen_random_uuid()::text, 'UTF8');
RETURN NEW
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER
todoitems_datasync
BEFORE INSERT OR UPDATE ON
"TodoItems"
FOR EACH ROW EXECUTE PROCEDURE
todoitems_datasync();
このトリガーは、移行を使用するか、データベースを作成した直後 EnsureCreated()
にインストールできます。
SqLite
警告
運用環境のサービスには SqLite を使用しないでください。 SqLite は、運用環境のクライアント側の使用にのみ適しています。
SqLite には、ミリ秒の精度をサポートする日付/時刻フィールドがありません。 そのため、テスト以外の目的には適していません。 SqLite を使用する場合は、日付/時刻プロパティ用に、各モデルに値コンバーターと値比較子を実装してください。 値コンバーターと値比較子を実装する最も簡単な方法は、DbContext
の OnModelCreating(ModelBuilder)
メソッドにあります。
protected override void OnModelCreating(ModelBuilder builder)
{
var timestampProps = builder.Model.GetEntityTypes().SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(byte[]) && p.ValueGenerated == ValueGenerated.OnAddOrUpdate);
var converter = new ValueConverter<byte[], string>(
v => Encoding.UTF8.GetString(v),
v => Encoding.UTF8.GetBytes(v)
);
foreach (var property in timestampProps)
{
property.SetValueConverter(converter);
property.SetDefaultValueSql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')");
}
base.OnModelCreating(builder);
}
データベースを初期化するときに更新トリガーをインストールします。
internal static void InstallUpdateTriggers(DbContext context)
{
foreach (var table in context.Model.GetEntityTypes())
{
var props = table.GetProperties().Where(prop => prop.ClrType == typeof(byte[]) && prop.ValueGenerated == ValueGenerated.OnAddOrUpdate);
foreach (var property in props)
{
var sql = $@"
CREATE TRIGGER s_{table.GetTableName()}_{prop.Name}_UPDATE AFTER UPDATE ON {table.GetTableName()}
BEGIN
UPDATE {table.GetTableName()}
SET {prop.Name} = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')
WHERE rowid = NEW.rowid;
END
";
context.Database.ExecuteSqlRaw(sql);
}
}
}
InstallUpdateTriggers
メソッドが、データベースの初期化中に 1 回だけ呼び出されることを確認します。
public void InitializeDatabase(DbContext context)
{
bool created = context.Database.EnsureCreated();
if (created && context.Database.IsSqlite())
{
InstallUpdateTriggers(context);
}
context.Database.SaveChanges();
}
LiteDB
LiteDB は、.NET C# マネージド コードで記述された単一の小さい DLL として提供されるサーバーレス データベースです。 これは、スタンドアロン アプリケーション向けのシンプルで高速な NoSQL データベース ソリューションです。 ディスク上の永続記憶装置で LiteDb を使用するには:
NuGet から
Microsoft.AspNetCore.Datasync.LiteDb
パッケージをインストールします。LiteDatabase
のシングルトンをProgram.cs
に追加します。const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
LiteDbTableData
からモデルを派生させます。public class TodoItem : LiteDbTableData { public string Title { get; set; } public bool Completed { get; set; } }
LiteDb NuGet パッケージで提供される任意の
BsonMapper
属性を使用できます。LiteDbRepository
を使用して、コントローラーを作成します。[Route("tables/[controller]")] public class TodoItemController : TableController<TodoItem> { public TodoItemController(LiteDatabase db) : base() { Repository = new LiteDbRepository<TodoItem>(db, "todoitems"); } }
OpenAPI のサポート
NSwag または Swashbuckle を使用して、データ同期コントローラーによって定義された API を発行できます。 どちらの場合も、まずは通常と同じように、選択したライブラリ用にサービスを設定します。
NSwag
NSwag を統合する基本的な手順に従った後、次のように変更します。
NSwag をサポートするパッケージをプロジェクトに追加します。 次のパッケージが必要です。
Program.cs
ファイルの先頭に以下を追加します。using Microsoft.AspNetCore.Datasync.NSwag;
Program.cs
ファイルに、OpenAPI 定義を生成するサービスを追加します。builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });
同じく
Program.cs
で、生成された JSON ドキュメントと Swagger UI を提供するミドルウェアを有効にします。if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUI3(); }
Web サービスの /swagger
エンドポイントを参照すると、API を参照できます。 その後、OpenAPI 定義を他のサービス (Azure API Management など) にインポートできます。 NSwag の構成の詳細については、「NSwag と ASP.NET Core の概要」を参照してください。
Swashbuckle
Swashbuckle を統合する基本的な手順に従った後、次のように変更します。
Swashbuckle をサポートするパッケージをプロジェクトに追加します。 次のパッケージが必要です。
Program.cs
ファイルに、OpenAPI 定義を生成するサービスを追加します。builder.Services.AddSwaggerGen(options => { options.AddDatasyncControllers(); }); builder.Services.AddSwaggerGenNewtonsoftSupport();
Note
AddDatasyncControllers()
メソッドは、オプションのパラメーターとして、テーブル コントローラーが格納されたアセンブリに対応するAssembly
を受け取ります。Assembly
パラメーターは、テーブル コントローラーがサービスとは異なるプロジェクトに含まれている場合にのみ必要です。同じく
Program.cs
で、生成された JSON ドキュメントと Swagger UI を提供するミドルウェアを有効にします。if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); options.RoutePrefix = string.Empty; }); }
この構成により、Web サービスのルートを参照すると API を参照できます。 その後、OpenAPI 定義を他のサービス (Azure API Management など) にインポートできます。 Swashbuckle の構成の詳細については、「Swashbuckle と ASP.NET Core の概要」を参照してください。
制限事項
サービス ライブラリの ASP.NET Core エディションでは、リスト操作用に OData v4 が実装されています。 サーバーが下位互換性モードで実行されている場合、substring のフィルター処理はサポートされません。
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示