Sdílet prostřednictvím


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

Poznámka

Tento produkt je vyřazený. Náhradu za projekty používající .NET 8 nebo novější najdete v knihovně Community Toolkit Datasync.

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 nebo pole typu Timestamp, které je uloženo s přesností milisekund. Implementace úložiště jsou poskytovány 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 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 pro vaši aplikaci.
  3. Vytvořte novou třídu TableController<T> 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ůsažnou 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í vymazaný.

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 DbContext. Napří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ě. Do Program.cspřidejte službu singleton pro úložiště:

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

Pomocí TableControllerOptionsmůžete nakonfigurovat určité aspekty kontroleru:

[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 (int, výchozí: 100) je maximální počet položek, které operace dotazu vrátí na jedné stránce.
  • MaxTop (int, výchozí hodnota: 512000) je maximální počet položek vrácených v operaci dotazu bez stránkování.
  • EnableSoftDelete (bool, výchozí hodnota: false) umožňuje 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 (int, výchozí: 401 Neautorizováno) je stavový kód vrácený, když 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 podrobnější kontrolu nad autorizací vytvořte třídu, která implementuje IAccessControlProvider. IAccessControlProvider používá k implementaci autorizace tři metody:

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

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 HttpContext, nakonfigurovatHttpContextAccessor .

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í IAccessControlProvider<T> můžete implementovat na řadiči, ale přesto musíte předat IHttpContextAccessor pro přístup k HttpContext bezpečným způsobem.

Pokud chcete použít tohoto zprostředkovatele řízení přístupu, aktualizujte 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, ozdobte 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. Přiřaďte objekt ILogger vlastnosti 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;
    }
}

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 PostCommitHookAsync() se volá 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í třída TableController<T> obsahuje obslužnou rutinu události, která je volána ve stejnou dobu jako PostCommitHookAsync() metoda.

[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 Azure App Service

Server pro synchronizaci dat ASP.NET Core podporujeASP.NET Identita Core 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 identity služby Azure App Service . Pokud chcete ve své aplikaci nakonfigurovat identitu služby Azure App Service, upravte Program.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áze

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 aktualizaci UpdatedAt 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() k 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 poskytovatel služby Azure Cosmos DB. Při používání služby Azure Cosmos DB s Azure Mobile Apps:

  1. Nastavte kontejner Cosmos s složeným indexem, který určuje pole UpdatedAt a Id. 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 bicep prostředku:

    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 třídy ETagEntityTableData:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. Přidejte do DbContextmetodu OnModelCreating(ModelBuilder) . 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 byla vlastnost EntityTag označena jako značka souběžnosti. Následující fragment kódu například ukládá entity TodoItem 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);
    }
    

Služba Azure Cosmos DB je podporována v balíčku NuGet Microsoft.AspNetCore.Datasync.EFCore 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() k vytvoření databáze.

SqLite

Varování

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šeho 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 se metoda InstallUpdateTriggers volá 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. Nainstalujte balíček Microsoft.AspNetCore.Datasync.LiteDb z NuGetu.

  2. Do Program.cspřidejte jedenton pro LiteDatabase:

    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í 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 souboru Program.cs přidejte následující:

    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 koncového bodu /swagger 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 s 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ž 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.