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:
IIncomingGrainCallFilter
implementasi yang dikonfigurasi dalam kontainer injeksi dependensi, dalam urutan terdaftar.- Filter tingkat butir, jika biji-bijian
IIncomingGrainCallFilter
mengimplementasikan . - 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();
}