Jak používat sadu SDK back-endového serveru ASP.NET Core

Tento článek ukazuje, že musíte nakonfigurovat a použít sadu SDK back-endového serveru ASP.NET Core k vytvoření serveru synchronizace dat.

Podporované platformy

Back-endový server ASP.NET Core podporuje ASP.NET 6.0 nebo novější.

Databázové servery musí splňovat následující kritéria DateTime a Timestamp pole typu, které je uloženo s přesností milisekund. Implementace úložiště jsou k dispozici pro Entity Framework Core a LiteDb.

Konkrétní podporu databází najdete v následujících částech:

Vytvoření nového serveru synchronizace dat

Server synchronizace dat používá pro vytvoření serveru normální mechanismy ASP.NET Core. Skládá se ze tří kroků:

  1. Vytvořte projekt serveru ASP.NET 6.0 (nebo novější).
  2. Přidání Entity Framework Core
  3. Přidání služeb synchronizace dat

Informace o vytvoření služby ASP.NET Core pomocí Entity Framework Core najdete v tomto kurzu.

Pokud chcete povolit služby synchronizace dat, musíte přidat následující knihovny NuGet:

Upravte soubor Program.cs. Do všech ostatních definic služby přidejte následující řádek:

builder.Services.AddDatasyncControllers();

Můžete také použít šablonu 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

Šablona obsahuje ukázkový model a kontroler.

Vytvoření kontroleru tabulky pro tabulku SQL

Výchozí úložiště používá Entity Framework Core. Vytvoření kontroleru tabulky je třístupňový proces:

  1. Vytvořte třídu modelu pro datový model.
  2. Přidejte třídu modelu do DbContext aplikace.
  3. Vytvořte novou TableController<T> třídu pro zveřejnění modelu.

Vytvoření třídy modelu

Všechny třídy modelu musí implementovat ITableData. Každý typ úložiště má abstraktní třídu, která implementuje ITableData. Úložiště Entity Framework Core používá 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; }
}

Rozhraní ITableData poskytuje ID záznamu spolu s dalšími vlastnostmi pro zpracování služeb synchronizace dat:

  • UpdatedAt (DateTimeOffset?) poskytuje datum poslední aktualizace záznamu.
  • Version (byte[]) poskytuje neprůhlenou hodnotu, která se změní při každém zápisu.
  • Deleted (bool) je true, pokud je záznam označen k odstranění, ale ještě není vyprázdněn.

Knihovna synchronizace dat tyto vlastnosti udržuje. Neupravujte tyto vlastnosti ve vlastním kódu.

Aktualizace DbContext

Každý model v databázi musí být registrován v souboru DbContext. Příklad:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    public DbSet<TodoItem> TodoItems { get; set; }
}

Vytvoření kontroleru tabulky

Kontroler tabulky je specializovaný ApiController. Tady je minimální kontroler tabulky:

[Route("tables/[controller]")]
public class TodoItemController : TableController<TodoItem>
{
    public TodoItemController(AppDbContext context) : base()
    {
        Repository = new EntityTableRepository<TodoItem>(context);
    }
}

Poznámka:

  • Kontroler musí mít trasu. Podle konvence jsou tabulky vystaveny na dílčí cestě /tables, ale mohou být umístěny kdekoli. Pokud používáte klientské knihovny starší než verze 5.0.0, musí být tabulka dílčí cestou /tables.
  • Kontroler musí dědit z TableController<T>, kde <T> je implementace ITableData implementace pro váš typ úložiště.
  • Přiřaďte úložiště na základě stejného typu jako váš model.

Implementace úložiště v paměti

Můžete také použít úložiště v paměti bez trvalého úložiště. Přidejte do svého Program.csúložiště službu singleton:

IEnumerable<Model> seedData = GenerateSeedData();
builder.Services.AddSingleton<IRepository<Model>>(new InMemoryRepository<Model>(seedData));

Nastavte kontroler tabulky následujícím způsobem:

[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
    public MovieController(IRepository<Model> repository) : base(repository)
    {
    }
}

Konfigurace možností kontroleru tabulky

Určité aspekty kontroleru můžete nakonfigurovat pomocí TableControllerOptions:

[Route("tables/[controller]")]
public class MoodelController : TableController<Model>
{
    public ModelController(IRepository<Model> repository) : base(repository)
    {
        Options = new TableControllerOptions { PageSize = 25 };
    }
}

Mezi možnosti, které můžete nastavit, patří:

  • PageSize (intvýchozí hodnota: 100) je maximální počet položek, které operace dotazu vrátí na jedné stránce.
  • MaxTop (intvýchozí hodnota: 512000) je maximální počet položek vrácených v operaci dotazu bez stránkování.
  • EnableSoftDelete (boolvýchozí hodnota: false) povolí obnovitelné odstranění, které místo odstranění z databáze označí položky jako odstraněné. Obnovitelné odstranění umožňuje klientům aktualizovat offline mezipaměť, ale vyžaduje, aby se odstraněné položky vyprázdnily z databáze samostatně.
  • UnauthorizedStatusCode (intvýchozí hodnota: 401 Neautorizováno) je stavový kód vrácený v případě, že uživatel nemůže provést akci.

