When I use an injected instance of a data service in the lambda statement that defines a relay command handler, the handler is never invoked (it is associated with a button). When I declare an instance of the data service within the lambda, it works fine. Any ideas?
Edited:
Created the class variable _dataService and initialized it in the view model constructor. Use the class variable within the relay command handler and all works.
private IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
Batches = new ObservableCollection<Batch>();
#region RefreshCommand
RefreshCommand = new RelayCommand(
() =>
{
var t1 = Task<ObservableCollection<Batch>>.Factory.StartNew(() =>
{
// WHEN I UNCOMMENT AND COMMENT OUT BELOW, WORKS FINE.
//DataService test = new DataService();
//ObservableCollection<Batch> batches = test.GetBatchesToProcess();
//
// THIS NOW WORKS.
return _dataService.GetBatchesToProcess();
});
try
{
t1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions)
{
if (e is SqlException)
{
MessageBox.Show("Sql Exception: " + e.Message);
}
else
{
MessageBox.Show("Unexpected error: " + e.Message);
}
}
return;
}
Batches = t1.Result;
}
);
#endregion
}
Using the dataService parameter to the MainViewModel constructor did not work within the relay command handler. Using a private class variable (_dataService) that is initialized within the constructor solved the dilemma.
Related
Hi I want to use the ObservableCollection (AddRange) in async Task but i get NotSupportedException
private ObservableCollection<CoronavirusCountry> _data = new ObservableCollection<CoronavirusCountry>();
public ObservableCollection<CoronavirusCountry> data
{
get => _data;
set => SetProperty(ref _data, value);
}
Task.Run(async()=>{
APIService service = new APIService();
data.AddRange(await service.GetTopCases());
Status = "Updated " + DateTime.Now;
});
Not sure which AddRange method you are referring to, because ObservableCollection doesn't have that out of the box.
Anyway - assuming you wrote an extension method - it has to be called in the UI thread, so running a Task doesn't make sense.
The awaitable method shown below should be sufficient. It would await the asynchronous service call, and update the collection in the main thread.
public async Task UpdateData()
{
var service = new APIService();
var newData = await service.GetTopCases();
Data.AddRange(newData); // use proper naming!
Status = "Updated " + DateTime.Now;
}
In order to call and await the above method, you could have an async Loaded event handler like this:
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
Loaded += async (s, e) => await viewModel.UpdateData();
}
I'm quite new to unit tests and I've got some troubles testing this command
internal async Task OnDeleteTreasurerCommandExecute(TesorieraItemResult tesoriera)
{
try
{
if (await MessageService.ShowAsync("Confermare l'operazione?", string.Empty, MessageButton.YesNo, MessageImage.Question) == MessageResult.Yes)
{
await repository.DeleteTesorieraItemAsync(tesoriera.ID_ISTITUTO,tesoriera.ID_DIVISA,tesoriera.PROGRESSIVO);
await MessageService.ShowInformationAsync("Operazione completata");
if (SelectedInstitute != null)
await OnLoadDataCommandExecute();
}
}
catch (Exception ex)
{
ErrorService.HandleError(GetType(), ex);
}
}
I'm using Catel as MVVM framework
how do I simulate the yes/no answers?
Thanks
You need to substitute the MessageService with a class that can return yes or no answer. Here's an example using NSubstitute.
Install-Package NSubstitute
Install-Package NUnit
Let us say you have a class that has a method that
needs Yes, then No:
public class AccountViewModel
{
readonly IMessageService _messageService;
readonly ICustomerRepository _customerRepository;
public AccountViewModel(IMessageService messageService, ICustomerRepository customerRepository)
{
_messageService = messageService;
_customerRepository = customerRepository;
}
public async Task OnDeleteCustomer(Customer customer)
{
if (await MessageService.ShowAsync(
"Confirm?",
string.Empty,
MessageButton.YesNo,
MessageImage.Question) == MessageResult.Yes)
{
_customerRepository.Delete(customer);
await MessageService.ShowInformationAsync("Completed");
}
}
}
Then your test case looks like this:
public class TestAccountViewModel
{
[TestCase]
public class TestDeleteCustomer()
{
// arrange
var messageService = Substitute.For<IMessageService>();
messageService
.ShowAsync(
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<MessageButton>(),
Arg.Any<MessageImage>())
.Returns(Task.FromResult(MessageResult.Yes);
messageService
.ShowInformationAsync(Arg.Any<string>())
.Returns(Task.FromResult<object>(null));
var customerRepository = Substitute.For<ICustomerRepository>();
// act
var sut = new AccountViewModel(messageService, customerRepository);
var customer = new Customer();
sut.OnDeleteCustomer(customer);
// assert
Assert.IsTrue(customerRepository.Received().DeleteCustomer(customer));
}
}
In a past version, Catel provided a test implementation of the IMessageService that allowed you to queue expected results so you could test the different paths inside a command.
I just noticed this class is no longer available, but you can easily implement a test stub yourself (using mocking, etc). Or you could contribute to Catel and revive the test implementation.
I use some invoke operation method in wcf ria service.like following method:
[Invoke]
public int GetCountOfAccountsByRequestId(long requestId)
{
return ObjectContext.Accounts.Count(a => a.RequestId ==requestId);
}
When i want get data from this method , i use the following code to run and get value from invoke method:
int countOfAccounts = 0;
InvokeOperation<int> invokeOperation = context.GetCountOfAccountsByRequestId(selectedRequest.RequestId);
invokeOperation.Completed += (s, args) =>
{
if (invokeOperation.HasError)
{
var messageWindow = new MessageWindow(invokeOperation.Error.Message);
messageWindow.Show();
invokeOperation.MarkErrorAsHandled();
}
else
{
countOfAccounts = invokeOperation.Value;
if (countOfAccounts == 0)
{
// do some thing
}
}
};
But this method requires a lot of coding to Run invoke method and get value from this.Also as part of this code is executed asynchronously.Similarly, some method must be implemented in tandem.And the return value of each method is related to previous methods.
How can i implement this actions?!
How can i write better than this without any problem?
For WCF RIA Services I usually create a service class in my project to handle all the interactions with the service in one place. So I would use this service class to make the calls easier to understand and use.
I put your existing code in a separate class so you can see how it might be called. Note: This is just an example. :)
public class ServiceWrapper
{
...
public void GetCountOfAccountsByRequestId(int requestId, Action<int> callback)
{
context.GetCountOfAccountsByRequestId(requestId, InvokeComplete, callback);
}
private void InvokeComplete<T>(InvokeOperation<T> io)
{
var callback = (Action<T>)io.UserState;
if (io.HasError == false)
{
callback(io.Value);
}
else
{
var messageWindow = new MessageWindow(io.Error.Message);
messageWindow.Show();
io.MarkErrorAsHandled();
}
}
}
public class YourCode
{
public void SomeMethod()
{
serviceWrapper.GetCountOfAccountsByRequestId(selectedRequest.RequestId, GetCountCompleted);
}
public void GetCountCompleted(int countOfAccounts)
{
if (countOfAccounts == 0)
{
// do some thing
}
}
}
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,
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.
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.
I have the following bit of code to set up my Rx hookups:
Event related definitions:
public class QueryEventArgs : EventArgs
{
public SomeParametersType SomeParameters
{
get;
set;
}
public object QueryContext
{
get;
set;
}
};
public delegate void QueryDelegate(object sender, QueryEventArgs e);
public event QueryDelegate QueryEvent;
Initialization:
queryObservable = Observable.FromEvent<QueryEventArgs>(this, "QueryEvent");
queryObservable.Subscribe((e) =>
{
tbQueryProgress.Text = "Querying... ";
client.QueryAsync(e.EventArgs.SomeParameters, e.EventArgs.QueryContext);
});
queryCompletedObservable = from e in Observable.FromEvent<QueryCompletedEventArgs>(client, "QueryCompleted").TakeUntil(queryObservable) select e;
queryCompletedObservable.Subscribe((e) =>
{
tbQueryProgress.Text = "Ready";
SilverlightClientService_QueryCompleted(e.Sender, e.EventArgs);
},
(Exception ex) =>
{
SetError("Query error: " + ex);
}
);
"client" is the WCF client and everything else is fairly self-explanatory.
The "TakeUntil" is there to stop the user stomping on himself when doing a new query while in the middle of a currently running one. However, while the code works if I remove the "TakeUntil" clause, if I put it in, the query is never completed.
Is this the correct pattern? If so, am I doing something wrong?
Cheers,
-Tim
TakeUntil terminates the subscription when a value is received from its argument, so your first queryObservable starts up the query but also terminates the subscription to the complete events.
The simpler solution is to setup an IObservable around your actual query, and then use Switch to ensure that only one query runs at a time.
public static class ClientExtensions
{
public static IObservable<QueryCompletedEventArgs> QueryObservable(
this QueryClient client,
object[] someParameters, object queryContext)
{
return Observable.CreateWithDisposable<QueryCompletedEventArgs>(observer =>
{
var subscription = Observable.FromEvent<QueryCompletedEventArgs>(
h => client.QueryCompleted += h,
h => client.QueryCompleted -= h
)
.Subscribe(observer);
client.QueryAsync(someParameters, queryContext);
return new CompositeDisposable(
subscription,
Disposable.Create(() => client.Abort())
);
});
}
}
Then you can do this:
queryObservable = Observable.FromEvent<QueryEventArgs>(this, "QueryEvent");
queryObservable
.Select(query => client.QueryObservable(
query.EventArgs.SomeParameters,
query.EventArgs.QueryContext
))
.Switch()
.Subscribe(queryComplete =>
{
tbQueryProgress.Text = "Ready";
// ... etc
});
This sets up one continuous flow, whereby each "Query" event starts a query which emits the complete event from that query. New queries automatically teriminate the previous query (if possible) and start a new one.