Så här använder du ASP.NET Core-serverdelsserverns SDK
Not
Den här produkten har dragits tillbaka. En ersättning för projekt som använder .NET 8 eller senare finns i Community Toolkit Datasync-biblioteket.
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
- eller Timestamp
typfält som lagras med millisekunders noggrannhet. Lagringsplatsimplementeringar 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:
- Skapa ett ASP.NET 6.0-serverprojekt (eller senare).
- Lägg till Entity Framework Core
- 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:
- Microsoft.AspNetCore.Datasync
- Microsoft.AspNetCore.Datasync.EFCore för Entity Framework Core-baserade tabeller.
- Microsoft.AspNetCore.Datasync.InMemory för minnesinterna tabeller.
Ä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:
- Skapa en modellklass för datamodellen.
- Lägg till modellklassen i
DbContext
för ditt program. - 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 ogenomskinliga 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);
}
}
Not
- 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 till/tables
. - Kontrollanten måste ärva från
TableController<T>
, där<T>
är en implementering avITableData
implementeringen 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 din 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
(int
, standard: 100) är det maximala antalet objekt som en frågeåtgärd returneras på en enda sida. -
MaxTop
(int
, standard: 512000) är det maximala antalet objekt som returneras i en frågeåtgärd utan växling. -
EnableSoftDelete
(bool
, standard: 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
(int
, standard: 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
.
IAccessControlProvider
använder 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 HttpContext
konfigurera 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 fortfarande skicka in IHttpContextAccessor
för att få åtkomst till HttpContext
på ett säkert sätt.
Om du vill använda den här åtkomstkontrollleverantören uppdaterar du din TableController
enligt följande:
[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 ILogger
-objektet 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 PostCommitHookAsync()
metoden anropas när 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 metoden 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
}
}
Aktivera Azure App Service-identitet
ASP.NET Core-datasynkroniseringsservern stöder ASP.NET Core Identityeller något annat autentiserings- och auktoriseringsschema 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 Identity. Om du vill konfigurera Azure App Service-identitet i ditt program redigerar du din 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-provider. När du använder Azure Cosmos DB med Azure Mobile Apps:
Konfigurera Cosmos-containern med ett sammansatt index som anger fälten
UpdatedAt
ochId
. Sammansatta index kan läggas till i en container via Azure-portalen, ARM, Bicep, Terraform eller i kod. Här är ett exempel 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.
Härled modeller från klassen
ETagEntityTableData
:public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }
Lägg till en
OnModelCreating(ModelBuilder)
-metod iDbContext
. Cosmos DB-drivrutinen för Entity Framework placerar alla entiteter i samma container som standard. Du måste minst välja en lämplig partitionsnyckel och se till att egenskapenEntityTag
är markerad som samtidighetstaggen. Följande kodfragment lagrar till exempel deTodoItem
entiteterna 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:
- Cosmos DB-exempel.
- dokumentation om EF Core Azure Cosmos DB-providern.
- Dokumentation om Cosmos DB-indexprincip.
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 din 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 metoden InstallUpdateTriggers
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:
Installera
Microsoft.AspNetCore.Datasync.LiteDb
-paketet från NuGet.Lägg till en singleton för
LiteDatabase
iProgram.cs
:const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
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 levereras med LiteDb NuGet-paketet.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 datasynkroniseringskontrollanter 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:
Lägg till paket i projektet för att stödja NSwag. Följande paket krävs:
Lägg till följande överst i din
Program.cs
-fil:using Microsoft.AspNetCore.Datasync.NSwag;
Lägg till en tjänst för att generera en OpenAPI-definition i din
Program.cs
-fil:builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });
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 webbtjänstens /swagger
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 Komma 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:
Lägg till paket i projektet för att stödja Swashbuckle. Följande paket krävs:
Lägg till en tjänst för att generera en OpenAPI-definition i din
Program.cs
-fil:builder.Services.AddSwaggerGen(options => { options.AddDatasyncControllers(); }); builder.Services.AddSwaggerGenNewtonsoftSupport();
Not
Metoden
AddDatasyncControllers()
tar en valfriAssembly
som motsvarar den sammansättning som innehåller dina tabellstyrenheter. ParameternAssembly
krävs bara om dina tabellkontrollanter är i ett annat projekt än tjänsten.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 Komma 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.