Share via


De SDK voor de ASP.NET Core-back-endserver gebruiken

In dit artikel leest u hoe u de SDK voor de ASP.NET Core-back-endserver moet configureren en gebruiken om een gegevenssynchronisatieserver te produceren.

Ondersteunde platforms

De ASP.NET Core-back-endserver ondersteunt ASP.NET 6.0 of hoger.

Databaseservers moeten voldoen aan de volgende criteria, hebben een DateTime of Timestamp typeveld dat is opgeslagen met nauwkeurigheid van milliseconden. Implementaties van opslagplaatsen zijn beschikbaar voor Entity Framework Core en LiteDb.

Zie de volgende secties voor specifieke databaseondersteuning:

Een nieuwe gegevenssynchronisatieserver maken

Een gegevenssynchronisatieserver maakt gebruik van de normale ASP.NET Core-mechanismen voor het maken van de server. Het bestaat uit drie stappen:

  1. Maak een ASP.NET 6.0-serverproject (of hoger).
  2. Entity Framework Core toevoegen
  3. Gegevenssynchronisatieservices toevoegen

Zie de zelfstudie voor meer informatie over het maken van een ASP.NET Core-service met Entity Framework Core.

Als u datasynchronisatieservices wilt inschakelen, moet u de volgende NuGet-bibliotheken toevoegen:

Wijzig het bestand Program.cs. Voeg de volgende regel toe onder alle andere servicedefinities:

builder.Services.AddDatasyncControllers();

U kunt ook de ASP.NET Core-sjabloon datasync-server gebruiken:

# 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

De sjabloon bevat een voorbeeldmodel en controller.

Een tabelcontroller maken voor een SQL-tabel

De standaardopslagplaats maakt gebruik van Entity Framework Core. Het maken van een tabelcontroller is een proces in drie stappen:

  1. Maak een modelklasse voor het gegevensmodel.
  2. Voeg de modelklasse toe aan de DbContext klasse voor uw toepassing.
  3. Maak een nieuwe TableController<T> klasse om uw model beschikbaar te maken.

Een modelklasse maken

Alle modelklassen moeten worden geïmplementeerd ITableData. Elk type opslagplaats heeft een abstracte klasse die wordt geïmplementeerd ITableData. De Entity Framework Core-opslagplaats maakt gebruik van 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; }
}

De ITableData interface biedt de id van de record, samen met extra eigenschappen voor het verwerken van services voor gegevenssynchronisatie:

  • UpdatedAt (DateTimeOffset?) geeft de datum op waarop de record voor het laatst is bijgewerkt.
  • Version (byte[]) biedt een ondoorzichtige waarde die bij elke schrijfbewerking wordt gewijzigd.
  • Deleted (bool) is waar als de record is gemarkeerd voor verwijdering, maar nog niet is verwijderd.

De gegevenssynchronisatiebibliotheek onderhoudt deze eigenschappen. Wijzig deze eigenschappen niet in uw eigen code.

Werk de DbContext

Elk model in de database moet worden geregistreerd in de DbContext. Voorbeeld:

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

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

Een tabelcontroller maken

Een tabelcontroller is een gespecialiseerd ApiController. Dit is een minimale tabelcontroller:

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

Notitie

  • De controller moet een route hebben. Volgens de conventie worden tabellen weergegeven op een subpad van /tables, maar ze kunnen overal worden geplaatst. Als u clientbibliotheken gebruikt die ouder zijn dan v5.0.0, moet de tabel een subpad van /tableszijn.
  • De controller moet overnemen van TableController<T>, waar <T> de implementatie van de ITableData implementatie voor uw opslagplaatstype is.
  • Wijs een opslagplaats toe op basis van hetzelfde type als uw model.

Een opslagplaats in het geheugen implementeren

U kunt ook een opslagplaats in het geheugen gebruiken zonder permanente opslag. Voeg een singleton-service toe voor de opslagplaats in uw Program.cs:

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

Stel de tabelcontroller als volgt in:

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

Opties voor tabelcontroller configureren

U kunt bepaalde aspecten van de controller configureren met behulp van TableControllerOptions:

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

De opties die u kunt instellen, zijn onder andere:

  • PageSize (intstandaard: 100) is het maximum aantal items dat een querybewerking op één pagina retourneert.
  • MaxTop (intstandaard: 512000) is het maximum aantal items dat wordt geretourneerd in een querybewerking zonder paginering.
  • EnableSoftDelete (boolstandaard: false) schakelt voorlopig verwijderen in, waarmee items worden gemarkeerd als verwijderd in plaats van ze uit de database te verwijderen. Met voorlopig verwijderen kunnen clients hun offlinecache bijwerken, maar moeten verwijderde items afzonderlijk uit de database worden verwijderd.
  • UnauthorizedStatusCode (intstandaard: 401 Niet geautoriseerd) is de statuscode die wordt geretourneerd wanneer de gebruiker geen actie mag uitvoeren.

Toegangsmachtigingen configureren

