SignalR has become one of the most popular libraries for real-time web communication in .NET. Whether you're building a chat application, a real-time dashboard, or a live notification system, SignalR enables efficient and scalable communication between clients and servers. However, as your SignalR application grows, you may want to introduce cross-cutting concerns like logging, authorization, or validation across your SignalR Hub methods.

In this article, we'll explore the power of HubFilter—an essential tool in SignalR that allows you to inject custom logic into your Hub method invocations. We'll walk you through how to set up and use HubFilter, compare it to middleware, and explore real-world use cases for better application architecture.


What Is HubFilter in SignalR?

HubFilter is a feature in SignalR that lets you apply custom logic directly to SignalR Hub method invocations. It’s like middleware, but specifically designed for SignalR, and it operates before or after a SignalR Hub method is called.

Using HubFilter, you can easily inject behavior such as:

  • Authorization: Check if a user has the right permissions to call a method.
  • Logging: Log method calls, input parameters, and results without modifying each Hub method.
  • Validation: Validate inputs or conditions before proceeding with method execution.
  • Error Handling: Catch exceptions globally in SignalR hubs and handle them accordingly.

This custom filter logic lets you centralize and reuse code in a non-intrusive way, as it doesn’t require changing the individual methods of your Hub.


Why Use HubFilter?

  1. Centralized Logic: Instead of adding authorization checks, logging, or validation to every individual Hub method, you can centralize the logic in one place (the HubFilter).
  2. Clean and Reusable Code: Hub filters help you avoid redundant code across multiple Hub methods. You can implement cross-cutting concerns like logging and error handling in a structured way.
  3. Granular Control: HubFilter allows you to control method execution at a granular level. You can decide whether a method should be executed or not, throw exceptions, or even modify the execution flow.
  4. Maintainability: With HubFilter, you can manage your application's logic in a scalable and maintainable way, especially as your application grows.

Key Features of HubFilter

  • Pre- and Post-Execution Logic: You can add code to execute before a Hub method runs or after it completes.
  • Method-Level Logic: Unlike middleware, which operates at a global HTTP request level, HubFilter applies only to SignalR methods.
  • Customizability: You can easily customize it to meet your application's specific needs, such as logging or authentication.

How Does HubFilter Work?

In SignalR, a Hub is a class that allows clients to call methods on the server. Each method in the Hub can be intercepted with a HubFilter, which allows you to insert custom logic before and after the method call.

Here’s how it works:

  1. Before Method Invocation: A HubFilter can perform actions, such as logging, validating the request, or checking the user’s permissions, before the SignalR method is invoked.
  2. After Method Invocation: Once the SignalR method has been executed, the HubFilter can inspect or manipulate the response, log the outcome, or handle errors.

Example: Using HubFilter for Logging and Authorization

Let’s create a custom HubFilter to handle logging and authorization checks for SignalR Hub methods.

Step 1: Create a Custom HubFilter

public class LoggingAndAuthorizationHubFilter : IHubFilter
{
    private readonly ILogger<LoggingAndAuthorizationHubFilter> _logger;

    public LoggingAndAuthorizationHubFilter(ILogger<LoggingAndAuthorizationHubFilter> logger)
    {
        _logger = logger;
    }

    public async ValueTask InvokeAsync(HubInvocationContext invocationContext, HubDelegate next)
    {
        // Log before method execution
        _logger.LogInformation("Method {MethodName} called with parameters {Parameters} at {Time}",
            invocationContext.MethodName, string.Join(", ", invocationContext.Arguments), DateTime.UtcNow);

        // Check if user is authenticated (authorization check)
        if (invocationContext.Hub.Context.User.Identity.IsAuthenticated == false)
        {
            _logger.LogWarning("Unauthorized access attempt to method: {MethodName}", invocationContext.MethodName);
            throw new UnauthorizedAccessException("You must be authenticated to call this method.");
        }

        // Call the next filter or the actual Hub method
        await next(invocationContext);

        // Log after method execution
        _logger.LogInformation("Method {MethodName} completed successfully at {Time}", 
            invocationContext.MethodName, DateTime.UtcNow);
    }
}

Step 2: Register the HubFilter in Program.cs

Now, register the custom HubFilter in the SignalR pipeline.

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Register SignalR services
        builder.Services.AddSignalR();

        // Register the custom HubFilter
        builder.Services.AddSingleton<IHubFilter, LoggingAndAuthorizationHubFilter>();

        var app = builder.Build();

        // Configure routing for SignalR hubs
        app.UseRouting();
        app.MapHub<ChatHub>("/chat");

        app.Run();
    }
}

Step 3: Implement SignalR Hub Methods

Here's an example SignalR Hub where the custom filter is applied:

public class ChatHub : Hub
{
    private readonly ILogger<ChatHub> _logger;

    public ChatHub(ILogger<ChatHub> logger)
    {
        _logger = logger;
    }

    public async Task SendMessage(string message)
    {
        _logger.LogInformation("Sending message: {Message}", message);

        // Broadcast the message to all clients
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

In this case, the LoggingAndAuthorizationHubFilter will log the method invocation, check if the user is authenticated, and then proceed with the execution of the SendMessage method.


Practical Use Cases for HubFilter

  1. Authorization:

    • Check if a user has the necessary permissions to invoke a specific SignalR method.
    • Example: Prevent non-admin users from sending system-wide messages in a chat app.
  2. Logging:

    • Automatically log method calls, including input parameters, response status, and execution time.
    • Example: Log messages sent by users in a chat app to track usage patterns or monitor activity.
  3. Validation:

    • Validate incoming data before it’s processed by the Hub method.
    • Example: Ensure that a chat message meets certain criteria (e.g., no profanity, valid length).
  4. Error Handling:

    • Handle exceptions globally for all SignalR Hub methods.
    • Example: Catch unhandled exceptions and send an appropriate message to the client.

HubFilter vs Middleware: What’s the Difference?

While both HubFilter and middleware are used to intercept requests and add logic, there are key differences:

  • Middleware operates at the HTTP request level and can be applied globally to all requests (including SignalR).
  • HubFilter is specific to SignalR Hubs and allows you to apply logic directly to Hub method calls, offering granular control over method invocation.

In short, use middleware for application-wide logic (like authentication, logging, etc.) and use HubFilter for SignalR-specific concerns.


Conclusion

The HubFilter is a powerful feature in SignalR that allows you to add custom behavior to your Hub methods in a clean and reusable way. Whether you’re looking to handle authorization, logging, validation, or error handling, HubFilter offers a flexible solution without cluttering the logic inside your Hub methods.

By implementing HubFilter, you can ensure that cross-cutting concerns are applied consistently across your SignalR Hubs, leading to a more maintainable and scalable real-time application.

Happy coding, and start leveraging the power of HubFilter in your next SignalR project!