-
-
Notifications
You must be signed in to change notification settings - Fork 41
Reactive
The Reactive
module provides a XAF DSL API for functional/stateless implementations.
This is a platform agnostic
module. All modules that use it as a dependency do not use controllers. Instead they use existing or new XAF events where they are modeled as operators with the prefix When
.
Observables are nothing more than a Type which provides operators/methods to CombineLatest, Merge, Zip, ObserveOn(Scheduler) using LINQ style syntax see (ReactiveX.IO).
An operator is an c# extension method. Such methods are static and best practice is to designed them without storing state as they may be concurrent. To pass the state we use the operator (method) arguments and it return value. Existing operators are more than enough to handle any case and you should avoid custom IObservable<T>
implementation as they may misbehave when concurrency introduced.
For more details see RX Contract.
For example to create an operator that emits when a XAF ListView created you may write:
public static IObservable<(XafApplication application, ListViewCreatedEventArgs e)> WhenListViewCreated(this XafApplication application){
return Observable
.FromEventPattern<EventHandler<ListViewCreatedEventArgs>, ListViewCreatedEventArgs>(
h => application.ListViewCreated += h, h => application.ListViewCreated -= h)
.TransformPattern<ListViewCreatedEventArgs, XafApplication>().TraceRX();
}
The above WhenListViewCreated
operator returns an IObservable<(XafApplication application, ListViewCreatedEventArgs e)
which may be not so handy in some cases. So let's write another one that projects that type to a ListView
public static IObservable<ListView> ToListView(this IObservable<(XafApplication application, ListViewCreatedEventArgs e)> source){
return source.Select(_ => _.e.ListView);
}
As you can see I used as input the output of the WhenListViewCreated and with the Select
I can project it to any type I like. System.ValueTuple is a great help here as you most probably want to avoid polluting your code base with classes that do noting more than storing state in a property.
The Xpand.XAF.Modules.Reactive
modules already provides a great number of operators found at Xpand.XAF.Modules.Reactive.Services namespace.
Now let's use the previous two operators to await the first Customer ListView created since our application start.
ListView listView=await application.WhenListViewCreated().ToListView().When(typeof(Customer))
Similarly to get the first new Customer created we can write:
Customer listView=await application.NewObject<Customer>()
Finally let's combine those await so to add that customer to the CollectionSource of the first view created:
var listView = application.WhenListViewCreated().ToListView().When(typeof(Customer));
var newCustomer = application.NewObject<Customer>();
await newCustomer.CombineLatest(listView, (customer, view) => {
view.CollectionSource.Add(customer);
return customer;
})
And so on, the sky is the limit here as you can write custom operators just like a common c# extension method that extends and returns an IObservable<T>
. All modules of this repository that have a dependency to the Reactive package are developed similar to the above example. Explore their docs, tests, source to find endless examples.
Possible future improvements:
- Any other need you may have.
Let me know if you want me to implement them for you.
All Xpand.XAF.Modules that reference this package are developed the RX way. Following the functional RX paradigm all modules do not use controllers rather describe the problem by composing events.
Below we will add interesting examples. All methods can live in a static class.
-
First we register the action inside our module Setup method as registration needs an
ApplicationModulesManager
. TheRegisterViewSimpleAction
is anObservable
which we need toSubscribe
to it else it does nothing, same as IEnumerable does nothing if we do not enumerate. We pass thethis
argument to dispose subscription resources along with the module.public override void Setup(ApplicationModulesManager manager){ base.Setup(manager); manager.RegisterViewSimpleAction(nameof(MyCustomAction)).Subscribe(this); }
-
Previously we used the
nameof(MyCustomAction)
to provide an action id, so now we will implement it like below:public static SimpleAction MyCustomAction(this (MyCustomModule, Frame frame) tuple) => tuple .frame.Action(nameof(MyCustomAction)).As<SimpleAction>();
now our code consumers can use the next snippet at any place they have an Application dependency (Controller, Module, XafApplication, Program.cs, Global.asax.cs etc.):
Application.WhenViewOnFrame(typeof(MyBO), ViewType.ListView, Nesting.Nested) .SelectMany(frame => frame.Action<MyCustomModule>().MyCustomAction().WhenExecute()) .ExecuteTheAction() .Subscribe(this);
For more examples consider the existing tests.
-
Modify view artifacts (ViewItem, ListEditor, Business object, Nested Views) by implementing the
ExecuteTheAction
from previous step.private static IObservable<Unit> ExecuteTheAction(this IObservable<SimpleActionExecuteEventArgs> source) => source.Do(e => { //get hold of the action var simpleAction = e.Action; //get the frame for the frame.GetController() var frame = simpleAction.Controller.Frame; var detailView = simpleAction.View<DetailView>(); //get a propertyeditor var boNamePropertyEditor = detailView.GetPropertyEditor<MyCustomBO>(bo =>bo.Name ); //get the listpropertyeditor var listPropertyEditor = detailView.GetListPropertyEditor<MyCustomBO>(bo => bo.Childs); //get the nestedlistView var nestedListView = listPropertyEditor.Frame.View; //implement any master-child view scenario as all dependencies are known });
- Modifying View artifacts similar to what was demoed with working with Actions:
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenApplication().WhenListViewCreated() .When(typeof(MyCustomBo)) .ControlsCreated() .Do(listview => { //filter the listview listview.CollectionSource.Criteria[""] = ... }) .Subscribe(this); }
- Utilizing the
Frame
useful to get for example traditional XAF Controllers.public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenApplication().WhenViewOnFrame(typeof(MyCustomBo),ViewType.ListView) .Do(frame => { //get a xaf build in controller var listViewProcessCurrentObjectController = frame.GetController<ListViewProcessCurrentObjectController>(); }) .Subscribe(this); }
- Working on Master-detail scenario: In previous step we saw how to wait for a view on a frame. In a master-detail scenario we need to wait for two views on a frame and then using the ReactiveX Zip operator to pair them together.
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); //every time a MyCustom DetailView created together wuth a MyCustomBoChildListView moduleManager.WhenApplication().WhenViewOnFrame(typeof(MyCustomBo),ViewType.DetailView) .Zip(moduleManager.WhenApplication().WhenViewOnFrame(typeof(MyCustomBoChild),ViewType.ListView), //emit the created pair of frames (masterFrame, nestedFrame) => { //set the parent object OrderCount to the count of child objects ((MyCustomBo) masterFrame.View.CurrentObject).OrderCount = nestedFrame.View.AsListView() .CollectionSource.Objects<MyCustomChildBo>().Count(); //return void return Unit.Default; }) .Subscribe(this); }
- Use a NonPersistentObjectSpace to query the members with a persistent type.
public class NonPersistentObject{ public PersistentObject PersistentObject{ get; set; } } var listView = application.NewView<ListView>(typeof(NonPersistentObject)); listView.ObjectSpace.GetObjects<PersistentObject>().ToArray().Length.ShouldBeGreaterThan(0);
- Populate a ListView at any context (Popup, Navigation, Root, Nested)
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenApplication().WhenListViewCreating(typeof(NonPersistentObject)) .SelectMany(tuple => ((NonPersistentObjectSpace) tuple.args.ObjectSpace) .WhenObjectsGetting().Do(_ => { _.e.Objects = new BindingList<NonPersistentObject>(); }) ) .Subscribe(this); }
-
Add a new member to an existing BO as:
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenCustomizeTypesInfo() .Do(_ => (_.e.TypesInfo.FindTypeInfo(typeof(MyCustomBO))).CreateMember("MemberName", typeof(string))) .Subscribe(this); }
-
Extend the ListView model with the IModelListViewTest interface.
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenExtendingModel() .Do(_ => _.Add<IModelListView,IModelListViewTest>()) .Subscribe(this); }
-
Generate model nodes, for example add a new IModelFileImageSource.
public override void Setup(ApplicationModulesManager moduleManager){ base.Setup(moduleManager); moduleManager.WhenGeneratingModelNodes(modelApplication => modelApplication.ImageSources) .Do(_ => _.AddNode<IModelFileImageSource>()) .Subscribe(this); }
The module is not bound to DevExpress versioning, which means you can use the latest version with your old DevExpress projects Read more.
The module follows the Nuget Version Basics.
.NetFramework: net461
DevExpress.ExpressApp | Any |
Fasterflect.Xpand | 2.0.7 |
JetBrains.Annotations | 2020.1.0 |
System.Interactive | 4.1.1 |
System.Reactive | 4.4.1 |
System.ValueTuple | 4.5.0 |
Xpand.Extensions | 2.202.57 |
Xpand.Extensions.Reactive | 2.202.58 |
Xpand.Extensions.XAF | 2.202.58 |
Xpand.Patcher | 2.0.24 |
Xpand.VersionConverter | 2.202.10 |
To Step in the source code
you need to enable Source Server support
in your Visual Studio/Tools/Options/Debugging/Enable Source Server Support. See also How to boost your DevExpress Debugging Experience.
If the package is installed in a way that you do not have access to uninstall it, then you can unload
it with the next call at the constructor of your module.
Xpand.XAF.Modules.Reactive.ReactiveModuleBase.Unload(typeof(Xpand.XAF.Modules.Reactive.ReactiveModule))
The module is tested on Azure for each build with these tests. All Tests run as per our Compatibility Matrix