PageRenderTime 59ms CodeModel.GetById 41ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/BusinessBase.cs

#
C# | 669 lines | 315 code | 92 blank | 262 comment | 38 complexity | e56e345545aef8f8a9bb085e77962626 MD5 | raw file
  1namespace BlogEngine.Core
  2{
  3    using System;
  4    using System.Collections.Generic;
  5    using System.Collections.Specialized;
  6    using System.ComponentModel;
  7    using System.Globalization;
  8    using System.Security;
  9    using System.Text;
 10    using System.Threading;
 11    using System.Web.Security;
 12
 13    /// <summary>
 14    /// This is the base class from which most business objects will be derived. 
 15    ///     To create a business object, inherit from this class.
 16    /// </summary>
 17    /// <typeparam name="T">
 18    /// The type of the derived class.
 19    /// </typeparam>
 20    /// <typeparam name="TKey">
 21    /// The type of the Id property.
 22    /// </typeparam>
 23    [Serializable]
 24    public abstract class BusinessBase<T, TKey> : IDataErrorInfo, INotifyPropertyChanged, INotifyPropertyChanging, IChangeTracking, IDisposable
 25        where T : BusinessBase<T, TKey>, new()
 26    {
 27        #region Constants and Fields
 28
 29        /// <summary>
 30        /// The broken rules.
 31        /// </summary>
 32        /// <remarks>
 33        /// This has been updated from using the old StringDictionary class from .Net 1.1. The StringDictionary class
 34        /// used case-insensitive keys, so this one needs to have StringComparer.OrdinalIgnoreCase passed in the constructor for
 35        /// backwards compatibility.
 36        /// 
 37        /// INotifyPropertyChanging is implemented in case down the line someone wants to create a provider that uses BusinessBase
 38        /// objects as Linq-To-SQL entities(DataContext performance skyrockets when this is used). 
 39        /// 
 40        /// </remarks>
 41        private readonly Dictionary<String, String> brokenRules = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
 42
 43        /// <summary>
 44        /// The changed properties.
 45        /// </summary>
 46        private readonly HashSet<string> changedProperties = new HashSet<string>();
 47
 48        /// <summary>
 49        /// The date created.
 50        /// </summary>
 51        private DateTime dateCreated = DateTime.MinValue;
 52
 53        /// <summary>
 54        /// The date modified.
 55        /// </summary>
 56        private DateTime dateModified = DateTime.MinValue;
 57
 58        /// <summary>
 59        /// Initializes a new instance of the <see cref="BusinessBase{T,TKey}"/> class. 
 60        /// </summary>
 61        protected BusinessBase()
 62        {
 63            this.New = true;
 64            this.IsChanged = true;
 65        }
 66
 67        #endregion
 68
 69        #region Events
 70
 71        /// <summary>
 72        ///     Occurs when the class is Saved
 73        /// </summary>
 74        public static event EventHandler<SavedEventArgs> Saved;
 75
 76        /// <summary>
 77        ///     Occurs when the class is Saved
 78        /// </summary>
 79        public static event EventHandler<SavedEventArgs> Saving;
 80
 81        /// <summary>
 82        ///     Occurs when this instance is marked dirty. 
 83        ///     It means the instance has been changed but not saved.
 84        /// </summary>
 85        public event PropertyChangedEventHandler PropertyChanged;
 86
 87        /// <summary>
 88        /// Event raised when an instance is about to change one of its property values.
 89        /// </summary>
 90        public event PropertyChangingEventHandler PropertyChanging;
 91
 92        #endregion
 93
 94        #region Properties
 95
 96        /// <summary>
 97        ///     Gets or sets the date on which the instance was created.
 98        /// </summary>
 99        public DateTime DateCreated
100        {
101            get
102            {
103                return this.dateCreated == DateTime.MinValue ? this.dateCreated : this.dateCreated.AddHours(BlogSettings.Instance.Timezone);
104            }
105
106            set
107            {
108                this.SetValue("DateCreated", value, ref this.dateCreated);
109            }
110        }
111
112        /// <summary>
113        ///     Gets or sets the date on which the instance was modified.
114        /// </summary>
115        public DateTime DateModified
116        {
117            get
118            {
119                return (this.dateModified == DateTime.MinValue ? this.dateModified : this.dateModified.AddHours(BlogSettings.Instance.Timezone));
120            }
121
122            set
123            {
124                this.SetValue("DateModifier", value, ref this.dateModified);
125                //  this.dateModified = value;
126            }
127        }
128
129        /// <summary>
130        ///     Gets an error message indicating what is wrong with this object.
131        /// </summary>
132        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
133        public string Error
134        {
135            get
136            {
137                return this.ValidationMessage;
138            }
139        }
140
141        /// <summary>
142        ///     Gets or sets the unique Identification of the object.
143        /// </summary>
144        public TKey Id { get; set; }
145
146        /// <summary>
147        ///     Gets a value indicating whether if this object's data has been changed.
148        /// </summary>
149        public virtual bool IsChanged { get; private set; }
150
151        /// <summary>
152        ///     Gets a value indicating whether if this object is marked for deletion.
153        /// </summary>
154        public bool Deleted { get; private set; }
155
156        /// <summary>
157        ///     Gets a value indicating whether if this is a new object, False if it is a pre-existing object.
158        /// </summary>
159        public bool New { get; private set; }
160
161        /// <summary>
162        ///     Gets a value indicating whether the object is valid or not.
163        /// </summary>
164        public bool Valid
165        {
166            get
167            {
168                this.ValidationRules();
169                return this.brokenRules.Count == 0;
170            }
171        }
172
173        /// ///
174        /// <summary>
175        ///     Gets if the object has broken business rules, use this property to get access
176        ///     to the different validation messages.
177        /// </summary>
178        public virtual string ValidationMessage
179        {
180            get
181            {
182                if (!this.Valid)
183                {
184                    var sb = new StringBuilder();
185                    foreach (string messages in this.brokenRules.Values)
186                    {
187                        sb.AppendLine(messages);
188                    }
189
190                    return sb.ToString();
191                }
192
193                return string.Empty;
194            }
195        }
196
197        /// <summary>
198        ///     Gets a collection of the properties that have 
199        ///     been marked as being dirty.
200        /// </summary>
201        protected virtual HashSet<string> ChangedProperties
202        {
203            get { return this.changedProperties; }
204        }
205
206        /// <summary>
207        /// Gets whether or not the current user owns this object.
208        /// </summary>
209        /// <returns></returns>
210        public virtual bool CurrentUserOwns
211        {
212            get { return false; }
213        }
214
215        /// <summary>
216        /// Gets whether the current user can delete this object.
217        /// </summary>
218        /// <returns></returns>
219        public virtual bool CanUserDelete
220        {
221            get { return Security.IsAdministrator; }
222        }
223
224        /// <summary>
225        /// Gets whether the current user can edit this object.
226        /// </summary>
227        /// <returns></returns>
228        public virtual bool CanUserEdit
229        {
230            get { return Security.IsAdministrator; }
231        }
232
233        /// <summary>
234        /// Gets a value indicating whether the object has been disposed.
235        /// <remarks>
236        /// If the objects is disposed, it must not be disposed a second
237        /// time. The Disposed property is set the first time the object
238        /// is disposed. If the Disposed property is true, then the Dispose()
239        /// method will not dispose again. This help not to prolong the object's
240        /// life if the Garbage Collector.
241        /// </remarks>
242        /// </summary>
243        /// <value>
244        ///     <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
245        /// </value>
246        protected bool Disposed { get; private set; }
247
248        #endregion
249
250        #region Indexers
251
252        /// <summary>
253        ///     Gets the <see cref = "System.String" /> with the specified column name.
254        /// </summary>
255        /// <param name="columnName">The column name.</param>
256        public string this[string columnName]
257        {
258            get
259            {
260                return this.brokenRules.ContainsKey(columnName) ? this.brokenRules[columnName] : string.Empty;
261            }
262        }
263
264        #endregion
265
266        #region Operators
267
268        /// <summary>
269        /// Checks to see if two business objects are the same.
270        /// </summary>
271        /// <param name="first">The first.</param>
272        /// <param name="second">The second.</param>
273        /// <returns>The result of the operator.</returns>
274        public static bool operator ==(BusinessBase<T, TKey> first, BusinessBase<T, TKey> second)
275        {
276            return ReferenceEquals(first, second) ||
277                   ((object)first != null && (object)second != null && first.GetHashCode() == second.GetHashCode());
278        }
279
280        /// <summary>
281        /// Checks to see if two business objects are different.
282        /// </summary>
283        /// <param name="first">The first.</param>
284        /// <param name="second">The second.</param>
285        /// <returns>The result of the operator.</returns>
286        public static bool operator !=(BusinessBase<T, TKey> first, BusinessBase<T, TKey> second)
287        {
288            return !(first == second);
289        }
290
291        #endregion
292
293        #region Public Methods
294
295        /// <summary>
296        /// Loads an instance of the object based on the Id.
297        /// </summary>
298        /// <param name="id">The unique identifier of the object</param>
299        /// <returns>The instance of the object based on the Id.</returns>
300        public static T Load(TKey id)
301        {
302            var instance = new T();
303            instance = instance.DataSelect(id);
304            instance.Id = id;
305
306            instance.MarkOld();
307            return instance;
308        }
309
310        /// <summary>
311        /// Marks the object for deletion. It will then be 
312        ///     deleted when the object's Save() method is called.
313        /// </summary>
314        public virtual void Delete()
315        {
316            this.Deleted = true;
317            this.IsChanged = true;
318        }
319
320        /// <summary>
321        /// Comapares this object with another
322        /// </summary>
323        /// <param name="obj">
324        /// The object to compare
325        /// </param>
326        /// <returns>
327        /// True if the two objects as equal
328        /// </returns>
329        public override bool Equals(object obj)
330        {
331            return obj != null && (obj.GetType() == this.GetType() && obj.GetHashCode() == this.GetHashCode());
332        }
333
334        /// <summary>
335        /// A uniquely key to identify this particullar instance of the class
336        /// </summary>
337        /// <returns>
338        /// A unique integer value
339        /// </returns>
340        public override int GetHashCode()
341        {
342            return this.Id.GetHashCode();
343        }
344
345        /// <summary>
346        /// Marks the object as being an clean, 
347        ///     which means not dirty.
348        /// </summary>
349        public virtual void MarkOld()
350        {
351            this.IsChanged = false;
352            this.New = false;
353            this.changedProperties.Clear();
354        }
355
356        /// <summary>
357        /// Saves the object to the data store (inserts, updates or deletes).
358        /// </summary>
359        /// <returns>The SaveAction.</returns>
360        public virtual SaveAction Save()
361        {
362            if (this.Deleted && !this.CanUserDelete)
363            {
364                throw new SecurityException("You are not authorized to delete the object");
365            }
366            
367            if (!this.Valid && !this.Deleted)
368            {
369                throw new InvalidOperationException(this.ValidationMessage);
370            }
371
372            if (this.Disposed && !this.Deleted)
373            {
374                throw new InvalidOperationException(
375                    string.Format(CultureInfo.InvariantCulture, "You cannot save a disposed {0}", this.GetType().Name));
376            }
377
378            return this.IsChanged ? this.Update() : SaveAction.None;
379        }
380
381        #endregion
382
383        #region Implemented Interfaces
384
385        #region IChangeTracking
386
387        /// <summary>
388        /// Resets the object's state to unchanged by accepting the modifications.
389        /// </summary>
390        public void AcceptChanges()
391        {
392            this.Save();
393        }
394
395        #endregion
396
397        #region IDisposable
398
399        /// <summary>
400        /// Disposes the object and frees ressources for the Garbage Collector.
401        /// </summary>
402        public void Dispose()
403        {
404            this.Dispose(true);
405            GC.SuppressFinalize(this);
406        }
407
408        #endregion
409
410        #region "INotifyPropertyChanged"
411
412        /// <summary>
413        /// Marks an object as being dirty, or changed.
414        /// </summary>
415        /// <param name="propertyName">
416        /// The name of the property to mark dirty.
417        /// </param>
418        protected virtual void MarkChanged(string propertyName)
419        {
420            this.IsChanged = true;
421
422            // No need to check for duplicates since changedProperties 
423            // is just a HashSet.
424            this.changedProperties.Add(propertyName);
425            this.OnPropertyChanged(propertyName);
426        }
427
428        /// <summary>
429        /// Raises the PropertyChanged event safely.
430        /// </summary>
431        /// <param name="propertyName">
432        /// The property Name.
433        /// </param>
434        protected virtual void OnPropertyChanged(string propertyName)
435        {
436            if (this.PropertyChanged != null)
437            {
438                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
439            }
440        }
441
442        #endregion
443
444        #region "INotifyPropertyChanging"
445
446        /// <summary>
447        /// Method called just before a property's value will change.
448        /// </summary>
449        /// <param name="propertyName">The name of the property whose value changing.</param>
450        /// <remarks>
451        /// 
452        /// This method should only be called when a value is definitely going to be changed. This
453        /// should occur after any value validation or other methods are called.
454        /// 
455        /// </remarks>
456        protected virtual void OnPropertyChanging(string propertyName)
457        {
458            if (PropertyChanging != null)
459            {
460                PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
461            }
462        }
463
464        #endregion
465
466        #endregion
467
468        #region Methods
469
470        /// <summary>
471        /// Raises the Saved event.
472        /// </summary>
473        /// <param name="businessObject">
474        /// The business Object.
475        /// </param>
476        /// <param name="action">
477        /// The action.
478        /// </param>
479        protected static void OnSaved(BusinessBase<T, TKey> businessObject, SaveAction action)
480        {
481            if (Saved != null)
482            {
483                Saved(businessObject, new SavedEventArgs(action));
484            }
485        }
486
487        /// <summary>
488        /// Raises the Saving event
489        /// </summary>
490        /// <param name="businessObject">
491        /// The business Object.
492        /// </param>
493        /// <param name="action">
494        /// The action.
495        /// </param>
496        protected static void OnSaving(BusinessBase<T, TKey> businessObject, SaveAction action)
497        {
498            if (Saving != null)
499            {
500                Saving(businessObject, new SavedEventArgs(action));
501            }
502        }
503
504        /// <summary>
505        /// Add or remove a broken rule.
506        /// </summary>
507        /// <param name="propertyName">
508        /// The name of the property.
509        /// </param>
510        /// <param name="errorMessage">
511        /// The description of the error
512        /// </param>
513        /// <param name="isbroken">
514        /// True if the validation rule is broken.
515        /// </param>
516        protected virtual void AddRule(string propertyName, string errorMessage, bool isbroken)
517        {
518            if (isbroken)
519            {
520                this.brokenRules[propertyName] = errorMessage;
521            }
522            else
523            {
524                this.brokenRules.Remove(propertyName);
525            }
526        }
527
528        /// <summary>
529        /// Deletes the object from the data store.
530        /// </summary>
531        protected abstract void DataDelete();
532
533        /// <summary>
534        /// Inserts a new object to the data store.
535        /// </summary>
536        protected abstract void DataInsert();
537
538        /// <summary>
539        /// Retrieves the object from the data store and populates it.
540        /// </summary>
541        /// <param name="id">
542        /// The unique identifier of the object.
543        /// </param>
544        /// <returns>
545        /// True if the object exists and is being populated successfully
546        /// </returns>
547        protected abstract T DataSelect(TKey id);
548
549        /// <summary>
550        /// Updates the object in its data store.
551        /// </summary>
552        protected abstract void DataUpdate();
553
554        /// <summary>
555        /// Disposes the object and frees ressources for the Garbage Collector.
556        /// </summary>
557        /// <param name="disposing">
558        /// If true, the object gets disposed.
559        /// </param>
560        protected virtual void Dispose(bool disposing)
561        {
562            try
563            {
564                if (this.Disposed)
565                {
566                    return;
567                }
568
569                if (!disposing)
570                {
571                    return;
572                }
573
574                this.changedProperties.Clear();
575                this.brokenRules.Clear();
576            }
577            finally
578            {
579                this.Disposed = true;
580            }
581        }
582
583
584
585        /// <summary>
586        /// Use this method to change values of properties that participate in INotifyPropertyChanged event notification.
587        /// </summary>
588        /// <typeparam name="TValueType">The object type for both the new value and old value.</typeparam>
589        /// <param name="propertyName">The name of the property that should be used when raising the PropertyChanged event.</param>
590        /// <param name="newValue">The new value to be set on the property if it's different from oldValue</param>
591        /// <param name="oldValue">The current value of the property.</param>
592        /// <returns>True if the the property value has been changed, false otherwise.</returns>
593        /// <remarks>
594        /// 
595        /// This is left as virtual so users can override this if they have their own validation needs.
596        /// 
597        /// </remarks>
598        protected virtual bool SetValue<TValueType>(string propertyName, TValueType newValue, ref TValueType oldValue)
599        {
600            bool isChanged = (!Object.Equals(newValue, oldValue));
601
602            if (isChanged)
603            {
604                OnPropertyChanging(propertyName);
605                oldValue = newValue;
606                MarkChanged(propertyName);
607            }
608            return isChanged;
609
610        }
611
612        /// <summary>
613        /// Reinforces the business rules by adding additional rules to the 
614        ///     broken rules collection.
615        /// </summary>
616        protected abstract void ValidationRules();
617
618        /// <summary>
619        /// Is called by the save method when the object is old and dirty.
620        /// </summary>
621        /// <returns>
622        /// The update.
623        /// </returns>
624        private SaveAction Update()
625        {
626            var action = SaveAction.None;
627
628            if (this.Deleted)
629            {
630                if (!this.New)
631                {
632                    action = SaveAction.Delete;
633                    OnSaving(this, action);
634                    this.DataDelete();
635                }
636            }
637            else
638            {
639                if (this.New)
640                {
641                    if (this.dateCreated == DateTime.MinValue)
642                    {
643                        this.dateCreated = DateTime.Now;
644                    }
645
646                    this.dateModified = DateTime.Now;
647                    action = SaveAction.Insert;
648                    OnSaving(this, action);
649                    this.DataInsert();
650                }
651                else
652                {
653                    this.dateModified = DateTime.Now;
654
655                    action = SaveAction.Update;
656                    OnSaving(this, action);
657                    this.DataUpdate();
658                }
659
660                this.MarkOld();
661            }
662
663            OnSaved(this, action);
664            return action;
665        }
666
667        #endregion
668    }
669}