Doctors, Patients and all those other people - revision 2

XAF Team Blog
21 March 2008

Yesterday I blogged about a particular business class modelling problem pertaining to people who can have different non-exclusive roles. You can find that article here.

In the forum, Robert pointed out that some things could be improved about the UI of the sample:

  1. It shouldn't be possible to delete the standard roles that are created during application initialization
  2. It would be nice if the link dialog that is used to assign new roles to a person would only show those roles that haven't been assigned to that person yet

I started working on suggestion number 1, and I found that I had to make a few changes to my sample to achieve an easy solution. It is possible to prevent deletion of objects on several different levels, and I decided to do it through permissions. Our security system also offers a different approach to preventing creation of new instances (different from the internal constructors I was using previously), so I decided to combine the two.

Configuring type specific permissions is only possible in conjunction with our "Complex Security System" module, so I had to configure my application to use this first.

Assigning permissions in the complex security system works by way of roles (you guessed it!), and I had one immediate problem with this: my Role class clashed with our existing Role class for that security system, so I renamed my class to PersonRole - a more appropriate name anyway.

The complex security system always creates a role called "Default", and since I didn't want to dilute my sample any more than necessary, I simply used the (standard in new solutions) Active Directory Authentication Strategy and the Default role for my permission settings. This is the code I added to my UpdateDatabaseAfterUpdateSchema method to establish the permissions:

  Role defaultRole = Session.FindObject(
new BinaryOperator("Name", "Default"));
if (defaultRole == null)
defaultRole = new Role(Session) { Name = "Default" };
while (defaultRole.PersistentPermissions.Count > 0)
Session.Delete(defaultRole.PersistentPermissions[0]);
defaultRole.AddPermission(new ObjectAccessPermission(
typeof(object), ObjectAccess.AllAccess));
defaultRole.AddPermission(new ObjectAccessPermission(
typeof(PersonRole), ObjectAccess.Delete, ObjectAccessModifier.Deny));
defaultRole.AddPermission(new ObjectAccessPermission(
typeof(PersonRole), ObjectAccess.Create, ObjectAccessModifier.Deny));
defaultRole.Save( );

This code either finds an existing Default role or otherwise just creates it. Then it clears any existing permissions from the role and adds those necessary to allow access to all objects, with the exception of Delete and Create rights for the PersonRole type.

The final changes I made at this point were largely cosmetic. I changed the names of all methods to reflect the name change of the Role class, and I configured all my classes to have public constructors again, and the CreatePersonRoleIfNeeded method to use the simpler overload of the Activator.CreateInstance method.

That's part 1 done - it is no longer possible to either create or delete PersonRole instances or those of derived types at runtime. (I'm sure somebody will very soon find something I missed... :-))

Moving on to part 2. When bringing up the dialog to assign roles, it should only list the roles that haven't been assigned yet. Luckily, XAF supports several attributes that allow to influence the content of lookup collections. In this case I decided to use the DataSourcePropertyAttribute to fetch the content for the lookup as an entire separate collection. The code to create that separate collection is this:

new XPCollection(
PersistentCriteriaEvaluationBehavior.InTransaction,
Session,
new NotOperator(new ContainsOperator("RolePersons",
new BinaryOperator("This", this))));

I think it's pretty much self-explaining - a query is defined that fetches all those PersonRole that don't have a reference to the current RolePerson in their RolePersons collection. I was able to find that our Knowledge Base article A2404 describes this approach in more detail, if you're curious about it.

There's one special option being used in this code, which is the PersistentCriteriaEvaluationBehavior.InTransaction flag. This is one of the use cases for this flag - since I want to see data that has already been changed in the current (non-committed) unit of work, I need to use this flag. Otherwise my query would return the status of configured roles outside the current unit of work, which obviously doesn't help me.

Here's how this collection is assigned to be used as the lookup source for the Roles property:

  [Association("RolePerson-Roles")]
[DataSourceProperty("AvailableRoles")]
public XPCollection Roles {
get {
return GetCollection("Roles");
}
}

[Browsable(false)]
public XPCollection AvailableRoles {
get {
return new XPCollection(
PersistentCriteriaEvaluationBehavior.InTransaction,
Session,
new NotOperator(new ContainsOperator("RolePersons",
new BinaryOperator("This", this))));
}
}

When I tested this code, I found that our dialog calls into the AvailableRoles property several times. The article I linked before therefore uses a variable to store the collection away for later reuse, so that the collection is only fetched once. Of course this would probably not be a major problem in this particular case, since it can probably be assumed that the number of available roles is not going to be huge. I still decided to make the code more efficient by storing away the collection.

Now, the problem with this is that the collection is still going to change every time the Roles collection changes. So I had to hook into an event for the Roles collection to detect when it changes and reset my temporary variable from there. Only when the temporary variable is in its initial state do I refetch the collection. The resulting code is less concise than that above, which is why I decided not to show it here - have a look at the complete sample to see how this works.

Right, that's it. The download of the new version is here: XAFRolesRev2.zip

Note: before you run this sample, you should probably drop the database XAFRoles to have everything recreated. I have not tested what's going to happen if you run this on the basis of the other database and I have not changed the connection string.

Again, I'm going to post to the forum about this in a few minutes. I've decided to move this sample forward as much as necessary - so please feel free to request any other changes or additions you would like to see.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
Tags
No Comments

Please login or register to post comments.