Konfigurace přístupových oprávnění

Ve výchozím nastavení může uživatel dělat cokoliv, co chce s entitami v tabulce – vytvářet, číst, aktualizovat a odstraňovat libovolný záznam. Pro jemněji odstupňovanou kontrolu nad autorizací vytvořte třídu, která implementuje IAccessControlProvider. K IAccessControlProvider implementaci autorizace se používají tři metody:

  • GetDataView() vrátí lambda, která omezuje, co vidí připojený uživatel.
  • IsAuthorizedAsync() určuje, jestli připojený uživatel může provést akci u konkrétní požadované entity.
  • PreCommitHookAsync() upraví jakoukoli entitu bezprostředně před zápisem do úložiště.

Mezi těmito třemi metodami můžete efektivně zvládnout většinu případů řízení přístupu. Pokud potřebujete přístup k nástroji HttpContextAccessor, nakonfigurujte hoHttpContext.

Například následující tabulka implementuje osobní tabulku, kde uživatel vidí jenom svoje vlastní záznamy.

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;
    }
}

Metody jsou asynchronní v případě, že potřebujete provést další vyhledávání databáze, abyste získali správnou odpověď. Rozhraní můžete implementovat IAccessControlProvider<T> na kontroleru, ale přesto musíte předat IHttpContextAccessor přístup k HttpContext vláknu bezpečným způsobem.

Pokud chcete použít tohoto zprostředkovatele řízení přístupu, aktualizujte ho TableController následujícím způsobem:

[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);
    }
}

Pokud chcete povolit neověřený i ověřený přístup k tabulce, vyzdobit ho [AllowAnonymous] místo [Authorize].

Konfigurace protokolování

Protokolování se zpracovává prostřednictvím normálního mechanismu protokolování pro ASP.NET Core. ILogger Přiřaďte objekt vlastnostiLogger:

[Authorize]
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
    public ModelController(AppDbContext context, Ilogger<ModelController> logger) : base()
    {
        Repository = new EntityTableRepository<Model>(context);
        Logger = logger;
    }
}

Monitorování změn úložiště

Když se úložiště změní, můžete aktivovat pracovní postupy, protokolovat odpověď klientovi nebo provádět jinou práci v jedné ze dvou metod:

Možnost 1: Implementace postCommitHookAsync

Rozhraní IAccessControlProvider<T> poskytuje metodu PostCommitHookAsync() . Metoda Th PostCommitHookAsync() je volána po zápisu dat do úložiště, ale před vrácením dat klientovi. Je třeba dbát na to, aby se v této metodě nezměnila data vrácená klientovi.

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.
    }
}

Tuto možnost použijte, pokud spouštíte asynchronní úlohy jako součást háku.

Možnost 2: Použití obslužné rutiny události RepositoryUpdated

Základní TableController<T> třída obsahuje obslužnou rutinu události, která je volána současně s metodou 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
    }
}

Povolení identity služby Aplikace Azure

Server pro synchronizaci dat ASP.NET Core podporuje ASP.NET Základní identitu nebo jakékoli jiné schéma ověřování a autorizace, které chcete podporovat. Abychom vám pomohli s upgrady z předchozích verzí Azure Mobile Apps, poskytujeme také zprostředkovatele identity, který implementuje identitu služby Aplikace Azure. Pokud chcete ve své aplikaci nakonfigurovat identitu služby Aplikace Azure, upravteProgram.cs:

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

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

Podpora databází

Entity Framework Core nenastavuje generování hodnot pro sloupce s datem a časem. (Viz Generování hodnot data a času) Úložiště Azure Mobile Apps pro Entity Framework Core automaticky aktualizuje UpdatedAt pole za vás. Pokud se ale vaše databáze aktualizuje mimo úložiště, musíte zajistit UpdatedAt aktualizaci polí a Version polí.

Azure SQL

Vytvořte trigger pro každou entitu:

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

Tento trigger můžete nainstalovat buď pomocí migrace, nebo bezprostředně po EnsureCreated() vytvoření databáze.

Azure Cosmos DB

Azure Cosmos DB je plně spravovaná databáze NoSQL pro vysoce výkonné aplikace libovolné velikosti nebo škálování. Informace o používání služby Azure Cosmos DB s Entity Framework Core najdete v tématu Zprostředkovatel služby Azure Cosmos DB. Při používání služby Azure Cosmos DB s Azure Mobile Apps:

  1. Nastavte kontejner Cosmos pomocí složeného indexu UpdatedAt , který určuje pole a Id pole. Složené indexy je možné přidat do kontejneru prostřednictvím webu Azure Portal, ARM, Bicep, Terraformu nebo kódu. Tady je příklad definice prostředku 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'
                            }
                        ]
                    ]
                }
            }
        }
    }
    

    Pokud stáhnete podmnožinu položek v tabulce, ujistěte se, že jste zadali všechny vlastnosti zahrnuté v dotazu.

  2. Odvozujte modely z ETagEntityTableData třídy:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. Přidejte metodu OnModelCreating(ModelBuilder) do objektu DbContext. Ovladač cosmos DB pro Entity Framework umístí všechny entity do stejného kontejneru ve výchozím nastavení. Minimálně musíte vybrat vhodný klíč oddílu a zajistit, aby EntityTag byla vlastnost označena jako značka souběžnosti. Následující fragment kódu například ukládá TodoItem entity do vlastního kontejneru s odpovídajícím nastavením pro 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 je podporována v Microsoft.AspNetCore.Datasync.EFCore balíčku NuGet od verze 5.0.11. Další informace najdete na následujících odkazech:

