PCL viewModel Win8/phone services async

Apr 22, 2013 at 8:31 PM
Laurent tweeted me his Belgium / Netherlands Tech days videos and source code.
Http://geekswithblogs.net/lbugnion/archive/2013/03/10/session-material-from-techdays-be-and-nl.aspx

After watching and studying for a week I have attempted to implements a Service in windows 8 / wp7 with much success. I'm able to Command bind to a Button in a Portable class library view model and say "hi from win8 service" and "hi from wp7 service" respectively. This is very cool.

I'm using NuGet version 2.2.40116.9051
I have Resharper disabled
Notable nuGet packages
MS BCL Build Components 1.0.4
MVVM Light libraries only (PCL) 4.1.27.4
BCL Portability Pack for .NET framework 1.0.19

In a synchronous manner I'm able to populate the view model with an ObservableCollection<T>

I would like to find an example of using an ObservableCollection<T> bound to a view model that is populated with async from a service.

As I attempt to implement this as async I'm running into error messages such as:

Error 2 Cannot await 'System.Threading.Tasks.Task<System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>>' C:\Users\Jeff\Documents\Visual Studio 2012\Projects\RightWeight\RightWeight.Pcl\ViewModel\MainViewModel.cs 51 23 RightWeight.Pcl

Error 3 Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>>' to 'System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>' C:\Users\Jeff\Documents\Visual Studio 2012\Projects\RightWeight\RightWeight.Pcl\ViewModel\MainViewModel.cs 51 23 RightWeight.Pcl

in the PCL viewModel ctor
LoadData()
        public async Task LoadData()
        {
            // Works
            Weights = _weightService.GetWeights();
            // doesn't work
            Weights = await _weightService.GetWeightsAsync();
            Weights = _weightService.GetWeightsAsync(); 
        }
Pcl IWeightService
namespace RightWeight.Pcl.Service
{
    public interface IWeightService
    {
        void SayPlatformFromService();
        ObservableCollection<WeightRecord> GetWeights();
        Task<ObservableCollection<WeightRecord>> GetWeightsAsync();
        void AddWeight(WeightRecord rec);
    }
}
win8 service
namespace RightWeight.Service
{
    public class WeightService : IWeightService
    {
        private static Utility _utility;
        private ObservableCollection<WeightRecord> _weights; //  = new ObservableCollection<WeightRecord>();

        public WeightService()
        {
            _utility = new Utility(); 
            _weights = new ObservableCollection<WeightRecord>();
            LoadData(); 
        }

        private async void LoadData()
        {
            _weights = await LoadFromLocalFile(); 
        }

        public void SayPlatformFromService()
        {
            Debug.WriteLine("\nFrom Win8 WeightService\n");
        }

        public ObservableCollection<WeightRecord> GetWeights()
        {
           return _weights; 
        }

        public Task<ObservableCollection<WeightRecord>> GetWeightsAsync()
        {
            // var blat = LoadFromLocalFile(); 
            // return Task.Run(() => blat);
            // return Task.Run(() => LoadFromLocalFile());
            return Task.Run(() => LoadFromLocalFile());
            //var blat = _weights;
            //return Task.Run(() => blat ());
        }

        public async void AddWeight(WeightRecord rec)
        {
            _weights.Add(rec);
            await PersistLocalWeightsToJsonStatic(_weights); 
        }

        public async Task<ObservableCollection<WeightRecord>> LoadFromLocalFile()
        {
            var localFolder = ApplicationData.Current.LocalFolder;
            StorageFile localFileName = await localFolder.GetFileAsync(Constants.FileName);
            // String lsf = await FileIO.ReadTextAsync(localSoundsFileName);
            String localFile = await FileIO.ReadTextAsync(localFileName);
            return _utility.Deserialize<ObservableCollection<WeightRecord>>(localFile);
        }

        public static async Task PersistLocalWeightsToJsonStatic(object toPersist)
        {
            var localFolder = ApplicationData.Current.LocalFolder;
            var localWeightsFile = await localFolder.CreateFileAsync(Constants.FileName, CreationCollisionOption.ReplaceExisting);
            await FileIO.WriteTextAsync(localWeightsFile, _utility.Serialize(toPersist));
        }

