Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF

Layers Pattern in Practice

Rate me:
Please Sign up or sign in to vote.
4.96/5 (59 votes)
23 Apr 2010CPOL25 min read 152.6K   8.1K   187  
Layers Pattern via a WPF project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
using System.Security.Permissions;
using BillsEntityLib;
using System.Threading;
using System.Globalization;

namespace BillsDalLib
{
    /// <summary>
    /// XML data source manager
    /// </summary>
    public class XmlDalBillsManager : IDalBillsManager
    {
        private static XmlDalBillsManager _manager = null;
        private string _archive;
        private static object lockObject = new object();
        private static BooleanSwitch _bSwitch;

        private static readonly Semaphore Turnstyle = new Semaphore(1, 65535);
        private static readonly Semaphore RoomEmpty = new Semaphore(1, 1);
        private static int _totalreaders = 0;
        private static readonly object ReadersLock = new object();

        /// <summary>
        /// Constants for Xml file. The same are used by XmlSerializer class. Any changes made here
        /// can prevent the deserialization of Xml storage file.
        /// </summary>
        #region XML constants

        private const string XSI = "http://www.w3.org/2001/XMLSchema-instance"; //xml schema instance
        private const string XSD = "http://www.w3.org/2001/XMLSchema";  //xml schema
        private static readonly XNamespace SCHEMA = "http://tempuri.org/billschema.xsd"; //local xml schema

        private readonly XName BILLS = SCHEMA + "Bills"; //<Bills>
        private readonly XName BILL = SCHEMA + "Bill";       //<Bill>
        private readonly XName NAME = SCHEMA + "Name";           //<Name>  
        private readonly XName DUEDATE = SCHEMA + "DueDate";     //<DueDate>
        private readonly XName AMOUNT = SCHEMA + "Amount";       //<Amount>
        private readonly XName ADDEDON = SCHEMA + "AddedOn";     //<AddedOn>
        private readonly XName STATUS = SCHEMA + "Status";       //<Status>
        private readonly XName ID = "ID";    //<Bill ID = "0000-0000-00000000"/>
        #endregion

        #region Constructor
        private XmlDalBillsManager()
        {
            _bSwitch = new BooleanSwitch("BooleanSwitch", "Switch from config file"); //if no such switch in config file _bSwitch == false
            if(_bSwitch.Enabled)
                Trace.TraceInformation("Singleton Instance Created:"+this.GetType().ToString());
        }
        /// <summary>
        /// Private constructor
        /// </summary>
        /// <param name="archive">Path to archive directory [path to directory]</param>
        private XmlDalBillsManager(string archive) : this()
        {
            archive = Path.GetFullPath(archive);
            if (String.IsNullOrEmpty(archive))
            {
                CreateArchiveFile(archive);
            }
            this._archive = archive;
        }
        #endregion
        #region Private members
        /// <summary>
        /// Get data archive
        /// </summary>
        /// <returns>Array of paths to data archive files</returns>
        private XDocument GetArchive()
        {

            if (String.IsNullOrEmpty(_archive))
                throw new DalBillManagerException("Path to archive is null");
            if (!File.Exists(_archive))
                return CreateArchiveFile(_archive);
            XDocument ret = null;
            try
            {
                ret = XDocument.Load(_archive);
            }
            catch(Exception ex)
            {
                throw new DalBillManagerException(ex.Message);
            }
            return ret;
        }
        /// <summary>
        /// Creates new Xml document in data storage location
        /// </summary>
        /// <param name="fileName">Filename to create</param>
        /// <returns>XDocument instance</returns>
        /// <exception cref="System.Security.SecurityException">Security exception</exception>
        private XDocument CreateArchiveFile(string fileName)
        {
            fileName = Path.GetFullPath(fileName);
            if (String.IsNullOrEmpty(fileName))
                throw new DalBillManagerException("Path to archive is null");
            FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Write, Path.GetFullPath(fileName));
            try
            {
                permission.Demand();    //demand for file creation
            }
            catch(System.Security.SecurityException e)
            {
                if(_bSwitch.Enabled)
                    Trace.TraceError("SecurityException - Message:"+e.Message+"\nStackTrace:"+e.StackTrace);
                throw;
            }
           
