De SDK voor de ASP.NET Core-back-endserver gebruiken
Notitie
Dit product is buiten gebruik gesteld. Zie de Community Toolkit Datasync-bibliotheekvoor een vervanging voor projecten met .NET 8 of hoger.
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 een 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:
- Maak een ASP.NET 6.0-serverproject (of hoger).
- Entity Framework Core toevoegen
- Gegevenssynchronisatieservices toevoegen
Zie de zelfstudievoor 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:
- Microsoft.AspNetCore.Datasync-
- Microsoft.AspNetCore.Datasync.EFCore voor tabellen op basis van Entity Framework Core.
- Microsoft.AspNetCore.Datasync.InMemory- voor tabellen in het geheugen.
Wijzig het bestand Program.cs
. Voeg de volgende regel toe onder alle andere servicedefinities:
builder.Services.AddDatasyncControllers();
U kunt ook de sjabloon ASP.NET Core 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:
- Maak een modelklasse voor het gegevensmodel.
- Voeg de modelklasse toe aan de
DbContext
voor uw toepassing. - Maak een nieuwe
TableController<T>
-klasse om uw model beschikbaar te maken.
Een modelklasse maken
Alle modelklassen moeten ITableData
implementeren. Elk type opslagplaats heeft een abstracte klasse die ITableData
implementeert. 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 afhandelen 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.
De DbContext
bijwerken
Elk model in de database moet worden geregistreerd in de DbContext
. Bijvoorbeeld:
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/tables
zijn. - De controller moet overnemen van
TableController<T>
, waarbij<T>
een implementatie is van deITableData
-implementatie voor uw opslagplaatstype. - 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
(int
, standaard: 100) is het maximum aantal items dat een querybewerking op één pagina retourneert. -
MaxTop
(int
, standaardwaarde: 512000) is het maximum aantal items dat wordt geretourneerd in een querybewerking zonder paginering. -
EnableSoftDelete
(bool
, standaard: 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
(int
, standaardwaarde: 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 gedetailleerde controle over autorisatie maakt u een klasse die IAccessControlProvider
implementeert. De IAccessControlProvider
gebruikt drie methoden 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
, een HttpContextAccessor-configureren.
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 nog steeds de IHttpContextAccessor
doorgeven voor toegang tot de HttpContext
op een veilige manier.
Als u deze provider voor toegangsbeheer wilt gebruiken, werkt u uw TableController
als volgt 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, kunt 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 eigenschap 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;
}
}
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 methode Th PostCommitHookAsync()
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 gebeurtenishandler die tegelijkertijd wordt aangeroepen als de PostCommitHookAsync()
methode.
[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 Identityof 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 Identityimplementeert. Als u Azure App Service Identity in uw toepassing wilt configureren, bewerkt u uw Program.cs
:
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/tijd-waarde genereren). De Azure Mobile Apps-opslagplaats voor Entity Framework Core werkt automatisch het UpdatedAt
veld voor u bij. Als uw database echter buiten de opslagplaats wordt bijgewerkt, moet u ervoor zorgen dat de UpdatedAt
en Version
velden 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()
om de database te maken.
Azure Cosmos DB
Azure Cosmos DB is een volledig beheerde NoSQL-database voor hoogwaardige toepassingen van elke grootte of schaal. Zie 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:
Stel de Cosmos-container in met een samengestelde index waarmee de velden
UpdatedAt
enId
worden opgegeven. Samengestelde indexen kunnen worden toegevoegd aan een container via Azure Portal, ARM, Bicep, Terraform of in code. Hier volgt een voorbeeld 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.
Modellen afleiden uit de klasse
ETagEntityTableData
:public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }
Voeg een
OnModelCreating(ModelBuilder)
methode toe aan deDbContext
. 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 eigenschapEntityTag
is gemarkeerd als de gelijktijdigheidstag. In het volgende codefragment worden deTodoItem
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:
- Cosmos DB-voorbeeld.
- EF Core Azure Cosmos DB-provider documentatie.
- Cosmos DB-indexbeleid documentatie.
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()
om de database te maken.
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 voor het implementeren van waardeconversieprogramma's en vergelijkingen 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 InstallUpdateTriggers
methode slechts eenmaal wordt aangeroepen tijdens de initialisatie van de 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 beheerde .NET C#-code. Het is een eenvoudige en snelle NoSQL-databaseoplossing voor zelfstandige toepassingen. LiteDb gebruiken met permanente opslag op schijf:
Installeer het
Microsoft.AspNetCore.Datasync.LiteDb
-pakket vanuit NuGet.Voeg een singleton voor de
LiteDatabase
toe aan deProgram.cs
:const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
Modellen afleiden uit de
LiteDbTableData
: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.Een controller maken met behulp van de
LiteDbRepository
:[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 die is gedefinieerd door datasynchronisatiecontrollers publiceren 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:
Voeg pakketten toe aan uw project ter ondersteuning van NSwag. De volgende pakketten zijn vereist:
Voeg het volgende toe aan het begin van het
Program.cs
-bestand:using Microsoft.AspNetCore.Datasync.NSwag;
Voeg een service toe om een OpenAPI-definitie te genereren aan uw
Program.cs
-bestand:builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });
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 Corevoor meer informatie over het configureren van NSwag.
Swashbuckle
Volg de basisinstructies voor Swashbuckle-integratie en wijzig deze als volgt:
Voeg pakketten toe aan uw project ter ondersteuning van Swashbuckle. De volgende pakketten zijn vereist:
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 methode
AddDatasyncControllers()
gebruikt een optioneleAssembly
die overeenkomt met de assembly die de tabelcontrollers bevat. De parameterAssembly
is alleen vereist als uw tabelcontrollers zich in een ander project voor de service bevinden.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 Corevoor 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.