如何使用 ASP.NET Core后端服务器 SDK

本文介绍如何配置和使用 ASP.NET Core后端服务器 SDK 生成数据同步服务器。

受支持的平台

ASP.NET Core后端服务器支持 ASP.NET Core 6.0。

数据库服务器必须满足以下条件DateTimeTimestamp,该字段的存储准确性为毫秒级。 为 Entity Framework CoreLiteDb 提供了存储库实现。

有关特定数据库支持,请参阅以下部分:

创建新的数据同步服务器

数据同步服务器使用常规 ASP.NET Core机制来创建服务器。 它由三个步骤组成:

  1. 创建 ASP.NET Core服务器项目。
  2. 添加 Entity Framework Core
  3. 添加数据同步服务

有关使用 Entity Framework Core 创建 ASP.NET Core服务的信息,请参阅本教程

若要启用数据同步服务,需要添加以下 NuGet 库:

修改 Program.cs 文件。 在所有其他服务定义下添加以下行:

builder.Services.AddDatasyncControllers();

还可以使用 ASP.NET Coredatasync-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) EntityTableData 实现的 (提供记录的 ID,以及用于处理数据同步服务的额外属性:

  • UpdatedAtDateTimeOffset? () 提供上次更新记录的日期。
  • Versionbyte[] () 提供了一个不透明值,用于对每次写入进行更改。
  • Deleted 如果记录已删除但尚未清除,则 (bool) 为 true。

请勿在代码中更改这些属性。 它们由存储库维护。

更新 DbContext

Each model in the database must be registered in the 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 ModelsController(AppDbContext context, Ilogger<ModelController> logger) : base()
    {
        Repository = new EntityTableRepository<Model>(context);
        Logger = logger;
    }
}

启用Azure 应用服务标识

ASP.NET Core数据同步服务器支持 ASP.NET Core标识,或者想要支持的任何其他身份验证和授权方案。 为了帮助升级早期版本的 Azure 移动应用,我们还提供实现Azure 应用服务标识的标识提供者。 若要在应用程序中配置Azure 应用服务标识,请编辑:Program.cs

builder.Services.AddAuthentication(AzureAppServiceAuthentication.AuthenticationScheme)
  .AddAzureAppServiceAuthentication(options => options.ForceEnable = true);

// Then later, after you have created the app
app.UseAuthentication();
app.UseAuthorization();

数据库支持

以下部分提供有关将 Azure 移动应用与特定数据库配合使用的信息。

Azure Cosmos DB

Azure Cosmos DB 是一个完全托管的无服务器 NoSQL 数据库,适用于任何大小或规模的高性能应用程序。 有关将 Azure Cosmos DB 与 Entity Framework Core 配合使用的信息,请参阅 Azure Cosmos DB 提供程序 。 将 Azure Cosmos DB 与 Azure 移动应用配合使用时:

  1. ETagEntityTableData 类派生模型:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  2. 将方法 OnModelCreating(ModelBuilder) 添加到 DbContext. 为每个公开表配置实体以使用 ETag 并发检查:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<TodoItem>().Property(t => t.EntityTag).IsETagConcurrency();
        base.OnModelCreating(builder);
    }
    

还可以在方法中 OnModelCreating(ModelBuilder) 设置容器、分区键和其他 Cosmos DB 设置。

自 v5.0.11 起, Microsoft.AspNetCore.Datasync.EFCore NuGet 包支持 Azure Cosmos DB。 有 一个示例演示如何实现 GitHub 存储库中提供的 Cosmos DB。

Sqlite

警告

请勿将 SqLite 用于生产服务。 SqLite 仅适用于生产中的客户端用法。

SqLite 没有支持毫秒准确性的日期/时间字段。 因此,除了测试之外,它不适合任何内容。 如果要使用 SqLite,请确保针对日期/时间属性在每个模型上实现值转换器和值比较器。 实现值转换器和比较器 OnModelCreating(ModelBuilder) 的最简单方法是采用以下方法 DbContext

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# 托管代码编写的单个小型 DL 获胜。 它是独立应用程序的简单且快速的 NoSQL 数据库解决方案。 若要将 LiteDb 与磁盘上的永久性存储配合使用,请执行以下操作:

  1. LiteDatabase 以下内容 Program.cs添加单一实例:

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  2. 从以下 LiteDbTableData来源派生模型:

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

    可以使用随 LiteDb NuGet 包一起提供的任何 BsonMapper 属性。

  3. 使用以下命令 LiteDbRepository创建控制器:

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

可以使用任何其他存储库功能 (,例如访问控制提供程序或记录器) 使用此模式。 NuGet 上的包支持 Microsoft.AspNetCore.Datasync.LiteDb LiteDb。

限制

服务库 ASP.NET Core版本为列表操作实现 OData v4。 当服务器在向后兼容模式下运行时,不支持对子字符串进行筛选。