├── .editorconfig ├── .gitignore ├── AsyncSearchDemo ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── AsyncSearchDemo.csproj ├── AsyncSearchDemo.csproj.user ├── Commands │ └── SearchCatFactsCommand.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Models │ └── CatFact.cs ├── Queries │ └── CatFactsQuery.cs └── ViewModels │ └── MainViewModel.cs ├── CountdownDemo ├── CountdownDemo.csproj └── Program.cs ├── HttpRequestDemo ├── HttpRequestDemo.csproj ├── Models │ └── CatFact.cs ├── Program.cs └── Queries │ └── CatFactQuery.cs ├── PaginationDemo ├── App.xaml ├── App.xaml.cs ├── AppShell.xaml ├── AppShell.xaml.cs ├── CatFacts │ ├── CatFact.cs │ ├── CatFactListing.cs │ ├── CatFactsQuery.cs │ ├── CatFactsView.xaml │ ├── CatFactsView.xaml.cs │ └── CatFactsViewModel.cs ├── MauiProgram.cs ├── PaginationDemo.csproj ├── PaginationDemo.csproj.user ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ ├── AppIcon │ │ ├── appicon.svg │ │ └── appiconfg.svg │ ├── Fonts │ │ ├── OpenSans-Regular.ttf │ │ └── OpenSans-Semibold.ttf │ ├── Images │ │ └── dotnet_bot.svg │ ├── Raw │ │ └── AboutAssets.txt │ ├── Splash │ │ └── splash.svg │ └── Styles │ │ ├── Colors.xaml │ │ └── Styles.xaml └── Utilities │ └── RelayCommand.cs ├── ParallelDemo ├── App.xaml ├── App.xaml.cs ├── AppShell.xaml ├── AppShell.xaml.cs ├── CatFacts │ ├── CatFact.cs │ ├── CatFactsObservable.cs │ ├── CatFactsQuery.cs │ ├── CatFactsView.xaml │ ├── CatFactsView.xaml.cs │ ├── CatFactsViewModel.cs │ └── DailyCatFactQuery.cs ├── MauiProgram.cs ├── ParallelDemo.csproj ├── ParallelDemo.csproj.user ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ ├── AppIcon │ │ ├── appicon.svg │ │ └── appiconfg.svg │ ├── Fonts │ │ ├── OpenSans-Regular.ttf │ │ └── OpenSans-Semibold.ttf │ ├── Images │ │ └── dotnet_bot.svg │ ├── Raw │ │ └── AboutAssets.txt │ ├── Splash │ │ └── splash.svg │ └── Styles │ │ ├── Colors.xaml │ │ └── Styles.xaml └── todo.txt ├── README.md ├── StoreDemo ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Commands │ └── AddGroceryListItemCommand.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── StoreDemo.csproj ├── StoreDemo.csproj.user ├── Stores │ └── GroceryListStore.cs ├── ViewModels │ ├── AddGroceryListItemViewModel.cs │ ├── GroceryListViewModel.cs │ └── GroceryViewModel.cs └── Views │ ├── AddGroceryListItemView.xaml │ ├── AddGroceryListItemView.xaml.cs │ ├── GroceryListView.xaml │ ├── GroceryListView.xaml.cs │ ├── GroceryView.xaml │ └── GroceryView.xaml.cs └── SystemReactiveDemo.sln /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 4 | dotnet_diagnostic.CS8618.severity = none 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | bin 3 | obj 4 | -------------------------------------------------------------------------------- /AsyncSearchDemo/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /AsyncSearchDemo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace AsyncSearchDemo 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AsyncSearchDemo/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /AsyncSearchDemo/AsyncSearchDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AsyncSearchDemo/AsyncSearchDemo.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Designer 7 | 8 | 9 | 10 | 11 | Designer 12 | 13 | 14 | -------------------------------------------------------------------------------- /AsyncSearchDemo/Commands/SearchCatFactsCommand.cs: -------------------------------------------------------------------------------- 1 | using AsyncSearchDemo.Models; 2 | using AsyncSearchDemo.Queries; 3 | using AsyncSearchDemo.ViewModels; 4 | using MVVMEssentials.Commands; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reactive.Linq; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Windows; 13 | 14 | namespace AsyncSearchDemo.Commands 15 | { 16 | public class SearchCatFactsCommand : CommandBase 17 | { 18 | private readonly MainViewModel _viewModel; 19 | private readonly CatFactsQuery _query; 20 | 21 | private IDisposable _currentSearch; 22 | 23 | public SearchCatFactsCommand(MainViewModel viewModel, CatFactsQuery query) 24 | { 25 | _viewModel = viewModel; 26 | _query = query; 27 | } 28 | 29 | public override void Execute(object parameter) 30 | { 31 | _viewModel.IsLoading = true; 32 | 33 | _currentSearch?.Dispose(); 34 | _currentSearch = Observable 35 | .FromAsync(() => _query.Execute(_viewModel.Search)) 36 | .ObserveOn(SynchronizationContext.Current) 37 | .Subscribe((catFacts) => 38 | { 39 | _viewModel.UpdateCatFacts(catFacts.Select(c => c.Content)); 40 | }, 41 | (error) => 42 | { 43 | MessageBox.Show("Failed to load cat facts.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); 44 | }, 45 | () => 46 | { 47 | _viewModel.IsLoading = false; 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AsyncSearchDemo/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /AsyncSearchDemo/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using AsyncSearchDemo.ViewModels; 2 | using System.Windows; 3 | 4 | namespace AsyncSearchDemo 5 | { 6 | public partial class MainWindow : Window 7 | { 8 | public MainWindow() 9 | { 10 | InitializeComponent(); 11 | 12 | DataContext = MainViewModel.LoadViewModel(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AsyncSearchDemo/Models/CatFact.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncSearchDemo.Models 2 | { 3 | public class CatFact 4 | { 5 | public string Content { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /AsyncSearchDemo/Queries/CatFactsQuery.cs: -------------------------------------------------------------------------------- 1 | using AsyncSearchDemo.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Net.Http.Json; 7 | using System.Threading.Tasks; 8 | 9 | namespace AsyncSearchDemo.Queries 10 | { 11 | public class CatFactsQuery 12 | { 13 | public async Task> Execute(string search = "") 14 | { 15 | using (HttpClient client = new HttpClient()) 16 | { 17 | await Task.Delay(1000); 18 | 19 | CatFactListingResponse? response = await client.GetFromJsonAsync("https://catfact.ninja/facts?limit=332"); 20 | 21 | if(response == null) 22 | { 23 | throw new Exception(); 24 | } 25 | 26 | return response.Data 27 | .Select(c => new CatFact() 28 | { 29 | Content = c.Fact 30 | }) 31 | .Where(c => c.Content.ToLower().Contains(search.ToLower())); 32 | } 33 | } 34 | 35 | private class CatFactListingResponse 36 | { 37 | public IEnumerable Data { get; set; } 38 | } 39 | 40 | private class CatFactResponse 41 | { 42 | public string Fact { get; set; } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AsyncSearchDemo/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using AsyncSearchDemo.Commands; 2 | using AsyncSearchDemo.Queries; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.ComponentModel; 7 | using System.Linq; 8 | using System.Reactive.Linq; 9 | using System.Threading; 10 | using System.Windows.Input; 11 | 12 | namespace AsyncSearchDemo.ViewModels 13 | { 14 | public class MainViewModel : INotifyPropertyChanged 15 | { 16 | private readonly IDisposable _disposeSearchObservable; 17 | 18 | private readonly ObservableCollection _catFacts; 19 | public IEnumerable CatFacts => _catFacts; 20 | 21 | private string _search = string.Empty; 22 | public string Search 23 | { 24 | get 25 | { 26 | return _search; 27 | } 28 | set 29 | { 30 | _search = value; 31 | OnPropertyChanged(nameof(Search)); 32 | } 33 | } 34 | 35 | private bool _isLoading; 36 | public bool IsLoading 37 | { 38 | get 39 | { 40 | return _isLoading; 41 | } 42 | set 43 | { 44 | _isLoading = value; 45 | OnPropertyChanged(nameof(IsLoading)); 46 | } 47 | } 48 | 49 | public ICommand SearchCatFactsCommand { get; } 50 | 51 | public event PropertyChangedEventHandler? PropertyChanged; 52 | 53 | public MainViewModel() 54 | { 55 | _catFacts = new ObservableCollection(); 56 | SearchCatFactsCommand = new SearchCatFactsCommand(this, new CatFactsQuery()); 57 | 58 | _disposeSearchObservable = Observable 59 | .FromEventPattern( 60 | h => PropertyChanged += h, 61 | h => PropertyChanged -= h) 62 | .Where(e => e.EventArgs.PropertyName == nameof(Search)) 63 | .Throttle(TimeSpan.FromSeconds(1)) 64 | .ObserveOn(SynchronizationContext.Current) 65 | .Subscribe((e) => 66 | { 67 | SearchCatFactsCommand.Execute(null); 68 | }); 69 | } 70 | 71 | public static MainViewModel LoadViewModel() 72 | { 73 | MainViewModel viewModel = new MainViewModel(); 74 | 75 | viewModel.SearchCatFactsCommand.Execute(null); 76 | 77 | return viewModel; 78 | } 79 | 80 | public void UpdateCatFacts(IEnumerable catFacts) 81 | { 82 | _catFacts.Clear(); 83 | 84 | foreach (string catFact in catFacts) 85 | { 86 | _catFacts.Add(catFact); 87 | } 88 | } 89 | 90 | private void OnPropertyChanged(string propertyName) 91 | { 92 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /CountdownDemo/CountdownDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CountdownDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | 3 | void Countdown(int seconds) 4 | { 5 | Observable 6 | .Timer(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(1)) 7 | .Select(currentSeconds => seconds - currentSeconds) 8 | .TakeWhile(currentSeconds => currentSeconds > 0) 9 | .Subscribe((currentSeconds) => 10 | { 11 | Console.WriteLine(currentSeconds); 12 | }, 13 | () => 14 | { 15 | Console.WriteLine("Blast off!"); 16 | }); 17 | } 18 | 19 | Countdown(5); 20 | 21 | Console.ReadLine(); -------------------------------------------------------------------------------- /HttpRequestDemo/HttpRequestDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HttpRequestDemo/Models/CatFact.cs: -------------------------------------------------------------------------------- 1 | namespace HttpRequestDemo.Models 2 | { 3 | public class CatFact 4 | { 5 | public string Content { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /HttpRequestDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using HttpRequestDemo.Models; 2 | using HttpRequestDemo.Queries; 3 | using System.Reactive.Linq; 4 | 5 | CatFactQuery catFactQuery = new CatFactQuery(); 6 | 7 | Observable 8 | .FromAsync(() => catFactQuery.Execute()) 9 | .ValidateCatFactLength(80) 10 | .Retry(3) 11 | .Catch(Observable.Return(new CatFact() { Content = "Cats are cool." })) 12 | .Subscribe((catFact) => 13 | { 14 | Console.WriteLine(catFact.Content); 15 | }); 16 | 17 | Console.ReadLine(); 18 | 19 | static class CatFactObservableExtensions 20 | { 21 | public static IObservable ValidateCatFactLength(this IObservable observable, int maxLength) 22 | { 23 | return observable 24 | .Select(catFact => 25 | { 26 | if (catFact.Content.Length > maxLength) 27 | { 28 | return Observable.Throw(new Exception("Cat fact was too long.")); 29 | } 30 | 31 | return Observable.Return(catFact); 32 | }) 33 | .Switch(); 34 | } 35 | }; -------------------------------------------------------------------------------- /HttpRequestDemo/Queries/CatFactQuery.cs: -------------------------------------------------------------------------------- 1 | using HttpRequestDemo.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http.Json; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace HttpRequestDemo.Queries 10 | { 11 | public class CatFactQuery 12 | { 13 | public async Task Execute() 14 | { 15 | using(HttpClient client = new HttpClient()) 16 | { 17 | CatFactResponse? response = await client.GetFromJsonAsync("https://catfact.ninja/fact"); 18 | 19 | if(response == null) 20 | { 21 | throw new Exception(); 22 | } 23 | 24 | return new CatFact() 25 | { 26 | Content = response.Fact 27 | }; 28 | } 29 | } 30 | 31 | private class CatFactResponse 32 | { 33 | public string Fact { get; set; } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PaginationDemo/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /PaginationDemo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace PaginationDemo 2 | { 3 | public partial class App : Application 4 | { 5 | public App() 6 | { 7 | InitializeComponent(); 8 | 9 | MainPage = new AppShell(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PaginationDemo/AppShell.xaml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /PaginationDemo/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace PaginationDemo 2 | { 3 | public partial class AppShell : Shell 4 | { 5 | public AppShell() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /PaginationDemo/CatFacts/CatFact.cs: -------------------------------------------------------------------------------- 1 | namespace PaginationDemo.CatFacts 2 | { 3 | public class CatFact 4 | { 5 | public string Content { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PaginationDemo/CatFacts/CatFactListing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PaginationDemo.CatFacts 8 | { 9 | public class CatFactListing 10 | { 11 | public IEnumerable CatFacts { get; set; } 12 | public int Total { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PaginationDemo/CatFacts/CatFactsQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | 3 | namespace PaginationDemo.CatFacts 4 | { 5 | public class CatFactsQuery 6 | { 7 | public async Task Execute(int offset = 0, int limit = 50, CancellationToken cancellationToken = default) 8 | { 9 | using (HttpClient client = new HttpClient()) 10 | { 11 | CatFactListingResponse response = await client.GetFromJsonAsync("https://catfact.ninja/facts?limit=45", cancellationToken); 12 | 13 | if (response == null) 14 | { 15 | throw new Exception(); 16 | } 17 | 18 | IEnumerable catFacts = response.Data 19 | .Skip(offset) 20 | .Take(limit) 21 | .Select(c => new CatFact() 22 | { 23 | Content = c.Fact 24 | }); 25 | 26 | return new CatFactListing() 27 | { 28 | CatFacts = catFacts, 29 | Total = response.Data.Count() 30 | }; 31 | } 32 | } 33 | private class CatFactListingResponse 34 | { 35 | public IEnumerable Data { get; set; } 36 | } 37 | 38 | private class CatFactResponse 39 | { 40 | public string Fact { get; set; } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PaginationDemo/CatFacts/CatFactsView.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 |