        public async Task<IList<WeightRecord>> GetWeightsListAsync()
        {
            var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
            StorageFile localFileName = await localFolder.GetFileAsync(Constants.FileName);
            // String lsf = await FileIO.ReadTextAsync(localSoundsFileName);
            String localFile = await FileIO.ReadTextAsync(localFileName);
            return _utility.Deserialize<List<WeightRecord>>(localFile);
        }
    }
}
I hesitate to mention the following as I think it's an anomaly from intelisense on a syntax error however as is typical with most questions I ask during the composition of this discussion I saw an error while checking a syntax error that I had not encountered over the last week or more of working with this code: It could be a culprit note that I am not working on the async in the windows phone code yet just win8. I seem to have some problem with the BCL RTM that's for later.
I saw this while working on a syntax in a lambda Task.Run return Task.Run(() => blat ());
Ambigous invocation:
System.ThreadingTasks.Task Run(System.Action)(in class Task)
System.Threading.Tasks.Task Run(System.Func<<System.Threading.Tasks.Task>)<in class Task)
match
Coordinator
Apr 22, 2013 at 9:53 PM
Hey Jeff,

The error "Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>>' to 'System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>" means that somewhere you forgot an "await" keyword.

"await" is what "converts" a Task<Something> into a Something instance.

Hope this helps,
Laurent
Apr 22, 2013 at 10:26 PM
Right. I've used it like that before in a non PCL project. In this project when I add the await I'm told it's not awaitable. Wondering if there is some .dll Task confusion?
        public async Task<ObservableCollection<WeightRecord>> GetWeightsAsync()
        {
            return await LoadFromLocalFile();
        }

        public async Task<ObservableCollection<WeightRecord>> LoadFromLocalFile()
        {
            var localFolder = ApplicationData.Current.LocalFolder;
            StorageFile localFileName = await localFolder.GetFileAsync(Constants.FileName);
            // String lsf = await FileIO.ReadTextAsync(localSoundsFileName);
            String localFile = await FileIO.ReadTextAsync(localFileName);
            return _utility.Deserialize<ObservableCollection<WeightRecord>>(localFile);
        }
in the LoadData() called from the ViewModel
        public async Task LoadData()
        {
            // Works
            Weights = _weightService.GetWeights();
            // doesn't work
            Weights = await _weightService.GetWeightsAsync(); // error on the await in this line
            //Weights = _weightService.GetWeightsAsync(); 
        }
I get the error
Error 2 Cannot await 'System.Threading.Tasks.Task<System.Collections.ObjectModel.ObservableCollection<RightWeight.Pcl.Model.WeightRecord>>' C:\Users\Jeff\Documents\Visual Studio 2012\Projects\RightWeight\RightWeight.Pcl\ViewModel\MainViewModel.cs 53 23 RightWeight.Pcl
Apr 23, 2013 at 12:04 AM
Success!

I created an entirely new project and wired it up again. This time when I created the PCL I targeted only windows 8 store and windows phone 8. Previously I had left the .net 4.5 target framework, Silverlight and targeted phone 7.5 rather than phone 8 hoping for BCL to work for async.

In the new project I noticed when I hovered over Task in the LoadData() the bubble help said '(awaitable) class System.Threading.Tasks.Task Represents an Asynchronous operation.' for grins I went back to the old project and hovered over the same Task. It indicated 'class System.Threading.Tasks.Task Represents an asynchronous operation.' Note it didn't indicate awaitable.

Guess Task can come from a couple different places?
May 16, 2013 at 7:14 PM
Edited May 16, 2013 at 7:14 PM
@jhalbrecht - I ran into the same problem with "cannot await" errors. It's some odd incompatibility between Assembly versions. I found this cryptic NuGet issue resolution page on the subject:

http://blogs.msdn.com/b/bclteam/p/asynctargetingpackkb.aspx

I wish they had explained what the problem was and why the fix works, but the fix does work. Here's what they say:

Issue 2

Symptom

After installing the Microsoft.Bcl or Microsoft.Bcl.Async packages to certain projects, you may get build errors or warnings similar to:

Cannot await 'System.Threading.Tasks.Task'.

or

The primary reference "Microsoft.Threading.Tasks" could not be resolved because it has an indirect dependency on the framework assembly "System.Runtime, Version=1.5.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v4.0". To resolve this problem, either remove the reference "Microsoft.Threading.Tasks" or retarget your application to a framework version which contains "System.Runtime, Version=1.5.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".

Resolution

Microsoft.Bcl.Async package is not supported in Visual Basic Web Application projects. As a workaround, place all async/await usage in a class library and consume that from the Web Application project.

Otherwise, for other project types add an App.Config to the project with the following contents, replacing [version] with the version (for example, 2.5.10.0) of System.Runtime and System.Threading.Tasks that you are referencing:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-[version]" newVersion="[version]" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-[version]" newVersion="[version]" />
  </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

Make sure you capitalize the first word in App.Config. In my case the DLLs I were using were 1.5.11.0 and replacing "version" (no quotes) with that value did the trick. Just check the version number of your System.Runtime and System.Threading.Tasks in the properties window. You'll see it in the path to the DLL(s).

-- roschler