共用方式為


如何使用 ASP.NET Core 後端伺服器 SDK

注意

此產品已淘汰。 如需使用 .NET 8 或更新版本的專案取代專案,請參閱 Community Toolkit Datasync 連結庫

本文說明您必須設定並使用 ASP.NET Core 後端伺服器 SDK 來產生資料同步伺服器。

支援的平臺

ASP.NET Core 後端伺服器支援 ASP.NET 6.0 或更新版本。

資料庫伺服器必須符合下列準則,才能使用毫秒精確度儲存的 DateTimeTimestamp 類型字段。 Entity Framework CoreLiteDb提供存放庫實作。

如需特定資料庫支援,請參閱下列各節:

建立新的資料同步伺服器

數據同步伺服器會使用一般 ASP.NET Core 機制來建立伺服器。 它包含三個步驟:

  1. 建立 ASP.NET 6.0 (或更新版本) 伺服器專案。
  2. 新增 Entity Framework Core
  3. 新增數據同步服務

如需使用 Entity Framework Core 建立 ASP.NET 核心服務的詳細資訊,請參閱教學課程

若要啟用資料同步服務,您需要新增下列 NuGet 連結庫:

修改 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。 建立資料表控制器是三個步驟的程式:

  1. 建立數據模型的模型類別。
  2. 將模型類別新增至應用程式的 DbContext
  3. 建立新的 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 介面會提供記錄的識別碼,以及處理數據同步服務的額外屬性:

  • UpdatedAtDateTimeOffset?) 提供上次更新記錄的日期。
  • Versionbyte[]) 提供不透明的值,可在每次寫入時變更。
  • 如果記錄標示為要刪除,但尚未清除,則 Deletedbool) 為 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);
    }
}

注意

  • 控制器必須有路由。 依照慣例,數據表會公開在 /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 };
    }
}

您可以設定的選項包括:

  • PageSizeint,預設值:100) 是單一頁面中傳回查詢作業的項目數上限。
  • MaxTopint,預設值:512000) 是查詢作業中傳回的項目數目上限,而不分頁。
  • EnableSoftDeletebool,預設值:false) 會啟用虛刪除,這會將項目標示為已刪除,而不是從資料庫刪除這些專案。 虛刪除可讓用戶端更新其離線快取,但需要個別從資料庫清除已刪除的專案。
  • UnauthorizedStatusCodeint,預設值:401 未經授權)是不允許使用者執行動作時傳回的狀態代碼。

設定訪問許可權

根據預設,用戶可以對數據表內的實體執行任何動作 - 建立、讀取、更新及刪除任何記錄。 如需更精細的授權控制,請建立實作 IAccessControlProvider的類別。 IAccessControlProvider 使用三種方法來實作授權:

  • GetDataView() 會傳回 Lambda,以限制連線的使用者可以看到的內容。
  • IsAuthorizedAsync() 判斷連線的使用者是否可以對要求的特定實體執行動作。
  • PreCommitHookAsync() 立即調整任何實體,再寫入存放庫。

在三種方法之間,您可以有效地處理大部分的訪問控制案例。 如果您需要存取 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> 介面,但仍必須傳入 IHttpContextAccessor,才能以安全線程的方式存取 HttpContext

若要使用此訪問控制提供者,請更新您的 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);
    }
}

如果您要允許未驗證與已驗證的資料表存取權,請使用 [AllowAnonymous] 裝飾它,而不是 [Authorize]

設定記錄

記錄是透過 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;
    }
}

監視存放庫變更

當存放庫變更時,您可以觸發工作流程、記錄用戶端的回應,或在下列兩種方法之一中執行其他工作:

選項 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 身分識別

ASP.NET Core 資料同步伺服器支援 ASP.NET Core Identity,或您想要支援的任何其他驗證和授權配置。 為了協助從舊版 Azure Mobile Apps 升級,我們也提供識別提供者,以實作 azure App Service 身分識別。 若要在應用程式中設定 Azure App Service 身分識別,請編輯您的 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 字段。 不過,如果您的資料庫在存放庫外部更新,您必須安排要更新的 UpdatedAtVersion 字段。

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 時:

  1. 使用複合索引設定 Cosmos 容器,以指定 UpdatedAtId 字段。 複合索引可以透過 Azure 入口網站、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'
                            }
                        ]
                    ]
                }
            }
        }
    }
    

    如果您提取資料表中的項目子集,請確定您指定查詢所涉及的所有屬性。

  2. ETagEntityTableData 類別衍生模型:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. 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);
    }
    

