Monday 28 October 2013

Strongly typed SignalR CommunicationService

Tenho usado o SignalR como "server support" nas minhas aplicações, sejam elas Silverlight, WindowsPhone ou Windows Store, mas, uma coisa que me aborrece é o facto de as chamadas ao servidor serem efectuadas com strings. Desta forma perdemos a capacidade de alterar a interface e em "compile time" sabermos todos os locais onde o método / propriedade é usado.
Ao usarmos strings estamos sujeitos a erros de digitação e com isso infidáveis horas a fazer debug até encontrar que o problema foi que escrevemos mal o nome do método ou do evento.

À umas semanas atrás resolvi dedicar-me a escrever uma interface com Expressions e Func que me permita usar a interface da Hub para executar os server-side methods.

Antes era assim que eu executava um método que não retornava dados do servidor:

public async Task ExecuteNonReturnMethod(string hubName, string methodName, params object[] args)
        {
            (...)
        }

Sempre que o usava tinha que fornecer a hubName, MethodName e os Parametros... tudo em strings. É fácil ver que os erros aconteciam MUITASSSSSS vezes.

A nova assinatura do método é:
public async Task ExecuteNonReturnMethod(string hubName, Expression> methodExpression)
{
            var methodCallExpression = methodExpression.Body as MethodCallExpression;
            if (methodCallExpression == null)
            {
                throw new InvalidOperationException("Need to provide a method to call.");
            }

            var methodInfo = methodCallExpression.Method;
            var methodName = methodInfo.Name;

            var i = 0;
            var body = (MethodCallExpression)methodExpression.Body;
            var parameters = new object[body.Arguments.Count()];
            foreach (var value in
                from MemberExpression expression in body.Arguments
                select ((FieldInfo)expression.Member).GetValue(((ConstantExpression)expression.Expression).Value))
            {
                parameters[i] = value;
                i++;
            }

            var clientProxy = _hubProxyList[hubName];
            await clientProxy.Invoke(methodName, parameters);
}

e é chamado desta forma:
public async Task CheckLocalUser(string externalUserId, string fullName, string userName)
        {
            return
                await
                this.Execute(
                    x => x.ValidateLocalUser(externalUserId, userName, fullName));
        }

Desta forma temos uma forma de invocar o método no servidor usando a interface e agora, sempre que alterar a interface obtenho erros em compile-time.

Esta foi a forma que encontrei para transformar uma invocação que tem que ser baseada em strings numa strongly typed.
Será que existe alguma forma mais simples de o fazer?


Paulo Aboim Pinto