Per-property authorization rule that depends on async, lazy-loaded property

Per-property authorization rule that depends on async, lazy-loaded property

Old forum URL: forums.lhotka.net/forums/t/11970.aspx


Chattererman posted on Tuesday, May 07, 2013

What is the best approach for implementing a per-property authorization rule that depends on the result of an async, lazy-loaded property?

Here's my suggestion:

 

[Serializable]

public sealed class Parent : BusinessBase<Parent>

{

public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);

public string Name

{

get { return GetProperty(NameProperty); }

set { SetProperty(NameProperty, value); }

}

public static readonly PropertyInfo<ChildrenList> ChildrenProperty = RegisterProperty<ChildrenList>(c => c.Children, RelationshipTypes.Child | RelationshipTypes.LazyLoad);

public ChildrenList Children

{

get

{

if (!FieldManager.FieldExists(ChildrenProperty))

{

if (IsNew)

LoadProperty(ChildrenProperty, ChildrenList.NewList());

else

{

ChildrenList.GetListAsync(Id, (o, e) =>

{

if (e.Error != null)

throw e.Error;

LoadProperty(ChildrenProperty, e.Object);

OnPropertyChanged(ChildrenProperty);

});

return null;

}

}

return GetProperty(ChildrenProperty);

}

}

protected override void AddBusinessRules()

{

base.AddBusinessRules();

BusinessRules.AddRule(new Required(NameProperty));

BusinessRules.AddRule(new MaxLength(NameProperty, 255));

BusinessRules.AddRule(new CanWriteParentNameRule(AuthorizationActions.WriteProperty, NameProperty));

BusinessRules.AddRule(new Dependency(ChildrenProperty, NameProperty));

}

}

 

public class CanWriteParentNameRule : AuthorizationRule

{

public CanWriteParentNameRule(AuthorizationActions action, IMemberInfo element)

: base(action, element) { }

 

protected override void Execute(AuthorizationContext context)

{

var parent = context.Target as Parent;

 

if (parent != null && parent.Children != null)

context.HasPermission = (parent.Children.Count == 0);

else

context.HasPermission = false;

}

}

The binding of the name property causes the authorization rule to be evaluated which in turn triggers the async retrieval by accessing the Children property. The property returns null and we default HasPermission to false until we have the children.

When the Children property eventually returns, it raises OnPropertyChanged which in turn (via the Dependency business rule) causes the name property rules to be re-evaluated including the authorization rule. This time the authorization rule gets the children and sets HasPermission accordingly.

What do you think? Does this seem plausible?

Thanks in advance,

Joe

 

JonnyBee replied on Wednesday, May 08, 2013

Hi,

First I'd like to question why you even consider to use a AuthorizationRule to perform lazy loading of a property?

Why not load/initilize the property right away in your data access? (at least for a new object !! preferrablyfor both new and existing)
Especially when you know at design time that you are going to need the children property for authorization.


Problem areas:

  1. As your code is now the property may be read a number of times and lazy loading triggered in parallel a number of times...
  2. Unless you set CacheResult to false - the Authz rule will only be executed ONCE and the result be cached in the BO.
  3. There can only be one authorization per AuthorizationAction and MethodInfo/Property - ie: you cannot add an extra IsInRole/IsNotInRole rule.

My preferences is:

  1. An authorization rule should NOT trigger lazy loading.
  2. Starting from CSLA 4.2 (or 4.3) you should use LoadPropertyAsync to lazy load the property (will make sure to only trigger 1 - one load)
  3. When you have "data driven" authorization I'd rather just override CanWriteProperty inside the BO to handle whether the property has been lazy loaded or not - and make sure to NOT trigger lazy loading.

 So I'd rather have code like this:

public override bool CanWriteProperty(IPropertyInfo property)
{
    if (!(base.CanWriteProperty(property))) return false; 
 
    if (property == NameProperty)
    {
        // check if ChildrenField has been loaded and has no rows 
        // will NOT trigger lazy loading of property. 
        var childrenFieldExistsAndHasNoRows = 
            FieldManager.FieldExists(ChildrenProperty) &&
            !ReadProperty(ChildrenProperty).Any();
        return childrenFieldExistsAndHasNoRows;
    }
    return true;
}

and still have the option of adding IsInRole/IsNotInRole authorization to the Name field.

Chattererman replied on Wednesday, May 08, 2013

Hi Jonny

In reality these business objects service a Silverlight application where they fit in the middle of a larger hierarchy. The children need to be lazy loaded because their retrieval could be expensive.

The basic requirements for this part of the hierarchy are that the parent's name cannot be changed when there are children present and, later, children cannot be added until the parent's name is 'valid'.

My initial stab at the first requirement was in fact to use the CanWriteProperty override but I opted for the AuthorizationRule because it seemed like a nice way to encapsulate the requirement in a well-defined class. To be honest I had forgotten about only being allowed to have one AuthorizationRule per property/method and about the CacheResult - thanks for the reminder.

As for your preferences:

1.       Triggering the lazy loading of the children within an AuthorizationRule was simply an unintentional side-effect that needed to be dealt with to satisfy the requirement.

2.       I'll certainly look into using LoadPropertyAsync for the lazy loading implementation. Examples are hard to come by so I guess I'll just have to man-up and break open Csla when necessary.

3.       I'll take your advice and re-implement the CanWriteProperty override whilst not triggering the lazy loading.

From point 3, I guess I'll just have to manually raise OnPropertyChanged for the parent's name property once the children have arrived to ensure that the UI can refresh the availability of the name field or could it be done through a DependencyRule? Not to worry.

Thanks for the advice.

 

 

JonnyBee replied on Wednesday, May 08, 2013

Hi,

I can post some examples on the LoadPropertyAsync and Rules tomorrow.

A Dependency rule may be used although I usually prefer another named rule like NotifyChanged just to clarifiy the purpose/action of the rule for readability in AddBusinessRules.  

Chattererman replied on Wednesday, May 08, 2013

This should do it,

 

public static readonly PropertyInfo<ChildrenList> ChildrenProperty = RegisterProperty<ChildrenList>(

          c => c.Rules, RelationshipTypes.Child | RelationshipTypes.LazyLoad);

public ChildrenList Children

{

      get

      {

          if (!FieldManager.FieldExists(ChildrenProperty))

          {

              if (IsNew)

                  LoadProperty(ChildrenProperty, ChildrenList.NewChildrenList());

              else

              {

                  LoadPropertyAsync(ChildrenProperty, (completed, parentId) => ChildrenList.GetChildrenListAsync(parentId, completed), ParentId);

                  return null;

              }

          }

          return GetProperty(ChildrenProperty);

      }

}

Thanks,

 

 

Copyright (c) Marimer LLC