            XDocument xmlDoc = new XDocument(new XDeclaration("1.0", "UTF-8", null),    //Create Root element
                new XElement(BILLS));
            if (xmlDoc.Root != null)
                xmlDoc.Root.Add(
                    new XAttribute(XNamespace.Xmlns + "xsi", XSI),
                    new XAttribute(XNamespace.Xmlns + "xsd", XSD));
            try
            {
                xmlDoc.Save(fileName);
            }
            catch (Exception)
            {
                if (_bSwitch.Enabled)
                    Trace.TraceError("Archive wasnt created:" + fileName);
                throw new DalBillManagerException("Cannot save Xml file. Review the path");
            }
            if (_bSwitch.Enabled)
                Trace.TraceInformation("Archive created:" + fileName);
            return xmlDoc;
        }
        #endregion
        #region Singleton
        /// <summary>
        /// Gets singleton instance of XmlDalBillsManager class
        /// </summary>
        /// <returns>XmlDalBillsManager instance</returns>
        public static XmlDalBillsManager GetInstance()
        {
            if (_manager == null)   //lazy initialization
            {
                lock (lockObject)
                {
                    if (_manager == null)
                    {
                        _manager = new XmlDalBillsManager();
                    }
                }
            }
            return _manager;
        }
        #endregion

