Quick Objects Documentation Akal Tech Logo
Implementing Centralized and Reusable Business Logic

Glossary Item Box

Business Logic Framework - Tutorial 22:

Centralized business logic can have many great benefits for application development. As the applications are getting more and more complex with increasing demands for various interfaces, it becomes imperative to centralize all the logic. Without such an effort the business logic is not only scattered but also results in duplication of effort, wastage of time and resources.

Benefits of Centralized Business Logic:

 

To purpose of this tutorial is to demonstrate how you can leverage the Business Logic Framework's capabilities in building centralized business logic layer. The tutorial's target is to customize the Users class, and embed the registration functionality into it and all the validation rules are automatically applied.  Multiple interfaces can leverage the same functionality and the logic for registering new users. If this logic, validation and functionality was developed inside an user interface, any time the application needed the ability for the users to register via a different interface all the logic, validation and functionality will have to be duplicated. Such duplication can result in maintenance night mare in the long run. This tutorial covers the customization of a smart business entity, and being able to preserve your customizations through multiple code regeneration cycles.

The entity classes can be customized by adding custom code in the Custom class files (.NET 1.1) or adding partial class files (.NET 2.0 or later).

 If you use the Custom class files generated by the Quick Objects designer, you must not regenerate those files or your customizations will be lost. In case you really need to regenerate the Custom classes (only if you added/removed tables or changed names), you can create a back up of the custom classes before regenerating and manually merge them.

 If you are developing in .NET 2.0 or later please use the partial class feature and create a new file with your custom code, that way regeneration will not affect your code at all.
1 private Users _user = null;  
2 public Users User  
3 {  
4     get 
5     {  
6         if (_user == null)  
7         {  
8             _user = new Users();  
9         }  
10         return _user;  
11     }  
12 }  
13  
14  
15 private void Tutorial22_Leave(object sender, EventArgs e)  
16 {  
17     // Since the user is leaving this screen it is time to dispose of the object.  
18     if (_user != null)  
19     {  
20         _user.Dispose();  
21         _user = null;  
22     }  
23 }  
24  
25 private void btnSaveUser_Click(object sender, EventArgs e)  
26 {  
27     // We have encapsulated all the logic and validation requirements into our class by customizing the Users class.  
28     // Now the only thing we need to do is call the RegisterNewUser method and pass the user supplied values.   
29     // There is no need to perform any validation since the business object will automatically do that for us.  
30     // This method also gives you the flexibility and control to decide where you want to house your business logic, and  
31     // the same can then be reused by multiple types of user interfaces, web services or other points of integrations.  
32  
33     // NOTE: Please refer to the "CustomizedUsers.cs|vb" file in the QuickObjects.ObjectBase.BusinessLogicLayer project.  
34       
35     Users.MembershipStatus status = this.User.RegisterNewUser(this.txtUserName.Text, this.txtFirstName.Text, this.txtLastName.Text, this.txtSecurityQuestion.Text, this.txtSecurityAnswer.Text, this.txtEmail.Text, this.txtPassword.Text);  
36     if (status == Users.MembershipStatus.Success)  
37     {  
38         MessageBox.Show("Registration successful!");  
39     }  
40     else 
41     {  
42         MessageBox.Show("Failed to create a new user!" + Environment.NewLine + "Reason:" + Environment.NewLine + this.User.ErrorString);  
43     }  
44 }          
45  
1 Private _user As Users = Nothing 
2 Public ReadOnly Property User() As Users  
3     Get 
4         If _user Is Nothing Then 
5             _user = New Users()  
6         End If 
7         Return _user  
8     End Get 
9 End Property 
10  
11  
12 Private Sub Tutorial22_Leave(ByVal sender As ObjectByVal e As EventArgs)  
13     ' Since the user is leaving this screen it is time to dispose of the object.  
14     If _user IsNot Nothing Then 
15         _user.Dispose()  
16         _user = Nothing 
17     End If 
18 End Sub 
19  
20 Private Sub btnSaveUser_Click(ByVal sender As ObjectByVal e As EventArgs)  
21     ' We have encapsulated all the logic and validation requirements into our class by customizing the Users class.  
22     ' Now the only thing we need to do is call the RegisterNewUser method and pass the user supplied values.  
23     ' There is no need to perform any validation since the business object will automatically do that for us.  
24     ' This method also gives you the flexibility and control to decide where you want to house your business logic, and  
25     ' the same can then be reused by multiple types of user interfaces, web services or other points of integrations.  
26  
27     ' NOTE: Please refer to the "CustomizedUsers.cs|vb" file in the QuickObjects.ObjectBase.BusinessLogicLayer project.  
28  
29     Dim status As Users.MembershipStatus = Me.User.RegisterNewUser(Me.txtUserName.Text, Me.txtFirstName.Text, Me.txtLastName.Text, Me.txtSecurityQuestion.Text, Me.txtSecurityAnswer.Text, Me.txtEmail.Text, _  
30     Me.txtPassword.Text)  
31     If status = Users.MembershipStatus.Success Then 
32         MessageBox.Show("Registration successful!")  
33     Else 
34         MessageBox.Show("Failed to create a new user!" + Environment.NewLine + "Reason:" + Environment.NewLine + Me.User.ErrorString)  
35     End If 
36 End Sub 

