title: WPF MVVM Control that connects to Web API
body: <p>I have an ASP.NET Web API with SQL Database / Entity Framework that I will be using as the server side of an application that I am building. The client side will be WPF for now. I am having some trouble determining the best method for the MVVM controls to access the data from the server. All of the tutorials and courses I have done for MVVM connect directly to a local database, and they update when the control is loaded. What I want is a constant loop where the API is called once per minute and the data in the control automatically refreshed at that point. I have this working for the two main controls (ran into a problem updating the collection from a different thread and had to use App.Current.Dispatcher.Invoke), however I am not sure if I am keeping to best practice running the API calls directly from the view model. Also, I need to be able to share data between the view models. </p>
  
  <p>ScheduledForwardsViewModel has data that ActiveForwardsViewModel needs, and ActiveForwardsViewModel has data that CreateForwardsViewModel needs. The ActiveForward for each entry needs to be able to see the scheduled date for each corresponding scheduled item so it can find the next event and display the time left until that event occurs in the ActiveForwardsView. The CreateForwardView simply needs access to the AllUsers,AllGroups,AllContacts observable collections created during the ActiveForwards data manipulation so it can use that data in the drop down fields when scheduling a new forward. I haven't been able to get the dropdowns to work yet. I do have access to the info I need from ScheduledForwardsView using a static object, but I feel like there may be a better way. I would prefer to refrain from making multiple calls to the API for the same data when part of my app already has the data I need. </p>
  
  <p>Basic Layout:</p>
  
  <pre><code>MainWindow
      CreateForwardView (with view model)
          [Drop down boxes to select user and schedule forward]
          [Buttons, etc.]
      [TabControl]
          [TabItem: Active Forwards]
              ActiveForwardsView (with view model)
          [TabItem: Scheduled Forwards]
              ScheduledForwardsView (with view model)
      [EndTabControl]
  </code></pre>
  
  <p>The ActiveForwardsViewModel is below. The ScheduledForwardsViewModel is essentially the same thing, but it calls a different API method and contains a static object that I am using in ActiveForwardsViewModel (see the part where I wait for ScheduledForwardsViewModel to complete 1 run before I continue with UpdateUserObjects). That didn't seem like the right way to do it, but I set it up that way just so I could move onto other things.</p>
  
  <pre><code>public class ActiveForwardsViewModel : INotifyPropertyChanged
  {
  
      private CancellationToken updater = default(CancellationToken);
      private AppLog log;
      private FFObject _selectedObject;
      private ObservableCollection&lt;FFObject&gt; _activeForwards;
      public ObservableCollection&lt;FFObject&gt; ActiveForwards
      {
          get { return _activeForwards; }
          set
          {
              if (_activeForwards != value)
              {
                  _activeForwards = value;
                  PropertyChanged(this, new PropertyChangedEventArgs("ActiveForwards"));
              }
  
          }
      }
  
      public ObservableCollection&lt;FFObject&gt; AllUsers { get; set; }
      public ObservableCollection&lt;FFObject&gt; AllGroups { get; set; }
      public ObservableCollection&lt;FFObject&gt; AllContacts { get; set; }
      public ObservableCollection&lt;FFObject&gt; AllObjects { get; set; }
  
      public bool Running = false;
      public bool FirstRunComplete = false;
  
  
      public event PropertyChangedEventHandler PropertyChanged = delegate { };
  
      public ActiveForwardsViewModel()
      {
          if (DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
          {
              updater.ThrowIfCancellationRequested();
              return;
          }
          ActiveForwards = new ObservableCollection&lt;FFObject&gt;();
          AllUsers = new ObservableCollection&lt;FFObject&gt;();
          AllGroups = new ObservableCollection&lt;FFObject&gt;();
          AllContacts = new ObservableCollection&lt;FFObject&gt;();
          AllObjects = new ObservableCollection&lt;FFObject&gt;();
          StartUpdater(updater);
          RemoveForwardCommand = new RelayCommand(OnRemove, CanRemove);
  
      }
  
  
      public async Task StartUpdater(CancellationToken token = default(CancellationToken))
      {
  
  
          while (!token.IsCancellationRequested)
          {
              Running = true;
              await this.Update();
              FirstRunComplete = true;
              try
              {
                  await Task.Delay(TimeSpan.FromMinutes(1), token);
              }
              catch (TaskCanceledException)
              {
                  Running = false;
                  break;
              }
  
          }
      }
  
      private List&lt;FFObject&gt; UpdateAllObjectList(string strJson)
      {
          var serializer = new JavaScriptSerializer();
          return serializer.Deserialize&lt;List&lt;FFObject&gt;&gt;(strJson);
          //return AllObjects;
      }
  
      public string GetString(string method)
      {
          using (var client = new WebClient())
          {
              var url = string.Format("https://mywebapi.domain.com/api/{0}", method);
              return client.DownloadString(url);
          }
      }
  
      public async Task Update()
      {
  
          Func&lt;string, List&lt;FFObject&gt;&gt; getUsersJson = UpdateAllObjectList;
          await Task&lt;string&gt;.Factory.StartNew(() =&gt; GetString("Users"))
                          .ContinueWith(antecendent =&gt; getUsersJson(antecendent.Result))
                          .ContinueWith(antecendent =&gt; UpdateUserObjects(antecendent.Result));
  
      }
  
      private void CheckRemoved(List&lt;FFObject&gt; all)
      {
          List&lt;FFObject&gt; RemoveObjects = new List&lt;FFObject&gt;();
  
          foreach (var obj in ActiveForwards)
          {
              var newObj = all.FirstOrDefault(i =&gt; i.ID == obj.ID);
              if (newObj == null)
              {
                  RemoveObjects.Add(obj);
              }
              else
              {
                  if (!(bool) newObj.IsForwarded)
                  {
                      RemoveObjects.Add(obj); 
                  }
              }
          }
  
          foreach (var obj in RemoveObjects)
          {
              App.Current.Dispatcher.Invoke((Action)delegate
              {
                  ActiveForwards.Remove(obj);
              });
          }
  
  
      }
  
      private void UpdateUserObjects(List&lt;FFObject&gt; all)
      {
          Debug.WriteLine("Starting UpdateUserObject");
          if (all != null)
          {
              AllObjects = new ObservableCollection&lt;FFObject&gt;(all);
              CheckRemoved(all);
  
              var x = 0;
              while (!ScheduledForwardsViewModel.RunOnce &amp;&amp; x &lt; 5)
              {
                  System.Threading.Thread.Sleep(1000);
                  x++;
              }
  
  
              foreach (var obj in all)
              {
                  if (obj.ObjectType.ToLower() == "user")
                  {
  
                      var existing = AllUsers.FirstOrDefault(i =&gt; i.DistinguishedName == obj.DistinguishedName);
  
                      if (existing != null)
                      {
                          existing.ForwardedTo = obj.ForwardedTo;
                          existing.ForwardedToAd = obj.ForwardedToAd;
                          existing.ForwardedToDn = obj.ForwardedToDn;
                          existing.ForwardedToPk = obj.ForwardedToPk;
                          existing.ForwardedToDisplay = obj.ForwardedToDisplay;
                          existing.IsForwarded = obj.IsForwarded;
                          existing.DeliverAndRedirect = obj.DeliverAndRedirect;
                          existing.IsScheduled = obj.IsScheduled;
                          existing.TimeLeft = obj.TimeLeft;
                      }
                      else
                      {
                          //AllUsers.Add(obj);
                          App.Current.Dispatcher.Invoke((Action)delegate // &lt;--- HERE
                          {
                              AllUsers.Add(obj);
                          });
                      }
  
  
                      if (obj.IsForwarded ?? false)
                      {
                          existing = ActiveForwards.FirstOrDefault(i =&gt; i.DistinguishedName == obj.DistinguishedName);
  
                          obj.TimeLeft = "";
                          var now = DateTime.Now;
                          var TimeRemaining = new TimeSpan?();
                          foreach (var schedule in ScheduledForwardsViewModel.ScheduledForwards)
                          {
  
                              if (schedule.DistinguishedName == obj.DistinguishedName)
                              {
  
                                  if (schedule.StopJobStatus == "Scheduled")
                                  {
                                      if (TimeRemaining == null)
                                      {
                                          TimeRemaining = schedule.StopTime - now;
                                      }
                                      else
                                      {
                                          if (schedule.StopTime - now &lt; TimeRemaining)
                                          {
                                              TimeRemaining = schedule.StopTime - now;
                                          }
                                      }
                                  }
                              }
                          }
  
                          if (TimeRemaining != null)
                          {
                              var remaining = (TimeSpan)TimeRemaining;
                              var min = new int();
                              if (remaining.Seconds &gt; 30)
                              {
                                  min = remaining.Minutes + 1;
                              }
                              double m = remaining.Minutes / (double)60;
                              double hm = remaining.Hours + m;
                              double h = hm / 24;
                              double dh = remaining.Days + h;
                              if (remaining.Days &gt; 0)
                              {
                                  var daysleft = Math.Round(dh, 2);
                                  var quarterRound = Math.Round(dh * 4, MidpointRounding.ToEven) / 4;
                                  obj.TimeLeft = quarterRound + "d";
                              }
                              else if (remaining.Hours &gt; 0)
                              {
                                  var hoursleft = Math.Round(hm, 2);
                                  var quarterRound = Math.Round(hm * 4, MidpointRounding.ToEven) / 4;
                                  obj.TimeLeft = quarterRound + "h";
  
                                  obj.TimeLeft = remaining.Hours + "h" + remaining.Minutes + "m";
                              }
                              else
                              {
                                  if (min == 0)
                                  {
                                      obj.TimeLeft = "&lt; 30s";
                                  }
                                  else
                                  {
                                      obj.TimeLeft = min + "m";
                                  }
  
                              }
                          }
  
                          if (existing != null)
                          {
                              existing.ForwardedTo = obj.ForwardedTo;
                              existing.ForwardedToAd = obj.ForwardedToAd;
                              existing.ForwardedToDn = obj.ForwardedToDn;
                              existing.ForwardedToPk = obj.ForwardedToPk;
                              existing.ForwardedToDisplay = obj.ForwardedToDisplay;
                              existing.IsForwarded = obj.IsForwarded;
                              existing.DeliverAndRedirect = obj.DeliverAndRedirect;
                              existing.IsScheduled = obj.IsScheduled;
                              existing.TimeLeft = obj.TimeLeft;
                          }
                          else
                          {
                              //ActiveForwards.Add(obj);
                              App.Current.Dispatcher.Invoke((Action)delegate // &lt;--- HERE
                              {
                                  ActiveForwards.Add(obj);
                              });
                          }
  
                      }
                  }
                  else if (obj.ObjectType.ToLower() == "group")
                  {
                      if (obj.IsForwarded ?? false)
                      {
                          var existing = AllGroups.FirstOrDefault(i =&gt; i.DistinguishedName == obj.DistinguishedName);
  
                          if (existing != null)
                          {
                              existing.ForwardedTo = obj.ForwardedTo;
                              existing.ForwardedToAd = obj.ForwardedToAd;
                              existing.ForwardedToDn = obj.ForwardedToDn;
                              existing.ForwardedToPk = obj.ForwardedToPk;
                              existing.ForwardedToDisplay = obj.ForwardedToDisplay;
                              existing.IsForwarded = obj.IsForwarded;
                              existing.DeliverAndRedirect = obj.DeliverAndRedirect;
                              existing.IsScheduled = obj.IsScheduled;
                              existing.TimeLeft = obj.TimeLeft;
                          }
                          else
                          {
                              //AllGroups.Add(obj);
                              App.Current.Dispatcher.Invoke((Action)delegate // &lt;--- HERE
                              {
                                  AllGroups.Add(obj);
                              });
                          }
                      }
                  }
                  else if (obj.ObjectType.ToLower() == "contact")
                  {
                      if (obj.IsForwarded ?? false)
                      {
                          var existing = AllContacts.FirstOrDefault(i =&gt; i.DistinguishedName == obj.DistinguishedName);
  
                          if (existing != null)
                          {
                              existing.ForwardedTo = obj.ForwardedTo;
                              existing.ForwardedToAd = obj.ForwardedToAd;
                              existing.ForwardedToDn = obj.ForwardedToDn;
                              existing.ForwardedToPk = obj.ForwardedToPk;
                              existing.ForwardedToDisplay = obj.ForwardedToDisplay;
                              existing.IsForwarded = obj.IsForwarded;
                              existing.DeliverAndRedirect = obj.DeliverAndRedirect;
                              existing.IsScheduled = obj.IsScheduled;
                              existing.TimeLeft = obj.TimeLeft;
                          }
                          else
                          {
                              //AllContacts.Add(obj);
                              App.Current.Dispatcher.Invoke((Action)delegate // &lt;--- HERE
                              {
                                  AllContacts.Add(obj);
                              });
                          }
                      }
  
                  }
                  else
                  {
                      throw new NotImplementedException();
                  }
              }
          }
          RunOnce = true;
      }
  </code></pre>