Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Grain-Call-Filter bieten eine Möglichkeit, Grain-Calls abzufangen. Filter können Code vor und nach einem Kornaufruf ausführen. Sie können mehrere Filter gleichzeitig installieren. Filter sind asynchron und können RequestContext, Argumente und den Rückgabewert der aufgerufenen Methode ändern. Filter können auch das MethodInfo der auf die Grain-Klasse angewendeten Methode prüfen und für das Auslösen oder Behandeln von Ausnahmen verwendet werden.
Einige Beispiele für die Verwendung von Kornanruffiltern sind:
-
Autorisierung: Ein Filter kann die aufgerufene Methode und die Argumente oder Autorisierungsinformationen im
RequestContextFeld überprüfen, um zu bestimmen, ob der Aufruf fortgesetzt werden soll. - Protokollierung/Telemetrie: Ein Filter kann Informationen protokollieren und Zeitmessdaten sowie andere Statistiken zu Methodenaufrufen erfassen.
- Fehlerbehandlung: Ein Filter kann Ausnahmen, die von einem Methodenaufruf ausgelöst werden, abfangen und in andere Ausnahmen umwandeln oder die Ausnahmen behandeln, während sie den Filter durchlaufen.
Filter sind in zwei Typen verfügbar:
- Filter für eingehende Anrufe
- Filter für ausgehende Anrufe
Eingehende Anruffilter werden beim Empfangen eines Anrufs ausgeführt. Ausgehende Anruffilter werden beim Tätigen eines Anrufs ausgeführt.
Filter für eingehende Anrufe
Eingehende Kornaufruffilter implementieren die IIncomingGrainCallFilter Schnittstelle, die eine Methode hat:
public interface IIncomingGrainCallFilter
{
Task Invoke(IIncomingGrainCallContext context);
}
Das IIncomingGrainCallContext an die Invoke Methode übergebene Argument weist die folgende Form auf:
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; }
}
Die IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext)-Methode muss await ausführen oder das Ergebnis von IIncomingGrainCallContext.Invoke() zurückgeben, um den nächsten konfigurierten Filter und schließlich die Grain-Methode selbst auszuführen. Sie können die Result Eigenschaft ändern, nachdem Sie auf die Invoke() Methode gewartet haben. Die ImplementationMethod Eigenschaft gibt die MethodInfo Implementierungsklasse zurück. Sie können auf die Schnittstellenmethode MethodInfo mithilfe der Eigenschaft InterfaceMethod zugreifen. Grainaufruffilter werden für alle Methodenaufrufe einer Grain-Instanz aufgerufen, einschließlich Aufrufen von Grain-Erweiterungen (Implementierungen von IGrainExtension), die in der Grain installiert sind. Beispielsweise verwendet Orleans Grain-Erweiterungen, um Streams und Abbruch-Token zu implementieren. Daher sollten Sie erwarten, dass der Wert von ImplementationMethod nicht immer eine Methode in der Grain-Klasse selbst ist.
Konfigurieren eingehender Grain-Call-Filter
Sie können Implementierungen von IIncomingGrainCallFilter entweder als siloweite Filter über Dependency Injection oder als Grain-Filter registrieren, indem ein Grain IIncomingGrainCallFilter direkt implementiert.
Siloweite Kornanruffilter
Sie können einen Delegaten als siloweiten Grain-Call-Filter mit Dependency Injection wie folgt registrieren:
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;
}
});
Ebenso können Sie eine Klasse als Kornaufruffilter mithilfe der AddIncomingGrainCallFilter Hilfsmethode registrieren. Hier ist ein Beispiel für einen Kornaufruffilter, der die Ergebnisse jeder Kornmethode protokolliert:
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;
}
}
}
Dieser Filter kann dann mithilfe der AddIncomingGrainCallFilter Erweiterungsmethode registriert werden:
siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();
Alternativ kann der Filter ohne die Erweiterungsmethode registriert werden:
siloHostBuilder.ConfigureServices(
services => services.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>());
Korn-Anruffilter pro Korn
Eine Kornklasse kann sich selbst als Kornanruffilter registrieren und alle Anrufe, die an sie gerichtet werden, filtern, indem sie wie folgt IIncomingGrainCallFilter implementiert:
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);
}
Im vorherigen Beispiel geben alle Aufrufe der GetFavoriteNumber-Methode 38 anstelle von 7 zurück, weil der Filter den Rückgabewert geändert hat.
Ein weiterer Anwendungsfall für Filter ist die Zugriffssteuerung, wie in diesem Beispiel gezeigt:
[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);
}
Im vorherigen Beispiel kann die SpecialAdminOnlyOperation-Methode nur aufgerufen werden, wenn "isAdmin" in der true auf RequestContext festgelegt ist. Auf diese Weise können Sie Kornaufruffilter für die Autorisierung verwenden. In diesem Beispiel liegt es in der Verantwortung des Aufrufers, sicherzustellen, dass der "isAdmin" Wert korrekt festgelegt ist und dass die Authentifizierung ordnungsgemäß ausgeführt wird. Beachten Sie, dass das [AdminOnly] Attribut für die Kornklassenmethode angegeben ist. Dies liegt daran, dass die ImplementationMethod Eigenschaft die MethodInfo Implementierung und nicht die Schnittstelle zurückgibt. Der Filter könnte auch die InterfaceMethod Eigenschaft überprüfen.
Sortierung des Kornaufruffilters
Kornaufruffilter folgen einer definierten Reihenfolge:
-
IIncomingGrainCallFilterImplementierungen, die im Container zum Einfügen von Abhängigkeiten konfiguriert sind, in der Reihenfolge, in der sie registriert sind. - Grain-Level-Filter, wenn das Korn
IIncomingGrainCallFilterimplementiert wird. - Implementierung der Getreideverfahren oder Implementierung der Getreideerweiterungsverfahren.
Jeder Aufruf von IIncomingGrainCallContext.Invoke() kapselt den nächsten definierten Filter und gibt jedem Filter die Gelegenheit, Code vor und nach dem nächsten Filter in der Kette und letztlich die Grain-Methode selbst auszuführen.
Filter für ausgehende Anrufe
Ausgehende Kornanruffilter ähneln eingehenden Kornanruffiltern. Der Hauptunterschied besteht darin, dass sie auf dem Aufrufer (Client) anstelle des Angerufenen (Korn) aufgerufen werden.
Ausgehende Grain-Aufruffilter implementieren die IOutgoingGrainCallFilter Schnittstelle, die eine Methode hat:
public interface IOutgoingGrainCallFilter
{
Task Invoke(IOutgoingGrainCallContext context);
}
Das IOutgoingGrainCallContext an die Invoke Methode übergebene Argument weist die folgende Form auf:
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; }
}
Die IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext)-Methode muss await ausführen oder das Ergebnis von IOutgoingGrainCallContext.Invoke() zurückgeben, um den nächsten konfigurierten Filter und schließlich die Grain-Methode selbst auszuführen. Sie können die Result Eigenschaft ändern, nachdem Sie auf die Invoke() Methode gewartet haben. Sie können mit der Eigenschaft MethodInfo auf die Schnittstellenmethode InterfaceMethod zugreifen, die gerade aufgerufen wird. Ausgehende Grain-Aufruffilter werden bei allen Methodenaufrufen an ein Grain ausgelöst, einschließlich der von Orleans vorgenommenen Aufrufe von Systemmethoden.
Konfigurieren von Filtern für ausgehende Getreideanrufe
Sie können Implementierungen von IOutgoingGrainCallFilter sowohl auf Silos als auch auf Clients mithilfe der Abhängigkeitsinjektion registrieren.
Registrieren Sie eine Stellvertretung wie folgt als Anruffilter:
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;
}
});
Im obigen Code kann es sich bei builder entweder um eine Instanz von ISiloHostBuilder oder IClientBuilder handeln.
Ebenso können Sie eine Klasse als ausgehenden Kornanruffilter registrieren. Hier ist ein Beispiel für einen Kornaufruffilter, der die Ergebnisse jeder Kornmethode protokolliert:
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;
}
}
}
Dieser Filter kann dann mithilfe der AddOutgoingGrainCallFilter Erweiterungsmethode registriert werden:
builder.AddOutgoingGrainCallFilter<LoggingCallFilter>();
Alternativ kann der Filter ohne die Erweiterungsmethode registriert werden:
builder.ConfigureServices(
services => services.AddSingleton<IOutgoingGrainCallFilter, LoggingCallFilter>());
Wie beim Beispiel des Delegatenaufruf-Filters kann builder eine Instanz von entweder ISiloHostBuilder oder IClientBuilder sein.
Anwendungsfälle
Ausnahmekonvertierung
Wenn eine Ausnahme, die vom Server ausgelöst wurde, auf dem Client deserialisiert wird, erhalten Sie manchmal die folgende Ausnahme anstelle der tatsächlichen Ausnahme: TypeLoadException: Could not find Whatever.dll.
Dies geschieht, wenn die Assembly, die die Ausnahme enthält, für den Client nicht verfügbar ist. Angenommen, Sie verwenden Entity Framework in Ihren Getreideimplementierungen; möglicherweise EntityException wird ein Fehler ausgelöst. Der Client verweist hingegen nicht darauf (und sollte dies auch nicht tun) EntityFramework.dll, da er die zugrunde liegende Datenzugriffsschicht nicht kennt.
Wenn der Client versucht, EntityException zu deserialisieren, schlägt dieser Versuch aufgrund der fehlenden DLL fehl. Als Folge wird eine TypeLoadException geworfen, die das OriginalEntityExceptionverbergen.
Man könnte argumentieren, dass dies akzeptabel ist, da der Kunde niemals das EntityException behandeln würde; andernfalls müsste er auf EntityFramework.dll verweisen.
Aber was geschieht, wenn der Client die Ausnahme zumindest protokollieren möchte? Das Problem besteht darin, dass die ursprüngliche Fehlermeldung verloren geht. Eine Möglichkeit, dieses Problem zu umgehen, besteht darin, serverseitige Ausnahmen abzufangen und durch einfache Ausnahmen vom Typ Exception zu ersetzen, wenn der Ausnahmetyp vermutlich auf clientseitiger Seite unbekannt ist.
Beachten Sie jedoch eine wichtige Sache: Sie möchten nur eine Ausnahme ersetzen, wenn der Aufrufer der Grain-Client ist. Sie möchten keine Ausnahme ersetzen, wenn der Aufrufer ein anderes Grain ist (oder die Infrastruktur, die zum Beispiel Orleans Grain-Aufrufe tätigt, auf dem GrainBasedReminderTable Grain).
Auf der Serverseite können Sie dies mit einem Interceptor auf Silo-Ebene tun.
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));
}
}
}
Dieser Filter kann dann im Silo registriert werden:
siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();
Aktivieren Sie den Filter für Anrufe, die vom Client getätigt werden, indem Sie einen Filter für ausgehende Anrufe hinzufügen:
clientBuilder.AddOutgoingGrainCallFilter(context =>
{
RequestContext.Set("IsExceptionConversionEnabled", true);
return context.Invoke();
});
Auf diese Weise teilt der Client dem Server mit, dass er die Ausnahmekonvertierung verwenden möchte.
Aufrufen von Körnern von Abfangenern
Sie können Grain-Abfragen von einem Interceptor durchführen, indem Sie IGrainFactory in die Interceptorklasse einfügen.
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();
}