Standaard kan een gebruiker alles doen wat ze in een tabel willen: een record maken, lezen, bijwerken en verwijderen. Voor meer verfijnde controle over autorisatie maakt u een klasse die wordt geïmplementeerd IAccessControlProvider. Er IAccessControlProvider worden drie methoden gebruikt om autorisatie te implementeren:

  • GetDataView() retourneert een lambda die beperkt wat de verbonden gebruiker kan zien.
  • IsAuthorizedAsync() bepaalt of de verbonden gebruiker de actie kan uitvoeren op de specifieke entiteit die wordt aangevraagd.
  • PreCommitHookAsync() past elke entiteit onmiddellijk aan voordat deze naar de opslagplaats wordt geschreven.

Tussen de drie methoden kunt u de meeste toegangsbeheercases effectief verwerken. Als u toegang nodig hebt tot de HttpContext, configureert u een HttpContextAccessor.

In het volgende voorbeeld wordt een persoonlijke tabel geïmplementeerd, waarbij een gebruiker alleen zijn eigen records kan zien.

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

De methoden zijn asynchroon voor het geval u een extra databasezoekactie moet uitvoeren om het juiste antwoord te krijgen. U kunt de IAccessControlProvider<T> interface op de controller implementeren, maar u moet de IHttpContextAccessor interface op een veilige manier doorgeven om toegang te krijgen tot de HttpContext thread.

Als u deze provider voor toegangsbeheer wilt gebruiken, werkt u het TableController volgende bij:

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

Als u zowel niet-geverifieerde als geverifieerde toegang tot een tabel wilt toestaan, moet u deze versieren met [AllowAnonymous] in plaats van [Authorize].

Logboekregistratie configureren

Logboekregistratie wordt verwerkt via het normale mechanisme voor logboekregistratie voor ASP.NET Core. Wijs het ILogger object toe aan de Logger eigenschap:

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

Wijzigingen in de opslagplaats controleren

Wanneer de opslagplaats wordt gewijzigd, kunt u werkstromen activeren, het antwoord op de client registreren of ander werk uitvoeren op een van de twee methoden:

Optie 1: Een PostCommitHookAsync implementeren

De IAccessControlProvider<T> interface biedt een PostCommitHookAsync() methode. De PostCommitHookAsync() methode wordt aangeroepen nadat de gegevens naar de opslagplaats zijn geschreven, maar voordat de gegevens naar de client worden geretourneerd. Zorg ervoor dat de gegevens die naar de client worden geretourneerd, niet in deze methode worden gewijzigd.

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

Gebruik deze optie als u asynchrone taken uitvoert als onderdeel van de hook.

Optie 2: De gebeurtenis-handler RepositoryUpdated gebruiken

De TableController<T> basisklasse bevat een gebeurtenis-handler die tegelijkertijd als de PostCommitHookAsync() methode wordt aangeroepen.

[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 Identity inschakelen

De ASP.NET Core-gegevenssynchronisatieserver ondersteunt ASP.NET Core Identity of een ander verificatie- en autorisatieschema dat u wilt ondersteunen. Om te helpen bij upgrades van eerdere versies van Azure Mobile Apps, bieden we ook een id-provider die Azure-app Service Identity implementeert. Als u Azure-app Service Identity in uw toepassing wilt configureren, bewerkt u het Program.csvolgende:

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

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

Databaseondersteuning

Entity Framework Core stelt het genereren van waarden niet in voor datum-/tijdkolommen. (Zie Datum-/tijdwaarde genereren). De Azure Mobile Apps-opslagplaats voor Entity Framework Core werkt het UpdatedAt veld automatisch voor u bij. Als uw database echter buiten de opslagplaats wordt bijgewerkt, moet u ervoor zorgen dat de UpdatedAt velden Version worden bijgewerkt.

Azure SQL

Maak een trigger voor elke entiteit:

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

U kunt deze trigger installeren met behulp van een migratie of direct na EnsureCreated() het maken van de database.

Azure Cosmos DB

Azure Cosmos DB is een volledig beheerde NoSQL-database voor hoogwaardige toepassingen van elke grootte of schaal. Zie De Azure Cosmos DB-provider voor informatie over het gebruik van Azure Cosmos DB met Entity Framework Core. Wanneer u Azure Cosmos DB gebruikt met Azure Mobile Apps:

  1. Stel de Cosmos-container in met een samengestelde index waarmee de UpdatedAt en Id velden worden opgegeven. Samengestelde indexen kunnen worden toegevoegd aan een container via Azure Portal, ARM, Bicep, Terraform of in code. Hier volgt een voorbeeld van een bicep-resourcedefinitie :

    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'
                            }
                        ]
                    ]
                }
            }
        }
    }
    

    Als u een subset met items in de tabel ophaalt, moet u ervoor zorgen dat u alle eigenschappen opgeeft die betrokken zijn bij de query.

  2. Modellen afleiden uit de ETagEntityTableData klasse:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. Voeg een OnModelCreating(ModelBuilder) methode toe aan de DbContext. Het Cosmos DB-stuurprogramma voor Entity Framework plaatst standaard alle entiteiten in dezelfde container. U moet minimaal een geschikte partitiesleutel kiezen en ervoor zorgen dat de EntityTag eigenschap is gemarkeerd als de gelijktijdigheidstag. In het volgende codefragment worden de TodoItem entiteiten bijvoorbeeld opgeslagen in hun eigen container met de juiste instellingen voor 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 wordt ondersteund in het Microsoft.AspNetCore.Datasync.EFCore NuGet-pakket sinds v5.0.11. Raadpleeg de volgende koppelingen voor meer informatie:

