выполнение асинхронных вызовов с использованием mvvm в silverlight

#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.