Share via


Så här använder du ASP.NET Core-serverdelsserverns SDK

Den här artikeln visar att du måste konfigurera och använda ASP.NET Core-serverdelsserverns SDK för att skapa en datasynkroniseringsserver.

Plattformar som stöds

Serverdelen ASP.NET Core stöder ASP.NET 6.0 eller senare.

Databasservrar måste uppfylla följande villkor med ett DateTime fält av typen eller Timestamp som lagras med millisekunders noggrannhet. Implementeringar av lagringsplatser tillhandahålls för Entity Framework Core och LiteDb.

Specifik databassupport finns i följande avsnitt:

Skapa en ny datasynkroniseringsserver

En datasynkroniseringsserver använder de normala ASP.NET Core-mekanismerna för att skapa servern. Den består av tre steg:

  1. Skapa ett ASP.NET 6.0-serverprojekt (eller senare).
  2. Lägg till Entity Framework Core
  3. Lägga till datasynkroniseringstjänster

Information om hur du skapar en ASP.NET Core-tjänst med Entity Framework Core finns i självstudien.

Om du vill aktivera datasynkroniseringstjänster måste du lägga till följande NuGet-bibliotek:

Ändra filen Program.cs. Lägg till följande rad under alla andra tjänstdefinitioner:

builder.Services.AddDatasyncControllers();

Du kan också använda mallen 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

Mallen innehåller en exempelmodell och kontrollant.

Skapa en tabellkontrollant för en SQL-tabell

Standardlagringsplatsen använder Entity Framework Core. Att skapa en tabellstyrenhet är en process i tre steg:

  1. Skapa en modellklass för datamodellen.
  2. Lägg till modellklassen i DbContext för ditt program.
  3. Skapa en ny TableController<T> klass för att exponera din modell.

Skapa en modellklass

Alla modellklasser måste implementera ITableData. Varje lagringsplatstyp har en abstrakt klass som implementerar ITableData. Entity Framework Core-lagringsplatsen använder 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; }
}

Gränssnittet ITableData tillhandahåller postens ID, tillsammans med extra egenskaper för hantering av datasynkroniseringstjänster:

  • UpdatedAt (DateTimeOffset?) anger det datum då posten senast uppdaterades.
  • Version (byte[]) ger ett täckande värde som ändras vid varje skrivning.
  • Deleted (bool) är sant om posten har markerats för borttagning men ännu inte rensats.

Datasynkroniseringsbiblioteket underhåller dessa egenskaper. Ändra inte dessa egenskaper i din egen kod.

Uppdatera DbContext

Varje modell i databasen måste vara registrerad i DbContext. Till exempel:

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

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

Skapa en tabellkontrollant

En tabellstyrenhet är en specialiserad ApiController. Här är en minimal tabellkontrollant:

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

Kommentar

  • Kontrollanten måste ha en väg. Enligt konventionen exponeras tabeller på en undersökväg för /tables, men de kan placeras var som helst. Om du använder klientbibliotek tidigare än v5.0.0 måste tabellen vara en undersökväg /tablestill .
  • Kontrollanten måste ärva från TableController<T>, där <T> är en implementering av implementeringen ITableData för din lagringsplatstyp.
  • Tilldela en lagringsplats baserat på samma typ som din modell.

Implementera en minnesintern lagringsplats

Du kan också använda en minnesintern lagringsplats utan beständig lagring. Lägg till en singleton-tjänst för lagringsplatsen i :Program.cs

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

Konfigurera tabellstyrenheten på följande sätt:

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

Konfigurera alternativ för tabellstyrenhet

Du kan konfigurera vissa aspekter av kontrollanten med hjälp av TableControllerOptions:

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

De alternativ som du kan ange är:

  • PageSize (intstandard: 100) är det maximala antalet objekt som en frågeåtgärd returneras på en enda sida.
  • MaxTop (intstandard: 512000) är det maximala antalet objekt som returneras i en frågeåtgärd utan växling.
  • EnableSoftDelete (boolstandard: false) aktiverar mjuk borttagning, vilket markerar objekt som borttagna i stället för att ta bort dem från databasen. Mjuk borttagning gör det möjligt för klienter att uppdatera sin offlinecache, men kräver att borttagna objekt rensas separat från databasen.
  • UnauthorizedStatusCode (intstandard: 401 Obehörig) är statuskoden som returneras när användaren inte får utföra en åtgärd.

Konfigurera åtkomstbehörigheter

