Модель - Вид - ViewModel & amp; WCF - модель WCF?
-
05-07-2019 - |
Вопрос
Я только изучаю шаблон Model / View / ViewModel и его варианты (DataModel / View / ViewModel или Model / View / Presenter).
Что мне интересно: если я использую этот шаблон с сервисом WCF, это сервис Model (DataModel) или мне нужна отдельная модель для инкапсуляции уровня сервиса WCF?
Когда я использую WCF в качестве DataModel, моя ViewModel не может тестироваться без насмешки всей службы WCF, поскольку вызовы WCF должны управлять соединением. Вызовы в этой ViewModel выглядят так:
List<Sam.Alyza.WcfInterface.Website> rc = null;
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});
Чтобы получить тестируемый ViewModel, я попытался добавить отдельную DataModel для абстрактного соединения WCF. После этого ViewModel был тестируемым, вызовы выглядели так:
List<Sam.Alyza.WcfInterface.Website> rc = new List<Sam.Alyza.WcfInterface.Website>(_datamodel.GetSites());
Проблема: большая часть кода, который должен был быть протестирован, теперь переместилась в DataModel, которая, опять же, нуждалась в WCF для тестирования. В ViewModel осталась тонкая оболочка, которую можно протестировать. Но поскольку основной код переместился в DataModel, тестирование ViewModel было совершенно бесполезным.
Поэтому мне кажется, что добавление отдельного слоя DataModel в приложение View / ViewModel с использованием WCF добавляет много работы, но тестируемость не улучшается.
Решение
Woot, после двух дней безумного отношения к этой проблеме я нашел решение, с которым я могу жить:
Как видно из приведенного выше примера кода, я использую этот вспомогательный класс для управления моим соединением WCF (из-за правильной обработки Close против 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();
}
}
}
}
Как видно из моего вопроса, этот класс используется в моей ViewModel:
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});
Чтобы смоделировать интерфейс WCF, мне нужно создать и разместить фиктивную службу WCF, изменить все строки подключения. Много работы, чтобы добавить несколько тестов.
Я нашел более простой способ: Создать фиктивный сервис, реализующий интерфейс, несложно:
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;
}
}
Единственная проблема: как заставить ViewModel вызывать этот mock-класс вместо службы?
Решение: Service.Use () управляет соединением. Добавив функциональность для переопределения управления соединениями, я могу добавить свой собственный объект-макет WCF в Service.Use ().
Для этого мне нужен способ заставить Service.Use () вызывать что-то отличное от WCF (посмотрите разделы #DEBUG):
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();
}
}
}
}
Добавив этот тестовый хук в Service, я могу проникнуть в любой объект, реализующий T в моих тестах:
MockWebsiteService mockmodel = new MockWebsiteService();
Service<WcfInterface.IServiceWebsites>.DebugOverride = mockmodel;
// run my tests here
Для меня это очень хороший способ издеваться над сервисом WCF!
PS: я знаю, что из-за #if DEBUG тесты не будут компилироваться в релизе. Просто выгоните их, если вам не все равно.
Другие советы
Мы используем Dependency Injection для решения этой проблемы, внедряя клиент сервиса (или, возможно, фабрику для клиентов сервиса) в ViewModel. Примерно так:
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;
}
}
}
Я показал пример с использованием ложных кодов. Обычно я хотел бы использовать платформу Mocking, например Moq .