{"id":31564260,"url":"https://github.com/gragra33/blazing.mvvm","last_synced_at":"2025-10-05T05:04:16.500Z","repository":{"id":163291457,"uuid":"638799680","full_name":"gragra33/Blazing.Mvvm","owner":"gragra33","description":"🔥 Blazing.Mvvm - Full MVVM support for Blazor with CommunityToolkit.Mvvm integration. Supports all hosting models (Server, WASM, SSR, Auto, Hybrid, MAUI). Features strongly-typed navigation, automatic ViewModel registration, parameter resolution, validation support, and comprehensive lifecycle management. Includes samples and full documentation.","archived":false,"fork":false,"pushed_at":"2025-09-04T23:24:52.000Z","size":493,"stargazers_count":77,"open_issues_count":2,"forks_count":15,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-09-19T08:33:29.434Z","etag":null,"topics":["blazor","blazor-server","blazor-webassembly","blazorhybrid","blazorssr","mvvm"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gragra33.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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":"2023-05-10T06:14:23.000Z","updated_at":"2025-09-16T09:21:47.000Z","dependencies_parsed_at":"2024-10-31T12:30:09.037Z","dependency_job_id":null,"html_url":"https://github.com/gragra33/Blazing.Mvvm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gragra33/Blazing.Mvvm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gragra33%2FBlazing.Mvvm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gragra33%2FBlazing.Mvvm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gragra33%2FBlazing.Mvvm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gragra33%2FBlazing.Mvvm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gragra33","download_url":"https://codeload.github.com/gragra33/Blazing.Mvvm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gragra33%2FBlazing.Mvvm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278411261,"owners_count":25982368,"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-10-05T02:00:06.059Z","response_time":54,"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":["blazor","blazor-server","blazor-webassembly","blazorhybrid","blazorssr","mvvm"],"created_at":"2025-10-05T05:03:25.667Z","updated_at":"2025-10-05T05:04:16.491Z","avatar_url":"https://github.com/gragra33.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# Blazor Extension for the MVVM CommunityToolkit\n\nThis project expands upon the [blazor-mvvm](https://github.com/IntelliTect-Samples/blazor-mvvm) repository by [Kelly Adams](https://github.com/adamskt), implementing full MVVM support via the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/). Enhancements include preventing cross-thread exceptions, adding extra base class types, MVVM-style navigation, and converting the project into a usable library.\n\n## Table of Contents\n\n\u003c!-- TOC --\u003e\n- [Blazor Extension for the MVVM CommunityToolkit](#blazor-extension-for-the-mvvm-communitytoolkit)\n  - [Table of Contents](#table-of-contents)\n  - [Quick Start](#quick-start)\n    - [Installation](#installation)\n      - [.NET CLI](#net-cli)\n      - [NuGet Package Manager](#nuget-package-manager)\n    - [Configuration](#configuration)\n      - [Registering ViewModels in a Different Assembly](#registering-viewmodels-in-a-different-assembly)\n    - [Usage](#usage)\n      - [Create a `ViewModel` inheriting the `ViewModelBase` class](#create-a-viewmodel-inheriting-the-viewmodelbase-class)\n      - [Create your Page inheriting the `MvvmComponentBase\u003cTViewModel\u003e` component](#create-your-page-inheriting-the-mvvmcomponentbasetviewmodel-component)\n  - [Give a ⭐](#give-a-)\n  - [Documentation](#documentation)\n    - [View Model](#view-model)\n      - [Lifecycle Methods](#lifecycle-methods)\n      - [Service Registration](#service-registration)\n        - [Registering ViewModels with Interfaces or Abstract Classes](#registering-viewmodels-with-interfaces-or-abstract-classes)\n        - [Registering Keyed ViewModels](#registering-keyed-viewmodels)\n      - [Parameter Resolution](#parameter-resolution)\n    - [MVVM Navigation](#mvvm-navigation)\n      - [Navigate by abstraction](#navigate-by-abstraction)\n    - [MVVM Validation](#mvvm-validation)\n  - [History](#history)\n    - [V2.0.0](#v200)\n\u003c!-- TOC --\u003e\n\n## Quick Start\n\n### Installation\n\nAdd the [Blazing.Mvvm](https://www.nuget.org/packages/Blazing.Mvvm) NuGet package to your project.\n\nInstall the package via .NET CLI or the NuGet Package Manager.\n\n#### .NET CLI\n\n```bash\ndotnet add package Blazing.Mvvm\n```\n\n#### NuGet Package Manager\n\n```powershell\nInstall-Package Blazing.Mvvm\n```\n\n### Configuration\n\nConfigure the library in your `Program.cs` file. The `AddMvvm` method will add the required services for the library and automatically register ViewModels that inherit from the `ViewModelBase`, `RecipientViewModelBase`, or `ValidatorViewModelBase` class in the calling assembly.\n\n```csharp\nusing Blazing.Mvvm;\n\nbuilder.Services.AddMvvm(options =\u003e\n{ \n    options.HostingModelType = BlazorHostingModelType.WebApp;\n});\n```\n\nIf you are using a different hosting model, set the `HostingModelType` property to the appropriate value. The available options are:\n\n- `BlazorHostingModelType.Hybrid`\n- `BlazorHostingModelType.Server`\n- `BlazorHostingModelType.WebApp`\n- `BlazorHostingModelType.WebAssembly`\n- `BlazorHostingModelType.HybridMaui`\n\n#### Registering ViewModels in a Different Assembly\n\nIf the ViewModels are in a different assembly, configure the library to scan that assembly for the ViewModels.\n\n```csharp\nusing Blazing.Mvvm;\n\nbuilder.Services.AddMvvm(options =\u003e\n{ \n    options.RegisterViewModelsFromAssemblyContaining\u003cMyViewModel\u003e();\n});\n\n// OR\n\nvar vmAssembly = typeof(MyViewModel).Assembly;\nbuilder.Services.AddMvvm(options =\u003e\n{ \n    options.RegisterViewModelsFromAssembly(vmAssembly);\n});\n```\n\n### Usage\n\n#### Create a `ViewModel` inheriting the `ViewModelBase` class\n\n```csharp\npublic partial class FetchDataViewModel : ViewModelBase\n{\n    private static readonly string[] Summaries = [\n        \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\n    ];\n\n    [ObservableProperty]\n    private ObservableCollection\u003cWeatherForecast\u003e _weatherForecasts = new();\n\n    public string Title =\u003e \"Weather forecast\";\n\n    public override void OnInitialized()\n        =\u003e WeatherForecasts = new ObservableCollection\u003cWeatherForecast\u003e(Get());\n\n    private IEnumerable\u003cWeatherForecast\u003e Get()\n    {\n        return Enumerable.Range(1, 5).Select(index =\u003e new WeatherForecast\n        {\n            Date = DateTime.Now.AddDays(index),\n            TemperatureC = Random.Shared.Next(-20, 55),\n            Summary = Summaries[Random.Shared.Next(Summaries.Length)]\n        });\n    }\n}\n```\n\n#### Create your Page inheriting the `MvvmComponentBase\u003cTViewModel\u003e` component\n\n\u003e ***NOTE:*** If working with repositories, database services, etc, that require a scope, then use `MvvmOwningComponentBase\u003cTViewModel\u003e` instead.\n\n```xml\n@page \"/fetchdata\"\n@inherits MvvmOwningComponentBase\u003cFetchDataViewModel\u003e\n\n\u003cPageTitle\u003e@ViewModel.Title\u003c/PageTitle\u003e\n\n\u003ch1\u003e@ViewModel.Title\u003c/h1\u003e\n\n@if (!ViewModel.WeatherForecasts.Any())\n{\n    \u003cp\u003e\u003cem\u003eLoading...\u003c/em\u003e\u003c/p\u003e\n}\nelse\n{\n    \u003ctable class=\"table\"\u003e\n        \u003cthead\u003e\n            \u003ctr\u003e\n                \u003cth\u003eDate\u003c/th\u003e\n                \u003cth\u003eTemp. (C)\u003c/th\u003e\n                \u003cth\u003eTemp. (F)\u003c/th\u003e\n                \u003cth\u003eSummary\u003c/th\u003e\n            \u003c/tr\u003e\n        \u003c/thead\u003e\n        \u003ctbody\u003e\n            @foreach (var forecast in ViewModel.WeatherForecasts)\n            {\n                \u003ctr\u003e\n                    \u003ctd\u003e@forecast.Date.ToShortDateString()\u003c/td\u003e\n                    \u003ctd\u003e@forecast.TemperatureC\u003c/td\u003e\n                    \u003ctd\u003e@forecast.TemperatureF\u003c/td\u003e\n                    \u003ctd\u003e@forecast.Summary\u003c/td\u003e\n                \u003c/tr\u003e\n            }\n        \u003c/tbody\u003e\n    \u003c/table\u003e\n}\n```\n\n## Give a ⭐\n\nIf you like or are using this project to learn or start your solution, please give it a star. Thanks!\n\nAlso, if you find this library useful, and you're feeling really generous, then please consider [buying me a coffee ☕](https://bmc.link/gragra33).\n\n## Documentation\n\nThe Library supports the following hosting models:\n\n- Blazor Server App\n- Blazor WebAssembly App (WASM)\n- Blazor Web App (.NET 8.0+)\n- Blazor Hybrid - Wpf, WinForms, MAUI, and Avalonia (Windows only)\n\nThe library package includes:\n\n- `MvvmComponentBase`, `MvvmOwningComponentBase` (Scoped service support), \u0026 `MvvmLayoutComponentBase` for quick and easy wiring up ViewModels.\n- `ViewModelBase`, `RecipientViewModelBase`, \u0026 `ValidatorViewModelBase` wrappers for the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/).\n- `MvvmNavigationManager` class, `MvvmNavLink`, and `MvvmKeyNavLink` component for MVVM-style navigation, no more hard-coded paths.\n- Sample applications for getting started quickly with all hosting models.\n\nThere are two additional sample projects in separate GitHub repositories:\n\n1. [Blazor MVVM Sample](https://github.com/gragra33/MvvmSampleBlazor) - takes Microsoft's [Xamarin Sample](https://github.com/CommunityToolkit/MVVM-Samples) project for the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) and converts it to: Blazor Wasm \u0026 Blazor Hybrid for Wpf \u0026 Avalonia. Minimal changes were made.\n2. [Dynamic Parent and Child](https://github.com/gragra33/Blazing.Mvvm.ParentChildSample) - demonstrates loose coupling of a parent component/page and an unknown number of child components using [Messenger](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger) for interactivity.\n\n### View Model\n\nThe library offers several base classes that extend the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) base classes:\n\n- `ViewModelBase`: Inherits from the [`ObservableObject`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observableobject) class.\n- `RecipientViewModelBase`: Inherits from the [`ObservableRecipient`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablerecipient) class.\n- `ValidatorViewModelBase`: Inherits from the [`ObservableValidator`](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablevalidator) class and supports the `EditForm` component.\n\n#### Lifecycle Methods\n\nThe `ViewModelBase`, `RecipientViewModelBase`, and `ValidatorViewModelBase` classes support the `ComponentBase` lifecycle methods, which are invoked when the corresponding `ComponentBase` method is called:\n\n- `OnAfterRender`\n- `OnAfterRenderAsync`\n- `OnInitialized`\n- `OnInitializedAsync`\n- `OnParametersSet`\n- `OnParametersSetAsync`\n- `ShouldRender`\n\n#### Service Registration\n\nViewModels are registered as `Transient` services by default. If you need to register a ViewModel with a different service lifetime (Scoped, Singleton, Transient), use the `ViewModelDefinition` attribute:\n\n```csharp\n[ViewModelDefinition(Lifetime = ServiceLifetime.Scoped)]\npublic partial class FetchDataViewModel : ViewModelBase\n{\n    // ViewModel code\n}\n```\n\nIn the `View` component, inherit the `MvvmComponentBase` type and set the generic argument to the `ViewModel`:\n\n```xml\n@page \"/fetchdata\"\n@inherits MvvmComponentBase\u003cFetchDataViewModel\u003e\n```\n\n##### Registering ViewModels with Interfaces or Abstract Classes\n\nTo register the `ViewModel` with a specific interface or abstract class, use the `ViewModelDefinition` generic attribute:\n\n```csharp\n[ViewModelDefinition\u003cIFetchDataViewModel\u003e]\npublic partial class FetchDataViewModel : ViewModelBase, IFetchDataViewModel\n{\n    // ViewModel code\n}\n```\n\nIn the `View` component, inherit the `MvvmComponentBase` type and set the generic argument to the interface or abstract class:\n\n```xml\n@page \"/fetchdata\"\n@inherits MvvmComponentBase\u003cIFetchDataViewModel\u003e\n```\n\n##### Registering Keyed ViewModels\n\nTo register the `ViewModel` as a keyed service, use the `ViewModelDefinition` attribute (this also applies to generic variant) and set the `Key` property:\n\n```csharp\n[ViewModelDefinition(Key = \"FetchDataViewModel\")]\npublic partial class FetchDataViewModel : ViewModelBase\n{\n    // ViewModel code\n}\n```\n\nIn the `View` component, use the `ViewModelKey` attribute to specify the key of the `ViewModel`:\n\n```xml\n@page \"/fetchdata\"\n@attribute [ViewModelKey(\"FetchDataViewModel\")]\n@inherits MvvmComponentBase\u003cFetchDataViewModel\u003e\n```\n\n#### Parameter Resolution\n\nThe library supports passing parameter values to the `ViewModel` which are defined in the `View`.\n\nThis feature is opt-in. To enable it, set the `ParameterResolutionMode` property to `ViewAndViewModel` in the `AddMvvm` method. This will resolve parameters in both the `View` component and the `ViewModel`.\n\n```csharp\nbuilder.Services.AddMvvm(options =\u003e\n{ \n    options.ParameterResolutionMode = ParameterResolutionMode.ViewAndViewModel;\n});\n```\n\nTo resolve parameters in the `ViewModel` only, set the `ParameterResolutionMode` property value to `ViewModel`.\n\nProperties in the `ViewModel` that should be set must be marked with the `ViewParameter` attribute.\n\n```csharp\npublic partial class SampleViewModel : ViewModelBase\n{\n    [ObservableProperty]\n    [property: ViewParameter]\n    private string _title;\n\n    [ViewParameter]\n    public int Count { get; set; }\n\n    [ViewParameter(\"Content\")]\n    private string Body { get; set; }\n}\n```\n\nIn the `View` component, the parameters should be defined as properties with the `Parameter` attribute:\n\n```xml\n@inherits MvvmComponentBase\u003cSampleViewModel\u003e\n\n@code {\n    [Parameter]\n    public string Title { get; set; }\n\n    [Parameter]\n    public int Count { get; set; }\n\n    [Parameter]\n    public string Content { get; set; }\n}\n```\n\n### MVVM Navigation\n\nNo more magic strings! Strongly-typed navigation is now possible. If the page URI changes, you no longer need to search through your source code to make updates. It is auto-magically resolved at runtime for you!\n\nWhen the `MvvmNavigationManager` is initialized by the IOC container as a Singleton, the class examines all assemblies and internally caches all ViewModels (classes and interfaces) along with their associated pages.\n\nWhen navigation is required, a quick lookup is performed, and the Blazor `NavigationManager` is used to navigate to the correct page. Any relative URI or query string passed via the `NavigateTo` method call is also included.\n\n\u003e **Note:** The `MvvmNavigationManager` class is not a complete replacement for the Blazor `NavigationManager` class; it only adds support for MVVM.\n\n**Modify the `NavMenu.razor` to use `MvvmNavLink`:**\n\n```xml\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmNavLink class=\"nav-link\" TViewModel=\"FetchDataViewModel\"\u003e\n        \u003cspan class=\"oi oi-list-rich\" aria-hidden=\"true\"\u003e\u003c/span\u003e Fetch data\n    \u003c/MvvmNavLink\u003e\n\u003c/div\u003e\n```\n\n\u003e The `MvvmNavLink` component is based on the Blazor `NavLink` component and includes additional `TViewModel` and `RelativeUri` properties. Internally, it uses the `MvvmNavigationManager` for navigation.\n\n**Navigate by ViewModel using the `MvvmNavigationManager` from code:**\n\nInject the `MvvmNavigationManager` class into your page or ViewModel, then use the `NavigateTo` method:\n\n```csharp\nmvvmNavigationManager.NavigateTo\u003cFetchDataViewModel\u003e();\n```\n\nThe `NavigateTo` method works the same as the standard Blazor `NavigationManager` and also supports passing a relative URL and/or query string.\n\n#### Navigate by abstraction\n\nIf you prefer abstraction, you can also navigate by interface as shown below:\n\n```csharp\nmvvmNavigationManager.NavigateTo\u003cITestNavigationViewModel\u003e();\n```\n\nThe same principle works with the `MvvmNavLink` component:\n\n```xml\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmNavLink class=\"nav-link\"\n                 TViewModel=ITestNavigationViewModel\n                 Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003eTest\n    \u003c/MvvmNavLink\u003e\n\u003c/div\u003e\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmNavLink class=\"nav-link\"\n                 TViewModel=ITestNavigationViewModel\n                 RelativeUri=\"this is a MvvmNavLink test\"\n                 Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003eTest + Params\n    \u003c/MvvmNavLink\u003e\n\u003c/div\u003e\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmNavLink class=\"nav-link\"\n                 TViewModel=ITestNavigationViewModel\n                 RelativeUri=\"?test=this%20is%20a%20MvvmNavLink%20querystring%20test\"\n                 Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003eTest + QueryString\n    \u003c/MvvmNavLink\u003e\n\u003c/div\u003e\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmNavLink class=\"nav-link\"\n                 TViewModel=ITestNavigationViewModel\n                 RelativeUri=\"this is a MvvmNvLink test/?test=this%20is%20a%20MvvmNavLink%20querystring%20test\"\n                 Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003eTest + Both\n    \u003c/MvvmNavLink\u003e\n\u003c/div\u003e\n```\n\n**Navigate by ViewModel Key using the `MvvmNavigationManager` from code:**\n\nInject the `MvvmNavigationManager` class into your page or ViewModel, then use the `NavigateTo` method:\n\n```csharp\nMvvmNavigationManager.NavigateTo(\"FetchDataViewModel\");\n```\n\nThe same principle works with the `MvvmKeyNavLink` component:\n\n```xml\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmKeyNavLink class=\"nav-link\"\n                    NavigationKey=\"@nameof(TestKeyedNavigationViewModel)\"\n                    Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003e Keyed Test\n    \u003c/MvvmKeyNavLink\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmKeyNavLink class=\"nav-link\"\n                    NavigationKey=\"@nameof(TestKeyedNavigationViewModel)\"\n                    RelativeUri=\"this is a MvvmKeyNavLink test\"\n                    Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003e Keyed + Params\n    \u003c/MvvmKeyNavLink\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmKeyNavLink class=\"nav-link\"\n                    NavigationKey=\"@nameof(TestKeyedNavigationViewModel)\"\n                    RelativeUri=\"?test=this%20is%20a%20MvvmKeyNavLink%20querystring%20test\"\n                    Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003e Keyed + QueryString\n    \u003c/MvvmKeyNavLink\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"nav-item px-3\"\u003e\n    \u003cMvvmKeyNavLink class=\"nav-link\"\n                    NavigationKey=\"@nameof(TestKeyedNavigationViewModel)\"\n                    RelativeUri=\"this is a MvvmKeyNavLink test/?test=this%20is%20a%20MvvmKeyNavLink%20querystring%20test\"\n                    Match=\"NavLinkMatch.All\"\u003e\n        \u003cspan class=\"oi oi-calculator\" aria-hidden=\"true\"\u003e\u003c/span\u003e Keyed + Both\n    \u003c/MvvmKeyNavLink\u003e\n\u003c/div\u003e\n```\n\n### MVVM Validation\n\nThe library provides an `MvvmObservableValidator` component that works with the `EditForm` component to enable validation using the `ObservableValidator` class from the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/) library.\n\nThe following example demonstrates how to use the `MvvmObservableValidator` component with the `EditForm` component to perform validation.\n\n**First, define a class that inherits from the `ObservableValidator` class and contains properties with validation attributes:**\n\n```csharp\npublic class ContactInfo : ObservableValidator\n{\n    private string? _name;\n\n    [Required]\n    [StringLength(100, MinimumLength = 2, ErrorMessage = \"The {0} field must have a length between {2} and {1}.\")]\n    [RegularExpression(@\"^[a-zA-Z\\s'-]+$\", ErrorMessage = \"The {0} field contains invalid characters. Only letters, spaces, apostrophes, and hyphens are allowed.\")]\n    public string? Name\n    {\n        get =\u003e _name;\n        set =\u003e SetProperty(ref _name, value, true);\n    }\n\n    private string? _email;\n\n    [Required]\n    [EmailAddress]\n    public string? Email\n    {\n        get =\u003e _email;\n        set =\u003e SetProperty(ref _email, value, true);\n    }\n\n    private string? _phoneNumber;\n\n    [Required]\n    [Phone]\n    [Display(Name = \"Phone Number\")]\n    public string? PhoneNumber\n    {\n        get =\u003e _phoneNumber;\n        set =\u003e SetProperty(ref _phoneNumber, value, true);\n    }\n}\n```\n\n**Next, in the `ViewModel` component, define the property that will hold the object to be validated and the methods that will be called when the form is submitted:**\n\n```csharp\npublic sealed partial class EditContactViewModel : ViewModelBase, IDisposable\n{\n    private readonly ILogger\u003cEditContactViewModel\u003e _logger;\n\n    [ObservableProperty]\n    private ContactInfo _contact = new();\n\n    public EditContactViewModel(ILogger\u003cEditContactViewModel\u003e logger)\n    {\n        _logger = logger;\n        Contact.PropertyChanged += ContactOnPropertyChanged;\n    }\n\n    public void Dispose()\n        =\u003e Contact.PropertyChanged -= ContactOnPropertyChanged;\n\n    [RelayCommand]\n    private void ClearForm()\n        =\u003e Contact = new ContactInfo();\n\n    [RelayCommand]\n    private void Save()\n        =\u003e _logger.LogInformation(\"Form is valid and submitted!\");\n\n    private void ContactOnPropertyChanged(object? sender, PropertyChangedEventArgs e)\n        =\u003e NotifyStateChanged();\n}\n```\n\n**Finally, in the `View` component, use the `EditForm` component with the `MvvmObservableValidator` component to enable validation:**\n\n```xml\n@page \"/form\"\n@inherits MvvmComponentBase\u003cEditContactViewModel\u003e\n\n\u003cEditForm Model=\"ViewModel.Contact\" FormName=\"EditContact\" OnValidSubmit=\"ViewModel.SaveCommand.Execute\"\u003e\n    \u003cMvvmObservableValidator /\u003e\n    \u003cValidationSummary /\u003e\n\n    \u003cdiv class=\"row g-3\"\u003e\n        \u003cdiv class=\"col-12\"\u003e\n            \u003clabel class=\"form-label\"\u003eName:\u003c/label\u003e\n            \u003cInputText aria-label=\"name\" @bind-Value=\"ViewModel.Contact.Name\" class=\"form-control\" placeholder=\"Some Name\"/\u003e\n            \u003cValidationMessage For=\"() =\u003e ViewModel.Contact.Name\" /\u003e\n        \u003c/div\u003e\n\n        \u003cdiv class=\"col-12\"\u003e\n            \u003clabel class=\"form-label\"\u003eEmail:\u003c/label\u003e\n            \u003cInputText aria-label=\"email\" @bind-Value=\"ViewModel.Contact.Email\" class=\"form-control\" placeholder=\"user@domain.tld\"/\u003e\n            \u003cValidationMessage For=\"() =\u003e ViewModel.Contact.Email\" /\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"col-12\"\u003e\n            \u003clabel class=\"form-label\"\u003ePhone Number:\u003c/label\u003e\n            \u003cInputText aria-label=\"phone number\" @bind-Value=\"ViewModel.Contact.PhoneNumber\" class=\"form-control\" placeholder=\"555-1212\"/\u003e\n            \u003cValidationMessage For=\"() =\u003e ViewModel.Contact.PhoneNumber\" /\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\n    \u003chr class=\"my-4\"\u003e\n\n    \u003cdiv class=\"row\"\u003e\n        \u003cbutton class=\"btn btn-primary btn-lg col\"\n                type=\"submit\"\n                disabled=\"@ViewModel.Contact.HasErrors\"\u003e\n        Save\n        \u003c/button\u003e\n        \u003cbutton class=\"btn btn-secondary btn-lg col\"\n                type=\"button\" \n                @onclick=\"ViewModel.ClearFormCommand.Execute\"\u003e\n            Clear Form\n        \u003c/button\u003e\n    \u003c/div\u003e\n\u003c/EditForm\u003e  \n```\n\n## History\n\n### V2.2.0 7 December, 2024\n\n- Added support for `ObservableRecipient` being set to inactive when disposing the `MvvmComponentBase`, `MvvmOwningComponentBase`, `MvvmLayoutComponentBase`, and `RecipientViewModelBase`. [@gragra33](https://github.com/gragra33) \u0026 [@teunlielu](https://github.com/teunlielu)\n\n### V2.1.1 4 December, 2024\n\n- Version bump to fix a nuget release issue\n\n### V2.1.0 3 December, 2024\n\n- Added MAUI Blazor Hybrid App support + sample HybridMaui app. [@hakakou](https://github.com/hakakou)\n\n### V2.0.0 30 November, 2024\n\nThis is a major release with breaking changes, migration notes can be found [here](docs/migration-notes/v1.4_to_v2.md).\n\n- Added auto registration and discovery of view models. [@mishael-o](https://github.com/mishael-o)\n- Added support for keyed view models. [@mishael-o](https://github.com/mishael-o)\n- Added support for keyed view models to `MvvmNavLink`, `MvvmKeyNavLink` (new component), `MvvmNavigationManager`, `MvvmComponentBase`, `MvvmOwningComponentBase`, \u0026 `MvvmLayoutComponentBase`. [@gragra33](https://github.com/gragra33)\n- Added a `MvvmObservableValidator` component which provides support for `ObservableValidator`. [@mishael-o](https://github.com/mishael-o)\n- Added parameter resolution in the ViewModel. [@mishael-o](https://github.com/mishael-o)\n- Added new `TestKeyedNavigation` samples for Keyed Navigation. [@gragra33](https://github.com/gragra33)\n- Added \u0026 Updated tests for all changes made. [@mishael-o](https://github.com/mishael-o) \u0026 [@gragra33](https://github.com/gragra33)\n- Added support for .NET 9. [@gragra33](https://github.com/gragra33)\n- Dropped support for .NET 7. [@mishael-o](https://github.com/mishael-o)\n- Documentation updates. [@mishael-o](https://github.com/mishael-o) \u0026 [@gragra33](https://github.com/gragra33)\n\n**BREAKING CHANGES:**\n\n- Renamed `BlazorHostingModel` to `BlazorHostingModelType` to avoid confusion\n\nThe full history can be found in the [Version Tracking](https://github.com/gragra33/Blazing.Mvvm/HISTORY.md) documentation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgragra33%2Fblazing.mvvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgragra33%2Fblazing.mvvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgragra33%2Fblazing.mvvm/lists"}