PostgreSQL

Vytvořte trigger pro každou entitu:

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();

Tento trigger můžete nainstalovat buď pomocí migrace, nebo bezprostředně po EnsureCreated() vytvoření databáze.

Sqlite

Upozorňující

Nepoužívejte SqLite pro produkční služby. SqLite je vhodný pouze pro použití na straně klienta v produkčním prostředí.

SqLite nemá pole data a času, které podporuje přesnost milisekund. Proto není vhodný pro nic kromě testování. Pokud chcete použít SqLite, ujistěte se, že v každém modelu implementujete převaděč hodnot a porovnávač hodnot pro vlastnosti data a času. Nejjednodušší metoda implementace převaděčů hodnot a porovnávačů je v OnModelCreating(ModelBuilder) metodě vaší 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);
}

Nainstalujte trigger aktualizace při inicializaci databáze:

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);
        }
    }
}

Ujistěte se, že InstallUpdateTriggers metoda je volána pouze jednou během inicializace databáze:

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

LiteDB

LiteDB je bezserverová databáze doručená v jedné malé knihovně DLL napsané ve spravovaném kódu .NET C#. Jedná se o jednoduché a rychlé databázové řešení NoSQL pro samostatné aplikace. Použití LiteDb s trvalým úložištěm na disku:

  1. Microsoft.AspNetCore.Datasync.LiteDb Nainstalujte balíček z NuGetu.

  2. Přidejte jedenton pro :LiteDatabaseProgram.cs

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  3. Odvozujte modely z LiteDbTableData:

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

    Můžete použít libovolný z BsonMapper atributů, které jsou dodávány s balíčkem NuGet LiteDb.

  4. Vytvořte kontroler pomocí příkazu LiteDbRepository:

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

Podpora OpenAPI

Rozhraní API definované kontrolery synchronizace dat můžete publikovat pomocí NSwag nebo Swashbuckle. V obou případech začněte nastavením služby jako obvykle pro vybranou knihovnu.

NSwag

Postupujte podle základníchpokynůch

  1. Přidejte do projektu balíčky pro podporu NSwag. Jsou vyžadovány následující balíčky:

  2. Na začátek Program.cs souboru přidejte následující položky:

    using Microsoft.AspNetCore.Datasync.NSwag;
    
  3. Přidejte službu pro vygenerování definice OpenAPI do souboru Program.cs :

    builder.Services.AddOpenApiDocument(options =>
    {
        options.AddDatasyncProcessors();
    });
    
  4. Povolte middleware pro obsluhu vygenerovaného dokumentu JSON a uživatelského rozhraní Swagger, a to také v Program.cs:

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

Procházením /swagger koncového bodu webové služby můžete procházet rozhraní API. Definici OpenAPI je pak možné importovat do jiných služeb (jako je Azure API Management). Další informace o konfiguraci NSwag najdete v tématu Začínáme se službou NSwag a ASP.NET Core.

Swashbuckle

Postupujte podle základních pokynů pro integraci Swashbuckle a pak upravte následujícím způsobem:

  1. Přidejte do projektu balíčky pro podporu Swashbuckle. Jsou vyžadovány následující balíčky:

  2. Přidejte službu pro vygenerování definice OpenAPI do souboru Program.cs :

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

    Poznámka:

    Metoda AddDatasyncControllers() přebírá volitelnou Assembly , která odpovídá sestavení, které obsahuje kontrolery tabulky. Parametr Assembly se vyžaduje jenom v případě, že jsou kontrolery tabulek v jiném projektu, než je služba.

  3. Povolte middleware pro obsluhu vygenerovaného dokumentu JSON a uživatelského rozhraní Swagger, a to také v Program.cs:

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(options => 
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
            options.RoutePrefix = string.Empty;
        });
    }
    

Při této konfiguraci vám procházení kořenového adresáře webové služby umožňuje procházet rozhraní API. Definici OpenAPI je pak možné importovat do jiných služeb (jako je Azure API Management). Další informace o konfiguraci Swashbuckle najdete v tématu Začínáme s Swashbuckle a ASP.NET Core.

Omezení

Edice ASP.NET Core knihoven služeb implementuje pro operaci seznamu OData v4. Pokud server běží v režimu zpětné kompatibility, filtrování podřetězce se nepodporuje.