自 v5.0.11 起,Microsoft.AspNetCore.Datasync.EFCore NuGet 套件支援 Azure 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,請確定您針對日期/時間屬性在每個模型上實作值轉換器和值比較子。 實作值轉換器和比較子的最簡單方法是在 DbContextOnModelCreating(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 方法一次:

public void InitializeDatabase(DbContext context)
{
    bool created = context.Database.EnsureCreated();
    if (created && context.Database.IsSqlite())
    {
        InstallUpdateTriggers(context);
    }
    context.Database.SaveChanges();
}

LiteDB

LiteDB 是在以 .NET C# Managed 程式代碼撰寫的單一小型 DLL 中傳遞的無伺服器資料庫。 它是獨立應用程式的簡單且快速的 NoSQL 資料庫解決方案。 若要搭配磁碟上的永續性記憶體使用 LiteDb:

  1. 從 NuGet 安裝 Microsoft.AspNetCore.Datasync.LiteDb 套件。

  2. LiteDatabase 的單一新增至 Program.cs

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  3. LiteDbTableData衍生模型:

    public class TodoItem : LiteDbTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    

    您可以使用隨 LiteDb NuGet 套件提供的任何 BsonMapper 屬性。

  4. 使用 LiteDbRepository建立控制器:

    [Route("tables/[controller]")]
    public class TodoItemController : TableController<TodoItem>
    {
        public TodoItemController(LiteDatabase db) : base()
        {
            Repository = new LiteDbRepository<TodoItem>(db, "todoitems");
        }
    }
    

OpenAPI 支援

您可以使用 NSwagSwashbuckle來發佈資料同步控制器所定義的 API。 在這兩種情況下,從設定服務開始,就像您一般會針對所選的連結庫一樣。

NSwag

遵循 NSwag 整合的基本指示,然後修改,如下所示:

  1. 將套件新增至您的專案以支援 NSwag。 需要下列套件:

  2. 將下列內容新增至 Program.cs 檔案頂端:

    using Microsoft.AspNetCore.Datasync.NSwag;
    
  3. 將服務新增至您的 Program.cs 檔案,以產生 OpenAPI 定義:

    builder.Services.AddOpenApiDocument(options =>
    {
        options.AddDatasyncProcessors();
    });
    
  4. 啟用中間件來提供產生的 JSON 檔和 Swagger UI,也會在 Program.cs中:

    if (app.Environment.IsDevelopment())
    {
        app.UseOpenApi();
        app.UseSwaggerUI3();
    }
    

流覽至 Web 服務的 /swagger 端點可讓您瀏覽 API。 然後,OpenAPI 定義可以匯入至其他服務(例如 Azure API 管理)。 如需設定 NSwag 的詳細資訊,請參閱 開始使用 NSwag 和 ASP.NET Core

Swashbuckle

遵循 Swashbuckle 整合的基本指示,然後修改,如下所示:

  1. 將套件新增至您的專案以支援 Swashbuckle。 需要下列套件:

  2. 將服務新增至您的 Program.cs 檔案,以產生 OpenAPI 定義:

    builder.Services.AddSwaggerGen(options => 
    {
        options.AddDatasyncControllers();
    });
    builder.Services.AddSwaggerGenNewtonsoftSupport();
    

    注意

    AddDatasyncControllers() 方法會採用選擇性 Assembly,對應至包含數據表控制器的元件。 只有當數據表控制器位於服務的不同專案中時,才需要 Assembly 參數。

  3. 啟用中間件來提供產生的 JSON 檔和 Swagger UI,也會在 Program.cs中:

    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 管理)。 如需設定 Swashbuckle 的詳細資訊,請參閱 開始使用 Swashbuckle 和 ASP.NET Core

局限性

服務連結庫的 ASP.NET Core 版本會針對清單作業實作 OData v4。 當伺服器以回溯相容性模式執行時,不支援對子字串進行篩選。