All the registration process logic has been embedded in the Users class.

1 public partial class Users : Users_Base  
2 {  
3     public enum MembershipStatus : int 
4     {  
5         Success = 0,  
6         InvalidUserName = 1,  
7         InvalidPassword = 2,  
8         InvalidQuestion = 3,  
9         InvalidAnswer = 4,  
10         InvalidEmail = 5,  
11         DuplicateUserName = 6,  
12         DuplicateEmail = 7,  
13         UserRejected = 8,  
14         InvalidProviderUserKey = 9,  
15         DuplicateProviderUserKey = 10,  
16         ProviderError = 11,  
17         ValidationFailed = 12,  
18     }  
19  
20     private static int PASSWORD_HASHED = 1;  
21     // ****************************************  
22     // NOTE: For this sample We are using a feature called "partial classes" only available from .NET 2.0 onwards.  
23     // If you would like to implement this in .NET 1.1 you can use the generated  
24     // Customers class under the "Custom" folder. In that case it would be important for you to   
25     // NOT regenerate the "Custom" classes, or your changes will get overwritten.   
26     // Normally it is not necessary to regenerate "Custom" classes and it is only necessary add/remove   
27     // tables from the underlying schema. If you have customized code, please make sure to create a backup  
28     // before regenerating and merge your changes manually (Only applies to .NET 1.1).  
29     // ****************************************  
30  
31     #region Object Initialization Code  
32     // Override the OnObjectInitialized method and it can be used  
33     // to further customize your business object after the object has been initialized.  
34     // For example: Below we will add validation  
35     // to the FirstName and LastName fields.   
36  
37     protected override void OnObjectInitialized(ObjectEventArgs e)  
38     {  
39         // Make sure to call the base OnObjectInitialized method else the ObjectInitialized event handlers will not be fired.  
40         base.OnObjectInitialized(e);  
41  
42         // Starting v3.x you are able to leverage the power of built in validation and  
43         // the following code shows how to add complex validation.  
44         // Built-in Validator Types:  
45         // BaseFieldValidator (act as the basic validator that can ensure that a field marked with AllowNull = false can not have IsNull set to true.)  
46         // RequiredFieldValidator - This is generic validator that can check any field for null values and ensure that any field (irrespective of AllowNull) does not have a null value. For StringField's it will check to see if the passed in value is empty, and in such case also the validation will fail.  
47         // RangeFieldValidator - This is a specialized validator that can check for range of values. Minimum or Maximum values can be explicitely specified or other field instances can be specified for obtaining such values at the time of validation.  
48         // RegularExpressionValidator - This validator provides you extreme control over how you want each field value validated to match certain regular expression.  
49         // CompareFieldValidator - This field validator can compare the field being validated against another field value and various types of comparison's can be specified based on what type of fields are being compared.  
50  
51         // We are going to add a RequiredFieldValidator. This field validator  
52         // is not dependent on the IsNull property of the field, and hence you can  
53         // mark any field as required even if the field in the database allows nulls.  
54         // We are going to require that a UserName, FirstName, LastName and Email values must be specified in all Insert/Update operations.  
55         new RequiredFieldValidator(this.UserName, true);  
56         // If you need to do anything further with the validator then you can store a reference to the validator locally to further customize it (See below where we create an instance of RegularExpressionFieldValidator).  
57         // For RequiredFieldValidator we don't have to do anything, so we simply created a new instance and used the constructor overload that will  
58         // add the validator into the field's Validators collection.  
59         new RequiredFieldValidator(this.FirstName, true);  
60         new RequiredFieldValidator(this.LastName, true);  
61         new RequiredFieldValidator(this.Email, true);  
62         new RequiredFieldValidator(this.Password, true);  
63         new RequiredFieldValidator(this.SecurityAnswer, true);  
64  
65  
66         // By default the BaseFieldValidator can enforce a string's minimum and maximum length limits.  
67         // MaximumLength value is automatically generated from the field size in the database. However, we can specify the   
68         // MinimumLength to enforce that as well.   
69         // NOTE: If the BaseFieldValidator instance is removed from the Validators collection of the field then this minimum length will not be enforced.  
70         this.UserName.MinimumLength = 5;  
71  
72         // We can set a custom error message that will be formatted and returned when the validator fails.  
73         // For formatting options please see the documentation for each validator's CustomErrorMessage property.  
74         // BaseFieldValidator will replace {0} with the name or the alias of the field being validated, and {4} with the minimum required length of the field (only applicable to StringField).  
75         this.UserName.Validators[ValidatorTypes.BaseFieldValidator].CustomErrorMessage = "{0} must be at least {4} characters long.";   
76         this.Email.MinimumLength = 6;  
77         this.Email.Validators[ValidatorTypes.BaseFieldValidator].CustomErrorMessage = "{0} must be at least {4} characters long.";  
78         this.Password.MinimumLength = 6;  
79         this.Password.Validators[ValidatorTypes.BaseFieldValidator].CustomErrorMessage = "{0} must be at least {4} characters long.";  
80         this.SecurityAnswer.MinimumLength = 6;  
81  
82         // We want to ensure that the password and the SecurityAnswer are not the same. To ensure that we can add a compare field validator  
83         // that can compare the values of the Password and SecurityAnswer fields.  
84         CompareFieldValidator cmValidator = new CompareFieldValidator(this.SecurityAnswer, true);  
85         // We can specify the Comparison type of NotEqual.  
86         cmValidator.ComparisonType = CompareFieldValidator.ComparisonTypes.NotEqual;  
87         // Lets specify the field that will be compared.  
88         cmValidator.FieldToCompare = this.Password;  
89         // We can take it a step further and specify a CustomErrorMessage that will be formatted and returned in case the validation fails.  
90         cmValidator.CustomErrorMessage = "{0} value should not be the same as the value of {4}";  
91  
92  
93         // We also want to ensure that a valid email address is being provided so we will create a  
94         // RegularExpressionFieldValidator and attach it to the Email field.  
95         RegularExpressionFieldValidator emValidator = new RegularExpressionFieldValidator(this.Email, true);  
96         emValidator.Expression = @"^[A-Za-z0-9_\-\.]+@(([A-Za-z0-9\-])+\.)+([A-Za-z\-])+$";  
97         emValidator.CustomErrorMessage = "{0} value of {2} does not match the required pattern of an email address (person@domain.com)";  
98  
99         // We are specifying a RegularExpressionFieldValidator for the Password field.  
100         RegularExpressionFieldValidator pwValidator = new RegularExpressionFieldValidator(this.Password, true);  
101         pwValidator.Expression = @"^.*(?=.{6,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$";  
102         pwValidator.CustomErrorMessage = "{0} must contain at least one upper case and one lower case letter along with numbers and special characters. Allowed Characters: @ # $ % ^ & + = ";  
103       
104     }
105     #endregion  
106  
107     #region Create New User Code  
108  
109     /// <summary>  
110     /// RegisterNewUser method will create a new user account. If Customer role exists in the database then the newly created user will be given the "Customer" role otherwise no role will be given.  
111     /// </summary>  
112     /// <param name="UserName"></param>  
113     /// <param name="FirstName"></param>  
114     /// <param name="LastName"></param>  
115     /// <param name="SecurityQuestion"></param>  
116     /// <param name="SecurityAnswer"></param>  
117     /// <param name="Email"></param>  
118     /// <param name="Password"></param>  
119     /// <returns></returns>  
120     public MembershipStatus RegisterNewUser(  
121         String UserName,   
122         String FirstName,   
123         String LastName,  
124         String SecurityQuestion,  
125         String SecurityAnswer,  
126         String Email,  
127         String Password  
128         )  
129     {  
130         DateTime dt = Helper.RoundToSeconds(DateTime.UtcNow);  
131         try 
132         {  
133             // We set the business object's property values with values of passed in parameters.  
134             // Since we have set the ObjectMode to ObjectModes.Save all field's UseInSave properties will be automatically set to true.  
135             this.ObjectMode = ObjectModes.Save;  
136             this.UserName.Value = UserName;  
137             this.Password.Value = Password;  
138             this.FirstName.Value = FirstName;  
139             this.LastName.Value = LastName;  
140             this.Email.Value = Email;  
141             this.SecurityQuestion.Value = SecurityQuestion;  
142             this.SecurityAnswer.Value = SecurityAnswer;  
143             this.PasswordFormat.Value = PASSWORD_HASHED;  
144  
145  
146             this.LastLockoutDate.Value = DateTimeField.SqlMinimumDate;  
147             this.LastLoginDate.Value = dt;  
148             this.LastActivityDate.Value = dt;  
149             this.LastPasswordChangedDate.Value = dt;  
150  
151             this.IsActive.Value = true;  
152             this.IsLockedOut.Value = false;  
153             this.RequirePasswordChange.Value = false;  
154  
155             this.FP_AnswerAttemptCount.Value = 0;  
156             this.FP_AnswerAttemptWindowStart.Value = DateTimeField.SqlMinimumDate;  
157             this.FP_AttemptCount.Value = 0;  
158             this.FP_AttemptWindowStart.Value = DateTimeField.SqlMinimumDate;  
159               
160             // To ensure that these values pass our validation criteria  
161             // Lets check the value of IsValid property of the business object.  
162             if (!this.IsValid)  
163             {  
164                 // Since the object values do not conform to the requirements we can return the ValidationFailed status code.  
165                 return MembershipStatus.ValidationFailed;  
166             }  
167             else 
168             {   
169                 // Now that the validation has passed, lets create a MD5 hash of the password and store the salt and the password.  
170                 // MD5 hash is an effective technique of protecting passwords since the passwords can not be reverse engineered.  
171                 this.PasswordSalt.Value = Helper.GenerateSalt();  
172                 this.Password.Value = Helper.EncodePassword(this.Password.Value, this.PasswordSalt.Value);  
173  
174                 // Lets do the same for SecurityAnswer  
175                 this.SecurityAnswer.Value = Helper.TrimValue(this.SecurityAnswer.Value);  
176                 this.SecurityAnswer.Value = Helper.EncodePassword(this.SecurityAnswer.Value.ToLower(CultureInfo.InvariantCulture), this.PasswordSalt.Value);   
177  
178             }  
179  
180             // The following line ensure that if either one of the values is found in the database FindAndLoad will return true.  
181             this.MoreResults = true;  
182             this.Email.Value = Email;  
183             this.Email.UseInSearch = true;  
184             this.UserName.Value = UserName;  
185             this.UserName.UseInSearch = true;  
186  
187             // We don't want to select all the fields hence we can set the visibility of the fields to false.  
188             this.UseAllFieldsForDisplay(false);  
189             // We need at least one field visible so lets set the UserID's Visible property to true.  
190             this.UserID.Visible = true;  
191               
192             // FindAndLoad method works just like Find method but Loads the values of the first found row. If the query returned more than one row the rest of the rows are  
193             // still available in the ResultSet.  
194             if (!this.FindAndLoad())  
195             {  
196                 // Since FindAndLoad returned false, that means there is no user record that matches either the Email or the UserName.  
197  
198                 // The following is used to ensure that any database commands from this point onwards will be running inside a transaction.  
199                 this.UseTransaction = true;  
200  
201                 // Inser the user record  
202                 if (this.Insert())  
203                 {  
204                     // Since the user record was inserted successfully, lets check the database for a Role called "Customer"  
205                     // By calling the Join_UserID_UserRoles_Child method we are automatically creating an instance of type UserRoles and sharing the connection and transaction both object instances.  
206                     this.Join_UserID_UserRoles_Child();  
207                     // Lets extend it further to the Roles object instance by calling the Join_RoleID_Roles_Parent method.  
208                     this.UserID_UserRoles_Child.Join_RoleID_Roles_Parent();  
209  
210                     // Each of these instances can be used independently of each other.  
211                     // Here we will use the this.UserID_UserRoles_Child.RoleID_Roles_Parent instance to search for "Customer" role.  
212                     this.UserID_UserRoles_Child.RoleID_Roles_Parent.ObjectMode = ObjectModes.Search;  
213                     this.UserID_UserRoles_Child.RoleID_Roles_Parent.RoleName.Value = "Customer";  
214                     // Calling the FindAndLoad method on the Roles object instances stored at RoleID_Roles_Parent will now return true if the database  
215                     // does contain a record where RoleName is Customer.  
216                     if (this.UserID_UserRoles_Child.RoleID_Roles_Parent.FindAndLoad())  
217                     {  
218                         // Since we have found the RoleID lets save the UserRoles record that will grant the Customer Role to the User.  
219                         this.UserID_UserRoles_Child.ObjectMode = ObjectModes.Save;  
220                         // Since the RoleID value of the this.UserID_UserRoles_Child.RoleID_Roles_Parent has already been loaded  
221                         // we can simply assign it.  
222                         this.UserID_UserRoles_Child.RoleID.Value = this.UserID_UserRoles_Child.RoleID_Roles_Parent.RoleID.Value;  
223                         this.UserID_UserRoles_Child.UserID.Value = this.UserID.Value;  
224                         this.UserID_UserRoles_Child.IsActive.Value = true;  
225                         if (this.UserID_UserRoles_Child.Insert())  
226                         {  
227                             // Since the UserRole was inserted successfully, lets call the CommitTransaction method to commit the active transaction.  
228                             this.CommitTransaction();  
229                             return MembershipStatus.Success;  
230                         }  
231                         else 
232                         {  
233                             // Since the Insert failed on this.UserID_UserRoles_Child instance, lets capture the ErrorString property.  
234                             // The reason to copy error is just to make it easy for the consuming code to simply use the Users object instance's ErrorString property without having to lookat any other instances.  
235                             this.ErrorString = this.UserID_UserRoles_Child.ErrorString;  
236                             // Since the UserRole record was not created its time to rollback the transaction.  
237                             this.RollBackTransaction();  
238                             return MembershipStatus.ProviderError;  
239                         }  
240                     }  
241                     else 
242                     {  
243                         // Since there is no role in the database called "Customer" we can simply keep the User record without the role assignment.  
244                         // NOTE: This is just as a demonstration, and the actual code or logic will depend on your business requirements.  
245                         this.CommitTransaction();  
246                         return MembershipStatus.Success;  
247                     }  
248                 }  
249                 else 
250                 {  
251                     // Since the user record insert failed, lets rollback the transaction.  
252                     this.RollBackTransaction();  
253  
254                     // We can check the reason for the insert to fail by checking the IsValid property to ensure that all field values are valid.  
255                     if (!this.IsValid)  
256                     {  
257                         return MembershipStatus.ValidationFailed;  
258                     }  
259                     else 
260                     {  
261                         return MembershipStatus.ProviderError;  
262                     }  
263                 }  
264             }  
265             else 
266             {  
267                 // Since either Email or UserName has already been used we can find out which one has already been used by doing the following.  
268                   
269                 // If we set the UseInSearch of Email to false then only the UserName field will be used to search.  
270                 this.Email.UseInSearch = false;  
271                 System.Collections.ArrayList ar = new System.Collections.ArrayList();  
272                 string sql = this.GetSelectSQL(ref ar);  
273                 string s = sql;  
274                 if (this.FindAndLoad())  
275                     return MembershipStatus.DuplicateUserName;  
276                 else 
277                     return MembershipStatus.DuplicateEmail;  
278             }  
279         }  
280         catch 
281         {  
282             throw;  
283         }  
284  
285     }
286     #endregion  
287     #region Change User Password Code  
288     public MembershipStatus ChangeUserPassword(string username, string oldPassword, string newPassword)  
289     {  
290         try 
291         {  
292             // By setting the MoreResults to false we ensure that the all the specified conditions will be met by the FindAndLoad method.  
293             this.MoreResults = false;  
294             this.ObjectMode = ObjectModes.Search;  
295             this.UserName.Value = username;  
296             this.IsActive.Value = true;  
297             if (this.FindAndLoad())  
298             {  
299                 // After the FindAndLoad method the original salt value can be retreive from the PasswordSalt field's Value property.  
300                 string pass = Helper.EncodePassword(oldPassword, this.PasswordSalt.Value);  
301  
302                 // If the currently encoded password does not match the stored password then the user supplied incorrect password.  
303                 if (this.Password.Value != pass)  
304                 {  
305                     return MembershipStatus.InvalidPassword;  
306                 }  
307  
308                 // Now that we know that the user supplied the correct "old" password so now we can update the User record with the new password.  
309                 this.ObjectMode = ObjectModes.Save;  
310                 this.Password.Value = newPassword;  
311                 this.LastPasswordChangedDate.Value = DateTime.UtcNow;  
312                 // Lets make sure that the Password is valid. IsValid property on the business object will return true if all the fields about to be updated (or inserted in case of Insert)  
313                 // are valid or not.  
314                 if (!this.IsValid)  
315                 {  
316                     return MembershipStatus.ValidationFailed;  
317                 }  
318                 else 
319                 {  
320                     // Password is valid, so lets encode the password before saving.  
321                     this.Password.Value = Helper.EncodePassword(this.Password.Value, this.PasswordSalt.Value);  
322                 }  
323                 // Now we simply call the Update method to change the password and the LastPasswordChangedDate values.  
324                 if (this.Update())  
325                 {  
326                     return MembershipStatus.Success;  
327                 }  
328                 else 
329                 {  
330                     return MembershipStatus.ProviderError;  
331                 }  
332                   
333             }  
334             else 
335             {  
336                 return MembershipStatus.ProviderError;  
337             }  
338         }  
339         catch 
340         {  
341             throw;  
342         }  
343         //return MembershipStatus.ProviderError;  
344     }
345     #endregion  
346     #region CheckLogin code  
347     /// <summary>  
348     /// CheckLogin validates the authentication information against the information stored in the database.  
349     /// </summary>  
350     /// <param name="username"></param>  
351     /// <param name="password"></param>  
352     /// <returns></returns>  
353     public MembershipStatus CheckLogin(string username, string password)  
354     {  
355         try 
356         {  
357             // To ensure that the FindAndLoad method will only load the record if the UserName and the IsActive values match the specified values.  
358             this.MoreResults = false;  
359             // Here we are demonstrating an alternate way of performing searches. Instead of setting the ObjectMode to ObjectModes.Search we are setting  
360             // the UseInSearch properties of each individual field. End result will be the same.  
361             this.UserName.Value = username;  
362             this.UserName.UseInSearch = true;  
363     &nbs