Beállítások minta a .NET-ben

A beállításminta osztályok használatával biztosít szigorú hozzáférést a kapcsolódó beállítások csoportjaihoz. Ha a konfigurációs beállításokat forgatókönyvek szerint külön osztályokba különítjük el, az alkalmazás két fontos szoftvermérnöki alapelvnek felel meg:

  • Az interfész szegregációjának elve (ISP) vagy beágyazás: A konfigurációs beállításoktól függő forgatókönyvek (osztályok) csak az általuk használt konfigurációs beállításoktól függnek.
  • A problémák elkülönítése: Gépház az alkalmazás különböző részei nem függenek egymástól, vagy nem állnak egymással kapcsolatban.

A beállítások emellett a konfigurációs adatok ellenőrzésére szolgáló mechanizmust is biztosítanak. További információt a Beállítások érvényesítési szakaszában talál.

Hierarchikus kötés konfigurálása

A kapcsolódó konfigurációs értékek olvasásának elsődleges módja a beállítási minta használata. A beállítási minta az IOptions<TOptions> interfészen keresztül lehetséges, ahol az általános típusparaméter TOptions egy class. Ezt IOptions<TOptions> később függőséginjektálással lehet biztosítani. További információ: Függőséginjektálás a .NET-ben.

Ha például egy appsettings.json fájlból szeretné beolvasni a kiemelt konfigurációs értékeket:

{
    "SecretKey": "Secret key value",
    "TransientFaultHandlingOptions": {
        "Enabled": true,
        "AutoRetryDelay": "00:00:07"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    }
}

Hozza létre a következő TransientFaultHandlingOptions osztályt:

public sealed class TransientFaultHandlingOptions
{
    public bool Enabled { get; set; }
    public TimeSpan AutoRetryDelay { get; set; }
}

A beállítási minta használatakor egy beállításosztály:

  • Nyilvános paraméter nélküli konstruktorral nem absztraktnak kell lennie
  • Nyilvános írási-olvasási tulajdonságokat tartalmaz a kötéshez (a mezők nem kötöttek)

A következő kód a Program.cs C# fájl része, és:

  • A ConfigurationBinder.Bind meghívásával köti az osztályt TransientFaultHandlingOptions a "TransientFaultHandlingOptions" szakaszhoz.
  • Megjeleníti a konfigurációs adatokat.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ConsoleJson.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Configuration.Sources.Clear();

IHostEnvironment env = builder.Environment;

builder.Configuration
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);

TransientFaultHandlingOptions options = new();
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
    .Bind(options);

Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

// <Output>
// Sample output:

Az előző kódban a JSON-konfigurációs fájl szakasza "TransientFaultHandlingOptions" a TransientFaultHandlingOptions példányhoz van kötve. Ez hidratálja a C#-objektumok tulajdonságait a konfiguráció megfelelő értékeivel.

ConfigurationBinder.Get<T> köti és visszaadja a megadott típust. ConfigurationBinder.Get<T> lehet, hogy kényelmesebb, mint a használata ConfigurationBinder.Bind. Az alábbi kód bemutatja, hogyan használható ConfigurationBinder.Get<T> az TransientFaultHandlingOptions osztály:

var options =
    builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
        .Get<TransientFaultHandlingOptions>();

Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

Az előző kódban a rendszer az ConfigurationBinder.Get<T> objektum egy példányát használja az TransientFaultHandlingOptions alapul szolgáló konfigurációból feltöltött tulajdonságértékekkel.

Fontos

Az ConfigurationBinder osztály számos API-t tesz elérhetővé, például .Get<T>().Bind(object instance) anem korlátozott classapi-kat. A Beállítások interfészek bármelyikének használatakor be kell tartania a fent említett beállításosztály-korlátozásokat.

A beállítási minta használatakor alternatív módszer a szakasz kötése "TransientFaultHandlingOptions" és hozzáadása a függőséginjektálási szolgáltatás tárolóhoz. A következő kódban TransientFaultHandlingOptions a rendszer hozzáadja a szolgáltatástárolót a konfigurációhoz Configure és a konfigurációhoz:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.Configure<TransientFaultHandlingOptions>(
    builder.Configuration.GetSection(
        key: nameof(TransientFaultHandlingOptions)));

Az builder előző példában a következő példány HostApplicationBuilderlátható: .

Tipp.

A key paraméter a keresendő konfigurációs szakasz neve. Nem kell megegyeznie az azt jelképező típus nevével. Lehet például egy szakasz neve "FaultHandling" , amelyet az TransientFaultHandlingOptions osztály jelölhet. Ebben a példában inkább a GetSection függvénynek adná át"FaultHandling". Az nameof operátor akkor használható kényelmesen, ha az elnevezett szakasz megfelel a megfelelő típusnak.

Az előző kód használatával a következő kód felolvassa a pozícióbeállításokat:

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ExampleService(IOptions<TransientFaultHandlingOptions> options)
{
    private readonly TransientFaultHandlingOptions _options = options.Value;

    public void DisplayValues()
    {
        Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
        Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
    }
}

Az előző kódban az alkalmazás elindítása után a JSON-konfigurációs fájl módosításai nem lesznek beolvasva. Az alkalmazás elindítása utáni módosítások olvasásához használja az IOptionsSnapshot vagy az IOptionsMonitor parancsot a változások figyeléséhez, és ennek megfelelően reagáljon.

Beállítási felületek

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

IOptionsFactory<TOptions> új beállításpéldányok létrehozásáért felelős. Egyetlen Create metódussal rendelkezik. Az alapértelmezett implementáció az összes regisztráltat IConfigureOptions<TOptions> veszi, és IPostConfigureOptions<TOptions> először az összes konfigurációt futtatja, majd a konfiguráció utánit. Megkülönbözteti IConfigureNamedOptions<TOptions> és IConfigureOptions<TOptions> csak a megfelelő felületet hívja meg.

IOptionsMonitorCache<TOptions>a példányok gyorsítótárazására TOptions szolgálIOptionsMonitor<TOptions>. Az IOptionsMonitorCache<TOptions> érvényteleníti a figyelőben lévő beállításpéldányokat, hogy az érték újrafordításra () legyen fordítva.TryRemove Az értékek manuálisan is bevezethetők a TryAdd. A Clear metódus akkor használatos, ha az összes elnevezett példányt igény szerint újra létre kell hozni.

IOptionsChangeTokenSource<TOptions>A rendszer a mögöttes TOptions példány módosításait nyomon követő lekérésére IChangeToken szolgál. A változási jogkivonat primitívjeiről további információt a Változásértesítések című témakörben talál.

A beállítási felületek előnyei

Egy általános burkolótípus használatával leválaszthatja a beállítás élettartamát a DI-tárolóról. Az IOptions<TOptions>.Value interfész absztrakciós réteget biztosít, beleértve az általános korlátozásokat is a beállítások típusához. Ez a következő előnyöket nyújtja:

  • A konfigurációs példány kiértékelése T nem az injektálási IOptions<TOptions>.Value, hanem a hozzáférésre halasztódik. Ez azért fontos, mert használhatja a T lehetőséget a különböző helyeken, és válassza ki az élettartam szemantikát anélkül, hogy bármit megváltoztatna.T
  • A típusbeállítások Tregisztrálásakor nem kell explicit módon regisztrálnia a típust T . Ez kényelmes, ha egyszerű alapértelmezésekkel rendelkező kódtárat hoz létre, és nem szeretné kényszeríteni a hívót, hogy a beállításokat egy adott élettartammal regisztrálja a DI-tárolóban.
  • Az API szempontjából lehetővé teszi a típus T korlátozásait (ebben az esetben T egy referenciatípusra van korlátozva).

Frissített adatok olvasása az IOptionsSnapshot használatával

Ha használja IOptionsSnapshot<TOptions>, a rendszer kérésenként egyszer számítja ki a beállításokat, amikor hozzáfér, és a rendszer gyorsítótárazza a kérés teljes élettartama alatt. A konfiguráció módosításai az alkalmazás elindítása után jelennek meg, amikor olyan konfigurációszolgáltatókat használ, amelyek támogatják a frissített konfigurációs értékek olvasását.

A különbség a következők között IOptionsMonitorIOptionsSnapshot van:

  • IOptionsMonitor egy singleton szolgáltatás , amely bármikor lekéri az aktuális beállításértékeket, ami különösen hasznos az önálló függőségekben.
  • IOptionsSnapshothatókörrel rendelkező szolgáltatás, és pillanatképet biztosít az objektum létrehozásakor IOptionsSnapshot<T> elérhető lehetőségekről. A beállítási pillanatképek átmeneti és hatókörű függőségekhez használhatók.

Az alábbi kód a következőt használja IOptionsSnapshot<TOptions>.

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ScopedService(IOptionsSnapshot<TransientFaultHandlingOptions> options)
{
    private readonly TransientFaultHandlingOptions _options = options.Value;

    public void DisplayValues()
    {
        Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
        Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
    }
}

Az alábbi kód regisztrál egy konfigurációs példányt, amely TransientFaultHandlingOptions a következőhöz kapcsolódik:

builder.Services
    .Configure<TransientFaultHandlingOptions>(
        configurationRoot.GetSection(
            nameof(TransientFaultHandlingOptions)));

Az előző kódban a rendszer egy olyan konfigurációs példány regisztrálására használja a Configure<TOptions> metódust, amely TOptions a konfigurálás módosításakor csatlakozik, és frissíti a beállításokat.

IOptionsMonitor

A beállításfigyelő használatához a beállításobjektumok ugyanúgy vannak konfigurálva, mint egy konfigurációs szakaszban.

builder.Services
    .Configure<TransientFaultHandlingOptions>(
        configurationRoot.GetSection(
            nameof(TransientFaultHandlingOptions)));

Az alábbi példa a következőket használja IOptionsMonitor<TOptions>:

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class MonitorService(IOptionsMonitor<TransientFaultHandlingOptions> monitor)
{
    public void DisplayValues()
    {
        TransientFaultHandlingOptions options = monitor.CurrentValue;

        Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
        Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
    }
}

Az előző kódban a JSON-konfigurációs fájl módosításai az alkalmazás elindítása után beolvasásra kerülnek.

Tipp.

Egyes fájlrendszerek, például a Docker-tárolók és a hálózati megosztások nem feltétlenül küldenek megbízhatóan változásértesítéseket. Ha az interfészt ezekben a IOptionsMonitor<TOptions> környezetekben használja, állítsa be a DOTNET_USE_POLLING_FILE_WATCHER környezeti változót 1 a fájlrendszer változásainak lekérdezésére vagy true lekérdezésére. A módosítások lekérdezésének időköze négy másodpercenként történik, és nem konfigurálható.

További információ a Docker-tárolókról: .NET-alkalmazás tárolóba helyezése.

Az elnevezett beállítások támogatása az IConfigureNamedOptions használatával

Elnevezett beállítások:

  • Akkor hasznos, ha több konfigurációs szakasz is ugyanahhoz a tulajdonsághoz kapcsolódik.
  • A kis- és nagybetűk megkülönböztetik a kis- és nagybetűket.

Vegye figyelembe a következő appsettings.json fájlt:

{
  "Features": {
    "Personalize": {
      "Enabled": true,
      "ApiKey": "aGEgaGEgeW91IHRob3VnaHQgdGhhdCB3YXMgcmVhbGx5IHNvbWV0aGluZw=="
    },
    "WeatherStation": {
      "Enabled": true,
      "ApiKey": "QXJlIHlvdSBhdHRlbXB0aW5nIHRvIGhhY2sgdXM/"
    }
  }
}

Ahelyett, hogy két osztályt hoz létre a kötéshez Features:Personalize , a Features:WeatherStationkövetkező osztályt használja az egyes szakaszokhoz:

public class Features
{
    public const string Personalize = nameof(Personalize);
    public const string WeatherStation = nameof(WeatherStation);

    public bool Enabled { get; set; }
    public string ApiKey { get; set; }
}

A következő kód konfigurálja a névvel ellátott beállításokat:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Omitted for brevity...

builder.Services.Configure<Features>(
    Features.Personalize,
    builder.Configuration.GetSection("Features:Personalize"));

builder.Services.Configure<Features>(
    Features.WeatherStation,
    builder.Configuration.GetSection("Features:WeatherStation"));

Az alábbi kód a névvel ellátott beállításokat jeleníti meg:

public class sealed Service
{
    private readonly Features _personalizeFeature;
    private readonly Features _weatherStationFeature;

    public Service(IOptionsSnapshot<Features> namedOptionsAccessor)
    {
        _personalizeFeature = namedOptionsAccessor.Get(Features.Personalize);
        _weatherStationFeature = namedOptionsAccessor.Get(Features.WeatherStation);
    }
}

Minden beállítás nevesített példány. IConfigureOptions<TOptions> a példányokat a rendszer a Options.DefaultName példányt célként kezeli, azaz string.Empty. IConfigureNamedOptions<TOptions> is megvalósítja IConfigureOptions<TOptions>. Az alapértelmezett implementációnak IOptionsFactory<TOptions> megfelelően kell használnia az egyes logikát. A null névvel ellátott beállítás az összes elnevezett példányt megcélozza egy adott elnevezett példány helyett. ConfigureAll és PostConfigureAll használja ezt az egyezményt.

OptionsBuilder API

OptionsBuilder<TOptions> a példányok konfigurálására TOptions szolgál. OptionsBuilder Egyszerűbbé teszi az elnevezett beállítások létrehozását, mivel ez csak egy paraméter a kezdeti AddOptions<TOptions>(string optionsName) híváshoz, ahelyett, hogy az összes későbbi hívásban megjelenne. A beállítások érvényesítése és a ConfigureOptions szolgáltatásfüggőségeket elfogadó túlterhelések csak a OptionsBuilder.

OptionsBuildera Beállítások érvényesítési szakaszában használható.

Beállítások konfigurálása a DI-szolgáltatások használatával

A szolgáltatások a függőséginjektálásból érhetők el, a beállítások kétféleképpen konfigurálhatók:

  • Adjon át egy konfigurációs meghatalmazottat az OptionsBuilder<TOptions> konfigurálásához. OptionsBuilder<TOptions> Túlterheléseket biztosít a Konfiguráláshoz , amelyek legfeljebb öt szolgáltatás használatát teszik lehetővé a beállítások konfigurálásához:

    builder.Services
        .AddOptions<MyOptions>("optionalName")
        .Configure<ExampleService, ScopedService, MonitorService>(
            (options, es, ss, ms) =>
                options.Property = DoSomethingWith(es, ss, ms));
    
  • Hozzon létre egy típust, amely megvalósítja IConfigureOptions<TOptions> vagy IConfigureNamedOptions<TOptions> regisztrálja a típust szolgáltatásként.

Javasoljuk, hogy adjon át egy konfigurációs meghatalmazottat a Konfigurálásnak, mivel a szolgáltatás létrehozása összetettebb. A típus létrehozása egyenértékű azzal, amit a keretrendszer a Konfigurálás hívásakor végez. A Hívás konfigurálása szolgáltatás egy átmeneti általánost IConfigureNamedOptions<TOptions>regisztrál, amelynek konstruktora elfogadja a megadott általános szolgáltatástípusokat.

Beállítások érvényesítése

A beállítások érvényesítése lehetővé teszi a beállításértékek érvényesítését.

Vegye figyelembe a következő appsettings.json fájlt:

{
  "MyCustomSettingsSection": {
    "SiteTitle": "Amazing docs from Awesome people!",
    "Scale": 10,
    "VerbosityLevel": 32
  }
}

A következő osztály a "MyCustomSettingsSection" konfigurációs szakaszhoz kötődik, és néhány DataAnnotations szabályt alkalmaz:

using System.ComponentModel.DataAnnotations;

namespace ConsoleJson.Example;

public sealed class SettingsOptions
{
    public const string ConfigurationSectionName = "MyCustomSettingsSection";

    [Required]
    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public required string SiteTitle { get; set; }

