PageRenderTime 41ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/BusinessBase.cs

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