Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los filtros de llamadas de grano proporcionan una manera de interceptar las llamadas de grano. Los filtros pueden ejecutar código tanto antes como después de una llamada al grano. Puede instalar varios filtros simultáneamente. Los filtros son asincrónicos y pueden modificar RequestContext, argumentos y el valor devuelto del método que se invoca. Los filtros también pueden inspeccionar el MethodInfo del método que se invoca en la clase de Grain y se pueden usar para lanzar o manejar excepciones.
Algunos ejemplos de uso de filtros de granulación de llamadas son:
-
Autorización: un filtro puede inspeccionar el método que se invoca y los argumentos, o la información de autorización en
RequestContext, para determinar si se debe permitir que la llamada continúe. - Registro o telemetría: un filtro puede registrar información y capturar datos de tiempo y otras estadísticas sobre la invocación del método.
- Control de errores: un filtro puede interceptar las excepciones producidas por una invocación de método y transformarlas en otras excepciones o controlar las excepciones a medida que pasan por el filtro.
Los filtros se incluyen en dos tipos:
- Filtros de llamadas entrantes
- Filtros de llamadas salientes
Los filtros de llamadas entrantes se ejecutan al recibir una llamada. Los filtros de llamadas salientes se ejecutan al realizar una llamada.
Filtros de llamadas entrantes
Los filtros de llamada de grano entrante implementan la IIncomingGrainCallFilter interfaz , que tiene un método:
public interface IIncomingGrainCallFilter
{
Task Invoke(IIncomingGrainCallContext context);
}
El IIncomingGrainCallContext argumento pasado al Invoke método tiene la siguiente 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; }
}
El método IIncomingGrainCallFilter.Invoke(IIncomingGrainCallContext) debe await o devolver el resultado de IIncomingGrainCallContext.Invoke() para ejecutar el siguiente filtro configurado y finalmente el método de grano en sí. Puede modificar la propiedad Result después de esperar al método Invoke(). La ImplementationMethod propiedad devuelve el MethodInfo de la clase de implementación. Puede acceder al MethodInfo del método de la interfaz mediante la propiedad InterfaceMethod. Se llaman los filtros de llamadas de Grain para cada llamada de método a un Grain, incluidas las llamadas a extensiones de Grain (implementaciones de IGrainExtension) instaladas en el Grain. Por ejemplo, Orleans usa extensiones de grano para implementar flujos y tokens de cancelación. Por lo tanto, espere que el valor de ImplementationMethod no siempre sea un método en la clase de grain en sí misma.
Configurar filtros de llamadas de grano recibidas
Puede registrar implementaciones de IIncomingGrainCallFilter ya sea como filtros para todo el silo mediante la Inyección de Dependencias o como filtros a nivel de grano haciendo que un grano implemente IIncomingGrainCallFilter directamente.
Filtros de granos para todo el silo
Puede registrar un delegado como filtro de llamadas de nivel de silo mediante la inserción de dependencias como este:
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;
}
});
Del mismo modo, puede registrar una clase como filtro de llamada de grano mediante el AddIncomingGrainCallFilter método auxiliar. Este es un ejemplo de un filtro de llamada de grano que registra los resultados de cada método de grano:
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;
}
}
}
A continuación, este filtro se puede registrar mediante el método de extensión AddIncomingGrainCallFilter.
siloHostBuilder.AddIncomingGrainCallFilter<LoggingCallFilter>();
Como alternativa, el filtro se puede registrar sin el método de extensión:
siloHostBuilder.ConfigureServices(
services => services.AddSingleton<IIncomingGrainCallFilter, LoggingCallFilter>());
Filtros de llamadas por grano
Una clase de grano puede registrarse como filtro de llamadas de grano y filtrar las llamadas realizadas a ella mediante la implementación IIncomingGrainCallFilter de esta manera:
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);
}
En el ejemplo anterior, todas las llamadas al GetFavoriteNumber método devuelven 38 en lugar de 7 porque el filtro modificó el valor devuelto.
Otro caso de uso para los filtros es el control de acceso, como se muestra en este ejemplo:
[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);
}
En el ejemplo anterior, solo se puede llamar al método SpecialAdminOnlyOperation si "isAdmin" se establece en true en el RequestContext. De este modo, puede usar filtros de llamadas a nivel granular para la autorización. En este ejemplo, es responsabilidad del autor de la llamada asegurarse de que el "isAdmin" valor se establece correctamente y que la autenticación se realiza correctamente. Tenga en cuenta que el [AdminOnly] atributo se especifica en el método de clase de grano. Esto se debe a que la ImplementationMethod propiedad devuelve el MethodInfo de la implementación, no la interfaz . El filtro también puede comprobar la InterfaceMethod propiedad.
Ordenación de filtros por granularidad en llamadas
Los filtros de llamadas 'grain' siguen un orden definido:
-
IIncomingGrainCallFilterimplementaciones configuradas en el contenedor de inserción de dependencias, en el orden en que se registran. - Filtro de nivel de grano, si el grano implementa
IIncomingGrainCallFilter. - Implementación del método de grano o implementación del método de extensión de grano.
Cada llamada a IIncomingGrainCallContext.Invoke() encapsula el siguiente filtro definido, lo que permite que cada filtro pueda ejecutar código antes y después del siguiente filtro de la cadena y, finalmente, el propio método de grano.
Filtros de llamadas salientes
Los filtros de llamadas de grano salientes son similares a los filtros de llamadas de grano entrantes. La principal diferencia es que se invocan en el autor de la llamada (cliente) en lugar del destinatario (grano).
Los filtros de llamada de grano saliente implementan la IOutgoingGrainCallFilter interfaz , que tiene un método:
public interface IOutgoingGrainCallFilter
{
Task Invoke(IOutgoingGrainCallContext context);
}
El IOutgoingGrainCallContext argumento pasado al Invoke método tiene la siguiente 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; }
}
El método IOutgoingGrainCallFilter.Invoke(IOutgoingGrainCallContext) debe await o devolver el resultado de IOutgoingGrainCallContext.Invoke() para ejecutar el siguiente filtro configurado y finalmente el método de grano en sí. Puede modificar la propiedad Result después de esperar al método Invoke(). Puede acceder al MethodInfo del método de interfaz al que se llama mediante la propiedad InterfaceMethod. Los filtros de llamadas salientes de grano se invocan para todas las llamadas de métodos a un grano, incluidas las llamadas a los métodos del sistema realizadas por Orleans.
Configurar filtros de llamadas de salida de grano
Puede registrar implementaciones de IOutgoingGrainCallFilter en silos y clientes mediante inyección de dependencias.
Registre un delegado como filtro de llamada de la siguiente manera:
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;
}
});
En el código anterior, builder puede ser una instancia de ISiloHostBuilder o IClientBuilder.
Del mismo modo, puede registrar una clase como filtro de llamada de grano saliente. Este es un ejemplo de un filtro de llamada de grano que registra los resultados de cada método de grano:
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;
}
}
}
A continuación, este filtro se puede registrar mediante el método de extensión AddOutgoingGrainCallFilter.
builder.AddOutgoingGrainCallFilter<LoggingCallFilter>();
Como alternativa, el filtro se puede registrar sin el método de extensión:
builder.ConfigureServices(
services => services.AddSingleton<IOutgoingGrainCallFilter, LoggingCallFilter>());
Al igual que con el ejemplo de filtro de llamada de delegado, builder puede ser una instancia de ISiloHostBuilder o IClientBuilder.
Casos de uso
Conversión de excepciones
Cuando se deserializa una excepción del servidor en el cliente, podría obtener la siguiente excepción en lugar de la original: TypeLoadException: Could not find Whatever.dll.
Esto sucede si el ensamblado que contiene la excepción no está disponible para el cliente. Por ejemplo, supongamos que usa Entity Framework en las implementaciones específicas; EntityException es posible que se produzca una excepción . Por otro lado, el cliente no hace referencia EntityFramework.dll a (y no debería) porque no conoce la capa de acceso a datos subyacente.
Cuando el cliente intenta deserializar , EntityExceptionse produce un error debido a que falta el archivo DLL. Como consecuencia, se lanza un TypeLoadException, ocultando el original EntityException.
Se podría argumentar que esto es aceptable, ya que el cliente nunca manejaría EntityException; de lo contrario, tendría que referirse a EntityFramework.dll.
Pero, ¿qué ocurre si el cliente quiere al menos registrar la excepción? El problema es que se pierde el mensaje de error original. Una manera de solucionar este problema es interceptar excepciones del lado servidor y reemplazarlas por excepciones sin formato de tipo Exception si el tipo de excepción es supuestamente desconocido en el lado cliente.
Sin embargo, tenga en cuenta una cosa importante: solo desea reemplazar una excepción si el llamador es el cliente de Grain. No desea reemplazar una excepción si el llamante es otro grain (o la infraestructura Orleans que realiza llamadas a grains, por ejemplo, en el grain GrainBasedReminderTable).
En el servidor, puede hacerlo con un interceptor de nivel de 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));
}
}
}
A continuación, este filtro se puede registrar en el silo:
siloHostBuilder.AddIncomingGrainCallFilter<ExceptionConversionFilter>();
Habilite el filtro para las llamadas realizadas por el cliente agregando un filtro de llamada saliente:
clientBuilder.AddOutgoingGrainCallFilter(context =>
{
RequestContext.Set("IsExceptionConversionEnabled", true);
return context.Invoke();
});
De este modo, el cliente indica al servidor que quiere usar la conversión de excepciones.
Llamar a granos de interceptores
Para realizar llamadas específicas desde un interceptor, inserte IGrainFactory en la clase 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();
}