├── src ├── NetRx.Store.Monitor.Shared │ ├── Key.snk │ ├── Constants.cs │ ├── Models │ │ └── TraceMessage.cs │ ├── TraceMessageSerializer.cs │ └── NetRx.Store.Monitor.Shared.csproj ├── NetRx.Store │ ├── Store │ │ ├── Reducer.cs │ │ ├── Exceptions │ │ │ ├── InvalidStateTypeException.cs │ │ │ └── InvalidStatePropertyTypeException.cs │ │ ├── Action.cs │ │ ├── IStore.cs │ │ ├── Subscription.cs │ │ ├── StoreItem.cs │ │ ├── BlankStore.cs │ │ ├── ReducerWrapper.cs │ │ ├── StateWrapper.cs │ │ └── Store.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Diagnostic │ │ ├── ITraceMessageWriter.cs │ │ └── TraceMessageWriter.cs │ ├── Effects │ │ ├── Effect.cs │ │ ├── EffectMethodWrapper.cs │ │ └── EffectWrapper.cs │ └── NetRx.Store.csproj ├── NetRx.Store.Monitor.Extension │ ├── Key.snk │ ├── icon.png │ ├── Newtonsoft.Json.dll │ ├── Resources │ │ └── MonitorToolWindowPackage.ico │ ├── Logic │ │ ├── IOutputPaneParser.cs │ │ ├── ViewModels │ │ │ ├── BaseViewModel.cs │ │ │ ├── HistoryRecordViewModel.cs │ │ │ ├── StateNodeViewModel.cs │ │ │ ├── Command.cs │ │ │ ├── MonitorToolViewModel.cs │ │ │ └── StoreHistoryViewModel.cs │ │ ├── OutputPaneParser.cs │ │ ├── DebugPaneListener.cs │ │ └── StateValueParser.cs │ ├── app.config │ ├── UI │ │ ├── Converters │ │ │ └── BoolToInvertVisibilityConverter.cs │ │ ├── MonitorToolWindowControl.xaml.cs │ │ └── MonitorToolWindowControl.xaml │ ├── README.md │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── source.extension.vsixmanifest │ ├── MonitorToolWindow.cs │ ├── packages.config │ ├── MonitorToolWindowCommand.cs │ ├── MonitorToolWindowPackage.vsct │ ├── MonitorToolWindowPackage.cs │ ├── VSPackage.resx │ └── NetRx.Store.Monitor.Extension.csproj ├── NetRx.Store.Tests │ ├── State │ │ ├── Actions │ │ │ └── TestStateActions.cs │ │ ├── Reducers │ │ │ ├── InvalidStateReducer.cs │ │ │ └── TestStateReducer.cs │ │ ├── States │ │ │ ├── InvalidState.cs │ │ │ └── TestState.cs │ │ └── Effects │ │ │ └── TestEffects.cs │ ├── NetRx.Store.Tests.csproj │ ├── Extensions.cs │ └── Store │ │ └── StoreTest.cs └── NetRx.Store.sln ├── samples ├── SampleEffects │ ├── State │ │ ├── Actions │ │ │ ├── LoadData.cs │ │ │ ├── SetUsername.cs │ │ │ ├── SetIsLoading.cs │ │ │ └── LoadDataSuccess.cs │ │ ├── Effects │ │ │ ├── UsernameChangedEffect.cs │ │ │ └── LoadDataEffect.cs │ │ ├── Store.cs │ │ ├── Selectors │ │ │ └── AppStateSelector.cs │ │ └── Reducers │ │ │ └── AppReducer.cs │ ├── SampleEffects.csproj │ └── Program.cs ├── SampleState │ ├── State │ │ ├── Actions │ │ │ ├── ClearContacts.cs │ │ │ ├── SetName.cs │ │ │ ├── SetEmail.cs │ │ │ └── AddContact.cs │ │ └── Reducers │ │ │ └── ProfileReducer.cs │ ├── SampleState.csproj │ └── Program.cs ├── SampleMVVM.Wpf │ ├── App.config │ ├── Models │ │ ├── Services │ │ │ ├── IAuthService.cs │ │ │ ├── IDataService.cs │ │ │ ├── AuthService.cs │ │ │ └── DataService.cs │ │ ├── Entities │ │ │ └── DataItem.cs │ │ └── State │ │ │ ├── Selectors │ │ │ ├── DataSelectors.cs │ │ │ ├── RootSelectors.cs │ │ │ └── UserSelectors.cs │ │ │ ├── Actions │ │ │ ├── UserActions.cs │ │ │ └── DataActions.cs │ │ │ ├── States │ │ │ ├── UserState.cs │ │ │ └── DataState.cs │ │ │ ├── Reducers │ │ │ ├── UserStateReducer.cs │ │ │ └── DataStateReducer.cs │ │ │ └── Effects │ │ │ ├── MessageEffects.cs │ │ │ ├── UserEffects.cs │ │ │ └── DataEffects.cs │ ├── App.xaml │ ├── Views │ │ ├── MainWindow.xaml.cs │ │ ├── Converters │ │ │ └── BooleanToVisibilityConverter.cs │ │ └── MainWindow.xaml │ ├── packages.config │ ├── ViewModels │ │ ├── MainViewModel.cs │ │ ├── ViewModelBase.cs │ │ ├── DataViewModel.cs │ │ └── UserViewModel.cs │ ├── App.xaml.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── SampleMVVM.Wpf.csproj └── NetRx.Store.Samples.sln ├── LICENSE ├── CHANGELOG.md ├── .gitignore └── README.md /src/NetRx.Store.Monitor.Shared/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilchenkob/NetRx.Store/HEAD/src/NetRx.Store.Monitor.Shared/Key.snk -------------------------------------------------------------------------------- /src/NetRx.Store/Store/Reducer.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store 2 | { 3 | public abstract class Reducer 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilchenkob/NetRx.Store/HEAD/src/NetRx.Store.Monitor.Extension/Key.snk -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilchenkob/NetRx.Store/HEAD/src/NetRx.Store.Monitor.Extension/icon.png -------------------------------------------------------------------------------- /src/NetRx.Store/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("NetRx.Store.Tests")] -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilchenkob/NetRx.Store/HEAD/src/NetRx.Store.Monitor.Extension/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /samples/SampleEffects/State/Actions/LoadData.cs: -------------------------------------------------------------------------------- 1 | namespace SampleEffects.State.Actions 2 | { 3 | public class LoadData : NetRx.Store.Action 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /samples/SampleState/State/Actions/ClearContacts.cs: -------------------------------------------------------------------------------- 1 | namespace SampleState.State.Actions 2 | { 3 | public class ClearContacts : NetRx.Store.Action 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Resources/MonitorToolWindowPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilchenkob/NetRx.Store/HEAD/src/NetRx.Store.Monitor.Extension/Resources/MonitorToolWindowPackage.ico -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/SampleState/State/Actions/SetName.cs: -------------------------------------------------------------------------------- 1 | namespace SampleState.State.Actions 2 | { 3 | public class SetName : NetRx.Store.Action 4 | { 5 | public SetName(string payload) : base(payload) 6 | { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/IOutputPaneParser.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | 3 | namespace NetRx.Store.Monitor.Logic 4 | { 5 | public interface IOutputPaneParser 6 | { 7 | string[] GetLastLines(TextDocument textDocument); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/Services/IAuthService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SampleMVVM.Wpf.Models.Services 4 | { 5 | public interface IAuthService 6 | { 7 | Task Login(string login); 8 | 9 | Task Logout(); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/SampleState/State/Actions/SetEmail.cs: -------------------------------------------------------------------------------- 1 | namespace SampleState.State.Actions 2 | { 3 | public class SetEmail : NetRx.Store.Action 4 | { 5 | public SetEmail(string payload) : base(payload) 6 | { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/NetRx.Store/Diagnostic/ITraceMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NetRx.Store.Diagnostic 4 | { 5 | internal interface ITraceMessageWriter 6 | { 7 | void Write(string actionTypeName, IList states); 8 | } 9 | } -------------------------------------------------------------------------------- /samples/SampleEffects/State/Actions/SetUsername.cs: -------------------------------------------------------------------------------- 1 | namespace SampleEffects.State.Actions 2 | { 3 | public class SetUsername : NetRx.Store.Action 4 | { 5 | public SetUsername(string payload) : base(payload) 6 | { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Actions/SetIsLoading.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SampleEffects.State.Actions 4 | { 5 | public class SetIsLoading : NetRx.Store.Action 6 | { 7 | public SetIsLoading(bool payload) : base(payload) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetRx.Store.Monitor.Shared 4 | { 5 | public static class Constants 6 | { 7 | public static class Message 8 | { 9 | public const string Tag = "netrx.store"; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/Exceptions/InvalidStateTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace NetRx.Store.Exceptions 3 | { 4 | public class InvalidStateTypeException : Exception 5 | { 6 | public InvalidStateTypeException(string message) : base(message) 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/SampleState/State/Actions/AddContact.cs: -------------------------------------------------------------------------------- 1 | using SampleState.State.Reducers; 2 | 3 | namespace SampleState.State.Actions 4 | { 5 | public class AddContact : NetRx.Store.Action 6 | { 7 | public AddContact(Contact payload) : base(payload) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Actions/LoadDataSuccess.cs: -------------------------------------------------------------------------------- 1 | using SampleEffects.State.Reducers; 2 | 3 | namespace SampleEffects.State.Actions 4 | { 5 | public class LoadDataSuccess : NetRx.Store.Action 6 | { 7 | public LoadDataSuccess(Data payload) : base(payload) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/Exceptions/InvalidStatePropertyTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace NetRx.Store.Exceptions 3 | { 4 | public class InvalidStatePropertyTypeException : Exception 5 | { 6 | public InvalidStatePropertyTypeException(string message) : base(message) 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Shared/Models/TraceMessage.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store.Monitor.Shared.Models 2 | { 3 | public class TraceMessage 4 | { 5 | public string StoreTypeName { get; set; } 6 | 7 | public string ActionTypeName { get; set; } 8 | 9 | public string StateValueJson { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/Action.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store 2 | { 3 | public abstract class Action 4 | { 5 | } 6 | 7 | public abstract class Action : Action 8 | { 9 | public TPayload Payload { get; private set; } 10 | 11 | public Action(TPayload payload) => Payload = payload; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/IStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetRx.Store 5 | { 6 | public interface IStore 7 | { 8 | void Dispatch(T action) where T : Action; 9 | 10 | IObservable Select(Expression> propertyExpression); 11 | } 12 | } -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/Services/IDataService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using SampleMVVM.Wpf.Models.Entities; 4 | 5 | namespace SampleMVVM.Wpf.Models.Services 6 | { 7 | public interface IDataService 8 | { 9 | Task> GetDataItems(int userId); 10 | 11 | Task SendItem(DataItem item); 12 | } 13 | } -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/Entities/DataItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SampleMVVM.Wpf.Models.Entities 4 | { 5 | public struct DataItem 6 | { 7 | public int Id { get; set; } 8 | 9 | public string Title { get; set; } 10 | 11 | public decimal Amount { get; set; } 12 | 13 | public bool IsPrimary { get; set; } 14 | 15 | public DateTime CreatedAt { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.ViewModels; 2 | using System.Windows; 3 | 4 | namespace SampleMVVM.Wpf.Views 5 | { 6 | /// 7 | /// Interaction logic for MainWindow.xaml 8 | /// 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | DataContext = new MainViewModel(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Selectors/DataSelectors.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.Entities; 2 | using SampleMVVM.Wpf.Models.State.States; 3 | using System; 4 | using System.Collections.Immutable; 5 | 6 | namespace SampleMVVM.Wpf.Models.State.Selectors 7 | { 8 | public static class DataSelectors 9 | { 10 | public static IObservable> Items 11 | = App.Store.Select>(state => state.Items); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/Actions/TestStateActions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NetRx.Store; 4 | 5 | namespace NetRx.Store.Tests.State.TestStateActions 6 | { 7 | public class ClearNameAction : Action { } 8 | 9 | public class LoadItemsAction : Action { } 10 | 11 | public class SetItemsAction : Action> 12 | { 13 | public SetItemsAction(List payload) : base(payload) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Actions/UserActions.cs: -------------------------------------------------------------------------------- 1 | namespace SampleMVVM.Wpf.Models.State.UserActions 2 | { 3 | public class LoginStart : NetRx.Store.Action 4 | { 5 | public LoginStart(string payload) : base(payload) 6 | { 7 | } 8 | } 9 | 10 | public class LoginResult : NetRx.Store.Action 11 | { 12 | public LoginResult(int payload) : base(payload) 13 | { 14 | } 15 | } 16 | 17 | public class Logout : NetRx.Store.Action 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/States/UserState.cs: -------------------------------------------------------------------------------- 1 | namespace SampleMVVM.Wpf.Models.State.States 2 | { 3 | public struct UserState 4 | { 5 | public int UserId { get; set; } 6 | 7 | public string Login { get; set; } 8 | 9 | public bool IsLoading { get; set; } 10 | 11 | public static UserState Initial() 12 | { 13 | return new UserState 14 | { 15 | UserId = 0, 16 | Login = string.Empty, 17 | IsLoading = false 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Effects/UsernameChangedEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NetRx.Store.Effects; 4 | using actions = SampleEffects.State.Actions; 5 | 6 | namespace SampleEffects.State.Effects 7 | { 8 | public class UsernameChangedEffect : Effect 9 | { 10 | public override async Task Invoke(actions.SetUsername action) 11 | { 12 | await Task.Delay(250); 13 | Console.WriteLine($"Effect. Username value: {action.Payload}"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/States/DataState.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.Entities; 2 | using System.Collections.Immutable; 3 | 4 | namespace SampleMVVM.Wpf.Models.State.States 5 | { 6 | public struct DataState 7 | { 8 | public bool IsLoading { get; set; } 9 | 10 | public ImmutableList Items { get; set; } 11 | 12 | public static DataState Initial() 13 | { 14 | return new DataState 15 | { 16 | IsLoading = false, 17 | Items = ImmutableList.Create() 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/Reducers/InvalidStateReducer.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store.Tests.State.Reducers 2 | { 3 | public class InvalidTypeStateReducer : Reducer 4 | { 5 | } 6 | 7 | public class InvalidListPropertyTypeStateReducer : Reducer 8 | { 9 | } 10 | 11 | public class InvalidPropertyTypeStateReducer : Reducer 12 | { 13 | } 14 | 15 | public class InvalidCollectionItemTypeStateReducer : Reducer 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/ViewModels/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace NetRx.Store.Monitor.Extension.Logic.ViewModels 4 | { 5 | public abstract class BaseViewModel : INotifyPropertyChanged 6 | { 7 | protected void NotifyPropertyChanged(string propertyName) 8 | { 9 | if (PropertyChanged != null) 10 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); 11 | } 12 | 13 | public event PropertyChangedEventHandler PropertyChanged; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/Services/AuthService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SampleMVVM.Wpf.Models.Services 5 | { 6 | public class AuthService : IAuthService 7 | { 8 | public async Task Login(string login) 9 | { 10 | // simulate the waiting for the response 11 | await Task.Delay(TimeSpan.FromMilliseconds(700)); 12 | 13 | return DateTime.Now.Second; 14 | } 15 | 16 | public Task Logout() 17 | { 18 | // simulate the waiting for the response 19 | return Task.Delay(TimeSpan.FromMilliseconds(600)); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/SampleState/SampleState.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Selectors/RootSelectors.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.State.States; 2 | using System; 3 | using System.Reactive.Linq; 4 | 5 | namespace SampleMVVM.Wpf.Models.State.Selectors 6 | { 7 | public static class RootSelectors 8 | { 9 | public static IObservable IsLoading 10 | => IsUserLoading.CombineLatest(IsDataLoading, (x, y) => x || y); 11 | 12 | private static IObservable IsUserLoading = App.Store.Select(state => state.IsLoading); 13 | 14 | private static IObservable IsDataLoading = App.Store.Select(state => state.IsLoading); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/NetRx.Store/Effects/Effect.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace NetRx.Store.Effects 4 | { 5 | public abstract class Effect 6 | { 7 | internal const string InvokeMethodName = "Invoke"; 8 | } 9 | 10 | public abstract class Effect : Effect where TInputAction : Action 11 | { 12 | public abstract Task Invoke(TInputAction action); 13 | } 14 | 15 | public abstract class Effect : Effect 16 | where TInputAction : Action 17 | where TOutputAction : Action 18 | { 19 | public abstract Task Invoke(TInputAction action); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Selectors/UserSelectors.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.State.States; 2 | using System; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | 6 | namespace SampleMVVM.Wpf.Models.State.Selectors 7 | { 8 | public static class UserSelectors 9 | { 10 | public static IObservable IsLoggedIn 11 | = App.Store.Select(state => state.UserId).Select(id => id > 0); 12 | 13 | public static IObservable Login 14 | = App.Store.Select(state => state.Login); 15 | 16 | public static IObservable UserId 17 | = App.Store.Select(state => state.UserId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/NetRx.Store.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/SampleEffects/SampleEffects.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Store.cs: -------------------------------------------------------------------------------- 1 | using SampleEffects.State.Reducers; 2 | using SampleEffects.State.Effects; 3 | using NetRxStore = NetRx.Store.Store; 4 | using NetRx.Store.Effects; 5 | 6 | namespace SampleEffects.State 7 | { 8 | public static class Store 9 | { 10 | public static NetRxStore Instance { get; private set; } 11 | 12 | static Store() 13 | { 14 | Instance = NetRxStore.Create() 15 | .WithState(AppState.Initial, AppState.Reducer) 16 | // or 17 | // .WithState(AppState.Initial, new AppReducer()) 18 | .WithEffects(new Effect[] { new LoadDataEffect(), new UsernameChangedEffect() }); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Reducers/UserStateReducer.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store; 2 | using SampleMVVM.Wpf.Models.State.States; 3 | 4 | namespace SampleMVVM.Wpf.Models.State.Reducers 5 | { 6 | public class UserStateReducer : Reducer 7 | { 8 | public UserState Reduce(UserState state, UserActions.LoginStart action) 9 | { 10 | state.IsLoading = true; 11 | return state; 12 | } 13 | 14 | public UserState Reduce(UserState state, UserActions.LoginResult action) 15 | { 16 | state.UserId = action.Payload; 17 | state.IsLoading = false; 18 | return state; 19 | } 20 | 21 | public UserState Reduce(UserState state, UserActions.Logout action) 22 | { 23 | state.UserId = 0; 24 | return state; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/Subscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | 5 | namespace NetRx.Store 6 | { 7 | internal interface ISubscription 8 | { 9 | void OnNext(object value); 10 | } 11 | 12 | internal class Subscription : ISubscription 13 | { 14 | private readonly BehaviorSubject _subject; 15 | 16 | public Subscription(T lastValue) 17 | { 18 | _subject = new BehaviorSubject(lastValue); 19 | } 20 | 21 | public void OnNext(object value) 22 | { 23 | _subject.OnNext((T)value); 24 | } 25 | 26 | public IObservable AsObservable() 27 | { 28 | return _subject.AsObservable(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /samples/SampleEffects/State/Effects/LoadDataEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NetRx.Store.Effects; 4 | using SampleEffects.State.Reducers; 5 | using actions = SampleEffects.State.Actions; 6 | 7 | namespace SampleEffects.State.Effects 8 | { 9 | public class LoadDataEffect : Effect 10 | { 11 | public override async Task Invoke(actions.LoadData action) 12 | { 13 | await Task.Delay(TimeSpan.FromSeconds(2)); 14 | return new actions.LoadDataSuccess(new Data 15 | { 16 | Count = DateTime.Now.Second, 17 | Amount = DateTime.Now.Minute, 18 | Category = Guid.NewGuid().ToString() 19 | }); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SampleMVVM.Wpf.Models.State.Selectors; 3 | 4 | namespace SampleMVVM.Wpf.ViewModels 5 | { 6 | public class MainViewModel : ViewModelBase 7 | { 8 | public MainViewModel() 9 | { 10 | DataVM = new DataViewModel(); 11 | UserVM = new UserViewModel(); 12 | 13 | RootSelectors.IsLoading.Subscribe(value => IsLoading = value); 14 | } 15 | 16 | public DataViewModel DataVM { get; private set; } 17 | 18 | public UserViewModel UserVM { get; private set; } 19 | 20 | private bool _loading; 21 | public bool IsLoading 22 | { 23 | get 24 | { 25 | return _loading; 26 | } 27 | private set 28 | { 29 | _loading = value; 30 | NotifyPropertyChanged(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/UI/Converters/BoolToInvertVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace NetRx.Store.Monitor.Extension.UI.Converters 7 | { 8 | public class BoolToInvertVisibilityConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value is bool boolValue) 13 | { 14 | return boolValue ? Visibility.Collapsed : Visibility.Visible; 15 | } 16 | 17 | return null; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | return null; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Views/Converters/BooleanToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace SampleMVVM.Wpf.Views.Converters 6 | { 7 | class BooleanToVisibilityConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 10 | { 11 | if (value is Boolean && (bool)value) 12 | { 13 | return Visibility.Visible; 14 | } 15 | return Visibility.Collapsed; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 19 | { 20 | if (value is Visibility && (Visibility)value == Visibility.Visible) 21 | { 22 | return true; 23 | } 24 | return false; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Actions/DataActions.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.Entities; 2 | using System.Collections.Generic; 3 | 4 | namespace SampleMVVM.Wpf.Models.State.DataActions 5 | { 6 | public class SendItemStart : NetRx.Store.Action 7 | { 8 | public SendItemStart(DataItem payload) : base(payload) 9 | { 10 | } 11 | } 12 | 13 | public class SendItemResult : NetRx.Store.Action 14 | { 15 | public SendItemResult(DataItem payload) : base(payload) 16 | { 17 | } 18 | } 19 | 20 | public class LoadDataStart : NetRx.Store.Action 21 | { 22 | public LoadDataStart(int payload) : base(payload) 23 | { 24 | } 25 | } 26 | 27 | public class LoadDataResult : NetRx.Store.Action> 28 | { 29 | public LoadDataResult(List payload) : base(payload) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/States/InvalidState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | 4 | namespace NetRx.Store.Tests.State 5 | { 6 | public class InvalidTypeState 7 | { 8 | public int Count { get; set; } 9 | 10 | public string Name { get; set; } 11 | } 12 | 13 | public struct InvalidPropertyTypeState 14 | { 15 | public string Name { get; set; } 16 | 17 | public InvalidTypeState Model { get; set; } 18 | } 19 | 20 | public struct InvalidListPropertyTypeState 21 | { 22 | public string Name { get; set; } 23 | 24 | public List Items { get; set; } 25 | } 26 | 27 | public struct InvalidCollectionItemTypeState 28 | { 29 | public string Name { get; set; } 30 | 31 | public ImmutableArray Data { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Selectors/AppStateSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SampleEffects.State.Reducers; 3 | 4 | namespace SampleEffects.State.Selectors 5 | { 6 | public static class AppStateSelector 7 | { 8 | static AppStateSelector() 9 | { 10 | IsLoading = Store.Instance.Select(state => state.IsLoading); 11 | DataCategory = Store.Instance.Select(state => state.Data.Category); 12 | DataAmount = Store.Instance.Select(state => state.Data.Amount); 13 | Data = Store.Instance.Select(state => state.Data); 14 | } 15 | 16 | public static IObservable IsLoading { get; private set; } 17 | 18 | public static IObservable DataCategory { get; private set; } 19 | 20 | public static IObservable DataAmount { get; private set; } 21 | 22 | public static IObservable Data { get; private set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/Reducers/TestStateReducer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | 5 | namespace NetRx.Store.Tests.State.Reducers 6 | { 7 | public class TestStateReducer : Reducer 8 | { 9 | public IList ReduceCalls { get; private set; } = new List(); 10 | 11 | public TestState Reduce(TestState state, TestStateActions.ClearNameAction action) 12 | { 13 | ReduceCalls.Add(action.GetType().FullName); 14 | state.Name = string.Empty; 15 | return state; 16 | } 17 | 18 | public TestState Reduce(TestState state, TestStateActions.SetItemsAction action) 19 | { 20 | ReduceCalls.Add(action.GetType().FullName); 21 | state.Items = action.Payload.ToImmutableList(); 22 | return state; 23 | } 24 | } 25 | 26 | public class SecondaryTestStateReducer : Reducer 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/ViewModels/HistoryRecordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace NetRx.Store.Monitor.Extension.Logic.ViewModels 5 | { 6 | public class HistoryRecordViewModel : BaseViewModel 7 | { 8 | public HistoryRecordViewModel(string actionName, string stateValueJson) 9 | { 10 | var actionNameParts = actionName.Split('.'); 11 | ActionName = actionNameParts[actionNameParts.Length - 1]; 12 | ActionFullName = actionName; 13 | 14 | State = StateValueParser.ParseJsonStateValue(stateValueJson); 15 | } 16 | 17 | public string ActionName { get; private set; } = string.Empty; 18 | 19 | public string ActionFullName { get; private set; } = string.Empty; 20 | 21 | public string ReceivedAt { get; private set; } = DateTime.Now.ToString("HH:mm:ss.fff"); 22 | 23 | public ObservableCollection State { get; private set; } = new ObservableCollection(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/README.md: -------------------------------------------------------------------------------- 1 | # NetRx.Store Monitor 2 | 3 | *Inspired by [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension)* 4 | 5 | This extension helps to debug an application which uses [NetRx.Store](https://www.nuget.org/packages/NetRx.Store) for state management. 6 | After installation you can find it in "Debug" -> "Windows" menu: 7 | 8 | ![menu](https://user-images.githubusercontent.com/2301026/66945984-48be6d00-f050-11e9-804d-d5e5125c5988.png) 9 | 10 | It helps to track dispatched actions and state changes. Its main window is presented on the screenshot below. 11 | 12 | ![main_window](https://user-images.githubusercontent.com/2301026/66946338-05183300-f051-11e9-9d79-ea5ebf343518.PNG) 13 | 14 | ### How to use it: 15 | 1. Make sure that your project uses [NetRx.Store](https://www.nuget.org/packages/NetRx.Store) of version 2.0 or above. 16 | 2. Run your application in "Debug" or attach the debugger to it. 17 | 3. Open NetRx.Store Monitor and it will be attached to the application automatically. So it will show the state changes in a real-time. 18 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/Services/DataService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using SampleMVVM.Wpf.Models.Entities; 6 | 7 | namespace SampleMVVM.Wpf.Models.Services 8 | { 9 | public class DataService : IDataService 10 | { 11 | public async Task> GetDataItems(int userId) 12 | { 13 | var now = DateTime.Now; 14 | 15 | // simulate the waiting for the response 16 | await Task.Delay(TimeSpan.FromSeconds(1)); 17 | 18 | return Enumerable.Range(0, now.Second) 19 | .Select(i => new DataItem 20 | { 21 | Id = i + 1, 22 | Amount = now.Millisecond + i * 10, 23 | CreatedAt = now, 24 | Title = $"{now.Second} - {i}", 25 | IsPrimary = i == 30 26 | }) 27 | .ToList(); 28 | } 29 | 30 | public async Task SendItem(DataItem item) 31 | { 32 | // simulate the waiting for the response 33 | await Task.Delay(TimeSpan.FromSeconds(1)); 34 | 35 | return true; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Shared/TraceMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store.Monitor.Shared.Models; 2 | using System.Linq; 3 | 4 | namespace NetRx.Store.Monitor.Shared 5 | { 6 | public static class TraceMessageSerializer 7 | { 8 | private const char Separator = '#'; 9 | 10 | public static string Serialize(TraceMessage message) 11 | { 12 | return $"{Constants.Message.Tag}{Separator}{message.StoreTypeName}{Separator}{message.ActionTypeName}{Separator}{message.StateValueJson}"; 13 | } 14 | 15 | public static TraceMessage Deserialize(string message) 16 | { 17 | if (string.IsNullOrWhiteSpace(message)) 18 | return null; 19 | 20 | var parts = message.Split(Separator); 21 | if (parts.Length < 4) 22 | return null; 23 | 24 | return new TraceMessage 25 | { 26 | StoreTypeName = parts[1], 27 | ActionTypeName = parts[2], 28 | StateValueJson = parts.Skip(3).Aggregate((a, b) => $"{a}{Separator}{b}") 29 | }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Shared/NetRx.Store.Monitor.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | Key.snk 7 | 8 | NetRx.Store.Monitor.Shared 9 | 2.0.0 10 | Vitalii Ilchenko 11 | Includes library which allows to attach NetRx.Store Monitor tool to an application at a runtime 12 | https://github.com/ilchenkob/NetRx.Store 13 | store;reactive;state;management;tools 14 | true 15 | true 16 | Includes library which allows to attach NetRx.Store Monitor tool to an application at a runtime 17 | NetRx.Store.Monitor.Shared 18 | 19 | 20 | 21 | MIT 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vitaly Ilchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using NetRx.Store.Effects; 5 | 6 | namespace NetRx.Store.Tests 7 | { 8 | public static class Extensions 9 | { 10 | public static T GetInstanceField(this Type type, object instance, string fieldName) 11 | { 12 | var field = type.GetField(fieldName, BindingFlags); 13 | return (T)field.GetValue(instance); 14 | } 15 | 16 | internal static IEnumerable GetStoreItems(this Store store) 17 | { 18 | return store.GetType().GetInstanceField>(store, "_items"); 19 | } 20 | 21 | internal static Dictionary> GetStoreEffects(this Store store) 22 | { 23 | return store.GetType().GetInstanceField>>(store, "_effects"); 24 | } 25 | 26 | private static BindingFlags BindingFlags 27 | => BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Effects/MessageEffects.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SampleMVVM.Wpf.Models.State.DataActions; 3 | using NetRx.Store.Effects; 4 | 5 | namespace SampleMVVM.Wpf.Models.State.Effects 6 | { 7 | public class SendingFailedMessageEffect : Effect 8 | { 9 | public override Task Invoke(SendItemResult action) 10 | { 11 | return action.Payload.Id > 0 12 | ? App.ShowMessge("Data item has been sent") 13 | : App.ShowMessge("Data item sending failed", true); 14 | } 15 | } 16 | 17 | public class DataLoadingFailedMessageEffect : Effect 18 | { 19 | public override Task Invoke(LoadDataResult action) 20 | { 21 | return action.Payload.Count > 0 22 | ? App.ShowMessge($"Loaded {action.Payload.Count} items") 23 | : App.ShowMessge("Loading failed", true); 24 | } 25 | } 26 | 27 | public static class MessageEffects 28 | { 29 | public static Effect[] GetAll() 30 | { 31 | return new Effect[] 32 | { 33 | new SendingFailedMessageEffect(), 34 | new DataLoadingFailedMessageEffect() 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/SampleEffects/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SampleEffects.State; 3 | using SampleEffects.State.Selectors; 4 | using actions = SampleEffects.State.Actions; 5 | 6 | namespace SimpleEffects 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | AppStateSelector.IsLoading.Subscribe(value => 13 | { 14 | Console.WriteLine($"Loading: {value}"); 15 | }); 16 | AppStateSelector.DataCategory.Subscribe(value => 17 | { 18 | Console.WriteLine($"Data Category: {value}"); 19 | }); 20 | AppStateSelector.DataAmount.Subscribe(value => 21 | { 22 | Console.WriteLine($"Data Amount: {value}"); 23 | }); 24 | AppStateSelector.Data.Subscribe(value => 25 | { 26 | Console.WriteLine($"Data: count - {value.Count}, amount - {value.Amount}, catogory - {value.Category}"); 27 | }); 28 | 29 | Store.Instance.Dispatch(new actions.SetUsername("Test name")); 30 | Store.Instance.Dispatch(new actions.LoadData()); 31 | 32 | Console.ReadLine(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/OutputPaneParser.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio.Shell; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace NetRx.Store.Monitor.Logic 7 | { 8 | public class OutputPaneParser : IOutputPaneParser 9 | { 10 | private int prevLinesCount = 0; 11 | 12 | public string[] GetLastLines(TextDocument textDocument) 13 | { 14 | string[] result = new string[0]; 15 | 16 | try 17 | { 18 | ThreadHelper.ThrowIfNotOnUIThread(); 19 | var selection = textDocument.Selection; 20 | 21 | var linesAdded = textDocument.EndPoint.Line - prevLinesCount; 22 | 23 | selection.EndOfDocument(); 24 | selection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn, true); 25 | selection.LineUp(true, linesAdded); 26 | 27 | result = selection.Text?.Split('\n'); 28 | 29 | selection.EndOfDocument(false); 30 | 31 | prevLinesCount = textDocument.EndPoint.Line; 32 | } 33 | catch (Exception ex) 34 | { 35 | var a = ex.Message; 36 | } 37 | 38 | return result; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/ViewModels/StateNodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace NetRx.Store.Monitor.Extension.Logic.ViewModels 4 | { 5 | public class StateNodeViewModel : BaseViewModel 6 | { 7 | private string _name = string.Empty; 8 | public string Name 9 | { 10 | get => _name; 11 | set 12 | { 13 | _name = value; 14 | NotifyPropertyChanged(nameof(Name)); 15 | } 16 | } 17 | 18 | private string _value = string.Empty; 19 | public string Value 20 | { 21 | get => _value; 22 | set 23 | { 24 | _value = value; 25 | NotifyPropertyChanged(nameof(Value)); 26 | } 27 | } 28 | 29 | private bool _isExpanded; 30 | public bool IsExpanded 31 | { 32 | get => _isExpanded; 33 | set 34 | { 35 | _isExpanded = Childs != null ? value : false; 36 | NotifyPropertyChanged(nameof(IsExpanded)); 37 | } 38 | } 39 | 40 | public ObservableCollection Childs { get; set; } = new ObservableCollection(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Reducers/DataStateReducer.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store; 2 | using SampleMVVM.Wpf.Models.State.States; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | 6 | namespace SampleMVVM.Wpf.Models.State.Reducers 7 | { 8 | public class DataStateReducer 9 | { 10 | public static DataState Function(DataState state, Action action) 11 | { 12 | if (action is DataActions.LoadDataStart) 13 | { 14 | state.IsLoading = true; 15 | return state; 16 | } 17 | if (action is DataActions.LoadDataResult loadResult) 18 | { 19 | state.Items = loadResult.Payload.ToImmutableList(); 20 | state.IsLoading = false; 21 | return state; 22 | } 23 | if (action is DataActions.SendItemStart) 24 | { 25 | state.IsLoading = true; 26 | return state; 27 | } 28 | if (action is DataActions.SendItemResult sendResult) 29 | { 30 | if (sendResult.Payload.Id > 0) 31 | state.Items = state.Items.Remove(state.Items.First(d => d.Id == sendResult.Payload.Id)); 32 | 33 | state.IsLoading = false; 34 | return state; 35 | } 36 | if (action is UserActions.Logout) 37 | { 38 | state.Items = state.Items.Clear(); 39 | return state; 40 | } 41 | return state; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/ViewModels/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace NetRx.Store.Monitor.Extension.Logic.ViewModels 5 | { 6 | public class Command : ICommand 7 | { 8 | private Func _canExecute; 9 | private Action _execute; 10 | 11 | public Command(Action execute, Func canExecute = null) 12 | { 13 | this._execute = (arg) => execute(); 14 | 15 | if (canExecute != null) 16 | this._canExecute = (arg) => canExecute(); 17 | } 18 | 19 | public Command(Action execute, Func canExecute = null) 20 | { 21 | this._execute = execute; 22 | this._canExecute = canExecute; 23 | } 24 | 25 | public event EventHandler CanExecuteChanged 26 | { 27 | add { CommandManager.RequerySuggested += value; } 28 | remove { CommandManager.RequerySuggested -= value; } 29 | } 30 | 31 | public bool CanExecute(object parameter) 32 | { 33 | if (_canExecute == null) 34 | return true; 35 | 36 | return _canExecute(parameter); 37 | } 38 | 39 | public void Execute(object parameter) 40 | { 41 | _execute(parameter); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/StoreItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NetRx.Store 4 | { 5 | internal class StoreItem 6 | { 7 | protected StoreItem(StateWrapper state) 8 | { 9 | State = state; 10 | } 11 | 12 | internal StoreItem(StateWrapper state, ReducerWrapper reducer) : this(state) 13 | { 14 | Reducer = reducer; 15 | } 16 | 17 | internal StateWrapper State { get; set; } 18 | 19 | internal ReducerWrapper Reducer { get; private set; } 20 | 21 | internal virtual object Dispatch(T action, string actionTypeName) where T : Action 22 | { 23 | return Reducer.CanHandle(actionTypeName) 24 | ? Reducer.Invoke(actionTypeName, State.Original, action) 25 | : null; 26 | } 27 | } 28 | 29 | internal class StoreItem : StoreItem 30 | { 31 | internal StoreItem(StateWrapper state, Func reducerFunc) : base(state) 32 | { 33 | ReducerFunc = reducerFunc; 34 | } 35 | 36 | internal Func ReducerFunc { get; private set; } 37 | 38 | internal override object Dispatch(T action, string actionTypeName) 39 | { 40 | return ReducerFunc((TState)State.Original, action); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq.Expressions; 4 | using System.Runtime.CompilerServices; 5 | using System.Windows.Input; 6 | 7 | namespace SampleMVVM.Wpf.ViewModels 8 | { 9 | public class ViewModelBase : INotifyPropertyChanged 10 | { 11 | #region INotifyPropertyChanged implementation 12 | public event PropertyChangedEventHandler PropertyChanged; 13 | 14 | protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) 15 | { 16 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 17 | } 18 | #endregion 19 | } 20 | 21 | public class RelayCommand : ICommand 22 | { 23 | private readonly Action _execute; 24 | private readonly Func _canExecute; 25 | 26 | public RelayCommand(Action execute, Func canExecute = null) 27 | { 28 | _execute = execute; 29 | _canExecute = canExecute; 30 | } 31 | 32 | public event EventHandler CanExecuteChanged 33 | { 34 | add { CommandManager.RequerySuggested += value; } 35 | remove { CommandManager.RequerySuggested -= value; } 36 | } 37 | 38 | public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute(); 39 | 40 | public void Execute(object parameter) 41 | { 42 | _execute(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("NetRx.Store.Monitor.Extension")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Vitalii Ilchenko")] 12 | [assembly: AssemblyProduct("NetRx.Store.Monitor.Extension")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using System.Windows; 4 | using NetRx.Store; 5 | using SampleMVVM.Wpf.Models.Services; 6 | using SampleMVVM.Wpf.Models.State.Effects; 7 | using SampleMVVM.Wpf.Models.State.Reducers; 8 | using SampleMVVM.Wpf.Models.State.States; 9 | 10 | namespace SampleMVVM.Wpf 11 | { 12 | /// 13 | /// Interaction logic for App.xaml 14 | /// 15 | public partial class App : Application 16 | { 17 | public static Store Store { get; private set; } 18 | 19 | public App() 20 | { 21 | var authService = new AuthService(); 22 | var dataService = new DataService(); 23 | 24 | var effects = UserEffects.GetAll(authService) 25 | .Concat(DataEffects.GetAll(dataService)) 26 | .Concat(MessageEffects.GetAll()); 27 | 28 | Store = Store.Create() 29 | .WithState(DataState.Initial(), DataStateReducer.Function) 30 | .WithState(UserState.Initial(), new UserStateReducer()) 31 | .WithEffects(effects); 32 | } 33 | 34 | public static Task ShowMessge(string message, bool isError = false) 35 | { 36 | MessageBox.Show( 37 | message, 38 | "NetRx.Store sample", 39 | MessageBoxButton.OK, 40 | isError ? MessageBoxImage.Error : MessageBoxImage.Information); 41 | return Task.CompletedTask; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NetRx.Store/Diagnostic/TraceMessageWriter.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store.Monitor.Shared; 2 | using NetRx.Store.Monitor.Shared.Models; 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | 8 | namespace NetRx.Store.Diagnostic 9 | { 10 | internal class TraceMessageWriter : ITraceMessageWriter 11 | { 12 | public void Write(string actionTypeName, IList states) 13 | { 14 | var stateNames = states.Select(s => s.State.OriginalTypeName.Split('.').Last()).Aggregate((a, b) => $"{a}, {b}"); 15 | var message = TraceMessageSerializer.Serialize(new TraceMessage 16 | { 17 | StoreTypeName = stateNames, 18 | ActionTypeName = actionTypeName, 19 | StateValueJson = ConvertToStateValueJson(states) 20 | }); 21 | Trace.WriteLine(message); 22 | } 23 | 24 | private string ConvertToStateValueJson(IList states) 25 | { 26 | var stateProps = states 27 | .Select(i => 28 | { 29 | var stateName = i.State.OriginalTypeName.Split('.').Last(); 30 | var value = JsonConvert.SerializeObject(i.State.Original); 31 | return $"\"{stateName}\":{value}"; 32 | }) 33 | .Aggregate((a, b) => $"{a},{b}"); 34 | 35 | return $"{{{stateProps}}}"; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/NetRx.Store/NetRx.Store.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | NetRx 6 | NetRx.Store 7 | 2.0.0 8 | Vitalii Ilchenko 9 | State management for .Net projects, inspired by @ngrx/store 10 | https://github.com/ilchenkob/NetRx.Store 11 | store;reactive;state;management;flux;redux;blazor;uwp;wpf;xamarin 12 | true 13 | true 14 | State management for .Net projects, inspired by @ngrx/store 15 | NetRx.Store 16 | 17 | 18 | 19 | MIT 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Effects/UserEffects.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store.Effects; 2 | using SampleMVVM.Wpf.Models.Services; 3 | using System.Threading.Tasks; 4 | 5 | namespace SampleMVVM.Wpf.Models.State.Effects 6 | { 7 | public class LoginEffect : Effect 8 | { 9 | private readonly IAuthService _authService; 10 | 11 | public LoginEffect(IAuthService authService) 12 | { 13 | _authService = authService; 14 | } 15 | 16 | public override async Task Invoke(UserActions.LoginStart action) 17 | { 18 | try 19 | { 20 | var result = await _authService.Login(action.Payload); 21 | return new UserActions.LoginResult(result); 22 | } 23 | catch 24 | { 25 | return new UserActions.LoginResult(0); 26 | } 27 | } 28 | } 29 | 30 | public class LogoutEffect : Effect 31 | { 32 | private readonly IAuthService _authService; 33 | 34 | public LogoutEffect(IAuthService authService) 35 | { 36 | _authService = authService; 37 | } 38 | 39 | public override Task Invoke(UserActions.Logout action) 40 | { 41 | return _authService.Logout(); 42 | } 43 | } 44 | 45 | public static class UserEffects 46 | { 47 | public static Effect[] GetAll(IAuthService authService) 48 | { 49 | return new Effect[] 50 | { 51 | new LogoutEffect(authService), 52 | new LoginEffect(authService), 53 | }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/UI/MonitorToolWindowControl.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store.Monitor.Extension.UI 2 | { 3 | using Microsoft.VisualStudio.Shell; 4 | using NetRx.Store.Monitor.Extension.Logic.ViewModels; 5 | using System; 6 | using System.ComponentModel; 7 | using System.Reactive.Linq; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | 11 | public partial class MonitorToolWindowControl : UserControl 12 | { 13 | public MonitorToolWindowControl(MonitorToolViewModel viewModel) 14 | { 15 | this.InitializeComponent(); 16 | this.DataContext = viewModel; 17 | 18 | Observable.FromEventPattern( 19 | h => FilterTextBox.TextChanged += h, 20 | h => FilterTextBox.TextChanged -= h) 21 | .Throttle(TimeSpan.FromMilliseconds(700)) 22 | #pragma warning disable VSTHRD101 // Avoid unsupported async delegates 23 | .Subscribe(async arg => 24 | { 25 | if (viewModel.SelectedStoreHistory != null && arg.Sender is TextBox textBox) 26 | { 27 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 28 | viewModel.SelectedStoreHistory.ActionFilter = textBox.Text.ToLower(); 29 | viewModel.SelectedStoreHistory.ApplyFilterCommand?.Execute(null); 30 | } 31 | }); 32 | #pragma warning restore VSTHRD101 // Avoid unsupported async delegates 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/NetRx.Store/Effects/EffectMethodWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace NetRx.Store.Effects 5 | { 6 | internal interface IEffectMethodWrapper 7 | { 8 | string ActionTypeName { get; } 9 | 10 | Task Invoke(Action action); 11 | } 12 | 13 | internal class EffectMethodWrapper : IEffectMethodWrapper where T : Action 14 | { 15 | private readonly Func _func; 16 | 17 | public EffectMethodWrapper(string actionTypeName, Delegate del) 18 | { 19 | ActionTypeName = actionTypeName; 20 | _func = (Func)del; 21 | } 22 | 23 | public string ActionTypeName { get; private set; } 24 | 25 | public Task Invoke(Action action) => _func((T)action); 26 | } 27 | 28 | internal class EffectMethodWrapper 29 | : IEffectMethodWrapper 30 | where TInputAction : Action 31 | where TOutputAction : Action 32 | { 33 | private readonly Func> _func; 34 | private readonly Store _store; 35 | 36 | public EffectMethodWrapper(string actionTypeName, Delegate del, NetRx.Store.Store store) 37 | { 38 | ActionTypeName = actionTypeName; 39 | _func = (Func>)del; 40 | _store = store; 41 | } 42 | 43 | public string ActionTypeName { get; private set; } 44 | 45 | public async Task Invoke(Action action) 46 | { 47 | _store.Dispatch(await _func((TInputAction)action)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/ViewModels/DataViewModel.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.Entities; 2 | using SampleMVVM.Wpf.Models.State.Selectors; 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.Windows.Input; 6 | using DataActions = SampleMVVM.Wpf.Models.State.DataActions; 7 | 8 | namespace SampleMVVM.Wpf.ViewModels 9 | { 10 | public class DataViewModel : ViewModelBase 11 | { 12 | private int _userId; 13 | private bool _isLoggedIn; 14 | 15 | public DataViewModel() 16 | { 17 | LoadCommand = new RelayCommand(Load, CanLoadAndSend); 18 | SendCommand = new RelayCommand(Send, CanLoadAndSend); 19 | 20 | Items = new ObservableCollection(); 21 | 22 | UserSelectors.IsLoggedIn.Subscribe(value => _isLoggedIn = value); 23 | UserSelectors.UserId.Subscribe(value => _userId = value); 24 | DataSelectors.Items.Subscribe(items => Items = new ObservableCollection(items)); 25 | } 26 | 27 | public ICommand LoadCommand { get; private set; } 28 | 29 | public ICommand SendCommand { get; private set; } 30 | 31 | private ObservableCollection _items; 32 | public ObservableCollection Items 33 | { 34 | get 35 | { 36 | return _items; 37 | } 38 | private set 39 | { 40 | _items = value; 41 | NotifyPropertyChanged(); 42 | } 43 | } 44 | 45 | public DataItem SelectedItem { get; set; } 46 | 47 | public void Send() 48 | { 49 | App.Store.Dispatch(new DataActions.SendItemStart(SelectedItem)); 50 | } 51 | 52 | public void Load() 53 | { 54 | App.Store.Dispatch(new DataActions.LoadDataStart(_userId)); 55 | } 56 | 57 | public bool CanLoadAndSend() => _isLoggedIn; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/ViewModels/MonitorToolViewModel.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store.Monitor.Shared.Models; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | 5 | namespace NetRx.Store.Monitor.Extension.Logic.ViewModels 6 | { 7 | public class MonitorToolViewModel : BaseViewModel 8 | { 9 | public ObservableCollection StoreHistories { get; private set; } = new ObservableCollection(); 10 | 11 | private StoreHistoryViewModel _selectedStoreHistory; 12 | public StoreHistoryViewModel SelectedStoreHistory 13 | { 14 | get => _selectedStoreHistory; 15 | set 16 | { 17 | _selectedStoreHistory = value; 18 | NotifyPropertyChanged(nameof(SelectedStoreHistory)); 19 | } 20 | } 21 | 22 | public void AddStateRecord(TraceMessage message) 23 | { 24 | System.Diagnostics.Debug.WriteLine($"--- {message.ActionTypeName}"); 25 | 26 | var stateHistory = StoreHistories.FirstOrDefault(r => r.Name == message.StoreTypeName); 27 | if (stateHistory == null) 28 | { 29 | var newStoreItem = new StoreHistoryViewModel(message.StoreTypeName, message.ActionTypeName, message.StateValueJson); 30 | StoreHistories.Add(newStoreItem); 31 | 32 | if (StoreHistories.Count == 1) 33 | SelectedStoreHistory = newStoreItem; 34 | } 35 | else 36 | { 37 | stateHistory.AddRecord(message.ActionTypeName, message.StateValueJson); 38 | } 39 | } 40 | 41 | public void Clear() 42 | { 43 | StoreHistories.Clear(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | #### 2.0.0 - 2019-08-27 5 | ##### Fixed 6 | * Action dispatching behavior: effects will be triggered only when all reducers have been called. 7 | 8 | ##### Added 9 | * Support of an extension for Visual Studio which is called 'NetRx.Store Monitor' and is available at Visual Studio Marketplace. 10 | 11 | ##### Changed 12 | * Effects namespace: all effects classes have been moved to `NetRx.Store.Effects` namespace (in prev. versions they were located in `NetRx.Effects`). 13 | 14 | #### 1.4.0 - 2018-10-15 15 | ##### Added 16 | * Support of a pure function as a reducer. Previously, ```WithState``` method supported only the class inherited from ```NetRx.Store.Reducer``` as a reducer. But now there is one more override of this method that supports ```Func``` as a reducer. 17 | 18 | #### 1.3.1 - 2018-10-10 19 | ##### Fixed 20 | * Ability to create a string collection as a state field. 21 | 22 | #### 1.3.0 - 2018-10-08 23 | ##### Added 24 | * Internal wrappers for Reducer, Subscription and Effect. It slightly slows down the store initialization time. But at the same time gives significant improvement in Reducer and Effect invocations and their execution takes much less time. 25 | 26 | #### 1.2.1 - 2018-10-04 27 | ##### Added 28 | * Restriction for collections changed: all types from ```System.Collections.Immutable``` namespace are allowed. 29 | 30 | #### 1.2.0 - 2018-10-03 31 | ##### Added 32 | * Restriction for the state fields: only ```ImmutableArray``` type is allowed for collections. 33 | 34 | #### 1.1.0 - 2018-10-01 35 | ##### Added 36 | * Few optimization to the internal class ```StateWrapper```. 37 | 38 | #### 1.0.0 - 2018-09-30 39 | * Project has been published to Nuget and uploaded to GitHub. -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/States/TestState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NetRx.Store.Tests.State 9 | { 10 | public struct SubState 11 | { 12 | public decimal Value { get; set; } 13 | 14 | public string Name { get; set; } 15 | 16 | public string Description { get; set; } 17 | 18 | public ImmutableArray Records { get; set; } 19 | 20 | public static SubState Initial => new SubState 21 | { 22 | Value = 0.5m, 23 | Name = "empty_name", 24 | Description = "empty_description" 25 | }; 26 | } 27 | 28 | public struct TestState 29 | { 30 | public int Id { get; set; } 31 | 32 | public string Name { get; set; } 33 | 34 | public decimal Amount { get; set; } 35 | 36 | public bool IsEnabled { get; set; } 37 | 38 | public SubState SubState { get; set; } 39 | 40 | public ImmutableList Items { get; set; } 41 | 42 | public static TestState Initial => new TestState 43 | { 44 | Id = 1, 45 | Name = "empty_name", 46 | Amount = 0.7m, 47 | IsEnabled = true, 48 | SubState = SubState.Initial 49 | }; 50 | } 51 | 52 | public struct SecondaryTestState 53 | { 54 | public int Count { get; set; } 55 | 56 | public string Description { get; set; } 57 | 58 | public bool IsLoading { get; set; } 59 | 60 | public static SecondaryTestState Initial => new SecondaryTestState 61 | { 62 | Count = -1, 63 | Description = "none", 64 | IsLoading = false 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/ViewModels/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using SampleMVVM.Wpf.Models.State.Selectors; 2 | using System; 3 | using System.Windows.Input; 4 | using UserActions = SampleMVVM.Wpf.Models.State.UserActions; 5 | 6 | namespace SampleMVVM.Wpf.ViewModels 7 | { 8 | public class UserViewModel : ViewModelBase 9 | { 10 | private bool _isLoggedIn; 11 | 12 | public UserViewModel() 13 | { 14 | SignInCommand = new RelayCommand(SignIn, CanSignIn); 15 | SignOutCommand = new RelayCommand(SignOut, CanSignOut); 16 | 17 | UserSelectors.Login.Subscribe(value => Login = value); 18 | UserSelectors.UserId.Subscribe(value => UserId = value); 19 | UserSelectors.IsLoggedIn.Subscribe(value => _isLoggedIn = value); 20 | } 21 | 22 | public ICommand SignInCommand { get; private set; } 23 | 24 | public ICommand SignOutCommand { get; private set; } 25 | 26 | private string _login; 27 | public string Login 28 | { 29 | get 30 | { 31 | return _login; 32 | } 33 | set 34 | { 35 | _login = value; 36 | NotifyPropertyChanged(); 37 | } 38 | } 39 | 40 | private int _userId; 41 | public int UserId 42 | { 43 | get 44 | { 45 | return _userId; 46 | } 47 | set 48 | { 49 | _userId = value; 50 | NotifyPropertyChanged(); 51 | CommandManager.InvalidateRequerySuggested(); 52 | } 53 | } 54 | 55 | private void SignIn() 56 | { 57 | App.Store.Dispatch(new UserActions.LoginStart(Login)); 58 | } 59 | 60 | private bool CanSignIn() => !string.IsNullOrWhiteSpace(Login) && !_isLoggedIn; 61 | 62 | private void SignOut() 63 | { 64 | App.Store.Dispatch(new UserActions.Logout()); 65 | } 66 | 67 | private bool CanSignOut() => _isLoggedIn; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/NetRx.Store.Tests/State/Effects/TestEffects.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using NetRx.Store.Effects; 5 | 6 | namespace NetRx.Store.Tests.State.Effects 7 | { 8 | public class SetItemsEffect : Effect 9 | { 10 | public int CallCount { get; private set; } 11 | 12 | public override Task Invoke(TestStateActions.SetItemsAction action) 13 | { 14 | CallCount++; 15 | return Task.CompletedTask; 16 | } 17 | } 18 | 19 | public class LoadItemsEffect : Effect 20 | { 21 | private readonly List _result; 22 | 23 | public int CallCount { get; private set; } 24 | 25 | public LoadItemsEffect(IEnumerable result) 26 | { 27 | _result = result.ToList(); 28 | } 29 | 30 | public LoadItemsEffect() : this(new List()) 31 | { 32 | } 33 | 34 | public override Task Invoke(TestStateActions.LoadItemsAction action) 35 | { 36 | CallCount++; 37 | return Task.FromResult(new TestStateActions.SetItemsAction(_result)); 38 | } 39 | } 40 | 41 | public class LogItemsLoadingEffect : Effect 42 | { 43 | private readonly List _result; 44 | 45 | public int CallCount { get; private set; } 46 | 47 | public LogItemsLoadingEffect(IEnumerable result) 48 | { 49 | _result = result.ToList(); 50 | } 51 | 52 | public LogItemsLoadingEffect() : this(new List()) 53 | { 54 | } 55 | 56 | public override Task Invoke(TestStateActions.LoadItemsAction action) 57 | { 58 | CallCount++; 59 | return Task.CompletedTask; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Models/State/Effects/DataEffects.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SampleMVVM.Wpf.Models.Services; 3 | using System.Collections.Generic; 4 | using SampleMVVM.Wpf.Models.Entities; 5 | using NetRx.Store.Effects; 6 | 7 | namespace SampleMVVM.Wpf.Models.State.Effects 8 | { 9 | public class LoadDataEffect : Effect 10 | { 11 | private readonly IDataService _dataService; 12 | 13 | public LoadDataEffect(IDataService dataService) 14 | { 15 | _dataService = dataService; 16 | } 17 | 18 | public override async Task Invoke(DataActions.LoadDataStart action) 19 | { 20 | try 21 | { 22 | var result = await _dataService.GetDataItems(action.Payload); 23 | return new DataActions.LoadDataResult(result); 24 | } 25 | catch 26 | { 27 | return new DataActions.LoadDataResult(new List()); 28 | } 29 | } 30 | } 31 | 32 | public class SendItemEffect : Effect 33 | { 34 | private readonly IDataService _dataService; 35 | 36 | public SendItemEffect(IDataService dataService) 37 | { 38 | _dataService = dataService; 39 | } 40 | 41 | public override async Task Invoke(DataActions.SendItemStart action) 42 | { 43 | try 44 | { 45 | var result = await _dataService.SendItem(action.Payload); 46 | return new DataActions.SendItemResult(action.Payload); 47 | } 48 | catch 49 | { 50 | return new DataActions.SendItemResult(new DataItem { Id = -1 }); 51 | } 52 | } 53 | } 54 | 55 | public static class DataEffects 56 | { 57 | public static Effect[] GetAll(IDataService dataService) 58 | { 59 | return new Effect[] 60 | { 61 | new LoadDataEffect(dataService), 62 | new SendItemEffect(dataService), 63 | }; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/DebugPaneListener.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using System; 3 | using System.Diagnostics; 4 | using NetRx.Store.Monitor.Shared; 5 | using MonitorConstants = NetRx.Store.Monitor.Shared.Constants; 6 | using NetRx.Store.Monitor.Shared.Models; 7 | using Microsoft.VisualStudio.Shell; 8 | 9 | namespace NetRx.Store.Monitor.Logic 10 | { 11 | public class DebugPaneListener : IDisposable 12 | { 13 | private const string TargetPaneName = "Debug"; 14 | 15 | private readonly OutputWindowEvents _outputWindowEvents; 16 | private readonly IOutputPaneParser _parser; 17 | 18 | public DebugPaneListener(OutputWindowEvents outputWindowEvents, IOutputPaneParser paneParser) 19 | { 20 | Debug.Assert(outputWindowEvents != null); 21 | Debug.Assert(paneParser != null); 22 | 23 | _outputWindowEvents = outputWindowEvents; 24 | _outputWindowEvents.PaneUpdated += onPaneUpdated; 25 | 26 | _parser = paneParser; 27 | } 28 | 29 | public void Dispose() 30 | { 31 | _outputWindowEvents.PaneUpdated -= onPaneUpdated; 32 | } 33 | 34 | public Action HandleUpdate { get; set; } 35 | 36 | private void onPaneUpdated(OutputWindowPane pane) 37 | { 38 | ThreadHelper.ThrowIfNotOnUIThread(); 39 | 40 | if (pane.Name == TargetPaneName) 41 | { 42 | var lastLines = _parser.GetLastLines(pane.TextDocument); 43 | foreach (var line in lastLines) 44 | { 45 | if (!string.IsNullOrWhiteSpace(line) && line.StartsWith(MonitorConstants.Message.Tag)) 46 | { 47 | var message = TraceMessageSerializer.Deserialize(line.TrimEnd()); 48 | if (message != null) 49 | HandleUpdate?.Invoke(message); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NetRx.Store Monitor 6 | This extension helps to debug an application which uses NetRx.Store for state management. It allows to track dispatched actions and state changes in a real-time. 7 | https://github.com/ilchenkob/NetRx.Store 8 | icon.png 9 | state;management;tracing;debug;monitor; 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/MonitorToolWindow.cs: -------------------------------------------------------------------------------- 1 | namespace NetRx.Store.Monitor.Extension 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using Microsoft.VisualStudio.Shell; 6 | using NetRx.Store.Monitor.Extension.Logic.ViewModels; 7 | using NetRx.Store.Monitor.Extension.UI; 8 | using NetRx.Store.Monitor.Logic; 9 | 10 | [Guid("680bdfa0-4831-42aa-82a5-931619a034e5")] 11 | public class MonitorToolWindow : ToolWindowPane 12 | { 13 | private EnvDTE.DebuggerEvents _debuggerEvents; 14 | private DebugPaneListener _debugPaneListener; 15 | private MonitorToolViewModel _monitorToolViewModel; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | public MonitorToolWindow() : base(null) 21 | { 22 | this.Caption = "NetRx.Store Monitor"; 23 | 24 | _monitorToolViewModel = new MonitorToolViewModel(); 25 | 26 | ThreadHelper.ThrowIfNotOnUIThread(); 27 | 28 | _debuggerEvents = MonitorToolWindowCommand.Instance.Ide.Events.DebuggerEvents; 29 | _debuggerEvents.OnEnterDesignMode += OnExitDebuggerMode; 30 | 31 | _debugPaneListener = new DebugPaneListener( 32 | MonitorToolWindowCommand.Instance.Ide.Events.OutputWindowEvents, 33 | new OutputPaneParser()) 34 | { 35 | HandleUpdate = message => _monitorToolViewModel.AddStateRecord(message) 36 | }; 37 | 38 | this.Content = new MonitorToolWindowControl(_monitorToolViewModel); 39 | } 40 | 41 | private void OnExitDebuggerMode(EnvDTE.dbgEventReason Reason) 42 | { 43 | _monitorToolViewModel?.Clear(); 44 | } 45 | 46 | protected override void Dispose(bool disposing) 47 | { 48 | _debugPaneListener.Dispose(); 49 | _debuggerEvents.OnEnterDesignMode -= OnExitDebuggerMode; 50 | 51 | base.Dispose(disposing); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/SampleState/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SampleState.State.Reducers; 3 | using NetRx.Store; 4 | using actions = SampleState.State.Actions; 5 | using System.Collections.Immutable; 6 | 7 | namespace SampleState 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var initialState = new ProfileState 14 | { 15 | Email = string.Empty, 16 | Name = string.Empty, 17 | Contacts = ImmutableList.Create() 18 | }; 19 | 20 | // Option 1: 21 | var store = Store.Create().WithState(initialState, new ProfileReducerFunc().Reduce); 22 | 23 | // Option 2: 24 | //var store = Store.Create().WithState(initialState, new ProfileReducer()); 25 | 26 | store.Select(state => state.Name).Subscribe(value => 27 | { 28 | Console.WriteLine($"Name: {value}"); 29 | }); 30 | store.Select(state => state.Email).Subscribe(value => 31 | { 32 | Console.WriteLine($"Email: {value}"); 33 | }); 34 | store.Select>(state => state.Contacts).Subscribe(value => 35 | { 36 | Console.WriteLine($"Contacts: {value.Count}"); 37 | }); 38 | store.Select(state => state).Subscribe(value => 39 | { 40 | Console.WriteLine($"State value: {value.Name} {value.Email} {value.Contacts.Count}"); 41 | }); 42 | 43 | store.Dispatch(new actions.SetEmail("test@mail.com")); 44 | store.Dispatch(new actions.SetName("Test Profile")); 45 | store.Dispatch(new actions.AddContact(new Contact { Name = "Contact 1", Email = "contact_1@mail.com" })); 46 | store.Dispatch(new actions.AddContact(new Contact { Name = "Contact 2", Email = "contact_2@mail.com" })); 47 | store.Dispatch(new actions.AddContact(new Contact { Name = "Contact 3", Email = "contact_3@mail.com" })); 48 | store.Dispatch(new actions.ClearContacts()); 49 | 50 | Console.ReadLine(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/NetRx.Store/Effects/EffectWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace NetRx.Store.Effects 5 | { 6 | internal abstract class EffectWrapper 7 | { 8 | public static IEffectMethodWrapper FromObject(Store store, Effect effect) 9 | { 10 | var effectType = effect.GetType(); 11 | var effectTypeGenericArguments = effectType.BaseType.GenericTypeArguments; 12 | var typeOfInputAction = effectTypeGenericArguments[0].FullName; 13 | 14 | var invokeMethod = effectType.GetMethod(Effect.InvokeMethodName); 15 | var actionArg = invokeMethod.GetParameters()[0]; 16 | var actionParameter = Expression.Parameter(actionArg.ParameterType, actionArg.Name); 17 | 18 | var methodExpr = Expression.Lambda( 19 | Expression.Call(Expression.Constant(effect, effectType), invokeMethod, actionParameter), 20 | actionParameter 21 | ); 22 | 23 | if (effectTypeGenericArguments.Length == 1) 24 | { 25 | return (IEffectMethodWrapper)typeof(EffectMethodWrapper<>) 26 | .MakeGenericType(new Type[] 27 | { 28 | actionArg.ParameterType 29 | }) 30 | .GetConstructors()[0] 31 | .Invoke(new object[] 32 | { 33 | typeOfInputAction, 34 | methodExpr.Compile() 35 | }); 36 | } 37 | else if (effectTypeGenericArguments.Length == 2) 38 | { 39 | return (IEffectMethodWrapper)typeof(EffectMethodWrapper<,>) 40 | .MakeGenericType(new Type[] 41 | { 42 | actionArg.ParameterType, 43 | invokeMethod.ReturnType.GenericTypeArguments[0] 44 | }) 45 | .GetConstructors()[0] 46 | .Invoke(new object[] 47 | { 48 | typeOfInputAction, 49 | methodExpr.Compile(), 50 | store 51 | }); 52 | } 53 | else 54 | { 55 | throw new InvalidOperationException(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /samples/SampleState/State/Reducers/ProfileReducer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using NetRx.Store; 3 | using actions = SampleState.State.Actions; 4 | 5 | namespace SampleState.State.Reducers 6 | { 7 | public struct Contact 8 | { 9 | public string Name { get; set; } 10 | 11 | public string Email { get; set; } 12 | } 13 | 14 | public struct ProfileState 15 | { 16 | public string Name { get; set; } 17 | 18 | public string Email { get; set; } 19 | 20 | public ImmutableList Contacts { get; set; } 21 | } 22 | 23 | public class ProfileReducerFunc 24 | { 25 | public ProfileState Reduce(ProfileState state, Action action) 26 | { 27 | if (action is actions.SetEmail setEmail) 28 | { 29 | state.Email = setEmail.Payload; 30 | return state; 31 | } 32 | if (action is actions.SetName setName) 33 | { 34 | state.Name = setName.Payload; 35 | return state; 36 | } 37 | if (action is actions.AddContact addContact) 38 | { 39 | state.Contacts = state.Contacts.Add(addContact.Payload); 40 | return state; 41 | } 42 | if (action is actions.ClearContacts) 43 | { 44 | state.Contacts = state.Contacts.Clear(); 45 | return state; 46 | } 47 | 48 | return state; 49 | } 50 | } 51 | 52 | public class ProfileReducer : Reducer 53 | { 54 | public ProfileState Reduce(ProfileState state, actions.SetEmail action) 55 | { 56 | state.Email = action.Payload; 57 | return state; 58 | } 59 | 60 | public ProfileState Reduce(ProfileState state, actions.SetName action) 61 | { 62 | state.Name = action.Payload; 63 | return state; 64 | } 65 | 66 | public ProfileState Reduce(ProfileState state, actions.AddContact action) 67 | { 68 | state.Contacts = state.Contacts.Add(action.Payload); 69 | return state; 70 | } 71 | 72 | public ProfileState Reduce(ProfileState state, actions.ClearContacts action) 73 | { 74 | state.Contacts = state.Contacts.Clear(); 75 | return state; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("SampleMVVM.PlusStore.Wpf")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Vitalii Ilchenko")] 14 | [assembly: AssemblyProduct("SampleMVVM.PlusStore.Wpf")] 15 | [assembly: AssemblyCopyright("Copyright © Vitalii Ilchenko 2018")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /samples/NetRx.Store.Samples.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27703.2042 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleState", "SampleState\SampleState.csproj", "{D3263B51-60BD-4DDE-B265-1725A238FDCA}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetRx.Store", "..\src\NetRx.Store\NetRx.Store.csproj", "{5F7DD1FB-D535-4C44-AE71-0594C2FC717A}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleEffects", "SampleEffects\SampleEffects.csproj", "{857B2339-FE07-448D-9C17-93E5382633E6}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleMVVM.Wpf", "SampleMVVM.Wpf\SampleMVVM.Wpf.csproj", "{D7EFCEE8-80E3-43C1-B86F-9D6B68D5C2FC}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {D3263B51-60BD-4DDE-B265-1725A238FDCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {D3263B51-60BD-4DDE-B265-1725A238FDCA}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {D3263B51-60BD-4DDE-B265-1725A238FDCA}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {D3263B51-60BD-4DDE-B265-1725A238FDCA}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {5F7DD1FB-D535-4C44-AE71-0594C2FC717A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {5F7DD1FB-D535-4C44-AE71-0594C2FC717A}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {5F7DD1FB-D535-4C44-AE71-0594C2FC717A}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {5F7DD1FB-D535-4C44-AE71-0594C2FC717A}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {857B2339-FE07-448D-9C17-93E5382633E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {857B2339-FE07-448D-9C17-93E5382633E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {857B2339-FE07-448D-9C17-93E5382633E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {857B2339-FE07-448D-9C17-93E5382633E6}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {D7EFCEE8-80E3-43C1-B86F-9D6B68D5C2FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {D7EFCEE8-80E3-43C1-B86F-9D6B68D5C2FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {D7EFCEE8-80E3-43C1-B86F-9D6B68D5C2FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {D7EFCEE8-80E3-43C1-B86F-9D6B68D5C2FC}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {780E3B76-B2F2-4D4E-969E-EE1805C7A41B} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /samples/SampleEffects/State/Reducers/AppReducer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NetRx.Store; 3 | using actions = SampleEffects.State.Actions; 4 | 5 | namespace SampleEffects.State.Reducers 6 | { 7 | public struct Data 8 | { 9 | public int Count { get; set; } 10 | 11 | public decimal Amount { get; set; } 12 | 13 | public string Category { get; set; } 14 | } 15 | 16 | public struct AppState 17 | { 18 | public bool IsLoading { get; set; } 19 | 20 | public string Username { get; set; } 21 | 22 | public Data Data { get; set; } 23 | 24 | public static AppState Initial => new AppState 25 | { 26 | IsLoading = false, 27 | Username = string.Empty, 28 | Data = new Data 29 | { 30 | Count = 0, 31 | Amount = 0, 32 | Category = string.Empty 33 | } 34 | }; 35 | 36 | public static Func Reducer = (state, action) => 37 | { 38 | if (action is actions.SetIsLoading setIsLoading) 39 | { 40 | state.IsLoading = setIsLoading.Payload; 41 | return state; 42 | } 43 | if (action is actions.SetUsername setUsername) 44 | { 45 | state.Username = setUsername.Payload; 46 | return state; 47 | } 48 | if (action is actions.LoadData) 49 | { 50 | state.IsLoading = true; 51 | return state; 52 | } 53 | if (action is actions.LoadDataSuccess loadDataSuccess) 54 | { 55 | state.IsLoading = false; 56 | state.Data = loadDataSuccess.Payload; 57 | return state; 58 | } 59 | return state; 60 | }; 61 | } 62 | 63 | public class AppReducer : Reducer 64 | { 65 | public AppState Reduce(AppState state, actions.SetIsLoading action) 66 | { 67 | state.IsLoading = action.Payload; 68 | return state; 69 | } 70 | 71 | public AppState Reduce(AppState state, actions.SetUsername action) 72 | { 73 | state.Username = action.Payload; 74 | return state; 75 | } 76 | 77 | public AppState Reduce(AppState state, actions.LoadData action) 78 | { 79 | state.IsLoading = true; 80 | return state; 81 | } 82 | 83 | public AppState Reduce(AppState state, actions.LoadDataSuccess action) 84 | { 85 | state.IsLoading = false; 86 | state.Data = action.Payload; 87 | return state; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/NetRx.Store/Store/BlankStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using NetRx.Store.Diagnostic; 5 | using NetRx.Store.Effects; 6 | using NetRx.Store.Exceptions; 7 | 8 | namespace NetRx.Store 9 | { 10 | public class BlankStore 11 | { 12 | internal IList _items; 13 | 14 | internal ConcurrentDictionary _subscriptions; 15 | 16 | internal Dictionary> _effects; 17 | 18 | internal BlankStore() 19 | { 20 | } 21 | 22 | /// 23 | /// Adds state to the store 24 | /// 25 | /// Store that inludes passed state and reducer 26 | /// Initial state value 27 | /// Reducer for the state 28 | /// State type 29 | public Store WithState(TState initialState, TReducer reducer) where TReducer : Reducer 30 | { 31 | return TryCreateStore(() => WithState( 32 | new StoreItem( 33 | StateWrapper.ForObject(() => initialState), 34 | ReducerWrapper.ForObject(reducer)) 35 | ) 36 | ); 37 | } 38 | 39 | /// 40 | /// Adds state to the store 41 | /// 42 | /// Store that inludes passed state and reducer 43 | /// Initial state value 44 | /// Reducer function 45 | /// State type 46 | public Store WithState(TState initialState, Func reducer) 47 | { 48 | return TryCreateStore(() => WithState( 49 | new StoreItem( 50 | StateWrapper.ForObject(() => initialState), 51 | reducer) 52 | ) 53 | ); 54 | } 55 | 56 | protected Store TryCreateStore(Func createFunc) 57 | { 58 | try 59 | { 60 | return createFunc(); 61 | } 62 | catch (InvalidStateTypeException stateTypeException) 63 | { 64 | throw new InvalidStateTypeException(stateTypeException.Message); 65 | } 66 | catch (InvalidStatePropertyTypeException propTypeException) 67 | { 68 | throw new InvalidStatePropertyTypeException(propTypeException.Message); 69 | } 70 | } 71 | 72 | private Store WithState(StoreItem item) 73 | { 74 | return TryCreateStore(() => new Store 75 | { 76 | _items = new List { item }, 77 | _subscriptions = new ConcurrentDictionary(), 78 | _effects = new Dictionary>() 79 | }); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/NetRx.Store.Monitor.Extension/Logic/StateValueParser.cs: -------------------------------------------------------------------------------- 1 | using NetRx.Store.Monitor.Extension.Logic.ViewModels; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | 7 | namespace NetRx.Store.Monitor.Extension.Logic 8 | { 9 | public static class StateValueParser 10 | { 11 | public static ObservableCollection ParseJsonStateValue(string jsonString) 12 | { 13 | JObject jObject = null; 14 | try 15 | { 16 | jObject = JObject.Parse(jsonString); 17 | } 18 | catch (JsonReaderException) 19 | { 20 | } 21 | return ParseJsonObject(jObject); 22 | } 23 | 24 | private static ObservableCollection ParseJsonObject(JObject jObject) 25 | { 26 | if (jObject == null) 27 | return null; 28 | 29 | var result = new ObservableCollection(); 30 | foreach (var property in jObject.Properties()) 31 | { 32 | result.Add(ParseJsonProperty(property)); 33 | } 34 | return result; 35 | } 36 | 37 | private static StateNodeViewModel ParseJsonProperty(JProperty property) 38 | { 39 | var node = new StateNodeViewModel 40 | { 41 | Name = property.Name 42 | }; 43 | 44 | if (property.Value.Type == JTokenType.Array) 45 | { 46 | var children = property.Value.Children(); 47 | var count = children.Count(); 48 | node.Value = $"[{count}]"; 49 | if (count > 0) 50 | { 51 | node.Childs = new ObservableCollection(); 52 | for (int i = 0; i < count; i++) 53 | { 54 | var currentArrayItem = children.ElementAt(i); 55 | var child = new StateNodeViewModel 56 | { 57 | Name = i.ToString(), 58 | Value = currentArrayItem.ToString() 59 | }; 60 | if (currentArrayItem is JObject jObj) 61 | { 62 | child.Value = FilterJsonString(child.Value); 63 | child.Childs = ParseJsonObject(jObj); 64 | } 65 | node.Childs.Add(child); 66 | } 67 | } 68 | } 69 | else 70 | { 71 | node.Value = property.Value.Type == JTokenType.Null ? "null" : property.Value.ToString(); 72 | if (property.Value.Type == JTokenType.Object) 73 | { 74 | node.Value = FilterJsonString(node.Value); 75 | } 76 | node.Childs = ParseJsonObject(property.Value as JObject); 77 | } 78 | return node; 79 | } 80 | 81 | private static string FilterJsonString(string json) 82 | { 83 | return json.Replace("\r\n", " ").Replace("\"", "").Replace("{ ", "{ "); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/NetRx.Store.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetRx.Store", "NetRx.Store\NetRx.Store.csproj", "{A6315850-7D4E-4BA5-A643-3BE4B645B928}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetRx.Store.Tests", "NetRx.Store.Tests\NetRx.Store.Tests.csproj", "{2FB873CA-3709-4C42-A565-56F7B71A1211}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetRx.Store.Monitor.Shared", "NetRx.Store.Monitor.Shared\NetRx.Store.Monitor.Shared.csproj", "{76053ACD-EC86-49EA-92EA-18501DAAB7F1}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetRx.Store.Monitor.Extension", "NetRx.Store.Monitor.Extension\NetRx.Store.Monitor.Extension.csproj", "{28C87D13-AEC6-4C34-A500-67FF8DAAF261}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{B1568EAE-835F-4052-B1E4-79DB6ABBC0FC}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{98F39D90-94C6-4E01-BCFC-D3A1DAB74506}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {A6315850-7D4E-4BA5-A643-3BE4B645B928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A6315850-7D4E-4BA5-A643-3BE4B645B928}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A6315850-7D4E-4BA5-A643-3BE4B645B928}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A6315850-7D4E-4BA5-A643-3BE4B645B928}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {2FB873CA-3709-4C42-A565-56F7B71A1211}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2FB873CA-3709-4C42-A565-56F7B71A1211}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2FB873CA-3709-4C42-A565-56F7B71A1211}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2FB873CA-3709-4C42-A565-56F7B71A1211}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {76053ACD-EC86-49EA-92EA-18501DAAB7F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {76053ACD-EC86-49EA-92EA-18501DAAB7F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {76053ACD-EC86-49EA-92EA-18501DAAB7F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {76053ACD-EC86-49EA-92EA-18501DAAB7F1}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {28C87D13-AEC6-4C34-A500-67FF8DAAF261}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {28C87D13-AEC6-4C34-A500-67FF8DAAF261}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {28C87D13-AEC6-4C34-A500-67FF8DAAF261}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {28C87D13-AEC6-4C34-A500-67FF8DAAF261}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(NestedProjects) = preSolution 45 | {A6315850-7D4E-4BA5-A643-3BE4B645B928} = {B1568EAE-835F-4052-B1E4-79DB6ABBC0FC} 46 | {2FB873CA-3709-4C42-A565-56F7B71A1211} = {B1568EAE-835F-4052-B1E4-79DB6ABBC0FC} 47 | {76053ACD-EC86-49EA-92EA-18501DAAB7F1} = {98F39D90-94C6-4E01-BCFC-D3A1DAB74506} 48 | {28C87D13-AEC6-4C34-A500-67FF8DAAF261} = {98F39D90-94C6-4E01-BCFC-D3A1DAB74506} 49 | EndGlobalSection 50 | GlobalSection(ExtensibilityGlobals) = postSolution 51 | SolutionGuid = {5B48136F-F57B-4D26-927C-77EF7BA05EB8} 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /samples/SampleMVVM.Wpf/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |