Skip to content

Instantly share code, notes, and snippets.

@runceel
Created April 19, 2022 12:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runceel/ef624a6d6e8a3b2b8ceb47a3fe269562 to your computer and use it in GitHub Desktop.
Save runceel/ef624a6d6e8a3b2b8ceb47a3fe269562 to your computer and use it in GitHub Desktop.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Disposables;
using System.Reflection.Metadata;
namespace BlazorApp1.Shared;
public class ReactivePropertyValidator : ComponentBase, IDisposable
{
private readonly CompositeDisposable _disposable = new();
[CascadingParameter]
public EditContext CurrentEditContext { get; set; } = default!;
private EditContext _originalEditContext = default!;
protected override void OnParametersSet()
{
if (_originalEditContext != CurrentEditContext)
throw new InvalidOperationException();
}
protected override void OnInitialized()
{
if (CurrentEditContext == null)
throw new InvalidOperationException();
_originalEditContext = CurrentEditContext;
_disposable.Add(new ReactivePropertyEventSubscription(_originalEditContext));
}
public void Dispose() => _disposable.Dispose();
}
class ReactivePropertyEventSubscription : IDisposable
{
private readonly CompositeDisposable _disposables = new();
private readonly IReadOnlyList<(FieldIdentifier FieldIdentifier, IReactiveProperty ReactiveProperty)> _properties;
private readonly EditContext _editContext;
private readonly ValidationMessageStore _messages;
public ReactivePropertyEventSubscription(EditContext editContext)
{
_editContext = editContext;
_messages = new ValidationMessageStore(_editContext);
_properties = CollectReactiveProperties();
SubscribeHasErrorsChanged();
}
private IReadOnlyList<(FieldIdentifier FieldIdentifier, IReactiveProperty ReactiveProperty)> CollectReactiveProperties()
{
return _editContext.Model.GetType()
.GetProperties()
.Where(x => x.PropertyType.IsGenericType && x.PropertyType.IsAssignableTo(typeof(IReactiveProperty)))
.Select(x => (Name: x.Name, ReactiveProperty: (IReactiveProperty?)x.GetValue(_editContext.Model)))
.Where(x => x.ReactiveProperty != null)
.Select(x => (
FieldIdentifier: new FieldIdentifier(x.ReactiveProperty!, nameof(IReactiveProperty.Value)),
ReactiveProperty: x.ReactiveProperty!))
.ToList();
}
private void SubscribeHasErrorsChanged()
{
foreach (var property in _properties)
{
property.ReactiveProperty
.ObserveErrorChanged
.Subscribe(x =>
{
_messages.Clear(property.FieldIdentifier);
if (x is not null)
{
foreach (var message in x.OfType<string>())
{
_messages.Add(property.FieldIdentifier, message);
}
}
_editContext.NotifyValidationStateChanged();
})
.AddTo(_disposables);
}
}
public void Dispose()
{
_messages.Clear();
_disposables.Dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment