Share via


Uso de filtros de concentrador en ASP.NET Core SignalR

Filtros de concentrador:

  • Están disponibles en ASP.NET Core 5.0 o versiones posteriores.
  • Permitir que la lógica se ejecute antes y después de que los clientes invoquen los métodos de concentrador.

En este artículo se proporcionan instrucciones para escribir y usar filtros de concentrador.

Configuración de filtros de concentrador

Los filtros de concentrador se pueden aplicar globalmente o por tipo de concentrador. El orden en el que se agregan filtros es el orden en el que se ejecutan los filtros. Los filtros de concentrador globales se ejecutan antes que los filtros de concentrador locales.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR(options =>
    {
        // Global filters will run first
        options.AddFilter<CustomFilter>();
    }).AddHubOptions<ChatHub>(options =>
    {
        // Local filters will run second
        options.AddFilter<CustomFilter2>();
    });
}

Se puede agregar un filtro de concentrador de una de las maneras siguientes:

  • Agregue un filtro por tipo concreto:

    hubOptions.AddFilter<TFilter>();
    

    Esto se resolverá a partir de la inserción de dependencias (DI) o el tipo activado.

  • Agregue un filtro por tipo de tiempo de ejecución:

    hubOptions.AddFilter(typeof(TFilter));
    

    Esto se resolverá desde la inserción de dependencias o el tipo activado.

  • Agregue un filtro por instancia:

    hubOptions.AddFilter(new MyFilter());
    

    Esta instancia se usará como singleton. Todas las invocaciones del método de concentrador usarán la misma instancia.

Los filtros de concentrador se crean y eliminan por invocación de concentrador. Si desea almacenar el estado global en el filtro o ningún estado, agregue el tipo de filtro de concentrador a DI como singleton para mejorar el rendimiento. Como alternativa, agregue el filtro como una instancia si puede.

Creación de filtros de concentrador

Cree un filtro declarando una clase que herede de IHubFilter y agregue el método InvokeMethodAsync. También hay OnConnectedAsync y OnDisconnectedAsync que se pueden implementar opcionalmente para encapsular los métodos de concentrador OnConnectedAsync y OnDisconnectedAsync respectivamente.

public class CustomFilter : IHubFilter
{
    public async ValueTask<object> InvokeMethodAsync(
        HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
    {
        Console.WriteLine($"Calling hub method '{invocationContext.HubMethodName}'");
        try
        {
            return await next(invocationContext);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception calling '{invocationContext.HubMethodName}': {ex}");
            throw;
        }
    }

    // Optional method
    public Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
    {
        return next(context);
    }

    // Optional method
    public Task OnDisconnectedAsync(
        HubLifetimeContext context, Exception exception, Func<HubLifetimeContext, Exception, Task> next)
    {
        return next(context, exception);
    }
}

Los filtros son muy similares al middleware. El método next invoca el siguiente filtro. El filtro final invocará el método de concentrador. Los filtros también pueden almacenar el resultado de esperar next y ejecutar la lógica después de llamar al método de concentrador antes de devolver el resultado de next.

Para omitir una invocación de método de concentrador en un filtro, inicie una excepción de tipo HubException en lugar de llamar a next. El cliente recibirá un error si esperaba un resultado.

Uso de filtros de concentrador

Al escribir la lógica de filtro, intente convertirlo en genérico mediante el uso de atributos en métodos de concentrador en lugar de comprobar si hay nombres de método de concentrador.

Considere un filtro que comprobará un argumento de método de concentrador para las frases prohibidas y reemplazará las frases que encuentre por ***. En este ejemplo, supongamos que se define una clase LanguageFilterAttribute. La clase tiene una propiedad denominada FilterArgument que se puede establecer al usar el atributo.

  1. Coloque el atributo en el método de concentrador que tiene un argumento de cadena que se va a limpiar:

    public class ChatHub
    {
        [LanguageFilter(filterArgument = 0)]
        public async Task SendMessage(string message, string username)
        {
            await Clients.All.SendAsync("SendMessage", $"{username} says: {message}");
        }
    }
    
  2. Defina un filtro de concentrador para comprobar el atributo y reemplazar las frases prohibidas en un argumento de método de concentrador por ***:

    public class LanguageFilter : IHubFilter
    {
        // populated from a file or inline
        private List<string> bannedPhrases = new List<string> { "async void", ".Result" };
    
        public async ValueTask<object> InvokeMethodAsync(HubInvocationContext invocationContext, 
            Func<HubInvocationContext, ValueTask<object>> next)
        {
            var languageFilter = (LanguageFilterAttribute)Attribute.GetCustomAttribute(
                invocationContext.HubMethod, typeof(LanguageFilterAttribute));
            if (languageFilter != null &&
                invocationContext.HubMethodArguments.Count > languageFilter.FilterArgument &&
                invocationContext.HubMethodArguments[languageFilter.FilterArgument] is string str)
            {
                foreach (var bannedPhrase in bannedPhrases)
                {
                    str = str.Replace(bannedPhrase, "***");
                }
    
                var arguments = invocationContext.HubMethodArguments.ToArray();
                arguments[languageFilter.FilterArgument] = str;
                invocationContext = new HubInvocationContext(invocationContext.Context,
                    invocationContext.ServiceProvider,
                    invocationContext.Hub,
                    invocationContext.HubMethod,
                    arguments);
            }
    
            return await next(invocationContext);
        }
    }
    
  3. Registre el filtro de concentrador en el método Startup.ConfigureServices. Para evitar reinicializar la lista de frases prohibidas para cada invocación, el filtro de concentrador se registra como singleton:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR(hubOptions =>
        {
            hubOptions.AddFilter<LanguageFilter>();
        });
    
        services.AddSingleton<LanguageFilter>();
    }
    

El objeto HubInvocationContext

HubInvocationContext contiene información para la invocación del método de concentrador actual.

Propiedad Descripción Tipo
Context HubCallerContext contiene información sobre la conexión. HubCallerContext
Hub Instancia del concentrador que se usa para esta invocación de método de concentrador. Hub
HubMethodName El nombre del método de concentrador que se va a invocar. string
HubMethodArguments Lista de argumentos que se pasan al método de concentrador. IReadOnlyList<string>
ServiceProvider Proveedor de servicios con ámbito para esta invocación de método de concentrador. IServiceProvider
HubMethod Información del método de concentrador. MethodInfo

El objeto HubLifetimeContext

HubLifetimeContext contiene información para los métodos de concentrador OnConnectedAsync y OnDisconnectedAsync.

Propiedad Descripción Tipo
Context HubCallerContext contiene información sobre la conexión. HubCallerContext
Hub Instancia del concentrador que se usa para esta invocación de método de concentrador. Hub
ServiceProvider Proveedor de servicios con ámbito para esta invocación de método de concentrador. IServiceProvider

Autorización y filtros

Los atributos de autorización en los métodos de concentrador se ejecutan antes de los filtros de concentrador.