Bagikan melalui


Filter panggilan grain

Filter panggilan biji-bijian menyediakan sarana untuk mencegat panggilan biji-bijian. Filter dapat menjalankan kode baik sebelum dan sesudah panggilan grain. Beberapa filter dapat diinstal secara bersamaan. Filter bersifat asinkron dan dapat memodifikasi RequestContext, argumen, dan nilai pengembalian metode yang dipanggil. Filter juga dapat memeriksa MethodInfo metode yang dipanggil pada kelas biji-bijian dan dapat digunakan untuk melempar atau menangani pengecualian.

Beberapa contoh penggunaan filter panggilan biji-bijian adalah:

  • Otorisasi: filter dapat memeriksa metode yang dipanggil dan argumen atau beberapa informasi otorisasi di RequestContext untuk menentukan apakah akan mengizinkan panggilan dilanjutkan atau tidak.
  • Pengelogan/Telemetri: filter dapat mencatat informasi dan menangkap data waktu dan statistik lain tentang pemanggilan metode.
  • Penanganan Kesalahan: filter dapat mencegat pengecualian yang dilemparkan oleh pemanggilan metode dan mengubahnya menjadi pengecualian lain atau menangani pengecualian saat melewati filter.

Filter hadir dalam dua rasa:

  • Filter panggilan masuk
  • Filter panggilan keluar

Filter panggilan masuk dijalankan saat menerima panggilan. Filter panggilan keluar dijalankan saat melakukan panggilan.

Filter panggilan masuk

Filter panggilan biji-bijian masuk mengimplementasikan IIncomingGrainCallFilter antarmuka, yang memiliki satu metode:

public interface IIncomingGrainCallFilter
{
    Task Invoke(IIncomingGrainCallContext context);
}

Argumen IIncomingGrainCallContext yang diteruskan ke Invoke metode memiliki bentuk berikut:

public interface IIncomingGrainCallContext
{
    /// <summary>
    /// Gets the grain being invoked.
    /// </summary>
    IAddressable Grain { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the interface method being invoked.
    /// </summary>
    MethodInfo InterfaceMethod { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the implementation method being invoked.
    /// </summary>
    MethodInfo ImplementationMethod { get; }

    /// <summary>
    /// Gets the arguments for this method invocation.
    /// </summary>
    object[] Arguments { get; }

    /// <summary>
    /// Invokes the request.
    /// </summary>
    Task Invoke();

    /// <summary>
    /// Gets or sets the result.
    /// </summary>
    object Result { get; set; }
}

Metode IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext) harus menunggu atau mengembalikan hasil IIncomingGrainCallContext.Invoke() untuk menjalankan filter yang dikonfigurasi berikutnya dan akhirnya metode biji-bijian itu sendiri. Properti Result dapat dimodifikasi setelah menunggu Invoke() metode . ImplementationMethod Properti mengembalikan MethodInfo kelas implementasi. Metode MethodInfo antarmuka dapat diakses menggunakan InterfaceMethod properti . Filter panggilan biji-bijian dipanggil untuk semua panggilan metode ke biji-bijian dan ini termasuk panggilan ke ekstensi biji-bijian IGrainExtension(implementasi ) yang dipasang dalam biji-bijian. Misalnya, ekstensi biji-bijian digunakan untuk mengimplementasikan token Aliran dan Pembatalan. Oleh karena itu, harus diharapkan bahwa nilai ImplementationMethod tidak selalu menjadi metode di kelas biji-bijian itu sendiri.

Mengonfigurasi filter panggilan grain masuk

IIncomingGrainCallFilter Implementasi dapat didaftarkan sebagai filter di seluruh silo melalui Injeksi Dependensi atau dapat didaftarkan sebagai filter tingkat biji-bijian melalui grain yang diimplementasikan IIncomingGrainCallFilter secara langsung.

Filter panggilan biji-bijian di seluruh silo

Delegasi dapat didaftarkan sebagai filter panggilan grain di seluruh silo menggunakan Injeksi Dependensi seperti:

siloHostBuilder.AddIncomingGrainCallFilter(async context =>
{
    // If the method being called is 'MyInterceptedMethod', then set a value
    // on the RequestContext which can then be read by other filters or the grain.
    if (string.Equals(
        context.InterfaceMethod.Name,
        nameof(IMyGrain.MyInterceptedMethod)))
    {
        RequestContext.Set(
            "intercepted value", "this value was added by the filter");
    }

    await context.Invoke();

    // If the grain method returned an int, set the result to double that value.
    if (context.Result is int resultValue)
    {
        context.Result = resultValue * 2;
    }
});

Demikian pula, kelas dapat didaftarkan sebagai filter panggilan biji-bijian menggunakan metode pembantu AddIncomingGrainCallFilter . Berikut adalah contoh filter panggilan biji-bijian yang mencatat hasil setiap metode biji-bijian:

public class LoggingCallFilter : IIncomingGrainCallFilter
{
    private readonly Logger _logger;