Som standard kan en användare göra vad de vill för entiteter i en tabell – skapa, läsa, uppdatera och ta bort alla poster. Om du vill ha mer detaljerad kontroll över auktorisering skapar du en klass som implementerar IAccessControlProvider. Använder IAccessControlProvider tre metoder för att implementera auktorisering:

  • GetDataView() returnerar en lambda som begränsar vad den anslutna användaren kan se.
  • IsAuthorizedAsync() avgör om den anslutna användaren kan utföra åtgärden på den specifika entitet som begärs.
  • PreCommitHookAsync() justerar en entitet omedelbart innan den skrivs till lagringsplatsen.

Mellan de tre metoderna kan du effektivt hantera de flesta åtkomstkontrollärenden. Om du behöver åtkomst till HttpContextkonfigurerar du en HttpContextAccessor.

Följande implementerar till exempel en personlig tabell, där en användare bara kan se sina egna poster.

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

Metoderna är asynkrona om du behöver göra en extra databassökning för att få rätt svar. Du kan implementera IAccessControlProvider<T> gränssnittet på kontrollanten, men du måste ändå skicka in IHttpContextAccessor för att få åtkomst till det HttpContext på ett säkert sätt.

Om du vill använda den här åtkomstkontrollleverantören uppdaterar du på TableController följande sätt:

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

Om du vill tillåta både oautentiserad och autentiserad åtkomst till en tabell kan du dekorera den med [AllowAnonymous] i stället för [Authorize].

Konfigurera loggning

Loggning hanteras via den normala loggningsmekanismen för ASP.NET Core. Tilldela objektet ILogger till egenskapen 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;
    }
}

Övervaka ändringar i lagringsplatsen

När lagringsplatsen ändras kan du utlösa arbetsflöden, logga svaret till klienten eller utföra annat arbete på någon av två metoder:

Alternativ 1: Implementera en PostCommitHookAsync

Gränssnittet IAccessControlProvider<T> innehåller en PostCommitHookAsync() metod. Den här PostCommitHookAsync() metoden anropas efter att data har skrivits till lagringsplatsen men innan data returneras till klienten. Försiktighet måste göras för att säkerställa att data som returneras till klienten inte ändras i den här metoden.

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

Använd det här alternativet om du kör asynkrona uppgifter som en del av kroken.

Alternativ 2: Använd händelsehanteraren RepositoryUpdated

Basklassen TableController<T> innehåller en händelsehanterare som anropas samtidigt som PostCommitHookAsync() metoden.

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

Aktivera Azure App Service-identitet

ASP.NET Core-datasynkroniseringsservern stöder ASP.NET Core Identity eller andra autentiserings- och auktoriseringsscheman som du vill stödja. För att hjälpa till med uppgraderingar från tidigare versioner av Azure Mobile Apps tillhandahåller vi även en identitetsprovider som implementerar Azure App Service-identitet. Om du vill konfigurera Azure App Service-identitet i ditt program redigerar du :Program.cs

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

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

Databasstöd

Entity Framework Core konfigurerar inte värdegenerering för datum/tid-kolumner. (Se Generering av datum/tid-värde). Azure Mobile Apps-lagringsplatsen för Entity Framework Core uppdaterar automatiskt fältet UpdatedAt åt dig. Men om databasen uppdateras utanför lagringsplatsen måste du se till att fälten UpdatedAt och Version uppdateras.

Azure SQL

Skapa en utlösare för varje entitet:

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

Du kan installera den här utlösaren med hjälp av antingen en migrering eller omedelbart efter EnsureCreated() för att skapa databasen.

Azure Cosmos DB

Azure Cosmos DB är en fullständigt hanterad NoSQL-databas för högpresterande program av valfri storlek eller skala. Mer information om hur du använder Azure Cosmos DB med Entity Framework Core finns i Azure Cosmos DB-providern . När du använder Azure Cosmos DB med Azure Mobile Apps:

  1. Konfigurera Cosmos-containern med ett sammansatt index som anger fälten UpdatedAt och Id . Sammansatta index kan läggas till i en container via Azure-portalen, ARM, Bicep, Terraform eller i kod. Här är ett exempel på en bicep-resursdefinition :

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

    Om du hämtar en delmängd av objekten i tabellen kontrollerar du att du anger alla egenskaper som ingår i frågan.

  2. Härled modeller från ETagEntityTableData klassen:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. Lägg till en OnModelCreating(ModelBuilder) metod i DbContext. Cosmos DB-drivrutinen för Entity Framework placerar alla entiteter i samma container som standard. Du måste åtminstone välja en lämplig partitionsnyckel och se till att egenskapen EntityTag är markerad som samtidighetstaggen. Följande kodfragment lagrar till exempel entiteterna TodoItem i sin egen container med lämpliga inställningar för 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 stöds i Microsoft.AspNetCore.Datasync.EFCore NuGet-paketet sedan v5.0.11. Mer information finns i följande länkar:

