Model - View - ViewModel & WCF - é WCF o modelo?
-
05-07-2019 - |
Pergunta
Estou aprendendo o padrão Model / View / ViewModel e é variações (DataModel / View / ViewModel, ou Model / View / Presenter).
O que eu pergunto é: se eu usar esse padrão com um serviço WCF, é o serviço do Modelo (DataModel), ou eu preciso um modelo separado para encapsular a camada de serviço WCF ??
Quando eu uso WCF como DataModel minha ViewModel não é testável sem zombando todo o serviço WCF, uma vez que as chamadas para WCF necessidade de gerenciar a conexão. As chamadas neste olhar ViewModel como esta:
List<Sam.Alyza.WcfInterface.Website> rc = null;
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});
Para obter o meu ViewModel testável Eu tentei adicionar um DataModel separado para conexão WCF abstrair. Depois disso, o ViewModel era testável, as chamadas ficou assim:
List<Sam.Alyza.WcfInterface.Website> rc = new List<Sam.Alyza.WcfInterface.Website>(_datamodel.GetSites());
Problema: a maior parte do código que precisam ser testados agora tinha se mudado para o DataModel, que, mais uma vez, precisava WCF para ser testado. O que permaneceu no ViewModel foi uma casca fina, que pode ser testado. Mas desde que o código principal se mudou para o DataModel, então testar o ViewModel foi completamente inútil.
Então, para mim, parece adicionar uma camada DataModel separado para uma aplicação Ver / ViewModel usando WCF não adicionar um monte de trabalho, mas a capacidade de teste não recebe qualquer melhor.
Solução
Woot, após dois dias de wacking afastado neste problema que eu encontrei uma solução que eu posso viver com:
Como visto no exemplo de código acima, eu uso essa classe auxiliar para gerenciar minha conexão WCF (por causa do tratamento adequado dos Fechar vs Abort):
public delegate void UseServiceDelegate<T>(T proxy);
public static class Service<T>
{
public static ChannelFactory<T> _channelFactory;
public static void Use(UseServiceDelegate<T> codeBlock)
{
if (_channelFactory == null)
_channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
bool success = false;
try
{
codeBlock((T)proxy);
proxy.Close();
success = true;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
}
}
Como visto na minha pergunta, esta é a forma como esta classe é usada na minha ViewModel:
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});
Para zombar do WCF interface I seria necessário para criar e hospedar um serviço WCF simulada, altere todas as seqüências de conexão. Um monte de trabalho apenas para adicionar alguns testes.
Eu encontrei uma maneira mais fácil: É simples de criar um serviço de simulação da implementação da interface:
public class MockWebsiteService : WcfInterface.IServiceWebsites
{
internal List<Sam.Alyza.WcfInterface.Website> _websites = new List<Sam.Alyza.WcfInterface.Website>();
internal int _GetSitesCallCount;
IEnumerable<Sam.Alyza.WcfInterface.Website> Sam.Alyza.WcfInterface.IServiceWebsites.GetSites()
{
_GetSitesCallCount++;
return _websites;
}
}
O único problema é:? como faço para fazer o ViewModel chamar este mock-classe em vez do serviço
A solução: Service.Use () faz gerenciar a conexão. Ao adicionar funcionalidades para gerenciamento de conexão override eu sou capaz de esgueirar-se meu próprio objeto WCF simulada em Service.Use ().
Para isso eu preciso de uma maneira de fazer Service.Use () chamada algo diferente do WCF (olhada nas seções #Depurar):
public static class Service<T>
{
#if DEBUG
public static T DebugOverride = default(T);
#endif
public static ChannelFactory<T> _channelFactory;
public static void Use(UseServiceDelegate<T> codeBlock)
{
#if DEBUG
if (!Object.Equals(DebugOverride, default(T)))
{
codeBlock(DebugOverride);
return;
}
#endif
if (_channelFactory == null)
_channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
bool success = false;
try
{
codeBlock((T)proxy);
proxy.Close();
success = true;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
}
}
Ao adicionar este gancho de teste em serviço, eu sou capaz de esgueirar-se em qualquer objeto implementando T em meus testes:
MockWebsiteService mockmodel = new MockWebsiteService();
Service<WcfInterface.IServiceWebsites>.DebugOverride = mockmodel;
// run my tests here
Para mim esta é uma maneira muito boa para zombar um serviço WCF!
PS: Estou ciente de que, devido à DEBUG #if os testes não irá compilar na liberação. Apenas expulsá-los se você se importa.
Outras dicas
Nós usamos Dependency Injection para resolver este problema, injetando o cliente de serviço (ou talvez uma fábrica para clientes de serviço) para o ViewModel. Algo parecido com isto:
interface IClientFactory
{
TClient CreateClient<TClient>();
}
class ClientFactory : IClientFactory
{
TClient CreateClient<TClient>()
{
var channelFactory = new ChannelFactory<TClient>("AlyzaServiceEndpoint");
var proxy = (TClient)channelFactory.CreateChannel();
return proxy;
}
}
public ViewModel
{
public ViewModel(IClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
private void DoWcfStuff()
{
using (var proxy = _clientFactory.CreateClient<IClientChannel>())
{
var result = proxy.GetThings();
}
}
}
public ViewModelTests
{
public void Setup()
{
_mockFactory = new MockClientFactory();
_viewModel = new ViewModel(_mockFactory);
}
[Test]
public void Test()
{
var testResult = new Result();
var mockClient = _mockFactory.CreateClient<IClientChannel>();
mockClient.SetResultForGetThings(testResult);
// put the viewmodel through its paces.
}
private class MockClientFactory : IClientFactory
{
MockClient _mockClient;
public MockClientFactory()
{
_mockClient = new MockClient();
}
public TClient CreateClient<TClient>()
{
if (typeof(TClient) == typeof(IClientChannel))
{
return _mockClient;
}
}
}
private class MockClient : IClientChannel
{
void SetupGetThingsResult(Result result)
{
_result = result;
}
Result GetThings()
{
return _result;
}
}
}
Eu mostrei um exemplo usando simulações de mão-código. Normalmente eu usaria um quadro de zombaria como Moq .