Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
I filtri delle chiamate granulari consentono di intercettare le chiamate granulari. I filtri possono eseguire codice sia prima che dopo una chiamata granulare. È possibile installare più filtri contemporaneamente. I filtri sono asincroni e possono modificare RequestContext, argomenti e il valore restituito del metodo richiamato. I filtri possono anche controllare il MethodInfo del metodo richiamato sulla classe grain e possono essere usati per sollevare o gestire le eccezioni.
Di seguito sono riportati alcuni esempi di uso di filtri di chiamata granulari:
- Autorizzazione: un filtro può esaminare il metodo richiamato e gli argomenti o le informazioni di autorizzazione in RequestContext, per determinare se consentire la continuazione della chiamata.
- Registrazione/telemetria: un filtro può registrare informazioni e acquisire dati di intervallo e altre statistiche sulla chiamata al metodo.
- Gestione degli errori: un filtro può intercettare le eccezioni generate da una chiamata al metodo e trasformarle in altre eccezioni o gestire le eccezioni mentre passano attraverso il filtro.
I filtri sono disponibili in due tipi:
- Filtri delle chiamate in ingresso
- Filtri delle chiamate in uscita
I filtri di chiamata in ingresso vengono eseguiti quando si riceve una chiamata. I filtri delle chiamate in uscita vengono eseguiti durante l'esecuzione di una chiamata.
Filtri delle chiamate in ingresso
I filtri delle chiamate granulari in ingresso implementano l'interfaccia IIncomingGrainCallFilter , che ha un metodo:
public interface IIncomingGrainCallFilter
{
Task Invoke(IIncomingGrainCallContext context);
}
L'argomento IIncomingGrainCallContext passato al Invoke metodo ha la forma seguente:
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; }
}
Il IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext) metodo deve await o restituire il risultato di IIncomingGrainCallContext.Invoke() per eseguire il filtro configurato successivo e infine il metodo granulare stesso. È possibile modificare la Result proprietà dopo aver atteso il Invoke() metodo . La ImplementationMethod proprietà restituisce l'oggetto MethodInfo della classe di implementazione. È possibile accedere all'oggetto MethodInfo del metodo di interfaccia usando la InterfaceMethod proprietà . I filtri delle chiamate granulari vengono chiamati per tutte le chiamate di metodo a un livello di granularità, incluse le chiamate alle estensioni granulari (implementazioni di IGrainExtension) installate nella granularità. Ad esempio, Orleans usa estensioni granulari per implementare flussi e token di annullamento. Pertanto, si prevede che il valore di ImplementationMethod non sia sempre un metodo nella classe granulare stessa.
Configurare i filtri delle chiamate granulari in ingresso
È possibile registrare le implementazioni di IIncomingGrainCallFilter come filtri a livello di silo tramite iniezione di dipendenze o come filtri a livello di grano grazie all'implementazione IIncomingGrainCallFilter diretta di un grano.
Filtri delle chiamate a livello di grano per l'intero silo
È possibile registrare un delegato come filtro di chiamata granulare a livello di silo usando l'inserimento delle dipendenze come segue:
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;
}
});
Analogamente, è possibile registrare una classe come filtro di chiamata granulare usando il AddIncomingGrainCallFilter metodo helper. Di seguito è riportato un esempio di filtro di chiamata granulare che registra i risultati di ogni metodo granulare:
public class LoggingCallFilter : IIncomingGrainCallFilter
{
private readonly ILogger<LoggingCallFilter> _logger;
public LoggingCallFilter(ILogger<LoggingCallFilter> logger)
{
_logger = logger;
}
public async Task Invoke(IIncomingGrainCallContext context)
{
try
{
await context.Invoke();
_logger.LogInformation(
"{GrainType}.{MethodName} returned value {Result}",
context.Grain.GetType(),
context.MethodName,
context.Result);
}
catch (Exception exception)
{
_logger.LogError(
exception,
"{GrainType}.{MethodName} threw an exception",
context.Grain.GetType(),
context.MethodName);
// If this exception is not re-thrown, it is considered to be
// handled by this filter.
throw;
}
}
}
Questo filtro può quindi essere registrato usando il AddIncomingGrainCallFilter metodo di estensione:
siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();
In alternativa, il filtro può essere registrato senza il metodo di estensione:
siloHostBuilder.Services
.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>();
Filtri per granularità delle chiamate
Una classe granulare può registrarsi come filtro di chiamata granulare e filtrare tutte le chiamate effettuate implementando IIncomingGrainCallFilter come segue:
public class MyFilteredGrain
: Grain, IMyFilteredGrain, Orleans.IIncomingGrainCallFilter
{
public async Task Invoke(Orleans.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);
}
Nell'esempio precedente tutte le chiamate al GetFavoriteNumber metodo restituiscono 38 anziché 7 perché il filtro ha modificato il valore restituito.
Un altro caso d'uso per i filtri è il controllo di accesso, come illustrato in questo esempio:
[AttributeUsage(AttributeTargets.Method)]
public class AdminOnlyAttribute : Attribute { }
public class MyAccessControlledGrain
: Grain, IMyFilteredGrain, Orleans.IIncomingGrainCallFilter
{
public Task Invoke(Orleans.IIncomingGrainCallContext context)
{
// Check access conditions.
var isAdminMethod =
context.ImplementationMethod.GetCustomAttribute<AdminOnlyAttribute>();
if (isAdminMethod is not null && RequestContext.Get("isAdmin") is not true)
{
throw new AccessDeniedException(
$"Only admins can access {context.ImplementationMethod.Name}!");
}
return context.Invoke();
}
[AdminOnly]
public Task<int> GetFavoriteNumber() => Task.FromResult(7);
}
Nell'esempio precedente il SpecialAdminOnlyOperation metodo può essere chiamato solo se "isAdmin" è impostato su true in RequestContext. In questo modo, è possibile usare filtri di chiamata granulari per l'autorizzazione. In questo esempio è responsabilità del chiamante assicurarsi che il "isAdmin" valore sia impostato correttamente e che l'autenticazione venga eseguita correttamente. Si noti che l'attributo [AdminOnly] viene specificato nel metodo della classe grain. Ciò è dovuto al fatto che la ImplementationMethod proprietà restituisce l'oggetto MethodInfo dell'implementazione, non l'interfaccia . Il filtro può anche controllare la InterfaceMethod proprietà .
Ordinamento del filtro delle chiamate granulari
I filtri di chiamata a livello granulare seguono un ordine definito.
-
IIncomingGrainCallFilterimplementazioni configurate nel contenitore di inserimento delle dipendenze, nell'ordine in cui vengono registrate. - Filtro a livello di grana, se la grana implementa
IIncomingGrainCallFilter. - Implementazione del metodo granulare o implementazione del metodo di estensione granulare.
Ogni chiamata a IIncomingGrainCallContext.Invoke() incapsula il filtro definito successivo, consentendo a ogni filtro di eseguire codice prima e dopo il filtro successivo nella catena e, infine, il metodo granulare stesso.
Filtri delle chiamate in uscita
I filtri delle chiamate granulari in uscita sono simili ai filtri delle chiamate granulari in ingresso. La differenza principale è che vengono richiamati sul chiamante (client) anziché sul chiamato (grano).
I filtri delle chiamate granulari in uscita implementano l'interfaccia IOutgoingGrainCallFilter , che ha un metodo:
public interface IOutgoingGrainCallFilter
{
Task Invoke(IOutgoingGrainCallContext context);
}
L'argomento IOutgoingGrainCallContext passato al Invoke metodo ha la forma seguente:
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; }
}
Il IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext) metodo deve await o restituire il risultato di IOutgoingGrainCallContext.Invoke() per eseguire il filtro configurato successivo e infine il metodo granulare stesso. È possibile modificare la Result proprietà dopo aver atteso il Invoke() metodo . È possibile accedere all'oggetto MethodInfo del metodo di interfaccia chiamato usando la InterfaceMethod proprietà . I filtri di chiamata ai grain in uscita vengono richiamati per tutte le chiamate ai metodi di un grain, incluse le chiamate ai metodi di sistema eseguite da Orleans.
Configurare i filtri delle chiamate granulari in uscita
È possibile registrare le implementazioni di IOutgoingGrainCallFilter sia sui silo che sui client utilizzando l'iniezione delle dipendenze.
Registrare un delegato come filtro di chiamata come segue:
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;
}
});
Nel codice precedente, builder può essere un'istanza di ISiloBuilder o IClientBuilder.
Analogamente, è possibile registrare una classe come filtro di chiamata granulare in uscita. Di seguito è riportato un esempio di filtro di chiamata granulare che registra i risultati di ogni metodo granulare:
public class OutgoingLoggingCallFilter : IOutgoingGrainCallFilter
{
private readonly ILogger<OutgoingLoggingCallFilter> _logger;
public OutgoingLoggingCallFilter(ILogger<OutgoingLoggingCallFilter> logger)
{
_logger = logger;
}
public async Task Invoke(IOutgoingGrainCallContext context)
{
try
{
await context.Invoke();
_logger.LogInformation(
"{GrainType}.{MethodName} returned value {Result}",
context.Grain.GetType(),
context.MethodName,
context.Result);
}
catch (Exception exception)
{
_logger.LogError(
exception,
"{GrainType}.{MethodName} threw an exception",
context.Grain.GetType(),
context.MethodName);
// If this exception is not re-thrown, it is considered to be
// handled by this filter.
throw;
}
}
}
Questo filtro può quindi essere registrato usando il AddOutgoingGrainCallFilter metodo di estensione:
builder.AddOutgoingGrainCallFilter<OutgoingLoggingCallFilter>();
In alternativa, il filtro può essere registrato senza il metodo di estensione:
builder.Services
.AddSingleton<IOutgoingGrainCallFilter, OutgoingLoggingCallFilter>();
Come per l'esempio di filtro di chiamata delegato, builder può essere un'istanza di ISiloBuilder o IClientBuilder.
Casi d'uso
Conversione delle eccezioni
Quando un'eccezione generata dal server viene deserializzata nel client, a volte si potrebbe ottenere l'eccezione seguente anziché quella effettiva: TypeLoadException: Could not find Whatever.dll.
Ciò si verifica se l'assembly contenente l'eccezione non è disponibile per il client. Si supponga, ad esempio, di usare Entity Framework nelle implementazioni granulari; potrebbe essere generata un'eccezione EntityException . Il client, d'altra parte, non fa riferimento EntityFramework.dll (e non deve) perché non conosce il livello di accesso ai dati sottostante.
Quando il client tenta di deserializzare EntityException, non riesce a causa della DLL mancante. Di conseguenza, viene generato un TypeLoadException, nascondendo il EntityException.
Si potrebbe sostenere che ciò sia accettabile dato che il client non gestirebbe mai il EntityException; altrimenti sarebbe necessario fare riferimento al EntityFramework.dll.
Ma cosa succede se il client vuole almeno registrare l'eccezione? Il problema è che il messaggio di errore originale viene perso. Un modo per risolvere questo problema consiste nell'intercettare le eccezioni lato server e sostituirle con eccezioni semplici di tipo Exception se il tipo di eccezione è presumibilmente sconosciuto sul lato client.
Tuttavia, devi tenere presente un aspetto importante: devi sostituire un'eccezione solo se il chiamante è il client grain. Non si dovrebbe sostituire un'eccezione se il chiamante è un altro grain (o l'infrastruttura Orleans che effettua chiamate a grain, ad esempio, sul grain GrainBasedReminderTable).
Sul lato server è possibile eseguire questa operazione con un intercettore a livello di silo:
public class ExceptionConversionFilter : Orleans.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(Orleans.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));
}
}
}
Questo filtro può quindi essere registrato nel silo:
siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();
Abilitare il filtro per le chiamate effettuate dal client aggiungendo un filtro di chiamata in uscita:
clientBuilder.AddOutgoingGrainCallFilter(context =>
{
RequestContext.Set("IsExceptionConversionEnabled", true);
return context.Invoke();
});
In questo modo, il client indica al server che vuole usare la conversione delle eccezioni.
Richiamare dati da intercettori
È possibile effettuare chiamate granulari da un intercettore inserendo IGrainFactory nella classe dell'intercettore:
public class CustomCallFilter : Orleans.IIncomingGrainCallFilter
{
private readonly IGrainFactory _grainFactory;
public CustomCallFilter(IGrainFactory grainFactory)
{
_grainFactory = grainFactory;
}
public async Task Invoke(Orleans.IIncomingGrainCallContext context)
{
// Hook calls to any grain other than ICustomFilterGrain implementations.
// This avoids potential infinite recursion when calling OnReceivedCall() below.
if (context.Grain is not ICustomFilterGrain)
{
var filterGrain = _grainFactory.GetGrain<ICustomFilterGrain>(
((IAddressable)context.Grain).GetPrimaryKeyLong());
// Perform some grain call here.
await filterGrain.OnReceivedCall();
}
// Continue invoking the call on the target grain.
await context.Invoke();
}
}