Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Nuta
Ten produkt jest wycofany. Aby zastąpić projekty przy użyciu platformy .NET 8 lub nowszej, zobacz bibliotekę datasync zestawu narzędzi Community Toolkit.
W tym artykule pokazano, jak skonfigurować i użyć zestawu SDK serwera zaplecza platformy ASP.NET Core do utworzenia serwera synchronizacji danych.
Obsługiwane platformy
Serwer zaplecza platformy ASP.NET Core obsługuje ASP.NET 6.0 lub nowszym.
Serwery baz danych muszą spełniać następujące kryteria, mają pole typu DateTime lub Timestamp przechowywane z dokładnością milisekund. Implementacje repozytorium są dostępne dla Entity Framework Core i LiteDb.
Aby uzyskać określoną obsługę bazy danych, zobacz następujące sekcje:
- usług Azure SQL i SQL Server
- Azure Cosmos DB
- PostgreSQL
- sqlite
- LiteDb
Tworzenie nowego serwera synchronizacji danych
Serwer synchronizacji danych używa normalnych mechanizmów ASP.NET Core do tworzenia serwera. Składa się z trzech kroków:
- Utwórz projekt serwera ASP.NET w wersji 6.0 (lub nowszej).
- Dodawanie programu Entity Framework Core
- Dodawanie usług synchronizacji danych
Aby uzyskać informacje na temat tworzenia usługi ASP.NET Core za pomocą programu Entity Framework Core, zobacz samouczka.
Aby włączyć usługi synchronizacji danych, należy dodać następujące biblioteki NuGet:
- microsoft.AspNetCore.Datasync
- microsoft.AspNetCore.Datasync.EFCore dla tabel opartych na programie Entity Framework Core.
- microsoft.AspNetCore.Datasync.InMemory dla tabel w pamięci.
Zmodyfikuj plik Program.cs. Dodaj następujący wiersz we wszystkich innych definicjach usługi:
builder.Services.AddDatasyncControllers();
Możesz również użyć szablonu datasync-server Core ASP.NET:
# 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
Szablon zawiera przykładowy model i kontroler.
Tworzenie kontrolera tabeli dla tabeli SQL
Domyślne repozytorium używa platformy Entity Framework Core. Tworzenie kontrolera tabeli jest procesem trzyetapowym:
- Utwórz klasę modelu dla modelu danych.
- Dodaj klasę modelu do
DbContextdla aplikacji. - Utwórz nową klasę
TableController<T>, aby uwidocznić model.
Tworzenie klasy modelu
Wszystkie klasy modeli muszą implementować ITableData. Każdy typ repozytorium ma abstrakcyjną klasę, która implementuje ITableData. Repozytorium Entity Framework Core używa 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; }
}
Interfejs ITableData udostępnia identyfikator rekordu wraz z dodatkowymi właściwościami do obsługi usług synchronizacji danych:
-
UpdatedAt(DateTimeOffset?) zawiera datę ostatniej aktualizacji rekordu. -
Version(byte[]) udostępnia nieprzezroczystą wartość, która zmienia się na każdym zapisie. -
Deleted(bool) ma wartość true, jeśli rekord jest oznaczony do usunięcia, ale nie jest jeszcze czyszczony.
Biblioteka synchronizacji danych utrzymuje te właściwości. Nie modyfikuj tych właściwości we własnym kodzie.
Aktualizowanie DbContext
Każdy model w bazie danych musi być zarejestrowany w DbContext. Na przykład:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
Tworzenie kontrolera tabeli
Kontroler tabeli jest wyspecjalizowanym ApiController. Oto minimalny kontroler tabeli:
[Route("tables/[controller]")]
public class TodoItemController : TableController<TodoItem>
{
public TodoItemController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<TodoItem>(context);
}
}
Nuta
- Kontroler musi mieć trasę. Zgodnie z konwencją tabele są widoczne na ścieżce podrzędnej
/tables, ale można je umieścić w dowolnym miejscu. Jeśli używasz bibliotek klienckich starszych niż wersja 5.0.0, tabela musi być ścieżką podrzędną/tables. - Kontroler musi dziedziczyć z
TableController<T>, gdzie<T>jest implementacją implementacjiITableDatadla typu repozytorium. - Przypisz repozytorium na podstawie tego samego typu co model.
Implementowanie repozytorium w pamięci
Można również użyć repozytorium w pamięci bez magazynu trwałego. Dodaj pojedynczą usługę dla repozytorium w Program.cs:
IEnumerable<Model> seedData = GenerateSeedData();
builder.Services.AddSingleton<IRepository<Model>>(new InMemoryRepository<Model>(seedData));
Skonfiguruj kontroler tabeli w następujący sposób:
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public MovieController(IRepository<Model> repository) : base(repository)
{
}
}
Konfigurowanie opcji kontrolera tabeli
Niektóre aspekty kontrolera można skonfigurować przy użyciu TableControllerOptions:
[Route("tables/[controller]")]
public class MoodelController : TableController<Model>
{
public ModelController(IRepository<Model> repository) : base(repository)
{
Options = new TableControllerOptions { PageSize = 25 };
}
}
Opcje, które można ustawić, obejmują:
-
PageSize(int, wartość domyślna: 100) to maksymalna liczba elementów zwracanych przez operację zapytania na jednej stronie. -
MaxTop(int, wartość domyślna: 512000) to maksymalna liczba elementów zwracanych w operacji zapytania bez stronicowania. -
EnableSoftDelete(bool, wartość domyślna: false) umożliwia usuwanie nietrwałe, co oznacza elementy jako usunięte zamiast usuwać je z bazy danych. Usuwanie nietrwałe umożliwia klientom aktualizowanie pamięci podręcznej w trybie offline, ale wymaga oddzielnego przeczyszczania usuniętych elementów z bazy danych. -
UnauthorizedStatusCode(int, wartość domyślna: 401 Brak autoryzacji) to kod stanu zwrócony, gdy użytkownik nie może wykonać akcji.
Konfigurowanie uprawnień dostępu
Domyślnie użytkownik może wykonywać dowolne czynności, które chcą jednostki w tabeli — tworzyć, odczytywać, aktualizować i usuwać dowolny rekord. Aby uzyskać bardziej szczegółową kontrolę nad autoryzacją, utwórz klasę, która implementuje IAccessControlProvider.
IAccessControlProvider używa trzech metod do zaimplementowania autoryzacji:
-
GetDataView()zwraca lambda, która ogranicza to, co widzi połączony użytkownik. -
IsAuthorizedAsync()określa, czy połączony użytkownik może wykonać akcję dla żądanej jednostki. -
PreCommitHookAsync()dostosowuje dowolną jednostkę bezpośrednio przed zapisaniem w repozytorium.
Między trzema metodami można skutecznie obsługiwać większość przypadków kontroli dostępu. Jeśli potrzebujesz dostępu do HttpContext, skonfigurowaćHttpContextAccessor.
Na przykład poniższa tabela implementuje tabelę osobistą, w której użytkownik może zobaczyć tylko własne rekordy.
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 są asynchroniczne w przypadku, gdy musisz wykonać dodatkowe wyszukiwanie bazy danych, aby uzyskać poprawną odpowiedź. Interfejs IAccessControlProvider<T> można zaimplementować na kontrolerze, ale nadal musisz przekazać IHttpContextAccessor, aby uzyskać bezpieczny dostęp do HttpContext wątku.
Aby użyć tego dostawcy kontroli dostępu, zaktualizuj TableController w następujący sposób:
[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);
}
}
Jeśli chcesz zezwolić zarówno na dostęp nieuwierzytelniony, jak i uwierzytelniony do tabeli, udekoruj go [AllowAnonymous] zamiast [Authorize].
Konfigurowanie rejestrowania
Rejestrowanie jest obsługiwane za pośrednictwem normalnego mechanizmu rejestrowania dla ASP.NET Core. Przypisz obiekt ILogger do właściwości 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;
}
}
Monitorowanie zmian repozytorium
Po zmianie repozytorium można wyzwolić przepływy pracy, zarejestrować odpowiedź na klienta lub wykonać inną pracę w jednej z dwóch metod:
Opcja 1. Implementowanie narzędzia PostCommitHookAsync
Interfejs IAccessControlProvider<T> udostępnia metodę PostCommitHookAsync(). Metoda PostCommitHookAsync() jest wywoływana po zapisaniu danych do repozytorium, ale przed zwróceniem danych do klienta. Należy zadbać o to, aby upewnić się, że dane zwracane do klienta nie są zmieniane w tej metodzie.
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.
}
}
Użyj tej opcji, jeśli uruchamiasz zadania asynchroniczne w ramach haka.
Opcja 2. Użyj programu obsługi zdarzeń RepositoryUpdated
Klasa podstawowa TableController<T> zawiera procedurę obsługi zdarzeń wywoływaną w tym samym czasie co metoda 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
}
}
Włączanie tożsamości usługi Azure App Service
Serwer synchronizacji danych ASP.NET Core obsługuje ASP.NET Core Identitylub dowolny inny schemat uwierzytelniania i autoryzacji, który chcesz obsługiwać. Aby ułatwić uaktualnianie z wcześniejszych wersji usługi Azure Mobile Apps, udostępniamy również dostawcę tożsamości, który implementuje azure App Service Identity. Aby skonfigurować tożsamość usługi Azure App Service w aplikacji, edytuj Program.cs:
builder.Services.AddAuthentication(AzureAppServiceAuthentication.AuthenticationScheme)
.AddAzureAppServiceAuthentication(options => options.ForceEnable = true);
// Then later, after you have created the app
app.UseAuthentication();
app.UseAuthorization();
Obsługa bazy danych
Program Entity Framework Core nie konfiguruje generowania wartości dla kolumn daty/godziny. (Zobacz generowanie wartości daty/godziny ). Repozytorium usługi Azure Mobile Apps dla platformy Entity Framework Core automatycznie aktualizuje pole UpdatedAt. Jeśli jednak baza danych jest aktualizowana poza repozytorium, należy zorganizować aktualizowanie pól UpdatedAt i Version.
Azure SQL
Utwórz wyzwalacz dla każdej jednostki:
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
Ten wyzwalacz można zainstalować przy użyciu migracji lub bezpośrednio po EnsureCreated() w celu utworzenia bazy danych.
Azure Cosmos DB
Usługa Azure Cosmos DB to w pełni zarządzana baza danych NoSQL na potrzeby aplikacji o wysokiej wydajności o dowolnym rozmiarze lub skali. Aby uzyskać informacje na temat korzystania z usługi Azure Cosmos DB z platformą Entity Framework Core, zobacz dostawca usługi Azure Cosmos DB. W przypadku korzystania z usługi Azure Cosmos DB z usługą Azure Mobile Apps:
Skonfiguruj kontener cosmos za pomocą indeksu złożonego, który określa pola
UpdatedAtiId. Indeksy złożone można dodawać do kontenera za pośrednictwem witryny Azure Portal, usługi ARM, Bicep, narzędzia Terraform lub w kodzie. Oto przykład definicji zasobu bicep: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' } ] ] } } } }Jeśli ściągniesz podzbiór elementów w tabeli, upewnij się, że określono wszystkie właściwości związane z zapytaniem.
Tworzenie modeli z klasy
ETagEntityTableData:public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }Dodaj metodę
OnModelCreating(ModelBuilder)doDbContext. Sterownik usługi Cosmos DB dla programu Entity Framework domyślnie umieszcza wszystkie jednostki w tym samym kontenerze. Co najmniej należy wybrać odpowiedni klucz partycji i upewnić się, że właściwośćEntityTagjest oznaczona jako tag współbieżności. Na przykład poniższy fragment kodu przechowuje jednostkiTodoItemwe własnym kontenerze z odpowiednimi ustawieniami usługi 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); }
Usługa Azure Cosmos DB jest obsługiwana w pakiecie NuGet Microsoft.AspNetCore.Datasync.EFCore od wersji 5.0.11. Aby uzyskać więcej informacji, zapoznaj się z następującymi linkami:
- przykład Cosmos DB — przykład.
- dokumentacji dostawcy usługi Azure Cosmos DB platformy Ef Core.
- dokumentacja zasad indeksowania usługi Cosmos DB.
PostgreSQL
Utwórz wyzwalacz dla każdej jednostki:
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();
Ten wyzwalacz można zainstalować przy użyciu migracji lub bezpośrednio po EnsureCreated() w celu utworzenia bazy danych.
SqLite
Ostrzeżenie
Nie używaj sqlite dla usług produkcyjnych. SqLite nadaje się tylko do użycia po stronie klienta w środowisku produkcyjnym.
SqLite nie ma pola daty/godziny, które obsługuje dokładność milisekund. W związku z tym nie nadaje się do niczego, z wyjątkiem testowania. Jeśli chcesz użyć narzędzia SqLite, upewnij się, że implementujesz konwerter wartości i moduł porównujący wartości w każdym modelu dla właściwości daty/godziny. Najprostszą metodą implementacji konwerterów wartości i porównań jest metoda OnModelCreating(ModelBuilder)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);
}
Zainstaluj wyzwalacz aktualizacji podczas inicjowania bazy danych:
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);
}
}
}
Upewnij się, że metoda InstallUpdateTriggers jest wywoływana tylko raz podczas inicjowania bazy danych:
public void InitializeDatabase(DbContext context)
{
bool created = context.Database.EnsureCreated();
if (created && context.Database.IsSqlite())
{
InstallUpdateTriggers(context);
}
context.Database.SaveChanges();
}
LiteDB
LiteDB to bezserwerowa baza danych dostarczana w pojedynczej małej biblioteki DLL napisanej w kodzie zarządzanym .NET C#. Jest to proste i szybkie rozwiązanie bazy danych NoSQL dla aplikacji autonomicznych. Aby użyć bazy danych LiteDb z magazynem trwałym na dysku:
Zainstaluj pakiet
Microsoft.AspNetCore.Datasync.LiteDbz narzędzia NuGet.Dodaj pojedynczy element dla
LiteDatabasedoProgram.cs:const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));Tworzenie modeli z
LiteDbTableData:public class TodoItem : LiteDbTableData { public string Title { get; set; } public bool Completed { get; set; } }Możesz użyć dowolnych atrybutów
BsonMapperdostarczanych z pakietem NuGet LiteDb.Utwórz kontroler przy użyciu
LiteDbRepository:[Route("tables/[controller]")] public class TodoItemController : TableController<TodoItem> { public TodoItemController(LiteDatabase db) : base() { Repository = new LiteDbRepository<TodoItem>(db, "todoitems"); } }
Obsługa interfejsu OpenAPI
Interfejs API zdefiniowany przez kontrolery synchronizacji danych można opublikować przy użyciu NSwag lub swashbuckle. W obu przypadkach rozpocznij od skonfigurowania usługi tak, jak zwykle dla wybranej biblioteki.
NSwag
Postępuj zgodnie z podstawowymi instrukcjami dotyczącymi integracji sieciowej grupy zabezpieczeń, a następnie zmodyfikuj w następujący sposób:
Dodaj pakiety do projektu, aby obsługiwać aplikację NSwag. Wymagane są następujące pakiety:
Dodaj następujące elementy na początku pliku
Program.cs:using Microsoft.AspNetCore.Datasync.NSwag;Dodaj usługę, aby wygenerować definicję interfejsu OpenAPI do pliku
Program.cs:builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });Włącz oprogramowanie pośredniczące do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger, również w pliku
Program.cs:if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUI3(); }
Przejście do /swagger punktu końcowego usługi internetowej umożliwia przeglądanie interfejsu API. Definicję interfejsu OpenAPI można następnie zaimportować do innych usług (takich jak Usługa Azure API Management). Aby uzyskać więcej informacji na temat konfigurowania sieciowej grupy zabezpieczeń, zobacz Rozpoczynanie pracy z siecią NSwag i ASP.NET Core.
Swashbuckle
Postępuj zgodnie z podstawowymi instrukcjami dotyczącymi integracji pakietu Swashbuckle, a następnie zmodyfikuj w następujący sposób:
Dodaj pakiety do projektu, aby obsługiwać pakiet Swashbuckle. Wymagane są następujące pakiety:
- Swashbuckle.AspNetCore.
- Swashbuckle.AspNetCore.Newtonsoft.
- Microsoft.AspNetCore.Datasync.Swashbuckle.
Dodaj usługę, aby wygenerować definicję interfejsu OpenAPI do pliku
Program.cs:builder.Services.AddSwaggerGen(options => { options.AddDatasyncControllers(); }); builder.Services.AddSwaggerGenNewtonsoftSupport();Nuta
Metoda
AddDatasyncControllers()przyjmuje opcjonalnąAssemblyodpowiadającą zestawowi zawierającemu kontrolery tabel. ParametrAssemblyjest wymagany tylko wtedy, gdy kontrolery tabel znajdują się w innym projekcie usługi.Włącz oprogramowanie pośredniczące do obsługi wygenerowanego dokumentu JSON i interfejsu użytkownika programu Swagger, również w pliku
Program.cs:if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); options.RoutePrefix = string.Empty; }); }
Dzięki tej konfiguracji przejście do katalogu głównego usługi internetowej umożliwia przeglądanie interfejsu API. Definicję interfejsu OpenAPI można następnie zaimportować do innych usług (takich jak Usługa Azure API Management). Aby uzyskać więcej informacji na temat konfigurowania pakietu Swashbuckle, zobacz Rozpoczynanie pracy z pakietem Swashbuckle i ASP.NET Core.
Ograniczenia
Wersja ASP.NET Core bibliotek usług implementuje operację listy OData w wersji 4. Gdy serwer działa w trybie zgodności z poprzednimi wersjami, filtrowanie podciągów nie jest obsługiwane.