Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Os filtros de chamada de grão fornecem uma maneira de interceptar chamadas de grãos. Os filtros podem executar código tanto antes quanto depois de uma chamada de grain. Você pode instalar vários filtros simultaneamente. Os filtros são assíncronos e podem modificar RequestContext, argumentos e o valor de retorno do método que está sendo invocado. Os filtros também podem inspecionar o MethodInfo do método sendo invocado na grain class e podem ser usados para lançar ou manipular exceções.
Alguns exemplos de usos de filtros de chamada de grão são:
-
Autorização: um filtro pode inspecionar o método que está sendo invocado e os argumentos ou as informações de autorização no
RequestContext. para determinar se deseja permitir que a chamada prossiga. - Registro em log/telemetria: um filtro pode registrar informações e capturar dados de tempo e outras estatísticas sobre invocação de método.
- Tratamento de erros: um filtro pode interceptar exceções geradas por uma invocação de método e transformá-las em outras exceções ou lidar com as exceções à medida que passam pelo filtro.
Os filtros vêm em dois tipos:
- Filtros de chamada de entrada
- Filtros de chamada de saída
Os filtros de chamada de entrada são executados ao receber uma chamada. Os filtros de chamada de saída são executados ao fazer uma chamada.
Filtros de chamada de entrada
Os filtros de chamada de grão de entrada implementam a IIncomingGrainCallFilter interface, que tem um método:
public interface IIncomingGrainCallFilter
{
Task Invoke(IIncomingGrainCallContext context);
}
O IIncomingGrainCallContext argumento passado para o Invoke método tem a seguinte forma:
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; }
}
O IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext) método deve await ou retornar o resultado de IIncomingGrainCallContext.Invoke() para executar o próximo filtro configurado e possivelmente o próprio método de grão. Você pode modificar a Result propriedade depois de aguardar o Invoke() método. A propriedade ImplementationMethod retorna a MethodInfo da classe de implementação. Você pode acessar o MethodInfo método de interface usando a InterfaceMethod propriedade. Filtros de chamada de grão são chamados para todas as chamadas de método para um grão, incluindo chamadas para extensões de grãos (implementações de IGrainExtension) instaladas no grão. Por exemplo, Orleans usa extensões de granulação para implementar Fluxos e Tokens de Cancelamento. Portanto, espere que o valor de ImplementationMethod nem sempre seja um método na própria classe de grãos.
Configurar filtros de chamada de grão de entrada
Você pode registrar implementações de IIncomingGrainCallFilter como filtros globais de silo por meio de Injeção de Dependência ou como filtros no nível do grão, implementando um grão IIncomingGrainCallFilter diretamente.
Filtros de chamada de grãos em todo o silo
Você pode registrar um delegado como um filtro de chamada de grãos em todo o silo usando a Injeção de Dependência assim:
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;
}
});
Da mesma forma, você pode registrar uma classe como um filtro de chamadas de grain usando o método auxiliar AddIncomingGrainCallFilter. Aqui está um exemplo de um filtro de chamada de grãos que registra os resultados de cada método de grãos:
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;
}
}
}
Esse filtro pode ser registrado usando o AddIncomingGrainCallFilter método de extensão:
siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();
Como alternativa, o filtro pode ser registrado sem o método de extensão:
siloHostBuilder.ConfigureServices(
services => services.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>());
Filtros de chamada por unidade de grão
Uma classe de grão pode registrar-se como um filtro de chamada de grão e filtrar quaisquer chamadas feitas a ela, implementando IIncomingGrainCallFilter desta forma:
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);
}
No exemplo anterior, todas as chamadas para o GetFavoriteNumber método retornam 38 em vez de 7 porque o filtro alterou o valor retornado.
Outro caso de uso para filtros é o controle de acesso, conforme mostrado neste exemplo:
[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);
}
No exemplo anterior, o método SpecialAdminOnlyOperation só pode ser chamado se "isAdmin" estiver definido como true no RequestContext. Dessa forma, você pode usar filtros de chamadas por granularidade para autorização. Neste exemplo, é responsabilidade do chamador garantir que o "isAdmin" valor seja definido corretamente e que a autenticação seja executada corretamente. Observe que o [AdminOnly] atributo é especificado no método da classe grain. Isso ocorre porque a ImplementationMethod propriedade retorna a MethodInfo implementação, não a interface. O filtro também pode verificar a InterfaceMethod propriedade.
Ordenação de filtro de chamada granular
Os filtros de chamada de 'Grain' seguem uma ordem definida:
-
IIncomingGrainCallFilterimplementações configuradas no contêiner de injeção de dependência, na ordem em que são registradas. - Filtro de nível de grão, se o grão for implementado
IIncomingGrainCallFilter. - Implementação do método de grão ou implementação do método de extensão de grão.
Cada chamada para IIncomingGrainCallContext.Invoke() encapsula o próximo filtro definido, permitindo que cada filtro tenha a chance de executar o código antes e depois do próximo filtro na cadeia e, eventualmente, o método do próprio grão.
Filtros de chamada de saída
Os filtros de chamada de grãos de saída são semelhantes aos filtros de chamada de grãos de entrada. A principal diferença é que eles são invocados no chamador (cliente) em vez do destinatário (grain).
Os filtros de chamada de grãos de saída implementam a IOutgoingGrainCallFilter interface, que tem um método:
public interface IOutgoingGrainCallFilter
{
Task Invoke(IOutgoingGrainCallContext context);
}
O IOutgoingGrainCallContext argumento passado para o Invoke método tem a seguinte forma:
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; }
}
O IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext) método deve await ou retornar o resultado de IOutgoingGrainCallContext.Invoke() para executar o próximo filtro configurado e possivelmente o próprio método de grão. Você pode modificar a Result propriedade depois de aguardar o Invoke() método. Você pode acessar o MethodInfo método de interface que está sendo chamado usando a InterfaceMethod propriedade. Os filtros de chamada de grãos de saída são invocados para todas as chamadas de método para um grão, incluindo chamadas para métodos de sistema feitos por Orleans.
Configurar filtros de chamada granular de saída
Você pode registrar implementações de IOutgoingGrainCallFilter tanto em silos quanto em clientes usando injeção de dependência.
Registre um delegado como um filtro de chamada como este:
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;
}
});
No código acima, builder pode ser uma instância de ISiloHostBuilder ou IClientBuilder.
Da mesma forma, você pode registrar uma classe como um filtro de chamada de grão de saída. Aqui está um exemplo de um filtro de chamada de grãos que registra os resultados de cada método de grãos:
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;
}
}
}
Esse filtro pode ser registrado usando o AddOutgoingGrainCallFilter método de extensão:
builder.AddOutgoingGrainCallFilter<LoggingCallFilter>();
Como alternativa, o filtro pode ser registrado sem o método de extensão:
builder.ConfigureServices(
services => services.AddSingleton<IOutgoingGrainCallFilter, LoggingCallFilter>());
Assim como no exemplo do filtro de chamada delegado, builder pode ser uma instância de ISiloHostBuilder ou de IClientBuilder.
Casos de uso
Conversão de exceção
Quando uma exceção gerada do servidor é desserializada no cliente, às vezes pode acontecer de você obter a seguinte exceção em vez da real: TypeLoadException: Could not find Whatever.dll.
Isso ocorrerá se o assembly que contém a exceção não estiver disponível para o cliente. Por exemplo, digamos que você use o Entity Framework em suas implementações de grãos; um EntityException pode ser jogado. O cliente, por outro lado, não faz referência EntityFramework.dll (e não deve) porque não conhece a camada de acesso a dados subjacente.
Quando o cliente tenta desserializar o EntityException, falha devido à DLL ausente. Como consequência, um TypeLoadException é lançado, escondendo o original EntityException.
Pode-se argumentar que isso é aceitável, pois o cliente nunca trataria o EntityException; caso contrário, ele precisaria referenciar EntityFramework.dll.
Mas e se o cliente quiser, pelo menos, registrar a exceção? O problema é que a mensagem de erro original é perdida. Uma maneira de contornar esse problema é interceptar exceções do lado do servidor e substituí-las por exceções simples de tipo Exception se o tipo de exceção for presumivelmente desconhecido no lado do cliente.
No entanto, tenha uma coisa importante em mente: você só deseja substituir uma exceção se o chamador for o cliente de grãos. Você não deseja substituir uma exceção se o chamador for outro grain (ou a infraestrutura Orleans que faz chamadas de grain, por exemplo, no grain GrainBasedReminderTable).
No lado do servidor, você pode fazer isso com um interceptador no nível do 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));
}
}
}
Este filtro pode então ser registrado no silo:
siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();
Habilite o filtro para chamadas feitas pelo cliente adicionando um filtro de chamada de saída:
clientBuilder.AddOutgoingGrainCallFilter(context =>
{
RequestContext.Set("IsExceptionConversionEnabled", true);
return context.Invoke();
});
Dessa forma, o cliente informa ao servidor que deseja usar a conversão de exceção.
Extrair informações dos interceptores
Você pode fazer chamadas de grain de um interceptor injetando IGrainFactory na classe de interceptor.
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();
}