Cara menggunakan SDK server backend ASP.NET Core
Nota
Produk ini dihentikan. Untuk pengganti proyek yang menggunakan .NET 8 atau yang lebih baru, lihat pustaka Community Toolkit Datasync.
Artikel ini menunjukkan Anda harus mengonfigurasi dan menggunakan SDK server backend ASP.NET Core untuk menghasilkan server sinkronisasi data.
Platform yang didukung
Server backend ASP.NET Core mendukung ASP.NET 6.0 atau yang lebih baru.
Server database harus memenuhi kriteria berikut memiliki bidang jenis DateTime
atau Timestamp
yang disimpan dengan akurasi milidetik. Implementasi repositori disediakan untuk Inti Kerangka Kerja Entitas
Untuk dukungan database tertentu, lihat bagian berikut ini:
- Azure SQL dan SQL Server
- Azure Cosmos DB
-
PostgreSQL -
SqLite - liteDb
Membuat server sinkronisasi data baru
Server sinkronisasi data menggunakan mekanisme normal ASP.NET Core untuk membuat server. Ini terdiri dari tiga langkah:
- Buat proyek server ASP.NET 6.0 (atau yang lebih baru).
- Menambahkan Entity Framework Core
- Menambahkan Layanan sinkronisasi data
Untuk informasi tentang membuat layanan ASP.NET Core dengan Entity Framework Core, lihat tutorial.
Untuk mengaktifkan layanan sinkronisasi data, Anda perlu menambahkan pustaka NuGet berikut:
- Microsoft.AspNetCore.Datasync
- tabel berbasis Microsoft.AspNetCore.Datasync.EFCore untuk Entity Framework Core.
- Microsoft.AspNetCore.Datasync.InMemory untuk tabel dalam memori.
Ubah file Program.cs
. Tambahkan baris berikut di bawah semua definisi layanan lainnya:
builder.Services.AddDatasyncControllers();
Anda juga dapat menggunakan templat datasync-server
ASP.NET Core:
# 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
Templat mencakup model sampel dan pengontrol.
Membuat pengontrol tabel untuk tabel SQL
Repositori default menggunakan Entity Framework Core. Membuat pengontrol tabel adalah proses tiga langkah:
- Buat kelas model untuk model data.
- Tambahkan kelas model ke
DbContext
untuk aplikasi Anda. - Buat kelas
TableController<T>
baru untuk mengekspos model Anda.
Membuat kelas model
Semua kelas model harus menerapkan ITableData
. Setiap jenis repositori memiliki kelas abstrak yang mengimplementasikan ITableData
. Repositori Entity Framework Core menggunakan 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; }
}
Antarmuka ITableData
menyediakan ID rekaman, bersama dengan properti tambahan untuk menangani layanan sinkronisasi data:
-
UpdatedAt
(DateTimeOffset?
) menyediakan tanggal rekaman terakhir diperbarui. -
Version
(byte[]
) memberikan nilai buram yang berubah pada setiap tulisan. -
Deleted
(bool
) benar jika rekaman ditandai untuk dihapus tetapi belum dihapus menyeluruh.
Pustaka Sinkronisasi data mempertahankan properti ini. Jangan ubah properti ini dalam kode Anda sendiri.
Memperbarui DbContext
Setiap model dalam database harus terdaftar di DbContext
. Misalnya:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
Membuat pengontrol tabel
Pengontrol tabel adalah ApiController
khusus . Berikut adalah pengontrol tabel minimal:
[Route("tables/[controller]")]
public class TodoItemController : TableController<TodoItem>
{
public TodoItemController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<TodoItem>(context);
}
}
Nota
- Pengontrol harus memiliki rute. Menurut konvensi, tabel diekspos pada subpath
/tables
, tetapi dapat ditempatkan di mana saja. Jika Anda menggunakan pustaka klien yang lebih lama dari v5.0.0, maka tabel harus merupakan subjalur/tables
. - Pengontrol harus mewarisi dari
TableController<T>
, di mana<T>
adalah implementasi implementasiITableData
untuk jenis repositori Anda. - Tetapkan repositori berdasarkan jenis yang sama dengan model Anda.
Menerapkan repositori dalam memori
Anda juga dapat menggunakan repositori dalam memori tanpa penyimpanan persisten. Tambahkan layanan singleton untuk repositori di Program.cs
Anda:
IEnumerable<Model> seedData = GenerateSeedData();
builder.Services.AddSingleton<IRepository<Model>>(new InMemoryRepository<Model>(seedData));
Siapkan pengontrol tabel Anda sebagai berikut:
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public MovieController(IRepository<Model> repository) : base(repository)
{
}
}
Mengonfigurasi opsi pengontrol tabel
Anda dapat mengonfigurasi aspek pengontrol tertentu menggunakan TableControllerOptions
:
[Route("tables/[controller]")]
public class MoodelController : TableController<Model>
{
public ModelController(IRepository<Model> repository) : base(repository)
{
Options = new TableControllerOptions { PageSize = 25 };
}
}
Opsi yang dapat Anda atur meliputi:
-
PageSize
(int
, default: 100) adalah jumlah maksimum item yang dikembalikan operasi kueri dalam satu halaman. -
MaxTop
(int
, default: 512000) adalah jumlah maksimum item yang dikembalikan dalam operasi kueri tanpa penomoran halaman. -
EnableSoftDelete
(bool
, default: false) memungkinkan penghapusan sementara, yang menandai item sebagai dihapus alih-alih menghapusnya dari database. Penghapusan sementara memungkinkan klien memperbarui cache offline mereka, tetapi mengharuskan item yang dihapus dihapus menyeluruh dari database secara terpisah. -
UnauthorizedStatusCode
(int
, default: 401 Tidak Sah) adalah kode status yang dikembalikan ketika pengguna tidak diizinkan untuk melakukan tindakan.
Mengonfigurasi izin akses
Secara default, pengguna dapat melakukan apa pun yang ingin mereka entitaskan dalam tabel - membuat, membaca, memperbarui, dan menghapus rekaman apa pun. Untuk kontrol yang lebih halus atas otorisasi, buat kelas yang mengimplementasikan IAccessControlProvider
.
IAccessControlProvider
menggunakan tiga metode untuk menerapkan otorisasi:
-
GetDataView()
mengembalikan lambda yang membatasi apa yang dapat dilihat pengguna yang terhubung. -
IsAuthorizedAsync()
menentukan apakah pengguna yang terhubung dapat melakukan tindakan pada entitas tertentu yang diminta. -
PreCommitHookAsync()
menyesuaikan entitas apa pun segera sebelum ditulis ke repositori.
Di antara ketiga metode tersebut, Anda dapat menangani sebagian besar kasus kontrol akses secara efektif. Jika Anda memerlukan akses ke HttpContext
, mengonfigurasi HttpContextAccessor.
Sebagai contoh, berikut ini mengimplementasikan tabel pribadi, di mana pengguna hanya dapat melihat rekaman mereka sendiri.
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;
}
}
Metode ini asinkron jika Anda perlu melakukan pencarian database tambahan untuk mendapatkan jawaban yang benar. Anda dapat mengimplementasikan antarmuka IAccessControlProvider<T>
pada pengontrol, tetapi Anda masih harus meneruskan IHttpContextAccessor
untuk mengakses HttpContext
dengan cara yang aman di utas.
Untuk menggunakan penyedia kontrol akses ini, perbarui TableController
Anda sebagai berikut:
[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);
}
}
Jika Anda ingin mengizinkan akses yang tidak diautentikasi dan diautentikasi ke tabel, hiasi dengan [AllowAnonymous]
alih-alih [Authorize]
.
Mengonfigurasi pengelogan
Pengelogan ditangani melalui mekanisme pengelogan normal untuk ASP.NET Core. Tetapkan objek ILogger
ke properti 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;
}
}
Memantau perubahan repositori
Saat repositori diubah, Anda dapat memicu alur kerja, mencatat respons ke klien, atau melakukan pekerjaan lain dalam salah satu dari dua metode:
Opsi 1: Menerapkan PostCommitHookAsync
Antarmuka IAccessControlProvider<T>
menyediakan metode PostCommitHookAsync()
. Metode Th PostCommitHookAsync()
dipanggil setelah data ditulis ke repositori tetapi sebelum mengembalikan data ke klien. Perawatan harus dilakukan untuk memastikan bahwa data yang dikembalikan ke klien tidak diubah dalam metode ini.
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.
}
}
Gunakan opsi ini jika Anda menjalankan tugas asinkron sebagai bagian dari hook.
Opsi 2: Gunakan penanganan aktivitas RepositoryUpdated
Kelas dasar TableController<T>
berisi penanganan aktivitas yang dipanggil secara bersamaan dengan metode 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
}
}
Mengaktifkan Identitas Azure App Service
Server sinkronisasi data ASP.NET Core mendukung ASP.NET Core Identity, atau skema autentikasi dan otorisasi lain yang ingin Anda dukung. Untuk membantu peningkatan dari versi Azure Mobile Apps sebelumnya, kami juga menyediakan idP yang menerapkan Azure App Service Identity. Untuk mengonfigurasi Azure App Service Identity di aplikasi Anda, edit Program.cs
Anda :
builder.Services.AddAuthentication(AzureAppServiceAuthentication.AuthenticationScheme)
.AddAzureAppServiceAuthentication(options => options.ForceEnable = true);
// Then later, after you have created the app
app.UseAuthentication();
app.UseAuthorization();
Dukungan Database
Entity Framework Core tidak menyiapkan pembuatan nilai untuk kolom tanggal/waktu. (Lihat pembuatan nilai tanggal/waktu). Repositori Azure Mobile Apps untuk Entity Framework Core secara otomatis memperbarui bidang UpdatedAt
untuk Anda. Namun, jika database Anda diperbarui di luar repositori, Anda harus mengatur bidang UpdatedAt
dan Version
untuk diperbarui.
Azure SQL
Buat pemicu untuk setiap entitas:
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
Anda dapat menginstal pemicu ini menggunakan migrasi atau segera setelah EnsureCreated()
untuk membuat database.
Azure Cosmos DB
Azure Cosmos DB adalah database NoSQL yang dikelola sepenuhnya untuk aplikasi berkinerja tinggi dengan ukuran atau skala apa pun. Lihat Penyedia Azure Cosmos DB untuk informasi tentang penggunaan Azure Cosmos DB dengan Entity Framework Core. Saat menggunakan Azure Cosmos DB dengan Azure Mobile Apps:
Siapkan Kontainer Cosmos dengan indeks komposit yang menentukan bidang
UpdatedAt
danId
. Indeks komposit dapat ditambahkan ke kontainer melalui portal Microsoft Azure, ARM, Bicep, Terraform, atau dalam kode. Berikut adalah contoh definisi sumber daya 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' } ] ] } } } }
Jika Anda menarik subset item dalam tabel, pastikan Anda menentukan semua properti yang terlibat dalam kueri.
Dapatkan model dari kelas
ETagEntityTableData
:public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }
Tambahkan metode
OnModelCreating(ModelBuilder)
keDbContext
. Driver Cosmos DB untuk Kerangka Kerja Entitas menempatkan semua entitas ke dalam kontainer yang sama secara default. Minimal, Anda harus memilih kunci partisi yang sesuai dan memastikan propertiEntityTag
ditandai sebagai tag konkurensi. Misalnya, cuplikan berikut menyimpan entitasTodoItem
dalam kontainer mereka sendiri dengan pengaturan yang sesuai untuk Azure Mobile Apps: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 didukung dalam paket nuGet Microsoft.AspNetCore.Datasync.EFCore
sejak v5.0.11. Untuk informasi selengkapnya, tinjau tautan berikut:
- Sampel Cosmos DB .
- dokumentasi Penyedia Azure Cosmos DB EF Core.
- dokumentasi kebijakan indeks Cosmos DB.
PostgreSQL
Buat pemicu untuk setiap entitas:
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();
Anda dapat menginstal pemicu ini menggunakan migrasi atau segera setelah EnsureCreated()
untuk membuat database.
SqLite
Peringatan
Jangan gunakan SqLite untuk layanan produksi. SqLite hanya cocok untuk penggunaan sisi klien dalam produksi.
SqLite tidak memiliki bidang tanggal/waktu yang mendukung akurasi milidetik. Dengan demikian, itu tidak cocok untuk apa pun kecuali untuk pengujian. Jika Anda ingin menggunakan SqLite, pastikan Anda menerapkan pengonversi nilai dan pembanding nilai pada setiap model untuk properti tanggal/waktu. Metode termudah untuk mengimplementasikan pengonversi nilai dan pembanding ada dalam metode OnModelCreating(ModelBuilder)
DbContext
Anda :
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);
}
Instal pemicu pembaruan saat Anda menginisialisasi database:
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);
}
}
}
Pastikan bahwa metode InstallUpdateTriggers
hanya dipanggil sekali selama inisialisasi database:
public void InitializeDatabase(DbContext context)
{
bool created = context.Database.EnsureCreated();
if (created && context.Database.IsSqlite())
{
InstallUpdateTriggers(context);
}
context.Database.SaveChanges();
}
LiteDB
LiteDB adalah database tanpa server yang dikirimkan dalam satu DLL kecil yang ditulis dalam kode terkelola .NET C#. Ini adalah solusi database NoSQL yang sederhana dan cepat untuk aplikasi yang berdiri sendiri. Untuk menggunakan LiteDb dengan penyimpanan persisten pada disk:
Instal paket
Microsoft.AspNetCore.Datasync.LiteDb
dari NuGet.Tambahkan singleton untuk
LiteDatabase
keProgram.cs
:const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
Memperoleh model dari
LiteDbTableData
:public class TodoItem : LiteDbTableData { public string Title { get; set; } public bool Completed { get; set; } }
Anda dapat menggunakan salah satu atribut
BsonMapper
yang disediakan dengan paket LiteDb NuGet.Buat pengontrol menggunakan
LiteDbRepository
:[Route("tables/[controller]")] public class TodoItemController : TableController<TodoItem> { public TodoItemController(LiteDatabase db) : base() { Repository = new LiteDbRepository<TodoItem>(db, "todoitems"); } }
Dukungan OpenAPI
Anda dapat menerbitkan API yang ditentukan oleh pengontrol sinkronisasi data menggunakan NSwag atau Swashbuckle. Dalam kedua kasus, mulailah dengan menyiapkan layanan seperti yang biasanya Anda lakukan untuk pustaka yang dipilih.
NSwag
Ikuti instruksi dasar untuk integrasi NSwag, lalu ubah sebagai berikut:
Tambahkan paket ke proyek Anda untuk mendukung NSwag. Paket berikut diperlukan:
Tambahkan yang berikut ini ke bagian atas file
Program.cs
Anda:using Microsoft.AspNetCore.Datasync.NSwag;
Tambahkan layanan untuk menghasilkan definisi OpenAPI ke file
Program.cs
Anda:builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });
Aktifkan middleware untuk melayani dokumen JSON yang dihasilkan dan antarmuka pengguna Swagger, juga di
Program.cs
:if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUI3(); }
Menelusuri ke titik akhir /swagger
layanan web memungkinkan Anda menelusuri API. Definisi OpenAPI kemudian dapat diimpor ke layanan lain (seperti Azure API Management). Untuk informasi selengkapnya tentang mengonfigurasi NSwag, lihat Mulai menggunakan NSwag dan ASP.NET Core.
Swashbuckle
Ikuti instruksi dasar untuk integrasi Swashbuckle, lalu ubah sebagai berikut:
Tambahkan paket ke proyek Anda untuk mendukung Swashbuckle. Paket berikut diperlukan:
Tambahkan layanan untuk menghasilkan definisi OpenAPI ke file
Program.cs
Anda:builder.Services.AddSwaggerGen(options => { options.AddDatasyncControllers(); }); builder.Services.AddSwaggerGenNewtonsoftSupport();
Nota
Metode
AddDatasyncControllers()
mengambilAssembly
opsional yang sesuai dengan rakitan yang berisi pengontrol tabel Anda. ParameterAssembly
hanya diperlukan jika pengontrol tabel Anda berada dalam proyek yang berbeda dengan layanan.Aktifkan middleware untuk melayani dokumen JSON yang dihasilkan dan antarmuka pengguna Swagger, juga di
Program.cs
:if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); options.RoutePrefix = string.Empty; }); }
Dengan konfigurasi ini, menelusuri ke akar layanan web memungkinkan Anda menelusuri API. Definisi OpenAPI kemudian dapat diimpor ke layanan lain (seperti Azure API Management). Untuk informasi selengkapnya tentang mengonfigurasi Swashbuckle, lihat Mulai menggunakan Swashbuckle dan ASP.NET Core.
Keterbatasan
Edisi ASP.NET Core dari pustaka layanan mengimplementasikan OData v4 untuk operasi daftar. Saat server berjalan dalam mode kompatibilitas mundur, pemfilteran pada substring tidak didukung.