在silverlight中使用mvvm进行异步调用

时间:2011-05-10 12:49:09

标签: silverlight mvvm c#-4.0

我正在尝试使用我的silverlight应用程序调用wcf服务,并且我在理解模型如何将结果返回到视图模型时遇到一些麻烦。在我的视图模型中,我有以下命令:

 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 = results)但是我似乎无法让此集合更新/通知视图模型结果。我只使用一个方法和虚拟类生成并运行类似的测试,它似乎工作,所以我怀疑这个问题是由于异步调用/线程。我在ViewModel中有INotifyPropertyChanged来同步View和ViewModel。我是否还需要在模型中实现INotifyPropChng?我是mvvm的新手,所以我将不胜感激任何帮助/示例。

谢谢,

更新 在进一步测试中,我将INotifyPropertyChanged添加到模型并更改了Completed事件,如下所示:

 void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
    {
        ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
        results.Add(e.Result);
        //this.LastSearchResults = results;
        SearchResults = results;

    }

将手表放在搜索结果上我现在看到它已使用来自WCF的结果进行更新。我的问题仍然是这个正确的方法吗?它现在似乎工作,但我很好奇,如果我错过了其他东西或我不应该在模型中放置INotify。

谢谢,

2 个答案:

答案 0 :(得分:2)

我发现最好将我的WCF服务封装在另一层Service类中。这使我可以更轻松地单元测试我的ViewModels。这样做有几种模式,虽然这是我用过的最简单的模式。该模式是创建一个匹配服务调用定义的方法,但也包含一个可以在服务调用完成后调用的Action。

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 :(得分:0)

我可能会使用以下内容:

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; }
}

当其SearchResults集合填充了适当的数据时,模型会触发SearchResultsRetrieved事件。我更喜欢自定义事件,而不是在我的模型上实现INotifyPropertyChanged,特别是如果只有一个或几个事件需要传递给视图模型。