Objetivo
Algum tempo atrás, tive a oportunidade de tarabalhar com a Microsoft Unified Communications API, também conhecida como UCC API. Para as pessoas que não estão familiarizadas com esse nome, trata-se da API do Microsoft Office Communicator (OCS).
O OCS disponibiliza esta API para pessoas que querem fazer seu próprio cliente de chat (ou outros tipos de comunicação) utilizando toda a infra-estrutura do servidor.
Enquanto estava mexendo com a API, percebi que a documentação da MSDN não era muito completa e nem muito simples de entender. Essa foi a motivação para postar toda minha experiência aqui.
O forum americano da UCC API não tem muita gente postando e não é muito ativo. A soma de todos estes elementos tornaram o trabalho um pouquinho complicado.
Infelizmente, não tenho muito a colaborar sobre tipos de mídia diferente de IM (voz por exmplo), porém, os conceitos da API são os mesmos quando se trata de outro tipo de mídia.
Visão Geral da UCC API
A API é a mesma para C++ e .Net (C#). Ela é implementada através de um componente COM e um primary interop assembly para .NET. O primeiro problema aqui é que geralmente a API é utilizada com registro “side-by-side” do componente COM.
Aqui está o link da documentação da MSDN que explica isso: Sample Application Manifest for Side-by-Side Execution.
Esse registro através de um arquivo de manifesto é um pouco complicado, mas evita a necessidade de registrar o componente COM (regsvr32) e minimiza problemas de deploy futuros.
O Visual Studio 2008 tem um bug engraçado com manifestos. Mesmo se vc adicionar o .manifest dentro do projeto, sempre que você modifica o csproj (adicionando um arquivo, por exemplo) e pressiona F5, ocorre um erro no momento de carregar o manifesto. Fechando e reabrindo o Visual Studio resolve o problema. Não sei se o mesmo ocorre no VS 2010.
Outra característica da UCC API é que ela é toda assícrona. Na prática, isso significa que se você executa um Endpoint.Enable() por exemplo, um método OnEnable da interface COM é chamada em resposta a essa solicitação. Quando você começa a aninhar esse tipo de chamada, o código fica bem complicado para entender. Se você não conhece a API, fica sem saber onde é o próximo passo no fluxo de execução da aplicação.
Todo objeto COM tem uma interface de “dispatch” amiga. Por exemplo: IUccEndpoint e _IUccEndpointEvents.
O que eu sugiro é que você nunca faça um advise de diferentes interfaces dispatch com cardinalidades diferentes. Por exemplo: Num endpoint você tem “n” sessions. Se você implementar _IUccEndpointEvents e _IUccSessionEvents no mesmo objeto, você está pedindo para ter problemas.
Para objetos de mesma cardinalidade, por exemplo _IUccSessionEvents e _IUccInstantMessagingSessionEvents não tem problema. Isso acontecerá várias vezes porque o COM estimula esse tipo de design. O objeto é o mesmo, porém o polimorfismo no COM é bastante peculiar.
Principais pontos da UCC API
Eu divido a UCC API nos seguintes pontos-chave:
- Criando conexão com o servidor: É como conectar e desconectar num servidor.
- Tratando eventos de category instances: Esse tópico trata-se de como receber informações do servidor. Tudo está no mesmo saco: informação de presença do usuário, configuração do servidor, containers, pesquisa.
- Tratando controle de acesso de informações nos containers: Esse é um ponto complicado da API. Você pode publicar informações diferentes para usuários diferentes, baseado em controle de acesso. Você pode dizer para um usuário que está online e para outro que está ocupado.
- Tratando conversações: Esse tópico fala sobre mandar e receber mensagens. Iniciar e abandonar sessões de conversação (vale para IM e outros tipos de mídia). Conferência faz parte de sessões de conversação, mas merece um capítulo a parte.
Informações de Login do OCS
As informações necessárias para login são:
- Uri: A Uri “Sip” do usuário. É necessário informar o prefixo sip:
- UserName: Nome do usuário. Pode ser o nome completo (user@domain.com) ou apenas user.
- Password: A senha do usuário
- Domain: Domínio
Quando vi isso pela primeira vez, pensei comigo: “Porque dois nomes de usuário?”. Continuo me perguntando, mas desenvolvi algumas teorias (não baseadas em nenhuma documentação, apenas num palpite): Está relacionada à estrutura do servidor OCS.
O processo de conexão segue os seguintes passos:
- A UCC API manda um pacote SIP para o servidor, localizando o usuário no OCS Front End server através de sua URL SIP. O servidor responde: Sim, este usuário é um usuário do OCS.
- A UCC API manda o usuário/senha/domínio para o OCS Front End Server. O servidor faz um forward dessa informação para o Active Directory que responde: Okay, amigo ou NÃO, você é do mal.
As informações de usuário, senha e domínio podem ser enviadas para o servidor de duas formas:
- User/Domain/Password: O nome “curto” do usuário. Este é validado no Active Directory’s com o nome “antigo” do usuário (pre-windows 2000). Por isso não precisa de domínio.
- Full Qualified Name / Password: O formato user@domain.com. Nesse caso, ele vai ser validado contra o “novo” login name no active directory e simplesmente ignora o campo domínio. Mande uma string vazia nele.
Tratando a conexão com o servidor: Endpoint
O conceito neste caso é simples. Você possui um objeto UccPlatform uqe representa uma instância da API na sua aplicação. Você terá apenas um e deverá inicializá-lo uma única vez:
public void CreatePlatform() { uccPlatform = new UccPlatform(); uccPlatform.Initialize("UCCConferenceSample", null); }
Se você quiser derrumar a plataforma, use o método Shutdown.
O endpoint é sua conexão com o servidor. Você precisa de uma plataforma inicializada. Você pode habilitar o endpoint da seguinte forma:
public void EnableEndpoint(MyPlatform platform, string UserUri, string UserName, string UserPassword, string UserDomain, string ServerName, UCC_TRANSPORT_MODE transportMode) { UccUri uri = UccHelper.GetUri(UserUri); uccEndpoint = platform.uccPlatform.CreateEndpoint(UCC_ENDPOINT_TYPE.UCCET_PRINCIPAL_SERVER_BASED, uri, null, null); ComHelper.Advise<_IUccEndpointEvents>(uccEndpoint, this); IUccServerSignalingSettings signalingSettings = (IUccServerSignalingSettings)uccEndpoint; UccCredential credential = signalingSettings.CredentialCache.CreateCredential(UserName, UserPassword, UserDomain); signalingSettings.CredentialCache.SetCredential("*", credential); signalingSettings.AllowedAuthenticationModes = (int)UCC_AUTHENTICATION_MODES.UCCAM_NTLM; signalingSettings.Server = signalingSettings.CreateSignalingServer(ServerName, UCC_TRANSPORT_MODE.UCCTM_TCP); uccEndpoint.Enable(null); }
Vamos discutir um pouco este código. UccHelper é um pequeno wrapper para interpretar Uri’s. Você precisará dele várias vezes. Eu vou fornecer o código logo abaixo. CreateEndpoint é o método principal aqui. Toda vez que aparecer um “Create” na API, não significa que você está “usando” o objeto. Apenas instanciando ele. Esse comportamento também ocorre várias vezes.
ComHelper é outra classe auxiliar. É um wrapper rápido para associar os eventos disparados por uma dispatch interface com um objeto. Neste caso estamos dizendo que os eventos disparados por _IUccEndpointEvents em “this”. Essa é uma característica do COM.
A classe precisa implementar os métodos da interface _IUccEndpointEvents e terá os métodos OnEnable ou OnDisable disparados assincronamente após a chamada do método Enable.
public class ComHelper { public static void Advise<T>(object source, T sink) { IConnectionPointContainer container = (IConnectionPointContainer)source; IConnectionPoint cp; int cookie = 0; Guid guid = typeof(T).GUID; container.FindConnectionPoint(ref guid, out cp); cp.Advise(sink, out cookie); } }
O cast que aparece aqui é muito interessante também. Quando você faz um cast (IUccServerSignallingSettings)uccEndpoint, internamente você está chamando o método QueryInterface do COM. Isso confunde muito desenvolvedores .Net porque quando você está inspecionando o código, não é possível perceber que a instância uccEndpoint “é” um IUccServerSignallingSettings. Você só tem essa informação lendo a documentação da API (os links estão no rodapé do post). Quando a documentação diz IUccAlgumaCoisa can be queried from the IUccOutraCoisa significa que no C# você pode fazer um cast direto.
As próximas linhas referentes ao “signalling settings” estão apenas passando as informações de conexão antes de chamar o método Enable.
A resposta será enviada pelo servidor no método OnEnable:
public void OnEnable(IUccEndpoint pEventSource, IUccOperationProgressEvent pEventData) { if (pEventData.StatusCode != 0) throw new Exception("Bad luck"); MessageBox.Show("Endpoint enabled"); }
Os controles para evitar que sua aplicação faça algo antes de receber a chamada em OnEnable devem ser feitos por sua conta.
A interface IUccOperationProgressEvent tem uma propriedade chamada StatusCode. Se for diferente de 0, ocorreu um problema. Sempre verifique isso em cada resposta.
Para compreender melhor os erros, ligue o “Hexadecimal View” do inspetor do C# e ele fará um pouco mais de sentido (não muito). Você pode encontrar o significado para os códigos de erro nos links:
Unified Communications Client API Error Codes
Unified Communications Client API Error Messages
Conclusão
Essa primeira parte não ajudará muito, mas é um começo. Em breve estarei trazendo os outros posts com os próximos passos.
Espero que com essas informações seja possível compreender um pouco das características da UCC API que ajudem no desenho de um novo cliente baseado nesta API e como funciona o processo de conexão com um servidor OCS.