    [Required]
    [Range(0, 1_000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public required int Scale { get; set; }

    [Required]
    public required int VerbosityLevel { get; set; }
}

Az előző SettingsOptions osztályban a ConfigurationSectionName tulajdonság tartalmazza annak a konfigurációs szakasznak a nevét, amelyhez csatlakozni szeretne. Ebben a forgatókönyvben a beállításobjektum adja meg a konfigurációs szakasz nevét.

Tipp.

A konfigurációs szakasz neve független a konfigurációs objektumtól, amelyhez kötést fűz. Más szóval egy elnevezett "FooBarOptions" konfigurációs szakasz egy névvel ellátott ZedOptionsbeállításobjektumhoz köthető. Bár gyakori lehet, hogy ugyanazt a nevet adja nekik, nem szükséges, és valójában névütközéseket okozhat.

A következő kód:

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations();

A ValidateDataAnnotations bővítménymetódus a Microsoft.Extensions.Options.DataAnnotations NuGet-csomagban van definiálva.

A következő kód megjeleníti a konfigurációs értékeket vagy a jelentések érvényesítési hibáit:

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ValidationService
{
    private readonly ILogger<ValidationService> _logger;
    private readonly IOptions<SettingsOptions> _config;

    public ValidationService(
        ILogger<ValidationService> logger,
        IOptions<SettingsOptions> config)
    {
        _config = config;
        _logger = logger;

        try
        {
            SettingsOptions options = _config.Value;
        }
        catch (OptionsValidationException ex)
        {
            foreach (string failure in ex.Failures)
            {
                _logger.LogError("Validation error: {FailureMessage}", failure);
            }
        }
    }
}

A következő kód egy összetettebb érvényesítési szabályt alkalmaz meghatalmazott használatával:

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.");

Az érvényesítés futásidőben történik, de úgy konfigurálhatja, hogy indításkor történjen, ha ehelyett a következőhöz ValidateOnStartláncolással kapcsol egy hívást:

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.")
    .ValidateOnStart();

A .NET 8-tól kezdve használhat egy másik API-t, AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String)amely lehetővé teszi az ellenőrzés indítását egy adott beállítástípus esetében:

builder.Services
    .AddOptionsWithValidateOnStart<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.");

IValidateOptions összetett ellenőrzéshez

A következő osztály implementálja IValidateOptions<TOptions>:

using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

sealed partial class ValidateSettingsOptions(
    IConfiguration config)
    : IValidateOptions<SettingsOptions>
{
    public SettingsOptions? Settings { get; private set; } =
        config.GetSection(SettingsOptions.ConfigurationSectionName)
              .Get<SettingsOptions>();

    public ValidateOptionsResult Validate(string? name, SettingsOptions options)
    {
        StringBuilder? failure = null;
    
        if (!ValidationRegex().IsMatch(options.SiteTitle))
        {
            (failure ??= new()).AppendLine($"{options.SiteTitle} doesn't match RegEx");
        }

        if (options.Scale is < 0 or > 1_000)
        {
            (failure ??= new()).AppendLine($"{options.Scale} isn't within Range 0 - 1000");
        }

        if (Settings is { Scale: 0 } && Settings.VerbosityLevel <= Settings.Scale)
        {
            (failure ??= new()).AppendLine("VerbosityLevel must be > than Scale.");
        }

        return failure is not null
            ? ValidateOptionsResult.Fail(failure.ToString())
            : ValidateOptionsResult.Success;
    }

    [GeneratedRegex("^[a-zA-Z''-'\\s]{1,40}$")]
    private static partial Regex ValidationRegex();
}

IValidateOptions lehetővé teszi az érvényesítési kód osztályba való áthelyezését.

Feljegyzés

Ez a példakód a Microsoft.Extensions.Configuration.Json NuGet csomagra támaszkodik.

Az előző kód használatával az ellenőrzés engedélyezve van, amikor a következő kóddal konfigurálja a szolgáltatásokat:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Omitted for brevity...

builder.Services.Configure<SettingsOptions>(
    builder.Configuration.GetSection(
        SettingsOptions.ConfigurationSectionName));

builder.Services.TryAddEnumerable(
    ServiceDescriptor.Singleton
        <IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());

Beállítások a konfigurációt követően

Konfigurálás utáni beállítások beállítása a következővel IPostConfigureOptions<TOptions>: . A konfiguráció utáni futtatás az összes IConfigureOptions<TOptions> konfiguráció után történik, és olyan helyzetekben lehet hasznos, amikor felül kell bírálnia a konfigurációt:

builder.Services.PostConfigure<CustomOptions>(customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

PostConfigure a névvel ellátott beállítások utólagos konfigurálásához érhető el:

builder.Services.PostConfigure<CustomOptions>("named_options_1", customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

Az összes konfigurációs példány konfigurálását követően használható PostConfigureAll :

builder.Services.PostConfigureAll<CustomOptions>(customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

Lásd még