{"id":26265665,"url":"https://github.com/baseflow/guardedactions","last_synced_at":"2025-10-26T04:33:08.790Z","repository":{"id":66114952,"uuid":"272186462","full_name":"Baseflow/GuardedActions","owner":"Baseflow","description":"A library to increase the error handling, testability and reusability for all your MVVM driven apps!","archived":false,"fork":false,"pushed_at":"2020-08-20T13:59:26.000Z","size":782,"stargazers_count":13,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"develop","last_synced_at":"2025-04-28T23:09:04.367Z","etag":null,"topics":["commands","mvvm","netcore","xamarin","xamarinforms"],"latest_commit_sha":null,"homepage":"https://baseflow.com","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/Baseflow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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},"funding":{"github":"Baseflow","custom":"https://baseflow.com/contact"}},"created_at":"2020-06-14T11:15:46.000Z","updated_at":"2024-03-29T10:20:42.000Z","dependencies_parsed_at":"2023-02-20T17:30:57.328Z","dependency_job_id":null,"html_url":"https://github.com/Baseflow/GuardedActions","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Baseflow%2FGuardedActions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Baseflow%2FGuardedActions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Baseflow%2FGuardedActions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Baseflow%2FGuardedActions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Baseflow","download_url":"https://codeload.github.com/Baseflow/GuardedActions/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251747763,"owners_count":21637404,"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","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":["commands","mvvm","netcore","xamarin","xamarinforms"],"created_at":"2025-03-14T03:14:35.632Z","updated_at":"2025-10-26T04:33:08.711Z","avatar_url":"https://github.com/Baseflow.png","language":"C#","funding_links":["https://github.com/sponsors/Baseflow","https://baseflow.com/contact"],"categories":[],"sub_categories":[],"readme":"# GuardedActions\nA library to :rocket: increase the :x: error handling, :test_tube: testability and :recycle: reusability for all your MVVM driven apps! \n\n- [Why should I use it?](#so-why-should-i-use-it)\n- [Which IoC containers are supported?](#supported-ioc-containers)\n- [How do I install this library?](#installation)\n- [Where can I find some sample projects?](https://github.com/Baseflow/GuardedActions.Samples)\n\n## So why should I use it?\nLike said before this library helps your to increase the error handling, testability and reusability of all of your MVVM driven apps. Now let's see how exactly this library will help you with all of these things!\n\n- [How does it help you with reusability?](#reusability)\n- [How does it help you with testability?](#testability)\n- [How does it help you with error handling](#error-handling)\n\n### Reusability\n\nThe command builders make it really easy to reuse commands troughout multiple view models without having to create some kind of base class like shown below: \n\n**Classic example:**\n```csharp\npublic class ViewModelA : SharedViewModel\n{\n}\n\npublic class ViewModelB : SharedViewModel\n{\n}\n\npublic class SharedViewModel : BaseViewModel\n{\n    private ICommand _scanCommand;\n    public ICommand ScanCommand =\u003e _scanCommand ??= new Command(Scan);\n    private void Scan()\n    {\n        ...\n    }\n}\n```\n\nThe solution above could cause some issues in bigger projects. In these projects people tend to reuse specific commands. The most common approach we see people take is creating a base class containing all the shared commands. \n\nNow in big projects this shared class tend to grow quickly and become a big 'spagetti' class with all kind of commands for different purposes.. \n\nNot really following the separation of concerns design priciples.. \n\nSo, this means that if you only need one or a couple of the commands you'll need to inherit the entire base class with all kinds of stuff you don't want/need or you'll create a copy with only those commands you like to use. But keep in mind when you copy past commands you don't reuse them.. \n\nSo with the Guarded actions you could solve this issue by creating `CommandBuilders` which can be loaded trough DI and so are reuseable. Note: you don't need to register the builders yourself if you [install](#installation) the `GuardedActions` library correctly it'll register and resolve the builders automatically.\n\n**GuardedAction example:**\n\n```csharp\n    public class ViewModelA : BaseViewModel, IScannable\n    {\n        private readonly IScanCommandBuilder _scanCommandBuilder;\n        \n        private ICommand _scanCommand;\n        public ICommand ScanCommand =\u003e _scanCommand ??= _scanCommandBuilder?.RegisterDataContext(this).BuildCommand()\n        \n        public ViewModelA(IScanCommandBuilder scanCommandBuilder)\n        {\n            _scanCommandBuilder = scanCommandBuilder ?? throw new ArgumentNullException(nameof(scanCommandBuilder));\n        }\n    }\n    \n    public class ViewModelB : BaseViewModel, IScannable\n    {\n        private readonly IScanCommandBuilder _scanCommandBuilder;\n        \n        private ICommand _scanCommand;\n        public ICommand ScanCommand =\u003e _scanCommand ??= _scanCommandBuilder?.RegisterDataContext(this).BuildCommand()\n        \n        public ViewModelA(IScanCommandBuilder scanCommandBuilder)\n        {\n            _scanCommandBuilder = scanCommandBuilder ?? throw new ArgumentNullException(nameof(scanCommandBuilder));\n        }\n    }\n    \n    public class ScanCommandBuilder : AsyncGuardedDataContextCommandBuilder\u003cIScannable\u003e, IScanCommandBuilder\n    {\n        protected override Task ExecuteCommandAction()\n        {\n            // 1. Multiple actions can be added handling the scanning feature\n            // 2. The DataContext could be modified directly. This is in this case the ViewModel but accessed trough it's IScannable contract.\n            return Task.CompletedTask;\n        }\n    }\n```\n\n### Testability\nIf you use this library in the way it's designed to be used you should end up having loads of small building blocks:\n\n - ViewModels\n - Command(Builder)s\n - Actions\n - ErrorHandlers\n\nEach of these buidling blocks should be isolated, loosely coupled and obey the seperation of concerne design priciples. This will mean that you only have to write really small tests with a clear goal wich will greatly improve the testability in your project.\n\nSo, for example if you seperate all the commands from the view models the view model should only contain some bindable properties with the pupose of storing and displaying data on the view. This results in the view model having one clear goal and that one clear goal becomes easily testable. See the example below:\n\n**GuardedAction - View model example**:\n```csharp\npublic class MainViewModel : BaseViewModel\n{\n    private string _errorMessage;\n\n    private ObservableCollection\u003cDownload\u003e _downloads;\n\n    private readonly IInitializeCommandBuilder _initializeCommandBuilder;\n    private readonly IDownloadAllCommandBuilder _downloadAllCommandBuilder;\n\n    private ICommand _initializeCommand;\n    private ICommand _downloadAllCommand;\n\n    public MainViewModel(IInitializeCommandBuilder initializeCommandBuilder, IDownloadAllCommandBuilder downloadAllCommandBuilder)\n    {\n        _initializeCommandBuilder = initializeCommandBuilder ?? throw new ArgumentNullException(nameof(initializeCommandBuilder));\n        _downloadAllCommandBuilder = downloadAllCommandBuilder ?? throw new ArgumentNullException(nameof(downloadAllCommandBuilder));\n\n        InitializeCommand.Execute(null);\n    }\n\n    public ICommand InitializeCommand =\u003e _initializeCommand ??= _initializeCommandBuilder?.RegisterDataContext(this).BuildCommand();\n    public ICommand DownloadAllCommand =\u003e _downloadAllCommand ??= _downloadAllCommandBuilder?.RegisterDataContext(this).BuildCommand();\n\n    public bool ShowErrorMessage =\u003e ErrorMessage != null;\n\n    public string ErrorMessage\n    {\n        get =\u003e _errorMessage;\n        set =\u003e SetProperty(ref _errorMessage, value, () =\u003e RaisePropertyChanged(nameof(ShowErrorMessage)));\n    }\n\n    public ObservableCollection\u003cDownload\u003e Downloads\n    {\n        get =\u003e _downloads;\n        set =\u003e SetProperty(ref _downloads, value);\n    }\n}\n```\n\n**GuardedAction - View model test example**:\n```csharp\n[Fact]\npublic void MainViewModel_InheritsBaseViewModel()\n{\n    // Arrange\n    var baseViewModelType = typeof(BaseViewModel);\n    var mainViewModelType = typeof(MainViewModel);\n\n    // Act\n    var result = baseViewModelType.IsAssignableFrom(mainViewModelType);\n\n    // Assert\n    Assert.True(result);\n}\n\n[Fact]\npublic void MainViewModel_CheckNotifyPropertyChanged()\n{\n    // Arrange\n    var initializeCommandBuilder = Mock.Of\u003cIInitializeCommandBuilder\u003e();\n    var downloadAllCommandBuilder = Mock.Of\u003cIDownloadAllCommandBuilder\u003e();\n    var viewModel = new MainViewModel(initializeCommandBuilder, downloadAllCommandBuilder);\n\n    var lastChangedPropertyName = string.Empty;\n    viewModel.PropertyChanged += (sender, args) =\u003e lastChangedPropertyName = args.PropertyName;\n\n    // Act\n    viewModel.Downloads = new ObservableCollection\u003cDownload\u003e();\n\n    // Assert\n    Assert.Equal(nameof(viewModel.Downloads), lastChangedPropertyName);\n}\n\n[Fact]\n// Check if the view receives a notification that also the ShowErrorMessage has been updated when setting the value of the ErrorMessage\npublic void MainViewModel_EditErrorMessage()\n{\n    // Arrange\n    var initializeCommandBuilder = Mock.Of\u003cIInitializeCommandBuilder\u003e();\n    var downloadAllCommandBuilder = Mock.Of\u003cIDownloadAllCommandBuilder\u003e();\n    var viewModel =  new MainViewModel(initializeCommandBuilder, downloadAllCommandBuilder);\n\n    var propertyChanges = new List\u003cstring\u003e();\n    viewModel.PropertyChanged += (sender, args) =\u003e propertyChanges.Add(args.PropertyName);\n\n    // Act\n    viewModel.ErrorMessage = \"Foo\";\n\n    // Assert\n    Assert.Equal(2, propertyChanges.Count);\n    Assert.Contains(nameof(viewModel.ErrorMessage), propertyChanges);\n    Assert.Contains(nameof(viewModel.ShowErrorMessage), propertyChanges);\n}\n```\n\n### Error handling\nWe all know how annoying it is when an app crashes and you've to start it again and navigate to the specific page that you were working on.. \n\nWell, in this kind of situation the `GuardedActions` library also comes in handy! \n\nBecause the `GuardedActions` library executes your command/action code trough the `ExceptionGuard` which automatically wrappes your command or action code in a try catch block.\n\n```csharp\npublic async Task Guard(object sender, Func\u003cTask\u003e job, Func\u003cTask\u003e onFinally = null)\n{\n    try\n    {\n        if (job == null)\n            throw new InvalidOperationException($\"{GetType().FullName}.{nameof(Guard)}(): The {nameof(job)} provided cannot be null.\");\n\n        await job();\n    }\n    catch (Exception exception)\n    {\n        await HandleException(sender, exception);\n    }\n    finally\n    {\n        if (onFinally != null)\n        {\n            await onFinally();\n        }\n    }\n}\n```\n\nAnd if you take a good look at the example above it contains the `HandleException` method. This method will search for valid exception handlers to handle the exception that has occured. \n\n#### Type of exception handlers\nThere are three different types of exception handlers handlers.\n\n1. Fallback\n2. Default\n3. Command/Action specific\n\nThe **fallback** exception handler is the one defined in the `GuardedActions` library and will be the ExceptionHandler to use if all other handlers have been executed. *Note: it's possible to let an exception handler to stop the executution of the rest of the exception handlers.*\n\nThe **default** exception handlers are the handlers that are not configured to be an action or command specific. *Note: it's possible to let an exception handler to stop the executution of the rest of the exception handlers.*\n\n```csharp\n[DefaultExceptionHandler]\npublic class NotImplementedExceptionHandler : ExceptionHandler\u003cNotImplementedException\u003e\n{\n    private IDialogService _dialogService;\n\n    // TODO: add ability to load use DI\n    protected IDialogService DialogService =\u003e _dialogService ??= IoCRegistration.Instance.GetService\u003cIDialogService\u003e();\n\n    public override Task Handle(IExceptionHandlingAction\u003cNotImplementedException\u003e exceptionHandlingAction)\n    {\n        if (exceptionHandlingAction == null) throw new ArgumentNullException(nameof(exceptionHandlingAction));\n\n        exceptionHandlingAction.HandlingShouldFinish = true;\n\n        var message = exceptionHandlingAction?.Exception?.Message ?? \"This is not implemented yet!\";\n\n        return DialogService.Alert(message, \"Coming soon™\", \"Ok, I will wait\");\n    }\n}\n```\n\nThe **command or action specific** exception handlers will only be resolved and handled when an error has occured in the command or action on which it's specified for. *Note: it's possible to let an exception handler to stop the executution of the rest of the exception handlers.*\n\n```csharp\n[ExceptionHandlerFor(typeof(IDownloadUrlAction))]\npublic class DownloadUrlActionUriFormatExceptionHandler : ContextExceptionHandler\u003cUriFormatException, Models.DownloadableUrl\u003e\n{\n    public override Task Handle(IExceptionHandlingAction\u003cUriFormatException, Models.DownloadableUrl\u003e exceptionHandlingAction)\n    {\n        if (exceptionHandlingAction?.DataContext != null)\n        {\n            exceptionHandlingAction.DataContext.ErrorMessage = \"Not an valid URL.\";\n        }\n        return Task.CompletedTask;\n    }\n}\n```\n\n#### Order of execution\nThe different type of exception handlers will be executed in a specific order:\n\n1. The command or action specific\n2. The default\n3. The fallback\n\n#### Stop error handlers executing\nIt could be possible that an exception handler already handles everything needed and the execution of any other exception handlers will only cause duplicate data to be logged or unecessary resources to be used. \n\nIn this situation you could use the `HandlingShouldFinish` in the exception handler and it'll prevent any other exception handlers from being executed.\n\n```csharp\npublic override Task Handle(IExceptionHandlingAction\u003cException, Models.DownloadableUrl\u003e exceptionHandlingAction)\n{\n    // Logic to handle the exception\n\n    exceptionHandlingAction.HandlingShouldFinish = true;\n}\n```\n\n## Supported IoC containers\nThe GuardedActions library comes with a set of providers to support some of the most commonly used of the IoC containers. Also it'll provide you with the option of creating your own IoC provider to allow you to connect GuardedActions to your favorite IoC container of choice.\n\n| IoC container | Supported |\n| ------------- | ------------- |\n| [.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/) | :white_check_mark: |\n| [MvvmCross](https://www.mvvmcross.com/) | :white_check_mark: |\n| [Unity](http://unitycontainer.org/) | :construction: |\n| [Autofac](https://autofac.org/) | :construction: |\n| Custom ([read more](#custom)) | :white_check_mark: |\n\n## Installation\n\nDifferent IoC containers need different providers and so different NuGet packages. Down here you'll see samples on how to setup each IoC container provider.\n\n - [.NET Core](#net-core)\n - [MvvmCross](#mvvmcross)\n - [Custom](#custom)\n\n:construction: The rest is to coming soon! :construction:\n\n### .NET Core\n\nGrab the latest [GuardedActions.NetCore NuGet](https://www.nuget.org/packages/GuardActions.NetCore/) package and install in your solution.\n\u003e Install-Package GuardedActions.NetCore\n\nThen the only thing you've to do is configuring and connect the GuardedActions library on the host builder. See the example below: \n\n```csharp\nusing GuardedActions.NetCore;\nusing GuardedActions.NetCore.Extensions;\n\npublic class Startup\n{\n    public static void Init()\n    {\n        var iocSetup = new GuardedActionsIoCSetup();\n\n        var host = new HostBuilder()\n            .ConfigureGuardedActions(iocSetup, \"YourAssembliesStartWith\")\n            .Build()\n            .ConnectGuardedActions(iocSetup);\n    }\n}\n```\n\n### MvvmCross\n\nGrab the latest [GuardedActions.MvvmCross NuGet](https://www.nuget.org/packages/GuardActions.MvvmCross/) package and install in your solution.\n\u003e Install-Package GuardedActions.MvvmCross\n\nThen the only thing you've to do is configuring GuardedActions before registering the AppStart. See the example below: \n\n```csharp\nusing GuardedActions.MvvmCross;\n\npublic class App : MvxApplication\n{\n    public override void Initialize()\n    {\n        new GuardedActionsIoCSetup().Configure(Mvx.IoCProvider, \"YourAssembliesStartWith\");\n\n        RegisterAppStart\u003cMainViewModel\u003e();\n    }\n}\n```\n\n### Custom\n\nGrab the latest [GuardedActions NuGet](https://www.nuget.org/packages/GuardActions/) package and install in your solution.\n\u003e Install-Package GuardedActions\n\nThen the only thing you've to do is to create your own IoC setup class in which you will connect your IoC container to the GuardedActions library. This can be done by creating a `GuardedActionCustomIoCSetup` class which inherits from the `GuardedActions.IoC.IoCRegistraton` class. See the example below: \n\n```csharp\nusing GuardedActions.IoC;\n\npublic class GuardedActionCustomIoCSetup : IoCRegistration\n{\n    private IYourIoCContainer? _yourIoCContainer;\n\n    private static string _customErrorMessage = $\"Make sure you've called the {nameof(Configure)} on the {nameof(GuardedActionCustomIoCSetup)} before your app starts.\";\n\n    public void Configure(IYourIoCContainer yourIoCContainer, params string[] assemblyNames)\n    {\n        _yourIoCContainer = yourIoCContainer ?? throw new ArgumentNullException(nameof(yourIoCContainer));\n            \n        Register(assemblyNames);\n    }\n\n    public override void AddSingletonInternal\u003cTServiceType\u003e(Func\u003cTServiceType\u003e constructor) where TServiceType : class =\u003e _yourIoCContainer.AddSingleton(() =\u003e constructor.Invoke());\n\n    public override void AddSingletonInternal(Type serviceType) =\u003e _yourIoCContainer.AddSingleton(serviceType);\n\n    public override void AddSingletonInternal(Type contractType, Type serviceType) =\u003e _yourIoCContainer.AddSingleton(contractType, serviceType);\n\n    public override void AddTransientInternal(Type serviceType) =\u003e _yourIoCContainer.AddTransient(serviceType);\n\n    public override void AddTransientInternal(Type contractType, Type serviceType) =\u003e _yourIoCContainer.AddTransient(contractType, serviceType);\n\n    public override TServiceType GetServiceInternal\u003cTServiceType\u003e() where TServiceType : class =\u003e _yourIoCContainer.GetService\u003cTServiceType\u003e();\n\n    public override TServiceType GetServiceInternal\u003cTServiceType\u003e(Type serviceType) where TServiceType : class =\u003e _yourIoCContainer.GetService\u003cTServiceType\u003e(serviceType);\n\n    public override bool CanRegister =\u003e _yourIoCContainer != null;\n\n    public override bool CanResolve =\u003e _yourIoCContainer != null;\n\n    public override string CannotRegisterErrorMessage =\u003e _customErrorMessage;\n\n    public override string CannotResolveErrorMessage =\u003e _customErrorMessage;\n}\n```\n\nAnd then of course don't forget to call your custom IoC setup class after setting up your IoC container and before loading your app.\n\n\n```csharp\nnew GuardedActionCustomIoCSetup().Configure(yourIoCContainer, \"YourAssembliesStartWith\");\n\n```\n\n## Filing issues\n\nWhen filing issues, please select the appropriate [issue template](https://github.com/Baseflow/GuardedActions/issues/new/choose). The best way to get your bug fixed is to be as detailed as you can be about the problem.\nProviding a minimal git repository with a project showing how to reproduce the problem is ideal. Here are a couple of questions you can answer before filing a bug.\n\n1. Did you include a snippet of the broken code in the issue?\n2. Can you reproduce the problem in a brand new project?\n3. What are the _*EXACT*_ steps to reproduce this problem?\n4. What platform(s) are you experiencing the problem on?\n\nRemember GitHub issues support [markdown](https://github.github.com/github-flavored-markdown/). When filing bugs please make sure you check the formatting of the issue before clicking submit.\n\n## Contributing code\n\nWe are happy to receive Pull Requests adding new features and solving bugs. As for new features, please contact us before doing major work. To ensure you are not working on something that will be rejected due to not fitting into the roadmap or ideal of the library.\n\n### Git setup\n\nSince Windows and UNIX-based systems differ in terms of line endings, it is a very good idea to configure git autocrlf settings.\n\nOn *Windows* we recommend setting `core.autocrlf` to `true`.\n\n```\ngit config --global core.autocrlf true\n```\n\nOn *Mac* we recommend setting `core.autocrlf` to `input`.\n\n```\ngit config --global core.autocrlf input\n```\n\n### Code style guidelines\n\nPlease use 4 spaces for indentation.\n\nOtherwise default ReSharper C# code style applies.\n\n### Project Workflow\n\nOur workflow is loosely based on [Github Flow](http://scottchacon.com/2011/08/31/github-flow.html).\nWe actively do development on the **develop** branch. This means that all pull requests by contributors need to be develop and requested against the develop branch.\nThe master branch contains tags reflecting what is currently on NuGet.org.\n\n### Submitting Pull Requests\n\nMake sure you can build the code. Familiarize yourself with the project workflow and our coding conventions. If you don't know what a pull request is\nread this https://help.github.com/articles/using-pull-requests.\n\nBefore submitting a feature or substantial code contribution please discuss it with the team and ensure it follows the GuardedAction roadmap.\nNote that code submissions will be reviewed and tested. Only code that meets quality and design/roadmap appropriateness will be merged into the source. [Don't \"Push\" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/)\n\n### Acknowledgements\n\n* Thanks to [Artur Malendowicz](https://github.com/Immons) for some ideas / code on which this library is based upon.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaseflow%2Fguardedactions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaseflow%2Fguardedactions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaseflow%2Fguardedactions/lists"}