PostgreSQL

Skapa en utlösare för varje entitet:

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

Du kan installera den här utlösaren med hjälp av antingen en migrering eller omedelbart efter EnsureCreated() för att skapa databasen.

Sqlite

Varning

Använd inte SqLite för produktionstjänster. SqLite lämpar sig endast för användning på klientsidan i produktion.

SqLite har inget datum-/tidsfält som stöder millisekunders noggrannhet. Därför är den inte lämplig för något annat än för testning. Om du vill använda SqLite kontrollerar du att du implementerar en värdekonverterare och värdejäxare för varje modell för datum-/tidsegenskaper. Den enklaste metoden för att implementera värdekonverterare och jämförelseverktyg finns i OnModelCreating(ModelBuilder) metoden för :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);
}

Installera en uppdateringsutlösare när du initierar databasen:

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

Kontrollera att InstallUpdateTriggers metoden bara anropas en gång under databasinitieringen:

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

LiteDB

LiteDB är en serverlös databas som levereras i en enda liten DLL som skrivits i .NET C#-hanterad kod. Det är en enkel och snabb NoSQL-databaslösning för fristående program. Så här använder du LiteDb med beständig lagring på disk:

  1. Microsoft.AspNetCore.Datasync.LiteDb Installera paketet från NuGet.

  2. Lägg till en singleton för LiteDatabase till Program.cs:

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  3. Härled modeller från LiteDbTableData:

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

    Du kan använda något av de BsonMapper attribut som medföljer LiteDb NuGet-paketet.

  4. Skapa en kontrollant med hjälp av LiteDbRepository:

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

OpenAPI-support

Du kan publicera API:et som definierats av datasynkroniseringsstyrenheter med hjälp av NSwag eller Swashbuckle. I båda fallen börjar du med att konfigurera tjänsten som vanligt för det valda biblioteket.

NSwag

Följ de grundläggande anvisningarna för NSwag-integrering och ändra sedan på följande sätt:

  1. Lägg till paket i projektet för att stödja NSwag. Följande paket krävs:

  2. Lägg till följande överst Program.cs i filen:

    using Microsoft.AspNetCore.Datasync.NSwag;
    
  3. Lägg till en tjänst för att generera en OpenAPI-definition i Program.cs filen:

    builder.Services.AddOpenApiDocument(options =>
    {
        options.AddDatasyncProcessors();
    });
    
  4. Aktivera mellanprogrammet för att hantera det genererade JSON-dokumentet och Swagger-användargränssnittet, även i Program.cs:

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

Genom att bläddra till /swagger webbtjänstens slutpunkt kan du bläddra i API:et. OpenAPI-definitionen kan sedan importeras till andra tjänster (till exempel Azure API Management). Mer information om hur du konfigurerar NSwag finns i Kom igång med NSwag och ASP.NET Core.

Swashbuckle

Följ de grundläggande anvisningarna för Swashbuckle-integrering och ändra sedan på följande sätt:

  1. Lägg till paket i projektet för att stödja Swashbuckle. Följande paket krävs:

  2. Lägg till en tjänst för att generera en OpenAPI-definition i Program.cs filen:

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

    Kommentar

    Metoden AddDatasyncControllers() tar ett valfritt Assembly alternativ som motsvarar den sammansättning som innehåller dina tabellstyrenheter. Parametern Assembly krävs bara om dina tabellstyrenheter finns i ett annat projekt än tjänsten.

  3. Aktivera mellanprogrammet för att hantera det genererade JSON-dokumentet och Swagger-användargränssnittet, även i Program.cs:

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

Med den här konfigurationen kan du bläddra till webbtjänstens rot och bläddra i API:et. OpenAPI-definitionen kan sedan importeras till andra tjänster (till exempel Azure API Management). Mer information om hur du konfigurerar Swashbuckle finns i Kom igång med Swashbuckle och ASP.NET Core.

Begränsningar

ASP.NET Core-utgåvan av tjänstbiblioteken implementerar OData v4 för liståtgärden. När servern körs i bakåtkompatibilitetsläge stöds inte filtrering på en delsträng.