reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.05k stars 1.12k forks source link

Add ViewModel support for binding DTOs #541

Open bradphelan opened 10 years ago

bradphelan commented 10 years ago

Given a DTO object defined like below

[DataContract(Name="MoineauCorrection", Namespace="http://weingartner.com/")]
public class MoineauCorrection : Immutable, IEquatable<MoineauCorrection>
{
    #region Main
    [DataMember]
    public bool Visible{get; private set;}
    [DataMember]
    public double Peak{ get; private set; }
    [DataMember]
    public double Flank{ get; private set; }
    [DataMember]
    public double Valley{ get; private set; }
    [DataMember]
    public string Name{ get; private set; }

}

Note there are no public setters. The object once created is immutable and can be treated like a value.

I generate a view model for this via the ImmutableSupportViewModelBase class. For example.

public class MoineauPumpCorrectionViewModel : 
    ImmutableSupportViewModelBase
    <MoineauPumpCorrectionViewModel, MoineauCorrection>
{

    public MoineauPumpCorrectionViewModel
        ( IIOService io
        ,  MoineauCorrection c = null 
        )  
        : base(c) 
    {

        // Add DTO properties to be exposed as data bindable properties.
        // The properties are generated as C# dynamic properties with hooks
        // to generate the correct INPC events
        AddProperty(p => p.Visible);
        AddProperty(p => p.Peak);
        AddProperty(p => p.Flank);
        AddProperty(p => p.Valley);
        AddProperty(p => p.Name);

        /////////////////////////////////////////////////////////////////////////////////////////
        // Validations based on FluentValidation package
        RuleFor(p => p.Data.Name)
            .Must(v => !String.IsNullOrWhiteSpace(v))
            .WithMessage("cannot be empty")
            .OverridePropertyName("Name");

        RuleFor(p => p.Data.Peak)
            .LengthInclusiveBetween(-0.010, 0.010, LengthUnits.Meters);

        RuleFor(p => p.Data.Valley)
            .LengthInclusiveBetween(-0.010, 0.010, LengthUnits.Meters);

        RuleFor(p => p.Data.Flank)
            .LengthInclusiveBetween(-0.01, 0.010, LengthUnits.Meters);

        Validate();

    }
}

Bindings are made within XAMLto the dynamic properties generated above in the normal way. The Data property on the viewmodel holds the most current version of the edited object.

Theoretically some typesafe binders could be created for static binding but formyself I prefer binding within XAML

You can subscribe to changes on the DTO by

 viewModel.WhenAnyValue(p=>p.Data)

in the usual ReactiveUI way. If you wish to subscribe to changes on specific combinations of subproperties of the DTO you can do

 viewModel.DataWhenAnyValue
     ( p =>p.Peak
     , p =>p.Name
     , (peak, name) => DoSomething(peak, name)
     );

To get a Lens ( a read write sub view ) onto a sub property of the DTO you could do

   var nameLens = viewModel.Focus( p=>p.Name )

and set the current value of the property ( thus creating a new current version of the DTO )

   name.Current = "Engineering";

Lens's are very clever in that they can give you read write access to a nested immutable property. For example your DTO had a nested property called Keys with

  class Keys : Immutable {
        string key1{public get;private set;}
        string key2{public get;private set;}
        string key3{public get;private set;}
  }

   var lensKey1 = viewModel.Focus( p=>p.Keys.key1);
   kensKey1.Current = "hi there";

The inbuilt validation let's you subscribe easily to changes in your DTO only if they are valid.

viewModel
    .WhenAnyValue(p=>p.Data)
    .Where(d=>viewModel.IsValid)

The ViewModel can be decorated with commands and custom properties in the the usual RUI way.

The above code is based on but not integrated into any ReactiveUI branch. Maybe it doesn't interest anybody. If such magic is interesting let me know and I can spend some time extracting it from my production code.

bradphelan commented 10 years ago

You may wonder how the immutable objects are updated without a constructor passing in new values. The interface is

MoineauCorrection correction;
correction = correction.Set(p=>p.Peak, 10);
correction = correction.Set(p=>p.Name,"Fred");

underneath this there is some reflection code that shallow clones the objects then using reflection set's the private property. It's as good as you can get in C# to the F# method of updating records.