        #region IDalBillsManager Members
        /// <summary>
        /// Reads bills from the data source. Method is thread-safe.
        /// </summary>
        /// <param name="fromDate">Most earliest date</param>
        /// <param name="toDate">Most latest date</param>
        /// <returns>List of bills</returns>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public Bills Read(DateTime fromDate, DateTime toDate)
        {
            Turnstyle.WaitOne();
            Turnstyle.Release();
            lock (ReadersLock)
            {
                _totalreaders += 1;
                if (_totalreaders == 1)
                    RoomEmpty.WaitOne();
            }
            XDocument xmlDoc = null;
            try
            {
                xmlDoc = this.GetArchive();
            }
            catch
            {
                lock (ReadersLock)
                {
                    _totalreaders--;
                    if (_totalreaders == 0)
                        RoomEmpty.Release();
                }
                throw;
            }

            List<Bill> listOfBills = new List<Bill>();
            var query = from result in xmlDoc.Elements(BILLS).Elements(BILL)
                        where Convert.ToDateTime(result.Element(ADDEDON).Value, CultureInfo.InvariantCulture).CompareTo(fromDate) > 0
                              && Convert.ToDateTime(result.Element(ADDEDON).Value, CultureInfo.InvariantCulture).CompareTo(toDate) < 0
                        select result;
            List<XElement> list = query.ToList();
            foreach (XElement record in list)
            {
                if (record != null)
                {
                    string name = record.Element(NAME).Value;
                    DateTime dueDate = Convert.ToDateTime(record.Element(DUEDATE).Value, CultureInfo.InvariantCulture);
                    decimal amount = Convert.ToDecimal(record.Element(AMOUNT).Value, CultureInfo.InvariantCulture);
                    DateTime addedOn = Convert.ToDateTime(record.Element(ADDEDON).Value, CultureInfo.InvariantCulture);
                    BillStatus status = BillHelper.Convert(record.Element(STATUS).Value);
                    Guid id = new Guid(record.Attribute(ID).Value);
                    listOfBills.Add(new Bill(name, dueDate, amount, addedOn, status, id));
                }
            }

            lock (ReadersLock)
            {
                _totalreaders--;
                if (_totalreaders == 0)
                    RoomEmpty.Release();
            }

            return new Bills(listOfBills);
        }
        /// <summary>
        /// Read a bill from the datasource by its ID. Method is thread-safe.
        /// </summary>
        /// <param name="guid">Bill's guid</param>
        /// <returns>Bill object or null if no such object in the datasource</returns>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public Bill ReadById(Guid guid)
        {
            if (guid == Guid.Empty)
                return null;

            Turnstyle.WaitOne();
            Turnstyle.Release();
            lock (ReadersLock)
            {
                _totalreaders += 1;
                if (_totalreaders == 1)
                    RoomEmpty.WaitOne();
            }
            XDocument xmlDoc = null;
            try
            {
                xmlDoc = this.GetArchive();
            }
            catch
            {
                lock (ReadersLock)
                {
                    _totalreaders--;
                    if (_totalreaders == 0)
                        RoomEmpty.Release();
                }
                throw;
            }
            string idStr = guid.ToString();
            var query = from record in xmlDoc.Elements(BILLS).Elements(BILL)
                        where (string)record.Attribute(ID) == idStr
                        select record;
            List<XElement> list = query.ToList();
            if (list.Count() == 0)
            {
                lock (ReadersLock)
                {
                    _totalreaders--;
                    if (_totalreaders == 0)
                        RoomEmpty.Release();
                }
                return null;
            }
            XElement bill = list.ElementAt(0);
            if (bill != null)
            {
                string name = bill.Element(NAME).Value;
                DateTime dueDate = Convert.ToDateTime(bill.Element(DUEDATE).Value, CultureInfo.InvariantCulture);
                decimal amount = Convert.ToDecimal(bill.Element(AMOUNT).Value, CultureInfo.InvariantCulture);
                DateTime addedOn = Convert.ToDateTime(bill.Element(ADDEDON).Value, CultureInfo.InvariantCulture);
                BillStatus status = BillHelper.Convert(bill.Element(STATUS).Value);
                Guid id = new Guid(bill.Attribute(ID).Value);

                lock (ReadersLock)
                {
                    _totalreaders--;
                    if (_totalreaders == 0)
                        RoomEmpty.Release();
                }

                return new Bill(name, dueDate, amount, addedOn, status, id);
            }
            else
                return null;
        }
        /// <summary>
        /// Insert bills into Xml. Method is thread-safe.
        /// </summary>
        /// <param name="bills">Bill to insert</param>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public void Insert(Bills bills)
        {
            if (bills == null || bills.Items == null)
                return;
            Turnstyle.WaitOne();
            RoomEmpty.WaitOne();
            try
            {
                XDocument xmlDoc = this.GetArchive();
                XElement current = xmlDoc.Root;
                foreach (Bill bill in bills.ToList())
                {
                    XElement toAdd = new XElement(BILL, new XAttribute(ID, bill.ID),
                                                  new XElement(NAME, bill.Name),
                                                  new XElement(DUEDATE, bill.DueDate),
                                                  new XElement(AMOUNT, bill.Amount),
                                                  new XElement(ADDEDON, bill.AddedOn),
                                                  new XElement(STATUS, bill.Status));
                    if (current != null) current.Add(toAdd);
                    if (_bSwitch.Enabled)
                        Trace.TraceInformation("New element inserted:" + bill.ID.ToString());
                }
                xmlDoc.Save(_archive);
            }
            catch
            {
                throw;
            }
            finally
            {
                Turnstyle.Release();
                RoomEmpty.Release();
            }
        }
        /// <summary>
        /// Delete a bill from list of recent bills. Method is thread-safe.
        /// </summary>
        /// <param name="guid">Delete a bill with the specified Guid.</param>
        /// <returns>Number of deleted items</returns>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public int Delete(Guid guid)
        {
            if (guid == Guid.Empty)
                return 0;

            Turnstyle.WaitOne();
            RoomEmpty.WaitOne();
            int count = 0;
            try
            {
                XDocument xmlDoc = this.GetArchive();
                string strBill = guid.ToString();
                var query = from bills in xmlDoc.Elements(BILLS).Elements(BILL)
                            where (string)bills.Attribute(ID) == strBill
                            //select bills with defined ID
                            select bills;
                count = query.Count();
                if (count != 0)
                {
                    List<XElement> list = query.ToList();
                    foreach (XElement record in list)
                        record.Remove();
                }
                xmlDoc.Save(_archive); //save the file
            }
            catch
            {
                throw;
            }
            finally
            {
                Turnstyle.Release();
                RoomEmpty.Release();
            }
            return count;
        }
        /// <summary>
        /// Delete a list of specified Bills from the datasource. Method is thread-safe.
        /// </summary>
        /// <param name="guids">Guids of bills to delete</param>
        /// <returns>Number of deleted items</returns>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public int Delete(IEnumerable<Guid> guids)
        {
            Turnstyle.WaitOne();
            RoomEmpty.WaitOne();
            int totalCount = 0;

            try
            {
                XDocument xmlDoc = this.GetArchive();              
                foreach (Guid id in guids)
                {
                    string strBill = id.ToString();
                    var query = from bills in xmlDoc.Elements(BILLS).Elements(BILL)
                                where (string)bills.Attribute(ID) == strBill
                                //select bills with defined ID
                                select bills;
                    totalCount += query.Count();
                    if (query.Count() != 0)
                    {
                        List<XElement> list = query.ToList();
                        foreach (XElement record in list)
                            record.Remove();
                    }
                }
                xmlDoc.Save(_archive); //save the file
            }
            catch
            {
                throw;
            }
            finally
            {
                Turnstyle.Release();
                RoomEmpty.Release();
            }

            return totalCount;
        }
        /// <summary>
        /// Update information about the bill. Method is thread-safe.
        /// </summary>
        /// <param name="billId">Bill Id</param>
        /// <param name="newBill">New bill information</param>
        /// <returns>Number of updated items</returns>
        /// <remarks>Written at 06.11.2009. Thread-safety defined using Readers/Writers pattern</remarks>
        public int Update(Guid billId, Bill newBill)
        {
            if (billId == Guid.Empty || newBill == null)
                return 0;

            Turnstyle.WaitOne();
            RoomEmpty.WaitOne();
            int totalCount = 0;
            try
            {
                XDocument xmlDoc = this.GetArchive();
                string billstr = billId.ToString();
                var query = from record in xmlDoc.Elements(BILLS).Elements(BILL)
                            where (string)record.Attribute(ID) == billstr
                            select record;

                totalCount = query.Count();
                if (totalCount != 0)
                {
                    List<XElement> list = query.ToList();
                    foreach (XElement record in list)
                    {
                        record.SetElementValue(NAME, newBill.Name);
                        record.SetElementValue(DUEDATE, newBill.DueDate);
                        record.SetElementValue(AMOUNT, newBill.Amount);
                        record.SetElementValue(ADDEDON, newBill.AddedOn);
                        record.SetElementValue(STATUS, newBill.Status);
                        if (_bSwitch.Enabled)
                            Trace.TraceInformation("Element Updated:" + billId.ToString());
                    }
                }
                xmlDoc.Save(_archive);
            }
            catch
            {
                throw;
            }
            finally
            {
                Turnstyle.Release();
                RoomEmpty.Release();
            }
            return totalCount;
        }
        /// <summary>
        /// Settings for the XmlDalBillsManager instance. Supports only 1 setting - [string]:archive path.
        /// </summary>
        public object[] Settings
        {
            get { return new object[2] { _archive, _bSwitch.Enabled }; }
            set 
            {
                if (value.Length != 2 || value.Length == 0 || (value[0].GetType() != typeof(string) && value[1].GetType() != typeof(bool)))
                    throw new DalBillManagerException("Wrong settings");
                string prevValue = _archive;
                _archive = Path.GetFullPath((string)value[0]);
                
                _bSwitch.Enabled = (bool)value[1];
                if (!File.Exists(_archive))
                {
                    this.CreateArchiveFile(_archive);
                }
                try
                {
                    /*Try Loading file*/
                    this.GetArchive();
                }
                catch (DalBillManagerException)
                {
                    _archive = prevValue;
                    throw;
                }
            }
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Moldova (Republic of) Moldova (Republic of)
Interested in computer science, math, research, and everything that relates to innovation. Fan of agnostic programming, don't mind developing under any platform/framework if it explores interesting topics. In search of a better programming paradigm.

Comments and Discussions