TheDoc
TheDoc

Reputation: 718

Using async to load form data in background - MVVM

I can't seem to get async to work the way I want. I'm pretty unfamiliar with it in general, but my research has led me to it. Here's my situation: I have a XAML form I'm loading that needs data from a web service. I can get the data, no problem. The issue is that I want the form to load with a "Please wait. Data loading..." screen that I have made, but nothing loads until the query finishes which can take up to 10 seconds, which is a poor user experience. Can anyone let me know what I'm doing wrong?

In my ViewModel main section, I call LoadData() to get the ball rolling.

I also had a thought...is async what I really want here?

    public async void LoadData()
    {
        IsLoading = Visibility.Visible;
        MonitorData downloadInfo = await GetWebApiInfo();
        try
        {
            AssignDataToControls(downloadInfo);
            IsLoading = Visibility.Collapsed;
            Console.WriteLine("Loaded successfully.");
        }
        catch (Exception e)
        {
            IsLoading = Visibility.Collapsed;
            Console.WriteLine("Error: " + e);
        }
    }

    private void AssignDataToControls(MonitorData mon)
    {
        MainPanel.Clear();
        mon.MainPanel.ToList().ForEach(x => MainPanel.Add(x));
        Information = mon.Information;
        MonitorText = mon.MonitorText;
        ProgressData = mon.progList;
    }

    public async Task<MonitorData> GetWebApiInfo()
    {
        main = new MonitorData();

        var url = "::::::::WEB API ADDRESS::::::::";
        var request = (HttpWebRequest)WebRequest.Create(url);
        //Task<HttpWebRequest> req = await (HttpWebRequest)WebRequest.Create(url);
        //HttpWebRequest request = await GetWebRequest(url);
        WebResponse response = request.GetResponse();
        Stream dataStream = response.GetResponseStream();
        StreamReader reader = new StreamReader(dataStream, Encoding.Unicode);
        string responseFromServer = reader.ReadToEnd();
        var deserializer = new JavaScriptSerializer();
        main = deserializer.Deserialize<MonitorData>(responseFromServer);

        reader.Dispose();
        response.Dispose();
        return main;
    }

Upvotes: 1

Views: 1072

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457382

The compiler will tell you exactly what's wrong with that approach. Specifically, your GetWebApiInfo method is going to run synchronously because you never use await.

In this case, you can use HttpClient to download the information asynchronously. It's a lot easier than mucking around with WebResponse and stuff:

private static readonly HttpClient _client = new HttpClient();
public async Task<MonitorData> GetWebApiInfo()
{
  var url = "::::::::WEB API ADDRESS::::::::";
  string responseFromServer;
  using (var dataStream = await _client.GetStreamAsync())
  using (var reader = new StreamReader(dataStream, Encoding.Unicode))
    responseFromServer = await reader.ReadToEndAsync();
  var deserializer = new JavaScriptSerializer();
  return deserializer.Deserialize<MonitorData>(responseFromServer);
}

In general, when you want to "make something async", you should start from the lowest-level APIs. E.g., don't start by marking GetWebApiInfo with async; start with the actual HTTP transfer and call it with await. Then let async/await grow from there.

You may find my NotifyTask<T> type helpful; it allows you to do things like show/hide busy indicators with data binding.

Upvotes: 3

Related Questions