From 03dadc33a46c00469a021f3577b99c1fefd9aad2 Mon Sep 17 00:00:00 2001 From: Ivan G Date: Fri, 29 Nov 2019 09:11:20 +0000 Subject: [PATCH] INPC support --- README.md | 3 +- azure-pipelines.yml | 13 ++-- doc/INotifyPropertyChanged.md | 27 ++++++++ .../NotifyPropertyChangedTest.cs | 67 +++++++++++++++++++ src/Config.Net.sln | 1 + src/Config.Net/Core/Interfacenterceptor.cs | 41 +++++++++++- 6 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 doc/INotifyPropertyChanged.md create mode 100644 src/Config.Net.Tests/NotifyPropertyChangedTest.cs diff --git a/README.md b/README.md index 2d4742b..3d1eae9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Config.Net [![NuGet](https://img.shields.io/nuget/v/Config.Net.svg)](https://www.nuget.org/packages/Config.Net) [![Build status](https://aloneguid.visualstudio.com/AllPublic/_apis/build/status/Config.Net)](https://aloneguid.visualstudio.com/AllPublic/_build/latest?definitionId=47) ![](https://img.shields.io/azure-devops/tests/aloneguid/AllPublic/47.svg) [![open collective backers and sponsors](https://img.shields.io/opencollective/all/config.svg)](https://opencollective.com/config) +# Config.Net [![NuGet](https://img.shields.io/nuget/v/Config.Net.svg)](https://www.nuget.org/packages/Config.Net) [![Build status](https://aloneguid.visualstudio.com/AllPublic/_apis/build/status/Config.Net)](https://aloneguid.visualstudio.com/AllPublic/_build/latest?definitionId=66) ![](https://img.shields.io/azure-devops/tests/aloneguid/AllPublic/66.svg) [![open collective backers and sponsors](https://img.shields.io/opencollective/all/config.svg)](https://opencollective.com/config) A comprehensive, easy to use and powerful .NET configuration library, fully covered with unit tests and tested in the wild on thousands of servers and applications. @@ -23,6 +23,7 @@ This library eliminates the problem of having configuration in different places, - [Nested Interfaces](doc/NestedInterfaces.md) - [Collections](doc/Collections.md) - [Binding to Interface Methods](doc/DynamicConfiguration.md) +- [INotifyPropertyChanged](doc/INotifyPropertyChanged.md) - Extending Config.Net - [Implementing a custom parser](doc/CustomParsers.md) - [Implementing a custom configuration store](doc/Stores_Custom.md) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 196b366..9f315d4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -61,8 +61,11 @@ steps: OverWrite: true flattenFolders: true -#- task: PublishBuildArtifacts@1 -# condition: "eq(variables['Build.SourceBranch'], 'refs/heads/master')" -# displayName: 'publish nugets' -# inputs: -# ArtifactName: nuget \ No newline at end of file +- task: NuGetCommand@2 + displayName: 'publish to nuget.org' + condition: "eq(variables['Build.SourceBranch'], 'refs/heads/master')" + inputs: + command: push + packagesToPush: '$(build.artifactstagingdirectory)/*.nupkg' + nuGetFeedType: external + publishFeedCredentials: 'nuget.org (aloneguid)' \ No newline at end of file diff --git a/doc/INotifyPropertyChanged.md b/doc/INotifyPropertyChanged.md new file mode 100644 index 0000000..eabd0f5 --- /dev/null +++ b/doc/INotifyPropertyChanged.md @@ -0,0 +1,27 @@ +# INotifyPropertyChanged Support + +INotifyPropertyChanged is part of .NET Framework and is often ised in situations when you want to monitor changes to a class' property. It is also an essential part of **Xamarin**, **WPF**, **UWP**, and **Windows Forms** data binding systems. + +Config.Net totally supports `INPC` interface out of the box, and all you need to do is derive your interface from `INPC`: + +```csharp +public interface IMyConfiguration : INotifyPropertyChanged +{ + string Name { get; set; } +} +``` + +then build your configuration as usual and subscribe to property changed event: + +```csharp +IMyConfiguration config = new ConfigurationBuilder() + //... + .Build(); + +config.PropertyChanged += (sender, e) => +{ + Assert.Equal("Name", e.PropertyName); +}; + +config.Name = "test"; //this will trigger PropertyChanged delegate +``` \ No newline at end of file diff --git a/src/Config.Net.Tests/NotifyPropertyChangedTest.cs b/src/Config.Net.Tests/NotifyPropertyChangedTest.cs new file mode 100644 index 0000000..2955f19 --- /dev/null +++ b/src/Config.Net.Tests/NotifyPropertyChangedTest.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Config.Net.Stores; +using Xunit; + +namespace Config.Net.Tests +{ + public class NotifyPropertyChangedTest + { + private INPC _interface; + private DictionaryConfigStore _store; + + public NotifyPropertyChangedTest() + { + _store = new DictionaryConfigStore(); + + _interface = new ConfigurationBuilder() + .UseConfigStore(_store) + .Build(); + } + + [Fact] + public void Change_property_calls_notify() + { + bool isSet = false; + string nameSet = null; + + _interface.PropertyChanged += (sender, e) => + { + isSet = true; + nameSet = e.PropertyName; + }; + + _interface.Name = "test"; + Assert.True(isSet); + Assert.Equal("Name", nameSet); + } + + [Fact] + public void Change_aliased_property_calls_notify() + { + bool isSet = false; + string nameSet = null; + + _interface.PropertyChanged += (sender, e) => + { + isSet = true; + nameSet = e.PropertyName; + }; + + _interface.AliasedName = "test"; + Assert.True(isSet); + Assert.Equal("Name1", nameSet); + } + } + + public interface INPC : INotifyPropertyChanged + { + string Name { get; set; } + + [Option(Alias = "Name1")] + string AliasedName { get; set; } + } + +} diff --git a/src/Config.Net.sln b/src/Config.Net.sln index 25c8ee0..69383fe 100644 --- a/src/Config.Net.sln +++ b/src/Config.Net.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{CDB02CCC-51F ..\doc\CustomParsers.md = ..\doc\CustomParsers.md ..\doc\DynamicConfiguration.md = ..\doc\DynamicConfiguration.md ..\doc\flatline.md = ..\doc\flatline.md + ..\doc\INotifyPropertyChanged.md = ..\doc\INotifyPropertyChanged.md ..\doc\jetbrains_rider.png = ..\doc\jetbrains_rider.png ..\doc\jetbrains_rider_small.png = ..\doc\jetbrains_rider_small.png ..\doc\NestedInterfaces.md = ..\doc\NestedInterfaces.md diff --git a/src/Config.Net/Core/Interfacenterceptor.cs b/src/Config.Net/Core/Interfacenterceptor.cs index bc0be37..b0dd9c4 100644 --- a/src/Config.Net/Core/Interfacenterceptor.cs +++ b/src/Config.Net/Core/Interfacenterceptor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Reflection; using System.Text; using Castle.DynamicProxy; @@ -14,6 +15,8 @@ class InterfaceInterceptor : IInterceptor private readonly string _prefix; private readonly DynamicReader _reader; private readonly DynamicWriter _writer; + private readonly bool _isInpc; + private PropertyChangedEventHandler _inpcHandler; public InterfaceInterceptor(Type interfaceType, IoHandler ioHandler, string prefix = null) { @@ -22,6 +25,7 @@ public InterfaceInterceptor(Type interfaceType, IoHandler ioHandler, string pref _prefix = prefix; _reader = new DynamicReader(prefix, ioHandler); _writer = new DynamicWriter(prefix, ioHandler); + _isInpc = interfaceType.GetInterface(nameof(INotifyPropertyChanged)) != null; } private ResultBox FindBox(IInvocation invocation) @@ -39,11 +43,13 @@ private ResultBox FindBox(IInvocation invocation) public void Intercept(IInvocation invocation) { + if (TryInterceptInpc(invocation)) return; + ResultBox rbox = FindBox(invocation); bool isRead = - (rbox is PropertyResultBox pbox && PropertyResultBox.IsGetProperty(invocation.Method)) || - (rbox is ProxyResultBox xbox && PropertyResultBox.IsGetProperty(invocation.Method)) || + (rbox is PropertyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) || + (rbox is ProxyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) || (rbox is MethodResultBox mbox && mbox.IsGettter) || (rbox is CollectionResultBox); @@ -55,7 +61,38 @@ public void Intercept(IInvocation invocation) else { _writer.Write(rbox, invocation.Arguments); + + TryNotifyInpc(invocation, rbox); + } + } + + private bool TryInterceptInpc(IInvocation invocation) + { + if (!_isInpc) return false; + + if (invocation.Method.Name == "add_PropertyChanged") + { + invocation.ReturnValue = + _inpcHandler = + (PropertyChangedEventHandler)Delegate.Combine(_inpcHandler, (Delegate)invocation.Arguments[0]); + return true; } + else if(invocation.Method.Name == "remove_PropertyChanged") + { + invocation.ReturnValue = + _inpcHandler = + (PropertyChangedEventHandler)Delegate.Remove(_inpcHandler, (Delegate)invocation.Arguments[0]); + return true; + } + + return false; + } + + private void TryNotifyInpc(IInvocation invocation, ResultBox rbox) + { + if (_inpcHandler == null || rbox is MethodResultBox) return; + + _inpcHandler.Invoke(invocation.InvocationTarget, new PropertyChangedEventArgs(rbox.StoreByName)); } } }