    public LoggingCallFilter(Factory<string, Logger> loggerFactory)
    {
        _logger = loggerFactory(nameof(LoggingCallFilter));
    }

    public async Task Invoke(IIncomingGrainCallContext context)
    {
        try
        {
            await context.Invoke();
            var msg = string.Format(
                "{0}.{1}({2}) returned value {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                context.Result);
            _logger.Info(msg);
        }
        catch (Exception exception)
        {
            var msg = string.Format(
                "{0}.{1}({2}) threw an exception: {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                exception);
            _logger.Info(msg);

            // If this exception is not re-thrown, it is considered to be
            // handled by this filter.
            throw;
        }
    }
}

Filter ini kemudian dapat didaftarkan menggunakan AddIncomingGrainCallFilter metode ekstensi:

siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();

Atau, filter dapat didaftarkan tanpa metode ekstensi:

siloHostBuilder.ConfigureServices(
    services => services.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>());

Filter panggilan Grain per butir

Kelas biji-bijian dapat mendaftarkan dirinya sebagai filter panggilan biji-bijian dan memfilter panggilan apa pun yang dilakukan padanya dengan menerapkan IIncomingGrainCallFilter seperti itu:

public class MyFilteredGrain
    : Grain, IMyFilteredGrain, IIncomingGrainCallFilter
{
    public async Task Invoke(IIncomingGrainCallContext context)
    {
        await context.Invoke();

        // Change the result of the call from 7 to 38.
        if (string.Equals(
            context.InterfaceMethod.Name,
            nameof(this.GetFavoriteNumber)))
        {
            context.Result = 38;
        }
    }

    public Task<int> GetFavoriteNumber() => Task.FromResult(7);
}

Dalam contoh di atas, semua panggilan ke GetFavoriteNumber metode akan kembali 38 alih-alih 7, karena nilai yang dikembalikan telah diubah oleh filter.

Kasus penggunaan lain untuk filter berada dalam kontrol akses, seperti dalam contoh ini:

[AttributeUsage(AttributeTargets.Method)]
public class AdminOnlyAttribute : Attribute { }

public class MyAccessControlledGrain
    : Grain, IMyFilteredGrain, IIncomingGrainCallFilter
{
    public Task Invoke(IIncomingGrainCallContext context)
    {
        // Check access conditions.
        var isAdminMethod =
            context.ImplementationMethod.GetCustomAttribute<AdminOnlyAttribute>();
        if (isAdminMethod && !(bool) RequestContext.Get("isAdmin"))
        {
            throw new AccessDeniedException(
                $"Only admins can access {context.ImplementationMethod.Name}!");
        }

        return context.Invoke();
    }

    [AdminOnly]
    public Task<int> SpecialAdminOnlyOperation() => Task.FromResult(7);
}

Dalam contoh di atas, SpecialAdminOnlyOperation metode hanya dapat dipanggil jika "isAdmin" diatur ke true dalam RequestContext. Dengan cara ini, filter panggilan biji-bijian dapat digunakan untuk otorisasi. Dalam contoh ini, adalah tanggung jawab pemanggil untuk memastikan bahwa "isAdmin" nilai diatur dengan benar dan autentikasi dilakukan dengan benar. Perhatikan bahwa [AdminOnly] atribut ditentukan pada metode kelas grain. Ini karena ImplementationMethod properti mengembalikan MethodInfo implementasi, bukan antarmuka. Filter juga dapat memeriksa InterfaceMethod properti .

Urutan filter panggilan grain

Filter panggilan grain mengikuti pengurutan yang ditentukan:

  1. IIncomingGrainCallFilter implementasi yang dikonfigurasi dalam kontainer injeksi dependensi, dalam urutan terdaftar.
  2. Filter tingkat butir, jika biji-bijian IIncomingGrainCallFiltermengimplementasikan .
  3. Implementasi metode biji-bijian atau implementasi metode ekstensi biji-bijian.

Setiap panggilan untuk IIncomingGrainCallContext.Invoke() merangkum filter yang ditentukan berikutnya sehingga setiap filter memiliki kesempatan untuk menjalankan kode sebelum dan sesudah filter berikutnya dalam rantai dan akhirnya metode biji-bijian itu sendiri.

Filter panggilan keluar

Filter panggilan biji-bijian keluar mirip dengan filter panggilan biji-bijian masuk dengan perbedaan utama adalah bahwa mereka dipanggil pada pemanggil (klien) daripada penerima panggilan (butir).

Filter panggilan grain keluar mengimplementasikan IOutgoingGrainCallFilter antarmuka, yang memiliki satu metode:

public interface IOutgoingGrainCallFilter
{
    Task Invoke(IOutgoingGrainCallContext context);
}

Argumen IOutgoingGrainCallContext yang diteruskan ke Invoke metode memiliki bentuk berikut:

public interface IOutgoingGrainCallContext
{
    /// <summary>
    /// Gets the grain being invoked.
    /// </summary>
    IAddressable Grain { get; }

    /// <summary>
    /// Gets the <see cref="MethodInfo"/> for the interface method being invoked.
    /// </summary>
    MethodInfo InterfaceMethod { get; }

    /// <summary>
    /// Gets the arguments for this method invocation.
    /// </summary>
    object[] Arguments { get; }

    /// <summary>
    /// Invokes the request.
    /// </summary>
    Task Invoke();

    /// <summary>
    /// Gets or sets the result.
    /// </summary>
    object Result { get; set; }
}

Metode IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext) harus menunggu atau mengembalikan hasil IOutgoingGrainCallContext.Invoke() untuk menjalankan filter yang dikonfigurasi berikutnya dan akhirnya metode biji-bijian itu sendiri. Properti Result dapat dimodifikasi setelah menunggu Invoke() metode . Metode MethodInfo antarmuka yang dipanggil dapat diakses menggunakan InterfaceMethod properti . Filter panggilan biji-bijian keluar dipanggil untuk semua panggilan metode ke biji-bijian dan ini termasuk panggilan ke metode sistem yang dilakukan oleh Orleans.

Mengonfigurasi filter panggilan grain keluar

IOutgoingGrainCallFilter Implementasi dapat didaftarkan pada silo dan klien menggunakan Injeksi Dependensi.

Delegasi dapat didaftarkan sebagai filter panggilan seperti:

builder.AddOutgoingGrainCallFilter(async context =>
{
    // If the method being called is 'MyInterceptedMethod', then set a value
    // on the RequestContext which can then be read by other filters or the grain.
    if (string.Equals(
        context.InterfaceMethod.Name,
        nameof(IMyGrain.MyInterceptedMethod)))
    {
        RequestContext.Set(
            "intercepted value", "this value was added by the filter");
    }

    await context.Invoke();

    // If the grain method returned an int, set the result to double that value.
    if (context.Result is int resultValue)
    {
        context.Result = resultValue * 2;
    }
});

Dalam kode di atas, builder mungkin merupakan instans ISiloHostBuilder atau IClientBuilder.

Demikian pula, kelas dapat didaftarkan sebagai filter panggilan grain keluar. Berikut adalah contoh filter panggilan biji-bijian yang mencatat hasil setiap metode biji-bijian:

public class LoggingCallFilter : IOutgoingGrainCallFilter
{
    private readonly Logger _logger;

