#silverlight #mvvm #c#-4.0
#silverlight #mvvm #c #-4.0
Вопрос:
Я пытаюсь выполнить вызов службы wcf с помощью моего приложения silverlight, и у меня возникают некоторые проблемы с пониманием того, как модель возвращает результат обратно в модель представления. В моей модели представления у меня есть следующая команда:
public DelegateCommand GetSearchResultCommand
{
get
{
if (this._getSearchResultCommand == null)
this._getSearchResultCommand = new DelegateCommand(GetSearchResultCommandExecute, CanGetSearchResultsCommandExecute);
return this._getSearchResultCommand;
}
}
private void GetSearchResultCommandExecute(object parameter)
{
this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
}
/// <summary>
/// Bindable property for SearchResults
/// </summary>
public ObservableCollection<QueryResponse> SearchResults
{
get
{
return this._SearchResults;
}
private set
{
if (this._SearchResults == value)
return;
// Set the new value and notify
this._SearchResults = value;
this.NotifyPropertyChanged("SearchResults");
}
}
тогда в моей модели у меня есть следующий код
public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted = new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
sc.QueryAsync(new Query { QueryText = searchQuery });
return LastSearchResults;
}
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
this.LastSearchResults = results;
}
Когда я вставляю точки останова в модель, я вижу, где выполняется запрос, и результат возвращается в модели (this.LastSearchResults = результаты) однако, похоже, я не могу заставить эту коллекцию обновлять / уведомлять модель представления о результате. Я сгенерировал и запустил аналогичный тест, используя только метод и фиктивный класс, и, похоже, он работает, поэтому я подозреваю, что проблема связана с асинхронным вызовом / потоковой передачей. Я изменил INotifyPropertyChanged в ViewModel для синхронизации View и ViewModel. Нужно ли мне также внедрять INotifyPropChng внутри модели? Я новичок в mvvm, поэтому буду признателен за любую помощь / пример того, как я должен подойти к этому.
Спасибо,
ОБНОВЛЕНИЕ В ходе дальнейшего тестирования я добавил INotifyPropertyChanged в модель и изменил завершенное событие следующим образом:
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
//this.LastSearchResults = results;
SearchResults = results;
}
Просматривая результаты поиска, я теперь вижу, что они обновлены результатами из WCF. Мой вопрос все еще актуален — правильный ли это подход? Кажется, это работает прямо сейчас, однако мне любопытно, не упускаю ли я чего-то еще или мне не следует размещать INotify в модели.
Спасибо,
Ответ №1:
Я обнаружил, что лучше всего инкапсулировать мои службы WCF в дополнительный уровень классов обслуживания. Это позволяет мне более легко модульно тестировать мои ViewModels. При выполнении этого есть несколько шаблонов, хотя это самый простой, который я использовал. Шаблон заключается в создании метода, который соответствует определению вызова службы, хотя также содержит действие, которое может быть вызвано после завершения вызова службы.
public class Service : IService
{
public void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted = (s,e) =>
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
reply(results);
};
sc.QueryAsync(new Query { QueryText = searchQuery });
}
}
Вы также можете предоставить интерфейс, который может использовать ваша ViewModel. Это делает модульное тестирование еще проще, хотя и необязательно.
public interface IService
{
void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}
Тогда ваша ViewModel выглядела бы примерно так:
public class MyViewModel : INotifyPropertyChanged
{
private IService _service;
public MyViewModel()
: this(new Service())
{ }
public MyViewModel(IService service)
{
_service = service;
SearchResults = new ObservableCollection<QueryResponse>();
}
private ObservableCollection<QueryResponse> _searchResults
public ObservableCollection<QueryResponse> SearchResults
{
get { return _searchResults; }
set
{
_searchResults = value;
NotifyPropertyChanged("SearchResults");
}
}
public void Search()
{
_service.GetSearchResults("abcd", results =>
{
SearchResults.AddRange(results);
});
}
protected void NotifyPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if(handler != null)
handler(new PropertyChangedEventArgs(property));
}
}
Дополнительная причина для инкапсуляции ваших служебных вызовов в другой класс, подобный этому, заключается в том, что он может предоставить единое место для таких вещей, как ведение журнала и обработка ошибок. Таким образом, вашей ViewModel самой не нужно заботиться о тех вещах, которые конкретно связаны с сервисом.
Комментарии:
1. Спасибо, Джо, глядя на это, я предполагаю, что команды (как я вызываю поиск) остаются неизменными для моих команд делегирования? Основное изменение заключается в модели путем ссылки на созданный интерфейс сервиса?
2. Правильно — вместо моего метода public Search (), который я создал, вы все еще можете использовать свои команды поиска.
3. Это хороший ответ. Даже если вы не выполняете асинхронность для Silverlight, мой предпочтительный подход заключается в абстрагировании коммуникаций на уровень сервиса. Это также упрощает использование одной и той же службы несколькими ViewModels и значительно упрощает имитацию уровня сервиса.
Ответ №2:
Я бы, скорее всего, использовал что-то вроде:
public class ViewModel : INotifyPropertyChanged
{
private readonly IModel model;
private readonly DelegateCommand getSearchResultsCommand;
public DelegateCommand GetSearchResultsCommand
{
get { return getSearchResultsCommand; }
}
public ObservableCollection<QueryResponse> SearchResults
{
get { return model.SearchResults; }
}
public ViewModel(IModel model)
{
this.model = model;
this.model.SearchResultsRetrieved = new EventHandler(model_SearchResultsRetrieved);
this.getSearchResultsCommand = new DelegateCommand(model.GetSearchResultCommandExecute, model.CanGetSearchResultsCommandExecute);
}
private void model_SearchResultsRetrieved(object sender, EventArgs e)
{
this.NotifyPropertyChanged("SearchResults");
}
}
public interface IModel
{
event EventHandler SearchResultsRetrieved;
void GetSearchResultCommandExecute(object parameter);
bool CanGetSearchResultsCommandExecute(object parameter);
ObservableCollection<QueryResponse> SearchResults { get; }
}
При этом событие SearchResultsRetrieved запускается моделью, когда ее коллекция SearchResults была заполнена соответствующими данными. Я предпочитаю иметь пользовательские события, а не реализовывать INotifyPropertyChanged в моих моделях, особенно если есть только одно или несколько событий, которые необходимо передать viewmodel.