Sorry if this is a bit of a ramble. I'm refactoring a project somewhat and reviewing how I do a few things.
I'm interested in any advice or guidance on the following. It's a common situation but the ProjectTracker example doesn't quite have it.
Let's say I have a class called TimeRecord. One of it's properties is ProjectId. When TimeRecord.ProjectId is set, I want some other properties of TimeRecord to be updated with infomation about the project that it gets from the db. Let's say ProjectName, ProjectCustomerName. I also want TimeRecord to have a bool property called ProjectIsValid which indicates if ProjectId refers to a valid Project. I have implemented these three "project" properties in TimeRecord as managed properties with private backing fields named _projectName etc ...
I'm thinking of this thread http://forums.lhotka.net/forums/permalink/33891/33892/ShowThread.aspx#33892 where Rocky recommends doing things in business rules rather than PropertyChanged events.
I have a readonly class called Project which gets the project info. It returns null if given a ProjectId that does not exist. I know that the merits of returning null is a whole other discussion but ... that's the way I do it. It efficiently returns null if Project == 0 as it will when TimeRecord is created.
So ... I have a business rule like this associated with the ProjectId property. It's really a bogus rule in the sense that it always returns true. I'm just making use of the fact that it will run it whenever ProjectId changes and at other appropriate times such as when TimeRecord is Fetched and Created.
private static bool ProjectRule(TimeRecord timeRec, RuleArgs e)
{
var project = Project.GetProject(timeRec.ProjectId);
timeRec._projectIsValid = (project != null);
if (timeRec._projectIsValid)
{
timeRec._projectName = project.Name;
timeRec._projectCustomerName = project.CustomerInfo.Name;
}
else
{
timeRec._projectName = String.Empty;
timeRec._projectCustomerName = String.Empty;
}
return true;
}
I also have this rule associated with ProjectIsValid
private static bool ProjectIsValidRule(TimeRecord timeRec, RuleArgs e)
{
if (!timeRec.ProjectIsValid)
{
e.Description = "Please select a project";
}
return timeRec.ProjectIsValid;
}
ProjectIsValid is set as a dependent property of ProjectId
I'm building WinForms apps and often bind this sort of property to an invisible checkbox. That lets me control where the error icon appears. That's also why I ProjectRule doesn't return timeRec.ProjectIsValid. If ProjectId is somehow on the UI (maybe a dropdown) then I don't necessarily want it showing the error icon.
That works nicely but there are a couple of awkward points.
The ProjectName and ProjectCustomerName "magically" change in the UI when I change ProjectId. That's good but it's only really working because of the WinForms behavior (bug?) which causes all properties to be refreshed. The only PropertyChanged event is for ProjectId. Unless excessive updates cause a performance issue, I'd like to future proof my code so it will work with WPF or whatever. What's a nice way to cause the other PropertyChanged events to happen?
You can see in ProjectRule why I chose private backing fields for those properties. Is there a better way of doing that? My code wouldn't work if ProjectRule was in a separate class. Do I need a SetProjectInfo() method? I guess that's the answer. If it's internal it will be hidden from any UI developer.
The way I've done this until now is with a ProjectInfo class defined within TimeRecord containing those project properties. I have a property called Project which does a lazy loading type thing. It checks if the private _projectInfo instance currently refers to the correct ProjectId and, if not, loads it with the correct data. Within TimeRecord I then refer to this.Project That works quite well but I do like the idea of putting stuff like this in a rule which might be normalized into another class. I nearly always access the project properties so I don't really gain anything from the lazy loading.
Thanks for any comments.
Ross
tetranz:The ProjectName and ProjectCustomerName "magically" change in the UI when I change ProjectId. That's good but it's only really working because of the WinForms behavior (bug?) which causes all properties to be refreshed. The only PropertyChanged event is for ProjectId. Unless excessive updates cause a performance issue, I'd like to future proof my code so it will work with WPF or whatever. What's a nice way to cause the other PropertyChanged events to happen?
If you attach the rule(s) to the "correct" properties, and use dependent properties so the rules run when appropriate, then WPF will just work (if you tell CSLA you are running in WPF).
In other words, attach the rules to ProjectName, and make ProjectId depend on ProjectName with AddDependentProperty() so when ProjectId changes the rules for ProjectName are checked.
If you set the Csla.ApplicationContext.PropertyChangedMode (?) to Xaml then CSLA raises PropertyChanged events properly to make sure things work right in WPF.
RockfordLhotka:If you attach the rule(s) to the "correct" properties, and use dependent properties so the rules run when appropriate, then WPF will just work (if you tell CSLA you are running in WPF).
In other words, attach the rules to ProjectName, and make ProjectId depend on ProjectName with AddDependentProperty() so when ProjectId changes the rules for ProjectName are checked.
If you set the Csla.ApplicationContext.PropertyChangedMode (?) to Xaml then CSLA raises PropertyChanged events properly to make sure things work right in WPF.
I think rule methods should be relatively narrowly focused –
each one should contain one rule.
But sometimes rules only apply in certain cases, and I have no
problem putting those conditions into the rule method too. So if a rule only
applies to an object that is not new, I’m happy to check IsNew in the
rule method and only really run the rule if it is false.
I do try to avoid putting multiple actual rules in a single rule
method – if for no other reason that you can’t get them to show up
in the UI correctly, but also because this makes the rules more maintainable.
Next, you should attach rules to the property they primarily
affect. Or another way to put it, is attach the rule to the property next to
which you want the icon to appear in the UI.
But some rules operate against multiple properties (reading or
even manipulating them). You should be able to use dependent properties to link
(one way or bi-directionally) those properties, so CSLA understands that when
the rules are checked for one of them, the rules should be checked for the
other(s) as well. If you do this, and tell CSLA you are using XAML, then CSLA
will also raise PropertyChanged events for all those properties so the UI
refreshes correctly.
Rocky
Copyright (c) Marimer LLC