    public LoggingCallFilter(Factory<string, Logger> loggerFactory)
    {
        _logger = loggerFactory(nameof(LoggingCallFilter));
    }

    public async Task Invoke(IOutgoingGrainCallContext context)
    {
        try
        {
            await context.Invoke();
            var msg = string.Format(
                "{0}.{1}({2}) returned value {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                context.Result);
            _logger.Info(msg);
        }
        catch (Exception exception)
        {
            var msg = string.Format(
                "{0}.{1}({2}) threw an exception: {3}",
                context.Grain.GetType(),
                context.InterfaceMethod.Name,
                string.Join(", ", context.Arguments),
                exception);
            this.log.Info(msg);

            // If this exception is not re-thrown, it is considered to be
            // handled by this filter.
            throw;
        }
    }
}

Filter ini kemudian dapat didaftarkan menggunakan AddOutgoingGrainCallFilter metode ekstensi:

builder.AddOutgoingGrainCallFilter<LoggingCallFilter>();

Atau, filter dapat didaftarkan tanpa metode ekstensi:

builder.ConfigureServices(
    services => services.AddSingleton<IOutgoingGrainCallFilter, LoggingCallFilter>());

Seperti contoh filter panggilan delegasi, builder mungkin merupakan instans dari atau ISiloHostBuilder IClientBuilder.

Kasus penggunaan

Konversi pengecualian

Ketika pengecualian yang telah dilemparkan dari server semakin deserialisasi pada klien, Anda terkadang mendapatkan pengecualian berikut alih-alih yang sebenarnya: TypeLoadException: Could not find Whatever.dll.

Ini terjadi jika rakitan yang berisi pengecualian tidak tersedia untuk klien. Misalnya, Anda menggunakan Kerangka Kerja Entitas dalam implementasi biji-bijian Anda; EntityException maka dapat dilemparkan. Klien di sisi lain tidak (dan tidak boleh) mereferensikan EntityFramework.dll karena tidak tahu lapisan akses data yang mendasar.

Ketika klien mencoba mendeserialisasi EntityException, itu akan gagal karena DLL yang hilang; sebagai konsekuensinya, dilemparkan TypeLoadException menyembunyikan yang asli EntityException.

Seseorang mungkin berpendapat bahwa ini cukup oke karena klien tidak akan pernah menangani EntityException; jika tidak, itu harus mereferensikan EntityFramework.dll.

Tetapi bagaimana jika klien ingin setidaknya mencatat pengecualian? Masalahnya adalah bahwa pesan kesalahan asli hilang. Salah satu cara untuk mengatasi masalah ini adalah dengan mencegat pengecualian sisi server dan menggantinya dengan pengecualian jenis Exception biasa jika jenis pengecualian mungkin tidak diketahui di sisi klien.

Namun, ada satu hal penting yang harus kita ingat: kita hanya ingin mengganti pengecualian jika pemanggil adalah klien biji-bijian. Kami tidak ingin mengganti pengecualian jika penelepon adalah biji-bijian lain (atau Orleans infrastruktur yang melakukan panggilan biji-bijian juga; misalnya pada GrainBasedReminderTable biji-bijian).

Di sisi server ini dapat dilakukan dengan pencegat tingkat silo:

public class ExceptionConversionFilter : IIncomingGrainCallFilter
{
    private static readonly HashSet<string> KnownExceptionTypeAssemblyNames =
        new HashSet<string>
        {
            typeof(string).Assembly.GetName().Name,
            "System",
            "System.ComponentModel.Composition",
            "System.ComponentModel.DataAnnotations",
            "System.Configuration",
            "System.Core",
            "System.Data",
            "System.Data.DataSetExtensions",
            "System.Net.Http",
            "System.Numerics",
            "System.Runtime.Serialization",
            "System.Security",
            "System.Xml",
            "System.Xml.Linq",
            "MyCompany.Microservices.DataTransfer",
            "MyCompany.Microservices.Interfaces",
            "MyCompany.Microservices.ServiceLayer"
        };

    public async Task Invoke(IIncomingGrainCallContext context)
    {
        var isConversionEnabled =
            RequestContext.Get("IsExceptionConversionEnabled") as bool? == true;

        if (!isConversionEnabled)
        {
            // If exception conversion is not enabled, execute the call without interference.
            await context.Invoke();
            return;
        }

        RequestContext.Remove("IsExceptionConversionEnabled");
        try
        {
            await context.Invoke();
        }
        catch (Exception exc)
        {
            var type = exc.GetType();

            if (KnownExceptionTypeAssemblyNames.Contains(
                type.Assembly.GetName().Name))
            {
                throw;
            }

            // Throw a base exception containing some exception details.
            throw new Exception(
                string.Format(
                    "Exception of non-public type '{0}' has been wrapped."
                    + " Original message: <<<<----{1}{2}{3}---->>>>",
                    type.FullName,
                    Environment.NewLine,
                    exc,
                    Environment.NewLine));
        }
    }
}

Filter ini kemudian dapat didaftarkan pada silo:

siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();

Aktifkan filter untuk panggilan yang dilakukan oleh klien dengan menambahkan filter panggilan keluar:

clientBuilder.AddOutgoingGrainCallFilter(context =>
{
    RequestContext.Set("IsExceptionConversionEnabled", true);
    return context.Invoke();
});

Dengan cara ini klien memberi tahu server bahwa ia ingin menggunakan konversi pengecualian.

Memanggil biji-bijian dari pencegat

Dimungkinkan untuk melakukan panggilan biji-bijian dari pencegat dengan menyuntikkan IGrainFactory ke kelas pencegat:

private readonly IGrainFactory _grainFactory;

public CustomCallFilter(IGrainFactory grainFactory)
{
    _grainFactory = grainFactory;
}

public async Task Invoke(IIncomingGrainCallContext context)
{
    // Hook calls to any grain other than ICustomFilterGrain implementations.
    // This avoids potential infinite recursion when calling OnReceivedCall() below.
    if (!(context.Grain is ICustomFilterGrain))
    {
        var filterGrain = _grainFactory.GetGrain<ICustomFilterGrain>(
            context.Grain.GetPrimaryKeyLong());

        // Perform some grain call here.
        await filterGrain.OnReceivedCall();
    }

    // Continue invoking the call on the target grain.
    await context.Invoke();
}