Embora o WCF (Windows Communication Foundation) seja uma tecnologia mais antiga, tendo sido proeminente há mais de uma década como a solução de comunicação unificaad da Microsoft para a plataforma .NET, ela foi amlpamente substituída. Desenvolvedores que trabalharam com WCF provavelmente compartilham suas próprias experiências e desafios com essa tecnologia.
Devido a requisitos de projeto recentes, tornou-se necessário utilizar WCF. Para facilitar o uso futuro ou a manutenção de projetos legados, um exemplo prático foi desenvolvido e está disponível para download no final deste artigo.
Cenários de Uso
A seguir, descrevemos o cenário prático que motivou a criação do exemplo demonstrativo:
- Os clientes exigiram que as contas de usuário do site fossem vinculadas às informações de hardware do computador, garantindo que uma conta pudesse ser usada apenas em máquinas específicas.
- Solicitou-se que os usuários pudessem interagir com um cliente WinForm executado localmente enquanto usavam o site. Isso envolveria a exibição de módulos de negócios relevantes do cliente.
O cliente WinForm possuía funcionalidades e lógicas de negócios complexas que não eram adequadas para implementação no site. Embora o site e o cliente fossem sistemas de negócios distintos, eles precisavam colaborar para completar o processo de negócios geral. Esta foi uma exigência bastatne peculiar.
O exemplo demonstrativo aborda os seguintes recursos:
- O WCF é hospedado em um formulário WinForm e expõe interfaces HTTP.
- Páginas da web invocam interfaces WCF usando JavaScript para controlar o WinForm (o exemplo demonstra a exibição de um formulário pop-up).
- Páginas da web invocam interfaces WCF usando JavaScript para recuperar informações de hardware do computador (o exemplo recupera o ID da CPU e o endereço MAC).
É importante notar que o site é implantado em um servidor, não na máquina do usuário local. Portanto, para chamar interfaces WCF hospedadas localmente no computador do usuário, o site deve usar JavaScript. Chamar WCF do código de back-end do site não é viável, pois isso introduziria problemas de origem cruzada. A solução implementada utiliza JSONP para requisições GET de origem cruzada para as interfaces WCF, o que atende às necessidades do negócio em questão.
Configuração do Serviço WCF
Crie um novo projeto WinForm (.NET Framework 4 ou superior) e adicione as seguintes referências de assembly DLL:
Microsoft.VisualBasic(usado principalmente para garantir a inicialização de instância única do WinForm)System.ServiceModeleSystem.ServiceModel.Web(necessários para WCF)System.Management(utilizado para obter informações de hardware)
Em seguida, crie as interfaces e as classes de implementação do serviço WCF. Este exemplo demonstra dois serviços WCF:
Interface e Implementação de Teste
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Windows.Forms;
namespace JobsWcfDemo
{
[ServiceContract]
public interface ITest
{
/// <summary>
/// Testa a requisição GET.
/// Devido a problemas de origem cruzada ao chamar interfaces WCF hospedadas no WinForm via JavaScript,
/// é necessário usar o método JSONP para requisições.
/// </summary>
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string GetTest(string p1, string p2);
/// <summary>
/// Testa a requisição POST.
/// O método POST em WebInvoke deve estar em maiúsculas.
/// Como não há uma solução conhecida para problemas de origem cruzada com serviços WCF hospedados no WinForm,
/// esta pode ser testada usando ferramentas como Postman.
/// Alternativamente, o código C# do back-end pode enviar requisições POST com dados JSON.
/// </summary>
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string PostTest(string p1, string p2);
/// <summary>
/// Exibe o formulário personalizado PopForm no WinForm.
/// </summary>
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
void ShowForm();
/// <summary>
/// Exibe uma caixa de mensagem do WinForm com o texto fornecido.
/// </summary>
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
void ShowMessage(string msg);
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TestImpl : ITest
{
public string GetTest(string p1, string p2)
{
return $"Teste GET bem-sucedido. Parâmetros recebidos: p1={p1}, p2={p2}";
}
public string PostTest(string p1, string p2)
{
return $"Teste POST bem-sucedido. Parâmetros recebidos: p1={p1}, p2={p2}";
}
public void ShowForm()
{
PopForm pf = new PopForm();
pf.Show();
// Garante que o formulário exibido fique em primeiro plano
pf.TopMost = true;
pf.TopMost = false;
}
public void ShowMessage(string msg)
{
MessageBox.Show(msg, "Mensagem", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
}
Interface e Implementação de Computador
using System;
using System.Collections.Generic;
using System.Management;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
namespace JobsWcfDemo
{
[ServiceContract]
public interface IComputer
{
/// <summary>
/// Obtém o ID do processador (CPU).
/// </summary>
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string GetCPU();
/// <summary>
/// Obtém o endereço MAC.
/// </summary>
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string GetMAC();
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ComputerImpl : IComputer
{
public string GetCPU()
{
List<string> cpus = new List<string>();
try
{
ManagementClass cpuInfo = new ManagementClass("Win32_Processor");
ManagementObjectCollection cpuCollection = cpuInfo.GetInstances();
foreach (ManagementObject mo in cpuCollection)
{
string cpuId = mo.Properties["ProcessorId"].Value.ToString().Trim();
if (!string.IsNullOrWhiteSpace(cpuId))
{
cpus.Add(cpuId);
}
}
return string.Join(",", cpus);
}
catch (Exception ex)
{
return $"Erro ao obter ID da CPU: {ex.Message}";
}
}
public string GetMAC()
{
List<string> macAddresses = new List<string>();
try
{
ManagementClass adapterInfo = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection adapterCollection = adapterInfo.GetInstances();
foreach (ManagementObject mo in adapterCollection)
{
if ((bool)mo["IPEnabled"] == true)
{
string mac = mo["MacAddress"].ToString().Replace(":", "-");
macAddresses.Add(mac);
}
}
return string.Join(",", macAddresses);
}
catch (Exception ex)
{
return $"Erro ao obter endereço MAC: {ex.Message}";
}
}
}
}
Em seguida, crie uma classe para gerenciar a criação e o início do serviço WCF:
Classe de Gerenciamento do Serviço WCF
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
namespace JobsWcfDemo
{
public class MyWcfService
{
private ServiceHost serviceHost;
/// <summary>
/// Inicializa e inicia o serviço WCF.
/// </summary>
/// <param name="interfaceType">O tipo da interface do serviço.</param>
/// <param name="implementationType">O tipo da classe que implementa a interface.</param>
/// <param name="baseAddress">O endereço base do serviço (ex: http://127.0.0.1:9000/nomeDoServico).</param>
public MyWcfService(Type interfaceType, Type implementationType, string baseAddress)
{
serviceHost = new ServiceHost(implementationType);
var webBinding = new WebHttpBinding
{
CrossDomainScriptAccessEnabled = true
};
var webBehavior = new WebHttpBehavior
{
AutomaticFormatSelectionEnabled = true,
DefaultOutgoingResponseFormat = WebMessageFormat.Json
};
var endpoint = serviceHost.AddServiceEndpoint(interfaceType, webBinding, baseAddress);
endpoint.Behaviors.Add(webBehavior);
if (serviceHost.State != CommunicationState.Opened)
{
serviceHost.Open();
}
}
/// <summary>
/// Fecha o serviço.
/// </summary>
public void Close()
{
if (serviceHost != null && serviceHost.State == CommunicationState.Opened)
{
serviceHost.Close();
}
}
}
}
Finalmente, no formulário principal do WinForm, inicie dois serviços WCF usando a mesma porta:
Inicialização dos Serviços WCF no WinForm
using System;
using System.Windows.Forms;
namespace JobsWcfDemo
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private MyWcfService testService;
private MyWcfService computerService;
private void MainForm_Load(object sender, EventArgs e)
{
string serviceBaseUrl = "http://127.0.0.1:9000";
testService = new MyWcfService(typeof(ITest), typeof(TestImpl), serviceBaseUrl + "/test");
computerService = new MyWcfService(typeof(IComputer), typeof(ComputerImpl), serviceBaseUrl + "/comp");
}
}
}
Neste ponto, dois serviços WCF foram configurados para expor interfaces HTTP. Ao iniciar o aplicativo WinForm, os serviços estarão prontos para uso.
Observação importante: Ao hospedar o WCF em um aplicativo WinForm, o Windows 7 pode iniciar o serviço diretamente. No entanto, em versões mais recentes do Windows (como o Windows 10), pode ser necessário executar o aplicativo como administrador para evitar erros. Recomenda-se abrir o projeto no Visual Studio clicando com o botão direito e selecionando "Executar como administrador" para facilitar a depuração e execução.
Para garantir que apenas uma instância do aplicativo WinForm seja executada (evitando conflitos de porta), consulte a documentação sobre inicialização de instância única de aplicativos WinForm. Isso é crucial, pois o aplicativo WinForm precisa iniciar o serviço WCF e escutar em uma porta específica.
Testando as Interfaces do Serviço WCF
Um aplicativo cliente de teste foi desenvolvido especificamente para este projeto. Ele permite testar a invocação de interfaces WCF via JavaScript por meio de uma página HTML estática. O exemplo também inclui uma interface POST do WCF. Como a invocação POST de origem cruzada de JavaScript para WCF não foi totalmente implementada neste exemplo, um trecho de código C# demonstra como enviar uma requisição POST usando WebClient. O código da página de teste é o seguinte:
Página de Teste HTML e JavaScript
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<title>Teste de Invocação WCF via JS</title>
</head>
<body>
<h1>Atualmente, a única maneira comprovada de chamar interfaces WCF hospedadas em WinForm via JavaScript é usando requisições GET com JSONP.</h1>
<h1>As interfaces POST do WCF podem ser testadas usando ferramentas como Postman.</h1>
<h1>Alternativamente, o JavaScript pode chamar um método do back-end do site, que por sua vez pode invocar a interface POST do WCF usando C#.</h1>
<h1>Como este é um exemplo de página estática, apenas a requisição GET com JSONP será demonstrada.</h1>
<fieldset>
<legend>Teste de Requisição com Parâmetros</legend>
<br />
Parâmetro 1: <input type="text" id="p1" /><br />
Parâmetro 2: <input type="text" id="p2" /><br /><br />
<input type="button" value="Teste Requisição GET" id="btnGet" />
</fieldset>
<hr />
<fieldset>
<legend>Teste de Operação de Formulário WinForm</legend>
<br />
<input type="button" value="Exibir Formulário Pop-up Personalizado" id="btnPop" /><br /><br />
Conteúdo da Mensagem: <input type="text" id="p3" />
<input type="button" value="Exibir Caixa de Mensagem" id="btnMsg" />
</fieldset>
<hr />
<fieldset>
<legend>Obter Informações de Hardware</legend>
<br />
<input type="button" value="Obter ID da CPU" id="btnCPU" /><br /><br />
<input type="button" value="Obter Endereço MAC" id="btnMAC" />
</fieldset>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function () {
// Teste de Requisição GET
$('#btnGet').click(function () {
var p1Value = $('#p1').val();
var p2Value = $('#p2').val();
$.ajax({
// O tipo "get" é implícito para JSONP, então esta linha pode ser omitida.
// type: "get",
dataType: "jsonp",
url: "http://localhost:9000/test/GetTest?p1=" + p1Value + "&p2=" + p2Value,
success: function (data) {
alert(data);
},
error: function (err) {
alert("Erro na requisição: " + JSON.stringify(err));
}
});
});
// Exibir formulário pop-up personalizado
$('#btnPop').click(function () {
$.ajax({
dataType: "jsonp",
url: "http://localhost:9000/test/ShowForm"
});
});
// Exibir caixa de mensagem
$('#btnMsg').click(function () {
var p3Value = $('#p3').val();
$.ajax({
dataType: "jsonp",
url: "http://localhost:9000/test/ShowMessage?msg=" + encodeURIComponent(p3Value)
});
});
// Obter ID da CPU
$('#btnCPU').click(function () {
$.ajax({
dataType: "jsonp",
url: "http://localhost:9000/comp/GetCPU",
success: function (data) {
alert("ID da CPU: " + data);
},
error: function (err) {
alert("Erro na requisição: " + JSON.stringify(err));
}
});
});
// Obter endereço MAC
$('#btnMAC').click(function () {
$.ajax({
dataType: "jsonp",
url: "http://localhost:9000/comp/GetMAC",
success: function (data) {
alert("Endereço MAC: " + data);
},
error: function (err) {
alert("Erro na requisição: " + JSON.stringify(err));
}
});
});
});
</script>
</body>
</html>
O código C# a seguir demonstra como o cliente pode invocar a interface POST do WCF:
Código Cliente C# para Requisição POST
using System;
using System.Text;
using System.Windows.Forms;
using System.Net;
// Assumindo que este código está em um manipulador de eventos de botão no cliente WinForm.
private void btnPost_Click(object sender, EventArgs e)
{
// Nota: Newtonsoft.Json não é usado aqui para manter o tamanho do demo pequeno.
string p1 = tbp1.Text.Trim(); // Supondo que tbp1 seja um controle TextBox
string p2 = tbp2.Text.Trim(); // Supondo que tbp2 seja um controle TextBox
string jsonPayload = $"{{\"p1\":\"{p1}\",\"p2\":\"{p2}\"}}";
using (WebClient client = new WebClient())
{
client.Encoding = Encoding.UTF8;
client.Headers.Add("Content-Type", "application/json");
try
{
string result = client.UploadString("http://localhost:9000/test/PostTest", jsonPayload);
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show($"Erro na requisição POST: {ex.Message}");
}
}
}
Para testar, execute o aplicativo cliente WCF (WinForm) primeiro e, em seguida, abra a página HTML estática em um navegador. A página permitirá que você interaja com os serviços WCF.
Embora o WCF seja considerado obsoleto, ele pode ser uma solução viável para certos cenários. Dominar diversas tecnologias oferece mais flexibilidade. O código-fonte completo está disponível para referência detalhada.