{"id":21730131,"url":"https://github.com/nventive/chinook.dynamicmvvm","last_synced_at":"2025-08-21T15:11:28.028Z","repository":{"id":37964438,"uuid":"269183941","full_name":"nventive/Chinook.DynamicMvvm","owner":"nventive","description":"Declarative and extensible ViewModels, properties, and commands.","archived":false,"fork":false,"pushed_at":"2025-05-20T13:40:23.000Z","size":321,"stargazers_count":12,"open_issues_count":5,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-08-01T01:19:39.100Z","etag":null,"topics":["chinook","dotnet","mobile","mvvm","uno-platform","uwp","viewmodels","winui","xamarin"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nventive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-03T20:08:04.000Z","updated_at":"2025-05-20T13:40:26.000Z","dependencies_parsed_at":"2023-01-22T22:46:06.997Z","dependency_job_id":"eb6b8f78-c895-4b65-96cf-c1781ee196d4","html_url":"https://github.com/nventive/Chinook.DynamicMvvm","commit_stats":{"total_commits":97,"total_committers":18,"mean_commits":5.388888888888889,"dds":0.6804123711340206,"last_synced_commit":"015f3031716250160280d385dddde54c4bb1e068"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":"nventive/Template","purl":"pkg:github/nventive/Chinook.DynamicMvvm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FChinook.DynamicMvvm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FChinook.DynamicMvvm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FChinook.DynamicMvvm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FChinook.DynamicMvvm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nventive","download_url":"https://codeload.github.com/nventive/Chinook.DynamicMvvm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FChinook.DynamicMvvm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271500223,"owners_count":24770370,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chinook","dotnet","mobile","mvvm","uno-platform","uwp","viewmodels","winui","xamarin"],"created_at":"2024-11-26T04:12:30.873Z","updated_at":"2025-08-21T15:11:27.974Z","avatar_url":"https://github.com/nventive.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# Chinook.DynamicMvvm\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](LICENSE) ![Version](https://img.shields.io/nuget/v/Chinook.DynamicMvvm.Abstractions?style=flat-square) ![Downloads](https://img.shields.io/nuget/dt/Chinook.DynamicMvvm.Abstractions?style=flat-square)\n\nThe `Chinook.DynamicMvvm` packages assists in .Net MVVM (Model - View - ViewModel) development.\n\n## Cornerstones\n\n- **Highly Extensible**\n  - Everything is interface-based to easily allow more implementations.\n  - A single framework can't cover everything. Our architecture is designed in a way that allows you to integrate your favorites tools easily. \n- **Declarative Syntax**\n  - We aim to understand the behavior of a property by glancing at its declaration.\n\n### More like this\nThe Chinook namespace has other recipes for .Net MVVM applications.\n- [Chinook.DataLoader](https://github.com/nventive/Chinook.DataLoader): Customizable async data loading recipes.\n- [Chinook.Navigation](https://github.com/nventive/Chinook.Navigation): Navigators for ViewModel-first navigation.\n- [Chinook.BackButtonManager](https://github.com/nventive/Chinook.BackButtonManager): An abstraction to deal with hardware back buttons.\n\n## Getting Started\n\n1. Add the `Chinook.DynamicMvvm` nuget package to your project.\n1. Create your first ViewModel. Here's one that covers the basics.\n   ```csharp\n   using Chinook.DynamicMvvm;\n   // (...)\n   public class MainPageViewModel : ViewModelBase\n   {\n     public string Content\n     {\n       get =\u003e this.Get(initialValue: string.Empty);\n       set =\u003e this.Set(value);\n     }\n\n     public IDynamicCommand Submit =\u003e this.GetCommand(() =\u003e\n     {\n       Result = Content;\n     });\n\n     public string Result\n     {\n       get =\u003e this.Get(initialValue: string.Empty);\n       private set =\u003e this.Set(value);\n     }\n   }\n   ```\n   \u003e 💡 Want to go **fast**? We recommend installing the [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets) Visual Studio Extension to benefit from code snippets. All our snippets start with `\"ck\"` (for \"Chinook\") and they will help you write those properties and commands extra fast.\n1. Set this `MainPageViewModel` as the `DataContext` of your `MainPage` in `MainPage.xaml.cs`.\n   ```csharp\n   public MainPage()\n   {\n     this.InitializeComponent();\n     DataContext = new MainPageViewModel();\n   }\n   ```\n   Here is some xaml for that `MainPage.xaml` that demonstrates the basics.\n   ```xml\n   \u003cPage x:Class=\"ChinookSample.MainPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\u003e\n     \u003cStackPanel\u003e\n        \u003cTextBox Text=\"{Binding Content, Mode=TwoWay}\" /\u003e\n\n        \u003cButton Content=\"Submit\"\n                Command=\"{Binding Submit}\" /\u003e\n\n        \u003cTextBlock Text=\"{Binding Result}\" /\u003e\n     \u003c/StackPanel\u003e\n   \u003c/Page\u003e\n   ```\n1. Configure a `System.IServiceProvider` containing the following:\n   - `IDynamicCommandBuilderFactory`\n   - `IDynamicPropertyFactory`\n\n   Here is a simple code sample that does that using `Microsoft.Extensions.DependencyInjection` and `Microsoft.Extensions.Hosting`.\n   ```csharp\n   var serviceProvider = new HostBuilder()\n     .ConfigureServices(serviceCollection =\u003e serviceCollection\n       .AddSingleton\u003cIDynamicCommandBuilderFactory, DynamicCommandBuilderFactory\u003e()\n       .AddSingleton\u003cIDynamicPropertyFactory, DynamicPropertyFactory\u003e()\n     )\n     .Build()\n     .Services;\n   ```\n1. Set the `IServiceProvider` into `ViewModelBase.DefaultServiceProvider` in the startup of your application.\n   ```csharp\n   ViewModelBase.DefaultServiceProvider = serviceProvider;\n   ```\n   \u003e 💡 It's also possible to avoid using this public static provider and pass it via the constructor of `ViewModelBase`.\n1. You're all set. You can start your app!\n\n## Features\nThe previous setup is pretty basic. Let's see what else we can do!\n\n### Dispatcher\nSet an `IDispatcher` to allow setting properties from any thread.\nThe `IDispatcher` ensures the `INotifyPropertyChanged.PropertyChanged` event is raised on the main thread.\nThis is optional, but you'll likely need it.\n\nFor WinUI or Uno.WinUI apps, install the `Chinook.DynamicMvvm.Uno.WinUI` nuget package.\nYou can then use `DispatcherQueueDispatcher` or `BatchingDispatcherQueueDispatcher`.\n```csharp\npublic MainPage()\n{\n    this.InitializeComponent();\n    DataContext = new MainPageViewModel()\n    {\n        Dispatcher = new DispatcherQueueDispatcher(this)\n    };\n}\n```\n\n#### Legacy\nFor UWP or Uno.UI apps, install the `Chinook.DynamicMvvm.Uno` nuget package.\nYou can then use `CoreDispatcherDispatcher` or `BatchingCoreDispatcherDispatcher`.\n```csharp\npublic MainPage()\n{\n    this.InitializeComponent();\n    DataContext = new MainPageViewModel()\n    {\n        Dispatcher = new CoreDispatcherDispatcher(this)\n    };\n}\n```\n\n### Create simple properties\nUsing `IViewModel.Get`, you can declare ViewModel properties that will raise the `INotifyPropertyChanged.PropertyChanged` event of the ViewModel when set.\nUnder the hood, an `IDynamicProperty` is lazy-initialized.\n\n\u003e 🔬 `IDynamicProperty` simply represents a property of a ViewModel.\n\u003e It has a name, a value, and an event to notify that the property's value changed.\n\u003e Having this interface is great because it allows the creation of custom implementations with various behaviors.\n\u003e You'll see that with the next sections of this document.\n```csharp\npublic string Content\n{\n  get =\u003e this.Get(initialValue: string.Empty);\n  set =\u003e this.Set(value);\n}\n```\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a property from value using the snippets `\"ckpropv\"` (**c**hinoo**k** **prop**erty from **v**alue) or `\"ckpropvg\"` (**c**hinoo**k** **prop**erty from **v**alue **g**et-only).\n\n\u003e 🎓 We like to call \"dynamic properties\" the properties of a ViewModel that are backed with a `IDynamicProperty`.\n\u003e You can still used _regular properties_ in your ViewModels, but they will not raise the `PropertyChanged` event automatically when they change.\n\u003e ```csharp\n\u003e public string Title =\u003e \"Hello\"; // Regular property\n\u003e public string Subtitle { get; } // Regular property\n\u003e \n\u003e public long Counter =\u003e this.GetFromObservable(ObserveTimer()); // Dynamic Property\n\u003e public bool IsFavorite // Dynamic property\n\u003e {\n\u003e   get =\u003e this.Get(initialValue: false);\n\u003e   set =\u003e this.Set(value);\n\u003e }\n\u003e ```\n\u003e You should prefer _regular properties_ over _dynamic properties_ for data that **never changes**, simply because dynamic properties allocate more memory.\n\n\n### Create properties from `IObservable\u003cT\u003e`\nIf you're familiar with [System.Reactive](https://github.com/dotnet/reactive), you'll probably like this.\nUsing `IViewModel.GetFromObservable`, you can declare a ViewModel property from an `IObservable\u003cT\u003e`.\nThe property automatically updates itself when the observable pushes a new value.\n```csharp\nusing System.Reactive.Linq;\n// (...)\npublic long Counter =\u003e this.GetFromObservable(Observable.Timer(\n  dueTime: TimeSpan.Zero,\n  period: TimeSpan.FromSeconds(1))\n);\t\t\n```\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a property from observable using the snippets `\"ckpropo\"` (**c**hinoo**k** **prop**erty from **o**bservable) or `\"ckpropog\"` (**c**hinoo**k** **prop**erty from **o**bservable **g**et-only).\n\n\u003e 💡 Consider using the overload that takes a `Func\u003cIObservable\u003cT\u003e\u003e` to avoid evaluating the observable every time the property is read and potentially save some memory allocations.\n\n### Create properties from `Task\u003cT\u003e`\nUsing `IViewModel.GetFromTask`, you can create a property that updates itself based on a `Task\u003cT\u003e` result.\n```csharp\n// This property is initialized with the value 10, but changes to 100 after 1 second.\npublic int Number =\u003e this.GetFromTask(async ct =\u003e\n{\n  await Task.Delay(1000, ct);\n  return 100;\n}, initialValue: 10);\n```\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a property from task using the snippets `\"ckpropt\"` (**c**hinoo**k** **prop**erty from **t**ask) or `\"ckproptg\"` (**c**hinoo**k** **prop**erty from **t**ask **g**et-only).\n\n### Decide whether you want a property setter\nThis could seem obvious, but any _`IDynamicProperty`-backed_ property can be _readonly_ by simply omitting the property setter.\n\n```csharp\npublic long Counter1 =\u003e this.GetFromObservable(ObserveTimer());\n\npublic long Counter2\n{\n  get =\u003e this.GetFromObservable(ObserveTimer());\n  set =\u003e this.Set(value);\n}\n\npublic long Counter3\n{\n  get =\u003e this.GetFromObservable(ObserveTimer());\n  private set =\u003e this.Set(value);\n}\n\nprivate IObservable\u003clong\u003e ObserveTimer() =\u003e Observable.Timer(\n  dueTime: TimeSpan.Zero,\n  period: TimeSpan.FromSeconds(1));\n\nprivate void SomeMethod()\n{\n  Counter1 = 0; // Doesn't build\n  Counter2 = 0; // Builds\n  Counter3 = 0; // Builds\n}\n```\nIn this code, all properties are updated from an observable. However, \n- `Counter1` can't be set manually.\n- `Counter2` can be set manually from anywhere (including `TwoWay` bindings).\n- `Counter3` can be set manually only from the ViewModel (which excludes `TwoWay` bindings).\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a get-only property using the `\"ckprop\"` snippets ending with `\"g\"` (for **g**et-only).\n\n### Access the underlying `IDynamicProperty` instance\nYou can access the backing `IDynamicProperty\u003cT\u003e` instance of any property from any ViewModel by using `IViewModel.GetProperty()`.\n```csharp\npublic MainPageViewModel()\n{\n  IDynamicProperty\u003cstring\u003e contentProperty;\n  contentProperty = this.GetProperty(vm =\u003e vm.Content);\n  // or\n  contentProperty = this.GetProperty\u003cstring\u003e(nameof(Content));\n}\n\npublic string Content\n{\n  get =\u003e this.Get(initialValue: string.Empty);\n  set =\u003e this.Set(value);\n}\n```\nYou can then interact with the property object itself.\n```csharp\ncontentProperty.Value = \"Hello\";\n// This sets the property value. It also raises the PropertyChanged event on the ViewModel.\n\ncontentProperty.ValueChanged += prop =\u003e Console.WriteLine($\"Property {prop.Name} changed to {prop.Value}.\");\n// This allows you to easily observe changes to a property value. Note that prop.Value is strongly type as a string here. \n```\n\u003e 💡 With the `IDynamicProperty` instance, even a _get-only_ property can be set manually.\n\u003e Use this knowledge responsively.\n\n#### Observe a property value using `IObservable`\nIf you are used to [System.Reactive](https://github.com/dotnet/reactive), you can install the `Chinook.DynamicMvvm.Reactive` package to benefit from some more extensions methods.\n```csharp\nIObservable\u003cstring\u003e observable;\nobservable = contentProperty.Observe(); // Gets an IObservable that yields when the value changes.\nobservable = contentProperty.GetAndObserve(); // Gets an IObservable that yields when the value changes and starts with the current value.\n```\n\n### Create commands from `Action` or `Action\u003cT\u003e`\nUsing `IViewModel.GetCommand`, you can create a command using an `Action`, or `Action\u003cT\u003e` when you want a command parameter.\n```csharp\npublic IDynamicCommand SayHi =\u003e this.GetCommand(() =\u003e\n{\n  Console.WriteLine(\"Hi\");\n});\n\npublic IDynamicCommand SaySomething =\u003e this.GetCommand\u003cstring\u003e(parameter =\u003e\n{\n  Console.WriteLine(parameter);\n});\n```\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a command from `Action` using the snippets `\"ckcmda\"` (**c**hinoo**k** **c**o**m**man**d** from **a**ction) or `\"ckcmdap\"` (**c**hinoo**k** **c**o**m**man**d**  from **a**ction with **p**arameter).\n\n### Create commands from an async method\nUsing `IViewModel.GetCommand`, you can create a `async` command using a `Func\u003cTask\u003e`, or `Func\u003cT, Task\u003e` when you want a command parameter.\n```csharp\npublic IDynamicCommand WaitASecond =\u003e this.GetCommandFromTask(async ct =\u003e\n{\n  await Task.Delay(1000, ct);\n});\n\npublic IDynamicCommand Wait =\u003e this.GetCommandFromTask\u003cint\u003e(async (ct, parameter) =\u003e\n{\n  await Task.Delay(TimeSpan.FromSeconds(parameter), ct);\n});\n```\n\u003e 💡 If you use [Chinook Snippets](https://marketplace.visualstudio.com/items?itemName=nventivecorp.ChinookSnippets), you can quickly generate a command from `Task` using the snippets `\"ckcmdt\"` (**c**hinoo**k** **c**o**m**man**d** from **t**ask) or `\"ckcmdtp\"` (**c**hinoo**k** **c**o**m**man**d**  from **t**ask with **p**arameter).\n\n\nThe provided `CancellationToken ct` is cancelled when the `IDynamicCommand` is disposed.\nThe command itself is disposed when the ViewModel is disposed.\nYou decide when the ViewModel gets disposed.\n\u003e 💡 Check out [Chinook.Navigation](https://github.com/nventive/Chinook.Navigation) if you want a ViewModel-based navigation system that automatically deals with disposing ViewModels.\n\n### Customize command behavior\nThe `IDynamicCommand` declarations come with an optional builder.\nYou can use this builder to customize the behavior of any command.\n\u003e 🔬The command implementation is done using a strategy pattern (very similarly to the [HTTP Message Handlers](https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers)).\n\u003e The builder simply accumulates strategies and chains them together when the command is built.\n\nYou can create a base configuration for all your commands at the factory level.\n```csharp\n.AddSingleton\u003cIDynamicCommandBuilderFactory\u003e(s =\u003e\n    new DynamicCommandBuilderFactory(builder =\u003e builder\n        .WithLogs(s.GetRequiredService\u003cILogger\u003cIDynamicCommand\u003e\u003e())\n        .OnBackgroundThread()\n))\n```\nYou can add more configuration at the command declaration level.\nThe builders are additive, meaning that the configuration at the command declaration level is applied after the one at the factory level.\n```csharp\npublic IDynamicCommand Submit =\u003e this.GetCommand(() =\u003e\n{\n  Result = Content;\n}, builder =\u003e builder\n  .SkipWhileExecuting()\n);\n```\n\u003e 🔬 For the previous code the strategy chain looks like this:\n\u003e ```\n\u003e - LoggerCommandStrategy (factory level)\n\u003e  - BackgroundCommandStrategy (factory level)\n\u003e   - SkipWhileExecutingCommandStrategy (command declaration level)\n\u003e    - ActionCommandStrategy, which actually executes the method (command declaration level)\n\u003e ```\n\u003e The strategy execution starts from the top and goes down the chain and then comes back up the chain for any subsequent processing.\n\u003e This means that strategies allow adding behavior both before and after the actual command execution.\n\n#### Supported strategies\n- [BackgroundCommandStrategy](src/DynamicMvvm/Command/Strategies/BackgroundCommandStrategy.cs) : Executes the command on a background thread.\n- [CanExecuteCommandStrategy](src/DynamicMvvm/Command/Strategies/CanExecuteCommandStrategy.cs) : Attaches the `CanExecute` to the value of a `IDynamicProperty`.\n- [ErrorHandlerCommandStrategy](src/DynamicMvvm/Command/Strategies/ErrorHandlerCommandStrategy.cs) : Catches any exception during the execution and delegates it to an error handler.\n- [LogCommandStrategy](src/DynamicMvvm/Command/Strategies/DynamicCommandWithLogger.cs) : Adds logs to the command execution.\n- [LockCommandStrategy](src/DynamicMvvm/Command/Strategies/LockCommandStrategy.cs) : Locks the command execution.\n- [CancelPreviousCommandStrategy](src/DynamicMvvm/Command/Strategies/CancelPreviousCommandStrategy.cs) : Cancels the previous command execution when executing the command.\n- [SkipWhileExecutingCommandStrategy](src/DynamicMvvm/Command/Strategies/SkipWhileExecutingCommandStrategy.cs) : Skips executions if the command is already executing.\n- [DisableWhileExecutingCommandStrategy](src/DynamicMvvm/Command/Strategies/DisableWhileExecutingCommandStrategy.cs) : Disables the command when it's executing.\n- [RaiseCanExecuteOnDispatcherCommandStrategy](src/DynamicMvvm/Command/Strategies/RaiseCanExecuteOnDispatcherCommandStrategy.cs) : Raises the `CanExecuteChanged` on the ViewModel's `IDispatcher`.\n\n### Observe whether a command is executing\n`IDynamicCommand` adds an `IsExecuting` property and an `IsExecutingChanged` event to the classic `System.Windows.Input.ICommand`.\n`IDynamicCommand` and also implements `INotifyPropertyChanged`, meaning that you can do a XAML binding on `IsExecuting`.\n\u003e 💡 This can be usefull if you want to add a loading indicator in your button's `ControlTemplate`.\n\n### Add child ViewModels\nUsing `IViewModel.GetChild`, you can declare an _inner ViewModel_ in an existing ViewModel.\nThis is great to extract repeating code or simply to separate concerns.\n\n```csharp\npublic SettingsViewModel Settings =\u003e this.GetChild\u003cSettingsViewModel\u003e();\n```\n\u003e ⚠ When creating child ViewModels, it's important to use the `GetChild`, `AttachChild`, or `AttachOrReplaceChild` methods to ensure linking the `IDispatcher` of the child to its parent.\n\u003e\n\u003e Consider the following code.\n\u003e ```csharp\n\u003e public SettingsViewModel Settings { get; } = new SettingsViewModel();\n\u003e ```\n\u003e This might seem to work at first, but know that the `IDispatcher` of  `Settings` is not set.\n\u003e Therefore, `PropertyChanged` events might not be raised on the correct thread, which could result in errors.\n\n#### ItemViewModels\nChild ViewModels are also very useful when using what we like to call _ItemViewModels_, meaning an item from a list.\nThis recipe is quite powerful when you want to change a property on a list item without updating the whole list itself.\n\nHere's an example where `ItemViewModel.IsFavorite` can be manipulated directly and any XAML binding to it will update as expected.\n```csharp\npublic class ItemViewModel : ViewModelBase\n{\n  public string Title { get; init; }\n\t\n  public bool IsFavorite\n  {\n    get =\u003e this.Get(initialValue: false);\n    set =\u003e this.Set(value);\n  }\n}\n\n// (...)\n\npublic MainPageViewModel()\n{\n  IEnumerable\u003cstring\u003e someSource = Enumerable\n    .Range(0, 10)\n    .Select(i =\u003e i.ToString());\n  Items = someSource\n    .Select(title =\u003e this.AttachChild(new ItemViewModel { Title = title }, name: title))\n    .ToArray();\n}\n\npublic ItemViewModel[] Items { get; }\n```\n\u003e 💡 ItemViewModels are great when individual list items change overtime.\n\u003e However, when your list items don't update themselves, you should probably avoid creating ItemViewModels.\n\n### Resolve services from a ViewModel\nUsing `IViewModel.GetService`, you can easily get a service from the service provider that you set.\nNote that the `IServiceProvider` is also directly exposed via `IViewModel.ServiceProvider`.\n```csharp\nvar logger = this.GetService\u003cILogger\u003cMainPageViewModel\u003e\u003e();\n```\n\n### Add disposables to a ViewModel\nUsing `IViewModel.AddDisposable`, you can add any `IDisposable` object to a `IViewModel`.\nWhen the ViewModel is disposed, all added disposables are disposed as well.\nYou can also get or remove previously added disposables using `IViewModel.TryGetDisposable` and `IViewModel.RemoveDisposable`.\n\n\u003e 💡 Adding disposables can be useful when subscribing to observables or events.\n\u003e You can easily setup the unsubscription to happen when the ViewModel is disposed.\n\u003e\n\u003e Check out [Chinook.Navigation](https://github.com/nventive/Chinook.Navigation) if you want a ViewModel-based navigation system that automatically deals with disposing ViewModels.\n\n\u003e 🔬 `IViewModel` can be seen as a dictionary of `IDisposable` objects.\n\u003e `IDynamicProperty` and `IDynamicCommand` both implement `IDisposable`, so that's how they're actually stored.\n\u003e It's the same thing for child ViewModels.\n\u003e This architecture contributes to the great extensibility of this library.\n\u003e You can see [Chinook.DataLoader](https://github.com/nventive/Chinook.DataLoader) as a demonstration of extensibility.\n\n### Add errors\n`IViewModel` implements `INotifyDataErrorInfo`.\nYou can use `IViewModel.SetErrors` and `IViewModel.ClearErrors` to manipulate the error info.\n\n### Add validation using [FluentValidations](https://fluentvalidation.net/)\nYou can install `Chinook.DynamicMvvm.FluentValidation` package to gain access to helpful extension methods.\nThe first step to add validation is to declare a validator on your ViewModel.\n```csharp\npublic class MainPageValidator : AbstractValidator\u003cMainPageViewModel\u003e\n{\n  public MainPageValidator()\n  {\n    RuleFor(vm =\u003e vm.Content).NotEmpty();\n  }\n}\n```\nYou can add all validators of your app to the service provider by using this line in your configuration.\n```csharp\nserviceCollection.AddValidatorsFromAssemblyContaining(typeof(App), ServiceLifetime.Singleton)\n```\nFor any dynamic property, you can use `IViewModel.AddValidation` to automatically run the validation rules when a property's value changes.\n```csharp\npublic MainPageViewModel()\n{\n  this.AddValidation(this.GetProperty(vm =\u003e vm.Content));\n}\n```\nYou can also run the validation manually.\n```csharp\npublic IDynamicCommand Submit =\u003e this.GetCommandFromTask(async ct =\u003e\n{\n  var result = await this.Validate(ct);\n  if (result.IsValid)\n  {\n    Result = Content;\n  }\n});\n```\n\u003c!-- TODO: Add doc about CollectionTracking --\u003e\n\n## Breaking Changes\n\nPlease consult [BREAKING_CHANGES.md](BREAKING_CHANGES.md) for more information about breaking changes and version history.\n\n## License\n\nThis project is licensed under the Apache 2.0 license - see the\n[LICENSE](LICENSE) file for details.\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the process for\ncontributing to this project.\n\nBe mindful of our [Code of Conduct](CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnventive%2Fchinook.dynamicmvvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnventive%2Fchinook.dynamicmvvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnventive%2Fchinook.dynamicmvvm/lists"}