{"id":23862756,"url":"https://github.com/ibebbs/mvx","last_synced_at":"2025-09-08T10:33:34.168Z","repository":{"id":49209045,"uuid":"214148804","full_name":"ibebbs/MVx","owner":"ibebbs","description":"Functional, Declarative and Reactive Extensions for MVVM \u0026 MVC patterns","archived":false,"fork":false,"pushed_at":"2021-09-02T20:15:59.000Z","size":59,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-25T21:05:34.693Z","etag":null,"topics":["reactive","rx","uwp","wpf","xaml"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ibebbs.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}},"created_at":"2019-10-10T10:03:22.000Z","updated_at":"2022-10-06T02:15:14.000Z","dependencies_parsed_at":"2022-09-15T23:41:37.138Z","dependency_job_id":null,"html_url":"https://github.com/ibebbs/MVx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ibebbs/MVx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibebbs%2FMVx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibebbs%2FMVx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibebbs%2FMVx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibebbs%2FMVx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ibebbs","download_url":"https://codeload.github.com/ibebbs/MVx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibebbs%2FMVx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274171238,"owners_count":25234847,"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-09-08T02:00:09.813Z","response_time":121,"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":["reactive","rx","uwp","wpf","xaml"],"created_at":"2025-01-03T07:23:49.925Z","updated_at":"2025-09-08T10:33:33.905Z","avatar_url":"https://github.com/ibebbs.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MVx - Model-View eXtensions\nA suite of libraries to simplify functional, declarative and reactive implementations of the [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel), [MVP](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) \u0026 [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) patterns.\n\n## MVx.Observable\nA (mostly) unopinionated, light-weight alternative to ReactiveUI provided as a library _not a framework_.\n\n### Getting Started\n\nInstall the [nuget package](https://www.nuget.org/packages/MVx.Observable/); net5.0 and .NETStandard 2.0 supported out of the box.\n\n### Example\n\nThe example below shows an MVVM style ViewModel for a typical log in page which exhibits the following behaviour:\n\n* When the user has entered a value in both the user name and password text boxes then enable the \"Log in\" button\n* When the user clicks the \"Log in\" button, asynchronously attempt to log in with the supplied credentials.\n* When a successful login attempt is made, emit a LogInSuccessful event\n* When an unsuccessful login attempt is made, display an error\n* When the user clicks the \"Cancel\" button, emit a LogInCancelled event\n\n```c#\npublic record LogInSuccessful(AuthenticationResponse AuthenticationResponse) {}\npublic record LogInCancelled {}\n\npublic class LogInPageViewModel : INotifyPropertyChanged\n{\n    private readonly IAuthenticationService _authenticationService;\n    private readonly MVx.Observable.IBus _eventBus;\n\n    private readonly MVx.Observable.Property\u003cstring\u003e _username;\n    private readonly MVx.Observable.Property\u003cstring\u003e _password;\n    private readonly MVx.Observable.Property\u003cstring\u003e _error;\n    private readonly MVx.Observable.Command _logInCommand;\n    private readonly MVx.Observable.Command _cancelCommand;\n\n    private readonly Subject\u003cAuthenticationResponse\u003e _logInResponse;\n    \n    public event PropertyChangedEventHandler PropertyChanged;\n\n    public LogInPageViewModel(IAuthenticationService authenticationService, MVx.Observable.IBus eventBus)\n    {\n        _authenticationService = authenticationService;\n        _eventBus = eventBus;\n\n        _username = new MVx.Observable.Property\u003cstring\u003e(nameof(Username), args =\u003e PropertyChanged?.Invoke(this, args));\n        _password = new MVx.Observable.Property\u003cstring\u003e(nameof(Password), args =\u003e PropertyChanged?.Invoke(this, args));\n        _error = new MVx.Observable.Property\u003cstring\u003e(nameof(Error), args =\u003e PropertyChanged?.Invoke(this, args));\n        _logInCommand = new MVx.Observable.Command(false);\n        _cancelCommand = new MVx.Observable.Command(true);\n\n        _logInResponse = new Subject\u003cAuthenticationResponse\u003e();\n    }\n\n    private IDisposable WhenTheUserHasEnteredBothUsernameAndPasswordThenEnableLogInButton()\n    {\n        return Observable\n            .CombineLatest(_username, _password, (username, password) =\u003e !string.IsNullOrWhiteSpace(username) \u0026\u0026 !string.IsNullOrWhiteSpace(password))\n            .Subscribe(_logInCommand);\n    }\n\n    private IDisposable WhenTheUserClicksTheLogInButtonAttemptToLogIn()\n    {\n        return _logInCommand\n            .SelectMany(_ =\u003e Observable.CombineLatest(_username, _password, (username, password) =\u003e new AuthenticationRequest(username, password)).Take(1))\n            .SelectMany(request =\u003e _authenticationService.AuthenticateAsync(request))\n            .Subscribe(_logInResponse);\n    }\n\n    private IDisposable WhenASuccessfulLogInAttemptIsMadeEmitALogInSuccessfulEvent()\n    {\n        return _logInResponse\n            .Where(response =\u003e response.Successful)\n            .Select(response =\u003e new LogInSuccessful(response))\n            .Subscribe(_eventBus.Publish);\n    }\n\n    private IDisposable WhenAnUnsuccessfulLogInAttemptIsMadeDisplayAnError()\n    {\n        return _logInResponse\n            .Where(response =\u003e !response.Successful)\n            .Select(response =\u003e response.Error.Message)\n            .Subscribe(_error);\n    }\n\n    private IDisposable WhenTheUserClicksTheCancelButtonEmitALogInCancelledEvent()\n    {\n        return _cancelCommand\n            .Select(_ =\u003e new LogInCancelled())\n            .Subscribe(_eventBus.Publish);\n    }\n\n    public IDisposable Activate()\n    {\n        return new CompositeDisposable(\n            WhenTheUserHasEnteredBothUsernameAndPasswordThenEnableLogInButton(),\n            WhenTheUserClicksTheLogInButtonAttemptToLogIn(),\n            WhenASuccessfulLogInAttemptIsMadeEmitALogInSuccessfulEvent(),\n            WhenAnUnsuccessfulLogInAttemptIsMadeDisplayAnError(),\n            WhenTheUserClicksTheCancelButtonEmitALogInCancelledEvent()\n        );\n    }\n\n    public string Username\n    {\n        get { return _username.Get(); }\n        set { _username.Set(value); }\n    }\n\n    public string Password\n    {\n        get { return _password.Get(); }\n        set { _password.Set(value); }\n    }\n\n    public string Error =\u003e _error.Get();\n\n    public ICommand LogInCommand =\u003e _logInCommand;\n\n    public ICommand CancelCommand =\u003e _cancelCommand;\n}\n```\n\nNote the following:\n\n1. There is no shared state as all variables are readonly. State is encapsulated in the underlying `MVx.Observable.Property` instances and composed through reactive pipelines.\n2. All behaviour is encapsulated in discrete \"WhenXThenY\" methods which can be easily modified without effecting other behaviours.\n3. Behaviour is not tied to the lifetime of the view model but to the lifetime of the `IDisposable` instance returned by the `Initialize` method.\n4. The boiler plate code required by `INotifyPropertyChanged` implementations is confined to a single point in the view models constructor.\n5. The view model is a POCO with no untoward constraints (i.e. a base class or specific interface implementation) placed on it by MVx.Observable\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fibebbs%2Fmvx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fibebbs%2Fmvx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fibebbs%2Fmvx/lists"}