2

I am trying to make a call to a wcf service with my silverlight application and I am having some trouble understanding how the model returns the result back to the view model. Within my view model I have the following command:

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

then within my model I have the following code

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

When I insert breakpoints within the model I see where the query is being executed and a result is returned within the model (this.LastSearchResults = results) however I cannot seem to get this collection to update/ notify the view model of the result. I've generated and run a similar test using just a method and dummy class and it seems to work so I suspect the issue is due to the async call /threading. I have INotifyPropertyChanged within the ViewModel to sync the View and ViewModel. Do I need to also implement INotifyPropChng within the model as well? I'm new to mvvm so any help / example of how I should approach this would be appreciated.

Thank you,

UPDATE In further testing I added INotifyPropertyChanged to the model and changed the Completed event as follows:

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

    }

Placing a watch on Search Results I now see it is updated with results from teh WCF. My question is still around is this teh correct approach? It seems to work right now however I am curious if I am missing something else or if I should not be placing INotify within the Model.

Thank you,

2 Answers 2

2

I've found that it's best to encapsulate my WCF services in an additional layer of Service classes. This allows me to more easily Unit Test my ViewModels. There are several patterns when doing this, though this is the simplest I've used. The pattern is to create a method that matches the definition of the service call, though also contains an Action that can be invoked after the service call completes.

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

You can also provide an interface that your ViewModel can use. This makes Unit Testing even easier, though is optional.

public interface IService
{
    void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}

Your ViewModel would then look something like this:

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

An additional reason for encapsulating your service calls into another class like this is that it can provide a single place for such things as logging and error handling. That way your ViewModel itself doesn't need to take care of those things specifically related to the Service.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks Joe, In looking at this I assume then that the commands (how I invoke the search) stay the same with my delegate commands? The main change is in the model by referring to the service interface created?
That's right - instead of my public Search() method that I created, you can still use your search commands.
This is a good response. Even if you aren't doing Async for Silverlight, abstracting the communications into a Service layer is my preferred approach. This also makes it easier for multiple ViewModels to use the same service and makes Service layer mocking much simpler.
0

I would likely use something along the lines of:

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

With the SearchResultsRetrieved event being fired by the Model when its SearchResults collection has been filled with the appropriate data. I prefer to have custom events rather than implement INotifyPropertyChanged on my models, particularly if there are only one, or a few, events that need to be communicated to the viewmodel.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.