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,
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:
My preferences is:
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.
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.
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.
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