Ticket Q410409
Visible to All Users

Domain Logic Base Class => Good Practice?

created 13 years ago

I created this base class for Domain Logic:

C#
using System; using System.ComponentModel; using System.Linq.Expressions; using DevExpress.ExpressApp; using DevExpress.Xpo; namespace Sphinx.Xaf.Demo.Module.Domain.Base { public class BaseLogic<T> where T : class { private WeakReference _entity; private bool _exclusivePropChange; /// <summary> /// Use the Init function to register onproperty changed events with this domain /// logic class /// </summary> public virtual void Init(){} public virtual void AfterConstruction(T target, IObjectSpace objectSpace){} public virtual void AfterConstruction(T target) { //assign the weakreference Entity = new WeakReference(target); } public virtual void OnDeleting(T target, IObjectSpace objectSpace){} public virtual void OnDeleting(T target){} public virtual void OnDeleted(T target, IObjectSpace objectSpace){} public virtual void OnDeleted(T target){} public virtual void OnSaving(T target, IObjectSpace objectSpace){} public virtual void OnSaving(T target){} public virtual void OnSaved(T target, IObjectSpace objectSpace){} public virtual void OnSaved(T target){} public virtual void OnLoaded(T target, IObjectSpace objectSpace){} public virtual void OnLoaded(T target) { //recreate the weakreference Entity = new WeakReference(target); } public void RegisterPropertyChanged<TProp>(Expression<Func<T, TProp>> property, Action<T, ChangeEventArgs<TProp>> onChanged) where TProp : class { RegisterPropertyChanged(property, x=> true, onChanged); } public void RegisterPropertyChanged<TProp>(Expression<Func<T, TProp>> property, Func<T, bool> onlyWhen, Action<T, ChangeEventArgs<TProp>> onChanged) where TProp : class { //parse memberexpression to check if only a property was provided var memberExpression = property.Body as MemberExpression; if (memberExpression == null) return; //extract the property name var memberName = memberExpression.Member.Name; //checks if weakreference was created and alive if (Entity == null || Entity.Target == null || !Entity.IsAlive) return; //if you would let your domain components derive from another class, this will not work var dcEntitity = Entity.Target as XPBaseObject; //checks if the target of weakreference is a DCEntity if (dcEntitity == null) return; //listen to the entity change event dcEntitity.Changed += (sender, args) => { //_exclusivePropChange is used to prevent an infinite loop of propertychanged events, //this is possible when a user assigns a new value to the property in the onChange Action if (_exclusivePropChange) return; //check for registered propertyname if (args.PropertyName == null || args.PropertyName != memberName || args.Reason != ObjectChangeReason.PropertyChanged) return; //check if the conditions to trigger onChange were met if (!onlyWhen(sender as T)) return; //execute onChanged action _exclusivePropChange = true; onChanged(sender as T, new ChangeEventArgs<TProp>(args, property)); _exclusivePropChange = false; }; } private WeakReference Entity { get { return _entity; } set { _entity = value; Init(); } } public class ChangeEventArgs<TProp> : EventArgs where TProp : class { private readonly Expression<Func<T, TProp>> _property; public T Entity { get; private set; } public TProp OldValue { get; private set; } public TProp NewValue { get; private set; } public void ChangeNewValue(TProp newValue) { //create an Action<T, TProp> to set the value of the property we intercept the change for var member = (MemberExpression)_property.Body; var param = Expression.Parameter(typeof(TProp), "value"); var set = Expression.Lambda<Action<T, TProp>>(Expression.Assign(member, param), _property.Parameters[0], param); //compile Linq expression, and immediatly run it set.Compile()(Entity, newValue); } public ChangeEventArgs(ObjectChangeEventArgs objectChangeEventArgs, Expression<Func<T, TProp>> property) { _property = property; Entity = objectChangeEventArgs.Object as T; NewValue = objectChangeEventArgs.NewValue as TProp; OldValue = objectChangeEventArgs.OldValue as TProp; } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public new Type GetType() { return base.GetType(); } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() { return base.ToString(); } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() { return base.GetHashCode(); } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) { return base.Equals(obj); } } } }

Could things go wrong by implementing the AfterConstruct, OnDeleted, etc in every Logic Component we use? Will this have impact on the Class Generation process of XAF?

If there is no drawback in doing things like this, shouldn't a baseclass like this be / become a first class citizen of XAF?

Answers

created 13 years ago (modified 12 years ago)

For those who are interested, this is the usage of the preceding BaseLogic<T>:

C#
[DomainLogic(typeof(IDocument))] public class DocumentLogic : BaseLogic<IDocument> { public override void Init() { //this will listen to the PropertyChange event of Description Property, //and executes the action after Description changes RegisterPropertyChanged(x => x.Description, (doc, args) => { doc.DocumentDate = DateTime.Now; }); //following line will only trigger the onChange when a certain condition was met, //in this case only when Description.Length > 5 RegisterPropertyChanged(x=> x.Description, x=> x.Description.Length > 5, (document, args) => { args.ChangeNewValue(args.NewValue.Substring(0,5)); }); } }

    Comments (2)
    Dennis Garavsky (DevExpress) 13 years ago

      Hello Pieter,
      Thank you for sharing this with other support center users. This technique is fine and there should not be known problems.
      I even remember that years back we used a similar concept for domain components (take special note of the EntityService base class used in this blog post).
      As for integrating this into XAF, thank you for the feedback. Our policies prevent us from integrating customer's code, but we will certainly take your suggestion into account.

      Apostolis Bekiaris (DevExpress) 12 years ago

        I added this class in eXpandFramework in version 13.1.4.13 thanks a lot Karel!

        Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

        Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.