Jak używać zestawu SDK serwera zaplecza platformy ASP.NET Core

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ą DateTime pole typu lub Timestamp , które jest przechowywane z dokładnością milisekund. Implementacje repozytorium są udostępniane dla platform Entity Framework Core i LiteDb.

Aby uzyskać określoną obsługę bazy danych, zobacz następujące sekcje:

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:

  1. Utwórz projekt serwera ASP.NET w wersji 6.0 (lub nowszej).
  2. Dodawanie programu Entity Framework Core
  3. Dodawanie usług synchronizacji danych

Aby uzyskać informacje na temat tworzenia usługi ASP.NET Core za pomocą programu Entity Framework Core, zobacz samouczek.

Aby włączyć usługi synchronizacji danych, należy dodać następujące biblioteki NuGet:

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

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:

  1. Utwórz klasę modelu dla modelu danych.
  2. Dodaj klasę modelu do DbContext klasy dla aplikacji.
  3. Utwórz nową TableController<T> klasę, aby uwidocznić model.

Tworzenie klasy modelu

Wszystkie klasy modeli muszą implementować wartość ITableData. Każdy typ repozytorium ma abstrakcyjną klasę, która implementuje ITableDataelement . Repozytorium Entity Framework Core używa elementu 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 obiekcie 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 ApiControllerkontrolerem . Oto minimalny kontroler tabeli:

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

Uwaga

  • Kontroler musi mieć trasę. Zgodnie z konwencją tabele są widoczne na ścieżce podrzędnej /tableselementu , 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>klasy , gdzie <T> jest implementacją implementacji ITableData dla 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 pliku 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 polecenia 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 (intwartość domyślna: 100) to maksymalna liczba elementów zwracanych przez operację zapytania na jednej stronie.
  • MaxTop (intwartość 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 usuwania ich 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 (intwartość domyślna: 401 Brak autoryzacji) to kod stanu zwracany, 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 IAccessControlProviderelement . Metoda IAccessControlProvider używa trzech metod do implementowania autoryzacji:

  • GetDataView() Zwraca wartość lambda, która ogranicza to, co widzi połączony użytkownik.
  • IsAuthorizedAsync() Określa, czy połączony użytkownik może wykonać akcję dla określonej jednostki, która jest żądana.
  • 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 obiektu HttpContext, skonfiguruj funkcję 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 można zaimplementować IAccessControlProvider<T> na kontrolerze, ale nadal musisz przekazać element , IHttpContextAccessor aby uzyskać bezpieczny dostęp do HttpContext wątku.

Aby użyć tego dostawcy kontroli dostępu, zaktualizuj plik 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 pomocą normalnego mechanizmu rejestrowania dla platformy ASP.NET Core. ILogger Przypisz obiekt do Logger właściwości:

[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 Th 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 TableController<T> bazowa zawiera procedurę obsługi zdarzeń, która jest wywoływana w tym samym czasie co PostCommitHookAsync() metoda.

[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 aplikacja systemu Azure

Serwer synchronizacji danych ASP.NET Core obsługuje ASP.NET Core Identity lub 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 tożsamość usługi aplikacja systemu Azure. Aby skonfigurować tożsamość usługi aplikacja systemu Azure w aplikacji, edytuj polecenie 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 baz 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 UpdatedAt pole. Jeśli jednak baza danych jest aktualizowana poza repozytorium, należy zorganizować UpdatedAt aktualizację pól 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 natychmiast po EnsureCreated() utworzeniu 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:

  1. Skonfiguruj kontener cosmos za pomocą indeksu złożonego, który określa UpdatedAt pola i Id . 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ładowa definicja 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.

  2. Tworzenie modeli z ETagEntityTableData klasy:

    public class TodoItem : ETagEntityTableData
    {
        public string Title { get; set; }
        public bool Completed { get; set; }
    }
    
  3. Dodaj metodę OnModelCreating(ModelBuilder) do metody DbContext. 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 EntityTag właściwość jest oznaczona jako tag współbieżności. Na przykład poniższy fragment kodu przechowuje TodoItem jednostki we 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 Microsoft.AspNetCore.Datasync.EFCore NuGet od wersji 5.0.11. Aby uzyskać więcej informacji, zapoznaj się z następującymi linkami:

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 natychmiast po EnsureCreated() utworzeniu 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ą implementowania konwerterów wartości i porównań jest OnModelCreating(ModelBuilder) metoda :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 jest wywoływana InstallUpdateTriggers 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:

  1. Microsoft.AspNetCore.Datasync.LiteDb Zainstaluj pakiet z narzędzia NuGet.

  2. Dodaj pojedynczy element dla elementu LiteDatabase do elementu Program.cs:

    const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString");
    builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
    
  3. Tworzenie modeli pochodnych z elementu LiteDbTableData:

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

    Możesz użyć dowolnych atrybutów dostarczanych BsonMapper z pakietem NuGet LiteDb.

  4. Utwórz kontroler przy użyciu polecenia 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 narzędzia 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:

  1. Dodaj pakiety do projektu, aby obsługiwać aplikację NSwag. Wymagane są następujące pakiety:

  2. Dodaj następujące elementy na początku Program.cs pliku:

    using Microsoft.AspNetCore.Datasync.NSwag;
    
  3. Dodaj usługę, aby wygenerować definicję interfejsu OpenAPI do Program.cs pliku:

    builder.Services.AddOpenApiDocument(options =>
    {
        options.AddDatasyncProcessors();
    });
    
  4. 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:

  1. Dodaj pakiety do projektu, aby obsługiwać pakiet Swashbuckle. Wymagane są następujące pakiety:

  2. Dodaj usługę, aby wygenerować definicję interfejsu OpenAPI do Program.cs pliku:

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

    Uwaga

    Metoda AddDatasyncControllers() przyjmuje opcjonalny element Assembly odpowiadający zestawowi zawierającemu kontrolery tabel. Parametr Assembly jest wymagany tylko wtedy, gdy kontrolery tabel znajdują się w innym projekcie usługi.

  3. 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 Wprowadzenie do programu 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.