PostgreSQL

Maak een trigger voor elke entiteit:

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

U kunt deze trigger installeren met behulp van een migratie of direct na EnsureCreated() het maken van de database.

Sqlite

Waarschuwing

Gebruik SqLite niet voor productieservices. SqLite is alleen geschikt voor gebruik aan de clientzijde in productie.

SqLite heeft geen datum/tijd-veld dat de nauwkeurigheid van milliseconden ondersteunt. Als zodanig is het niet geschikt voor iets behalve voor testen. Als u SqLite wilt gebruiken, moet u ervoor zorgen dat u een waardeconversieprogramma en waardevergelijker implementeert op elk model voor datum-/tijdeigenschappen. De eenvoudigste methode om waardeconversieprogramma's en vergelijkingsprogramma's te implementeren, is in de OnModelCreating(ModelBuilder) methode van uw 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);
}

Installeer een updatetrigger wanneer u de database initialiseert:

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

Zorg ervoor dat de methode slechts eenmaal wordt aangeroepen tijdens de initialisatie van de InstallUpdateTriggers database:

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

LiteDB

LiteDB is een serverloze database die wordt geleverd in één kleine DLL die is geschreven in .NET C# beheerde code. Het is een eenvoudige en snelle NoSQL-databaseoplossing voor zelfstandige toepassingen. LiteDb gebruiken met permanente opslag op schijf:

  1. Installeer het Microsoft.AspNetCore.Datasync.LiteDb pakket vanuit NuGet.

  2. Voeg een singleton toe aan het LiteDatabaseProgram.csvolgende:

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  3. Modellen afleiden van het LiteDbTableDatavolgende:

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

    U kunt een van de BsonMapper kenmerken gebruiken die worden geleverd bij het LiteDb NuGet-pakket.

  4. Een controller maken met behulp van het LiteDbRepositoryvolgende:

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

OpenAPI-ondersteuning

U kunt de API publiceren die is gedefinieerd door datasynchronisatiecontrollers met behulp van NSwag of Swashbuckle. Begin in beide gevallen met het instellen van de service zoals u dat normaal zou doen voor de gekozen bibliotheek.

NSwag

Volg de basisinstructies voor NSwag-integratie en wijzig deze als volgt:

  1. Voeg pakketten toe aan uw project ter ondersteuning van NSwag. De volgende pakketten zijn vereist:

  2. Voeg het volgende toe aan het begin van het Program.cs bestand:

    using Microsoft.AspNetCore.Datasync.NSwag;
    
  3. Voeg een service toe om een OpenAPI-definitie te genereren aan uw Program.cs bestand:

    builder.Services.AddOpenApiDocument(options =>
    {
        options.AddDatasyncProcessors();
    });
    
  4. Schakel de middleware in voor het leveren van het gegenereerde JSON-document en de Swagger-gebruikersinterface, ook in Program.cs:

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

Als u naar het /swagger eindpunt van de webservice bladert, kunt u door de API bladeren. De OpenAPI-definitie kan vervolgens worden geïmporteerd in andere services (zoals Azure API Management). Zie Aan de slag met NSwag en ASP.NET Core voor meer informatie over het configureren van NSwag.

Swashbuckle

Volg de basisinstructies voor Swashbuckle-integratie en wijzig deze als volgt:

  1. Voeg pakketten toe aan uw project ter ondersteuning van Swashbuckle. De volgende pakketten zijn vereist:

  2. Voeg een service toe om een OpenAPI-definitie te genereren aan uw Program.cs bestand:

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

    Notitie

    De AddDatasyncControllers() methode gebruikt een optioneel Assembly bestand dat overeenkomt met de assembly die de tabelcontrollers bevat. De Assembly parameter is alleen vereist als uw tabelcontrollers zich in een ander project voor de service bevinden.

  3. Schakel de middleware in voor het leveren van het gegenereerde JSON-document en de Swagger-gebruikersinterface, ook in Program.cs:

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

Met deze configuratie kunt u bladeren naar de hoofdmap van de webservice om door de API te bladeren. De OpenAPI-definitie kan vervolgens worden geïmporteerd in andere services (zoals Azure API Management). Zie Aan de slag met Swashbuckle en ASP.NET Core voor meer informatie over het configureren van Swashbuckle.

Beperkingen

De ASP.NET Core-editie van de servicebibliotheken implementeert OData v4 voor de lijstbewerking. Wanneer de server wordt uitgevoerd in de compatibiliteitsmodus met eerdere versies, wordt filteren op een subtekenreeks niet ondersteund.